All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCHSET v3 0/2] xfs_db: add minimal directory navigation
@ 2021-01-09  6:27 Darrick J. Wong
  2021-01-09  6:27 ` [PATCH 1/2] xfs_db: add a directory path lookup command Darrick J. Wong
  2021-01-09  6:27 ` [PATCH 2/2] xfs_db: add an ls command Darrick J. Wong
  0 siblings, 2 replies; 15+ messages in thread
From: Darrick J. Wong @ 2021-01-09  6:27 UTC (permalink / raw)
  To: sandeen, darrick.wong; +Cc: linux-xfs

Hi all,

This patchset improves the usability of xfs_db by enabling users to
navigate to inodes by path and to list the contents of directories.

v2: Various cleanups and reorganizing suggested by dchinner
v3: Rebase to 5.10-rc0

If you're going to start using this mess, you probably ought to just
pull from my git trees, which are linked below.

This is an extraordinary way to destroy everything.  Enjoy!
Comments and questions are, as always, welcome.

--D

xfsprogs git tree:
https://git.kernel.org/cgit/linux/kernel/git/djwong/xfsprogs-dev.git/log/?h=xfs_db-directory-navigation

fstests git tree:
https://git.kernel.org/cgit/linux/kernel/git/djwong/xfstests-dev.git/log/?h=xfs_db-directory-navigation
---
 db/Makefile              |    3 
 db/command.c             |    1 
 db/command.h             |    1 
 db/namei.c               |  612 ++++++++++++++++++++++++++++++++++++++++++++++
 libxfs/libxfs_api_defs.h |    1 
 man/man8/xfs_db.8        |   20 ++
 6 files changed, 637 insertions(+), 1 deletion(-)
 create mode 100644 db/namei.c


^ permalink raw reply	[flat|nested] 15+ messages in thread

* [PATCH 1/2] xfs_db: add a directory path lookup command
  2021-01-09  6:27 [PATCHSET v3 0/2] xfs_db: add minimal directory navigation Darrick J. Wong
@ 2021-01-09  6:27 ` Darrick J. Wong
  2021-01-09  6:27 ` [PATCH 2/2] xfs_db: add an ls command Darrick J. Wong
  1 sibling, 0 replies; 15+ messages in thread
From: Darrick J. Wong @ 2021-01-09  6:27 UTC (permalink / raw)
  To: sandeen, darrick.wong; +Cc: linux-xfs

From: Darrick J. Wong <djwong@kernel.org>

Add a command to xfs_db so that we can navigate to inodes by path.

Signed-off-by: Darrick J. Wong <djwong@kernel.org>
---
 db/Makefile       |    3 -
 db/command.c      |    1 
 db/command.h      |    1 
 db/namei.c        |  223 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 man/man8/xfs_db.8 |    4 +
 5 files changed, 231 insertions(+), 1 deletion(-)
 create mode 100644 db/namei.c


diff --git a/db/Makefile b/db/Makefile
index 9d502bf0..beafb105 100644
--- a/db/Makefile
+++ b/db/Makefile
@@ -14,7 +14,8 @@ HFILES = addr.h agf.h agfl.h agi.h attr.h attrshort.h bit.h block.h bmap.h \
 	io.h logformat.h malloc.h metadump.h output.h print.h quit.h sb.h \
 	sig.h strvec.h text.h type.h write.h attrset.h symlink.h fsmap.h \
 	fuzz.h
-CFILES = $(HFILES:.h=.c) btdump.c btheight.c convert.c info.c timelimit.c
+CFILES = $(HFILES:.h=.c) btdump.c btheight.c convert.c info.c namei.c \
+	timelimit.c
 LSRCFILES = xfs_admin.sh xfs_ncheck.sh xfs_metadump.sh
 
 LLDLIBS	= $(LIBXFS) $(LIBXLOG) $(LIBFROG) $(LIBUUID) $(LIBRT) $(LIBPTHREAD)
diff --git a/db/command.c b/db/command.c
index 43828369..02f778b9 100644
--- a/db/command.c
+++ b/db/command.c
@@ -131,6 +131,7 @@ init_commands(void)
 	logformat_init();
 	io_init();
 	metadump_init();
+	namei_init();
 	output_init();
 	print_init();
 	quit_init();
diff --git a/db/command.h b/db/command.h
index 6913c817..498983ff 100644
--- a/db/command.h
+++ b/db/command.h
@@ -33,3 +33,4 @@ extern void		btdump_init(void);
 extern void		info_init(void);
 extern void		btheight_init(void);
 extern void		timelimit_init(void);
+extern void		namei_init(void);
diff --git a/db/namei.c b/db/namei.c
new file mode 100644
index 00000000..eebebe15
--- /dev/null
+++ b/db/namei.c
@@ -0,0 +1,223 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2021 Oracle.  All Rights Reserved.
+ * Author: Darrick J. Wong <djwong@kernel.org>
+ */
+#include "libxfs.h"
+#include "command.h"
+#include "output.h"
+#include "init.h"
+#include "io.h"
+#include "type.h"
+#include "input.h"
+#include "faddr.h"
+#include "fprint.h"
+#include "field.h"
+#include "inode.h"
+
+/* Path lookup */
+
+/* Key for looking up metadata inodes. */
+struct dirpath {
+	/* Array of string pointers. */
+	char		**path;
+
+	/* Number of strings in path. */
+	unsigned int	depth;
+};
+
+static void
+path_free(
+	struct dirpath	*dirpath)
+{
+	unsigned int	i;
+
+	for (i = 0; i < dirpath->depth; i++)
+		free(dirpath->path[i]);
+	free(dirpath->path);
+	free(dirpath);
+}
+
+/* Chop a freeform string path into a structured path. */
+static struct dirpath *
+path_parse(
+	const char	*path)
+{
+	struct dirpath	*dirpath;
+	const char	*p = path;
+	const char	*endp = path + strlen(path);
+
+	dirpath = calloc(sizeof(*dirpath), 1);
+	if (!dirpath)
+		return NULL;
+
+	while (p < endp) {
+		char		**new_path;
+		const char	*next_slash;
+
+		next_slash = strchr(p, '/');
+		if (next_slash == p) {
+			p++;
+			continue;
+		}
+		if (!next_slash)
+			next_slash = endp;
+
+		new_path = realloc(dirpath->path,
+				(dirpath->depth + 1) * sizeof(char *));
+		if (!new_path) {
+			path_free(dirpath);
+			return NULL;
+		}
+
+		dirpath->path = new_path;
+		dirpath->path[dirpath->depth] = strndup(p, next_slash - p);
+		dirpath->depth++;
+
+		p = next_slash + 1;
+	}
+
+	return dirpath;
+}
+
+/* Given a directory and a structured path, walk the path and set the cursor. */
+static int
+path_navigate(
+	struct xfs_mount	*mp,
+	xfs_ino_t		rootino,
+	struct dirpath		*dirpath)
+{
+	struct xfs_inode	*dp;
+	xfs_ino_t		ino = rootino;
+	unsigned int		i;
+	int			error;
+
+	error = -libxfs_iget(mp, NULL, ino, 0, &dp);
+	if (error)
+		return error;
+
+	for (i = 0; i < dirpath->depth; i++) {
+		struct xfs_name	xname = {
+			.name	= dirpath->path[i],
+			.len	= strlen(dirpath->path[i]),
+		};
+
+		if (!S_ISDIR(VFS_I(dp)->i_mode)) {
+			error = ENOTDIR;
+			goto rele;
+		}
+
+		error = -libxfs_dir_lookup(NULL, dp, &xname, &ino, NULL);
+		if (error)
+			goto rele;
+		if (!xfs_verify_ino(mp, ino)) {
+			error = EFSCORRUPTED;
+			goto rele;
+		}
+
+		libxfs_irele(dp);
+		dp = NULL;
+
+		error = -libxfs_iget(mp, NULL, ino, 0, &dp);
+		switch (error) {
+		case EFSCORRUPTED:
+		case EFSBADCRC:
+		case 0:
+			break;
+		default:
+			return error;
+		}
+	}
+
+	set_cur_inode(ino);
+rele:
+	if (dp)
+		libxfs_irele(dp);
+	return error;
+}
+
+/* Walk a directory path to an inode and set the io cursor to that inode. */
+static int
+path_walk(
+	char		*path)
+{
+	struct dirpath	*dirpath;
+	char		*p = path;
+	xfs_ino_t	rootino = mp->m_sb.sb_rootino;
+	int		error = 0;
+
+	if (*p == '/') {
+		/* Absolute path, start from the root inode. */
+		p++;
+	} else {
+		/* Relative path, start from current dir. */
+		if (iocur_top->typ != &typtab[TYP_INODE] ||
+		    !S_ISDIR(iocur_top->mode))
+			return ENOTDIR;
+
+		rootino = iocur_top->ino;
+	}
+
+	dirpath = path_parse(p);
+	if (!dirpath)
+		return ENOMEM;
+
+	error = path_navigate(mp, rootino, dirpath);
+	if (error)
+		return error;
+
+	path_free(dirpath);
+	return 0;
+}
+
+static void
+path_help(void)
+{
+	dbprintf(_(
+"\n"
+" Navigate to an inode via directory path.\n"
+	));
+}
+
+static int
+path_f(
+	int		argc,
+	char		**argv)
+{
+	int		c;
+	int		error;
+
+	while ((c = getopt(argc, argv, "")) != -1) {
+		switch (c) {
+		default:
+			path_help();
+			return 0;
+		}
+	}
+
+	error = path_walk(argv[optind]);
+	if (error) {
+		dbprintf("%s: %s\n", argv[optind], strerror(error));
+		exitcode = 1;
+	}
+
+	return 0;
+}
+
+static struct cmdinfo path_cmd = {
+	.name		= "path",
+	.altname	= NULL,
+	.cfunc		= path_f,
+	.argmin		= 1,
+	.argmax		= 1,
+	.canpush	= 0,
+	.args		= "",
+	.help		= path_help,
+};
+
+void
+namei_init(void)
+{
+	path_cmd.oneline = _("navigate to an inode by path");
+	add_command(&path_cmd);
+}
diff --git a/man/man8/xfs_db.8 b/man/man8/xfs_db.8
index 55388be6..4df265ec 100644
--- a/man/man8/xfs_db.8
+++ b/man/man8/xfs_db.8
@@ -831,6 +831,10 @@ See the
 .B print
 command.
 .TP
+.BI "path " dir_path
+Walk the directory tree to an inode using the supplied path.
+Absolute and relative paths are supported.
+.TP
 .B pop
 Pop location from the stack.
 .TP


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH 2/2] xfs_db: add an ls command
  2021-01-09  6:27 [PATCHSET v3 0/2] xfs_db: add minimal directory navigation Darrick J. Wong
  2021-01-09  6:27 ` [PATCH 1/2] xfs_db: add a directory path lookup command Darrick J. Wong
@ 2021-01-09  6:27 ` Darrick J. Wong
  1 sibling, 0 replies; 15+ messages in thread
From: Darrick J. Wong @ 2021-01-09  6:27 UTC (permalink / raw)
  To: sandeen, darrick.wong; +Cc: linux-xfs

From: Darrick J. Wong <djwong@kernel.org>

Add to xfs_db the ability to list a directory.

Signed-off-by: Darrick J. Wong <djwong@kernel.org>
---
 db/namei.c               |  389 ++++++++++++++++++++++++++++++++++++++++++++++
 libxfs/libxfs_api_defs.h |    1 
 man/man8/xfs_db.8        |   16 ++
 3 files changed, 406 insertions(+)


diff --git a/db/namei.c b/db/namei.c
index eebebe15..e75b5ebd 100644
--- a/db/namei.c
+++ b/db/namei.c
@@ -215,9 +215,398 @@ static struct cmdinfo path_cmd = {
 	.help		= path_help,
 };
 
+/* List a directory's entries. */
+
+static const char *filetype_strings[XFS_DIR3_FT_MAX] = {
+	[XFS_DIR3_FT_UNKNOWN]	= "unknown",
+	[XFS_DIR3_FT_REG_FILE]	= "regular",
+	[XFS_DIR3_FT_DIR]	= "directory",
+	[XFS_DIR3_FT_CHRDEV]	= "chardev",
+	[XFS_DIR3_FT_BLKDEV]	= "blkdev",
+	[XFS_DIR3_FT_FIFO]	= "fifo",
+	[XFS_DIR3_FT_SOCK]	= "socket",
+	[XFS_DIR3_FT_SYMLINK]	= "symlink",
+	[XFS_DIR3_FT_WHT]	= "whiteout",
+};
+
+static const char *
+get_dstr(
+	struct xfs_mount	*mp,
+	uint8_t			filetype)
+{
+	if (!xfs_sb_version_hasftype(&mp->m_sb))
+		return filetype_strings[XFS_DIR3_FT_UNKNOWN];
+
+	if (filetype >= XFS_DIR3_FT_MAX)
+		return filetype_strings[XFS_DIR3_FT_UNKNOWN];
+
+	return filetype_strings[filetype];
+}
+
+static void
+dir_emit(
+	struct xfs_mount	*mp,
+	xfs_dir2_dataptr_t	off,
+	char			*name,
+	ssize_t			namelen,
+	xfs_ino_t		ino,
+	uint8_t			dtype)
+{
+	char			*display_name;
+	struct xfs_name		xname = { .name = name };
+	const char		*dstr = get_dstr(mp, dtype);
+	xfs_dahash_t		hash;
+	bool			good;
+
+	if (namelen < 0) {
+		/* Negative length means that name is null-terminated. */
+		display_name = name;
+		xname.len = strlen(name);
+		good = true;
+	} else {
+		/*
+		 * Otherwise, name came from a directory entry, so we have to
+		 * copy the string to a buffer so that we can add the null
+		 * terminator.
+		 */
+		display_name = malloc(namelen + 1);
+		memcpy(display_name, name, namelen);
+		display_name[namelen] = 0;
+		xname.len = namelen;
+		good = libxfs_dir2_namecheck(name, namelen);
+	}
+	hash = libxfs_dir2_hashname(mp, &xname);
+
+	dbprintf("%-10u %-18llu %-14s 0x%08llx %3d %s %s\n", off & 0xFFFFFFFF,
+			ino, dstr, hash, xname.len,
+			display_name, good ? _("(good)") : _("(corrupt)"));
+
+	if (display_name != name)
+		free(display_name);
+}
+
+static int
+list_sfdir(
+	struct xfs_da_args		*args)
+{
+	struct xfs_inode		*dp = args->dp;
+	struct xfs_mount		*mp = dp->i_mount;
+	struct xfs_da_geometry		*geo = args->geo;
+	struct xfs_dir2_sf_entry	*sfep;
+	struct xfs_dir2_sf_hdr		*sfp;
+	xfs_ino_t			ino;
+	xfs_dir2_dataptr_t		off;
+	unsigned int			i;
+	uint8_t				filetype;
+
+	sfp = (struct xfs_dir2_sf_hdr *)dp->i_df.if_u1.if_data;
+
+	/* . and .. entries */
+	off = xfs_dir2_db_off_to_dataptr(geo, geo->datablk,
+			geo->data_entry_offset);
+	dir_emit(args->dp->i_mount, off, ".", -1, dp->i_ino, XFS_DIR3_FT_DIR);
+
+	ino = libxfs_dir2_sf_get_parent_ino(sfp);
+	off = xfs_dir2_db_off_to_dataptr(geo, geo->datablk,
+			geo->data_entry_offset +
+			libxfs_dir2_data_entsize(mp, sizeof(".") - 1));
+	dir_emit(args->dp->i_mount, off, "..", -1, ino, XFS_DIR3_FT_DIR);
+
+	/* Walk everything else. */
+	sfep = xfs_dir2_sf_firstentry(sfp);
+	for (i = 0; i < sfp->count; i++) {
+		ino = libxfs_dir2_sf_get_ino(mp, sfp, sfep);
+		filetype = libxfs_dir2_sf_get_ftype(mp, sfep);
+		off = xfs_dir2_db_off_to_dataptr(geo, geo->datablk,
+				xfs_dir2_sf_get_offset(sfep));
+
+		dir_emit(args->dp->i_mount, off, (char *)sfep->name,
+				sfep->namelen, ino, filetype);
+		sfep = libxfs_dir2_sf_nextentry(mp, sfp, sfep);
+	}
+
+	return 0;
+}
+
+/* List entries in block format directory. */
+static int
+list_blockdir(
+	struct xfs_da_args	*args)
+{
+	struct xfs_inode	*dp = args->dp;
+	struct xfs_mount	*mp = dp->i_mount;
+	struct xfs_buf		*bp;
+	struct xfs_da_geometry	*geo = mp->m_dir_geo;
+	xfs_dir2_dataptr_t	diroff;
+	unsigned int		offset;
+	unsigned int		end;
+	int			error;
+
+	error = xfs_dir3_block_read(NULL, dp, &bp);
+	if (error)
+		return error;
+
+	end = xfs_dir3_data_end_offset(geo, bp->b_addr);
+	for (offset = geo->data_entry_offset; offset < end;) {
+		struct xfs_dir2_data_unused	*dup = bp->b_addr + offset;
+		struct xfs_dir2_data_entry	*dep = bp->b_addr + offset;
+		uint8_t				filetype;
+
+		if (be16_to_cpu(dup->freetag) == XFS_DIR2_DATA_FREE_TAG) {
+			/* Unused entry */
+			offset += be16_to_cpu(dup->length);
+			continue;
+		}
+
+		/* Real entry */
+		diroff = xfs_dir2_db_off_to_dataptr(geo, geo->datablk, offset);
+		offset += libxfs_dir2_data_entsize(mp, dep->namelen);
+		filetype = libxfs_dir2_data_get_ftype(dp->i_mount, dep);
+		dir_emit(mp, diroff, (char *)dep->name, dep->namelen,
+				be64_to_cpu(dep->inumber), filetype);
+	}
+
+	libxfs_trans_brelse(args->trans, bp);
+	return error;
+}
+
+/* List entries in leaf format directory. */
+static int
+list_leafdir(
+	struct xfs_da_args	*args)
+{
+	struct xfs_bmbt_irec	map;
+	struct xfs_iext_cursor	icur;
+	struct xfs_inode	*dp = args->dp;
+	struct xfs_mount	*mp = dp->i_mount;
+	struct xfs_buf		*bp = NULL;
+	struct xfs_ifork	*ifp = XFS_IFORK_PTR(dp, XFS_DATA_FORK);
+	struct xfs_da_geometry	*geo = mp->m_dir_geo;
+	xfs_dir2_off_t		dirboff;
+	xfs_dablk_t		dabno = 0;
+	int			error = 0;
+
+	/* Read extent map. */
+	if (!(ifp->if_flags & XFS_IFEXTENTS)) {
+		error = -libxfs_iread_extents(NULL, dp, XFS_DATA_FORK);
+		if (error)
+			return error;
+	}
+
+	while (dabno < geo->leafblk) {
+		unsigned int	offset;
+		unsigned int	length;
+
+		/* Find mapping for leaf block. */
+		if (!xfs_iext_lookup_extent(dp, ifp, dabno, &icur, &map))
+			break;
+		if (map.br_startoff >= geo->leafblk)
+			break;
+		libxfs_trim_extent(&map, dabno, geo->leafblk - dabno);
+
+		/* Read the directory block of that first mapping. */
+		error = xfs_dir3_data_read(NULL, dp, map.br_startoff, 0, &bp);
+		if (error)
+			break;
+
+		dirboff = xfs_dir2_da_to_byte(geo, map.br_startoff);
+		for (offset = geo->data_entry_offset; offset < geo->blksize;) {
+			struct xfs_dir2_data_entry	*dep;
+			struct xfs_dir2_data_unused	*dup;
+			uint8_t				filetype;
+
+			dup = bp->b_addr + offset;
+			dep = bp->b_addr + offset;
+
+			if (be16_to_cpu(dup->freetag) ==
+			    XFS_DIR2_DATA_FREE_TAG) {
+				/* Skip unused entry */
+				length = be16_to_cpu(dup->length);
+				offset += length;
+				continue;
+			}
+
+			offset += libxfs_dir2_data_entsize(mp, dep->namelen);
+			filetype = libxfs_dir2_data_get_ftype(mp, dep);
+
+			dir_emit(mp, xfs_dir2_byte_to_dataptr(dirboff + offset),
+					(char *)dep->name, dep->namelen,
+					be64_to_cpu(dep->inumber), filetype);
+		}
+
+		dabno += XFS_DADDR_TO_FSB(mp, bp->b_length);
+		libxfs_buf_relse(bp);
+		bp = NULL;
+	}
+
+	if (bp)
+		libxfs_buf_relse(bp);
+
+	return error;
+}
+
+/* Read the directory, display contents. */
+int
+listdir(
+	struct xfs_inode	*dp)
+{
+	struct xfs_da_args	args = {
+		.dp		= dp,
+		.geo		= dp->i_mount->m_dir_geo,
+	};
+	int			error;
+	int			isblock;
+
+	if (dp->i_df.if_format == XFS_DINODE_FMT_LOCAL)
+		return list_sfdir(&args);
+
+	error = -libxfs_dir2_isblock(&args, &isblock);
+	if (error)
+		return error;
+
+	if (isblock)
+		return list_blockdir(&args);
+	return list_leafdir(&args);
+}
+
+/* List the inode number of the currently selected inode. */
+static int
+inum_cur(void)
+{
+	if (iocur_top->typ != &typtab[TYP_INODE])
+		return ENOENT;
+
+	dbprintf("%llu\n", iocur_top->ino);
+	return 0;
+}
+
+/* If the io cursor points to a directory, list its contents. */
+static int
+ls_cur(
+	char			*tag)
+{
+	struct xfs_inode	*dp;
+	int			error = 0;
+
+	if (iocur_top->typ != &typtab[TYP_INODE] ||
+	    !S_ISDIR(iocur_top->mode))
+		return ENOTDIR;
+
+	error = -libxfs_iget(mp, NULL, iocur_top->ino, 0, &dp);
+	if (error)
+		return error;
+
+	if (!S_ISDIR(VFS_I(dp)->i_mode)) {
+		error = ENOTDIR;
+		goto rele;
+	}
+
+	/* List the contents of a directory. */
+	if (tag)
+		dbprintf(_("%s:\n"), tag);
+
+	error = listdir(dp);
+	if (error)
+		goto rele;
+
+rele:
+	libxfs_irele(dp);
+	return error;
+}
+
+static void
+ls_help(void)
+{
+	dbprintf(_(
+"\n"
+" List the contents of the currently selected directory inode.\n"
+"\n"
+" Options:\n"
+"   -i -- Resolve the given paths to their corresponding inode numbers.\n"
+"         If no paths are given, display the current inode number.\n"
+"\n"
+" Directory contents will be listed in the format:\n"
+" dir_cookie	inode_number	type	hash	name_length	name\n"
+	));
+}
+
+static int
+ls_f(
+	int			argc,
+	char			**argv)
+{
+	bool			inum_only = false;
+	int			c;
+	int			error = 0;
+
+	while ((c = getopt(argc, argv, "i")) != -1) {
+		switch (c) {
+		case 'i':
+			inum_only = true;
+			break;
+		default:
+			ls_help();
+			return 0;
+		}
+	}
+
+	if (optind == argc) {
+		if (inum_only)
+			error = inum_cur();
+		else
+			error = ls_cur(NULL);
+		if (error) {
+			dbprintf("%s\n", strerror(error));
+			exitcode = 1;
+		}
+
+		return 0;
+	}
+
+	for (c = optind; c < argc; c++) {
+		push_cur();
+
+		error = path_walk(argv[c]);
+		if (error)
+			goto err_cur;
+
+		if (inum_only)
+			error = inum_cur();
+		else
+			error = ls_cur(argv[c]);
+		if (error)
+			goto err_cur;
+
+		pop_cur();
+	}
+
+	return 0;
+err_cur:
+	pop_cur();
+	if (error) {
+		dbprintf("%s: %s\n", argv[c], strerror(error));
+		exitcode = 1;
+	}
+	return 0;
+}
+
+static struct cmdinfo ls_cmd = {
+	.name		= "ls",
+	.altname	= "l",
+	.cfunc		= ls_f,
+	.argmin		= 0,
+	.argmax		= -1,
+	.canpush	= 0,
+	.args		= "[-i] [paths...]",
+	.help		= ls_help,
+};
+
 void
 namei_init(void)
 {
 	path_cmd.oneline = _("navigate to an inode by path");
 	add_command(&path_cmd);
+
+	ls_cmd.oneline = _("list directory contents");
+	add_command(&ls_cmd);
 }
