/* * 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 "kerncompat.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kernel-lib/sizes.h" #include "kernel-shared/ctree.h" #include "kernel-shared/disk-io.h" #include "kernel-shared/volumes.h" #include "kernel-shared/accessors.h" #include "kernel-shared/uapi/btrfs_tree.h" #include "common/defs.h" #include "common/utils.h" #include "common/string-table.h" #include "common/open-utils.h" #include "common/units.h" #include "common/help.h" #include "common/device-utils.h" #include "common/sysfs-utils.h" #include "common/messages.h" #include "common/path-utils.h" #include "cmds/filesystem-usage.h" #include "cmds/commands.h" /* * Add the chunk info to the chunk_info list */ static int add_info_to_list(struct array *chunkinfos, 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 = NULL; struct btrfs_stripe *stripe; u64 devid; stripe = btrfs_stripe_nr(chunk, j); devid = btrfs_stack_stripe_devid(stripe); for (i = 0; i < chunkinfos->length; i++) { struct chunk_info *cinfo = chunkinfos->data[i]; if (cinfo->type == type && cinfo->devid == devid && cinfo->num_stripes == num_stripes ) { p = cinfo; break; } } if (!p) { int ret; p = calloc(1, sizeof(struct chunk_info)); if (!p) { error_msg(ERROR_MSG_MEMORY, NULL); return -ENOMEM; } p->devid = devid; p->type = type; p->size = 0; p->num_stripes = num_stripes; ret = array_append(chunkinfos, p); if (ret < 0) { error_msg(ERROR_MSG_MEMORY, NULL); return -ENOMEM; } } 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) { const struct chunk_info * const *pa = a; const struct chunk_info * const *pb = b; return cmp_chunk_block_group((*pa)->type, (*pb)->type); } static int load_chunk_info(int fd, struct array *chunkinfos) { 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; 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); if (ret < 0) { if (errno == EPERM) return -errno; error("cannot look up chunk tree info: %m"); 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(chunkinfos, item); if (ret) return 1; off += btrfs_search_header_len(sh); sk->min_objectid = btrfs_search_header_objectid(sh); sk->min_type = btrfs_search_header_type(sh); sk->min_offset = btrfs_search_header_offset(sh)+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(chunkinfos->data, chunkinfos->length, 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, const char *path) { struct btrfs_ioctl_space_args *sargs = NULL, *sargs_orig = NULL; int ret, count; sargs_orig = sargs = calloc(1, sizeof(struct btrfs_ioctl_space_args)); if (!sargs) { error_msg(ERROR_MSG_MEMORY, NULL); return NULL; } sargs->space_slots = 0; sargs->total_spaces = 0; ret = ioctl(fd, BTRFS_IOC_SPACE_INFO, sargs); if (ret < 0) { error("cannot get space info on '%s': %m", path); free(sargs); return NULL; } if (!sargs->total_spaces) { free(sargs); pr_verbose(LOG_DEFAULT, "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); error_msg(ERROR_MSG_MEMORY, NULL); return NULL; } sargs->space_slots = count; sargs->total_spaces = 0; ret = ioctl(fd, BTRFS_IOC_SPACE_INFO, sargs); if (ret < 0) { error("cannot get space info with %u slots: %m", count); free(sargs); return NULL; } qsort(&(sargs->spaces), count, sizeof(struct btrfs_ioctl_space_info), cmp_btrfs_ioctl_space_info); return sargs; } /* * Compute the ratio between logical space used over logical space allocated * by profile basis */ static void get_raid56_logical_ratio(struct btrfs_ioctl_space_args *sargs, u64 type, double *data_ratio, double *metadata_ratio, double *system_ratio) { u64 l_data_chunk = 0, l_data_used = 0; u64 l_metadata_chunk = 0, l_metadata_used = 0; u64 l_system_chunk = 0, l_system_used = 0; int i; for (i = 0; i < sargs->total_spaces; i++) { u64 flags = sargs->spaces[i].flags; if (!(flags & type)) continue; if (flags & BTRFS_BLOCK_GROUP_DATA) { l_data_used += sargs->spaces[i].used_bytes; l_data_chunk += sargs->spaces[i].total_bytes; } else if (flags & BTRFS_BLOCK_GROUP_METADATA) { l_metadata_used += sargs->spaces[i].used_bytes; l_metadata_chunk += sargs->spaces[i].total_bytes; } else if (flags & BTRFS_BLOCK_GROUP_SYSTEM) { l_system_used += sargs->spaces[i].used_bytes; l_system_chunk += sargs->spaces[i].total_bytes; } } *data_ratio = -1.0; *metadata_ratio = -1.0; *system_ratio = -1.0; if (l_data_chunk) *data_ratio = (double)l_data_used / l_data_chunk; if (l_metadata_chunk) *metadata_ratio = (double)l_metadata_used / l_metadata_chunk; if (l_system_chunk) *system_ratio = (double)l_system_used / l_system_chunk; } /* * Compute the "raw" space allocated for a chunk (r_*_chunks) * and the "raw" space used by a chunk (r_*_used) */ static void get_raid56_space_info(struct btrfs_ioctl_space_args *sargs, const struct array *chunkinfos, double *max_data_ratio, u64 *r_data_chunks, u64 *r_data_used, u64 *r_metadata_chunks, u64 *r_metadata_used, u64 *r_system_chunks, u64 *r_system_used) { double l_data_ratio_r5, l_metadata_ratio_r5, l_system_ratio_r5; double l_data_ratio_r6, l_metadata_ratio_r6, l_system_ratio_r6; get_raid56_logical_ratio(sargs, BTRFS_BLOCK_GROUP_RAID5, &l_data_ratio_r5, &l_metadata_ratio_r5, &l_system_ratio_r5); get_raid56_logical_ratio(sargs, BTRFS_BLOCK_GROUP_RAID6, &l_data_ratio_r6, &l_metadata_ratio_r6, &l_system_ratio_r6); for (int i = 0; i < chunkinfos->length; i++) { const struct chunk_info *info_ptr = chunkinfos->data[i]; int parities_count; u64 size; double l_data_ratio, l_metadata_ratio, l_system_ratio, rt; parities_count = btrfs_bg_type_to_nparity(info_ptr->type); if (info_ptr->type & BTRFS_BLOCK_GROUP_RAID5) { l_data_ratio = l_data_ratio_r5; l_metadata_ratio = l_metadata_ratio_r5; l_system_ratio = l_system_ratio_r5; } else if (info_ptr->type & BTRFS_BLOCK_GROUP_RAID6) { l_data_ratio = l_data_ratio_r6; l_metadata_ratio = l_metadata_ratio_r6; l_system_ratio = l_system_ratio_r6; } else { continue; } rt = (double)info_ptr->num_stripes / (info_ptr->num_stripes - parities_count); if (rt > *max_data_ratio) *max_data_ratio = rt; /* * size is the total disk(s) space occupied by a chunk * the product of 'size' and '*_ratio' is "in average" * the disk(s) space used by the data */ size = info_ptr->size / (info_ptr->num_stripes - parities_count); if (info_ptr->type & BTRFS_BLOCK_GROUP_DATA) { UASSERT(l_data_ratio >= 0); *r_data_chunks += size; *r_data_used += size * l_data_ratio; } else if (info_ptr->type & BTRFS_BLOCK_GROUP_METADATA) { UASSERT(l_metadata_ratio >= 0); *r_metadata_chunks += size; *r_metadata_used += size * l_metadata_ratio; } else if (info_ptr->type & BTRFS_BLOCK_GROUP_SYSTEM) { UASSERT(l_system_ratio >= 0); *r_system_chunks += size; *r_system_used += size * l_system_ratio; } } } static u64 get_first_device_zone_size(int fd) { int dirfd; DIR *dir; struct dirent *de; char name[NAME_MAX] = {0}; u64 ret; dirfd = sysfs_open_fsid_dir(fd, "devices"); if (dirfd < 0) return 0; dir = fdopendir(dirfd); if (!dir) { ret = 0; goto out; } while (1) { de = readdir(dir); if (strcmp(".", de->d_name) == 0 || strcmp("..", de->d_name) == 0) continue; strcpy(name, de->d_name); name[NAME_MAX - 1] = 0; break; } ret = device_get_zone_size(fd, name); ret *= 512; out: closedir(dir); return ret; } static u64 calc_slack_size(const struct device_info *devinfo) { if (devinfo->device_size > 0) return devinfo->device_size - devinfo->size; return 0; } #define MIN_UNALOCATED_THRESH SZ_16M static int print_filesystem_usage_overall(int fd, const struct array *chunkinfos, const struct array *devinfos, const char *path, unsigned unit_mode) { struct btrfs_ioctl_space_args *sargs = NULL; char *tmp; int i; int ret = 0; int width = 10; /* default 10 for human units */ /* * r_* prefix is for raw data * l_* prefix is for logical * *_used suffix is for space used for data or metadata * *_chunks suffix is for total space used by the chunk */ 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_total_slack = 0; 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 l_global_reserve = 0; u64 l_global_reserve_used = 0; u64 free_estimated = 0; u64 free_min = 0; u64 zone_unusable = 0; double max_data_ratio = 1.0; bool mixed = false; struct statvfs statvfs_buf; struct btrfs_ioctl_feature_flags feature_flags; bool raid56 = false; bool unreliable_allocated = false; sargs = load_space_info(fd, path); if (!sargs) { ret = 1; goto exit; } r_total_size = 0; for (i = 0; i < devinfos->length; i++) { const struct device_info *devinfo = devinfos->data[i]; r_total_size += devinfo->size; r_total_slack += calc_slack_size(devinfo); if (!devinfo->device_size) r_total_missing += devinfo->size; } if (r_total_size == 0) { error("cannot get space info on '%s': %m", path); ret = 1; goto exit; } get_raid56_space_info(sargs, chunkinfos, &max_data_ratio, &r_data_chunks, &r_data_used, &r_metadata_chunks, &r_metadata_used, &r_system_chunks, &r_system_used); for (i = 0; i < sargs->total_spaces; i++) { int ratio; u64 flags = sargs->spaces[i].flags; ratio = btrfs_bg_type_to_ncopies(flags); /* * The RAID5/6 ratio depends on the number of stripes and is * computed separately. Setting ratio to 0 will not account * the chunks in this loop. */ if (flags & BTRFS_BLOCK_GROUP_RAID56_MASK) { ratio = 0; raid56 = true; } 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)) { mixed = true; } else { /* * As mixed mode is not supported in zoned mode, this * will account for all profile types */ u64 unusable; unusable = device_get_zone_unusable(fd, flags); if (unusable != DEVICE_ZONE_UNUSABLE_UNKNOWN) zone_unusable += unusable; } 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_system_chunks; r_total_used = r_data_used + r_system_used; if (!mixed) { r_total_chunks += r_metadata_chunks; r_total_used += r_metadata_used; } r_total_unused = r_total_size - r_total_chunks; /* Raw / Logical = raid factor, >= 1 */ data_ratio = (double)r_data_chunks / l_data_chunks; if (mixed) metadata_ratio = data_ratio; else metadata_ratio = (double)r_metadata_chunks / l_metadata_chunks; /* * 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; /* * For mixed-bg the metadata are left out in calculations thus global * reserve would be lost. Part of it could be permanently allocated, * we have to subtract the used bytes so we don't go under zero free. */ if (mixed) free_estimated -= l_global_reserve - l_global_reserve_used; 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; ret = statvfs(path, &statvfs_buf); if (ret) { warning("cannot get space info with statvfs() on '%s': %m", path); memset(&statvfs_buf, 0, sizeof(statvfs_buf)); ret = 0; } /* * If we don't have any chunk information (e.g. due to missing * privileges) and there's a raid56 profile, the computation of * "unallocated", "data/metadata ratio", "free estimated" are wrong. */ unreliable_allocated = (raid56 && chunkinfos->length == 0); if (unreliable_allocated) { warning("radid56 found, we cannots compute some values, run as root if needed"); ret = 1; goto exit; } pr_verbose(LOG_DEFAULT, "Overall:\n"); pr_verbose(LOG_DEFAULT, " Device size:\t\t%*s\n", width, pretty_size_mode(r_total_size, unit_mode)); pr_verbose(LOG_DEFAULT, " Device allocated:\t\t%*s\n", width, pretty_size_mode(r_total_chunks, unit_mode)); pr_verbose(LOG_DEFAULT, " Device unallocated:\t\t%*s\n", width, pretty_size_mode(r_total_unused, unit_mode | UNITS_NEGATIVE)); pr_verbose(LOG_DEFAULT, " Device missing:\t\t%*s\n", width, pretty_size_mode(r_total_missing, unit_mode)); pr_verbose(LOG_DEFAULT, " Device slack:\t\t%*s\n", width, pretty_size_mode(r_total_slack, unit_mode)); ret = ioctl(fd, BTRFS_IOC_GET_FEATURES, &feature_flags); if (ret == 0 && (feature_flags.incompat_flags & BTRFS_FEATURE_INCOMPAT_ZONED)) { u64 zone_size; pr_verbose(LOG_DEFAULT, " Device zone unusable:\t%*s\n", width, pretty_size_mode(zone_unusable, unit_mode)); zone_size = get_first_device_zone_size(fd); pr_verbose(LOG_DEFAULT, " Device zone size:\t\t%*s\n", width, pretty_size_mode(zone_size, unit_mode)); } pr_verbose(LOG_DEFAULT, " Used:\t\t\t%*s\n", width, pretty_size_mode(r_total_used, unit_mode)); pr_verbose(LOG_DEFAULT, " Free (estimated):\t\t%*s\t(", width, pretty_size_mode(free_estimated, unit_mode)); pr_verbose(LOG_DEFAULT, "min: %s)\n", pretty_size_mode(free_min, unit_mode)); pr_verbose(LOG_DEFAULT, " Free (statfs, df):\t\t%*s\n", width, pretty_size_mode(statvfs_buf.f_bavail * statvfs_buf.f_bsize, unit_mode)); pr_verbose(LOG_DEFAULT, " Data ratio:\t\t\t%*.2f\n", width, data_ratio); pr_verbose(LOG_DEFAULT, " Metadata ratio:\t\t%*.2f\n", width, metadata_ratio); pr_verbose(LOG_DEFAULT, " 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)); tmp = btrfs_test_for_multiple_profiles(fd); if (tmp[0]) pr_verbose(LOG_DEFAULT, " Multiple profiles:\t\t%*s\t(%s)\n", width, "yes", tmp); else pr_verbose(LOG_DEFAULT, " Multiple profiles:\t\t%*s\n", width, "no"); free(tmp); 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) { const struct device_info * const *deva = a; const struct device_info * const *devb = b; if ((*deva)->devid < (*devb)->devid) return -1; if ((*deva)->devid > (*devb)->devid) return 1; return 0; } int dev_to_fsid(const char *dev, u8 *fsid) { struct btrfs_super_block disk_super; int ret; int fd; fd = open(dev, O_RDONLY); if (fd < 0) { ret = -errno; return ret; } ret = btrfs_read_dev_super(fd, &disk_super, BTRFS_SUPER_INFO_OFFSET, SBREAD_DEFAULT); if (ret) goto out; memcpy(fsid, disk_super.fsid, BTRFS_FSID_SIZE); ret = 0; out: close(fd); return ret; } static int device_is_seed(int fd, const char *dev_path, u64 devid, const u8 *mnt_fsid) { char fsid_str[BTRFS_UUID_UNPARSED_SIZE]; char fsid_path[PATH_MAX]; char devid_str[20]; u8 fsid[BTRFS_UUID_SIZE]; int ret = -1; int sysfs_fd; snprintf(devid_str, 20, "%llu", devid); /* devinfo//fsid */ ret = path_cat3_out(fsid_path, "devinfo", devid_str, "fsid"); if (ret < 0) return ret; /* /sys/fs/btrfs//devinfo//fsid */ sysfs_fd = sysfs_open_fsid_file(fd, fsid_path); if (sysfs_fd >= 0) { sysfs_read_file(sysfs_fd, fsid_str, BTRFS_UUID_UNPARSED_SIZE); fsid_str[BTRFS_UUID_UNPARSED_SIZE - 1] = 0; ret = uuid_parse(fsid_str, fsid); close(sysfs_fd); } if (ret || sysfs_fd < 0) { ret = dev_to_fsid(dev_path, fsid); if (ret) return ret; } if (memcmp(mnt_fsid, fsid, BTRFS_FSID_SIZE) != 0) return 0; return -1; } /* * This function loads the device_info structure and put them in an array */ static int load_device_info(int fd, struct array *devinfos) { int ret, i, ndevs; struct btrfs_ioctl_fs_info_args fi_args; struct btrfs_ioctl_dev_info_args dev_info; struct device_info *info; ret = ioctl(fd, BTRFS_IOC_FS_INFO, &fi_args); if (ret < 0) { if (errno == EPERM) return -errno; error("cannot get filesystem info: %m"); return 1; } for (i = 0, ndevs = 0 ; i <= fi_args.max_id ; i++) { if (ndevs >= fi_args.num_devices) { error("unexpected number of devices: %d >= %llu", ndevs, fi_args.num_devices); error( "if seed device is used, try running this command as root"); goto out; } memset(&dev_info, 0, sizeof(dev_info)); ret = device_get_info(fd, i, &dev_info); if (ret == -ENODEV) continue; if (ret) { error("cannot get info about device devid=%d", i); goto out; } /* * Skip seed device by checking device's fsid (requires root if * kernel is not patched to provide fsid from the sysfs). * And we will skip only if device_is_seed is successful and dev * is a seed device. * Ignore any other error including -EACCES, which is seen when * a non-root process calls dev_to_fsid(path)->open(path). */ ret = device_is_seed(fd, (const char *)dev_info.path, i, fi_args.fsid); if (!ret) continue; info = calloc(1, sizeof(struct device_info)); if (!info) { error_msg(ERROR_MSG_MEMORY, NULL); return 1; } ret = array_append(devinfos, info); if (ret < 0) { error_msg(ERROR_MSG_MEMORY, NULL); return -ENOMEM; } info->devid = dev_info.devid; if (!dev_info.path[0]) { strcpy(info->path, "missing"); } else { strcpy(info->path, (char *)dev_info.path); info->device_size = device_get_partition_size((const char *)dev_info.path); } info->size = dev_info.total_bytes; ndevs++; } if (ndevs != fi_args.num_devices) { error("unexpected number of devices: %d != %llu", ndevs, fi_args.num_devices); goto out; } qsort(devinfos->data, devinfos->length, sizeof(struct device_info *), cmp_device_info); return 0; out: return ret; } int load_chunk_and_device_info(int fd, struct array *chunkinfos, struct array *devinfos) { int ret; ret = load_chunk_info(fd, chunkinfos); if (ret == -EPERM) { warning( "cannot read detailed chunk info, per-device usage will not be shown, run as root"); } else if (ret) { return ret; } ret = load_device_info(fd, devinfos); if (ret == -EPERM) { warning( "cannot get filesystem info from ioctl(FS_INFO), run as root"); ret = 0; } return ret; } /* * This function computes the size of a chunk in a disk */ static u64 calc_chunk_size(const struct chunk_info *ci) { u32 div = 1; /* * The formula doesn't work for RAID1/DUP types, we should just return the * chunk size */ if (!(ci->type & (BTRFS_BLOCK_GROUP_RAID1_MASK|BTRFS_BLOCK_GROUP_DUP))) { /* No parity + sub_stripes, so order of "-" and "/" does not matter */ div = (ci->num_stripes - btrfs_bg_type_to_nparity(ci->type)) / btrfs_bg_type_to_sub_stripes(ci->type); } return ci->size / div; } /* * 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, const struct array *chunkinfos, const struct array *devinfos) { int i; int devcount = devinfos->length; u64 total_unused = 0; u64 total_total = 0; u64 total_slack = 0; struct string_table *matrix = NULL; int ncols, nrows; int col; int unallocated_col; int spaceinfos_col; int total_col; int slack_col; u64 slack; const int vhdr_skip = 3; /* amount of vertical header space */ /* id, path, unallocated, total, slack */ ncols = 5; spaceinfos_col = 2; /* Properly count the real space infos */ for (i = 0; i < sargs->total_spaces; i++) { if (sargs->spaces[i].flags & BTRFS_SPACE_INFO_GLOBAL_RSV) continue; ncols++; } /* 2 for header, empty line, devices, ===, total, used */ nrows = vhdr_skip + devcount + 1 + 2; matrix = table_create(ncols, nrows); if (!matrix) { error_msg(ERROR_MSG_MEMORY, NULL); return; } /* * We have to skip the global block reserve everywhere as it's an * artificial blockgroup */ /* header */ for (i = 0, col = spaceinfos_col; i < sargs->total_spaces; i++) { u64 flags = sargs->spaces[i].flags; if (flags & BTRFS_SPACE_INFO_GLOBAL_RSV) continue; table_printf(matrix, col, 0, "<%s", btrfs_group_type_str(flags)); table_printf(matrix, col, 1, "<%s", btrfs_group_profile_str(flags)); col++; } unallocated_col = col++; total_col = col++; slack_col = col++; table_printf(matrix, 0, 1, "data[i]; u64 total_allocated = 0, unused; p = strrchr(devinfo->path, '/'); if (!p) p = devinfo->path; else p++; table_printf(matrix, 0, vhdr_skip + i, ">%llu", devinfo->devid); table_printf(matrix, 1, vhdr_skip + i, "<%s", devinfo->path); for (col = spaceinfos_col, k = 0; k < sargs->total_spaces; k++) { u64 flags = sargs->spaces[k].flags; u64 devid = devinfo->devid; u64 size = 0; if (flags & BTRFS_SPACE_INFO_GLOBAL_RSV) continue; for (int j = 0; j < chunkinfos->length; j++) { const struct chunk_info *chunk = chunkinfos->data[j]; if (chunk->type != flags) continue; if (chunk->devid != devid) continue; size += calc_chunk_size(chunk); } if (size) table_printf(matrix, col, vhdr_skip+ i, ">%s", pretty_size_mode(size, unit_mode)); else table_printf(matrix, col, vhdr_skip + i, ">-"); total_allocated += size; col++; } unused = device_get_partition_size(devinfo->path) - total_allocated; unused = devinfo->size - total_allocated; table_printf(matrix, unallocated_col, vhdr_skip + i, ">%s", pretty_size_mode(unused, unit_mode | UNITS_NEGATIVE)); table_printf(matrix, total_col, vhdr_skip + i, ">%s", pretty_size_mode(devinfo->size, unit_mode | UNITS_NEGATIVE)); slack = calc_slack_size(devinfo); if (slack > 0) { table_printf(matrix, slack_col, vhdr_skip + i, ">%s", pretty_size_mode(slack, unit_mode | UNITS_NEGATIVE)); } else { table_printf(matrix, slack_col, vhdr_skip + i, ">-"); } total_unused += unused; total_slack += slack; total_total += devinfo->size; } for (i = 0; i < spaceinfos_col; i++) { table_printf(matrix, i, vhdr_skip - 1, "*-"); table_printf(matrix, i, vhdr_skip + devcount, "*-"); } for (i = 0, col = spaceinfos_col; i < sargs->total_spaces; i++) { if (sargs->spaces[i].flags & BTRFS_SPACE_INFO_GLOBAL_RSV) continue; table_printf(matrix, col, vhdr_skip - 1, "*-"); table_printf(matrix, col, vhdr_skip + devcount, "*-"); col++; } /* Line under Unallocated, Total, Slack */ table_printf(matrix, col, vhdr_skip - 1, "*-"); table_printf(matrix, col, vhdr_skip + devcount, "*-"); table_printf(matrix, col + 1, vhdr_skip - 1, "*-"); table_printf(matrix, col + 1, vhdr_skip + devcount, "*-"); table_printf(matrix, col + 2, vhdr_skip - 1, "*-"); table_printf(matrix, col + 2, vhdr_skip + devcount, "*-"); /* footer */ table_printf(matrix, 1, vhdr_skip + devcount + 1, "total_spaces; i++) { if (sargs->spaces[i].flags & BTRFS_SPACE_INFO_GLOBAL_RSV) continue; table_printf(matrix, col++, vhdr_skip + devcount + 1, ">%s", pretty_size_mode(sargs->spaces[i].total_bytes, unit_mode)); } table_printf(matrix, unallocated_col, vhdr_skip + devcount + 1, ">%s", pretty_size_mode(total_unused, unit_mode | UNITS_NEGATIVE)); table_printf(matrix, total_col, vhdr_skip + devcount + 1, ">%s", pretty_size_mode(total_total, unit_mode | UNITS_NEGATIVE)); table_printf(matrix, slack_col, vhdr_skip + devcount + 1, ">%s", pretty_size_mode(total_slack, unit_mode | UNITS_NEGATIVE)); table_printf(matrix, 1, vhdr_skip + devcount + 2, "total_spaces; i++) { if (sargs->spaces[i].flags & BTRFS_SPACE_INFO_GLOBAL_RSV) continue; table_printf(matrix, col++, vhdr_skip + devcount + 2, ">%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(const struct array *chunkinfos, const struct array *devinfos, unsigned unit_mode) { for (int i = 0; i < devinfos->length; i++) { u64 total = 0; const struct device_info *devinfo = devinfos->data[i]; for (int j = 0; j < chunkinfos->length; j++) { const struct chunk_info *chunk = chunkinfos->data[j]; if (chunk->devid == devinfo->devid) total += calc_chunk_size(chunk); } pr_verbose(LOG_DEFAULT, " %s\t%10s\n", devinfo->path, pretty_size_mode(devinfo->size - total, unit_mode)); } } /* * This function prints the allocated chunk per every disk */ static void print_chunk_device(u64 chunk_type, const struct array *chunkinfos, const struct array *devinfos, unsigned unit_mode) { for (int i = 0; i < devinfos->length; i++) { const struct device_info *devinfo = devinfos->data[i]; u64 total = 0; for (int j = 0; j < chunkinfos->length; j++) { const struct chunk_info *chunk = chunkinfos->data[j]; if (chunk->type != chunk_type) continue; if (chunk->devid != devinfo->devid) continue; total += calc_chunk_size(chunk); } if (total > 0) pr_verbose(LOG_DEFAULT, " %s\t%10s\n", devinfo->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, const struct array *chunkinfos, const struct array *devinfos) { 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); pr_verbose(LOG_DEFAULT, "%s,%s: Size:%s, ", description, r_mode, pretty_size_mode(sargs->spaces[i].total_bytes, unit_mode)); pr_verbose(LOG_DEFAULT, "Used:%s (%.2f%%)\n", pretty_size_mode(sargs->spaces[i].used_bytes, unit_mode), 100.0f * sargs->spaces[i].used_bytes / (sargs->spaces[i].total_bytes + 1)); print_chunk_device(flags, chunkinfos, devinfos, unit_mode); pr_verbose(LOG_DEFAULT, "\n"); } if (chunkinfos->length > 0) { pr_verbose(LOG_DEFAULT, "Unallocated:\n"); print_unused(chunkinfos, devinfos, unit_mode | UNITS_NEGATIVE); } } static int print_filesystem_usage_by_chunk(int fd, const struct array *chunkinfos, const struct array *devinfos, const char *path, unsigned unit_mode, int tabular) { struct btrfs_ioctl_space_args *sargs; int ret = 0; sargs = load_space_info(fd, path); if (!sargs) { ret = 1; goto out; } if (tabular) _cmd_filesystem_usage_tabular(unit_mode, sargs, chunkinfos, devinfos); else _cmd_filesystem_usage_linear(unit_mode, sargs, chunkinfos, devinfos); free(sargs); out: return ret; } static const char * const cmd_filesystem_usage_usage[] = { "btrfs filesystem usage [options] [..]", "Show detailed information about internal filesystem usage .", "", HELPINFO_UNITS_SHORT_LONG, OPTLINE("-T", "show data in tabular format"), NULL }; static int cmd_filesystem_usage(const struct cmd_struct *cmd, int argc, char **argv) { int ret = 0; unsigned unit_mode; int i; int more_than_one = 0; int tabular = 0; unit_mode = get_unit_mode_from_arg(&argc, argv, 1); optind = 0; while (1) { int c; c = getopt(argc, argv, "T"); if (c < 0) break; switch (c) { case 'T': tabular = 1; break; default: usage_unknown_option(cmd, argv); } } if (check_argc_min(argc - optind, 1)) return 1; for (i = optind; i < argc; i++) { int fd; DIR *dirstream = NULL; struct array chunkinfos = { 0 }; struct array devinfos = { 0 }; fd = btrfs_open_dir(argv[i], &dirstream, 1); if (fd < 0) { ret = 1; goto out; } if (more_than_one) pr_verbose(LOG_DEFAULT, "\n"); ret = load_chunk_and_device_info(fd, &chunkinfos, &devinfos); if (ret) goto cleanup; ret = print_filesystem_usage_overall(fd, &chunkinfos, &devinfos, argv[i], unit_mode); if (ret) goto cleanup; pr_verbose(LOG_DEFAULT, "\n"); ret = print_filesystem_usage_by_chunk(fd, &chunkinfos, &devinfos, argv[i], unit_mode, tabular); cleanup: close_file_or_dir(fd, dirstream); array_free_elements(&chunkinfos); array_free(&chunkinfos); array_free_elements(&devinfos); array_free(&devinfos); if (ret) goto out; more_than_one = 1; } out: return !!ret; } DEFINE_SIMPLE_COMMAND(filesystem_usage, "usage"); void print_device_chunks(const struct device_info *devinfo, const struct array *chunkinfos, unsigned unit_mode) { int i; u64 allocated = 0; for (i = 0; i < chunkinfos->length; i++) { const char *description; const char *r_mode; const struct chunk_info *chunk_info; u64 flags; u64 size; u64 num_stripes; u64 profile; chunk_info = chunkinfos->data[i]; if (chunk_info->devid != devinfo->devid) continue; flags = chunk_info->type; profile = (flags & BTRFS_BLOCK_GROUP_PROFILE_MASK); description = btrfs_group_type_str(flags); r_mode = btrfs_group_profile_str(flags); size = calc_chunk_size(chunk_info); num_stripes = chunk_info->num_stripes; if (btrfs_bg_type_is_stripey(profile)) { pr_verbose(LOG_DEFAULT, " %s,%s/%llu:%*s%10s\n", description, r_mode, num_stripes, (int)(20 - strlen(description) - strlen(r_mode) - count_digits(num_stripes) - 1), "", pretty_size_mode(size, unit_mode)); } else { pr_verbose(LOG_DEFAULT, " %s,%s:%*s%10s\n", description, r_mode, (int)(20 - strlen(description) - strlen(r_mode)), "", pretty_size_mode(size, unit_mode)); } allocated += size; } /* * If chunkinfos is empty, we cannot compute the unallocated size, so * don't print incorrect data. */ if (chunkinfos->length == 0) pr_verbose(LOG_DEFAULT, " Unallocated: %*s%10s\n", (int)(20 - strlen("Unallocated")), "", "N/A"); else pr_verbose(LOG_DEFAULT, " Unallocated: %*s%10s\n", (int)(20 - strlen("Unallocated")), "", pretty_size_mode(devinfo->size - allocated, unit_mode | UNITS_NEGATIVE)); } void print_device_sizes(const struct device_info *devinfo, unsigned unit_mode) { pr_verbose(LOG_DEFAULT, " Device size: %*s%10s\n", (int)(20 - strlen("Device size")), "", pretty_size_mode(devinfo->device_size, unit_mode)); pr_verbose(LOG_DEFAULT, " Device slack: %*s%10s\n", (int)(20 - strlen("Device slack")), "", pretty_size_mode(calc_slack_size(devinfo), unit_mode)); }