diff mbox

[1/9] xfs_io: support the new getfsmap ioctl

Message ID 149417258268.24656.3441330396895207773.stgit@birch.djwong.org (mailing list archive)
State Superseded, archived
Headers show

Commit Message

Darrick J. Wong May 7, 2017, 3:56 p.m. UTC
From: Darrick J. Wong <darrick.wong@oracle.com>

Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
---
 io/Makefile          |    4 
 io/copy_file_range.c |    2 
 io/encrypt.c         |    1 
 io/fsmap.c           |  559 ++++++++++++++++++++++++++++++++++++++++++++++++++
 io/init.c            |    8 +
 io/io.h              |   14 +
 io/open.c            |   21 ++
 io/pwrite.c          |    2 
 io/reflink.c         |    4 
 io/sendfile.c        |    2 
 man/man8/xfs_io.8    |   47 ++++
 11 files changed, 651 insertions(+), 13 deletions(-)
 create mode 100644 io/fsmap.c



--
To unsubscribe from this list: send the line "unsubscribe linux-xfs" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Comments

Eric Sandeen May 8, 2017, 9:01 p.m. UTC | #1
On 5/7/17 10:56 AM, Darrick J. Wong wrote:
> From: Darrick J. Wong <darrick.wong@oracle.com>
> 
> Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
> ---
>  io/Makefile          |    4 
>  io/copy_file_range.c |    2 
>  io/encrypt.c         |    1 
>  io/fsmap.c           |  559 ++++++++++++++++++++++++++++++++++++++++++++++++++
>  io/init.c            |    8 +
>  io/io.h              |   14 +
>  io/open.c            |   21 ++
>  io/pwrite.c          |    2 
>  io/reflink.c         |    4 
>  io/sendfile.c        |    2 
>  man/man8/xfs_io.8    |   47 ++++
>  11 files changed, 651 insertions(+), 13 deletions(-)
>  create mode 100644 io/fsmap.c
> 
> 
> diff --git a/io/Makefile b/io/Makefile
> index 435ccff..8d3a30e 100644
> --- a/io/Makefile
> +++ b/io/Makefile
> @@ -99,6 +99,10 @@ ifeq ($(HAVE_MREMAP),yes)
>  LCFLAGS += -DHAVE_MREMAP
>  endif
>  
> +ifeq ($(HAVE_GETFSMAP),yes)
> +CFILES += fsmap.c
> +endif
> +
>  default: depend $(LTCOMMAND)
>  
>  include $(BUILDRULES)
> diff --git a/io/copy_file_range.c b/io/copy_file_range.c
> index 249c649..d1dfc5a 100644
> --- a/io/copy_file_range.c
> +++ b/io/copy_file_range.c
> @@ -121,7 +121,7 @@ copy_range_f(int argc, char **argv)
>  	if (optind != argc - 1)
>  		return command_usage(&copy_range_cmd);
>  
> -	fd = openfile(argv[optind], NULL, IO_READONLY, 0);
> +	fd = openfile(argv[optind], NULL, IO_READONLY, 0, NULL);
>  	if (fd < 0)
>  		return 0;
>  
> diff --git a/io/encrypt.c b/io/encrypt.c
> index d844c5e..26ab97c 100644
> --- a/io/encrypt.c
> +++ b/io/encrypt.c
> @@ -20,6 +20,7 @@
>  #include "platform_defs.h"
>  #include "command.h"
>  #include "init.h"
> +#include "path.h"
>  #include "io.h"
>  
>  #ifndef ARRAY_SIZE
> diff --git a/io/fsmap.c b/io/fsmap.c
> new file mode 100644
> index 0000000..4128fae
> --- /dev/null
> +++ b/io/fsmap.c
> @@ -0,0 +1,559 @@
> +/*
> + * Copyright (C) 2017 Oracle.  All Rights Reserved.
> + *
> + * Author: Darrick J. Wong <darrick.wong@oracle.com>
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License
> + * as published by the Free Software Foundation; either version 2
> + * of the License, or (at your option) any later version.
> + *
> + * This program is distributed in the hope that it would be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write the Free Software Foundation,
> + * Inc.,  51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA.
> + */
> +#include "platform_defs.h"
> +#include "command.h"
> +#include "init.h"
> +#include "path.h"
> +#include "io.h"
> +#include "input.h"
> +
> +static cmdinfo_t	fsmap_cmd;
> +static dev_t		xfs_data_dev;
> +
> +static void
> +fsmap_help(void)
> +{
> +	printf(_(
> +"\n"
> +" prints the block mapping for an XFS filesystem"

Did you want an \n" on the above line too?  Seems a bit odd to have it only on the next line.
(one \n is fine too, but not like this) :)

> +"\n"
> +" Example:\n"
> +" 'fsmap -dlrv [-n nr] [startoff] [endoff]' - tabular format verbose map, including unwritten extents\n"

This doesn't match the short help:

+	fsmap_cmd.args = _("[-v] [-n nx] [start] [end]");

or the manpage:

+.BI "fsmap [ \-v ] [ \-n " nx " ] [ " start " ] [ " end " ]

but getopt sez:

> +	while ((c = getopt(argc, argv, "dln:rv")) != EOF) {

so I guess the short help & manpage need updates.

Also, I don't think the "Example:" above is valid:

        if (dflag + lflag + rflag > 1)
                return command_usage(&fsmap_cmd);

so the help needs to indicate that exactly 1 of -d, -l, or -r is required.

> +"\n"
> +" fsmap prints the map of disk blocks used by the whole filesystem.\n"
> +" The map lists each extent used by the file

<newbie>

"the file?"  what file?

                                               , as well as regions in the\n"
> +" filesystem that do not have any corresponding blocks (free space).\n"
> +" By default, each line of the listing takes the following form:\n"
> +"     extent: [startoffset..endoffset] owner startblock..endblock\n"

say what "owner" means in this case?  </newbie>

> +" All the file offsets and disk blocks are in units of 512-byte blocks.\n"
> +" -d -- query only the data device.\n"
> +" -l -- query only the log device.\n"
> +" -r -- query only the realtime device.\n"
> +" -n -- query n extents.\n"
> +" -v -- Verbose information, specify ag info.  Show flags legend on 2nd -v\n"
> +"\n"));
> +}
> +
> +static int
> +numlen(
> +	off64_t	val)
> +{
> +	off64_t	tmp;
> +	int	len;
> +
> +	for (len = 0, tmp = val; tmp > 0; tmp = tmp/10)
> +		len++;
> +	return (len == 0 ? 1 : len);
> +}