diff --git a/libxfs/libxfs_api_defs.h b/libxfs/libxfs_api_defs.h
index 9492955d..9a00ce66 100644
--- a/libxfs/libxfs_api_defs.h
+++ b/libxfs/libxfs_api_defs.h
@@ -190,6 +190,7 @@
 #define xfs_trans_resv_calc		libxfs_trans_resv_calc
 #define xfs_trans_roll_inode		libxfs_trans_roll_inode
 #define xfs_trans_roll			libxfs_trans_roll
+#define xfs_trim_extent			libxfs_trim_extent
 
 #define xfs_verify_agbno		libxfs_verify_agbno
 #define xfs_verify_agino		libxfs_verify_agino
diff --git a/man/man8/xfs_db.8 b/man/man8/xfs_db.8
index 4df265ec..58727495 100644
--- a/man/man8/xfs_db.8
+++ b/man/man8/xfs_db.8
@@ -806,6 +806,22 @@ This makes it easier to find discrepancies in the reservation calculations
 between xfsprogs and the kernel, which will help when diagnosing minimum
 log size calculation errors.
 .TP
+.BI "ls [\-i] [" paths "]..."
+List the contents of a directory.
+If a path resolves to a directory, the directory will be listed.
+If no paths are supplied and the IO cursor points at a directory inode,
+the contents of that directory will be listed.
+
+The output format is:
+directory cookie, inode number, file type, hash, name length, name.
+.RS 1.0i
+.TP 0.4i
+.B \-i
+Resolve each of the given paths to an inode number and print that number.
+If no paths are given and the IO cursor points to an inode, print the inode
+number.
+.RE
+.TP
 .BI "metadump [\-egow] " filename
 Dumps metadata to a file. See
 .BR xfs_metadump (8)


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH 2/2] xfs_db: add an ls command
  2021-02-09  4:09 [PATCHSET v5 0/2] xfs_db: add minimal directory navigation Darrick J. Wong
@ 2021-02-09  4:10 ` Darrick J. Wong
  0 siblings, 0 replies; 15+ messages in thread
From: Darrick J. Wong @ 2021-02-09  4:10 UTC (permalink / raw)
  To: sandeen, djwong; +Cc: Chandan Babu R, linux-xfs, chandanrlinux

From: Darrick J. Wong <djwong@kernel.org>

Add to xfs_db the ability to list a directory.

Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Chandan Babu R <chandanrlinux@gmail.com>
---
 db/namei.c               |  389 ++++++++++++++++++++++++++++++++++++++++++++++
 libxfs/libxfs_api_defs.h |    1 
 man/man8/xfs_db.8        |   16 ++
 3 files changed, 406 insertions(+)


diff --git a/db/namei.c b/db/namei.c
index 6fddbc4a..02c77443 100644
--- a/db/namei.c
+++ b/db/namei.c
@@ -212,9 +212,398 @@ static struct cmdinfo path_cmd = {
 	.help		= path_help,
 };
 
+/* List a directory's entries. */
+
+static const char *filetype_strings[XFS_DIR3_FT_MAX] = {
+	[XFS_DIR3_FT_UNKNOWN]	= "unknown",
+	[XFS_DIR3_FT_REG_FILE]	= "regular",
+	[XFS_DIR3_FT_DIR]	= "directory",
+	[XFS_DIR3_FT_CHRDEV]	= "chardev",
+	[XFS_DIR3_FT_BLKDEV]	= "blkdev",
+	[XFS_DIR3_FT_FIFO]	= "fifo",
+	[XFS_DIR3_FT_SOCK]	= "socket",
+	[XFS_DIR3_FT_SYMLINK]	= "symlink",
+	[XFS_DIR3_FT_WHT]	= "whiteout",
+};
+
+static const char *
+get_dstr(
+	struct xfs_mount	*mp,
+	uint8_t			filetype)
+{
+	if (!xfs_sb_version_hasftype(&mp->m_sb))
+		return filetype_strings[XFS_DIR3_FT_UNKNOWN];
+
+	if (filetype >= XFS_DIR3_FT_MAX)
+		return filetype_strings[XFS_DIR3_FT_UNKNOWN];
+
+	return filetype_strings[filetype];
+}
+
+static void
+dir_emit(
+	struct xfs_mount	*mp,
+	xfs_dir2_dataptr_t	off,
+	char			*name,
+	ssize_t			namelen,
+	xfs_ino_t		ino,
+	uint8_t			dtype)
+{
+	char			*display_name;
+	struct xfs_name		xname = { .name = name };
+	const char		*dstr = get_dstr(mp, dtype);
+	xfs_dahash_t		hash;
+	bool			good;
+
+	if (namelen < 0) {
+		/* Negative length means that name is null-terminated. */
+		display_name = name;
+		xname.len = strlen(name);
+		good = true;
+	} else {
+		/*
+		 * Otherwise, name came from a directory entry, so we have to
+		 * copy the string to a buffer so that we can add the null
+		 * terminator.
+		 */
+		display_name = malloc(namelen + 1);
+		memcpy(display_name, name, namelen);
+		display_name[namelen] = 0;
+		xname.len = namelen;
+		good = libxfs_dir2_namecheck(name, namelen);
+	}
+	hash = libxfs_dir2_hashname(mp, &xname);
+
+	dbprintf("%-10u %-18llu %-14s 0x%08llx %3d %s %s\n", off & 0xFFFFFFFF,
+			ino, dstr, hash, xname.len,
+			display_name, good ? _("(good)") : _("(corrupt)"));
+
+	if (display_name != name)
+		free(display_name);
+}
+
+static int
+list_sfdir(
+	struct xfs_da_args		*args)
+{
+	struct xfs_inode		*dp = args->dp;
+	struct xfs_mount		*mp = dp->i_mount;
+	struct xfs_da_geometry		*geo = args->geo;
+	struct xfs_dir2_sf_entry	*sfep;
+	struct xfs_dir2_sf_hdr		*sfp;
+	xfs_ino_t			ino;
+	xfs_dir2_dataptr_t		off;
+	unsigned int			i;
+	uint8_t				filetype;
+
+	sfp = (struct xfs_dir2_sf_hdr *)dp->i_df.if_u1.if_data;
+
+	/* . and .. entries */
+	off = xfs_dir2_db_off_to_dataptr(geo, geo->datablk,
+			geo->data_entry_offset);
+	dir_emit(args->dp->i_mount, off, ".", -1, dp->i_ino, XFS_DIR3_FT_DIR);
+
+	ino = libxfs_dir2_sf_get_parent_ino(sfp);
+	off = xfs_dir2_db_off_to_dataptr(geo, geo->datablk,
+			geo->data_entry_offset +
+			libxfs_dir2_data_entsize(mp, sizeof(".") - 1));
+	dir_emit(args->dp->i_mount, off, "..", -1, ino, XFS_DIR3_FT_DIR);
+
+	/* Walk everything else. */
+	sfep = xfs_dir2_sf_firstentry(sfp);
+	for (i = 0; i < sfp->count; i++) {
+		ino = libxfs_dir2_sf_get_ino(mp, sfp, sfep);
+		filetype = libxfs_dir2_sf_get_ftype(mp, sfep);
+		off = xfs_dir2_db_off_to_dataptr(geo, geo->datablk,
+				xfs_dir2_sf_get_offset(sfep));
+
+		dir_emit(args->dp->i_mount, off, (char *)sfep->name,
+				sfep->namelen, ino, filetype);
+		sfep = libxfs_dir2_sf_nextentry(mp, sfp, sfep);
+	}
+
+	return 0;
+}
+
+/* List entries in block format directory. */
+static int
+list_blockdir(
+	struct xfs_da_args	*args)
+{
+	struct xfs_inode	*dp = args->dp;
+	struct xfs_mount	*mp = dp->i_mount;
+	struct xfs_buf		*bp;
+	struct xfs_da_geometry	*geo = mp->m_dir_geo;
+	xfs_dir2_dataptr_t	diroff;
+	unsigned int		offset;
+	unsigned int		end;
+	int			error;
+
+	error = xfs_dir3_block_read(NULL, dp, &bp);
+	if (error)
+		return error;
+
+	end = xfs_dir3_data_end_offset(geo, bp->b_addr);
+	for (offset = geo->data_entry_offset; offset < end;) {
+		struct xfs_dir2_data_unused	*dup = bp->b_addr + offset;
+		struct xfs_dir2_data_entry	*dep = bp->b_addr + offset;
+		uint8_t				filetype;
+
+		if (be16_to_cpu(dup->freetag) == XFS_DIR2_DATA_FREE_TAG) {
+			/* Unused entry */
+			offset += be16_to_cpu(dup->length);
+			continue;
+		}
+
+		/* Real entry */
+		diroff = xfs_dir2_db_off_to_dataptr(geo, geo->datablk, offset);
+		offset += libxfs_dir2_data_entsize(mp, dep->namelen);
+		filetype = libxfs_dir2_data_get_ftype(dp->i_mount, dep);
+		dir_emit(mp, diroff, (char *)dep->name, dep->namelen,
+				be64_to_cpu(dep->inumber), filetype);
+	}
+
+	libxfs_trans_brelse(args->trans, bp);
+	return error;
+}
+
+/* List entries in leaf format directory. */
+static int
+list_leafdir(
+	struct xfs_da_args	*args)
+{
+	struct xfs_bmbt_irec	map;
+	struct xfs_iext_cursor	icur;
+	struct xfs_inode	*dp = args->dp;
+	struct xfs_mount	*mp = dp->i_mount;
+	struct xfs_buf		*bp = NULL;
+	struct xfs_ifork	*ifp = XFS_IFORK_PTR(dp, XFS_DATA_FORK);
+	struct xfs_da_geometry	*geo = mp->m_dir_geo;
+	xfs_dir2_off_t		dirboff;
+	xfs_dablk_t		dabno = 0;
+	int			error = 0;
+
+	/* Read extent map. */
+	if (!(ifp->if_flags & XFS_IFEXTENTS)) {
+		error = -libxfs_iread_extents(NULL, dp, XFS_DATA_FORK);
+		if (error)
+			return error;
+	}
+
+	while (dabno < geo->leafblk) {
+		unsigned int	offset;
+		unsigned int	length;
+
+		/* Find mapping for leaf block. */
+		if (!xfs_iext_lookup_extent(dp, ifp, dabno, &icur, &map))
+			break;
+		if (map.br_startoff >= geo->leafblk)
+			break;
+		libxfs_trim_extent(&map, dabno, geo->leafblk - dabno);
+
+		/* Read the directory block of that first mapping. */
+		error = xfs_dir3_data_read(NULL, dp, map.br_startoff, 0, &bp);
+		if (error)
+			break;
+
+		dirboff = xfs_dir2_da_to_byte(geo, map.br_startoff);
+		for (offset = geo->data_entry_offset; offset < geo->blksize;) {
+			struct xfs_dir2_data_entry	*dep;
+			struct xfs_dir2_data_unused	*dup;
+			uint8_t				filetype;
+
+			dup = bp->b_addr + offset;
+			dep = bp->b_addr + offset;
+
+			if (be16_to_cpu(dup->freetag) ==
+			    XFS_DIR2_DATA_FREE_TAG) {
+				/* Skip unused entry */
+				length = be16_to_cpu(dup->length);
+				offset += length;
+				continue;
+			}
+
+			offset += libxfs_dir2_data_entsize(mp, dep->namelen);
+			filetype = libxfs_dir2_data_get_ftype(mp, dep);
+
+			dir_emit(mp, xfs_dir2_byte_to_dataptr(dirboff + offset),
+					(char *)dep->name, dep->namelen,
+					be64_to_cpu(dep->inumber), filetype);
+		}
+
+		dabno += XFS_DADDR_TO_FSB(mp, bp->b_length);
+		libxfs_buf_relse(bp);
+		bp = NULL;
+	}
+
+	if (bp)
+		libxfs_buf_relse(bp);
+
+	return error;
+}
+
+/* Read the directory, display contents. */
+int
+listdir(
+	struct xfs_inode	*dp)
+{
+	struct xfs_da_args	args = {
+		.dp		= dp,
+		.geo		= dp->i_mount->m_dir_geo,
+	};
+	int			error;
+	int			isblock;
+
+	if (dp->i_df.if_format == XFS_DINODE_FMT_LOCAL)
+		return list_sfdir(&args);
+
+	error = -libxfs_dir2_isblock(&args, &isblock);
+	if (error)
+		return error;
+
+	if (isblock)
+		return list_blockdir(&args);
+	return list_leafdir(&args);
+}
+
+/* List the inode number of the currently selected inode. */
+static int
+inum_cur(void)
+{
+	if (iocur_top->typ != &typtab[TYP_INODE])
+		return ENOENT;
+
+	dbprintf("%llu\n", iocur_top->ino);
+	return 0;
+}
+
+/* If the io cursor points to a directory, list its contents. */
+static int
+ls_cur(
+	char			*tag)
+{
+	struct xfs_inode	*dp;
+	int			error = 0;
+
+	if (iocur_top->typ != &typtab[TYP_INODE] ||
+	    !S_ISDIR(iocur_top->mode))
+		return ENOTDIR;
+
+	error = -libxfs_iget(mp, NULL, iocur_top->ino, 0, &dp);
+	if (error)
+		return error;
+
+	if (!S_ISDIR(VFS_I(dp)->i_mode)) {
+		error = ENOTDIR;
+		goto rele;
+	}
+
+	/* List the contents of a directory. */
+	if (tag)
+		dbprintf(_("%s:\n"), tag);
+
+	error = listdir(dp);
+	if (error)
+		goto rele;
+
+rele:
+	libxfs_irele(dp);
+	return error;
+}
+
+static void
+ls_help(void)
+{
+	dbprintf(_(
+"\n"
+" List the contents of the currently selected directory inode.\n"
+"\n"
+" Options:\n"
+"   -i -- Resolve the given paths to their corresponding inode numbers.\n"
+"         If no paths are given, display the current inode number.\n"
+"\n"
+" Directory contents will be listed in the format:\n"
+" dir_cookie	inode_number	type	hash	name_length	name\n"
+	));
+}
+
+static int
+ls_f(
+	int			argc,
+	char			**argv)
+{
+	bool			inum_only = false;
+	int			c;
+	int			error = 0;
+
+	while ((c = getopt(argc, argv, "i")) != -1) {
+		switch (c) {
+		case 'i':
+			inum_only = true;
+			break;
+		default:
+			ls_help();
+			return 0;
+		}
+	}
+
+	if (optind == argc) {
+		if (inum_only)
+			error = inum_cur();
+		else
+			error = ls_cur(NULL);
+		if (error) {
+			dbprintf("%s\n", strerror(error));
+			exitcode = 1;
+		}
+
+		return 0;
+	}
+
+	for (c = optind; c < argc; c++) {
+		push_cur();
+
+		error = path_walk(argv[c]);
+		if (error)
+			goto err_cur;
+
+		if (inum_only)
+			error = inum_cur();
+		else
+			error = ls_cur(argv[c]);
+		if (error)
+			goto err_cur;
+
+		pop_cur();
+	}
+
+	return 0;
+err_cur:
+	pop_cur();
+	if (error) {
+		dbprintf("%s: %s\n", argv[c], strerror(error));
+		exitcode = 1;
+	}
+	return 0;
+}
+
+static struct cmdinfo ls_cmd = {
+	.name		= "ls",
+	.altname	= "l",
+	.cfunc		= ls_f,
+	.argmin		= 0,
+	.argmax		= -1,
+	.canpush	= 0,
+	.args		= "[-i] [paths...]",
+	.help		= ls_help,
+};
+
 void
 namei_init(void)
 {
 	path_cmd.oneline = _("navigate to an inode by path");
 	add_command(&path_cmd);
+
+	ls_cmd.oneline = _("list directory contents");
+	add_command(&ls_cmd);
 }
diff --git a/libxfs/libxfs_api_defs.h b/libxfs/libxfs_api_defs.h
index 9492955d..9a00ce66 100644
--- a/libxfs/libxfs_api_defs.h
+++ b/libxfs/libxfs_api_defs.h
@@ -190,6 +190,7 @@
 #define xfs_trans_resv_calc		libxfs_trans_resv_calc
 #define xfs_trans_roll_inode		libxfs_trans_roll_inode
 #define xfs_trans_roll			libxfs_trans_roll
+#define xfs_trim_extent			libxfs_trim_extent
 
 #define xfs_verify_agbno		libxfs_verify_agbno
 #define xfs_verify_agino		libxfs_verify_agino
diff --git a/man/man8/xfs_db.8 b/man/man8/xfs_db.8
index 4df265ec..58727495 100644
--- a/man/man8/xfs_db.8
+++ b/man/man8/xfs_db.8
@@ -806,6 +806,22 @@ This makes it easier to find discrepancies in the reservation calculations
 between xfsprogs and the kernel, which will help when diagnosing minimum
 log size calculation errors.
 .TP
+.BI "ls [\-i] [" paths "]..."
+List the contents of a directory.
+If a path resolves to a directory, the directory will be listed.
+If no paths are supplied and the IO cursor points at a directory inode,
+the contents of that directory will be listed.
+
+The output format is:
+directory cookie, inode number, file type, hash, name length, name.
+.RS 1.0i
+.TP 0.4i
+.B \-i
+Resolve each of the given paths to an inode number and print that number.
+If no paths are given and the IO cursor points to an inode, print the inode
+number.
+.RE
+.TP
 .BI "metadump [\-egow] " filename
 Dumps metadata to a file. See
 .BR xfs_metadump (8)


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH 2/2] xfs_db: add an ls command
  2021-02-03 19:42 [PATCHSET v4 0/2] xfs_db: add minimal directory navigation Darrick J. Wong
@ 2021-02-03 19:43 ` Darrick J. Wong
  0 siblings, 0 replies; 15+ messages in thread
