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