blob: bf1196390cf35d066b5ca4df3aecf4ff491915b1 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2017 Oracle. All Rights Reserved.
* Author: Darrick J. Wong <darrick.wong@oracle.com>
*/
#include "platform_defs.h"
#include <linux/fsmap.h>
#include "command.h"
#include "init.h"
#include "libfrog/paths.h"
#include "io.h"
#include "input.h"
#include "libfrog/fsgeom.h"
static cmdinfo_t fsmap_cmd;
static dev_t xfs_data_dev;
static void
fsmap_help(void)
{
printf(_(
"\n"
" Prints the block mapping for the filesystem hosting the current file"
"\n"
" fsmap prints the map of disk blocks used by the whole filesystem.\n"
" When possible, owner and offset information will be included in the\n"
" space report.\n"
"\n"
" By default, each line of the listing takes the following form:\n"
" extent: major:minor [startblock..endblock]: owner startoffset..endoffset length\n"
" The owner field is either an inode number or a special value.\n"
" All the file offsets and disk blocks are in units of 512-byte blocks.\n"
" -d -- query only the data device (default).\n"
" -l -- query only the log device.\n"
" -r -- query only the realtime device.\n"
" -n -- query n extents at a time.\n"
" -m -- output machine-readable format.\n"
" -v -- Verbose information, show AG and offsets. Show flags legend on 2nd -v\n"
"\n"
"The optional start and end arguments require one of -d, -l, or -r to be set.\n"
"\n"));
}
#define OWNER_BUF_SZ 32
static const char *
special_owner(
int64_t owner,
char *buf)
{
switch (owner) {
case XFS_FMR_OWN_FREE:
return _("free space");
case XFS_FMR_OWN_UNKNOWN:
return _("unknown");
case XFS_FMR_OWN_FS:
return _("static fs metadata");
case XFS_FMR_OWN_LOG:
return _("journalling log");
case XFS_FMR_OWN_AG:
return _("per-AG metadata");
case XFS_FMR_OWN_INOBT:
return _("inode btree");
case XFS_FMR_OWN_INODES:
return _("inodes");
case XFS_FMR_OWN_REFC:
return _("refcount btree");
case XFS_FMR_OWN_COW:
return _("cow reservation");
case XFS_FMR_OWN_DEFECTIVE:
return _("defective");
default:
snprintf(buf, OWNER_BUF_SZ, _("special %u:%u"),
FMR_OWNER_TYPE(owner), FMR_OWNER_CODE(owner));
return buf;
}
}
static void
dump_map(
unsigned long long *nr,
struct fsmap_head *head)
{
unsigned long long i;
struct fsmap *p;
char owner[OWNER_BUF_SZ];
char *fork;
for (i = 0, p = head->fmh_recs; i < head->fmh_entries; i++, p++) {
printf("\t%llu: %u:%u [%lld..%lld]: ", i + (*nr),
major(p->fmr_device), minor(p->fmr_device),
(long long)BTOBBT(p->fmr_physical),
(long long)BTOBBT(p->fmr_physical + p->fmr_length - 1));
fork = (p->fmr_flags & FMR_OF_ATTR_FORK) ? _("attr") : _("data");
if (p->fmr_flags & FMR_OF_SPECIAL_OWNER)
printf("%s", special_owner(p->fmr_owner, owner));
else if (p->fmr_flags & FMR_OF_EXTENT_MAP)
printf(_("inode %lld %s extent map"),
(long long) p->fmr_owner, fork);
else
printf(_("inode %lld %s %lld..%lld"),
(long long)p->fmr_owner, fork,
(long long)BTOBBT(p->fmr_offset),
(long long)BTOBBT(p->fmr_offset + p->fmr_length - 1));
printf(_(" %lld\n"),
(long long)BTOBBT(p->fmr_length));
}
(*nr) += head->fmh_entries;
}
static void
dump_map_machine(
unsigned long long *nr,
struct fsmap_head *head)
{
unsigned long long i;
struct fsmap *p;
char *fork;
if (*nr == 0)
printf(_("EXT,MAJOR,MINOR,PSTART,PEND,OWNER,OSTART,OEND,LENGTH\n"));
for (i = 0, p = head->fmh_recs; i < head->fmh_entries; i++, p++) {
printf("%llu,%u,%u,%lld,%lld,", i + (*nr),
major(p->fmr_device), minor(p->fmr_device),
(long long)BTOBBT(p->fmr_physical),
(long long)BTOBBT(p->fmr_physical + p->fmr_length - 1));
fork = (p->fmr_flags & FMR_OF_ATTR_FORK) ? "attr" : "data";
if (p->fmr_flags & FMR_OF_SPECIAL_OWNER)
printf("special_%u:%u,,,", FMR_OWNER_TYPE(p->fmr_owner),
FMR_OWNER_CODE(p->fmr_owner));
else if (p->fmr_flags & FMR_OF_EXTENT_MAP)
printf(_("inode_%lld_%s_bmbt,,,"),
(long long) p->fmr_owner, fork);
else
printf(_("inode_%lld_%s,%lld,%lld,"),
(long long)p->fmr_owner, fork,
(long long)BTOBBT(p->fmr_offset),
(long long)BTOBBT(p->fmr_offset + p->fmr_length - 1));
printf("%lld\n",
(long long)BTOBBT(p->fmr_length));
}
(*nr) += head->fmh_entries;
}
/*
* Verbose mode displays:
* extent: major:minor [startblock..endblock]: startoffset..endoffset \
* ag# (agoffset..agendoffset) totalbbs flags
*/
#define MINRANGE_WIDTH 16
#define MINAG_WIDTH 2
#define MINTOT_WIDTH 5
#define NFLG 7 /* count of flags */
#define FLG_NULL 00000000 /* Null flag */
#define FLG_ATTR_FORK 01000000 /* attribute fork */
#define FLG_SHARED 00100000 /* shared extent */
#define FLG_PRE 00010000 /* Unwritten extent */
#define FLG_BSU 00001000 /* Not on begin of stripe unit */
#define FLG_ESU 00000100 /* Not on end of stripe unit */
#define FLG_BSW 00000010 /* Not on begin of stripe width */
#define FLG_ESW 00000001 /* Not on end of stripe width */
static void
dump_map_verbose(
unsigned long long *nr,
struct fsmap_head *head,
bool *dumped_flags,
struct xfs_fsop_geom *fsgeo)
{
unsigned long long i;
struct fsmap *p;
int agno;
off_t agoff, bperag;
int foff_w, boff_w, aoff_w, tot_w, agno_w, own_w;
int nr_w, dev_w;
char rbuf[40], bbuf[40], abuf[40], obuf[40];
char nbuf[40], dbuf[40], gbuf[40];
char owner[OWNER_BUF_SZ];
int sunit, swidth;
int flg = 0;
foff_w = boff_w = aoff_w = own_w = MINRANGE_WIDTH;
dev_w = 3;
nr_w = 4;
tot_w = MINTOT_WIDTH;
bperag = (off_t)fsgeo->agblocks *
(off_t)fsgeo->blocksize;
sunit = (fsgeo->sunit * fsgeo->blocksize);
swidth = (fsgeo->swidth * fsgeo->blocksize);
/*
* Go through the extents and figure out the width
* needed for all columns.
*/
for (i = 0, p = head->fmh_recs; i < head->fmh_entries; i++, p++) {
if (p->fmr_flags & FMR_OF_PREALLOC ||
p->fmr_flags & FMR_OF_ATTR_FORK ||
p->fmr_flags & FMR_OF_SHARED)
flg = 1;
if (sunit && p->fmr_device == xfs_data_dev &&
(p->fmr_physical % sunit != 0 ||
((p->fmr_physical + p->fmr_length) % sunit) != 0 ||
p->fmr_physical % swidth != 0 ||
((p->fmr_physical + p->fmr_length) % swidth) != 0))
flg = 1;
if (flg)
*dumped_flags = true;
snprintf(nbuf, sizeof(nbuf), "%llu", (*nr) + i);
nr_w = max(nr_w, strlen(nbuf));
if (head->fmh_oflags & FMH_OF_DEV_T)
snprintf(dbuf, sizeof(dbuf), "%u:%u",
major(p->fmr_device),
minor(p->fmr_device));
else
snprintf(dbuf, sizeof(dbuf), "0x%x", p->fmr_device);
dev_w = max(dev_w, strlen(dbuf));
snprintf(bbuf, sizeof(bbuf), "[%lld..%lld]:",
(long long)BTOBBT(p->fmr_physical),
(long long)BTOBBT(p->fmr_physical + p->fmr_length - 1));
boff_w = max(boff_w, strlen(bbuf));
if (p->fmr_flags & FMR_OF_SPECIAL_OWNER)
own_w = max(own_w, strlen(
special_owner(p->fmr_owner, owner)));
else {
snprintf(obuf, sizeof(obuf), "%lld",
(long long)p->fmr_owner);
own_w = max(own_w, strlen(obuf));
}
if (p->fmr_flags & FMR_OF_EXTENT_MAP)
foff_w = max(foff_w, strlen(_("extent_map")));
else if (p->fmr_flags & FMR_OF_SPECIAL_OWNER)
;
else {
snprintf(rbuf, sizeof(rbuf), "%lld..%lld",
(long long)BTOBBT(p->fmr_offset),
(long long)BTOBBT(p->fmr_offset + p->fmr_length - 1));
foff_w = max(foff_w, strlen(rbuf));
}
if (p->fmr_device == xfs_data_dev) {
agno = p->fmr_physical / bperag;
agoff = p->fmr_physical - (agno * bperag);
snprintf(abuf, sizeof(abuf),
"(%lld..%lld)",
(long long)BTOBBT(agoff),
(long long)BTOBBT(agoff + p->fmr_length - 1));
} else
abuf[0] = 0;
aoff_w = max(aoff_w, strlen(abuf));
tot_w = max(tot_w,
numlen(BTOBBT(p->fmr_length), 10));
}
agno_w = max(MINAG_WIDTH, numlen(fsgeo->agcount, 10));
if (*nr == 0)
printf("%*s: %-*s %-*s %-*s %-*s %*s %-*s %*s%s\n",
nr_w, _("EXT"),
dev_w, _("DEV"),
boff_w, _("BLOCK-RANGE"),
own_w, _("OWNER"),
foff_w, _("FILE-OFFSET"),
agno_w, _("AG"),
aoff_w, _("AG-OFFSET"),
tot_w, _("TOTAL"),
flg ? _(" FLAGS") : "");
for (i = 0, p = head->fmh_recs; i < head->fmh_entries; i++, p++) {
flg = FLG_NULL;
if (p->fmr_flags & FMR_OF_PREALLOC)
flg |= FLG_PRE;
if (p->fmr_flags & FMR_OF_ATTR_FORK)
flg |= FLG_ATTR_FORK;
if (p->fmr_flags & FMR_OF_SHARED)
flg |= FLG_SHARED;
/*
* If striping enabled, determine if extent starts/ends
* on a stripe unit boundary.
*/
if (sunit && p->fmr_device == xfs_data_dev) {
if (p->fmr_physical % sunit != 0)
flg |= FLG_BSU;
if (((p->fmr_physical +
p->fmr_length ) % sunit ) != 0)
flg |= FLG_ESU;
if (p->fmr_physical % swidth != 0)
flg |= FLG_BSW;
if (((p->fmr_physical +
p->fmr_length ) % swidth ) != 0)
flg |= FLG_ESW;
}
if (head->fmh_oflags & FMH_OF_DEV_T)
snprintf(dbuf, sizeof(dbuf), "%u:%u",
major(p->fmr_device),
minor(p->fmr_device));
else
snprintf(dbuf, sizeof(dbuf), "0x%x", p->fmr_device);
snprintf(bbuf, sizeof(bbuf), "[%lld..%lld]:",
(long long)BTOBBT(p->fmr_physical),
(long long)BTOBBT(p->fmr_physical + p->fmr_length - 1));
if (p->fmr_flags & FMR_OF_SPECIAL_OWNER) {
snprintf(obuf, sizeof(obuf), "%s",
special_owner(p->fmr_owner, owner));
snprintf(rbuf, sizeof(rbuf), " ");
} else {
snprintf(obuf, sizeof(obuf), "%lld",
(long long)p->fmr_owner);
snprintf(rbuf, sizeof(rbuf), "%lld..%lld",
(long long)BTOBBT(p->fmr_offset),
(long long)BTOBBT(p->fmr_offset + p->fmr_length - 1));
}
if (p->fmr_device == xfs_data_dev) {
agno = p->fmr_physical / bperag;
agoff = p->fmr_physical - (agno * bperag);
snprintf(abuf, sizeof(abuf),
"(%lld..%lld)",
(long long)BTOBBT(agoff),
(long long)BTOBBT(agoff + p->fmr_length - 1));
snprintf(gbuf, sizeof(gbuf),
"%lld",
(long long)agno);
} else {
abuf[0] = 0;
gbuf[0] = 0;
}
if (p->fmr_flags & FMR_OF_EXTENT_MAP)
printf("%*llu: %-*s %-*s %-*s %-*s %-*s %-*s %*lld\n",
nr_w, (*nr) + i,
dev_w, dbuf,
boff_w, bbuf,
own_w, obuf,
foff_w, _("extent map"),
agno_w, gbuf,
aoff_w, abuf,
tot_w, (long long)BTOBBT(p->fmr_length));
else {
printf("%*llu: %-*s %-*s %-*s %-*s", nr_w, (*nr) + i,
dev_w, dbuf, boff_w, bbuf, own_w, obuf,
foff_w, rbuf);
printf(" %-*s %-*s", agno_w, gbuf,
aoff_w, abuf);
printf(" %*lld", tot_w,
(long long)BTOBBT(p->fmr_length));
if (flg == FLG_NULL)
printf("\n");
else
printf(" %-*.*o\n", NFLG, NFLG, flg);
}
}
(*nr) += head->fmh_entries;
}
static void
dump_verbose_key(void)
{
printf(_(" FLAG Values:\n"));
printf(_(" %*.*o Attribute fork\n"),
NFLG+1, NFLG+1, FLG_ATTR_FORK);
printf(_(" %*.*o Shared extent\n"),
NFLG+1, NFLG+1, FLG_SHARED);
printf(_(" %*.*o Unwritten preallocated extent\n"),
NFLG+1, NFLG+1, FLG_PRE);
printf(_(" %*.*o Doesn't begin on stripe unit\n"),
NFLG+1, NFLG+1, FLG_BSU);
printf(_(" %*.*o Doesn't end on stripe unit\n"),
NFLG+1, NFLG+1, FLG_ESU);
printf(_(" %*.*o Doesn't begin on stripe width\n"),
NFLG+1, NFLG+1, FLG_BSW);
printf(_(" %*.*o Doesn't end on stripe width\n"),
NFLG+1, NFLG+1, FLG_ESW);
}
static int
fsmap_f(
int argc,
char **argv)
{
struct fsmap *p;
struct fsmap_head *head;
struct fsmap *l, *h;
struct xfs_fsop_geom fsgeo;
long long start = 0;
long long end = -1;
int map_size;
int nflag = 0;
int vflag = 0;
int mflag = 0;
int i = 0;
int c;
unsigned long long nr = 0;
size_t fsblocksize, fssectsize;
struct fs_path *fs;
static bool tab_init;
bool dumped_flags = false;
int dflag, lflag, rflag;
init_cvtnum(&fsblocksize, &fssectsize);
dflag = lflag = rflag = 0;
while ((c = getopt(argc, argv, "dlmn:rv")) != EOF) {
switch (c) {
case 'd': /* data device */
dflag = 1;
break;
case 'l': /* log device */
lflag = 1;
break;
case 'm': /* machine readable format */
mflag++;
break;
case 'n': /* number of extents specified */
nflag = cvt_u32(optarg, 10);
if (errno)
return command_usage(&fsmap_cmd);
break;
case 'r': /* rt device */
rflag = 1;
break;
case 'v': /* Verbose output */
vflag++;
break;
default:
exitcode = 1;
return command_usage(&fsmap_cmd);
}
}
if ((dflag + lflag + rflag > 1) || (mflag > 0 && vflag > 0) ||
(argc > optind && dflag + lflag + rflag == 0)) {
exitcode = 1;
return command_usage(&fsmap_cmd);
}
if (argc > optind) {
start = cvtnum(fsblocksize, fssectsize, argv[optind]);
if (start < 0) {
fprintf(stderr,
_("Bad rmap start_bblock %s.\n"),
argv[optind]);
exitcode = 1;
return 0;
}
start <<= BBSHIFT;
}
if (argc > optind + 1) {
end = cvtnum(fsblocksize, fssectsize, argv[optind + 1]);
if (end < 0) {
fprintf(stderr,
_("Bad rmap end_bblock %s.\n"),
argv[optind + 1]);
exitcode = 1;
return 0;
}
end <<= BBSHIFT;
}
if (vflag) {
c = -xfrog_geometry(file->fd, &fsgeo);
if (c) {
fprintf(stderr,
_("%s: can't get geometry [\"%s\"]: %s\n"),
progname, file->name, strerror(c));
exitcode = 1;
return 0;
}
}
map_size = nflag ? nflag : 131072 / sizeof(struct fsmap);
head = malloc(fsmap_sizeof(map_size));
if (head == NULL) {
fprintf(stderr, _("%s: malloc of %zu bytes failed.\n"),
progname, fsmap_sizeof(map_size));
exitcode = 1;
return 0;
}
memset(head, 0, sizeof(*head));
l = head->fmh_keys;
h = head->fmh_keys + 1;
if (dflag) {
l->fmr_device = h->fmr_device = file->fs_path.fs_datadev;
} else if (lflag) {
l->fmr_device = h->fmr_device = file->fs_path.fs_logdev;
} else if (rflag) {
l->fmr_device = h->fmr_device = file->fs_path.fs_rtdev;
} else {
l->fmr_device = 0;
h->fmr_device = UINT_MAX;
}
l->fmr_physical = start;
h->fmr_physical = end;
h->fmr_owner = ULLONG_MAX;
h->fmr_flags = UINT_MAX;
h->fmr_offset = ULLONG_MAX;
/*
* If this is an XFS filesystem, remember the data device.
* (We report AG number/block for data device extents on XFS).
*/
if (!tab_init) {
fs_table_initialise(0, NULL, 0, NULL);
tab_init = true;
}
fs = fs_table_lookup(file->name, FS_MOUNT_POINT);
xfs_data_dev = fs ? fs->fs_datadev : 0;
head->fmh_count = map_size;
do {
/* Get some extents */
i = ioctl(file->fd, FS_IOC_GETFSMAP, head);
if (i < 0) {
fprintf(stderr, _("%s: xfsctl(XFS_IOC_GETFSMAP)"
" iflags=0x%x [\"%s\"]: %s\n"),
progname, head->fmh_iflags, file->name,
strerror(errno));
free(head);
exitcode = 1;
return 0;
}
if (head->fmh_entries == 0)
break;
if (vflag)
dump_map_verbose(&nr, head, &dumped_flags, &fsgeo);
else if (mflag)
dump_map_machine(&nr, head);
else
dump_map(&nr, head);
p = &head->fmh_recs[head->fmh_entries - 1];
if (p->fmr_flags & FMR_OF_LAST)
break;
fsmap_advance(head);
} while (true);
if (dumped_flags)
dump_verbose_key();
free(head);
return 0;
}
void
fsmap_init(void)
{
fsmap_cmd.name = "fsmap";
fsmap_cmd.cfunc = fsmap_f;
fsmap_cmd.argmin = 0;
fsmap_cmd.argmax = -1;
fsmap_cmd.flags = CMD_NOMAP_OK | CMD_FLAG_FOREIGN_OK;
fsmap_cmd.args = _("[-d|-l|-r] [-m|-v] [-n nx] [start] [end]");
fsmap_cmd.oneline = _("print filesystem mapping for a range of blocks");
fsmap_cmd.help = fsmap_help;
add_command(&fsmap_cmd);
}