From: Darrick J. Wong @ 2021-02-03 19:43 UTC (permalink / raw)
  To: sandeen, djwong; +Cc: Chandan Babu R, linux-xfs, chandanrlinux

From: Darrick J. Wong <djwong@kernel.org>

Add to xfs_db the ability to list a directory.

Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Chandan Babu R <chandanrlinux@gmail.com>
---
 db/namei.c               |  389 ++++++++++++++++++++++++++++++++++++++++++++++
 libxfs/libxfs_api_defs.h |    1 
 man/man8/xfs_db.8        |   16 ++
 3 files changed, 406 insertions(+)


diff --git a/db/namei.c b/db/namei.c
index 6fddbc4a..02c77443 100644
--- a/db/namei.c
+++ b/db/namei.c
@@ -212,9 +212,398 @@ static struct cmdinfo path_cmd = {
 	.help		= path_help,
 };
 
+/* List a directory's entries. */
+
+static const char *filetype_strings[XFS_DIR3_FT_MAX] = {
+	[XFS_DIR3_FT_UNKNOWN]	= "unknown",
+	[XFS_DIR3_FT_REG_FILE]	= "regular",
+	[XFS_DIR3_FT_DIR]	= "directory",
+	[XFS_DIR3_FT_CHRDEV]	= "chardev",
+	[XFS_DIR3_FT_BLKDEV]	= "blkdev",
+	[XFS_DIR3_FT_FIFO]	= "fifo",
+	[XFS_DIR3_FT_SOCK]	= "socket",
+	[XFS_DIR3_FT_SYMLINK]	= "symlink",
+	[XFS_DIR3_FT_WHT]	= "whiteout",
+};
+
+static const char *
+get_dstr(
+	struct xfs_mount	*mp,
+	uint8_t			filetype)
+{
+	if (!xfs_sb_version_hasftype(&mp->m_sb))
+		return filetype_strings[XFS_DIR3_FT_UNKNOWN];
+
+	if (filetype >= XFS_DIR3_FT_MAX)
+		return filetype_strings[XFS_DIR3_FT_UNKNOWN];
+
+	return filetype_strings[filetype];
+}
+
+static void
+dir_emit(
+	struct xfs_mount	*mp,
+	xfs_dir2_dataptr_t	off,
+	char			*name,
+	ssize_t			namelen,
+	xfs_ino_t		ino,
+	uint8_t			dtype)
+{
+	char			*display_name;
+	struct xfs_name		xname = { .name = name };
+	const char		*dstr = get_dstr(mp, dtype);
+	xfs_dahash_t		hash;
+	bool			good;
+
+	if (namelen < 0) {
+		/* Negative length means that name is null-terminated. */
+		display_name = name;
+		xname.len = strlen(name);
+		good = true;
+	} else {
+		/*
+		 * Otherwise, name came from a directory entry, so we have to
+		 * copy the string to a buffer so that we can add the null
+		 * terminator.
+		 */
+		display_name = malloc(namelen + 1);
+		memcpy(display_name, name, namelen);
+		display_name[namelen] = 0;
+		xname.len = namelen;
+		good = libxfs_dir2_namecheck(name, namelen);
+	}
+	hash = libxfs_dir2_hashname(mp, &xname);
+
+	dbprintf("%-10u %-18llu %-14s 0x%08llx %3d %s %s\n", off & 0xFFFFFFFF,
+			ino, dstr, hash, xname.len,
+			display_name, good ? _("(good)") : _("(corrupt)"));
+
+	if (display_name != name)
+		free(display_name);
+}
+
+static int
+list_sfdir(
+	struct xfs_da_args		*args)
+{
+	struct xfs_inode		*dp = args->dp;
+	struct xfs_mount		*mp = dp->i_mount;
+	struct xfs_da_geometry		*geo = args->geo;
+	struct xfs_dir2_sf_entry	*sfep;
+	struct xfs_dir2_sf_hdr		*sfp;
+	xfs_ino_t			ino;
+	xfs_dir2_dataptr_t		off;
+	unsigned int			i;
+	uint8_t				filetype;
+
+	sfp = (struct xfs_dir2_sf_hdr *)dp->i_df.if_u1.if_data;
+
+	/* . and .. entries */
+	off = xfs_dir2_db_off_to_dataptr(geo, geo->datablk,
+			geo->data_entry_offset);
+	dir_emit(args->dp->i_mount, off, ".", -1, dp->i_ino, XFS_DIR3_FT_DIR);
+
+	ino = libxfs_dir2_sf_get_parent_ino(sfp);
+	off = xfs_dir2_db_off_to_dataptr(geo, geo->datablk,
+			geo->data_entry_offset +
+			libxfs_dir2_data_entsize(mp, sizeof(".") - 1));
+	dir_emit(args->dp->i_mount, off, "..", -1, ino, XFS_DIR3_FT_DIR);
+
+	/* Walk everything else. */
+	sfep = xfs_dir2_sf_firstentry(sfp);
+	for (i = 0; i < sfp->count; i++) {
+		ino = libxfs_dir2_sf_get_ino(mp, sfp, sfep);
+		filetype = libxfs_dir2_sf_get_ftype(mp, sfep);
+		off = xfs_dir2_db_off_to_dataptr(geo, geo->datablk,
+				xfs_dir2_sf_get_offset(sfep));
+
+		dir_emit(args->dp->i_mount, off, (char *)sfep->name,
+				sfep->namelen, ino, filetype);
+		sfep = libxfs_dir2_sf_nextentry(mp, sfp, sfep);
+	}
+
+	return 0;
+}
+
+/* List entries in block format directory. */
+static int
+list_blockdir(
+	struct xfs_da_args	*args)
+{
+	struct xfs_inode	*dp = args->dp;
+	struct xfs_mount	*mp = dp->i_mount;
+	struct xfs_buf		*bp;
+	struct xfs_da_geometry	*geo = mp->m_dir_geo;
+	xfs_dir2_dataptr_t	diroff;
+	unsigned int		offset;
+	unsigned int		end;
+	int			error;
+
+	error = xfs_dir3_block_read(NULL, dp, &bp);
+	if (error)
+		return error;
+
+	end = xfs_dir3_data_end_offset(geo, bp->b_addr);
+	for (offset = geo->data_entry_offset; offset < end;) {
+		struct xfs_dir2_data_unused	*dup = bp->b_addr + offset;
+		struct xfs_dir2_data_entry	*dep = bp->b_addr + offset;
+		uint8_t				filetype;
+
+		if (be16_to_cpu(dup->freetag) == XFS_DIR2_DATA_FREE_TAG) {
+			/* Unused entry */
+			offset += be16_to_cpu(dup->length);
+			continue;
+		}
+
+		/* Real entry */
+		diroff = xfs_dir2_db_off_to_dataptr(geo, geo->datablk, offset);
+		offset += libxfs_dir2_data_entsize(mp, dep->namelen);
+		filetype = libxfs_dir2_data_get_ftype(dp->i_mount, dep);
+		dir_emit(mp, diroff, (char *)dep->name, dep->namelen,
+				be64_to_cpu(dep->inumber), filetype);
+	}
+
+	libxfs_trans_brelse(args->trans, bp);
+	return error;
+}
+
+/* List entries in leaf format directory. */
+static int
+list_leafdir(
+	struct xfs_da_args	*args)
+{
+	struct xfs_bmbt_irec	map;
+	struct xfs_iext_cursor	icur;
+	struct xfs_inode	*dp = args->dp;
+	struct xfs_mount	*mp = dp->i_mount;
+	struct xfs_buf		*bp = NULL;
+	struct xfs_ifork	*ifp = XFS_IFORK_PTR(dp, XFS_DATA_FORK);
+	struct xfs_da_geometry	*geo = mp->m_dir_geo;
+	xfs_dir2_off_t		dirboff;
+	xfs_dablk_t		dabno = 0;
+	int			error = 0;
+
+	/* Read extent map. */
+	if (!(ifp->if_flags & XFS_IFEXTENTS)) {
+		error = -libxfs_iread_extents(NULL, dp, XFS_DATA_FORK);
+		if (error)
+			return error;
+	}
+
+	while (dabno < geo->leafblk) {
+		unsigned int	offset;
+		unsigned int	length;
+
+		/* Find mapping for leaf block. */
+		if (!xfs_iext_lookup_extent(dp, ifp, dabno, &icur, &map))
+			break;
+		if (map.br_startoff >= geo->leafblk)
+			break;
+		libxfs_trim_extent(&map, dabno, geo->leafblk - dabno);
+
+		/* Read the directory block of that first mapping. */
+		error = xfs_dir3_data_read(NULL, dp, map.br_startoff, 0, &bp);
+		if (error)
+			break;
+
+		dirboff = xfs_dir2_da_to_byte(geo, map.br_startoff);
+		for (offset = geo->data_entry_offset; offset < geo->blksize;) {
+			struct xfs_dir2_data_entry	*dep;
+			struct xfs_dir2_data_unused	*dup;
+			uint8_t				filetype;
+
+			dup = bp->b_addr + offset;
+			dep = bp->b_addr + offset;
+
+			if (be16_to_cpu(dup->freetag) ==
+			    XFS_DIR2_DATA_FREE_TAG) {
+				/* Skip unused entry */
+				length = be16_to_cpu(dup->length);
+				offset += length;
+				continue;
+			}
+
+			offset += libxfs_dir2_data_entsize(mp, dep->namelen);
+			filetype = libxfs_dir2_data_get_ftype(mp, dep);
+
+			dir_emit(mp, xfs_dir2_byte_to_dataptr(dirboff + offset),
+					(char *)dep->name, dep->namelen,
+					be64_to_cpu(dep->inumber), filetype);
+		}
+
+		dabno += XFS_DADDR_TO_FSB(mp, bp->b_length);
+		libxfs_buf_relse(bp);
+		bp = NULL;
+	}
+
+	if (bp)
+		libxfs_buf_relse(bp);
+
+	return error;
+}
+
+/* Read the directory, display contents. */
+int
+listdir(
+	struct xfs_inode	*dp)
+{
+	struct xfs_da_args	args = {
+		.dp		= dp,
+		.geo		= dp->i_mount->m_dir_geo,
+	};
+	int			error;
+	int			isblock;
+
+	if (dp->i_df.if_format == XFS_DINODE_FMT_LOCAL)
+		return list_sfdir(&args);
+
+	error = -libxfs_dir2_isblock(&args, &isblock);
+	if (error)
+		return error;
+
+	if (isblock)
+		return list_blockdir(&args);
+	return list_leafdir(&args);
+}
+
+/* List the inode number of the currently selected inode. */
+static int
+inum_cur(void)
+{
+	if (iocur_top->typ != &typtab[TYP_INODE])
+		return ENOENT;
+
+	dbprintf("%llu\n", iocur_top->ino);
+	return 0;
+}
+
+/* If the io cursor points to a directory, list its contents. */
+static int
+ls_cur(
+	char			*tag)
+{
+	struct xfs_inode	*dp;
+	int			error = 0;
+
+	if (iocur_top->typ != &typtab[TYP_INODE] ||
+	    !S_ISDIR(iocur_top->mode))
+		return ENOTDIR;
+
+	error = -libxfs_iget(mp, NULL, iocur_top->ino, 0, &dp);
+	if (error)
+		return error;
+
+	if (!S_ISDIR(VFS_I(dp)->i_mode)) {
+		error = ENOTDIR;
+		goto rele;
+	}
+
+	/* List the contents of a directory. */
+	if (tag)
+		dbprintf(_("%s:\n"), tag);
+
+	error = listdir(dp);
+	if (error)
+		goto rele;
+
+rele:
+	libxfs_irele(dp);
+	return error;
+}
+
+static void
+ls_help(void)
+{
+	dbprintf(_(
+"\n"
+" List the contents of the currently selected directory inode.\n"
+"\n"
+" Options:\n"
+"   -i -- Resolve the given paths to their corresponding inode numbers.\n"
+"         If no paths are given, display the current inode number.\n"
+"\n"
+" Directory contents will be listed in the format:\n"
+" dir_cookie	inode_number	type	hash	name_length	name\n"
+	));
+}
+
+static int
+ls_f(
+	int			argc,
+	char			**argv)
+{
+	bool			inum_only = false;
+	int			c;
+	int			error = 0;
+
+	while ((c = getopt(argc, argv, "i")) != -1) {
+		switch (c) {
+		case 'i':
+			inum_only = true;
+			break;
+		default:
+			ls_help();
+			return 0;
+		}
+	}
+
+	if (optind == argc) {
+		if (inum_only)
+			error = inum_cur();
+		else
+			error = ls_cur(NULL);
+		if (error) {
+			dbprintf("%s\n", strerror(error));
+			exitcode = 1;
+		}
+
+		return 0;
+	}
+
+	for (c = optind; c < argc; c++) {
+		push_cur();
+
+		error = path_walk(argv[c]);
+		if (error)
+			goto err_cur;
+
+		if (inum_only)
+			error = inum_cur();
+		else
+			error = ls_cur(argv[c]);
+		if (error)
+			goto err_cur;
+
+		pop_cur();
+	}
+
+	return 0;
+err_cur:
+	pop_cur();
+	if (error) {
+		dbprintf("%s: %s\n", argv[c], strerror(error));
+		exitcode = 1;
+	}
+	return 0;
+}
+
+static struct cmdinfo ls_cmd = {
+	.name		= "ls",
+	.altname	= "l",
+	.cfunc		= ls_f,
+	.argmin		= 0,
+	.argmax		= -1,
+	.canpush	= 0,
+	.args		= "[-i] [paths...]",
+	.help		= ls_help,
+};
+
 void
 namei_init(void)
 {
 	path_cmd.oneline = _("navigate to an inode by path");
 	add_command(&path_cmd);
+
+	ls_cmd.oneline = _("list directory contents");
+	add_command(&ls_cmd);
 }
diff --git a/libxfs/libxfs_api_defs.h b/libxfs/libxfs_api_defs.h
index 9492955d..9a00ce66 100644
--- a/libxfs/libxfs_api_defs.h
+++ b/libxfs/libxfs_api_defs.h
@@ -190,6 +190,7 @@
 #define xfs_trans_resv_calc		libxfs_trans_resv_calc
 #define xfs_trans_roll_inode		libxfs_trans_roll_inode
 #define xfs_trans_roll			libxfs_trans_roll
+#define xfs_trim_extent			libxfs_trim_extent
 
 #define xfs_verify_agbno		libxfs_verify_agbno
 #define xfs_verify_agino		libxfs_verify_agino
diff --git a/man/man8/xfs_db.8 b/man/man8/xfs_db.8
index 4df265ec..58727495 100644
--- a/man/man8/xfs_db.8
+++ b/man/man8/xfs_db.8
@@ -806,6 +806,22 @@ This makes it easier to find discrepancies in the reservation calculations
 between xfsprogs and the kernel, which will help when diagnosing minimum
 log size calculation errors.
 .TP
+.BI "ls [\-i] [" paths "]..."
+List the contents of a directory.
+If a path resolves to a directory, the directory will be listed.
+If no paths are supplied and the IO cursor points at a directory inode,
+the contents of that directory will be listed.
+
+The output format is:
+directory cookie, inode number, file type, hash, name length, name.
+.RS 1.0i
+.TP 0.4i
+.B \-i
+Resolve each of the given paths to an inode number and print that number.
+If no paths are given and the IO cursor points to an inode, print the inode
+number.
+.RE
+.TP
 .BI "metadump [\-egow] " filename
 Dumps metadata to a file. See
 .BR xfs_metadump (8)


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* Re: [PATCH 2/2] xfs_db: add an ls command
  2021-01-16  1:24 ` [PATCH 2/2] xfs_db: add an ls command Darrick J. Wong
@ 2021-01-20 15:24   ` Chandan Babu R
  0 siblings, 0 replies; 15+ messages in thread
From: Chandan Babu R @ 2021-01-20 15:24 UTC (permalink / raw)
  To: Darrick J. Wong; +Cc: sandeen, linux-xfs


On 16 Jan 2021 at 06:54, Darrick J. Wong wrote:
> From: Darrick J. Wong <djwong@kernel.org>
>
> Add to xfs_db the ability to list a directory.
>

W.r.t logical correctness of the code,

Reviewed-by: Chandan Babu R <chandanrlinux@gmail.com>

