Btrfs-progs: check, ability to detect and fix outdated snapshot root items
[platform/upstream/btrfs-progs.git] / utils.c
diff --git a/utils.c b/utils.c
index f9ad946..f10c178 100644 (file)
--- a/utils.c
+++ b/utils.c
@@ -585,25 +585,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,
@@ -680,11 +678,46 @@ 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;
 
@@ -702,10 +735,8 @@ int btrfs_prepare_device(int fd, char *file, int zero_end, u64 *block_count_ret,
        if (max_block_count)
                block_count = min(block_count, max_block_count);
 
-       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) {
                /*
@@ -720,36 +751,23 @@ int btrfs_prepare_device(int fd, char *file, int zero_end, u64 *block_count_ret,
                }
        }
 
-       ret = zero_dev_start(fd);
-       if (ret)
-               goto zero_dev_error;
-
-       for (i = 0 ; i < BTRFS_SUPER_MIRROR_MAX; i++) {
-               bytenr = btrfs_sb_offset(i);
-               if (bytenr >= block_count)
-                       break;
-               ret = zero_blocks(fd, bytenr, BTRFS_SUPER_INFO_SIZE);
-               if (ret)
-                       goto zero_dev_error;
-       }
-
-       if (zero_end) {
-               ret = zero_dev_end(fd, block_count);
-               if (ret)
-                       goto zero_dev_error;
-       }
-       *block_count_ret = block_count;
+       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);
 
-zero_dev_error:
        if (ret < 0) {
                fprintf(stderr, "ERROR: failed to zero device '%s' - %s\n",
                        file, strerror(-ret));
                return 1;
-       } else if (ret > 0) {
-               fprintf(stderr, "ERROR: failed to zero device '%s' - %d\n",
-                       file, ret);
-               return 1;
        }
+
+       btrfs_wipe_existing_sb(fd);
+
+       *block_count_ret = block_count;
        return 0;
 }
 
@@ -1168,7 +1186,8 @@ int check_mounted_where(int fd, const char *file, char *where, int size,
 
        /* scan other devices */
        if (is_btrfs && total_devs > 1) {
-               if ((ret = btrfs_scan_for_fsid(!BTRFS_UPDATE_KERNEL)))
+               ret = btrfs_scan_lblkid(!BTRFS_UPDATE_KERNEL);
+               if (ret)
                        return ret;
        }
 
@@ -1227,7 +1246,7 @@ void btrfs_register_one_device(char *fname)
        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",
@@ -1245,127 +1264,6 @@ void btrfs_register_one_device(char *fname)
        close(fd);
 }
 
-int btrfs_scan_one_dir(char *dirname, int run_ioctl)
-{
-       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 = -errno;
-               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;
-                       }
-                       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, 0);
-               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;
-}
-
 int btrfs_device_already_in_root(struct btrfs_root *root, int fd,
                                 int super_offset)
 {
@@ -1396,35 +1294,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]);
 }
 
 /*
@@ -1583,22 +1522,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)
@@ -1633,7 +1579,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
@@ -1693,7 +1644,7 @@ static int fls64(u64 x)
        int i;
 
        for (i = 0; i <64; i++)
-               if (x << i & (1UL << 63))
+               if (x << i & (1ULL << 63))
                        return 64 - i;
        return 64 - i;
 }
@@ -1914,12 +1865,29 @@ int get_fs_info(char *path, struct btrfs_ioctl_fs_info_args *fi_args,
        if (!fi_args->num_devices)
                goto out;
 
-       di_args = *di_ret = malloc(fi_args->num_devices * sizeof(*di_args));
+       /*
+        * with kernel patch
+        * btrfs: ioctl BTRFS_IOC_FS_INFO and BTRFS_IOC_DEV_INFO miss-matched with slots
+        * the kernel now returns total_devices which does not include
+        * replacing device if running.
+        * As we need to get dev info of the replace device if it is running,
+        * so just add one to fi_args->num_devices.
+        */
+
+       di_args = *di_ret = malloc((fi_args->num_devices + 1) * sizeof(*di_args));
        if (!di_args) {
                ret = -errno;
                goto out;
        }
 
+       /* get the replace target device if it is there */
+       ret = get_device_info(fd, i, &di_args[ndevs]);
+       if (!ret) {
+               ndevs++;
+               fi_args->num_devices++;
+       }
+       i++;
+
        for (; i <= fi_args->max_id; ++i) {
                BUG_ON(ndevs >= fi_args->num_devices);
                ret = get_device_info(fd, i, &di_args[ndevs]);
@@ -2246,27 +2214,6 @@ int btrfs_scan_lblkid(int update_kernel)
        return 0;
 }
 
-/*
- * scans devs for the btrfs
-*/
-int scan_for_btrfs(int where, int update_kernel)
-{
-       int ret = 0;
-
-       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;
-}
-
 int is_vol_small(char *file)
 {
        int fd = -1;
@@ -2287,7 +2234,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 {
@@ -2317,7 +2264,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
  */
@@ -2344,6 +2291,11 @@ 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;
@@ -2351,6 +2303,7 @@ int find_mount_root(const char *path, char **mount_root)
        struct mntent *ent;
        int len;
        int ret;
+       int not_btrfs = 1;
        int longest_matchlen = 0;
        char *longest_match = NULL;
 
@@ -2366,21 +2319,22 @@ int find_mount_root(const char *path, char **mount_root)
        while ((ent = getmntent(mnttab))) {
                len = strlen(ent->mnt_dir);
                if (strncmp(ent->mnt_dir, path, len) == 0) {
-                       /* match found */
-                       if (longest_matchlen < len) {
+                       /* 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) {
-               fprintf(stderr,
-                       "ERROR: Failed to find mount root for path %s.\n",
-                       path);
+       if (!longest_match)
                return -ENOENT;
+       if (not_btrfs) {
+               free(longest_match);
+               return 1;
        }
 
        ret = 0;
@@ -2411,3 +2365,69 @@ int test_minimum_size(const char *file, u32 leafsize)
        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;
+}