From c8927d51164f68a5c96090c5f52d8ffa81ec98b7 Mon Sep 17 00:00:00 2001 From: David Sterba Date: Fri, 17 Apr 2015 18:29:38 +0200 Subject: btrfs-progs: cleanup, rename *disk_usage* files to usage Signed-off-by: David Sterba --- cmds-fi-usage.c | 1036 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1036 insertions(+) create mode 100644 cmds-fi-usage.c (limited to 'cmds-fi-usage.c') diff --git a/cmds-fi-usage.c b/cmds-fi-usage.c new file mode 100644 index 00000000..adf1c27f --- /dev/null +++ b/cmds-fi-usage.c @@ -0,0 +1,1036 @@ +/* + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License v2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 021110-1307, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utils.h" +#include "kerncompat.h" +#include "ctree.h" +#include "string-table.h" +#include "cmds-fi-usage.h" +#include "commands.h" + +#include "version.h" + +/* + * Add the chunk info to the chunk_info list + */ +static int add_info_to_list(struct chunk_info **info_ptr, + int *info_count, + struct btrfs_chunk *chunk) +{ + + u64 type = btrfs_stack_chunk_type(chunk); + u64 size = btrfs_stack_chunk_length(chunk); + int num_stripes = btrfs_stack_chunk_num_stripes(chunk); + int j; + + for (j = 0 ; j < num_stripes ; j++) { + int i; + struct chunk_info *p = 0; + struct btrfs_stripe *stripe; + u64 devid; + + stripe = btrfs_stripe_nr(chunk, j); + devid = btrfs_stack_stripe_devid(stripe); + + for (i = 0 ; i < *info_count ; i++) + if ((*info_ptr)[i].type == type && + (*info_ptr)[i].devid == devid && + (*info_ptr)[i].num_stripes == num_stripes ) { + p = (*info_ptr) + i; + break; + } + + if (!p) { + int size = sizeof(struct btrfs_chunk) * (*info_count+1); + struct chunk_info *res = realloc(*info_ptr, size); + + if (!res) { + free(*info_ptr); + fprintf(stderr, "ERROR: not enough memory\n"); + return -ENOMEM; + } + + *info_ptr = res; + p = res + *info_count; + (*info_count)++; + + p->devid = devid; + p->type = type; + p->size = 0; + p->num_stripes = num_stripes; + } + + p->size += size; + + } + + return 0; + +} + +/* + * Helper to sort the chunk type + */ +static int cmp_chunk_block_group(u64 f1, u64 f2) +{ + + u64 mask; + + if ((f1 & BTRFS_BLOCK_GROUP_TYPE_MASK) == + (f2 & BTRFS_BLOCK_GROUP_TYPE_MASK)) + mask = BTRFS_BLOCK_GROUP_PROFILE_MASK; + else if (f2 & BTRFS_BLOCK_GROUP_SYSTEM) + return -1; + else if (f1 & BTRFS_BLOCK_GROUP_SYSTEM) + return +1; + else + mask = BTRFS_BLOCK_GROUP_TYPE_MASK; + + if ((f1 & mask) > (f2 & mask)) + return +1; + else if ((f1 & mask) < (f2 & mask)) + return -1; + else + return 0; +} + +/* + * Helper to sort the chunk + */ +static int cmp_chunk_info(const void *a, const void *b) +{ + return cmp_chunk_block_group( + ((struct chunk_info *)a)->type, + ((struct chunk_info *)b)->type); +} + +static int load_chunk_info(int fd, struct chunk_info **info_ptr, int *info_count) +{ + int ret; + struct btrfs_ioctl_search_args args; + struct btrfs_ioctl_search_key *sk = &args.key; + struct btrfs_ioctl_search_header *sh; + unsigned long off = 0; + int i, e; + + memset(&args, 0, sizeof(args)); + + /* + * there may be more than one ROOT_ITEM key if there are + * snapshots pending deletion, we have to loop through + * them. + */ + sk->tree_id = BTRFS_CHUNK_TREE_OBJECTID; + + sk->min_objectid = 0; + sk->max_objectid = (u64)-1; + sk->max_type = 0; + sk->min_type = (u8)-1; + sk->min_offset = 0; + sk->max_offset = (u64)-1; + sk->min_transid = 0; + sk->max_transid = (u64)-1; + sk->nr_items = 4096; + + while (1) { + ret = ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args); + e = errno; + if (e == EPERM) + return -e; + + if (ret < 0) { + fprintf(stderr, + "ERROR: can't perform the search - %s\n", + strerror(e)); + return 1; + } + /* the ioctl returns the number of item it found in nr_items */ + + if (sk->nr_items == 0) + break; + + off = 0; + for (i = 0; i < sk->nr_items; i++) { + struct btrfs_chunk *item; + sh = (struct btrfs_ioctl_search_header *)(args.buf + + off); + + off += sizeof(*sh); + item = (struct btrfs_chunk *)(args.buf + off); + + ret = add_info_to_list(info_ptr, info_count, item); + if (ret) { + *info_ptr = 0; + return 1; + } + + off += sh->len; + + sk->min_objectid = sh->objectid; + sk->min_type = sh->type; + sk->min_offset = sh->offset+1; + + } + if (!sk->min_offset) /* overflow */ + sk->min_type++; + else + continue; + + if (!sk->min_type) + sk->min_objectid++; + else + continue; + + if (!sk->min_objectid) + break; + } + + qsort(*info_ptr, *info_count, sizeof(struct chunk_info), + cmp_chunk_info); + + return 0; +} + +/* + * Helper to sort the struct btrfs_ioctl_space_info + */ +static int cmp_btrfs_ioctl_space_info(const void *a, const void *b) +{ + return cmp_chunk_block_group( + ((struct btrfs_ioctl_space_info *)a)->flags, + ((struct btrfs_ioctl_space_info *)b)->flags); +} + +/* + * This function load all the information about the space usage + */ +static struct btrfs_ioctl_space_args *load_space_info(int fd, char *path) +{ + struct btrfs_ioctl_space_args *sargs = 0, *sargs_orig = 0; + int e, ret, count; + + sargs_orig = sargs = calloc(1, sizeof(struct btrfs_ioctl_space_args)); + if (!sargs) { + fprintf(stderr, "ERROR: not enough memory\n"); + return NULL; + } + + sargs->space_slots = 0; + sargs->total_spaces = 0; + + ret = ioctl(fd, BTRFS_IOC_SPACE_INFO, sargs); + e = errno; + if (ret) { + fprintf(stderr, + "ERROR: couldn't get space info on '%s' - %s\n", + path, strerror(e)); + free(sargs); + return NULL; + } + if (!sargs->total_spaces) { + free(sargs); + printf("No chunks found\n"); + return NULL; + } + + count = sargs->total_spaces; + + sargs = realloc(sargs, sizeof(struct btrfs_ioctl_space_args) + + (count * sizeof(struct btrfs_ioctl_space_info))); + if (!sargs) { + free(sargs_orig); + fprintf(stderr, "ERROR: not enough memory\n"); + return NULL; + } + + sargs->space_slots = count; + sargs->total_spaces = 0; + + ret = ioctl(fd, BTRFS_IOC_SPACE_INFO, sargs); + e = errno; + + if (ret) { + fprintf(stderr, + "ERROR: couldn't get space info on '%s' - %s\n", + path, strerror(e)); + free(sargs); + return NULL; + } + + qsort(&(sargs->spaces), count, sizeof(struct btrfs_ioctl_space_info), + cmp_btrfs_ioctl_space_info); + + return sargs; +} + +/* + * This function computes the space occuped by a *single* RAID5/RAID6 chunk. + * The computation is performed on the basis of the number of stripes + * which compose the chunk, which could be different from the number of devices + * if a disk is added later. + */ +static void get_raid56_used(int fd, struct chunk_info *chunks, int chunkcount, + u64 *raid5_used, u64 *raid6_used) +{ + struct chunk_info *info_ptr = chunks; + *raid5_used = 0; + *raid6_used = 0; + + while (chunkcount-- > 0) { + if (info_ptr->type & BTRFS_BLOCK_GROUP_RAID5) + (*raid5_used) += info_ptr->size / (info_ptr->num_stripes - 1); + if (info_ptr->type & BTRFS_BLOCK_GROUP_RAID6) + (*raid6_used) += info_ptr->size / (info_ptr->num_stripes - 2); + info_ptr++; + } +} + +#define MIN_UNALOCATED_THRESH (16 * 1024 * 1024) +static int print_filesystem_usage_overall(int fd, struct chunk_info *chunkinfo, + int chunkcount, struct device_info *devinfo, int devcount, + char *path, unsigned unit_mode) +{ + struct btrfs_ioctl_space_args *sargs = 0; + int i; + int ret = 0; + int width = 10; /* default 10 for human units */ + /* + * r_* prefix is for raw data + * l_* is for logical + */ + u64 r_total_size = 0; /* filesystem size, sum of device sizes */ + u64 r_total_chunks = 0; /* sum of chunks sizes on disk(s) */ + u64 r_total_used = 0; + u64 r_total_unused = 0; + u64 r_total_missing = 0; /* sum of missing devices size */ + u64 r_data_used = 0; + u64 r_data_chunks = 0; + u64 l_data_chunks = 0; + u64 r_metadata_used = 0; + u64 r_metadata_chunks = 0; + u64 l_metadata_chunks = 0; + u64 r_system_used = 0; + u64 r_system_chunks = 0; + double data_ratio; + double metadata_ratio; + /* logical */ + u64 raid5_used = 0; + u64 raid6_used = 0; + u64 l_global_reserve = 0; + u64 l_global_reserve_used = 0; + u64 free_estimated = 0; + u64 free_min = 0; + int max_data_ratio = 1; + + sargs = load_space_info(fd, path); + if (!sargs) { + ret = 1; + goto exit; + } + + r_total_size = 0; + for (i = 0; i < devcount; i++) { + r_total_size += devinfo[i].size; + if (!devinfo[i].device_size) + r_total_missing += devinfo[i].size; + } + + if (r_total_size == 0) { + fprintf(stderr, + "ERROR: couldn't get space info on '%s' - %s\n", + path, strerror(errno)); + + ret = 1; + goto exit; + } + get_raid56_used(fd, chunkinfo, chunkcount, &raid5_used, &raid6_used); + + for (i = 0; i < sargs->total_spaces; i++) { + int ratio; + u64 flags = sargs->spaces[i].flags; + + /* + * The raid5/raid6 ratio depends by the stripes number + * used by every chunk. It is computed separately + */ + if (flags & BTRFS_BLOCK_GROUP_RAID0) + ratio = 1; + else if (flags & BTRFS_BLOCK_GROUP_RAID1) + ratio = 2; + else if (flags & BTRFS_BLOCK_GROUP_RAID5) + ratio = 0; + else if (flags & BTRFS_BLOCK_GROUP_RAID6) + ratio = 0; + else if (flags & BTRFS_BLOCK_GROUP_DUP) + ratio = 2; + else if (flags & BTRFS_BLOCK_GROUP_RAID10) + ratio = 2; + else + ratio = 1; + + if (!ratio) + fprintf(stderr, "WARNING: RAID56 detected, not implemented\n"); + + if (ratio > max_data_ratio) + max_data_ratio = ratio; + + if (flags & BTRFS_SPACE_INFO_GLOBAL_RSV) { + l_global_reserve = sargs->spaces[i].total_bytes; + l_global_reserve_used = sargs->spaces[i].used_bytes; + } + if ((flags & (BTRFS_BLOCK_GROUP_DATA | BTRFS_BLOCK_GROUP_METADATA)) + == (BTRFS_BLOCK_GROUP_DATA | BTRFS_BLOCK_GROUP_METADATA)) { + fprintf(stderr, "WARNING: MIXED blockgroups not handled\n"); + } + + if (flags & BTRFS_BLOCK_GROUP_DATA) { + r_data_used += sargs->spaces[i].used_bytes * ratio; + r_data_chunks += sargs->spaces[i].total_bytes * ratio; + l_data_chunks += sargs->spaces[i].total_bytes; + } + if (flags & BTRFS_BLOCK_GROUP_METADATA) { + r_metadata_used += sargs->spaces[i].used_bytes * ratio; + r_metadata_chunks += sargs->spaces[i].total_bytes * ratio; + l_metadata_chunks += sargs->spaces[i].total_bytes; + } + if (flags & BTRFS_BLOCK_GROUP_SYSTEM) { + r_system_used += sargs->spaces[i].used_bytes * ratio; + r_system_chunks += sargs->spaces[i].total_bytes * ratio; + } + } + + r_total_chunks = r_data_chunks + r_metadata_chunks + r_system_chunks; + r_total_used = r_data_used + r_metadata_used + r_system_used; + r_total_unused = r_total_size - r_total_chunks; + + /* Raw / Logical = raid factor, >= 1 */ + data_ratio = (double)r_data_chunks / l_data_chunks; + metadata_ratio = (double)r_metadata_chunks / l_metadata_chunks; + +#if 0 + /* add the raid5/6 allocated space */ + total_chunks += raid5_used + raid6_used; +#endif + + /* + * We're able to fill at least DATA for the unused space + * + * With mixed raid levels, this gives a rough estimate but more + * accurate than just counting the logical free space + * (l_data_chunks - l_data_used) + * + * In non-mixed case there's no difference. + */ + free_estimated = (r_data_chunks - r_data_used) / data_ratio; + free_min = free_estimated; + + /* Chop unallocatable space */ + /* FIXME: must be applied per device */ + if (r_total_unused >= MIN_UNALOCATED_THRESH) { + free_estimated += r_total_unused / data_ratio; + /* Match the calculation of 'df', use the highest raid ratio */ + free_min += r_total_unused / max_data_ratio; + } + + if (unit_mode != UNITS_HUMAN) + width = 18; + + printf("Overall:\n"); + + printf(" Device size:\t\t%*s\n", width, + pretty_size_mode(r_total_size, unit_mode)); + printf(" Device allocated:\t\t%*s\n", width, + pretty_size_mode(r_total_chunks, unit_mode)); + printf(" Device unallocated:\t\t%*s\n", width, + pretty_size_mode(r_total_unused, unit_mode)); + printf(" Device missing:\t\t%*s\n", width, + pretty_size_mode(r_total_missing, unit_mode)); + printf(" Used:\t\t\t%*s\n", width, + pretty_size_mode(r_total_used, unit_mode)); + printf(" Free (estimated):\t\t%*s\t(", + width, + pretty_size_mode(free_estimated, unit_mode)); + printf("min: %s)\n", pretty_size_mode(free_min, unit_mode)); + printf(" Data ratio:\t\t\t%*.2f\n", + width, data_ratio); + printf(" Metadata ratio:\t\t%*.2f\n", + width, metadata_ratio); + printf(" Global reserve:\t\t%*s\t(used: %s)\n", width, + pretty_size_mode(l_global_reserve, unit_mode), + pretty_size_mode(l_global_reserve_used, unit_mode)); + +exit: + + if (sargs) + free(sargs); + + return ret; +} + +/* + * Helper to sort the device_info structure + */ +static int cmp_device_info(const void *a, const void *b) +{ + return strcmp(((struct device_info *)a)->path, + ((struct device_info *)b)->path); +} + +/* + * This function loads the device_info structure and put them in an array + */ +static int load_device_info(int fd, struct device_info **device_info_ptr, + int *device_info_count) +{ + int ret, i, ndevs, e; + struct btrfs_ioctl_fs_info_args fi_args; + struct btrfs_ioctl_dev_info_args dev_info; + struct device_info *info; + + *device_info_count = 0; + *device_info_ptr = 0; + + ret = ioctl(fd, BTRFS_IOC_FS_INFO, &fi_args); + e = errno; + if (e == EPERM) + return -e; + if (ret < 0) { + fprintf(stderr, "ERROR: cannot get filesystem info - %s\n", + strerror(e)); + return 1; + } + + info = calloc(fi_args.num_devices, sizeof(struct device_info)); + if (!info) { + fprintf(stderr, "ERROR: not enough memory\n"); + return 1; + } + + for (i = 0, ndevs = 0 ; i <= fi_args.max_id ; i++) { + BUG_ON(ndevs >= fi_args.num_devices); + memset(&dev_info, 0, sizeof(dev_info)); + ret = get_device_info(fd, i, &dev_info); + + if (ret == -ENODEV) + continue; + if (ret) { + fprintf(stderr, + "ERROR: cannot get info about device devid=%d\n", + i); + free(info); + return ret; + } + + info[ndevs].devid = dev_info.devid; + if (!dev_info.path[0]) { + strcpy(info[ndevs].path, "missing"); + } else { + strcpy(info[ndevs].path, (char *)dev_info.path); + info[ndevs].device_size = + get_partition_size((char *)dev_info.path); + } + info[ndevs].size = dev_info.total_bytes; + ++ndevs; + } + + BUG_ON(ndevs != fi_args.num_devices); + qsort(info, fi_args.num_devices, + sizeof(struct device_info), cmp_device_info); + + *device_info_count = fi_args.num_devices; + *device_info_ptr = info; + + return 0; +} + +int load_chunk_and_device_info(int fd, struct chunk_info **chunkinfo, + int *chunkcount, struct device_info **devinfo, int *devcount) +{ + int ret; + + ret = load_chunk_info(fd, chunkinfo, chunkcount); + if (ret == -EPERM) { + fprintf(stderr, + "WARNING: can't read detailed chunk info, RAID5/6 numbers will be incorrect, run as root\n"); + } else if (ret) { + return ret; + } + + ret = load_device_info(fd, devinfo, devcount); + if (ret == -EPERM) { + fprintf(stderr, + "WARNING: can't get filesystem info from ioctl(FS_INFO), run as root\n"); + ret = 0; + } + + return ret; +} + +/* + * This function computes the size of a chunk in a disk + */ +static u64 calc_chunk_size(struct chunk_info *ci) +{ + if (ci->type & BTRFS_BLOCK_GROUP_RAID0) + return ci->size / ci->num_stripes; + else if (ci->type & BTRFS_BLOCK_GROUP_RAID1) + return ci->size ; + else if (ci->type & BTRFS_BLOCK_GROUP_DUP) + return ci->size ; + else if (ci->type & BTRFS_BLOCK_GROUP_RAID5) + return ci->size / (ci->num_stripes -1); + else if (ci->type & BTRFS_BLOCK_GROUP_RAID6) + return ci->size / (ci->num_stripes -2); + else if (ci->type & BTRFS_BLOCK_GROUP_RAID10) + return ci->size / ci->num_stripes; + return ci->size; +} + +/* + * This function print the results of the command "btrfs fi usage" + * in tabular format + */ +static void _cmd_filesystem_usage_tabular(unsigned unit_mode, + struct btrfs_ioctl_space_args *sargs, + struct chunk_info *chunks_info_ptr, + int chunks_info_count, + struct device_info *device_info_ptr, + int device_info_count) +{ + int i; + u64 total_unused = 0; + struct string_table *matrix = 0; + int ncols, nrows; + + ncols = sargs->total_spaces + 2; + nrows = 2 + 1 + device_info_count + 1 + 2; + + matrix = table_create(ncols, nrows); + if (!matrix) { + fprintf(stderr, "ERROR: not enough memory\n"); + return; + } + + /* header */ + for (i = 0; i < sargs->total_spaces; i++) { + const char *description; + u64 flags = sargs->spaces[i].flags; + + if (flags & BTRFS_SPACE_INFO_GLOBAL_RSV) + continue; + + description = btrfs_group_type_str(flags); + + table_printf(matrix, 1+i, 0, "<%s", description); + } + + for (i = 0; i < sargs->total_spaces; i++) { + const char *r_mode; + + u64 flags = sargs->spaces[i].flags; + r_mode = btrfs_group_profile_str(flags); + + table_printf(matrix, 1+i, 1, "<%s", r_mode); + } + + table_printf(matrix, 1+sargs->total_spaces, 1, "total_spaces ; k++) { + u64 flags = sargs->spaces[k].flags; + u64 devid = device_info_ptr[i].devid; + int j; + u64 size = 0; + + for (j = 0 ; j < chunks_info_count ; j++) { + if (chunks_info_ptr[j].type != flags ) + continue; + if (chunks_info_ptr[j].devid != devid) + continue; + + size += calc_chunk_size(chunks_info_ptr+j); + } + + if (size) + table_printf(matrix, col, i+3, + ">%s", pretty_size_mode(size, unit_mode)); + else + table_printf(matrix, col, i+3, ">-"); + + total_allocated += size; + col++; + } + + unused = get_partition_size(device_info_ptr[i].path) + - total_allocated; + + table_printf(matrix, sargs->total_spaces + 1, i + 3, + ">%s", pretty_size_mode(unused, unit_mode)); + total_unused += unused; + + } + + for (i = 0; i <= sargs->total_spaces; i++) + table_printf(matrix, i + 1, device_info_count + 3, "="); + + /* footer */ + table_printf(matrix, 0, device_info_count + 4, "total_spaces; i++) + table_printf(matrix, 1 + i, device_info_count + 4, ">%s", + pretty_size_mode(sargs->spaces[i].total_bytes, unit_mode)); + + table_printf(matrix, sargs->total_spaces + 1, device_info_count + 4, + ">%s", pretty_size_mode(total_unused, unit_mode)); + + table_printf(matrix, 0, device_info_count + 5, "total_spaces; i++) + table_printf(matrix, 1 + i, device_info_count+5, ">%s", + pretty_size_mode(sargs->spaces[i].used_bytes, unit_mode)); + + table_dump(matrix); + table_free(matrix); +} + +/* + * This function prints the unused space per every disk + */ +static void print_unused(struct chunk_info *info_ptr, + int info_count, + struct device_info *device_info_ptr, + int device_info_count, + unsigned unit_mode) +{ + int i; + for (i = 0; i < device_info_count; i++) { + int j; + u64 total = 0; + + for (j = 0; j < info_count; j++) + if (info_ptr[j].devid == device_info_ptr[i].devid) + total += calc_chunk_size(info_ptr+j); + + printf(" %s\t%10s\n", + device_info_ptr[i].path, + pretty_size_mode(device_info_ptr[i].size - total, + unit_mode)); + } +} + +/* + * This function prints the allocated chunk per every disk + */ +static void print_chunk_device(u64 chunk_type, + struct chunk_info *chunks_info_ptr, + int chunks_info_count, + struct device_info *device_info_ptr, + int device_info_count, + unsigned unit_mode) +{ + int i; + + for (i = 0; i < device_info_count; i++) { + int j; + u64 total = 0; + + for (j = 0; j < chunks_info_count; j++) { + + if (chunks_info_ptr[j].type != chunk_type) + continue; + if (chunks_info_ptr[j].devid != device_info_ptr[i].devid) + continue; + + total += calc_chunk_size(&(chunks_info_ptr[j])); + //total += chunks_info_ptr[j].size; + } + + if (total > 0) + printf(" %s\t%10s\n", + device_info_ptr[i].path, + pretty_size_mode(total, unit_mode)); + } +} + +/* + * This function print the results of the command "btrfs fi usage" + * in linear format + */ +static void _cmd_filesystem_usage_linear(unsigned unit_mode, + struct btrfs_ioctl_space_args *sargs, + struct chunk_info *info_ptr, + int info_count, + struct device_info *device_info_ptr, + int device_info_count) +{ + int i; + + for (i = 0; i < sargs->total_spaces; i++) { + const char *description; + const char *r_mode; + u64 flags = sargs->spaces[i].flags; + + if (flags & BTRFS_SPACE_INFO_GLOBAL_RSV) + continue; + + description = btrfs_group_type_str(flags); + r_mode = btrfs_group_profile_str(flags); + + printf("%s,%s: Size:%s, ", + description, + r_mode, + pretty_size_mode(sargs->spaces[i].total_bytes, + unit_mode)); + printf("Used:%s\n", + pretty_size_mode(sargs->spaces[i].used_bytes, unit_mode)); + print_chunk_device(flags, info_ptr, info_count, + device_info_ptr, device_info_count, unit_mode); + printf("\n"); + } + + printf("Unallocated:\n"); + print_unused(info_ptr, info_count, device_info_ptr, device_info_count, + unit_mode); +} + +static int print_filesystem_usage_by_chunk(int fd, + struct chunk_info *chunkinfo, int chunkcount, + struct device_info *devinfo, int devcount, + char *path, unsigned unit_mode, int tabular) +{ + struct btrfs_ioctl_space_args *sargs; + int ret = 0; + + if (!chunkinfo) + return 0; + + sargs = load_space_info(fd, path); + if (!sargs) { + ret = 1; + goto out; + } + + if (tabular) + _cmd_filesystem_usage_tabular(unit_mode, sargs, chunkinfo, + chunkcount, devinfo, devcount); + else + _cmd_filesystem_usage_linear(unit_mode, sargs, chunkinfo, + chunkcount, devinfo, devcount); + + free(sargs); +out: + return ret; +} + +const char * const cmd_filesystem_usage_usage[] = { + "btrfs filesystem usage [options] [..]", + "Show detailed information about internal filesystem usage .", + "-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", + "-T show data in tabular format", + NULL +}; + +int cmd_filesystem_usage(int argc, char **argv) +{ + unsigned unit_mode = UNITS_DEFAULT; + int ret = 0; + int i, more_than_one = 0; + int tabular = 0; + + optind = 1; + 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 } + }; + + c = getopt_long(argc, argv, "bhHkmgtT", 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; + case 'T': + tabular = 1; + break; + default: + usage(cmd_filesystem_usage_usage); + } + } + + if (check_argc_min(argc - optind, 1)) + usage(cmd_filesystem_usage_usage); + + for (i = optind; i < argc; i++) { + int fd; + DIR *dirstream = NULL; + struct chunk_info *chunkinfo = NULL; + struct device_info *devinfo = NULL; + int chunkcount = 0; + int devcount = 0; + + fd = open_file_or_dir(argv[i], &dirstream); + if (fd < 0) { + fprintf(stderr, "ERROR: can't access '%s'\n", + argv[i]); + ret = 1; + goto out; + } + if (more_than_one) + printf("\n"); + + ret = load_chunk_and_device_info(fd, &chunkinfo, &chunkcount, + &devinfo, &devcount); + if (ret) + goto cleanup; + + ret = print_filesystem_usage_overall(fd, chunkinfo, chunkcount, + devinfo, devcount, argv[i], unit_mode); + if (ret) + goto cleanup; + printf("\n"); + ret = print_filesystem_usage_by_chunk(fd, chunkinfo, chunkcount, + devinfo, devcount, argv[i], unit_mode, tabular); +cleanup: + close_file_or_dir(fd, dirstream); + free(chunkinfo); + free(devinfo); + + if (ret) + goto out; + more_than_one = 1; + } + +out: + return !!ret; +} + +void print_device_chunks(int fd, struct device_info *devinfo, + struct chunk_info *chunks_info_ptr, + int chunks_info_count, unsigned unit_mode) +{ + int i; + u64 allocated = 0; + + for (i = 0 ; i < chunks_info_count ; i++) { + const char *description; + const char *r_mode; + u64 flags; + u64 size; + + if (chunks_info_ptr[i].devid != devinfo->devid) + continue; + + flags = chunks_info_ptr[i].type; + + description = btrfs_group_type_str(flags); + r_mode = btrfs_group_profile_str(flags); + size = calc_chunk_size(chunks_info_ptr+i); + printf(" %s,%s:%*s%10s\n", + description, + r_mode, + (int)(20 - strlen(description) - strlen(r_mode)), "", + pretty_size_mode(size, unit_mode)); + + allocated += size; + + } + printf(" Unallocated: %*s%10s\n", + (int)(20 - strlen("Unallocated")), "", + pretty_size_mode(devinfo->size - allocated, unit_mode)); +} + +void print_device_sizes(int fd, struct device_info *devinfo, unsigned unit_mode) +{ + printf(" Device size: %*s%10s\n", + (int)(20 - strlen("Device size")), "", + pretty_size_mode(devinfo->device_size, unit_mode)); +#if 0 + /* + * The term has not seen an agreement and we don't want to change it + * once it's in non-development branches or even released. + */ + printf(" FS occupied: %*s%10s\n", + (int)(20 - strlen("FS occupied")), "", + pretty_size_mode(devinfo->size, unit_mode)); +#endif +} -- cgit v1.2.3