> Signed-off-by: Darrick J. Wong <djwong@kernel.org>
> ---
>  db/namei.c               |  389 ++++++++++++++++++++++++++++++++++++++++++++++
>  libxfs/libxfs_api_defs.h |    1 
>  man/man8/xfs_db.8        |   16 ++
>  3 files changed, 406 insertions(+)
>
>
> diff --git a/db/namei.c b/db/namei.c
> index eebebe15..e75b5ebd 100644
> --- a/db/namei.c
> +++ b/db/namei.c
> @@ -215,9 +215,398 @@ static struct cmdinfo path_cmd = {
>  	.help		= path_help,
>  };
>  
> +/* List a directory's entries. */
> +
> +static const char *filetype_strings[XFS_DIR3_FT_MAX] = {
> +	[XFS_DIR3_FT_UNKNOWN]	= "unknown",
> +	[XFS_DIR3_FT_REG_FILE]	= "regular",
> +	[XFS_DIR3_FT_DIR]	= "directory",
> +	[XFS_DIR3_FT_CHRDEV]	= "chardev",
> +	[XFS_DIR3_FT_BLKDEV]	= "blkdev",
> +	[XFS_DIR3_FT_FIFO]	= "fifo",
> +	[XFS_DIR3_FT_SOCK]	= "socket",
> +	[XFS_DIR3_FT_SYMLINK]	= "symlink",
> +	[XFS_DIR3_FT_WHT]	= "whiteout",
> +};
> +
> +static const char *
> +get_dstr(
> +	struct xfs_mount	*mp,
> +	uint8_t			filetype)
> +{
> +	if (!xfs_sb_version_hasftype(&mp->m_sb))
> +		return filetype_strings[XFS_DIR3_FT_UNKNOWN];
> +
> +	if (filetype >= XFS_DIR3_FT_MAX)
> +		return filetype_strings[XFS_DIR3_FT_UNKNOWN];
> +
> +	return filetype_strings[filetype];
> +}
> +
> +static void
> +dir_emit(
> +	struct xfs_mount	*mp,
> +	xfs_dir2_dataptr_t	off,
> +	char			*name,
> +	ssize_t			namelen,
> +	xfs_ino_t		ino,
> +	uint8_t			dtype)
> +{
> +	char			*display_name;
> +	struct xfs_name		xname = { .name = name };
> +	const char		*dstr = get_dstr(mp, dtype);
> +	xfs_dahash_t		hash;
> +	bool			good;
> +
> +	if (namelen < 0) {
> +		/* Negative length means that name is null-terminated. */
> +		display_name = name;
> +		xname.len = strlen(name);
> +		good = true;
> +	} else {
> +		/*
> +		 * Otherwise, name came from a directory entry, so we have to
> +		 * copy the string to a buffer so that we can add the null
> +		 * terminator.
> +		 */
> +		display_name = malloc(namelen + 1);
> +		memcpy(display_name, name, namelen);
> +		display_name[namelen] = 0;
> +		xname.len = namelen;
> +		good = libxfs_dir2_namecheck(name, namelen);
> +	}
> +	hash = libxfs_dir2_hashname(mp, &xname);
> +
> +	dbprintf("%-10u %-18llu %-14s 0x%08llx %3d %s %s\n", off & 0xFFFFFFFF,
> +			ino, dstr, hash, xname.len,
> +			display_name, good ? _("(good)") : _("(corrupt)"));
> +
> +	if (display_name != name)
> +		free(display_name);
> +}
> +
> +static int
> +list_sfdir(
> +	struct xfs_da_args		*args)
> +{
> +	struct xfs_inode		*dp = args->dp;
> +	struct xfs_mount		*mp = dp->i_mount;
> +	struct xfs_da_geometry		*geo = args->geo;
> +	struct xfs_dir2_sf_entry	*sfep;
> +	struct xfs_dir2_sf_hdr		*sfp;
> +	xfs_ino_t			ino;
> +	xfs_dir2_dataptr_t		off;
> +	unsigned int			i;
> +	uint8_t				filetype;
> +
> +	sfp = (struct xfs_dir2_sf_hdr *)dp->i_df.if_u1.if_data;
> +
> +	/* . and .. entries */
> +	off = xfs_dir2_db_off_to_dataptr(geo, geo->datablk,
> +			geo->data_entry_offset);
> +	dir_emit(args->dp->i_mount, off, ".", -1, dp->i_ino, XFS_DIR3_FT_DIR);
> +
> +	ino = libxfs_dir2_sf_get_parent_ino(sfp);
> +	off = xfs_dir2_db_off_to_dataptr(geo, geo->datablk,
> +			geo->data_entry_offset +
> +			libxfs_dir2_data_entsize(mp, sizeof(".") - 1));
> +	dir_emit(args->dp->i_mount, off, "..", -1, ino, XFS_DIR3_FT_DIR);
> +
> +	/* Walk everything else. */
> +	sfep = xfs_dir2_sf_firstentry(sfp);
> +	for (i = 0; i < sfp->count; i++) {
> +		ino = libxfs_dir2_sf_get_ino(mp, sfp, sfep);
> +		filetype = libxfs_dir2_sf_get_ftype(mp, sfep);
> +		off = xfs_dir2_db_off_to_dataptr(geo, geo->datablk,
> +				xfs_dir2_sf_get_offset(sfep));
> +
> +		dir_emit(args->dp->i_mount, off, (char *)sfep->name,
> +				sfep->namelen, ino, filetype);
> +		sfep = libxfs_dir2_sf_nextentry(mp, sfp, sfep);
> +	}
> +
> +	return 0;
> +}
> +
> +/* List entries in block format directory. */
> +static int
> +list_blockdir(
> +	struct xfs_da_args	*args)
> +{
> +	struct xfs_inode	*dp = args->dp;
> +	struct xfs_mount	*mp = dp->i_mount;
> +	struct xfs_buf		*bp;
> +	struct xfs_da_geometry	*geo = mp->m_dir_geo;
> +	xfs_dir2_dataptr_t	diroff;
> +	unsigned int		offset;
> +	unsigned int		end;
> +	int			error;
> +
> +	error = xfs_dir3_block_read(NULL, dp, &bp);
> +	if (error)
> +		return error;
> +
> +	end = xfs_dir3_data_end_offset(geo, bp->b_addr);
> +	for (offset = geo->data_entry_offset; offset < end;) {
> +		struct xfs_dir2_data_unused	*dup = bp->b_addr + offset;
> +		struct xfs_dir2_data_entry	*dep = bp->b_addr + offset;
> +		uint8_t				filetype;
> +
> +		if (be16_to_cpu(dup->freetag) == XFS_DIR2_DATA_FREE_TAG) {
> +			/* Unused entry */
> +			offset += be16_to_cpu(dup->length);
> +			continue;
> +		}
> +
> +		/* Real entry */
> +		diroff = xfs_dir2_db_off_to_dataptr(geo, geo->datablk, offset);
> +		offset += libxfs_dir2_data_entsize(mp, dep->namelen);
> +		filetype = libxfs_dir2_data_get_ftype(dp->i_mount, dep);
> +		dir_emit(mp, diroff, (char *)dep->name, dep->namelen,
> +				be64_to_cpu(dep->inumber), filetype);
> +	}
> +
> +	libxfs_trans_brelse(args->trans, bp);
> +	return error;
> +}
> +
> +/* List entries in leaf format directory. */
> +static int
> +list_leafdir(
> +	struct xfs_da_args	*args)
> +{
> +	struct xfs_bmbt_irec	map;
> +	struct xfs_iext_cursor	icur;
> +	struct xfs_inode	*dp = args->dp;
> +	struct xfs_mount	*mp = dp->i_mount;
> +	struct xfs_buf		*bp = NULL;
> +	struct xfs_ifork	*ifp = XFS_IFORK_PTR(dp, XFS_DATA_FORK);
> +	struct xfs_da_geometry	*geo = mp->m_dir_geo;
> +	xfs_dir2_off_t		dirboff;
> +	xfs_dablk_t		dabno = 0;
> +	int			error = 0;
> +
> +	/* Read extent map. */
> +	if (!(ifp->if_flags & XFS_IFEXTENTS)) {
> +		error = -libxfs_iread_extents(NULL, dp, XFS_DATA_FORK);
> +		if (error)
> +			return error;
> +	}
> +
> +	while (dabno < geo->leafblk) {
> +		unsigned int	offset;
> +		unsigned int	length;
> +
> +		/* Find mapping for leaf block. */
> +		if (!xfs_iext_lookup_extent(dp, ifp, dabno, &icur, &map))
> +			break;
> +		if (map.br_startoff >= geo->leafblk)
> +			break;
> +		libxfs_trim_extent(&map, dabno, geo->leafblk - dabno);
> +
> +		/* Read the directory block of that first mapping. */
> +		error = xfs_dir3_data_read(NULL, dp, map.br_startoff, 0, &bp);
> +		if (error)
> +			break;
> +
> +		dirboff = xfs_dir2_da_to_byte(geo, map.br_startoff);
> +		for (offset = geo->data_entry_offset; offset < geo->blksize;) {
> +			struct xfs_dir2_data_entry	*dep;
> +			struct xfs_dir2_data_unused	*dup;
> +			uint8_t				filetype;
> +
> +			dup = bp->b_addr + offset;
> +			dep = bp->b_addr + offset;
> +
> +			if (be16_to_cpu(dup->freetag) ==
> +			    XFS_DIR2_DATA_FREE_TAG) {
> +				/* Skip unused entry */
> +				length = be16_to_cpu(dup->length);
> +				offset += length;
> +				continue;
> +			}
> +
> +			offset += libxfs_dir2_data_entsize(mp, dep->namelen);
> +			filetype = libxfs_dir2_data_get_ftype(mp, dep);
> +
> +			dir_emit(mp, xfs_dir2_byte_to_dataptr(dirboff + offset),
> +					(char *)dep->name, dep->namelen,
> +					be64_to_cpu(dep->inumber), filetype);
> +		}
> +
> +		dabno += XFS_DADDR_TO_FSB(mp, bp->b_length);
> +		libxfs_buf_relse(bp);
> +		bp = NULL;
> +	}
> +
> +	if (bp)
> +		libxfs_buf_relse(bp);
> +
> +	return error;
> +}
> +
> +/* Read the directory, display contents. */
> +int
> +listdir(
> +	struct xfs_inode	*dp)
> +{
> +	struct xfs_da_args	args = {
> +		.dp		= dp,
> +		.geo		= dp->i_mount->m_dir_geo,
> +	};
> +	int			error;
> +	int			isblock;
> +
> +	if (dp->i_df.if_format == XFS_DINODE_FMT_LOCAL)
> +		return list_sfdir(&args);
> +
> +	error = -libxfs_dir2_isblock(&args, &isblock);
> +	if (error)
> +		return error;
> +
> +	if (isblock)
> +		return list_blockdir(&args);
> +	return list_leafdir(&args);
> +}
> +
> +/* List the inode number of the currently selected inode. */
> +static int
> +inum_cur(void)
> +{
> +	if (iocur_top->typ != &typtab[TYP_INODE])
> +		return ENOENT;
> +
> +	dbprintf("%llu\n", iocur_top->ino);
> +	return 0;
> +}
> +
> +/* If the io cursor points to a directory, list its contents. */
> +static int
> +ls_cur(
> +	char			*tag)
> +{
> +	struct xfs_inode	*dp;
> +	int			error = 0;
> +
> +	if (iocur_top->typ != &typtab[TYP_INODE] ||
> +	    !S_ISDIR(iocur_top->mode))
> +		return ENOTDIR;
> +
> +	error = -libxfs_iget(mp, NULL, iocur_top->ino, 0, &dp);
> +	if (error)
> +		return error;
> +
> +	if (!S_ISDIR(VFS_I(dp)->i_mode)) {
> +		error = ENOTDIR;
> +		goto rele;
> +	}
> +
> +	/* List the contents of a directory. */
> +	if (tag)
> +		dbprintf(_("%s:\n"), tag);
> +
> +	error = listdir(dp);
> +	if (error)
> +		goto rele;
> +
> +rele:
> +	libxfs_irele(dp);
> +	return error;
> +}
> +
> +static void
> +ls_help(void)
> +{
> +	dbprintf(_(
> +"\n"
> +" List the contents of the currently selected directory inode.\n"
> +"\n"
> +" Options:\n"
> +"   -i -- Resolve the given paths to their corresponding inode numbers.\n"
> +"         If no paths are given, display the current inode number.\n"
> +"\n"
> +" Directory contents will be listed in the format:\n"
> +" dir_cookie	inode_number	type	hash	name_length	name\n"
> +	));
> +}
> +
> +static int
> +ls_f(
> +	int			argc,
> +	char			**argv)
> +{
> +	bool			inum_only = false;
> +	int			c;
> +	int			error = 0;
> +
> +	while ((c = getopt(argc, argv, "i")) != -1) {
> +		switch (c) {
> +		case 'i':
> +			inum_only = true;
> +			break;
> +		default:
> +			ls_help();
> +			return 0;
> +		}
> +	}
> +
> +	if (optind == argc) {
> +		if (inum_only)
> +			error = inum_cur();
> +		else
> +			error = ls_cur(NULL);
> +		if (error) {
> +			dbprintf("%s\n", strerror(error));
> +			exitcode = 1;
> +		}
> +
> +		return 0;
> +	}
> +
> +	for (c = optind; c < argc; c++) {
> +		push_cur();
> +
> +		error = path_walk(argv[c]);
> +		if (error)
> +			goto err_cur;
> +
> +		if (inum_only)
> +			error = inum_cur();
> +		else
> +			error = ls_cur(argv[c]);
> +		if (error)
> +			goto err_cur;
> +
> +		pop_cur();
> +	}
> +
> +	return 0;
> +err_cur:
> +	pop_cur();
> +	if (error) {
> +		dbprintf("%s: %s\n", argv[c], strerror(error));
> +		exitcode = 1;
> +	}
> +	return 0;
> +}
> +
> +static struct cmdinfo ls_cmd = {
> +	.name		= "ls",
> +	.altname	= "l",
> +	.cfunc		= ls_f,
> +	.argmin		= 0,
> +	.argmax		= -1,
> +	.canpush	= 0,
> +	.args		= "[-i] [paths...]",
> +	.help		= ls_help,
> +};
> +
>  void
>  namei_init(void)
>  {
>  	path_cmd.oneline = _("navigate to an inode by path");
>  	add_command(&path_cmd);
> +
> +	ls_cmd.oneline = _("list directory contents");
> +	add_command(&ls_cmd);
>  }
> diff --git a/libxfs/libxfs_api_defs.h b/libxfs/libxfs_api_defs.h
> index 9492955d..9a00ce66 100644
> --- a/libxfs/libxfs_api_defs.h
> +++ b/libxfs/libxfs_api_defs.h
> @@ -190,6 +190,7 @@
>  #define xfs_trans_resv_calc		libxfs_trans_resv_calc
>  #define xfs_trans_roll_inode		libxfs_trans_roll_inode
>  #define xfs_trans_roll			libxfs_trans_roll
> +#define xfs_trim_extent			libxfs_trim_extent
>  
>  #define xfs_verify_agbno		libxfs_verify_agbno
>  #define xfs_verify_agino		libxfs_verify_agino
> diff --git a/man/man8/xfs_db.8 b/man/man8/xfs_db.8
> index 4df265ec..58727495 100644
> --- a/man/man8/xfs_db.8
> +++ b/man/man8/xfs_db.8
> @@ -806,6 +806,22 @@ This makes it easier to find discrepancies in the reservation calculations
>  between xfsprogs and the kernel, which will help when diagnosing minimum
>  log size calculation errors.
>  .TP
> +.BI "ls [\-i] [" paths "]..."
> +List the contents of a directory.
> +If a path resolves to a directory, the directory will be listed.
> +If no paths are supplied and the IO cursor points at a directory inode,
> +the contents of that directory will be listed.
> +
> +The output format is:
> +directory cookie, inode number, file type, hash, name length, name.
> +.RS 1.0i
> +.TP 0.4i
> +.B \-i
> +Resolve each of the given paths to an inode number and print that number.
> +If no paths are given and the IO cursor points to an inode, print the inode
> +number.
> +.RE
> +.TP
>  .BI "metadump [\-egow] " filename
>  Dumps metadata to a file. See
>  .BR xfs_metadump (8)


-- 
chandan

^ permalink raw reply	[flat|nested] 15+ messages in thread

* [PATCH 2/2] xfs_db: add an ls command
  2021-01-16  1:24 [PATCHSET v3 0/2] xfs_db: add minimal directory navigation Darrick J. Wong
@ 2021-01-16  1:24 ` Darrick J. Wong
  2021-01-20 15:24   ` Chandan Babu R
  0 siblings, 1 reply; 15+ messages in thread
From: Darrick J. Wong @ 2021-01-16  1:24 UTC (permalink / raw)
  To: sandeen, djwong; +Cc: linux-xfs

From: Darrick J. Wong <djwong@kernel.org>

Add to xfs_db the ability to list a directory.

Signed-off-by: Darrick J. Wong <djwong@kernel.org>
---
 db/namei.c               |  389 ++++++++++++++++++++++++++++++++++++++++++++++
 libxfs/libxfs_api_defs.h |    1 
 man/man8/xfs_db.8        |   16 ++
 3 files changed, 406 insertions(+)


diff --git a/db/namei.c b/db/namei.c
index eebebe15..e75b5ebd 100644
--- a/db/namei.c
+++ b/db/namei.c
@@ -215,9 +215,398 @@ static struct cmdinfo path_cmd = {
 	.help		= path_help,
 };
 
+/* List a directory's entries. */
+
+static const char *filetype_strings[XFS_DIR3_FT_MAX] = {
+	[XFS_DIR3_FT_UNKNOWN]	= "unknown",
+	[XFS_DIR3_FT_REG_FILE]	= "regular",
+	[XFS_DIR3_FT_DIR]	= "directory",
+	[XFS_DIR3_FT_CHRDEV]	= "chardev",
+	[XFS_DIR3_FT_BLKDEV]	= "blkdev",
+	[XFS_DIR3_FT_FIFO]	= "fifo",
+	[XFS_DIR3_FT_SOCK]	= "socket",
+	[XFS_DIR3_FT_SYMLINK]	= "symlink",
+	[XFS_DIR3_FT_WHT]	= "whiteout",
+};
+
+static const char *
+get_dstr(
+	struct xfs_mount	*mp,
+	uint8_t			filetype)
+{
+	if (!xfs_sb_version_hasftype(&mp->m_sb))
+		return filetype_strings[XFS_DIR3_FT_UNKNOWN];
+
+	if (filetype >= XFS_DIR3_FT_MAX)
+		return filetype_strings[XFS_DIR3_FT_UNKNOWN];
+
+	return filetype_strings[filetype];
+}
+
+static void
+dir_emit(
+	struct xfs_mount	*mp,
+	xfs_dir2_dataptr_t	off,
+	char			*name,
+	ssize_t			namelen,
+	xfs_ino_t		ino,
+	uint8_t			dtype)
+{
+	char			*display_name;
+	struct xfs_name		xname = { .name = name };
+	const char		*dstr = get_dstr(mp, dtype);
+	xfs_dahash_t		hash;
+	bool			good;
+
+	if (namelen < 0) {
+		/* Negative length means that name is null-terminated. */
+		display_name = name;
+		xname.len = strlen(name);
+		good = true;
+	} else {
+		/*
+		 * Otherwise, name came from a directory entry, so we have to
+		 * copy the string to a buffer so that we can add the null
+		 * terminator.
+		 */
+		display_name = malloc(namelen + 1);
+		memcpy(display_name, name, namelen);
+		display_name[namelen] = 0;
+		xname.len = namelen;
+		good = libxfs_dir2_namecheck(name, namelen);
+	}
+	hash = libxfs_dir2_hashname(mp, &xname);
+
+	dbprintf("%-10u %-18llu %-14s 0x%08llx %3d %s %s\n", off & 0xFFFFFFFF,
+			ino, dstr, hash, xname.len,
+			display_name, good ? _("(good)") : _("(corrupt)"));
+
+	if (display_name != name)
+		free(display_name);
+}
+
+static int
+list_sfdir(
+	struct xfs_da_args		*args)
+{
+	struct xfs_inode		*dp = args->dp;
+	struct xfs_mount		*mp = dp->i_mount;
+	struct xfs_da_geometry		*geo = args->geo;
+	struct xfs_dir2_sf_entry	*sfep;
+	struct xfs_dir2_sf_hdr		*sfp;
+	xfs_ino_t			ino;
+	xfs_dir2_dataptr_t		off;
+	unsigned int			i;
+	uint8_t				filetype;
+
+	sfp = (struct xfs_dir2_sf_hdr *)dp->i_df.if_u1.if_data;
+
+	/* . and .. entries */
+	off = xfs_dir2_db_off_to_dataptr(geo, geo->datablk,
+			geo->data_entry_offset);
+	dir_emit(args->dp->i_mount, off, ".", -1, dp->i_ino, XFS_DIR3_FT_DIR);
+
+	ino = libxfs_dir2_sf_get_parent_ino(sfp);
+	off = xfs_dir2_db_off_to_dataptr(geo, geo->datablk,
+			geo->data_entry_offset +
+			libxfs_dir2_data_entsize(mp, sizeof(".") - 1));
+	dir_emit(args->dp->i_mount, off, "..", -1, ino, XFS_DIR3_FT_DIR);
+
+	/* Walk everything else. */
+	sfep = xfs_dir2_sf_firstentry(sfp);
+	for (i = 0; i < sfp->count; i++) {
+		ino = libxfs_dir2_sf_get_ino(mp, sfp, sfep);
+		filetype = libxfs_dir2_sf_get_ftype(mp, sfep);
+		off = xfs_dir2_db_off_to_dataptr(geo, geo->datablk,
+				xfs_dir2_sf_get_offset(sfep));
+
+		dir_emit(args->dp->i_mount, off, (char *)sfep->name,
+				sfep->namelen, ino, filetype);
+		sfep = libxfs_dir2_sf_nextentry(mp, sfp, sfep);
+	}
+
+	return 0;
+}
+
+/* List entries in block format directory. */
+static int
+list_blockdir(
+	struct xfs_da_args	*args)
+{
+	struct xfs_inode	*dp = args->dp;
+	struct xfs_mount	*mp = dp->i_mount;
+	struct xfs_buf		*bp;
+	struct xfs_da_geometry	*geo = mp->m_dir_geo;
+	xfs_dir2_dataptr_t	diroff;
+	unsigned int		offset;
+	unsigned int		end;
+	int			error;
+
+	error = xfs_dir3_block_read(NULL, dp, &bp);
+	if (error)
+		return error;
+
+	end = xfs_dir3_data_end_offset(geo, bp->b_addr);
+	for (offset = geo->data_entry_offset; offset < end;) {
+		struct xfs_dir2_data_unused	*dup = bp->b_addr + offset;
+		struct xfs_dir2_data_entry	*dep = bp->b_addr + offset;
+		uint8_t				filetype;
+
+		if (be16_to_cpu(dup->freetag) == XFS_DIR2_DATA_FREE_TAG) {
+			/* Unused entry */
+			offset += be16_to_cpu(dup->length);
+			continue;
+		}
+
+		/* Real entry */
+		diroff = xfs_dir2_db_off_to_dataptr(geo, geo->datablk, offset);
+		offset += libxfs_dir2_data_entsize(mp, dep->namelen);
+		filetype = libxfs_dir2_data_get_ftype(dp->i_mount, dep);
+		dir_emit(mp, diroff, (char *)dep->name, dep->namelen,
+				be64_to_cpu(dep->inumber), filetype);
+	}
+
+	libxfs_trans_brelse(args->trans, bp);
+	return error;
+}
+
+/* List entries in leaf format directory. */
+static int
+list_leafdir(
+	struct xfs_da_args	*args)
+{
+	struct xfs_bmbt_irec	map;
+	struct xfs_iext_cursor	icur;
+	struct xfs_inode	*dp = args->dp;
+	struct xfs_mount	*mp = dp->i_mount;
+	struct xfs_buf		*bp = NULL;
+	struct xfs_ifork	*ifp = XFS_IFORK_PTR(dp, XFS_DATA_FORK);
+	struct xfs_da_geometry	*geo = mp->m_dir_geo;
+	xfs_dir2_off_t		dirboff;
+	xfs_dablk_t		dabno = 0;
+	int			error = 0;
+
+	/* Read extent map. */
+	if (!(ifp->if_flags & XFS_IFEXTENTS)) {
+		error = -libxfs_iread_extents(NULL, dp, XFS_DATA_FORK);
+		if (error)
+			return error;
+	}
+
+	while (dabno < geo->leafblk) {
+		unsigned int	offset;
+		unsigned int	length;
+
+		/* Find mapping for leaf block. */
+		if (!xfs_iext_lookup_extent(dp, ifp, dabno, &icur, &map))
+			break;
+		if (map.br_startoff >= geo->leafblk)
+			break;
+		libxfs_trim_extent(&map, dabno, geo->leafblk - dabno);
+
+		/* Read the directory block of that first mapping. */
+		error = xfs_dir3_data_read(NULL, dp, map.br_startoff, 0, &bp);
+		if (error)
+			break;
+
+		dirboff = xfs_dir2_da_to_byte(geo, map.br_startoff);
+		for (offset = geo->data_entry_offset; offset < geo->blksize;) {
+			struct xfs_dir2_data_entry	*dep;
+			struct xfs_dir2_data_unused	*dup;
+			uint8_t				filetype;
+
+			dup = bp->b_addr + offset;
+			dep = bp->b_addr + offset;
+
+			if (be16_to_cpu(dup->freetag) ==
+			    XFS_DIR2_DATA_FREE_TAG) {
+				/* Skip unused entry */
+				length = be16_to_cpu(dup->length);
+				offset += length;
+				continue;
+			}
+
+			offset += libxfs_dir2_data_entsize(mp, dep->namelen);
+			filetype = libxfs_dir2_data_get_ftype(mp, dep);
+
+			dir_emit(mp, xfs_dir2_byte_to_dataptr(dirboff + offset),
+					(char *)dep->name, dep->namelen,
+					be64_to_cpu(dep->inumber), filetype);
+		}
+
+		dabno += XFS_DADDR_TO_FSB(mp, bp->b_length);
+		libxfs_buf_relse(bp);
+		bp = NULL;
+	}
+
+	if (bp)
+		libxfs_buf_relse(bp);
+
+	return error;
+}
+
+/* Read the directory, display contents. */
+int
+listdir(
+	struct xfs_inode	*dp)
+{
+	struct xfs_da_args	args = {
+		.dp		= dp,
+		.geo		= dp->i_mount->m_dir_geo,
+	};
+	int			error;
+	int			isblock;
+
+	if (dp->i_df.if_format == XFS_DINODE_FMT_LOCAL)
+		return list_sfdir(&args);
+
+	error = -libxfs_dir2_isblock(&args, &isblock);
+	if (error)
+		return error;
+
+	if (isblock)
+		return list_blockdir(&args);
+	return list_leafdir(&args);
+}
+
+/* List the inode number of the currently selected inode. */
+static int
+inum_cur(void)
+{
+	if (iocur_top->typ != &typtab[TYP_INODE])
+		return ENOENT;
+
+	dbprintf("%llu\n", iocur_top->ino);
+	return 0;
+}
+
+/* If the io cursor points to a directory, list its contents. */
+static int
+ls_cur(
+	char			*tag)
+{
+	struct xfs_inode	*dp;
+	int			error = 0;
+
+	if (iocur_top->typ != &typtab[TYP_INODE] ||
+	    !S_ISDIR(iocur_top->mode))
+		return ENOTDIR;
+
+	error = -libxfs_iget(mp, NULL, iocur_top->ino, 0, &dp);
+	if (error)
+		return error;
+
+	if (!S_ISDIR(VFS_I(dp)->i_mode)) {
+		error = ENOTDIR;
+		goto rele;
+	}
+
+	/* List the contents of a directory. */
+	if (tag)
+		dbprintf(_("%s:\n"), tag);
+
+	error = listdir(dp);
+	if (error)
+		goto rele;
+
+rele:
+	libxfs_irele(dp);
+	return error;
+}
+
+static void
+ls_help(void)
+{
+	dbprintf(_(
+"\n"
+" List the contents of the currently selected directory inode.\n"
+"\n"
+" Options:\n"
+"   -i -- Resolve the given paths to their corresponding inode numbers.\n"
+"         If no paths are given, display the current inode number.\n"
+"\n"
+" Directory contents will be listed in the format:\n"
+" dir_cookie	inode_number	type	hash	name_length	name\n"
+	));
+}
+
+static int
+ls_f(
+	int			argc,
+	char			**argv)
+{
+	bool			inum_only = false;
+	int			c;
+	int			error = 0;
+
+	while ((c = getopt(argc, argv, "i")) != -1) {
+		switch (c) {
+		case 'i':
+			inum_only = true;
+			break;
+		default:
+			ls_help();
+			return 0;
+		}
+	}
+
+	if (optind == argc) {
+		if (inum_only)
+			error = inum_cur();
+		else
+			error = ls_cur(NULL);
+		if (error) {
+			dbprintf("%s\n", strerror(error));
+			exitcode = 1;
+		}
+
+		return 0;
+	}
+
+	for (c = optind; c < argc; c++) {
+		push_cur();
+
+		error = path_walk(argv[c]);
+		if (error)
+			goto err_cur;
+
+		if (inum_only)
+			error = inum_cur();
+		else
+			error = ls_cur(argv[c]);
+		if (error)
+			goto err_cur;
+
+		pop_cur();
+	}
+
+	return 0;
+err_cur:
+	pop_cur();
+	if (error) {
+		dbprintf("%s: %s\n", argv[c], strerror(error));
+		exitcode = 1;
+	}
+	return 0;
+}
+
+static struct cmdinfo ls_cmd = {
+	.name		= "ls",
+	.altname	= "l",
+	.cfunc		= ls_f,
+	.argmin		= 0,
+	.argmax		= -1,
+	.canpush	= 0,
+	.args		= "[-i] [paths...]",
+	.help		= ls_help,
+};
+
 void
 namei_init(void)
 {
 	path_cmd.oneline = _("navigate to an inode by path");
 	add_command(&path_cmd);
+
+	ls_cmd.oneline = _("list directory contents");
+	add_command(&ls_cmd);
 }