Hm, copy #3, do we still not have anywhere to put common stuff like this?

> +
> +#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];
> +
> +	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));
> +		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 extent map"),
> +				(long long) p->fmr_owner);
> +		else
> +			printf(_("inode %lld %lld..%lld"),
> +				(long long)p->fmr_owner,
> +				(long long)BTOBBT(p->fmr_offset),
> +				(long long)BTOBBT(p->fmr_offset + p->fmr_length - 1));
> +		printf(_(" %lld blocks\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_SHARED	01000000	/* shared extent */
> +#define	FLG_ATTR_FORK	00100000	/* attribute fork */
> +#define	FLG_PRE		00010000	/* Unwritten extent */
> +#define	FLG_BSU		00001000	/* Not on begin of stripe unit  */
> +#define	FLG_ESU		00000100	/* Not on end   of stripe unit  */
> +#define	FLG_BSW		00000010	/* Not on begin of stripe width */
> +#define	FLG_ESW		00000001	/* Not on end   of stripe width */

These really couldn't share w/ io/bmap.c?  :(
(could FLG_ATTR_FORK go to the end so that it matches bmap until then?)

speakinawhich, I wonder how much of this is copied from bmap, and what could
be shared?

I'm going to go off & look at that a bit, may have further comments.  :)


> diff --git a/man/man8/xfs_io.8 b/man/man8/xfs_io.8
> index 29a036c..2c956b8 100644
> --- a/man/man8/xfs_io.8
> +++ b/man/man8/xfs_io.8
> @@ -301,6 +301,53 @@ ioctl.  Options behave as described in the
>  .BR xfs_bmap (8)
>  manual page.
>  .TP
> +.BI "fsmap [ \-v ] [ \-n " nx " ] [ " start " ] [ " end " ]
> +Prints the mapping of disk blocks used by an XFS filesystem.

FOREIGN_OK too right?

-Eric

>  The map
> +lists each extent used by files, allocation group metadata,
> +journalling logs, and static filesystem metadata, as well as any
> +regions that are unused.  Each line of the listings takes the
> +following form:
> +.PP
> +.RS
> +.IR extent ": " major ":" minor " [" startblock .. endblock "]: " owner " " startoffset .. endoffset " " length
> +.PP
> +Static filesystem metadata, allocation group metadata, btrees,
> +journalling logs, and free space are marked by replacing the
> +.IR startoffset .. endoffset
> +with the appropriate marker.  All blocks, offsets, and lengths are specified
> +in units of 512-byte blocks, no matter what the filesystem's block size is.
> +.BI "The optional " start " and " end " arguments can be used to constrain
> +the output to a particular range of disk blocks.
> +.RE
> +.RS 1.0i
> +.PD 0
> +.TP
> +.BI \-n " num_extents"
> +If this option is given,
> +.B xfs_fsmap
> +obtains the extent list of the file in groups of
> +.I num_extents
> +extents. In the absence of
> +.BR \-n ", " xfs_fsmap
> +queries the system for the number of extents in the filesystem and uses that
> +value to compute the group size.
> +.TP
> +.B \-v
> +Shows verbose information. When this flag is specified, additional AG
> +specific information is appended to each line in the following form:
> +.IP
> +.RS 1.2i
> +.IR agno " (" startagblock .. endagblock ") " nblocks " " flags
> +.RE
> +.IP
> +A second
> +.B \-v
> +option will print out the
> +.I flags
> +legend.
> +.RE
> +.PD
> +.TP
>  .BI "extsize [ \-R | \-D ] [ " value " ]"
>  Display and/or modify the preferred extent size used when allocating
>  space for the currently open file. If the
> 
> --
> To unsubscribe from this list: send the line "unsubscribe linux-xfs" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> 
--
To unsubscribe from this list: send the line "unsubscribe linux-xfs" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Darrick J. Wong May 15, 2017, 7:18 p.m. UTC | #2
On Mon, May 08, 2017 at 04:01:11PM -0500, Eric Sandeen wrote:
> On 5/7/17 10:56 AM, Darrick J. Wong wrote:
> > From: Darrick J. Wong <darrick.wong@oracle.com>
> > 
> > Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
> > ---
> >  io/Makefile          |    4 
> >  io/copy_file_range.c |    2 
> >  io/encrypt.c         |    1 
> >  io/fsmap.c           |  559 ++++++++++++++++++++++++++++++++++++++++++++++++++
> >  io/init.c            |    8 +
> >  io/io.h              |   14 +
> >  io/open.c            |   21 ++
> >  io/pwrite.c          |    2 
> >  io/reflink.c         |    4 
> >  io/sendfile.c        |    2 
> >  man/man8/xfs_io.8    |   47 ++++
> >  11 files changed, 651 insertions(+), 13 deletions(-)
> >  create mode 100644 io/fsmap.c
> > 
> > 
> > diff --git a/io/Makefile b/io/Makefile
> > index 435ccff..8d3a30e 100644
> > --- a/io/Makefile
> > +++ b/io/Makefile
> > @@ -99,6 +99,10 @@ ifeq ($(HAVE_MREMAP),yes)
> >  LCFLAGS += -DHAVE_MREMAP
> >  endif
> >  
> > +ifeq ($(HAVE_GETFSMAP),yes)
> > +CFILES += fsmap.c
> > +endif
> > +
> >  default: depend $(LTCOMMAND)
> >  
> >  include $(BUILDRULES)
> > diff --git a/io/copy_file_range.c b/io/copy_file_range.c
> > index 249c649..d1dfc5a 100644
> > --- a/io/copy_file_range.c
> > +++ b/io/copy_file_range.c
> > @@ -121,7 +121,7 @@ copy_range_f(int argc, char **argv)
> >  	if (optind != argc - 1)
> >  		return command_usage(&copy_range_cmd);
> >  
> > -	fd = openfile(argv[optind], NULL, IO_READONLY, 0);
> > +	fd = openfile(argv[optind], NULL, IO_READONLY, 0, NULL);
> >  	if (fd < 0)
> >  		return 0;
> >  
> > diff --git a/io/encrypt.c b/io/encrypt.c
> > index d844c5e..26ab97c 100644
> > --- a/io/encrypt.c
> > +++ b/io/encrypt.c
> > @@ -20,6 +20,7 @@
> >  #include "platform_defs.h"
> >  #include "command.h"
> >  #include "init.h"
> > +#include "path.h"
> >  #include "io.h"
> >  
> >  #ifndef ARRAY_SIZE
> > diff --git a/io/fsmap.c b/io/fsmap.c
> > new file mode 100644
> > index 0000000..4128fae
> > --- /dev/null
> > +++ b/io/fsmap.c
> > @@ -0,0 +1,559 @@
> > +/*
> > + * Copyright (C) 2017 Oracle.  All Rights Reserved.
> > + *
> > + * Author: Darrick J. Wong <darrick.wong@oracle.com>
> > + *
> > + * This program is free software; you can redistribute it and/or
> > + * modify it under the terms of the GNU General Public License
> > + * as published by the Free Software Foundation; either version 2
> > + * of the License, or (at your option) any later version.
> > + *
> > + * This program is distributed in the hope that it would be useful,
> > + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> > + * GNU General Public License for more details.
> > + *
> > + * You should have received a copy of the GNU General Public License
> > + * along with this program; if not, write the Free Software Foundation,
> > + * Inc.,  51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA.
> > + */
> > +#include "platform_defs.h"
> > +#include "command.h"
> > +#include "init.h"
> > +#include "path.h"
> > +#include "io.h"
> > +#include "input.h"
> > +
> > +static cmdinfo_t	fsmap_cmd;
> > +static dev_t		xfs_data_dev;
> > +
> > +static void
> > +fsmap_help(void)
> > +{
> > +	printf(_(
> > +"\n"
> > +" prints the block mapping for an XFS filesystem"
> 
> Did you want an \n" on the above line too?  Seems a bit odd to have it only on the next line.
> (one \n is fine too, but not like this) :)
> 
> > +"\n"
> > +" Example:\n"
> > +" 'fsmap -dlrv [-n nr] [startoff] [endoff]' - tabular format verbose map, including unwritten extents\n"
> 
> This doesn't match the short help:
> 
> +	fsmap_cmd.args = _("[-v] [-n nx] [start] [end]");
> 
> or the manpage:
> 
> +.BI "fsmap [ \-v ] [ \-n " nx " ] [ " start " ] [ " end " ]
> 
> but getopt sez:
> 
> > +	while ((c = getopt(argc, argv, "dln:rv")) != EOF) {
> 
> so I guess the short help & manpage need updates.
> 
> Also, I don't think the "Example:" above is valid:
> 
>         if (dflag + lflag + rflag > 1)
>                 return command_usage(&fsmap_cmd);
> 
> so the help needs to indicate that exactly 1 of -d, -l, or -r is required.
> 
> > +"\n"
> > +" fsmap prints the map of disk blocks used by the whole filesystem.\n"
> > +" The map lists each extent used by the file
> 
> <newbie>
> 
> "the file?"  what file?
> 
>                                                , as well as regions in the\n"
> > +" filesystem that do not have any corresponding blocks (free space).\n"
> > +" By default, each line of the listing takes the following form:\n"
> > +"     extent: [startoffset..endoffset] owner startblock..endblock\n"
> 
> say what "owner" means in this case?  </newbie>
> 
> > +" All the file offsets and disk blocks are in units of 512-byte blocks.\n"
> > +" -d -- query only the data device.\n"
> > +" -l -- query only the log device.\n"
> > +" -r -- query only the realtime device.\n"
> > +" -n -- query n extents.\n"
> > +" -v -- Verbose information, specify ag info.  Show flags legend on 2nd -v\n"
> > +"\n"));
> > +}
> > +
> > +static int
> > +numlen(
> > +	off64_t	val)
> > +{
> > +	off64_t	tmp;
> > +	int	len;
> > +
> > +	for (len = 0, tmp = val; tmp > 0; tmp = tmp/10)
> > +		len++;
> > +	return (len == 0 ? 1 : len);
> > +}
> 
> Hm, copy #3, do we still not have anywhere to put common stuff like this?
> 
> > +
> > +#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];
> > +
> > +	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));
> > +		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 extent map"),
> > +				(long long) p->fmr_owner);
> > +		else
> > +			printf(_("inode %lld %lld..%lld"),
> > +				(long long)p->fmr_owner,
> > +				(long long)BTOBBT(p->fmr_offset),
> > +				(long long)BTOBBT(p->fmr_offset + p->fmr_length - 1));
> > +		printf(_(" %lld blocks\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_SHARED	01000000	/* shared extent */
> > +#define	FLG_ATTR_FORK	00100000	/* attribute fork */
> > +#define	FLG_PRE		00010000	/* Unwritten extent */
> > +#define	FLG_BSU		00001000	/* Not on begin of stripe unit  */
> > +#define	FLG_ESU		00000100	/* Not on end   of stripe unit  */
> > +#define	FLG_BSW		00000010	/* Not on begin of stripe width */
> > +#define	FLG_ESW		00000001	/* Not on end   of stripe width */
> 
> These really couldn't share w/ io/bmap.c?  :(
> (could FLG_ATTR_FORK go to the end so that it matches bmap until then?)

Well yes, we could flip them (I already did); afaict the only change
that needs to happen is an update to xfs/274.

> speakinawhich, I wonder how much of this is copied from bmap, and what could
> be shared?
> 
> I'm going to go off & look at that a bit, may have further comments.  :)

Do you have further comments?

--D

> 
> 
> > diff --git a/man/man8/xfs_io.8 b/man/man8/xfs_io.8
> > index 29a036c..2c956b8 100644
> > --- a/man/man8/xfs_io.8
> > +++ b/man/man8/xfs_io.8
> > @@ -301,6 +301,53 @@ ioctl.  Options behave as described in the
> >  .BR xfs_bmap (8)
> >  manual page.
> >  .TP
> > +.BI "fsmap [ \-v ] [ \-n " nx " ] [ " start " ] [ " end " ]
> > +Prints the mapping of disk blocks used by an XFS filesystem.
> 
> FOREIGN_OK too right?
> 
> -Eric
> 
> >  The map
> > +lists each extent used by files, allocation group metadata,
> > +journalling logs, and static filesystem metadata, as well as any
> > +regions that are unused.  Each line of the listings takes the
> > +following form:
> > +.PP
> > +.RS
> > +.IR extent ": " major ":" minor " [" startblock .. endblock "]: " owner " " startoffset .. endoffset " " length
> > +.PP
> > +Static filesystem metadata, allocation group metadata, btrees,
> > +journalling logs, and free space are marked by replacing the
> > +.IR startoffset .. endoffset
> > +with the appropriate marker.  All blocks, offsets, and lengths are specified
> > +in units of 512-byte blocks, no matter what the filesystem's block size is.
> > +.BI "The optional " start " and " end " arguments can be used to constrain
> > +the output to a particular range of disk blocks.
> > +.RE
> > +.RS 1.0i
> > +.PD 0
> > +.TP
> > +.BI \-n " num_extents"
> > +If this option is given,
> > +.B xfs_fsmap
> > +obtains the extent list of the file in groups of
> > +.I num_extents
> > +extents. In the absence of
> > +.BR \-n ", " xfs_fsmap
> > +queries the system for the number of extents in the filesystem and uses that
> > +value to compute the group size.
> > +.TP
> > +.B \-v
> > +Shows verbose information. When this flag is specified, additional AG
> > +specific information is appended to each line in the following form:
> > +.IP
> > +.RS 1.2i
> > +.IR agno " (" startagblock .. endagblock ") " nblocks " " flags
> > +.RE
> > +.IP
> > +A second
> > +.B \-v
> > +option will print out the
> > +.I flags
> > +legend.
> > +.RE
> > +.PD
> > +.TP
> >  .BI "extsize [ \-R | \-D ] [ " value " ]"
> >  Display and/or modify the preferred extent size used when allocating
> >  space for the currently open file. If the
> > 
> > --
> > To unsubscribe from this list: send the line "unsubscribe linux-xfs" in
> > the body of a message to majordomo@vger.kernel.org
> > More majordomo info at  http://vger.kernel.org/majordomo-info.html
> > 
--
To unsubscribe from this list: send the line "unsubscribe linux-xfs" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Eric Sandeen May 15, 2017, 7:30 p.m. UTC | #3
On 5/15/17 2:18 PM, Darrick J. Wong wrote:
>> speakinawhich, I wonder how much of this is copied from bmap, and what could
>> be shared?
>>
>> I'm going to go off & look at that a bit, may have further comments.  :)
> Do you have further comments?

Not on this patch, I guess - I had thought that there was an inordinate amount
of cut & paste from bmap but at least a quick look didn't bear that out.

TBH haven't looked at the rest of the series yet, I keep getting sucked
into other things, sorry.

-Eric
--
To unsubscribe from this list: send the line "unsubscribe linux-xfs" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/io/Makefile b/io/Makefile
index 435ccff..8d3a30e 100644
--- a/io/Makefile
+++ b/io/Makefile
@@ -99,6 +99,10 @@  ifeq ($(HAVE_MREMAP),yes)
 LCFLAGS += -DHAVE_MREMAP
 endif
 
+ifeq ($(HAVE_GETFSMAP),yes)
+CFILES += fsmap.c
+endif
+
 default: depend $(LTCOMMAND)
 
 include $(BUILDRULES)
diff --git a/io/copy_file_range.c b/io/copy_file_range.c
index 249c649..d1dfc5a 100644
--- a/io/copy_file_range.c
+++ b/io/copy_file_range.c
@@ -121,7 +121,7 @@  copy_range_f(int argc, char **argv)
 	if (optind != argc - 1)
 		return command_usage(&copy_range_cmd);
 
-	fd = openfile(argv[optind], NULL, IO_READONLY, 0);
+	fd = openfile(argv[optind], NULL, IO_READONLY, 0, NULL);
 	if (fd < 0)
 		return 0;
 
diff --git a/io/encrypt.c b/io/encrypt.c
index d844c5e..26ab97c 100644
--- a/io/encrypt.c
+++ b/io/encrypt.c
@@ -20,6 +20,7 @@ 
 #include "platform_defs.h"
 #include "command.h"
 #include "init.h"
+#include "path.h"
 #include "io.h"
 
 #ifndef ARRAY_SIZE
diff --git a/io/fsmap.c b/io/fsmap.c
new file mode 100644
index 0000000..4128fae
--- /dev/null
+++ b/io/fsmap.c
@@ -0,0 +1,559 @@ 
+/*
+ * Copyright (C) 2017 Oracle.  All Rights Reserved.
+ *
+ * Author: Darrick J. Wong <darrick.wong@oracle.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it would be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write the Free Software Foundation,
+ * Inc.,  51 Franklin St, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+#include "platform_defs.h"
+#include "command.h"
+#include "init.h"
+#include "path.h"
+#include "io.h"
+#include "input.h"
+
+static cmdinfo_t	fsmap_cmd;
+static dev_t		xfs_data_dev;
+
+static void
+fsmap_help(void)
+{
+	printf(_(
+"\n"
+" prints the block mapping for an XFS filesystem"
+"\n"
+" Example:\n"
+" 'fsmap -dlrv [-n nr] [startoff] [endoff]' - tabular format verbose map, including unwritten extents\n"
+"\n"
+" fsmap prints the map of disk blocks used by the whole filesystem.\n"
+" The map lists each extent used by the file, as well as regions in the\n"
+" filesystem that do not have any corresponding blocks (free space).\n"
+" By default, each line of the listing takes the following form:\n"
+"     extent: [startoffset..endoffset] owner startblock..endblock\n"
+" All the file offsets and disk blocks are in units of 512-byte blocks.\n"
+" -d -- query only the data device.\n"
+" -l -- query only the log device.\n"
+" -r -- query only the realtime device.\n"
+" -n -- query n extents.\n"
+" -v -- Verbose information, specify ag info.  Show flags legend on 2nd -v\n"
+"\n"));
+}
+
+static int
+numlen(
+	off64_t	val)
+{
+	off64_t	tmp;
+	int	len;
+
+	for (len = 0, tmp = val; tmp > 0; tmp = tmp/10)
+		len++;
+	return (len == 0 ? 1 : len);
+}
+
+#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];
+
+	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));
+		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 extent map"),
+				(long long) p->fmr_owner);
+		else
+			printf(_("inode %lld %lld..%lld"),
+				(long long)p->fmr_owner,
+				(long long)BTOBBT(p->fmr_offset),
+				(long long)BTOBBT(p->fmr_offset + p->fmr_length - 1));
+		printf(_(" %lld blocks\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_SHARED	01000000	/* shared extent */
+#define	FLG_ATTR_FORK	00100000	/* attribute fork */
+#define	FLG_PRE		00010000	/* Unwritten extent */
+#define	FLG_BSU		00001000	/* Not on begin of stripe unit  */
+#define	FLG_ESU		00000100	/* Not on end   of stripe unit  */
+#define	FLG_BSW		00000010	/* Not on begin of stripe width */
+#define	FLG_ESW		00000001	/* Not on end   of stripe width */
+static void
+dump_map_verbose(
+	unsigned long long	*nr,
+	struct fsmap_head	*head,
+	bool			*dumped_flags,
+	struct xfs_fsop_geom	*fsgeo)
+{
+	unsigned long long	i;
+	struct fsmap		*p;
+	int			agno;
+	off64_t			agoff, bperag;
+	int			foff_w, boff_w, aoff_w, tot_w, agno_w, own_w;
+	int			nr_w, dev_w;
+	char			rbuf[32], bbuf[32], abuf[32], obuf[32];
+	char			nbuf[32], dbuf[32], gbuf[32];
+	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 = (off64_t)fsgeo->agblocks *
+		  (off64_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_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)));
+	}
+	agno_w = max(MINAG_WIDTH, numlen(fsgeo->agcount));
+	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) {
+			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 Shared extent\n"),
+		NFLG+1, NFLG+1, FLG_SHARED);
+	printf(_("    %*.*o Attribute fork\n"),
+		NFLG+1, NFLG+1, FLG_ATTR_FORK);
+	printf(_("    %*.*o Unwritten preallocated extent\n"),
+		NFLG+1, NFLG+1, FLG_PRE);
+	printf(_("    %*.*o Doesn't begin on stripe unit\n"),
+		NFLG+1, NFLG+1, FLG_BSU);
+	printf(_("    %*.*o Doesn't end   on stripe unit\n"),
+		NFLG+1, NFLG+1, FLG_ESU);
+	printf(_("    %*.*o Doesn't begin on stripe width\n"),
+		NFLG+1, NFLG+1, FLG_BSW);
+	printf(_("    %*.*o Doesn't end   on stripe width\n"),
+		NFLG+1, NFLG+1, FLG_ESW);
+}
+
+int
+fsmap_f(
+	int			argc,
+	char			**argv)
+{
+	struct fsmap		*p;
+	struct fsmap_head	*nhead;
+	struct fsmap_head	*head;
+	struct fsmap		*l, *h;
+	struct xfs_fsop_geom	fsgeo;
+	long long		start = 0;
+	long long		end = -1;
+	int			nmap_size;
+	int			map_size;
+	int			nflag = 0;
+	int			vflag = 0;
+	int			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, "dln:rv")) != EOF) {
+		switch (c) {
+		case 'd':	/* data device */
+			dflag = 1;
+			break;
+		case 'l':	/* log device */
+			lflag = 1;
+			break;
+		case 'n':	/* number of extents specified */
+			nflag = atoi(optarg);
+			break;
+		case 'r':	/* rt device */
+			rflag = 1;
+			break;
+		case 'v':	/* Verbose output */
+			vflag++;
+			break;
+		default:
+			return command_usage(&fsmap_cmd);
+		}
+	}
+
+	if (dflag + lflag + rflag > 1)
+		return command_usage(&fsmap_cmd);
+
+	if (argc > optind && dflag + lflag + rflag == 0)
+		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]);
+			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]);
+			return 0;
+		}
+		end <<= BBSHIFT;
+	}
+
+	if (vflag) {
+		c = ioctl(file->fd, XFS_IOC_FSGEOMETRY, &fsgeo);
+		if (c < 0) {
+			fprintf(stderr,
+				_("%s: can't get geometry [\"%s\"]: %s\n"),
+				progname, file->name, strerror(errno));
+			exitcode = 1;
+			return 0;
+		}
+	}
+
+	map_size = nflag ? nflag : 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;
+
+	/* Count mappings */
+	if (!nflag) {
+		head->fmh_count = 0;
+		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 > map_size + 2) {
+			map_size = 11ULL * head->fmh_entries / 10;
+			nmap_size = map_size > (1 << 24) ? (1 << 24) : map_size;
+			nhead = realloc(head, fsmap_sizeof(nmap_size));
+			if (nhead == NULL) {
+				fprintf(stderr,
+					_("%s: cannot realloc %zu bytes\n"),
+					progname, fsmap_sizeof(nmap_size));
+			} else {
+				head = nhead;
+				map_size = nmap_size;
+			}
+		}
+	}
+
+	/*
+	 * 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(&nr, head);
+		else
+			dump_map_verbose(&nr, head, &dumped_flags, &fsgeo);
+
+		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 = _("[-v] [-n nx] [start] [end]");
+	fsmap_cmd.oneline = _("print filesystem mapping for a range of blocks");
+	fsmap_cmd.help = fsmap_help;
+
+	add_command(&fsmap_cmd);
+}
diff --git a/io/init.c b/io/init.c
index c15a1e1..20d5f80 100644
--- a/io/init.c
+++ b/io/init.c
@@ -66,6 +66,7 @@  init_commands(void)
 	file_init();
 	flink_init();
 	freeze_init();
+	fsmap_init();
 	fsync_init();
 	getrusage_init();
 	help_init();
@@ -139,6 +140,7 @@  init(
 	char		*sp;
 	mode_t		mode = 0600;
 	xfs_fsop_geom_t	geometry = { 0 };
+	struct fs_path	fsp;
 
 	progname = basename(argv[0]);
 	setlocale(LC_ALL, "");
@@ -148,6 +150,7 @@  init(
 	pagesize = getpagesize();
 	gettimeofday(&stopwatch, NULL);
 
+	fs_table_initialise(0, NULL, 0, NULL);
 	while ((c = getopt(argc, argv, "ac:C:dFfim:p:nrRstTVx")) != EOF) {
 		switch (c) {
 		case 'a':
@@ -212,11 +215,12 @@  init(
 	}
 
 	while (optind < argc) {
-		if ((c = openfile(argv[optind], &geometry, flags, mode)) < 0)
+		c = openfile(argv[optind], &geometry, flags, mode, &fsp);
+		if (c < 0)
 			exit(1);
 		if (!platform_test_xfs_fd(c))
 			flags |= IO_FOREIGN;
-		if (addfile(argv[optind], c, &geometry, flags) < 0)
+		if (addfile(argv[optind], c, &geometry, flags, &fsp) < 0)
 			exit(1);
 		optind++;
 	}
diff --git a/io/io.h b/io/io.h
index 952bdb8..6a0fe65 100644
--- a/io/io.h
+++ b/io/io.h
@@ -17,6 +17,7 @@ 
  */
 
 #include "xfs.h"
