Btrfs-progs: make btrfs-image restore with a valid chunk tree V2
[platform/upstream/btrfs-progs.git] / utils.c
diff --git a/utils.c b/utils.c
index f68436d..a4f7b06 100644 (file)
--- a/utils.c
+++ b/utils.c
@@ -415,7 +415,7 @@ int make_btrfs(int fd, const char *device, const char *label,
        return 0;
 }
 
-static u64 device_size(int fd, struct stat *st)
+u64 btrfs_device_size(int fd, struct stat *st)
 {
        u64 size;
        if (S_ISREG(st->st_mode)) {
@@ -555,7 +555,7 @@ int btrfs_prepare_device(int fd, char *file, int zero_end, u64 *block_count_ret,
                exit(1);
        }
 
-       block_count = device_size(fd, &st);
+       block_count = btrfs_device_size(fd, &st);
        if (block_count == 0) {
                fprintf(stderr, "unable to find %s size\n", file);
                exit(1);
@@ -640,6 +640,93 @@ error:
        return ret;
 }
 
+/*
+ * checks if a path is a block device node
+ * Returns negative errno on failure, otherwise
+ * returns 1 for blockdev, 0 for not-blockdev
+ */
+int is_block_device(const char *path) {
+       struct stat statbuf;
+
+       if (stat(path, &statbuf) < 0)
+               return -errno;
+
+       return S_ISBLK(statbuf.st_mode);
+}
+
+/*
+ * Find the mount point for a mounted device.
+ * On success, returns 0 with mountpoint in *mp.
+ * On failure, returns -errno (not mounted yields -EINVAL)
+ * Is noisy on failures, expects to be given a mounted device.
+ */
+int get_btrfs_mount(const char *dev, char *mp, size_t mp_size) {
+       int ret;
+       int fd = -1;
+
+       ret = is_block_device(dev);
+       if (ret <= 0) {
+               if (!ret) {
+                       fprintf(stderr, "%s is not a block device\n", dev);
+                       ret = -EINVAL;
+               } else {
+                       fprintf(stderr, "Could not check %s: %s\n",
+                               dev, strerror(-ret));
+               }
+               goto out;
+       }
+
+       fd = open(dev, O_RDONLY);
+       if (fd < 0) {
+               ret = -errno;
+               fprintf(stderr, "Could not open %s: %s\n", dev, strerror(errno));
+               goto out;
+       }
+
+       ret = check_mounted_where(fd, dev, mp, mp_size, NULL);
+       if (!ret) {
+               fprintf(stderr, "%s is not a mounted btrfs device\n", dev);
+               ret = -EINVAL;
+       } else { /* mounted, all good */
+               ret = 0;
+       }
+out:
+       if (fd != -1)
+               close(fd);
+       if (ret)
+               fprintf(stderr, "Could not get mountpoint for %s\n", dev);
+       return ret;
+}
+
+/*
+ * Given a pathname, return a filehandle to:
+ *     the original pathname or,
+ *     if the pathname is a mounted btrfs device, to its mountpoint.
+ *
+ * On error, return -1, errno should be set.
+ */
+int open_path_or_dev_mnt(const char *path)
+{
+       char mp[BTRFS_PATH_NAME_MAX + 1];
+       int fdmnt;
+
+       if (is_block_device(path)) {
+               int ret;
+
+               ret = get_btrfs_mount(path, mp, sizeof(mp));
+               if (ret < 0) {
+                       /* not a mounted btrfs dev */
+                       errno = EINVAL;
+                       return -1;
+               }
+               fdmnt = open_file_or_dir(mp);
+       } else {
+               fdmnt = open_file_or_dir(path);
+       }
+
+       return fdmnt;
+}
+
 /* checks if a device is a loop device */
 int is_loop_device (const char* device) {
        struct stat statbuf;
@@ -937,7 +1024,8 @@ void btrfs_register_one_device(char *fname)
        fd = open("/dev/btrfs-control", O_RDONLY);
        if (fd < 0) {
                fprintf(stderr, "failed to open /dev/btrfs-control "
-                       "skipping device registration\n");
+                       "skipping device registration: %s\n",
+                       strerror(errno));
                return;
        }
        strncpy(args.name, fname, BTRFS_PATH_NAME_MAX);
@@ -1217,6 +1305,7 @@ static int set_label_mounted(const char *mount_path, const char *label)
                return -1;
        }
 
+       close(fd);
        return 0;
 }
 