diff --git a/libxfs/libxfs_api_defs.h b/libxfs/libxfs_api_defs.h
index 9492955d..9a00ce66 100644
--- a/libxfs/libxfs_api_defs.h
+++ b/libxfs/libxfs_api_defs.h
@@ -190,6 +190,7 @@
 #define xfs_trans_resv_calc		libxfs_trans_resv_calc
 #define xfs_trans_roll_inode		libxfs_trans_roll_inode
 #define xfs_trans_roll			libxfs_trans_roll
+#define xfs_trim_extent			libxfs_trim_extent
 
 #define xfs_verify_agbno		libxfs_verify_agbno
 #define xfs_verify_agino		libxfs_verify_agino
diff --git a/man/man8/xfs_db.8 b/man/man8/xfs_db.8
index 4df265ec..58727495 100644
--- a/man/man8/xfs_db.8
+++ b/man/man8/xfs_db.8
@@ -806,6 +806,22 @@ This makes it easier to find discrepancies in the reservation calculations
 between xfsprogs and the kernel, which will help when diagnosing minimum
 log size calculation errors.
 .TP
+.BI "ls [\-i] [" paths "]..."
+List the contents of a directory.
+If a path resolves to a directory, the directory will be listed.
+If no paths are supplied and the IO cursor points at a directory inode,
+the contents of that directory will be listed.
+
+The output format is:
+directory cookie, inode number, file type, hash, name length, name.
+.RS 1.0i
+.TP 0.4i
+.B \-i
+Resolve each of the given paths to an inode number and print that number.
+If no paths are given and the IO cursor points to an inode, print the inode
+number.
+.RE
+.TP
 .BI "metadump [\-egow] " filename
 Dumps metadata to a file. See
 .BR xfs_metadump (8)


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* Re: [PATCH 2/2] xfs_db: add an ls command
  2020-11-25 20:38 ` [PATCH 2/2] xfs_db: add an ls command Darrick J. Wong
@ 2020-12-01 10:17   ` Christoph Hellwig
  0 siblings, 0 replies; 15+ messages in thread
From: Christoph Hellwig @ 2020-12-01 10:17 UTC (permalink / raw)
  To: Darrick J. Wong; +Cc: sandeen, linux-xfs

On Wed, Nov 25, 2020 at 12:38:42PM -0800, Darrick J. Wong wrote:
> From: Darrick J. Wong <darrick.wong@oracle.com>
> 
> Add to xfs_db the ability to list a directory.
> 
> Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>

Looks good,

Reviewed-by: Christoph Hellwig <hch@lst.de>

^ permalink raw reply	[flat|nested] 15+ messages in thread

* [PATCH 2/2] xfs_db: add an ls command
  2020-11-25 20:38 [PATCH v3 0/2] xfs_db: add minimal directory navigation Darrick J. Wong
@ 2020-11-25 20:38 ` Darrick J. Wong
  2020-12-01 10:17   ` Christoph Hellwig
  0 siblings, 1 reply; 15+ messages in thread
From: Darrick J. Wong @ 2020-11-25 20:38 UTC (permalink / raw)
  To: sandeen, darrick.wong; +Cc: linux-xfs

From: Darrick J. Wong <darrick.wong@oracle.com>

Add to xfs_db the ability to list a directory.

Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
---
 db/namei.c               |  389 ++++++++++++++++++++++++++++++++++++++++++++++
 libxfs/libxfs_api_defs.h |    1 
 man/man8/xfs_db.8        |   16 ++
 3 files changed, 406 insertions(+)


diff --git a/db/namei.c b/db/namei.c
index 13ae7f7d116b..4b467ff8f1f5 100644
--- a/db/namei.c
+++ b/db/namei.c
@@ -215,9 +215,398 @@ static struct cmdinfo path_cmd = {
 	.help		= path_help,
 };
 
+/* List a directory's entries. */
+
+static const char *filetype_strings[XFS_DIR3_FT_MAX] = {
+	[XFS_DIR3_FT_UNKNOWN]	= "unknown",
+	[XFS_DIR3_FT_REG_FILE]	= "regular",
+	[XFS_DIR3_FT_DIR]	= "directory",
+	[XFS_DIR3_FT_CHRDEV]	= "chardev",
+	[XFS_DIR3_FT_BLKDEV]	= "blkdev",
+	[XFS_DIR3_FT_FIFO]	= "fifo",
+	[XFS_DIR3_FT_SOCK]	= "socket",
+	[XFS_DIR3_FT_SYMLINK]	= "symlink",
+	[XFS_DIR3_FT_WHT]	= "whiteout",
+};
+
+static const char *
+get_dstr(
+	struct xfs_mount	*mp,
+	uint8_t			filetype)
+{
+	if (!xfs_sb_version_hasftype(&mp->m_sb))
+		return filetype_strings[XFS_DIR3_FT_UNKNOWN];
+
+	if (filetype >= XFS_DIR3_FT_MAX)
+		return filetype_strings[XFS_DIR3_FT_UNKNOWN];
+
+	return filetype_strings[filetype];
+}
+
+static void
+dir_emit(
+	struct xfs_mount	*mp,
+	xfs_dir2_dataptr_t	off,
+	char			*name,
+	ssize_t			namelen,
+	xfs_ino_t		ino,
+	uint8_t			dtype)
+{
+	char			*display_name;
+	struct xfs_name		xname = { .name = name };
+	const char		*dstr = get_dstr(mp, dtype);
+	xfs_dahash_t		hash;
+	bool			good;
+
+	if (namelen < 0) {
+		/* Negative length means that name is null-terminated. */
+		display_name = name;
+		xname.len = strlen(name);
+		good = true;
+	} else {
+		/*
+		 * Otherwise, name came from a directory entry, so we have to
+		 * copy the string to a buffer so that we can add the null
+		 * terminator.
+		 */
+		display_name = malloc(namelen + 1);
+		memcpy(display_name, name, namelen);
+		display_name[namelen] = 0;
+		xname.len = namelen;
+		good = libxfs_dir2_namecheck(name, namelen);
+	}
+	hash = libxfs_dir2_hashname(mp, &xname);
+
+	dbprintf("%-10u %-18llu %-14s 0x%08llx %3d %s %s\n", off & 0xFFFFFFFF,
+			ino, dstr, hash, xname.len,
+			display_name, good ? _("(good)") : _("(corrupt)"));
+
+	if (display_name != name)
+		free(display_name);
+}
+
+static int
+list_sfdir(
+	struct xfs_da_args		*args)
+{
+	struct xfs_inode		*dp = args->dp;
+	struct xfs_mount		*mp = dp->i_mount;
+	struct xfs_da_geometry		*geo = args->geo;
+	struct xfs_dir2_sf_entry	*sfep;
+	struct xfs_dir2_sf_hdr		*sfp;
+	xfs_ino_t			ino;
+	xfs_dir2_dataptr_t		off;
+	unsigned int			i;
+	uint8_t				filetype;
+
+	sfp = (struct xfs_dir2_sf_hdr *)dp->i_df.if_u1.if_data;
+
+	/* . and .. entries */
+	off = xfs_dir2_db_off_to_dataptr(geo, geo->datablk,
+			geo->data_entry_offset);
+	dir_emit(args->dp->i_mount, off, ".", -1, dp->i_ino, XFS_DIR3_FT_DIR);
+
+	ino = libxfs_dir2_sf_get_parent_ino(sfp);
+	off = xfs_dir2_db_off_to_dataptr(geo, geo->datablk,
+			geo->data_entry_offset +
+			libxfs_dir2_data_entsize(mp, sizeof(".") - 1));
+	dir_emit(args->dp->i_mount, off, "..", -1, ino, XFS_DIR3_FT_DIR);
+
+	/* Walk everything else. */
+	sfep = xfs_dir2_sf_firstentry(sfp);
+	for (i = 0; i < sfp->count; i++) {
+		ino = libxfs_dir2_sf_get_ino(mp, sfp, sfep);
+		filetype = libxfs_dir2_sf_get_ftype(mp, sfep);
+		off = xfs_dir2_db_off_to_dataptr(geo, geo->datablk,
+				xfs_dir2_sf_get_offset(sfep));
+
+		dir_emit(args->dp->i_mount, off, (char *)sfep->name,
+				sfep->namelen, ino, filetype);
+		sfep = libxfs_dir2_sf_nextentry(mp, sfp, sfep);
+	}
+
+	return 0;
+}
+
+/* List entries in block format directory. */
+static int
+list_blockdir(
+	struct xfs_da_args	*args)
+{
+	struct xfs_inode	*dp = args->dp;
+	struct xfs_mount	*mp = dp->i_mount;
+	struct xfs_buf		*bp;
+	struct xfs_da_geometry	*geo = mp->m_dir_geo;
+	xfs_dir2_dataptr_t	diroff;
+	unsigned int		offset;
+	unsigned int		end;
+	int			error;
+
+	error = xfs_dir3_block_read(NULL, dp, &bp);
+	if (error)
+		return error;
+
+	end = xfs_dir3_data_end_offset(geo, bp->b_addr);
+	for (offset = geo->data_entry_offset; offset < end;) {
+		struct xfs_dir2_data_unused	*dup = bp->b_addr + offset;
+		struct xfs_dir2_data_entry	*dep = bp->b_addr + offset;
+		uint8_t				filetype;
+
+		if (be16_to_cpu(dup->freetag) == XFS_DIR2_DATA_FREE_TAG) {
+			/* Unused entry */
+			offset += be16_to_cpu(dup->length);
+			continue;
+		}
+
+		/* Real entry */
+		diroff = xfs_dir2_db_off_to_dataptr(geo, geo->datablk, offset);
+		offset += libxfs_dir2_data_entsize(mp, dep->namelen);
+		filetype = libxfs_dir2_data_get_ftype(dp->i_mount, dep);
+		dir_emit(mp, diroff, (char *)dep->name, dep->namelen,
+				be64_to_cpu(dep->inumber), filetype);
+	}
+
+	libxfs_trans_brelse(args->trans, bp);
+	return error;
+}
+
+/* List entries in leaf format directory. */
+static int
+list_leafdir(
+	struct xfs_da_args	*args)
+{
+	struct xfs_bmbt_irec	map;
+	struct xfs_iext_cursor	icur;
+	struct xfs_inode	*dp = args->dp;
+	struct xfs_mount	*mp = dp->i_mount;
+	struct xfs_buf		*bp = NULL;
+	struct xfs_ifork	*ifp = XFS_IFORK_PTR(dp, XFS_DATA_FORK);
+	struct xfs_da_geometry	*geo = mp->m_dir_geo;
+	xfs_dir2_off_t		dirboff;
+	xfs_dablk_t		dabno = 0;
+	int			error = 0;
+
+	/* Read extent map. */
+	if (!(ifp->if_flags & XFS_IFEXTENTS)) {
+		error = -libxfs_iread_extents(NULL, dp, XFS_DATA_FORK);
+		if (error)
+			return error;
+	}
+
+	while (dabno < geo->leafblk) {
+		unsigned int	offset;
+		unsigned int	length;
+
+		/* Find mapping for leaf block. */
+		if (!xfs_iext_lookup_extent(dp, ifp, dabno, &icur, &map))
+			break;
+		if (map.br_startoff >= geo->leafblk)
+			break;
+		libxfs_trim_extent(&map, dabno, geo->leafblk - dabno);
+
+		/* Read the directory block of that first mapping. */
+		error = xfs_dir3_data_read(NULL, dp, map.br_startoff, 0, &bp);
+		if (error)
+			break;
+
+		dirboff = xfs_dir2_da_to_byte(geo, map.br_startoff);
+		for (offset = geo->data_entry_offset; offset < geo->blksize;) {
+			struct xfs_dir2_data_entry	*dep;
+			struct xfs_dir2_data_unused	*dup;
+			uint8_t				filetype;
+
+			dup = bp->b_addr + offset;
+			dep = bp->b_addr + offset;
+
+			if (be16_to_cpu(dup->freetag) ==
+			    XFS_DIR2_DATA_FREE_TAG) {
+				/* Skip unused entry */
+				length = be16_to_cpu(dup->length);
+				offset += length;
+				continue;
+			}
+
+			offset += libxfs_dir2_data_entsize(mp, dep->namelen);
+			filetype = libxfs_dir2_data_get_ftype(mp, dep);
+
+			dir_emit(mp, xfs_dir2_byte_to_dataptr(dirboff + offset),
+					(char *)dep->name, dep->namelen,
+					be64_to_cpu(dep->inumber), filetype);
+		}
+
+		dabno += XFS_DADDR_TO_FSB(mp, bp->b_length);
+		libxfs_buf_relse(bp);
+		bp = NULL;
+	}
+
+	if (bp)
+		libxfs_buf_relse(bp);
+
+	return error;
+}
+
+/* Read the directory, display contents. */
+int
+listdir(
+	struct xfs_inode	*dp)
+{
+	struct xfs_da_args	args = {
+		.dp		= dp,
+		.geo		= dp->i_mount->m_dir_geo,
+	};
+	int			error;
+	int			isblock;
+
+	if (dp->i_df.if_format == XFS_DINODE_FMT_LOCAL)
+		return list_sfdir(&args);
+
+	error = -libxfs_dir2_isblock(&args, &isblock);
+	if (error)
+		return error;
+
+	if (isblock)
+		return list_blockdir(&args);
+	return list_leafdir(&args);
+}
+
+/* List the inode number of the currently selected inode. */
+static int
+inum_cur(void)
+{
+	if (iocur_top->typ != &typtab[TYP_INODE])
+		return ENOENT;
+
+	dbprintf("%llu\n", iocur_top->ino);
+	return 0;
+}
+
+/* If the io cursor points to a directory, list its contents. */
+static int
+ls_cur(
+	char			*tag)
+{
+	struct xfs_inode	*dp;
+	int			error = 0;
+
+	if (iocur_top->typ != &typtab[TYP_INODE] ||
+	    !S_ISDIR(iocur_top->mode))
+		return ENOTDIR;
+
+	error = -libxfs_iget(mp, NULL, iocur_top->ino, 0, &dp);
+	if (error)
+		return error;
+
+	if (!S_ISDIR(VFS_I(dp)->i_mode)) {
+		error = ENOTDIR;
+		goto rele;
+	}
+
+	/* List the contents of a directory. */
+	if (tag)
+		dbprintf(_("%s:\n"), tag);
+
+	error = listdir(dp);
+	if (error)
+		goto rele;
+
+rele:
+	libxfs_irele(dp);
+	return error;
+}
+
+static void
+ls_help(void)
+{
+	dbprintf(_(
+"\n"
+" List the contents of the currently selected directory inode.\n"
+"\n"
+" Options:\n"
+"   -i -- Resolve the given paths to their corresponding inode numbers.\n"
+"         If no paths are given, display the current inode number.\n"
+"\n"
+" Directory contents will be listed in the format:\n"
+" dir_cookie	inode_number	type	hash	name_length	name\n"
+	));
+}
+
+static int
+ls_f(
+	int			argc,
+	char			**argv)
+{
+	bool			inum_only = false;
+	int			c;
+	int			error = 0;
+
+	while ((c = getopt(argc, argv, "i")) != -1) {
+		switch (c) {
+		case 'i':
+			inum_only = true;
+			break;
+		default:
+			ls_help();
+			return 0;
+		}
+	}
+
+	if (optind == argc) {
+		if (inum_only)
+			error = inum_cur();
+		else
+			error = ls_cur(NULL);
+		if (error) {
+			dbprintf("%s\n", strerror(error));
+			exitcode = 1;
+		}
+
+		return 0;
+	}
+
+	for (c = optind; c < argc; c++) {
+		push_cur();
+
+		error = path_walk(argv[c]);
+		if (error)
+			goto err_cur;
+
+		if (inum_only)
+			error = inum_cur();
+		else
+			error = ls_cur(argv[c]);
+		if (error)
+			goto err_cur;
+
+		pop_cur();
+	}
+
+	return 0;
+err_cur:
+	pop_cur();
+	if (error) {
+		dbprintf("%s: %s\n", argv[c], strerror(error));
+		exitcode = 1;
+	}
+	return 0;
+}
+
+static struct cmdinfo ls_cmd = {
+	.name		= "ls",
+	.altname	= "l",
+	.cfunc		= ls_f,
+	.argmin		= 0,
+	.argmax		= -1,
+	.canpush	= 0,
+	.args		= "[-i] [paths...]",
+	.help		= ls_help,
+};
+
 void
 namei_init(void)
 {
 	path_cmd.oneline = _("navigate to an inode by path");
 	add_command(&path_cmd);
+
+	ls_cmd.oneline = _("list directory contents");
+	add_command(&ls_cmd);
 }
