|  | // SPDX-License-Identifier: GPL-2.0 | 
|  | /* | 
|  | * Copyright (c) 2000-2005 Silicon Graphics, Inc. | 
|  | * All Rights Reserved. | 
|  | */ | 
|  |  | 
|  | #include "platform_defs.h" | 
|  | #include "command.h" | 
|  | #include "input.h" | 
|  | #include "init.h" | 
|  | #include "io.h" | 
|  | #include "libfrog/fsgeom.h" | 
|  |  | 
|  | static cmdinfo_t bmap_cmd; | 
|  |  | 
|  | static void | 
|  | bmap_help(void) | 
|  | { | 
|  | printf(_( | 
|  | "\n" | 
|  | " prints the block mapping for an XFS file's data or attribute forks" | 
|  | "\n" | 
|  | " Example:\n" | 
|  | " 'bmap -vp' - tabular format verbose map, including unwritten extents\n" | 
|  | "\n" | 
|  | " bmap prints the map of disk blocks used by the current file.\n" | 
|  | " The map lists each extent used by the file, as well as regions in the\n" | 
|  | " file that do not have any corresponding blocks (holes).\n" | 
|  | " By default, each line of the listing takes the following form:\n" | 
|  | "     extent: [startoffset..endoffset]: startblock..endblock\n" | 
|  | " Holes are marked by replacing the startblock..endblock with 'hole'.\n" | 
|  | " All the file offsets and disk blocks are in units of 512-byte blocks.\n" | 
|  | " -a -- prints the attribute fork map instead of the data fork.\n" | 
|  | " -c -- prints the copy-on-write fork map instead of the data fork.\n" | 
|  | "       This works only if the kernel was compiled in debug mode.\n" | 
|  | " -e -- print delayed allocation extents.\n" | 
|  | " -l -- also displays the length of each extent in 512-byte blocks.\n" | 
|  | " -n -- query n extents.\n" | 
|  | " -p -- obtain all unwritten extents as well (w/ -v show which are unwritten.)\n" | 
|  | " -v -- Verbose information, specify ag info.  Show flags legend on 2nd -v\n" | 
|  | " Note: the bmap for non-regular files can be obtained provided the file\n" | 
|  | " was opened appropriately (in particular, must be opened read-only).\n" | 
|  | "\n")); | 
|  | } | 
|  |  | 
|  | static int | 
|  | bmap_f( | 
|  | int			argc, | 
|  | char			**argv) | 
|  | { | 
|  | struct fsxattr		fsx; | 
|  | struct getbmapx		*map; | 
|  | struct xfs_fsop_geom	fsgeo; | 
|  | int			map_size; | 
|  | int			loop = 0; | 
|  | int			flg = 0; | 
|  | int			aflag = 0; | 
|  | int			cflag = 0; | 
|  | int			lflag = 0; | 
|  | int			nflag = 0; | 
|  | int			pflag = 0; | 
|  | int			vflag = 0; | 
|  | int			is_rt = 0; | 
|  | int			bmv_iflags = 0;	/* flags for XFS_IOC_GETBMAPX */ | 
|  | int			i = 0; | 
|  | int			c; | 
|  | int			egcnt; | 
|  |  | 
|  | while ((c = getopt(argc, argv, "aceln:pv")) != EOF) { | 
|  | switch (c) { | 
|  | case 'a':	/* Attribute fork. */ | 
|  | bmv_iflags |= BMV_IF_ATTRFORK; | 
|  | aflag = 1; | 
|  | break; | 
|  | case 'c':	/* CoW fork. */ | 
|  | bmv_iflags |= BMV_IF_COWFORK | BMV_IF_DELALLOC; | 
|  | cflag = 1; | 
|  | break; | 
|  | case 'e': | 
|  | bmv_iflags |= BMV_IF_DELALLOC; | 
|  | break; | 
|  | case 'l':	/* list number of blocks with each extent */ | 
|  | lflag = 1; | 
|  | break; | 
|  | case 'n':	/* number of extents specified */ | 
|  | nflag = atoi(optarg); | 
|  | break; | 
|  | case 'p': | 
|  | /* report unwritten preallocated blocks */ | 
|  | pflag = 1; | 
|  | bmv_iflags |= BMV_IF_PREALLOC; | 
|  | break; | 
|  | case 'v':	/* Verbose output */ | 
|  | vflag++; | 
|  | break; | 
|  | default: | 
|  | return command_usage(&bmap_cmd); | 
|  | } | 
|  | } | 
|  | if (aflag || cflag) | 
|  | bmv_iflags &= ~BMV_IF_PREALLOC; | 
|  |  | 
|  | 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; | 
|  | } | 
|  | c = xfsctl(file->name, file->fd, FS_IOC_FSGETXATTR, &fsx); | 
|  | if (c < 0) { | 
|  | fprintf(stderr, | 
|  | _("%s: cannot read attrs on \"%s\": %s\n"), | 
|  | progname, file->name, strerror(errno)); | 
|  | exitcode = 1; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | if (fsx.fsx_xflags & FS_XFLAG_REALTIME) { | 
|  | /* | 
|  | * ag info not applicable to rt, continue | 
|  | * without ag output. | 
|  | */ | 
|  | is_rt = 1; | 
|  | } | 
|  | } | 
|  |  | 
|  | map_size = nflag ? nflag+2 : 32;	/* initial guess - 32 */ | 
|  | map = malloc(map_size*sizeof(*map)); | 
|  | if (map == NULL) { | 
|  | fprintf(stderr, _("%s: malloc of %d bytes failed.\n"), | 
|  | progname, (int)(map_size * sizeof(*map))); | 
|  | exitcode = 1; | 
|  | return 0; | 
|  | } | 
|  |  | 
|  |  | 
|  | /*	Try the xfsctl(XFS_IOC_GETBMAPX) for the number of extents specified | 
|  | *	by nflag, or the initial guess number of extents (32). | 
|  | * | 
|  | *	If there are more extents than we guessed, use xfsctl | 
|  | *	(FS_IOC_FSGETXATTR[A]) to get the extent count, realloc some more | 
|  | *	space based on this count, and try again. | 
|  | * | 
|  | *	If the initial FGETBMAPX attempt returns EINVAL, this may mean | 
|  | *	that we tried the FGETBMAPX on a zero length file.  If we get | 
|  | *	EINVAL, check the length with fstat() and return "no extents" | 
|  | *	if the length == 0. | 
|  | * | 
|  | *	Why not do the xfsctl(FS_IOC_FSGETXATTR[A]) first? | 
|  | *	The extent count may be wrong for a file with delayed | 
|  | *	allocation blocks.  The XFS_IOC_GETBMAPX forces the real | 
|  | *	allocation and fixes up the extent count. | 
|  | */ | 
|  |  | 
|  | do {	/* loop a miximum of two times */ | 
|  |  | 
|  | memset(map, 0, sizeof(*map));	/* zero header */ | 
|  |  | 
|  | map->bmv_length = -1; | 
|  | map->bmv_count = map_size; | 
|  | map->bmv_iflags = bmv_iflags; | 
|  |  | 
|  | i = xfsctl(file->name, file->fd, XFS_IOC_GETBMAPX, map); | 
|  | if (i < 0) { | 
|  | if (   errno == EINVAL | 
|  | && !aflag && filesize() == 0) { | 
|  | break; | 
|  | } else	{ | 
|  | fprintf(stderr, _("%s: xfsctl(XFS_IOC_GETBMAPX)" | 
|  | " iflags=0x%x [\"%s\"]: %s\n"), | 
|  | progname, map->bmv_iflags, file->name, | 
|  | strerror(errno)); | 
|  | free(map); | 
|  | exitcode = 1; | 
|  | return 0; | 
|  | } | 
|  | } | 
|  | if (nflag) | 
|  | break; | 
|  | if (map->bmv_entries < map->bmv_count-1) | 
|  | break; | 
|  | /* Get number of extents from xfsctl FS_IOC_FSGETXATTR[A] | 
|  | * syscall. | 
|  | */ | 
|  | i = xfsctl(file->name, file->fd, aflag ? | 
|  | XFS_IOC_FSGETXATTRA : FS_IOC_FSGETXATTR, &fsx); | 
|  | if (i < 0) { | 
|  | fprintf(stderr, "%s: xfsctl(FS_IOC_FSGETXATTR%s) " | 
|  | "[\"%s\"]: %s\n", progname, aflag ? "A" : "", | 
|  | file->name, strerror(errno)); | 
|  | free(map); | 
|  | exitcode = 1; | 
|  | return 0; | 
|  | } | 
|  | if (2 * fsx.fsx_nextents > map_size) { | 
|  | map_size = 2 * fsx.fsx_nextents + 1; | 
|  | map = realloc(map, map_size*sizeof(*map)); | 
|  | if (map == NULL) { | 
|  | fprintf(stderr, | 
|  | _("%s: cannot realloc %d bytes\n"), | 
|  | progname, (int)(map_size*sizeof(*map))); | 
|  | exitcode = 1; | 
|  | return 0; | 
|  | } | 
|  | } | 
|  | } while (++loop < 2); | 
|  | if (!nflag) { | 
|  | if (map->bmv_entries <= 0) { | 
|  | printf(_("%s: no extents\n"), file->name); | 
|  | free(map); | 
|  | return 0; | 
|  | } | 
|  | } | 
|  | egcnt = nflag ? min(nflag, map->bmv_entries) : map->bmv_entries; | 
|  | printf("%s:\n", file->name); | 
|  | if (!vflag) { | 
|  | for (i = 0; i < egcnt; i++) { | 
|  | printf("\t%d: [%lld..%lld]: ", i, | 
|  | (long long) map[i + 1].bmv_offset, | 
|  | (long long)(map[i + 1].bmv_offset + | 
|  | map[i + 1].bmv_length - 1LL)); | 
|  | if (map[i + 1].bmv_block == -1) | 
|  | printf(_("hole")); | 
|  | else if (map[i + 1].bmv_block == -2) | 
|  | printf(_("delalloc")); | 
|  | else { | 
|  | printf("%lld..%lld", | 
|  | (long long) map[i + 1].bmv_block, | 
|  | (long long)(map[i + 1].bmv_block + | 
|  | map[i + 1].bmv_length - 1LL)); | 
|  |  | 
|  | } | 
|  | if (lflag) | 
|  | printf(_(" %lld blocks\n"), | 
|  | (long long)map[i+1].bmv_length); | 
|  | else | 
|  | printf("\n"); | 
|  | } | 
|  | } else { | 
|  | /* | 
|  | * Verbose mode displays: | 
|  | *   extent: [startoffset..endoffset]: startblock..endblock \ | 
|  | *	ag# (agoffset..agendoffset) totalbbs | 
|  | */ | 
|  | #define MINRANGE_WIDTH	16 | 
|  | #define MINAG_WIDTH	2 | 
|  | #define MINTOT_WIDTH	5 | 
|  | #define NFLG		6	/* count of flags */ | 
|  | #define	FLG_NULL	0000000	/* Null flag */ | 
|  | #define	FLG_SHARED	0100000	/* shared extent */ | 
|  | #define	FLG_PRE		0010000	/* Unwritten extent */ | 
|  | #define	FLG_BSU		0001000	/* Not on begin of stripe unit  */ | 
|  | #define	FLG_ESU		0000100	/* Not on end   of stripe unit  */ | 
|  | #define	FLG_BSW		0000010	/* Not on begin of stripe width */ | 
|  | #define	FLG_ESW		0000001	/* Not on end   of stripe width */ | 
|  | int	agno; | 
|  | off64_t agoff, bbperag; | 
|  | int	foff_w, boff_w, aoff_w, tot_w, agno_w; | 
|  | char	rbuf[32], bbuf[32], abuf[32]; | 
|  | int	sunit, swidth; | 
|  |  | 
|  | foff_w = boff_w = aoff_w = MINRANGE_WIDTH; | 
|  | tot_w = MINTOT_WIDTH; | 
|  | if (is_rt) | 
|  | sunit = swidth = bbperag = 0; | 
|  | else { | 
|  | bbperag = (off64_t)fsgeo.agblocks * | 
|  | (off64_t)fsgeo.blocksize / BBSIZE; | 
|  | sunit = (fsgeo.sunit * fsgeo.blocksize) / BBSIZE; | 
|  | swidth = (fsgeo.swidth * fsgeo.blocksize) / BBSIZE; | 
|  | } | 
|  | flg = sunit | pflag; | 
|  |  | 
|  | /* | 
|  | * Go through the extents and figure out the width | 
|  | * needed for all columns. | 
|  | */ | 
|  | for (i = 0; i < egcnt; i++) { | 
|  | snprintf(rbuf, sizeof(rbuf), "[%lld..%lld]:", | 
|  | (long long) map[i + 1].bmv_offset, | 
|  | (long long)(map[i + 1].bmv_offset + | 
|  | map[i + 1].bmv_length - 1LL)); | 
|  | if (map[i + 1].bmv_oflags & BMV_OF_PREALLOC) | 
|  | flg = 1; | 
|  | if (map[i + 1].bmv_block == -1) { | 
|  | foff_w = max(foff_w, strlen(rbuf)); | 
|  | tot_w = max(tot_w, | 
|  | numlen(map[i+1].bmv_length, 10)); | 
|  | } else { | 
|  | snprintf(bbuf, sizeof(bbuf), "%lld..%lld", | 
|  | (long long) map[i + 1].bmv_block, | 
|  | (long long)(map[i + 1].bmv_block + | 
|  | map[i + 1].bmv_length - 1LL)); | 
|  | boff_w = max(boff_w, strlen(bbuf)); | 
|  | if (!is_rt) { | 
|  | agno = map[i + 1].bmv_block / bbperag; | 
|  | agoff = map[i + 1].bmv_block - | 
|  | (agno * bbperag); | 
|  | snprintf(abuf, sizeof(abuf), | 
|  | "(%lld..%lld)", | 
|  | (long long)agoff, | 
|  | (long long)(agoff + | 
|  | map[i + 1].bmv_length - 1LL)); | 
|  | aoff_w = max(aoff_w, strlen(abuf)); | 
|  | } else | 
|  | aoff_w = 0; | 
|  | foff_w = max(foff_w, strlen(rbuf)); | 
|  | tot_w = max(tot_w, | 
|  | numlen(map[i+1].bmv_length, 10)); | 
|  | } | 
|  | } | 
|  | agno_w = is_rt ? 0 : max(MINAG_WIDTH, numlen(fsgeo.agcount, 10)); | 
|  | printf("%4s: %-*s %-*s %*s %-*s %*s%s\n", | 
|  | _("EXT"), | 
|  | foff_w, _("FILE-OFFSET"), | 
|  | boff_w, is_rt ? _("RT-BLOCK-RANGE") : _("BLOCK-RANGE"), | 
|  | agno_w, is_rt ? "" : _("AG"), | 
|  | aoff_w, is_rt ? "" : _("AG-OFFSET"), | 
|  | tot_w, _("TOTAL"), | 
|  | flg ? _(" FLAGS") : ""); | 
|  | for (i = 0; i < egcnt; i++) { | 
|  | flg = FLG_NULL; | 
|  | if (map[i + 1].bmv_oflags & BMV_OF_PREALLOC) { | 
|  | flg |= FLG_PRE; | 
|  | } | 
|  | if (map[i + 1].bmv_oflags & BMV_OF_SHARED) | 
|  | flg |= FLG_SHARED; | 
|  | if (map[i + 1].bmv_oflags & BMV_OF_DELALLOC) | 
|  | map[i + 1].bmv_block = -2; | 
|  | /* | 
|  | * If striping enabled, determine if extent starts/ends | 
|  | * on a stripe unit boundary. | 
|  | */ | 
|  | if (sunit) { | 
|  | if (map[i + 1].bmv_block  % sunit != 0) { | 
|  | flg |= FLG_BSU; | 
|  | } | 
|  | if (((map[i + 1].bmv_block + | 
|  | map[i + 1].bmv_length ) % sunit ) != 0) { | 
|  | flg |= FLG_ESU; | 
|  | } | 
|  | if (map[i + 1].bmv_block % swidth != 0) { | 
|  | flg |= FLG_BSW; | 
|  | } | 
|  | if (((map[i + 1].bmv_block + | 
|  | map[i + 1].bmv_length ) % swidth ) != 0) { | 
|  | flg |= FLG_ESW; | 
|  | } | 
|  | } | 
|  | snprintf(rbuf, sizeof(rbuf), "[%lld..%lld]:", | 
|  | (long long) map[i + 1].bmv_offset, | 
|  | (long long)(map[i + 1].bmv_offset + | 
|  | map[i + 1].bmv_length - 1LL)); | 
|  | if (map[i + 1].bmv_block == -1) { | 
|  | printf("%4d: %-*s %-*s %*s %-*s %*lld\n", | 
|  | i, | 
|  | foff_w, rbuf, | 
|  | boff_w, _("hole"), | 
|  | agno_w, "", | 
|  | aoff_w, "", | 
|  | tot_w, (long long)map[i+1].bmv_length); | 
|  | } else if (map[i + 1].bmv_block == -2) { | 
|  | printf("%4d: %-*s %-*s %*s %-*s %*lld\n", | 
|  | i, | 
|  | foff_w, rbuf, | 
|  | boff_w, _("delalloc"), | 
|  | agno_w, "", | 
|  | aoff_w, "", | 
|  | tot_w, (long long)map[i+1].bmv_length); | 
|  | } else { | 
|  | snprintf(bbuf, sizeof(bbuf), "%lld..%lld", | 
|  | (long long) map[i + 1].bmv_block, | 
|  | (long long)(map[i + 1].bmv_block + | 
|  | map[i + 1].bmv_length - 1LL)); | 
|  | printf("%4d: %-*s %-*s", i, foff_w, rbuf, | 
|  | boff_w, bbuf); | 
|  | if (!is_rt) { | 
|  | agno = map[i + 1].bmv_block / bbperag; | 
|  | agoff = map[i + 1].bmv_block - | 
|  | (agno * bbperag); | 
|  | snprintf(abuf, sizeof(abuf), | 
|  | "(%lld..%lld)", | 
|  | (long long)agoff, | 
|  | (long long)(agoff + | 
|  | map[i + 1].bmv_length - 1LL)); | 
|  | printf(" %*d %-*s", agno_w, agno, | 
|  | aoff_w, abuf); | 
|  | } else | 
|  | printf("  "); | 
|  | printf(" %*lld", tot_w, | 
|  | (long long)map[i+1].bmv_length); | 
|  | if (flg == FLG_NULL && !pflag) { | 
|  | printf("\n"); | 
|  | } else { | 
|  | printf(" %-*.*o\n", NFLG, NFLG, flg); | 
|  | } | 
|  | } | 
|  | } | 
|  | if ((flg || pflag) && vflag > 1) { | 
|  | printf(_(" FLAG Values:\n")); | 
|  | 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); | 
|  | } | 
|  | } | 
|  | free(map); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | void | 
|  | bmap_init(void) | 
|  | { | 
|  | bmap_cmd.name = "bmap"; | 
|  | bmap_cmd.cfunc = bmap_f; | 
|  | bmap_cmd.argmin = 0; | 
|  | bmap_cmd.argmax = -1; | 
|  | bmap_cmd.flags = CMD_NOMAP_OK; | 
|  | bmap_cmd.args = _("[-acelpv] [-n nx]"); | 
|  | bmap_cmd.oneline = _("print block mapping for an XFS file"); | 
|  | bmap_cmd.help = bmap_help; | 
|  |  | 
|  | add_command(&bmap_cmd); | 
|  | } |