]> nv-tegra.nvidia Code Review - linux-2.6.git/blobdiff - fs/block_dev.c
slub: Fix full list corruption if debugging is on
[linux-2.6.git] / fs / block_dev.c
index 6017389711eec8d620d4e132591bcce068a68aa4..ff77262e887cb66819f31d781e679eccb2d1975a 100644 (file)
@@ -11,7 +11,6 @@
 #include <linux/slab.h>
 #include <linux/kmod.h>
 #include <linux/major.h>
-#include <linux/smp_lock.h>
 #include <linux/device_cgroup.h>
 #include <linux/highmem.h>
 #include <linux/blkdev.h>
@@ -45,22 +44,28 @@ inline struct block_device *I_BDEV(struct inode *inode)
 {
        return &BDEV_I(inode)->bdev;
 }
-
 EXPORT_SYMBOL(I_BDEV);
 
 /*
- * move the inode from it's current bdi to the a new bdi. if the inode is dirty
- * we need to move it onto the dirty list of @dst so that the inode is always
- * on the right list.
+ * Move the inode from its current bdi to a new bdi. If the inode is dirty we
+ * need to move it onto the dirty list of @dst so that the inode is always on
+ * the right list.
  */
 static void bdev_inode_switch_bdi(struct inode *inode,
                        struct backing_dev_info *dst)
 {
-       spin_lock(&inode_lock);
+       struct backing_dev_info *old = inode->i_data.backing_dev_info;
+
+       if (unlikely(dst == old))               /* deadlock avoidance */
+               return;
+       bdi_lock_two(&old->wb, &dst->wb);
+       spin_lock(&inode->i_lock);
        inode->i_data.backing_dev_info = dst;
        if (inode->i_state & I_DIRTY)
                list_move(&inode->i_wb_list, &dst->wb.b_dirty);
-       spin_unlock(&inode_lock);
+       spin_unlock(&inode->i_lock);
+       spin_unlock(&old->wb.list_lock);
+       spin_unlock(&dst->wb.list_lock);
 }
 
 static sector_t max_block(struct block_device *bdev)
@@ -354,43 +359,48 @@ static loff_t block_llseek(struct file *file, loff_t offset, int origin)
        mutex_lock(&bd_inode->i_mutex);
        size = i_size_read(bd_inode);
 
+       retval = -EINVAL;
        switch (origin) {
-               case 2:
+               case SEEK_END:
                        offset += size;
                        break;
-               case 1:
+               case SEEK_CUR:
                        offset += file->f_pos;
+               case SEEK_SET:
+                       break;
+               default:
+                       goto out;
        }
-       retval = -EINVAL;
        if (offset >= 0 && offset <= size) {
                if (offset != file->f_pos) {
                        file->f_pos = offset;
                }
                retval = offset;
        }
+out:
        mutex_unlock(&bd_inode->i_mutex);
        return retval;
 }
        
-int blkdev_fsync(struct file *filp, int datasync)
+int blkdev_fsync(struct file *filp, loff_t start, loff_t end, int datasync)
 {
        struct inode *bd_inode = filp->f_mapping->host;
        struct block_device *bdev = I_BDEV(bd_inode);
        int error;
+       
+       error = filemap_write_and_wait_range(filp->f_mapping, start, end);
+       if (error)
+               return error;
 
        /*
         * There is no need to serialise calls to blkdev_issue_flush with
         * i_mutex and doing so causes performance issues with concurrent
         * O_SYNC writers to a block device.
         */
-       mutex_unlock(&bd_inode->i_mutex);
-
        error = blkdev_issue_flush(bdev, GFP_KERNEL, NULL);
        if (error == -EOPNOTSUPP)
                error = 0;
 
-       mutex_lock(&bd_inode->i_mutex);
-
        return error;
 }
 EXPORT_SYMBOL(blkdev_fsync);
@@ -410,13 +420,20 @@ static struct inode *bdev_alloc_inode(struct super_block *sb)
        return &ei->vfs_inode;
 }
 
