btrfs-progs: mkfs, deprecate leafsize and clean up the code
[platform/upstream/btrfs-progs.git] / utils.c
diff --git a/utils.c b/utils.c
index c46e09a..e89cb26 100644 (file)
--- a/utils.c
+++ b/utils.c
  * Boston, MA 021110-1307, USA.
  */
 
-#define _XOPEN_SOURCE 700
-#define __USE_XOPEN2K8
-#define __XOPEN2K8 /* due to an error in dirent.h, to get dirfd() */
-#define _GNU_SOURCE    /* O_NOATIME */
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -38,6 +34,8 @@
 #include <linux/kdev_t.h>
 #include <limits.h>
 #include <blkid/blkid.h>
+#include <sys/vfs.h>
+
 #include "kerncompat.h"
 #include "radix-tree.h"
 #include "ctree.h"
 #define BLKDISCARD     _IO(0x12,119)
 #endif
 
-static int
-discard_blocks(int fd, u64 start, u64 len)
+static int btrfs_scan_done = 0;
+
+static char argv0_buf[ARGV0_BUF_SIZE] = "btrfs";
+
+void fixup_argv0(char **argv, const char *token)
+{
+       int len = strlen(argv0_buf);
+
+       snprintf(argv0_buf + len, sizeof(argv0_buf) - len, " %s", token);
+       argv[0] = argv0_buf;
+}
+
+void set_argv0(char **argv)
+{
+       strncpy(argv0_buf, argv[0], sizeof(argv0_buf));
+       argv0_buf[sizeof(argv0_buf) - 1] = 0;
+}
+
+int check_argc_exact(int nargs, int expected)
+{
+       if (nargs < expected)
+               fprintf(stderr, "%s: too few arguments\n", argv0_buf);
+       if (nargs > expected)
+               fprintf(stderr, "%s: too many arguments\n", argv0_buf);
+
+       return nargs != expected;
+}
+
+int check_argc_min(int nargs, int expected)
+{
+       if (nargs < expected) {
+               fprintf(stderr, "%s: too few arguments\n", argv0_buf);
+               return 1;
+       }
+
+       return 0;
+}
+
+int check_argc_max(int nargs, int expected)
+{
+       if (nargs > expected) {
+               fprintf(stderr, "%s: too many arguments\n", argv0_buf);
+               return 1;
+       }
+
+       return 0;
+}
+
+
+/*
+ * Discard the given range in one go
+ */
+static int discard_range(int fd, u64 start, u64 len)
 {
        u64 range[2] = { start, len };
 
@@ -62,6 +111,26 @@ discard_blocks(int fd, u64 start, u64 len)
        return 0;
 }
 
