mirror of
https://github.com/RsyncProject/rsync.git
synced 2026-09-14 22:28:57 -04:00
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.
107 lines
2.8 KiB
C
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;
|
|
}
|