diff --git a/libxfs/libxfs_api_defs.h b/libxfs/libxfs_api_defs.h
index 9492955d84f9..9a00ce6609b3 100644
--- a/libxfs/libxfs_api_defs.h
+++ b/libxfs/libxfs_api_defs.h
@@ -190,6 +190,7 @@
 #define xfs_trans_resv_calc		libxfs_trans_resv_calc
 #define xfs_trans_roll_inode		libxfs_trans_roll_inode
 #define xfs_trans_roll			libxfs_trans_roll
+#define xfs_trim_extent			libxfs_trim_extent
 
 #define xfs_verify_agbno		libxfs_verify_agbno
 #define xfs_verify_agino		libxfs_verify_agino
diff --git a/man/man8/xfs_db.8 b/man/man8/xfs_db.8
index 4df265ecd59e..587274957de3 100644
--- a/man/man8/xfs_db.8
+++ b/man/man8/xfs_db.8
@@ -806,6 +806,22 @@ This makes it easier to find discrepancies in the reservation calculations
 between xfsprogs and the kernel, which will help when diagnosing minimum
 log size calculation errors.
 .TP
+.BI "ls [\-i] [" paths "]..."
+List the contents of a directory.
+If a path resolves to a directory, the directory will be listed.
+If no paths are supplied and the IO cursor points at a directory inode,
+the contents of that directory will be listed.
+
+The output format is:
+directory cookie, inode number, file type, hash, name length, name.
+.RS 1.0i
+.TP 0.4i
+.B \-i
+Resolve each of the given paths to an inode number and print that number.
+If no paths are given and the IO cursor points to an inode, print the inode
+number.
+.RE
+.TP
 .BI "metadump [\-egow] " filename
 Dumps metadata to a file. See
 .BR xfs_metadump (8)


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH 2/2] xfs_db: add an ls command
  2020-11-16 17:35 [PATCH v2 0/2] xfs_db: add minimal directory navigation Darrick J. Wong
@ 2020-11-16 17:35 ` Darrick J. Wong
  0 siblings, 0 replies; 15+ messages in thread
From: Darrick J. Wong @ 2020-11-16 17:35 UTC (permalink / raw)
  To: sandeen, darrick.wong; +Cc: linux-xfs

From: Darrick J. Wong <darrick.wong@oracle.com>

Add to xfs_db the ability to list a directory.

Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
---
 db/namei.c               |  389 ++++++++++++++++++++++++++++++++++++++++++++++
 libxfs/libxfs_api_defs.h |    1 
 man/man8/xfs_db.8        |   16 ++
 3 files changed, 406 insertions(+)


diff --git a/db/namei.c b/db/namei.c
index 13ae7f7d116b..4b467ff8f1f5 100644
--- a/db/namei.c
+++ b/db/namei.c
@@ -215,9 +215,398 @@ static struct cmdinfo path_cmd = {
 	.help		= path_help,
 };
 
+/* List a directory's entries. */
+
+static const char *filetype_strings[XFS_DIR3_FT_MAX] = {
+	[XFS_DIR3_FT_UNKNOWN]	= "unknown",
+	[XFS_DIR3_FT_REG_FILE]	= "regular",
+	[XFS_DIR3_FT_DIR]	= "directory",
+	[XFS_DIR3_FT_CHRDEV]	= "chardev",
+	[XFS_DIR3_FT_BLKDEV]	= "blkdev",
+	[XFS_DIR3_FT_FIFO]	= "fifo",
+	[XFS_DIR3_FT_SOCK]	= "socket",
+	[XFS_DIR3_FT_SYMLINK]	= "symlink",
+	[XFS_DIR3_FT_WHT]	= "whiteout",
+};
+
+static const char *
+get_dstr(
+	struct xfs_mount	*mp,
+	uint8_t			filetype)
+{
+	if (!xfs_sb_version_hasftype(&mp->m_sb))
+		return filetype_strings[XFS_DIR3_FT_UNKNOWN];
+
+	if (filetype >= XFS_DIR3_FT_MAX)
+		return filetype_strings[XFS_DIR3_FT_UNKNOWN];
+
+	return filetype_strings[filetype];
+}
+
+static void
+dir_emit(
+	struct xfs_mount	*mp,
+	xfs_dir2_dataptr_t	off,
+	char			*name,
+	ssize_t			namelen,
+	xfs_ino_t		ino,
+	uint8_t			dtype)
+{
+	char			*display_name;
+	struct xfs_name		xname = { .name = name };
+	const char		*dstr = get_dstr(mp, dtype);
+	xfs_dahash_t		hash;
+	bool			good;
+
+	if (namelen < 0) {
+		/* Negative length means that name is null-terminated. */
+		display_name = name;
+		xname.len = strlen(name);
+		good = true;
+	} else {
+		/*
+		 * Otherwise, name came from a directory entry, so we have to
+		 * copy the string to a buffer so that we can add the null
+		 * terminator.
+		 */
+		display_name = malloc(namelen + 1);
+		memcpy(display_name, name, namelen);
+		display_name[namelen] = 0;
+		xname.len = namelen;
+		good = libxfs_dir2_namecheck(name, namelen);
+	}
+	hash = libxfs_dir2_hashname(mp, &xname);
+
+	dbprintf("%-10u %-18llu %-14s 0x%08llx %3d %s %s\n", off & 0xFFFFFFFF,
+			ino, dstr, hash, xname.len,
+			display_name, good ? _("(good)") : _("(corrupt)"));
+
+	if (display_name != name)
+		free(display_name);
+}
+
+static int
+list_sfdir(
+	struct xfs_da_args		*args)
+{
+	struct xfs_inode		*dp = args->dp;
+	struct xfs_mount		*mp = dp->i_mount;
+	struct xfs_da_geometry		*geo = args->geo;
+	struct xfs_dir2_sf_entry	*sfep;
+	struct xfs_dir2_sf_hdr		*sfp;
+	xfs_ino_t			ino;
+	xfs_dir2_dataptr_t		off;
+	unsigned int			i;
+	uint8_t				filetype;
+
+	sfp = (struct xfs_dir2_sf_hdr *)dp->i_df.if_u1.if_data;
+
+	/* . and .. entries */
+	off = xfs_dir2_db_off_to_dataptr(geo, geo->datablk,
+			geo->data_entry_offset);
+	dir_emit(args->dp->i_mount, off, ".", -1, dp->i_ino, XFS_DIR3_FT_DIR);
+
+	ino = libxfs_dir2_sf_get_parent_ino(sfp);
+	off = xfs_dir2_db_off_to_dataptr(geo, geo->datablk,
+			geo->data_entry_offset +
+			libxfs_dir2_data_entsize(mp, sizeof(".") - 1));
+	dir_emit(args->dp->i_mount, off, "..", -1, ino, XFS_DIR3_FT_DIR);
+
+	/* Walk everything else. */
+	sfep = xfs_dir2_sf_firstentry(sfp);
+	for (i = 0; i < sfp->count; i++) {
+		ino = libxfs_dir2_sf_get_ino(mp, sfp, sfep);
+		filetype = libxfs_dir2_sf_get_ftype(mp, sfep);
+		off = xfs_dir2_db_off_to_dataptr(geo, geo->datablk,
+				xfs_dir2_sf_get_offset(sfep));
+
+		dir_emit(args->dp->i_mount, off, (char *)sfep->name,
+				sfep->namelen, ino, filetype);
+		sfep = libxfs_dir2_sf_nextentry(mp, sfp, sfep);
+	}
+
+	return 0;
+}
+
+/* List entries in block format directory. */
+static int
+list_blockdir(
+	struct xfs_da_args	*args)
+{
+	struct xfs_inode	*dp = args->dp;
+	struct xfs_mount	*mp = dp->i_mount;
+	struct xfs_buf		*bp;
+	struct xfs_da_geometry	*geo = mp->m_dir_geo;
+	xfs_dir2_dataptr_t	diroff;
+	unsigned int		offset;
+	unsigned int		end;
+	int			error;
+
+	error = xfs_dir3_block_read(NULL, dp, &bp);
+	if (error)
+		return error;
+
+	end = xfs_dir3_data_end_offset(geo, bp->b_addr);
+	for (offset = geo->data_entry_offset; offset < end;) {
+		struct xfs_dir2_data_unused	*dup = bp->b_addr + offset;
+		struct xfs_dir2_data_entry	*dep = bp->b_addr + offset;
+		uint8_t				filetype;
+
+		if (be16_to_cpu(dup->freetag) == XFS_DIR2_DATA_FREE_TAG) {
+			/* Unused entry */
+			offset += be16_to_cpu(dup->length);
+			continue;
+		}
+
+		/* Real entry */
+		diroff = xfs_dir2_db_off_to_dataptr(geo, geo->datablk, offset);
+		offset += libxfs_dir2_data_entsize(mp, dep->namelen);
+		filetype = libxfs_dir2_data_get_ftype(dp->i_mount, dep);
+		dir_emit(mp, diroff, (char *)dep->name, dep->namelen,
+				be64_to_cpu(dep->inumber), filetype);
+	}
+
+	libxfs_trans_brelse(args->trans, bp);
+	return error;
+}
+
+/* List entries in leaf format directory. */
+static int
+list_leafdir(
+	struct xfs_da_args	*args)
+{
+	struct xfs_bmbt_irec	map;
+	struct xfs_iext_cursor	icur;
+	struct xfs_inode	*dp = args->dp;
+	struct xfs_mount	*mp = dp->i_mount;
+	struct xfs_buf		*bp = NULL;
+	struct xfs_ifork	*ifp = XFS_IFORK_PTR(dp, XFS_DATA_FORK);
+	struct xfs_da_geometry	*geo = mp->m_dir_geo;
+	xfs_dir2_off_t		dirboff;
+	xfs_dablk_t		dabno = 0;
+	int			error = 0;
+
+	/* Read extent map. */
+	if (!(ifp->if_flags & XFS_IFEXTENTS)) {
+		error = -libxfs_iread_extents(NULL, dp, XFS_DATA_FORK);
+		if (error)
+			return error;
+	}
+
+	while (dabno < geo->leafblk) {
+		unsigned int	offset;
+		unsigned int	length;
+
+		/* Find mapping for leaf block. */
+		if (!xfs_iext_lookup_extent(dp, ifp, dabno, &icur, &map))
+			break;
+		if (map.br_startoff >= geo->leafblk)
+			break;
+		libxfs_trim_extent(&map, dabno, geo->leafblk - dabno);
+
+		/* Read the directory block of that first mapping. */
+		error = xfs_dir3_data_read(NULL, dp, map.br_startoff, 0, &bp);
+		if (error)
+			break;
+
+		dirboff = xfs_dir2_da_to_byte(geo, map.br_startoff);
+		for (offset = geo->data_entry_offset; offset < geo->blksize;) {
+			struct xfs_dir2_data_entry	*dep;
+			struct xfs_dir2_data_unused	*dup;
+			uint8_t				filetype;
+
+			dup = bp->b_addr + offset;
+			dep = bp->b_addr + offset;
+
+			if (be16_to_cpu(dup->freetag) ==
+			    XFS_DIR2_DATA_FREE_TAG) {
+				/* Skip unused entry */
+				length = be16_to_cpu(dup->length);
+				offset += length;
+				continue;
+			}
+
+			offset += libxfs_dir2_data_entsize(mp, dep->namelen);
+			filetype = libxfs_dir2_data_get_ftype(mp, dep);
+
+			dir_emit(mp, xfs_dir2_byte_to_dataptr(dirboff + offset),
+					(char *)dep->name, dep->namelen,
+					be64_to_cpu(dep->inumber), filetype);
+		}
+
+		dabno += XFS_DADDR_TO_FSB(mp, bp->b_length);
+		libxfs_buf_relse(bp);
+		bp = NULL;
+	}
+
+	if (bp)
+		libxfs_buf_relse(bp);
+
+	return error;
+}
+
+/* Read the directory, display contents. */
+int
+listdir(
+	struct xfs_inode	*dp)
+{
+	struct xfs_da_args	args = {
+		.dp		= dp,
+		.geo		= dp->i_mount->m_dir_geo,
+	};
+	int			error;
+	int			isblock;
+
+	if (dp->i_df.if_format == XFS_DINODE_FMT_LOCAL)
+		return list_sfdir(&args);
+
+	error = -libxfs_dir2_isblock(&args, &isblock);
+	if (error)
+		return error;
+
+	if (isblock)
+		return list_blockdir(&args);
+	return list_leafdir(&args);
+}
+
+/* List the inode number of the currently selected inode. */
+static int
+inum_cur(void)
+{
+	if (iocur_top->typ != &typtab[TYP_INODE])
+		return ENOENT;
+
+	dbprintf("%llu\n", iocur_top->ino);
+	return 0;
+}
+
+/* If the io cursor points to a directory, list its contents. */
+static int
+ls_cur(
+	char			*tag)
+{
+	struct xfs_inode	*dp;
+	int			error = 0;
+
+	if (iocur_top->typ != &typtab[TYP_INODE] ||
+	    !S_ISDIR(iocur_top->mode))
+		return ENOTDIR;
+
+	error = -libxfs_iget(mp, NULL, iocur_top->ino, 0, &dp);
+	if (error)
+		return error;
+
+	if (!S_ISDIR(VFS_I(dp)->i_mode)) {
+		error = ENOTDIR;
+		goto rele;
+	}
+
+	/* List the contents of a directory. */
+	if (tag)
+		dbprintf(_("%s:\n"), tag);
+
+	error = listdir(dp);
+	if (error)
+		goto rele;
+
+rele:
+	libxfs_irele(dp);
+	return error;
+}
+
+static void
+ls_help(void)
+{
+	dbprintf(_(
+"\n"
+" List the contents of the currently selected directory inode.\n"
+"\n"
+" Options:\n"
+"   -i -- Resolve the given paths to their corresponding inode numbers.\n"
+"         If no paths are given, display the current inode number.\n"
+"\n"
+" Directory contents will be listed in the format:\n"
+" dir_cookie	inode_number	type	hash	name_length	name\n"
+	));
+}
+
+static int
+ls_f(
+	int			argc,
+	char			**argv)
+{
+	bool			inum_only = false;
+	int			c;
+	int			error = 0;
+
+	while ((c = getopt(argc, argv, "i")) != -1) {
+		switch (c) {
+		case 'i':
+			inum_only = true;
+			break;
+		default:
+			ls_help();
+			return 0;
+		}
+	}
+
+	if (optind == argc) {
+		if (inum_only)
+			error = inum_cur();
+		else
+			error = ls_cur(NULL);
+		if (error) {
+			dbprintf("%s\n", strerror(error));
+			exitcode = 1;
+		}
+
+		return 0;
+	}
+
+	for (c = optind; c < argc; c++) {
+		push_cur();
+
+		error = path_walk(argv[c]);
+		if (error)
+			goto err_cur;
+
+		if (inum_only)
+			error = inum_cur();
+		else
+			error = ls_cur(argv[c]);
+		if (error)
+			goto err_cur;
+
+		pop_cur();
+	}
+
+	return 0;
+err_cur:
+	pop_cur();
+	if (error) {
+		dbprintf("%s: %s\n", argv[c], strerror(error));
+		exitcode = 1;
+	}
+	return 0;
+}
+
+static struct cmdinfo ls_cmd = {
+	.name		= "ls",
+	.altname	= "l",
+	.cfunc		= ls_f,
+	.argmin		= 0,
+	.argmax		= -1,
+	.canpush	= 0,
+	.args		= "[-i] [paths...]",
+	.help		= ls_help,
+};
+
 void
 namei_init(void)
 {
 	path_cmd.oneline = _("navigate to an inode by path");
 	add_command(&path_cmd);
+
+	ls_cmd.oneline = _("list directory contents");
+	add_command(&ls_cmd);
 }
diff --git a/libxfs/libxfs_api_defs.h b/libxfs/libxfs_api_defs.h
index e7e42e93a07e..7029d0e7daf7 100644
--- a/libxfs/libxfs_api_defs.h
+++ b/libxfs/libxfs_api_defs.h
@@ -187,6 +187,7 @@
 #define xfs_trans_resv_calc		libxfs_trans_resv_calc
 #define xfs_trans_roll_inode		libxfs_trans_roll_inode
 #define xfs_trans_roll			libxfs_trans_roll
+#define xfs_trim_extent			libxfs_trim_extent
 
 #define xfs_verify_agbno		libxfs_verify_agbno
 #define xfs_verify_agino		libxfs_verify_agino
diff --git a/man/man8/xfs_db.8 b/man/man8/xfs_db.8
index d67e108a706a..463e6f05ad82 100644
--- a/man/man8/xfs_db.8
+++ b/man/man8/xfs_db.8
@@ -806,6 +806,22 @@ This makes it easier to find discrepancies in the reservation calculations
 between xfsprogs and the kernel, which will help when diagnosing minimum
 log size calculation errors.
 .TP
+.BI "ls [\-i] [" paths "]..."
+List the contents of a directory.
+If a path resolves to a directory, the directory will be listed.
+If no paths are supplied and the IO cursor points at a directory inode,
+the contents of that directory will be listed.
+
+The output format is:
+directory cookie, inode number, file type, hash, name length, name.
+.RS 1.0i
+.TP 0.4i
+.B \-i
+Resolve each of the given paths to an inode number and print that number.
+If no paths are given and the IO cursor points to an inode, print the inode
+number.
+.RE
+.TP
 .BI "metadump [\-egow] " filename
 Dumps metadata to a file. See
 .BR xfs_metadump (8)


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* Re: [PATCH 2/2] xfs_db: add an ls command
  2020-10-28 23:20       ` Dave Chinner
@ 2020-10-29 18:41         ` Darrick J. Wong
  0 siblings, 0 replies; 15+ messages in thread
From: Darrick J. Wong @ 2020-10-29 18:41 UTC (permalink / raw)
  To: Dave Chinner; +Cc: sandeen, linux-xfs

On Thu, Oct 29, 2020 at 10:20:56AM +1100, Dave Chinner wrote:
> On Wed, Oct 28, 2020 at 03:50:46PM -0700, Darrick J. Wong wrote:
> > On Wed, Oct 28, 2020 at 12:27:03PM +1100, Dave Chinner wrote:
> > > On Mon, Oct 26, 2020 at 04:32:41PM -0700, Darrick J. Wong wrote:
> > > > +	hash = libxfs_dir2_hashname(mp, &xname);
> > > > +
> > > > +	dbprintf("%-18llu %-14s 0x%08llx %3d %s", ino, dstr, hash, xname.len,
> > > > +			display_name);
> > > > +	if (!good)
> > > > +		dbprintf(_(" (corrupt)"));
> > > > +	dbprintf("\n");
> > > 
> > > Can we get this to emit the directory offset of the entry as well?
> > 
> > Er... I think so.  Do you want to report the u32 value that gets loaded
> > in ctx->pos?  Or the actual byte offset within the directory?
> 
> I'd suggest that it should be the same as the telldir cookie that is
> returned by the kernel for the given entry.

Done.

> > > > +	} else if (direct || !S_ISDIR(VFS_I(dp)->i_mode)) {
> > > > +		/* List the directory entry associated with a single file. */
> > > > +		char		inum[32];
> > > > +
> > > > +		if (!tag) {
> > > > +			snprintf(inum, sizeof(inum), "<%llu>",
> > > > +					(unsigned long long)iocur_top->ino);
> > > > +			tag = inum;
> > > > +		} else {
> > > > +			char	*p = strrchr(tag, '/');
> > > > +
> > > > +			if (p)
> > > > +				tag = p + 1;
> > > > +		}
> > > > +
> > > > +		dir_emit(mp, tag, -1, iocur_top->ino,
> > > > +				libxfs_mode_to_ftype(VFS_I(dp)->i_mode));
> > > 
> > > I'm not sure what this is supposed to do - we turn the current inode
> > > if it's not a directory into a -directory entry- without actually
> > > know it's name? And we can pass in an inode that isn't a directory
> > > and do the same? This doesn't make a huge amount of sense to me - it
> > > tries to display the inode number as a dirent?
> > 
> > I added this (somewhat confusing) ability so that fstests could resolve
> > a path to an inode number without having to dig any farther into the
> > disk format.
> > 
> > IOWs, you can do:
> > 
> > ino=$(_scratch_xfs_db -c 'ls -d /usr/bin/bash')
> >
> > to get the inode number directly.  Without this, you'd have to do
> > something horrible like this...
> 
> You mean:
> 
> $ ls -i /bin/bash | cut -f 1 -d " "
> 175492
> $
> 
> i.e. if you want to provide the inode number rather than just the
> path, then let's use the same names as a real ls  implementation :)

Done.  The option is now -i instead of -d.

> > To map a path to an inode number.  I thought it made a lot more sense to
> > do that in C (even if it makes the xfs_db CLI a little weird) than
> > implement a bunch of string parsing after the fact.
> 
> I also suspect it would be simpler to separate it out into two
> functions rather than the way it is implemented now....

Done.

--D

> > Maybe I should just simplify it to "display the inode number of whatever
> > the path resolves to" instead of constructing an artificial directory
> > entry.
> 
> *nod*
> 
> Cheers,
> 
> Dave.
> -- 
> Dave Chinner
> david@fromorbit.com

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH 2/2] xfs_db: add an ls command
  2020-10-28 22:50     ` Darrick J. Wong
@ 2020-10-28 23:20       ` Dave Chinner
  2020-10-29 18:41         ` Darrick J. Wong
  0 siblings, 1 reply; 15+ messages in thread
From: Dave Chinner @ 2020-10-28 23:20 UTC (permalink / raw)
  To: Darrick J. Wong; +Cc: sandeen, linux-xfs

On Wed, Oct 28, 2020 at 03:50:46PM -0700, Darrick J. Wong wrote:
> On Wed, Oct 28, 2020 at 12:27:03PM +1100, Dave Chinner wrote:
> > On Mon, Oct 26, 2020 at 04:32:41PM -0700, Darrick J. Wong wrote:
> > > +	hash = libxfs_dir2_hashname(mp, &xname);
> > > +
> > > +	dbprintf("%-18llu %-14s 0x%08llx %3d %s", ino, dstr, hash, xname.len,
> > > +			display_name);
> > > +	if (!good)
> > > +		dbprintf(_(" (corrupt)"));
> > > +	dbprintf("\n");
> > 
> > Can we get this to emit the directory offset of the entry as well?
> 
> Er... I think so.  Do you want to report the u32 value that gets loaded
> in ctx->pos?  Or the actual byte offset within the directory?

I'd suggest that it should be the same as the telldir cookie that is
returned by the kernel for the given entry.

> > > +	} else if (direct || !S_ISDIR(VFS_I(dp)->i_mode)) {
> > > +		/* List the directory entry associated with a single file. */
> > > +		char		inum[32];
> > > +
> > > +		if (!tag) {
> > > +			snprintf(inum, sizeof(inum), "<%llu>",
> > > +					(unsigned long long)iocur_top->ino);
> > > +			tag = inum;
> > > +		} else {
> > > +			char	*p = strrchr(tag, '/');
> > > +
> > > +			if (p)
> > > +				tag = p + 1;
> > > +		}
> > > +
> > > +		dir_emit(mp, tag, -1, iocur_top->ino,
> > > +				libxfs_mode_to_ftype(VFS_I(dp)->i_mode));
> > 
> > I'm not sure what this is supposed to do - we turn the current inode
> > if it's not a directory into a -directory entry- without actually
> > know it's name? And we can pass in an inode that isn't a directory
> > and do the same? This doesn't make a huge amount of sense to me - it
> > tries to display the inode number as a dirent?
> 
> I added this (somewhat confusing) ability so that fstests could resolve
> a path to an inode number without having to dig any farther into the
> disk format.
> 
> IOWs, you can do:
> 
> ino=$(_scratch_xfs_db -c 'ls -d /usr/bin/bash')
>
> to get the inode number directly.  Without this, you'd have to do
> something horrible like this...

You mean:

$ ls -i /bin/bash | cut -f 1 -d " "
175492
$

i.e. if you want to provide the inode number rather than just the
path, then let's use the same names as a real ls  implementation :)

> To map a path to an inode number.  I thought it made a lot more sense to
> do that in C (even if it makes the xfs_db CLI a little weird) than
> implement a bunch of string parsing after the fact.

I also suspect it would be simpler to separate it out into two
functions rather than the way it is implemented now....

> Maybe I should just simplify it to "display the inode number of whatever
> the path resolves to" instead of constructing an artificial directory
> entry.

*nod*

Cheers,

Dave.
-- 
Dave Chinner
david@fromorbit.com

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH 2/2] xfs_db: add an ls command
  2020-10-28  1:27   ` Dave Chinner
@ 2020-10-28 22:50     ` Darrick J. Wong
  2020-10-28 23:20       ` Dave Chinner
  0 siblings, 1 reply; 15+ messages in thread
From: Darrick J. Wong @ 2020-10-28 22:50 UTC (permalink / raw)
  To: Dave Chinner; +Cc: sandeen, linux-xfs

On Wed, Oct 28, 2020 at 12:27:03PM +1100, Dave Chinner wrote:
> On Mon, Oct 26, 2020 at 04:32:41PM -0700, Darrick J. Wong wrote:
> > From: Darrick J. Wong <darrick.wong@oracle.com>
> > 
> > Add to xfs_db the ability to list a directory.
> > 
> > Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
> > ---
> >  db/namei.c               |  380 ++++++++++++++++++++++++++++++++++++++++++++++
> >  libxfs/libxfs_api_defs.h |    1 
> >  man/man8/xfs_db.8        |   14 ++
> >  3 files changed, 395 insertions(+)
> > 
> > 
> > diff --git a/db/namei.c b/db/namei.c
> > index 3c9889d62338..b2c036e6777a 100644
> > --- a/db/namei.c
> > +++ b/db/namei.c
> > @@ -221,8 +221,388 @@ static const cmdinfo_t path_cmd = {
> >  	.help		= path_help,
> >  };
> >  
> > +/* List a directory's entries. */
> > +
> > +static const char *filetype_strings[XFS_DIR3_FT_MAX] = {
> > +	[XFS_DIR3_FT_UNKNOWN]	= N_("unknown"),
> > +	[XFS_DIR3_FT_REG_FILE]	= N_("regular"),
> > +	[XFS_DIR3_FT_DIR]	= N_("directory"),
> > +	[XFS_DIR3_FT_CHRDEV]	= N_("chardev"),
> > +	[XFS_DIR3_FT_BLKDEV]	= N_("blkdev"),
> > +	[XFS_DIR3_FT_FIFO]	= N_("fifo"),
> > +	[XFS_DIR3_FT_SOCK]	= N_("socket"),
> > +	[XFS_DIR3_FT_SYMLINK]	= N_("symlink"),
> > +	[XFS_DIR3_FT_WHT]	= N_("whiteout"),
> > +};
> 
> What does N_() do that is different to _()?

LOL, it doesn't do anything at all!

Sigh... WTF was the point of commit 97294b227aefd?

> > +static const char *
> > +get_dstr(
> > +	struct xfs_mount	*mp,
> > +	uint8_t			filetype)
> > +{
> > +	if (!xfs_sb_version_hasftype(&mp->m_sb))
> > +		return filetype_strings[XFS_DIR3_FT_UNKNOWN];
> > +
> > +	if (filetype >= XFS_DIR3_FT_MAX)
> > +		return filetype_strings[XFS_DIR3_FT_UNKNOWN];
> > +
> > +	return filetype_strings[filetype];
> > +}
> > +
> > +static void
> > +dir_emit(
> > +	struct xfs_mount	*mp,
> > +	char			*name,
> > +	ssize_t			namelen,
> > +	xfs_ino_t		ino,
> > +	uint8_t			dtype)
> > +{
> > +	char			*display_name;
> > +	struct xfs_name		xname = { .name = name };
> > +	const char		*dstr = get_dstr(mp, dtype);
> > +	xfs_dahash_t		hash;
> > +	bool			good;
> > +
> > +	if (namelen < 0) {
> > +		/* Negative length means that name is null-terminated. */
> > +		display_name = name;
> > +		xname.len = strlen(name);
> > +		good = true;
> > +	} else {
> > +		/*
> > +		 * Otherwise, name came from a directory entry, so we have to
> > +		 * copy the string to a buffer so that we can add the null
> > +		 * terminator.
> > +		 */
> > +		display_name = malloc(namelen + 1);
> > +		memcpy(display_name, name, namelen);
> > +		display_name[namelen] = 0;
> > +		xname.len = namelen;
> > +		good = libxfs_dir2_namecheck(name, namelen);
> > +	}
> > +	hash = libxfs_dir2_hashname(mp, &xname);
> > +
> > +	dbprintf("%-18llu %-14s 0x%08llx %3d %s", ino, dstr, hash, xname.len,
> > +			display_name);
> > +	if (!good)
> > +		dbprintf(_(" (corrupt)"));
> > +	dbprintf("\n");
> 
> Can we get this to emit the directory offset of the entry as well?

Er... I think so.  Do you want to report the u32 value that gets loaded
in ctx->pos?  Or the actual byte offset within the directory?

> Also, can this be done as a single dbprintf call like this?
> 
> 	dbprintf(%-18llu %-14s 0x%08llx %3d %s %s\n",
> 		ino, dstr, hash, xname.len, display_name,
> 		good ? _("(good)") : _("(corrupt)"));
> 
> (there will be lots of output on big directories....)

Ok.

> > +static int
> > +list_sfdir(
> > +	struct xfs_da_args		*args)
> > +{
> > +	struct xfs_inode		*dp = args->dp;
> > +	struct xfs_mount		*mp = dp->i_mount;
> > +	struct xfs_dir2_sf_entry	*sfep;
> > +	struct xfs_dir2_sf_hdr		*sfp;
> > +	xfs_ino_t			ino;
> > +	unsigned int			i;
> > +	uint8_t				filetype;
> > +
> > +	sfp = (struct xfs_dir2_sf_hdr *)dp->i_df.if_u1.if_data;
> > +
> > +	/* . and .. entries */
> > +	dir_emit(args->dp->i_mount, ".", -1, dp->i_ino, XFS_DIR3_FT_DIR);
> > +
> > +	ino = libxfs_dir2_sf_get_parent_ino(sfp);
> > +	dir_emit(args->dp->i_mount, "..", -1, ino, XFS_DIR3_FT_DIR);
> > +
> > +	/* Walk everything else. */
> > +	sfep = xfs_dir2_sf_firstentry(sfp);
> > +	for (i = 0; i < sfp->count; i++) {
> > +		ino = libxfs_dir2_sf_get_ino(mp, sfp, sfep);
> > +		filetype = libxfs_dir2_sf_get_ftype(mp, sfep);
> > +
> > +		dir_emit(args->dp->i_mount, (char *)sfep->name, sfep->namelen,
> > +				ino, filetype);
> > +		sfep = libxfs_dir2_sf_nextentry(mp, sfp, sfep);
> > +	}
> > +
> > +	return 0;
> > +}
> 
> Hmmm - how much of the xfs_readdir() implementation from the kernel
> does this duplicate? It doesn't contain the seek cookie stuff, but
> otherwise it's almost identical, right?

Yep.  I think it also omits a fair amount of error handling since we'd
rather just keep going for as long as we can.

> [....]
> 
> > +/* If the io cursor points to a directory, list its contents. */
> > +static int
> > +ls_cur(
> > +	char			*tag,
> > +	bool			direct)
> 
> I find the name "direct" rather confusing here. according to
> the help below, it will be true when we want to "list the directory
> itself, not it's contents"....
> 
> 
> > +{
> > +	struct xfs_inode	*dp;
> > +	int			ret = 0;
> > +
> > +	if (iocur_top->typ != &typtab[TYP_INODE]) {
> > +		dbprintf(_("current object is not an inode.\n"));
> > +		return -1;
> > +	}
> > +
> > +	ret = -libxfs_iget(mp, NULL, iocur_top->ino, 0, &dp);
> > +	if (ret) {
> > +		dbprintf(_("failed to iget directory %llu, error %d\n"),
> > +				(unsigned long long)iocur_top->ino, ret);
> > +		return -1;
> > +	}
> > +
> > +	if (S_ISDIR(VFS_I(dp)->i_mode) && !direct) {
> > +		/* List the contents of a directory. */
> > +		if (tag)
> > +			dbprintf(_("%s:\n"), tag);
> > +
> > +		ret = listdir(dp);
> > +		if (ret) {
> > +			dbprintf(_("failed to list directory %llu: %s\n"),
> > +					(unsigned long long)iocur_top->ino,
> > +					strerror(ret));
> > +			ret = -1;
> > +			goto rele;
> > +		}
> > +	} else if (direct || !S_ISDIR(VFS_I(dp)->i_mode)) {
> > +		/* List the directory entry associated with a single file. */
> > +		char		inum[32];
> > +
> > +		if (!tag) {
> > +			snprintf(inum, sizeof(inum), "<%llu>",
> > +					(unsigned long long)iocur_top->ino);
> > +			tag = inum;
> > +		} else {
> > +			char	*p = strrchr(tag, '/');
> > +
> > +			if (p)
> > +				tag = p + 1;
> > +		}
> > +
> > +		dir_emit(mp, tag, -1, iocur_top->ino,
> > +				libxfs_mode_to_ftype(VFS_I(dp)->i_mode));
> 
> I'm not sure what this is supposed to do - we turn the current inode
> if it's not a directory into a -directory entry- without actually
> know it's name? And we can pass in an inode that isn't a directory
> and do the same? This doesn't make a huge amount of sense to me - it
> tries to display the inode number as a dirent?

I added this (somewhat confusing) ability so that fstests could resolve
a path to an inode number without having to dig any farther into the
disk format.

IOWs, you can do:

ino=$(_scratch_xfs_db -c 'ls -d /usr/bin/bash')

to get the inode number directly.  Without this, you'd have to do
something horrible like this...

ino=$(_scratch_xfs_db -c 'path /usr/bin/bash' -c 'print' -c 'stack' /dev/sda | \
	tr ',' ' ' | \
	awk '{if ($1 ~ /inumber/) {print $3; exit(0); } else if ($1 == "inode") {print $2; exit(0);}}')

To map a path to an inode number.  I thought it made a lot more sense to
do that in C (even if it makes the xfs_db CLI a little weird) than
implement a bunch of string parsing after the fact.

Maybe I should just simplify it to "display the inode number of whatever
the path resolves to" instead of constructing an artificial directory
entry.

> > +	} else {
> > +		dbprintf(_("current inode %llu is not a directory.\n"),
> > +				(unsigned long long)iocur_top->ino);
> > +		ret = -1;
> > +		goto rele;
> > +	}
> 
> I don't think we can get to this else branch. If we don't take the
> first branch (dir && !direct), the either we are not a dir or direct
> is set. The second branch will then be taken if we are not a dir or
> direct is set....

Yes, I /will/ do that.

--D

> Cheers,
> 
> Dave.
> -- 
> Dave Chinner
> david@fromorbit.com

^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH 2/2] xfs_db: add an ls command
  2020-10-26 23:32 ` [PATCH 2/2] xfs_db: add an ls command Darrick J. Wong
@ 2020-10-28  1:27   ` Dave Chinner
  2020-10-28 22:50     ` Darrick J. Wong
  0 siblings, 1 reply; 15+ messages in thread
From: Dave Chinner @ 2020-10-28  1:27 UTC (permalink / raw)
  To: Darrick J. Wong; +Cc: sandeen, linux-xfs

On Mon, Oct 26, 2020 at 04:32:41PM -0700, Darrick J. Wong wrote:
> From: Darrick J. Wong <darrick.wong@oracle.com>
> 
> Add to xfs_db the ability to list a directory.
> 
> Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
> ---
>  db/namei.c               |  380 ++++++++++++++++++++++++++++++++++++++++++++++
>  libxfs/libxfs_api_defs.h |    1 
>  man/man8/xfs_db.8        |   14 ++
>  3 files changed, 395 insertions(+)
> 
> 
> diff --git a/db/namei.c b/db/namei.c
> index 3c9889d62338..b2c036e6777a 100644
> --- a/db/namei.c
> +++ b/db/namei.c
> @@ -221,8 +221,388 @@ static const cmdinfo_t path_cmd = {
>  	.help		= path_help,
>  };
>  
> +/* List a directory's entries. */
> +
> +static const char *filetype_strings[XFS_DIR3_FT_MAX] = {
> +	[XFS_DIR3_FT_UNKNOWN]	= N_("unknown"),
> +	[XFS_DIR3_FT_REG_FILE]	= N_("regular"),
> +	[XFS_DIR3_FT_DIR]	= N_("directory"),
> +	[XFS_DIR3_FT_CHRDEV]	= N_("chardev"),
> +	[XFS_DIR3_FT_BLKDEV]	= N_("blkdev"),
> +	[XFS_DIR3_FT_FIFO]	= N_("fifo"),
> +	[XFS_DIR3_FT_SOCK]	= N_("socket"),
> +	[XFS_DIR3_FT_SYMLINK]	= N_("symlink"),
> +	[XFS_DIR3_FT_WHT]	= N_("whiteout"),
> +};

What does N_() do that is different to _()?

