Btrfs: add btrfs_trim_fs() to handle FITRIM
authorLi Dongyang <lidongyang@novell.com>
Thu, 24 Mar 2011 10:24:28 +0000 (10:24 +0000)
committerroot <Chris Mason chris.mason@oracle.com>
Mon, 28 Mar 2011 09:37:47 +0000 (05:37 -0400)
We take an free extent out from allocator, trim it, then put it back,
but before we trim the block group, we should make sure the block group is
cached, so plus a little change to make cache_block_group() run without a
transaction.

Signed-off-by: Li Dongyang <lidongyang@novell.com>
Signed-off-by: Chris Mason <chris.mason@oracle.com>
fs/btrfs/ctree.h
fs/btrfs/extent-tree.c
fs/btrfs/free-space-cache.c
fs/btrfs/free-space-cache.h
fs/btrfs/ioctl.c

index a18b7bc2b22ce1554aa295bab3dbccbc828bc013..93a0191aded66c6661c17a99038e742f3f8e76de 100644 (file)
@@ -2232,6 +2232,7 @@ int btrfs_error_discard_extent(struct btrfs_root *root, u64 bytenr,
                               u64 num_bytes, u64 *actual_bytes);
 int btrfs_force_chunk_alloc(struct btrfs_trans_handle *trans,
                            struct btrfs_root *root, u64 type);
