blob: e6c3f486b0ffdcee4a8611a3d4f3bf93883b8748 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Copyright (c) 2020-2024 Oracle. All Rights Reserved.
* Author: Darrick J. Wong <djwong@kernel.org>
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <string.h>
#include "xfs.h"
#include "fsgeom.h"
#include "bulkstat.h"
#include "libfrog/file_exchange.h"
/* Prepare for a file contents exchange. */
void
xfrog_exchangerange_prep(
struct xfs_exchange_range *fxr,
off_t file2_offset,
int file1_fd,
off_t file1_offset,
uint64_t length)
{
memset(fxr, 0, sizeof(*fxr));
fxr->file1_fd = file1_fd;
fxr->file1_offset = file1_offset;
fxr->length = length;
fxr->file2_offset = file2_offset;
}
/*
* Execute an exchange-range operation. Returns 0 for success or a negative
* errno.
*/
int
xfrog_exchangerange(
int file2_fd,
struct xfs_exchange_range *fxr,
uint64_t flags)
{
int ret;
fxr->flags = flags;
ret = ioctl(file2_fd, XFS_IOC_EXCHANGE_RANGE, fxr);
if (ret)
return -errno;
return 0;
}
/*
* Prepare for committing a file contents exchange if nobody changes file2 in
* the meantime by asking the kernel to sample file2's change attributes.
*
* Returns 0 for success or a negative errno.
*/
int
xfrog_commitrange_prep(
struct xfs_commit_range *xcr,
int file2_fd,
off_t file2_offset,
int file1_fd,
off_t file1_offset,
uint64_t length)
{
int ret;
memset(xcr, 0, sizeof(*xcr));
xcr->file1_fd = file1_fd;
xcr->file1_offset = file1_offset;
xcr->length = length;
xcr->file2_offset = file2_offset;
ret = ioctl(file2_fd, XFS_IOC_START_COMMIT, xcr);
if (ret)
return -errno;
return 0;
}
/*
* Execute an exchange-commit operation. Returns 0 for success or a negative
* errno.
*/
int
xfrog_commitrange(
int file2_fd,
struct xfs_commit_range *xcr,
uint64_t flags)
{
int ret;
xcr->flags = flags;
ret = ioctl(file2_fd, XFS_IOC_COMMIT_RANGE, xcr);
if (ret)
return -errno;
return 0;
}
/* Opaque freshness blob for XFS_IOC_COMMIT_RANGE */
struct xfs_commit_range_fresh {
xfs_fsid_t fsid; /* m_fixedfsid */
__u64 file2_ino; /* inode number */
__s64 file2_mtime; /* modification time */
__s64 file2_ctime; /* change time */
__s32 file2_mtime_nsec; /* mod time, nsec */
__s32 file2_ctime_nsec; /* change time, nsec */
__u32 file2_gen; /* inode generation */
__u32 magic; /* zero */
};
/* magic flag to force use of swapext */
#define XCR_SWAPEXT_MAGIC 0x43524150 /* CRAP */
/*
* Import file2 freshness information for a XFS_IOC_SWAPEXT call from bulkstat
* information. We can skip the fsid and file2_gen members because old swapext
* did not verify those things.
*/
static void
xfrog_swapext_prep(
struct xfs_commit_range *xdf,
const struct xfs_bulkstat *file2_stat)
{
struct xfs_commit_range_fresh *f;
f = (struct xfs_commit_range_fresh *)&xdf->file2_freshness;
f->file2_ino = file2_stat->bs_ino;
f->file2_mtime = file2_stat->bs_mtime;
f->file2_mtime_nsec = file2_stat->bs_mtime_nsec;
f->file2_ctime = file2_stat->bs_ctime;
f->file2_ctime_nsec = file2_stat->bs_ctime_nsec;
f->magic = XCR_SWAPEXT_MAGIC;
}
/* Invoke the old swapext ioctl. */
static int
xfrog_ioc_swapext(
int file2_fd,
struct xfs_commit_range *xdf)
{
struct xfs_swapext args = {
.sx_version = XFS_SX_VERSION,
.sx_fdtarget = file2_fd,
.sx_length = xdf->length,
.sx_fdtmp = xdf->file1_fd,
};
struct xfs_commit_range_fresh *f;
int ret;
BUILD_BUG_ON(sizeof(struct xfs_commit_range_fresh) !=
sizeof(xdf->file2_freshness));
f = (struct xfs_commit_range_fresh *)&xdf->file2_freshness;
args.sx_stat.bs_ino = f->file2_ino;
args.sx_stat.bs_mtime.tv_sec = f->file2_mtime;
args.sx_stat.bs_mtime.tv_nsec = f->file2_mtime_nsec;
args.sx_stat.bs_ctime.tv_sec = f->file2_ctime;
args.sx_stat.bs_ctime.tv_nsec = f->file2_ctime_nsec;
ret = ioctl(file2_fd, XFS_IOC_SWAPEXT, &args);
if (ret) {
/*
* Old swapext returns EFAULT if file1 or file2 length doesn't
* match. The new new COMMIT_RANGE doesn't check the file
* length, but the freshness checks will trip and return EBUSY.
* If we see EFAULT from the old ioctl, turn that into EBUSY.
*/
if (errno == EFAULT)
return -EBUSY;
return -errno;
}
return 0;
}
/*
* Prepare for defragmenting a file by committing a file contents exchange if
* if nobody changes file2 in the meantime by asking the kernel to sample
* file2's change attributes.
*
* If the kernel supports only the old XFS_IOC_SWAPEXT ioctl, the @file2_stat
* information will be used to sample the change attributes.
*
* Returns 0 or a negative errno.
*/
int
xfrog_defragrange_prep(
struct xfs_commit_range *xdf,
int file2_fd,
const struct xfs_bulkstat *file2_stat,
int file1_fd)
{
int ret;
memset(xdf, 0, sizeof(*xdf));
xdf->file1_fd = file1_fd;
xdf->length = file2_stat->bs_size;
ret = ioctl(file2_fd, XFS_IOC_START_COMMIT, xdf);
if (ret && (errno == EOPNOTSUPP || errno == ENOTTY)) {
xfrog_swapext_prep(xdf, file2_stat);
return 0;
}
if (ret)
return -errno;
return 0;
}
/* Execute an exchange operation. Returns 0 for success or a negative errno. */
int
xfrog_defragrange(
int file2_fd,
struct xfs_commit_range *xdf)
{
struct xfs_commit_range_fresh *f;
int ret;
f = (struct xfs_commit_range_fresh *)&xdf->file2_freshness;
if (f->magic == XCR_SWAPEXT_MAGIC)
goto legacy_fallback;
ret = ioctl(file2_fd, XFS_IOC_COMMIT_RANGE, xdf);
if (ret) {
if (errno == EOPNOTSUPP || errno != ENOTTY)
goto legacy_fallback;
return -errno;
}
return 0;
legacy_fallback:
ret = xfrog_ioc_swapext(file2_fd, xdf);
if (ret)
return -errno;
return 0;
}