[RFC 18/26] union-mount: Changes to the namespace handling

From: Jan Blunck
Date: Mon Jul 30 2007 - 12:18:05 EST


Creates the proper struct union_mount when mounting something into a
union. If the topmost filesystem isn't capable of handling the white-out
filetype it could only be mount read-only.

Signed-off-by: Jan Blunck <jblunck@xxxxxxx>
---
fs/namespace.c | 46 ++++++++++++++++++++++++++++++++++++++--
fs/union.c | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++
include/linux/mount.h | 3 ++
include/linux/union.h | 6 +++++
4 files changed, 110 insertions(+), 2 deletions(-)

--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -25,6 +25,7 @@
#include <linux/security.h>
#include <linux/mount.h>
#include <linux/ramfs.h>
+#include <linux/union.h>
#include <asm/uaccess.h>
#include <asm/unistd.h>
#include "pnode.h"
@@ -68,6 +69,9 @@ struct vfsmount *alloc_vfsmnt(const char
INIT_LIST_HEAD(&mnt->mnt_share);
INIT_LIST_HEAD(&mnt->mnt_slave_list);
INIT_LIST_HEAD(&mnt->mnt_slave);
+#ifdef CONFIG_UNION_MOUNT
+ INIT_LIST_HEAD(&mnt->mnt_unions);
+#endif
if (name) {
int size = strlen(name) + 1;
char *newname = kmalloc(size, GFP_KERNEL);
@@ -157,6 +161,7 @@ static void __touch_mnt_namespace(struct

static void detach_mnt(struct vfsmount *mnt, struct nameidata *old_nd)
{
+ detach_mnt_union(mnt);
old_nd->dentry = mnt->mnt_mountpoint;
old_nd->mnt = mnt->mnt_parent;
mnt->mnt_parent = mnt;
@@ -180,6 +185,7 @@ static void attach_mnt(struct vfsmount *
list_add_tail(&mnt->mnt_hash, mount_hashtable +
hash(nd->mnt, nd->dentry));
list_add_tail(&mnt->mnt_child, &nd->mnt->mnt_mounts);
+ attach_mnt_union(mnt, nd->mnt, nd->dentry);
}

/*
@@ -202,6 +208,7 @@ static void commit_tree(struct vfsmount
list_add_tail(&mnt->mnt_hash, mount_hashtable +
hash(parent, mnt->mnt_mountpoint));
list_add_tail(&mnt->mnt_child, &parent->mnt_mounts);
+ attach_mnt_union(mnt, mnt->mnt_parent, mnt->mnt_mountpoint);
touch_mnt_namespace(n);
}

@@ -577,6 +584,7 @@ void release_mounts(struct list_head *he
struct dentry *dentry;
struct vfsmount *m;
spin_lock(&vfsmount_lock);
+ detach_mnt_union(mnt);
dentry = mnt->mnt_mountpoint;
m = mnt->mnt_parent;
mnt->mnt_mountpoint = mnt->mnt_root;
@@ -999,6 +1007,10 @@ static int do_change_type(struct nameida
if (nd->dentry != nd->mnt->mnt_root)
return -EINVAL;

+ /* Don't change the type of union mounts */
+ if (IS_MNT_UNION(nd->mnt))
+ return -EINVAL;
+
down_write(&namespace_sem);
spin_lock(&vfsmount_lock);
for (m = mnt; m; m = (recurse ? next_mnt(m, mnt) : NULL))
@@ -1011,7 +1023,8 @@ static int do_change_type(struct nameida
/*
* do loopback mount.
*/
-static int do_loopback(struct nameidata *nd, char *old_name, int flags)
+static int do_loopback(struct nameidata *nd, char *old_name, int flags,
+ int mnt_flags)
{
int clone_flags = 0;
uid_t owner = 0;
@@ -1049,6 +1062,18 @@ static int do_loopback(struct nameidata
if (IS_ERR(mnt))
goto out;

+ /*
+ * Unions couldn't be writable if the filesystem doesn't know about
+ * whiteouts
+ */
+ err = -ENOTSUPP;
+ if ((mnt_flags & MNT_UNION) &&
+ !(mnt->mnt_sb->s_flags & (MS_WHITEOUT|MS_RDONLY)))
+ goto out;
+
+ if (mnt_flags & MNT_UNION)
+ mnt->mnt_flags |= MNT_UNION;
+
err = graft_tree(mnt, nd);
if (err) {
LIST_HEAD(umount_list);
@@ -1121,6 +1146,13 @@ static int do_move_mount(struct nameidat
if (err)
return err;

+ /* moving to or from a union mount is not supported */
+ err = -EINVAL;
+ if (IS_MNT_UNION(nd->mnt))
+ goto exit;
+ if (IS_MNT_UNION(old_nd.mnt))
+ goto exit;
+
down_write(&namespace_sem);
while (d_mountpoint(nd->dentry) && follow_down(&nd->mnt, &nd->dentry))
;
@@ -1176,6 +1208,7 @@ out:
up_write(&namespace_sem);
if (!err)
path_release(&parent_nd);
+exit:
path_release(&old_nd);
return err;
}
@@ -1253,6 +1286,15 @@ int do_add_mount(struct vfsmount *newmnt
if (S_ISLNK(newmnt->mnt_root->d_inode->i_mode))
goto unlock;

+ /*
+ * Unions couldn't be writable if the filesystem doesn't know about
+ * whiteouts
+ */
+ err = -ENOTSUPP;
+ if ((mnt_flags & MNT_UNION) &&
+ !(newmnt->mnt_sb->s_flags & (MS_WHITEOUT|MS_RDONLY)))
+ goto unlock;
+
/* some flags may have been set earlier */
newmnt->mnt_flags |= mnt_flags;
if ((err = graft_tree(newmnt, nd)))
@@ -1579,7 +1621,7 @@ long do_mount(char *dev_name, char *dir_
retval = do_remount(&nd, flags & ~MS_REMOUNT, mnt_flags,
data_page);
else if (flags & MS_BIND)
- retval = do_loopback(&nd, dev_name, flags);
+ retval = do_loopback(&nd, dev_name, flags, mnt_flags);
else if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE))
retval = do_change_type(&nd, flags);
else if (flags & MS_MOVE)
--- a/fs/union.c
+++ b/fs/union.c
@@ -114,6 +114,7 @@ struct union_mount *union_alloc(struct d

atomic_set(&um->u_count, 1);
INIT_LIST_HEAD(&um->u_unions);
+ INIT_LIST_HEAD(&um->u_list);
INIT_HLIST_NODE(&um->u_hash);
INIT_HLIST_NODE(&um->u_rhash);

@@ -258,6 +259,7 @@ int append_to_union(struct vfsmount *mnt
union_put(this);
return 0;
}
+ list_add(&this->u_list, &mnt->mnt_unions);
list_add(&this->u_unions, &dentry->d_unions);
dest_dentry->d_unionized++;
__union_hash(this);
@@ -366,6 +368,7 @@ repeat:
list_for_each_entry_safe(this, next, &dentry->d_unions, u_unions) {
BUG_ON(!hlist_unhashed(&this->u_hash));
BUG_ON(!hlist_unhashed(&this->u_rhash));
+ list_del(&this->u_list);
list_del(&this->u_unions);
this->u_next.dentry->d_unionized--;
spin_unlock(&union_lock);
@@ -394,6 +397,7 @@ repeat:

BUG_ON(!hlist_unhashed(&this->u_hash));
BUG_ON(!hlist_unhashed(&this->u_rhash));
+ list_del(&this->u_list);
list_del(&this->u_unions);
this->u_next.dentry->d_unionized--;
spin_unlock(&union_lock);
@@ -405,3 +409,56 @@ repeat:
}
spin_unlock(&union_lock);
}
+
+/*
+ * Remove all union_mounts structures belonging to this vfsmount from the
+ * union lookup hashtable and so on ...
+ */
+void shrink_mnt_unions(struct vfsmount *mnt)
+{
+ struct union_mount *this, *next;
+
+repeat:
+ spin_lock(&union_lock);
+ list_for_each_entry_safe(this, next, &mnt->mnt_unions, u_list) {
+ if (this->u_this.dentry == mnt->mnt_root)
+ continue;
+ __union_unhash(this);
+ list_del(&this->u_list);
+ list_del(&this->u_unions);
+ this->u_next.dentry->d_unionized--;
+ spin_unlock(&union_lock);
+ union_put(this);
+ goto repeat;
+ }
+ spin_unlock(&union_lock);
+}
+
+int attach_mnt_union(struct vfsmount *mnt, struct vfsmount *dest_mnt,
+ struct dentry *dest_dentry)
+{
+ if (!IS_MNT_UNION(mnt))
+ return 0;
+
+ return append_to_union(mnt, mnt->mnt_root, dest_mnt, dest_dentry);
+}
+
+void detach_mnt_union(struct vfsmount *mnt)
+{
+ struct union_mount *um;
+
+ if (!IS_MNT_UNION(mnt))
+ return;
+
+ shrink_mnt_unions(mnt);
+
+ spin_lock(&union_lock);
+ um = union_lookup(mnt->mnt_root, mnt);
+ __union_unhash(um);
+ list_del(&um->u_list);
+ list_del(&um->u_unions);
+ um->u_next.dentry->d_unionized--;
+ spin_unlock(&union_lock);
+ union_put(um);
+ return;
+}
--- a/include/linux/mount.h
+++ b/include/linux/mount.h
@@ -56,6 +56,9 @@ struct vfsmount {
struct list_head mnt_slave; /* slave list entry */
struct vfsmount *mnt_master; /* slave is on master->mnt_slave_list */
struct mnt_namespace *mnt_ns; /* containing namespace */
+#ifdef CONFIG_UNION_MOUNT
+ struct list_head mnt_unions; /* list of union_mount structures */
+#endif
/*
* We put mnt_count & mnt_expiry_mark at the end of struct vfsmount
* to let these frequently modified fields in a separate cache line
--- a/include/linux/union.h
+++ b/include/linux/union.h
@@ -30,6 +30,7 @@ struct union_mount {
atomic_t u_count; /* reference count */
struct mutex u_mutex;
struct list_head u_unions; /* list head for d_unions */
+ struct list_head u_list; /* list head for mnt_unions */
struct hlist_node u_hash; /* list head for seaching */
struct hlist_node u_rhash; /* list head for reverse seaching */

@@ -49,6 +50,9 @@ extern int follow_union_mount(struct vfs
extern void __d_drop_unions(struct dentry *);
extern void shrink_d_unions(struct dentry *);
extern void __shrink_d_unions(struct dentry *, struct list_head *);
+extern int attach_mnt_union(struct vfsmount *, struct vfsmount *,
+ struct dentry *);
+extern void detach_mnt_union(struct vfsmount *);

#else /* CONFIG_UNION_MOUNT */

@@ -61,6 +65,8 @@ extern void __shrink_d_unions(struct den
#define __d_drop_unions(x) do { } while (0)
#define shrink_d_unions(x) do { } while (0)
#define __shrink_d_unions(x) do { } while (0)
+#define attach_mnt_union(x, y, z) do { } while (0)
+#define detach_mnt_union(x) do { } while (0)

#endif /* CONFIG_UNION_MOUNT */
#endif /* __KERNEL__ */

--

-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/