-static void bdev_destroy_inode(struct inode *inode)
+static void bdev_i_callback(struct rcu_head *head)
 {
+       struct inode *inode = container_of(head, struct inode, i_rcu);
        struct bdev_inode *bdi = BDEV_I(inode);
 
+       INIT_LIST_HEAD(&inode->i_dentry);
        kmem_cache_free(bdev_cachep, bdi);
 }
 
+static void bdev_destroy_inode(struct inode *inode)
+{
+       call_rcu(&inode->i_rcu, bdev_i_callback);
+}
+
 static void init_once(void *foo)
 {
        struct bdev_inode *ei = (struct bdev_inode *) foo;
@@ -426,6 +443,9 @@ static void init_once(void *foo)
        mutex_init(&bdev->bd_mutex);
        INIT_LIST_HEAD(&bdev->bd_inodes);
        INIT_LIST_HEAD(&bdev->bd_list);
+#ifdef CONFIG_SYSFS
+       INIT_LIST_HEAD(&bdev->bd_holder_disks);
+#endif
        inode_init_once(&ei->vfs_inode);
        /* Initialize mutex for freeze. */
        mutex_init(&bdev->bd_fsfreeze_mutex);
@@ -464,7 +484,7 @@ static const struct super_operations bdev_sops = {
 static struct dentry *bd_mount(struct file_system_type *fs_type,
        int flags, const char *dev_name, void *data)
 {
-       return mount_pseudo(fs_type, "bdev:", &bdev_sops, 0x62646576);
+       return mount_pseudo(fs_type, "bdev:", &bdev_sops, NULL, 0x62646576);
 }
 
 static struct file_system_type bd_type = {
@@ -536,6 +556,7 @@ struct block_device *bdget(dev_t dev)
 
        if (inode->i_state & I_NEW) {
                bdev->bd_contains = NULL;
+               bdev->bd_super = NULL;
                bdev->bd_inode = inode;
                bdev->bd_block_size = (1 << inode->i_blkbits);
                bdev->bd_part_count = 0;
@@ -642,7 +663,7 @@ void bd_forget(struct inode *inode)
  * @whole: whole block device containing @bdev, may equal @bdev
  * @holder: holder trying to claim @bdev
  *
- * Test whther @bdev can be claimed by @holder.
+ * Test whether @bdev can be claimed by @holder.
  *
  * CONTEXT:
  * spin_lock(&bdev_lock).
@@ -751,7 +772,19 @@ static struct block_device *bd_start_claiming(struct block_device *bdev,
        if (!disk)
                return ERR_PTR(-ENXIO);
 
-       whole = bdget_disk(disk, 0);
+       /*
+        * Normally, @bdev should equal what's returned from bdget_disk()
+        * if partno is 0; however, some drivers (floppy) use multiple
+        * bdev's for the same physical device and @bdev may be one of the
+        * aliases.  Keep @bdev if partno is 0.  This means claimer
+        * tracking is broken for those devices but it has always been that
+        * way.
+        */
+       if (partno)
+               whole = bdget_disk(disk, 0);
+       else
+               whole = bdgrab(bdev);
+
        module_put(disk->fops->owner);
        put_disk(disk);
        if (!whole)
@@ -773,6 +806,23 @@ static struct block_device *bd_start_claiming(struct block_device *bdev,
 }
 
 #ifdef CONFIG_SYSFS
+struct bd_holder_disk {
+       struct list_head        list;
+       struct gendisk          *disk;
+       int                     refcnt;
+};
+
+static struct bd_holder_disk *bd_find_holder_disk(struct block_device *bdev,
+                                                 struct gendisk *disk)
+{
+       struct bd_holder_disk *holder;
+
+       list_for_each_entry(holder, &bdev->bd_holder_disks, list)
+               if (holder->disk == disk)
+                       return holder;
+       return NULL;
+}
+
 static int add_symlink(struct kobject *from, struct kobject *to)
 {
        return sysfs_create_link(from, to, kobject_name(to));
@@ -788,6 +838,8 @@ static void del_symlink(struct kobject *from, struct kobject *to)
  * @bdev: the claimed slave bdev
  * @disk: the holding disk
  *
+ * DON'T USE THIS UNLESS YOU'RE ALREADY USING IT.
+ *
  * This functions creates the following sysfs symlinks.
  *
  * - from "slaves" directory of the holder @disk to the claimed @bdev
@@ -811,61 +863,104 @@ static void del_symlink(struct kobject *from, struct kobject *to)
  */
 int bd_link_disk_holder(struct block_device *bdev, struct gendisk *disk)
 {
+       struct bd_holder_disk *holder;
        int ret = 0;
 
        mutex_lock(&bdev->bd_mutex);
 
-       WARN_ON_ONCE(!bdev->bd_holder || bdev->bd_holder_disk);
+       WARN_ON_ONCE(!bdev->bd_holder);
 
        /* FIXME: remove the following once add_disk() handles errors */
        if (WARN_ON(!disk->slave_dir || !bdev->bd_part->holder_dir))
                goto out_unlock;
 
-       ret = add_symlink(disk->slave_dir, &part_to_dev(bdev->bd_part)->kobj);
-       if (ret)
+       holder = bd_find_holder_disk(bdev, disk);
+       if (holder) {
+               holder->refcnt++;
                goto out_unlock;
+       }
 
-       ret = add_symlink(bdev->bd_part->holder_dir, &disk_to_dev(disk)->kobj);
-       if (ret) {
-               del_symlink(disk->slave_dir, &part_to_dev(bdev->bd_part)->kobj);
+       holder = kzalloc(sizeof(*holder), GFP_KERNEL);
+       if (!holder) {
+               ret = -ENOMEM;
                goto out_unlock;
        }
 
-       bdev->bd_holder_disk = disk;
+       INIT_LIST_HEAD(&holder->list);
+       holder->disk = disk;
+       holder->refcnt = 1;
+
+       ret = add_symlink(disk->slave_dir, &part_to_dev(bdev->bd_part)->kobj);
+       if (ret)
+               goto out_free;
+
+       ret = add_symlink(bdev->bd_part->holder_dir, &disk_to_dev(disk)->kobj);
+       if (ret)
+               goto out_del;
+       /*
+        * bdev could be deleted beneath us which would implicitly destroy
+        * the holder directory.  Hold on to it.
+        */
+       kobject_get(bdev->bd_part->holder_dir);
+
+       list_add(&holder->list, &bdev->bd_holder_disks);
+       goto out_unlock;
+
+out_del:
+       del_symlink(disk->slave_dir, &part_to_dev(bdev->bd_part)->kobj);
+out_free:
+       kfree(holder);
 out_unlock:
        mutex_unlock(&bdev->bd_mutex);
        return ret;
 }
 EXPORT_SYMBOL_GPL(bd_link_disk_holder);
 
-static void bd_unlink_disk_holder(struct block_device *bdev)
+/**
+ * bd_unlink_disk_holder - destroy symlinks created by bd_link_disk_holder()
+ * @bdev: the calimed slave bdev
+ * @disk: the holding disk
+ *
+ * DON'T USE THIS UNLESS YOU'RE ALREADY USING IT.
+ *
+ * CONTEXT:
+ * Might sleep.
+ */
+void bd_unlink_disk_holder(struct block_device *bdev, struct gendisk *disk)
 {
-       struct gendisk *disk = bdev->bd_holder_disk;
+       struct bd_holder_disk *holder;
 
-       bdev->bd_holder_disk = NULL;
-       if (!disk)
-               return;
+       mutex_lock(&bdev->bd_mutex);
 
-       del_symlink(disk->slave_dir, &part_to_dev(bdev->bd_part)->kobj);
-       del_symlink(bdev->bd_part->holder_dir, &disk_to_dev(disk)->kobj);
+       holder = bd_find_holder_disk(bdev, disk);
+
+       if (!WARN_ON_ONCE(holder == NULL) && !--holder->refcnt) {
+               del_symlink(disk->slave_dir, &part_to_dev(bdev->bd_part)->kobj);
+               del_symlink(bdev->bd_part->holder_dir,
+                           &disk_to_dev(disk)->kobj);
+               kobject_put(bdev->bd_part->holder_dir);
+               list_del_init(&holder->list);
+               kfree(holder);
+       }
+
+       mutex_unlock(&bdev->bd_mutex);
 }
-#else
-static inline void bd_unlink_disk_holder(struct block_device *bdev)
-{ }
+EXPORT_SYMBOL_GPL(bd_unlink_disk_holder);
 #endif
 
 /**
  * flush_disk - invalidates all buffer-cache entries on a disk
  *
  * @bdev:      struct block device to be flushed
+ * @kill_dirty: flag to guide handling of dirty inodes
  *
  * Invalidates all buffer-cache entries on a disk. It should be called
  * when a disk has been changed -- either by a media change or online
  * resize.
  */
-static void flush_disk(struct block_device *bdev)
+static void flush_disk(struct block_device *bdev, bool kill_dirty)
 {
-       if (__invalidate_device(bdev)) {
+       if (__invalidate_device(bdev, kill_dirty)) {
                char name[BDEVNAME_SIZE] = "";
 
                if (bdev->bd_disk)
@@ -902,7 +997,7 @@ void check_disk_size_change(struct gendisk *disk, struct block_device *bdev)
                       "%s: detected capacity change from %lld to %lld\n",
                       name, bdev_size, disk_size);
                i_size_write(bdev->bd_inode, disk_size);
-               flush_disk(bdev);
+               flush_disk(bdev, false);
        }
 }
 EXPORT_SYMBOL(check_disk_size_change);
@@ -955,7 +1050,7 @@ int check_disk_change(struct block_device *bdev)
        if (!(events & DISK_EVENT_MEDIA_CHANGE))
                return 0;
 
-       flush_disk(bdev);
+       flush_disk(bdev, true);
        if (bdops->revalidate_disk)
                bdops->revalidate_disk(bdev->bd_disk);
        return 1;
@@ -1016,6 +1111,7 @@ static int __blkdev_get(struct block_device *bdev, fmode_t mode, int for_part)
        if (!disk)
                goto out;
 
+       disk_block_events(disk);
        mutex_lock_nested(&bdev->bd_mutex, for_part);
        if (!bdev->bd_openers) {
                bdev->bd_disk = disk;
@@ -1028,6 +1124,7 @@ static int __blkdev_get(struct block_device *bdev, fmode_t mode, int for_part)
                        if (!bdev->bd_part)
                                goto out_clear;
 
+                       ret = 0;
                        if (disk->fops->open) {
                                ret = disk->fops->open(bdev, mode);
                                if (ret == -ERESTARTSYS) {
@@ -1037,24 +1134,33 @@ static int __blkdev_get(struct block_device *bdev, fmode_t mode, int for_part)
                                         */
                                        disk_put_part(bdev->bd_part);
                                        bdev->bd_part = NULL;
-                                       module_put(disk->fops->owner);
-                                       put_disk(disk);
                                        bdev->bd_disk = NULL;
                                        mutex_unlock(&bdev->bd_mutex);
+                                       disk_unblock_events(disk);
+                                       module_put(disk->fops->owner);
+                                       put_disk(disk);
                                        goto restart;
                                }
-                               if (ret)
-                                       goto out_clear;
                        }
-                       if (!bdev->bd_openers) {
+
+                       if (!ret && !bdev->bd_openers) {
                                bd_set_size(bdev,(loff_t)get_capacity(disk)<<9);
                                bdi = blk_get_backing_dev_info(bdev);
                                if (bdi == NULL)
                                        bdi = &default_backing_dev_info;
                                bdev_inode_switch_bdi(bdev->bd_inode, bdi);
                        }
-                       if (bdev->bd_invalidated)
+
+                       /*
+                        * If the device is invalidated, rescan partition
+                        * if open succeeded or failed with -ENOMEDIUM.
+                        * The latter is necessary to prevent ghost
+                        * partitions on a removed medium.
+                        */
+                       if (bdev->bd_invalidated && (!ret || ret == -ENOMEDIUM))
                                rescan_partitions(disk, bdev);
+                       if (ret)
+                               goto out_clear;
                } else {
                        struct block_device *whole;
                        whole = bdget_disk(disk, 0);
@@ -1077,23 +1183,25 @@ static int __blkdev_get(struct block_device *bdev, fmode_t mode, int for_part)
                        bd_set_size(bdev, (loff_t)bdev->bd_part->nr_sects << 9);
                }
        } else {
-               module_put(disk->fops->owner);
-               put_disk(disk);
-               disk = NULL;
                if (bdev->bd_contains == bdev) {
-                       if (bdev->bd_disk->fops->open) {
+                       ret = 0;
+                       if (bdev->bd_disk->fops->open)
                                ret = bdev->bd_disk->fops->open(bdev, mode);
-                               if (ret)
-                                       goto out_unlock_bdev;
-                       }
-                       if (bdev->bd_invalidated)
+                       /* the same as first opener case, read comment there */
+                       if (bdev->bd_invalidated && (!ret || ret == -ENOMEDIUM))
                                rescan_partitions(bdev->bd_disk, bdev);
+                       if (ret)
+                               goto out_unlock_bdev;
                }
+               /* only one opener holds refs to the module and disk */
+               module_put(disk->fops->owner);
+               put_disk(disk);
        }
        bdev->bd_openers++;
        if (for_part)
                bdev->bd_part_count++;
        mutex_unlock(&bdev->bd_mutex);
+       disk_unblock_events(disk);
        return 0;
 
  out_clear:
@@ -1106,10 +1214,10 @@ static int __blkdev_get(struct block_device *bdev, fmode_t mode, int for_part)
        bdev->bd_contains = NULL;
  out_unlock_bdev:
        mutex_unlock(&bdev->bd_mutex);
- out:
-       if (disk)
-               module_put(disk->fops->owner);
+       disk_unblock_events(disk);
+       module_put(disk->fops->owner);
        put_disk(disk);
+ out:
        bdput(bdev);
 
        return ret;
@@ -1151,13 +1259,9 @@ int blkdev_get(struct block_device *bdev, fmode_t mode, void *holder)
 
        res = __blkdev_get(bdev, mode, 0);
 
-       /* __blkdev_get() may alter read only status, check it afterwards */
-       if (!res && (mode & FMODE_WRITE) && bdev_read_only(bdev)) {
-               __blkdev_put(bdev, mode, 0);
-               res = -EACCES;
-       }
-
        if (whole) {
+               struct gendisk *disk = whole->bd_disk;
+
                /* finish claiming */
                mutex_lock(&bdev->bd_mutex);
                spin_lock(&bdev_lock);
@@ -1184,15 +1288,16 @@ int blkdev_get(struct block_device *bdev, fmode_t mode, void *holder)
                spin_unlock(&bdev_lock);
 
                /*
-                * Block event polling for write claims.  Any write
-                * holder makes the write_holder state stick until all
-                * are released.  This is good enough and tracking
-                * individual writeable reference is too fragile given
-                * the way @mode is used in blkdev_get/put().
+                * Block event polling for write claims if requested.  Any
+                * write holder makes the write_holder state stick until
+                * all are released.  This is good enough and tracking
+                * individual writeable reference is too fragile given the
+                * way @mode is used in blkdev_get/put().
                 */
-               if (!res && (mode & FMODE_WRITE) && !bdev->bd_write_holder) {
+               if (!res && (mode & FMODE_WRITE) && !bdev->bd_write_holder &&
+                   (disk->flags & GENHD_FL_BLOCK_EVENTS_ON_EXCL_WRITE)) {
                        bdev->bd_write_holder = true;
-                       disk_block_events(bdev->bd_disk);
+                       disk_block_events(disk);
                }
 
                mutex_unlock(&bdev->bd_mutex);
@@ -1234,6 +1339,11 @@ struct block_device *blkdev_get_by_path(const char *path, fmode_t mode,
        if (err)
                return ERR_PTR(err);
 
+       if ((mode & FMODE_WRITE) && bdev_read_only(bdev)) {
+               blkdev_put(bdev, mode);
+               return ERR_PTR(-EACCES);
+       }
+
        return bdev;
 }
 EXPORT_SYMBOL(blkdev_get_by_path);
@@ -1347,6 +1457,8 @@ static int __blkdev_put(struct block_device *bdev, fmode_t mode, int for_part)
 
 int blkdev_put(struct block_device *bdev, fmode_t mode)
 {
+       mutex_lock(&bdev->bd_mutex);
+
        if (mode & FMODE_EXCL) {
                bool bdev_free;
 
@@ -1355,7 +1467,6 @@ int blkdev_put(struct block_device *bdev, fmode_t mode)
                 * are protected with bdev_lock.  bd_mutex is to
                 * synchronize disk_holder unlinking.
                 */
-               mutex_lock(&bdev->bd_mutex);
                spin_lock(&bdev_lock);
 
                WARN_ON_ONCE(--bdev->bd_holders < 0);
@@ -1373,18 +1484,20 @@ int blkdev_put(struct block_device *bdev, fmode_t mode)
                 * If this was the last claim, remove holder link and
                 * unblock evpoll if it was a write holder.
                 */
-               if (bdev_free) {
-                       bd_unlink_disk_holder(bdev);
-                       if (bdev->bd_write_holder) {
-                               disk_unblock_events(bdev->bd_disk);
-                               bdev->bd_write_holder = false;
-                       } else
-                               disk_check_events(bdev->bd_disk);
+               if (bdev_free && bdev->bd_write_holder) {
+                       disk_unblock_events(bdev->bd_disk);
+                       bdev->bd_write_holder = false;
                }
+       }
 
-               mutex_unlock(&bdev->bd_mutex);
-       } else
-               disk_check_events(bdev->bd_disk);
+       /*
+        * Trigger event checking and tell drivers to flush MEDIA_CHANGE
+        * event.  This is to ensure detection of media removal commanded
+        * from userland - e.g. eject(1).
+        */
+       disk_flush_events(bdev->bd_disk, DISK_EVENT_MEDIA_CHANGE);
+
+       mutex_unlock(&bdev->bd_mutex);
 
        return __blkdev_put(bdev, mode, 0);
 }
@@ -1458,7 +1571,6 @@ static int blkdev_releasepage(struct page *page, gfp_t wait)
 static const struct address_space_operations def_blk_aops = {
        .readpage       = blkdev_readpage,
        .writepage      = blkdev_writepage,
-       .sync_page      = block_sync_page,
        .write_begin    = blkdev_write_begin,
        .write_end      = blkdev_write_end,
        .writepages     = generic_writepages,
@@ -1538,7 +1650,7 @@ fail:
 }
 EXPORT_SYMBOL(lookup_bdev);
 
-int __invalidate_device(struct block_device *bdev)
+int __invalidate_device(struct block_device *bdev, bool kill_dirty)
 {
        struct super_block *sb = get_super(bdev);
        int res = 0;
@@ -1551,7 +1663,7 @@ int __invalidate_device(struct block_device *bdev)
                 * hold).
                 */
                shrink_dcache_sb(sb);
-               res = invalidate_inodes(sb);
+               res = invalidate_inodes(sb, kill_dirty);
                drop_super(sb);
        }
        invalidate_bdev(bdev);