From 49c21e1cacd74a8c83407c70ad860c994e606e25 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Sat, 13 Dec 2014 00:59:42 +0100 Subject: [PATCH 01/22] ovl: check whiteout while reading directory Don't make a separate pass for checking whiteouts, since we can do it while reading the upper directory. This will make it easier to handle multiple layers. Signed-off-by: Miklos Szeredi --- fs/overlayfs/readdir.c | 77 +++++++++++++++--------------------------- 1 file changed, 28 insertions(+), 49 deletions(-) diff --git a/fs/overlayfs/readdir.c b/fs/overlayfs/readdir.c index ab1e3dcbed95..3efa44acf98b 100644 --- a/fs/overlayfs/readdir.c +++ b/fs/overlayfs/readdir.c @@ -40,6 +40,7 @@ struct ovl_readdir_data { struct rb_root root; struct list_head *list; struct list_head middle; + struct dentry *dir; int count; int err; }; @@ -126,6 +127,32 @@ static int ovl_cache_entry_add_rb(struct ovl_readdir_data *rdd, if (p == NULL) return -ENOMEM; + if (d_type == DT_CHR) { + struct dentry *dentry; + const struct cred *old_cred; + struct cred *override_cred; + + override_cred = prepare_creds(); + if (!override_cred) { + kfree(p); + return -ENOMEM; + } + + /* + * CAP_DAC_OVERRIDE for lookup + */ + cap_raise(override_cred->cap_effective, CAP_DAC_OVERRIDE); + old_cred = override_creds(override_cred); + + dentry = lookup_one_len(name, rdd->dir, len); + if (!IS_ERR(dentry)) { + p->is_whiteout = ovl_is_whiteout(dentry); + dput(dentry); + } + revert_creds(old_cred); + put_cred(override_cred); + } + list_add_tail(&p->l_node, rdd->list); rb_link_node(&p->node, parent, newp); rb_insert_color(&p->node, &rdd->root); @@ -231,49 +258,6 @@ static void ovl_dir_reset(struct file *file) od->is_real = false; } -static int ovl_dir_mark_whiteouts(struct dentry *dir, - struct ovl_readdir_data *rdd) -{ - struct ovl_cache_entry *p; - struct dentry *dentry; - const struct cred *old_cred; - struct cred *override_cred; - - override_cred = prepare_creds(); - if (!override_cred) { - ovl_cache_free(rdd->list); - return -ENOMEM; - } - - /* - * CAP_DAC_OVERRIDE for lookup - */ - cap_raise(override_cred->cap_effective, CAP_DAC_OVERRIDE); - old_cred = override_creds(override_cred); - - mutex_lock(&dir->d_inode->i_mutex); - list_for_each_entry(p, rdd->list, l_node) { - if (p->is_cursor) - continue; - - if (p->type != DT_CHR) - continue; - - dentry = lookup_one_len(p->name, dir, p->len); - if (IS_ERR(dentry)) - continue; - - p->is_whiteout = ovl_is_whiteout(dentry); - dput(dentry); - } - mutex_unlock(&dir->d_inode->i_mutex); - - revert_creds(old_cred); - put_cred(override_cred); - - return 0; -} - static int ovl_dir_read_merged(struct dentry *dentry, struct list_head *list) { int err; @@ -290,15 +274,10 @@ static int ovl_dir_read_merged(struct dentry *dentry, struct list_head *list) ovl_path_upper(dentry, &upperpath); if (upperpath.dentry) { + rdd.dir = upperpath.dentry; err = ovl_dir_read(&upperpath, &rdd); if (err) goto out; - - if (lowerpath.dentry) { - err = ovl_dir_mark_whiteouts(upperpath.dentry, &rdd); - if (err) - goto out; - } } if (lowerpath.dentry) { /* From 1afaba1ecb5299cdd0f69b5bad98b0185fe71e79 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Sat, 13 Dec 2014 00:59:42 +0100 Subject: [PATCH 02/22] ovl: make path-type a bitmap OVL_PATH_PURE_UPPER -> __OVL_PATH_UPPER | __OVL_PATH_PURE OVL_PATH_UPPER -> __OVL_PATH_UPPER OVL_PATH_MERGE -> __OVL_PATH_UPPER | __OVL_PATH_MERGE OVL_PATH_LOWER -> 0 Multiple R/O layers will allow __OVL_PATH_MERGE without __OVL_PATH_UPPER. Signed-off-by: Miklos Szeredi --- fs/overlayfs/copy_up.c | 4 ++-- fs/overlayfs/dir.c | 22 +++++++++++----------- fs/overlayfs/inode.c | 9 ++++++--- fs/overlayfs/overlayfs.h | 13 +++++++++---- fs/overlayfs/readdir.c | 10 +++++----- fs/overlayfs/super.c | 19 ++++++++----------- 6 files changed, 41 insertions(+), 36 deletions(-) diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c index ea10a8719107..a5bfd60f4f6f 100644 --- a/fs/overlayfs/copy_up.c +++ b/fs/overlayfs/copy_up.c @@ -385,7 +385,7 @@ int ovl_copy_up(struct dentry *dentry) struct kstat stat; enum ovl_path_type type = ovl_path_type(dentry); - if (type != OVL_PATH_LOWER) + if (OVL_TYPE_UPPER(type)) break; next = dget(dentry); @@ -394,7 +394,7 @@ int ovl_copy_up(struct dentry *dentry) parent = dget_parent(next); type = ovl_path_type(parent); - if (type != OVL_PATH_LOWER) + if (OVL_TYPE_UPPER(type)) break; dput(next); diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index 8ffc4b980f1b..ab50bd111feb 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -152,7 +152,7 @@ static int ovl_dir_getattr(struct vfsmount *mnt, struct dentry *dentry, * correct link count. nlink=1 seems to pacify 'find' and * other utilities. */ - if (type == OVL_PATH_MERGE) + if (OVL_TYPE_MERGE(type)) stat->nlink = 1; return 0; @@ -630,7 +630,7 @@ static int ovl_do_remove(struct dentry *dentry, bool is_dir) goto out_drop_write; type = ovl_path_type(dentry); - if (type == OVL_PATH_PURE_UPPER) { + if (OVL_TYPE_PURE_UPPER(type)) { err = ovl_remove_upper(dentry, is_dir); } else { const struct cred *old_cred; @@ -712,7 +712,7 @@ static int ovl_rename2(struct inode *olddir, struct dentry *old, /* Don't copy up directory trees */ old_type = ovl_path_type(old); err = -EXDEV; - if ((old_type == OVL_PATH_LOWER || old_type == OVL_PATH_MERGE) && is_dir) + if (OVL_TYPE_MERGE_OR_LOWER(old_type) && is_dir) goto out; if (new->d_inode) { @@ -725,25 +725,25 @@ static int ovl_rename2(struct inode *olddir, struct dentry *old, new_type = ovl_path_type(new); err = -EXDEV; - if (!overwrite && (new_type == OVL_PATH_LOWER || new_type == OVL_PATH_MERGE) && new_is_dir) + if (!overwrite && OVL_TYPE_MERGE_OR_LOWER(new_type) && new_is_dir) goto out; err = 0; - if (new_type == OVL_PATH_LOWER && old_type == OVL_PATH_LOWER) { + if (!OVL_TYPE_UPPER(new_type) && !OVL_TYPE_UPPER(old_type)) { if (ovl_dentry_lower(old)->d_inode == ovl_dentry_lower(new)->d_inode) goto out; } - if (new_type != OVL_PATH_LOWER && old_type != OVL_PATH_LOWER) { + if (OVL_TYPE_UPPER(new_type) && OVL_TYPE_UPPER(old_type)) { if (ovl_dentry_upper(old)->d_inode == ovl_dentry_upper(new)->d_inode) goto out; } } else { if (ovl_dentry_is_opaque(new)) - new_type = OVL_PATH_UPPER; + new_type = __OVL_PATH_UPPER; else - new_type = OVL_PATH_PURE_UPPER; + new_type = __OVL_PATH_UPPER | __OVL_PATH_PURE; } err = ovl_want_write(old); @@ -763,8 +763,8 @@ static int ovl_rename2(struct inode *olddir, struct dentry *old, goto out_drop_write; } - old_opaque = old_type != OVL_PATH_PURE_UPPER; - new_opaque = new_type != OVL_PATH_PURE_UPPER; + old_opaque = !OVL_TYPE_PURE_UPPER(old_type); + new_opaque = !OVL_TYPE_PURE_UPPER(new_type); if (old_opaque || new_opaque) { err = -ENOMEM; @@ -787,7 +787,7 @@ static int ovl_rename2(struct inode *olddir, struct dentry *old, old_cred = override_creds(override_cred); } - if (overwrite && (new_type == OVL_PATH_LOWER || new_type == OVL_PATH_MERGE) && new_is_dir) { + if (overwrite && OVL_TYPE_MERGE_OR_LOWER(new_type) && new_is_dir) { opaquedir = ovl_check_empty_and_clear(new); err = PTR_ERR(opaquedir); if (IS_ERR(opaquedir)) { diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index 07d74b24913b..48492f1240ad 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -238,7 +238,10 @@ int ovl_setxattr(struct dentry *dentry, const char *name, static bool ovl_need_xattr_filter(struct dentry *dentry, enum ovl_path_type type) { - return type == OVL_PATH_UPPER && S_ISDIR(dentry->d_inode->i_mode); + if ((type & (__OVL_PATH_PURE | __OVL_PATH_UPPER)) == __OVL_PATH_UPPER) + return S_ISDIR(dentry->d_inode->i_mode); + else + return false; } ssize_t ovl_getxattr(struct dentry *dentry, const char *name, @@ -299,7 +302,7 @@ int ovl_removexattr(struct dentry *dentry, const char *name) if (ovl_need_xattr_filter(dentry, type) && ovl_is_private_xattr(name)) goto out_drop_write; - if (type == OVL_PATH_LOWER) { + if (!OVL_TYPE_UPPER(type)) { err = vfs_getxattr(realpath.dentry, name, NULL, 0); if (err < 0) goto out_drop_write; @@ -321,7 +324,7 @@ int ovl_removexattr(struct dentry *dentry, const char *name) static bool ovl_open_need_copy_up(int flags, enum ovl_path_type type, struct dentry *realdentry) { - if (type != OVL_PATH_LOWER) + if (OVL_TYPE_UPPER(type)) return false; if (special_file(realdentry->d_inode->i_mode)) diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index 814bed33dd07..d39eaa8f5e47 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -12,12 +12,17 @@ struct ovl_entry; enum ovl_path_type { - OVL_PATH_PURE_UPPER, - OVL_PATH_UPPER, - OVL_PATH_MERGE, - OVL_PATH_LOWER, + __OVL_PATH_PURE = (1 << 0), + __OVL_PATH_UPPER = (1 << 1), + __OVL_PATH_MERGE = (1 << 2), }; +#define OVL_TYPE_UPPER(type) ((type) & __OVL_PATH_UPPER) +#define OVL_TYPE_MERGE(type) ((type) & __OVL_PATH_MERGE) +#define OVL_TYPE_PURE_UPPER(type) ((type) & __OVL_PATH_PURE) +#define OVL_TYPE_MERGE_OR_LOWER(type) \ + (OVL_TYPE_MERGE(type) || !OVL_TYPE_UPPER(type)) + extern const char *ovl_opaque_xattr; static inline int ovl_do_rmdir(struct inode *dir, struct dentry *dentry) diff --git a/fs/overlayfs/readdir.c b/fs/overlayfs/readdir.c index 3efa44acf98b..481e44873b65 100644 --- a/fs/overlayfs/readdir.c +++ b/fs/overlayfs/readdir.c @@ -253,8 +253,8 @@ static void ovl_dir_reset(struct file *file) ovl_cache_put(od, dentry); od->cache = NULL; } - WARN_ON(!od->is_real && type != OVL_PATH_MERGE); - if (od->is_real && type == OVL_PATH_MERGE) + WARN_ON(!od->is_real && !OVL_TYPE_MERGE(type)); + if (od->is_real && OVL_TYPE_MERGE(type)) od->is_real = false; } @@ -429,7 +429,7 @@ static int ovl_dir_fsync(struct file *file, loff_t start, loff_t end, /* * Need to check if we started out being a lower dir, but got copied up */ - if (!od->is_upper && ovl_path_type(dentry) != OVL_PATH_LOWER) { + if (!od->is_upper && OVL_TYPE_UPPER(ovl_path_type(dentry))) { struct inode *inode = file_inode(file); realfile = lockless_dereference(od->upperfile); @@ -495,8 +495,8 @@ static int ovl_dir_open(struct inode *inode, struct file *file) } INIT_LIST_HEAD(&od->cursor.l_node); od->realfile = realfile; - od->is_real = (type != OVL_PATH_MERGE); - od->is_upper = (type != OVL_PATH_LOWER); + od->is_real = !OVL_TYPE_MERGE(type); + od->is_upper = OVL_TYPE_UPPER(type); od->cursor.is_cursor = true; file->private_data = od; diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index f16d318b71f8..821719cc8537 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -64,22 +64,19 @@ const char *ovl_opaque_xattr = "trusted.overlay.opaque"; enum ovl_path_type ovl_path_type(struct dentry *dentry) { struct ovl_entry *oe = dentry->d_fsdata; + enum ovl_path_type type = 0; if (oe->__upperdentry) { + type = __OVL_PATH_UPPER; + if (oe->lowerdentry) { if (S_ISDIR(dentry->d_inode->i_mode)) - return OVL_PATH_MERGE; - else - return OVL_PATH_UPPER; - } else { - if (oe->opaque) - return OVL_PATH_UPPER; - else - return OVL_PATH_PURE_UPPER; + type |= __OVL_PATH_MERGE; + } else if (!oe->opaque) { + type |= __OVL_PATH_PURE; } - } else { - return OVL_PATH_LOWER; } + return type; } static struct dentry *ovl_upperdentry_dereference(struct ovl_entry *oe) @@ -101,7 +98,7 @@ enum ovl_path_type ovl_path_real(struct dentry *dentry, struct path *path) enum ovl_path_type type = ovl_path_type(dentry); - if (type == OVL_PATH_LOWER) + if (!OVL_TYPE_UPPER(type)) ovl_path_lower(dentry, path); else ovl_path_upper(dentry, path); From 263b4a0fee43f1239c4d6f3c3a62fb5a20d84f2e Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Sat, 13 Dec 2014 00:59:43 +0100 Subject: [PATCH 03/22] ovl: dont replace opaque dir When removing an empty opaque directory, then it makes no sense to replace it with an exact replica of itself before removal. Signed-off-by: Miklos Szeredi --- fs/overlayfs/dir.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index ab50bd111feb..dcae3ac5aa76 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -506,7 +506,7 @@ static int ovl_remove_and_whiteout(struct dentry *dentry, bool is_dir) struct dentry *opaquedir = NULL; int err; - if (is_dir) { + if (is_dir && OVL_TYPE_MERGE_OR_LOWER(ovl_path_type(dentry))) { opaquedir = ovl_check_empty_and_clear(dentry); err = PTR_ERR(opaquedir); if (IS_ERR(opaquedir)) From dd662667e6d3e55b42798a6e6e7f37dddc639460 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Sat, 13 Dec 2014 00:59:43 +0100 Subject: [PATCH 04/22] ovl: add mutli-layer infrastructure Add multiple lower layers to 'struct ovl_fs' and 'struct ovl_entry'. ovl_entry will have an array of paths, instead of just the dentry. This allows a compact array containing just the layers which exist at current point in the tree (which is expected to be a small number for the majority of dentries). The number of layers is not limited by this infrastructure. Signed-off-by: Miklos Szeredi --- fs/overlayfs/super.c | 98 +++++++++++++++++++++++++++++--------------- 1 file changed, 64 insertions(+), 34 deletions(-) diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index 821719cc8537..460d866b97a2 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -35,7 +35,8 @@ struct ovl_config { /* private information held for overlayfs's superblock */ struct ovl_fs { struct vfsmount *upper_mnt; - struct vfsmount *lower_mnt; + unsigned numlower; + struct vfsmount **lower_mnt; struct dentry *workdir; long lower_namelen; /* pathnames of lower and upper dirs, for show_options */ @@ -47,7 +48,6 @@ struct ovl_dir_cache; /* private information held for every overlayfs dentry */ struct ovl_entry { struct dentry *__upperdentry; - struct dentry *lowerdentry; struct ovl_dir_cache *cache; union { struct { @@ -56,10 +56,16 @@ struct ovl_entry { }; struct rcu_head rcu; }; + unsigned numlower; + struct path lowerstack[]; }; const char *ovl_opaque_xattr = "trusted.overlay.opaque"; +static struct dentry *__ovl_dentry_lower(struct ovl_entry *oe) +{ + return oe->numlower ? oe->lowerstack[0].dentry : NULL; +} enum ovl_path_type ovl_path_type(struct dentry *dentry) { @@ -69,7 +75,7 @@ enum ovl_path_type ovl_path_type(struct dentry *dentry) if (oe->__upperdentry) { type = __OVL_PATH_UPPER; - if (oe->lowerdentry) { + if (oe->numlower) { if (S_ISDIR(dentry->d_inode->i_mode)) type |= __OVL_PATH_MERGE; } else if (!oe->opaque) { @@ -117,7 +123,7 @@ struct dentry *ovl_dentry_lower(struct dentry *dentry) { struct ovl_entry *oe = dentry->d_fsdata; - return oe->lowerdentry; + return __ovl_dentry_lower(oe); } struct dentry *ovl_dentry_real(struct dentry *dentry) @@ -127,7 +133,7 @@ struct dentry *ovl_dentry_real(struct dentry *dentry) realdentry = ovl_upperdentry_dereference(oe); if (!realdentry) - realdentry = oe->lowerdentry; + realdentry = __ovl_dentry_lower(oe); return realdentry; } @@ -140,7 +146,7 @@ struct dentry *ovl_entry_real(struct ovl_entry *oe, bool *is_upper) if (realdentry) { *is_upper = true; } else { - realdentry = oe->lowerdentry; + realdentry = __ovl_dentry_lower(oe); *is_upper = false; } return realdentry; @@ -162,11 +168,9 @@ void ovl_set_dir_cache(struct dentry *dentry, struct ovl_dir_cache *cache) void ovl_path_lower(struct dentry *dentry, struct path *path) { - struct ovl_fs *ofs = dentry->d_sb->s_fs_info; struct ovl_entry *oe = dentry->d_fsdata; - path->mnt = ofs->lower_mnt; - path->dentry = oe->lowerdentry; + *path = oe->numlower ? oe->lowerstack[0] : (struct path) { NULL, NULL }; } int ovl_want_write(struct dentry *dentry) @@ -258,8 +262,11 @@ static void ovl_dentry_release(struct dentry *dentry) struct ovl_entry *oe = dentry->d_fsdata; if (oe) { + unsigned int i; + dput(oe->__upperdentry); - dput(oe->lowerdentry); + for (i = 0; i < oe->numlower; i++) + dput(oe->lowerstack[i].dentry); kfree_rcu(oe, rcu); } } @@ -268,9 +275,15 @@ static const struct dentry_operations ovl_dentry_operations = { .d_release = ovl_dentry_release, }; -static struct ovl_entry *ovl_alloc_entry(void) +static struct ovl_entry *ovl_alloc_entry(unsigned int numlower) { - return kzalloc(sizeof(struct ovl_entry), GFP_KERNEL); + size_t size = offsetof(struct ovl_entry, lowerstack[numlower]); + struct ovl_entry *oe = kzalloc(size, GFP_KERNEL); + + if (oe) + oe->numlower = numlower; + + return oe; } static inline struct dentry *ovl_lookup_real(struct dentry *dir, @@ -297,19 +310,19 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, { struct ovl_entry *oe; struct dentry *upperdir; - struct dentry *lowerdir; + struct path lowerdir; struct dentry *upperdentry = NULL; struct dentry *lowerdentry = NULL; struct inode *inode = NULL; int err; err = -ENOMEM; - oe = ovl_alloc_entry(); + oe = ovl_alloc_entry(1); if (!oe) goto out; upperdir = ovl_dentry_upper(dentry->d_parent); - lowerdir = ovl_dentry_lower(dentry->d_parent); + ovl_path_lower(dentry->d_parent, &lowerdir); if (upperdir) { upperdentry = ovl_lookup_real(upperdir, &dentry->d_name); @@ -317,7 +330,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, if (IS_ERR(upperdentry)) goto out_put_dir; - if (lowerdir && upperdentry) { + if (lowerdir.dentry && upperdentry) { if (ovl_is_whiteout(upperdentry)) { dput(upperdentry); upperdentry = NULL; @@ -327,8 +340,8 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, } } } - if (lowerdir && !oe->opaque) { - lowerdentry = ovl_lookup_real(lowerdir, &dentry->d_name); + if (lowerdir.dentry && !oe->opaque) { + lowerdentry = ovl_lookup_real(lowerdir.dentry, &dentry->d_name); err = PTR_ERR(lowerdentry); if (IS_ERR(lowerdentry)) goto out_dput_upper; @@ -355,8 +368,12 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, } oe->__upperdentry = upperdentry; - oe->lowerdentry = lowerdentry; - + if (lowerdentry) { + oe->lowerstack[0].dentry = lowerdentry; + oe->lowerstack[0].mnt = lowerdir.mnt; + } else { + oe->numlower = 0; + } dentry->d_fsdata = oe; d_add(dentry, inode); @@ -380,10 +397,12 @@ struct file *ovl_path_open(struct path *path, int flags) static void ovl_put_super(struct super_block *sb) { struct ovl_fs *ufs = sb->s_fs_info; + unsigned i; dput(ufs->workdir); mntput(ufs->upper_mnt); - mntput(ufs->lower_mnt); + for (i = 0; i < ufs->numlower; i++) + mntput(ufs->lower_mnt[i]); kfree(ufs->config.lowerdir); kfree(ufs->config.upperdir); @@ -641,6 +660,8 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) struct ovl_entry *oe; struct ovl_fs *ufs; struct kstatfs statfs; + struct vfsmount *mnt; + unsigned int i; int err; err = -ENOMEM; @@ -661,7 +682,7 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) } err = -ENOMEM; - oe = ovl_alloc_entry(); + oe = ovl_alloc_entry(1); if (oe == NULL) goto out_free_config; @@ -727,12 +748,24 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) goto out_put_workpath; } - ufs->lower_mnt = clone_private_mount(&lowerpath); - err = PTR_ERR(ufs->lower_mnt); - if (IS_ERR(ufs->lower_mnt)) { - pr_err("overlayfs: failed to clone lowerpath\n"); + ufs->lower_mnt = kcalloc(1, sizeof(struct vfsmount *), GFP_KERNEL); + if (ufs->lower_mnt == NULL) goto out_put_upper_mnt; + + mnt = clone_private_mount(&lowerpath); + err = PTR_ERR(mnt); + if (IS_ERR(mnt)) { + pr_err("overlayfs: failed to clone lowerpath\n"); + goto out_put_lower_mnt; } + /* + * Make lower_mnt R/O. That way fchmod/fchown on lower file + * will fail instead of modifying lower fs. + */ + mnt->mnt_flags |= MNT_READONLY; + + ufs->lower_mnt[0] = mnt; + ufs->numlower = 1; ufs->workdir = ovl_workdir_create(ufs->upper_mnt, workpath.dentry); err = PTR_ERR(ufs->workdir); @@ -742,12 +775,6 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) goto out_put_lower_mnt; } - /* - * Make lower_mnt R/O. That way fchmod/fchown on lower file - * will fail instead of modifying lower fs. - */ - ufs->lower_mnt->mnt_flags |= MNT_READONLY; - /* If the upper fs is r/o, we mark overlayfs r/o too */ if (ufs->upper_mnt->mnt_sb->s_flags & MS_RDONLY) sb->s_flags |= MS_RDONLY; @@ -768,7 +795,8 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) path_put(&workpath); oe->__upperdentry = upperpath.dentry; - oe->lowerdentry = lowerpath.dentry; + oe->lowerstack[0].dentry = lowerpath.dentry; + oe->lowerstack[0].mnt = ufs->lower_mnt[0]; root_dentry->d_fsdata = oe; @@ -782,7 +810,9 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) out_put_workdir: dput(ufs->workdir); out_put_lower_mnt: - mntput(ufs->lower_mnt); + for (i = 0; i < ufs->numlower; i++) + mntput(ufs->lower_mnt[i]); + kfree(ufs->lower_mnt); out_put_upper_mnt: mntput(ufs->upper_mnt); out_put_workpath: From 5ef88da56a77bfb3b9631f5e5775f3bff86b6219 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Sat, 13 Dec 2014 00:59:43 +0100 Subject: [PATCH 05/22] ovl: helper to iterate layers Add helper to iterate through all the layers, starting from the upper layer (if exists) and continuing down through the lower layers. Signed-off-by: Miklos Szeredi --- fs/overlayfs/overlayfs.h | 1 + fs/overlayfs/super.c | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index d39eaa8f5e47..d176b679f526 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -135,6 +135,7 @@ void ovl_dentry_version_inc(struct dentry *dentry); void ovl_path_upper(struct dentry *dentry, struct path *path); void ovl_path_lower(struct dentry *dentry, struct path *path); enum ovl_path_type ovl_path_real(struct dentry *dentry, struct path *path); +int ovl_path_next(int idx, struct dentry *dentry, struct path *path); struct dentry *ovl_dentry_upper(struct dentry *dentry); struct dentry *ovl_dentry_lower(struct dentry *dentry); struct dentry *ovl_dentry_real(struct dentry *dentry); diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index 460d866b97a2..07e4c576e93e 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -305,6 +305,27 @@ static inline struct dentry *ovl_lookup_real(struct dentry *dir, return dentry; } +/* + * Returns next layer in stack starting from top. + * Returns -1 if this is the last layer. + */ +int ovl_path_next(int idx, struct dentry *dentry, struct path *path) +{ + struct ovl_entry *oe = dentry->d_fsdata; + + BUG_ON(idx < 0); + if (idx == 0) { + ovl_path_upper(dentry, path); + if (path->dentry) + return oe->numlower ? 1 : -1; + idx++; + } + BUG_ON(idx > oe->numlower); + *path = oe->lowerstack[idx - 1]; + + return (idx < oe->numlower) ? idx + 1 : -1; +} + struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags) { From 9d7459d834c28f55c82f1737f638a6c90e0c0e0f Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Sat, 13 Dec 2014 00:59:44 +0100 Subject: [PATCH 06/22] ovl: multi-layer readdir If multiple lower layers exist, merge them as well in readdir according to the same rules as merging upper with lower. I.e. take whiteouts and opaque directories into account on all but the lowers layer. Signed-off-by: Miklos Szeredi --- fs/overlayfs/readdir.c | 39 +++++++++++++++++++-------------------- fs/overlayfs/super.c | 3 +++ 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/fs/overlayfs/readdir.c b/fs/overlayfs/readdir.c index 481e44873b65..dfef6ca53dfe 100644 --- a/fs/overlayfs/readdir.c +++ b/fs/overlayfs/readdir.c @@ -261,35 +261,34 @@ static void ovl_dir_reset(struct file *file) static int ovl_dir_read_merged(struct dentry *dentry, struct list_head *list) { int err; - struct path lowerpath; - struct path upperpath; + struct path realpath; struct ovl_readdir_data rdd = { .ctx.actor = ovl_fill_merge, .list = list, .root = RB_ROOT, .is_merge = false, }; + int idx, next; - ovl_path_lower(dentry, &lowerpath); - ovl_path_upper(dentry, &upperpath); + for (idx = 0; idx != -1; idx = next) { + next = ovl_path_next(idx, dentry, &realpath); - if (upperpath.dentry) { - rdd.dir = upperpath.dentry; - err = ovl_dir_read(&upperpath, &rdd); - if (err) - goto out; + if (next != -1) { + rdd.dir = realpath.dentry; + err = ovl_dir_read(&realpath, &rdd); + if (err) + break; + } else { + /* + * Insert lowest layer entries before upper ones, this + * allows offsets to be reasonably constant + */ + list_add(&rdd.middle, rdd.list); + rdd.is_merge = true; + err = ovl_dir_read(&realpath, &rdd); + list_del(&rdd.middle); + } } - if (lowerpath.dentry) { - /* - * Insert lowerpath entries before upperpath ones, this allows - * offsets to be reasonably constant - */ - list_add(&rdd.middle, rdd.list); - rdd.is_merge = true; - err = ovl_dir_read(&lowerpath, &rdd); - list_del(&rdd.middle); - } -out: return err; } diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index 07e4c576e93e..c245043aa1b9 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -81,6 +81,9 @@ enum ovl_path_type ovl_path_type(struct dentry *dentry) } else if (!oe->opaque) { type |= __OVL_PATH_PURE; } + } else { + if (oe->numlower > 1) + type |= __OVL_PATH_MERGE; } return type; } From 3d3c6b89399a1b5e8a59ffbb8cb2a7797a9ef154 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Sat, 13 Dec 2014 00:59:44 +0100 Subject: [PATCH 07/22] ovl: multi-layer lookup Look up dentry in all relevant layers. Signed-off-by: Miklos Szeredi --- fs/overlayfs/super.c | 143 ++++++++++++++++++++++++++++--------------- 1 file changed, 94 insertions(+), 49 deletions(-) diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index c245043aa1b9..f72b82fdc1e6 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -333,82 +333,127 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags) { struct ovl_entry *oe; - struct dentry *upperdir; - struct path lowerdir; - struct dentry *upperdentry = NULL; - struct dentry *lowerdentry = NULL; + struct ovl_entry *poe = dentry->d_parent->d_fsdata; + struct path *stack = NULL; + struct dentry *upperdir, *upperdentry = NULL; + unsigned int ctr = 0; struct inode *inode = NULL; + bool upperopaque = false; + struct dentry *this, *prev = NULL; + unsigned int i; int err; - err = -ENOMEM; - oe = ovl_alloc_entry(1); - if (!oe) - goto out; - - upperdir = ovl_dentry_upper(dentry->d_parent); - ovl_path_lower(dentry->d_parent, &lowerdir); - + upperdir = ovl_upperdentry_dereference(poe); if (upperdir) { - upperdentry = ovl_lookup_real(upperdir, &dentry->d_name); - err = PTR_ERR(upperdentry); - if (IS_ERR(upperdentry)) - goto out_put_dir; + this = ovl_lookup_real(upperdir, &dentry->d_name); + err = PTR_ERR(this); + if (IS_ERR(this)) + goto out; - if (lowerdir.dentry && upperdentry) { - if (ovl_is_whiteout(upperdentry)) { - dput(upperdentry); - upperdentry = NULL; - oe->opaque = true; - } else if (ovl_is_opaquedir(upperdentry)) { - oe->opaque = true; + /* + * If this is not the lowermost layer, check whiteout and opaque + * directory. + */ + if (poe->numlower && this) { + if (ovl_is_whiteout(this)) { + dput(this); + this = NULL; + upperopaque = true; + } else if (ovl_is_opaquedir(this)) { + upperopaque = true; } } - } - if (lowerdir.dentry && !oe->opaque) { - lowerdentry = ovl_lookup_real(lowerdir.dentry, &dentry->d_name); - err = PTR_ERR(lowerdentry); - if (IS_ERR(lowerdentry)) - goto out_dput_upper; + upperdentry = prev = this; } - if (lowerdentry && upperdentry && - (!S_ISDIR(upperdentry->d_inode->i_mode) || - !S_ISDIR(lowerdentry->d_inode->i_mode))) { - dput(lowerdentry); - lowerdentry = NULL; - oe->opaque = true; + if (!upperopaque && poe->numlower) { + err = -ENOMEM; + stack = kcalloc(poe->numlower, sizeof(struct path), GFP_KERNEL); + if (!stack) + goto out_put_upper; } - if (lowerdentry || upperdentry) { + for (i = 0; !upperopaque && i < poe->numlower; i++) { + bool opaque = false; + struct path lowerpath = poe->lowerstack[i]; + + opaque = false; + this = ovl_lookup_real(lowerpath.dentry, &dentry->d_name); + err = PTR_ERR(this); + if (IS_ERR(this)) + goto out_put; + if (!this) + continue; + + /* + * If this is not the lowermost layer, check whiteout and opaque + * directory. + */ + if (i < poe->numlower - 1) { + if (ovl_is_whiteout(this)) { + dput(this); + break; + } else if (ovl_is_opaquedir(this)) { + opaque = true; + } + } + /* + * If this is a non-directory then stop here. + * + * FIXME: check for opaqueness maybe better done in remove code. + */ + if (!S_ISDIR(this->d_inode->i_mode)) { + opaque = true; + } else if (prev && (!S_ISDIR(prev->d_inode->i_mode) || + !S_ISDIR(this->d_inode->i_mode))) { + if (prev == upperdentry) + upperopaque = true; + dput(this); + break; + } + stack[ctr].dentry = this; + stack[ctr].mnt = lowerpath.mnt; + ctr++; + prev = this; + if (opaque) + break; + } + + oe = ovl_alloc_entry(ctr); + err = -ENOMEM; + if (!oe) + goto out_put; + + if (upperdentry || ctr) { struct dentry *realdentry; - realdentry = upperdentry ? upperdentry : lowerdentry; + realdentry = upperdentry ? upperdentry : stack[0].dentry; + err = -ENOMEM; inode = ovl_new_inode(dentry->d_sb, realdentry->d_inode->i_mode, oe); if (!inode) - goto out_dput; + goto out_free_oe; ovl_copyattr(realdentry->d_inode, inode); } + oe->opaque = upperopaque; oe->__upperdentry = upperdentry; - if (lowerdentry) { - oe->lowerstack[0].dentry = lowerdentry; - oe->lowerstack[0].mnt = lowerdir.mnt; - } else { - oe->numlower = 0; - } + memcpy(oe->lowerstack, stack, sizeof(struct path) * ctr); + kfree(stack); dentry->d_fsdata = oe; d_add(dentry, inode); return NULL; -out_dput: - dput(lowerdentry); -out_dput_upper: - dput(upperdentry); -out_put_dir: +out_free_oe: kfree(oe); +out_put: + for (i = 0; i < ctr; i++) + dput(stack[i].dentry); + kfree(stack); +out_put_upper: + dput(upperdentry); out: return ERR_PTR(err); } From 3e01cee3b980f96463cb6f378ab05303a99903d9 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Sat, 13 Dec 2014 00:59:45 +0100 Subject: [PATCH 08/22] ovl: check whiteout on lowest layer as well Not checking whiteouts on lowest layer was an optimization (there's nothing to white out there), but it could result in inconsitent behavior when a layer previously used as upper/middle is later used as lowest. Signed-off-by: Miklos Szeredi --- fs/overlayfs/readdir.c | 79 +++++++++++++++++++++--------------------- fs/overlayfs/super.c | 29 ++++++---------- 2 files changed, 51 insertions(+), 57 deletions(-) diff --git a/fs/overlayfs/readdir.c b/fs/overlayfs/readdir.c index dfef6ca53dfe..9df848f2e622 100644 --- a/fs/overlayfs/readdir.c +++ b/fs/overlayfs/readdir.c @@ -80,23 +80,50 @@ static struct ovl_cache_entry *ovl_cache_entry_find(struct rb_root *root, return NULL; } -static struct ovl_cache_entry *ovl_cache_entry_new(const char *name, int len, +static struct ovl_cache_entry *ovl_cache_entry_new(struct dentry *dir, + const char *name, int len, u64 ino, unsigned int d_type) { struct ovl_cache_entry *p; size_t size = offsetof(struct ovl_cache_entry, name[len + 1]); p = kmalloc(size, GFP_KERNEL); - if (p) { - memcpy(p->name, name, len); - p->name[len] = '\0'; - p->len = len; - p->type = d_type; - p->ino = ino; - p->is_whiteout = false; - p->is_cursor = false; - } + if (!p) + return NULL; + memcpy(p->name, name, len); + p->name[len] = '\0'; + p->len = len; + p->type = d_type; + p->ino = ino; + p->is_whiteout = false; + p->is_cursor = false; + + if (d_type == DT_CHR) { + struct dentry *dentry; + const struct cred *old_cred; + struct cred *override_cred; + + override_cred = prepare_creds(); + if (!override_cred) { + kfree(p); + return NULL; + } + + /* + * CAP_DAC_OVERRIDE for lookup + */ + cap_raise(override_cred->cap_effective, CAP_DAC_OVERRIDE); + old_cred = override_creds(override_cred); + + dentry = lookup_one_len(name, dir, len); + if (!IS_ERR(dentry)) { + p->is_whiteout = ovl_is_whiteout(dentry); + dput(dentry); + } + revert_creds(old_cred); + put_cred(override_cred); + } return p; } @@ -123,36 +150,10 @@ static int ovl_cache_entry_add_rb(struct ovl_readdir_data *rdd, return 0; } - p = ovl_cache_entry_new(name, len, ino, d_type); + p = ovl_cache_entry_new(rdd->dir, name, len, ino, d_type); if (p == NULL) return -ENOMEM; - if (d_type == DT_CHR) { - struct dentry *dentry; - const struct cred *old_cred; - struct cred *override_cred; - - override_cred = prepare_creds(); - if (!override_cred) { - kfree(p); - return -ENOMEM; - } - - /* - * CAP_DAC_OVERRIDE for lookup - */ - cap_raise(override_cred->cap_effective, CAP_DAC_OVERRIDE); - old_cred = override_creds(override_cred); - - dentry = lookup_one_len(name, rdd->dir, len); - if (!IS_ERR(dentry)) { - p->is_whiteout = ovl_is_whiteout(dentry); - dput(dentry); - } - revert_creds(old_cred); - put_cred(override_cred); - } - list_add_tail(&p->l_node, rdd->list); rb_link_node(&p->node, parent, newp); rb_insert_color(&p->node, &rdd->root); @@ -170,7 +171,7 @@ static int ovl_fill_lower(struct ovl_readdir_data *rdd, if (p) { list_move_tail(&p->l_node, &rdd->middle); } else { - p = ovl_cache_entry_new(name, namelen, ino, d_type); + p = ovl_cache_entry_new(rdd->dir, name, namelen, ino, d_type); if (p == NULL) rdd->err = -ENOMEM; else @@ -229,6 +230,7 @@ static inline int ovl_dir_read(struct path *realpath, if (IS_ERR(realfile)) return PTR_ERR(realfile); + rdd->dir = realpath->dentry; rdd->ctx.pos = 0; do { rdd->count = 0; @@ -274,7 +276,6 @@ static int ovl_dir_read_merged(struct dentry *dentry, struct list_head *list) next = ovl_path_next(idx, dentry, &realpath); if (next != -1) { - rdd.dir = realpath.dentry; err = ovl_dir_read(&realpath, &rdd); if (err) break; diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index f72b82fdc1e6..5dbc6789fd5f 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -350,16 +350,12 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, if (IS_ERR(this)) goto out; - /* - * If this is not the lowermost layer, check whiteout and opaque - * directory. - */ - if (poe->numlower && this) { + if (this) { if (ovl_is_whiteout(this)) { dput(this); this = NULL; upperopaque = true; - } else if (ovl_is_opaquedir(this)) { + } else if (poe->numlower && ovl_is_opaquedir(this)) { upperopaque = true; } } @@ -384,19 +380,16 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, goto out_put; if (!this) continue; - - /* - * If this is not the lowermost layer, check whiteout and opaque - * directory. - */ - if (i < poe->numlower - 1) { - if (ovl_is_whiteout(this)) { - dput(this); - break; - } else if (ovl_is_opaquedir(this)) { - opaque = true; - } + if (ovl_is_whiteout(this)) { + dput(this); + break; } + /* + * Only makes sense to check opaque dir if this is not the + * lowermost layer. + */ + if (i < poe->numlower - 1 && ovl_is_opaquedir(this)) + opaque = true; /* * If this is a non-directory then stop here. * From 09e10322b71716adf567d453889ef0871cf226b9 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Sat, 13 Dec 2014 00:59:45 +0100 Subject: [PATCH 09/22] ovl: lookup ENAMETOOLONG on lower means ENOENT "Suppose you have in one of the lower layers a filesystem with ->lookup()-enforced upper limit on name length. Pretty much every local fs has one, but... they are not all equal. 255 characters is the common upper limit, but e.g. jffs2 stops at 254, minixfs upper limit is somewhere from 14 to 60, depending upon version, etc. You are doing a lookup for something that is present in upper layer, but happens to be too long for one of the lower layers. Too bad - ENAMETOOLONG for you..." Reported-by: Al Viro Signed-off-by: Miklos Szeredi --- fs/overlayfs/super.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index 5dbc6789fd5f..110c968dcb3b 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -376,8 +376,14 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, opaque = false; this = ovl_lookup_real(lowerpath.dentry, &dentry->d_name); err = PTR_ERR(this); - if (IS_ERR(this)) + if (IS_ERR(this)) { + /* + * If it's positive, then treat ENAMETOOLONG as ENOENT. + */ + if (err == -ENAMETOOLONG && (upperdentry || ctr)) + continue; goto out_put; + } if (!this) continue; if (ovl_is_whiteout(this)) { From 4ebc581828d5d0fe189ca06cef8b7a63cb4583d5 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Sat, 13 Dec 2014 00:59:46 +0100 Subject: [PATCH 10/22] ovl: allow statfs if no upper layer Handle "no upper layer" case in statfs. Signed-off-by: Miklos Szeredi --- fs/overlayfs/super.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index 110c968dcb3b..cc7a0f3aa0dd 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -484,7 +484,7 @@ static void ovl_put_super(struct super_block *sb) * @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 same filesystem. + * filesystem pass the statfs to the upper filesystem (if it exists) */ static int ovl_statfs(struct dentry *dentry, struct kstatfs *buf) { @@ -493,7 +493,7 @@ static int ovl_statfs(struct dentry *dentry, struct kstatfs *buf) struct path path; int err; - ovl_path_upper(root_dentry, &path); + ovl_path_real(root_dentry, &path); err = vfs_statfs(&path, buf); if (!err) { From 3b7a9a249a93e68b7bb318de40e64d3b68ba1a6d Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Sat, 13 Dec 2014 00:59:48 +0100 Subject: [PATCH 11/22] ovl: mount: change order of initialization Move allocation of root entry above to where it's needed. Move initializations related to upperdir and workdir near each other. Signed-off-by: Miklos Szeredi --- fs/overlayfs/super.c | 74 ++++++++++++++++++++------------------------ 1 file changed, 34 insertions(+), 40 deletions(-) diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index cc7a0f3aa0dd..a17702833dd0 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -723,7 +723,6 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) struct path lowerpath; struct path upperpath; struct path workpath; - struct inode *root_inode; struct dentry *root_dentry; struct ovl_entry *oe; struct ovl_fs *ufs; @@ -749,54 +748,49 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) goto out_free_config; } - err = -ENOMEM; - oe = ovl_alloc_entry(1); - if (oe == NULL) - goto out_free_config; - err = ovl_mount_dir(ufs->config.upperdir, &upperpath); if (err) - goto out_free_oe; - - err = ovl_mount_dir(ufs->config.lowerdir, &lowerpath); - if (err) - goto out_put_upperpath; + goto out_free_config; err = ovl_mount_dir(ufs->config.workdir, &workpath); if (err) - goto out_put_lowerpath; + goto out_put_upperpath; + + err = ovl_mount_dir(ufs->config.lowerdir, &lowerpath); + if (err) + goto out_put_workpath; err = -EINVAL; if (!S_ISDIR(upperpath.dentry->d_inode->i_mode) || !S_ISDIR(lowerpath.dentry->d_inode->i_mode) || !S_ISDIR(workpath.dentry->d_inode->i_mode)) { pr_err("overlayfs: upperdir or lowerdir or workdir not a directory\n"); - goto out_put_workpath; + goto out_put_lowerpath; } if (upperpath.mnt != workpath.mnt) { pr_err("overlayfs: workdir and upperdir must reside under the same mount\n"); - goto out_put_workpath; + goto out_put_lowerpath; } if (!ovl_workdir_ok(workpath.dentry, upperpath.dentry)) { pr_err("overlayfs: workdir and upperdir must be separate subtrees\n"); - goto out_put_workpath; + goto out_put_lowerpath; } if (!ovl_is_allowed_fs_type(upperpath.dentry)) { pr_err("overlayfs: filesystem of upperdir is not supported\n"); - goto out_put_workpath; + goto out_put_lowerpath; } if (!ovl_is_allowed_fs_type(lowerpath.dentry)) { pr_err("overlayfs: filesystem of lowerdir is not supported\n"); - goto out_put_workpath; + goto out_put_lowerpath; } err = vfs_statfs(&lowerpath, &statfs); if (err) { pr_err("overlayfs: statfs failed on lowerpath\n"); - goto out_put_workpath; + goto out_put_lowerpath; } ufs->lower_namelen = statfs.f_namelen; @@ -806,19 +800,27 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) err = -EINVAL; if (sb->s_stack_depth > FILESYSTEM_MAX_STACK_DEPTH) { pr_err("overlayfs: maximum fs stacking depth exceeded\n"); - goto out_put_workpath; + goto out_put_lowerpath; } ufs->upper_mnt = clone_private_mount(&upperpath); err = PTR_ERR(ufs->upper_mnt); if (IS_ERR(ufs->upper_mnt)) { pr_err("overlayfs: failed to clone upperpath\n"); - goto out_put_workpath; + goto out_put_lowerpath; + } + + ufs->workdir = ovl_workdir_create(ufs->upper_mnt, workpath.dentry); + err = PTR_ERR(ufs->workdir); + if (IS_ERR(ufs->workdir)) { + pr_err("overlayfs: failed to create directory %s/%s\n", + ufs->config.workdir, OVL_WORKDIR_NAME); + goto out_put_upper_mnt; } ufs->lower_mnt = kcalloc(1, sizeof(struct vfsmount *), GFP_KERNEL); if (ufs->lower_mnt == NULL) - goto out_put_upper_mnt; + goto out_put_workdir; mnt = clone_private_mount(&lowerpath); err = PTR_ERR(mnt); @@ -835,14 +837,6 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) ufs->lower_mnt[0] = mnt; ufs->numlower = 1; - ufs->workdir = ovl_workdir_create(ufs->upper_mnt, workpath.dentry); - err = PTR_ERR(ufs->workdir); - if (IS_ERR(ufs->workdir)) { - pr_err("overlayfs: failed to create directory %s/%s\n", - ufs->config.workdir, OVL_WORKDIR_NAME); - goto out_put_lower_mnt; - } - /* If the upper fs is r/o, we mark overlayfs r/o too */ if (ufs->upper_mnt->mnt_sb->s_flags & MS_RDONLY) sb->s_flags |= MS_RDONLY; @@ -850,13 +844,13 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) sb->s_d_op = &ovl_dentry_operations; err = -ENOMEM; - root_inode = ovl_new_inode(sb, S_IFDIR, oe); - if (!root_inode) - goto out_put_workdir; + oe = ovl_alloc_entry(1); + if (!oe) + goto out_put_lower_mnt; - root_dentry = d_make_root(root_inode); + root_dentry = d_make_root(ovl_new_inode(sb, S_IFDIR, oe)); if (!root_dentry) - goto out_put_workdir; + goto out_free_oe; mntput(upperpath.mnt); mntput(lowerpath.mnt); @@ -875,22 +869,22 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) return 0; -out_put_workdir: - dput(ufs->workdir); +out_free_oe: + kfree(oe); out_put_lower_mnt: for (i = 0; i < ufs->numlower; i++) mntput(ufs->lower_mnt[i]); kfree(ufs->lower_mnt); +out_put_workdir: + dput(ufs->workdir); out_put_upper_mnt: mntput(ufs->upper_mnt); -out_put_workpath: - path_put(&workpath); out_put_lowerpath: path_put(&lowerpath); +out_put_workpath: + path_put(&workpath); out_put_upperpath: path_put(&upperpath); -out_free_oe: - kfree(oe); out_free_config: kfree(ufs->config.lowerdir); kfree(ufs->config.upperdir); From ab508822cab4c84f07373cd6ad107a1fd1362831 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Sat, 13 Dec 2014 00:59:49 +0100 Subject: [PATCH 12/22] ovl: improve mount helpers Move common checks into ovl_mount_dir() helper. Create helper for looking up lower directories. Signed-off-by: Miklos Szeredi --- fs/overlayfs/super.c | 135 +++++++++++++++++++++++++------------------ 1 file changed, 78 insertions(+), 57 deletions(-) diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index a17702833dd0..592370ff453a 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -669,24 +669,6 @@ static void ovl_unescape(char *s) } } -static int ovl_mount_dir(const char *name, struct path *path) -{ - int err; - char *tmp = kstrdup(name, GFP_KERNEL); - - if (!tmp) - return -ENOMEM; - - ovl_unescape(tmp); - err = kern_path(tmp, LOOKUP_FOLLOW, path); - if (err) { - pr_err("overlayfs: failed to resolve '%s': %i\n", tmp, err); - err = -EINVAL; - } - kfree(tmp); - return err; -} - static bool ovl_is_allowed_fs_type(struct dentry *root) { const struct dentry_operations *dop = root->d_op; @@ -706,6 +688,71 @@ static bool ovl_is_allowed_fs_type(struct dentry *root) return true; } +static int ovl_mount_dir_noesc(const char *name, struct path *path) +{ + int err; + + 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_is_allowed_fs_type(path->dentry)) { + pr_err("overlayfs: filesystem on '%s' not supported\n", name); + goto out_put; + } + if (!S_ISDIR(path->dentry->d_inode->i_mode)) { + pr_err("overlayfs: '%s' not a directory\n", name); + goto out_put; + } + return 0; + +out_put: + path_put(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); + kfree(tmp); + } + return err; +} + +static int ovl_lower_dir(const char *name, struct path *path, long *namelen, + int *stack_depth) +{ + int err; + struct kstatfs statfs; + + err = ovl_mount_dir(name, path); + if (err) + goto out; + + err = vfs_statfs(path, &statfs); + if (err) { + pr_err("overlayfs: statfs failed on '%s'\n", name); + goto out_put; + } + *namelen = max(*namelen, statfs.f_namelen); + *stack_depth = max(*stack_depth, path->mnt->mnt_sb->s_stack_depth); + + return 0; + +out_put: + path_put(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) { @@ -726,7 +773,6 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) struct dentry *root_dentry; struct ovl_entry *oe; struct ovl_fs *ufs; - struct kstatfs statfs; struct vfsmount *mnt; unsigned int i; int err; @@ -756,48 +802,23 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) if (err) goto out_put_upperpath; - err = ovl_mount_dir(ufs->config.lowerdir, &lowerpath); + if (upperpath.mnt != workpath.mnt) { + pr_err("overlayfs: workdir and upperdir must reside under the same mount\n"); + goto out_put_workpath; + } + if (!ovl_workdir_ok(workpath.dentry, upperpath.dentry)) { + pr_err("overlayfs: workdir and upperdir must be separate subtrees\n"); + goto out_put_workpath; + } + sb->s_stack_depth = upperpath.mnt->mnt_sb->s_stack_depth; + + err = ovl_lower_dir(ufs->config.lowerdir, &lowerpath, + &ufs->lower_namelen, &sb->s_stack_depth); if (err) goto out_put_workpath; err = -EINVAL; - if (!S_ISDIR(upperpath.dentry->d_inode->i_mode) || - !S_ISDIR(lowerpath.dentry->d_inode->i_mode) || - !S_ISDIR(workpath.dentry->d_inode->i_mode)) { - pr_err("overlayfs: upperdir or lowerdir or workdir not a directory\n"); - goto out_put_lowerpath; - } - - if (upperpath.mnt != workpath.mnt) { - pr_err("overlayfs: workdir and upperdir must reside under the same mount\n"); - goto out_put_lowerpath; - } - if (!ovl_workdir_ok(workpath.dentry, upperpath.dentry)) { - pr_err("overlayfs: workdir and upperdir must be separate subtrees\n"); - goto out_put_lowerpath; - } - - if (!ovl_is_allowed_fs_type(upperpath.dentry)) { - pr_err("overlayfs: filesystem of upperdir is not supported\n"); - goto out_put_lowerpath; - } - - if (!ovl_is_allowed_fs_type(lowerpath.dentry)) { - pr_err("overlayfs: filesystem of lowerdir is not supported\n"); - goto out_put_lowerpath; - } - - err = vfs_statfs(&lowerpath, &statfs); - if (err) { - pr_err("overlayfs: statfs failed on lowerpath\n"); - goto out_put_lowerpath; - } - ufs->lower_namelen = statfs.f_namelen; - - sb->s_stack_depth = max(upperpath.mnt->mnt_sb->s_stack_depth, - lowerpath.mnt->mnt_sb->s_stack_depth) + 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_put_lowerpath; From 53a08cb9b8bccfe58f1228c7c27baf34a83da78b Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Sat, 13 Dec 2014 00:59:51 +0100 Subject: [PATCH 13/22] ovl: make upperdir optional Make "upperdir=" mount option optional. If "upperdir=" is not given, then the "workdir=" option is also optional (and ignored if given). Signed-off-by: Miklos Szeredi --- fs/overlayfs/super.c | 83 +++++++++++++++++++++++++------------------- 1 file changed, 47 insertions(+), 36 deletions(-) diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index 592370ff453a..35bb0adf10cf 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -516,8 +516,10 @@ static int ovl_show_options(struct seq_file *m, struct dentry *dentry) struct ovl_fs *ufs = sb->s_fs_info; seq_printf(m, ",lowerdir=%s", ufs->config.lowerdir); - seq_printf(m, ",upperdir=%s", ufs->config.upperdir); - seq_printf(m, ",workdir=%s", ufs->config.workdir); + if (ufs->config.upperdir) { + seq_printf(m, ",upperdir=%s", ufs->config.upperdir); + seq_printf(m, ",workdir=%s", ufs->config.workdir); + } return 0; } @@ -768,8 +770,8 @@ static bool ovl_workdir_ok(struct dentry *workdir, struct dentry *upperdir) static int ovl_fill_super(struct super_block *sb, void *data, int silent) { struct path lowerpath; - struct path upperpath; - struct path workpath; + struct path upperpath = { NULL, NULL }; + struct path workpath = { NULL, NULL }; struct dentry *root_dentry; struct ovl_entry *oe; struct ovl_fs *ufs; @@ -786,31 +788,38 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) if (err) goto out_free_config; - /* FIXME: workdir is not needed for a R/O mount */ err = -EINVAL; - if (!ufs->config.upperdir || !ufs->config.lowerdir || - !ufs->config.workdir) { - pr_err("overlayfs: missing upperdir or lowerdir or workdir\n"); + if (!ufs->config.lowerdir) { + pr_err("overlayfs: missing 'lowerdir'\n"); goto out_free_config; } - err = ovl_mount_dir(ufs->config.upperdir, &upperpath); - if (err) - goto out_free_config; + sb->s_stack_depth = 0; + if (ufs->config.upperdir) { + /* FIXME: workdir is not needed for a R/O mount */ + if (!ufs->config.workdir) { + pr_err("overlayfs: missing 'workdir'\n"); + goto out_free_config; + } - err = ovl_mount_dir(ufs->config.workdir, &workpath); - if (err) - goto out_put_upperpath; + err = ovl_mount_dir(ufs->config.upperdir, &upperpath); + if (err) + goto out_free_config; - if (upperpath.mnt != workpath.mnt) { - pr_err("overlayfs: workdir and upperdir must reside under the same mount\n"); - goto out_put_workpath; + err = ovl_mount_dir(ufs->config.workdir, &workpath); + if (err) + goto out_put_upperpath; + + if (upperpath.mnt != workpath.mnt) { + pr_err("overlayfs: workdir and upperdir must reside under the same mount\n"); + goto out_put_workpath; + } + if (!ovl_workdir_ok(workpath.dentry, upperpath.dentry)) { + pr_err("overlayfs: workdir and upperdir must be separate subtrees\n"); + goto out_put_workpath; + } + sb->s_stack_depth = upperpath.mnt->mnt_sb->s_stack_depth; } - if (!ovl_workdir_ok(workpath.dentry, upperpath.dentry)) { - pr_err("overlayfs: workdir and upperdir must be separate subtrees\n"); - goto out_put_workpath; - } - sb->s_stack_depth = upperpath.mnt->mnt_sb->s_stack_depth; err = ovl_lower_dir(ufs->config.lowerdir, &lowerpath, &ufs->lower_namelen, &sb->s_stack_depth); @@ -824,19 +833,21 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) goto out_put_lowerpath; } - ufs->upper_mnt = clone_private_mount(&upperpath); - err = PTR_ERR(ufs->upper_mnt); - if (IS_ERR(ufs->upper_mnt)) { - pr_err("overlayfs: failed to clone upperpath\n"); - goto out_put_lowerpath; - } + if (ufs->config.upperdir) { + ufs->upper_mnt = clone_private_mount(&upperpath); + err = PTR_ERR(ufs->upper_mnt); + if (IS_ERR(ufs->upper_mnt)) { + pr_err("overlayfs: failed to clone upperpath\n"); + goto out_put_lowerpath; + } - ufs->workdir = ovl_workdir_create(ufs->upper_mnt, workpath.dentry); - err = PTR_ERR(ufs->workdir); - if (IS_ERR(ufs->workdir)) { - pr_err("overlayfs: failed to create directory %s/%s\n", - ufs->config.workdir, OVL_WORKDIR_NAME); - goto out_put_upper_mnt; + ufs->workdir = ovl_workdir_create(ufs->upper_mnt, workpath.dentry); + err = PTR_ERR(ufs->workdir); + if (IS_ERR(ufs->workdir)) { + pr_err("overlayfs: failed to create directory %s/%s\n", + ufs->config.workdir, OVL_WORKDIR_NAME); + goto out_put_upper_mnt; + } } ufs->lower_mnt = kcalloc(1, sizeof(struct vfsmount *), GFP_KERNEL); @@ -858,8 +869,8 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) ufs->lower_mnt[0] = mnt; ufs->numlower = 1; - /* If the upper fs is r/o, we mark overlayfs r/o too */ - if (ufs->upper_mnt->mnt_sb->s_flags & MS_RDONLY) + /* If the upper fs is r/o or nonexistent, we mark overlayfs r/o too */ + if (!ufs->upper_mnt || (ufs->upper_mnt->mnt_sb->s_flags & MS_RDONLY)) sb->s_flags |= MS_RDONLY; sb->s_d_op = &ovl_dentry_operations; From a78d9f0d5d5ca9054703376c7c23c901807ddd87 Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Sat, 13 Dec 2014 00:59:52 +0100 Subject: [PATCH 14/22] ovl: support multiple lower layers Allow "lowerdir=" option to contain multiple lower directories separated by a colon (e.g. "lowerdir=/bin:/usr/bin"). Colon characters in filenames can be escaped with a backslash. Signed-off-by: Miklos Szeredi --- Documentation/filesystems/overlayfs.txt | 12 +++ fs/overlayfs/super.c | 110 ++++++++++++++++++------ 2 files changed, 95 insertions(+), 27 deletions(-) diff --git a/Documentation/filesystems/overlayfs.txt b/Documentation/filesystems/overlayfs.txt index a27c950ece61..b37092886dcc 100644 --- a/Documentation/filesystems/overlayfs.txt +++ b/Documentation/filesystems/overlayfs.txt @@ -159,6 +159,18 @@ overlay filesystem (though an operation on the name of the file such as rename or unlink will of course be noticed and handled). +Multiple lower layers +--------------------- + +Multiple lower layers can now be given using the the colon (":") as a +separator character between the directory names. For example: + + mount -t overlay overlay -olowerdir=/lower1:/lower2:/lower3 /merged + +As the example shows, "upperdir=" and "workdir=" may be omitted. In that case +the overlay will be read-only. + + Non-standard behavior --------------------- diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index 35bb0adf10cf..5c495a17a5a3 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -60,6 +60,8 @@ struct ovl_entry { struct path lowerstack[]; }; +#define OVL_MAX_STACK 500 + const char *ovl_opaque_xattr = "trusted.overlay.opaque"; static struct dentry *__ovl_dentry_lower(struct ovl_entry *oe) @@ -692,8 +694,12 @@ static bool ovl_is_allowed_fs_type(struct dentry *root) static int ovl_mount_dir_noesc(const char *name, struct path *path) { - int err; + 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); @@ -735,7 +741,7 @@ static int ovl_lower_dir(const char *name, struct path *path, long *namelen, int err; struct kstatfs statfs; - err = ovl_mount_dir(name, path); + err = ovl_mount_dir_noesc(name, path); if (err) goto out; @@ -767,15 +773,38 @@ static bool ovl_workdir_ok(struct dentry *workdir, struct dentry *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 ovl_fill_super(struct super_block *sb, void *data, int silent) { - struct path lowerpath; struct path upperpath = { NULL, NULL }; struct path workpath = { NULL, NULL }; struct dentry *root_dentry; struct ovl_entry *oe; struct ovl_fs *ufs; - struct vfsmount *mnt; + struct path *stack = NULL; + char *lowertmp; + char *lower; + unsigned int numlower; + unsigned int stacklen = 0; unsigned int i; int err; @@ -820,12 +849,30 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) } sb->s_stack_depth = upperpath.mnt->mnt_sb->s_stack_depth; } - - err = ovl_lower_dir(ufs->config.lowerdir, &lowerpath, - &ufs->lower_namelen, &sb->s_stack_depth); - if (err) + err = -ENOMEM; + lowertmp = kstrdup(ufs->config.lowerdir, GFP_KERNEL); + if (!lowertmp) goto out_put_workpath; + err = -EINVAL; + stacklen = ovl_split_lowerdirs(lowertmp); + if (stacklen > OVL_MAX_STACK) + goto out_free_lowertmp; + + stack = kcalloc(stacklen, sizeof(struct path), GFP_KERNEL); + if (!stack) + goto out_free_lowertmp; + + lower = lowertmp; + for (numlower = 0; numlower < stacklen; numlower++) { + err = ovl_lower_dir(lower, &stack[numlower], + &ufs->lower_namelen, &sb->s_stack_depth); + if (err) + goto out_put_lowerpath; + + lower = strchr(lower, '\0') + 1; + } + err = -EINVAL; sb->s_stack_depth++; if (sb->s_stack_depth > FILESYSTEM_MAX_STACK_DEPTH) { @@ -850,24 +897,25 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) } } - ufs->lower_mnt = kcalloc(1, sizeof(struct vfsmount *), GFP_KERNEL); + ufs->lower_mnt = kcalloc(numlower, sizeof(struct vfsmount *), GFP_KERNEL); if (ufs->lower_mnt == NULL) goto out_put_workdir; + for (i = 0; i < numlower; i++) { + struct vfsmount *mnt = clone_private_mount(&stack[i]); - mnt = clone_private_mount(&lowerpath); - err = PTR_ERR(mnt); - if (IS_ERR(mnt)) { - pr_err("overlayfs: failed to clone lowerpath\n"); - goto out_put_lower_mnt; + if (IS_ERR(mnt)) { + pr_err("overlayfs: failed to clone lowerpath\n"); + goto out_put_lower_mnt; + } + /* + * Make lower_mnt R/O. That way fchmod/fchown on lower file + * will fail instead of modifying lower fs. + */ + mnt->mnt_flags |= MNT_READONLY; + + ufs->lower_mnt[ufs->numlower] = mnt; + ufs->numlower++; } - /* - * Make lower_mnt R/O. That way fchmod/fchown on lower file - * will fail instead of modifying lower fs. - */ - mnt->mnt_flags |= MNT_READONLY; - - ufs->lower_mnt[0] = mnt; - ufs->numlower = 1; /* If the upper fs is r/o or nonexistent, we mark overlayfs r/o too */ if (!ufs->upper_mnt || (ufs->upper_mnt->mnt_sb->s_flags & MS_RDONLY)) @@ -876,7 +924,7 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) sb->s_d_op = &ovl_dentry_operations; err = -ENOMEM; - oe = ovl_alloc_entry(1); + oe = ovl_alloc_entry(numlower); if (!oe) goto out_put_lower_mnt; @@ -885,12 +933,16 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) goto out_free_oe; mntput(upperpath.mnt); - mntput(lowerpath.mnt); + for (i = 0; i < numlower; i++) + mntput(stack[i].mnt); path_put(&workpath); + kfree(lowertmp); oe->__upperdentry = upperpath.dentry; - oe->lowerstack[0].dentry = lowerpath.dentry; - oe->lowerstack[0].mnt = ufs->lower_mnt[0]; + for (i = 0; i < numlower; i++) { + oe->lowerstack[i].dentry = stack[i].dentry; + oe->lowerstack[i].mnt = ufs->lower_mnt[i]; + } root_dentry->d_fsdata = oe; @@ -912,7 +964,11 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) out_put_upper_mnt: mntput(ufs->upper_mnt); out_put_lowerpath: - path_put(&lowerpath); + for (i = 0; i < numlower; i++) + path_put(&stack[i]); + kfree(stack); +out_free_lowertmp: + kfree(lowertmp); out_put_workpath: path_put(&workpath); out_put_upperpath: From 1ba38725a351f91769918b132c17fb7fcaf6c2f5 Mon Sep 17 00:00:00 2001 From: hujianyang Date: Wed, 26 Nov 2014 16:16:59 +0800 Subject: [PATCH 15/22] ovl: Cleanup redundant blank lines This patch removes redundant blanks lines in overlayfs. Signed-off-by: hujianyang Signed-off-by: Miklos Szeredi --- fs/overlayfs/copy_up.c | 1 - fs/overlayfs/inode.c | 1 - fs/overlayfs/super.c | 1 - 3 files changed, 3 deletions(-) diff --git a/fs/overlayfs/copy_up.c b/fs/overlayfs/copy_up.c index a5bfd60f4f6f..24f640441bd9 100644 --- a/fs/overlayfs/copy_up.c +++ b/fs/overlayfs/copy_up.c @@ -191,7 +191,6 @@ int ovl_set_attr(struct dentry *upperdentry, struct kstat *stat) ovl_set_timestamps(upperdentry, stat); return err; - } static int ovl_copy_up_locked(struct dentry *workdir, struct dentry *upperdir, diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index 48492f1240ad..5ac1236afb15 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -433,5 +433,4 @@ struct inode *ovl_new_inode(struct super_block *sb, umode_t mode, } return inode; - } diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index 5c495a17a5a3..e9ce4a9e8749 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -106,7 +106,6 @@ void ovl_path_upper(struct dentry *dentry, struct path *path) enum ovl_path_type ovl_path_real(struct dentry *dentry, struct path *path) { - enum ovl_path_type type = ovl_path_type(dentry); if (!OVL_TYPE_UPPER(type)) From cead89bb08c0f64e23886f1c18df9bb98e97c55c Mon Sep 17 00:00:00 2001 From: hujianyang Date: Mon, 24 Nov 2014 18:25:21 +0800 Subject: [PATCH 16/22] ovl: Use macros to present ovl_xattr This patch adds two macros: OVL_XATTR_PRE_NAME and OVL_XATTR_PRE_LEN to present ovl_xattr name prefix and its length. Also, a new macro OVL_XATTR_OPAQUE is introduced to replace old *ovl_opaque_xattr*. Fix the length of "trusted.overlay." to *16*. Signed-off-by: hujianyang Signed-off-by: Miklos Szeredi --- fs/overlayfs/dir.c | 4 ++-- fs/overlayfs/inode.c | 2 +- fs/overlayfs/overlayfs.h | 4 +++- fs/overlayfs/super.c | 4 +--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index dcae3ac5aa76..0dc4c33a0a1b 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -118,14 +118,14 @@ int ovl_create_real(struct inode *dir, struct dentry *newdentry, static int ovl_set_opaque(struct dentry *upperdentry) { - return ovl_do_setxattr(upperdentry, ovl_opaque_xattr, "y", 1, 0); + return ovl_do_setxattr(upperdentry, OVL_XATTR_OPAQUE, "y", 1, 0); } static void ovl_remove_opaque(struct dentry *upperdentry) { int err; - err = ovl_do_removexattr(upperdentry, ovl_opaque_xattr); + err = ovl_do_removexattr(upperdentry, OVL_XATTR_OPAQUE); if (err) { pr_warn("overlayfs: failed to remove opaque from '%s' (%i)\n", upperdentry->d_name.name, err); diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index 5ac1236afb15..04f124884687 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -205,7 +205,7 @@ static int ovl_readlink(struct dentry *dentry, char __user *buf, int bufsiz) static bool ovl_is_private_xattr(const char *name) { - return strncmp(name, "trusted.overlay.", 14) == 0; + return strncmp(name, OVL_XATTR_PRE_NAME, OVL_XATTR_PRE_LEN) == 0; } int ovl_setxattr(struct dentry *dentry, const char *name, diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index d176b679f526..17ac5afc9ffb 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -23,7 +23,9 @@ enum ovl_path_type { #define OVL_TYPE_MERGE_OR_LOWER(type) \ (OVL_TYPE_MERGE(type) || !OVL_TYPE_UPPER(type)) -extern const char *ovl_opaque_xattr; +#define OVL_XATTR_PRE_NAME "trusted.overlay." +#define OVL_XATTR_PRE_LEN 16 +#define OVL_XATTR_OPAQUE OVL_XATTR_PRE_NAME"opaque" static inline int ovl_do_rmdir(struct inode *dir, struct dentry *dentry) { diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index e9ce4a9e8749..84f3144e1b33 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -62,8 +62,6 @@ struct ovl_entry { #define OVL_MAX_STACK 500 -const char *ovl_opaque_xattr = "trusted.overlay.opaque"; - static struct dentry *__ovl_dentry_lower(struct ovl_entry *oe) { return oe->numlower ? oe->lowerstack[0].dentry : NULL; @@ -254,7 +252,7 @@ static bool ovl_is_opaquedir(struct dentry *dentry) if (!S_ISDIR(inode->i_mode) || !inode->i_op->getxattr) return false; - res = inode->i_op->getxattr(dentry, ovl_opaque_xattr, &val, 1); + res = inode->i_op->getxattr(dentry, OVL_XATTR_OPAQUE, &val, 1); if (res == 1 && val == 'y') return true; From 2b7a8f36f092a7855f6438cd42d6990394f450fa Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Sat, 13 Dec 2014 00:59:53 +0100 Subject: [PATCH 17/22] ovl: add testsuite to docs Reported-by: Sedat Dilek Signed-off-by: Miklos Szeredi --- Documentation/filesystems/overlayfs.txt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Documentation/filesystems/overlayfs.txt b/Documentation/filesystems/overlayfs.txt index b37092886dcc..006ea480506d 100644 --- a/Documentation/filesystems/overlayfs.txt +++ b/Documentation/filesystems/overlayfs.txt @@ -208,3 +208,15 @@ Changes to the underlying filesystems while part of a mounted overlay filesystem are not allowed. If the underlying filesystem is changed, the behavior of the overlay is undefined, though it will not result in a crash or deadlock. + +Testsuite +--------- + +There's testsuite developed by David Howells at: + + git://git.infradead.org/users/dhowells/unionmount-testsuite.git + +Run as root: + + # cd unionmount-testsuite + # ./run --ov From 2f83fd8c2849a388082f30d755a75c1e67c4643b Mon Sep 17 00:00:00 2001 From: hujianyang Date: Tue, 6 Jan 2015 12:52:13 +0800 Subject: [PATCH 18/22] ovl: Fix kernel panic while mounting overlayfs The function ovl_fill_super() in recently multi-layer support version will incorrectly return 0 at error handling path and then cause kernel panic. This failure can be reproduced by mounting a overlayfs with upperdir and workdir in different mounts. And also, If the memory allocation of *lower_mnt* fail, this function may return an zero either. This patch fix this problem by setting *err* to proper error number before jumping to error handling path. Signed-off-by: hujianyang Signed-off-by: Miklos Szeredi --- fs/overlayfs/super.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index 84f3144e1b33..6ca8ea8cb9b2 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -836,6 +836,7 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) if (err) goto out_put_upperpath; + err = -EINVAL; if (upperpath.mnt != workpath.mnt) { pr_err("overlayfs: workdir and upperdir must reside under the same mount\n"); goto out_put_workpath; @@ -894,12 +895,14 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) } } + err = -ENOMEM; ufs->lower_mnt = kcalloc(numlower, sizeof(struct vfsmount *), GFP_KERNEL); if (ufs->lower_mnt == NULL) goto out_put_workdir; for (i = 0; i < numlower; i++) { struct vfsmount *mnt = clone_private_mount(&stack[i]); + err = PTR_ERR(mnt); if (IS_ERR(mnt)) { pr_err("overlayfs: failed to clone lowerpath\n"); goto out_put_lower_mnt; From a425c037f3dd8a56469158ab5f37beb46402d958 Mon Sep 17 00:00:00 2001 From: hujianyang Date: Tue, 6 Jan 2015 16:10:01 +0800 Subject: [PATCH 19/22] ovl: Fix opaque regression in ovl_lookup Current multi-layer support overlayfs has a regression in .lookup(). If there is a directory in upperdir and a regular file has same name in lowerdir in a merged directory, lower file is hidden and upper directory is set to opaque in former case. But it is changed in present code. In lowerdir lookup path, if a found inode is not directory, the type checking of previous inode is missing. This inode will be copied to the lowerstack of ovl_entry directly. That will lead to several wrong conditions, for example, the reading of the directory in upperdir may return an error like: ls: reading directory .: Not a directory This patch makes the lowerdir lookup path check the opaque for non-directory file too. Signed-off-by: hujianyang Signed-off-by: Miklos Szeredi --- fs/overlayfs/super.c | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index 6ca8ea8cb9b2..9e94f4acef1a 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -372,7 +372,6 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, bool opaque = false; struct path lowerpath = poe->lowerstack[i]; - opaque = false; this = ovl_lookup_real(lowerpath.dentry, &dentry->d_name); err = PTR_ERR(this); if (IS_ERR(this)) { @@ -395,20 +394,24 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, */ if (i < poe->numlower - 1 && ovl_is_opaquedir(this)) opaque = true; - /* - * If this is a non-directory then stop here. - * - * FIXME: check for opaqueness maybe better done in remove code. - */ - if (!S_ISDIR(this->d_inode->i_mode)) { - opaque = true; - } else if (prev && (!S_ISDIR(prev->d_inode->i_mode) || - !S_ISDIR(this->d_inode->i_mode))) { + + if (prev && (!S_ISDIR(prev->d_inode->i_mode) || + !S_ISDIR(this->d_inode->i_mode))) { + /* + * FIXME: check for upper-opaqueness maybe better done + * in remove code. + */ if (prev == upperdentry) upperopaque = true; dput(this); break; } + /* + * If this is a non-directory then stop here. + */ + if (!S_ISDIR(this->d_inode->i_mode)) + opaque = true; + stack[ctr].dentry = this; stack[ctr].mnt = lowerpath.mnt; ctr++; From 3cdf6fe91041b3afd6761f76254f7b6cbe8020fc Mon Sep 17 00:00:00 2001 From: Seunghun Lee Date: Sat, 3 Jan 2015 02:26:49 +0900 Subject: [PATCH 20/22] ovl: Prevent rw remount when it should be ro mount Overlayfs should be mounted read-only when upper-fs is read-only or nonexistent. But now it can be remounted read-write and this can cause kernel panic. So we should prevent read-write remount when the above situation happens. Signed-off-by: Seunghun Lee Signed-off-by: Miklos Szeredi --- fs/overlayfs/super.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index 9e94f4acef1a..b90952f528b1 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -525,10 +525,22 @@ static int ovl_show_options(struct seq_file *m, struct dentry *dentry) return 0; } +static int ovl_remount(struct super_block *sb, int *flags, char *data) +{ + struct ovl_fs *ufs = sb->s_fs_info; + + if (!(*flags & MS_RDONLY) && + (!ufs->upper_mnt || (ufs->upper_mnt->mnt_sb->s_flags & MS_RDONLY))) + return -EROFS; + + return 0; +} + static const struct super_operations ovl_super_operations = { .put_super = ovl_put_super, .statfs = ovl_statfs, .show_options = ovl_show_options, + .remount_fs = ovl_remount, }; enum { From 6d900f5a33393067e370736d39798f814f5e25cc Mon Sep 17 00:00:00 2001 From: Miklos Szeredi Date: Thu, 8 Jan 2015 15:09:15 +0100 Subject: [PATCH 21/22] ovl: document lower layer ordering Reported-by: Fabian Sturm Signed-off-by: Miklos Szeredi --- Documentation/filesystems/overlayfs.txt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Documentation/filesystems/overlayfs.txt b/Documentation/filesystems/overlayfs.txt index 006ea480506d..6db0e5d1da07 100644 --- a/Documentation/filesystems/overlayfs.txt +++ b/Documentation/filesystems/overlayfs.txt @@ -167,8 +167,12 @@ separator character between the directory names. For example: mount -t overlay overlay -olowerdir=/lower1:/lower2:/lower3 /merged -As the example shows, "upperdir=" and "workdir=" may be omitted. In that case -the overlay will be read-only. +As the example shows, "upperdir=" and "workdir=" may be omitted. In +that case the overlay will be read-only. + +The specified lower directories will be stacked beginning from the +rightmost one and going left. In the above example lower1 will be the +top, lower2 the middle and lower3 the bottom layer. Non-standard behavior From 4330397e4e8a662f36d101659e2a59ce32e76ff4 Mon Sep 17 00:00:00 2001 From: hujianyang Date: Thu, 11 Dec 2014 10:30:18 +0800 Subject: [PATCH 22/22] ovl: discard independent cursor in readdir() Since the ovl_dir_cache is stable during a directory reading, the cursor of struct ovl_dir_file don't need to be an independent entry in the list of a merged directory. This patch changes *cursor* to a pointer which points to the entry in the ovl_dir_cache. After this, we don't need to check *is_cursor* either. Signed-off-by: hujianyang Signed-off-by: Miklos Szeredi --- fs/overlayfs/readdir.c | 38 ++++++++++++++------------------------ 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/fs/overlayfs/readdir.c b/fs/overlayfs/readdir.c index 9df848f2e622..dcf1d412888d 100644 --- a/fs/overlayfs/readdir.c +++ b/fs/overlayfs/readdir.c @@ -24,7 +24,6 @@ struct ovl_cache_entry { struct list_head l_node; struct rb_node node; bool is_whiteout; - bool is_cursor; char name[]; }; @@ -49,7 +48,7 @@ struct ovl_dir_file { bool is_real; bool is_upper; struct ovl_dir_cache *cache; - struct ovl_cache_entry cursor; + struct list_head *cursor; struct file *realfile; struct file *upperfile; }; @@ -97,7 +96,6 @@ static struct ovl_cache_entry *ovl_cache_entry_new(struct dentry *dir, p->type = d_type; p->ino = ino; p->is_whiteout = false; - p->is_cursor = false; if (d_type == DT_CHR) { struct dentry *dentry; @@ -196,7 +194,6 @@ static void ovl_cache_put(struct ovl_dir_file *od, struct dentry *dentry) { struct ovl_dir_cache *cache = od->cache; - list_del_init(&od->cursor.l_node); WARN_ON(cache->refcount <= 0); cache->refcount--; if (!cache->refcount) { @@ -254,6 +251,7 @@ static void ovl_dir_reset(struct file *file) if (cache && ovl_dentry_version_get(dentry) != cache->version) { ovl_cache_put(od, dentry); od->cache = NULL; + od->cursor = NULL; } WARN_ON(!od->is_real && !OVL_TYPE_MERGE(type)); if (od->is_real && OVL_TYPE_MERGE(type)) @@ -295,17 +293,16 @@ static int ovl_dir_read_merged(struct dentry *dentry, struct list_head *list) static void ovl_seek_cursor(struct ovl_dir_file *od, loff_t pos) { - struct ovl_cache_entry *p; + struct list_head *p; loff_t off = 0; - list_for_each_entry(p, &od->cache->entries, l_node) { - if (p->is_cursor) - continue; + list_for_each(p, &od->cache->entries) { if (off >= pos) break; off++; } - list_move_tail(&od->cursor.l_node, &p->l_node); + /* Cursor is safe since the cache is stable */ + od->cursor = p; } static struct ovl_dir_cache *ovl_cache_get(struct dentry *dentry) @@ -344,6 +341,7 @@ static int ovl_iterate(struct file *file, struct dir_context *ctx) { struct ovl_dir_file *od = file->private_data; struct dentry *dentry = file->f_path.dentry; + struct ovl_cache_entry *p; if (!ctx->pos) ovl_dir_reset(file); @@ -362,19 +360,13 @@ static int ovl_iterate(struct file *file, struct dir_context *ctx) ovl_seek_cursor(od, ctx->pos); } - while (od->cursor.l_node.next != &od->cache->entries) { - struct ovl_cache_entry *p; - - p = list_entry(od->cursor.l_node.next, struct ovl_cache_entry, l_node); - /* Skip cursors */ - if (!p->is_cursor) { - if (!p->is_whiteout) { - if (!dir_emit(ctx, p->name, p->len, p->ino, p->type)) - break; - } - ctx->pos++; - } - list_move(&od->cursor.l_node, &p->l_node); + while (od->cursor != &od->cache->entries) { + p = list_entry(od->cursor, struct ovl_cache_entry, l_node); + if (!p->is_whiteout) + if (!dir_emit(ctx, p->name, p->len, p->ino, p->type)) + break; + od->cursor = p->l_node.next; + ctx->pos++; } return 0; } @@ -493,11 +485,9 @@ static int ovl_dir_open(struct inode *inode, struct file *file) kfree(od); return PTR_ERR(realfile); } - INIT_LIST_HEAD(&od->cursor.l_node); od->realfile = realfile; od->is_real = !OVL_TYPE_MERGE(type); od->is_upper = OVL_TYPE_UPPER(type); - od->cursor.is_cursor = true; file->private_data = od; return 0;