block: Do not discard buffers under a mounted filesystem
[linux-2.6-microblaze.git] / fs / block_dev.c
index 8ae833e..c70c41e 100644 (file)
@@ -103,6 +103,35 @@ void invalidate_bdev(struct block_device *bdev)
 }
 EXPORT_SYMBOL(invalidate_bdev);
 
+/*
+ * Drop all buffers & page cache for given bdev range. This function bails
+ * with error if bdev has other exclusive owner (such as filesystem).
+ */
+int truncate_bdev_range(struct block_device *bdev, fmode_t mode,
+                       loff_t lstart, loff_t lend)
+{
+       struct block_device *claimed_bdev = NULL;
+       int err;
+
+       /*
+        * If we don't hold exclusive handle for the device, upgrade to it
+        * while we discard the buffer cache to avoid discarding buffers
+        * under live filesystem.
+        */
+       if (!(mode & FMODE_EXCL)) {
+               claimed_bdev = bdev->bd_contains;
+               err = bd_prepare_to_claim(bdev, claimed_bdev,
+                                         truncate_bdev_range);
+               if (err)
+                       return err;
+       }
+       truncate_inode_pages_range(bdev->bd_inode->i_mapping, lstart, lend);
+       if (claimed_bdev)
+               bd_abort_claiming(bdev, claimed_bdev, truncate_bdev_range);
+       return 0;
+}
+EXPORT_SYMBOL(truncate_bdev_range);
+
 static void set_init_blocksize(struct block_device *bdev)
 {
        bdev->bd_inode->i_blkbits = blksize_bits(bdev_logical_block_size(bdev));
@@ -876,11 +905,12 @@ struct block_device *bdget(dev_t dev)
        bdev = &BDEV_I(inode)->bdev;
 
        if (inode->i_state & I_NEW) {
+               spin_lock_init(&bdev->bd_size_lock);
                bdev->bd_contains = NULL;
                bdev->bd_super = NULL;
                bdev->bd_inode = inode;
                bdev->bd_part_count = 0;
-               bdev->bd_invalidated = 0;
+               bdev->bd_flags = 0;
                inode->i_mode = S_IFBLK;
                inode->i_rdev = dev;
                inode->i_bdev = bdev;
@@ -1290,6 +1320,7 @@ static void check_disk_size_change(struct gendisk *disk,
 {
        loff_t disk_size, bdev_size;
 
+       spin_lock(&bdev->bd_size_lock);
        disk_size = (loff_t)get_capacity(disk) << 9;
        bdev_size = i_size_read(bdev->bd_inode);
        if (disk_size != bdev_size) {
@@ -1299,46 +1330,43 @@ static void check_disk_size_change(struct gendisk *disk,
                               disk->disk_name, bdev_size, disk_size);
                }
                i_size_write(bdev->bd_inode, disk_size);
-               if (bdev_size > disk_size && __invalidate_device(bdev, false))
+       }
+       spin_unlock(&bdev->bd_size_lock);
+
+       if (bdev_size > disk_size) {
+               if (__invalidate_device(bdev, false))
                        pr_warn("VFS: busy inodes on resized disk %s\n",
                                disk->disk_name);
        }
-       bdev->bd_invalidated = 0;
 }
 
 /**
- * revalidate_disk - wrapper for lower-level driver's revalidate_disk call-back
- * @disk: struct gendisk to be revalidated
+ * revalidate_disk_size - checks for disk size change and adjusts bdev size.
+ * @disk: struct gendisk to check
+ * @verbose: if %true log a message about a size change if there is any
  *
- * This routine is a wrapper for lower-level driver's revalidate_disk
- * call-backs.  It is used to do common pre and post operations needed
- * for all revalidate_disk operations.
+ * This routine checks to see if the bdev size does not match the disk size
+ * and adjusts it if it differs. When shrinking the bdev size, its all caches
+ * are freed.
  */
-int revalidate_disk(struct gendisk *disk)
+void revalidate_disk_size(struct gendisk *disk, bool verbose)
 {
-       int ret = 0;
-
-       if (disk->fops->revalidate_disk)
-               ret = disk->fops->revalidate_disk(disk);
+       struct block_device *bdev;
 
        /*
         * Hidden disks don't have associated bdev so there's no point in
-        * revalidating it.
+        * revalidating them.
         */
-       if (!(disk->flags & GENHD_FL_HIDDEN)) {
-               struct block_device *bdev = bdget_disk(disk, 0);
-
-               if (!bdev)
-                       return ret;
+       if (disk->flags & GENHD_FL_HIDDEN)
+               return;
 
-               mutex_lock(&bdev->bd_mutex);
-               check_disk_size_change(disk, bdev, ret == 0);
-               mutex_unlock(&bdev->bd_mutex);
+       bdev = bdget_disk(disk, 0);
+       if (bdev) {
+               check_disk_size_change(disk, bdev, verbose);
                bdput(bdev);
        }
-       return ret;
 }
-EXPORT_SYMBOL(revalidate_disk);
+EXPORT_SYMBOL(revalidate_disk_size);
 
 /*
  * This routine checks whether a removable media has been changed,
@@ -1363,7 +1391,7 @@ int check_disk_change(struct block_device *bdev)
        if (__invalidate_device(bdev, true))
                pr_warn("VFS: busy inodes on changed media %s\n",
                        disk->disk_name);
-       bdev->bd_invalidated = 1;
+       set_bit(BDEV_NEED_PART_SCAN, &bdev->bd_flags);
        if (bdops->revalidate_disk)
                bdops->revalidate_disk(bdev->bd_disk);
        return 1;
@@ -1371,13 +1399,13 @@ int check_disk_change(struct block_device *bdev)
 
 EXPORT_SYMBOL(check_disk_change);
 
-void bd_set_size(struct block_device *bdev, loff_t size)
+void bd_set_nr_sectors(struct block_device *bdev, sector_t sectors)
 {
-       inode_lock(bdev->bd_inode);
-       i_size_write(bdev->bd_inode, size);
-       inode_unlock(bdev->bd_inode);
+       spin_lock(&bdev->bd_size_lock);
+       i_size_write(bdev->bd_inode, (loff_t)sectors << SECTOR_SHIFT);
+       spin_unlock(&bdev->bd_size_lock);
 }
-EXPORT_SYMBOL(bd_set_size);
+EXPORT_SYMBOL(bd_set_nr_sectors);
 
 static void __blkdev_put(struct block_device *bdev, fmode_t mode, int for_part);
 
@@ -1388,6 +1416,8 @@ int bdev_disk_changed(struct block_device *bdev, bool invalidate)
 
        lockdep_assert_held(&bdev->bd_mutex);
 
+       clear_bit(BDEV_NEED_PART_SCAN, &bdev->bd_flags);
+
 rescan:
        ret = blk_drop_partitions(bdev);
        if (ret)
@@ -1446,22 +1476,8 @@ static int __blkdev_get(struct block_device *bdev, fmode_t mode, void *holder,
        struct gendisk *disk;
        int ret;
        int partno;
-       int perm = 0;
        bool first_open = false, unblock_events = true, need_restart;
 
-       if (mode & FMODE_READ)
-               perm |= MAY_READ;
-       if (mode & FMODE_WRITE)
-               perm |= MAY_WRITE;
-       /*
-        * hooks: /n/, see "layering violations".
-        */
-       if (!for_part) {
-               ret = devcgroup_inode_permission(bdev->bd_inode, perm);
-               if (ret != 0)
-                       return ret;
-       }
-
  restart:
        need_restart = false;
        ret = -ENXIO;
@@ -1514,7 +1530,7 @@ static int __blkdev_get(struct block_device *bdev, fmode_t mode, void *holder,
                        }
 
                        if (!ret) {
-                               bd_set_size(bdev,(loff_t)get_capacity(disk)<<9);
+                               bd_set_nr_sectors(bdev, get_capacity(disk));
                                set_init_blocksize(bdev);
                        }
 
@@ -1524,7 +1540,7 @@ static int __blkdev_get(struct block_device *bdev, fmode_t mode, void *holder,
                         * The latter is necessary to prevent ghost
                         * partitions on a removed medium.
                         */
-                       if (bdev->bd_invalidated &&
+                       if (test_bit(BDEV_NEED_PART_SCAN, &bdev->bd_flags) &&
                            (!ret || ret == -ENOMEDIUM))
                                bdev_disk_changed(bdev, ret == -ENOMEDIUM);
 
@@ -1542,7 +1558,7 @@ static int __blkdev_get(struct block_device *bdev, fmode_t mode, void *holder,
                                ret = -ENXIO;
                                goto out_clear;
                        }
-                       bd_set_size(bdev, (loff_t)bdev->bd_part->nr_sects << 9);
+                       bd_set_nr_sectors(bdev, bdev->bd_part->nr_sects);
                        set_init_blocksize(bdev);
                }
 
@@ -1554,7 +1570,7 @@ static int __blkdev_get(struct block_device *bdev, fmode_t mode, void *holder,
                        if (bdev->bd_disk->fops->open)
                                ret = bdev->bd_disk->fops->open(bdev, mode);
                        /* the same as first opener case, read comment there */
-                       if (bdev->bd_invalidated &&
+                       if (test_bit(BDEV_NEED_PART_SCAN, &bdev->bd_flags) &&
                            (!ret || ret == -ENOMEDIUM))
                                bdev_disk_changed(bdev, ret == -ENOMEDIUM);
                        if (ret)
@@ -1634,12 +1650,24 @@ static int __blkdev_get(struct block_device *bdev, fmode_t mode, void *holder,
  */
 int blkdev_get(struct block_device *bdev, fmode_t mode, void *holder)
 {
-       int res;
+       int ret, perm = 0;
 
-       res =__blkdev_get(bdev, mode, holder, 0);
-       if (res)
-               bdput(bdev);
-       return res;
+       if (mode & FMODE_READ)
+               perm |= MAY_READ;
+       if (mode & FMODE_WRITE)
+               perm |= MAY_WRITE;
+       ret = devcgroup_inode_permission(bdev->bd_inode, perm);
+       if (ret)
+               goto bdput;
+
+       ret =__blkdev_get(bdev, mode, holder, 0);
+       if (ret)
+               goto bdput;
+       return 0;
+
+bdput:
+       bdput(bdev);
+       return ret;
 }
 EXPORT_SYMBOL(blkdev_get);
 
@@ -1969,7 +1997,6 @@ static long blkdev_fallocate(struct file *file, int mode, loff_t start,
                             loff_t len)
 {
        struct block_device *bdev = I_BDEV(bdev_file_inode(file));
-       struct address_space *mapping;
        loff_t end = start + len - 1;
        loff_t isize;
        int error;
@@ -1997,8 +2024,9 @@ static long blkdev_fallocate(struct file *file, int mode, loff_t start,
                return -EINVAL;
 
        /* Invalidate the page cache, including dirty pages. */
-       mapping = bdev->bd_inode->i_mapping;
-       truncate_inode_pages_range(mapping, start, end);
+       error = truncate_bdev_range(bdev, file->f_mode, start, end);
+       if (error)
+               return error;
 
        switch (mode) {
        case FALLOC_FL_ZERO_RANGE:
@@ -2025,7 +2053,7 @@ static long blkdev_fallocate(struct file *file, int mode, loff_t start,
         * the caller will be given -EBUSY.  The third argument is
         * inclusive, so the rounding here is safe.
         */
-       return invalidate_inode_pages2_range(mapping,
+       return invalidate_inode_pages2_range(bdev->bd_inode->i_mapping,
                                             start >> PAGE_SHIFT,
                                             end >> PAGE_SHIFT);
 }