@@ -1274,6 +1363,7 @@ static int get_label_mounted(const char *mount_path)
        }
 
        fprintf(stdout, "%s\n", label);
+       close(fd);
        return 0;
 }
 
@@ -1458,9 +1548,20 @@ int get_device_info(int fd, u64 devid,
        return ret ? -errno : 0;
 }
 
-int get_fs_info(int fd, char *path, struct btrfs_ioctl_fs_info_args *fi_args,
+/*
+ * For a given path, fill in the ioctl fs_ and info_ args.
+ * If the path is a btrfs mountpoint, fill info for all devices.
+ * If the path is a btrfs device, fill in only that device.
+ *
+ * The path provided must be either on a mounted btrfs fs,
+ * or be a mounted btrfs device.
+ *
+ * Returns 0 on success, or a negative errno.
+ */
+int get_fs_info(char *path, struct btrfs_ioctl_fs_info_args *fi_args,
                struct btrfs_ioctl_dev_info_args **di_ret)
 {
+       int fd = -1;
        int ret = 0;
        int ndevs = 0;
        int i = 1;
@@ -1470,33 +1571,56 @@ int get_fs_info(int fd, char *path, struct btrfs_ioctl_fs_info_args *fi_args,
 
        memset(fi_args, 0, sizeof(*fi_args));
 
-       ret = ioctl(fd, BTRFS_IOC_FS_INFO, fi_args);
-       if (ret && (errno == EINVAL || errno == ENOTTY)) {
-               /* path is not a mounted btrfs. Try if it's a device */
+       if (is_block_device(path)) {
+               /* Ensure it's mounted, then set path to the mountpoint */
+               fd = open(path, O_RDONLY);
+               if (fd < 0) {
+                       ret = -errno;
+                       fprintf(stderr, "Couldn't open %s: %s\n",
+                               path, strerror(errno));
+                       goto out;
+               }
                ret = check_mounted_where(fd, path, mp, sizeof(mp),
                                          &fs_devices_mnt);
-               if (!ret)
-                       return -EINVAL;
+               if (!ret) {
+                       ret = -EINVAL;
+                       goto out;
+               }
                if (ret < 0)
-                       return ret;
+                       goto out;
+               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;
                memcpy(fi_args->fsid, fs_devices_mnt->fsid, BTRFS_FSID_SIZE);
                close(fd);
-               fd = open_file_or_dir(mp);
-               if (fd < 0)
-                       return -errno;
-       } else if (ret) {
-               return -errno;
+       }
+
+       /* at this point path must not be for a block device */
+       fd = open_file_or_dir(path);
+       if (fd < 0) {
+               ret = -errno;
+               goto out;
+       }
+
+       /* fill in fi_args if not just a single device */
+       if (fi_args->num_devices != 1) {
+               ret = ioctl(fd, BTRFS_IOC_FS_INFO, fi_args);
+               if (ret < 0) {
+                       ret = -errno;
+                       goto out;
+               }
        }
 
        if (!fi_args->num_devices)
-               return 0;
+               goto out;
 
        di_args = *di_ret = malloc(fi_args->num_devices * sizeof(*di_args));
-       if (!di_args)
-               return -errno;
+       if (!di_args) {
+               ret = -errno;
+               goto out;
+       }
 
        for (; i <= fi_args->max_id; ++i) {
                BUG_ON(ndevs >= fi_args->num_devices);
@@ -1504,13 +1628,16 @@ int get_fs_info(int fd, char *path, struct btrfs_ioctl_fs_info_args *fi_args,
                if (ret == -ENODEV)
                        continue;
                if (ret)
-                       return ret;
+                       goto out;
                ndevs++;
        }
 
        BUG_ON(ndevs == 0);
-
-       return 0;
+       ret = 0;
+out:
+       if (fd != -1)
+               close(fd);
+       return ret;
 }
 
 #define isoctal(c)     (((c) & ~7) == '0')