btrfs: check generation as replace duplicates devid+uuid
authorAnand Jain <anand.jain@oracle.com>
Thu, 3 Jul 2014 10:22:06 +0000 (18:22 +0800)
committerChris Mason <clm@fb.com>
Tue, 19 Aug 2014 15:36:30 +0000 (08:36 -0700)
When FS in unmounted we need to check generation number as well
since devid+uuid combination could match with the missing replaced
disk when it reappears, and without this patch it might pair with
the replaced disk again.

 device_list_add() function is called in the following threads,
mount device option
mount argument
ioctl BTRFS_IOC_SCAN_DEV (btrfs dev scan)
ioctl BTRFS_IOC_DEVICES_READY (btrfs dev ready <dev>)
 they have been unit tested to work fine with this patch.

 If the user knows what he is doing and really want to pair with
 replaced disk (which is not a standard operation), then he should
 first clear the kernel btrfs device list in the memory by doing
 the module unload/load and followed with the mount -o device option.

Signed-off-by: Anand Jain <anand.jain@oracle.com>
Signed-off-by: Wang Shilong <wangsl.fnst@cn.fujitsu.com>
Signed-off-by: Miao Xie <miaox@cn.fujitsu.com>
Signed-off-by: Chris Mason <clm@fb.com>
fs/btrfs/volumes.c

index 7c538f65214bb23f5a813009c793ac0b3ab3cf71..5700ab03e84bba10a4b5c4920eadced5099a8fc5 100644 (file)
@@ -532,8 +532,19 @@ static noinline int device_list_add(const char *path,
                 * As of now don't allow update to btrfs_fs_device through
                 * the btrfs dev scan cli, after FS has been mounted.
                 */
-               if (fs_devices->opened)
+               if (fs_devices->opened) {
                        return -EBUSY;
+               } else {
+                       /*
+                        * That is if the FS is _not_ mounted and if you
+                        * are here, that means there is more than one
+                        * disk with same uuid and devid.We keep the one
+                        * with larger generation number or the last-in if
+                        * generation are equal.
+                        */
+                       if (found_transid < device->generation)
+                               return -EEXIST;
+               }
 
                name = rcu_string_strdup(path, GFP_NOFS);
                if (!name)
@@ -546,6 +557,15 @@ static noinline int device_list_add(const char *path,
                }
        }
 
+       /*
+        * Unmount does not free the btrfs_device struct but would zero
+        * generation along with most of the other members. So just update
+        * it back. We need it to pick the disk with largest generation
+        * (as above).
+        */
+       if (!fs_devices->opened)
+               device->generation = found_transid;
+
        if (found_transid > fs_devices->latest_trans) {
                fs_devices->latest_devid = devid;
                fs_devices->latest_trans = found_transid;