+int btrfs_trim_fs(struct btrfs_root *root, struct fstrim_range *range);
 
 /* ctree.c */
 int btrfs_bin_search(struct extent_buffer *eb, struct btrfs_key *key,
index e990d2d1ba4ade0fc40660d4d4114a792564988a..1efeda3b2f6f95492c036ec7fd53c021138fb15f 100644 (file)
@@ -440,7 +440,7 @@ static int cache_block_group(struct btrfs_block_group_cache *cache,
         * allocate blocks for the tree root we can't do the fast caching since
         * we likely hold important locks.
         */
-       if (!trans->transaction->in_commit &&
+       if (trans && (!trans->transaction->in_commit) &&
            (root && root != root->fs_info->tree_root)) {
                spin_lock(&cache->lock);
                if (cache->cached != BTRFS_CACHE_NO) {
@@ -8778,3 +8778,51 @@ int btrfs_error_discard_extent(struct btrfs_root *root, u64 bytenr,
 {
        return btrfs_discard_extent(root, bytenr, num_bytes, actual_bytes);
 }
+
+int btrfs_trim_fs(struct btrfs_root *root, struct fstrim_range *range)
+{
+       struct btrfs_fs_info *fs_info = root->fs_info;
+       struct btrfs_block_group_cache *cache = NULL;
+       u64 group_trimmed;
+       u64 start;
+       u64 end;
+       u64 trimmed = 0;
+       int ret = 0;
+
+       cache = btrfs_lookup_block_group(fs_info, range->start);
+
+       while (cache) {
+               if (cache->key.objectid >= (range->start + range->len)) {
+                       btrfs_put_block_group(cache);
+                       break;
+               }
+
+               start = max(range->start, cache->key.objectid);
+               end = min(range->start + range->len,
+                               cache->key.objectid + cache->key.offset);
+
+               if (end - start >= range->minlen) {
+                       if (!block_group_cache_done(cache)) {
+                               ret = cache_block_group(cache, NULL, root, 0);
+                               if (!ret)
+                                       wait_block_group_cache_done(cache);
+                       }
+                       ret = btrfs_trim_block_group(cache,
+                                                    &group_trimmed,
+                                                    start,
+                                                    end,
+                                                    range->minlen);
+
+                       trimmed += group_trimmed;
+                       if (ret) {
+                               btrfs_put_block_group(cache);
+                               break;
+                       }
+               }
+
+               cache = next_block_group(fs_info->tree_root, cache);
+       }
+
+       range->len = trimmed;
+       return ret;
+}
index f03ef97c3b21d3a924884af859c9d416d8a8f533..0037427d8a9d27b6a14b980898b01ca39209f51a 100644 (file)
@@ -2178,3 +2178,95 @@ void btrfs_init_free_cluster(struct btrfs_free_cluster *cluster)
        cluster->block_group = NULL;
 }
 
+int btrfs_trim_block_group(struct btrfs_block_group_cache *block_group,
+                          u64 *trimmed, u64 start, u64 end, u64 minlen)
+{
+       struct btrfs_free_space *entry = NULL;
+       struct btrfs_fs_info *fs_info = block_group->fs_info;
+       u64 bytes = 0;
+       u64 actually_trimmed;
+       int ret = 0;
+
+       *trimmed = 0;
+
+       while (start < end) {
+               spin_lock(&block_group->tree_lock);
+
+               if (block_group->free_space < minlen) {
+                       spin_unlock(&block_group->tree_lock);
+                       break;
+               }
+
+               entry = tree_search_offset(block_group, start, 0, 1);
+               if (!entry)
+                       entry = tree_search_offset(block_group,
+                                                  offset_to_bitmap(block_group,
+                                                                   start),
+                                                  1, 1);
+
+               if (!entry || entry->offset >= end) {
+                       spin_unlock(&block_group->tree_lock);
+                       break;
+               }
+
+               if (entry->bitmap) {
+                       ret = search_bitmap(block_group, entry, &start, &bytes);
+                       if (!ret) {
+                               if (start >= end) {
+                                       spin_unlock(&block_group->tree_lock);
+                                       break;
+                               }
+                               bytes = min(bytes, end - start);
+                               bitmap_clear_bits(block_group, entry,
+                                                 start, bytes);
+                               if (entry->bytes == 0)
+                                       free_bitmap(block_group, entry);
+                       } else {
+                               start = entry->offset + BITS_PER_BITMAP *
+                                       block_group->sectorsize;
+                               spin_unlock(&block_group->tree_lock);
+                               ret = 0;
+                               continue;
+                       }
+               } else {
+                       start = entry->offset;
+                       bytes = min(entry->bytes, end - start);
+                       unlink_free_space(block_group, entry);
+                       kfree(entry);
+               }
+
+               spin_unlock(&block_group->tree_lock);
+
+               if (bytes >= minlen) {
+                       int update_ret;
+                       update_ret = btrfs_update_reserved_bytes(block_group,
+                                                                bytes, 1, 1);
+
+                       ret = btrfs_error_discard_extent(fs_info->extent_root,
+                                                        start,
+                                                        bytes,
+                                                        &actually_trimmed);
+
+                       btrfs_add_free_space(block_group,
+                                            start, bytes);
+                       if (!update_ret)
+                               btrfs_update_reserved_bytes(block_group,
+                                                           bytes, 0, 1);
+
+                       if (ret)
+                               break;
+                       *trimmed += actually_trimmed;
+               }
+               start += bytes;
+               bytes = 0;
+
+               if (fatal_signal_pending(current)) {
+                       ret = -ERESTARTSYS;
+                       break;
+               }
+
+               cond_resched();
+       }
+
+       return ret;
+}
index e49ca5c321b571e5886c557e76d131441f1acc87..65c3b935289f3814747947e8967e091cf0e4b1a3 100644 (file)
@@ -68,4 +68,6 @@ u64 btrfs_alloc_from_cluster(struct btrfs_block_group_cache *block_group,
 int btrfs_return_cluster_to_free_space(
                               struct btrfs_block_group_cache *block_group,
                               struct btrfs_free_cluster *cluster);
+int btrfs_trim_block_group(struct btrfs_block_group_cache *block_group,
+                          u64 *trimmed, u64 start, u64 end, u64 minlen);
 #endif
index 32c980ae0f1c63a601681df1273fa9c6bb4db1cd..649f47d2afb41302850904a9f3b19fd8633de4aa 100644 (file)
@@ -40,6 +40,7 @@
 #include <linux/xattr.h>
 #include <linux/vmalloc.h>
 #include <linux/slab.h>
+#include <linux/blkdev.h>
 #include "compat.h"
 #include "ctree.h"
 #include "disk-io.h"
@@ -258,6 +259,49 @@ static int btrfs_ioctl_getversion(struct file *file, int __user *arg)
        return put_user(inode->i_generation, arg);
 }
 
+static noinline int btrfs_ioctl_fitrim(struct file *file, void __user *arg)
+{
+       struct btrfs_root *root = fdentry(file)->d_sb->s_fs_info;
+       struct btrfs_fs_info *fs_info = root->fs_info;
+       struct btrfs_device *device;
+       struct request_queue *q;
+       struct fstrim_range range;
+       u64 minlen = ULLONG_MAX;
+       u64 num_devices = 0;
+       int ret;
+
+       if (!capable(CAP_SYS_ADMIN))
+               return -EPERM;
+
+       mutex_lock(&fs_info->fs_devices->device_list_mutex);
+       list_for_each_entry(device, &fs_info->fs_devices->devices, dev_list) {
+               if (!device->bdev)
+                       continue;
+               q = bdev_get_queue(device->bdev);
+               if (blk_queue_discard(q)) {
+                       num_devices++;
+                       minlen = min((u64)q->limits.discard_granularity,
+                                    minlen);
+               }
+       }
+       mutex_unlock(&fs_info->fs_devices->device_list_mutex);
+       if (!num_devices)
+               return -EOPNOTSUPP;
+
+       if (copy_from_user(&range, arg, sizeof(range)))
+               return -EFAULT;
+
+       range.minlen = max(range.minlen, minlen);
+       ret = btrfs_trim_fs(root, &range);
+       if (ret < 0)
+               return ret;
+
+       if (copy_to_user(arg, &range, sizeof(range)))
+               return -EFAULT;
+
+       return 0;
+}
+
 static noinline int create_subvol(struct btrfs_root *root,
                                  struct dentry *dentry,
                                  char *name, int namelen,
@@ -2426,6 +2470,8 @@ long btrfs_ioctl(struct file *file, unsigned int
                return btrfs_ioctl_setflags(file, argp);
        case FS_IOC_GETVERSION:
                return btrfs_ioctl_getversion(file, argp);
+       case FITRIM:
+               return btrfs_ioctl_fitrim(file, argp);
        case BTRFS_IOC_SNAP_CREATE:
                return btrfs_ioctl_snap_create(file, argp, 0);
        case BTRFS_IOC_SNAP_CREATE_V2: