X-Git-Url: http://review.tizen.org/git/?a=blobdiff_plain;f=cmds-filesystem.c;h=b44a65532bf9d48998efec000afad389d849ac54;hb=a57606e815f3e1c4fe66c61ce3c6e3621ea522e9;hp=384d1b96b7a0581abe21b04938cd367a254273da;hpb=ef3f6124f0b5e8be126999f27f7f299a6bb3fbf6;p=platform%2Fupstream%2Fbtrfs-progs.git diff --git a/cmds-filesystem.c b/cmds-filesystem.c index 384d1b9..b44a655 100644 --- a/cmds-filesystem.c +++ b/cmds-filesystem.c @@ -14,7 +14,6 @@ * Boston, MA 021110-1307, USA. */ -#define _XOPEN_SOURCE 500 #include #include #include @@ -34,9 +33,10 @@ #include "ioctl.h" #include "utils.h" #include "volumes.h" -#include "version.h" #include "commands.h" +#include "cmds-fi-usage.h" #include "list_sort.h" +#include "disk-io.h" /* @@ -52,6 +52,15 @@ struct seen_fsid { static struct seen_fsid *seen_fsid_hash[SEEN_FSID_HASH_SIZE] = {NULL,}; +static int is_seen_fsid(u8 *fsid) +{ + u8 hash = fsid[0]; + int slot = hash % SEEN_FSID_HASH_SIZE; + struct seen_fsid *seen = seen_fsid_hash[slot]; + + return seen ? 1 : 0; +} + static int add_seen_fsid(u8 *fsid) { u8 hash = fsid[0]; @@ -111,50 +120,22 @@ static const char * const filesystem_cmd_group_usage[] = { NULL }; -static const char * const cmd_df_usage[] = { - "btrfs filesystem df ", - "Show space usage information for a mount point", - NULL +static const char * const cmd_filesystem_df_usage[] = { + "btrfs filesystem df [options] ", + "Show space usage information for a mount point", + "-b|--raw raw numbers in bytes", + "-h|--human-readable", + " human friendly numbers, base 1024 (default)", + "-H human friendly numbers, base 1000", + "--iec use 1024 as a base (KiB, MiB, GiB, TiB)", + "--si use 1000 as a base (kB, MB, GB, TB)", + "-k|--kbytes show sizes in KiB, or kB with --si", + "-m|--mbytes show sizes in MiB, or MB with --si", + "-g|--gbytes show sizes in GiB, or GB with --si", + "-t|--tbytes show sizes in TiB, or TB with --si", + NULL }; -static char *group_type_str(u64 flag) -{ - switch (flag & BTRFS_BLOCK_GROUP_TYPE_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"; - default: - return "unknown"; - } -} - -static char *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"; - } -} - static int get_df(int fd, struct btrfs_ioctl_space_args **sargs_ret) { u64 count = 0; @@ -187,7 +168,7 @@ static int get_df(int fd, struct btrfs_ioctl_space_args **sargs_ret) sargs = malloc(sizeof(struct btrfs_ioctl_space_args) + (count * sizeof(struct btrfs_ioctl_space_info))); if (!sargs) - ret = -ENOMEM; + return -ENOMEM; sargs->space_slots = count; sargs->total_spaces = 0; @@ -203,32 +184,85 @@ static int get_df(int fd, struct btrfs_ioctl_space_args **sargs_ret) return 0; } -static void print_df(struct btrfs_ioctl_space_args *sargs) +static void print_df(struct btrfs_ioctl_space_args *sargs, unsigned unit_mode) { u64 i; struct btrfs_ioctl_space_info *sp = sargs->spaces; for (i = 0; i < sargs->total_spaces; i++, sp++) { printf("%s, %s: total=%s, used=%s\n", - group_type_str(sp->flags), - group_profile_str(sp->flags), - pretty_size(sp->total_bytes), - pretty_size(sp->used_bytes)); + btrfs_group_type_str(sp->flags), + btrfs_group_profile_str(sp->flags), + pretty_size_mode(sp->total_bytes, unit_mode), + pretty_size_mode(sp->used_bytes, unit_mode)); } } -static int cmd_df(int argc, char **argv) +static int cmd_filesystem_df(int argc, char **argv) { struct btrfs_ioctl_space_args *sargs = NULL; int ret; int fd; char *path; - DIR *dirstream = NULL; + DIR *dirstream = NULL; + unsigned unit_mode = UNITS_DEFAULT; - if (check_argc_exact(argc, 2)) - usage(cmd_df_usage); + while (1) { + int c; + static const struct option long_options[] = { + { "raw", no_argument, NULL, 'b'}, + { "kbytes", no_argument, NULL, 'k'}, + { "mbytes", no_argument, NULL, 'm'}, + { "gbytes", no_argument, NULL, 'g'}, + { "tbytes", no_argument, NULL, 't'}, + { "si", no_argument, NULL, GETOPT_VAL_SI}, + { "iec", no_argument, NULL, GETOPT_VAL_IEC}, + { "human-readable", no_argument, NULL, + GETOPT_VAL_HUMAN_READABLE}, + { NULL, 0, NULL, 0 } + }; - path = argv[1]; + c = getopt_long(argc, argv, "bhHkmgt", long_options, NULL); + if (c < 0) + break; + switch (c) { + case 'b': + unit_mode = UNITS_RAW; + break; + case 'k': + units_set_base(&unit_mode, UNITS_KBYTES); + break; + case 'm': + units_set_base(&unit_mode, UNITS_MBYTES); + break; + case 'g': + units_set_base(&unit_mode, UNITS_GBYTES); + break; + case 't': + units_set_base(&unit_mode, UNITS_TBYTES); + break; + case GETOPT_VAL_HUMAN_READABLE: + case 'h': + unit_mode = UNITS_HUMAN_BINARY; + break; + case 'H': + unit_mode = UNITS_HUMAN_DECIMAL; + break; + case GETOPT_VAL_SI: + units_set_mode(&unit_mode, UNITS_DECIMAL); + break; + case GETOPT_VAL_IEC: + units_set_mode(&unit_mode, UNITS_BINARY); + break; + default: + usage(cmd_filesystem_df_usage); + } + } + + if (check_argc_exact(argc, optind + 1)) + usage(cmd_filesystem_df_usage); + + path = argv[optind]; fd = open_file_or_dir(path, &dirstream); if (fd < 0) { @@ -237,8 +271,8 @@ static int cmd_df(int argc, char **argv) } ret = get_df(fd, &sargs); - if (!ret && sargs) { - print_df(sargs); + if (ret == 0) { + print_df(sargs, unit_mode); free(sargs); } else { fprintf(stderr, "ERROR: get_df failed %s\n", strerror(-ret)); @@ -304,10 +338,73 @@ static int cmp_device_id(void *priv, struct list_head *a, da->devid > db->devid ? 1 : 0; } -static void print_one_uuid(struct btrfs_fs_devices *fs_devices) +static void splice_device_list(struct list_head *seed_devices, + struct list_head *all_devices) +{ + struct btrfs_device *in_all, *next_all; + struct btrfs_device *in_seed, *next_seed; + + list_for_each_entry_safe(in_all, next_all, all_devices, dev_list) { + list_for_each_entry_safe(in_seed, next_seed, seed_devices, + dev_list) { + if (in_all->devid == in_seed->devid) { + /* + * When do dev replace in a sprout fs + * to a dev in its seed fs, the replacing + * dev will reside in the sprout fs and + * the replaced dev will still exist + * in the seed fs. + * So pick the latest one when showing + * the sprout fs. + */ + if (in_all->generation + < in_seed->generation) { + list_del(&in_all->dev_list); + free(in_all); + } else if (in_all->generation + > in_seed->generation) { + list_del(&in_seed->dev_list); + free(in_seed); + } + break; + } + } + } + + list_splice(seed_devices, all_devices); +} + +static void print_devices(struct btrfs_fs_devices *fs_devices, + u64 *devs_found, unsigned unit_mode) +{ + struct btrfs_device *device; + struct btrfs_fs_devices *cur_fs; + struct list_head *all_devices; + + all_devices = &fs_devices->devices; + cur_fs = fs_devices->seed; + /* add all devices of seed fs to the fs to be printed */ + while (cur_fs) { + splice_device_list(&cur_fs->devices, all_devices); + cur_fs = cur_fs->seed; + } + + list_sort(NULL, all_devices, cmp_device_id); + list_for_each_entry(device, all_devices, dev_list) { + printf("\tdevid %4llu size %s used %s path %s\n", + (unsigned long long)device->devid, + pretty_size_mode(device->total_bytes, unit_mode), + pretty_size_mode(device->bytes_used, unit_mode), + device->name); + + (*devs_found)++; + } +} + +static void print_one_uuid(struct btrfs_fs_devices *fs_devices, + unsigned unit_mode) { char uuidbuf[BTRFS_UUID_UNPARSED_SIZE]; - struct list_head *cur; struct btrfs_device *device; u64 devs_found = 0; u64 total; @@ -323,23 +420,13 @@ static void print_one_uuid(struct btrfs_fs_devices *fs_devices) else printf("Label: none "); - total = device->total_devs; printf(" uuid: %s\n\tTotal devices %llu FS bytes used %s\n", uuidbuf, (unsigned long long)total, - pretty_size(device->super_bytes_used)); + pretty_size_mode(device->super_bytes_used, unit_mode)); - list_sort(NULL, &fs_devices->devices, cmp_device_id); - list_for_each(cur, &fs_devices->devices) { - device = list_entry(cur, struct btrfs_device, dev_list); - - printf("\tdevid %4llu size %s used %s path %s\n", - (unsigned long long)device->devid, - pretty_size(device->total_bytes), - pretty_size(device->bytes_used), device->name); + print_devices(fs_devices, &devs_found, unit_mode); - devs_found++; - } if (devs_found < total) { printf("\t*** Some devices missing\n"); } @@ -360,9 +447,11 @@ static u64 calc_used_bytes(struct btrfs_ioctl_space_args *si) static int print_one_fs(struct btrfs_ioctl_fs_info_args *fs_info, struct btrfs_ioctl_dev_info_args *dev_info, struct btrfs_ioctl_space_args *space_info, - char *label, char *path) + char *label, char *path, unsigned unit_mode) { int i; + int fd; + int missing = 0; char uuidbuf[BTRFS_UUID_UNPARSED_SIZE]; struct btrfs_ioctl_dev_info_args *tmp_dev_info; int ret; @@ -381,61 +470,46 @@ static int print_one_fs(struct btrfs_ioctl_fs_info_args *fs_info, printf(" uuid: %s\n\tTotal devices %llu FS bytes used %s\n", uuidbuf, fs_info->num_devices, - pretty_size(calc_used_bytes(space_info))); + pretty_size_mode(calc_used_bytes(space_info), + unit_mode)); for (i = 0; i < fs_info->num_devices; i++) { + char *canonical_path; + tmp_dev_info = (struct btrfs_ioctl_dev_info_args *)&dev_info[i]; + + /* Add check for missing devices even mounted */ + fd = open((char *)tmp_dev_info->path, O_RDONLY); + if (fd < 0) { + missing = 1; + continue; + } + close(fd); + canonical_path = canonicalize_path((char *)tmp_dev_info->path); printf("\tdevid %4llu size %s used %s path %s\n", tmp_dev_info->devid, - pretty_size(tmp_dev_info->total_bytes), - pretty_size(tmp_dev_info->bytes_used), - tmp_dev_info->path); + pretty_size_mode(tmp_dev_info->total_bytes, unit_mode), + pretty_size_mode(tmp_dev_info->bytes_used, unit_mode), + canonical_path); + + free(canonical_path); } + if (missing) + printf("\t*** Some devices missing\n"); printf("\n"); return 0; } -/* This function checks if the given input parameter is - * an uuid or a path - * return -1: some error in the given input - * return 0: unknow input - * return 1: given input is uuid - * return 2: given input is path - */ -static int check_arg_type(char *input) -{ - uuid_t out; - char path[PATH_MAX]; - - if (!input) - return -EINVAL; - - if (realpath(input, path)) { - if (is_block_device(input) == 1) - return BTRFS_ARG_BLKDEV; - - if (is_mount_point(input) == 1) - return BTRFS_ARG_MNTPOINT; - - return BTRFS_ARG_UNKNOWN; - } - - if (strlen(input) == (BTRFS_UUID_UNPARSED_SIZE - 1) && - !uuid_parse(input, out)) - return BTRFS_ARG_UUID; - - return BTRFS_ARG_UNKNOWN; -} - -static int btrfs_scan_kernel(void *search) +static int btrfs_scan_kernel(void *search, unsigned unit_mode) { int ret = 0, fd; + int found = 0; FILE *f; struct mntent *mnt; struct btrfs_ioctl_fs_info_args fs_info_arg; struct btrfs_ioctl_dev_info_args *dev_info_arg = NULL; - struct btrfs_ioctl_space_args *space_info_arg; + struct btrfs_ioctl_space_args *space_info_arg = NULL; char label[BTRFS_LABEL_SIZE]; f = setmntent("/proc/self/mounts", "r"); @@ -448,12 +522,13 @@ static int btrfs_scan_kernel(void *search) continue; ret = get_fs_info(mnt->mnt_dir, &fs_info_arg, &dev_info_arg); - if (ret) + if (ret) { + kfree(dev_info_arg); goto out; + } if (get_label_mounted(mnt->mnt_dir, label)) { kfree(dev_info_arg); - ret = 1; goto out; } if (search && !match_search_item_kernel(fs_info_arg.fsid, @@ -465,63 +540,365 @@ static int btrfs_scan_kernel(void *search) fd = open(mnt->mnt_dir, O_RDONLY); if ((fd != -1) && !get_df(fd, &space_info_arg)) { print_one_fs(&fs_info_arg, dev_info_arg, - space_info_arg, label, mnt->mnt_dir); + space_info_arg, label, mnt->mnt_dir, + unit_mode); kfree(space_info_arg); memset(label, 0, sizeof(label)); + found = 1; } if (fd != -1) close(fd); kfree(dev_info_arg); - if (search) - ret = 0; } - if (search) - ret = 1; out: endmntent(f); + return !found; +} + +static int dev_to_fsid(char *dev, __u8 *fsid) +{ + struct btrfs_super_block *disk_super; + char *buf; + int ret; + int fd; + + buf = malloc(4096); + if (!buf) + return -ENOMEM; + + fd = open(dev, O_RDONLY); + if (fd < 0) { + ret = -errno; + free(buf); + return ret; + } + + disk_super = (struct btrfs_super_block *)buf; + ret = btrfs_read_dev_super(fd, disk_super, + BTRFS_SUPER_INFO_OFFSET, 0); + if (ret) + goto out; + + memcpy(fsid, disk_super->fsid, BTRFS_FSID_SIZE); + ret = 0; + +out: + close(fd); + free(buf); + return ret; +} + +static void free_fs_devices(struct btrfs_fs_devices *fs_devices) +{ + struct btrfs_fs_devices *cur_seed, *next_seed; + struct btrfs_device *device; + + while (!list_empty(&fs_devices->devices)) { + device = list_entry(fs_devices->devices.next, + struct btrfs_device, dev_list); + list_del(&device->dev_list); + + free(device->name); + free(device->label); + free(device); + } + + /* free seed fs chain */ + cur_seed = fs_devices->seed; + fs_devices->seed = NULL; + while (cur_seed) { + next_seed = cur_seed->seed; + free(cur_seed); + + cur_seed = next_seed; + } + + list_del(&fs_devices->list); + free(fs_devices); +} + +static int copy_device(struct btrfs_device *dst, + struct btrfs_device *src) +{ + dst->devid = src->devid; + memcpy(dst->uuid, src->uuid, BTRFS_UUID_SIZE); + if (src->name == NULL) + dst->name = NULL; + else { + dst->name = strdup(src->name); + if (!dst->name) + return -ENOMEM; + } + if (src->label == NULL) + dst->label = NULL; + else { + dst->label = strdup(src->label); + if (!dst->label) { + free(dst->name); + return -ENOMEM; + } + } + dst->total_devs = src->total_devs; + dst->super_bytes_used = src->super_bytes_used; + dst->total_bytes = src->total_bytes; + dst->bytes_used = src->bytes_used; + dst->generation = src->generation; + + return 0; +} + +static int copy_fs_devices(struct btrfs_fs_devices *dst, + struct btrfs_fs_devices *src) +{ + struct btrfs_device *cur_dev, *dev_copy; + int ret = 0; + + memcpy(dst->fsid, src->fsid, BTRFS_FSID_SIZE); + INIT_LIST_HEAD(&dst->devices); + dst->seed = NULL; + + list_for_each_entry(cur_dev, &src->devices, dev_list) { + dev_copy = malloc(sizeof(*dev_copy)); + if (!dev_copy) { + ret = -ENOMEM; + break; + } + + ret = copy_device(dev_copy, cur_dev); + if (ret) { + free(dev_copy); + break; + } + + list_add(&dev_copy->dev_list, &dst->devices); + dev_copy->fs_devices = dst; + } + + return ret; +} + +static int find_and_copy_seed(struct btrfs_fs_devices *seed, + struct btrfs_fs_devices *copy, + struct list_head *fs_uuids) { + struct btrfs_fs_devices *cur_fs; + + list_for_each_entry(cur_fs, fs_uuids, list) + if (!memcmp(seed->fsid, cur_fs->fsid, BTRFS_FSID_SIZE)) + return copy_fs_devices(copy, cur_fs); + + return 1; +} + +static int has_seed_devices(struct btrfs_fs_devices *fs_devices) +{ + struct btrfs_device *device; + int dev_cnt_total, dev_cnt = 0; + + device = list_first_entry(&fs_devices->devices, struct btrfs_device, + dev_list); + + dev_cnt_total = device->total_devs; + + list_for_each_entry(device, &fs_devices->devices, dev_list) + dev_cnt++; + + return dev_cnt_total != dev_cnt; +} + +static int search_umounted_fs_uuids(struct list_head *all_uuids, + char *search, int *found) +{ + struct btrfs_fs_devices *cur_fs, *fs_copy; + struct list_head *fs_uuids; + int ret = 0; + + fs_uuids = btrfs_scanned_uuids(); + + /* + * The fs_uuids list is global, and open_ctree_* will + * modify it, make a private copy here + */ + list_for_each_entry(cur_fs, fs_uuids, list) { + /* don't bother handle all fs, if search target specified */ + if (search) { + if (uuid_search(cur_fs, search) == 0) + continue; + if (found) + *found = 1; + } + + /* skip all fs already shown as mounted fs */ + if (is_seen_fsid(cur_fs->fsid)) + continue; + + fs_copy = malloc(sizeof(*fs_copy)); + if (!fs_copy) { + ret = -ENOMEM; + goto out; + } + + ret = copy_fs_devices(fs_copy, cur_fs); + if (ret) { + free(fs_copy); + goto out; + } + + list_add(&fs_copy->list, all_uuids); + } + +out: return ret; } +static int map_seed_devices(struct list_head *all_uuids) +{ + struct btrfs_fs_devices *cur_fs, *cur_seed; + struct btrfs_fs_devices *seed_copy; + struct btrfs_fs_devices *opened_fs; + struct btrfs_device *device; + struct btrfs_fs_info *fs_info; + struct list_head *fs_uuids; + int ret = 0; + + fs_uuids = btrfs_scanned_uuids(); + + list_for_each_entry(cur_fs, all_uuids, list) { + device = list_first_entry(&cur_fs->devices, + struct btrfs_device, dev_list); + if (!device) + continue; + + /* skip fs without seeds */ + if (!has_seed_devices(cur_fs)) + continue; + + /* + * open_ctree_* detects seed/sprout mapping + */ + fs_info = open_ctree_fs_info(device->name, 0, 0, + OPEN_CTREE_PARTIAL); + if (!fs_info) + continue; + + /* + * copy the seed chain under the opened fs + */ + opened_fs = fs_info->fs_devices; + cur_seed = cur_fs; + while (opened_fs->seed) { + seed_copy = malloc(sizeof(*seed_copy)); + if (!seed_copy) { + ret = -ENOMEM; + goto fail_out; + } + ret = find_and_copy_seed(opened_fs->seed, seed_copy, + fs_uuids); + if (ret) { + free(seed_copy); + goto fail_out; + } + + cur_seed->seed = seed_copy; + + opened_fs = opened_fs->seed; + cur_seed = cur_seed->seed; + } + + close_ctree(fs_info->chunk_root); + } + +out: + return ret; +fail_out: + close_ctree(fs_info->chunk_root); + goto out; +} + static const char * const cmd_show_usage[] = { "btrfs filesystem show [options] [|||label]", "Show the structure of a filesystem", "-d|--all-devices show only disks under /dev containing btrfs filesystem", "-m|--mounted show only mounted btrfs", + "--raw raw numbers in bytes", + "--human-readable human friendly numbers, base 1024 (default)", + "--iec use 1024 as a base (KiB, MiB, GiB, TiB)", + "--si use 1000 as a base (kB, MB, GB, TB)", + "--kbytes show sizes in KiB, or kB with --si", + "--mbytes show sizes in MiB, or MB with --si", + "--gbytes show sizes in GiB, or GB with --si", + "--tbytes show sizes in TiB, or TB with --si", "If no argument is given, structure of all present filesystems is shown.", NULL }; static int cmd_show(int argc, char **argv) { - struct list_head *all_uuids; + LIST_HEAD(all_uuids); struct btrfs_fs_devices *fs_devices; - struct list_head *cur_uuid; char *search = NULL; int ret; - int where = BTRFS_SCAN_LBLKID; + /* default, search both kernel and udev */ + int where = -1; int type = 0; - char mp[BTRFS_PATH_NAME_MAX + 1]; + char mp[PATH_MAX]; char path[PATH_MAX]; + __u8 fsid[BTRFS_FSID_SIZE]; + char uuid_buf[BTRFS_UUID_UNPARSED_SIZE]; + unsigned unit_mode = UNITS_DEFAULT; + int found = 0; while (1) { - int long_index; - static struct option long_options[] = { + int c; + static const struct option long_options[] = { { "all-devices", no_argument, NULL, 'd'}, { "mounted", no_argument, NULL, 'm'}, - { NULL, no_argument, NULL, 0 }, + { "raw", no_argument, NULL, GETOPT_VAL_RAW}, + { "kbytes", no_argument, NULL, GETOPT_VAL_KBYTES}, + { "mbytes", no_argument, NULL, GETOPT_VAL_MBYTES}, + { "gbytes", no_argument, NULL, GETOPT_VAL_GBYTES}, + { "tbytes", no_argument, NULL, GETOPT_VAL_TBYTES}, + { "si", no_argument, NULL, GETOPT_VAL_SI}, + { "iec", no_argument, NULL, GETOPT_VAL_IEC}, + { "human-readable", no_argument, NULL, + GETOPT_VAL_HUMAN_READABLE}, + { NULL, 0, NULL, 0 } }; - int c = getopt_long(argc, argv, "dm", long_options, - &long_index); + + c = getopt_long(argc, argv, "dm", long_options, NULL); if (c < 0) break; switch (c) { case 'd': - where = BTRFS_SCAN_DEV; + where = BTRFS_SCAN_LBLKID; break; case 'm': where = BTRFS_SCAN_MOUNTED; break; + case GETOPT_VAL_RAW: + units_set_mode(&unit_mode, UNITS_RAW); + break; + case GETOPT_VAL_KBYTES: + units_set_base(&unit_mode, UNITS_KBYTES); + break; + case GETOPT_VAL_MBYTES: + units_set_base(&unit_mode, UNITS_MBYTES); + break; + case GETOPT_VAL_GBYTES: + units_set_base(&unit_mode, UNITS_GBYTES); + break; + case GETOPT_VAL_TBYTES: + units_set_base(&unit_mode, UNITS_TBYTES); + break; + case GETOPT_VAL_SI: + units_set_mode(&unit_mode, UNITS_DECIMAL); + break; + case GETOPT_VAL_IEC: + units_set_mode(&unit_mode, UNITS_BINARY); + break; + case GETOPT_VAL_HUMAN_READABLE: + units_set_mode(&unit_mode, UNITS_HUMAN_BINARY); + break; default: usage(cmd_show_usage); } @@ -535,60 +912,99 @@ static int cmd_show(int argc, char **argv) if (strlen(search) == 0) usage(cmd_show_usage); type = check_arg_type(search); - if (type == BTRFS_ARG_BLKDEV) { - if (where == BTRFS_SCAN_DEV) { - /* we need to do this because - * legacy BTRFS_SCAN_DEV - * provides /dev/dm-x paths - */ - if (realpath(search, path)) - search = path; + + /* + * For search is a device: + * realpath do /dev/mapper/XX => /dev/dm-X + * which is required by BTRFS_SCAN_DEV + * For search is a mountpoint: + * realpath do /mnt/btrfs/ => /mnt/btrfs + * which shall be recognized by btrfs_scan_kernel() + */ + if (realpath(search, path)) + search = path; + + /* + * Needs special handling if input arg is block dev And if + * input arg is mount-point just print it right away + */ + if (type == BTRFS_ARG_BLKDEV && where != BTRFS_SCAN_LBLKID) { + ret = get_btrfs_mount(search, mp, sizeof(mp)); + if (!ret) { + /* given block dev is mounted */ + search = mp; + type = BTRFS_ARG_MNTPOINT; } else { - ret = get_btrfs_mount(search, - mp, sizeof(mp)); - if (!ret) - /* given block dev is mounted*/ - search = mp; - else - goto devs_only; + ret = dev_to_fsid(search, fsid); + if (ret) { + fprintf(stderr, + "ERROR: No btrfs on %s\n", + search); + return 1; + } + uuid_unparse(fsid, uuid_buf); + search = uuid_buf; + type = BTRFS_ARG_UUID; + goto devs_only; } } } - if (where == BTRFS_SCAN_DEV) + if (where == BTRFS_SCAN_LBLKID) goto devs_only; /* show mounted btrfs */ - ret = btrfs_scan_kernel(search); - if (search && !ret) - return 0; + ret = btrfs_scan_kernel(search, unit_mode); + if (search && !ret) { + /* since search is found we are done */ + goto out; + } /* shows mounted only */ if (where == BTRFS_SCAN_MOUNTED) goto out; devs_only: - ret = scan_for_btrfs(where, !BTRFS_UPDATE_KERNEL); + ret = btrfs_scan_lblkid(); if (ret) { fprintf(stderr, "ERROR: %d while scanning\n", ret); return 1; } - - all_uuids = btrfs_scanned_uuids(); - list_for_each(cur_uuid, all_uuids) { - fs_devices = list_entry(cur_uuid, struct btrfs_fs_devices, - list); - if (search && uuid_search(fs_devices, search) == 0) - continue; - print_one_uuid(fs_devices); + ret = search_umounted_fs_uuids(&all_uuids, search, &found); + if (ret < 0) { + fprintf(stderr, + "ERROR: %d while searching target device\n", ret); + return 1; } + /* + * The seed/sprout mapping are not detected yet, + * do mapping build for all umounted fs + */ + ret = map_seed_devices(&all_uuids); + if (ret) { + fprintf(stderr, + "ERROR: %d while mapping seed devices\n", ret); + return 1; + } + + list_for_each_entry(fs_devices, &all_uuids, list) + print_one_uuid(fs_devices, unit_mode); + + if (search && !found) + ret = 1; + + while (!list_empty(&all_uuids)) { + fs_devices = list_entry(all_uuids.next, + struct btrfs_fs_devices, list); + free_fs_devices(fs_devices); + } out: - printf("%s\n", BTRFS_BUILD_VERSION); + printf("%s\n", PACKAGE_STRING); free_seen_fsid(); - return 0; + return ret; } static const char * const cmd_sync_usage[] = { @@ -649,7 +1065,7 @@ static const char * const cmd_defrag_usage[] = { "-f flush data to disk immediately after defragmenting", "-s start defragment only from byte onward", "-l len defragment only up to len bytes", - "-t size minimal size of file to be considered for defragmenting", + "-t size target extent size hint", NULL }; @@ -711,7 +1127,7 @@ static int cmd_defrag(int argc, char **argv) int flush = 0; u64 start = 0; u64 len = (u64)-1; - u32 thresh = 0; + u64 thresh = 0; int i; int recursive = 0; int ret = 0; @@ -754,6 +1170,11 @@ static int cmd_defrag(int argc, char **argv) break; case 't': thresh = parse_size(optarg); + if (thresh > (u32)-1) { + fprintf(stderr, + "WARNING: target extent size %llu too big, trimmed to %u", + thresh, (u32)-1); + } defrag_global_fancy_ioctl = 1; break; case 'r': @@ -770,7 +1191,7 @@ static int cmd_defrag(int argc, char **argv) memset(&defrag_global_range, 0, sizeof(range)); defrag_global_range.start = start; defrag_global_range.len = len; - defrag_global_range.extent_thresh = thresh; + defrag_global_range.extent_thresh = (u32)thresh; if (compress_type) { defrag_global_range.flags |= BTRFS_DEFRAG_RANGE_COMPRESS; defrag_global_range.compress_type = compress_type; @@ -842,7 +1263,7 @@ static int cmd_defrag(int argc, char **argv) } } if (defrag_global_verbose) - printf("%s\n", BTRFS_BUILD_VERSION); + printf("%s\n", PACKAGE_STRING); if (defrag_global_errors) fprintf(stderr, "total %d failures\n", defrag_global_errors); @@ -850,19 +1271,271 @@ static int cmd_defrag(int argc, char **argv) } static const char * const cmd_resize_usage[] = { - "btrfs filesystem resize [devid:][+/-][gkm]|[devid:]max ", + "btrfs filesystem resize [devid:][+/-][kKmMgGtTpPeE]|[devid:]max|[devid:]get_min_size ", "Resize a filesystem", "If 'max' is passed, the filesystem will occupy all available space", "on the device 'devid'.", + "If 'get_min_size' is passed, return the minimum size the device can", + "be shrunk to.", + "[kK] means KiB, which denotes 1KiB = 1024B, 1MiB = 1024KiB, etc.", NULL }; +struct dev_extent_elem { + u64 start; + /* inclusive end */ + u64 end; + struct list_head list; +}; + +static int add_dev_extent(struct list_head *list, + const u64 start, const u64 end, + const int append) +{ + struct dev_extent_elem *e; + + e = malloc(sizeof(*e)); + if (!e) + return -ENOMEM; + + e->start = start; + e->end = end; + + if (append) + list_add_tail(&e->list, list); + else + list_add(&e->list, list); + + return 0; +} + +static void free_dev_extent_list(struct list_head *list) +{ + while (!list_empty(list)) { + struct dev_extent_elem *e; + + e = list_first_entry(list, struct dev_extent_elem, list); + list_del(&e->list); + free(e); + } +} + +static int hole_includes_sb_mirror(const u64 start, const u64 end) +{ + int i; + int ret = 0; + + for (i = 0; i < BTRFS_SUPER_MIRROR_MAX; i++) { + u64 bytenr = btrfs_sb_offset(i); + + if (bytenr >= start && bytenr <= end) { + ret = 1; + break; + } + } + + return ret; +} + +static void adjust_dev_min_size(struct list_head *extents, + struct list_head *holes, + u64 *min_size) +{ + /* + * If relocation of the block group of a device extent must happen (see + * below) scratch space is used for the relocation. So track here the + * size of the largest device extent that has to be relocated. We track + * only the largest and not the sum of the sizes of all relocated block + * groups because after each block group is relocated the running + * transaction is committed so that pinned space is released. + */ + u64 scratch_space = 0; + + /* + * List of device extents is sorted by descending order of the extent's + * end offset. If some extent goes beyond the computed minimum size, + * which initially matches the sum of the lenghts of all extents, + * we need to check if the extent can be relocated to an hole in the + * device between [0, *min_size[ (which is what the resize ioctl does). + */ + while (!list_empty(extents)) { + struct dev_extent_elem *e; + struct dev_extent_elem *h; + int found = 0; + u64 extent_len; + u64 hole_len = 0; + + e = list_first_entry(extents, struct dev_extent_elem, list); + if (e->end <= *min_size) + break; + + /* + * Our extent goes beyond the computed *min_size. See if we can + * find a hole large enough to relocate it to. If not we must stop + * and set *min_size to the end of the extent. + */ + extent_len = e->end - e->start + 1; + list_for_each_entry(h, holes, list) { + hole_len = h->end - h->start + 1; + if (hole_len >= extent_len) { + found = 1; + break; + } + } + + if (!found) { + *min_size = e->end + 1; + break; + } + + /* + * If the hole found contains the location for a superblock + * mirror, we are pessimistic and require allocating one + * more extent of the same size. This is because the block + * group could be in the worst case used by a single extent + * with a size >= (block_group.length - superblock.size). + */ + if (hole_includes_sb_mirror(h->start, + h->start + extent_len - 1)) + *min_size += extent_len; + + if (hole_len > extent_len) { + h->start += extent_len; + } else { + list_del(&h->list); + free(h); + } + + list_del(&e->list); + free(e); + + if (extent_len > scratch_space) + scratch_space = extent_len; + } + + if (scratch_space) { + *min_size += scratch_space; + /* + * Chunk allocation requires inserting/updating items in the + * chunk tree, so often this can lead to the need of allocating + * a new system chunk too, which has a maximum size of 32Mb. + */ + *min_size += 32 * 1024 * 1024; + } +} + +static int get_min_size(int fd, DIR *dirstream, const char *amount) +{ + int ret = 1; + char *p = strstr(amount, ":"); + u64 devid = 1; + /* + * Device allocations starts at 1Mb or at the value passed through the + * mount option alloc_start if it's bigger than 1Mb. The alloc_start + * option is used for debugging and testing only, and recently the + * possibility of deprecating/removing it has been discussed, so we + * ignore it here. + */ + u64 min_size = 1 * 1024 * 1024ull; + struct btrfs_ioctl_search_args args; + struct btrfs_ioctl_search_key *sk = &args.key; + u64 last_pos = (u64)-1; + LIST_HEAD(extents); + LIST_HEAD(holes); + + if (p && sscanf(amount, "%llu:get_min_size", &devid) != 1) { + fprintf(stderr, "Invalid parameter: %s\n", amount); + goto out; + } + + memset(&args, 0, sizeof(args)); + sk->tree_id = BTRFS_DEV_TREE_OBJECTID; + sk->min_objectid = devid; + sk->max_objectid = devid; + sk->max_type = BTRFS_DEV_EXTENT_KEY; + sk->min_type = BTRFS_DEV_EXTENT_KEY; + sk->min_offset = 0; + sk->max_offset = (u64)-1; + sk->min_transid = 0; + sk->max_transid = (u64)-1; + sk->nr_items = 4096; + + while (1) { + int i; + struct btrfs_ioctl_search_header *sh; + unsigned long off = 0; + + ret = ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args); + if (ret < 0) { + fprintf(stderr, + "Error invoking tree search ioctl: %s\n", + strerror(errno)); + ret = 1; + goto out; + } + + if (sk->nr_items == 0) + break; + + for (i = 0; i < sk->nr_items; i++) { + struct btrfs_dev_extent *extent; + u64 len; + + sh = (struct btrfs_ioctl_search_header *)(args.buf + + off); + off += sizeof(*sh); + extent = (struct btrfs_dev_extent *)(args.buf + off); + off += sh->len; + + sk->min_objectid = sh->objectid; + sk->min_type = sh->type; + sk->min_offset = sh->offset + 1; + + if (sh->objectid != devid || + sh->type != BTRFS_DEV_EXTENT_KEY) + continue; + + len = btrfs_stack_dev_extent_length(extent); + min_size += len; + ret = add_dev_extent(&extents, sh->offset, + sh->offset + len - 1, 0); + + if (!ret && last_pos != (u64)-1 && + last_pos != sh->offset) + ret = add_dev_extent(&holes, last_pos, + sh->offset - 1, 1); + if (ret) { + fprintf(stderr, "Error: %s\n", strerror(-ret)); + ret = 1; + goto out; + } + + last_pos = sh->offset + len; + } + + if (sk->min_type != BTRFS_DEV_EXTENT_KEY || + sk->min_objectid != devid) + break; + } + + adjust_dev_min_size(&extents, &holes, &min_size); + printf("%llu bytes (%s)\n", min_size, pretty_size(min_size)); + ret = 0; +out: + close_file_or_dir(fd, dirstream); + free_dev_extent_list(&extents); + free_dev_extent_list(&holes); + + return ret; +} + static int cmd_resize(int argc, char **argv) { struct btrfs_ioctl_vol_args args; int fd, res, len, e; char *amount, *path; DIR *dirstream = NULL; + struct stat st; if (check_argc_exact(argc, 3)) usage(cmd_resize_usage); @@ -877,13 +1550,31 @@ static int cmd_resize(int argc, char **argv) return 1; } + res = stat(path, &st); + if (res < 0) { + fprintf(stderr, "ERROR: resize: cannot stat %s: %s\n", + path, strerror(errno)); + return 1; + } + if (!S_ISDIR(st.st_mode)) { + fprintf(stderr, + "ERROR: resize works on mounted filesystems and accepts only\n" + "directories as argument. Passing file containing a btrfs image\n" + "would resize the underlying filesystem instead of the image.\n"); + return 1; + } + fd = open_file_or_dir(path, &dirstream); if (fd < 0) { fprintf(stderr, "ERROR: can't access '%s'\n", path); return 1; } + if (strstr(amount, "get_min_size")) + return get_min_size(fd, dirstream, amount); + printf("Resize '%s' of '%s'\n", path, amount); + memset(&args, 0, sizeof(args)); strncpy_null(args.name, amount); res = ioctl(fd, BTRFS_IOC_RESIZE, &args); e = errno; @@ -892,6 +1583,18 @@ static int cmd_resize(int argc, char **argv) fprintf(stderr, "ERROR: unable to resize '%s' - %s\n", path, strerror(e)); return 1; + } else if (res > 0) { + const char *err_str = btrfs_err_str(res); + + if (err_str) { + fprintf(stderr, "ERROR: btrfs error resizing '%s' - %s\n", + path, err_str); + } else { + fprintf(stderr, + "ERROR: btrfs error resizing '%s' - unknown btrfs_err_code %d\n", + path, res); + } + return 1; } return 0; } @@ -923,15 +1626,21 @@ static int cmd_label(int argc, char **argv) } } +static const char filesystem_cmd_group_info[] = +"overall filesystem tasks and information"; + const struct cmd_group filesystem_cmd_group = { - filesystem_cmd_group_usage, NULL, { - { "df", cmd_df, cmd_df_usage, NULL, 0 }, + filesystem_cmd_group_usage, filesystem_cmd_group_info, { + { "df", cmd_filesystem_df, cmd_filesystem_df_usage, NULL, 0 }, { "show", cmd_show, cmd_show_usage, NULL, 0 }, { "sync", cmd_sync, cmd_sync_usage, NULL, 0 }, { "defragment", cmd_defrag, cmd_defrag_usage, NULL, 0 }, - { "balance", cmd_balance, NULL, &balance_cmd_group, 1 }, + { "balance", cmd_balance, NULL, &balance_cmd_group, CMD_HIDDEN }, { "resize", cmd_resize, cmd_resize_usage, NULL, 0 }, { "label", cmd_label, cmd_label_usage, NULL, 0 }, + { "usage", cmd_filesystem_usage, + cmd_filesystem_usage_usage, NULL, 0 }, + NULL_CMD_STRUCT } };