Skip to content

Instantly share code, notes, and snippets.

@teknoraver
Created July 6, 2026 21:06
Show Gist options
  • Select an option

  • Save teknoraver/67b50ce366d7cf430bb7f82a8018acfa to your computer and use it in GitHub Desktop.

Select an option

Save teknoraver/67b50ce366d7cf430bb7f82a8018acfa to your computer and use it in GitHub Desktop.
XFS truncate after clone PoC
/* clone_bench - measure the per-file cost of FICLONERANGE on many
small files, compared to a plain copy.
Creates NFILES files of SIZE bytes each, in the current directory,
from a common source file, in one of three ways:
write plain write(2) of the whole content
clone-trunc FICLONERANGE of size rounded up to 4k, then
ftruncate to the real size (what a program cloning
a non-block-aligned range out of a larger file,
like tar --reflink, has to do)
clone-tail FICLONERANGE of the whole 4k blocks, then write(2)
of the final partial block (no truncation)
write-trunc write(2) of size rounded up to 4k, then ftruncate
to the real size (same shrink as clone-trunc but on
unshared extents)
Prints the loop time, the syncfs time (deferred writeback and log
traffic), and the per-file cost broken down by syscall.
gcc -O2 -o clone_bench clone_bench.c
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
#include <sys/ioctl.h>
#include <linux/fs.h>
#define BLOCK 4096L
static double now(void)
{
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return ts.tv_sec + ts.tv_nsec / 1e9;
}
int main(int argc, char **argv)
{
if (argc != 4) {
fprintf(stderr, "usage: %s write|clone-trunc|clone-tail|write-trunc <nfiles> <size>\n", argv[0]);
return 1;
}
const char *mode = argv[1];
long n = atol(argv[2]);
long size = atol(argv[3]);
long whole = size / BLOCK * BLOCK;
long src_len = (size + BLOCK - 1) / BLOCK * BLOCK;
char *buf = malloc(src_len);
memset(buf, 'x', src_len);
int src = open("src.dat", O_RDWR | O_CREAT | O_TRUNC, 0644);
if (src < 0 || write(src, buf, src_len) != src_len || fsync(src) < 0) {
perror("src.dat");
return 1;
}
double t_open = 0, t_data = 0, t_trunc = 0, t_close = 0;
double t0 = now();
for (long i = 0; i < n; i++) {
char name[64];
double ta, tb, tc;
snprintf(name, sizeof name, "f%06ld", i);
ta = now();
int fd = open(name, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd < 0) {
perror(name);
return 1;
}
tb = now();
t_open += tb - ta;
if (!strcmp(mode, "write")) {
if (write(fd, buf, size) != size) {
perror("write");
return 1;
}
tc = now();
t_data += tc - tb;
} else if (!strcmp(mode, "write-trunc")) {
if (write(fd, buf, src_len) != src_len) {
perror("write");
return 1;
}
tc = now();
t_data += tc - tb;
if (ftruncate(fd, size) < 0) {
perror("ftruncate");
return 1;
}
tb = now();
t_trunc += tb - tc;
tc = tb;
} else {
long clone_len = !strcmp(mode, "clone-trunc") ? src_len : whole;
struct file_clone_range fcr = {
.src_fd = src,
.src_offset = 0,
.src_length = clone_len,
};
if (clone_len && ioctl(fd, FICLONERANGE, &fcr) < 0) {
perror("FICLONERANGE");
return 1;
}
tc = now();
t_data += tc - tb;
if (!strcmp(mode, "clone-trunc")) {
if (ftruncate(fd, size) < 0) {
perror("ftruncate");
return 1;
}
} else if (whole < size) {
if (lseek(fd, whole, SEEK_SET) < 0
|| write(fd, buf + whole, size - whole) != size - whole) {
perror("tail write");
return 1;
}
}
tb = now();
t_trunc += tb - tc;
tc = tb;
}
close(fd);
t_close += now() - tc;
}
double t1 = now();
int dirfd = open(".", O_RDONLY);
if (dirfd < 0 || syncfs(dirfd) < 0) {
perror("syncfs");
return 1;
}
double t2 = now();
printf("%-12s n=%ld size=%ld loop=%6.3fs sync=%6.3fs total=%6.3fs us/file: open=%.1f %s=%.1f %s=%.1f close=%.1f\n",
mode, n, size, t1 - t0, t2 - t1, t2 - t0,
t_open * 1e6 / n,
!strncmp(mode, "write", 5) ? "write" : "clone", t_data * 1e6 / n,
strstr(mode, "trunc") ? "trunc" : "tail", t_trunc * 1e6 / n,
t_close * 1e6 / n);
return 0;
}
#!/bin/sh
# usage: ./run.sh <dir-on-target-fs> [nfiles]
# Runs the write / clone-tail / clone-trunc matrix over a few file
# sizes, each in a fresh subdirectory, and prints system info for the
# bug report.
set -e
dir=${1:?usage: $0 <dir-on-target-fs> [nfiles]}
n=${2:-20000}
bench=$(cd "$(dirname "$0")" && pwd)/clone_bench
uname -r
grep -w "$(stat -f -c %T "$dir" >/dev/null 2>&1; df --output=source "$dir" | tail -1)" /proc/mounts || true
stat -f -c 'fstype=%T bsize=%S' "$dir"
command -v xfs_info >/dev/null && xfs_info "$dir" 2>/dev/null | head -6 || true
echo
for size in 1000 5000 20000 65536 90000; do
for mode in write write-trunc clone-tail clone-trunc; do
sub=$dir/bench-$mode-$size
rm -rf "$sub"; mkdir -p "$sub"
(cd "$sub" && "$bench" "$mode" "$n" "$size")
rm -rf "$sub"
sync
done
echo
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment