diff mbox series

[1/4] xfs_io: add a bulkstat command

Message ID 156944718001.297551.8841062987630720604.stgit@magnolia (mailing list archive)
State Superseded
Headers show
Series xfsprogs: bulkstat v5 | expand

Commit Message

Darrick J. Wong Sept. 25, 2019, 9:33 p.m. UTC
From: Darrick J. Wong <darrick.wong@oracle.com>

Add a bulkstat command to xfs_io so that we can test our new xfrog code.

Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
---
 io/Makefile        |    9 -
 io/bulkstat.c      |  522 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 io/init.c          |    1 
 io/io.h            |    1 
 libfrog/bulkstat.c |   20 ++
 libfrog/bulkstat.h |    3 
 man/man8/xfs_io.8  |   66 +++++++
 7 files changed, 618 insertions(+), 4 deletions(-)
 create mode 100644 io/bulkstat.c

Comments

Eric Sandeen Sept. 26, 2019, 10:40 p.m. UTC | #1
On 9/25/19 4:33 PM, Darrick J. Wong wrote:
> From: Darrick J. Wong <darrick.wong@oracle.com>
> 
> Add a bulkstat command to xfs_io so that we can test our new xfrog code.
> 
> Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
> ---
>  io/Makefile        |    9 -
>  io/bulkstat.c      |  522 ++++++++++++++++++++++++++++++++++++++++++++++++++++
>  io/init.c          |    1 
>  io/io.h            |    1 
>  libfrog/bulkstat.c |   20 ++
>  libfrog/bulkstat.h |    3 
>  man/man8/xfs_io.8  |   66 +++++++
>  7 files changed, 618 insertions(+), 4 deletions(-)
>  create mode 100644 io/bulkstat.c
> 
> 
> diff --git a/io/Makefile b/io/Makefile
> index 484e2b5a..1112605e 100644
> --- a/io/Makefile
> +++ b/io/Makefile
> @@ -9,10 +9,11 @@ LTCOMMAND = xfs_io
>  LSRCFILES = xfs_bmap.sh xfs_freeze.sh xfs_mkfile.sh
>  HFILES = init.h io.h
>  CFILES = init.c \
> -	attr.c bmap.c crc32cselftest.c cowextsize.c encrypt.c file.c freeze.c \
> -	fsync.c getrusage.c imap.c inject.c label.c link.c mmap.c open.c \
> -	parent.c pread.c prealloc.c pwrite.c reflink.c resblks.c scrub.c \
> -	seek.c shutdown.c stat.c swapext.c sync.c truncate.c utimes.c
> +	attr.c bmap.c bulkstat.c crc32cselftest.c cowextsize.c encrypt.c \
> +	file.c freeze.c fsync.c getrusage.c imap.c inject.c label.c link.c \
> +	mmap.c open.c parent.c pread.c prealloc.c pwrite.c reflink.c \
> +	resblks.c scrub.c seek.c shutdown.c stat.c swapext.c sync.c \
> +	truncate.c utimes.c
>  
>  LLDLIBS = $(LIBXCMD) $(LIBHANDLE) $(LIBFROG) $(LIBPTHREAD)
>  LTDEPENDENCIES = $(LIBXCMD) $(LIBHANDLE) $(LIBFROG)
> diff --git a/io/bulkstat.c b/io/bulkstat.c
> new file mode 100644
> index 00000000..625f0abe
> --- /dev/null
> +++ b/io/bulkstat.c
> @@ -0,0 +1,522 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright (C) 2019 Oracle.  All Rights Reserved.
> + * Author: Darrick J. Wong <darrick.wong@oracle.com>
> + */
> +#include "xfs.h"
> +#include "platform_defs.h"
> +#include "command.h"
> +#include "init.h"
> +#include "libfrog/fsgeom.h"
> +#include "libfrog/bulkstat.h"
> +#include "libfrog/paths.h"
> +#include "io.h"
> +#include "input.h"
> +
> +static bool debug;

since I'm old and my brain is weakening... do you need to turn this global
debug back off in each foo_f function unless the -d debug option was specified?

> +
> +static void
> +dump_bulkstat_time(
> +	const char		*tag,
> +	uint64_t		sec,
> +	uint32_t		nsec)
> +{
> +	printf("\t%s = %"PRIu64".%"PRIu32"\n", tag, sec, nsec);
> +}
> +
> +static void
> +dump_bulkstat(
> +	struct xfs_bulkstat	*bstat)
> +{
> +	printf("bs_ino = %"PRIu64"\n", bstat->bs_ino);
> +	printf("\tbs_size = %"PRIu64"\n", bstat->bs_size);
> +
> +	printf("\tbs_blocks = %"PRIu64"\n", bstat->bs_blocks);
> +	printf("\tbs_xflags = 0x%"PRIx64"\n", bstat->bs_xflags);
> +
> +	dump_bulkstat_time("bs_atime", bstat->bs_atime, bstat->bs_atime_nsec);
> +	dump_bulkstat_time("bs_ctime", bstat->bs_ctime, bstat->bs_ctime_nsec);
> +	dump_bulkstat_time("bs_mtime", bstat->bs_mtime, bstat->bs_mtime_nsec);
> +	dump_bulkstat_time("bs_btime", bstat->bs_btime, bstat->bs_btime_nsec);
> +
> +	printf("\tbs_gen = 0x%"PRIx32"\n", bstat->bs_gen);
> +	printf("\tbs_uid = %"PRIu32"\n", bstat->bs_uid);
> +	printf("\tbs_gid = %"PRIu32"\n", bstat->bs_gid);
> +	printf("\tbs_projectid = %"PRIu32"\n", bstat->bs_projectid);
> +
> +	printf("\tbs_blksize = %"PRIu32"\n", bstat->bs_blksize);
> +	printf("\tbs_rdev = %"PRIu32"\n", bstat->bs_rdev);
> +	printf("\tbs_cowextsize_blks = %"PRIu32"\n", bstat->bs_cowextsize_blks);
> +	printf("\tbs_extsize_blks = %"PRIu32"\n", bstat->bs_extsize_blks);
> +
> +	printf("\tbs_nlink = %"PRIu32"\n", bstat->bs_nlink);
> +	printf("\tbs_extents = %"PRIu32"\n", bstat->bs_extents);
> +	printf("\tbs_aextents = %"PRIu32"\n", bstat->bs_aextents);
> +	printf("\tbs_version = %"PRIu16"\n", bstat->bs_version);
> +	printf("\tbs_forkoff = %"PRIu16"\n", bstat->bs_forkoff);
> +
> +	printf("\tbs_sick = 0x%"PRIx16"\n", bstat->bs_sick);
> +	printf("\tbs_checked = 0x%"PRIx16"\n", bstat->bs_checked);
> +	printf("\tbs_mode = 0%"PRIo16"\n", bstat->bs_mode);
> +};
> +
> +static void
> +bulkstat_help(void)
> +{
> +	printf(_(
> +"Bulk-queries the filesystem for inode stat information and prints it.\n"
> +"\n"
> +"   -a   Only iterate this AG.\n"
> +"   -d   Print debugging output.\n"
> +"   -e   Stop after this inode.\n"
> +"   -n   Ask for this many results at once.\n"
> +"   -s   Inode to start with.\n"
> +"   -v   Use this version of the ioctl (1 or 5).\n"));

+"   -a <agno>  Only iterate this AG.\n"
+"   -d         Print debugging output.\n"
+"   -e <ino>   Stop after this inode.\n"
+"   -n <nr>    Ask for this many results at once.\n"
+"   -s <ino>   Inode to start with.\n"
+"   -v <ver>   Use this version of the ioctl (1 or 5).\n"));

