fix prune_dcache()/umount() race
[linux-2.6-block.git] / fs / super.c
index 1527e6a0ee358d084a29f542816a91c4418f5ef6..1f72e0d42d8fea7d77b1bb1c0f11b396eb6de1d5 100644 (file)
@@ -93,7 +93,7 @@ static struct super_block *alloc_super(struct file_system_type *type)
                 * subclass.
                 */
                down_write_nested(&s->s_umount, SINGLE_DEPTH_NESTING);
-               s->s_count = S_BIAS;
+               s->s_count = 1;
                atomic_set(&s->s_active, 1);
                mutex_init(&s->s_vfs_rename_mutex);
                mutex_init(&s->s_dquot.dqio_mutex);
@@ -130,11 +130,12 @@ static inline void destroy_super(struct super_block *s)
  * Drop a superblock's refcount.  Returns non-zero if the superblock was
  * destroyed.  The caller must hold sb_lock.
  */
-static int __put_super(struct super_block *sb)
+int __put_super(struct super_block *sb)
 {
        int ret = 0;
 
        if (!--sb->s_count) {
+               list_del_init(&sb->s_list);
                destroy_super(sb);
                ret = 1;
        }
@@ -151,7 +152,7 @@ static int __put_super(struct super_block *sb)
 int __put_super_and_need_restart(struct super_block *sb)
 {
        /* check for race with generic_shutdown_super() */
-       if (list_empty(&sb->s_list)) {
+       if (list_empty(&sb->s_instances)) {
                /* super block is removed, need to restart... */
                __put_super(sb);
                return 1;
@@ -178,57 +179,48 @@ void put_super(struct super_block *sb)
 
 
 /**
- *     deactivate_super        -       drop an active reference to superblock
+ *     deactivate_locked_super -       drop an active reference to superblock
  *     @s: superblock to deactivate
  *
- *     Drops an active reference to superblock, acquiring a temprory one if
- *     there is no active references left.  In that case we lock superblock,
+ *     Drops an active reference to superblock, converting it into a temprory
+ *     one if there is no other active references left.  In that case we
  *     tell fs driver to shut it down and drop the temporary reference we
  *     had just acquired.
+ *
+ *     Caller holds exclusive lock on superblock; that lock is released.
  */
-void deactivate_super(struct super_block *s)
+void deactivate_locked_super(struct super_block *s)
 {
        struct file_system_type *fs = s->s_type;
-       if (atomic_dec_and_lock(&s->s_active, &sb_lock)) {
-               s->s_count -= S_BIAS-1;
-               spin_unlock(&sb_lock);
+       if (atomic_dec_and_test(&s->s_active)) {
                vfs_dq_off(s, 0);
-               down_write(&s->s_umount);
                fs->kill_sb(s);
                put_filesystem(fs);
                put_super(s);
+       } else {
+               up_write(&s->s_umount);
        }
 }
 
-EXPORT_SYMBOL(deactivate_super);
+EXPORT_SYMBOL(deactivate_locked_super);
 
 /**
- *     deactivate_locked_super -       drop an active reference to superblock
+ *     deactivate_super        -       drop an active reference to superblock
  *     @s: superblock to deactivate
  *
- *     Equivalent of up_write(&s->s_umount); deactivate_super(s);, except that
- *     it does not unlock it until it's all over.  As the result, it's safe to
- *     use to dispose of new superblock on ->get_sb() failure exits - nobody
- *     will see the sucker until it's all over.  Equivalent using up_write +
- *     deactivate_super is safe for that purpose only if superblock is either
- *     safe to use or has NULL ->s_root when we unlock.
+ *     Variant of deactivate_locked_super(), except that superblock is *not*
+ *     locked by caller.  If we are going to drop the final active reference,
+ *     lock will be acquired prior to that.
  */
-void deactivate_locked_super(struct super_block *s)
+void deactivate_super(struct super_block *s)
 {
-       struct file_system_type *fs = s->s_type;
-       if (atomic_dec_and_lock(&s->s_active, &sb_lock)) {
-               s->s_count -= S_BIAS-1;
-               spin_unlock(&sb_lock);
-               vfs_dq_off(s, 0);
-               fs->kill_sb(s);
-               put_filesystem(fs);
-               put_super(s);
-       } else {
-               up_write(&s->s_umount);
+        if (!atomic_add_unless(&s->s_active, -1, 1)) {
+               down_write(&s->s_umount);
+               deactivate_locked_super(s);
        }
 }
 
-EXPORT_SYMBOL(deactivate_locked_super);
+EXPORT_SYMBOL(deactivate_super);
 
 /**
  *     grab_super - acquire an active reference
@@ -243,22 +235,18 @@ EXPORT_SYMBOL(deactivate_locked_super);
  */
 static int grab_super(struct super_block *s) __releases(sb_lock)
 {
+       if (atomic_inc_not_zero(&s->s_active)) {
+               spin_unlock(&sb_lock);
+               down_write(&s->s_umount);
+               return 1;
+       }
+       /* it's going away */
        s->s_count++;
        spin_unlock(&sb_lock);
+       /* wait for it to die */
        down_write(&s->s_umount);
-       if (s->s_root) {
-               spin_lock(&sb_lock);
-               if (s->s_count > S_BIAS) {
-                       atomic_inc(&s->s_active);
-                       s->s_count--;
-                       spin_unlock(&sb_lock);
-                       return 1;
-               }
-               spin_unlock(&sb_lock);
-       }
        up_write(&s->s_umount);
        put_super(s);
-       yield();
        return 0;
 }
 
@@ -321,8 +309,7 @@ void generic_shutdown_super(struct super_block *sb)
        }
        spin_lock(&sb_lock);
        /* should be initialized for __put_super_and_need_restart() */
-       list_del_init(&sb->s_list);
-       list_del(&sb->s_instances);
+       list_del_init(&sb->s_instances);
        spin_unlock(&sb_lock);
        up_write(&sb->s_umount);
 }
@@ -408,11 +395,12 @@ EXPORT_SYMBOL(drop_super);
  */
 void sync_supers(void)
 {
-       struct super_block *sb;
+       struct super_block *sb, *n;
 
        spin_lock(&sb_lock);
-restart:
-       list_for_each_entry(sb, &super_blocks, s_list) {
+       list_for_each_entry_safe(sb, n, &super_blocks, s_list) {
+               if (list_empty(&sb->s_instances))
+                       continue;
                if (sb->s_op->write_super && sb->s_dirt) {
                        sb->s_count++;
                        spin_unlock(&sb_lock);
@@ -423,8 +411,7 @@ restart:
                        up_read(&sb->s_umount);
 
                        spin_lock(&sb_lock);
-                       if (__put_super_and_need_restart(sb))
-                               goto restart;
+                       __put_super(sb);
                }
        }
        spin_unlock(&sb_lock);
@@ -438,7 +425,7 @@ restart:
  *     mounted on the device given. %NULL is returned if no match is found.
  */
 
-struct super_block * get_super(struct block_device *bdev)
+struct super_block *get_super(struct block_device *bdev)
 {
        struct super_block *sb;
 
@@ -448,17 +435,20 @@ struct super_block * get_super(struct block_device *bdev)
        spin_lock(&sb_lock);
 rescan:
        list_for_each_entry(sb, &super_blocks, s_list) {
+               if (list_empty(&sb->s_instances))
+                       continue;
                if (sb->s_bdev == bdev) {
                        sb->s_count++;
                        spin_unlock(&sb_lock);
                        down_read(&sb->s_umount);
+                       /* still alive? */
                        if (sb->s_root)
                                return sb;
                        up_read(&sb->s_umount);
-                       /* restart only when sb is no longer on the list */
+                       /* nope, got unmounted */
                        spin_lock(&sb_lock);
-                       if (__put_super_and_need_restart(sb))
-                               goto rescan;
+                       __put_super(sb);
+                       goto rescan;
                }
        }
        spin_unlock(&sb_lock);
@@ -482,51 +472,43 @@ struct super_block *get_active_super(struct block_device *bdev)
        if (!bdev)
                return NULL;
 
+restart:
        spin_lock(&sb_lock);
        list_for_each_entry(sb, &super_blocks, s_list) {
-               if (sb->s_bdev != bdev)
+               if (list_empty(&sb->s_instances))
                        continue;
-
-               sb->s_count++;
-               spin_unlock(&sb_lock);
-               down_write(&sb->s_umount);
-               if (sb->s_root) {
-                       spin_lock(&sb_lock);
-                       if (sb->s_count > S_BIAS) {
-                               atomic_inc(&sb->s_active);
-                               sb->s_count--;
-                               spin_unlock(&sb_lock);
+               if (sb->s_bdev == bdev) {
+                       if (grab_super(sb)) /* drops sb_lock */
                                return sb;
-                       }
-                       spin_unlock(&sb_lock);
+                       else
+                               goto restart;
                }
-               up_write(&sb->s_umount);
-               put_super(sb);
-               yield();
-               spin_lock(&sb_lock);
        }
        spin_unlock(&sb_lock);
        return NULL;
 }
  
-struct super_block * user_get_super(dev_t dev)
+struct super_block *user_get_super(dev_t dev)
 {
        struct super_block *sb;
 
        spin_lock(&sb_lock);
 rescan:
        list_for_each_entry(sb, &super_blocks, s_list) {
+               if (list_empty(&sb->s_instances))
+                       continue;
                if (sb->s_dev ==  dev) {
                        sb->s_count++;
                        spin_unlock(&sb_lock);
                        down_read(&sb->s_umount);
+                       /* still alive? */
                        if (sb->s_root)
                                return sb;
                        up_read(&sb->s_umount);
-                       /* restart only when sb is no longer on the list */
+                       /* nope, got unmounted */
                        spin_lock(&sb_lock);
-                       if (__put_super_and_need_restart(sb))
-                               goto rescan;
+                       __put_super(sb);
+                       goto rescan;
                }
        }
        spin_unlock(&sb_lock);
@@ -622,24 +604,24 @@ int do_remount_sb(struct super_block *sb, int flags, void *data, int force)
 
 static void do_emergency_remount(struct work_struct *work)
 {
-       struct super_block *sb;
+       struct super_block *sb, *n;
 
        spin_lock(&sb_lock);
-       list_for_each_entry(sb, &super_blocks, s_list) {
+       list_for_each_entry_safe(sb, n, &super_blocks, s_list) {
+               if (list_empty(&sb->s_instances))
+                       continue;
                sb->s_count++;
                spin_unlock(&sb_lock);
                down_write(&sb->s_umount);
                if (sb->s_root && sb->s_bdev && !(sb->s_flags & MS_RDONLY)) {
                        /*
-                        * ->remount_fs needs lock_kernel().
-                        *
                         * What lock protects sb->s_flags??
                         */
                        do_remount_sb(sb, MS_RDONLY, NULL, 1);
                }
                up_write(&sb->s_umount);
-               put_super(sb);
                spin_lock(&sb_lock);
+               __put_super(sb);
        }
        spin_unlock(&sb_lock);
        kfree(work);