mirror of
https://github.com/AuxXxilium/linux_dsm_epyc7002.git
synced 2024-12-28 11:18:45 +07:00
2ca3c148a0
This change relaxes copy up on encode of merge dir with lower layer > 1 and handles the case of encoding a merge dir with lower layer 1, where an ancestor is a non-indexed merge dir. In that case, decode of the lower file handle will not have been possible if the non-indexed ancestor is redirected before or after encode. Before encoding a non-upper directory file handle from real layer N, we need to check if it will be possible to reconnect an overlay dentry from the real lower decoded dentry. This is done by following the overlay ancestry up to a "layer N connected" ancestor and verifying that all parents along the way are "layer N connectable". If an ancestor that is NOT "layer N connectable" is found, we need to copy up an ancestor, which is "layer N connectable", thus making that ancestor "layer N connected". For example: layer 1: /a layer 2: /a/b/c The overlay dentry /a is NOT "layer 2 connectable", because if dir /a is copied up and renamed, upper dir /a will be indexed by lower dir /a from layer 1. The dir /a from layer 2 will never be indexed, so the algorithm in ovl_lookup_real_ancestor() (*) will not be able to lookup a connected overlay dentry from the connected lower dentry /a/b/c. To avoid this problem on decode time, we need to copy up an ancestor of /a/b/c, which is "layer 2 connectable", on encode time. That ancestor is /a/b. After copy up (and index) of /a/b, it will become "layer 2 connected" and when the time comes to decode the file handle from lower dentry /a/b/c, ovl_lookup_real_ancestor() will find the indexed ancestor /a/b and decoding a connected overlay dentry will be accomplished. (*) the algorithm in ovl_lookup_real_ancestor() can be improved to lookup an entry /a in the lower layers above layer N and find the indexed dir /a from layer 1. If that improvement is made, then the check for "layer N connected" will need to verify there are no redirects in lower layers above layer N. In the example above, /a will be "layer 2 connectable". However, if layer 2 dir /a is a target of a layer 1 redirect, then /a will NOT be "layer 2 connectable": layer 1: /A (redirect = /a) layer 2: /a/b/c Signed-off-by: Amir Goldstein <amir73il@gmail.com> Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
1435 lines
34 KiB
C
1435 lines
34 KiB
C
/*
|
|
*
|
|
* Copyright (C) 2011 Novell Inc.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 as published by
|
|
* the Free Software Foundation.
|
|
*/
|
|
|
|
#include <uapi/linux/magic.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/namei.h>
|
|
#include <linux/xattr.h>
|
|
#include <linux/mount.h>
|
|
#include <linux/parser.h>
|
|
#include <linux/module.h>
|
|
#include <linux/statfs.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/posix_acl_xattr.h>
|
|
#include "overlayfs.h"
|
|
|
|
MODULE_AUTHOR("Miklos Szeredi <miklos@szeredi.hu>");
|
|
MODULE_DESCRIPTION("Overlay filesystem");
|
|
MODULE_LICENSE("GPL");
|
|
|
|
|
|
struct ovl_dir_cache;
|
|
|
|
#define OVL_MAX_STACK 500
|
|
|
|
static bool ovl_redirect_dir_def = IS_ENABLED(CONFIG_OVERLAY_FS_REDIRECT_DIR);
|
|
module_param_named(redirect_dir, ovl_redirect_dir_def, bool, 0644);
|
|
MODULE_PARM_DESC(ovl_redirect_dir_def,
|
|
"Default to on or off for the redirect_dir feature");
|
|
|
|
static bool ovl_redirect_always_follow =
|
|
IS_ENABLED(CONFIG_OVERLAY_FS_REDIRECT_ALWAYS_FOLLOW);
|
|
module_param_named(redirect_always_follow, ovl_redirect_always_follow,
|
|
bool, 0644);
|
|
MODULE_PARM_DESC(ovl_redirect_always_follow,
|
|
"Follow redirects even if redirect_dir feature is turned off");
|
|
|
|
static bool ovl_index_def = IS_ENABLED(CONFIG_OVERLAY_FS_INDEX);
|
|
module_param_named(index, ovl_index_def, bool, 0644);
|
|
MODULE_PARM_DESC(ovl_index_def,
|
|
"Default to on or off for the inodes index feature");
|
|
|
|
static bool ovl_nfs_export_def = IS_ENABLED(CONFIG_OVERLAY_FS_NFS_EXPORT);
|
|
module_param_named(nfs_export, ovl_nfs_export_def, bool, 0644);
|
|
MODULE_PARM_DESC(ovl_nfs_export_def,
|
|
"Default to on or off for the NFS export feature");
|
|
|
|
static void ovl_entry_stack_free(struct ovl_entry *oe)
|
|
{
|
|
unsigned int i;
|
|
|
|
for (i = 0; i < oe->numlower; i++)
|
|
dput(oe->lowerstack[i].dentry);
|
|
}
|
|
|
|
static void ovl_dentry_release(struct dentry *dentry)
|
|
{
|
|
struct ovl_entry *oe = dentry->d_fsdata;
|
|
|
|
if (oe) {
|
|
ovl_entry_stack_free(oe);
|
|
kfree_rcu(oe, rcu);
|
|
}
|
|
}
|
|
|
|
static int ovl_check_append_only(struct inode *inode, int flag)
|
|
{
|
|
/*
|
|
* This test was moot in vfs may_open() because overlay inode does
|
|
* not have the S_APPEND flag, so re-check on real upper inode
|
|
*/
|
|
if (IS_APPEND(inode)) {
|
|
if ((flag & O_ACCMODE) != O_RDONLY && !(flag & O_APPEND))
|
|
return -EPERM;
|
|
if (flag & O_TRUNC)
|
|
return -EPERM;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct dentry *ovl_d_real(struct dentry *dentry,
|
|
const struct inode *inode,
|
|
unsigned int open_flags, unsigned int flags)
|
|
{
|
|
struct dentry *real;
|
|
int err;
|
|
|
|
if (flags & D_REAL_UPPER)
|
|
return ovl_dentry_upper(dentry);
|
|
|
|
if (!d_is_reg(dentry)) {
|
|
if (!inode || inode == d_inode(dentry))
|
|
return dentry;
|
|
goto bug;
|
|
}
|
|
|
|
if (open_flags) {
|
|
err = ovl_open_maybe_copy_up(dentry, open_flags);
|
|
if (err)
|
|
return ERR_PTR(err);
|
|
}
|
|
|
|
real = ovl_dentry_upper(dentry);
|
|
if (real && (!inode || inode == d_inode(real))) {
|
|
if (!inode) {
|
|
err = ovl_check_append_only(d_inode(real), open_flags);
|
|
if (err)
|
|
return ERR_PTR(err);
|
|
}
|
|
return real;
|
|
}
|
|
|
|
real = ovl_dentry_lower(dentry);
|
|
if (!real)
|
|
goto bug;
|
|
|
|
/* Handle recursion */
|
|
real = d_real(real, inode, open_flags, 0);
|
|
|
|
if (!inode || inode == d_inode(real))
|
|
return real;
|
|
bug:
|
|
WARN(1, "ovl_d_real(%pd4, %s:%lu): real dentry not found\n", dentry,
|
|
inode ? inode->i_sb->s_id : "NULL", inode ? inode->i_ino : 0);
|
|
return dentry;
|
|
}
|
|
|
|
static int ovl_dentry_revalidate(struct dentry *dentry, unsigned int flags)
|
|
{
|
|
struct ovl_entry *oe = dentry->d_fsdata;
|
|
unsigned int i;
|
|
int ret = 1;
|
|
|
|
for (i = 0; i < oe->numlower; i++) {
|
|
struct dentry *d = oe->lowerstack[i].dentry;
|
|
|
|
if (d->d_flags & DCACHE_OP_REVALIDATE) {
|
|
ret = d->d_op->d_revalidate(d, flags);
|
|
if (ret < 0)
|
|
return ret;
|
|
if (!ret) {
|
|
if (!(flags & LOOKUP_RCU))
|
|
d_invalidate(d);
|
|
return -ESTALE;
|
|
}
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int ovl_dentry_weak_revalidate(struct dentry *dentry, unsigned int flags)
|
|
{
|
|
struct ovl_entry *oe = dentry->d_fsdata;
|
|
unsigned int i;
|
|
int ret = 1;
|
|
|
|
for (i = 0; i < oe->numlower; i++) {
|
|
struct dentry *d = oe->lowerstack[i].dentry;
|
|
|
|
if (d->d_flags & DCACHE_OP_WEAK_REVALIDATE) {
|
|
ret = d->d_op->d_weak_revalidate(d, flags);
|
|
if (ret <= 0)
|
|
break;
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static const struct dentry_operations ovl_dentry_operations = {
|
|
.d_release = ovl_dentry_release,
|
|
.d_real = ovl_d_real,
|
|
};
|
|
|
|
static const struct dentry_operations ovl_reval_dentry_operations = {
|
|
.d_release = ovl_dentry_release,
|
|
.d_real = ovl_d_real,
|
|
.d_revalidate = ovl_dentry_revalidate,
|
|
.d_weak_revalidate = ovl_dentry_weak_revalidate,
|
|
};
|
|
|
|
static struct kmem_cache *ovl_inode_cachep;
|
|
|
|
static struct inode *ovl_alloc_inode(struct super_block *sb)
|
|
{
|
|
struct ovl_inode *oi = kmem_cache_alloc(ovl_inode_cachep, GFP_KERNEL);
|
|
|
|
if (!oi)
|
|
return NULL;
|
|
|
|
oi->cache = NULL;
|
|
oi->redirect = NULL;
|
|
oi->version = 0;
|
|
oi->flags = 0;
|
|
oi->__upperdentry = NULL;
|
|
oi->lower = NULL;
|
|
mutex_init(&oi->lock);
|
|
|
|
return &oi->vfs_inode;
|
|
}
|
|
|
|
static void ovl_i_callback(struct rcu_head *head)
|
|
{
|
|
struct inode *inode = container_of(head, struct inode, i_rcu);
|
|
|
|
kmem_cache_free(ovl_inode_cachep, OVL_I(inode));
|
|
}
|
|
|
|
static void ovl_destroy_inode(struct inode *inode)
|
|
{
|
|
struct ovl_inode *oi = OVL_I(inode);
|
|
|
|
dput(oi->__upperdentry);
|
|
iput(oi->lower);
|
|
kfree(oi->redirect);
|
|
ovl_dir_cache_free(inode);
|
|
mutex_destroy(&oi->lock);
|
|
|
|
call_rcu(&inode->i_rcu, ovl_i_callback);
|
|
}
|
|
|
|
static void ovl_free_fs(struct ovl_fs *ofs)
|
|
{
|
|
unsigned i;
|
|
|
|
dput(ofs->indexdir);
|
|
dput(ofs->workdir);
|
|
if (ofs->workdir_locked)
|
|
ovl_inuse_unlock(ofs->workbasedir);
|
|
dput(ofs->workbasedir);
|
|
if (ofs->upperdir_locked)
|
|
ovl_inuse_unlock(ofs->upper_mnt->mnt_root);
|
|
mntput(ofs->upper_mnt);
|
|
for (i = 0; i < ofs->numlower; i++) {
|
|
mntput(ofs->lower_layers[i].mnt);
|
|
free_anon_bdev(ofs->lower_layers[i].pseudo_dev);
|
|
}
|
|
kfree(ofs->lower_layers);
|
|
|
|
kfree(ofs->config.lowerdir);
|
|
kfree(ofs->config.upperdir);
|
|
kfree(ofs->config.workdir);
|
|
kfree(ofs->config.redirect_mode);
|
|
if (ofs->creator_cred)
|
|
put_cred(ofs->creator_cred);
|
|
kfree(ofs);
|
|
}
|
|
|
|
static void ovl_put_super(struct super_block *sb)
|
|
{
|
|
struct ovl_fs *ofs = sb->s_fs_info;
|
|
|
|
ovl_free_fs(ofs);
|
|
}
|
|
|
|
/* Sync real dirty inodes in upper filesystem (if it exists) */
|
|
static int ovl_sync_fs(struct super_block *sb, int wait)
|
|
{
|
|
struct ovl_fs *ofs = sb->s_fs_info;
|
|
struct super_block *upper_sb;
|
|
int ret;
|
|
|
|
if (!ofs->upper_mnt)
|
|
return 0;
|
|
|
|
/*
|
|
* If this is a sync(2) call or an emergency sync, all the super blocks
|
|
* will be iterated, including upper_sb, so no need to do anything.
|
|
*
|
|
* If this is a syncfs(2) call, then we do need to call
|
|
* sync_filesystem() on upper_sb, but enough if we do it when being
|
|
* called with wait == 1.
|
|
*/
|
|
if (!wait)
|
|
return 0;
|
|
|
|
upper_sb = ofs->upper_mnt->mnt_sb;
|
|
|
|
down_read(&upper_sb->s_umount);
|
|
ret = sync_filesystem(upper_sb);
|
|
up_read(&upper_sb->s_umount);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* ovl_statfs
|
|
* @sb: The overlayfs super block
|
|
* @buf: The struct kstatfs to fill in with stats
|
|
*
|
|
* Get the filesystem statistics. As writes always target the upper layer
|
|
* filesystem pass the statfs to the upper filesystem (if it exists)
|
|
*/
|
|
static int ovl_statfs(struct dentry *dentry, struct kstatfs *buf)
|
|
{
|
|
struct ovl_fs *ofs = dentry->d_sb->s_fs_info;
|
|
struct dentry *root_dentry = dentry->d_sb->s_root;
|
|
struct path path;
|
|
int err;
|
|
|
|
ovl_path_real(root_dentry, &path);
|
|
|
|
err = vfs_statfs(&path, buf);
|
|
if (!err) {
|
|
buf->f_namelen = ofs->namelen;
|
|
buf->f_type = OVERLAYFS_SUPER_MAGIC;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
/* Will this overlay be forced to mount/remount ro? */
|
|
static bool ovl_force_readonly(struct ovl_fs *ofs)
|
|
{
|
|
return (!ofs->upper_mnt || !ofs->workdir);
|
|
}
|
|
|
|
static const char *ovl_redirect_mode_def(void)
|
|
{
|
|
return ovl_redirect_dir_def ? "on" : "off";
|
|
}
|
|
|
|
/**
|
|
* ovl_show_options
|
|
*
|
|
* Prints the mount options for a given superblock.
|
|
* Returns zero; does not fail.
|
|
*/
|
|
static int ovl_show_options(struct seq_file *m, struct dentry *dentry)
|
|
{
|
|
struct super_block *sb = dentry->d_sb;
|
|
struct ovl_fs *ofs = sb->s_fs_info;
|
|
|
|
seq_show_option(m, "lowerdir", ofs->config.lowerdir);
|
|
if (ofs->config.upperdir) {
|
|
seq_show_option(m, "upperdir", ofs->config.upperdir);
|
|
seq_show_option(m, "workdir", ofs->config.workdir);
|
|
}
|
|
if (ofs->config.default_permissions)
|
|
seq_puts(m, ",default_permissions");
|
|
if (strcmp(ofs->config.redirect_mode, ovl_redirect_mode_def()) != 0)
|
|
seq_printf(m, ",redirect_dir=%s", ofs->config.redirect_mode);
|
|
if (ofs->config.index != ovl_index_def)
|
|
seq_printf(m, ",index=%s", ofs->config.index ? "on" : "off");
|
|
if (ofs->config.nfs_export != ovl_nfs_export_def)
|
|
seq_printf(m, ",nfs_export=%s", ofs->config.nfs_export ?
|
|
"on" : "off");
|
|
return 0;
|
|
}
|
|
|
|
static int ovl_remount(struct super_block *sb, int *flags, char *data)
|
|
{
|
|
struct ovl_fs *ofs = sb->s_fs_info;
|
|
|
|
if (!(*flags & SB_RDONLY) && ovl_force_readonly(ofs))
|
|
return -EROFS;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct super_operations ovl_super_operations = {
|
|
.alloc_inode = ovl_alloc_inode,
|
|
.destroy_inode = ovl_destroy_inode,
|
|
.drop_inode = generic_delete_inode,
|
|
.put_super = ovl_put_super,
|
|
.sync_fs = ovl_sync_fs,
|
|
.statfs = ovl_statfs,
|
|
.show_options = ovl_show_options,
|
|
.remount_fs = ovl_remount,
|
|
};
|
|
|
|
enum {
|
|
OPT_LOWERDIR,
|
|
OPT_UPPERDIR,
|
|
OPT_WORKDIR,
|
|
OPT_DEFAULT_PERMISSIONS,
|
|
OPT_REDIRECT_DIR,
|
|
OPT_INDEX_ON,
|
|
OPT_INDEX_OFF,
|
|
OPT_NFS_EXPORT_ON,
|
|
OPT_NFS_EXPORT_OFF,
|
|
OPT_ERR,
|
|
};
|
|
|
|
static const match_table_t ovl_tokens = {
|
|
{OPT_LOWERDIR, "lowerdir=%s"},
|
|
{OPT_UPPERDIR, "upperdir=%s"},
|
|
{OPT_WORKDIR, "workdir=%s"},
|
|
{OPT_DEFAULT_PERMISSIONS, "default_permissions"},
|
|
{OPT_REDIRECT_DIR, "redirect_dir=%s"},
|
|
{OPT_INDEX_ON, "index=on"},
|
|
{OPT_INDEX_OFF, "index=off"},
|
|
{OPT_NFS_EXPORT_ON, "nfs_export=on"},
|
|
{OPT_NFS_EXPORT_OFF, "nfs_export=off"},
|
|
{OPT_ERR, NULL}
|
|
};
|
|
|
|
static char *ovl_next_opt(char **s)
|
|
{
|
|
char *sbegin = *s;
|
|
char *p;
|
|
|
|
if (sbegin == NULL)
|
|
return NULL;
|
|
|
|
for (p = sbegin; *p; p++) {
|
|
if (*p == '\\') {
|
|
p++;
|
|
if (!*p)
|
|
break;
|
|
} else if (*p == ',') {
|
|
*p = '\0';
|
|
*s = p + 1;
|
|
return sbegin;
|
|
}
|
|
}
|
|
*s = NULL;
|
|
return sbegin;
|
|
}
|
|
|
|
static int ovl_parse_redirect_mode(struct ovl_config *config, const char *mode)
|
|
{
|
|
if (strcmp(mode, "on") == 0) {
|
|
config->redirect_dir = true;
|
|
/*
|
|
* Does not make sense to have redirect creation without
|
|
* redirect following.
|
|
*/
|
|
config->redirect_follow = true;
|
|
} else if (strcmp(mode, "follow") == 0) {
|
|
config->redirect_follow = true;
|
|
} else if (strcmp(mode, "off") == 0) {
|
|
if (ovl_redirect_always_follow)
|
|
config->redirect_follow = true;
|
|
} else if (strcmp(mode, "nofollow") != 0) {
|
|
pr_err("overlayfs: bad mount option \"redirect_dir=%s\"\n",
|
|
mode);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ovl_parse_opt(char *opt, struct ovl_config *config)
|
|
{
|
|
char *p;
|
|
|
|
config->redirect_mode = kstrdup(ovl_redirect_mode_def(), GFP_KERNEL);
|
|
if (!config->redirect_mode)
|
|
return -ENOMEM;
|
|
|
|
while ((p = ovl_next_opt(&opt)) != NULL) {
|
|
int token;
|
|
substring_t args[MAX_OPT_ARGS];
|
|
|
|
if (!*p)
|
|
continue;
|
|
|
|
token = match_token(p, ovl_tokens, args);
|
|
switch (token) {
|
|
case OPT_UPPERDIR:
|
|
kfree(config->upperdir);
|
|
config->upperdir = match_strdup(&args[0]);
|
|
if (!config->upperdir)
|
|
return -ENOMEM;
|
|
break;
|
|
|
|
case OPT_LOWERDIR:
|
|
kfree(config->lowerdir);
|
|
config->lowerdir = match_strdup(&args[0]);
|
|
if (!config->lowerdir)
|
|
return -ENOMEM;
|
|
break;
|
|
|
|
case OPT_WORKDIR:
|
|
kfree(config->workdir);
|
|
config->workdir = match_strdup(&args[0]);
|
|
if (!config->workdir)
|
|
return -ENOMEM;
|
|
break;
|
|
|
|
case OPT_DEFAULT_PERMISSIONS:
|
|
config->default_permissions = true;
|
|
break;
|
|
|
|
case OPT_REDIRECT_DIR:
|
|
kfree(config->redirect_mode);
|
|
config->redirect_mode = match_strdup(&args[0]);
|
|
if (!config->redirect_mode)
|
|
return -ENOMEM;
|
|
break;
|
|
|
|
case OPT_INDEX_ON:
|
|
config->index = true;
|
|
break;
|
|
|
|
case OPT_INDEX_OFF:
|
|
config->index = false;
|
|
break;
|
|
|
|
case OPT_NFS_EXPORT_ON:
|
|
config->nfs_export = true;
|
|
break;
|
|
|
|
case OPT_NFS_EXPORT_OFF:
|
|
config->nfs_export = false;
|
|
break;
|
|
|
|
default:
|
|
pr_err("overlayfs: unrecognized mount option \"%s\" or missing value\n", p);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
/* Workdir is useless in non-upper mount */
|
|
if (!config->upperdir && config->workdir) {
|
|
pr_info("overlayfs: option \"workdir=%s\" is useless in a non-upper mount, ignore\n",
|
|
config->workdir);
|
|
kfree(config->workdir);
|
|
config->workdir = NULL;
|
|
}
|
|
|
|
return ovl_parse_redirect_mode(config, config->redirect_mode);
|
|
}
|
|
|
|
#define OVL_WORKDIR_NAME "work"
|
|
#define OVL_INDEXDIR_NAME "index"
|
|
|
|
static struct dentry *ovl_workdir_create(struct ovl_fs *ofs,
|
|
const char *name, bool persist)
|
|
{
|
|
struct inode *dir = ofs->workbasedir->d_inode;
|
|
struct vfsmount *mnt = ofs->upper_mnt;
|
|
struct dentry *work;
|
|
int err;
|
|
bool retried = false;
|
|
bool locked = false;
|
|
|
|
inode_lock_nested(dir, I_MUTEX_PARENT);
|
|
locked = true;
|
|
|
|
retry:
|
|
work = lookup_one_len(name, ofs->workbasedir, strlen(name));
|
|
|
|
if (!IS_ERR(work)) {
|
|
struct iattr attr = {
|
|
.ia_valid = ATTR_MODE,
|
|
.ia_mode = S_IFDIR | 0,
|
|
};
|
|
|
|
if (work->d_inode) {
|
|
err = -EEXIST;
|
|
if (retried)
|
|
goto out_dput;
|
|
|
|
if (persist)
|
|
goto out_unlock;
|
|
|
|
retried = true;
|
|
ovl_workdir_cleanup(dir, mnt, work, 0);
|
|
dput(work);
|
|
goto retry;
|
|
}
|
|
|
|
err = ovl_create_real(dir, work,
|
|
&(struct cattr){.mode = S_IFDIR | 0},
|
|
NULL, true);
|
|
if (err)
|
|
goto out_dput;
|
|
|
|
/*
|
|
* Try to remove POSIX ACL xattrs from workdir. We are good if:
|
|
*
|
|
* a) success (there was a POSIX ACL xattr and was removed)
|
|
* b) -ENODATA (there was no POSIX ACL xattr)
|
|
* c) -EOPNOTSUPP (POSIX ACL xattrs are not supported)
|
|
*
|
|
* There are various other error values that could effectively
|
|
* mean that the xattr doesn't exist (e.g. -ERANGE is returned
|
|
* if the xattr name is too long), but the set of filesystems
|
|
* allowed as upper are limited to "normal" ones, where checking
|
|
* for the above two errors is sufficient.
|
|
*/
|
|
err = vfs_removexattr(work, XATTR_NAME_POSIX_ACL_DEFAULT);
|
|
if (err && err != -ENODATA && err != -EOPNOTSUPP)
|
|
goto out_dput;
|
|
|
|
err = vfs_removexattr(work, XATTR_NAME_POSIX_ACL_ACCESS);
|
|
if (err && err != -ENODATA && err != -EOPNOTSUPP)
|
|
goto out_dput;
|
|
|
|
/* Clear any inherited mode bits */
|
|
inode_lock(work->d_inode);
|
|
err = notify_change(work, &attr, NULL);
|
|
inode_unlock(work->d_inode);
|
|
if (err)
|
|
goto out_dput;
|
|
} else {
|
|
err = PTR_ERR(work);
|
|
goto out_err;
|
|
}
|
|
out_unlock:
|
|
if (locked)
|
|
inode_unlock(dir);
|
|
|
|
return work;
|
|
|
|
out_dput:
|
|
dput(work);
|
|
out_err:
|
|
pr_warn("overlayfs: failed to create directory %s/%s (errno: %i); mounting read-only\n",
|
|
ofs->config.workdir, name, -err);
|
|
work = NULL;
|
|
goto out_unlock;
|
|
}
|
|
|
|
static void ovl_unescape(char *s)
|
|
{
|
|
char *d = s;
|
|
|
|
for (;; s++, d++) {
|
|
if (*s == '\\')
|
|
s++;
|
|
*d = *s;
|
|
if (!*s)
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int ovl_mount_dir_noesc(const char *name, struct path *path)
|
|
{
|
|
int err = -EINVAL;
|
|
|
|
if (!*name) {
|
|
pr_err("overlayfs: empty lowerdir\n");
|
|
goto out;
|
|
}
|
|
err = kern_path(name, LOOKUP_FOLLOW, path);
|
|
if (err) {
|
|
pr_err("overlayfs: failed to resolve '%s': %i\n", name, err);
|
|
goto out;
|
|
}
|
|
err = -EINVAL;
|
|
if (ovl_dentry_weird(path->dentry)) {
|
|
pr_err("overlayfs: filesystem on '%s' not supported\n", name);
|
|
goto out_put;
|
|
}
|
|
if (!d_is_dir(path->dentry)) {
|
|
pr_err("overlayfs: '%s' not a directory\n", name);
|
|
goto out_put;
|
|
}
|
|
return 0;
|
|
|
|
out_put:
|
|
path_put_init(path);
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
static int ovl_mount_dir(const char *name, struct path *path)
|
|
{
|
|
int err = -ENOMEM;
|
|
char *tmp = kstrdup(name, GFP_KERNEL);
|
|
|
|
if (tmp) {
|
|
ovl_unescape(tmp);
|
|
err = ovl_mount_dir_noesc(tmp, path);
|
|
|
|
if (!err)
|
|
if (ovl_dentry_remote(path->dentry)) {
|
|
pr_err("overlayfs: filesystem on '%s' not supported as upperdir\n",
|
|
tmp);
|
|
path_put_init(path);
|
|
err = -EINVAL;
|
|
}
|
|
kfree(tmp);
|
|
}
|
|
return err;
|
|
}
|
|
|
|
static int ovl_check_namelen(struct path *path, struct ovl_fs *ofs,
|
|
const char *name)
|
|
{
|
|
struct kstatfs statfs;
|
|
int err = vfs_statfs(path, &statfs);
|
|
|
|
if (err)
|
|
pr_err("overlayfs: statfs failed on '%s'\n", name);
|
|
else
|
|
ofs->namelen = max(ofs->namelen, statfs.f_namelen);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int ovl_lower_dir(const char *name, struct path *path,
|
|
struct ovl_fs *ofs, int *stack_depth, bool *remote)
|
|
{
|
|
int err;
|
|
|
|
err = ovl_mount_dir_noesc(name, path);
|
|
if (err)
|
|
goto out;
|
|
|
|
err = ovl_check_namelen(path, ofs, name);
|
|
if (err)
|
|
goto out_put;
|
|
|
|
*stack_depth = max(*stack_depth, path->mnt->mnt_sb->s_stack_depth);
|
|
|
|
if (ovl_dentry_remote(path->dentry))
|
|
*remote = true;
|
|
|
|
/*
|
|
* The inodes index feature and NFS export need to encode and decode
|
|
* file handles, so they require that all layers support them.
|
|
*/
|
|
if ((ofs->config.nfs_export ||
|
|
(ofs->config.index && ofs->config.upperdir)) &&
|
|
!ovl_can_decode_fh(path->dentry->d_sb)) {
|
|
ofs->config.index = false;
|
|
ofs->config.nfs_export = false;
|
|
pr_warn("overlayfs: fs on '%s' does not support file handles, falling back to index=off,nfs_export=off.\n",
|
|
name);
|
|
}
|
|
|
|
return 0;
|
|
|
|
out_put:
|
|
path_put_init(path);
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
/* Workdir should not be subdir of upperdir and vice versa */
|
|
static bool ovl_workdir_ok(struct dentry *workdir, struct dentry *upperdir)
|
|
{
|
|
bool ok = false;
|
|
|
|
if (workdir != upperdir) {
|
|
ok = (lock_rename(workdir, upperdir) == NULL);
|
|
unlock_rename(workdir, upperdir);
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
static unsigned int ovl_split_lowerdirs(char *str)
|
|
{
|
|
unsigned int ctr = 1;
|
|
char *s, *d;
|
|
|
|
for (s = d = str;; s++, d++) {
|
|
if (*s == '\\') {
|
|
s++;
|
|
} else if (*s == ':') {
|
|
*d = '\0';
|
|
ctr++;
|
|
continue;
|
|
}
|
|
*d = *s;
|
|
if (!*s)
|
|
break;
|
|
}
|
|
return ctr;
|
|
}
|
|
|
|
static int __maybe_unused
|
|
ovl_posix_acl_xattr_get(const struct xattr_handler *handler,
|
|
struct dentry *dentry, struct inode *inode,
|
|
const char *name, void *buffer, size_t size)
|
|
{
|
|
return ovl_xattr_get(dentry, inode, handler->name, buffer, size);
|
|
}
|
|
|
|
static int __maybe_unused
|
|
ovl_posix_acl_xattr_set(const struct xattr_handler *handler,
|
|
struct dentry *dentry, struct inode *inode,
|
|
const char *name, const void *value,
|
|
size_t size, int flags)
|
|
{
|
|
struct dentry *workdir = ovl_workdir(dentry);
|
|
struct inode *realinode = ovl_inode_real(inode);
|
|
struct posix_acl *acl = NULL;
|
|
int err;
|
|
|
|
/* Check that everything is OK before copy-up */
|
|
if (value) {
|
|
acl = posix_acl_from_xattr(&init_user_ns, value, size);
|
|
if (IS_ERR(acl))
|
|
return PTR_ERR(acl);
|
|
}
|
|
err = -EOPNOTSUPP;
|
|
if (!IS_POSIXACL(d_inode(workdir)))
|
|
goto out_acl_release;
|
|
if (!realinode->i_op->set_acl)
|
|
goto out_acl_release;
|
|
if (handler->flags == ACL_TYPE_DEFAULT && !S_ISDIR(inode->i_mode)) {
|
|
err = acl ? -EACCES : 0;
|
|
goto out_acl_release;
|
|
}
|
|
err = -EPERM;
|
|
if (!inode_owner_or_capable(inode))
|
|
goto out_acl_release;
|
|
|
|
posix_acl_release(acl);
|
|
|
|
/*
|
|
* Check if sgid bit needs to be cleared (actual setacl operation will
|
|
* be done with mounter's capabilities and so that won't do it for us).
|
|
*/
|
|
if (unlikely(inode->i_mode & S_ISGID) &&
|
|
handler->flags == ACL_TYPE_ACCESS &&
|
|
!in_group_p(inode->i_gid) &&
|
|
!capable_wrt_inode_uidgid(inode, CAP_FSETID)) {
|
|
struct iattr iattr = { .ia_valid = ATTR_KILL_SGID };
|
|
|
|
err = ovl_setattr(dentry, &iattr);
|
|
if (err)
|
|
return err;
|
|
}
|
|
|
|
err = ovl_xattr_set(dentry, inode, handler->name, value, size, flags);
|
|
if (!err)
|
|
ovl_copyattr(ovl_inode_real(inode), inode);
|
|
|
|
return err;
|
|
|
|
out_acl_release:
|
|
posix_acl_release(acl);
|
|
return err;
|
|
}
|
|
|
|
static int ovl_own_xattr_get(const struct xattr_handler *handler,
|
|
struct dentry *dentry, struct inode *inode,
|
|
const char *name, void *buffer, size_t size)
|
|
{
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
static int ovl_own_xattr_set(const struct xattr_handler *handler,
|
|
struct dentry *dentry, struct inode *inode,
|
|
const char *name, const void *value,
|
|
size_t size, int flags)
|
|
{
|
|
return -EOPNOTSUPP;
|
|
}
|
|
|
|
static int ovl_other_xattr_get(const struct xattr_handler *handler,
|
|
struct dentry *dentry, struct inode *inode,
|
|
const char *name, void *buffer, size_t size)
|
|
{
|
|
return ovl_xattr_get(dentry, inode, name, buffer, size);
|
|
}
|
|
|
|
static int ovl_other_xattr_set(const struct xattr_handler *handler,
|
|
struct dentry *dentry, struct inode *inode,
|
|
const char *name, const void *value,
|
|
size_t size, int flags)
|
|
{
|
|
return ovl_xattr_set(dentry, inode, name, value, size, flags);
|
|
}
|
|
|
|
static const struct xattr_handler __maybe_unused
|
|
ovl_posix_acl_access_xattr_handler = {
|
|
.name = XATTR_NAME_POSIX_ACL_ACCESS,
|
|
.flags = ACL_TYPE_ACCESS,
|
|
.get = ovl_posix_acl_xattr_get,
|
|
.set = ovl_posix_acl_xattr_set,
|
|
};
|
|
|
|
static const struct xattr_handler __maybe_unused
|
|
ovl_posix_acl_default_xattr_handler = {
|
|
.name = XATTR_NAME_POSIX_ACL_DEFAULT,
|
|
.flags = ACL_TYPE_DEFAULT,
|
|
.get = ovl_posix_acl_xattr_get,
|
|
.set = ovl_posix_acl_xattr_set,
|
|
};
|
|
|
|
static const struct xattr_handler ovl_own_xattr_handler = {
|
|
.prefix = OVL_XATTR_PREFIX,
|
|
.get = ovl_own_xattr_get,
|
|
.set = ovl_own_xattr_set,
|
|
};
|
|
|
|
static const struct xattr_handler ovl_other_xattr_handler = {
|
|
.prefix = "", /* catch all */
|
|
.get = ovl_other_xattr_get,
|
|
.set = ovl_other_xattr_set,
|
|
};
|
|
|
|
static const struct xattr_handler *ovl_xattr_handlers[] = {
|
|
#ifdef CONFIG_FS_POSIX_ACL
|
|
&ovl_posix_acl_access_xattr_handler,
|
|
&ovl_posix_acl_default_xattr_handler,
|
|
#endif
|
|
&ovl_own_xattr_handler,
|
|
&ovl_other_xattr_handler,
|
|
NULL
|
|
};
|
|
|
|
static int ovl_get_upper(struct ovl_fs *ofs, struct path *upperpath)
|
|
{
|
|
struct vfsmount *upper_mnt;
|
|
int err;
|
|
|
|
err = ovl_mount_dir(ofs->config.upperdir, upperpath);
|
|
if (err)
|
|
goto out;
|
|
|
|
/* Upper fs should not be r/o */
|
|
if (sb_rdonly(upperpath->mnt->mnt_sb)) {
|
|
pr_err("overlayfs: upper fs is r/o, try multi-lower layers mount\n");
|
|
err = -EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
err = ovl_check_namelen(upperpath, ofs, ofs->config.upperdir);
|
|
if (err)
|
|
goto out;
|
|
|
|
err = -EBUSY;
|
|
if (ovl_inuse_trylock(upperpath->dentry)) {
|
|
ofs->upperdir_locked = true;
|
|
} else if (ofs->config.index) {
|
|
pr_err("overlayfs: upperdir is in-use by another mount, mount with '-o index=off' to override exclusive upperdir protection.\n");
|
|
goto out;
|
|
} else {
|
|
pr_warn("overlayfs: upperdir is in-use by another mount, accessing files from both mounts will result in undefined behavior.\n");
|
|
}
|
|
|
|
upper_mnt = clone_private_mount(upperpath);
|
|
err = PTR_ERR(upper_mnt);
|
|
if (IS_ERR(upper_mnt)) {
|
|
pr_err("overlayfs: failed to clone upperpath\n");
|
|
goto out;
|
|
}
|
|
|
|
/* Don't inherit atime flags */
|
|
upper_mnt->mnt_flags &= ~(MNT_NOATIME | MNT_NODIRATIME | MNT_RELATIME);
|
|
ofs->upper_mnt = upper_mnt;
|
|
err = 0;
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
static int ovl_make_workdir(struct ovl_fs *ofs, struct path *workpath)
|
|
{
|
|
struct vfsmount *mnt = ofs->upper_mnt;
|
|
struct dentry *temp;
|
|
int err;
|
|
|
|
err = mnt_want_write(mnt);
|
|
if (err)
|
|
return err;
|
|
|
|
ofs->workdir = ovl_workdir_create(ofs, OVL_WORKDIR_NAME, false);
|
|
if (!ofs->workdir)
|
|
goto out;
|
|
|
|
/*
|
|
* Upper should support d_type, else whiteouts are visible. Given
|
|
* workdir and upper are on same fs, we can do iterate_dir() on
|
|
* workdir. This check requires successful creation of workdir in
|
|
* previous step.
|
|
*/
|
|
err = ovl_check_d_type_supported(workpath);
|
|
if (err < 0)
|
|
goto out;
|
|
|
|
/*
|
|
* We allowed this configuration and don't want to break users over
|
|
* kernel upgrade. So warn instead of erroring out.
|
|
*/
|
|
if (!err)
|
|
pr_warn("overlayfs: upper fs needs to support d_type.\n");
|
|
|
|
/* Check if upper/work fs supports O_TMPFILE */
|
|
temp = ovl_do_tmpfile(ofs->workdir, S_IFREG | 0);
|
|
ofs->tmpfile = !IS_ERR(temp);
|
|
if (ofs->tmpfile)
|
|
dput(temp);
|
|
else
|
|
pr_warn("overlayfs: upper fs does not support tmpfile.\n");
|
|
|
|
/*
|
|
* Check if upper/work fs supports trusted.overlay.* xattr
|
|
*/
|
|
err = ovl_do_setxattr(ofs->workdir, OVL_XATTR_OPAQUE, "0", 1, 0);
|
|
if (err) {
|
|
ofs->noxattr = true;
|
|
ofs->config.index = false;
|
|
pr_warn("overlayfs: upper fs does not support xattr, falling back to index=off.\n");
|
|
err = 0;
|
|
} else {
|
|
vfs_removexattr(ofs->workdir, OVL_XATTR_OPAQUE);
|
|
}
|
|
|
|
/* Check if upper/work fs supports file handles */
|
|
if (ofs->config.index &&
|
|
!ovl_can_decode_fh(ofs->workdir->d_sb)) {
|
|
ofs->config.index = false;
|
|
pr_warn("overlayfs: upper fs does not support file handles, falling back to index=off.\n");
|
|
}
|
|
|
|
/* NFS export of r/w mount depends on index */
|
|
if (ofs->config.nfs_export && !ofs->config.index) {
|
|
pr_warn("overlayfs: NFS export requires \"index=on\", falling back to nfs_export=off.\n");
|
|
ofs->config.nfs_export = false;
|
|
}
|
|
|
|
out:
|
|
mnt_drop_write(mnt);
|
|
return err;
|
|
}
|
|
|
|
static int ovl_get_workdir(struct ovl_fs *ofs, struct path *upperpath)
|
|
{
|
|
int err;
|
|
struct path workpath = { };
|
|
|
|
err = ovl_mount_dir(ofs->config.workdir, &workpath);
|
|
if (err)
|
|
goto out;
|
|
|
|
err = -EINVAL;
|
|
if (upperpath->mnt != workpath.mnt) {
|
|
pr_err("overlayfs: workdir and upperdir must reside under the same mount\n");
|
|
goto out;
|
|
}
|
|
if (!ovl_workdir_ok(workpath.dentry, upperpath->dentry)) {
|
|
pr_err("overlayfs: workdir and upperdir must be separate subtrees\n");
|
|
goto out;
|
|
}
|
|
|
|
err = -EBUSY;
|
|
if (ovl_inuse_trylock(workpath.dentry)) {
|
|
ofs->workdir_locked = true;
|
|
} else if (ofs->config.index) {
|
|
pr_err("overlayfs: workdir is in-use by another mount, mount with '-o index=off' to override exclusive workdir protection.\n");
|
|
goto out;
|
|
} else {
|
|
pr_warn("overlayfs: workdir is in-use by another mount, accessing files from both mounts will result in undefined behavior.\n");
|
|
}
|
|
|
|
ofs->workbasedir = dget(workpath.dentry);
|
|
err = ovl_make_workdir(ofs, &workpath);
|
|
if (err)
|
|
goto out;
|
|
|
|
err = 0;
|
|
out:
|
|
path_put(&workpath);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int ovl_get_indexdir(struct ovl_fs *ofs, struct ovl_entry *oe,
|
|
struct path *upperpath)
|
|
{
|
|
struct vfsmount *mnt = ofs->upper_mnt;
|
|
int err;
|
|
|
|
err = mnt_want_write(mnt);
|
|
if (err)
|
|
return err;
|
|
|
|
/* Verify lower root is upper root origin */
|
|
err = ovl_verify_origin(upperpath->dentry, oe->lowerstack[0].dentry,
|
|
true);
|
|
if (err) {
|
|
pr_err("overlayfs: failed to verify upper root origin\n");
|
|
goto out;
|
|
}
|
|
|
|
ofs->indexdir = ovl_workdir_create(ofs, OVL_INDEXDIR_NAME, true);
|
|
if (ofs->indexdir) {
|
|
/*
|
|
* Verify upper root is exclusively associated with index dir.
|
|
* Older kernels stored upper fh in "trusted.overlay.origin"
|
|
* xattr. If that xattr exists, verify that it is a match to
|
|
* upper dir file handle. In any case, verify or set xattr
|
|
* "trusted.overlay.upper" to indicate that index may have
|
|
* directory entries.
|
|
*/
|
|
if (ovl_check_origin_xattr(ofs->indexdir)) {
|
|
err = ovl_verify_set_fh(ofs->indexdir, OVL_XATTR_ORIGIN,
|
|
upperpath->dentry, true, false);
|
|
if (err)
|
|
pr_err("overlayfs: failed to verify index dir 'origin' xattr\n");
|
|
}
|
|
err = ovl_verify_upper(ofs->indexdir, upperpath->dentry, true);
|
|
if (err)
|
|
pr_err("overlayfs: failed to verify index dir 'upper' xattr\n");
|
|
|
|
/* Cleanup bad/stale/orphan index entries */
|
|
if (!err)
|
|
err = ovl_indexdir_cleanup(ofs);
|
|
}
|
|
if (err || !ofs->indexdir)
|
|
pr_warn("overlayfs: try deleting index dir or mounting with '-o index=off' to disable inodes index.\n");
|
|
|
|
out:
|
|
mnt_drop_write(mnt);
|
|
return err;
|
|
}
|
|
|
|
static int ovl_get_lower_layers(struct ovl_fs *ofs, struct path *stack,
|
|
unsigned int numlower)
|
|
{
|
|
int err;
|
|
unsigned int i;
|
|
|
|
err = -ENOMEM;
|
|
ofs->lower_layers = kcalloc(numlower, sizeof(struct ovl_layer),
|
|
GFP_KERNEL);
|
|
if (ofs->lower_layers == NULL)
|
|
goto out;
|
|
for (i = 0; i < numlower; i++) {
|
|
struct vfsmount *mnt;
|
|
dev_t dev;
|
|
|
|
err = get_anon_bdev(&dev);
|
|
if (err) {
|
|
pr_err("overlayfs: failed to get anonymous bdev for lowerpath\n");
|
|
goto out;
|
|
}
|
|
|
|
mnt = clone_private_mount(&stack[i]);
|
|
err = PTR_ERR(mnt);
|
|
if (IS_ERR(mnt)) {
|
|
pr_err("overlayfs: failed to clone lowerpath\n");
|
|
free_anon_bdev(dev);
|
|
goto out;
|
|
}
|
|
/*
|
|
* Make lower layers R/O. That way fchmod/fchown on lower file
|
|
* will fail instead of modifying lower fs.
|
|
*/
|
|
mnt->mnt_flags |= MNT_READONLY | MNT_NOATIME;
|
|
|
|
ofs->lower_layers[ofs->numlower].mnt = mnt;
|
|
ofs->lower_layers[ofs->numlower].pseudo_dev = dev;
|
|
ofs->lower_layers[ofs->numlower].idx = i + 1;
|
|
ofs->numlower++;
|
|
|
|
/* Check if all lower layers are on same sb */
|
|
if (i == 0)
|
|
ofs->same_sb = mnt->mnt_sb;
|
|
else if (ofs->same_sb != mnt->mnt_sb)
|
|
ofs->same_sb = NULL;
|
|
}
|
|
err = 0;
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
static struct ovl_entry *ovl_get_lowerstack(struct super_block *sb,
|
|
struct ovl_fs *ofs)
|
|
{
|
|
int err;
|
|
char *lowertmp, *lower;
|
|
struct path *stack = NULL;
|
|
unsigned int stacklen, numlower = 0, i;
|
|
bool remote = false;
|
|
struct ovl_entry *oe;
|
|
|
|
err = -ENOMEM;
|
|
lowertmp = kstrdup(ofs->config.lowerdir, GFP_KERNEL);
|
|
if (!lowertmp)
|
|
goto out_err;
|
|
|
|
err = -EINVAL;
|
|
stacklen = ovl_split_lowerdirs(lowertmp);
|
|
if (stacklen > OVL_MAX_STACK) {
|
|
pr_err("overlayfs: too many lower directories, limit is %d\n",
|
|
OVL_MAX_STACK);
|
|
goto out_err;
|
|
} else if (!ofs->config.upperdir && stacklen == 1) {
|
|
pr_err("overlayfs: at least 2 lowerdir are needed while upperdir nonexistent\n");
|
|
goto out_err;
|
|
} else if (!ofs->config.upperdir && ofs->config.nfs_export &&
|
|
ofs->config.redirect_follow) {
|
|
pr_warn("overlayfs: NFS export requires \"redirect_dir=nofollow\" on non-upper mount, falling back to nfs_export=off.\n");
|
|
ofs->config.nfs_export = false;
|
|
}
|
|
|
|
err = -ENOMEM;
|
|
stack = kcalloc(stacklen, sizeof(struct path), GFP_KERNEL);
|
|
if (!stack)
|
|
goto out_err;
|
|
|
|
err = -EINVAL;
|
|
lower = lowertmp;
|
|
for (numlower = 0; numlower < stacklen; numlower++) {
|
|
err = ovl_lower_dir(lower, &stack[numlower], ofs,
|
|
&sb->s_stack_depth, &remote);
|
|
if (err)
|
|
goto out_err;
|
|
|
|
lower = strchr(lower, '\0') + 1;
|
|
}
|
|
|
|
err = -EINVAL;
|
|
sb->s_stack_depth++;
|
|
if (sb->s_stack_depth > FILESYSTEM_MAX_STACK_DEPTH) {
|
|
pr_err("overlayfs: maximum fs stacking depth exceeded\n");
|
|
goto out_err;
|
|
}
|
|
|
|
err = ovl_get_lower_layers(ofs, stack, numlower);
|
|
if (err)
|
|
goto out_err;
|
|
|
|
err = -ENOMEM;
|
|
oe = ovl_alloc_entry(numlower);
|
|
if (!oe)
|
|
goto out_err;
|
|
|
|
for (i = 0; i < numlower; i++) {
|
|
oe->lowerstack[i].dentry = dget(stack[i].dentry);
|
|
oe->lowerstack[i].layer = &ofs->lower_layers[i];
|
|
}
|
|
|
|
if (remote)
|
|
sb->s_d_op = &ovl_reval_dentry_operations;
|
|
else
|
|
sb->s_d_op = &ovl_dentry_operations;
|
|
|
|
out:
|
|
for (i = 0; i < numlower; i++)
|
|
path_put(&stack[i]);
|
|
kfree(stack);
|
|
kfree(lowertmp);
|
|
|
|
return oe;
|
|
|
|
out_err:
|
|
oe = ERR_PTR(err);
|
|
goto out;
|
|
}
|
|
|
|
static int ovl_fill_super(struct super_block *sb, void *data, int silent)
|
|
{
|
|
struct path upperpath = { };
|
|
struct dentry *root_dentry;
|
|
struct ovl_entry *oe;
|
|
struct ovl_fs *ofs;
|
|
struct cred *cred;
|
|
int err;
|
|
|
|
err = -ENOMEM;
|
|
ofs = kzalloc(sizeof(struct ovl_fs), GFP_KERNEL);
|
|
if (!ofs)
|
|
goto out;
|
|
|
|
ofs->creator_cred = cred = prepare_creds();
|
|
if (!cred)
|
|
goto out_err;
|
|
|
|
ofs->config.index = ovl_index_def;
|
|
ofs->config.nfs_export = ovl_nfs_export_def;
|
|
err = ovl_parse_opt((char *) data, &ofs->config);
|
|
if (err)
|
|
goto out_err;
|
|
|
|
err = -EINVAL;
|
|
if (!ofs->config.lowerdir) {
|
|
if (!silent)
|
|
pr_err("overlayfs: missing 'lowerdir'\n");
|
|
goto out_err;
|
|
}
|
|
|
|
sb->s_stack_depth = 0;
|
|
sb->s_maxbytes = MAX_LFS_FILESIZE;
|
|
if (ofs->config.upperdir) {
|
|
if (!ofs->config.workdir) {
|
|
pr_err("overlayfs: missing 'workdir'\n");
|
|
goto out_err;
|
|
}
|
|
|
|
err = ovl_get_upper(ofs, &upperpath);
|
|
if (err)
|
|
goto out_err;
|
|
|
|
err = ovl_get_workdir(ofs, &upperpath);
|
|
if (err)
|
|
goto out_err;
|
|
|
|
if (!ofs->workdir)
|
|
sb->s_flags |= SB_RDONLY;
|
|
|
|
sb->s_stack_depth = ofs->upper_mnt->mnt_sb->s_stack_depth;
|
|
sb->s_time_gran = ofs->upper_mnt->mnt_sb->s_time_gran;
|
|
|
|
}
|
|
oe = ovl_get_lowerstack(sb, ofs);
|
|
err = PTR_ERR(oe);
|
|
if (IS_ERR(oe))
|
|
goto out_err;
|
|
|
|
/* If the upper fs is nonexistent, we mark overlayfs r/o too */
|
|
if (!ofs->upper_mnt)
|
|
sb->s_flags |= SB_RDONLY;
|
|
else if (ofs->upper_mnt->mnt_sb != ofs->same_sb)
|
|
ofs->same_sb = NULL;
|
|
|
|
if (!(ovl_force_readonly(ofs)) && ofs->config.index) {
|
|
err = ovl_get_indexdir(ofs, oe, &upperpath);
|
|
if (err)
|
|
goto out_free_oe;
|
|
|
|
/* Force r/o mount with no index dir */
|
|
if (!ofs->indexdir) {
|
|
dput(ofs->workdir);
|
|
ofs->workdir = NULL;
|
|
sb->s_flags |= SB_RDONLY;
|
|
}
|
|
|
|
}
|
|
|
|
/* Show index=off in /proc/mounts for forced r/o mount */
|
|
if (!ofs->indexdir) {
|
|
ofs->config.index = false;
|
|
if (ofs->upper_mnt && ofs->config.nfs_export) {
|
|
pr_warn("overlayfs: NFS export requires an index dir, falling back to nfs_export=off.\n");
|
|
ofs->config.nfs_export = false;
|
|
}
|
|
}
|
|
|
|
if (ofs->config.nfs_export)
|
|
sb->s_export_op = &ovl_export_operations;
|
|
|
|
/* Never override disk quota limits or use reserved space */
|
|
cap_lower(cred->cap_effective, CAP_SYS_RESOURCE);
|
|
|
|
sb->s_magic = OVERLAYFS_SUPER_MAGIC;
|
|
sb->s_op = &ovl_super_operations;
|
|
sb->s_xattr = ovl_xattr_handlers;
|
|
sb->s_fs_info = ofs;
|
|
sb->s_flags |= SB_POSIXACL | SB_NOREMOTELOCK;
|
|
|
|
err = -ENOMEM;
|
|
root_dentry = d_make_root(ovl_new_inode(sb, S_IFDIR, 0));
|
|
if (!root_dentry)
|
|
goto out_free_oe;
|
|
|
|
root_dentry->d_fsdata = oe;
|
|
|
|
mntput(upperpath.mnt);
|
|
if (upperpath.dentry) {
|
|
ovl_dentry_set_upper_alias(root_dentry);
|
|
if (ovl_is_impuredir(upperpath.dentry))
|
|
ovl_set_flag(OVL_IMPURE, d_inode(root_dentry));
|
|
}
|
|
|
|
/* Root is always merge -> can have whiteouts */
|
|
ovl_set_flag(OVL_WHITEOUTS, d_inode(root_dentry));
|
|
ovl_dentry_set_flag(OVL_E_CONNECTED, root_dentry);
|
|
ovl_inode_init(d_inode(root_dentry), upperpath.dentry,
|
|
ovl_dentry_lower(root_dentry));
|
|
|
|
sb->s_root = root_dentry;
|
|
|
|
return 0;
|
|
|
|
out_free_oe:
|
|
ovl_entry_stack_free(oe);
|
|
kfree(oe);
|
|
out_err:
|
|
path_put(&upperpath);
|
|
ovl_free_fs(ofs);
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
static struct dentry *ovl_mount(struct file_system_type *fs_type, int flags,
|
|
const char *dev_name, void *raw_data)
|
|
{
|
|
return mount_nodev(fs_type, flags, raw_data, ovl_fill_super);
|
|
}
|
|
|
|
static struct file_system_type ovl_fs_type = {
|
|
.owner = THIS_MODULE,
|
|
.name = "overlay",
|
|
.mount = ovl_mount,
|
|
.kill_sb = kill_anon_super,
|
|
};
|
|
MODULE_ALIAS_FS("overlay");
|
|
|
|
static void ovl_inode_init_once(void *foo)
|
|
{
|
|
struct ovl_inode *oi = foo;
|
|
|
|
inode_init_once(&oi->vfs_inode);
|
|
}
|
|
|
|
static int __init ovl_init(void)
|
|
{
|
|
int err;
|
|
|
|
ovl_inode_cachep = kmem_cache_create("ovl_inode",
|
|
sizeof(struct ovl_inode), 0,
|
|
(SLAB_RECLAIM_ACCOUNT|
|
|
SLAB_MEM_SPREAD|SLAB_ACCOUNT),
|
|
ovl_inode_init_once);
|
|
if (ovl_inode_cachep == NULL)
|
|
return -ENOMEM;
|
|
|
|
err = register_filesystem(&ovl_fs_type);
|
|
if (err)
|
|
kmem_cache_destroy(ovl_inode_cachep);
|
|
|
|
return err;
|
|
}
|
|
|
|
static void __exit ovl_exit(void)
|
|
{
|
|
unregister_filesystem(&ovl_fs_type);
|
|
|
|
/*
|
|
* Make sure all delayed rcu free inodes are flushed before we
|
|
* destroy cache.
|
|
*/
|
|
rcu_barrier();
|
|
kmem_cache_destroy(ovl_inode_cachep);
|
|
|
|
}
|
|
|
|
module_init(ovl_init);
|
|
module_exit(ovl_exit);
|