summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndy Whitcroft <apw@canonical.com>2015-05-18 10:09:52 (GMT)
committerTim Gardner <tim.gardner@canonical.com>2015-07-22 15:15:03 (GMT)
commit6a9e889d4a130e6445b0e9bbae2308dc4b139dfc (patch)
tree1bc1e713e6fcf15f364f278732352893fcc7fdc3
parentc9977f5494a4143585a9650f329d7c085acfc5bc (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/Kconfig7
-rw-r--r--fs/overlayfs/dir.c78
-rw-r--r--fs/overlayfs/overlayfs.h22
-rw-r--r--fs/overlayfs/readdir.c13
-rw-r--r--fs/overlayfs/super.c96
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);