diff options
| author | Andy Whitcroft <apw@canonical.com> | 2015-05-18 10:09:52 (GMT) |
|---|---|---|
| committer | Tim Gardner <tim.gardner@canonical.com> | 2015-07-22 15:15:03 (GMT) |
| commit | 6a9e889d4a130e6445b0e9bbae2308dc4b139dfc (patch) | |
| tree | 1bc1e713e6fcf15f364f278732352893fcc7fdc3 | |
| parent | c9977f5494a4143585a9650f329d7c085acfc5bc (diff) | |
UBUNTU: SAUCE: overlay: add backwards compatible overlayfs format support V3
Adds a nearly completely compatible overlayfs filesystem type to overlay
fs, allowing it to mount those filesystems. It does still require the
new workdir= arguement to allow them to be writable. This is aimed to
be paired with an overlayfs userspace mount helper.
V2: Fix up rename handling, which was leaving chardev-0 style whiteouts
lying about.
V3: pull up to mainline v4.0.
BugLink: http://bugs.launchpad.net/bugs/1395877
BugLink: http://bugs.launchpad.net/bugs/1410480
Signed-off-by: Andy Whitcroft <apw@canonical.com>
Signed-off-by: Tim Gardner <tim.gardner@canonical.com>
| -rw-r--r-- | fs/overlayfs/Kconfig | 7 | ||||
| -rw-r--r-- | fs/overlayfs/dir.c | 78 | ||||
| -rw-r--r-- | fs/overlayfs/overlayfs.h | 22 | ||||
| -rw-r--r-- | fs/overlayfs/readdir.c | 13 | ||||
| -rw-r--r-- | fs/overlayfs/super.c | 96 |
5 files changed, 205 insertions, 11 deletions
diff --git a/fs/overlayfs/Kconfig b/fs/overlayfs/Kconfig index 3435581..b58e77b 100644 --- a/fs/overlayfs/Kconfig +++ b/fs/overlayfs/Kconfig @@ -8,3 +8,10 @@ config OVERLAY_FS merged with the 'upper' object. For more information see Documentation/filesystems/overlayfs.txt + +config OVERLAY_FS_V1 + bool "Overlayfs filesystem (V1) format support" + help + Support the older whiteout format overlayfs filesystems via + the overlay module. This is needed to support legacy kernels + built using the original overlayfs patch set. diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index 692ceda..b16b7aa 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -48,6 +48,34 @@ struct dentry *ovl_lookup_temp(struct dentry *workdir, struct dentry *dentry) return temp; } +#ifdef CONFIG_OVERLAY_FS_V1 +static const char *ovl_whiteout_symlink = "(overlay-whiteout)"; +int ovl_do_whiteout_v1(struct inode *workdir, + struct dentry *dentry) +{ + int err; + + err = vfs_symlink(workdir, dentry, ovl_whiteout_symlink); + if (err) + return err; + + err = vfs_setxattr(dentry, ovl_whiteout_xattr, "y", 1, 0); + if (err) + vfs_unlink(workdir, dentry, NULL); + + if (err) { + /* + * There's no way to recover from failure to whiteout. + * What should we do? Log a big fat error and... ? + */ + pr_err("overlayfs: ERROR - failed to whiteout '%s'\n", + dentry->d_name.name); + } + + return err; +} +#endif + /* caller holds i_mutex on workdir */ static struct dentry *ovl_whiteout(struct dentry *workdir, struct dentry *dentry) @@ -60,7 +88,7 @@ static struct dentry *ovl_whiteout(struct dentry *workdir, if (IS_ERR(whiteout)) return whiteout; - err = ovl_do_whiteout(wdir, whiteout); + err = ovl_do_whiteout(wdir, whiteout, dentry); if (err) { dput(whiteout); whiteout = ERR_PTR(err); @@ -699,6 +727,51 @@ static int ovl_rmdir(struct inode *dir, struct dentry *dentry) return ovl_do_remove(dentry, true); } +/* + * ovl_downgrade_whiteout -- build a symlink whiteout and install it + * over the existing chardev whiteout. + */ +static void ovl_downgrade_whiteout(struct dentry *old_upperdir, + struct dentry *old) +{ + struct dentry *workdir = ovl_workdir(old); + struct dentry *legacy_whiteout = NULL; + struct dentry *whtdentry; + int err; + + err = ovl_lock_rename_workdir(workdir, old_upperdir); + if (err) + goto out; + + whtdentry = lookup_one_len(old->d_name.name, old_upperdir, + old->d_name.len); + if (IS_ERR(whtdentry)) { + err = PTR_ERR(whtdentry); + goto out_unlock_workdir; + } + + legacy_whiteout = ovl_whiteout(workdir, old); + if (IS_ERR(legacy_whiteout)) { + err = PTR_ERR(legacy_whiteout); + goto out_dput; + } + + err = ovl_do_rename(workdir->d_inode, legacy_whiteout, + old_upperdir->d_inode, whtdentry, 0); + if (err) + ovl_cleanup(workdir->d_inode, legacy_whiteout); + +out_dput: + dput(whtdentry); + dput(legacy_whiteout); +out_unlock_workdir: + unlock_rename(workdir, old_upperdir); +out: + if (err) + pr_err("overlayfs: dowgrade of '%pd2' whiteout failed (%i)\n", + old, err); +} + static int ovl_rename2(struct inode *olddir, struct dentry *old, struct inode *newdir, struct dentry *new, unsigned int flags) @@ -919,6 +992,9 @@ out_dput: dput(newdentry); out_unlock: unlock_rename(new_upperdir, old_upperdir); + + if (!err && ovl_config_legacy(old) && flags & RENAME_WHITEOUT) + ovl_downgrade_whiteout(old_upperdir, old); out_revert_creds: if (old_opaque || new_opaque) { revert_creds(old_cred); diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index 17ac5af..13fd4e2 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -27,6 +27,8 @@ enum ovl_path_type { #define OVL_XATTR_PRE_LEN 16 #define OVL_XATTR_OPAQUE OVL_XATTR_PRE_NAME"opaque" +extern const char *ovl_whiteout_xattr; /* XXX: should be ^^ */ + static inline int ovl_do_rmdir(struct inode *dir, struct dentry *dentry) { int err = vfs_rmdir(dir, dentry); @@ -124,12 +126,28 @@ static inline int ovl_do_rename(struct inode *olddir, struct dentry *olddentry, return err; } -static inline int ovl_do_whiteout(struct inode *dir, struct dentry *dentry) +#ifdef CONFIG_OVERLAY_FS_V1 +extern int ovl_config_legacy(struct dentry *dentry); +#else +#define ovl_config_legacy(x) (0) +#endif + +int ovl_do_whiteout_v1(struct inode *dir, struct dentry *dentry); + +static inline int ovl_do_whiteout_v2(struct inode *dir, struct dentry *dentry) { int err = vfs_whiteout(dir, dentry); pr_debug("whiteout(%pd2) = %i\n", dentry, err); return err; } +static inline int ovl_do_whiteout(struct inode *dir, struct dentry *dentry, + struct dentry *ovlentry) +{ + if (ovl_config_legacy(ovlentry)) + return ovl_do_whiteout_v1(dir, dentry); + + return ovl_do_whiteout_v2(dir, dentry); +} enum ovl_path_type ovl_path_type(struct dentry *dentry); u64 ovl_dentry_version_get(struct dentry *dentry); @@ -149,7 +167,7 @@ int ovl_want_write(struct dentry *dentry); void ovl_drop_write(struct dentry *dentry); bool ovl_dentry_is_opaque(struct dentry *dentry); void ovl_dentry_set_opaque(struct dentry *dentry, bool opaque); -bool ovl_is_whiteout(struct dentry *dentry); +bool ovl_is_whiteout(struct dentry *dentry, int is_legacy); void ovl_dentry_update(struct dentry *dentry, struct dentry *upperdentry); struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, unsigned int flags); diff --git a/fs/overlayfs/readdir.c b/fs/overlayfs/readdir.c index 907870e..a563e34 100644 --- a/fs/overlayfs/readdir.c +++ b/fs/overlayfs/readdir.c @@ -42,6 +42,7 @@ struct ovl_readdir_data { struct dentry *dir; int count; int err; + int is_legacy; }; struct ovl_dir_file { @@ -81,7 +82,8 @@ static struct ovl_cache_entry *ovl_cache_entry_find(struct rb_root *root, static struct ovl_cache_entry *ovl_cache_entry_new(struct dentry *dir, const char *name, int len, - u64 ino, unsigned int d_type) + u64 ino, unsigned int d_type, + int is_legacy) { struct ovl_cache_entry *p; size_t size = offsetof(struct ovl_cache_entry, name[len + 1]); @@ -97,7 +99,7 @@ static struct ovl_cache_entry *ovl_cache_entry_new(struct dentry *dir, p->ino = ino; p->is_whiteout = false; - if (d_type == DT_CHR) { + if ((d_type == DT_CHR && !is_legacy) || (d_type == DT_LNK && is_legacy)) { struct dentry *dentry; const struct cred *old_cred; struct cred *override_cred; @@ -116,7 +118,7 @@ static struct ovl_cache_entry *ovl_cache_entry_new(struct dentry *dir, dentry = lookup_one_len(name, dir, len); if (!IS_ERR(dentry)) { - p->is_whiteout = ovl_is_whiteout(dentry); + p->is_whiteout = ovl_is_whiteout(dentry, is_legacy); dput(dentry); } revert_creds(old_cred); @@ -148,7 +150,7 @@ static int ovl_cache_entry_add_rb(struct ovl_readdir_data *rdd, return 0; } - p = ovl_cache_entry_new(rdd->dir, name, len, ino, d_type); + p = ovl_cache_entry_new(rdd->dir, name, len, ino, d_type, rdd->is_legacy); if (p == NULL) return -ENOMEM; @@ -169,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(rdd->dir, name, namelen, ino, d_type); + p = ovl_cache_entry_new(rdd->dir, name, namelen, ino, d_type, rdd->is_legacy); if (p == NULL) rdd->err = -ENOMEM; else @@ -269,6 +271,7 @@ static int ovl_dir_read_merged(struct dentry *dentry, struct list_head *list) .list = list, .root = RB_ROOT, .is_merge = false, + .is_legacy = ovl_config_legacy(dentry), }; int idx, next; diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index bf8537c..5ec2caf 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -39,6 +39,7 @@ struct ovl_fs { struct vfsmount **lower_mnt; struct dentry *workdir; long lower_namelen; + int legacy; /* pathnames of lower and upper dirs, for show_options */ struct ovl_config config; }; @@ -236,13 +237,56 @@ u64 ovl_dentry_version_get(struct dentry *dentry) return oe->version; } -bool ovl_is_whiteout(struct dentry *dentry) +#ifdef CONFIG_OVERLAY_FS_V1 +int ovl_config_legacy(struct dentry *dentry) +{ + struct super_block *sb = dentry->d_sb; + struct ovl_fs *ufs = sb->s_fs_info; + + return ufs->legacy; +} + +const char *ovl_whiteout_xattr = "trusted.overlay.whiteout"; + +bool ovl_is_whiteout_v1(struct dentry *dentry) +{ + int res; + char val; + + if (!dentry) + return false; + if (!dentry->d_inode) + return false; + if (!S_ISLNK(dentry->d_inode->i_mode)) + return false; + if (!dentry->d_inode->i_op->getxattr) + return false; + + res = dentry->d_inode->i_op->getxattr(dentry, ovl_whiteout_xattr, &val, 1); + if (res == 1 && val == 'y') + return true; + + return false; +} +#else +#define ovl_is_whiteout_v1(x) (0) +#endif + +bool ovl_is_whiteout_v2(struct dentry *dentry) { struct inode *inode = dentry->d_inode; return inode && IS_WHITEOUT(inode); } +bool ovl_is_whiteout(struct dentry *dentry, int is_legacy) +{ + if (is_legacy) + return ovl_is_whiteout_v2(dentry) || ovl_is_whiteout_v1(dentry); + + return ovl_is_whiteout_v2(dentry); +} + static bool ovl_is_opaquedir(struct dentry *dentry) { int res; @@ -341,6 +385,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, struct dentry *this, *prev = NULL; unsigned int i; int err; + int is_legacy = ovl_config_legacy(dentry); upperdir = ovl_upperdentry_dereference(poe); if (upperdir) { @@ -350,7 +395,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, goto out; if (this) { - if (ovl_is_whiteout(this)) { + if (ovl_is_whiteout(this, is_legacy)) { dput(this); this = NULL; upperopaque = true; @@ -384,7 +429,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry, } if (!this) continue; - if (ovl_is_whiteout(this)) { + if (ovl_is_whiteout(this, is_legacy)) { dput(this); break; } @@ -1032,14 +1077,59 @@ static struct file_system_type ovl_fs_type = { }; MODULE_ALIAS_FS("overlay"); +#ifdef CONFIG_OVERLAY_FS_V1 +static int ovl_v1_fill_super(struct super_block *sb, void *data, int silent) +{ + int ret; + struct ovl_fs *ufs; + + ret = ovl_fill_super(sb, data, silent); + if (ret) + return ret; + + /* Mark this as a overlayfs format. */ + ufs = sb->s_fs_info; + ufs->legacy = 1; + + return ret; +} + +static struct dentry *ovl_mount_v1(struct file_system_type *fs_type, int flags, + const char *dev_name, void *raw_data) +{ + return mount_nodev(fs_type, flags, raw_data, ovl_v1_fill_super); +} + +static struct file_system_type ovl_v1_fs_type = { + .owner = THIS_MODULE, + .name = "overlayfs", + .mount = ovl_mount_v1, + .kill_sb = kill_anon_super, + .fs_flags = FS_USERNS_MOUNT, /* XXX */ +}; +MODULE_ALIAS_FS("overlayfs"); +MODULE_ALIAS("overlayfs"); +#endif + static int __init ovl_init(void) { + int ret; + + if (IS_ENABLED(CONFIG_OVERLAY_FS_V1)) { + ret = register_filesystem(&ovl_v1_fs_type); + if (ret) + return ret; + } + return register_filesystem(&ovl_fs_type); } static void __exit ovl_exit(void) { unregister_filesystem(&ovl_fs_type); + + if (IS_ENABLED(CONFIG_OVERLAY_FS_V1)) + unregister_filesystem(&ovl_v1_fs_type); } module_init(ovl_init); |
