Re: [PATCH 4/4] xfs_db: add command to copy directory trees out of filesystems

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On 2025-02-06 15:03:32, Darrick J. Wong wrote:
> From: Darrick J. Wong <djwong@xxxxxxxxxx>
> 
> Aheada of deprecating V4 support in the kernel, let's give people a way
> to extract their files from a filesystem without needing to mount.
> 
> Signed-off-by: "Darrick J. Wong" <djwong@xxxxxxxxxx>
> ---
>  db/Makefile              |    3 
>  db/command.c             |    1 
>  db/command.h             |    1 
>  db/namei.c               |    5 
>  db/namei.h               |   18 +
>  db/rdump.c               |  999 ++++++++++++++++++++++++++++++++++++++++++++++

Should rdump show progress? I suppose this will be rarely used/one
time use backup command, I can see the log of all of the files
restored being quite handy. Or maybe silent by default but with flag
like -l restore.log. Also, without any errors on huge filesystem it
would be helpful to see if it's working

Otherwise, looks good to me
Reviewed-by: Andrey Albershteyn <aalbersh@xxxxxxxxxx>

>  libxfs/libxfs_api_defs.h |    2 
>  man/man8/xfs_db.8        |   26 +
>  8 files changed, 1052 insertions(+), 3 deletions(-)
>  create mode 100644 db/namei.h
>  create mode 100644 db/rdump.c
> 
> 
> diff --git a/db/Makefile b/db/Makefile
> index 02eeead25b49d0..e36e775eee6021 100644
> --- a/db/Makefile
> +++ b/db/Makefile
> @@ -45,6 +45,7 @@ HFILES = \
>  	logformat.h \
>  	malloc.h \
>  	metadump.h \
> +	namei.h \
>  	obfuscate.h \
>  	output.h \
>  	print.h \
> @@ -64,7 +65,7 @@ CFILES = $(HFILES:.h=.c) \
>  	convert.c \
>  	info.c \
>  	iunlink.c \
> -	namei.c \
> +	rdump.c \
>  	timelimit.c
>  LSRCFILES = xfs_admin.sh xfs_ncheck.sh xfs_metadump.sh
>  
> diff --git a/db/command.c b/db/command.c
> index 1b46c3fec08a0e..15bdabbcb7d728 100644
> --- a/db/command.c
> +++ b/db/command.c
> @@ -145,4 +145,5 @@ init_commands(void)
>  	timelimit_init();
>  	iunlink_init();
>  	bmapinflate_init();
> +	rdump_init();
>  }
> diff --git a/db/command.h b/db/command.h
> index 2c2926afd7b516..21419bbe65bfeb 100644
> --- a/db/command.h
> +++ b/db/command.h
> @@ -36,3 +36,4 @@ extern void		timelimit_init(void);
>  extern void		namei_init(void);
>  extern void		iunlink_init(void);
>  extern void		bmapinflate_init(void);
> +extern void		rdump_init(void);
> diff --git a/db/namei.c b/db/namei.c
> index 6f277a65ed91ac..2586e0591c2357 100644
> --- a/db/namei.c
> +++ b/db/namei.c
> @@ -14,6 +14,7 @@
>  #include "fprint.h"
>  #include "field.h"
>  #include "inode.h"
> +#include "namei.h"
>  
>  /* Path lookup */
>  
> @@ -144,7 +145,7 @@ path_navigate(
>  }
>  
>  /* Walk a directory path to an inode and set the io cursor to that inode. */
> -static int
> +int
>  path_walk(
>  	xfs_ino_t	rootino,
>  	const char	*path)
> @@ -493,7 +494,7 @@ list_leafdir(
>  }
>  
>  /* Read the directory, display contents. */
> -static int
> +int
>  listdir(
>  	struct xfs_trans	*tp,
>  	struct xfs_inode	*dp,
> diff --git a/db/namei.h b/db/namei.h
> new file mode 100644
> index 00000000000000..05c384bc9a6c35
> --- /dev/null
> +++ b/db/namei.h
> @@ -0,0 +1,18 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Copyright (c) 2025 Oracle.  All Rights Reserved.
> + * Author: Darrick J. Wong <djwong@xxxxxxxxxx>
> + */
> +#ifndef DB_NAMEI_H_
> +#define DB_NAMEI_H_
> +
> +int path_walk(xfs_ino_t rootino, const char *path);
> +
> +typedef int (*dir_emit_t)(struct xfs_trans *tp, struct xfs_inode *dp,
> +		xfs_dir2_dataptr_t off, char *name, ssize_t namelen,
> +		xfs_ino_t ino, uint8_t dtype, void *private);
> +
> +int listdir(struct xfs_trans *tp, struct xfs_inode *dp, dir_emit_t dir_emit,
> +		void *private);
> +
> +#endif /* DB_NAMEI_H_ */
> diff --git a/db/rdump.c b/db/rdump.c
> new file mode 100644
> index 00000000000000..1e31b40a072bdc
> --- /dev/null
> +++ b/db/rdump.c
> @@ -0,0 +1,999 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (C) 2025 Oracle.  All Rights Reserved.
> + * Author: Darrick J. Wong <djwong@xxxxxxxxxx>
> + */
> +#include "libxfs.h"
> +#include "command.h"
> +#include "output.h"
> +#include "init.h"
> +#include "io.h"
> +#include "namei.h"
> +#include "type.h"
> +#include "input.h"
> +#include "faddr.h"
> +#include "fprint.h"
> +#include "field.h"
> +#include "inode.h"
> +#include "listxattr.h"
> +#include <sys/xattr.h>
> +#include <linux/xattr.h>
> +
> +static bool strict_errors;
> +
> +/* file attributes that we might have lost */
> +#define LOST_OWNER		(1U << 0)
> +#define LOST_MODE		(1U << 1)
> +#define LOST_TIME		(1U << 2)
> +#define LOST_SOME_FSXATTR	(1U << 3)
> +#define LOST_FSXATTR		(1U << 4)
> +#define LOST_XATTR		(1U << 5)
> +
> +static unsigned int lost_mask;
> +
> +static void
> +rdump_help(void)
> +{
> +	dbprintf(_(
> +"\n"
> +" Recover files out of the filesystem into a directory.\n"
> +"\n"
> +" Options:\n"
> +"   -s      -- Fail on errors when reading content from the filesystem.\n"
> +"   paths   -- Copy only these paths.  If no paths are given, copy everything.\n"
> +"   destdir -- The destination into which files are recovered.\n"
> +	));
> +}
> +
> +struct destdir {
> +	int	fd;
> +	char	*path;
> +	char	*sep;
> +};
> +
> +struct pathbuf {
> +	size_t	len;
> +	char	path[PATH_MAX + 1];
> +};
> +
> +static int rdump_file(struct xfs_trans *tp, xfs_ino_t ino,
> +		const struct destdir *destdir, struct pathbuf *pbuf);
> +
> +static inline unsigned int xflags2getflags(const struct fsxattr *fa)
> +{
> +	unsigned int	ret = 0;
> +
> +	if (fa->fsx_xflags & FS_XFLAG_IMMUTABLE)
> +		ret |= FS_IMMUTABLE_FL;
> +	if (fa->fsx_xflags & FS_XFLAG_APPEND)
> +		ret |= FS_APPEND_FL;
> +	if (fa->fsx_xflags & FS_XFLAG_SYNC)
> +		ret |= FS_SYNC_FL;
> +	if (fa->fsx_xflags & FS_XFLAG_NOATIME)
> +		ret |= FS_NOATIME_FL;
> +	if (fa->fsx_xflags & FS_XFLAG_NODUMP)
> +		ret |= FS_NODUMP_FL;
> +	if (fa->fsx_xflags & FS_XFLAG_DAX)
> +		ret |= FS_DAX_FL;
> +	if (fa->fsx_xflags & FS_XFLAG_PROJINHERIT)
> +		ret |= FS_PROJINHERIT_FL;
> +	return ret;
> +}
> +
> +/* Copy common file attributes to this fd */
> +static int
> +rdump_fileattrs_fd(
> +	struct xfs_inode	*ip,
> +	const struct destdir	*destdir,
> +	const struct pathbuf	*pbuf,
> +	int			fd)
> +{
> +	struct fsxattr		fsxattr = {
> +		.fsx_extsize	= ip->i_extsize,
> +		.fsx_projid	= ip->i_projid,
> +		.fsx_cowextsize	= ip->i_cowextsize,
> +		.fsx_xflags	= xfs_ip2xflags(ip),
> +	};
> +	int			ret;
> +
> +	ret = fchmod(fd, VFS_I(ip)->i_mode & ~S_IFMT);
> +	if (ret) {
> +		if (errno == EPERM)
> +			lost_mask |= LOST_MODE;
> +		else
> +			dbprintf(_("%s%s%s: fchmod %s\n"), destdir->path,
> +					destdir->sep, pbuf->path,
> +					strerror(errno));
> +		if (strict_errors)
> +			return 1;
> +	}
> +
> +	ret = fchown(fd, i_uid_read(VFS_I(ip)), i_gid_read(VFS_I(ip)));
> +	if (ret) {
> +		if (errno == EPERM)
> +			lost_mask |= LOST_OWNER;
> +		else
> +			dbprintf(_("%s%s%s: fchown %s\n"), destdir->path,
> +					destdir->sep, pbuf->path,
> +					strerror(errno));
> +		if (strict_errors)
> +			return 1;
> +	}
> +
> +	ret = ioctl(fd, XFS_IOC_FSSETXATTR, &fsxattr);
> +	if (ret) {
> +		unsigned int	getflags = xflags2getflags(&fsxattr);
> +
> +		/* try to use setflags if the target is not xfs */
> +		if (errno == EOPNOTSUPP || errno == ENOTTY) {
> +			lost_mask |= LOST_SOME_FSXATTR;
> +			ret = ioctl(fd, FS_IOC_SETFLAGS, &getflags);
> +		}
> +
> +		if (errno == EOPNOTSUPP || errno == EPERM || errno == ENOTTY)
> +			lost_mask |= LOST_FSXATTR;
> +		else
> +			dbprintf(_("%s%s%s: fssetxattr %s\n"), destdir->path,
> +					destdir->sep, pbuf->path,
> +					strerror(errno));
> +		if (strict_errors)
> +			return 1;
> +	}
> +
> +	return 0;
> +}
> +
> +/* Copy common file attributes to this path */
> +static int
> +rdump_fileattrs_path(
> +	struct xfs_inode	*ip,
> +	const struct destdir	*destdir,
> +	const struct pathbuf	*pbuf)
> +{
> +	int			ret;
> +
> +	ret = fchmodat(destdir->fd, pbuf->path, VFS_I(ip)->i_mode & ~S_IFMT,
> +			AT_SYMLINK_NOFOLLOW);
> +	if (ret) {
> +		/* fchmodat on a symlink is not supported */
> +		if (errno == EPERM || errno == EOPNOTSUPP)
> +			lost_mask |= LOST_MODE;
> +		else
> +			dbprintf(_("%s%s%s: fchmodat %s\n"), destdir->path,
> +					destdir->sep, pbuf->path,
> +					strerror(errno));
> +		if (strict_errors)
> +			return 1;
> +	}
> +
> +	ret = fchownat(destdir->fd, pbuf->path, i_uid_read(VFS_I(ip)),
> +			i_gid_read(VFS_I(ip)), AT_SYMLINK_NOFOLLOW);
> +	if (ret) {
> +		if (errno == EPERM)
> +			lost_mask |= LOST_OWNER;
> +		else
> +			dbprintf(_("%s%s%s: fchownat %s\n"), destdir->path,
> +					destdir->sep, pbuf->path,
> +					strerror(errno));
> +		if (strict_errors)
> +			return 1;
> +	}
> +
> +	/* XXX cannot copy fsxattrs */
> +
> +	return 0;
> +}
> +
> +/* Copy access and modification timestamps to this fd. */
> +static int
> +rdump_timestamps_fd(
> +	struct xfs_inode	*ip,
> +	const struct destdir	*destdir,
> +	const struct pathbuf	*pbuf,
> +	int			fd)
> +{
> +	struct timespec		times[2] = {
> +		[0] = {
> +			.tv_sec  = inode_get_atime_sec(VFS_I(ip)),
> +			.tv_nsec = inode_get_atime_nsec(VFS_I(ip)),
> +		},
> +		[1] = {
> +			.tv_sec  = inode_get_mtime_sec(VFS_I(ip)),
> +			.tv_nsec = inode_get_mtime_nsec(VFS_I(ip)),
> +		},
> +	};
> +	int			ret;
> +
> +	/* XXX cannot copy ctime or btime */
> +
> +	ret = futimens(fd, times);
> +	if (ret) {
> +		if (errno == EPERM)
> +			lost_mask |= LOST_TIME;
> +		else
> +			dbprintf(_("%s%s%s: futimens %s\n"), destdir->path,
> +					destdir->sep, pbuf->path,
> +					strerror(errno));
> +		if (strict_errors)
> +			return 1;
> +	}
> +
> +	return 0;
> +}
> +
> +/* Copy access and modification timestamps to this path. */
> +static int
> +rdump_timestamps_path(
> +	struct xfs_inode	*ip,
> +	const struct destdir	*destdir,
> +	const struct pathbuf	*pbuf)
> +{
> +	struct timespec		times[2] = {
> +		[0] = {
> +			.tv_sec  = inode_get_atime_sec(VFS_I(ip)),
> +			.tv_nsec = inode_get_atime_nsec(VFS_I(ip)),
> +		},
> +		[1] = {
> +			.tv_sec  = inode_get_mtime_sec(VFS_I(ip)),
> +			.tv_nsec = inode_get_mtime_nsec(VFS_I(ip)),
> +		},
> +	};
> +	int			ret;
> +
> +	/* XXX cannot copy ctime or btime */
> +
> +	ret = utimensat(destdir->fd, pbuf->path, times, AT_SYMLINK_NOFOLLOW);
> +	if (ret) {
> +		if (errno == EPERM)
> +			lost_mask |= LOST_TIME;
> +		else
> +			dbprintf(_("%s%s%s: utimensat %s\n"), destdir->path,
> +					destdir->sep, pbuf->path,
> +					strerror(errno));
> +		if (strict_errors)
> +			return 1;
> +	}
> +
> +	return 0;
> +}
> +
> +struct copyxattr {
> +	const struct destdir	*destdir;
> +	const struct pathbuf	*pbuf;
> +	int			fd;
> +	char			name[XATTR_NAME_MAX + 1];
> +	char			value[XATTR_SIZE_MAX];
> +};
> +
> +/* Copy one extended attribute */
> +static int
> +rdump_xattr(
> +	struct xfs_trans	*tp,
> +	struct xfs_inode	*ip,
> +	unsigned int		attr_flags,
> +	const unsigned char	*name,
> +	unsigned int		namelen,
> +	const void		*value,
> +	unsigned int		valuelen,
> +	void			*priv)
> +{
> +	const char		*nsp;
> +	struct copyxattr	*cx = priv;
> +	const size_t		remaining = sizeof(cx->name);
> +	ssize_t			added;
> +	int			ret;
> +
> +	if (attr_flags & XFS_ATTR_PARENT)
> +		return 0;
> +
> +	/* Format xattr name */
> +	if (attr_flags & XFS_ATTR_ROOT)
> +		nsp = XATTR_TRUSTED_PREFIX;
> +	else if (attr_flags & XFS_ATTR_SECURE)
> +		nsp = XATTR_SECURITY_PREFIX;
> +	else
> +		nsp = XATTR_USER_PREFIX;
> +	added = snprintf(cx->name, remaining, "%s.%.*s", nsp, namelen, name);
> +	if (added > remaining) {
> +		dbprintf(_("%s%s%s: ran out of space formatting xattr name\n"),
> +				cx->destdir->path, cx->destdir->sep,
> +				cx->pbuf->path);
> +		return strict_errors ? ECANCELED : 0;
> +	}
> +
> +	/* Retrieve xattr value if needed */
> +	if (valuelen > 0 && !value) {
> +		struct xfs_da_args	args = {
> +			.trans		= tp,
> +			.dp		= ip,
> +			.geo		= mp->m_attr_geo,
> +			.owner		= ip->i_ino,
> +			.attr_filter	= attr_flags & XFS_ATTR_NSP_ONDISK_MASK,
> +			.namelen	= namelen,
> +			.name		= name,
> +			.value		= cx->value,
> +			.valuelen	= valuelen,
> +		};
> +
> +		ret = -libxfs_attr_rmtval_get(&args);
> +		if (ret) {
> +			dbprintf(_("%s: reading xattr \"%.*s\" value %s\n"),
> +					cx->pbuf->path, namelen, name,
> +					strerror(ret));
> +			return strict_errors ? ECANCELED : 0;
> +		}
> +	}
> +
> +	ret = fsetxattr(cx->fd, cx->name, cx->value, valuelen, 0);
> +	if (ret) {
> +		if (ret == EOPNOTSUPP)
> +			lost_mask |= LOST_XATTR;
> +		else
> +			dbprintf(_("%s%s%s: fsetxattr \"%.*s\" %s\n"),
> +					cx->destdir->path, cx->destdir->sep,
> +					cx->pbuf->path, namelen, name,
> +					strerror(errno));
> +		if (strict_errors)
> +			return ECANCELED;
> +	}
> +
> +	return 0;
> +}
> +
> +/* Copy extended attributes */
> +static int
> +rdump_xattrs(
> +	struct xfs_trans	*tp,
> +	struct xfs_inode	*ip,
> +	const struct destdir	*destdir,
> +	const struct pathbuf	*pbuf,
> +	int			fd)
> +{
> +	struct copyxattr	*cx;
> +	int			ret;
> +
> +	cx = calloc(1, sizeof(struct copyxattr));
> +	if (!cx) {
> +		dbprintf(_("%s%s%s: allocating xattr buffer %s\n"),
> +				destdir->path, destdir->sep, pbuf->path,
> +				strerror(errno));
> +		return 1;
> +	}
> +	cx->destdir = destdir;
> +	cx->pbuf = pbuf;
> +	cx->fd = fd;
> +
> +	ret = xattr_walk(tp, ip, rdump_xattr, cx);
> +	if (ret && ret != ECANCELED) {
> +		dbprintf(_("%s%s%s: listxattr %s\n"), destdir->path,
> +				destdir->sep, pbuf->path, strerror(errno));
> +		if (strict_errors)
> +			return 1;
> +	}
> +
> +	free(cx);
> +	return 0;
> +}
> +
> +struct copydirent {
> +	const struct destdir	*destdir;
> +	struct pathbuf		*pbuf;
> +	int			fd;
> +};
> +
> +/* Copy a directory entry. */
> +static int
> +rdump_dirent(
> +	struct xfs_trans	*tp,
> +	struct xfs_inode	*dp,
> +	xfs_dir2_dataptr_t	off,
> +	char			*name,
> +	ssize_t			namelen,
> +	xfs_ino_t		ino,
> +	uint8_t			dtype,
> +	void			*private)
> +{
> +	struct copydirent	*cd = private;
> +	size_t			oldlen = cd->pbuf->len;
> +	const size_t		remaining = PATH_MAX + 1 - oldlen;
> +	ssize_t			added;
> +	int			ret;
> +
> +	/* Negative length means name is null-terminated */
> +	if (namelen < 0)
> +		namelen = -namelen;
> +
> +	/* Ignore dot and dotdot */
> +	if (namelen == 1 && name[0] == '.')
> +		return 0;
> +	if (namelen == 2 && name[0] == '.' && name[1] == '.')
> +		return 0;
> +
> +	if (namelen > FILENAME_MAX) {
> +		dbprintf(_("%s%s%s: %s\n"),
> +				cd->destdir->path, cd->destdir->sep,
> +				cd->pbuf->path, strerror(ENAMETOOLONG));
> +		return strict_errors ? ECANCELED : 0;
> +	}
> +
> +	added = snprintf(&cd->pbuf->path[oldlen], remaining, "%s%.*s",
> +			oldlen ? "/" : "", (int)namelen, name);
> +	if (added > remaining) {
> +		dbprintf(_("%s%s%s: ran out of space formatting file name\n"),
> +				cd->destdir->path, cd->destdir->sep,
> +				cd->pbuf->path);
> +		return strict_errors ? ECANCELED : 0;
> +	}
> +
> +	cd->pbuf->len += added;
> +	ret = rdump_file(tp, ino, cd->destdir, cd->pbuf);
> +	cd->pbuf->len = oldlen;
> +	cd->pbuf->path[oldlen] = 0;
> +	return ret;
> +}
> +
> +/* Copy a directory */
> +static int
> +rdump_directory(
> +	struct xfs_trans	*tp,
> +	struct xfs_inode	*dp,
> +	const struct destdir	*destdir,
> +	struct pathbuf		*pbuf)
> +{
> +	struct copydirent	*cd;
> +	int			ret, ret2;
> +
> +	cd = calloc(1, sizeof(struct copydirent));
> +	if (!cd) {
> +		dbprintf(_("%s%s%s: %s\n"), destdir->path, destdir->sep,
> +				pbuf->path, strerror(errno));
> +		return 1;
> +	}
> +	cd->destdir = destdir;
> +	cd->pbuf = pbuf;
> +
> +	if (pbuf->len) {
> +		/*
> +		 * If path is non-empty, we want to create a child somewhere
> +		 * underneath the target directory.
> +		 */
> +		ret = mkdirat(destdir->fd, pbuf->path, 0600);
> +		if (ret && errno != EEXIST) {
> +			dbprintf(_("%s%s%s: %s\n"), destdir->path,
> +					destdir->sep, pbuf->path,
> +					strerror(errno));
> +			goto out_cd;
> +		}
> +
> +		cd->fd = openat(destdir->fd, pbuf->path,
> +				O_RDONLY | O_DIRECTORY);
> +		if (cd->fd < 0) {
> +			dbprintf(_("%s%s%s: %s\n"), destdir->path,
> +					destdir->sep, pbuf->path,
> +					strerror(errno));
> +			ret = 1;
> +			goto out_cd;
> +		}
> +	} else {
> +		/*
> +		 * If path is empty, then we're copying the children of a
> +		 * directory into the target directory.
> +		 */
> +		cd->fd = destdir->fd;
> +	}
> +
> +	ret = rdump_fileattrs_fd(dp, destdir, pbuf, cd->fd);
> +	if (ret && strict_errors)
> +		goto out_close;
> +
> +	if (xfs_inode_has_attr_fork(dp)) {
> +		ret = rdump_xattrs(tp, dp, destdir, pbuf, cd->fd);
> +		if (ret && strict_errors)
> +			goto out_close;
> +	}
> +
> +	ret = listdir(tp, dp, rdump_dirent, cd);
> +	if (ret && ret != ECANCELED) {
> +		dbprintf(_("%s%s%s: readdir %s\n"), destdir->path,
> +				destdir->sep, pbuf->path, strerror(ret));
> +		if (strict_errors)
> +			goto out_close;
> +	}
> +
> +	ret = rdump_timestamps_fd(dp, destdir, pbuf, cd->fd);
> +	if (ret && strict_errors)
> +		goto out_close;
> +
> +	ret = 0;
> +
> +out_close:
> +	if (cd->fd != destdir->fd) {
> +		ret2 = close(cd->fd);
> +		if (ret2) {
> +			if (!ret)
> +				ret = ret2;
> +			dbprintf(_("%s%s%s: %s\n"), destdir->path,
> +					destdir->sep, pbuf->path,
> +					strerror(errno));
> +		}
> +	}
> +
> +out_cd:
> +	free(cd);
> +	return ret;
> +}
> +
> +/* Copy file data */
> +static int
> +rdump_regfile_data(
> +	struct xfs_trans	*tp,
> +	struct xfs_inode	*ip,
> +	const struct destdir	*destdir,
> +	const struct pathbuf	*pbuf,
> +	int			fd)
> +{
> +	struct xfs_bmbt_irec	irec = { };
> +	struct xfs_buftarg	*btp;
> +	int			nmaps;
> +	off_t			pos = 0;
> +	const off_t		isize = ip->i_disk_size;
> +	int			ret;
> +
> +	if (XFS_IS_REALTIME_INODE(ip))
> +		btp = ip->i_mount->m_rtdev_targp;
> +	else
> +		btp = ip->i_mount->m_ddev_targp;
> +
> +	for (;
> +	     pos < isize;
> +	     pos = XFS_FSB_TO_B(mp, irec.br_startoff + irec.br_blockcount)) {
> +		struct xfs_buf	*bp;
> +		off_t		buf_pos;
> +		off_t		fd_pos;
> +		xfs_fileoff_t	off = XFS_B_TO_FSBT(mp, pos);
> +		xfs_filblks_t	max_read = XFS_B_TO_FSB(mp, 1048576);
> +		xfs_daddr_t	daddr;
> +		size_t		count;
> +
> +		nmaps = 1;
> +		ret = -libxfs_bmapi_read(ip, off, max_read, &irec, &nmaps, 0);
> +		if (ret) {
> +			dbprintf(_("%s: %s\n"), pbuf->path, strerror(ret));
> +			if (strict_errors)
> +				return 1;
> +			continue;
> +		}
> +		if (!nmaps)
> +			break;
> +
> +		if (!xfs_bmap_is_written_extent(&irec))
> +			continue;
> +
> +		fd_pos = XFS_FSB_TO_B(mp, irec.br_startoff);
> +		if (XFS_IS_REALTIME_INODE(ip))
> +			daddr =  xfs_rtb_to_daddr(mp, irec.br_startblock);
> +		else
> +			daddr = XFS_FSB_TO_DADDR(mp, irec.br_startblock);
> +
> +		ret = -libxfs_buf_read_uncached(btp, daddr,
> +				XFS_FSB_TO_BB(mp, irec.br_blockcount), 0, &bp,
> +				NULL);
> +		if (ret) {
> +			dbprintf(_("%s: reading pos 0x%llx %s\n"), pbuf->path,
> +					fd_pos, strerror(ret));
> +			if (strict_errors)
> +				return 1;
> +			continue;
> +		}
> +
> +		count = XFS_FSB_TO_B(mp, irec.br_blockcount);
> +		if (fd_pos + count > isize)
> +			count = isize - fd_pos;
> +
> +		buf_pos = 0;
> +		while (count > 0) {
> +			ssize_t	written;
> +
> +			written = pwrite(fd, bp->b_addr + buf_pos, count,
> +					fd_pos);
> +			if (written < 0) {
> +				libxfs_buf_relse(bp);
> +				dbprintf(_("%s%s%s: writing pos 0x%llx %s\n"),
> +						destdir->path, destdir->sep,
> +						pbuf->path, fd_pos,
> +						strerror(errno));
> +				return 1;
> +			}
> +			if (!written) {
> +				libxfs_buf_relse(bp);
> +				dbprintf(
> + _("%s%s%s: wrote zero at pos 0x%llx %s\n"),
> +						destdir->path, destdir->sep,
> +						pbuf->path, fd_pos);
> +				return 1;
> +			}
> +
> +			fd_pos += written;
> +			buf_pos += written;
> +			count -= written;
> +		}
> +
> +		libxfs_buf_relse(bp);
> +	}
> +
> +	ret = ftruncate(fd, isize);
> +	if (ret) {
> +		dbprintf(_("%s%s%s: setting file length 0x%llx %s\n"),
> +				destdir->path, destdir->sep, pbuf->path,
> +				isize, strerror(errno));
> +		return 1;
> +	}
> +
> +	return 0;
> +}
> +
> +/* Copy a regular file */
> +static int
> +rdump_regfile(
> +	struct xfs_trans	*tp,
> +	struct xfs_inode	*ip,
> +	const struct destdir	*destdir,
> +	const struct pathbuf	*pbuf)
> +{
> +	int			fd;
> +	int			ret, ret2;
> +
> +	fd = openat(destdir->fd, pbuf->path, O_RDWR | O_CREAT | O_TRUNC, 0600);
> +	if (fd < 0) {
> +		dbprintf(_("%s%s%s: %s\n"), destdir->path, destdir->sep,
> +				pbuf->path, strerror(errno));
> +		return 1;
> +	}
> +
> +	ret = rdump_fileattrs_fd(ip, destdir, pbuf, fd);
> +	if (ret && strict_errors)
> +		goto out_close;
> +
> +	if (xfs_inode_has_attr_fork(ip)) {
> +		ret = rdump_xattrs(tp, ip, destdir, pbuf, fd);
> +		if (ret && strict_errors)
> +			goto out_close;
> +	}
> +
> +	ret = rdump_regfile_data(tp, ip, destdir, pbuf, fd);
> +	if (ret && strict_errors)
> +		goto out_close;
> +
> +	ret = rdump_timestamps_fd(ip, destdir, pbuf, fd);
> +	if (ret && strict_errors)
> +		goto out_close;
> +
> +out_close:
> +	ret2 = close(fd);
> +	if (ret2) {
> +		if (!ret)
> +			ret = ret2;
> +		dbprintf(_("%s%s%s: %s\n"), destdir->path, destdir->sep,
> +				pbuf->path, strerror(errno));
> +		return 1;
> +	}
> +
> +	return ret;
> +}
> +
> +/* Copy a symlink */
> +static int
> +rdump_symlink(
> +	struct xfs_trans	*tp,
> +	struct xfs_inode	*ip,
> +	const struct destdir	*destdir,
> +	const struct pathbuf	*pbuf)
> +{
> +	char			target[XFS_SYMLINK_MAXLEN + 1];
> +	struct xfs_ifork	*ifp = xfs_ifork_ptr(ip, XFS_DATA_FORK);
> +	unsigned int		targetlen = ip->i_disk_size;
> +	int			ret;
> +
> +	if (ifp->if_format == XFS_DINODE_FMT_LOCAL) {
> +		memcpy(target, ifp->if_data, targetlen);
> +	} else {
> +		ret = -libxfs_symlink_remote_read(ip, target);
> +		if (ret) {
> +			dbprintf(_("%s: %s\n"), pbuf->path, strerror(ret));
> +			return strict_errors ? 1 : 0;
> +		}
> +	}
> +	target[targetlen] = 0;
> +
> +	ret = symlinkat(target, destdir->fd, pbuf->path);
> +	if (ret) {
> +		dbprintf(_("%s%s%s: %s\n"), destdir->path, destdir->sep,
> +				pbuf->path, strerror(errno));
> +		return 1;
> +	}
> +
> +	ret = rdump_fileattrs_path(ip, destdir, pbuf);
> +	if (ret && strict_errors)
> +		goto out;
> +
> +	ret = rdump_timestamps_path(ip, destdir, pbuf);
> +	if (ret && strict_errors)
> +		goto out;
> +
> +	ret = 0;
> +out:
> +	return ret;
> +}
> +
> +/* Copy a special file */
> +static int
> +rdump_special(
> +	struct xfs_trans	*tp,
> +	struct xfs_inode	*ip,
> +	const struct destdir	*destdir,
> +	const struct pathbuf	*pbuf)
> +{
> +	const unsigned int	major = IRIX_DEV_MAJOR(VFS_I(ip)->i_rdev);
> +	const unsigned int	minor = IRIX_DEV_MINOR(VFS_I(ip)->i_rdev);
> +	int			ret;
> +
> +	ret = mknodat(destdir->fd, pbuf->path, VFS_I(ip)->i_mode & S_IFMT,
> +			makedev(major, minor));
> +	if (ret) {
> +		dbprintf(_("%s%s%s: %s\n"), destdir->path, destdir->sep,
> +				pbuf->path, strerror(errno));
> +		return 1;
> +	}
> +
> +	ret = rdump_fileattrs_path(ip, destdir, pbuf);
> +	if (ret && strict_errors)
> +		goto out;
> +
> +	ret = rdump_timestamps_path(ip, destdir, pbuf);
> +	if (ret && strict_errors)
> +		goto out;
> +
> +	ret = 0;
> +out:
> +	return ret;
> +}
> +
> +/* Dump some kind of file. */
> +static int
> +rdump_file(
> +	struct xfs_trans	*tp,
> +	xfs_ino_t		ino,
> +	const struct destdir	*destdir,
> +	struct pathbuf		*pbuf)
> +{
> +	struct xfs_inode	*ip;
> +	int			ret;
> +
> +	ret = -libxfs_iget(mp, tp, ino, 0, &ip);
> +	if (ret) {
> +		dbprintf(_("%s: %s\n"), pbuf->path, strerror(ret));
> +		return strict_errors ? ret : 0;
> +	}
> +
> +	switch(VFS_I(ip)->i_mode & S_IFMT) {
> +	case S_IFDIR:
> +		ret = rdump_directory(tp, ip, destdir, pbuf);
> +		break;
> +	case S_IFREG:
> +		ret = rdump_regfile(tp, ip, destdir, pbuf);
> +		break;
> +	case S_IFLNK:
> +		ret = rdump_symlink(tp, ip, destdir, pbuf);
> +		break;
> +	default:
> +		ret = rdump_special(tp, ip, destdir, pbuf);
> +		break;
> +	}
> +
> +	libxfs_irele(ip);
> +	return ret;
> +}
> +
> +/* Copy one path out of the filesystem. */
> +static int
> +rdump_path(
> +	struct xfs_mount	*mp,
> +	bool			sole_path,
> +	const char		*path,
> +	const struct destdir	*destdir)
> +{
> +	struct xfs_trans	*tp;
> +	struct pathbuf		*pbuf;
> +	const char		*basename;
> +	ssize_t			pathlen = strlen(path);
> +	int			ret = 1;
> +
> +	/* Set up destination path data */
> +	if (pathlen > PATH_MAX) {
> +		dbprintf(_("%s: %s\n"), path, strerror(ENAMETOOLONG));
> +		return 1;
> +	}
> +
> +	pbuf = calloc(1, sizeof(struct pathbuf));
> +	if (!pbuf) {
> +		dbprintf(_("allocating path buf: %s\n"), strerror(errno));
> +		return 1;
> +	}
> +	basename = strrchr(path, '/');
> +	if (basename) {
> +		pbuf->len = pathlen - (basename + 1 - path);
> +		memcpy(pbuf->path, basename + 1, pbuf->len);
> +	} else {
> +		pbuf->len = pathlen;
> +		memcpy(pbuf->path, path, pbuf->len);
> +	}
> +	pbuf->path[pbuf->len] = 0;
> +
> +	/* Dump the inode referenced. */
> +	if (pathlen) {
> +		ret = path_walk(mp->m_sb.sb_rootino, path);
> +		if (ret) {
> +			dbprintf(_("%s: %s\n"), path, strerror(ret));
> +			return 1;
> +		}
> +
> +		if (sole_path) {
> +			struct xfs_dinode	*dip = iocur_top->data;
> +
> +			/*
> +			 * If this is the only path to copy out and it's a dir,
> +			 * then we can copy the children directly into the
> +			 * target.
> +			 */
> +			if (S_ISDIR(be16_to_cpu(dip->di_mode))) {
> +				pbuf->len = 0;
> +				pbuf->path[0] = 0;
> +			}
> +		}
> +	} else {
> +		set_cur_inode(mp->m_sb.sb_rootino);
> +	}
> +
> +	ret = -libxfs_trans_alloc_empty(mp, &tp);
> +	if (ret) {
> +		dbprintf(_("allocating state: %s\n"), strerror(ret));
> +		goto out_pbuf;
> +	}
> +
> +	ret = rdump_file(tp, iocur_top->ino, destdir, pbuf);
> +	libxfs_trans_cancel(tp);
> +out_pbuf:
> +	free(pbuf);
> +	return ret;
> +}
> +
> +static int
> +rdump_f(
> +	int		argc,
> +	char		*argv[])
> +{
> +	struct destdir	destdir;
> +	int		i;
> +	int		c;
> +	int		ret;
> +
> +	lost_mask = 0;
> +	strict_errors = false;
> +	while ((c = getopt(argc, argv, "s")) != -1) {
> +		switch (c) {
> +		case 's':
> +			strict_errors = true;
> +			break;
> +		default:
> +			rdump_help();
> +			return 0;
> +		}
> +	}
> +
> +	if (argc < optind + 1) {
> +		dbprintf(
> + _("Must supply destination directory.\n"));
> +		return 0;
> +	}
> +
> +	/* Create and open destination directory */
> +	destdir.path = argv[argc - 1];
> +	ret = mkdir(destdir.path, 0755);
> +	if (ret && errno != EEXIST) {
> +		dbprintf(_("%s: %s\n"), destdir.path, strerror(errno));
> +		exitcode = 1;
> +		return 0;
> +	}
> +
> +	if (destdir.path[0] == 0) {
> +		dbprintf(
> + _("Destination dir must be at least one character.\n"));
> +		exitcode = 1;
> +		return 0;
> +	}
> +
> +	if (destdir.path[strlen(destdir.path) - 1] != '/')
> +		destdir.sep = "/";
> +	else
> +		destdir.sep = "";
> +	destdir.fd = open(destdir.path, O_DIRECTORY | O_RDONLY);
> +	if (destdir.fd < 0) {
> +		dbprintf(_("%s: %s\n"), destdir.path, strerror(errno));
> +		exitcode = 1;
> +		return 0;
> +	}
> +
> +	if (optind == argc - 1) {
> +		/* no dirs given, just do the whole fs */
> +		push_cur();
> +		ret = rdump_path(mp, false, "", &destdir);
> +		pop_cur();
> +		if (ret)
> +			exitcode = 1;
> +		goto out_close;
> +	}
> +
> +	for (i = optind; i < argc - 1; i++) {
> +		size_t	len = strlen(argv[i]);
> +
> +		/* trim trailing slashes */
> +		while (len && argv[i][len - 1] == '/')
> +			len--;
> +		argv[i][len] = 0;
> +
> +		push_cur();
> +		ret = rdump_path(mp, argc == optind + 2, argv[i], &destdir);
> +		pop_cur();
> +
> +		if (ret) {
> +			exitcode = 1;
> +			if (strict_errors)
> +				break;
> +		}
> +	}
> +
> +out_close:
> +	ret = close(destdir.fd);
> +	if (ret) {
> +		dbprintf(_("%s: %s\n"), destdir.path, strerror(errno));
> +		exitcode = 1;
> +	}
> +
> +	if (lost_mask & LOST_OWNER)
> +		dbprintf(_("%s: some uid/gid could not be set\n"),
> +				destdir.path);
> +	if (lost_mask & LOST_MODE)
> +		dbprintf(_("%s: some file modes could not be set\n"),
> +				destdir.path);
> +	if (lost_mask & LOST_TIME)
> +		dbprintf(_("%s: some timestamps could not be set\n"),
> +				destdir.path);
> +	if (lost_mask & LOST_SOME_FSXATTR)
> +		dbprintf(_("%s: some xfs file attr bits could not be set\n"),
> +				destdir.path);
> +	if (lost_mask & LOST_FSXATTR)
> +		dbprintf(_("%s: some xfs file attrs could not be set\n"),
> +				destdir.path);
> +	if (lost_mask & LOST_XATTR)
> +		dbprintf(_("%s: some extended xattrs could not be set\n"),
> +				destdir.path);
> +
> +	return 0;
> +}
> +
> +static struct cmdinfo rdump_cmd = {
> +	.name		= "rdump",
> +	.cfunc		= rdump_f,
> +	.argmin		= 0,
> +	.argmax		= -1,
> +	.canpush	= 0,
> +	.args		= "[-s] [paths...] dest_directory",
> +	.help		= rdump_help,
> +};
> +
> +void
> +rdump_init(void)
> +{
> +	rdump_cmd.oneline = _("recover files out of a filesystem");
> +	add_command(&rdump_cmd);
> +}
> diff --git a/libxfs/libxfs_api_defs.h b/libxfs/libxfs_api_defs.h
> index 530feef2a47db8..14a67c8c24dd7e 100644
> --- a/libxfs/libxfs_api_defs.h
> +++ b/libxfs/libxfs_api_defs.h
> @@ -47,6 +47,7 @@
>  #define xfs_attr_leaf_newentsize	libxfs_attr_leaf_newentsize
>  #define xfs_attr_namecheck		libxfs_attr_namecheck
>  #define xfs_attr_removename		libxfs_attr_removename
> +#define xfs_attr_rmtval_get		libxfs_attr_rmtval_get
>  #define xfs_attr_set			libxfs_attr_set
>  #define xfs_attr_sethash		libxfs_attr_sethash
>  #define xfs_attr_sf_firstentry		libxfs_attr_sf_firstentry
> @@ -353,6 +354,7 @@
>  #define xfs_sb_version_to_features	libxfs_sb_version_to_features
>  #define xfs_symlink_blocks		libxfs_symlink_blocks
>  #define xfs_symlink_hdr_ok		libxfs_symlink_hdr_ok
> +#define xfs_symlink_remote_read		libxfs_symlink_remote_read
>  #define xfs_symlink_write_target	libxfs_symlink_write_target
>  
>  #define xfs_trans_add_item		libxfs_trans_add_item
> diff --git a/man/man8/xfs_db.8 b/man/man8/xfs_db.8
> index 08f38f37ca01cc..2a9322560584b0 100644
> --- a/man/man8/xfs_db.8
> +++ b/man/man8/xfs_db.8
> @@ -1132,6 +1132,32 @@ .SH COMMANDS
>  Exit
>  .BR xfs_db .
>  .TP
> +.BI "rdump [-s] [" "paths..." "] " "destination_dir"
> +Recover the files given by the
> +.B path
> +arguments by copying them of the filesystem into the directory specified in
> +.BR destination_dir .
> +
> +If the
> +.B -s
> +option is specified, errors are fatal.
> +By default, read errors are ignored in favor of recovering as much data from
> +the filesystem as possible.
> +
> +If zero
> +.B paths
> +are specified, the entire filesystem is dumped.
> +If only one
> +.B path
> +is specified and it is a directory, the children of that directory will be
> +copied directly to the destination.
> +If multiple
> +.B paths
> +are specified, each file is copied into the directory as a new child.
> +
> +If possible, sparse holes, xfs file attributes, and extended attributes will be
> +preserved.
> +.TP
>  .BI "rgresv [" rgno ]
>  Displays the per-rtgroup reservation size, and per-rtgroup
>  reservation usage for a given realtime allocation group.
> 

-- 
- Andrey





[Index of Archives]     [XFS Filesystem Development (older mail)]     [Linux Filesystem Development]     [Linux Audio Users]     [Yosemite Trails]     [Linux Kernel]     [Linux RAID]     [Linux SCSI]


  Powered by Linux