+/*
+ * Discard blocks in the given range in 1G chunks, the process is interruptible
+ */
+static int discard_blocks(int fd, u64 start, u64 len)
+{
+       while (len > 0) {
+               /* 1G granularity */
+               u64 chunk_size = min_t(u64, len, 1*1024*1024*1024);
+               int ret;
+
+               ret = discard_range(fd, start, chunk_size);
+               if (ret)
+                       return ret;
+               len -= chunk_size;
+               start += chunk_size;
+       }
+
+       return 0;
+}
+
 static u64 reference_root_table[] = {
        [1] =   BTRFS_ROOT_TREE_OBJECTID,
        [2] =   BTRFS_EXTENT_TREE_OBJECTID,
@@ -71,12 +140,41 @@ static u64 reference_root_table[] = {
        [6] =   BTRFS_CSUM_TREE_OBJECTID,
 };
 
-int make_btrfs(int fd, const char *device, const char *label,
+int test_uuid_unique(char *fs_uuid)
+{
+       int unique = 1;
+       blkid_dev_iterate iter = NULL;
+       blkid_dev dev = NULL;
+       blkid_cache cache = NULL;
+
+       if (blkid_get_cache(&cache, 0) < 0) {
+               printf("ERROR: lblkid cache get failed\n");
+               return 1;
+       }
+       blkid_probe_all(cache);
+       iter = blkid_dev_iterate_begin(cache);
+       blkid_dev_set_search(iter, "UUID", fs_uuid);
+
+       while (blkid_dev_next(iter, &dev) == 0) {
+               dev = blkid_verify(cache, dev);
+               if (dev) {
+                       unique = 0;
+                       break;
+               }
+       }
+
+       blkid_dev_iterate_end(iter);
+       blkid_put_cache(cache);
+
+       return unique;
+}
+
+int make_btrfs(int fd, const char *device, const char *label, char *fs_uuid,
               u64 blocks[7], u64 num_bytes, u32 nodesize,
-              u32 leafsize, u32 sectorsize, u32 stripesize, u64 features)
+              u32 sectorsize, u32 stripesize, u64 features)
 {
        struct btrfs_super_block super;
-       struct extent_buffer *buf;
+       struct extent_buffer *buf = NULL;
        struct btrfs_root_item root_item;
        struct btrfs_disk_key disk_key;
        struct btrfs_extent_item *extent_item;
@@ -103,7 +201,20 @@ int make_btrfs(int fd, const char *device, const char *label,
        memset(&super, 0, sizeof(super));
 
        num_bytes = (num_bytes / sectorsize) * sectorsize;
-       uuid_generate(super.fsid);
+       if (fs_uuid) {
+               if (uuid_parse(fs_uuid, super.fsid) != 0) {
+                       fprintf(stderr, "could not parse UUID: %s\n", fs_uuid);
+                       ret = -EINVAL;
+                       goto out;
+               }
+               if (!test_uuid_unique(fs_uuid)) {
+                       fprintf(stderr, "non-unique UUID: %s\n", fs_uuid);
+                       ret = -EBUSY;
+                       goto out;
+               }
+       } else {
+               uuid_generate(super.fsid);
+       }
        uuid_generate(super.dev_item.uuid);
        uuid_generate(chunk_tree_uuid);
 
@@ -114,9 +225,9 @@ int make_btrfs(int fd, const char *device, const char *label,
        btrfs_set_super_root(&super, blocks[1]);
        btrfs_set_super_chunk_root(&super, blocks[3]);
        btrfs_set_super_total_bytes(&super, num_bytes);
-       btrfs_set_super_bytes_used(&super, 6 * leafsize);
+       btrfs_set_super_bytes_used(&super, 6 * nodesize);
        btrfs_set_super_sectorsize(&super, sectorsize);
-       btrfs_set_super_leafsize(&super, leafsize);
+       btrfs_set_super_leafsize(&super, nodesize);
        btrfs_set_super_nodesize(&super, nodesize);
        btrfs_set_super_stripesize(&super, stripesize);
        btrfs_set_super_csum_type(&super, BTRFS_CSUM_TYPE_CRC32);
@@ -126,11 +237,11 @@ int make_btrfs(int fd, const char *device, const char *label,
        if (label)
                strncpy(super.label, label, BTRFS_LABEL_SIZE - 1);
 
-       buf = malloc(sizeof(*buf) + max(sectorsize, leafsize));
+       buf = malloc(sizeof(*buf) + max(sectorsize, nodesize));
 
        /* create the tree of root objects */
-       memset(buf->data, 0, leafsize);
-       buf->len = leafsize;
+       memset(buf->data, 0, nodesize);
+       buf->len = nodesize;
        btrfs_set_header_bytenr(buf, blocks[1]);
        btrfs_set_header_nritems(buf, 4);
        btrfs_set_header_generation(buf, 1);
@@ -139,7 +250,7 @@ int make_btrfs(int fd, const char *device, const char *label,
        write_extent_buffer(buf, super.fsid, btrfs_header_fsid(),
                            BTRFS_FSID_SIZE);
 
-       write_extent_buffer(buf, chunk_tree_uuid, (unsigned long)
+       write_extent_buffer(buf, chunk_tree_uuid,
                            btrfs_header_chunk_tree_uuid(buf),
                            BTRFS_UUID_SIZE);
 
@@ -149,10 +260,10 @@ int make_btrfs(int fd, const char *device, const char *label,
        btrfs_set_stack_inode_generation(inode_item, 1);
        btrfs_set_stack_inode_size(inode_item, 3);
        btrfs_set_stack_inode_nlink(inode_item, 1);
-       btrfs_set_stack_inode_nbytes(inode_item, leafsize);
+       btrfs_set_stack_inode_nbytes(inode_item, nodesize);
        btrfs_set_stack_inode_mode(inode_item, S_IFDIR | 0755);
        btrfs_set_root_refs(&root_item, 1);
-       btrfs_set_root_used(&root_item, leafsize);
+       btrfs_set_root_used(&root_item, nodesize);
        btrfs_set_root_generation(&root_item, 1);
 
        memset(&disk_key, 0, sizeof(disk_key));
@@ -160,7 +271,7 @@ int make_btrfs(int fd, const char *device, const char *label,
        btrfs_set_disk_key_offset(&disk_key, 0);
        nritems = 0;
 
-       itemoff = __BTRFS_LEAF_DATA_SIZE(leafsize) - sizeof(root_item);
+       itemoff = __BTRFS_LEAF_DATA_SIZE(nodesize) - sizeof(root_item);
        btrfs_set_root_bytenr(&root_item, blocks[2]);
        btrfs_set_disk_key_objectid(&disk_key, BTRFS_EXTENT_TREE_OBJECTID);
        btrfs_set_item_key(buf, &disk_key, nritems);
@@ -209,17 +320,17 @@ int make_btrfs(int fd, const char *device, const char *label,
 
 
        csum_tree_block_size(buf, BTRFS_CRC32_SIZE, 0);
-       ret = pwrite(fd, buf->data, leafsize, blocks[1]);
-       if (ret != leafsize) {
+       ret = pwrite(fd, buf->data, nodesize, blocks[1]);
+       if (ret != nodesize) {
                ret = (ret < 0 ? -errno : -EIO);
                goto out;
        }
 
        /* create the items for the extent tree */
-       memset(buf->data+sizeof(struct btrfs_header), 0,
-               leafsize-sizeof(struct btrfs_header));
+       memset(buf->data + sizeof(struct btrfs_header), 0,
+               nodesize - sizeof(struct btrfs_header));
        nritems = 0;
-       itemoff = __BTRFS_LEAF_DATA_SIZE(leafsize);
+       itemoff = __BTRFS_LEAF_DATA_SIZE(nodesize);
        for (i = 1; i < 7; i++) {
                item_size = sizeof(struct btrfs_extent_item);
                if (!skinny_metadata)
@@ -238,7 +349,7 @@ int make_btrfs(int fd, const char *device, const char *label,
                } else {
                        btrfs_set_disk_key_type(&disk_key,
                                                BTRFS_EXTENT_ITEM_KEY);
-                       btrfs_set_disk_key_offset(&disk_key, leafsize);
+                       btrfs_set_disk_key_offset(&disk_key, nodesize);
                }
                btrfs_set_item_key(buf, &disk_key, nritems);
                btrfs_set_item_offset(buf, btrfs_item_nr(nritems),
@@ -268,18 +379,18 @@ int make_btrfs(int fd, const char *device, const char *label,
        btrfs_set_header_owner(buf, BTRFS_EXTENT_TREE_OBJECTID);
        btrfs_set_header_nritems(buf, nritems);
        csum_tree_block_size(buf, BTRFS_CRC32_SIZE, 0);
-       ret = pwrite(fd, buf->data, leafsize, blocks[2]);
-       if (ret != leafsize) {
+       ret = pwrite(fd, buf->data, nodesize, blocks[2]);
+       if (ret != nodesize) {
                ret = (ret < 0 ? -errno : -EIO);
                goto out;
        }
 
        /* create the chunk tree */
-       memset(buf->data+sizeof(struct btrfs_header), 0,
-               leafsize-sizeof(struct btrfs_header));
+       memset(buf->data + sizeof(struct btrfs_header), 0,
+               nodesize - sizeof(struct btrfs_header));
        nritems = 0;
        item_size = sizeof(*dev_item);
-       itemoff = __BTRFS_LEAF_DATA_SIZE(leafsize) - item_size;
+       itemoff = __BTRFS_LEAF_DATA_SIZE(nodesize) - item_size;
 
        /* first device 1 (there is no device 0) */
        btrfs_set_disk_key_objectid(&disk_key, BTRFS_DEV_ITEMS_OBJECTID);
@@ -355,17 +466,17 @@ int make_btrfs(int fd, const char *device, const char *label,
        btrfs_set_header_owner(buf, BTRFS_CHUNK_TREE_OBJECTID);
        btrfs_set_header_nritems(buf, nritems);
        csum_tree_block_size(buf, BTRFS_CRC32_SIZE, 0);
-       ret = pwrite(fd, buf->data, leafsize, blocks[3]);
-       if (ret != leafsize) {
+       ret = pwrite(fd, buf->data, nodesize, blocks[3]);
+       if (ret != nodesize) {
                ret = (ret < 0 ? -errno : -EIO);
                goto out;
        }
 
        /* create the device tree */
-       memset(buf->data+sizeof(struct btrfs_header), 0,
-               leafsize-sizeof(struct btrfs_header));
+       memset(buf->data + sizeof(struct btrfs_header), 0,
+               nodesize - sizeof(struct btrfs_header));
        nritems = 0;
-       itemoff = __BTRFS_LEAF_DATA_SIZE(leafsize) -
+       itemoff = __BTRFS_LEAF_DATA_SIZE(nodesize) -
                sizeof(struct btrfs_dev_extent);
 
        btrfs_set_disk_key_objectid(&disk_key, 1);
@@ -394,33 +505,33 @@ int make_btrfs(int fd, const char *device, const char *label,
        btrfs_set_header_owner(buf, BTRFS_DEV_TREE_OBJECTID);
        btrfs_set_header_nritems(buf, nritems);
        csum_tree_block_size(buf, BTRFS_CRC32_SIZE, 0);
-       ret = pwrite(fd, buf->data, leafsize, blocks[4]);
-       if (ret != leafsize) {
+       ret = pwrite(fd, buf->data, nodesize, blocks[4]);
+       if (ret != nodesize) {
                ret = (ret < 0 ? -errno : -EIO);
                goto out;
        }
 
        /* create the FS root */
-       memset(buf->data+sizeof(struct btrfs_header), 0,
-               leafsize-sizeof(struct btrfs_header));
+       memset(buf->data + sizeof(struct btrfs_header), 0,
+               nodesize - sizeof(struct btrfs_header));
        btrfs_set_header_bytenr(buf, blocks[5]);
        btrfs_set_header_owner(buf, BTRFS_FS_TREE_OBJECTID);
        btrfs_set_header_nritems(buf, 0);
        csum_tree_block_size(buf, BTRFS_CRC32_SIZE, 0);
-       ret = pwrite(fd, buf->data, leafsize, blocks[5]);
-       if (ret != leafsize) {
+       ret = pwrite(fd, buf->data, nodesize, blocks[5]);
+       if (ret != nodesize) {
                ret = (ret < 0 ? -errno : -EIO);
                goto out;
        }
        /* finally create the csum root */
-       memset(buf->data+sizeof(struct btrfs_header), 0,
-               leafsize-sizeof(struct btrfs_header));
+       memset(buf->data + sizeof(struct btrfs_header), 0,
+               nodesize - sizeof(struct btrfs_header));
        btrfs_set_header_bytenr(buf, blocks[6]);
        btrfs_set_header_owner(buf, BTRFS_CSUM_TREE_OBJECTID);
        btrfs_set_header_nritems(buf, 0);
        csum_tree_block_size(buf, BTRFS_CRC32_SIZE, 0);
-       ret = pwrite(fd, buf->data, leafsize, blocks[6]);
-       if (ret != leafsize) {
+       ret = pwrite(fd, buf->data, nodesize, blocks[6]);
+       if (ret != nodesize) {
                ret = (ret < 0 ? -errno : -EIO);
                goto out;
        }
@@ -475,25 +586,23 @@ static int zero_blocks(int fd, off_t start, size_t len)
        return ret;
 }
 
-static int zero_dev_start(int fd)
+#define ZERO_DEV_BYTES (2 * 1024 * 1024)
+
+/* don't write outside the device by clamping the region to the device size */
+static int zero_dev_clamped(int fd, off_t start, ssize_t len, u64 dev_size)
 {
-       off_t start = 0;
-       size_t len = 2 * 1024 * 1024;
+       off_t end = max(start, start + len);
 
 #ifdef __sparc__
-       /* don't overwrite the disk labels on sparc */
-       start = 1024;
-       len -= 1024;
+       /* and don't overwrite the disk labels on sparc */
+       start = max(start, 1024);
+       end = max(end, 1024);
 #endif
-       return zero_blocks(fd, start, len);
-}
 
-static int zero_dev_end(int fd, u64 dev_size)
-{
-       size_t len = 2 * 1024 * 1024;
-       off_t start = dev_size - len;
+       start = min_t(u64, start, dev_size);
+       end = min_t(u64, end, dev_size);
 
-       return zero_blocks(fd, start, len);
+       return zero_blocks(fd, start, end - start);
 }
 
 int btrfs_add_to_fsid(struct btrfs_trans_handle *trans,
@@ -570,33 +679,65 @@ int btrfs_add_to_fsid(struct btrfs_trans_handle *trans,
        return 0;
 }
 
+static void btrfs_wipe_existing_sb(int fd)
+{
+       const char *off = NULL;
+       size_t len = 0;
+       loff_t offset;
+       char buf[BUFSIZ];
+       int rc = 0;
+       blkid_probe pr = NULL;
+
+       pr = blkid_new_probe();
+       if (!pr)
+               return;
+
+       if (blkid_probe_set_device(pr, fd, 0, 0))
+               goto out;
+
+       rc = blkid_probe_lookup_value(pr, "SBMAGIC_OFFSET", &off, NULL);
+       if (!rc)
+               rc = blkid_probe_lookup_value(pr, "SBMAGIC", NULL, &len);
+
+       if (rc || len == 0 || off == NULL)
+               goto out;
+
+       offset = strtoll(off, NULL, 10);
+       if (len > sizeof(buf))
+               len = sizeof(buf);
+
+       memset(buf, 0, len);
+       rc = pwrite(fd, buf, len, offset);
+       fsync(fd);
+
+out:
+       blkid_free_probe(pr);
+       return;
+}
+
 int btrfs_prepare_device(int fd, char *file, int zero_end, u64 *block_count_ret,
                           u64 max_block_count, int *mixed, int discard)
 {
        u64 block_count;
-       u64 bytenr;
        struct stat st;
        int i, ret;
 
        ret = fstat(fd, &st);
        if (ret < 0) {
                fprintf(stderr, "unable to stat %s\n", file);
-               exit(1);
+               return 1;
        }
 
        block_count = btrfs_device_size(fd, &st);
        if (block_count == 0) {
                fprintf(stderr, "unable to find %s size\n", file);
-               exit(1);
+               return 1;
        }
        if (max_block_count)
                block_count = min(block_count, max_block_count);
-       zero_end = 1;
 
-       if (block_count < 1024 * 1024 * 1024 && !(*mixed)) {
-               printf("SMALL VOLUME: forcing mixed metadata/data groups\n");
+       if (block_count < BTRFS_MKFS_SMALL_VOLUME_SIZE && !(*mixed))
                *mixed = 1;
-       }
 
        if (discard) {
                /*
@@ -604,33 +745,29 @@ int btrfs_prepare_device(int fd, char *file, int zero_end, u64 *block_count_ret,
                 * is not necessary for the mkfs functionality but just an
                 * optimization.
                 */
-               if (discard_blocks(fd, 0, 0) == 0) {
-                       fprintf(stderr, "Performing full device TRIM (%s) ...\n",
+               if (discard_range(fd, 0, 0) == 0) {
+                       printf("Performing full device TRIM (%s) ...\n",
                                pretty_size(block_count));
                        discard_blocks(fd, 0, block_count);
                }
        }
 
-       ret = zero_dev_start(fd);
-       if (ret) {
-               fprintf(stderr, "failed to zero device start %d\n", ret);
-               exit(1);
-       }
+       ret = zero_dev_clamped(fd, 0, ZERO_DEV_BYTES, block_count);
+       for (i = 0 ; !ret && i < BTRFS_SUPER_MIRROR_MAX; i++)
+               ret = zero_dev_clamped(fd, btrfs_sb_offset(i),
+                                      BTRFS_SUPER_INFO_SIZE, block_count);
+       if (!ret && zero_end)
+               ret = zero_dev_clamped(fd, block_count - ZERO_DEV_BYTES,
+                                      ZERO_DEV_BYTES, block_count);
 
-       for (i = 0 ; i < BTRFS_SUPER_MIRROR_MAX; i++) {
-               bytenr = btrfs_sb_offset(i);
-               if (bytenr >= block_count)
-                       break;
-               zero_blocks(fd, bytenr, BTRFS_SUPER_INFO_SIZE);
+       if (ret < 0) {
+               fprintf(stderr, "ERROR: failed to zero device '%s' - %s\n",
+                       file, strerror(-ret));
+               return 1;
        }
 
-       if (zero_end) {
-               ret = zero_dev_end(fd, block_count);
-               if (ret) {
-                       fprintf(stderr, "failed to zero device end %d\n", ret);
-                       exit(1);
-               }
-       }
+       btrfs_wipe_existing_sb(fd);
+
        *block_count_ret = block_count;
        return 0;
 }
@@ -646,7 +783,7 @@ int btrfs_make_root_dir(struct btrfs_trans_handle *trans,
        btrfs_set_stack_inode_generation(&inode_item, trans->transid);
        btrfs_set_stack_inode_size(&inode_item, 0);
        btrfs_set_stack_inode_nlink(&inode_item, 1);
-       btrfs_set_stack_inode_nbytes(&inode_item, root->leafsize);
+       btrfs_set_stack_inode_nbytes(&inode_item, root->nodesize);
        btrfs_set_stack_inode_mode(&inode_item, S_IFDIR | 0755);
        btrfs_set_stack_timespec_sec(&inode_item.atime, now);
        btrfs_set_stack_timespec_nsec(&inode_item.atime, 0);
@@ -713,6 +850,52 @@ int is_mount_point(const char *path)
        return ret;
 }
 
+static int is_reg_file(const char *path)
+{
+       struct stat statbuf;
+
+       if (stat(path, &statbuf) < 0)
+               return -errno;
+       return S_ISREG(statbuf.st_mode);
+}
+
+/*
+ * This function checks if the given input parameter is
+ * an uuid or a path
+ * return <0 : some error in the given input
+ * return BTRFS_ARG_UNKNOWN:   unknown input
+ * return BTRFS_ARG_UUID:      given input is uuid
+ * return BTRFS_ARG_MNTPOINT:  given input is path
+ * return BTRFS_ARG_REG:       given input is regular file
+ */
+int check_arg_type(const char *input)
+{
+       uuid_t uuid;
+       char path[PATH_MAX];
+
+       if (!input)
+               return -EINVAL;
+
+       if (realpath(input, path)) {
+               if (is_block_device(path) == 1)
+                       return BTRFS_ARG_BLKDEV;
+
+               if (is_mount_point(path) == 1)
+                       return BTRFS_ARG_MNTPOINT;
+
+               if (is_reg_file(path))
+                       return BTRFS_ARG_REG;
+
+               return BTRFS_ARG_UNKNOWN;
+       }
+
+       if (strlen(input) == (BTRFS_UUID_UNPARSED_SIZE - 1) &&
+               !uuid_parse(input, uuid))
+               return BTRFS_ARG_UUID;
+
+       return BTRFS_ARG_UNKNOWN;
+}
+
 /*
  * Find the mount point for a mounted device.
  * On success, returns 0 with mountpoint in *mp.
@@ -822,7 +1005,8 @@ static int resolve_loop_device(const char* loop_dev, char* loop_file,
        return 0;
 }
 
-/* Checks whether a and b are identical or device
+/*
+ * Checks whether a and b are identical or device
  * files associated with the same block device
  */
 static int is_same_blk_file(const char* a, const char* b)
@@ -831,36 +1015,31 @@ static int is_same_blk_file(const char* a, const char* b)
        char real_a[PATH_MAX];
        char real_b[PATH_MAX];
 
-       if(!realpath(a, real_a))
-               strcpy(real_a, a);
+       if (!realpath(a, real_a))
+               strncpy_null(real_a, a);
 
        if (!realpath(b, real_b))
-               strcpy(real_b, b);
+               strncpy_null(real_b, b);
 
        /* Identical path? */
-       if(strcmp(real_a, real_b) == 0)
+       if (strcmp(real_a, real_b) == 0)
                return 1;
 
-       if(stat(a, &st_buf_a) < 0 ||
-          stat(b, &st_buf_b) < 0)
-       {
+       if (stat(a, &st_buf_a) < 0 || stat(b, &st_buf_b) < 0) {
                if (errno == ENOENT)
                        return 0;
                return -errno;
        }
 
        /* Same blockdevice? */
-       if(S_ISBLK(st_buf_a.st_mode) &&
-          S_ISBLK(st_buf_b.st_mode) &&
-          st_buf_a.st_rdev == st_buf_b.st_rdev)
-       {
+       if (S_ISBLK(st_buf_a.st_mode) && S_ISBLK(st_buf_b.st_mode) &&
+           st_buf_a.st_rdev == st_buf_b.st_rdev) {
                return 1;
        }
 
        /* Hardlink? */
        if (st_buf_a.st_dev == st_buf_b.st_dev &&
-           st_buf_a.st_ino == st_buf_b.st_ino)
-       {
+           st_buf_a.st_ino == st_buf_b.st_ino) {
                return 1;
        }
 
@@ -955,6 +1134,64 @@ static int blk_file_in_dev_list(struct btrfs_fs_devices* fs_devices,
 }
 
 /*
+ * Resolve a pathname to a device mapper node to /dev/mapper/<name>
+ * Returns NULL on invalid input or malloc failure; Other failures
+ * will be handled by the caller using the input pathame.
+ */
+char *canonicalize_dm_name(const char *ptname)
+{
+       FILE    *f;
+       size_t  sz;
+       char    path[PATH_MAX], name[PATH_MAX], *res = NULL;
+
+       if (!ptname || !*ptname)
+               return NULL;
+
+       snprintf(path, sizeof(path), "/sys/block/%s/dm/name", ptname);
+       if (!(f = fopen(path, "r")))
+               return NULL;
+
+       /* read <name>\n from sysfs */
+       if (fgets(name, sizeof(name), f) && (sz = strlen(name)) > 1) {
+               name[sz - 1] = '\0';
+               snprintf(path, sizeof(path), "/dev/mapper/%s", name);
+
+               if (access(path, F_OK) == 0)
+                       res = strdup(path);
+       }
+       fclose(f);
+       return res;
+}
+
+/*
+ * Resolve a pathname to a canonical device node, e.g. /dev/sda1 or
+ * to a device mapper pathname.
+ * Returns NULL on invalid input or malloc failure; Other failures
+ * will be handled by the caller using the input pathame.
+ */
+char *canonicalize_path(const char *path)
+{
+       char *canonical, *p;
+
+       if (!path || !*path)
+               return NULL;
+
+       canonical = realpath(path, NULL);
+       if (!canonical)
+               return strdup(path);
+       p = strrchr(canonical, '/');
+       if (p && strncmp(p, "/dm-", 4) == 0 && isdigit(*(p + 4))) {
+               char *dm = canonicalize_dm_name(p + 1);
+
+               if (dm) {
+                       free(canonical);
+                       return dm;
+               }
+       }
+       return canonical;
+}
+
+/*
  * returns 1 if the device was mounted, < 0 on error or 0 if everything
  * is safe to continue.
  */
@@ -987,12 +1224,13 @@ int check_mounted_where(int fd, const char *file, char *where, int size,
 
        /* scan the initial device */
        ret = btrfs_scan_one_device(fd, file, &fs_devices_mnt,
-                                   &total_devs, BTRFS_SUPER_INFO_OFFSET);
+                                   &total_devs, BTRFS_SUPER_INFO_OFFSET, 0);
        is_btrfs = (ret >= 0);
 
        /* scan other devices */
        if (is_btrfs && total_devs > 1) {
-               if ((ret = btrfs_scan_for_fsid(!BTRFS_UPDATE_KERNEL)))
+               ret = btrfs_scan_lblkid();
+               if (ret)
                        return ret;
        }
 
@@ -1044,150 +1282,58 @@ struct pending_dir {
        char name[PATH_MAX];
 };
 
-void btrfs_register_one_device(char *fname)
+int btrfs_register_one_device(const char *fname)
 {
        struct btrfs_ioctl_vol_args args;
        int fd;
        int ret;
        int e;
 
-       fd = open("/dev/btrfs-control", O_RDONLY);
+       fd = open("/dev/btrfs-control", O_RDWR);
        if (fd < 0) {
                fprintf(stderr, "failed to open /dev/btrfs-control "
                        "skipping device registration: %s\n",
                        strerror(errno));
-               return;
+               return -errno;
        }
        strncpy(args.name, fname, BTRFS_PATH_NAME_MAX);
        args.name[BTRFS_PATH_NAME_MAX-1] = 0;
        ret = ioctl(fd, BTRFS_IOC_SCAN_DEV, &args);
        e = errno;
-       if(ret<0){
+       if (ret < 0) {
                fprintf(stderr, "ERROR: device scan failed '%s' - %s\n",
                        fname, strerror(e));
+               ret = -e;
        }
        close(fd);
+       return ret;
 }
 
-int btrfs_scan_one_dir(char *dirname, int run_ioctl)
+/*
+ * Register all devices in the fs_uuid list created in the user
+ * space. Ensure btrfs_scan_lblkid() is called before this func.
+ */
+int btrfs_register_all_devices(void)
 {
-       DIR *dirp = NULL;
-       struct dirent *dirent;
-       struct pending_dir *pending;
-       struct stat st;
-       int ret;
-       int fd;
-       int dirname_len;
-       char *fullpath;
-       struct list_head pending_list;
-       struct btrfs_fs_devices *tmp_devices;
-       u64 num_devices;
-
-       INIT_LIST_HEAD(&pending_list);
-
-       pending = malloc(sizeof(*pending));
-       if (!pending)
-               return -ENOMEM;
-       strcpy(pending->name, dirname);
-
-again:
-       dirname_len = strlen(pending->name);
-       fullpath = malloc(PATH_MAX);
-       dirname = pending->name;
-
-       if (!fullpath) {
-               ret = -ENOMEM;
-               goto fail;
-       }
-       dirp = opendir(dirname);
-       if (!dirp) {
-               fprintf(stderr, "Unable to open %s for scanning\n", dirname);
-               ret = -ENOENT;
-               goto fail;
-       }
-       while(1) {
-               dirent = readdir(dirp);
-               if (!dirent)
-                       break;
-               if (dirent->d_name[0] == '.')
-                       continue;
-               if (dirname_len + strlen(dirent->d_name) + 2 > PATH_MAX) {
-                       ret = -EFAULT;
-                       goto fail;
-               }
-               snprintf(fullpath, PATH_MAX, "%s/%s", dirname, dirent->d_name);
-               ret = lstat(fullpath, &st);
-               if (ret < 0) {
-                       fprintf(stderr, "failed to stat %s\n", fullpath);
-                       continue;
-               }
-               if (S_ISLNK(st.st_mode))
-                       continue;
-               if (S_ISDIR(st.st_mode)) {
-                       struct pending_dir *next = malloc(sizeof(*next));
-                       if (!next) {
-                               ret = -ENOMEM;
-                               goto fail;
+       int err;
+       struct btrfs_fs_devices *fs_devices;
+       struct btrfs_device *device;
+       struct list_head *all_uuids;
+
+       all_uuids = btrfs_scanned_uuids();
+
+       list_for_each_entry(fs_devices, all_uuids, list) {
+               list_for_each_entry(device, &fs_devices->devices, dev_list) {
+                       if (strlen(device->name) != 0) {
+                               err = btrfs_register_one_device(device->name);
+                               if (err < 0)
+                                       return err;
+                               if (err > 0)
+                                       return -err;
                        }
-                       strcpy(next->name, fullpath);
-                       list_add_tail(&next->list, &pending_list);
-               }
-               if (!S_ISBLK(st.st_mode)) {
-                       continue;
                }
-               fd = open(fullpath, O_RDONLY);
-               if (fd < 0) {
-                       /* ignore the following errors:
-                               ENXIO (device don't exists) 
-                               ENOMEDIUM (No medium found -> 
-                                       like a cd tray empty)
-                       */
-                       if(errno != ENXIO && errno != ENOMEDIUM) 
-                               fprintf(stderr, "failed to read %s: %s\n", 
-                                       fullpath, strerror(errno));
-                       continue;
-               }
-               ret = btrfs_scan_one_device(fd, fullpath, &tmp_devices,
-                                           &num_devices,
-                                           BTRFS_SUPER_INFO_OFFSET);
-               if (ret == 0 && run_ioctl > 0) {
-                       btrfs_register_one_device(fullpath);
-               }
-               close(fd);
-       }
-       if (!list_empty(&pending_list)) {
-               free(pending);
-               pending = list_entry(pending_list.next, struct pending_dir,
-                                    list);
-               free(fullpath);
-               list_del(&pending->list);
-               closedir(dirp);
-               dirp = NULL;
-               goto again;
        }
-       ret = 0;
-fail:
-       free(pending);
-       free(fullpath);
-       while (!list_empty(&pending_list)) {
-               pending = list_entry(pending_list.next, struct pending_dir,
-                                    list);
-               list_del(&pending->list);
-               free(pending);
-       }
-       if (dirp)
-               closedir(dirp);
-       return ret;
-}
-
-int btrfs_scan_for_fsid(int run_ioctls)
-{
-       int ret;
-
-       ret = scan_for_btrfs(BTRFS_SCAN_PROC, run_ioctls);
-       if (ret)
-               ret = scan_for_btrfs(BTRFS_SCAN_DEV, run_ioctls);
-       return ret;
+       return 0;
 }
 
 int btrfs_device_already_in_root(struct btrfs_root *root, int fd,
@@ -1220,35 +1366,76 @@ out:
        return ret;
 }
 
-static char *size_strs[] = { "", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"};
-int pretty_size_snprintf(u64 size, char *str, size_t str_bytes)
+static const char* unit_suffix_binary[] =
+       { "B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"};
+static const char* unit_suffix_decimal[] =
+       { "B", "kB", "MB", "GB", "TB", "PB", "EB"};
+
+int pretty_size_snprintf(u64 size, char *str, size_t str_size, unsigned unit_mode)
 {
-       int num_divs = 0;
+       int num_divs;
        float fraction;
+       u64 base = 0;
+       int mult = 0;
+       const char** suffix = NULL;
+       u64 last_size;
 
-       if (str_bytes == 0)
+       if (str_size == 0)
                return 0;
 
-       if( size < 1024 ){
-               fraction = size;
-               num_divs = 0;
-       } else {
-               u64 last_size = size;
-               num_divs = 0;
-               while(size >= 1024){
+       if ((unit_mode & ~UNITS_MODE_MASK) == UNITS_RAW) {
+               snprintf(str, str_size, "%llu", size);
+               return 0;
+       }
+
+       if ((unit_mode & ~UNITS_MODE_MASK) == UNITS_BINARY) {
+               base = 1024;
+               mult = 1024;
+               suffix = unit_suffix_binary;
+       } else if ((unit_mode & ~UNITS_MODE_MASK) == UNITS_DECIMAL) {
+               base = 1000;
+               mult = 1000;
+               suffix = unit_suffix_decimal;
+       }
+
+       /* Unknown mode */
+       if (!base) {
+               fprintf(stderr, "INTERNAL ERROR: unknown unit base, mode %d\n",
+                               unit_mode);
+               assert(0);
+               return -1;
+       }
+
+       num_divs = 0;
+       last_size = size;
+       switch (unit_mode & UNITS_MODE_MASK) {
+       case UNITS_TBYTES: base *= mult; num_divs++;
+       case UNITS_GBYTES: base *= mult; num_divs++;
+       case UNITS_MBYTES: base *= mult; num_divs++;
+       case UNITS_KBYTES: num_divs++;
+                          break;
+       case UNITS_BYTES:
+                          base = 1;
+                          num_divs = 0;
+                          break;
+       default:
+               while (size >= mult) {
                        last_size = size;
-                       size /= 1024;
-                       num_divs ++;
+                       size /= mult;
+                       num_divs++;
                }
+       }
 
-               if (num_divs >= ARRAY_SIZE(size_strs)) {
-                       str[0] = '\0';
-                       return -1;
-               }
-               fraction = (float)last_size / 1024;
+       if (num_divs >= ARRAY_SIZE(unit_suffix_binary)) {
+               str[0] = '\0';
+               printf("INTERNAL ERROR: unsupported unit suffix, index %d\n",
+                               num_divs);
+               assert(0);
+               return -1;
        }
-       return snprintf(str, str_bytes, "%.2f%s", fraction,
-                       size_strs[num_divs]);
+       fraction = (float)last_size / base;
+
+       return snprintf(str, str_size, "%.2f%s", fraction, suffix[num_divs]);
 }
 
 /*
@@ -1330,7 +1517,7 @@ static int set_label_mounted(const char *mount_path, const char *label)
 
        fd = open(mount_path, O_RDONLY | O_NOATIME);
        if (fd < 0) {
-               fprintf(stderr, "ERROR: unable access to '%s'\n", mount_path);
+               fprintf(stderr, "ERROR: unable to access '%s'\n", mount_path);
                return -1;
        }
 
@@ -1387,7 +1574,7 @@ int get_label_mounted(const char *mount_path, char *labelp)
 
        fd = open(mount_path, O_RDONLY | O_NOATIME);
        if (fd < 0) {
-               fprintf(stderr, "ERROR: unable access to '%s'\n", mount_path);
+               fprintf(stderr, "ERROR: unable to access '%s'\n", mount_path);
                return -1;
        }
 
@@ -1407,22 +1594,29 @@ int get_label(const char *btrfs_dev, char *label)
 {
        int ret;
 
-       if (is_existing_blk_or_reg_file(btrfs_dev))
-               ret = get_label_unmounted(btrfs_dev, label);
-       else
+       ret = is_existing_blk_or_reg_file(btrfs_dev);
+       if (!ret)
                ret = get_label_mounted(btrfs_dev, label);
+       else if (ret > 0)
+               ret = get_label_unmounted(btrfs_dev, label);
 
        return ret;
 }
 
 int set_label(const char *btrfs_dev, const char *label)
 {
+       int ret;
+
        if (check_label(label))
                return -1;
 
-       return is_existing_blk_or_reg_file(btrfs_dev) ?
-               set_label_unmounted(btrfs_dev, label) :
-               set_label_mounted(btrfs_dev, label);
+       ret = is_existing_blk_or_reg_file(btrfs_dev);
+       if (!ret)
+               ret = set_label_mounted(btrfs_dev, label);
+       else if (ret > 0)
+               ret = set_label_unmounted(btrfs_dev, label);
+
+       return ret;
 }
 
 int btrfs_scan_block_devices(int run_ioctl)
@@ -1457,7 +1651,12 @@ scan_again:
 
        strcpy(fullpath,"/dev/");
        while(fgets(buf, 1023, proc_partitions)) {
-               i = sscanf(buf," %*d %*d %*d %99s", fullpath+5);
+               ret = sscanf(buf," %*d %*d %*d %99s", fullpath + 5);
+               if (ret != 1) {
+                       fprintf(stderr,
+                               "failed to scan device name from /proc/partitions\n");
+                       break;
+               }
 
                /*
                 * multipath and MD devices may register as a btrfs filesystem
@@ -1492,7 +1691,7 @@ scan_again:
                }
                ret = btrfs_scan_one_device(fd, fullpath, &tmp_devices,
                                            &num_devices,
-                                           BTRFS_SUPER_INFO_OFFSET);
+                                           BTRFS_SUPER_INFO_OFFSET, 0);
                if (ret == 0 && run_ioctl > 0) {
                        btrfs_register_one_device(fullpath);
                }
@@ -1508,20 +1707,76 @@ scan_again:
        return 0;
 }
 
-u64 parse_size(char *s)
+/*
+ * Unsafe subvolume check.
+ *
+ * This only checks ino == BTRFS_FIRST_FREE_OBJECTID, even it is not in a
+ * btrfs mount point.
+ * Must use together with other reliable method like btrfs ioctl.
+ */
+static int __is_subvol(const char *path)
+{
+       struct stat st;
+       int ret;
+
+       ret = lstat(path, &st);
+       if (ret < 0)
+               return ret;
+
+       return st.st_ino == BTRFS_FIRST_FREE_OBJECTID;
+}
+
+/*
+ * A not-so-good version fls64. No fascinating optimization since
+ * no one except parse_size use it
+ */
+static int fls64(u64 x)
 {
        int i;
+
+       for (i = 0; i <64; i++)
+               if (x << i & (1ULL << 63))
+                       return 64 - i;
+       return 64 - i;
+}
+
+u64 parse_size(char *s)
+{
        char c;
+       char *endptr;
        u64 mult = 1;
+       u64 ret;
 
-       for (i = 0; s && s[i] && isdigit(s[i]); i++) ;
-       if (!i) {
-               fprintf(stderr, "ERROR: size value is empty\n");
-               exit(50);
+       if (!s) {
+               fprintf(stderr, "ERROR: Size value is empty\n");
+               exit(1);
        }
-
-       if (s[i]) {
-               c = tolower(s[i]);
+       if (s[0] == '-') {
+               fprintf(stderr,
+                       "ERROR: Size value '%s' is less equal than 0\n", s);
+               exit(1);
+       }
+       ret = strtoull(s, &endptr, 10);
+       if (endptr == s) {
+               fprintf(stderr, "ERROR: Size value '%s' is invalid\n", s);
+               exit(1);
+       }
+       if (endptr[0] && endptr[1]) {
+               fprintf(stderr, "ERROR: Illegal suffix contains character '%c' in wrong position\n",
+                       endptr[1]);
+               exit(1);
+       }
+       /*
+        * strtoll returns LLONG_MAX when overflow, if this happens,
+        * need to call strtoull to get the real size
+        */
+       if (errno == ERANGE && ret == ULLONG_MAX) {
+               fprintf(stderr,
+                       "ERROR: Size value '%s' is too large for u64\n", s);
+               exit(1);
+       }
+       if (endptr[0]) {
+               c = tolower(endptr[0]);
                switch (c) {
                case 'e':
                        mult *= 1024;
@@ -1544,21 +1799,71 @@ u64 parse_size(char *s)
                case 'b':
                        break;
                default:
-                       fprintf(stderr, "ERROR: Unknown size descriptor "
-                               "'%c'\n", c);
+                       fprintf(stderr, "ERROR: Unknown size descriptor '%c'\n",
+                               c);
                        exit(1);
                }
        }
-       if (s[i] && s[i+1]) {
-               fprintf(stderr, "ERROR: Illegal suffix contains "
-                       "character '%c' in wrong position\n",
-                       s[i+1]);
-               exit(51);
+       /* Check whether ret * mult overflow */
+       if (fls64(ret) + fls64(mult) - 1 > 64) {
+               fprintf(stderr,
+                       "ERROR: Size value '%s' is too large for u64\n", s);
+               exit(1);
        }
-       return strtoull(s, NULL, 10) * mult;
+       ret *= mult;
+       return ret;
 }
 
-int open_file_or_dir(const char *fname, DIR **dirstream)
+u64 parse_qgroupid(const char *p)
+{
+       char *s = strchr(p, '/');
+       const char *ptr_src_end = p + strlen(p);
+       char *ptr_parse_end = NULL;
+       u64 level;
+       u64 id;
+       int fd;
+       int ret = 0;
+
+       if (p[0] == '/')
+               goto path;
+
+       /* Numeric format like '0/257' is the primary case */
+       if (!s) {
+               id = strtoull(p, &ptr_parse_end, 10);
+               if (ptr_parse_end != ptr_src_end)
+                       goto path;
+               return id;
+       }
+       level = strtoull(p, &ptr_parse_end, 10);
+       if (ptr_parse_end != s)
+               goto path;
+
+       id = strtoull(s + 1, &ptr_parse_end, 10);
+       if (ptr_parse_end != ptr_src_end)
+               goto  path;
+
+       return (level << BTRFS_QGROUP_LEVEL_SHIFT) | id;
+
+path:
+       /* Path format like subv at 'my_subvol' is the fallback case */
+       ret = __is_subvol(p);
+       if (ret < 0 || !ret)
+               goto err;
+       fd = open(p, O_RDONLY);
+       if (fd < 0)
+               goto err;
+       ret = lookup_ino_rootid(fd, &id);
+       close(fd);
+       if (ret < 0)
+               goto err;
+       return id;
+
+err:
+       fprintf(stderr, "ERROR: invalid qgroupid or subvolume path: %s\n", p);
+       exit(-1);
+}
+
+int open_file_or_dir3(const char *fname, DIR **dirstream, int open_flags)
 {
        int ret;
        struct stat st;
@@ -1571,19 +1876,31 @@ int open_file_or_dir(const char *fname, DIR **dirstream)
        if (S_ISDIR(st.st_mode)) {
                *dirstream = opendir(fname);
                if (!*dirstream)
-                       return -2;
+                       return -1;
                fd = dirfd(*dirstream);
+       } else if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) {
+               fd = open(fname, open_flags);
        } else {
-               fd = open(fname, O_RDWR);
+               /*
+                * we set this on purpose, in case the caller output
+                * strerror(errno) as success
+                */
+               errno = EINVAL;
+               return -1;
        }
        if (fd < 0) {
-               fd = -3;
+               fd = -1;
                if (*dirstream)
                        closedir(*dirstream);
        }
        return fd;
 }
 
+int open_file_or_dir(const char *fname, DIR **dirstream)
+{
+       return open_file_or_dir3(fname, dirstream, O_RDWR);
+}
+
 void close_file_or_dir(int fd, DIR *dirstream)
 {
        if (dirstream)
@@ -1592,7 +1909,7 @@ void close_file_or_dir(int fd, DIR *dirstream)
                close(fd);
 }
 
-static int get_device_info(int fd, u64 devid,
+int get_device_info(int fd, u64 devid,
                struct btrfs_ioctl_dev_info_args *di_args)
 {
        int ret;
@@ -1604,6 +1921,75 @@ static int get_device_info(int fd, u64 devid,
        return ret ? -errno : 0;
 }
 
+static u64 find_max_device_id(struct btrfs_ioctl_search_args *search_args,
+                             int nr_items)
+{
+       struct btrfs_dev_item *dev_item;
+       char *buf = search_args->buf;
+
+       buf += (nr_items - 1) * (sizeof(struct btrfs_ioctl_search_header)
+                                      + sizeof(struct btrfs_dev_item));
+       buf += sizeof(struct btrfs_ioctl_search_header);
+
+       dev_item = (struct btrfs_dev_item *)buf;
+
+       return btrfs_stack_device_id(dev_item);
+}
+
+static int search_chunk_tree_for_fs_info(int fd,
+                               struct btrfs_ioctl_fs_info_args *fi_args)
+{
+       int ret;
+       int max_items;
+       u64 start_devid = 1;
+       struct btrfs_ioctl_search_args search_args;
+       struct btrfs_ioctl_search_key *search_key = &search_args.key;
+
+       fi_args->num_devices = 0;
+
+       max_items = BTRFS_SEARCH_ARGS_BUFSIZE
+              / (sizeof(struct btrfs_ioctl_search_header)
+                              + sizeof(struct btrfs_dev_item));
+
+       search_key->tree_id = BTRFS_CHUNK_TREE_OBJECTID;
+       search_key->min_objectid = BTRFS_DEV_ITEMS_OBJECTID;
+       search_key->max_objectid = BTRFS_DEV_ITEMS_OBJECTID;
+       search_key->min_type = BTRFS_DEV_ITEM_KEY;
+       search_key->max_type = BTRFS_DEV_ITEM_KEY;
+       search_key->min_transid = 0;
+       search_key->max_transid = (u64)-1;
+       search_key->nr_items = max_items;
+       search_key->max_offset = (u64)-1;
+
+again:
+       search_key->min_offset = start_devid;
+
+       ret = ioctl(fd, BTRFS_IOC_TREE_SEARCH, &search_args);
+       if (ret < 0)
+               return -errno;
+
+       fi_args->num_devices += (u64)search_key->nr_items;
+
+       if (search_key->nr_items == max_items) {
+               start_devid = find_max_device_id(&search_args,
+                                       search_key->nr_items) + 1;
+               goto again;
+       }
+
+       /* get the lastest max_id to stay consistent with the num_devices */
+       if (search_key->nr_items == 0)
+               /*
+                * last tree_search returns an empty buf, use the devid of
+                * the last dev_item of the previous tree_search
+                */
+               fi_args->max_id = start_devid - 1;
+       else
+               fi_args->max_id = find_max_device_id(&search_args,
+                                               search_key->nr_items);
+
+       return 0;
+}
+
 /*
  * For a given path, fill in the ioctl fs_ and info_ args.
  * If the path is a btrfs mountpoint, fill info for all devices.
@@ -1620,15 +2006,21 @@ int get_fs_info(char *path, struct btrfs_ioctl_fs_info_args *fi_args,
        int fd = -1;
        int ret = 0;
        int ndevs = 0;
-       int i = 1;
+       int i = 0;
+       int replacing = 0;
        struct btrfs_fs_devices *fs_devices_mnt = NULL;
        struct btrfs_ioctl_dev_info_args *di_args;
+       struct btrfs_ioctl_dev_info_args tmp;
        char mp[BTRFS_PATH_NAME_MAX + 1];
        DIR *dirstream = NULL;
 
        memset(fi_args, 0, sizeof(*fi_args));
 
        if (is_block_device(path)) {
+               struct btrfs_super_block *disk_super;
+               char buf[BTRFS_SUPER_INFO_SIZE];
+               u64 devid;
+
                /* Ensure it's mounted, then set path to the mountpoint */
                fd = open(path, O_RDONLY);
                if (fd < 0) {
@@ -1648,8 +2040,19 @@ int get_fs_info(char *path, struct btrfs_ioctl_fs_info_args *fi_args,
                path = mp;
                /* Only fill in this one device */
                fi_args->num_devices = 1;
-               fi_args->max_id = fs_devices_mnt->latest_devid;
-               i = fs_devices_mnt->latest_devid;
+
+               disk_super = (struct btrfs_super_block *)buf;
+               ret = btrfs_read_dev_super(fd, disk_super,
+                                          BTRFS_SUPER_INFO_OFFSET, 0);
+               if (ret < 0) {
+                       ret = -EIO;
+                       goto out;
+               }
+               devid = btrfs_stack_device_id(&disk_super->dev_item);
+
+               fi_args->max_id = devid;
+               i = devid;
+
                memcpy(fi_args->fsid, fs_devices_mnt->fsid, BTRFS_FSID_SIZE);
                close(fd);
        }
@@ -1668,19 +2071,40 @@ int get_fs_info(char *path, struct btrfs_ioctl_fs_info_args *fi_args,
                        ret = -errno;
                        goto out;
                }
+
+               /*
+                * The fs_args->num_devices does not include seed devices
+                */
+               ret = search_chunk_tree_for_fs_info(fd, fi_args);
+               if (ret)
+                       goto out;
+
+               /*
+                * search_chunk_tree_for_fs_info() will lacks the devid 0
+                * so manual probe for it here.
+                */
+               ret = get_device_info(fd, 0, &tmp);
+               if (!ret) {
+                       fi_args->num_devices++;
+                       ndevs++;
+                       replacing = 1;
+                       if (i == 0)
+                               i++;
+               }
        }
 
        if (!fi_args->num_devices)
                goto out;
 
-       di_args = *di_ret = malloc(fi_args->num_devices * sizeof(*di_args));
+       di_args = *di_ret = malloc((fi_args->num_devices) * sizeof(*di_args));
        if (!di_args) {
                ret = -errno;
                goto out;
        }
 
+       if (replacing)
+               memcpy(di_args, &tmp, sizeof(tmp));
        for (; i <= fi_args->max_id; ++i) {
-               BUG_ON(ndevs >= fi_args->num_devices);
                ret = get_device_info(fd, i, &di_args[ndevs]);
                if (ret == -ENODEV)
                        continue;
@@ -1689,8 +2113,15 @@ int get_fs_info(char *path, struct btrfs_ioctl_fs_info_args *fi_args,
                ndevs++;
        }
 
-       BUG_ON(ndevs == 0);
-       ret = 0;
+       /*
+       * only when the only dev we wanted to find is not there then
+       * let any error be returned
+       */
+       if (fi_args->num_devices != 1) {
+               BUG_ON(ndevs == 0);
+               ret = 0;
+       }
+
 out:
        close_file_or_dir(fd, dirstream);
        return ret;
@@ -1851,6 +2282,25 @@ out:
        return ret;
 }
 
+static int group_profile_devs_min(u64 flag)
+{
+       switch (flag & BTRFS_BLOCK_GROUP_PROFILE_MASK) {
+       case 0: /* single */
+       case BTRFS_BLOCK_GROUP_DUP:
+               return 1;
+       case BTRFS_BLOCK_GROUP_RAID0:
+       case BTRFS_BLOCK_GROUP_RAID1:
+       case BTRFS_BLOCK_GROUP_RAID5:
+               return 2;
+       case BTRFS_BLOCK_GROUP_RAID6:
+               return 3;
+       case BTRFS_BLOCK_GROUP_RAID10:
+               return 4;
+       default:
+               return -1;
+       }
+}
+
 int test_num_disk_vs_raid(u64 metadata_profile, u64 data_profile,
        u64 dev_cnt, int mixed, char *estr)
 {
@@ -1871,16 +2321,26 @@ int test_num_disk_vs_raid(u64 metadata_profile, u64 data_profile,
                allowed |= BTRFS_BLOCK_GROUP_DUP;
        }
 
+       if (dev_cnt > 1 &&
+           ((metadata_profile | data_profile) & BTRFS_BLOCK_GROUP_DUP)) {
+               snprintf(estr, sz,
+                       "DUP is not allowed when FS has multiple devices\n");
+               return 1;
+       }
        if (metadata_profile & ~allowed) {
-               snprintf(estr, sz, "unable to create FS with metadata "
-                       "profile %llu (have %llu devices)\n",
-                       metadata_profile, dev_cnt);
+               snprintf(estr, sz,
+                       "unable to create FS with metadata profile %s "
+                       "(have %llu devices but %d devices are required)\n",
+                       btrfs_group_profile_str(metadata_profile), dev_cnt,
+                       group_profile_devs_min(metadata_profile));
                return 1;
        }
        if (data_profile & ~allowed) {
-               snprintf(estr, sz, "unable to create FS with data "
-                       "profile %llu (have %llu devices)\n",
-                       metadata_profile, dev_cnt);
+               snprintf(estr, sz,
+                       "unable to create FS with data profile %s "
+                       "(have %llu devices but %d devices are required)\n",
+                       btrfs_group_profile_str(data_profile), dev_cnt,
+                       group_profile_devs_min(data_profile));
                return 1;
        }
 
@@ -1951,22 +2411,7 @@ int test_dev_for_mkfs(char *file, int force_overwrite, char *estr)
        return 0;
 }
 
-static int test_skip_this_disk(char *path)
-{
-       int fd;
-
-       /*
-        * this will eliminate disks which are mounted (btrfs)
-        * and non-dm disk path when dm is enabled
-        */
-       fd = open(path, O_RDWR|O_EXCL);
-       if (fd < 0)
-               return 1;
-       close(fd);
-       return 0;
-}
-
-int btrfs_scan_lblkid(int update_kernel)
+int btrfs_scan_lblkid()
 {
        int fd = -1;
        int ret;
@@ -1977,6 +2422,9 @@ int btrfs_scan_lblkid(int update_kernel)
        blkid_cache cache = NULL;
        char path[PATH_MAX];
 
+       if (btrfs_scan_done)
+               return 0;
+
        if (blkid_get_cache(&cache, 0) < 0) {
                printf("ERROR: lblkid cache get failed\n");
                return 1;
@@ -1989,9 +2437,7 @@ int btrfs_scan_lblkid(int update_kernel)
                if (!dev)
                        continue;
                /* if we are here its definitely a btrfs disk*/
-               strncpy(path, blkid_dev_devname(dev), PATH_MAX);
-               if (test_skip_this_disk(path))
-                       continue;
+               strncpy_null(path, blkid_dev_devname(dev));
 
                fd = open(path, O_RDONLY);
                if (fd < 0) {
@@ -1999,7 +2445,7 @@ int btrfs_scan_lblkid(int update_kernel)
                        continue;
                }
                ret = btrfs_scan_one_device(fd, path, &tmp_devices,
-                               &num_devices, BTRFS_SUPER_INFO_OFFSET);
+                               &num_devices, BTRFS_SUPER_INFO_OFFSET, 0);
                if (ret) {
                        printf("ERROR: could not scan %s\n", path);
                        close (fd);
@@ -2007,32 +2453,13 @@ int btrfs_scan_lblkid(int update_kernel)
                }
 
                close(fd);
-               if (update_kernel)
-                       btrfs_register_one_device(path);
        }
        blkid_dev_iterate_end(iter);
-       return 0;
-}
+       blkid_put_cache(cache);
 
-/*
- * scans devs for the btrfs
-*/
-int scan_for_btrfs(int where, int update_kernel)
-{
-       int ret = 0;
+       btrfs_scan_done = 1;
 
-       switch (where) {
-       case BTRFS_SCAN_PROC:
-               ret = btrfs_scan_block_devices(update_kernel);
-               break;
-       case BTRFS_SCAN_DEV:
-               ret = btrfs_scan_one_dir("/dev", update_kernel);
-               break;
-       case BTRFS_SCAN_LBLKID:
-               ret = btrfs_scan_lblkid(update_kernel);
-               break;
-       }
-       return ret;
+       return 0;
 }
 
 int is_vol_small(char *file)
@@ -2055,7 +2482,7 @@ int is_vol_small(char *file)
                close(fd);
                return -1;
        }
-       if (size < 1024 * 1024 * 1024) {
+       if (size < BTRFS_MKFS_SMALL_VOLUME_SIZE) {
                close(fd);
                return 1;
        } else {
@@ -2085,7 +2512,7 @@ int ask_user(char *question)
 /*
  * For a given:
  * - file or directory return the containing tree root id
- * - subvolume return it's own tree id
+ * - subvolume return its own tree id
  * - BTRFS_EMPTY_SUBVOL_DIR_OBJECTID (directory with ino == 2) the result is
  *   undefined and function returns -1
  */
@@ -2111,3 +2538,274 @@ int lookup_ino_rootid(int fd, u64 *rootid)
 
        return 0;
 }
+
+/*
+ * return 0 if a btrfs mount point is found
+ * return 1 if a mount point is found but not btrfs
+ * return <0 if something goes wrong
+ */
+int find_mount_root(const char *path, char **mount_root)
+{
+       FILE *mnttab;
+       int fd;
+       struct mntent *ent;
+       int len;
+       int ret;
+       int not_btrfs = 1;
+       int longest_matchlen = 0;
+       char *longest_match = NULL;
+
+       fd = open(path, O_RDONLY | O_NOATIME);
+       if (fd < 0)
+               return -errno;
+       close(fd);
+
+       mnttab = setmntent("/proc/self/mounts", "r");
+       if (!mnttab)
+               return -errno;
+
+       while ((ent = getmntent(mnttab))) {
+               len = strlen(ent->mnt_dir);
+               if (strncmp(ent->mnt_dir, path, len) == 0) {
+                       /* match found and use the latest match */
+                       if (longest_matchlen <= len) {
+                               free(longest_match);
+                               longest_matchlen = len;
+                               longest_match = strdup(ent->mnt_dir);
+                               not_btrfs = strcmp(ent->mnt_type, "btrfs");
+                       }
+               }
+       }
+       endmntent(mnttab);
+
+       if (!longest_match)
+               return -ENOENT;
+       if (not_btrfs) {
+               free(longest_match);
+               return 1;
+       }
+
+       ret = 0;
+       *mount_root = realpath(longest_match, NULL);
+       if (!*mount_root)
+               ret = -errno;
+
+       free(longest_match);
+       return ret;
+}
+
+int test_minimum_size(const char *file, u32 nodesize)
+{
+       int fd;
+       struct stat statbuf;
+
+       fd = open(file, O_RDONLY);
+       if (fd < 0)
+               return -errno;
+       if (stat(file, &statbuf) < 0) {
+               close(fd);
+               return -errno;
+       }
+       if (btrfs_device_size(fd, &statbuf) < btrfs_min_dev_size(nodesize)) {
+               close(fd);
+               return 1;
+       }
+       close(fd);
+       return 0;
+}
+
+/*
+ * test if name is a correct subvolume name
+ * this function return
+ * 0-> name is not a correct subvolume name
+ * 1-> name is a correct subvolume name
+ */
+int test_issubvolname(const char *name)
+{
+       return name[0] != '\0' && !strchr(name, '/') &&
+               strcmp(name, ".") && strcmp(name, "..");
+}
+
+/*
+ * test if path is a directory
+ * this function return
+ * 0-> path exists but it is not a directory
+ * 1-> path exists and it is a directory
+ * -1 -> path is unaccessible
+ */
+int test_isdir(const char *path)
+{
+       struct stat st;
+       int ret;
+
+       ret = stat(path, &st);
+       if(ret < 0 )
+               return -1;
+
+       return S_ISDIR(st.st_mode);
+}
+
+void units_set_mode(unsigned *units, unsigned mode)
+{
+       unsigned base = *units & UNITS_MODE_MASK;
+
+       *units = base | mode;
+}
+
+void units_set_base(unsigned *units, unsigned base)
+{
+       unsigned mode = *units & ~UNITS_MODE_MASK;
+
+       *units = base | mode;
+}
+
+int find_next_key(struct btrfs_path *path, struct btrfs_key *key)
+{
+       int level;
+
+       for (level = 0; level < BTRFS_MAX_LEVEL; level++) {
+               if (!path->nodes[level])
+                       break;
+               if (path->slots[level] + 1 >=
+                   btrfs_header_nritems(path->nodes[level]))
+                       continue;
+               if (level == 0)
+                       btrfs_item_key_to_cpu(path->nodes[level], key,
+                                             path->slots[level] + 1);
+               else
+                       btrfs_node_key_to_cpu(path->nodes[level], key,
+                                             path->slots[level] + 1);
+               return 0;
+       }
+       return 1;
+}
+
+char* btrfs_group_type_str(u64 flag)
+{
+       u64 mask = BTRFS_BLOCK_GROUP_TYPE_MASK |
+               BTRFS_SPACE_INFO_GLOBAL_RSV;
+
+       switch (flag & mask) {
+       case BTRFS_BLOCK_GROUP_DATA:
+               return "Data";
+       case BTRFS_BLOCK_GROUP_SYSTEM:
+               return "System";
+       case BTRFS_BLOCK_GROUP_METADATA:
+               return "Metadata";
+       case BTRFS_BLOCK_GROUP_DATA|BTRFS_BLOCK_GROUP_METADATA:
+               return "Data+Metadata";
+       case BTRFS_SPACE_INFO_GLOBAL_RSV:
+               return "GlobalReserve";
+       default:
+               return "unknown";
+       }
+}
+
+char* btrfs_group_profile_str(u64 flag)
+{
+       switch (flag & BTRFS_BLOCK_GROUP_PROFILE_MASK) {
+       case 0:
+               return "single";
+       case BTRFS_BLOCK_GROUP_RAID0:
+               return "RAID0";
+       case BTRFS_BLOCK_GROUP_RAID1:
+               return "RAID1";
+       case BTRFS_BLOCK_GROUP_RAID5:
+               return "RAID5";
+       case BTRFS_BLOCK_GROUP_RAID6:
+               return "RAID6";
+       case BTRFS_BLOCK_GROUP_DUP:
+               return "DUP";
+       case BTRFS_BLOCK_GROUP_RAID10:
+               return "RAID10";
+       default:
+               return "unknown";
+       }
+}
+
+u64 disk_size(char *path)
+{
+       struct statfs sfs;
+
+       if (statfs(path, &sfs) < 0)
+               return 0;
+       else
+               return sfs.f_bsize * sfs.f_blocks;
+}
+
+u64 get_partition_size(char *dev)
+{
+       u64 result;
+       int fd = open(dev, O_RDONLY);
+
+       if (fd < 0)
+               return 0;
+       if (ioctl(fd, BLKGETSIZE64, &result) < 0) {
+               close(fd);
+               return 0;
+       }
+       close(fd);
+
+       return result;
+}
+
+int btrfs_tree_search2_ioctl_supported(int fd)
+{
+       struct btrfs_ioctl_search_args_v2 *args2;
+       struct btrfs_ioctl_search_key *sk;
+       int args2_size = 1024;
+       char args2_buf[args2_size];
+       int ret;
+       static int v2_supported = -1;
+
+       if (v2_supported != -1)
+               return v2_supported;
+
+       args2 = (struct btrfs_ioctl_search_args_v2 *)args2_buf;
+       sk = &(args2->key);
+
+       /*
+        * Search for the extent tree item in the root tree.
+        */
+       sk->tree_id = BTRFS_ROOT_TREE_OBJECTID;
+       sk->min_objectid = BTRFS_EXTENT_TREE_OBJECTID;
+       sk->max_objectid = BTRFS_EXTENT_TREE_OBJECTID;
+       sk->min_type = BTRFS_ROOT_ITEM_KEY;
+       sk->max_type = BTRFS_ROOT_ITEM_KEY;
+       sk->min_offset = 0;
+       sk->max_offset = (u64)-1;
+       sk->min_transid = 0;
+       sk->max_transid = (u64)-1;
+       sk->nr_items = 1;
+       args2->buf_size = args2_size - sizeof(struct btrfs_ioctl_search_args_v2);
+       ret = ioctl(fd, BTRFS_IOC_TREE_SEARCH_V2, args2);
+       if (ret == -EOPNOTSUPP)
+               v2_supported = 0;
+       else if (ret == 0)
+               v2_supported = 1;
+       else
+               return ret;
+
+       return v2_supported;
+}
+
+int btrfs_check_nodesize(u32 nodesize, u32 sectorsize)
+{
+       if (nodesize < sectorsize) {
+               fprintf(stderr,
+                       "ERROR: Illegal nodesize %u (smaller than %u)\n",
+                       nodesize, sectorsize);
+               return -1;
+       } else if (nodesize > BTRFS_MAX_METADATA_BLOCKSIZE) {
+               fprintf(stderr,
+                       "ERROR: Illegal nodesize %u (larger than %u)\n",
+                       nodesize, BTRFS_MAX_METADATA_BLOCKSIZE);
+               return -1;
+       } else if (nodesize & (sectorsize - 1)) {
+               fprintf(stderr,
+                       "ERROR: Illegal nodesize %u (not aligned to %u)\n",
+                       nodesize, sectorsize);
+               return -1;
+       }
+       return 0;
+}