From: "Darrick J. Wong" <djwong@kernel.org>
To: Norbert Szetei <norbert@doyensec.com>
Cc: Dave Chinner <dgc@kernel.org>, Carlos Maiolino <cem@kernel.org>,
linux-xfs@vger.kernel.org, Christoph Hellwig <hch@infradead.org>
Subject: Re: [PATCH v2] xfs: stop exchanging reflink flags when exchanging mappings
Date: Wed, 7 Oct 2026 09:55:39 -0700 [thread overview]
Message-ID: <20261007165539.GF2705364@frogsfrogsfrogs> (raw)
In-Reply-To: <1E97806E-3236-4BC4-A3F5-8197FE07F077@doyensec.com>
On Wed, Oct 07, 2026 at 04:46:39PM +0200, Norbert Szetei wrote:
> Here is a reproducer for the XFS_IOC_SWAPEXT case, since there wasn't
> one on the list yet, in case it's useful for fstests.
>
> $ truncate -s 512M img && mkfs.xfs -q -m rmapbt=1,reflink=1 img
> $ mkdir -p /tmp/m && sudo mount -o loop img /tmp/m && sudo chmod 1777 /tmp/m
> $ sudo xfs_io -f -c "pwrite -q -S 0x50 0 4096" /tmp/m/peer
> $ sudo chmod 644 /tmp/m/peer
> $ cd /tmp/m && ./xfs_swapext_poc
>
> On an unpatched kernel it reports peer as rewritten. With v2 applied,
> peer is left intact. It needs rmapbt=1.
Thanks for the third repro, I'll kludge that into a fstests too.
--D
> // SPDX-License-Identifier: GPL-2.0
> #define _GNU_SOURCE
> #include <stdio.h>
> #include <stdlib.h>
> #include <string.h>
> #include <unistd.h>
> #include <fcntl.h>
> #include <sys/ioctl.h>
> #include <sys/stat.h>
> #include <sys/vfs.h>
> #include <linux/fs.h>
> #include <linux/fiemap.h>
>
> #ifndef FICLONERANGE
> #define FICLONERANGE _IOW(0x94, 13, struct file_clone_range)
> #endif
>
> /* fs/xfs/libxfs/xfs_fs.h */
> struct xfs_exchange_range {
> __s32 file1_fd;
> __u32 pad;
> __u64 file1_offset;
> __u64 file2_offset;
> __u64 length;
> __u64 flags;
> };
> #define XFS_IOC_EXCHANGE_RANGE _IOW('X', 129, struct xfs_exchange_range)
> #define XFS_EXCHANGE_RANGE_TO_EOF (1ULL << 0)
>
> typedef struct xfs_bstime {
> long tv_sec;
> __s32 tv_nsec;
> } xfs_bstime_t;
>
> struct xfs_bstat {
> __u64 bs_ino;
> __u16 bs_mode;
> __u16 bs_nlink;
> __u32 bs_uid;
> __u32 bs_gid;
> __u32 bs_rdev;
> __s32 bs_blksize;
> __s64 bs_size;
> xfs_bstime_t bs_atime;
> xfs_bstime_t bs_mtime;
> xfs_bstime_t bs_ctime;
> __s64 bs_blocks;
> __u32 bs_xflags;
> __s32 bs_extsize;
> __s32 bs_extents;
> __u32 bs_gen;
> __u16 bs_projid_lo;
> __u16 bs_forkoff;
> __u16 bs_projid_hi;
> __u16 bs_sick;
> __u16 bs_checked;
> unsigned char bs_pad[2];
> __u32 bs_cowextsize;
> __u32 bs_dmevmask;
> __u16 bs_dmstate;
> __u16 bs_aextents;
> };
>
> struct xfs_swapext {
> __s64 sx_version;
> #define XFS_SX_VERSION 0
> __s64 sx_fdtarget;
> __s64 sx_fdtmp;
> __s64 sx_offset;
> __s64 sx_length;
> char sx_pad[16];
> struct xfs_bstat sx_stat;
> };
> #define XFS_IOC_SWAPEXT _IOWR('X', 109, struct xfs_swapext)
>
> static unsigned long blksz;
> static int peer, f1;
>
> static unsigned long long phys(int fd, unsigned long long off)
> {
> struct { struct fiemap f; struct fiemap_extent e[1]; } q;
>
> memset(&q, 0, sizeof q);
> q.f.fm_start = off;
> q.f.fm_length = blksz;
> q.f.fm_extent_count = 1;
> if (ioctl(fd, FS_IOC_FIEMAP, &q.f) || q.f.fm_mapped_extents < 1)
> return 0;
> return (q.e[0].fe_physical + (off - q.e[0].fe_logical)) / blksz;
> }
>
> static void show(const char *tag)
> {
> struct stat st;
> int i;
>
> fstat(f1, &st);
> printf("%-26s peer [%llu] file1 size %5lld [", tag, phys(peer, 0),
> (long long)st.st_size);
> for (i = 0; i < 3; i++)
> printf("%llu%s", phys(f1, (unsigned long long)i * blksz),
> i == 2 ? "]\n" : "] [");
> }
>
> static int mkfile(const char *name, char fill, int blocks)
> {
> char *buf;
> int fd;
>
> unlink(name);
> fd = open(name, O_RDWR | O_CREAT | O_EXCL, 0600);
> if (fd < 0)
> return fprintf(stderr, "open %s: %m\n", name), -1;
> buf = malloc(blocks * blksz);
> memset(buf, fill, blocks * blksz);
> if (pwrite(fd, buf, blocks * blksz, 0) != (ssize_t)(blocks * blksz))
> return fprintf(stderr, "pwrite %s: %m\n", name), -1;
> free(buf);
> fsync(fd);
> return fd;
> }
>
> int main(void)
> {
> struct xfs_exchange_range xr;
> struct file_clone_range cr;
> struct xfs_swapext sx;
> char before[65536], after[65536], *buf;
> struct statfs sfs;
> struct stat st;
> int d1, d2;
>
> if (statfs(".", &sfs))
> return fprintf(stderr, "statfs: %m\n"), 2;
> blksz = sfs.f_bsize;
>
> peer = open("peer", O_RDONLY);
> if (peer < 0)
> return fprintf(stderr, "open peer: %m\n"), 2;
> fstat(peer, &st);
> printf("peer uid %u mode %04o size %lld, block size %lu\n\n",
> st.st_uid, st.st_mode & 07777, (long long)st.st_size, blksz);
> if (pread(peer, before, blksz, 0) != (ssize_t)blksz)
> return fprintf(stderr, "read peer: %m\n"), 2;
> syncfs(peer); /* so FIEMAP reports peer's real block */
>
> f1 = mkfile("file1", 'A', 3);
> d1 = mkfile("donor1", 'B', 1);
> d2 = mkfile("donor2", 'C', 1);
> if (f1 < 0 || d1 < 0 || d2 < 0)
> return 2;
> show("start");
>
> /* 1. share peer's block into the middle of file1 */
> cr = (struct file_clone_range){ .src_fd = peer, .src_offset = 0,
> .src_length = blksz,
> .dest_offset = blksz };
> if (ioctl(f1, FICLONERANGE, &cr))
> return fprintf(stderr, "FICLONERANGE: %m\n"), 2;
> show("1 FICLONERANGE");
>
> /*
> * 2. exchange file1's last block against the whole of donor1 with
> * TO_EOF. The sizes are exchanged too, so file1 claims one block
> * while still owning three. file2_offset is block 2, so this exchange
> * cannot clear a reflink flag.
> */
> xr = (struct xfs_exchange_range){ .file1_fd = d1, .file1_offset = 0,
> .file2_offset = 2 * blksz,
> .length = 0,
> .flags = XFS_EXCHANGE_RANGE_TO_EOF };
> if (ioctl(f1, XFS_IOC_EXCHANGE_RANGE, &xr))
> return fprintf(stderr, "EXCHANGE_RANGE TO_EOF: %m\n"), 2;
> show("2 EXCHANGE_RANGE TO_EOF");
>
> /*
> * 3. XFS_IOC_SWAPEXT with file1 as the target. sx_offset is 0 and
> * sx_length equals both inodes' i_disk_size, so the "Verify all data
> * are being swapped" test passes. xfs_swap_extent_rmap() then bounds
> * its remap loop with XFS_B_TO_FSB(mp, i_size_read(VFS_I(ip))), which
> * is one block, so file1's two mappings above its size are left in
> * place -- and the reflink flags are swapped anyway, clearing file1's.
> */
> fsync(f1);
> fsync(d2);
> if (fstat(f1, &st))
> return fprintf(stderr, "fstat file1: %m\n"), 2;
> memset(&sx, 0, sizeof sx);
> sx.sx_version = XFS_SX_VERSION;
> sx.sx_fdtarget = f1;
> sx.sx_fdtmp = d2;
> sx.sx_offset = 0;
> sx.sx_length = st.st_size;
> sx.sx_stat.bs_mtime.tv_sec = st.st_mtim.tv_sec;
> sx.sx_stat.bs_mtime.tv_nsec = st.st_mtim.tv_nsec;
> sx.sx_stat.bs_ctime.tv_sec = st.st_ctim.tv_sec;
> sx.sx_stat.bs_ctime.tv_nsec = st.st_ctim.tv_nsec;
> if (ioctl(f1, XFS_IOC_SWAPEXT, &sx))
> return fprintf(stderr, "SWAPEXT: %m\n"), 2;
> show("3 SWAPEXT");
>
> /* 4. write to the block file1 still shares with peer */
> buf = malloc(blksz);
> memset(buf, 'X', blksz);
> if (pwrite(f1, buf, blksz, blksz) != (ssize_t)blksz)
> return fprintf(stderr, "pwrite: %m\n"), 2;
> fsync(f1);
> show("4 pwrite(file1, 4096)");
>
> posix_fadvise(peer, 0, 0, POSIX_FADV_DONTNEED);
> if (pread(peer, after, blksz, 0) != (ssize_t)blksz)
> return fprintf(stderr, "re-read peer: %m\n"), 2;
> printf("\npeer first byte %c -> %c %s\n", before[0], after[0],
> memcmp(before, after, blksz) ? "PEER REWRITTEN" : "peer intact");
> return memcmp(before, after, blksz) ? 0 : 1;
> }
>
>
next prev parent reply other threads:[~2026-10-07 16:55 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-07 14:43 [PATCH v2] xfs: stop exchanging reflink flags when exchanging mappings Norbert Szetei
2026-10-07 14:46 ` Norbert Szetei
2026-10-07 16:55 ` Darrick J. Wong [this message]
2026-10-07 16:54 ` Darrick J. Wong
2026-10-07 20:31 ` Dave Chinner
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261007165539.GF2705364@frogsfrogsfrogs \
--to=djwong@kernel.org \
--cc=cem@kernel.org \
--cc=dgc@kernel.org \
--cc=hch@infradead.org \
--cc=linux-xfs@vger.kernel.org \
--cc=norbert@doyensec.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox