blob: 9ff833553ccbfbddd0b0eeee3deb2123c254b7a3 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2023-2025 Oracle. All Rights Reserved.
* Author: Catherine Hoang <catherine.hoang@oracle.com>
* Author: Darrick J. Wong <djwong@kernel.org>
*/
#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)
#define LOST_ACL (1U << 6)
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;
}
/* Cannot copy fsxattrs until setfsxattrat gets merged */
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;
/* Cannot set 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;
/* Cannot set 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 +
XATTR_SECURITY_PREFIX_LEN + 1];
char value[XATTR_SIZE_MAX];
};
/*
* ACL xattrs can be copied verbatim to another XFS filesystem without issue
* because the name of the ondisk xattr is not that of the magic POSIX ACL
* xattr. However, if we're rdumping to another filesystem type, we need to
* warn the user that the ACLs most likely won't work because we don't want to
* introduce a hard dep on libacl to perform a translation for a foreign fs.
*/
static inline bool
cannot_translate_acl(
int fd,
const char *name,
unsigned int namelen)
{
struct statfs statfsbuf;
int ret;
if (namelen == SGI_ACL_FILE_SIZE &&
!strncmp(name, SGI_ACL_FILE, SGI_ACL_FILE_SIZE))
return false;
if (namelen == SGI_ACL_DEFAULT_SIZE &&
!strncmp(name, SGI_ACL_DEFAULT, SGI_ACL_DEFAULT_SIZE))
return false;
ret = fstatfs(fd, &statfsbuf);
if (ret)
return false;
return statfsbuf.f_type != XFS_SUPER_MAGIC;
}
/* 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)
{
struct copyxattr *cx = priv;
const char *namespace;
char *realname = (char *)name;
const size_t remaining = sizeof(cx->name);
ssize_t added;
int realnamelen = namelen;
int ret;
switch (attr_flags & XFS_ATTR_NSP_ONDISK_MASK) {
case XFS_ATTR_PARENT:
return 0;
case XFS_ATTR_ROOT:
namespace = XATTR_TRUSTED_PREFIX;
if (!(lost_mask & LOST_ACL) &&
cannot_translate_acl(cx->fd, (const char *)name, namelen))
lost_mask |= LOST_ACL;
break;
case XFS_ATTR_SECURE:
namespace = XATTR_SECURITY_PREFIX;
break;
case 0: /* user xattrs */
namespace = XATTR_USER_PREFIX;
break;
default:
dbprintf(_("%s%s%s: unknown xattr namespace 0x%x\n"),
cx->destdir->path, cx->destdir->sep,
cx->pbuf->path,
attr_flags & XFS_ATTR_NSP_ONDISK_MASK);
return strict_errors ? ECANCELED : 0;
}
added = snprintf(cx->name, remaining, "%s%.*s", namespace,
realnamelen, realname);
if (added > remaining) {
dbprintf(
_("%s%s%s: ran out of space formatting xattr name %s%.*s\n"),
cx->destdir->path, cx->destdir->sep,
cx->pbuf->path, namespace, realnamelen,
realname);
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, cx->name,
strerror(ret));
return strict_errors ? ECANCELED : 0;
}
value = cx->value;
}
ret = fsetxattr(cx->fd, cx->name, 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, cx->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);
cd->pbuf->path[oldlen] = 0;
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);
if (lost_mask & LOST_ACL)
dbprintf(_("%s: some ACLs could not be translated\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);
}