btrfs: add helper to describe block group flags
[sfrench/cifs-2.6.git] / fs / btrfs / volumes.c
index 032310dacf7788c3c1b9d919764c3adc3d19e93c..bb38b86b837441a33a70edd286271c7723d3febe 100644 (file)
@@ -130,6 +130,60 @@ const char *get_raid_name(enum btrfs_raid_types type)
        return btrfs_raid_array[type].raid_name;
 }
 
+/*
+ * Fill @buf with textual description of @bg_flags, no more than @size_buf
+ * bytes including terminating null byte.
+ */
+void btrfs_describe_block_groups(u64 bg_flags, char *buf, u32 size_buf)
+{
+       int i;
+       int ret;
+       char *bp = buf;
+       u64 flags = bg_flags;
+       u32 size_bp = size_buf;
+
+       if (!flags) {
+               strcpy(bp, "NONE");
+               return;
+       }
+
+#define DESCRIBE_FLAG(flag, desc)                                              \
+       do {                                                            \
+               if (flags & (flag)) {                                   \
+                       ret = snprintf(bp, size_bp, "%s|", (desc));     \
+                       if (ret < 0 || ret >= size_bp)                  \
+                               goto out_overflow;                      \
+                       size_bp -= ret;                                 \
+                       bp += ret;                                      \
+                       flags &= ~(flag);                               \
+               }                                                       \
+       } while (0)
+
+       DESCRIBE_FLAG(BTRFS_BLOCK_GROUP_DATA, "data");
+       DESCRIBE_FLAG(BTRFS_BLOCK_GROUP_SYSTEM, "system");
+       DESCRIBE_FLAG(BTRFS_BLOCK_GROUP_METADATA, "metadata");
+
+       DESCRIBE_FLAG(BTRFS_AVAIL_ALLOC_BIT_SINGLE, "single");
+       for (i = 0; i < BTRFS_NR_RAID_TYPES; i++)
+               DESCRIBE_FLAG(btrfs_raid_array[i].bg_flag,
+                             btrfs_raid_array[i].raid_name);
+#undef DESCRIBE_FLAG
+
+       if (flags) {
+               ret = snprintf(bp, size_bp, "0x%llx|", flags);
+               size_bp -= ret;
+       }
+
+       if (size_bp < size_buf)
+               buf[size_buf - size_bp - 1] = '\0'; /* remove last | */
+
+       /*
+        * The text is trimmed, it's up to the caller to provide sufficiently
+        * large buffer
+        */
+out_overflow:;
+}
+
 static int init_first_rw_device(struct btrfs_trans_handle *trans,
                                struct btrfs_fs_info *fs_info);
 static int btrfs_relocate_sys_chunks(struct btrfs_fs_info *fs_info);