> +static const char *
> +get_dstr(
> +	struct xfs_mount	*mp,
> +	uint8_t			filetype)
> +{
> +	if (!xfs_sb_version_hasftype(&mp->m_sb))
> +		return filetype_strings[XFS_DIR3_FT_UNKNOWN];
> +
> +	if (filetype >= XFS_DIR3_FT_MAX)
> +		return filetype_strings[XFS_DIR3_FT_UNKNOWN];
> +
> +	return filetype_strings[filetype];
> +}
> +
> +static void
> +dir_emit(
> +	struct xfs_mount	*mp,
> +	char			*name,
> +	ssize_t			namelen,
> +	xfs_ino_t		ino,
> +	uint8_t			dtype)
> +{
> +	char			*display_name;
> +	struct xfs_name		xname = { .name = name };
> +	const char		*dstr = get_dstr(mp, dtype);
> +	xfs_dahash_t		hash;
> +	bool			good;
> +
> +	if (namelen < 0) {
> +		/* Negative length means that name is null-terminated. */
> +		display_name = name;
> +		xname.len = strlen(name);
> +		good = true;
> +	} else {
> +		/*
> +		 * Otherwise, name came from a directory entry, so we have to
> +		 * copy the string to a buffer so that we can add the null
> +		 * terminator.
> +		 */
> +		display_name = malloc(namelen + 1);
> +		memcpy(display_name, name, namelen);
> +		display_name[namelen] = 0;
> +		xname.len = namelen;
> +		good = libxfs_dir2_namecheck(name, namelen);
> +	}
> +	hash = libxfs_dir2_hashname(mp, &xname);
> +
> +	dbprintf("%-18llu %-14s 0x%08llx %3d %s", ino, dstr, hash, xname.len,
> +			display_name);
> +	if (!good)
> +		dbprintf(_(" (corrupt)"));
> +	dbprintf("\n");

Can we get this to emit the directory offset of the entry as well?
Also, can this be done as a single dbprintf call like this?

	dbprintf(%-18llu %-14s 0x%08llx %3d %s %s\n",
		ino, dstr, hash, xname.len, display_name,
		good ? _("(good)") : _("(corrupt)"));

(there will be lots of output on big directories....)

> +static int
> +list_sfdir(
> +	struct xfs_da_args		*args)
> +{
> +	struct xfs_inode		*dp = args->dp;
> +	struct xfs_mount		*mp = dp->i_mount;
> +	struct xfs_dir2_sf_entry	*sfep;
> +	struct xfs_dir2_sf_hdr		*sfp;
> +	xfs_ino_t			ino;
> +	unsigned int			i;
> +	uint8_t				filetype;
> +
> +	sfp = (struct xfs_dir2_sf_hdr *)dp->i_df.if_u1.if_data;
> +
> +	/* . and .. entries */
> +	dir_emit(args->dp->i_mount, ".", -1, dp->i_ino, XFS_DIR3_FT_DIR);
> +
> +	ino = libxfs_dir2_sf_get_parent_ino(sfp);
> +	dir_emit(args->dp->i_mount, "..", -1, ino, XFS_DIR3_FT_DIR);
> +
> +	/* Walk everything else. */
> +	sfep = xfs_dir2_sf_firstentry(sfp);
> +	for (i = 0; i < sfp->count; i++) {
> +		ino = libxfs_dir2_sf_get_ino(mp, sfp, sfep);
> +		filetype = libxfs_dir2_sf_get_ftype(mp, sfep);
> +
> +		dir_emit(args->dp->i_mount, (char *)sfep->name, sfep->namelen,
> +				ino, filetype);
> +		sfep = libxfs_dir2_sf_nextentry(mp, sfp, sfep);
> +	}
> +
> +	return 0;
> +}

Hmmm - how much of the xfs_readdir() implementation from the kernel
does this duplicate? It doesn't contain the seek cookie stuff, but
otherwise it's almost identical, right?

[....]

> +/* If the io cursor points to a directory, list its contents. */
> +static int
> +ls_cur(
> +	char			*tag,
> +	bool			direct)

I find the name "direct" rather confusing here. according to
the help below, it will be true when we want to "list the directory
itself, not it's contents"....


> +{
> +	struct xfs_inode	*dp;
> +	int			ret = 0;
> +
> +	if (iocur_top->typ != &typtab[TYP_INODE]) {
> +		dbprintf(_("current object is not an inode.\n"));
> +		return -1;
> +	}
> +
> +	ret = -libxfs_iget(mp, NULL, iocur_top->ino, 0, &dp);
> +	if (ret) {
> +		dbprintf(_("failed to iget directory %llu, error %d\n"),
> +				(unsigned long long)iocur_top->ino, ret);
> +		return -1;
> +	}
> +
> +	if (S_ISDIR(VFS_I(dp)->i_mode) && !direct) {
> +		/* List the contents of a directory. */
> +		if (tag)
> +			dbprintf(_("%s:\n"), tag);
> +
> +		ret = listdir(dp);
> +		if (ret) {
> +			dbprintf(_("failed to list directory %llu: %s\n"),
> +					(unsigned long long)iocur_top->ino,
> +					strerror(ret));
> +			ret = -1;
> +			goto rele;
> +		}
> +	} else if (direct || !S_ISDIR(VFS_I(dp)->i_mode)) {
> +		/* List the directory entry associated with a single file. */
> +		char		inum[32];
> +
> +		if (!tag) {
> +			snprintf(inum, sizeof(inum), "<%llu>",
> +					(unsigned long long)iocur_top->ino);
> +			tag = inum;
> +		} else {
> +			char	*p = strrchr(tag, '/');
> +
> +			if (p)
> +				tag = p + 1;
> +		}
> +
> +		dir_emit(mp, tag, -1, iocur_top->ino,
> +				libxfs_mode_to_ftype(VFS_I(dp)->i_mode));

I'm not sure what this is supposed to do - we turn the current inode
if it's not a directory into a -directory entry- without actually
know it's name? And we can pass in an inode that isn't a directory
and do the same? This doesn't make a huge amount of sense to me - it
tries to display the inode number as a dirent?

> +	} else {
> +		dbprintf(_("current inode %llu is not a directory.\n"),
> +				(unsigned long long)iocur_top->ino);
> +		ret = -1;
> +		goto rele;
> +	}

I don't think we can get to this else branch. If we don't take the
first branch (dir && !direct), the either we are not a dir or direct
is set. The second branch will then be taken if we are not a dir or
direct is set....

Cheers,

Dave.
-- 
Dave Chinner
david@fromorbit.com

^ permalink raw reply	[flat|nested] 15+ messages in thread

* [PATCH 2/2] xfs_db: add an ls command
  2020-10-26 23:32 [PATCH 0/2] xfs_db: add minimal directory navigation Darrick J. Wong
@ 2020-10-26 23:32 ` Darrick J. Wong
  2020-10-28  1:27   ` Dave Chinner
  0 siblings, 1 reply; 15+ messages in thread
From: Darrick J. Wong @ 2020-10-26 23:32 UTC (permalink / raw)
  To: sandeen, darrick.wong; +Cc: linux-xfs

From: Darrick J. Wong <darrick.wong@oracle.com>

Add to xfs_db the ability to list a directory.

Signed-off-by: Darrick J. Wong <darrick.wong@oracle.com>
---
 db/namei.c               |  380 ++++++++++++++++++++++++++++++++++++++++++++++
 libxfs/libxfs_api_defs.h |    1 
 man/man8/xfs_db.8        |   14 ++
 3 files changed, 395 insertions(+)


diff --git a/db/namei.c b/db/namei.c
index 3c9889d62338..b2c036e6777a 100644
--- a/db/namei.c
+++ b/db/namei.c
@@ -221,8 +221,388 @@ static const cmdinfo_t path_cmd = {
 	.help		= path_help,
 };
 
+/* List a directory's entries. */
+
+static const char *filetype_strings[XFS_DIR3_FT_MAX] = {
+	[XFS_DIR3_FT_UNKNOWN]	= N_("unknown"),
+	[XFS_DIR3_FT_REG_FILE]	= N_("regular"),
+	[XFS_DIR3_FT_DIR]	= N_("directory"),
+	[XFS_DIR3_FT_CHRDEV]	= N_("chardev"),
+	[XFS_DIR3_FT_BLKDEV]	= N_("blkdev"),
+	[XFS_DIR3_FT_FIFO]	= N_("fifo"),
+	[XFS_DIR3_FT_SOCK]	= N_("socket"),
+	[XFS_DIR3_FT_SYMLINK]	= N_("symlink"),
+	[XFS_DIR3_FT_WHT]	= N_("whiteout"),
+};
+
+static const char *
+get_dstr(
+	struct xfs_mount	*mp,
+	uint8_t			filetype)
+{
+	if (!xfs_sb_version_hasftype(&mp->m_sb))
+		return filetype_strings[XFS_DIR3_FT_UNKNOWN];
+
+	if (filetype >= XFS_DIR3_FT_MAX)
+		return filetype_strings[XFS_DIR3_FT_UNKNOWN];
+
+	return filetype_strings[filetype];
+}
+
+static void
+dir_emit(
+	struct xfs_mount	*mp,
+	char			*name,
+	ssize_t			namelen,
+	xfs_ino_t		ino,
+	uint8_t			dtype)
+{
+	char			*display_name;
+	struct xfs_name		xname = { .name = name };
+	const char		*dstr = get_dstr(mp, dtype);
+	xfs_dahash_t		hash;
+	bool			good;
+
+	if (namelen < 0) {
+		/* Negative length means that name is null-terminated. */
+		display_name = name;
+		xname.len = strlen(name);
+		good = true;
+	} else {
+		/*
+		 * Otherwise, name came from a directory entry, so we have to
+		 * copy the string to a buffer so that we can add the null
+		 * terminator.
+		 */
+		display_name = malloc(namelen + 1);
+		memcpy(display_name, name, namelen);
+		display_name[namelen] = 0;
+		xname.len = namelen;
+		good = libxfs_dir2_namecheck(name, namelen);
+	}
+	hash = libxfs_dir2_hashname(mp, &xname);
+
+	dbprintf("%-18llu %-14s 0x%08llx %3d %s", ino, dstr, hash, xname.len,
+			display_name);
+	if (!good)
+		dbprintf(_(" (corrupt)"));
+	dbprintf("\n");
+
+	if (display_name != name)
+		free(display_name);
+}
+
+static int
+list_sfdir(
+	struct xfs_da_args		*args)
+{
+	struct xfs_inode		*dp = args->dp;
+	struct xfs_mount		*mp = dp->i_mount;
+	struct xfs_dir2_sf_entry	*sfep;
+	struct xfs_dir2_sf_hdr		*sfp;
+	xfs_ino_t			ino;
+	unsigned int			i;
+	uint8_t				filetype;
+
+	sfp = (struct xfs_dir2_sf_hdr *)dp->i_df.if_u1.if_data;
+
+	/* . and .. entries */
+	dir_emit(args->dp->i_mount, ".", -1, dp->i_ino, XFS_DIR3_FT_DIR);
+
+	ino = libxfs_dir2_sf_get_parent_ino(sfp);
+	dir_emit(args->dp->i_mount, "..", -1, ino, XFS_DIR3_FT_DIR);
+
+	/* Walk everything else. */
+	sfep = xfs_dir2_sf_firstentry(sfp);
+	for (i = 0; i < sfp->count; i++) {
+		ino = libxfs_dir2_sf_get_ino(mp, sfp, sfep);
+		filetype = libxfs_dir2_sf_get_ftype(mp, sfep);
+
+		dir_emit(args->dp->i_mount, (char *)sfep->name, sfep->namelen,
+				ino, filetype);
+		sfep = libxfs_dir2_sf_nextentry(mp, sfp, sfep);
+	}
+
+	return 0;
+}
+
+/* List entries in block format directory. */
+static int
+list_blockdir(
+	struct xfs_da_args	*args)
+{
+	struct xfs_inode	*dp = args->dp;
+	struct xfs_mount	*mp = dp->i_mount;
+	struct xfs_buf		*bp;
+	struct xfs_da_geometry	*geo = mp->m_dir_geo;
+	unsigned int		offset;
+	unsigned int		end;
+	int			error;
+
+	error = xfs_dir3_block_read(NULL, dp, &bp);
+	if (error)
+		return error;
+
+	end = xfs_dir3_data_end_offset(geo, bp->b_addr);
+	for (offset = geo->data_entry_offset; offset < end;) {
+		struct xfs_dir2_data_unused	*dup = bp->b_addr + offset;
+		struct xfs_dir2_data_entry	*dep = bp->b_addr + offset;
+		uint8_t				filetype;
+
+		if (be16_to_cpu(dup->freetag) == XFS_DIR2_DATA_FREE_TAG) {
+			/* Unused entry */
+			offset += be16_to_cpu(dup->length);
+			continue;
+		}
+
+		/* Real entry */
+		offset += libxfs_dir2_data_entsize(mp, dep->namelen);
+		filetype = libxfs_dir2_data_get_ftype(dp->i_mount, dep);
+		dir_emit(mp, (char *)dep->name, dep->namelen,
+				be64_to_cpu(dep->inumber), filetype);
+	}
+
+	libxfs_trans_brelse(args->trans, bp);
+	return error;
+}
+
+/* List entries in leaf format directory. */
+static int
+list_leafdir(
+	struct xfs_da_args	*args)
+{
+	struct xfs_bmbt_irec	map;
+	struct xfs_iext_cursor	icur;
+	struct xfs_inode	*dp = args->dp;
+	struct xfs_mount	*mp = dp->i_mount;
+	struct xfs_buf		*bp = NULL;
+	struct xfs_ifork	*ifp = XFS_IFORK_PTR(dp, XFS_DATA_FORK);
+	struct xfs_da_geometry	*geo = mp->m_dir_geo;
+	xfs_dablk_t		dabno = 0;
+	int			error = 0;
+
+	/* Read extent map. */
+	if (!(ifp->if_flags & XFS_IFEXTENTS)) {
+		error = -libxfs_iread_extents(NULL, dp, XFS_DATA_FORK);
+		if (error)
+			return error;
+	}
+
+	while (dabno < geo->leafblk) {
+		unsigned int	offset;
+		unsigned int	length;
+
+		/* Find mapping for leaf block. */
+		if (!xfs_iext_lookup_extent(dp, ifp, dabno, &icur, &map))
+			break;
+		if (map.br_startoff >= geo->leafblk)
+			break;
+		libxfs_trim_extent(&map, dabno, geo->leafblk - dabno);
+
+		/* Read the directory block of that first mapping. */
+		error = xfs_dir3_data_read(NULL, dp, map.br_startoff, 0, &bp);
+		if (error)
+			break;
+
+		for (offset = geo->data_entry_offset; offset < geo->blksize;) {
+			struct xfs_dir2_data_entry	*dep;
+			struct xfs_dir2_data_unused	*dup;
+			uint8_t				filetype;
+
+			dup = bp->b_addr + offset;
+			dep = bp->b_addr + offset;
+
+			if (be16_to_cpu(dup->freetag) ==
+			    XFS_DIR2_DATA_FREE_TAG) {
+				/* Skip unused entry */
+				length = be16_to_cpu(dup->length);
+				offset += length;
+				continue;
+			}
+
+			offset += libxfs_dir2_data_entsize(mp, dep->namelen);
+			filetype = libxfs_dir2_data_get_ftype(mp, dep);
+
+			dir_emit(mp, (char *)dep->name, dep->namelen,
+					be64_to_cpu(dep->inumber), filetype);
+		}
+
+		dabno += XFS_DADDR_TO_FSB(mp, bp->b_length);
+		libxfs_buf_relse(bp);
+		bp = NULL;
+	}
+
+	if (bp)
+		libxfs_buf_relse(bp);
+
+	return error;
+}
+
+/* Read the directory, display contents. */
+int
+listdir(
+	struct xfs_inode	*dp)
+{
+	struct xfs_da_args	args = {
+		.dp		= dp,
+		.geo		= dp->i_mount->m_dir_geo,
+	};
+	int			error;
+	int			isblock;
+
+	if (dp->i_df.if_format == XFS_DINODE_FMT_LOCAL)
+		return list_sfdir(&args);
+
+	error = -libxfs_dir2_isblock(&args, &isblock);
+	if (error)
+		return error;
+
+	if (isblock)
+		return list_blockdir(&args);
+	return list_leafdir(&args);
+}
+
+/* If the io cursor points to a directory, list its contents. */
+static int
+ls_cur(
+	char			*tag,
+	bool			direct)
+{
+	struct xfs_inode	*dp;
+	int			ret = 0;
+
+	if (iocur_top->typ != &typtab[TYP_INODE]) {
+		dbprintf(_("current object is not an inode.\n"));
+		return -1;
+	}
+
+	ret = -libxfs_iget(mp, NULL, iocur_top->ino, 0, &dp);
+	if (ret) {
+		dbprintf(_("failed to iget directory %llu, error %d\n"),
+				(unsigned long long)iocur_top->ino, ret);
+		return -1;
+	}
+
+	if (S_ISDIR(VFS_I(dp)->i_mode) && !direct) {
+		/* List the contents of a directory. */
+		if (tag)
+			dbprintf(_("%s:\n"), tag);
+
+		ret = listdir(dp);
+		if (ret) {
+			dbprintf(_("failed to list directory %llu: %s\n"),
+					(unsigned long long)iocur_top->ino,
+					strerror(ret));
+			ret = -1;
+			goto rele;
+		}
+	} else if (direct || !S_ISDIR(VFS_I(dp)->i_mode)) {
+		/* List the directory entry associated with a single file. */
+		char		inum[32];
+
+		if (!tag) {
+			snprintf(inum, sizeof(inum), "<%llu>",
+					(unsigned long long)iocur_top->ino);
+			tag = inum;
+		} else {
+			char	*p = strrchr(tag, '/');
+
+			if (p)
+				tag = p + 1;
+		}
+
+		dir_emit(mp, tag, -1, iocur_top->ino,
+				libxfs_mode_to_ftype(VFS_I(dp)->i_mode));
+	} else {
+		dbprintf(_("current inode %llu is not a directory.\n"),
+				(unsigned long long)iocur_top->ino);
+		ret = -1;
+		goto rele;
+	}
+
+rele:
+	libxfs_irele(dp);
+	return ret;
+}
+
+static void
+ls_help(void)
+{
+	dbprintf(_(
+"\n"
+" List the contents of the currently selected directory inode.\n"
+"\n"
+" Options:\n"
+"   -d -- List directories themselves, not their contents.\n"
+"\n"
+" Directory contents will be listed in the format:\n"
+" inode_number	type	hash	name_length	name\n"
+	));
+}
+
+static int
+ls_f(
+	int			argc,
+	char			**argv)
+{
+	bool			direct = false;
+	int			c;
+	int			ret = 0;
+
+	while ((c = getopt(argc, argv, "d")) != -1) {
+		switch (c) {
+		case 'd':
+			direct = true;
+			break;
+		default:
+			ls_help();
+			return 0;
+		}
+	}
+
+	if (optind == argc) {
+		if (ls_cur(NULL, direct))
+			exitcode = 1;
+		return 0;
+	}
+
+	for (c = optind; c < argc; c++) {
+		push_cur();
+
+		ret = path_walk(argv[c]);
+		if (ret)
+			goto err_cur;
+
+		ret = ls_cur(argv[c], direct);
+		if (ret)
+			goto err_cur;
+
+		pop_cur();
+	}
+
+	return 0;
+err_cur:
+	pop_cur();
+	if (ret)
+		exitcode = 1;
+	return 0;
+}
+
+static const cmdinfo_t ls_cmd = {
+	.name		= "ls",
+	.altname	= "l",
+	.cfunc		= ls_f,
+	.argmin		= 0,
+	.argmax		= -1,
+	.canpush	= 0,
+	.args		= "[-d] [paths...]",
+	.oneline	= N_("list directory contents"),
+	.help		= ls_help,
+};
+
 void
 namei_init(void)
 {
 	add_command(&path_cmd);
+	add_command(&ls_cmd);
 }
diff --git a/libxfs/libxfs_api_defs.h b/libxfs/libxfs_api_defs.h
index e7e42e93a07e..7029d0e7daf7 100644
--- a/libxfs/libxfs_api_defs.h
+++ b/libxfs/libxfs_api_defs.h
@@ -187,6 +187,7 @@
 #define xfs_trans_resv_calc		libxfs_trans_resv_calc
 #define xfs_trans_roll_inode		libxfs_trans_roll_inode
 #define xfs_trans_roll			libxfs_trans_roll
+#define xfs_trim_extent			libxfs_trim_extent
 
 #define xfs_verify_agbno		libxfs_verify_agbno
 #define xfs_verify_agino		libxfs_verify_agino
diff --git a/man/man8/xfs_db.8 b/man/man8/xfs_db.8
index d67e108a706a..fefe862b7564 100644
--- a/man/man8/xfs_db.8
+++ b/man/man8/xfs_db.8
@@ -806,6 +806,20 @@ This makes it easier to find discrepancies in the reservation calculations
 between xfsprogs and the kernel, which will help when diagnosing minimum
 log size calculation errors.
 .TP
+.BI "ls [\-d] [" dir_path "]..."
+List the contents of the given paths.
+If a path resolves to a directory, the directory will be listed.
+If a path resolves to a file that is not a directory, the entry for the file
+within the parent directory will be listed.
+If no paths are supplied and the IO cursor points at a directory inode,
+the contents of that directory will be listed.
+If no paths are supplied and the IO cursor points at a file that is not a
+directory, a directory entry will be synthesized for the file.
+If the
+.B \-d
+option is specified, list the directory itself and not its contents.
+The output format is: inode number, file type, hash, name length, name.
+.TP
 .BI "metadump [\-egow] " filename
 Dumps metadata to a file. See
 .BR xfs_metadump (8)


^ permalink raw reply related	[flat|nested] 15+ messages in thread

end of thread, other threads:[~2021-02-09  4:14 UTC | newest]

Thread overview: 15+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-01-09  6:27 [PATCHSET v3 0/2] xfs_db: add minimal directory navigation Darrick J. Wong
2021-01-09  6:27 ` [PATCH 1/2] xfs_db: add a directory path lookup command Darrick J. Wong
2021-01-09  6:27 ` [PATCH 2/2] xfs_db: add an ls command Darrick J. Wong
  -- strict thread matches above, loose matches on Subject: below --
2021-02-09  4:09 [PATCHSET v5 0/2] xfs_db: add minimal directory navigation Darrick J. Wong
2021-02-09  4:10 ` [PATCH 2/2] xfs_db: add an ls command Darrick J. Wong
2021-02-03 19:42 [PATCHSET v4 0/2] xfs_db: add minimal directory navigation Darrick J. Wong
2021-02-03 19:43 ` [PATCH 2/2] xfs_db: add an ls command Darrick J. Wong
2021-01-16  1:24 [PATCHSET v3 0/2] xfs_db: add minimal directory navigation Darrick J. Wong
2021-01-16  1:24 ` [PATCH 2/2] xfs_db: add an ls command Darrick J. Wong
2021-01-20 15:24   ` Chandan Babu R
2020-11-25 20:38 [PATCH v3 0/2] xfs_db: add minimal directory navigation Darrick J. Wong
2020-11-25 20:38 ` [PATCH 2/2] xfs_db: add an ls command Darrick J. Wong
2020-12-01 10:17   ` Christoph Hellwig
2020-11-16 17:35 [PATCH v2 0/2] xfs_db: add minimal directory navigation Darrick J. Wong
2020-11-16 17:35 ` [PATCH 2/2] xfs_db: add an ls command Darrick J. Wong
2020-10-26 23:32 [PATCH 0/2] xfs_db: add minimal directory navigation Darrick J. Wong
2020-10-26 23:32 ` [PATCH 2/2] xfs_db: add an ls command Darrick J. Wong
2020-10-28  1:27   ` Dave Chinner
2020-10-28 22:50     ` Darrick J. Wong
2020-10-28 23:20       ` Dave Chinner
2020-10-29 18:41         ` Darrick J. Wong

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.