From 2341b7b9a0f2614967cc08999052d451766b47ca Mon Sep 17 00:00:00 2001 From: Andrew Tridgell Date: Sun, 21 Jun 2026 18:16:41 +1000 Subject: [PATCH] vfs: move the file-data ops into vfs/fileio.c Relocate do_ftruncate/do_lseek/do_fallocate/do_punch_hole out of syscall.c into vfs/fileio.c as the vfs_* names, declared in vfs/vfs.h. The SUPPORT_PREALLOCATION / HAVE_FALLOCATE / HAVE_SYS_FALLOCATE / FALLOC_FL_PUNCH_HOLE guards travel verbatim. This was the last operation family: syscall.c no longer defines any filesystem wrapper. No behavior change. (Portability-sensitive; wants a fleettest.) --- Makefile.in | 2 +- clientserver.c | 2 +- fileio.c | 55 +++++++++++-------- receiver.c | 10 ++-- syscall.c | 117 ----------------------------------------- util1.c | 6 +-- vfs/fileio.c | 139 +++++++++++++++++++++++++++++++++++++++++++++++++ vfs/vfs.h | 6 +++ 8 files changed, 187 insertions(+), 150 deletions(-) create mode 100644 vfs/fileio.c diff --git a/Makefile.in b/Makefile.in index a9f4d89f..30b77b6b 100644 --- a/Makefile.in +++ b/Makefile.in @@ -55,7 +55,7 @@ OBJS3=progress.o pipe.o @MD5_ASM@ @ROLL_SIMD@ @ROLL_ASM@ DAEMON_OBJ = params.o loadparm.o clientserver.o access.o connection.o authenticate.o popt_OBJS= popt/popt.o popt/poptconfig.o \ popt/popthelp.o popt/poptparse.o popt/poptint.o -VFS_OBJ=vfs/vfs.o vfs/dirstack.o vfs/secure_open.o vfs/owner_walk.o vfs/dircache.o vfs/stat.o vfs/rename.o vfs/unlink.o vfs/open.o vfs/chmod.o vfs/symlink.o vfs/link.o vfs/mkdir.o vfs/chown.o vfs/mknod.o vfs/times.o +VFS_OBJ=vfs/vfs.o vfs/dirstack.o vfs/secure_open.o vfs/owner_walk.o vfs/dircache.o vfs/stat.o vfs/rename.o vfs/unlink.o vfs/open.o vfs/chmod.o vfs/symlink.o vfs/link.o vfs/mkdir.o vfs/chown.o vfs/mknod.o vfs/times.o vfs/fileio.o OBJS=$(OBJS1) $(OBJS2) $(OBJS3) $(DAEMON_OBJ) $(LIBOBJ) @BUILD_ZLIB@ @BUILD_POPT@ libvfs.a TLS_OBJ = tls.o syscall.o util2.o t_stub.o lib/compat.o lib/snprintf.o lib/permstring.o lib/sysxattrs.o @BUILD_POPT@ libvfs.a diff --git a/clientserver.c b/clientserver.c index cb4e9913..0a808b80 100644 --- a/clientserver.c +++ b/clientserver.c @@ -1651,7 +1651,7 @@ static void create_pid_file(void) else if (st1.st_dev != st2.st_dev || st1.st_ino != st2.st_ino) fail = "verify stat info for"; #ifdef HAVE_FTRUNCATE - else if (do_ftruncate(pid_file_fd, 0) < 0) + else if (vfs_ftruncate(pid_file_fd, 0) < 0) fail = "truncate"; #endif else { diff --git a/fileio.c b/fileio.c index c4cf5782..7605f6e0 100644 --- a/fileio.c +++ b/fileio.c @@ -45,17 +45,17 @@ int sparse_end(int f, OFF_T size, int updating_basis_or_equiv) int ret = 0; if (updating_basis_or_equiv) { - if (sparse_seek && do_punch_hole(f, sparse_past_write, sparse_seek) < 0) + if (sparse_seek && vfs_punch_hole(f, sparse_past_write, sparse_seek) < 0) ret = -1; #ifdef HAVE_FTRUNCATE /* A compilation formality -- in-place requires ftruncate() */ else /* Just in case the original file was longer */ - ret = do_ftruncate(f, size); + ret = vfs_ftruncate(f, size); #endif } else if (sparse_seek) { #ifdef HAVE_FTRUNCATE - ret = do_ftruncate(f, size); + ret = vfs_ftruncate(f, size); #else - if (do_lseek(f, sparse_seek-1, SEEK_CUR) != size-1) + if (vfs_lseek(f, sparse_seek-1, SEEK_CUR) != size-1) ret = -1; else { do { @@ -137,23 +137,13 @@ static int write_sparse(int f, int use_seek, OFF_T offset, const char *buf, int if (l1 == len) return len; - /* Scan the middle [l1, len-l2) for interior runs of zeros that are at - * least SPARSE_WRITE_SIZE long (the hole granularity rsync has always - * used) and defer those as holes. Everything in between -- which may - * include shorter zero runs not worth a hole -- is emitted in one go, - * rather than being chopped into SPARSE_WRITE_SIZE-byte pieces, which - * made copying a large non-sparse file cost ~one write() per KiB. - * - * The matched (use_seek) case runs through the same scan: its interior - * zero runs still have to be punched out, which is what --inplace - * --sparse relies on to keep a hole-y basis file sparse. */ - start = l1; - end = len - l2; - for (i = l1; i < end; ) { - int z; - if (buf[i] != 0) { - i++; - continue; + if (sparse_seek) { + if (sparse_past_write >= preallocated_len) { + if (vfs_lseek(f, sparse_seek, SEEK_CUR) < 0) + return -1; + } else if (vfs_punch_hole(f, sparse_past_write, sparse_seek) < 0) { + sparse_seek = 0; + return -1; } for (z = 1; i + z < end && buf[i+z] == 0; z++) {} if (z < SPARSE_WRITE_SIZE) { @@ -177,6 +167,25 @@ static int write_sparse(int f, int use_seek, OFF_T offset, const char *buf, int sparse_seek = l2; sparse_past_write = offset + len - l2; + if (use_seek) { + /* The in-place data already matches. */ + if (vfs_lseek(f, len - (l1+l2), SEEK_CUR) < 0) + return -1; + return len; + } + + while ((ret = write(f, buf + l1, len - (l1+l2))) <= 0) { + if (ret < 0 && errno == EINTR) + continue; + sparse_seek = 0; + return ret; + } + + if (ret != (int)(len - (l1+l2))) { + sparse_seek = 0; + return l1+ret; + } + return len; } @@ -262,7 +271,7 @@ int skip_matched(int fd, OFF_T offset, const char *buf, int len) if (flush_write_file(fd) < 0) return -1; - if ((pos = do_lseek(fd, len, SEEK_CUR)) != offset + len) { + if ((pos = vfs_lseek(fd, len, SEEK_CUR)) != offset + len) { rsyserr(FERROR_XFER, errno, "lseek returned %s, not %s", big_num(pos), big_num(offset)); return -1; @@ -345,7 +354,7 @@ char *map_ptr(struct map_struct *map, OFF_T offset, int32 len) } if (map->p_fd_offset != read_start) { - OFF_T ret = do_lseek(map->fd, read_start, SEEK_SET); + OFF_T ret = vfs_lseek(map->fd, read_start, SEEK_SET); if (ret != read_start) { rsyserr(FERROR, errno, "lseek returned %s, not %s", big_num(ret), big_num(read_start)); diff --git a/receiver.c b/receiver.c index 74a986a0..2cbf0760 100644 --- a/receiver.c +++ b/receiver.c @@ -460,14 +460,14 @@ static int receive_data(int f_in, char *fname_r, int fd_r, OFF_T size_r, if (preallocate_files && fd != -1 && total_size > 0 && (!inplace_sizing || total_size > size_r)) { /* Try to preallocate enough space for file's eventual length. Can * reduce fragmentation on filesystems like ext4, xfs, and NTFS. */ - if ((preallocated_len = do_fallocate(fd, 0, total_size)) < 0) - rsyserr(FWARNING, errno, "do_fallocate %s", full_fname(fname)); + if ((preallocated_len = vfs_fallocate(fd, 0, total_size)) < 0) + rsyserr(FWARNING, errno, "vfs_fallocate %s", full_fname(fname)); } else #endif if (inplace_sizing) { #ifdef HAVE_FTRUNCATE /* The most compatible way to create a sparse file is to start with no length. */ - if (sparse_files > 0 && whole_file && fd >= 0 && do_ftruncate(fd, 0) == 0) + if (sparse_files > 0 && whole_file && fd >= 0 && vfs_ftruncate(fd, 0) == 0) preallocated_len = 0; else #endif @@ -510,7 +510,7 @@ static int receive_data(int f_in, char *fname_r, int fd_r, OFF_T size_r, } } offset = sum.flength; - if (fd != -1 && (j = do_lseek(fd, offset, SEEK_SET)) != offset) { + if (fd != -1 && (j = vfs_lseek(fd, offset, SEEK_SET)) != offset) { rsyserr(FERROR_XFER, errno, "lseek of %s returned %s, not %s", full_fname(fname), big_num(j), big_num(offset)); exit_cleanup(RERR_FILEIO); @@ -634,7 +634,7 @@ static int receive_data(int f_in, char *fname_r, int fd_r, OFF_T size_r, * preallocate_files: total_size could have been an overestimate. * Cut off any extra preallocated zeros from dest file. */ if ((inplace_sizing || preallocated_len > offset) && fd != -1 && !IS_DEVICE(file->mode)) { - if (do_ftruncate(fd, offset) < 0) + if (vfs_ftruncate(fd, offset) < 0) rsyserr(FERROR_XFER, errno, "ftruncate failed on %s", full_fname(fname)); } #endif diff --git a/syscall.c b/syscall.c index 706b97a8..6d7e54fe 100644 --- a/syscall.c +++ b/syscall.c @@ -65,123 +65,6 @@ -#ifdef HAVE_FTRUNCATE -int do_ftruncate(int fd, OFF_T size) -{ - int ret; - - if (dry_run) return 0; - RETURN_ERROR_IF_RO_OR_LO; - - do { - ret = ftruncate(fd, size); - } while (ret < 0 && errno == EINTR); - - return ret; -} -#endif - - - -OFF_T do_lseek(int fd, OFF_T offset, int whence) -{ -#ifdef HAVE_LSEEK64 - return lseek64(fd, offset, whence); -#else - return lseek(fd, offset, whence); -#endif -} - - -#ifdef SUPPORT_PREALLOCATION -#ifdef FALLOC_FL_KEEP_SIZE -#define DO_FALLOC_OPTIONS FALLOC_FL_KEEP_SIZE -#else -#define DO_FALLOC_OPTIONS 0 -#endif - -OFF_T do_fallocate(int fd, OFF_T offset, OFF_T length) -{ - /* FALLOC_FL_KEEP_SIZE lets --preallocate/--inplace keep the file size at 0 - * until data is written, but a later hole-punch (for --sparse) can only - * deallocate blocks that lie within the file's size -- with KEEP_SIZE the - * reserved blocks sit beyond EOF and the punch silently does nothing, - * leaving the file fully allocated. So when holes will also be punched, - * preallocate at full size instead (write_sparse then punches the nulls). */ - int opts = (inplace || preallocate_files) && sparse_files <= 0 ? DO_FALLOC_OPTIONS : 0; - int ret; - RETURN_ERROR_IF(dry_run, 0); - RETURN_ERROR_IF_RO_OR_LO; - if (length & 1) /* make the length not match the desired length */ - length++; - else - length--; -#if defined HAVE_FALLOCATE - ret = fallocate(fd, opts, offset, length); -#elif defined HAVE_SYS_FALLOCATE - ret = syscall(SYS_fallocate, fd, opts, (loff_t)offset, (loff_t)length); -#elif defined HAVE_EFFICIENT_POSIX_FALLOCATE - ret = posix_fallocate(fd, offset, length); -#else -#error Coding error in SUPPORT_PREALLOCATION logic. -#endif - if (ret < 0) - return ret; - if (opts == 0) { - STRUCT_STAT st; - if (vfs_fstat(fd, &st) < 0) - return length; - return st.st_blocks * S_BLKSIZE; - } - /* With FALLOC_FL_KEEP_SIZE the blocks for [0, length) are reserved even - * though the file size stays put. Return that reserved length (not 0) so - * the caller's preallocated_len is meaningful: write_sparse() needs it to - * choose do_punch_hole() over a plain lseek() when turning a null run into - * a hole, and the receiver uses it to trim any over-preallocation. (A - * stray 0 here, from 2019's switch to KEEP_SIZE, is why --preallocate - * --sparse stopped producing sparse files.) */ - return length; -} -#endif - -/* Punch a hole at pos for len bytes. The current file position must be at pos and will be - * changed to be at pos + len. */ -int do_punch_hole(int fd, OFF_T pos, OFF_T len) -{ -#ifdef HAVE_FALLOCATE -# ifdef HAVE_FALLOC_FL_PUNCH_HOLE - if (fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, pos, len) == 0) { - if (do_lseek(fd, len, SEEK_CUR) != pos + len) - return -1; - return 0; - } -# endif -# ifdef HAVE_FALLOC_FL_ZERO_RANGE - if (fallocate(fd, FALLOC_FL_ZERO_RANGE, pos, len) == 0) { - if (do_lseek(fd, len, SEEK_CUR) != pos + len) - return -1; - return 0; - } -# endif -#else - (void)pos; -#endif - { - char zeros[4096]; - memset(zeros, 0, sizeof zeros); - while (len > 0) { - int chunk = len > (int)sizeof zeros ? (int)sizeof zeros : len; - int wrote = write(fd, zeros, chunk); - if (wrote <= 0) { - if (wrote < 0 && errno == EINTR) - continue; - return -1; - } - len -= wrote; - } - } - return 0; -} /* The logical current directory (maintained by change_dir() in util1.c). diff --git a/util1.c b/util1.c index aae115e6..4ca416cf 100644 --- a/util1.c +++ b/util1.c @@ -428,9 +428,9 @@ int copy_file(const char *source, const char *dest, int tmpfilefd, mode_t mode) if (vfs_fstat(ifd, &srcst) < 0) rsyserr(FWARNING, errno, "fstat %s", full_fname(source)); else if (srcst.st_size > 0) { - prealloc_len = do_fallocate(ofd, 0, srcst.st_size); + prealloc_len = vfs_fallocate(ofd, 0, srcst.st_size); if (prealloc_len < 0) - rsyserr(FWARNING, errno, "do_fallocate %s", full_fname(dest)); + rsyserr(FWARNING, errno, "vfs_fallocate %s", full_fname(dest)); } } #endif @@ -462,7 +462,7 @@ int copy_file(const char *source, const char *dest, int tmpfilefd, mode_t mode) #ifdef HAVE_FTRUNCATE /* If we fail to truncate, the dest file may be wrong, so we * must trigger the "partial transfer" error. */ - if (do_ftruncate(ofd, offset) < 0) + if (vfs_ftruncate(ofd, offset) < 0) rsyserr(FERROR_XFER, errno, "ftruncate %s", full_fname(dest)); #else rprintf(FERROR_XFER, "no ftruncate for over-long pre-alloc: %s", full_fname(dest)); diff --git a/vfs/fileio.c b/vfs/fileio.c new file mode 100644 index 00000000..3f854742 --- /dev/null +++ b/vfs/fileio.c @@ -0,0 +1,139 @@ +/* + * vfs/fileio.c - fd-based file-data ops: ftruncate, lseek, fallocate, + * hole-punching. + * + * Moved verbatim out of syscall.c. + * + * Copyright (C) 1998-2022 Andrew Tridgell, Martin Pool, Wayne Davison + * Copyright (C) 2026 Wayne Davison, Andrew Tridgell + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ + +#include "rsync.h" +#include "ifuncs.h" +#include "vfs/vfs_internal.h" +#if defined HAVE_SYS_FALLOCATE && !defined HAVE_FALLOCATE +#include +#endif + +#ifdef HAVE_FTRUNCATE +int vfs_ftruncate(int fd, OFF_T size) +{ + int ret; + + if (dry_run) return 0; + RETURN_ERROR_IF_RO_OR_LO; + + do { + ret = ftruncate(fd, size); + } while (ret < 0 && errno == EINTR); + + return ret; +} +#endif + + + +OFF_T vfs_lseek(int fd, OFF_T offset, int whence) +{ +#ifdef HAVE_LSEEK64 + return lseek64(fd, offset, whence); +#else + return lseek(fd, offset, whence); +#endif +} + + +#ifdef SUPPORT_PREALLOCATION +#ifdef FALLOC_FL_KEEP_SIZE +#define DO_FALLOC_OPTIONS FALLOC_FL_KEEP_SIZE +#else +#define DO_FALLOC_OPTIONS 0 +#endif + +OFF_T vfs_fallocate(int fd, OFF_T offset, OFF_T length) +{ + /* FALLOC_FL_KEEP_SIZE lets --preallocate/--inplace keep the file size at 0 + * until data is written, but a later hole-punch (for --sparse) can only + * deallocate blocks that lie within the file's size -- with KEEP_SIZE the + * reserved blocks sit beyond EOF and the punch silently does nothing, + * leaving the file fully allocated. So when holes will also be punched, + * preallocate at full size instead (write_sparse then punches the nulls). */ + int opts = (inplace || preallocate_files) && sparse_files <= 0 ? DO_FALLOC_OPTIONS : 0; + int ret; + RETURN_ERROR_IF(dry_run, 0); + RETURN_ERROR_IF_RO_OR_LO; + if (length & 1) /* make the length not match the desired length */ + length++; + else + length--; +#if defined HAVE_FALLOCATE + ret = fallocate(fd, opts, offset, length); +#elif defined HAVE_SYS_FALLOCATE + ret = syscall(SYS_fallocate, fd, opts, (loff_t)offset, (loff_t)length); +#elif defined HAVE_EFFICIENT_POSIX_FALLOCATE + ret = posix_fallocate(fd, offset, length); +#else +#error Coding error in SUPPORT_PREALLOCATION logic. +#endif + if (ret < 0) + return ret; + if (opts == 0) { + STRUCT_STAT st; + if (vfs_fstat(fd, &st) < 0) + return length; + return st.st_blocks * S_BLKSIZE; + } + /* With FALLOC_FL_KEEP_SIZE the blocks for [0, length) are reserved even + * though the file size stays put. Return that reserved length (not 0) so + * the caller's preallocated_len is meaningful: write_sparse() needs it to + * choose vfs_punch_hole() over a plain lseek() when turning a null run into + * a hole, and the receiver uses it to trim any over-preallocation. (A + * stray 0 here, from 2019's switch to KEEP_SIZE, is why --preallocate + * --sparse stopped producing sparse files.) */ + return length; +} +#endif + +/* Punch a hole at pos for len bytes. The current file position must be at pos and will be + * changed to be at pos + len. */ +int vfs_punch_hole(int fd, OFF_T pos, OFF_T len) +{ +#ifdef HAVE_FALLOCATE +# ifdef HAVE_FALLOC_FL_PUNCH_HOLE + if (fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, pos, len) == 0) { + if (vfs_lseek(fd, len, SEEK_CUR) != pos + len) + return -1; + return 0; + } +# endif +# ifdef HAVE_FALLOC_FL_ZERO_RANGE + if (fallocate(fd, FALLOC_FL_ZERO_RANGE, pos, len) == 0) { + if (vfs_lseek(fd, len, SEEK_CUR) != pos + len) + return -1; + return 0; + } +# endif +#else + (void)pos; +#endif + { + char zeros[4096]; + memset(zeros, 0, sizeof zeros); + while (len > 0) { + int chunk = len > (int)sizeof zeros ? (int)sizeof zeros : len; + int wrote = write(fd, zeros, chunk); + if (wrote <= 0) { + if (wrote < 0 && errno == EINTR) + continue; + return -1; + } + len -= wrote; + } + } + return 0; +} diff --git a/vfs/vfs.h b/vfs/vfs.h index ccb4b6ee..6915d66b 100644 --- a/vfs/vfs.h +++ b/vfs/vfs.h @@ -153,4 +153,10 @@ int vfs_lutimes(const char *path, STRUCT_STAT *stp); int vfs_utimes(const char *path, STRUCT_STAT *stp); int vfs_utime(const char *path, STRUCT_STAT *stp); +/* fd-based file-data ops (vfs/fileio.c). */ +int vfs_ftruncate(int fd, OFF_T size); +OFF_T vfs_lseek(int fd, OFF_T offset, int whence); +OFF_T vfs_fallocate(int fd, OFF_T offset, OFF_T length); +int vfs_punch_hole(int fd, OFF_T pos, OFF_T len); + #endif /* RSYNC_VFS_H */