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 <[EMAIL PROTECTED]>
---
 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 [EMAIL PROTECTED]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to