Btrfs-progs: fix a regression in mkfs.btrfs
[platform/upstream/btrfs-progs.git] / utils.c
diff --git a/utils.c b/utils.c
index d92f317..43e7e32 100644 (file)
--- a/utils.c
+++ b/utils.c
@@ -1,5 +1,6 @@
 /*
  * Copyright (C) 2007 Oracle.  All rights reserved.
+ * Copyright (C) 2008 Morey Roof.  All rights reserved.
  *
  * This program is free software; you can redistribute it and/or
  * modify it under the terms of the GNU General Public
@@ -19,6 +20,7 @@
 #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>
@@ -29,7 +31,6 @@
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <uuid/uuid.h>
-#include <dirent.h>
 #include <fcntl.h>
 #include <unistd.h>
 #include <mntent.h>
@@ -38,6 +39,7 @@
 #include <linux/major.h>
 #include <linux/kdev_t.h>
 #include <limits.h>
+#include <blkid/blkid.h>
 #include "kerncompat.h"
 #include "radix-tree.h"
 #include "ctree.h"
@@ -112,7 +114,7 @@ int make_btrfs(int fd, const char *device, const char *label,
 
        btrfs_set_super_bytenr(&super, blocks[0]);
        btrfs_set_super_num_devices(&super, 1);
-       strncpy((char *)&super.magic, BTRFS_MAGIC, sizeof(super.magic));
+       btrfs_set_super_magic(&super, BTRFS_MAGIC);
        btrfs_set_super_generation(&super, 1);
        btrfs_set_super_root(&super, blocks[1]);
        btrfs_set_super_chunk_root(&super, blocks[3]);
@@ -212,7 +214,10 @@ 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]);
-       BUG_ON(ret != leafsize);
+       if (ret < 0)
+               return -errno;
+       else if (ret != leafsize)
+               return -EIO;
 
        /* create the items for the extent tree */
        memset(buf->data+sizeof(struct btrfs_header), 0,
@@ -259,7 +264,10 @@ int make_btrfs(int fd, const char *device, const char *label,
        btrfs_set_header_nritems(buf, nritems);
        csum_tree_block_size(buf, BTRFS_CRC32_SIZE, 0);
        ret = pwrite(fd, buf->data, leafsize, blocks[2]);
-       BUG_ON(ret != leafsize);
+       if (ret < 0)
+               return -errno;
+       else if (ret != leafsize)
+               return -EIO;
 
        /* create the chunk tree */
        memset(buf->data+sizeof(struct btrfs_header), 0,
@@ -343,6 +351,10 @@ int make_btrfs(int fd, const char *device, const char *label,
        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 < 0)
+               return -errno;
+       else if (ret != leafsize)
+               return -EIO;
 
        /* create the device tree */
        memset(buf->data+sizeof(struct btrfs_header), 0,
@@ -378,6 +390,10 @@ int make_btrfs(int fd, const char *device, const char *label,
        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 < 0)
+               return -errno;
+       else if (ret != leafsize)
+               return -EIO;
 
        /* create the FS root */
        memset(buf->data+sizeof(struct btrfs_header), 0,
@@ -387,7 +403,10 @@ int make_btrfs(int fd, const char *device, const char *label,
        btrfs_set_header_nritems(buf, 0);
        csum_tree_block_size(buf, BTRFS_CRC32_SIZE, 0);
        ret = pwrite(fd, buf->data, leafsize, blocks[5]);
-       BUG_ON(ret != leafsize);
+       if (ret < 0)
+               return -errno;
+       else if (ret != leafsize)
+               return -EIO;
 
        /* finally create the csum root */
        memset(buf->data+sizeof(struct btrfs_header), 0,
@@ -397,7 +416,10 @@ int make_btrfs(int fd, const char *device, const char *label,
        btrfs_set_header_nritems(buf, 0);
        csum_tree_block_size(buf, BTRFS_CRC32_SIZE, 0);
        ret = pwrite(fd, buf->data, leafsize, blocks[6]);
-       BUG_ON(ret != leafsize);
+       if (ret < 0)
+               return -errno;
+       else if (ret != leafsize)
+               return -EIO;
 
        /* and write out the super block */
        BUG_ON(sizeof(super) > sectorsize);
@@ -406,14 +428,16 @@ int make_btrfs(int fd, const char *device, const char *label,
        buf->len = sectorsize;
        csum_tree_block_size(buf, BTRFS_CRC32_SIZE, 0);
        ret = pwrite(fd, buf->data, sectorsize, blocks[0]);
-       BUG_ON(ret != sectorsize);
-
+       if (ret < 0)
+               return -errno;
+       else if (ret != sectorsize)
+               return -EIO;
 
        free(buf);
        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)) {
@@ -471,7 +495,7 @@ int btrfs_add_to_fsid(struct btrfs_trans_handle *trans,
                      u32 sectorsize)
 {
        struct btrfs_super_block *disk_super;
-       struct btrfs_super_block *super = &root->fs_info->super_copy;
+       struct btrfs_super_block *super = root->fs_info->super_copy;
        struct btrfs_device *device;
        struct btrfs_dev_item *dev_item;
        char *buf;
@@ -479,7 +503,7 @@ int btrfs_add_to_fsid(struct btrfs_trans_handle *trans,
        u64 num_devs;
        int ret;
 
-       device = kmalloc(sizeof(*device), GFP_NOFS);
+       device = kzalloc(sizeof(*device), GFP_NOFS);
        if (!device)
                return -ENOMEM;
        buf = kmalloc(sectorsize, GFP_NOFS);
@@ -553,7 +577,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);
@@ -622,7 +646,7 @@ int btrfs_make_root_dir(struct btrfs_trans_handle *trans,
        btrfs_set_stack_timespec_nsec(&inode_item.otime, 0);
 
        if (root->fs_info->tree_root == root)
-               btrfs_set_super_root_dir(&root->fs_info->super_copy, objectid);
+               btrfs_set_super_root_dir(root->fs_info->super_copy, objectid);
 
        ret = btrfs_insert_inode(trans, root, objectid, &inode_item);
        if (ret)
@@ -638,6 +662,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.
+ */
+static 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, DIR **dirstream)
+{
+       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, dirstream);
+       } else {
+               fdmnt = open_file_or_dir(path, dirstream);
+       }
+
+       return fdmnt;
+}
+
 /* checks if a device is a loop device */
 int is_loop_device (const char* device) {
        struct stat statbuf;
@@ -838,7 +949,7 @@ 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(fs_devices_mnt, total_devs, 1)))
+               if((ret = btrfs_scan_for_fsid(1)))
                        return ret;
        }
 
@@ -885,44 +996,9 @@ out_mntloop_err:
        return ret;
 }
 
-/* Gets the mount point of btrfs filesystem that is using the specified device.
- * Returns 0 is everything is good, <0 if we have an error.
- * TODO: Fix this fucntion and check_mounted to work with multiple drive BTRFS
- * setups.
- */
-int get_mountpt(char *dev, char *mntpt, size_t size)
-{
-       struct mntent *mnt;
-       FILE *f;
-       int ret = 0;
-
-       f = setmntent("/proc/mounts", "r");
-       if (f == NULL)
-               return -errno;
-
-       while ((mnt = getmntent(f)) != NULL )
-       {
-               if (strcmp(dev, mnt->mnt_fsname) == 0)
-               {
-                       strncpy(mntpt, mnt->mnt_dir, size);
-                       if (size)
-                                mntpt[size-1] = 0;
-                       break;
-               }
-       }
-
-       if (mnt == NULL)
-       {
-               /* We didn't find an entry so lets report an error */
-               ret = -1;
-       }
-
-       return ret;
-}
-
 struct pending_dir {
        struct list_head list;
-       char name[256];
+       char name[PATH_MAX];
 };
 
 void btrfs_register_one_device(char *fname)
@@ -935,7 +1011,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);
@@ -943,7 +1020,7 @@ void btrfs_register_one_device(char *fname)
        ret = ioctl(fd, BTRFS_IOC_SCAN_DEV, &args);
        e = errno;
        if(ret<0){
-               fprintf(stderr, "ERROR: unable to scan the device '%s' - %s\n",
+               fprintf(stderr, "ERROR: device scan failed '%s' - %s\n",
                        fname, strerror(e));
        }
        close(fd);
@@ -958,7 +1035,6 @@ int btrfs_scan_one_dir(char *dirname, int run_ioctl)
        int ret;
        int fd;
        int dirname_len;
-       int pathlen;
        char *fullpath;
        struct list_head pending_list;
        struct btrfs_fs_devices *tmp_devices;
@@ -973,8 +1049,7 @@ int btrfs_scan_one_dir(char *dirname, int run_ioctl)
 
 again:
        dirname_len = strlen(pending->name);
-       pathlen = 1024;
-       fullpath = malloc(pathlen);
+       fullpath = malloc(PATH_MAX);
        dirname = pending->name;
 
        if (!fullpath) {
@@ -993,11 +1068,11 @@ again:
                        break;
                if (dirent->d_name[0] == '.')
                        continue;
-               if (dirname_len + strlen(dirent->d_name) + 2 > pathlen) {
+               if (dirname_len + strlen(dirent->d_name) + 2 > PATH_MAX) {
                        ret = -EFAULT;
                        goto fail;
                }
-               snprintf(fullpath, pathlen, "%s/%s", dirname, dirent->d_name);
+               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);
@@ -1056,14 +1131,13 @@ fail:
        return ret;
 }
 
-int btrfs_scan_for_fsid(struct btrfs_fs_devices *fs_devices, u64 total_devs,
-                       int run_ioctls)
+int btrfs_scan_for_fsid(int run_ioctls)
 {
        int ret;
 
-       ret = btrfs_scan_block_devices(run_ioctls);
+       ret = scan_for_btrfs(BTRFS_SCAN_PROC, run_ioctls);
        if (ret)
-               ret = btrfs_scan_one_dir("/dev", run_ioctls);
+               ret = scan_for_btrfs(BTRFS_SCAN_DEV, run_ioctls);
        return ret;
 }
 
@@ -1085,11 +1159,10 @@ int btrfs_device_already_in_root(struct btrfs_root *root, int fd,
 
        ret = 0;
        disk_super = (struct btrfs_super_block *)buf;
-       if (strncmp((char *)(&disk_super->magic), BTRFS_MAGIC,
-           sizeof(disk_super->magic)))
+       if (btrfs_super_magic(disk_super) != BTRFS_MAGIC)
                goto brelse;
 
-       if (!memcmp(disk_super->fsid, root->fs_info->super_copy.fsid,
+       if (!memcmp(disk_super->fsid, root->fs_info->super_copy->fsid,
                    BTRFS_FSID_SIZE))
                ret = 1;
 brelse:
@@ -1098,14 +1171,14 @@ out:
        return ret;
 }
 
-static char *size_strs[] = { "", "KB", "MB", "GB", "TB",
-                           "PB", "EB", "ZB", "YB"};
-char *pretty_sizes(u64 size)
+static char *size_strs[] = { "", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"};
+void pretty_size_snprintf(u64 size, char *str, size_t str_bytes)
 {
        int num_divs = 0;
-        int pretty_len = 16;
        float fraction;
-       char *pretty;
+
+       if (str_bytes == 0)
+               return;
 
        if( size < 1024 ){
                fraction = size;
@@ -1119,13 +1192,33 @@ char *pretty_sizes(u64 size)
                        num_divs ++;
                }
 
-               if (num_divs > ARRAY_SIZE(size_strs))
-                       return NULL;
+               if (num_divs >= ARRAY_SIZE(size_strs)) {
+                       str[0] = '\0';
+                       return;
+               }
                fraction = (float)last_size / 1024;
        }
-       pretty = malloc(pretty_len);
-       snprintf(pretty, pretty_len, "%.2f%s", fraction, size_strs[num_divs]);
-       return pretty;
+       snprintf(str, str_bytes, "%.2f%s", fraction, size_strs[num_divs]);
+}
+
+/*
+ * __strncpy__null - strncpy with null termination
+ * @dest:      the target array
+ * @src:       the source string
+ * @n:         maximum bytes to copy (size of *dest)
+ *
+ * Like strncpy, but ensures destination is null-terminated.
+ *
+ * Copies the string pointed to by src, including the terminating null
+ * byte ('\0'), to the buffer pointed to by dest, up to a maximum
+ * of n bytes.  Then ensure that dest is null-terminated.
+ */
+char *__strncpy__null(char *dest, const char *src, size_t n)
+{
+       strncpy(dest, src, n);
+       if (n > 0)
+               dest[n - 1] = '\0';
+       return dest;
 }
 
 /*
@@ -1133,26 +1226,158 @@ char *pretty_sizes(u64 size)
  * Returns:
        0    if everything is safe and usable
       -1    if the label is too long
-      -2    if the label contains an invalid character
  */
-int check_label(char *input)
+static int check_label(const char *input)
 {
-       int i;
        int len = strlen(input);
 
-       if (len > BTRFS_LABEL_SIZE) {
+       if (len > BTRFS_LABEL_SIZE - 1) {
+               fprintf(stderr, "ERROR: Label %s is too long (max %d)\n",
+                       input, BTRFS_LABEL_SIZE - 1);
                return -1;
        }
 
-       for (i = 0; i < len; i++) {
-               if (input[i] == '/' || input[i] == '\\') {
-                       return -2;
-               }
-       }
-
        return 0;
 }
 
+static int set_label_unmounted(const char *dev, const char *label)
+{
+       struct btrfs_trans_handle *trans;
+       struct btrfs_root *root;
+       int ret;
+
+       ret = check_mounted(dev);
+       if (ret < 0) {
+              fprintf(stderr, "FATAL: error checking %s mount status\n", dev);
+              return -1;
+       }
+       if (ret > 0) {
+               fprintf(stderr, "ERROR: dev %s is mounted, use mount point\n",
+                       dev);
+               return -1;
+       }
+
+       /* Open the super_block at the default location
+        * and as read-write.
+        */
+       root = open_ctree(dev, 0, 1);
+       if (!root) /* errors are printed by open_ctree() */
+               return -1;
+
+       trans = btrfs_start_transaction(root, 1);
+       snprintf(root->fs_info->super_copy->label, BTRFS_LABEL_SIZE, "%s",
+                label);
+       btrfs_commit_transaction(trans, root);
+
+       /* Now we close it since we are done. */
+       close_ctree(root);
+       return 0;
+}
+
+static int set_label_mounted(const char *mount_path, const char *label)
+{
+       int fd;
+
+       fd = open(mount_path, O_RDONLY | O_NOATIME);
+       if (fd < 0) {
+               fprintf(stderr, "ERROR: unable access to '%s'\n", mount_path);
+               return -1;
+       }
+
+       if (ioctl(fd, BTRFS_IOC_SET_FSLABEL, label) < 0) {
+               fprintf(stderr, "ERROR: unable to set label %s\n",
+                       strerror(errno));
+               close(fd);
+               return -1;
+       }
+
+       close(fd);
+       return 0;
+}
+
+static int get_label_unmounted(const char *dev)
+{
+       struct btrfs_root *root;
+       int ret;
+
+       ret = check_mounted(dev);
+       if (ret < 0) {
+              fprintf(stderr, "FATAL: error checking %s mount status\n", dev);
+              return -1;
+       }
+       if (ret > 0) {
+               fprintf(stderr, "ERROR: dev %s is mounted, use mount point\n",
+                       dev);
+               return -1;
+       }
+
+       /* Open the super_block at the default location
+        * and as read-only.
+        */
+       root = open_ctree(dev, 0, 0);
+       if(!root)
+               return -1;
+
+       fprintf(stdout, "%s\n", root->fs_info->super_copy->label);
+
+       /* Now we close it since we are done. */
+       close_ctree(root);
+       return 0;
+}
+
+/*
+ * If a partition is mounted, try to get the filesystem label via its
+ * mounted path rather than device.  Return the corresponding error
+ * the user specified the device path.
+ */
+int get_label_mounted(const char *mount_path, char *labelp)
+{
+       char label[BTRFS_LABEL_SIZE];
+       int fd;
+
+       fd = open(mount_path, O_RDONLY | O_NOATIME);
+       if (fd < 0) {
+               fprintf(stderr, "ERROR: unable access to '%s'\n", mount_path);
+               return -1;
+       }
+
+       memset(label, '\0', sizeof(label));
+       if (ioctl(fd, BTRFS_IOC_GET_FSLABEL, label) < 0) {
+               fprintf(stderr, "ERROR: unable get label %s\n", strerror(errno));
+               close(fd);
+               return -1;
+       }
+
+       strncpy(labelp, label, sizeof(label));
+       close(fd);
+       return 0;
+}
+
+int get_label(const char *btrfs_dev)
+{
+       int ret;
+       char label[BTRFS_LABEL_SIZE];
+
+       if (is_existing_blk_or_reg_file(btrfs_dev))
+               ret = get_label_unmounted(btrfs_dev);
+       else {
+               ret = get_label_mounted(btrfs_dev, label);
+               if (!ret)
+                       fprintf(stdout, "%s\n", label);
+       }
+       return ret;
+}
+
+int set_label(const char *btrfs_dev, const char *label)
+{
+       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);
+}
+
 int btrfs_scan_block_devices(int run_ioctl)
 {
 
@@ -1175,12 +1400,13 @@ scan_again:
                return -ENOENT;
        }
        /* skip the header */
-       for(i=0; i < 2 ; i++)
-               if(!fgets(buf, 1023, proc_partitions)){
-               fprintf(stderr, "Unable to read '/proc/partitions' for scanning\n");
-               fclose(proc_partitions);
-               return -ENOENT;
-       }
+       for (i = 0; i < 2; i++)
+               if (!fgets(buf, 1023, proc_partitions)) {
+                       fprintf(stderr,
+                               "Unable to read '/proc/partitions' for scanning\n");
+                       fclose(proc_partitions);
+                       return -ENOENT;
+               }
 
        strcpy(fullpath,"/dev/");
        while(fgets(buf, 1023, proc_partitions)) {
@@ -1212,7 +1438,9 @@ scan_again:
 
                fd = open(fullpath, O_RDONLY);
                if (fd < 0) {
-                       fprintf(stderr, "failed to read %s\n", fullpath);
+                       if (errno != ENOMEDIUM)
+                               fprintf(stderr, "failed to open %s: %s\n",
+                                       fullpath, strerror(errno));
                        continue;
                }
                ret = btrfs_scan_one_device(fd, fullpath, &tmp_devices,
@@ -1239,7 +1467,7 @@ u64 parse_size(char *s)
        char c;
        u64 mult = 1;
 
-       for (i=0 ; s[i] && isdigit(s[i]) ; i++) ;
+       for (i = 0; s && s[i] && isdigit(s[i]); i++) ;
        if (!i) {
                fprintf(stderr, "ERROR: size value is empty\n");
                exit(50);
@@ -1277,11 +1505,10 @@ u64 parse_size(char *s)
        return strtoull(s, NULL, 10) * mult;
 }
 
-int open_file_or_dir(const char *fname)
+int open_file_or_dir(const char *fname, DIR **dirstream)
 {
        int ret;
        struct stat st;
-       DIR *dirstream;
        int fd;
 
        ret = stat(fname, &st);
@@ -1289,20 +1516,29 @@ int open_file_or_dir(const char *fname)
                return -1;
        }
        if (S_ISDIR(st.st_mode)) {
-               dirstream = opendir(fname);
-               if (!dirstream) {
+               *dirstream = opendir(fname);
+               if (!*dirstream)
                        return -2;
-               }
-               fd = dirfd(dirstream);
+               fd = dirfd(*dirstream);
        } else {
                fd = open(fname, O_RDWR);
        }
        if (fd < 0) {
-               return -3;
+               fd = -3;
+               if (*dirstream)
+                       closedir(*dirstream);
        }
        return fd;
 }
 
+void close_file_or_dir(int fd, DIR *dirstream)
+{
+       if (dirstream)
+               closedir(dirstream);
+       else if (fd >= 0)
+               close(fd);
+}
+
 int get_device_info(int fd, u64 devid,
                    struct btrfs_ioctl_dev_info_args *di_args)
 {
@@ -1315,45 +1551,80 @@ 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;
        struct btrfs_fs_devices *fs_devices_mnt = NULL;
        struct btrfs_ioctl_dev_info_args *di_args;
        char mp[BTRFS_PATH_NAME_MAX + 1];
+       DIR *dirstream = NULL;
 
        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, &dirstream);
+       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);
@@ -1361,11 +1632,243 @@ 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);
+       ret = 0;
+out:
+       close_file_or_dir(fd, dirstream);
+       return ret;
+}
+
+#define isoctal(c)     (((c) & ~7) == '0')
+
+static inline void translate(char *f, char *t)
+{
+       while (*f != '\0') {
+               if (*f == '\\' &&
+                   isoctal(f[1]) && isoctal(f[2]) && isoctal(f[3])) {
+                       *t++ = 64*(f[1] & 7) + 8*(f[2] & 7) + (f[3] & 7);
+                       f += 4;
+               } else
+                       *t++ = *f++;
+       }
+       *t = '\0';
+       return;
+}
+
+/*
+ * Checks if the swap device.
+ * Returns 1 if swap device, < 0 on error or 0 if not swap device.
+ */
+int is_swap_device(const char *file)
+{
+       FILE    *f;
+       struct stat     st_buf;
+       dev_t   dev;
+       ino_t   ino = 0;
+       char    tmp[PATH_MAX];
+       char    buf[PATH_MAX];
+       char    *cp;
+       int     ret = 0;
+
+       if (stat(file, &st_buf) < 0)
+               return -errno;
+       if (S_ISBLK(st_buf.st_mode))
+               dev = st_buf.st_rdev;
+       else if (S_ISREG(st_buf.st_mode)) {
+               dev = st_buf.st_dev;
+               ino = st_buf.st_ino;
+       } else
+               return 0;
+
+       if ((f = fopen("/proc/swaps", "r")) == NULL)
+               return 0;
+
+       /* skip the first line */
+       if (fgets(tmp, sizeof(tmp), f) == NULL)
+               goto out;
+
+       while (fgets(tmp, sizeof(tmp), f) != NULL) {
+               if ((cp = strchr(tmp, ' ')) != NULL)
+                       *cp = '\0';
+               if ((cp = strchr(tmp, '\t')) != NULL)
+                       *cp = '\0';
+               translate(tmp, buf);
+               if (stat(buf, &st_buf) != 0)
+                       continue;
+               if (S_ISBLK(st_buf.st_mode)) {
+                       if (dev == st_buf.st_rdev) {
+                               ret = 1;
+                               break;
+                       }
+               } else if (S_ISREG(st_buf.st_mode)) {
+                       if (dev == st_buf.st_dev && ino == st_buf.st_ino) {
+                               ret = 1;
+                               break;
+                       }
+               }
+       }
+
+out:
+       fclose(f);
+
+       return ret;
+}
+
+/*
+ * Check for existing filesystem or partition table on device.
+ * Returns:
+ *      1 for existing fs or partition
+ *      0 for nothing found
+ *     -1 for internal error
+ */
+static int
+check_overwrite(
+       char            *device)
+{
+       const char      *type;
+       blkid_probe     pr = NULL;
+       int             ret;
+       blkid_loff_t    size;
+
+       if (!device || !*device)
+               return 0;
+
+       ret = -1; /* will reset on success of all setup calls */
+
+       pr = blkid_new_probe_from_filename(device);
+       if (!pr)
+               goto out;
+
+       size = blkid_probe_get_size(pr);
+       if (size < 0)
+               goto out;
+
+       /* nothing to overwrite on a 0-length device */
+       if (size == 0) {
+               ret = 0;
+               goto out;
+       }
+
+       ret = blkid_probe_enable_partitions(pr, 1);
+       if (ret < 0)
+               goto out;
+
+       ret = blkid_do_fullprobe(pr);
+       if (ret < 0)
+               goto out;
+
+       /*
+        * Blkid returns 1 for nothing found and 0 when it finds a signature,
+        * but we want the exact opposite, so reverse the return value here.
+        *
+        * In addition print some useful diagnostics about what actually is
+        * on the device.
+        */
+       if (ret) {
+               ret = 0;
+               goto out;
+       }
+
+       if (!blkid_probe_lookup_value(pr, "TYPE", &type, NULL)) {
+               fprintf(stderr,
+                       "%s appears to contain an existing "
+                       "filesystem (%s).\n", device, type);
+       } else if (!blkid_probe_lookup_value(pr, "PTTYPE", &type, NULL)) {
+               fprintf(stderr,
+                       "%s appears to contain a partition "
+                       "table (%s).\n", device, type);
+       } else {
+               fprintf(stderr,
+                       "%s appears to contain something weird "
+                       "according to blkid\n", device);
+       }
+       ret = 1;
+
+out:
+       if (pr)
+               blkid_free_probe(pr);
+       if (ret == -1)
+               fprintf(stderr,
+                       "probe of %s failed, cannot detect "
+                         "existing filesystem.\n", device);
+       return ret;
+}
 
+/* Check if disk is suitable for btrfs
+ * returns:
+ *  1: something is wrong, estr provides the error
+ *  0: all is fine
+ */
+int test_dev_for_mkfs(char *file, int force_overwrite, char *estr)
+{
+       int ret, fd;
+       size_t sz = 100;
+       struct stat st;
+
+       ret = is_swap_device(file);
+       if (ret < 0) {
+               snprintf(estr, sz, "error checking %s status: %s\n", file,
+                       strerror(-ret));
+               return 1;
+       }
+       if (ret == 1) {
+               snprintf(estr, sz, "%s is a swap device\n", file);
+               return 1;
+       }
+       if (!force_overwrite) {
+               if (check_overwrite(file)) {
+                       snprintf(estr, sz, "Use the -f option to force overwrite.\n");
+                       return 1;
+               }
+       }
+       ret = check_mounted(file);
+       if (ret < 0) {
+               snprintf(estr, sz, "error checking %s mount status\n",
+                       file);
+               return 1;
+       }
+       if (ret == 1) {
+               snprintf(estr, sz, "%s is mounted\n", file);
+               return 1;
+       }
+       /* check if the device is busy */
+       fd = open(file, O_RDWR|O_EXCL);
+       if (fd < 0) {
+               snprintf(estr, sz, "unable to open %s: %s\n", file,
+                       strerror(errno));
+               return 1;
+       }
+       if (fstat(fd, &st)) {
+               snprintf(estr, sz, "unable to stat %s: %s\n", file,
+                       strerror(errno));
+               return 1;
+       }
+       if (!S_ISBLK(st.st_mode)) {
+               fprintf(stderr, "'%s' is not a block device\n", file);
+               return 1;
+       }
+       close(fd);
        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;
+       }
+       return ret;
+}