+#include "path.h"
 
 /*
  * Read/write patterns (default is always "forward")
@@ -47,6 +48,7 @@  typedef struct fileio {
 	int		flags;		/* flags describing file state */
 	char		*name;		/* file name at time of open */
 	xfs_fsop_geom_t	geom;		/* XFS filesystem geometry */
+	struct fs_path	fs_path;	/* XFS path information */
 } fileio_t;
 
 extern fileio_t		*filetable;	/* open file table */
@@ -76,8 +78,10 @@  extern void *check_mapping_range(mmap_region_t *, off64_t, size_t, int);
  */
 
 extern off64_t		filesize(void);
-extern int		openfile(char *, xfs_fsop_geom_t *, int, mode_t);
-extern int		addfile(char *, int , xfs_fsop_geom_t *, int);
+extern int		openfile(char *, xfs_fsop_geom_t *, int, mode_t,
+				 struct fs_path *);
+extern int		addfile(char *, int , xfs_fsop_geom_t *, int,
+				struct fs_path *);
 extern void		printxattr(uint, int, int, const char *, int, int);
 
 extern unsigned int	recurse_all;
@@ -174,3 +178,9 @@  extern void		readdir_init(void);
 extern void		reflink_init(void);
 
 extern void		cowextsize_init(void);