> +}
> +
> +static void
> +set_xfd_flags(
> +	struct xfs_fd	*xfd,
> +	uint32_t	ver)
> +{
> +	switch (ver) {
> +	case 1:
> +		xfd->flags |= XFROG_FLAG_BULKSTAT_FORCE_V1;
> +		break;
> +	case 5:
> +		xfd->flags |= XFROG_FLAG_BULKSTAT_FORCE_V5;
> +		break;
> +	default:
> +		break;
> +	}
> +}
> +
> +static int
> +bulkstat_f(
> +	int			argc,
> +	char			**argv)
> +{
> +	struct xfs_fd		xfd = XFS_FD_INIT(file->fd);
> +	struct xfs_bulkstat_req	*breq;
> +	uint64_t		startino = 0;
> +	uint64_t		endino = -1ULL;
> +	uint32_t		batch_size = 4096;
> +	uint32_t		agno = 0;
> +	uint32_t		ver = 0;
> +	bool			has_agno = false;
> +	unsigned int		i;
> +	int			c;
> +	int			ret;
> +
> +	while ((c = getopt(argc, argv, "a:de:n:s:v:")) != -1) {
> +		switch (c) {
> +		case 'a':
> +			agno = cvt_u32(optarg, 10);
> +			if (errno) {
> +				perror(optarg);
> +				return 1;
> +			}
> +			has_agno = true;
> +			break;
> +		case 'd':
> +			debug = true;
> +			break;
> +		case 'e':
> +			endino = cvt_u64(optarg, 10);
> +			if (errno) {
> +				perror(optarg);
> +				return 1;
> +			}
> +			break;
> +		case 'n':
> +			batch_size = cvt_u32(optarg, 10);
> +			if (errno) {
> +				perror(optarg);
> +				return 1;
> +			}
> +			break;
> +		case 's':
> +			startino = cvt_u64(optarg, 10);
> +			if (errno) {
> +				perror(optarg);
> +				return 1;
> +			}
> +			break;
> +		case 'v':
> +			ver = cvt_u32(optarg, 10);
> +			if (errno) {
> +				perror(optarg);
> +				return 1;
> +			}
> +			if (ver != 1 && ver != 5) {
> +				fprintf(stderr, "version must be 1 or 5.\n");
> +				return 1;
> +			}
> +			break;
> +		default:
> +			bulkstat_help();
> +			return 0;
> +		}
> +	}
> +	if (optind != argc) {
> +		bulkstat_help();
> +		return 0;
> +	}
> +
> +	ret = xfd_prepare_geometry(&xfd);
> +	if (ret) {
> +		errno = ret;
> +		perror("xfd_prepare_geometry");
> +		exitcode = 1;
> +		return 0;
> +	}
> +
> +	breq = xfrog_bulkstat_alloc_req(batch_size, startino);
> +	if (!breq) {
> +		perror("alloc bulkreq");
> +		exitcode = 1;
> +		return 0;
> +	}
> +
> +	if (has_agno)
> +		xfrog_bulkstat_set_ag(breq, agno);
> +
> +	set_xfd_flags(&xfd, ver);
> +
> +	while ((ret = xfrog_bulkstat(&xfd, breq)) == 0) {
> +		if (debug)
> +			printf(
> +_("bulkstat: startino=%lld flags=0x%x agno=%u ret=%d icount=%u ocount=%u\n"),
> +				(long long)breq->hdr.ino,
> +				(unsigned int)breq->hdr.flags,
> +				(unsigned int)breq->hdr.agno,
> +				ret,
> +				(unsigned int)breq->hdr.icount,
> +				(unsigned int)breq->hdr.ocount);
> +		if (breq->hdr.ocount == 0)
> +			break;
> +
> +		for (i = 0; i < breq->hdr.ocount; i++) {
> +			if (breq->bulkstat[i].bs_ino > endino)
> +				break;
> +			dump_bulkstat(&breq->bulkstat[i]);
> +		}
> +	}
> +	if (ret) {
> +		errno = ret;
> +		perror("xfrog_bulkstat");
> +		exitcode = 1;

free(breq) (or just drop the return & fall through ...)

> +		return 0;
> +	}
> +
> +	free(breq);
> +	return 0;
> +}
> +
> +static void
> +bulkstat_single_help(void)
> +{
> +	printf(_(
> +"Queries the filesystem for a single inode's stat information and prints it.\n"
> +"\n"
> +"   -v   Use this version of the ioctl (1 or 5).\n"

+"   -v <ver>   Use this version of the ioctl (1 or 5).\n"));

(I'm realizing all our long help is preeeeettttty free form but still worth
noting that it requires an optarg)

since it can take more than one, I wonder if the man page's text
("individual inodes") is better than "a single inode's"

The other interesting thing is that it takes a start ino but gives you the
first allocated inode >= that number, right:

xfs_io> bulkstat_single 128 129 130
bs_ino = 160
...
bs_ino = 160
...
bs_ino = 160
...

and while that's how the interface works, I wonder if the help and manpage
should be (sigh) more clear about it somehow.

also, probably wants a -d debug option


> +"\n"
> +"Pass in inode numbers or a special inode name:\n"
> +"    root    Root directory.\n"));
> +}
> +
> +struct single_map {
> +	const char		*tag;
> +	uint64_t		code;
> +};
> +
> +struct single_map tags[] = {
> +	{"root", XFS_BULK_IREQ_SPECIAL_ROOT},
> +	{NULL, 0},
> +};
> +
> +static int
> +bulkstat_single_f(
> +	int			argc,
> +	char			**argv)
> +{
> +	struct xfs_fd		xfd = XFS_FD_INIT(file->fd);
> +	struct xfs_bulkstat	bulkstat;
> +	unsigned long		ver = 0;
> +	unsigned int		i;
> +	int			c;
> +	int			ret;
> +
> +	while ((c = getopt(argc, argv, "v:")) != -1) {
> +		switch (c) {
> +		case 'v':
> +			errno = 0;
> +			ver = strtoull(optarg, NULL, 10);
> +			if (errno) {
> +				perror(optarg);
> +				return 1;
> +			}
> +			if (ver != 1 && ver != 5) {
> +				fprintf(stderr, "version must be 1 or 5.\n");
> +				return 1;
> +			}
> +			break;
> +		default:
> +			bulkstat_single_help();
> +			return 0;
> +		}
> +	}
> +
> +	ret = xfd_prepare_geometry(&xfd);
> +	if (ret) {
> +		errno = ret;
> +		perror("xfd_prepare_geometry");
> +		exitcode = 1;
> +		return 0;
> +	}
> +
> +	switch (ver) {

set_xfd_flags() ?

> +	case 1:
> +		xfd.flags |= XFROG_FLAG_BULKSTAT_FORCE_V1;
> +		break;
> +	case 5:
> +		xfd.flags |= XFROG_FLAG_BULKSTAT_FORCE_V5;
> +		break;
> +	default:
> +		break;
> +	}
> +
> +	for (i = optind; i < argc; i++) {
> +		struct single_map	*sm = tags;
> +		uint64_t		ino;
> +		unsigned int		flags = 0;
> +
> +		/* Try to look up our tag... */
> +		for (sm = tags; sm->tag; sm++) {
> +			if (!strcmp(argv[i], sm->tag)) {
> +				ino = sm->code;
> +				flags |= XFS_BULK_IREQ_SPECIAL;
> +				break;
> +			}
> +		}
> +
> +		/* ...or else it's an inode number. */
> +		if (sm->tag == NULL) {
> +			errno = 0;
> +			ino = strtoull(argv[i], NULL, 10);
> +			if (errno) {
> +				perror(argv[i]);
> +				exitcode = 1;
> +				return 0;
> +			}
> +		}
> +
> +		ret = xfrog_bulkstat_single(&xfd, ino, flags, &bulkstat);
> +		if (ret) {
> +			errno = ret;
> +			perror("xfrog_bulkstat_single");
> +			continue;
> +		}
> +
> +		if (debug)

I guess there should be a -d option for this cmd as well?

> +			printf(
> +_("bulkstat_single: startino=%"PRIu64" flags=0x%"PRIx32" ret=%d\n"),
> +				ino, flags, ret);
> +
> +		dump_bulkstat(&bulkstat);
> +	}
> +
> +	return 0;
> +}
> +
> +static void
> +dump_inumbers(
> +	struct xfs_inumbers	*inumbers)
> +{
> +	printf("xi_startino = %"PRIu64"\n", inumbers->xi_startino);
> +	printf("\txi_allocmask = 0x%"PRIx64"\n", inumbers->xi_allocmask);
> +	printf("\txi_alloccount = %"PRIu8"\n", inumbers->xi_alloccount);
> +	printf("\txi_version = %"PRIu8"\n", inumbers->xi_version);
> +}
> +
> +static void
> +inumbers_help(void)
> +{
> +	printf(_(
> +"Queries the filesystem for inode group information and prints it.\n"
> +"\n"
> +"   -a   Only iterate this AG.\n"

-a <agno> ....

> +"   -d   Print debugging output.\n"
> +"   -e   Stop after this inode.\n"
> +"   -n   Ask for this many results at once.\n"
> +"   -s   Inode to start with.\n"
> +"   -v   Use this version of the ioctl (1 or 5).\n"));
> +}
> +
> +static int
> +inumbers_f(
> +	int			argc,
> +	char			**argv)
> +{
> +	struct xfs_fd		xfd = XFS_FD_INIT(file->fd);
> +	struct xfs_inumbers_req	*ireq;
> +	uint64_t		startino = 0;
> +	uint64_t		endino = -1ULL;
> +	uint32_t		batch_size = 4096;
> +	uint32_t		agno = 0;
> +	uint32_t		ver = 0;
> +	bool			has_agno = false;
> +	unsigned int		i;
> +	int			c;
> +	int			ret;
> +
> +	while ((c = getopt(argc, argv, "a:de:n:s:v:")) != -1) {
> +		switch (c) {
> +		case 'a':
> +			agno = cvt_u32(optarg, 10);
> +			if (errno) {
> +				perror(optarg);
> +				return 1;
> +			}
> +			has_agno = true;
> +			break;
> +		case 'd':
> +			debug = true;
> +			break;
> +		case 'e':
> +			endino = cvt_u64(optarg, 10);
> +			if (errno) {
> +				perror(optarg);
> +				return 1;
> +			}
> +			break;
> +		case 'n':
> +			batch_size = cvt_u32(optarg, 10);
> +			if (errno) {
> +				perror(optarg);
> +				return 1;
> +			}
> +			break;
> +		case 's':
> +			startino = cvt_u64(optarg, 10);
> +			if (errno) {
> +				perror(optarg);
> +				return 1;
> +			}
> +			break;
> +		case 'v':
> +			ver = cvt_u32(optarg, 10);
> +			if (errno) {
> +				perror(optarg);
> +				return 1;
> +			}
> +			if (ver != 1 && ver != 5) {
> +				fprintf(stderr, "version must be 1 or 5.\n");
> +				return 1;
> +			}
> +			break;
> +		default:
> +			bulkstat_help();
> +			return 0;
> +		}
> +	}
> +	if (optind != argc) {
> +		bulkstat_help();
> +		return 0;
> +	}
> +
> +	ret = xfd_prepare_geometry(&xfd);
> +	if (ret) {
> +		errno = ret;
> +		perror("xfd_prepare_geometry");
> +		exitcode = 1;
> +		return 0;
> +	}
> +
> +	ireq = xfrog_inumbers_alloc_req(batch_size, startino);
> +	if (!ireq) {
> +		perror("alloc inumbersreq");
> +		exitcode = 1;
> +		return 0;
> +	}
> +
> +	if (has_agno)
> +		xfrog_inumbers_set_ag(ireq, agno);
> +
> +	set_xfd_flags(&xfd, ver);
> +
> +	while ((ret = xfrog_inumbers(&xfd, ireq)) == 0) {
> +		if (debug)
> +			printf(
> +_("bulkstat: startino=%"PRIu64" flags=0x%"PRIx32" agno=%"PRIu32" ret=%d icount=%"PRIu32" ocount=%"PRIu32"\n"),
> +				ireq->hdr.ino,
> +				ireq->hdr.flags,
> +				ireq->hdr.agno,
> +				ret,
> +				ireq->hdr.icount,
> +				ireq->hdr.ocount);
> +		if (ireq->hdr.ocount == 0)
> +			break;
> +
> +		for (i = 0; i < ireq->hdr.ocount; i++) {
> +			if (ireq->inumbers[i].xi_startino > endino)
> +				break;
> +			dump_inumbers(&ireq->inumbers[i]);
> +		}
> +	}
> +	if (ret) {
> +		errno = ret;
> +		perror("xfrog_inumbers");
> +		exitcode = 1;

ireq leak here, just drop return to fall through?

> +		return 0;
> +	}
> +
> +	free(ireq);
> +	return 0;
> +}
> +
> +static cmdinfo_t	bulkstat_cmd = {
> +	.name = "bulkstat",
> +	.cfunc = bulkstat_f,
> +	.argmin = 0,
> +	.argmax = -1,
> +	.flags = CMD_NOMAP_OK | CMD_FLAG_ONESHOT,
> +	.help = bulkstat_help,
> +};
> +
> +static cmdinfo_t	bulkstat_single_cmd = {
> +	.name = "bulkstat_single",
> +	.cfunc = bulkstat_single_f,
> +	.argmin = 1,
> +	.argmax = -1,
> +	.flags = CMD_NOMAP_OK | CMD_FLAG_ONESHOT,
> +	.help = bulkstat_single_help,
> +};
> +
> +static cmdinfo_t	inumbers_cmd = {
> +	.name = "inumbers",
> +	.cfunc = inumbers_f,
> +	.argmin = 0,
> +	.argmax = -1,
> +	.flags = CMD_NOMAP_OK | CMD_FLAG_ONESHOT,
> +	.help = inumbers_help,
> +};
> +
> +void
> +bulkstat_init(void)
> +{
> +	bulkstat_cmd.args =
> +		_("[-a agno] [-d] [-e endino] [-n batchsize] [-s startino]");

<missing the -v option>

> +	bulkstat_cmd.oneline = _("Bulk stat of inodes in a filesystem");
> +
> +	bulkstat_single_cmd.args = _("inum...");

<-d if you add it>

> +	bulkstat_single_cmd.oneline = _("Stat one inode in a filesystem");

"Stat individual inodes in a filesystem (or ones that come later etc etc etc)"

> +
> +	inumbers_cmd.args =
> +		_("[-a agno] [-d] [-e endino] [-n batchsize] [-s startino]");

<missing the -v option>

> +	inumbers_cmd.oneline = _("Query inode groups in a filesystem");

I'm confused, why aren't all these ^^^ just in the structure definitions?

> +	add_command(&bulkstat_cmd);
> +	add_command(&bulkstat_single_cmd);
> +	add_command(&inumbers_cmd);
> +}
> diff --git a/io/init.c b/io/init.c
> index 7025aea5..033ed67d 100644
> --- a/io/init.c
> +++ b/io/init.c
> @@ -46,6 +46,7 @@ init_commands(void)
>  {
>  	attr_init();
>  	bmap_init();
> +	bulkstat_init();
>  	copy_range_init();
>  	cowextsize_init();
>  	encrypt_init();
> diff --git a/io/io.h b/io/io.h
> index 00dff2b7..49db902f 100644
> --- a/io/io.h
> +++ b/io/io.h
> @@ -183,3 +183,4 @@ extern void		log_writes_init(void);
>  extern void		scrub_init(void);
>  extern void		repair_init(void);
>  extern void		crc32cselftest_init(void);
> +extern void		bulkstat_init(void);
> diff --git a/libfrog/bulkstat.c b/libfrog/bulkstat.c
> index 85594e5e..538b5197 100644
> --- a/libfrog/bulkstat.c
> +++ b/libfrog/bulkstat.c
> @@ -435,6 +435,16 @@ xfrog_bulkstat_alloc_req(
>  	return breq;
>  }
>  
> +/* Set a bulkstat cursor to iterate only a particular AG. */
> +void
> +xfrog_bulkstat_set_ag(
> +	struct xfs_bulkstat_req	*req,
> +	uint32_t		agno)
> +{
> +	req->hdr.agno = agno;
> +	req->hdr.flags |= XFS_BULK_IREQ_AGNO;
> +}
> +
>  /* Convert a inumbers data from v5 format to v1 format. */
>  void
>  xfrog_inumbers_v5_to_v1(
> @@ -562,3 +572,13 @@ xfrog_inumbers_alloc_req(
>  
>  	return ireq;
>  }
> +
> +/* Set an inumbers cursor to iterate only a particular AG. */
> +void
> +xfrog_inumbers_set_ag(
> +	struct xfs_inumbers_req	*req,
> +	uint32_t		agno)
> +{
> +	req->hdr.agno = agno;
> +	req->hdr.flags |= XFS_BULK_IREQ_AGNO;
> +}
> diff --git a/libfrog/bulkstat.h b/libfrog/bulkstat.h
> index a085da3d..133a99b8 100644
> --- a/libfrog/bulkstat.h
> +++ b/libfrog/bulkstat.h
> @@ -19,11 +19,14 @@ int xfrog_bulkstat_v5_to_v1(struct xfs_fd *xfd, struct xfs_bstat *bs1,
>  void xfrog_bulkstat_v1_to_v5(struct xfs_fd *xfd, struct xfs_bulkstat *bstat,
>  		const struct xfs_bstat *bs1);
>  
> +void xfrog_bulkstat_set_ag(struct xfs_bulkstat_req *req, uint32_t agno);
> +
>  struct xfs_inogrp;
>  int xfrog_inumbers(struct xfs_fd *xfd, struct xfs_inumbers_req *req);
>  
>  struct xfs_inumbers_req *xfrog_inumbers_alloc_req(uint32_t nr,
>  		uint64_t startino);
> +void xfrog_inumbers_set_ag(struct xfs_inumbers_req *req, uint32_t agno);
>  void xfrog_inumbers_v5_to_v1(struct xfs_inogrp *ig1,
>  		const struct xfs_inumbers *ig);
>  void xfrog_inumbers_v1_to_v5(struct xfs_inumbers *ig,
> diff --git a/man/man8/xfs_io.8 b/man/man8/xfs_io.8
> index 6e064bdd..1e09b9e4 100644
> --- a/man/man8/xfs_io.8
> +++ b/man/man8/xfs_io.8
> @@ -996,6 +996,44 @@ for the current memory mapping.
>  
>  .SH FILESYSTEM COMMANDS
>  .TP
> +.BI "bulkstat [ \-a " agno " ] [ \-d ] [ \-e " endino " ] [ \-n " batchsize " ] [ \-s " startino " ]
> +Display raw stat information about a bunch of inodes in an XFS filesystem.
> +Options are as follows:
> +.RS 1.0i
> +.PD 0
> +.TP
> +.BI \-a " agno"
> +Display only results from the given allocation group.
> +If not specified, all results returned will be displayed.
> +.TP
> +.BI \-d
> +Print debugging information about call results.
> +.TP
> +.BI \-e " endino"
> +Stop displaying records when this inode number is reached.
> +Defaults to stopping when the system call stops returning results.
> +.TP
> +.BI \-n " batchsize"
> +Retrieve at most this many records per call.
> +Defaults to 4,096.
> +.TP
> +.BI \-s " startino"
> +Display inode allocation records starting with this inode.
> +Defaults to the first inode in the filesystem.

-v option not documented

> +.RE
> +.PD
> +.TP
> +.BI "bulkstat_single [ " inum... " | " special... " ]
> +Display raw stat information about individual inodes in an XFS filesystem.
> +Arguments must be inode numbers or any of the special values:

add -d ?

> +.RS 1.0i
> +.PD 0
> +.TP
> +.B root
> +Display information about the root directory inode.
> +.RE
> +.PD
> +.TP
>  .B freeze
>  Suspend all write I/O requests to the filesystem of the current file.
>  Only available in expert mode and requires privileges.
> @@ -1067,6 +1105,34 @@ was specified on the command line, the maximum possible inode number in
>  the system will be printed along with its size.
>  .PD
>  .TP
> +.BI "inumbers [ \-a " agno " ] [ \-d ] [ \-e " endino " ] [ \-n " batchsize " ] [ \-s " startino " ]
> +Prints allocation information about groups of inodes in an XFS filesystem.
> +Callers can use this information to figure out which inodes are allocated.
> +Options are as follows:
> +.RS 1.0i
> +.PD 0
> +.TP
> +.BI \-a " agno"
> +Display only results from the given allocation group.
> +If not specified, all results returned will be displayed.
> +.TP
> +.BI \-d
> +Print debugging information about call results.
> +.TP
> +.BI \-e " endino"
> +Stop displaying records when this inode number is reached.
> +Defaults to stopping when the system call stops returning results.
> +.TP
> +.BI \-n " batchsize"
> +Retrieve at most this many records per call.
> +Defaults to 4,096.
> +.TP
> +.BI \-s " startino"
> +Display inode allocation records starting with this inode.
> +Defaults to the first inode in the filesystem.

-v option not documented

> +.RE
> +.PD
> +.TP
>  .BI "scrub " type " [ " agnumber " | " "ino" " " "gen" " ]"
>  Scrub internal XFS filesystem metadata.  The
>  .BI type
>
Eric Sandeen Sept. 26, 2019, 10:46 p.m. UTC | #2
On 9/26/19 5:40 PM, Eric Sandeen wrote:

>> +static bool debug;
> since I'm old and my brain is weakening... do you need to turn this global
> debug back off in each foo_f function unless the -d debug option was specified?
> 

Actually it doesn't seem to be used in any sub-functions so it should
probably be local to each foo_f()

-Eric
Darrick J. Wong Sept. 27, 2019, 4:18 a.m. UTC | #3
On Thu, Sep 26, 2019 at 05:40:11PM -0500, Eric Sandeen wrote:
> On 9/25/19 4:33 PM, Darrick J. Wong wrote:
> > From: Darrick J. Wong <darrick.wong@oracle.com>
> > 
> > Add a bulkstat command to xfs_io so that we can test our new xfrog code.
> > 
> > Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
> > ---
> >  io/Makefile        |    9 -
> >  io/bulkstat.c      |  522 ++++++++++++++++++++++++++++++++++++++++++++++++++++
> >  io/init.c          |    1 
> >  io/io.h            |    1 
> >  libfrog/bulkstat.c |   20 ++
> >  libfrog/bulkstat.h |    3 
> >  man/man8/xfs_io.8  |   66 +++++++
> >  7 files changed, 618 insertions(+), 4 deletions(-)
> >  create mode 100644 io/bulkstat.c
> > 
> > 
> > diff --git a/io/Makefile b/io/Makefile
> > index 484e2b5a..1112605e 100644
> > --- a/io/Makefile
> > +++ b/io/Makefile
> > @@ -9,10 +9,11 @@ LTCOMMAND = xfs_io
> >  LSRCFILES = xfs_bmap.sh xfs_freeze.sh xfs_mkfile.sh
> >  HFILES = init.h io.h
> >  CFILES = init.c \
> > -	attr.c bmap.c crc32cselftest.c cowextsize.c encrypt.c file.c freeze.c \
> > -	fsync.c getrusage.c imap.c inject.c label.c link.c mmap.c open.c \
> > -	parent.c pread.c prealloc.c pwrite.c reflink.c resblks.c scrub.c \
> > -	seek.c shutdown.c stat.c swapext.c sync.c truncate.c utimes.c
> > +	attr.c bmap.c bulkstat.c crc32cselftest.c cowextsize.c encrypt.c \
> > +	file.c freeze.c fsync.c getrusage.c imap.c inject.c label.c link.c \
> > +	mmap.c open.c parent.c pread.c prealloc.c pwrite.c reflink.c \
> > +	resblks.c scrub.c seek.c shutdown.c stat.c swapext.c sync.c \
> > +	truncate.c utimes.c
> >  
> >  LLDLIBS = $(LIBXCMD) $(LIBHANDLE) $(LIBFROG) $(LIBPTHREAD)
> >  LTDEPENDENCIES = $(LIBXCMD) $(LIBHANDLE) $(LIBFROG)
> > diff --git a/io/bulkstat.c b/io/bulkstat.c
> > new file mode 100644
> > index 00000000..625f0abe
> > --- /dev/null
> > +++ b/io/bulkstat.c
> > @@ -0,0 +1,522 @@
> > +// SPDX-License-Identifier: GPL-2.0+
> > +/*
> > + * Copyright (C) 2019 Oracle.  All Rights Reserved.
> > + * Author: Darrick J. Wong <darrick.wong@oracle.com>
> > + */
> > +#include "xfs.h"
> > +#include "platform_defs.h"
> > +#include "command.h"
> > +#include "init.h"
> > +#include "libfrog/fsgeom.h"
> > +#include "libfrog/bulkstat.h"
> > +#include "libfrog/paths.h"
> > +#include "io.h"
> > +#include "input.h"
> > +
> > +static bool debug;
> 
> since I'm old and my brain is weakening... do you need to turn this
> global debug back off in each foo_f function unless the -d debug
> option was specified?

Yes.  Fixed.

> > +
> > +static void
> > +dump_bulkstat_time(
> > +	const char		*tag,
> > +	uint64_t		sec,
> > +	uint32_t		nsec)
> > +{
> > +	printf("\t%s = %"PRIu64".%"PRIu32"\n", tag, sec, nsec);
> > +}
> > +
> > +static void
> > +dump_bulkstat(
> > +	struct xfs_bulkstat	*bstat)
> > +{
> > +	printf("bs_ino = %"PRIu64"\n", bstat->bs_ino);
> > +	printf("\tbs_size = %"PRIu64"\n", bstat->bs_size);
> > +
> > +	printf("\tbs_blocks = %"PRIu64"\n", bstat->bs_blocks);
> > +	printf("\tbs_xflags = 0x%"PRIx64"\n", bstat->bs_xflags);
> > +
> > +	dump_bulkstat_time("bs_atime", bstat->bs_atime, bstat->bs_atime_nsec);
> > +	dump_bulkstat_time("bs_ctime", bstat->bs_ctime, bstat->bs_ctime_nsec);
> > +	dump_bulkstat_time("bs_mtime", bstat->bs_mtime, bstat->bs_mtime_nsec);
> > +	dump_bulkstat_time("bs_btime", bstat->bs_btime, bstat->bs_btime_nsec);
> > +
> > +	printf("\tbs_gen = 0x%"PRIx32"\n", bstat->bs_gen);
> > +	printf("\tbs_uid = %"PRIu32"\n", bstat->bs_uid);
> > +	printf("\tbs_gid = %"PRIu32"\n", bstat->bs_gid);
> > +	printf("\tbs_projectid = %"PRIu32"\n", bstat->bs_projectid);
> > +
> > +	printf("\tbs_blksize = %"PRIu32"\n", bstat->bs_blksize);
> > +	printf("\tbs_rdev = %"PRIu32"\n", bstat->bs_rdev);
> > +	printf("\tbs_cowextsize_blks = %"PRIu32"\n", bstat->bs_cowextsize_blks);
> > +	printf("\tbs_extsize_blks = %"PRIu32"\n", bstat->bs_extsize_blks);
> > +
> > +	printf("\tbs_nlink = %"PRIu32"\n", bstat->bs_nlink);
> > +	printf("\tbs_extents = %"PRIu32"\n", bstat->bs_extents);
> > +	printf("\tbs_aextents = %"PRIu32"\n", bstat->bs_aextents);
> > +	printf("\tbs_version = %"PRIu16"\n", bstat->bs_version);
> > +	printf("\tbs_forkoff = %"PRIu16"\n", bstat->bs_forkoff);
> > +
> > +	printf("\tbs_sick = 0x%"PRIx16"\n", bstat->bs_sick);
> > +	printf("\tbs_checked = 0x%"PRIx16"\n", bstat->bs_checked);
> > +	printf("\tbs_mode = 0%"PRIo16"\n", bstat->bs_mode);
> > +};
> > +
> > +static void
> > +bulkstat_help(void)
> > +{
> > +	printf(_(
> > +"Bulk-queries the filesystem for inode stat information and prints it.\n"
> > +"\n"
> > +"   -a   Only iterate this AG.\n"
> > +"   -d   Print debugging output.\n"
> > +"   -e   Stop after this inode.\n"
> > +"   -n   Ask for this many results at once.\n"
> > +"   -s   Inode to start with.\n"
> > +"   -v   Use this version of the ioctl (1 or 5).\n"));
> 
> +"   -a <agno>  Only iterate this AG.\n"
> +"   -d         Print debugging output.\n"
> +"   -e <ino>   Stop after this inode.\n"
> +"   -n <nr>    Ask for this many results at once.\n"
> +"   -s <ino>   Inode to start with.\n"
> +"   -v <ver>   Use this version of the ioctl (1 or 5).\n"));

Fixed.

> > +}
> > +
> > +static void
> > +set_xfd_flags(
> > +	struct xfs_fd	*xfd,
> > +	uint32_t	ver)
> > +{
> > +	switch (ver) {
> > +	case 1:
> > +		xfd->flags |= XFROG_FLAG_BULKSTAT_FORCE_V1;
> > +		break;
> > +	case 5:
> > +		xfd->flags |= XFROG_FLAG_BULKSTAT_FORCE_V5;
> > +		break;
> > +	default:
> > +		break;
> > +	}
> > +}
> > +
> > +static int
> > +bulkstat_f(
> > +	int			argc,
> > +	char			**argv)
> > +{
> > +	struct xfs_fd		xfd = XFS_FD_INIT(file->fd);
> > +	struct xfs_bulkstat_req	*breq;
> > +	uint64_t		startino = 0;
> > +	uint64_t		endino = -1ULL;
> > +	uint32_t		batch_size = 4096;
> > +	uint32_t		agno = 0;
> > +	uint32_t		ver = 0;
> > +	bool			has_agno = false;
> > +	unsigned int		i;
> > +	int			c;
> > +	int			ret;
> > +
> > +	while ((c = getopt(argc, argv, "a:de:n:s:v:")) != -1) {
> > +		switch (c) {
> > +		case 'a':
> > +			agno = cvt_u32(optarg, 10);
> > +			if (errno) {
> > +				perror(optarg);
> > +				return 1;
> > +			}
> > +			has_agno = true;
> > +			break;
> > +		case 'd':
> > +			debug = true;
> > +			break;
> > +		case 'e':
> > +			endino = cvt_u64(optarg, 10);
> > +			if (errno) {
> > +				perror(optarg);
> > +				return 1;
> > +			}
> > +			break;
> > +		case 'n':
> > +			batch_size = cvt_u32(optarg, 10);
> > +			if (errno) {
> > +				perror(optarg);
> > +				return 1;
> > +			}
> > +			break;
> > +		case 's':
> > +			startino = cvt_u64(optarg, 10);
> > +			if (errno) {
> > +				perror(optarg);
> > +				return 1;
> > +			}
> > +			break;
> > +		case 'v':
> > +			ver = cvt_u32(optarg, 10);
> > +			if (errno) {
> > +				perror(optarg);
> > +				return 1;
> > +			}
> > +			if (ver != 1 && ver != 5) {
> > +				fprintf(stderr, "version must be 1 or 5.\n");
> > +				return 1;
> > +			}
> > +			break;
> > +		default:
> > +			bulkstat_help();
> > +			return 0;
> > +		}
> > +	}
> > +	if (optind != argc) {
> > +		bulkstat_help();
> > +		return 0;
> > +	}
> > +
> > +	ret = xfd_prepare_geometry(&xfd);
> > +	if (ret) {
> > +		errno = ret;
> > +		perror("xfd_prepare_geometry");
> > +		exitcode = 1;
> > +		return 0;
> > +	}
> > +
> > +	breq = xfrog_bulkstat_alloc_req(batch_size, startino);
> > +	if (!breq) {
> > +		perror("alloc bulkreq");
> > +		exitcode = 1;
> > +		return 0;
> > +	}
> > +
> > +	if (has_agno)
> > +		xfrog_bulkstat_set_ag(breq, agno);
> > +
> > +	set_xfd_flags(&xfd, ver);
> > +
> > +	while ((ret = xfrog_bulkstat(&xfd, breq)) == 0) {
> > +		if (debug)
> > +			printf(
> > +_("bulkstat: startino=%lld flags=0x%x agno=%u ret=%d icount=%u ocount=%u\n"),
> > +				(long long)breq->hdr.ino,
> > +				(unsigned int)breq->hdr.flags,
> > +				(unsigned int)breq->hdr.agno,
> > +				ret,
> > +				(unsigned int)breq->hdr.icount,
> > +				(unsigned int)breq->hdr.ocount);
> > +		if (breq->hdr.ocount == 0)
> > +			break;
> > +
> > +		for (i = 0; i < breq->hdr.ocount; i++) {
> > +			if (breq->bulkstat[i].bs_ino > endino)
> > +				break;
> > +			dump_bulkstat(&breq->bulkstat[i]);
> > +		}
> > +	}
> > +	if (ret) {
> > +		errno = ret;
> > +		perror("xfrog_bulkstat");
> > +		exitcode = 1;
> 
> free(breq) (or just drop the return & fall through ...)

Good catch, thank you. :)

> 
> > +		return 0;
> > +	}
> > +
> > +	free(breq);
> > +	return 0;
> > +}
> > +
> > +static void
> > +bulkstat_single_help(void)
> > +{
> > +	printf(_(
> > +"Queries the filesystem for a single inode's stat information and prints it.\n"
> > +"\n"
> > +"   -v   Use this version of the ioctl (1 or 5).\n"
> 
> +"   -v <ver>   Use this version of the ioctl (1 or 5).\n"));
> 
> (I'm realizing all our long help is preeeeettttty free form but still worth
> noting that it requires an optarg)

Er... more than bulkstat_single_cmd.args?

Ok, I suppose I could change it to "[-d] [-v] inum..." to imply that we
take optarg arguments.

> since it can take more than one, I wonder if the man page's text
> ("individual inodes") is better than "a single inode's"
> 
> The other interesting thing is that it takes a start ino but gives you the
> first allocated inode >= that number, right:
> 
> xfs_io> bulkstat_single 128 129 130
> bs_ino = 160
> ...
> bs_ino = 160
> ...
> bs_ino = 160
> ...
> 
> and while that's how the interface works, I wonder if the help and manpage
> should be (sigh) more clear about it somehow.

Yeah, I'll make a note of that.

> also, probably wants a -d debug option

yep.

> 
> > +"\n"
> > +"Pass in inode numbers or a special inode name:\n"
> > +"    root    Root directory.\n"));
> > +}
> > +
> > +struct single_map {
> > +	const char		*tag;
> > +	uint64_t		code;
> > +};
> > +
> > +struct single_map tags[] = {
> > +	{"root", XFS_BULK_IREQ_SPECIAL_ROOT},
> > +	{NULL, 0},
> > +};
> > +
> > +static int
> > +bulkstat_single_f(
> > +	int			argc,
> > +	char			**argv)
> > +{
> > +	struct xfs_fd		xfd = XFS_FD_INIT(file->fd);
> > +	struct xfs_bulkstat	bulkstat;
> > +	unsigned long		ver = 0;
> > +	unsigned int		i;
> > +	int			c;
> > +	int			ret;
> > +
> > +	while ((c = getopt(argc, argv, "v:")) != -1) {
> > +		switch (c) {
> > +		case 'v':
> > +			errno = 0;
> > +			ver = strtoull(optarg, NULL, 10);
> > +			if (errno) {
> > +				perror(optarg);
> > +				return 1;
> > +			}
> > +			if (ver != 1 && ver != 5) {
> > +				fprintf(stderr, "version must be 1 or 5.\n");
> > +				return 1;
> > +			}
> > +			break;
> > +		default:
> > +			bulkstat_single_help();
> > +			return 0;
> > +		}
> > +	}
> > +
> > +	ret = xfd_prepare_geometry(&xfd);
> > +	if (ret) {
> > +		errno = ret;
> > +		perror("xfd_prepare_geometry");
> > +		exitcode = 1;
> > +		return 0;
> > +	}
> > +
> > +	switch (ver) {
> 
> set_xfd_flags() ?

I thought I fixed that! :/

> > +	case 1:
> > +		xfd.flags |= XFROG_FLAG_BULKSTAT_FORCE_V1;
> > +		break;
> > +	case 5:
> > +		xfd.flags |= XFROG_FLAG_BULKSTAT_FORCE_V5;
> > +		break;
> > +	default:
> > +		break;
> > +	}
> > +
> > +	for (i = optind; i < argc; i++) {
> > +		struct single_map	*sm = tags;
> > +		uint64_t		ino;
> > +		unsigned int		flags = 0;
> > +
> > +		/* Try to look up our tag... */
> > +		for (sm = tags; sm->tag; sm++) {
> > +			if (!strcmp(argv[i], sm->tag)) {
> > +				ino = sm->code;
> > +				flags |= XFS_BULK_IREQ_SPECIAL;
> > +				break;
> > +			}
> > +		}
> > +
> > +		/* ...or else it's an inode number. */
> > +		if (sm->tag == NULL) {
> > +			errno = 0;
> > +			ino = strtoull(argv[i], NULL, 10);
> > +			if (errno) {
> > +				perror(argv[i]);
> > +				exitcode = 1;
> > +				return 0;
> > +			}
> > +		}
> > +
> > +		ret = xfrog_bulkstat_single(&xfd, ino, flags, &bulkstat);
> > +		if (ret) {
> > +			errno = ret;
> > +			perror("xfrog_bulkstat_single");
> > +			continue;
> > +		}
> > +
> > +		if (debug)
> 
> I guess there should be a -d option for this cmd as well?

Yes (we're still on bulkstat_single iirc).

> > +			printf(
> > +_("bulkstat_single: startino=%"PRIu64" flags=0x%"PRIx32" ret=%d\n"),
> > +				ino, flags, ret);
> > +
> > +		dump_bulkstat(&bulkstat);
> > +	}
> > +
> > +	return 0;
> > +}
> > +
> > +static void
> > +dump_inumbers(
> > +	struct xfs_inumbers	*inumbers)
> > +{
> > +	printf("xi_startino = %"PRIu64"\n", inumbers->xi_startino);
> > +	printf("\txi_allocmask = 0x%"PRIx64"\n", inumbers->xi_allocmask);
> > +	printf("\txi_alloccount = %"PRIu8"\n", inumbers->xi_alloccount);
> > +	printf("\txi_version = %"PRIu8"\n", inumbers->xi_version);
> > +}
> > +
> > +static void
> > +inumbers_help(void)
> > +{
> > +	printf(_(
> > +"Queries the filesystem for inode group information and prints it.\n"
> > +"\n"
> > +"   -a   Only iterate this AG.\n"
> 
> -a <agno> ....

Fixed.

> > +"   -d   Print debugging output.\n"
> > +"   -e   Stop after this inode.\n"
> > +"   -n   Ask for this many results at once.\n"
> > +"   -s   Inode to start with.\n"
> > +"   -v   Use this version of the ioctl (1 or 5).\n"));
> > +}
> > +
> > +static int
> > +inumbers_f(
> > +	int			argc,
> > +	char			**argv)
> > +{
> > +	struct xfs_fd		xfd = XFS_FD_INIT(file->fd);
> > +	struct xfs_inumbers_req	*ireq;
> > +	uint64_t		startino = 0;
> > +	uint64_t		endino = -1ULL;
> > +	uint32_t		batch_size = 4096;
> > +	uint32_t		agno = 0;
> > +	uint32_t		ver = 0;
> > +	bool			has_agno = false;
> > +	unsigned int		i;
> > +	int			c;
> > +	int			ret;
> > +
> > +	while ((c = getopt(argc, argv, "a:de:n:s:v:")) != -1) {
> > +		switch (c) {
> > +		case 'a':
> > +			agno = cvt_u32(optarg, 10);
> > +			if (errno) {
> > +				perror(optarg);
> > +				return 1;
> > +			}
> > +			has_agno = true;
> > +			break;
> > +		case 'd':
> > +			debug = true;
> > +			break;
> > +		case 'e':
> > +			endino = cvt_u64(optarg, 10);
> > +			if (errno) {
> > +				perror(optarg);
> > +				return 1;
> > +			}
> > +			break;
> > +		case 'n':
> > +			batch_size = cvt_u32(optarg, 10);
> > +			if (errno) {
> > +				perror(optarg);
> > +				return 1;
> > +			}
> > +			break;
> > +		case 's':
> > +			startino = cvt_u64(optarg, 10);
> > +			if (errno) {
> > +				perror(optarg);
> > +				return 1;
> > +			}
> > +			break;
> > +		case 'v':
> > +			ver = cvt_u32(optarg, 10);
> > +			if (errno) {
> > +				perror(optarg);
> > +				return 1;
> > +			}
> > +			if (ver != 1 && ver != 5) {
> > +				fprintf(stderr, "version must be 1 or 5.\n");
> > +				return 1;
> > +			}
> > +			break;
> > +		default:
> > +			bulkstat_help();
> > +			return 0;
> > +		}
> > +	}
> > +	if (optind != argc) {
> > +		bulkstat_help();
> > +		return 0;
> > +	}
> > +
> > +	ret = xfd_prepare_geometry(&xfd);
> > +	if (ret) {
> > +		errno = ret;
> > +		perror("xfd_prepare_geometry");
> > +		exitcode = 1;
> > +		return 0;
> > +	}
> > +
> > +	ireq = xfrog_inumbers_alloc_req(batch_size, startino);
> > +	if (!ireq) {
> > +		perror("alloc inumbersreq");
> > +		exitcode = 1;
> > +		return 0;
> > +	}
> > +
> > +	if (has_agno)
> > +		xfrog_inumbers_set_ag(ireq, agno);
> > +
> > +	set_xfd_flags(&xfd, ver);
> > +
> > +	while ((ret = xfrog_inumbers(&xfd, ireq)) == 0) {
> > +		if (debug)
> > +			printf(
> > +_("bulkstat: startino=%"PRIu64" flags=0x%"PRIx32" agno=%"PRIu32" ret=%d icount=%"PRIu32" ocount=%"PRIu32"\n"),
> > +				ireq->hdr.ino,
> > +				ireq->hdr.flags,
> > +				ireq->hdr.agno,
> > +				ret,
> > +				ireq->hdr.icount,
> > +				ireq->hdr.ocount);
> > +		if (ireq->hdr.ocount == 0)
> > +			break;
> > +
> > +		for (i = 0; i < ireq->hdr.ocount; i++) {
> > +			if (ireq->inumbers[i].xi_startino > endino)
> > +				break;
> > +			dump_inumbers(&ireq->inumbers[i]);
> > +		}
> > +	}
> > +	if (ret) {
> > +		errno = ret;
> > +		perror("xfrog_inumbers");
> > +		exitcode = 1;
> 
> ireq leak here, just drop return to fall through?

Fixed.

> > +		return 0;
> > +	}
> > +
> > +	free(ireq);
> > +	return 0;
> > +}
> > +
> > +static cmdinfo_t	bulkstat_cmd = {
> > +	.name = "bulkstat",
> > +	.cfunc = bulkstat_f,
> > +	.argmin = 0,
> > +	.argmax = -1,
> > +	.flags = CMD_NOMAP_OK | CMD_FLAG_ONESHOT,
> > +	.help = bulkstat_help,
> > +};
> > +
> > +static cmdinfo_t	bulkstat_single_cmd = {
> > +	.name = "bulkstat_single",
> > +	.cfunc = bulkstat_single_f,
> > +	.argmin = 1,
> > +	.argmax = -1,
> > +	.flags = CMD_NOMAP_OK | CMD_FLAG_ONESHOT,
> > +	.help = bulkstat_single_help,
> > +};
> > +
> > +static cmdinfo_t	inumbers_cmd = {
> > +	.name = "inumbers",
> > +	.cfunc = inumbers_f,
> > +	.argmin = 0,
> > +	.argmax = -1,
> > +	.flags = CMD_NOMAP_OK | CMD_FLAG_ONESHOT,
> > +	.help = inumbers_help,
> > +};
> > +
> > +void
> > +bulkstat_init(void)
> > +{
> > +	bulkstat_cmd.args =
> > +		_("[-a agno] [-d] [-e endino] [-n batchsize] [-s startino]");
> 
> <missing the -v option>
> 
> > +	bulkstat_cmd.oneline = _("Bulk stat of inodes in a filesystem");
> > +
> > +	bulkstat_single_cmd.args = _("inum...");
> 
> <-d if you add it>
> 
> > +	bulkstat_single_cmd.oneline = _("Stat one inode in a filesystem");
> 
> "Stat individual inodes in a filesystem (or ones that come later etc etc etc)"

I'll add that to the long format help.

> > +
> > +	inumbers_cmd.args =
> > +		_("[-a agno] [-d] [-e endino] [-n batchsize] [-s startino]");
> 
> <missing the -v option>
> 
> > +	inumbers_cmd.oneline = _("Query inode groups in a filesystem");
> 
> I'm confused, why aren't all these ^^^ just in the structure definitions?

All of these ... what?  I'm confused, sorry.

> > +	add_command(&bulkstat_cmd);
> > +	add_command(&bulkstat_single_cmd);
> > +	add_command(&inumbers_cmd);
> > +}
> > diff --git a/io/init.c b/io/init.c
> > index 7025aea5..033ed67d 100644
> > --- a/io/init.c
> > +++ b/io/init.c
> > @@ -46,6 +46,7 @@ init_commands(void)
> >  {
> >  	attr_init();
> >  	bmap_init();
> > +	bulkstat_init();
> >  	copy_range_init();
> >  	cowextsize_init();
> >  	encrypt_init();
> > diff --git a/io/io.h b/io/io.h
> > index 00dff2b7..49db902f 100644
> > --- a/io/io.h
> > +++ b/io/io.h
> > @@ -183,3 +183,4 @@ extern void		log_writes_init(void);
> >  extern void		scrub_init(void);
> >  extern void		repair_init(void);
> >  extern void		crc32cselftest_init(void);
> > +extern void		bulkstat_init(void);
> > diff --git a/libfrog/bulkstat.c b/libfrog/bulkstat.c
> > index 85594e5e..538b5197 100644
> > --- a/libfrog/bulkstat.c
> > +++ b/libfrog/bulkstat.c
> > @@ -435,6 +435,16 @@ xfrog_bulkstat_alloc_req(
> >  	return breq;
> >  }
> >  
> > +/* Set a bulkstat cursor to iterate only a particular AG. */
> > +void
> > +xfrog_bulkstat_set_ag(
> > +	struct xfs_bulkstat_req	*req,
> > +	uint32_t		agno)
> > +{
> > +	req->hdr.agno = agno;
> > +	req->hdr.flags |= XFS_BULK_IREQ_AGNO;
> > +}
> > +
> >  /* Convert a inumbers data from v5 format to v1 format. */
> >  void
> >  xfrog_inumbers_v5_to_v1(
> > @@ -562,3 +572,13 @@ xfrog_inumbers_alloc_req(
> >  
> >  	return ireq;
> >  }
> > +
> > +/* Set an inumbers cursor to iterate only a particular AG. */
> > +void
> > +xfrog_inumbers_set_ag(
> > +	struct xfs_inumbers_req	*req,
> > +	uint32_t		agno)
> > +{
> > +	req->hdr.agno = agno;
> > +	req->hdr.flags |= XFS_BULK_IREQ_AGNO;
> > +}
> > diff --git a/libfrog/bulkstat.h b/libfrog/bulkstat.h
> > index a085da3d..133a99b8 100644
> > --- a/libfrog/bulkstat.h
> > +++ b/libfrog/bulkstat.h
> > @@ -19,11 +19,14 @@ int xfrog_bulkstat_v5_to_v1(struct xfs_fd *xfd, struct xfs_bstat *bs1,
> >  void xfrog_bulkstat_v1_to_v5(struct xfs_fd *xfd, struct xfs_bulkstat *bstat,
> >  		const struct xfs_bstat *bs1);
> >  
> > +void xfrog_bulkstat_set_ag(struct xfs_bulkstat_req *req, uint32_t agno);
> > +
> >  struct xfs_inogrp;
> >  int xfrog_inumbers(struct xfs_fd *xfd, struct xfs_inumbers_req *req);
> >  
> >  struct xfs_inumbers_req *xfrog_inumbers_alloc_req(uint32_t nr,
> >  		uint64_t startino);
> > +void xfrog_inumbers_set_ag(struct xfs_inumbers_req *req, uint32_t agno);
> >  void xfrog_inumbers_v5_to_v1(struct xfs_inogrp *ig1,
> >  		const struct xfs_inumbers *ig);
> >  void xfrog_inumbers_v1_to_v5(struct xfs_inumbers *ig,
> > diff --git a/man/man8/xfs_io.8 b/man/man8/xfs_io.8
> > index 6e064bdd..1e09b9e4 100644
> > --- a/man/man8/xfs_io.8
> > +++ b/man/man8/xfs_io.8
> > @@ -996,6 +996,44 @@ for the current memory mapping.
> >  
> >  .SH FILESYSTEM COMMANDS
> >  .TP
> > +.BI "bulkstat [ \-a " agno " ] [ \-d ] [ \-e " endino " ] [ \-n " batchsize " ] [ \-s " startino " ]
> > +Display raw stat information about a bunch of inodes in an XFS filesystem.
> > +Options are as follows:
> > +.RS 1.0i
> > +.PD 0
> > +.TP
> > +.BI \-a " agno"
> > +Display only results from the given allocation group.
> > +If not specified, all results returned will be displayed.
> > +.TP
> > +.BI \-d
> > +Print debugging information about call results.
> > +.TP
> > +.BI \-e " endino"
> > +Stop displaying records when this inode number is reached.
> > +Defaults to stopping when the system call stops returning results.
> > +.TP
> > +.BI \-n " batchsize"
> > +Retrieve at most this many records per call.
> > +Defaults to 4,096.
> > +.TP
> > +.BI \-s " startino"
> > +Display inode allocation records starting with this inode.
> > +Defaults to the first inode in the filesystem.
> 
> -v option not documented
> 
> > +.RE
> > +.PD
> > +.TP
> > +.BI "bulkstat_single [ " inum... " | " special... " ]
> > +Display raw stat information about individual inodes in an XFS filesystem.
> > +Arguments must be inode numbers or any of the special values:
> 
> add -d ?

Ok to both.

> > +.RS 1.0i
> > +.PD 0
> > +.TP
> > +.B root
> > +Display information about the root directory inode.
> > +.RE
> > +.PD
> > +.TP
> >  .B freeze
> >  Suspend all write I/O requests to the filesystem of the current file.
> >  Only available in expert mode and requires privileges.
> > @@ -1067,6 +1105,34 @@ was specified on the command line, the maximum possible inode number in
> >  the system will be printed along with its size.
> >  .PD
> >  .TP
> > +.BI "inumbers [ \-a " agno " ] [ \-d ] [ \-e " endino " ] [ \-n " batchsize " ] [ \-s " startino " ]
> > +Prints allocation information about groups of inodes in an XFS filesystem.
> > +Callers can use this information to figure out which inodes are allocated.
> > +Options are as follows:
> > +.RS 1.0i
> > +.PD 0
> > +.TP
> > +.BI \-a " agno"
> > +Display only results from the given allocation group.
> > +If not specified, all results returned will be displayed.
> > +.TP
> > +.BI \-d
> > +Print debugging information about call results.
> > +.TP
> > +.BI \-e " endino"
> > +Stop displaying records when this inode number is reached.
> > +Defaults to stopping when the system call stops returning results.
> > +.TP
> > +.BI \-n " batchsize"
> > +Retrieve at most this many records per call.
> > +Defaults to 4,096.
> > +.TP
> > +.BI \-s " startino"
> > +Display inode allocation records starting with this inode.
> > +Defaults to the first inode in the filesystem.
> 
> -v option not documented

Fixed.

--D

> > +.RE
> > +.PD
> > +.TP
> >  .BI "scrub " type " [ " agnumber " | " "ino" " " "gen" " ]"
> >  Scrub internal XFS filesystem metadata.  The
> >  .BI type
> >
Eric Sandeen Sept. 30, 2019, 8:02 p.m. UTC | #4
On 9/26/19 11:18 PM, Darrick J. Wong wrote:
>>> +
>>> +	inumbers_cmd.args =
>>> +		_("[-a agno] [-d] [-e endino] [-n batchsize] [-s startino]");
>> <missing the -v option>
>>
>>> +	inumbers_cmd.oneline = _("Query inode groups in a filesystem");
>> I'm confused, why aren't all these ^^^ just in the structure definitions?
> All of these ... what?  I'm confused, sorry.
> 

I'm wondering why these 2 fields get set up in bulkstat_init(), vs at
cmdinfo_t structure definition time, i.e.

static cmdinfo_t        inumbers_cmd = {
        .name = "inumbers",
        .cfunc = inumbers_f,
        .argmin = 0,
        .argmax = -1,
        .flags = CMD_NOMAP_OK | CMD_FLAG_ONESHOT,
        .args =
_("[-a agno] [-d] [-e endino] [-n batchsize] [-s startino] [-v version]");
        .oneline = _("Query inode groups in a filesystem");
        .help = inumbers_help,
};

like ~every other command does?


-Eric
Darrick J. Wong Sept. 30, 2019, 8:15 p.m. UTC | #5
On Mon, Sep 30, 2019 at 03:02:27PM -0500, Eric Sandeen wrote:
> On 9/26/19 11:18 PM, Darrick J. Wong wrote:
> >>> +
> >>> +	inumbers_cmd.args =
> >>> +		_("[-a agno] [-d] [-e endino] [-n batchsize] [-s startino]");
> >> <missing the -v option>
> >>
> >>> +	inumbers_cmd.oneline = _("Query inode groups in a filesystem");
> >> I'm confused, why aren't all these ^^^ just in the structure definitions?
> > All of these ... what?  I'm confused, sorry.
> > 
> 
> I'm wondering why these 2 fields get set up in bulkstat_init(), vs at
> cmdinfo_t structure definition time, i.e.
> 
> static cmdinfo_t        inumbers_cmd = {
>         .name = "inumbers",
>         .cfunc = inumbers_f,
>         .argmin = 0,
>         .argmax = -1,
>         .flags = CMD_NOMAP_OK | CMD_FLAG_ONESHOT,
>         .args =
> _("[-a agno] [-d] [-e endino] [-n batchsize] [-s startino] [-v version]");
>         .oneline = _("Query inode groups in a filesystem");
>         .help = inumbers_help,
> };
> 
> like ~every other command does?

[repeating irc conversation]

_() is a function, but static initializers require constant rvalues.

--D

> 
> 
> -Eric
Eric Sandeen Oct. 7, 2019, 7:13 p.m. UTC | #6
On 9/30/19 3:15 PM, Darrick J. Wong wrote:
> On Mon, Sep 30, 2019 at 03:02:27PM -0500, Eric Sandeen wrote:
>> On 9/26/19 11:18 PM, Darrick J. Wong wrote:
>>>>> +
>>>>> +	inumbers_cmd.args =
>>>>> +		_("[-a agno] [-d] [-e endino] [-n batchsize] [-s startino]");
>>>> <missing the -v option>
>>>>
>>>>> +	inumbers_cmd.oneline = _("Query inode groups in a filesystem");
>>>> I'm confused, why aren't all these ^^^ just in the structure definitions?
>>> All of these ... what?  I'm confused, sorry.
>>>
>>
>> I'm wondering why these 2 fields get set up in bulkstat_init(), vs at
>> cmdinfo_t structure definition time, i.e.
>>
>> static cmdinfo_t        inumbers_cmd = {
>>         .name = "inumbers",
>>         .cfunc = inumbers_f,
>>         .argmin = 0,
>>         .argmax = -1,
>>         .flags = CMD_NOMAP_OK | CMD_FLAG_ONESHOT,
>>         .args =
>> _("[-a agno] [-d] [-e endino] [-n batchsize] [-s startino] [-v version]");
>>         .oneline = _("Query inode groups in a filesystem");
>>         .help = inumbers_help,
>> };
>>
>> like ~every other command does?
> 
> [repeating irc conversation]
> 
> _() is a function, but static initializers require constant rvalues.

Sorry, my bad for missing that.

Thanks,
-Eric
diff mbox series

Patch

diff --git a/io/Makefile b/io/Makefile
index 484e2b5a..1112605e 100644
--- a/io/Makefile
+++ b/io/Makefile
@@ -9,10 +9,11 @@  LTCOMMAND = xfs_io
 LSRCFILES = xfs_bmap.sh xfs_freeze.sh xfs_mkfile.sh
 HFILES = init.h io.h
 CFILES = init.c \
-	attr.c bmap.c crc32cselftest.c cowextsize.c encrypt.c file.c freeze.c \
-	fsync.c getrusage.c imap.c inject.c label.c link.c mmap.c open.c \
-	parent.c pread.c prealloc.c pwrite.c reflink.c resblks.c scrub.c \
-	seek.c shutdown.c stat.c swapext.c sync.c truncate.c utimes.c
+	attr.c bmap.c bulkstat.c crc32cselftest.c cowextsize.c encrypt.c \
+	file.c freeze.c fsync.c getrusage.c imap.c inject.c label.c link.c \
+	mmap.c open.c parent.c pread.c prealloc.c pwrite.c reflink.c \
+	resblks.c scrub.c seek.c shutdown.c stat.c swapext.c sync.c \
+	truncate.c utimes.c
 
 LLDLIBS = $(LIBXCMD) $(LIBHANDLE) $(LIBFROG) $(LIBPTHREAD)
 LTDEPENDENCIES = $(LIBXCMD) $(LIBHANDLE) $(LIBFROG)
diff --git a/io/bulkstat.c b/io/bulkstat.c
new file mode 100644
index 00000000..625f0abe
--- /dev/null
+++ b/io/bulkstat.c
@@ -0,0 +1,522 @@ 
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2019 Oracle.  All Rights Reserved.
+ * Author: Darrick J. Wong <darrick.wong@oracle.com>
+ */
+#include "xfs.h"
+#include "platform_defs.h"
+#include "command.h"
+#include "init.h"
+#include "libfrog/fsgeom.h"
+#include "libfrog/bulkstat.h"
+#include "libfrog/paths.h"
+#include "io.h"
+#include "input.h"
+
+static bool debug;
+
+static void
+dump_bulkstat_time(
+	const char		*tag,
+	uint64_t		sec,
+	uint32_t		nsec)
+{
+	printf("\t%s = %"PRIu64".%"PRIu32"\n", tag, sec, nsec);
+}
+
+static void
+dump_bulkstat(
+	struct xfs_bulkstat	*bstat)
+{
+	printf("bs_ino = %"PRIu64"\n", bstat->bs_ino);
+	printf("\tbs_size = %"PRIu64"\n", bstat->bs_size);
+
+	printf("\tbs_blocks = %"PRIu64"\n", bstat->bs_blocks);
+	printf("\tbs_xflags = 0x%"PRIx64"\n", bstat->bs_xflags);
+
+	dump_bulkstat_time("bs_atime", bstat->bs_atime, bstat->bs_atime_nsec);
+	dump_bulkstat_time("bs_ctime", bstat->bs_ctime, bstat->bs_ctime_nsec);
+	dump_bulkstat_time("bs_mtime", bstat->bs_mtime, bstat->bs_mtime_nsec);
+	dump_bulkstat_time("bs_btime", bstat->bs_btime, bstat->bs_btime_nsec);
+
+	printf("\tbs_gen = 0x%"PRIx32"\n", bstat->bs_gen);
+	printf("\tbs_uid = %"PRIu32"\n", bstat->bs_uid);
+	printf("\tbs_gid = %"PRIu32"\n", bstat->bs_gid);
+	printf("\tbs_projectid = %"PRIu32"\n", bstat->bs_projectid);
+
+	printf("\tbs_blksize = %"PRIu32"\n", bstat->bs_blksize);
+	printf("\tbs_rdev = %"PRIu32"\n", bstat->bs_rdev);
+	printf("\tbs_cowextsize_blks = %"PRIu32"\n", bstat->bs_cowextsize_blks);
+	printf("\tbs_extsize_blks = %"PRIu32"\n", bstat->bs_extsize_blks);
+
+	printf("\tbs_nlink = %"PRIu32"\n", bstat->bs_nlink);
+	printf("\tbs_extents = %"PRIu32"\n", bstat->bs_extents);
+	printf("\tbs_aextents = %"PRIu32"\n", bstat->bs_aextents);
+	printf("\tbs_version = %"PRIu16"\n", bstat->bs_version);
+	printf("\tbs_forkoff = %"PRIu16"\n", bstat->bs_forkoff);
+
+	printf("\tbs_sick = 0x%"PRIx16"\n", bstat->bs_sick);
+	printf("\tbs_checked = 0x%"PRIx16"\n", bstat->bs_checked);
+	printf("\tbs_mode = 0%"PRIo16"\n", bstat->bs_mode);
+};
+
+static void
+bulkstat_help(void)
+{
+	printf(_(
+"Bulk-queries the filesystem for inode stat information and prints it.\n"
+"\n"
+"   -a   Only iterate this AG.\n"
+"   -d   Print debugging output.\n"
+"   -e   Stop after this inode.\n"
+"   -n   Ask for this many results at once.\n"
+"   -s   Inode to start with.\n"
+"   -v   Use this version of the ioctl (1 or 5).\n"));
+}
+
+static void
+set_xfd_flags(
+	struct xfs_fd	*xfd,
+	uint32_t	ver)
+{
+	switch (ver) {
+	case 1:
+		xfd->flags |= XFROG_FLAG_BULKSTAT_FORCE_V1;
+		break;
+	case 5:
+		xfd->flags |= XFROG_FLAG_BULKSTAT_FORCE_V5;
+		break;
+	default:
+		break;
+	}
+}
+
+static int
+bulkstat_f(
+	int			argc,
+	char			**argv)
+{
+	struct xfs_fd		xfd = XFS_FD_INIT(file->fd);
+	struct xfs_bulkstat_req	*breq;
+	uint64_t		startino = 0;
+	uint64_t		endino = -1ULL;
+	uint32_t		batch_size = 4096;
+	uint32_t		agno = 0;
+	uint32_t		ver = 0;
+	bool			has_agno = false;
+	unsigned int		i;
+	int			c;
+	int			ret;
+
+	while ((c = getopt(argc, argv, "a:de:n:s:v:")) != -1) {
+		switch (c) {
+		case 'a':
+			agno = cvt_u32(optarg, 10);
+			if (errno) {
+				perror(optarg);
+				return 1;
+			}
+			has_agno = true;
+			break;
+		case 'd':
+			debug = true;
+			break;
+		case 'e':
+			endino = cvt_u64(optarg, 10);
+			if (errno) {
+				perror(optarg);
+				return 1;
+			}
+			break;
+		case 'n':
+			batch_size = cvt_u32(optarg, 10);
+			if (errno) {
+				perror(optarg);
+				return 1;
+			}
+			break;
+		case 's':
+			startino = cvt_u64(optarg, 10);
+			if (errno) {
+				perror(optarg);
+				return 1;
+			}
+			break;
+		case 'v':
+			ver = cvt_u32(optarg, 10);
+			if (errno) {
+				perror(optarg);
+				return 1;
+			}
+			if (ver != 1 && ver != 5) {
+				fprintf(stderr, "version must be 1 or 5.\n");
+				return 1;
+			}
+			break;
+		default:
+			bulkstat_help();
+			return 0;
+		}
+	}
+	if (optind != argc) {
+		bulkstat_help();
+		return 0;
+	}
+
+	ret = xfd_prepare_geometry(&xfd);
+	if (ret) {
+		errno = ret;
+		perror("xfd_prepare_geometry");
+		exitcode = 1;
+		return 0;
+	}
+
+	breq = xfrog_bulkstat_alloc_req(batch_size, startino);
+	if (!breq) {
+		perror("alloc bulkreq");
+		exitcode = 1;
+		return 0;
+	}
+
+	if (has_agno)
+		xfrog_bulkstat_set_ag(breq, agno);
+
+	set_xfd_flags(&xfd, ver);
+
+	while ((ret = xfrog_bulkstat(&xfd, breq)) == 0) {
+		if (debug)
+			printf(
+_("bulkstat: startino=%lld flags=0x%x agno=%u ret=%d icount=%u ocount=%u\n"),
+				(long long)breq->hdr.ino,
+				(unsigned int)breq->hdr.flags,
+				(unsigned int)breq->hdr.agno,
+				ret,
+				(unsigned int)breq->hdr.icount,
+				(unsigned int)breq->hdr.ocount);
+		if (breq->hdr.ocount == 0)
+			break;
+
+		for (i = 0; i < breq->hdr.ocount; i++) {
+			if (breq->bulkstat[i].bs_ino > endino)
+				break;
+			dump_bulkstat(&breq->bulkstat[i]);
+		}
+	}
+	if (ret) {
+		errno = ret;
+		perror("xfrog_bulkstat");
+		exitcode = 1;
+		return 0;
+	}
+
+	free(breq);
+	return 0;
+}
+
+static void
+bulkstat_single_help(void)
+{
+	printf(_(
+"Queries the filesystem for a single inode's stat information and prints it.\n"
+"\n"
+"   -v   Use this version of the ioctl (1 or 5).\n"
+"\n"
+"Pass in inode numbers or a special inode name:\n"
+"    root    Root directory.\n"));
+}
+
+struct single_map {
+	const char		*tag;
+	uint64_t		code;
+};
+
+struct single_map tags[] = {
+	{"root", XFS_BULK_IREQ_SPECIAL_ROOT},
+	{NULL, 0},
+};
+
+static int
+bulkstat_single_f(
+	int			argc,
+	char			**argv)
+{
+	struct xfs_fd		xfd = XFS_FD_INIT(file->fd);
+	struct xfs_bulkstat	bulkstat;
+	unsigned long		ver = 0;
+	unsigned int		i;
+	int			c;
+	int			ret;
+
+	while ((c = getopt(argc, argv, "v:")) != -1) {
+		switch (c) {
+		case 'v':
+			errno = 0;
+			ver = strtoull(optarg, NULL, 10);
+			if (errno) {
+				perror(optarg);
+				return 1;
+			}
+			if (ver != 1 && ver != 5) {
+				fprintf(stderr, "version must be 1 or 5.\n");
+				return 1;
+			}
+			break;
+		default:
+			bulkstat_single_help();
+			return 0;
+		}
+	}
+
+	ret = xfd_prepare_geometry(&xfd);
+	if (ret) {
+		errno = ret;
+		perror("xfd_prepare_geometry");
+		exitcode = 1;
+		return 0;
+	}
+
+	switch (ver) {
+	case 1:
+		xfd.flags |= XFROG_FLAG_BULKSTAT_FORCE_V1;
+		break;
+	case 5:
+		xfd.flags |= XFROG_FLAG_BULKSTAT_FORCE_V5;
+		break;
+	default:
+		break;
+	}
+
+	for (i = optind; i < argc; i++) {
+		struct single_map	*sm = tags;
+		uint64_t		ino;
+		unsigned int		flags = 0;
+
+		/* Try to look up our tag... */
+		for (sm = tags; sm->tag; sm++) {
+			if (!strcmp(argv[i], sm->tag)) {
+				ino = sm->code;
+				flags |= XFS_BULK_IREQ_SPECIAL;
+				break;
+			}
+		}
+
+		/* ...or else it's an inode number. */
+		if (sm->tag == NULL) {
+			errno = 0;
+			ino = strtoull(argv[i], NULL, 10);
+			if (errno) {
+				perror(argv[i]);
+				exitcode = 1;
+				return 0;
+			}
+		}
+
+		ret = xfrog_bulkstat_single(&xfd, ino, flags, &bulkstat);
+		if (ret) {
+			errno = ret;
+			perror("xfrog_bulkstat_single");
+			continue;
+		}
+
+		if (debug)
+			printf(
+_("bulkstat_single: startino=%"PRIu64" flags=0x%"PRIx32" ret=%d\n"),
+				ino, flags, ret);
+
+		dump_bulkstat(&bulkstat);
+	}
+
+	return 0;
+}
+
+static void
+dump_inumbers(
+	struct xfs_inumbers	*inumbers)
+{
+	printf("xi_startino = %"PRIu64"\n", inumbers->xi_startino);
+	printf("\txi_allocmask = 0x%"PRIx64"\n", inumbers->xi_allocmask);
+	printf("\txi_alloccount = %"PRIu8"\n", inumbers->xi_alloccount);
+	printf("\txi_version = %"PRIu8"\n", inumbers->xi_version);
+}
+
+static void
+inumbers_help(void)
+{
+	printf(_(
+"Queries the filesystem for inode group information and prints it.\n"
+"\n"
+"   -a   Only iterate this AG.\n"
+"   -d   Print debugging output.\n"
+"   -e   Stop after this inode.\n"
+"   -n   Ask for this many results at once.\n"
+"   -s   Inode to start with.\n"
+"   -v   Use this version of the ioctl (1 or 5).\n"));
+}
+
+static int
+inumbers_f(
+	int			argc,
+	char			**argv)
+{
+	struct xfs_fd		xfd = XFS_FD_INIT(file->fd);
+	struct xfs_inumbers_req	*ireq;
+	uint64_t		startino = 0;
+	uint64_t		endino = -1ULL;
+	uint32_t		batch_size = 4096;
+	uint32_t		agno = 0;
+	uint32_t		ver = 0;
+	bool			has_agno = false;
+	unsigned int		i;
+	int			c;
+	int			ret;
+
+	while ((c = getopt(argc, argv, "a:de:n:s:v:")) != -1) {
+		switch (c) {
+		case 'a':
+			agno = cvt_u32(optarg, 10);
+			if (errno) {
+				perror(optarg);
+				return 1;
+			}
+			has_agno = true;
+			break;
+		case 'd':
+			debug = true;
+			break;
+		case 'e':
+			endino = cvt_u64(optarg, 10);
+			if (errno) {
+				perror(optarg);
+				return 1;
+			}
+			break;
+		case 'n':
+			batch_size = cvt_u32(optarg, 10);
+			if (errno) {
+				perror(optarg);
+				return 1;
+			}
+			break;
+		case 's':
+			startino = cvt_u64(optarg, 10);
+			if (errno) {
+				perror(optarg);
+				return 1;
+			}
+			break;
+		case 'v':
+			ver = cvt_u32(optarg, 10);
+			if (errno) {
+				perror(optarg);
+				return 1;
+			}
+			if (ver != 1 && ver != 5) {
+				fprintf(stderr, "version must be 1 or 5.\n");
+				return 1;
+			}
+			break;
+		default:
+			bulkstat_help();
+			return 0;
+		}
+	}
+	if (optind != argc) {
+		bulkstat_help();
+		return 0;
+	}
+
+	ret = xfd_prepare_geometry(&xfd);
+	if (ret) {
+		errno = ret;
+		perror("xfd_prepare_geometry");
+		exitcode = 1;
+		return 0;
+	}
+
+	ireq = xfrog_inumbers_alloc_req(batch_size, startino);
+	if (!ireq) {
+		perror("alloc inumbersreq");
+		exitcode = 1;
+		return 0;
+	}
+
+	if (has_agno)
+		xfrog_inumbers_set_ag(ireq, agno);
+
+	set_xfd_flags(&xfd, ver);
+
+	while ((ret = xfrog_inumbers(&xfd, ireq)) == 0) {
+		if (debug)
+			printf(
+_("bulkstat: startino=%"PRIu64" flags=0x%"PRIx32" agno=%"PRIu32" ret=%d icount=%"PRIu32" ocount=%"PRIu32"\n"),
+				ireq->hdr.ino,
+				ireq->hdr.flags,
+				ireq->hdr.agno,
+				ret,
+				ireq->hdr.icount,
+				ireq->hdr.ocount);
+		if (ireq->hdr.ocount == 0)
+			break;
+
+		for (i = 0; i < ireq->hdr.ocount; i++) {
+			if (ireq->inumbers[i].xi_startino > endino)
+				break;
+			dump_inumbers(&ireq->inumbers[i]);
+		}
+	}
+	if (ret) {
+		errno = ret;
+		perror("xfrog_inumbers");
+		exitcode = 1;
+		return 0;
+	}
+
+	free(ireq);
+	return 0;
+}
+
+static cmdinfo_t	bulkstat_cmd = {
+	.name = "bulkstat",
+	.cfunc = bulkstat_f,
+	.argmin = 0,
+	.argmax = -1,
+	.flags = CMD_NOMAP_OK | CMD_FLAG_ONESHOT,
+	.help = bulkstat_help,
+};
+
+static cmdinfo_t	bulkstat_single_cmd = {
+	.name = "bulkstat_single",
+	.cfunc = bulkstat_single_f,
+	.argmin = 1,
+	.argmax = -1,
+	.flags = CMD_NOMAP_OK | CMD_FLAG_ONESHOT,
+	.help = bulkstat_single_help,
+};
+
+static cmdinfo_t	inumbers_cmd = {
+	.name = "inumbers",
+	.cfunc = inumbers_f,
+	.argmin = 0,
+	.argmax = -1,
+	.flags = CMD_NOMAP_OK | CMD_FLAG_ONESHOT,
+	.help = inumbers_help,
+};
+
+void
+bulkstat_init(void)
+{
+	bulkstat_cmd.args =
+		_("[-a agno] [-d] [-e endino] [-n batchsize] [-s startino]");
+	bulkstat_cmd.oneline = _("Bulk stat of inodes in a filesystem");
+
+	bulkstat_single_cmd.args = _("inum...");
+	bulkstat_single_cmd.oneline = _("Stat one inode in a filesystem");
+
+	inumbers_cmd.args =
+		_("[-a agno] [-d] [-e endino] [-n batchsize] [-s startino]");
+	inumbers_cmd.oneline = _("Query inode groups in a filesystem");
+
+	add_command(&bulkstat_cmd);
+	add_command(&bulkstat_single_cmd);
+	add_command(&inumbers_cmd);
+}
diff --git a/io/init.c b/io/init.c
index 7025aea5..033ed67d 100644
--- a/io/init.c
+++ b/io/init.c
@@ -46,6 +46,7 @@  init_commands(void)
 {
 	attr_init();
 	bmap_init();
+	bulkstat_init();
 	copy_range_init();
 	cowextsize_init();
 	encrypt_init();
diff --git a/io/io.h b/io/io.h
index 00dff2b7..49db902f 100644
--- a/io/io.h
+++ b/io/io.h
@@ -183,3 +183,4 @@  extern void		log_writes_init(void);
 extern void		scrub_init(void);
 extern void		repair_init(void);
 extern void		crc32cselftest_init(void);
+extern void		bulkstat_init(void);
diff --git a/libfrog/bulkstat.c b/libfrog/bulkstat.c
index 85594e5e..538b5197 100644
--- a/libfrog/bulkstat.c
+++ b/libfrog/bulkstat.c
@@ -435,6 +435,16 @@  xfrog_bulkstat_alloc_req(
 	return breq;
 }
 
+/* Set a bulkstat cursor to iterate only a particular AG. */
+void
+xfrog_bulkstat_set_ag(
+	struct xfs_bulkstat_req	*req,
+	uint32_t		agno)
+{
+	req->hdr.agno = agno;
+	req->hdr.flags |= XFS_BULK_IREQ_AGNO;
+}
+
 /* Convert a inumbers data from v5 format to v1 format. */
 void
 xfrog_inumbers_v5_to_v1(
@@ -562,3 +572,13 @@  xfrog_inumbers_alloc_req(
 
 	return ireq;
 }
+
+/* Set an inumbers cursor to iterate only a particular AG. */
+void
+xfrog_inumbers_set_ag(
+	struct xfs_inumbers_req	*req,
+	uint32_t		agno)
+{
+	req->hdr.agno = agno;
+	req->hdr.flags |= XFS_BULK_IREQ_AGNO;
+}
diff --git a/libfrog/bulkstat.h b/libfrog/bulkstat.h
index a085da3d..133a99b8 100644
--- a/libfrog/bulkstat.h
+++ b/libfrog/bulkstat.h
@@ -19,11 +19,14 @@  int xfrog_bulkstat_v5_to_v1(struct xfs_fd *xfd, struct xfs_bstat *bs1,
 void xfrog_bulkstat_v1_to_v5(struct xfs_fd *xfd, struct xfs_bulkstat *bstat,
 		const struct xfs_bstat *bs1);
 
+void xfrog_bulkstat_set_ag(struct xfs_bulkstat_req *req, uint32_t agno);
+
 struct xfs_inogrp;
 int xfrog_inumbers(struct xfs_fd *xfd, struct xfs_inumbers_req *req);
 
 struct xfs_inumbers_req *xfrog_inumbers_alloc_req(uint32_t nr,
 		uint64_t startino);
+void xfrog_inumbers_set_ag(struct xfs_inumbers_req *req, uint32_t agno);
 void xfrog_inumbers_v5_to_v1(struct xfs_inogrp *ig1,
 		const struct xfs_inumbers *ig);
 void xfrog_inumbers_v1_to_v5(struct xfs_inumbers *ig,
diff --git a/man/man8/xfs_io.8 b/man/man8/xfs_io.8
index 6e064bdd..1e09b9e4 100644
--- a/man/man8/xfs_io.8
+++ b/man/man8/xfs_io.8
@@ -996,6 +996,44 @@  for the current memory mapping.
 
 .SH FILESYSTEM COMMANDS
 .TP
+.BI "bulkstat [ \-a " agno " ] [ \-d ] [ \-e " endino " ] [ \-n " batchsize " ] [ \-s " startino " ]
+Display raw stat information about a bunch of inodes in an XFS filesystem.
+Options are as follows:
+.RS 1.0i
+.PD 0
+.TP
+.BI \-a " agno"
+Display only results from the given allocation group.
+If not specified, all results returned will be displayed.
+.TP
+.BI \-d
+Print debugging information about call results.
+.TP
+.BI \-e " endino"
+Stop displaying records when this inode number is reached.
+Defaults to stopping when the system call stops returning results.
+.TP
+.BI \-n " batchsize"
+Retrieve at most this many records per call.
+Defaults to 4,096.
+.TP
+.BI \-s " startino"
+Display inode allocation records starting with this inode.
+Defaults to the first inode in the filesystem.
+.RE
+.PD
+.TP
+.BI "bulkstat_single [ " inum... " | " special... " ]
+Display raw stat information about individual inodes in an XFS filesystem.
+Arguments must be inode numbers or any of the special values:
+.RS 1.0i
+.PD 0
+.TP
+.B root
+Display information about the root directory inode.
+.RE
+.PD
+.TP
 .B freeze
 Suspend all write I/O requests to the filesystem of the current file.
 Only available in expert mode and requires privileges.
@@ -1067,6 +1105,34 @@  was specified on the command line, the maximum possible inode number in
 the system will be printed along with its size.
 .PD
 .TP
+.BI "inumbers [ \-a " agno " ] [ \-d ] [ \-e " endino " ] [ \-n " batchsize " ] [ \-s " startino " ]
+Prints allocation information about groups of inodes in an XFS filesystem.
+Callers can use this information to figure out which inodes are allocated.
+Options are as follows:
+.RS 1.0i
+.PD 0
+.TP
+.BI \-a " agno"
+Display only results from the given allocation group.
+If not specified, all results returned will be displayed.
+.TP
+.BI \-d
+Print debugging information about call results.
+.TP
+.BI \-e " endino"
+Stop displaying records when this inode number is reached.
+Defaults to stopping when the system call stops returning results.
+.TP
+.BI \-n " batchsize"
+Retrieve at most this many records per call.
+Defaults to 4,096.
+.TP
+.BI \-s " startino"
+Display inode allocation records starting with this inode.
+Defaults to the first inode in the filesystem.
+.RE
+.PD
+.TP
 .BI "scrub " type " [ " agnumber " | " "ino" " " "gen" " ]"
 Scrub internal XFS filesystem metadata.  The
 .BI type