Files
rsync/vfs/make_path.c
Andrew Tridgell 1214c4a2f9 vfs: unify stat/lstat, chmod, lchown into dirfd+flags form
Collapse the remaining three-form ops into one call each, matching
mkdir/mknod/symlink/unlink:
  vfs_stat(int dirfd, path, st, flags)
  vfs_lstat(int dirfd, path, st, flags)
  vfs_chmod(int dirfd, path, mode, flags)
  vfs_lchown(int dirfd, path, owner, group, flags)
dirfd == VFS_AT_FDCWD resolves the path (VFS_ALLOW_SYMLINK = plain libc op,
default 0 = secure receiver resolve, VFS_OPERATOR_PATH = ownership walk for
stat); a real held dirfd operates on a single component under it.  The old
plain/_at/_atfd bodies become static helpers behind the dispatchers;
vfs_fstat stays (fd-based).  chmod/lchown keep their no-owner-walk behaviour
(VFS_OPERATOR_PATH resolves the same as the default secure walk).

Pure API-narrowing -- the operator policy was already explicit; no global is
involved.  This is behaviour-preserving (plain sites -> VFS_ALLOW_SYMLINK,
_at -> the flag they already carried, _atfd -> held-fd 0).

Held-fd validation differs by op: mkdir/mknod/symlink/unlink reject "."/".."
(creating/removing them is nonsensical), but stat/chmod/lchown ALLOW "."
(a read or metadata op on the dir itself is legitimate -- link_stat_at and
set_file_attrs do it for directory entries); only empty and multi-component
("/") names are rejected there.  Caught by the suite (chmod/metadata/
ownership-depth + the implied-"." dir mkdir path).

Full suite 190/50.
2026-08-13 10:20:13 +10:00

107 lines
2.8 KiB
C

/*
* vfs/make_path.c - compound VFS op: create a directory path, making any
* missing parent components. Layered on the vfs_mkdir/vfs_stat primitives.
*
* Moved out of util1.c as part of the VFS compound layer: filesystem mechanics
* live in vfs/, so the operator-path resolution policy travels as an explicit
* vfs_flags argument rather than ambient state.
*
* 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 "vfs/vfs_internal.h"
/* Creates a directory path, making missing components as needed. mkp_flags is
* MKP_DROP_NAME / MKP_SKIP_SLASH (path handling); vfs_flags is the resolution
* policy passed straight to vfs_mkdir (VFS_OPERATOR_PATH for operator-supplied
* --backup-dir trees, else 0). Returns the number of dirs created, or a
* negative value on error. */
int vfs_make_path(char *fname, int mkp_flags, int vfs_flags)
{
char *end, *p;
int ret = 0;
if (mkp_flags & MKP_SKIP_SLASH) {
while (*fname == '/')
fname++;
}
while (*fname == '.' && fname[1] == '/')
fname += 2;
if (mkp_flags & MKP_DROP_NAME) {
end = strrchr(fname, '/');
if (!end || end == fname)
return 0;
*end = '\0';
} else
end = fname + strlen(fname);
/* Try to find an existing dir, starting from the deepest dir. */
for (p = end; ; ) {
if (dry_run) {
STRUCT_STAT st;
if (vfs_stat(VFS_AT_FDCWD, fname, &st, VFS_ALLOW_SYMLINK) == 0) {
if (S_ISDIR(st.st_mode))
errno = EEXIST;
else
errno = ENOTDIR;
}
} else if (vfs_mkdir(VFS_AT_FDCWD, fname, ACCESSPERMS, vfs_flags) == 0) {
ret++;
break;
}
if (errno != ENOENT) {
STRUCT_STAT st;
if (errno != EEXIST || (vfs_stat(VFS_AT_FDCWD, fname, &st, VFS_ALLOW_SYMLINK) == 0 && !S_ISDIR(st.st_mode)))
ret = -ret - 1;
break;
}
while (1) {
if (p == fname) {
/* We got a relative path that doesn't exist, so assume that '.'
* is there and just break out and create the whole thing. */
p = NULL;
goto double_break;
}
if (*--p == '/') {
if (p == fname) {
/* We reached the "/" dir, which we assume is there. */
goto double_break;
}
*p = '\0';
break;
}
}
}
double_break:
/* Make all the dirs that we didn't find on the way here. */
while (p != end) {
if (p)
*p = '/';
else
p = fname;
p += strlen(p);
if (ret < 0) /* Skip mkdir on error, but keep restoring the path. */
continue;
if (vfs_mkdir(VFS_AT_FDCWD, fname, ACCESSPERMS, vfs_flags) < 0)
ret = -ret - 1;
else
ret++;
}
if (mkp_flags & MKP_DROP_NAME)
*end = '/';
return ret;
}