Files
Andrew Tridgell 4c142f671b vfs: adapt the merged-in base changes to the VFS layer
Linear-rebase counterpart of the conflict resolutions made when the
sec-fixes base was integrated (see the merge for reference):

- Port ddda7ba5's operator-path confinement of do_symlink_at and
  do_rmdir_at into the relocated VFS code.  vfs__symlink_secure gains
  the VFS_OPERATOR_PATH ownership-walk branch (parent confined via
  vfs_owner_walk_parent, shared leaf-creation preserved so fake-super
  emulation still applies); vfs__unlink_secure extends its existing
  operator branch to the rmdir/AT_REMOVEDIR case.  Callers pass the
  policy explicitly where the base set operator_path_resolve: the
  keep_backup symlink create (backup.c), the backup-tree rmdir in
  delete_item (delete.c, DEL_FOR_BACKUP), and handle_partial_dir's
  rmdir (util1.c).
- Port 1f8f89c2's robust_rename EXDEV-fallback confinement into
  vfs/robust.c: an absolute --temp-dir/--partial-dir operand routes the
  copy_file dest-write and the source-unlink through the ownership walk
  (VFS_OPERATOR_PATH), so a raced parent symlink can't redirect either
  out of the module.
- Drop the stale "no ownership-walk branch" notes in vfs/vfs.h and
  vfs/symlink.c now that symlink and rmdir carry the branch.

Tree is byte-identical to the validated merge result.
2026-08-13 10:20:13 +10:00

147 lines
4.5 KiB
C

/*
* vfs/robust.c - compound VFS ops: robust unlink/rename that retry around
* busy files (ETXTBSY) and fall back to a cross-filesystem copy. Layered on
* the vfs_* unlink/rename primitives and vfs_copy_file; calls out to the
* partial-dir handler for the EXDEV copy path.
*
* Moved out of util1.c as part of the VFS compound layer.
*
* Copyright (C) 1996-2022 Andrew Tridgell, Paul Mackerras, 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 "vfs/vfs_internal.h"
/* MAX_RENAMES should be 10**MAX_RENAMES_DIGITS */
#define MAX_RENAMES_DIGITS 3
#define MAX_RENAMES 1000
/**
* Robust unlink: some OS'es (HPUX) refuse to unlink busy files, so
* rename to <path>/.rsyncNNN instead.
*
* Note that successive rsync runs will shuffle the filenames around a
* bit as long as the file is still busy; this is because this function
* does not know if the unlink call is due to a new file coming in, or
* --delete trying to remove old .rsyncNNN files, hence it renames it
* each time.
**/
int robust_unlink(const char *fname, int vfs_flags)
{
#ifndef ETXTBSY
return vfs_unlink(VFS_AT_FDCWD, fname, vfs_flags);
#else
static int counter = 1;
int rc, pos, start;
char path[MAXPATHLEN];
rc = vfs_unlink(VFS_AT_FDCWD, fname, vfs_flags);
if (rc == 0 || errno != ETXTBSY)
return rc;
if ((pos = strlcpy(path, fname, MAXPATHLEN)) >= MAXPATHLEN)
pos = MAXPATHLEN - 1;
while (pos > 0 && path[pos-1] != '/')
pos--;
pos += strlcpy(path+pos, ".rsync", MAXPATHLEN-pos);
if (pos > (MAXPATHLEN-MAX_RENAMES_DIGITS-1)) {
errno = ETXTBSY;
return -1;
}
/* start where the last one left off to reduce chance of clashes */
start = counter;
do {
snprintf(&path[pos], MAX_RENAMES_DIGITS+1, "%03d", counter);
if (++counter >= MAX_RENAMES)
counter = 1;
} while (access(path, 0) == 0 && counter != start);
if (INFO_GTE(MISC, 1)) {
rprintf(FWARNING, "renaming %s to %s because of text busy\n",
fname, path);
}
/* maybe we should return rename()'s exit status? Nah. path is a sibling of
* fname in the same parent, so both sides share fname's policy (vfs_flags). */
if (vfs_rename_at(fname, path, vfs_flags, vfs_flags) != 0) {
errno = ETXTBSY;
return -1;
}
return 0;
#endif
}
/* Returns 0 on successful rename, 1 if we successfully copied the file
* across filesystems, -2 if copy_file() failed, and -1 on other errors.
* If partialptr is not NULL and we need to do a copy, copy the file into
* the active partial-dir instead of over the destination file. */
int robust_rename(const char *from, const char *to, const char *partialptr,
int mode, struct file_struct *file)
{
int tries = 4;
/* A resumed in-place partial-dir transfer might call us with from and
* to pointing to the same buf if the transfer failed yet again. */
if (from == to)
return 0;
while (tries--) {
/* tmp -> final usually live in the entry's own dir: rename via the
* held dir fd when both do, else the full-path wrapper. */
int ofd = vfs_cached_dirfd(from, file);
int nfd = vfs_cached_dirfd(to, file);
int rr;
if (ofd >= 0 && nfd >= 0) {
const char *os = strrchr(from, '/');
const char *ns = strrchr(to, '/');
rr = vfs_rename_atfd(ofd, os ? os + 1 : from, nfd, ns ? ns + 1 : to);
} else
rr = vfs_rename_at(from, to, 0, 0);
if (rr == 0)
return 0;
switch (errno) {
#ifdef ETXTBSY
case ETXTBSY:
if (robust_unlink(to, 0) != 0) {
errno = ETXTBSY;
return -1;
}
errno = ETXTBSY;
break;
#endif
case EXDEV:
if (partialptr) {
if (!handle_partial_dir(partialptr,PDIR_CREATE))
return -2;
to = partialptr;
}
/* Cross-fs fallback: copy then unlink. An absolute --temp-dir
* source / --partial-dir dest is an operator path whose parents
* the plain-libc arm would otherwise follow -- confine them
* through the ownership walk (VFS_OPERATOR_PATH) so a raced
* parent symlink can't redirect the dest-write or the
* source-unlink out of the module. copy_file already confines
* the source READ; a relative in-module path stays on the
* secure-relative arm, so only flag an absolute (operator)
* path. */
if (copy_file(from, to, -1, mode, *to == '/' ? VFS_OPERATOR_PATH : 0) != 0)
return -2;
vfs_unlink(VFS_AT_FDCWD, from, *from == '/' ? VFS_OPERATOR_PATH : 0);
return 1;
default:
return -1;
}
}
return -1;
}