+
+#ifdef HAVE_GETFSMAP
+extern void		fsmap_init(void);
+#else
+# define fsmap_init()	do { } while (0)
+#endif
diff --git a/io/open.c b/io/open.c
index 2ed55cf..b50f068 100644
--- a/io/open.c
+++ b/io/open.c
@@ -52,8 +52,10 @@  openfile(
 	char		*path,
 	xfs_fsop_geom_t	*geom,
 	int		flags,
-	mode_t		mode)
+	mode_t		mode,
+	struct fs_path	*fs_path)
 {
+	struct fs_path	*fsp;
 	int		fd;
 	int		oflags;
 
@@ -118,6 +120,14 @@  openfile(
 			}
 		}
 	}
+
+	if (fs_path) {
+		fsp = fs_table_lookup(path, FS_MOUNT_POINT);
+		if (!fsp)
+			memset(fs_path, 0, sizeof(*fs_path));
+		else
+			*fs_path = *fsp;
+	}
 	return fd;
 }
 
@@ -126,7 +136,8 @@  addfile(
 	char		*name,
 	int		fd,
 	xfs_fsop_geom_t	*geometry,
-	int		flags)
+	int		flags,
+	struct fs_path	*fs_path)
 {
 	char		*filename;
 
@@ -154,6 +165,7 @@  addfile(
 	file->flags = flags;
 	file->name = filename;
 	file->geom = *geometry;
+	file->fs_path = *fs_path;
 	return 0;
 }
 
@@ -195,6 +207,7 @@  open_f(
 	char		*sp;
 	mode_t		mode = 0600;
 	xfs_fsop_geom_t	geometry = { 0 };
+	struct fs_path	fsp;
 
 	if (argc == 1) {
 		if (file)
@@ -257,14 +270,14 @@  open_f(
 		return -1;
 	}
 
-	fd = openfile(argv[optind], &geometry, flags, mode);
+	fd = openfile(argv[optind], &geometry, flags, mode, &fsp);
 	if (fd < 0)
 		return 0;
 
 	if (!platform_test_xfs_fd(fd))
 		flags |= IO_FOREIGN;
 
-	addfile(argv[optind], fd, &geometry, flags);
+	addfile(argv[optind], fd, &geometry, flags, &fsp);
 	return 0;
 }
 
diff --git a/io/pwrite.c b/io/pwrite.c
index 7c0bb7f..1c5dfca 100644
--- a/io/pwrite.c
+++ b/io/pwrite.c
@@ -357,7 +357,7 @@  pwrite_f(
 		return 0;
 
 	c = IO_READONLY | (dflag ? IO_DIRECT : 0);
-	if (infile && ((fd = openfile(infile, NULL, c, 0)) < 0))
+	if (infile && ((fd = openfile(infile, NULL, c, 0, NULL)) < 0))
 		return 0;
 
 	gettimeofday(&t1, NULL);
diff --git a/io/reflink.c b/io/reflink.c
index fe05d1e..f584e8f 100644
--- a/io/reflink.c
+++ b/io/reflink.c
@@ -154,7 +154,7 @@  dedupe_f(
 		return 0;
 	}
 
-	fd = openfile(infile, NULL, IO_READONLY, 0);
+	fd = openfile(infile, NULL, IO_READONLY, 0, NULL);
 	if (fd < 0)
 		return 0;
 
@@ -278,7 +278,7 @@  reflink_f(
 	}
 
 clone_all:
-	fd = openfile(infile, NULL, IO_READONLY, 0);
+	fd = openfile(infile, NULL, IO_READONLY, 0, NULL);
 	if (fd < 0)
 		return 0;
 
diff --git a/io/sendfile.c b/io/sendfile.c
index edd31c9..063fa7f 100644
--- a/io/sendfile.c
+++ b/io/sendfile.c
@@ -115,7 +115,7 @@  sendfile_f(
 
 	if (!infile)
 		fd = filetable[fd].fd;
-	else if ((fd = openfile(infile, NULL, IO_READONLY, 0)) < 0)
+	else if ((fd = openfile(infile, NULL, IO_READONLY, 0, NULL)) < 0)
 		return 0;
 
 	if (optind == argc - 2) {
diff --git a/man/man8/xfs_io.8 b/man/man8/xfs_io.8
index 29a036c..2c956b8 100644
--- a/man/man8/xfs_io.8
+++ b/man/man8/xfs_io.8
@@ -301,6 +301,53 @@  ioctl.  Options behave as described in the
 .BR xfs_bmap (8)
 manual page.
 .TP
+.BI "fsmap [ \-v ] [ \-n " nx " ] [ " start " ] [ " end " ]
+Prints the mapping of disk blocks used by an XFS filesystem.  The map
+lists each extent used by files, allocation group metadata,
+journalling logs, and static filesystem metadata, as well as any
+regions that are unused.  Each line of the listings takes the
+following form:
+.PP
+.RS
+.IR extent ": " major ":" minor " [" startblock .. endblock "]: " owner " " startoffset .. endoffset " " length
+.PP
+Static filesystem metadata, allocation group metadata, btrees,
+journalling logs, and free space are marked by replacing the
+.IR startoffset .. endoffset
+with the appropriate marker.  All blocks, offsets, and lengths are specified
+in units of 512-byte blocks, no matter what the filesystem's block size is.
+.BI "The optional " start " and " end " arguments can be used to constrain
+the output to a particular range of disk blocks.
+.RE
+.RS 1.0i
+.PD 0
+.TP
+.BI \-n " num_extents"
+If this option is given,
+.B xfs_fsmap
+obtains the extent list of the file in groups of
+.I num_extents
+extents. In the absence of
+.BR \-n ", " xfs_fsmap
+queries the system for the number of extents in the filesystem and uses that
+value to compute the group size.
+.TP
+.B \-v
+Shows verbose information. When this flag is specified, additional AG
+specific information is appended to each line in the following form:
+.IP
+.RS 1.2i
+.IR agno " (" startagblock .. endagblock ") " nblocks " " flags
+.RE
+.IP
+A second
+.B \-v
+option will print out the
+.I flags
+legend.
+.RE
+.PD
+.TP
 .BI "extsize [ \-R | \-D ] [ " value " ]"
 Display and/or modify the preferred extent size used when allocating
 space for the currently open file. If the