blob: 1cfc6c2c27eeafe78aa66ac2087100484805cd8d [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2000-2003,2005 Silicon Graphics, Inc.
* All Rights Reserved.
*/
#include "libxfs.h"
#include <sys/time.h>
#include "bmap.h"
#include "command.h"
#include "frag.h"
#include "io.h"
#include "output.h"
#include "type.h"
#include "init.h"
#include "malloc.h"
typedef struct extent {
xfs_fileoff_t startoff;
xfs_filblks_t blockcount;
} extent_t;
typedef struct extmap {
int naents;
int nents;
extent_t ents[1];
} extmap_t;
#define EXTMAP_SIZE(n) \
(offsetof(extmap_t, ents) + (sizeof(extent_t) * (n)))
static int aflag;
static int dflag;
static uint64_t extcount_actual;
static uint64_t extcount_ideal;
static int fflag;
static int lflag;
static int qflag;
static int Rflag;
static int rflag;
static int vflag;
typedef void (*scan_lbtree_f_t)(struct xfs_btree_block *block,
int level,
extmap_t **extmapp,
typnm_t btype);
typedef void (*scan_sbtree_f_t)(struct xfs_btree_block *block,
int level,
xfs_agf_t *agf);
static extmap_t *extmap_alloc(xfs_extnum_t nex);
static xfs_extnum_t extmap_ideal(extmap_t *extmap);
static void extmap_set_ext(extmap_t **extmapp, xfs_fileoff_t o,
xfs_extlen_t c);
static int frag_f(int argc, char **argv);
static int init(int argc, char **argv);
static void process_bmbt_reclist(xfs_bmbt_rec_t *rp, int numrecs,
extmap_t **extmapp);
static void process_btinode(xfs_dinode_t *dip, extmap_t **extmapp,
int whichfork);
static void process_exinode(xfs_dinode_t *dip, extmap_t **extmapp,
int whichfork);
static void process_fork(xfs_dinode_t *dip, int whichfork);
static void process_inode(xfs_agf_t *agf, xfs_agino_t agino,
xfs_dinode_t *dip);
static void scan_ag(xfs_agnumber_t agno);
static void scan_lbtree(xfs_fsblock_t root, int nlevels,
scan_lbtree_f_t func, extmap_t **extmapp,
typnm_t btype);
static void scan_sbtree(xfs_agf_t *agf, xfs_agblock_t root,
int nlevels, scan_sbtree_f_t func,
typnm_t btype);
static void scanfunc_bmap(struct xfs_btree_block *block, int level,
extmap_t **extmapp, typnm_t btype);
static void scanfunc_ino(struct xfs_btree_block *block, int level,
xfs_agf_t *agf);
static const cmdinfo_t frag_cmd =
{ "frag", NULL, frag_f, 0, -1, 0,
"[-a] [-d] [-f] [-l] [-q] [-R] [-r] [-v]",
"get file fragmentation data", NULL };
static extmap_t *
extmap_alloc(
xfs_extnum_t nex)
{
extmap_t *extmap;
if (nex < 1)
nex = 1;
extmap = xmalloc(EXTMAP_SIZE(nex));
extmap->naents = nex;
extmap->nents = 0;
return extmap;
}
static xfs_extnum_t
extmap_ideal(
extmap_t *extmap)
{
extent_t *ep;
xfs_extnum_t rval;
for (ep = &extmap->ents[0], rval = 0;
ep < &extmap->ents[extmap->nents];
ep++) {
if (ep == &extmap->ents[0] ||
ep->startoff != ep[-1].startoff + ep[-1].blockcount)
rval++;
}
return rval;
}
static void
extmap_set_ext(
extmap_t **extmapp,
xfs_fileoff_t o,
xfs_extlen_t c)
{
extmap_t *extmap;
extent_t *ent;
extmap = *extmapp;
if (extmap->nents == extmap->naents) {
extmap->naents++;
extmap = xrealloc(extmap, EXTMAP_SIZE(extmap->naents));
*extmapp = extmap;
}
ent = &extmap->ents[extmap->nents];
ent->startoff = o;
ent->blockcount = c;
extmap->nents++;
}
void
frag_init(void)
{
add_command(&frag_cmd);
}
/*
* Get file fragmentation information.
*/
static int
frag_f(
int argc,
char **argv)
{
xfs_agnumber_t agno;
double answer;
if (!init(argc, argv))
return 0;
for (agno = 0; agno < mp->m_sb.sb_agcount; agno++)
scan_ag(agno);
if (extcount_actual)
answer = (double)(extcount_actual - extcount_ideal) * 100.0 /
(double)extcount_actual;
else
answer = 0.0;
dbprintf(_("actual %llu, ideal %llu, fragmentation factor %.2f%%\n"),
extcount_actual, extcount_ideal, answer);
dbprintf(_("Note, this number is largely meaningless.\n"));
answer = (double)extcount_actual / (double)extcount_ideal;
dbprintf(_("Files on this filesystem average %.2f extents per file\n"),
answer);
return 0;
}
static int
init(
int argc,
char **argv)
{
int c;
aflag = dflag = fflag = lflag = qflag = Rflag = rflag = vflag = 0;
optind = 0;
while ((c = getopt(argc, argv, "adflqRrv")) != EOF) {
switch (c) {
case 'a':
aflag = 1;
break;
case 'd':
dflag = 1;
break;
case 'f':
fflag = 1;
break;
case 'l':
lflag = 1;
break;
case 'q':
qflag = 1;
break;
case 'R':
Rflag = 1;
break;
case 'r':
rflag = 1;
break;
case 'v':
vflag = 1;
break;
default:
dbprintf(_("bad option for frag command\n"));
return 0;
}
}
if (!aflag && !dflag && !fflag && !lflag && !qflag && !Rflag && !rflag)
aflag = dflag = fflag = lflag = qflag = Rflag = rflag = 1;
extcount_actual = extcount_ideal = 0;
return 1;
}
static void
process_bmbt_reclist(
xfs_bmbt_rec_t *rp,
int numrecs,
extmap_t **extmapp)
{
xfs_filblks_t c;
int f;
int i;
xfs_fileoff_t o;
xfs_fsblock_t s;
for (i = 0; i < numrecs; i++, rp++) {
convert_extent(rp, &o, &s, &c, &f);
extmap_set_ext(extmapp, (xfs_fileoff_t)o, (xfs_extlen_t)c);
}
}
static void
process_btinode(
xfs_dinode_t *dip,
extmap_t **extmapp,
int whichfork)
{
xfs_bmdr_block_t *dib;
int i;
xfs_bmbt_ptr_t *pp;
dib = (xfs_bmdr_block_t *)XFS_DFORK_PTR(dip, whichfork);
if (be16_to_cpu(dib->bb_level) == 0) {
xfs_bmbt_rec_t *rp = XFS_BMDR_REC_ADDR(dib, 1);
process_bmbt_reclist(rp, be16_to_cpu(dib->bb_numrecs), extmapp);
return;
}
pp = XFS_BMDR_PTR_ADDR(dib, 1,
libxfs_bmdr_maxrecs(XFS_DFORK_SIZE(dip, mp, whichfork), 0));
for (i = 0; i < be16_to_cpu(dib->bb_numrecs); i++)
scan_lbtree(get_unaligned_be64(&pp[i]),
be16_to_cpu(dib->bb_level), scanfunc_bmap, extmapp,
whichfork == XFS_DATA_FORK ? TYP_BMAPBTD : TYP_BMAPBTA);
}
static void
process_exinode(
xfs_dinode_t *dip,
extmap_t **extmapp,
int whichfork)
{
xfs_bmbt_rec_t *rp;
rp = (xfs_bmbt_rec_t *)XFS_DFORK_PTR(dip, whichfork);
process_bmbt_reclist(rp, XFS_DFORK_NEXTENTS(dip, whichfork), extmapp);
}
static void
process_fork(
xfs_dinode_t *dip,
int whichfork)
{
extmap_t *extmap;
int nex;
nex = XFS_DFORK_NEXTENTS(dip, whichfork);
if (!nex)
return;
extmap = extmap_alloc(nex);
switch (XFS_DFORK_FORMAT(dip, whichfork)) {
case XFS_DINODE_FMT_EXTENTS:
process_exinode(dip, &extmap, whichfork);
break;
case XFS_DINODE_FMT_BTREE:
process_btinode(dip, &extmap, whichfork);
break;
}
extcount_actual += extmap->nents;
extcount_ideal += extmap_ideal(extmap);
xfree(extmap);
}
static void
process_inode(
xfs_agf_t *agf,
xfs_agino_t agino,
xfs_dinode_t *dip)
{
uint64_t actual;
uint64_t ideal;
xfs_ino_t ino;
int skipa;
int skipd;
ino = XFS_AGINO_TO_INO(mp, be32_to_cpu(agf->agf_seqno), agino);
switch (be16_to_cpu(dip->di_mode) & S_IFMT) {
case S_IFDIR:
skipd = !dflag;
break;
case S_IFREG:
if (!rflag && (be16_to_cpu(dip->di_flags) & XFS_DIFLAG_REALTIME))
skipd = 1;
else if (!Rflag &&
(ino == mp->m_sb.sb_rbmino ||
ino == mp->m_sb.sb_rsumino))
skipd = 1;
else if (!qflag &&
(ino == mp->m_sb.sb_uquotino ||
ino == mp->m_sb.sb_gquotino ||
ino == mp->m_sb.sb_pquotino))
skipd = 1;
else
skipd = !fflag;
break;
case S_IFLNK:
skipd = !lflag;
break;
default:
skipd = 1;
break;
}
actual = extcount_actual;
ideal = extcount_ideal;
if (!skipd)
process_fork(dip, XFS_DATA_FORK);
skipa = !aflag || !XFS_DFORK_Q(dip);
if (!skipa)
process_fork(dip, XFS_ATTR_FORK);
if (vflag && (!skipd || !skipa))
dbprintf(_("inode %lld actual %lld ideal %lld\n"),
ino, extcount_actual - actual, extcount_ideal - ideal);
}
static void
scan_ag(
xfs_agnumber_t agno)
{
xfs_agf_t *agf;
xfs_agi_t *agi;
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);
if ((agf = iocur_top->data) == NULL) {
dbprintf(_("can't read agf block for ag %u\n"), agno);
pop_cur();
return;
}
push_cur();
set_cur(&typtab[TYP_AGI],
XFS_AG_DADDR(mp, agno, XFS_AGI_DADDR(mp)),
XFS_FSS_TO_BB(mp, 1), DB_RING_IGN, NULL);
if ((agi = iocur_top->data) == NULL) {
dbprintf(_("can't read agi block for ag %u\n"), agno);
pop_cur();
pop_cur();
return;
}
scan_sbtree(agf, be32_to_cpu(agi->agi_root),
be32_to_cpu(agi->agi_level), scanfunc_ino, TYP_INOBT);
pop_cur();
pop_cur();
}
static void
scan_lbtree(
xfs_fsblock_t root,
int nlevels,
scan_lbtree_f_t func,
extmap_t **extmapp,
typnm_t btype)
{
push_cur();
set_cur(&typtab[btype], XFS_FSB_TO_DADDR(mp, root), blkbb, DB_RING_IGN,
NULL);
if (iocur_top->data == NULL) {
dbprintf(_("can't read btree block %u/%u\n"),
XFS_FSB_TO_AGNO(mp, root),
XFS_FSB_TO_AGBNO(mp, root));
return;
}
(*func)(iocur_top->data, nlevels - 1, extmapp, btype);
pop_cur();
}
static void
scan_sbtree(
xfs_agf_t *agf,
xfs_agblock_t root,
int nlevels,
scan_sbtree_f_t func,
typnm_t btype)
{
xfs_agnumber_t seqno = be32_to_cpu(agf->agf_seqno);
push_cur();
set_cur(&typtab[btype], 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, nlevels - 1, agf);
pop_cur();
}
static void
scanfunc_bmap(
struct xfs_btree_block *block,
int level,
extmap_t **extmapp,
typnm_t btype)
{
int i;
xfs_bmbt_ptr_t *pp;
xfs_bmbt_rec_t *rp;
int nrecs;
nrecs = be16_to_cpu(block->bb_numrecs);
if (level == 0) {
if (nrecs > mp->m_bmap_dmxr[0]) {
dbprintf(_("invalid numrecs (%u) in %s block\n"),
nrecs, typtab[btype].name);
return;
}
rp = XFS_BMBT_REC_ADDR(mp, block, 1);
process_bmbt_reclist(rp, nrecs, extmapp);
return;
}
if (nrecs > mp->m_bmap_dmxr[1]) {
dbprintf(_("invalid numrecs (%u) in %s block\n"),
nrecs, typtab[btype].name);
return;
}
pp = XFS_BMBT_PTR_ADDR(mp, block, 1, mp->m_bmap_dmxr[0]);
for (i = 0; i < nrecs; i++)
scan_lbtree(be64_to_cpu(pp[i]), level, scanfunc_bmap, extmapp,
btype);
}
static void
scanfunc_ino(
struct xfs_btree_block *block,
int level,
xfs_agf_t *agf)
{
xfs_agino_t agino;
xfs_agnumber_t seqno = be32_to_cpu(agf->agf_seqno);
int i;
int j;
int off;
xfs_inobt_ptr_t *pp;
xfs_inobt_rec_t *rp;
xfs_agblock_t agbno;
xfs_agblock_t end_agbno;
struct xfs_dinode *dip;
int blks_per_buf;
int inodes_per_buf;
int ioff;
struct xfs_ino_geometry *igeo = M_IGEO(mp);
if (xfs_sb_version_hassparseinodes(&mp->m_sb))
blks_per_buf = igeo->blocks_per_cluster;
else
blks_per_buf = igeo->ialloc_blks;
inodes_per_buf = min(XFS_FSB_TO_INO(mp, blks_per_buf),
XFS_INODES_PER_CHUNK);
if (level == 0) {
rp = XFS_INOBT_REC_ADDR(mp, block, 1);
for (i = 0; i < be16_to_cpu(block->bb_numrecs); i++) {
agino = be32_to_cpu(rp[i].ir_startino);
agbno = XFS_AGINO_TO_AGBNO(mp, agino);
off = XFS_AGINO_TO_OFFSET(mp, agino);
end_agbno = agbno + igeo->ialloc_blks;
push_cur();
ioff = 0;
while (agbno < end_agbno &&
ioff < XFS_INODES_PER_CHUNK) {
if (xfs_inobt_is_sparse_disk(&rp[i], ioff))
goto next_buf;
set_cur(&typtab[TYP_INODE],
XFS_AGB_TO_DADDR(mp, seqno, agbno),
XFS_FSB_TO_BB(mp, blks_per_buf),
DB_RING_IGN, NULL);
if (iocur_top->data == NULL) {
dbprintf(_("can't read inode block %u/%u\n"),
seqno, agbno);
goto next_buf;
}
for (j = 0; j < inodes_per_buf; j++) {
if (XFS_INOBT_IS_FREE_DISK(&rp[i], ioff + j))
continue;
dip = (xfs_dinode_t *)((char *)iocur_top->data +
((off + j) << mp->m_sb.sb_inodelog));
process_inode(agf, agino + ioff + j, dip);
}
next_buf:
agbno += blks_per_buf;
ioff += inodes_per_buf;
}
pop_cur();
}
return;
}
pp = XFS_INOBT_PTR_ADDR(mp, block, 1, igeo->inobt_mxr[1]);
for (i = 0; i < be16_to_cpu(block->bb_numrecs); i++)
scan_sbtree(agf, be32_to_cpu(pp[i]), level, scanfunc_ino,
TYP_INOBT);
}