| // 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); |
| } |