diff options
Diffstat (limited to 'utils.c')
-rw-r--r-- | utils.c | 839 |
1 files changed, 734 insertions, 105 deletions
@@ -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 @@ -16,10 +17,13 @@ * Boston, MA 021110-1307, USA. */ -#define _XOPEN_SOURCE 600 -#define __USE_XOPEN2K +#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> #ifndef __CHECKER__ #include <sys/ioctl.h> #include <sys/mount.h> @@ -31,10 +35,12 @@ #include <fcntl.h> #include <unistd.h> #include <mntent.h> +#include <ctype.h> #include <linux/loop.h> #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" @@ -109,7 +115,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)); + super.magic = cpu_to_le64(BTRFS_MAGIC); btrfs_set_super_generation(&super, 1); btrfs_set_super_root(&super, blocks[1]); btrfs_set_super_chunk_root(&super, blocks[3]); @@ -410,7 +416,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)) { @@ -468,7 +474,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; @@ -476,7 +482,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); @@ -537,7 +543,7 @@ int btrfs_add_to_fsid(struct btrfs_trans_handle *trans, } int btrfs_prepare_device(int fd, char *file, int zero_end, u64 *block_count_ret, - int *mixed) + u64 max_block_count, int *mixed, int nodiscard) { u64 block_count; u64 bytenr; @@ -550,11 +556,13 @@ 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); } + if (max_block_count) + block_count = min(block_count, max_block_count); zero_end = 1; if (block_count < 1024 * 1024 * 1024 && !(*mixed)) { @@ -562,11 +570,13 @@ int btrfs_prepare_device(int fd, char *file, int zero_end, u64 *block_count_ret, *mixed = 1; } - /* - * We intentionally ignore errors from the discard ioctl. It is - * not necessary for the mkfs functionality but just an optimization. - */ - discard_blocks(fd, 0, block_count); + if (!nodiscard) { + /* + * We intentionally ignore errors from the discard ioctl. It is + * not necessary for the mkfs functionality but just an optimization. + */ + discard_blocks(fd, 0, block_count); + } ret = zero_dev_start(fd); if (ret) { @@ -604,7 +614,7 @@ int btrfs_make_root_dir(struct btrfs_trans_handle *trans, 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_mode(&inode_item, S_IFDIR | 0555); + 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); btrfs_set_stack_timespec_sec(&inode_item.ctime, now); @@ -615,7 +625,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) @@ -631,6 +641,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; @@ -647,19 +744,22 @@ int is_loop_device (const char* device) { * the associated file (e.g. /images/my_btrfs.img) */ int resolve_loop_device(const char* loop_dev, char* loop_file, int max_len) { - int loop_fd; - int ret_ioctl; - struct loop_info loopinfo; + int ret; + FILE *f; + char fmt[20]; + char p[PATH_MAX]; + char real_loop_dev[PATH_MAX]; - if ((loop_fd = open(loop_dev, O_RDONLY)) < 0) + if (!realpath(loop_dev, real_loop_dev)) + return -errno; + snprintf(p, PATH_MAX, "/sys/block/%s/loop/backing_file", strrchr(real_loop_dev, '/')); + if (!(f = fopen(p, "r"))) return -errno; - ret_ioctl = ioctl(loop_fd, LOOP_GET_STATUS, &loopinfo); - close(loop_fd); - - if (ret_ioctl == 0) - strncpy(loop_file, loopinfo.lo_name, max_len); - else + snprintf(fmt, 20, "%%%i[^\n]", max_len-1); + ret = fscanf(f, fmt, loop_file); + fclose(f); + if (ret == EOF) return -errno; return 0; @@ -860,8 +960,10 @@ int check_mounted_where(int fd, const char *file, char *where, int size, } /* Did we find an entry in mnt table? */ - if (mnt && size && where) + if (mnt && size && where) { strncpy(where, mnt->mnt_dir, size); + where[size-1] = 0; + } if (fs_dev_ret) *fs_dev_ret = fs_devices_mnt; @@ -873,42 +975,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); - 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) @@ -921,14 +990,16 @@ 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); + args.name[BTRFS_PATH_NAME_MAX-1] = 0; 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); @@ -943,7 +1014,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; @@ -958,8 +1028,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) { @@ -969,6 +1038,7 @@ again: dirp = opendir(dirname); if (!dirp) { fprintf(stderr, "Unable to open %s for scanning\n", dirname); + free(fullpath); return -ENOENT; } while(1) { @@ -977,11 +1047,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); @@ -1003,8 +1073,14 @@ again: } fd = open(fullpath, O_RDONLY); if (fd < 0) { - fprintf(stderr, "failed to read %s: %s\n", fullpath, - strerror(errno)); + /* 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, @@ -1019,13 +1095,16 @@ again: 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); if (dirp) closedir(dirp); return ret; @@ -1034,7 +1113,12 @@ fail: int btrfs_scan_for_fsid(struct btrfs_fs_devices *fs_devices, u64 total_devs, int run_ioctls) { - return btrfs_scan_one_dir("/dev", run_ioctls); + int ret; + + ret = btrfs_scan_block_devices(run_ioctls); + if (ret) + ret = btrfs_scan_one_dir("/dev", run_ioctls); + return ret; } int btrfs_device_already_in_root(struct btrfs_root *root, int fd, @@ -1055,11 +1139,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 (disk_super->magic != cpu_to_le64(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: @@ -1074,53 +1157,199 @@ char *pretty_sizes(u64 size) { int num_divs = 0; int pretty_len = 16; - u64 last_size = size; - u64 fract_size = size; float fraction; char *pretty; - while(size > 0) { - fract_size = last_size; - last_size = size; - size /= 1024; - num_divs++; - } - if (num_divs == 0) - num_divs = 1; - if (num_divs > ARRAY_SIZE(size_strs)) - return NULL; + if( size < 1024 ){ + fraction = size; + num_divs = 0; + } else { + u64 last_size = size; + num_divs = 0; + while(size >= 1024){ + last_size = size; + size /= 1024; + num_divs ++; + } - fraction = (float)fract_size / 1024; + if (num_divs >= ARRAY_SIZE(size_strs)) + return NULL; + fraction = (float)last_size / 1024; + } pretty = malloc(pretty_len); - snprintf(pretty, pretty_len, "%.2f%s", fraction, size_strs[num_divs-1]); + snprintf(pretty, pretty_len, "%.2f%s", fraction, size_strs[num_divs]); return pretty; } /* + * __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; +} + +/* * Checks to make sure that the label matches our requirements. * 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. + */ +static int get_label_mounted(const char *mount_path) +{ + 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; + } + + fprintf(stdout, "%s\n", label); + close(fd); + return 0; +} + +int get_label(const char *btrfs_dev) +{ + return is_existing_blk_or_reg_file(btrfs_dev) ? + get_label_unmounted(btrfs_dev) : + get_label_mounted(btrfs_dev); +} + +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) { @@ -1133,24 +1362,43 @@ int btrfs_scan_block_devices(int run_ioctl) int i; char buf[1024]; char fullpath[110]; + int scans = 0; + int special; +scan_again: proc_partitions = fopen("/proc/partitions","r"); if (!proc_partitions) { fprintf(stderr, "Unable to open '/proc/partitions' for scanning\n"); 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)) { - i = sscanf(buf," %*d %*d %*d %99s", fullpath+5); + + /* + * multipath and MD devices may register as a btrfs filesystem + * both through the original block device and through + * the special (/dev/mapper or /dev/mdX) entry. + * This scans the special entries last + */ + special = strncmp(fullpath, "/dev/dm-", strlen("/dev/dm-")) == 0; + if (!special) + special = strncmp(fullpath, "/dev/md", strlen("/dev/md")) == 0; + + if (scans == 0 && special) + continue; + if (scans > 0 && !special) + continue; + ret = lstat(fullpath, &st); if (ret < 0) { fprintf(stderr, "failed to stat %s\n", fullpath); @@ -1162,7 +1410,8 @@ int btrfs_scan_block_devices(int run_ioctl) fd = open(fullpath, O_RDONLY); if (fd < 0) { - fprintf(stderr, "failed to read %s\n", fullpath); + fprintf(stderr, "failed to open %s: %s\n", + fullpath, strerror(errno)); continue; } ret = btrfs_scan_one_device(fd, fullpath, &tmp_devices, @@ -1175,6 +1424,386 @@ int btrfs_scan_block_devices(int run_ioctl) } fclose(proc_partitions); + + if (scans == 0) { + scans++; + goto scan_again; + } return 0; } +u64 parse_size(char *s) +{ + int i; + char c; + u64 mult = 1; + + for (i = 0; s && s[i] && isdigit(s[i]); i++) ; + if (!i) { + fprintf(stderr, "ERROR: size value is empty\n"); + exit(50); + } + + if (s[i]) { + c = tolower(s[i]); + switch (c) { + case 'e': + mult *= 1024; + case 'p': + mult *= 1024; + case 't': + mult *= 1024; + case 'g': + mult *= 1024; + case 'm': + mult *= 1024; + case 'k': + mult *= 1024; + case 'b': + break; + default: + 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); + } + return strtoull(s, NULL, 10) * mult; +} + +int open_file_or_dir(const char *fname) +{ + int ret; + struct stat st; + DIR *dirstream; + int fd; + + ret = stat(fname, &st); + if (ret < 0) { + return -1; + } + if (S_ISDIR(st.st_mode)) { + dirstream = opendir(fname); + if (!dirstream) { + return -2; + } + fd = dirfd(dirstream); + } else { + fd = open(fname, O_RDWR); + } + if (fd < 0) { + return -3; + } + return fd; +} + +int get_device_info(int fd, u64 devid, + struct btrfs_ioctl_dev_info_args *di_args) +{ + int ret; + + di_args->devid = devid; + memset(&di_args->uuid, '\0', sizeof(di_args->uuid)); + + ret = ioctl(fd, BTRFS_IOC_DEV_INFO, di_args); + return ret ? -errno : 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. + * 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]; + + memset(fi_args, 0, sizeof(*fi_args)); + + 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) { + ret = -EINVAL; + goto out; + } + if (ret < 0) + 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); + } + + /* 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) + goto out; + + di_args = *di_ret = malloc(fi_args->num_devices * sizeof(*di_args)); + if (!di_args) { + ret = -errno; + goto out; + } + + 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; + if (ret) + goto out; + ndevs++; + } + + BUG_ON(ndevs == 0); + ret = 0; +out: + if (fd != -1) + close(fd); + 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; + + 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; + } + close(fd); + return 0; +} |