Add a new command, 'fsmap', to xfs_io so that we can query the filesystem extent map on a live filesystem. Signed-off-by: Darrick J. Wong <darrick.wong@xxxxxxxxxx> --- io/Makefile | 2 io/fsmap.c | 485 +++++++++++++++++++++++++++++++++++++++++++++++++++++ io/init.c | 1 io/io.h | 1 man/man8/xfs_io.8 | 47 +++++ 5 files changed, 535 insertions(+), 1 deletion(-) create mode 100644 io/fsmap.c diff --git a/io/Makefile b/io/Makefile index 0b53f41..6439e1d 100644 --- a/io/Makefile +++ b/io/Makefile @@ -11,7 +11,7 @@ HFILES = init.h io.h CFILES = init.c \ attr.c bmap.c file.c freeze.c fsync.c getrusage.c imap.c link.c \ mmap.c open.c parent.c pread.c prealloc.c pwrite.c seek.c shutdown.c \ - sync.c truncate.c reflink.c + sync.c truncate.c reflink.c fsmap.c LLDLIBS = $(LIBXCMD) $(LIBHANDLE) LTDEPENDENCIES = $(LIBXCMD) $(LIBHANDLE) diff --git a/io/fsmap.c b/io/fsmap.c new file mode 100644 index 0000000..bf72555 --- /dev/null +++ b/io/fsmap.c @@ -0,0 +1,485 @@ +/* + * Copyright (c) 2016 Oracle. + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it would 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 the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "platform_defs.h" +#include "command.h" +#include "init.h" +#include "io.h" +#include "input.h" + +static cmdinfo_t fsmap_cmd; + +static void +fsmap_help(void) +{ + printf(_( +"\n" +" prints the block mapping for an XFS filesystem" +"\n" +" Example:\n" +" 'fsmap -vp' - tabular format verbose map, including unwritten extents\n" +"\n" +" fsmap prints the map of disk blocks used by the whole filesystem.\n" +" The map lists each extent used by the file, as well as regions in the\n" +" filesystem that do not have any corresponding blocks (free space).\n" +" By default, each line of the listing takes the following form:\n" +" extent: [startoffset..endoffset] owner startblock..endblock\n" +" All the file offsets and disk blocks are in units of 512-byte blocks.\n" +" -n -- query n extents.\n" +" -v -- Verbose information, specify ag info. Show flags legend on 2nd -v\n" +"\n")); +} + +static int +numlen( + off64_t val) +{ + off64_t tmp; + int len; + + for (len = 0, tmp = val; tmp > 0; tmp = tmp/10) + len++; + return (len == 0 ? 1 : len); +} + +static const char * +special_owner( + __int64_t owner) +{ + switch (owner) { + case FMV_OWN_FREE: + return _("free space"); + case FMV_OWN_UNKNOWN: + return _("unknown"); + case FMV_OWN_FS: + return _("static fs metadata"); + case FMV_OWN_LOG: + return _("journalling log"); + case FMV_OWN_AG: + return _("per-AG metadata"); + case FMV_OWN_INOBT: + return _("inode btree"); + case FMV_OWN_INODES: + return _("inodes"); + case FMV_OWN_REFC: + return _("refcount btree"); + case FMV_OWN_COW: + return _("cow reservation"); + case FMV_OWN_DEFECTIVE: + return _("defective"); + default: + return _("unknown"); + } +} + +static void +dump_map( + unsigned long long nr, + struct getfsmapx *map) +{ + unsigned long long i; + struct getfsmapx *p; + + for (i = 0, p = map + 2; i < map->fmv_entries; i++, p++) { + printf("\t%llu: [%lld..%lld]: ", i + nr, + (long long) p->fmv_block, + (long long)(p->fmv_block + p->fmv_length - 1)); + if (p->fmv_oflags & FMV_OF_SPECIAL_OWNER) + printf("%s", special_owner(p->fmv_owner)); + else if (p->fmv_oflags & FMV_OF_EXTENT_MAP) + printf(_("inode %lld extent map"), + (long long) p->fmv_owner); + else + printf(_("inode %lld %lld..%lld"), + (long long) p->fmv_owner, + (long long) p->fmv_offset, + (long long)(p->fmv_offset + p->fmv_length - 1)); + printf(_(" %lld blocks\n"), + (long long)p->fmv_length); + } +} + +/* + * Verbose mode displays: + * extent: [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_SHARED 01000000 /* shared extent */ +#define FLG_ATTR_FORK 00100000 /* attribute fork */ +#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 getfsmapx *map, + bool *dumped_flags, + struct xfs_fsop_geom *fsgeo) +{ + unsigned long long i; + struct getfsmapx *p; + int agno; + off64_t agoff, bbperag; + int foff_w, boff_w, aoff_w, tot_w, agno_w, own_w, nr_w; + char rbuf[32], bbuf[32], abuf[32], obuf[32], nbuf[32]; + int sunit, swidth; + int flg = 0; + + foff_w = boff_w = aoff_w = own_w = MINRANGE_WIDTH; + nr_w = 4; + tot_w = MINTOT_WIDTH; + bbperag = (off64_t)fsgeo->agblocks * + (off64_t)fsgeo->blocksize / BBSIZE; + sunit = (fsgeo->sunit * fsgeo->blocksize) / BBSIZE; + swidth = (fsgeo->swidth * fsgeo->blocksize) / BBSIZE; + + /* + * Go through the extents and figure out the width + * needed for all columns. + */ + for (i = 0, p = map + 2; i < map->fmv_entries; i++, p++) { + if (p->fmv_oflags & FMV_OF_PREALLOC || + p->fmv_oflags & FMV_OF_ATTR_FORK || + p->fmv_oflags & FMV_OF_SHARED) + flg = 1; + if (sunit && + (p->fmv_block % sunit != 0 || + ((p->fmv_block + p->fmv_length) % sunit) != 0 || + p->fmv_block % swidth != 0 || + ((p->fmv_block + p->fmv_length) % swidth) != 0)) + flg = 1; + if (flg) + *dumped_flags = true; + snprintf(nbuf, sizeof(nbuf), "%llu", nr + i); + nr_w = max(nr_w, strlen(nbuf)); + snprintf(bbuf, sizeof(bbuf), "[%lld..%lld]:", + (long long) p->fmv_block, + (long long)(p->fmv_block + p->fmv_length - 1)); + boff_w = max(boff_w, strlen(bbuf)); + if (p->fmv_oflags & FMV_OF_SPECIAL_OWNER) + own_w = max(own_w, strlen(special_owner(p->fmv_owner))); + else { + snprintf(obuf, sizeof(obuf), "%lld", + (long long)p->fmv_owner); + own_w = max(own_w, strlen(obuf)); + } + if (p->fmv_oflags & FMV_OF_EXTENT_MAP) + foff_w = max(foff_w, strlen(_("extent_map"))); + else if (p->fmv_oflags & FMV_OF_SPECIAL_OWNER) + ; + else { + snprintf(rbuf, sizeof(rbuf), "%lld..%lld", + (long long) p->fmv_offset, + (long long)(p->fmv_offset + p->fmv_length - 1)); + foff_w = max(foff_w, strlen(rbuf)); + } + agno = p->fmv_block / bbperag; + agoff = p->fmv_block - (agno * bbperag); + snprintf(abuf, sizeof(abuf), + "(%lld..%lld)", + (long long)agoff, + (long long)(agoff + p->fmv_length - 1)); + aoff_w = max(aoff_w, strlen(abuf)); + tot_w = max(tot_w, + numlen(p->fmv_length)); + } + agno_w = max(MINAG_WIDTH, numlen(fsgeo->agcount)); + if (nr == 0) + printf("%*s: %-*s %-*s %-*s %*s %-*s %*s%s\n", + nr_w, _("EXT"), + 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 = map + 2; i < map->fmv_entries; i++, p++) { + flg = FLG_NULL; + if (p->fmv_oflags & FMV_OF_PREALLOC) + flg |= FLG_PRE; + if (p->fmv_oflags & FMV_OF_ATTR_FORK) + flg |= FLG_ATTR_FORK; + if (p->fmv_oflags & FMV_OF_SHARED) + flg |= FLG_SHARED; + /* + * If striping enabled, determine if extent starts/ends + * on a stripe unit boundary. + */ + if (sunit) { + if (p->fmv_block % sunit != 0) { + flg |= FLG_BSU; + } + if (((p->fmv_block + + p->fmv_length ) % sunit ) != 0) { + flg |= FLG_ESU; + } + if (p->fmv_block % swidth != 0) { + flg |= FLG_BSW; + } + if (((p->fmv_block + + p->fmv_length ) % swidth ) != 0) { + flg |= FLG_ESW; + } + } + snprintf(bbuf, sizeof(bbuf), "[%lld..%lld]:", + (long long) p->fmv_block, + (long long)(p->fmv_block + p->fmv_length - 1)); + if (p->fmv_oflags & FMV_OF_SPECIAL_OWNER) { + snprintf(obuf, sizeof(obuf), "%s", + special_owner(p->fmv_owner)); + snprintf(rbuf, sizeof(rbuf), " "); + } else { + snprintf(obuf, sizeof(obuf), "%lld", + (long long)p->fmv_owner); + snprintf(rbuf, sizeof(rbuf), "%lld..%lld", + (long long) p->fmv_offset, + (long long)(p->fmv_offset + p->fmv_length - 1)); + } + agno = p->fmv_block / bbperag; + agoff = p->fmv_block - (agno * bbperag); + snprintf(abuf, sizeof(abuf), + "(%lld..%lld)", + (long long)agoff, + (long long)(agoff + p->fmv_length - 1)); + if (p->fmv_oflags & FMV_OF_EXTENT_MAP) + printf("%*llu: %-*s %-*s %-*s %*d %-*s %*lld\n", + nr_w, nr + i, + boff_w, bbuf, + own_w, obuf, + foff_w, _("extent map"), + agno_w, agno, + aoff_w, abuf, + tot_w, (long long)p->fmv_length); + else { + printf("%*llu: %-*s %-*s %-*s", nr_w, nr + i, + boff_w, bbuf, own_w, obuf, foff_w, rbuf); + printf(" %*d %-*s", agno_w, agno, + aoff_w, abuf); + printf(" %*lld", tot_w, + (long long)p->fmv_length); + if (flg == FLG_NULL) { + printf("\n"); + } else { + printf(" %-*.*o\n", NFLG, NFLG, flg); + } + } + } +} + +static void +dump_verbose_key(void) +{ + printf(_(" FLAG Values:\n")); + printf(_(" %*.*o Shared extent\n"), + NFLG+1, NFLG+1, FLG_SHARED); + printf(_(" %*.*o Attribute fork\n"), + NFLG+1, NFLG+1, FLG_ATTR_FORK); + 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); +} + +int +fsmap_f( + int argc, + char **argv) +{ + struct getfsmapx *p; + struct getfsmapx *nmap; + struct getfsmapx *map; + struct xfs_fsop_geom fsgeo; + long long start = 0; + long long end = -1; + int nmap_size; + int map_size; + int nflag = 0; + int vflag = 0; + int fmv_iflags = 0; /* flags for XFS_IOC_GETFSMAPX */ + int i = 0; + int c; + unsigned long long nr = 0; + size_t fsblocksize, fssectsize; + bool dumped_flags = false; + + init_cvtnum(&fsblocksize, &fssectsize); + + while ((c = getopt(argc, argv, "n:v")) != EOF) { + switch (c) { + case 'n': /* number of extents specified */ + nflag = atoi(optarg); + break; + case 'v': /* Verbose output */ + vflag++; + break; + default: + return command_usage(&fsmap_cmd); + } + } + + if (argc > optind) { + start = cvtnum(fsblocksize, fssectsize, argv[optind]); + if (start < 0) { + fprintf(stderr, + _("Bad rmap start_fsb %s.\n"), + argv[optind]); + return 0; + } + } + + if (argc > optind + 1) { + end = cvtnum(fsblocksize, fssectsize, argv[optind + 1]); + if (end < 0) { + fprintf(stderr, + _("Bad rmap end_fsb %s.\n"), + argv[optind + 1]); + return 0; + } + } + + if (vflag) { + c = xfsctl(file->name, file->fd, XFS_IOC_FSGEOMETRY_V1, &fsgeo); + if (c < 0) { + fprintf(stderr, + _("%s: can't get geometry [\"%s\"]: %s\n"), + progname, file->name, strerror(errno)); + exitcode = 1; + return 0; + } + } + + map_size = nflag ? nflag + 2 : 32; /* initial guess - 32 */ + map = malloc(map_size * sizeof(*map)); + if (map == NULL) { + fprintf(stderr, _("%s: malloc of %lu bytes failed.\n"), + progname, map_size * sizeof(*map)); + exitcode = 1; + return 0; + } + + map->fmv_iflags = fmv_iflags; + map->fmv_device = FMV_DEV_DEFAULT; + map->fmv_block = start / 512; + map->fmv_owner = 0; + map->fmv_offset = 0; + map->fmv_length = 0; + (map + 1)->fmv_device = ULLONG_MAX; + (map + 1)->fmv_block = (unsigned long long)end / 512; + (map + 1)->fmv_owner = ULLONG_MAX; + (map + 1)->fmv_offset = ULLONG_MAX; + + /* Count mappings */ + if (!nflag) { + map->fmv_count = 2; + i = xfsctl(file->name, file->fd, XFS_IOC_GETFSMAPX, map); + if (i < 0) { + fprintf(stderr, _("%s: xfsctl(XFS_IOC_GETFSMAPX)" + " iflags=0x%x [\"%s\"]: %s\n"), + progname, map->fmv_iflags, file->name, + strerror(errno)); + free(map); + exitcode = 1; + return 0; + } + if (map->fmv_entries > map_size * 2) { + unsigned long long nr; + + nr = 5ULL * map->fmv_entries / 4 + 2; + nmap_size = nr > INT_MAX ? INT_MAX : nr; + nmap = realloc(map, nmap_size * sizeof(*map)); + if (nmap == NULL) { + fprintf(stderr, + _("%s: cannot realloc %lu bytes\n"), + progname, map_size*sizeof(*map)); + } else { + map = nmap; + map_size = nmap_size; + } + } + } + + map->fmv_count = map_size; + do { + /* Get some extents */ + i = xfsctl(file->name, file->fd, XFS_IOC_GETFSMAPX, map); + if (i < 0) { + fprintf(stderr, _("%s: xfsctl(XFS_IOC_GETFSMAPX)" + " iflags=0x%x [\"%s\"]: %s\n"), + progname, map->fmv_iflags, file->name, + strerror(errno)); + free(map); + exitcode = 1; + return 0; + } + + if (map->fmv_entries == 0) + break; + + if (!vflag) + dump_map(nr, map); + else + dump_map_verbose(nr, map, &dumped_flags, &fsgeo); + + p = map + 1 + map->fmv_entries; + if (p->fmv_oflags & FMV_OF_LAST) + break; + + nr += map->fmv_entries; + map->fmv_device = p->fmv_device; + map->fmv_block = p->fmv_block; + map->fmv_owner = p->fmv_owner; + map->fmv_offset = p->fmv_offset; + map->fmv_oflags = p->fmv_oflags; + map->fmv_length = p->fmv_length; + } while(true); + + if (dumped_flags) + dump_verbose_key(); + + free(map); + 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; + fsmap_cmd.args = _("[-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); +} diff --git a/io/init.c b/io/init.c index 51f1f5c..4ae8274 100644 --- a/io/init.c +++ b/io/init.c @@ -60,6 +60,7 @@ init_commands(void) file_init(); flink_init(); freeze_init(); + fsmap_init(); fsync_init(); getrusage_init(); help_init(); diff --git a/io/io.h b/io/io.h index 172b1f8..cef1763 100644 --- a/io/io.h +++ b/io/io.h @@ -97,6 +97,7 @@ extern void bmap_init(void); extern void file_init(void); extern void flink_init(void); extern void freeze_init(void); +extern void fsmap_init(void); extern void fsync_init(void); extern void getrusage_init(void); extern void help_init(void); diff --git a/man/man8/xfs_io.8 b/man/man8/xfs_io.8 index 34f90c7..60c46ee 100644 --- a/man/man8/xfs_io.8 +++ b/man/man8/xfs_io.8 @@ -267,6 +267,53 @@ ioctl. Options behave as described in the .BR xfs_bmap (8) manual page. .TP +.BI "fsmap [ \-v ] [ \-n " nx " ] [ " start " ] [ " end " ] +Prints the mapping of disk blocks used by an XFS filesystem. The map +lists each extent used by files, allocation group metadata, +journalling logs, and static filesystem metadata, as well as any +regions that are unused. Each line of the listings takes the +following form: +.PP +.RS +.IR extent ": [" startblock .. endblock "]: " owner " " startoffset .. endoffset " " length +.PP +Static filesystem metadata, allocation group metadata, btrees, +journalling logs, and free space are marked by replacing the +.IR startoffset .. endoffset +with the appropriate marker. All blocks, offsets, and lengths are specified +in units of 512-byte blocks, no matter what the filesystem's block size is. +.BI "The optional " start " and " end " arguments can be used to constrain +the output to a particular range of disk blocks. +.RE +.RS 1.0i +.PD 0 +.TP +.BI \-n " num_extents" +If this option is given, +.B xfs_fsmap +obtains the extent list of the file in groups of +.I num_extents +extents. In the absence of +.BR \-n ", " xfs_fsmap +queries the system for the number of extents in the filesystem and uses that +value to compute the group size. +.TP +.B \-v +Shows verbose information. When this flag is specified, additional AG +specific information is appended to each line in the following form: +.IP +.RS 1.2i +.IR agno " (" startagblock .. endagblock ") " nblocks " " flags +.RE +.IP +A second +.B \-v +option will print out the +.I flags +legend. +.RE +.PD +.TP .BI "extsize [ \-R | \-D ] [ " value " ]" Display and/or modify the preferred extent size used when allocating space for the currently open file. If the -- To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html