namespace: only take read lock in do_reconfigure_mnt()
[linux-2.6-microblaze.git] / fs / namespace.c
index 2b681f6..367f1c7 100644 (file)
@@ -87,6 +87,16 @@ EXPORT_SYMBOL_GPL(fs_kobj);
  */
 __cacheline_aligned_in_smp DEFINE_SEQLOCK(mount_lock);
 
+static inline void lock_mount_hash(void)
+{
+       write_seqlock(&mount_lock);
+}
+
+static inline void unlock_mount_hash(void)
+{
+       write_sequnlock(&mount_lock);
+}
+
 static inline struct hlist_head *m_hash(struct vfsmount *mnt, struct dentry *dentry)
 {
        unsigned long tmp = ((unsigned long)mnt / L1_CACHE_BYTES);
@@ -156,10 +166,10 @@ static inline void mnt_add_count(struct mount *mnt, int n)
 /*
  * vfsmount lock must be held for write
  */
-unsigned int mnt_get_count(struct mount *mnt)
+int mnt_get_count(struct mount *mnt)
 {
 #ifdef CONFIG_SMP
-       unsigned int count = 0;
+       int count = 0;
        int cpu;
 
        for_each_possible_cpu(cpu) {
@@ -210,6 +220,7 @@ static struct mount *alloc_vfsmnt(const char *name)
                INIT_HLIST_NODE(&mnt->mnt_mp_list);
                INIT_LIST_HEAD(&mnt->mnt_umounting);
                INIT_HLIST_HEAD(&mnt->mnt_stuck_children);
+               mnt->mnt.mnt_userns = &init_user_ns;
        }
        return mnt;
 
@@ -463,7 +474,6 @@ static int mnt_make_readonly(struct mount *mnt)
 {
        int ret = 0;
 
-       lock_mount_hash();
        mnt->mnt.mnt_flags |= MNT_WRITE_HOLD;
        /*
         * After storing MNT_WRITE_HOLD, we'll read the counters. This store
@@ -497,18 +507,9 @@ static int mnt_make_readonly(struct mount *mnt)
         */
        smp_wmb();
        mnt->mnt.mnt_flags &= ~MNT_WRITE_HOLD;
-       unlock_mount_hash();
        return ret;
 }
 
-static int __mnt_unmake_readonly(struct mount *mnt)
-{
-       lock_mount_hash();
-       mnt->mnt.mnt_flags &= ~MNT_READONLY;
-       unlock_mount_hash();
-       return 0;
-}
-
 int sb_prepare_remount_readonly(struct super_block *sb)
 {
        struct mount *mnt;
@@ -547,6 +548,11 @@ int sb_prepare_remount_readonly(struct super_block *sb)
 
 static void free_vfsmnt(struct mount *mnt)
 {
+       struct user_namespace *mnt_userns;
+
+       mnt_userns = mnt_user_ns(&mnt->mnt);
+       if (mnt_userns != &init_user_ns)
+               put_user_ns(mnt_userns);
        kfree_const(mnt->mnt_devname);
 #ifdef CONFIG_SMP
        free_percpu(mnt->mnt_pcp);
@@ -1055,6 +1061,9 @@ static struct mount *clone_mnt(struct mount *old, struct dentry *root,
        mnt->mnt.mnt_flags &= ~(MNT_WRITE_HOLD|MNT_MARKED|MNT_INTERNAL);
 
        atomic_inc(&sb->s_active);
+       mnt->mnt.mnt_userns = mnt_user_ns(&old->mnt);
+       if (mnt->mnt.mnt_userns != &init_user_ns)
+               mnt->mnt.mnt_userns = get_user_ns(mnt->mnt.mnt_userns);
        mnt->mnt.mnt_sb = sb;
        mnt->mnt.mnt_root = dget(root);
        mnt->mnt_mountpoint = mnt->mnt.mnt_root;
@@ -1139,6 +1148,7 @@ static DECLARE_DELAYED_WORK(delayed_mntput_work, delayed_mntput);
 static void mntput_no_expire(struct mount *mnt)
 {
        LIST_HEAD(list);
+       int count;
 
        rcu_read_lock();
        if (likely(READ_ONCE(mnt->mnt_ns))) {
@@ -1162,7 +1172,9 @@ static void mntput_no_expire(struct mount *mnt)
         */
        smp_mb();
        mnt_add_count(mnt, -1);
-       if (mnt_get_count(mnt)) {
+       count = mnt_get_count(mnt);
+       if (count != 0) {
+               WARN_ON(count < 0);
                rcu_read_unlock();
                unlock_mount_hash();
                return;
@@ -1710,8 +1722,6 @@ static int can_umount(const struct path *path, int flags)
 {
        struct mount *mnt = real_mount(path->mnt);
 
-       if (flags & ~(MNT_FORCE | MNT_DETACH | MNT_EXPIRE | UMOUNT_NOFOLLOW))
-               return -EINVAL;
        if (!may_mount())
                return -EPERM;
        if (path->dentry != path->mnt->mnt_root)
@@ -1725,6 +1735,7 @@ static int can_umount(const struct path *path, int flags)
        return 0;
 }
 
+// caller is responsible for flags being sane
 int path_umount(struct path *path, int flags)
 {
        struct mount *mnt = real_mount(path->mnt);
@@ -1746,6 +1757,10 @@ static int ksys_umount(char __user *name, int flags)
        struct path path;
        int ret;
 
+       // basic validity checks done first
+       if (flags & ~(MNT_FORCE | MNT_DETACH | MNT_EXPIRE | UMOUNT_NOFOLLOW))
+               return -EINVAL;
+
        if (!(flags & UMOUNT_NOFOLLOW))
                lookup_flags |= LOOKUP_FOLLOW;
        ret = user_path_at(AT_FDCWD, name, lookup_flags, &path);
@@ -2508,20 +2523,15 @@ static int change_mount_ro_state(struct mount *mnt, unsigned int mnt_flags)
        if (readonly_request)
                return mnt_make_readonly(mnt);
 
-       return __mnt_unmake_readonly(mnt);
+       mnt->mnt.mnt_flags &= ~MNT_READONLY;
+       return 0;
 }
 
-/*
- * Update the user-settable attributes on a mount.  The caller must hold
- * sb->s_umount for writing.
- */
 static void set_mount_attributes(struct mount *mnt, unsigned int mnt_flags)
 {
-       lock_mount_hash();
        mnt_flags |= mnt->mnt.mnt_flags & ~MNT_USER_SETTABLE_MASK;
        mnt->mnt.mnt_flags = mnt_flags;
        touch_mnt_namespace(mnt->mnt_ns);
-       unlock_mount_hash();
 }
 
 static void mnt_warn_timestamp_expiry(struct path *mountpoint, struct vfsmount *mnt)
@@ -2566,11 +2576,17 @@ static int do_reconfigure_mnt(struct path *path, unsigned int mnt_flags)
        if (!can_change_locked_flags(mnt, mnt_flags))
                return -EPERM;
 
-       down_write(&sb->s_umount);
+       /*
+        * We're only checking whether the superblock is read-only not
+        * changing it, so only take down_read(&sb->s_umount).
+        */
+       down_read(&sb->s_umount);
+       lock_mount_hash();
        ret = change_mount_ro_state(mnt, mnt_flags);
        if (ret == 0)
                set_mount_attributes(mnt, mnt_flags);
-       up_write(&sb->s_umount);
+       unlock_mount_hash();
+       up_read(&sb->s_umount);
 
        mnt_warn_timestamp_expiry(path, &mnt->mnt);
 
@@ -2610,8 +2626,11 @@ static int do_remount(struct path *path, int ms_flags, int sb_flags,
                err = -EPERM;
                if (ns_capable(sb->s_user_ns, CAP_SYS_ADMIN)) {
                        err = reconfigure_super(fc);
-                       if (!err)
+                       if (!err) {
+                               lock_mount_hash();
                                set_mount_attributes(mnt, mnt_flags);
+                               unlock_mount_hash();
+                       }
                }
                up_write(&sb->s_umount);
        }