| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (c) 2000-2001,2005 Silicon Graphics, Inc. |
| * Copyright (c) 2012 Red Hat, Inc. |
| * Copyright (c) 2017 Oracle. |
| * All Rights Reserved. |
| */ |
| |
| #include "libxfs.h" |
| #include <linux/fiemap.h> |
| #include <linux/fsmap.h> |
| #include "libfrog/fsgeom.h" |
| #include "command.h" |
| #include "init.h" |
| #include "libfrog/paths.h" |
| #include "space.h" |
| #include "input.h" |
| |
| struct histent |
| { |
| long long low; |
| long long high; |
| long long count; |
| long long blocks; |
| }; |
| |
| static int agcount; |
| static xfs_agnumber_t *aglist; |
| static struct histent *hist; |
| static int dumpflag; |
| static long long equalsize; |
| static long long multsize; |
| static int histcount; |
| static int seen1; |
| static int summaryflag; |
| static int gflag; |
| static bool rtflag; |
| static long long totblocks; |
| static long long totexts; |
| |
| static cmdinfo_t freesp_cmd; |
| |
| static void |
| addhistent( |
| long long h) |
| { |
| if (histcount == INT_MAX) { |
| printf(_("Too many histogram buckets.\n")); |
| return; |
| } |
| hist = realloc(hist, (histcount + 1) * sizeof(*hist)); |
| if (h == 0) |
| h = 1; |
| hist[histcount].low = h; |
| hist[histcount].count = hist[histcount].blocks = 0; |
| histcount++; |
| if (h == 1) |
| seen1 = 1; |
| } |
| |
| static void |
| addtohist( |
| xfs_agnumber_t agno, |
| xfs_agblock_t agbno, |
| off64_t len) |
| { |
| long i; |
| |
| if (dumpflag) |
| printf("%8d %8d %8"PRId64"\n", agno, agbno, len); |
| totexts++; |
| totblocks += len; |
| for (i = 0; i < histcount; i++) { |
| if (hist[i].high >= len) { |
| hist[i].count++; |
| hist[i].blocks += len; |
| break; |
| } |
| } |
| } |
| |
| static int |
| hcmp( |
| const void *a, |
| const void *b) |
| { |
| return ((struct histent *)a)->low - ((struct histent *)b)->low; |
| } |
| |
| static void |
| histinit( |
| long long maxlen) |
| { |
| long long i; |
| |
| if (equalsize) { |
| for (i = 1; i < maxlen; i += equalsize) |
| addhistent(i); |
| } else if (multsize) { |
| for (i = 1; i < maxlen; i *= multsize) |
| addhistent(i); |
| } else { |
| if (!seen1) |
| addhistent(1); |
| qsort(hist, histcount, sizeof(*hist), hcmp); |
| } |
| for (i = 0; i < histcount; i++) { |
| if (i < histcount - 1) |
| hist[i].high = hist[i + 1].low - 1; |
| else |
| hist[i].high = maxlen; |
| } |
| } |
| |
| static void |
| printhist(void) |
| { |
| int i; |
| |
| printf("%7s %7s %7s %7s %6s\n", |
| _("from"), _("to"), _("extents"), _("blocks"), _("pct")); |
| for (i = 0; i < histcount; i++) { |
| if (hist[i].count) |
| printf("%7lld %7lld %7lld %7lld %6.2f\n", hist[i].low, |
| hist[i].high, hist[i].count, hist[i].blocks, |
| hist[i].blocks * 100.0 / totblocks); |
| } |
| } |
| |
| static int |
| inaglist( |
| xfs_agnumber_t agno) |
| { |
| int i; |
| |
| if (agcount == 0) |
| return 1; |
| for (i = 0; i < agcount; i++) |
| if (aglist[i] == agno) |
| return 1; |
| return 0; |
| } |
| |
| #define NR_EXTENTS 128 |
| |
| static void |
| scan_ag( |
| xfs_agnumber_t agno) |
| { |
| struct fsmap_head *fsmap; |
| struct fsmap *extent; |
| struct fsmap *l, *h; |
| struct fsmap *p; |
| struct xfs_fd *xfd = &file->xfd; |
| off64_t aglen; |
| xfs_agblock_t agbno; |
| unsigned long long freeblks = 0; |
| unsigned long long freeexts = 0; |
| int ret; |
| int i; |
| |
| fsmap = malloc(fsmap_sizeof(NR_EXTENTS)); |
| if (!fsmap) { |
| fprintf(stderr, _("%s: fsmap malloc failed.\n"), progname); |
| exitcode = 1; |
| return; |
| } |
| |
| memset(fsmap, 0, sizeof(*fsmap)); |
| fsmap->fmh_count = NR_EXTENTS; |
| l = fsmap->fmh_keys; |
| h = fsmap->fmh_keys + 1; |
| if (agno != NULLAGNUMBER) { |
| l->fmr_physical = cvt_agbno_to_b(xfd, agno, 0); |
| h->fmr_physical = cvt_agbno_to_b(xfd, agno + 1, 0); |
| l->fmr_device = h->fmr_device = file->fs_path.fs_datadev; |
| } else { |
| l->fmr_physical = 0; |
| h->fmr_physical = ULLONG_MAX; |
| l->fmr_device = h->fmr_device = file->fs_path.fs_rtdev; |
| } |
| h->fmr_owner = ULLONG_MAX; |
| h->fmr_flags = UINT_MAX; |
| h->fmr_offset = ULLONG_MAX; |
| |
| while (true) { |
| ret = ioctl(file->xfd.fd, FS_IOC_GETFSMAP, fsmap); |
| if (ret < 0) { |
| fprintf(stderr, _("%s: FS_IOC_GETFSMAP [\"%s\"]: %s\n"), |
| progname, file->name, strerror(errno)); |
| free(fsmap); |
| exitcode = 1; |
| return; |
| } |
| |
| /* No more extents to map, exit */ |
| if (!fsmap->fmh_entries) |
| break; |
| |
| for (i = 0, extent = fsmap->fmh_recs; |
| i < fsmap->fmh_entries; |
| i++, extent++) { |
| if (!(extent->fmr_flags & FMR_OF_SPECIAL_OWNER) || |
| extent->fmr_owner != XFS_FMR_OWN_FREE) |
| continue; |
| agbno = cvt_b_to_agbno(xfd, extent->fmr_physical); |
| aglen = cvt_b_to_off_fsbt(xfd, extent->fmr_length); |
| freeblks += aglen; |
| freeexts++; |
| |
| addtohist(agno, agbno, aglen); |
| } |
| |
| p = &fsmap->fmh_recs[fsmap->fmh_entries - 1]; |
| if (p->fmr_flags & FMR_OF_LAST) |
| break; |
| fsmap_advance(fsmap); |
| } |
| |
| if (gflag) { |
| if (agno == NULLAGNUMBER) |
| printf(_(" rtdev %10llu %10llu\n"), freeexts, |
| freeblks); |
| else |
| printf(_("%10u %10llu %10llu\n"), agno, freeexts, |
| freeblks); |
| } |
| free(fsmap); |
| } |
| |
| static void |
| aglistadd( |
| char *a) |
| { |
| xfs_agnumber_t x; |
| |
| aglist = realloc(aglist, (agcount + 1) * sizeof(*aglist)); |
| x = cvt_u32(a, 0); |
| if (errno) { |
| printf(_("Unrecognized AG number: %s\n"), a); |
| return; |
| } |
| aglist[agcount] = x; |
| agcount++; |
| } |
| |
| static int |
| init( |
| int argc, |
| char **argv) |
| { |
| struct xfs_fsop_geom *fsgeom = &file->xfd.fsgeom; |
| long long x; |
| int c; |
| int speced = 0; /* only one of -b -e -h or -m */ |
| |
| agcount = dumpflag = equalsize = multsize = optind = gflag = 0; |
| histcount = seen1 = summaryflag = 0; |
| totblocks = totexts = 0; |
| aglist = NULL; |
| hist = NULL; |
| rtflag = false; |
| |
| while ((c = getopt(argc, argv, "a:bde:gh:m:rs")) != EOF) { |
| switch (c) { |
| case 'a': |
| aglistadd(optarg); |
| break; |
| case 'b': |
| if (speced) |
| goto many_spec; |
| multsize = 2; |
| speced = 1; |
| break; |
| case 'd': |
| dumpflag = 1; |
| break; |
| case 'e': |
| if (speced) |
| goto many_spec; |
| equalsize = cvt_s64(optarg, 0); |
| if (errno) |
| return command_usage(&freesp_cmd); |
| speced = 1; |
| break; |
| case 'g': |
| gflag++; |
| break; |
| case 'h': |
| if (speced && !histcount) |
| goto many_spec; |
| /* addhistent increments histcount */ |
| x = cvt_s64(optarg, 0); |
| if (errno) |
| return command_usage(&freesp_cmd); |
| addhistent(x); |
| speced = 1; |
| break; |
| case 'm': |
| if (speced) |
| goto many_spec; |
| multsize = cvt_s64(optarg, 0); |
| if (errno) |
| return command_usage(&freesp_cmd); |
| speced = 1; |
| break; |
| case 'r': |
| rtflag = true; |
| break; |
| case 's': |
| summaryflag = 1; |
| break; |
| default: |
| return command_usage(&freesp_cmd); |
| } |
| } |
| if (optind != argc) |
| return 0; |
| if (!speced) |
| multsize = 2; |
| histinit(fsgeom->agblocks); |
| return 1; |
| many_spec: |
| return command_usage(&freesp_cmd); |
| } |
| |
| /* |
| * Report on freespace usage in xfs filesystem. |
| */ |
| static int |
| freesp_f( |
| int argc, |
| char **argv) |
| { |
| struct xfs_fsop_geom *fsgeom = &file->xfd.fsgeom; |
| xfs_agnumber_t agno; |
| |
| if (!init(argc, argv)) |
| return 0; |
| if (gflag) |
| printf(_(" AG extents blocks\n")); |
| if (rtflag) |
| scan_ag(NULLAGNUMBER); |
| for (agno = 0; !rtflag && agno < fsgeom->agcount; agno++) { |
| if (inaglist(agno)) |
| scan_ag(agno); |
| } |
| if (histcount && !gflag) |
| printhist(); |
| if (summaryflag) { |
| printf(_("total free extents %lld\n"), totexts); |
| printf(_("total free blocks %lld\n"), totblocks); |
| printf(_("average free extent size %g\n"), |
| (double)totblocks / (double)totexts); |
| } |
| if (aglist) |
| free(aglist); |
| if (hist) |
| free(hist); |
| return 0; |
| } |
| |
| static void |
| freesp_help(void) |
| { |
| printf(_( |
| "\n" |
| "Examine filesystem free space\n" |
| "\n" |
| " -a agno -- Scan only the given AG agno.\n" |
| " -b -- binary histogram bin size\n" |
| " -d -- debug output\n" |
| " -e bsize -- Use fixed histogram bin size of bsize\n" |
| " -g -- Print only a per-AG summary.\n" |
| " -h hbsz -- Use custom histogram bin size of h1.\n" |
| " Multiple specifications are allowed.\n" |
| " -m bmult -- Use histogram bin size multiplier of bmult.\n" |
| " -r -- Display realtime device free space information.\n" |
| " -s -- Emit freespace summary information.\n" |
| "\n" |
| "Only one of -b, -e, -h, or -m may be specified.\n" |
| "\n")); |
| |
| } |
| |
| void |
| freesp_init(void) |
| { |
| freesp_cmd.name = "freesp"; |
| freesp_cmd.altname = "fsp"; |
| freesp_cmd.cfunc = freesp_f; |
| freesp_cmd.argmin = 0; |
| freesp_cmd.argmax = -1; |
| freesp_cmd.args = "[-dgrs] [-a agno]... [ -b | -e bsize | -h h1... | -m bmult ]"; |
| freesp_cmd.flags = CMD_FLAG_ONESHOT; |
| freesp_cmd.oneline = _("Examine filesystem free space"); |
| freesp_cmd.help = freesp_help; |
| |
| add_command(&freesp_cmd); |
| } |
| |