@@ -389,6 +443,40 @@ static noinline struct btrfs_fs_devices *find_fsid(
 
        ASSERT(fsid);
 
+       if (metadata_fsid) {
+               /*
+                * Handle scanned device having completed its fsid change but
+                * belonging to a fs_devices that was created by first scanning
+                * a device which didn't have its fsid/metadata_uuid changed
+                * at all and the CHANGING_FSID_V2 flag set.
+                */
+               list_for_each_entry(fs_devices, &fs_uuids, fs_list) {
+                       if (fs_devices->fsid_change &&
+                           memcmp(metadata_fsid, fs_devices->fsid,
+                                  BTRFS_FSID_SIZE) == 0 &&
+                           memcmp(fs_devices->fsid, fs_devices->metadata_uuid,
+                                  BTRFS_FSID_SIZE) == 0) {
+                               return fs_devices;
+                       }
+               }
+               /*
+                * Handle scanned device having completed its fsid change but
+                * belonging to a fs_devices that was created by a device that
+                * has an outdated pair of fsid/metadata_uuid and
+                * CHANGING_FSID_V2 flag set.
+                */
+               list_for_each_entry(fs_devices, &fs_uuids, fs_list) {
+                       if (fs_devices->fsid_change &&
+                           memcmp(fs_devices->metadata_uuid,
+                                  fs_devices->fsid, BTRFS_FSID_SIZE) != 0 &&
+                           memcmp(metadata_fsid, fs_devices->metadata_uuid,
+                                  BTRFS_FSID_SIZE) == 0) {
+                               return fs_devices;
+                       }
+               }
+       }
+
+       /* Handle non-split brain cases */
        list_for_each_entry(fs_devices, &fs_uuids, fs_list) {
                if (metadata_fsid) {
                        if (memcmp(fsid, fs_devices->fsid, BTRFS_FSID_SIZE) == 0
@@ -774,6 +862,51 @@ error_brelse:
        return -EINVAL;
 }
 
+/*
+ * Handle scanned device having its CHANGING_FSID_V2 flag set and the fs_devices
+ * being created with a disk that has already completed its fsid change.
+ */
+static struct btrfs_fs_devices *find_fsid_inprogress(
+                                       struct btrfs_super_block *disk_super)
+{
+       struct btrfs_fs_devices *fs_devices;
+
+       list_for_each_entry(fs_devices, &fs_uuids, fs_list) {
+               if (memcmp(fs_devices->metadata_uuid, fs_devices->fsid,
+                          BTRFS_FSID_SIZE) != 0 &&
+                   memcmp(fs_devices->metadata_uuid, disk_super->fsid,
+                          BTRFS_FSID_SIZE) == 0 && !fs_devices->fsid_change) {
+                       return fs_devices;
+               }
+       }
+
+       return NULL;
+}
+
+
+static struct btrfs_fs_devices *find_fsid_changed(
+                                       struct btrfs_super_block *disk_super)
+{
+       struct btrfs_fs_devices *fs_devices;
+
+       /*
+        * Handles the case where scanned device is part of an fs that had
+        * multiple successful changes of FSID but curently device didn't
+        * observe it. Meaning our fsid will be different than theirs.
+        */
+       list_for_each_entry(fs_devices, &fs_uuids, fs_list) {
+               if (memcmp(fs_devices->metadata_uuid, fs_devices->fsid,
+                          BTRFS_FSID_SIZE) != 0 &&
+                   memcmp(fs_devices->metadata_uuid, disk_super->metadata_uuid,
+                          BTRFS_FSID_SIZE) == 0 &&
+                   memcmp(fs_devices->fsid, disk_super->fsid,
+                          BTRFS_FSID_SIZE) != 0) {
+                       return fs_devices;
+               }
+       }
+
+       return NULL;
+}
 /*
  * Add new device to list of registered devices
  *
@@ -786,17 +919,36 @@ static noinline struct btrfs_device *device_list_add(const char *path,
                           bool *new_device_added)
 {
        struct btrfs_device *device;
-       struct btrfs_fs_devices *fs_devices;
+       struct btrfs_fs_devices *fs_devices = NULL;
        struct rcu_string *name;
        u64 found_transid = btrfs_super_generation(disk_super);
        u64 devid = btrfs_stack_device_id(&disk_super->dev_item);
        bool has_metadata_uuid = (btrfs_super_incompat_flags(disk_super) &
                BTRFS_FEATURE_INCOMPAT_METADATA_UUID);
+       bool fsid_change_in_progress = (btrfs_super_flags(disk_super) &
+                                       BTRFS_SUPER_FLAG_CHANGING_FSID_V2);
 
-       if (has_metadata_uuid)
-               fs_devices = find_fsid(disk_super->fsid, disk_super->metadata_uuid);
-       else
+       if (fsid_change_in_progress) {
+               if (!has_metadata_uuid) {
+                       /*
+                        * When we have an image which has CHANGING_FSID_V2 set
+                        * it might belong to either a filesystem which has
+                        * disks with completed fsid change or it might belong
+                        * to fs with no UUID changes in effect, handle both.
+                        */
+                       fs_devices = find_fsid_inprogress(disk_super);
+                       if (!fs_devices)
+                               fs_devices = find_fsid(disk_super->fsid, NULL);
+               } else {
+                       fs_devices = find_fsid_changed(disk_super);
+               }
+       } else if (has_metadata_uuid) {
+               fs_devices = find_fsid(disk_super->fsid,
+                                      disk_super->metadata_uuid);
+       } else {
                fs_devices = find_fsid(disk_super->fsid, NULL);
+       }
+
 
        if (!fs_devices) {
                if (has_metadata_uuid)
@@ -805,6 +957,8 @@ static noinline struct btrfs_device *device_list_add(const char *path,
                else
                        fs_devices = alloc_fs_devices(disk_super->fsid, NULL);
 
+               fs_devices->fsid_change = fsid_change_in_progress;
+
                if (IS_ERR(fs_devices))
                        return ERR_CAST(fs_devices);
 
@@ -816,6 +970,21 @@ static noinline struct btrfs_device *device_list_add(const char *path,
                mutex_lock(&fs_devices->device_list_mutex);
                device = find_device(fs_devices, devid,
                                disk_super->dev_item.uuid);
+
+               /*
+                * If this disk has been pulled into an fs devices created by
+                * a device which had the CHANGING_FSID_V2 flag then replace the
+                * metadata_uuid/fsid values of the fs_devices.
+                */
+               if (has_metadata_uuid && fs_devices->fsid_change &&
+                   found_transid > fs_devices->latest_generation) {
+                       memcpy(fs_devices->fsid, disk_super->fsid,
+                                       BTRFS_FSID_SIZE);
+                       memcpy(fs_devices->metadata_uuid,
+                                       disk_super->metadata_uuid, BTRFS_FSID_SIZE);
+
+                       fs_devices->fsid_change = false;
+               }
        }
 
        if (!device) {
@@ -940,8 +1109,11 @@ static noinline struct btrfs_device *device_list_add(const char *path,
         * it back. We need it to pick the disk with largest generation
         * (as above).
         */
-       if (!fs_devices->opened)
+       if (!fs_devices->opened) {
                device->generation = found_transid;
+               fs_devices->latest_generation = max_t(u64, found_transid,
+                                               fs_devices->latest_generation);
+       }
 
        fs_devices->total_devices = btrfs_super_num_devices(disk_super);
 
@@ -3820,6 +3992,7 @@ int btrfs_balance(struct btrfs_fs_info *fs_info,
        int ret;
        u64 num_devices;
        unsigned seq;
+       bool reducing_integrity;
 
        if (btrfs_fs_closing(fs_info) ||
            atomic_read(&fs_info->balance_pause_req) ||
@@ -3899,24 +4072,30 @@ int btrfs_balance(struct btrfs_fs_info *fs_info,
                     !(bctl->sys.target & allowed)) ||
                    ((bctl->meta.flags & BTRFS_BALANCE_ARGS_CONVERT) &&
                     (fs_info->avail_metadata_alloc_bits & allowed) &&
-                    !(bctl->meta.target & allowed))) {
-                       if (bctl->flags & BTRFS_BALANCE_FORCE) {
-                               btrfs_info(fs_info,
-                               "balance: force reducing metadata integrity");
-                       } else {
-                               btrfs_err(fs_info,
-       "balance: reduces metadata integrity, use --force if you want this");
-                               ret = -EINVAL;
-                               goto out;
-                       }
-               }
+                    !(bctl->meta.target & allowed)))
+                       reducing_integrity = true;
+               else
+                       reducing_integrity = false;
+
+               /* if we're not converting, the target field is uninitialized */
+               meta_target = (bctl->meta.flags & BTRFS_BALANCE_ARGS_CONVERT) ?
+                       bctl->meta.target : fs_info->avail_metadata_alloc_bits;
+               data_target = (bctl->data.flags & BTRFS_BALANCE_ARGS_CONVERT) ?
+                       bctl->data.target : fs_info->avail_data_alloc_bits;
        } while (read_seqretry(&fs_info->profiles_lock, seq));
 
-       /* if we're not converting, the target field is uninitialized */
-       meta_target = (bctl->meta.flags & BTRFS_BALANCE_ARGS_CONVERT) ?
-               bctl->meta.target : fs_info->avail_metadata_alloc_bits;
-       data_target = (bctl->data.flags & BTRFS_BALANCE_ARGS_CONVERT) ?
-               bctl->data.target : fs_info->avail_data_alloc_bits;
+       if (reducing_integrity) {
+               if (bctl->flags & BTRFS_BALANCE_FORCE) {
+                       btrfs_info(fs_info,
+                                  "balance: force reducing metadata integrity");
+               } else {
+                       btrfs_err(fs_info,
+         "balance: reduces metadata integrity, use --force if you want this");
+                       ret = -EINVAL;
+                       goto out;
+               }
+       }
+
        if (btrfs_get_num_tolerated_disk_barrier_failures(meta_target) <
                btrfs_get_num_tolerated_disk_barrier_failures(data_target)) {
                int meta_index = btrfs_bg_flags_to_raid_index(meta_target);