| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * Copyright (c) 2000-2001,2005 Silicon Graphics, Inc. |
| * All Rights Reserved. |
| */ |
| |
| #include "libxfs.h" |
| #include "command.h" |
| #include "freesp.h" |
| #include "io.h" |
| #include "type.h" |
| #include "output.h" |
| #include "init.h" |
| #include "malloc.h" |
| |
| typedef struct histent |
| { |
| int low; |
| int high; |
| long long count; |
| long long blocks; |
| } histent_t; |
| |
| static void addhistent(int h); |
| static void addtohist(xfs_agnumber_t agno, xfs_agblock_t agbno, |
| xfs_extlen_t len); |
| static int freesp_f(int argc, char **argv); |
| static void histinit(int maxlen); |
| static int init(int argc, char **argv); |
| static void printhist(void); |
| static void scan_ag(xfs_agnumber_t agno); |
| static void scanfunc_bno(struct xfs_btree_block *block, typnm_t typ, int level, |
| xfs_agf_t *agf); |
| static void scanfunc_cnt(struct xfs_btree_block *block, typnm_t typ, int level, |
| xfs_agf_t *agf); |
| static void scan_freelist(xfs_agf_t *agf); |
| static void scan_sbtree(xfs_agf_t *agf, xfs_agblock_t root, typnm_t typ, |
| int nlevels, |
| void (*func)(struct xfs_btree_block *block, typnm_t typ, |
| int level, xfs_agf_t *agf)); |
| static int usage(void); |
| |
| static int agcount; |
| static xfs_agnumber_t *aglist; |
| static int alignment; |
| static int countflag; |
| static int dumpflag; |
| static int equalsize; |
| static histent_t *hist; |
| static int histcount; |
| static int multsize; |
| static int seen1; |
| static int summaryflag; |
| static long long totblocks; |
| static long long totexts; |
| |
| static const cmdinfo_t freesp_cmd = |
| { "freesp", NULL, freesp_f, 0, -1, 0, |
| "[-bcdfs] [-A alignment] [-a agno]... [-e binsize] [-h h1]... [-m binmult]", |
| "summarize free space for filesystem", NULL }; |
| |
| 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; |
| } |
| |
| /* |
| * Report on freespace usage in xfs filesystem. |
| */ |
| static int |
| freesp_f( |
| int argc, |
| char **argv) |
| { |
| xfs_agnumber_t agno; |
| |
| if (!init(argc, argv)) |
| return 0; |
| |
| if (dumpflag) |
| dbprintf("%8s %8s %8s\n", "agno", "agbno", "len"); |
| |
| for (agno = 0; agno < mp->m_sb.sb_agcount; agno++) { |
| if (inaglist(agno)) |
| scan_ag(agno); |
| } |
| if (histcount) |
| printhist(); |
| if (summaryflag) { |
| dbprintf(_("total free extents %lld\n"), totexts); |
| dbprintf(_("total free blocks %lld\n"), totblocks); |
| dbprintf(_("average free extent size %g\n"), |
| (double)totblocks / (double)totexts); |
| } |
| if (aglist) |
| xfree(aglist); |
| if (hist) |
| xfree(hist); |
| return 0; |
| } |
| |
| void |
| freesp_init(void) |
| { |
| add_command(&freesp_cmd); |
| } |
| |
| static void |
| aglistadd( |
| char *a) |
| { |
| aglist = xrealloc(aglist, (agcount + 1) * sizeof(*aglist)); |
| aglist[agcount] = (xfs_agnumber_t)atoi(a); |
| agcount++; |
| } |
| |
| static int |
| init( |
| int argc, |
| char **argv) |
| { |
| int c; |
| int speced = 0; |
| |
| agcount = countflag = dumpflag = equalsize = multsize = optind = 0; |
| histcount = seen1 = summaryflag = 0; |
| totblocks = totexts = 0; |
| aglist = NULL; |
| hist = NULL; |
| while ((c = getopt(argc, argv, "A:a:bcde:h:m:s")) != EOF) { |
| switch (c) { |
| case 'A': |
| alignment = atoi(optarg); |
| break; |
| case 'a': |
| aglistadd(optarg); |
| break; |
| case 'b': |
| if (speced) |
| return usage(); |
| multsize = 2; |
| speced = 1; |
| break; |
| case 'c': |
| countflag = 1; |
| break; |
| case 'd': |
| dumpflag = 1; |
| break; |
| case 'e': |
| if (speced) |
| return usage(); |
| equalsize = atoi(optarg); |
| speced = 1; |
| break; |
| case 'h': |
| if (speced && !histcount) |
| return usage(); |
| addhistent(atoi(optarg)); |
| speced = 1; |
| break; |
| case 'm': |
| if (speced) |
| return usage(); |
| multsize = atoi(optarg); |
| speced = 1; |
| break; |
| case 's': |
| summaryflag = 1; |
| break; |
| default: |
| return usage(); |
| } |
| } |
| if (optind != argc) |
| return usage(); |
| if (!speced) |
| multsize = 2; |
| histinit((int)mp->m_sb.sb_agblocks); |
| return 1; |
| } |
| |
| static int |
| usage(void) |
| { |
| dbprintf(_("freesp arguments: [-bcds] [-a agno] [-e binsize] [-h h1]... " |
| "[-m binmult]\n")); |
| return 0; |
| } |
| |
| static void |
| scan_ag( |
| xfs_agnumber_t agno) |
| { |
| xfs_agf_t *agf; |
| |
| push_cur(); |
| set_cur(&typtab[TYP_AGF], XFS_AG_DADDR(mp, agno, XFS_AGF_DADDR(mp)), |
| XFS_FSS_TO_BB(mp, 1), DB_RING_IGN, NULL); |
| agf = iocur_top->data; |
| scan_freelist(agf); |
| if (countflag) |
| scan_sbtree(agf, be32_to_cpu(agf->agf_roots[XFS_BTNUM_CNT]), |
| TYP_CNTBT, be32_to_cpu(agf->agf_levels[XFS_BTNUM_CNT]), |
| scanfunc_cnt); |
| else |
| scan_sbtree(agf, be32_to_cpu(agf->agf_roots[XFS_BTNUM_BNO]), |
| TYP_BNOBT, be32_to_cpu(agf->agf_levels[XFS_BTNUM_BNO]), |
| scanfunc_bno); |
| pop_cur(); |
| } |
| |
| static int |
| scan_agfl( |
| struct xfs_mount *mp, |
| xfs_agblock_t bno, |
| void *priv) |
| { |
| addtohist(*(xfs_agnumber_t *)priv, bno, 1); |
| return 0; |
| } |
| |
| static void |
| scan_freelist( |
| xfs_agf_t *agf) |
| { |
| xfs_agnumber_t seqno = be32_to_cpu(agf->agf_seqno); |
| |
| if (be32_to_cpu(agf->agf_flcount) == 0) |
| return; |
| push_cur(); |
| set_cur(&typtab[TYP_AGFL], XFS_AG_DADDR(mp, seqno, XFS_AGFL_DADDR(mp)), |
| XFS_FSS_TO_BB(mp, 1), DB_RING_IGN, NULL); |
| |
| /* verify agf values before proceeding */ |
| if (be32_to_cpu(agf->agf_flfirst) >= libxfs_agfl_size(mp) || |
| be32_to_cpu(agf->agf_fllast) >= libxfs_agfl_size(mp)) { |
| dbprintf(_("agf %d freelist blocks bad, skipping " |
| "freelist scan\n"), seqno); |
| pop_cur(); |
| return; |
| } |
| |
| libxfs_agfl_walk(mp, agf, iocur_top->bp, scan_agfl, &seqno); |
| pop_cur(); |
| } |
| |
| static void |
| scan_sbtree( |
| xfs_agf_t *agf, |
| xfs_agblock_t root, |
| typnm_t typ, |
| int nlevels, |
| void (*func)(struct xfs_btree_block *block, |
| typnm_t typ, |
| int level, |
| xfs_agf_t *agf)) |
| { |
| xfs_agnumber_t seqno = be32_to_cpu(agf->agf_seqno); |
| |
| push_cur(); |
| set_cur(&typtab[typ], XFS_AGB_TO_DADDR(mp, seqno, root), |
| blkbb, DB_RING_IGN, NULL); |
| if (iocur_top->data == NULL) { |
| dbprintf(_("can't read btree block %u/%u\n"), seqno, root); |
| return; |
| } |
| (*func)(iocur_top->data, typ, nlevels - 1, agf); |
| pop_cur(); |
| } |
| |
| /*ARGSUSED*/ |
| static void |
| scanfunc_bno( |
| struct xfs_btree_block *block, |
| typnm_t typ, |
| int level, |
| xfs_agf_t *agf) |
| { |
| int i; |
| xfs_alloc_ptr_t *pp; |
| xfs_alloc_rec_t *rp; |
| |
| if (!(be32_to_cpu(block->bb_magic) == XFS_ABTB_MAGIC || |
| be32_to_cpu(block->bb_magic) == XFS_ABTB_CRC_MAGIC)) |
| return; |
| |
| if (level == 0) { |
| rp = XFS_ALLOC_REC_ADDR(mp, block, 1); |
| for (i = 0; i < be16_to_cpu(block->bb_numrecs); i++) |
| addtohist(be32_to_cpu(agf->agf_seqno), |
| be32_to_cpu(rp[i].ar_startblock), |
| be32_to_cpu(rp[i].ar_blockcount)); |
| return; |
| } |
| pp = XFS_ALLOC_PTR_ADDR(mp, block, 1, mp->m_alloc_mxr[1]); |
| for (i = 0; i < be16_to_cpu(block->bb_numrecs); i++) |
| scan_sbtree(agf, be32_to_cpu(pp[i]), typ, level, scanfunc_bno); |
| } |
| |
| static void |
| scanfunc_cnt( |
| struct xfs_btree_block *block, |
| typnm_t typ, |
| int level, |
| xfs_agf_t *agf) |
| { |
| int i; |
| xfs_alloc_ptr_t *pp; |
| xfs_alloc_rec_t *rp; |
| |
| if (!(be32_to_cpu(block->bb_magic) == XFS_ABTC_MAGIC || |
| be32_to_cpu(block->bb_magic) == XFS_ABTC_CRC_MAGIC)) |
| return; |
| |
| if (level == 0) { |
| rp = XFS_ALLOC_REC_ADDR(mp, block, 1); |
| for (i = 0; i < be16_to_cpu(block->bb_numrecs); i++) |
| addtohist(be32_to_cpu(agf->agf_seqno), |
| be32_to_cpu(rp[i].ar_startblock), |
| be32_to_cpu(rp[i].ar_blockcount)); |
| return; |
| } |
| pp = XFS_ALLOC_PTR_ADDR(mp, block, 1, mp->m_alloc_mxr[1]); |
| for (i = 0; i < be16_to_cpu(block->bb_numrecs); i++) |
| scan_sbtree(agf, be32_to_cpu(pp[i]), typ, level, scanfunc_cnt); |
| } |
| |
| static void |
| addhistent( |
| int h) |
| { |
| hist = xrealloc(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, |
| xfs_extlen_t len) |
| { |
| int i; |
| |
| if (alignment && (XFS_AGB_TO_FSB(mp,agno,agbno) % alignment)) |
| return; |
| |
| if (dumpflag) |
| dbprintf("%8d %8d %8d\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 ((histent_t *)a)->low - ((histent_t *)b)->low; |
| } |
| |
| static void |
| histinit( |
| int maxlen) |
| { |
| int 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; |
| |
| dbprintf("%7s %7s %7s %7s %6s\n", |
| _("from"), _("to"), _("extents"), _("blocks"), _("pct")); |
| for (i = 0; i < histcount; i++) { |
| if (hist[i].count) |
| dbprintf("%7d %7d %7lld %7lld %6.2f\n", hist[i].low, |
| hist[i].high, hist[i].count, hist[i].blocks, |
| hist[i].blocks * 100.0 / totblocks); |
| } |
| } |