diff options
Diffstat (limited to 'fs/overlayfs/readdir.c')
-rw-r--r-- | fs/overlayfs/readdir.c | 76 |
1 files changed, 65 insertions, 11 deletions
diff --git a/fs/overlayfs/readdir.c b/fs/overlayfs/readdir.c index 6918b98faeb6..01620ebae1bd 100644 --- a/fs/overlayfs/readdir.c +++ b/fs/overlayfs/readdir.c @@ -606,6 +606,7 @@ static struct ovl_dir_cache *ovl_cache_get_impure(struct path *path) { int res; struct dentry *dentry = path->dentry; + struct ovl_fs *ofs = OVL_FS(dentry->d_sb); struct ovl_dir_cache *cache; cache = ovl_dir_cache(d_inode(dentry)); @@ -632,7 +633,7 @@ static struct ovl_dir_cache *ovl_cache_get_impure(struct path *path) * Removing the "impure" xattr is best effort. */ if (!ovl_want_write(dentry)) { - ovl_do_removexattr(ovl_dentry_upper(dentry), + ovl_do_removexattr(ofs, ovl_dentry_upper(dentry), OVL_XATTR_IMPURE); ovl_drop_write(dentry); } @@ -839,7 +840,7 @@ out_unlock: return res; } -static struct file *ovl_dir_open_realfile(struct file *file, +static struct file *ovl_dir_open_realfile(const struct file *file, struct path *realpath) { struct file *res; @@ -852,16 +853,22 @@ static struct file *ovl_dir_open_realfile(struct file *file, return res; } -static int ovl_dir_fsync(struct file *file, loff_t start, loff_t end, - int datasync) +/* + * Like ovl_real_fdget(), returns upperfile if dir was copied up since open. + * Unlike ovl_real_fdget(), this caches upperfile in file->private_data. + * + * TODO: use same abstract type for file->private_data of dir and file so + * upperfile could also be cached for files as well. + */ +struct file *ovl_dir_real_file(const struct file *file, bool want_upper) { + struct ovl_dir_file *od = file->private_data; struct dentry *dentry = file->f_path.dentry; struct file *realfile = od->realfile; - /* Nothing to sync for lower */ if (!OVL_TYPE_UPPER(ovl_path_type(dentry))) - return 0; + return want_upper ? NULL : realfile; /* * Need to check if we started out being a lower dir, but got copied up @@ -880,7 +887,7 @@ static int ovl_dir_fsync(struct file *file, loff_t start, loff_t end, if (!od->upperfile) { if (IS_ERR(realfile)) { inode_unlock(inode); - return PTR_ERR(realfile); + return realfile; } smp_store_release(&od->upperfile, realfile); } else { @@ -893,6 +900,25 @@ static int ovl_dir_fsync(struct file *file, loff_t start, loff_t end, } } + return realfile; +} + +static int ovl_dir_fsync(struct file *file, loff_t start, loff_t end, + int datasync) +{ + struct file *realfile; + int err; + + if (!ovl_should_sync(OVL_FS(file->f_path.dentry->d_sb))) + return 0; + + realfile = ovl_dir_real_file(file, true); + err = PTR_ERR_OR_ZERO(realfile); + + /* Nothing to sync for lower */ + if (!realfile || err) + return err; + return vfs_fsync_range(realfile, start, end, datasync); } @@ -945,6 +971,10 @@ const struct file_operations ovl_dir_operations = { .llseek = ovl_dir_llseek, .fsync = ovl_dir_fsync, .release = ovl_dir_release, + .unlocked_ioctl = ovl_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = ovl_compat_ioctl, +#endif }; int ovl_check_empty_dir(struct dentry *dentry, struct list_head *list) @@ -1051,7 +1081,9 @@ int ovl_check_d_type_supported(struct path *realpath) return rdd.d_type_supported; } -static void ovl_workdir_cleanup_recurse(struct path *path, int level) +#define OVL_INCOMPATDIR_NAME "incompat" + +static int ovl_workdir_cleanup_recurse(struct path *path, int level) { int err; struct inode *dir = path->dentry->d_inode; @@ -1065,6 +1097,19 @@ static void ovl_workdir_cleanup_recurse(struct path *path, int level) .root = &root, .is_lowest = false, }; + bool incompat = false; + + /* + * The "work/incompat" directory is treated specially - if it is not + * empty, instead of printing a generic error and mounting read-only, + * we will error about incompat features and fail the mount. + * + * When called from ovl_indexdir_cleanup(), path->dentry->d_name.name + * starts with '#'. + */ + if (level == 2 && + !strcmp(path->dentry->d_name.name, OVL_INCOMPATDIR_NAME)) + incompat = true; err = ovl_dir_read(path, &rdd); if (err) @@ -1079,17 +1124,25 @@ static void ovl_workdir_cleanup_recurse(struct path *path, int level) continue; if (p->len == 2 && p->name[1] == '.') continue; + } else if (incompat) { + pr_err("overlay with incompat feature '%s' cannot be mounted\n", + p->name); + err = -EINVAL; + break; } dentry = lookup_one_len(p->name, path->dentry, p->len); if (IS_ERR(dentry)) continue; if (dentry->d_inode) - ovl_workdir_cleanup(dir, path->mnt, dentry, level); + err = ovl_workdir_cleanup(dir, path->mnt, dentry, level); dput(dentry); + if (err) + break; } inode_unlock(dir); out: ovl_cache_free(&list); + return err; } int ovl_workdir_cleanup(struct inode *dir, struct vfsmount *mnt, @@ -1106,9 +1159,10 @@ int ovl_workdir_cleanup(struct inode *dir, struct vfsmount *mnt, struct path path = { .mnt = mnt, .dentry = dentry }; inode_unlock(dir); - ovl_workdir_cleanup_recurse(&path, level + 1); + err = ovl_workdir_cleanup_recurse(&path, level + 1); inode_lock_nested(dir, I_MUTEX_PARENT); - err = ovl_cleanup(dir, dentry); + if (!err) + err = ovl_cleanup(dir, dentry); } return err; |