Honor the symbolic link option when copying files

Inspect source links without following them when COPYFILE_SYM is requested, before rejecting directories. This makes the documented option recreate link text instead of copying the referent, including dangling and directory links. Preserve requested link timestamps and report link-creation errors without overwriting the destination. Cover direct calls, destination directories, normal dereferencing and the existing rsync file caller.
This commit is contained in:
omrzk authored and Joachim Wiberg committed 2026-11-14 12:41:18 +01:00
1 parent dff88a622d
commit 666a8ef7cd
5 files changed
+174 -14

No files matched your search

+17 -9
View File
@@ -157,7 +157,13 @@ ssize_t copyfile(const char *src, const char *dst, int len, int opt)
if (!buffer)
return 0;
if (fisdir(src)) {
if (sym ? lstat(src, &st) : stat(src, &st)) {
saved_errno = errno;
size = -1;
goto exit;
}
if (S_ISDIR(st.st_mode)) {
saved_errno = EISDIR; /* Error: source is a directory */
goto exit;
}
@@ -165,13 +171,7 @@ ssize_t copyfile(const char *src, const char *dst, int len, int opt)
/* Check if target is a directory, then append src filename. */
isdir = adjust_target(src, &dest);
/* Check if the source file is a symlink ... */
if (stat(src, &st)) {
size = -1;
goto exit;
}
/* ... only try readlink() if symlink and @sym is set. */
/* Only try readlink() if symlink and @sym is set. */
if (sym && (st.st_mode & S_IFMT) == S_IFLNK) {
size = readlink(src, buffer, BUFSIZ);
if (size > 0) {
@@ -181,8 +181,16 @@ ssize_t copyfile(const char *src, const char *dst, int len, int opt)
} else {
buffer[size] = 0;
size = !symlink(buffer, dest);
if (!size)
saved_errno = errno;
else if (keep_mtim) {
struct timespec tv[2] = { st.st_atim, st.st_mtim };
utimensat(AT_FDCWD, dest, tv, AT_SYMLINK_NOFOLLOW);
}
}
}
} else
saved_errno = errno;
/* Don't fall back to copy, that is a potentially
* dangerous race condition, see CWE-367. */
+40 -5
View File
@@ -24,6 +24,7 @@
#include <errno.h>
#include <fcntl.h> /* AT_* macros */
#include <libgen.h> /* dirname() */
#include <stdlib.h> /* NULL, free() */
#include <string.h> /* strlen() */
#include <strings.h> /* rindex() */
@@ -159,15 +160,49 @@ int rsync(char *src, char *dst, int opt, int (*filter)(const char *file))
static int copy(char *src, char *dst, int opt)
{
char *dest = NULL, *temp = NULL, *file = NULL;
int result = 1, saved_errno, made_temp = 0;
copyfile(src, dst, 0, opt | LITE_FOPT_COPYFILE_SYM);
if (errno) {
if (errno != EEXIST)
if (errno != EEXIST)
return errno ? 1 : 0;
/* Replace an existing destination only after the new link is ready. */
if (fisdir(dst)) {
char *base = strrchr(src, '/');
if (asprintf(&dest, "%s%s%s", dst, fisslashdir(dst) ? "" : "/", base ? base + 1 : src) < 0)
return 1;
} else {
dest = strdup(dst);
if (!dest)
return 1;
errno = 0;
}
if (asprintf(&temp, "%s/.rsync-XXXXXX", dirname(strdupa(dest))) < 0)
goto exit;
if (!mkdtemp(temp))
goto exit;
made_temp = 1;
if (asprintf(&file, "%s/link", temp) < 0)
goto exit;
return 0;
copyfile(src, file, 0, opt | LITE_FOPT_COPYFILE_SYM);
if (errno)
goto exit;
result = rename(file, dest) != 0;
exit:
saved_errno = errno;
if (file)
unlink(file);
if (made_temp)
rmdir(temp);
free(file);
free(temp);
free(dest);
errno = saved_errno;
return result;
}
/* Creates dir/name @mode ... skipping / if dir already ends so. */