Compare commits

...
15 Commits
Author SHA1 Message Date
Joachim Wiberg 798d70e2a2 Merge branch 'copyfile-symlinks'
Closes #23

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-11-14 12:49:17 +01:00
Joachim Wiberg 2f6a7f1595 Update ChangeLog with fcopyfile() fix
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-11-14 12:47:14 +01:00
Joachim Wiberg ef8b17f438 Do not let rsync() write through symlinks in the destination
With LITE_FOPT_RSYNC_LINKS, rsync() leaves symlinks in the destination.
If the source later has a regular file or a directory by the same name,
the next run follows the old link and writes to whatever it points at,
which can be outside the destination tree.

Remove such a link before copying.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-11-14 12:47:14 +01:00
Joachim Wiberg 4609c53ddd Make rsync() recreate symlinks only on request
Fixing LITE_FOPT_COPYFILE_SYM in copyfile() also changed rsync(), which
has always passed that flag.  Every caller would start getting links in
the destination where they used to get a copy of the file.

Keep following links by default and add LITE_FOPT_RSYNC_LINKS for
callers that want them recreated.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-11-14 12:46:48 +01:00
omrzk 666a8ef7cd 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.
2026-11-14 12:41:18 +01:00
Joachim Wiberg dff88a622d Merge pull request #22 from omrzk/fix/copy-binary-file-streams
Preserve binary data when copying file streams
2026-11-14 12:38:36 +01:00
omrzk 2dfc02e67d Preserve binary data when copying file streams
Copy the bytes returned by fread instead of treating each input chunk as a NUL-terminated string. Preserve the public stream positions and report read and write failures rather than returning success after a partial copy. Add binary boundary, current-position, native pipe error and full-device regression coverage.
2026-11-13 09:12:43 -06:00
Joachim Wiberg b69d8a6013 Fix copyfile() not preserving source mode with restrictive umask
copyfile() passes the source mode to open(2), which applies the umask,
so a 0644 file copied with umask 0027 ends up 0640.

Apply the source mode with fchmod() after creating the destination.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-09-13 18:53:40 +02:00
Joachim Wiberg e874608b76 Fix mkpath() ignoring mode when umask is more restrictive
mkpath(dir, 0755) with umask 0027 silently creates a 0750 directory,
since mkdir(2) applies the umask to the requested mode.  Callers pass
an explicit mode and expect exactly that.

Apply the mode with chmod() after each successful mkdir().

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2026-09-13 17:17:37 +02:00
Joachim Wiberg c1e050f93c Update ChangeLog and bump version for v2.6.2 release
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-11-22 21:59:46 +01:00
Joachim Wiberg 0e45929323 Make note of possible extensions
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-11-22 21:44:20 +01:00
Joachim Wiberg 958502d982 Fix memory leak in which() on realloc() failure
Avoid leaking memory when realloc() fails by using a temporary pointer
and only updating the original on success. Also remove redundant NULL
check before free().

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-11-22 20:48:35 +01:00
Joachim Wiberg f0cb0b7bf2 .github: bump actions
Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2025-04-28 08:34:00 +02:00
Joachim Wiberg ee5788d49b Create SECURITY.md policy 2025-04-28 07:54:35 +02:00
Joachim Wiberg 9bfe54f959 pidfile(): ensure prefix path ends with slash
For some builds using Buildroot, the system default _PATH_VARRUN did not
include a trailing slas '/', which cause the pidfile() function to fail.

Example:
  pidfile("arg") => "/var/runarg.pid"

Since the __pidfile_path can be overridden we add a check if that prefix
path ends with '/', if not we add one in the path composition.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
2024-03-25 05:23:41 +01:00
20 changed files with 537 additions and 47 deletions

No files matched your search

+12
View File
@@ -0,0 +1,12 @@
# Security Policy
## Supported Versions
libite (-lite) is a small project, as such we have no possibility to support older versions.
The only supported version is the latest released on GitHub:
<https://github.com/troglobit/libite/releases>
## Reporting a Vulnerability
Contact the project's main author and owner to report and discuss vulnerabilities.
+3 -3
View File
@@ -28,7 +28,7 @@ jobs:
run: |
sudo apt-get -y update
sudo apt-get -y install tree doxygen
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Configure
run: |
./autogen.sh
@@ -44,7 +44,7 @@ jobs:
run: |
make check || (cat test/test-suite.log; false)
- name: Upload Test Results
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: libite-test-${{ matrix.compiler }}
path: test/*
@@ -55,7 +55,7 @@ jobs:
env:
MAKEFLAGS: -j3
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Installing dependencies
run: |
apt-get update
+3 -3
View File
@@ -15,7 +15,7 @@ jobs:
coverity:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Fetch latest Coverity Scan MD5
id: var
env:
@@ -25,7 +25,7 @@ jobs:
--post-data "token=$TOKEN&project=${COVERITY_PROJ}&md5=1" \
-O coverity-latest.tar.gz.md5
echo "md5=$(cat coverity-latest.tar.gz.md5)" | tee -a $GITHUB_OUTPUT
- uses: actions/cache@v3
- uses: actions/cache@v4
id: cache
with:
path: coverity-latest.tar.gz
@@ -74,7 +74,7 @@ jobs:
--form description="${PROJECT_NAME} $(git rev-parse HEAD)" \
https://scan.coverity.com/builds?project=${COVERITY_PROJ}
- name: Upload build.log
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: coverity-build.log
path: cov-int/build-log.txt
+1 -1
View File
@@ -11,7 +11,7 @@ jobs:
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Installing dependencies ...
run: |
sudo apt-get -y update
+29 -1
View File
@@ -4,6 +4,33 @@ Change Log
All notable changes to the project are documented in this file.
[UNRELEASED][]
--------------
### Changes
- Add `LITE_FOPT_RSYNC_LINKS` option to `rsync()`, recreates symlinks to
files in the destination instead of copying what they point to
### Fixes
- Fix #23: `copyfile()` ignored `LITE_FOPT_COPYFILE_SYM` and always
copied what a symlink points to. It now recreates the symlink, also
when it is dangling or points to a directory
- Fix #22: `fcopyfile()` truncated binary data at the first NUL byte in
each chunk, and returned success on read and write errors
- Fix `copyfile()` not preserving the source file mode when the process
umask is more restrictive
- Fix `mkpath()`, and `fmkpath()`, not applying the given mode when the
process umask is more restrictive, e.g., requesting 0755 with umask
0027 silently created a 0750 directory
[v2.6.2][] - 2025-11-22
-----------------------
### Fixes
- Fix memory leak in `which()` on `realloc()` failure
- Fix `pidfile()` to handle missing trailing slash in prefix path
[v2.6.1][] - 2023-10-15
-----------------------
@@ -511,7 +538,8 @@ problems this may cause!
Initial extraction of frog DNA from [Finit][]. See [README][] for API details.
[UNRELEASED]: https://github.com/troglobit/libite/compare/v2.6.1...HEAD
[UNRELEASED]: https://github.com/troglobit/libite/compare/v2.6.2...HEAD
[v2.6.2]: https://github.com/troglobit/libite/compare/v2.6.1...v2.6.2
[v2.6.1]: https://github.com/troglobit/libite/compare/v2.6.0...v2.6.1
[v2.6.0]: https://github.com/troglobit/libite/compare/v2.5.3...v2.6.0
[v2.5.3]: https://github.com/troglobit/libite/compare/v2.5.2...v2.5.3
+14
View File
@@ -1 +1,15 @@
* TODO Write tests for new APIs
* More formatted variants?
- symlinkf()
- accessf()
- statf() / lstatf()
* Relocate from Finit
- fnread()
- fnwrite()
- fngetint()
- paste()
- unquote()
+1 -1
View File
@@ -1,4 +1,4 @@
AC_INIT(libite, 2.6.1, https://github.com/troglobit/libite/issues)
AC_INIT(libite, 2.6.2, https://github.com/troglobit/libite/issues)
AC_CONFIG_AUX_DIR(aux)
AM_INIT_AUTOMAKE([1.11 foreign dist-xz])
AM_SILENT_RULES([yes])
+10
View File
@@ -244,6 +244,16 @@ Generic Functions
- `LITE_FOPT_RSYNC_DELETE (0x01)`
- `LITE_FOPT_KEEP_MTIME (0x02)`
- `LITE_FOPT_RSYNC_LINKS (0x04)`
Symlinks in the source tree are followed by default, the destination
gets a copy of what they point to. With `LITE_FOPT_RSYNC_LINKS` a
symlink to a file, or a dangling symlink, is recreated in the
destination instead. Symlinks to directories are always followed.
With `LITE_FOPT_RSYNC_LINKS` a symlink in the destination is never
followed. If one sits where a file or directory from the source
goes, it is removed first.
In releases prior to v2.0 the argument controlling pruning was called
`delete`, it is now called `opt`. The APIs are 100% compatible if the
+41 -14
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. */
@@ -208,12 +216,20 @@ ssize_t copyfile(const char *src, const char *dst, int len, int opt)
goto exit;
}
/* open(2) applies umask, we want the source file mode */
if (fchmod(out, st.st_mode & 07777)) {
saved_errno = errno;
size = -1;
goto close_exit;
}
size = do_copy(in, out, num, buffer, BUFSIZ);
if (size < 0)
saved_errno = errno;
else if (keep_mtim)
set_mtime(in, out);
close_exit:
close(out);
close(in);
@@ -278,6 +294,8 @@ int movefile(const char *src, const char *dst)
int fcopyfile(FILE *src, FILE *dst)
{
char *buf;
size_t len;
int rc, saved_errno;
if (!src || !dst) {
errno = EINVAL;
@@ -288,12 +306,21 @@ int fcopyfile(FILE *src, FILE *dst)
if (!buf)
return -1;
while (fgets(buf, BUFSIZ, src))
fputs(buf, dst);
free(buf);
rc = 0;
while ((len = fread(buf, 1, BUFSIZ, src)) > 0) {
if (fwrite(buf, 1, len, dst) != len) {
rc = -1;
break;
}
}
if (ferror(src))
rc = -1;
return 0;
saved_errno = errno;
free(buf);
errno = saved_errno;
return rc;
}
/**
+1
View File
@@ -67,6 +67,7 @@ extern "C"
#define LITE_FOPT_RSYNC_DELETE 0x01 /**< Prune files from destination that are not in source */
#define LITE_FOPT_COPYFILE_SYM 0x01 /**< Recreate symlink or follow to copy target */
#define LITE_FOPT_KEEP_MTIME 0x02 /**< Preserve modification time */
#define LITE_FOPT_RSYNC_LINKS 0x04 /**< Recreate symlinks to files instead of copying their target */
typedef struct lfile lfile_t; /**< Opqaue context struct for lfile APIs */
typedef struct sdbuf sdbuf_t; /**< Opqaue context struct for telnet APIs */
+7 -1
View File
@@ -35,6 +35,9 @@
* @param dir Directory to created, relative or absolute
* @param mode A &mode_t mode to create @a dir with
*
* Directories are created with exactly @a mode, the process umask is
* not applied like it is for mkdir(2).
*
* @returns POSIX OK(0) on success, otherwise -1 with @a errno set.
*/
int mkpath(const char *dir, mode_t mode)
@@ -51,7 +54,10 @@ int mkpath(const char *dir, mode_t mode)
mkpath(dirname(strdupa(dir)), mode);
return mkdir(dir, mode);
if (mkdir(dir, mode))
return -1;
return chmod(dir, mode);
}
/**
+5 -1
View File
@@ -45,6 +45,7 @@
#include <paths.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#ifndef pidfile
@@ -93,7 +94,10 @@ int pidfile(const char *basename)
}
if (basename[0] != '/') {
if (asprintf(&pidfile_path, "%s%s.pid", __pidfile_path, basename) == -1)
size_t len = strlen(__pidfile_path);
int slash = __pidfile_path[len > 0 ? len - 1 : 0] != '/';
if (asprintf(&pidfile_path, "%s%s%s.pid", __pidfile_path, slash ? "/" : "", basename) == -1)
return (-1);
} else {
if (asprintf(&pidfile_path, "%s", basename) == -1)
+94 -13
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() */
@@ -35,7 +36,7 @@
#include "lite.h"
static int copy(char *src, char *dst, int opt);
static int mdir(char *buf, size_t buf_len, char *dir, char *name, struct stat *st);
static int mdir(char *buf, size_t buf_len, char *dir, char *name, struct stat *st, int opt);
static int prune(char *dst, char **new_files, int new_num);
static int set_mtime(char *fn, struct stat *st);
@@ -44,7 +45,8 @@ static int set_mtime(char *fn, struct stat *st);
* Synchronize contents and optionally remove non-existing backups
* @param src Source directory
* @param dst Destination directory
* @param opt An option mask of ::LITE_FOPT_RSYNC_DELETE, ::LITE_FOPT_KEEP_MTIME
* @param opt An option mask of ::LITE_FOPT_RSYNC_DELETE, ::LITE_FOPT_KEEP_MTIME,
* ::LITE_FOPT_RSYNC_LINKS
* @param filter Optional filtering function for source directory.
*
* This is a miniature implementation of the famous rsync for local use
@@ -59,6 +61,17 @@ static int set_mtime(char *fn, struct stat *st);
*
* %LITE_FOPT_RSYNC_DELETE: Prune files from @p dst that no longer exist in @p src.
* %LITE_FOPT_KEEP_MTIME: Preserve modification time
* %LITE_FOPT_RSYNC_LINKS: Recreate symlinks to files in @p dst
*
* Symlinks in @p src are followed by default, so @p dst gets a copy of
* what they point to. With ::LITE_FOPT_RSYNC_LINKS a symlink to a file,
* or a dangling symlink, is recreated in @p dst with the same target
* text, replacing any file or symlink already there. Symlinks to
* directories in @p src are always followed.
*
* With ::LITE_FOPT_RSYNC_LINKS a symlink in @p dst is never followed.
* If one sits where a file or directory from @p src goes, it is removed
* first.
*
* The filter callback, @p filter, if provided, is used to determine
* what files to include from the source directory when backing up. If
@@ -86,7 +99,7 @@ int rsync(char *src, char *dst, int opt, int (*filter)(const char *file))
if (!fexist(src))
return 1;
if (copy(src, dst, keep_mtim))
if (copy(src, dst, opt))
return 1;
return 0;
@@ -104,7 +117,7 @@ int rsync(char *src, char *dst, int opt, int (*filter)(const char *file))
if (stat(src, &st))
return 1;
if (mdir(dest, sizeof(dest), dst, ptr, &st))
if (mdir(dest, sizeof(dest), dst, ptr, &st, opt))
return 1;
dst = dest;
do_mtim = keep_mtim;
@@ -124,7 +137,7 @@ int rsync(char *src, char *dst, int opt, int (*filter)(const char *file))
continue;
}
if (mdir(dst2, sizeof(dst2), dst, files[i], &sb)) {
if (mdir(dst2, sizeof(dst2), dst, files[i], &sb, opt)) {
result++;
continue;
}
@@ -136,7 +149,7 @@ int rsync(char *src, char *dst, int opt, int (*filter)(const char *file))
continue; /* Next file/dir in @src to copy... */
}
if (copy(source, dst, keep_mtim))
if (copy(source, dst, opt))
result++;
}
@@ -157,23 +170,91 @@ int rsync(char *src, char *dst, int opt, int (*filter)(const char *file))
return result;
}
/* Leaves errno alone, a missing @path is not an error to rsync() */
static int islink(const char *path)
{
int rc, saved_errno = errno;
struct stat st;
rc = !lstat(path, &st) && S_ISLNK(st.st_mode);
errno = saved_errno;
return rc;
}
static int copy(char *src, char *dst, int opt)
{
copyfile(src, dst, 0, opt | LITE_FOPT_COPYFILE_SYM);
if (errno) {
if (errno != EEXIST)
return 1;
char *dest = NULL, *temp = NULL, *file = NULL;
int result = 1, saved_errno, made_temp = 0;
int copt = opt & LITE_FOPT_KEEP_MTIME;
errno = 0;
if (!(opt & LITE_FOPT_RSYNC_LINKS)) {
copyfile(src, dst, 0, copt);
return errno ? 1 : 0;
}
copt |= LITE_FOPT_COPYFILE_SYM;
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;
}
return 0;
/*
* A link in the destination, e.g. from a previous run, may point
* anywhere. Remove it so copyfile() cannot write through it.
*/
if (islink(dest) && !islink(src))
unlink(dest);
copyfile(src, dst, 0, copt);
if (errno != EEXIST) {
result = errno ? 1 : 0;
goto exit;
}
/* Replace an existing destination only after the new link is ready. */
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;
copyfile(src, file, 0, copt);
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. */
static int mdir(char *buf, size_t buf_len, char *dir, char *name, struct stat *st)
static int mdir(char *buf, size_t buf_len, char *dir, char *name, struct stat *st, int opt)
{
snprintf(buf, buf_len, "%s%s%s", dir, fisslashdir(dir) ? "" : "/", name);
/* Same as in copy(), do not descend into a stale link. */
if ((opt & LITE_FOPT_RSYNC_LINKS) && islink(buf))
unlink(buf);
if (mkdir(buf, st->st_mode)) {
if (EEXIST != errno)
return 1;
+9 -6
View File
@@ -53,8 +53,9 @@ static char *strip_args(char *path)
*/
char *which(const char *cmd)
{
char *ptr, *env, *path = NULL;
size_t pathlen = 0;
char *ptr, *tok, *env, *path = NULL;
const char *tok;
if (!cmd) {
errno = EINVAL;
@@ -69,9 +70,7 @@ char *which(const char *cmd)
if (!access(path, X_OK))
return path;
if (path)
free(path);
free(path);
return NULL;
}
@@ -88,11 +87,15 @@ char *which(const char *cmd)
size_t len = strlen(tok) + strlen(cmd) + 2;
if (pathlen < len) {
path = realloc(path, len);
if (!path) {
char *tmp = realloc(path, len);
if (!tmp) {
free(path);
free(env);
return NULL;
}
path = tmp;
pathlen = len;
}
+2
View File
@@ -4,6 +4,8 @@
.deps/
/chomp
/copyfile
/fcopyfile
/copyfile-symlink
/dir
/fexist
/fisdir
+3
View File
@@ -19,6 +19,8 @@ clean-local:
TESTS =
TESTS += chomp
TESTS += copyfile
TESTS += fcopyfile
TESTS += copyfile-symlink
TESTS += dir
TESTS += fexist
TESTS += fisdir
@@ -40,3 +42,4 @@ TESTS += which
TESTS += yorn
check_PROGRAMS = $(TESTS)
copyfile_symlink_SOURCES = copyfile-symlink.c
+152
View File
@@ -0,0 +1,152 @@
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include "check.h"
static void check_link(const char *path, const char *target)
{
char buf[256];
struct stat st;
ssize_t len;
fail_unless(lstat(path, &st) == 0 && S_ISLNK(st.st_mode));
len = readlink(path, buf, sizeof(buf));
fail_unless(len == (ssize_t)strlen(target));
fail_unless(memcmp(buf, target, len) == 0);
}
/* A link left in the destination by an earlier run must never be written through. */
static void rsync_stale_links(void)
{
struct stat st;
FILE *fp;
fail_unless(mkdir("src", 0700) == 0 && mkdir("dst", 0700) == 0);
fail_unless(touch("victim") == 0);
fail_unless(symlink("../victim", "src/a") == 0);
fail_unless(symlink("../victims", "src/d") == 0);
fail_unless(rsync("src/", "dst/", LITE_FOPT_RSYNC_LINKS, NULL) == 0);
check_link("dst/a", "../victim");
check_link("dst/d", "../victims");
/* The source links become a file and a directory, and dst/d stops dangling. */
fail_unless(unlink("src/a") == 0 && unlink("src/d") == 0);
fp = fopen("src/a", "w");
fail_unless(fp && fputs("data", fp) >= 0 && fclose(fp) == 0);
fail_unless(mkdir("src/d", 0700) == 0 && touch("src/d/f") == 0);
fail_unless(mkdir("victims", 0700) == 0);
fail_unless(rsync("src/", "dst/", LITE_FOPT_RSYNC_LINKS, NULL) == 0);
fail_unless(lstat("dst/a", &st) == 0 && S_ISREG(st.st_mode) && st.st_size == 4);
fail_unless(stat("victim", &st) == 0 && st.st_size == 0);
fail_unless(lstat("dst/d", &st) == 0 && S_ISDIR(st.st_mode));
fail_unless(fexist("dst/d/f") && !fexist("victims/f"));
fail_unless(unlink("src/a") == 0 && unlink("src/d/f") == 0 && rmdir("src/d") == 0);
fail_unless(unlink("dst/a") == 0 && unlink("dst/d/f") == 0 && rmdir("dst/d") == 0);
fail_unless(rmdir("src") == 0 && rmdir("dst") == 0);
fail_unless(unlink("victim") == 0 && rmdir("victims") == 0);
}
int main(void)
{
char tmp[] = "/tmp/libite-copylink-XXXXXX";
const char payload[] = "target contents";
char buf[sizeof(payload)];
const char *targets[] = { "file", "dir", "missing", ".", "link" };
struct timespec times[2] = { { 1234567800, 0 }, { 1234567801, 0 } };
struct stat src_st, dst_st;
char cwd[4096];
size_t i;
FILE *fp;
fail_unless(getcwd(cwd, sizeof(cwd)) && mkdtemp(tmp));
fail_unless(chdir(tmp) == 0);
fail_unless(mkdir("dir", 0700) == 0);
fail_unless(mkdir("out", 0700) == 0);
fp = fopen("file", "w");
fail_unless(fp && fwrite(payload, 1, sizeof(payload), fp) == sizeof(payload));
fail_unless(fclose(fp) == 0);
for (i = 0; i < NELEMS(targets); i++) {
fail_unless(symlink(targets[i], "link") == 0);
fail_unless(copyfile("link", "copied", 0, LITE_FOPT_COPYFILE_SYM) == 1);
check_link("copied", targets[i]);
fail_unless(copyfile("link", "out", 0, LITE_FOPT_COPYFILE_SYM) == 1);
check_link("out/link", targets[i]);
/* Existing targets must survive a failed symlink creation. */
errno = 0;
fail_unless(copyfile("link", "file", 0, LITE_FOPT_COPYFILE_SYM) == 0);
fail_unless(errno == EEXIST);
check_link("copied", targets[i]);
fail_unless(unlink("copied") == 0 && unlink("out/link") == 0);
fail_unless(unlink("link") == 0);
}
fail_unless(symlink("file", "link") == 0);
/* Without SYM, continue to copy target data and preserve regular-file metadata. */
fail_unless(copyfile("link", "copied", 0, LITE_FOPT_KEEP_MTIME) == sizeof(payload));
fail_unless(lstat("copied", &dst_st) == 0 && S_ISREG(dst_st.st_mode));
fail_unless(stat("file", &src_st) == 0 && src_st.st_mtime == dst_st.st_mtime);
fp = fopen("copied", "r");
fail_unless(fp && fread(buf, 1, sizeof(buf), fp) == sizeof(payload));
fail_unless(memcmp(buf, payload, sizeof(payload)) == 0 && fclose(fp) == 0);
fail_unless(unlink("copied") == 0);
fail_unless(utimensat(AT_FDCWD, "link", times, AT_SYMLINK_NOFOLLOW) == 0);
fail_unless(copyfile("link", "copied", 0, LITE_FOPT_COPYFILE_SYM | LITE_FOPT_KEEP_MTIME) == 1);
check_link("copied", "file");
fail_unless(lstat("copied", &dst_st) == 0 && dst_st.st_mtime == times[1].tv_sec);
fail_unless(unlink("copied") == 0);
/* rsync() follows links unless asked to recreate them. */
fail_unless(rsync("link", "copied", 0, NULL) == 0);
fail_unless(lstat("copied", &dst_st) == 0 && S_ISREG(dst_st.st_mode));
fail_unless(dst_st.st_size == sizeof(payload));
fail_unless(unlink("copied") == 0);
fail_unless(rsync("link", "copied", LITE_FOPT_RSYNC_LINKS, NULL) == 0);
check_link("copied", "file");
/* Reuse the destination after changing the source link. */
fail_unless(unlink("link") == 0 && symlink("file", "link") == 0);
fail_unless(rsync("link", "out", LITE_FOPT_RSYNC_LINKS, NULL) == 0);
check_link("out/link", "file");
fail_unless(unlink("link") == 0 && symlink("copied", "link") == 0);
fail_unless(rsync("link", "out", LITE_FOPT_RSYNC_LINKS, NULL) == 0);
check_link("out/link", "copied");
/* A same-path copy must not unlink its source. */
fail_unless(rsync("link", "link", LITE_FOPT_RSYNC_LINKS, NULL) == 0);
check_link("link", "copied");
fail_unless(unlink("out/link") == 0);
/* Replacing a regular destination must also retain the source link. */
fail_unless(touch("out/link") == 0);
fail_unless(rsync("link", "out", LITE_FOPT_RSYNC_LINKS, NULL) == 0);
check_link("out/link", "copied");
fail_unless(unlink("out/link") == 0);
/* A failed replacement must keep both the destination and source. */
fail_unless(mkdir("out/link", 0700) == 0);
fail_unless(touch("out/link/keep") == 0);
fail_unless(rsync("link", "out", LITE_FOPT_RSYNC_LINKS, NULL) != 0);
fail_unless(fexist("out/link/keep"));
check_link("link", "copied");
fail_unless(unlink("out/link/keep") == 0 && rmdir("out/link") == 0);
if (getuid() != 0) {
fail_unless(mkdir("protected", 0700) == 0);
fail_unless(symlink("original", "protected/link") == 0);
fail_unless(chmod("protected", 0500) == 0);
fail_unless(rsync("link", "protected", LITE_FOPT_RSYNC_LINKS, NULL) != 0);
check_link("protected/link", "original");
check_link("link", "copied");
fail_unless(chmod("protected", 0700) == 0);
fail_unless(unlink("protected/link") == 0 && rmdir("protected") == 0);
}
fail_unless(unlink("copied") == 0 && unlink("link") == 0);
rsync_stale_links();
errno = 0;
fail_unless(copyfile("dir", "copied", 0, LITE_FOPT_COPYFILE_SYM) == 0 && errno == EISDIR);
errno = 0;
fail_unless(copyfile("missing", "copied", 0, LITE_FOPT_COPYFILE_SYM) == -1 && errno == ENOENT);
fail_unless(unlink("file") == 0 && rmdir("dir") == 0 && rmdir("out") == 0);
fail_unless(chdir(cwd) == 0 && rmdir(tmp) == 0);
return 0;
}
+24 -1
View File
@@ -123,9 +123,32 @@ static int _copyfile()
return rc;
}
/* copyfile() must preserve source mode, even with a restrictive umask */
static int _mode(void)
{
const char *src = "/tmp/libite-mode-src", *dst = "/tmp/libite-mode-dst";
struct stat st;
mode_t old;
int rc = 1;
if (touch(src) || chmod(src, 0644))
return test(1, "copyfile() mode, prepare %s", src);
old = umask(0077);
if (copyfile(src, dst, 0, 0) >= 0 && !stat(dst, &st))
rc = (st.st_mode & 07777) != 0644;
umask(old);
erase(src);
erase(dst);
return test(rc, "copyfile() preserves mode 0644 with umask 0077");
}
int main(void)
{
return _fcopyfile() ||
_copyfile() ||
_sz();
_sz() ||
_mode();
}
+92
View File
@@ -0,0 +1,92 @@
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include "check.h"
static void copy_binary(size_t len)
{
unsigned char data[3 * BUFSIZ + 37], copied[sizeof(data)];
const char prefix[] = "destination prefix";
FILE *src = tmpfile(), *dst = tmpfile();
size_t i;
fail_unless(src && dst);
for (i = 0; i < sizeof(data); i++)
data[i] = i % 253;
/* Begin at the current stream positions, preserving the destination prefix. */
fail_unless(fwrite(prefix, 1, sizeof(prefix), src) == sizeof(prefix));
fail_unless(fwrite(data, 1, len, src) == len);
fail_unless(fseek(src, sizeof(prefix), SEEK_SET) == 0);
fail_unless(fwrite(prefix, 1, sizeof(prefix), dst) == sizeof(prefix));
fail_unless(fcopyfile(src, dst) == 0);
fail_unless(ftell(src) == (long)(sizeof(prefix) + len));
fail_unless(ftell(dst) == (long)(sizeof(prefix) + len));
fail_unless(fseek(dst, 0, SEEK_SET) == 0);
fail_unless(fread(copied, 1, sizeof(prefix), dst) == sizeof(prefix));
fail_unless(memcmp(copied, prefix, sizeof(prefix)) == 0);
fail_unless(fread(copied, 1, sizeof(copied), dst) == len);
fail_unless(memcmp(copied, data, len) == 0);
fail_unless(fgetc(dst) == EOF && feof(dst));
fail_unless(fclose(src) == 0);
fail_unless(fclose(dst) == 0);
}
static void read_error(int partial)
{
const char data[] = "bytes before the read error";
char copied[sizeof(data)];
int fd[2];
FILE *src, *dst = tmpfile();
fail_unless(dst && pipe(fd) == 0);
fail_unless(fcntl(fd[0], F_SETFL, O_NONBLOCK) == 0);
if (partial)
fail_unless(write(fd[1], data, sizeof(data)) == sizeof(data));
src = fdopen(fd[0], "r");
fail_unless(src);
errno = 0;
fail_unless(fcopyfile(src, dst) == -1);
fail_unless(errno == EAGAIN || errno == EWOULDBLOCK);
fail_unless(ferror(src));
fail_unless(ftell(dst) == (long)(partial ? sizeof(data) : 0));
if (partial) {
rewind(dst);
fail_unless(fread(copied, 1, sizeof(copied), dst) == sizeof(data));
fail_unless(memcmp(copied, data, sizeof(data)) == 0);
}
fclose(src);
fclose(dst);
close(fd[1]);
}
int main(void)
{
const size_t lengths[] = { 0, 1, BUFSIZ - 1, BUFSIZ, BUFSIZ + 1, 3 * BUFSIZ + 37 };
size_t i;
for (i = 0; i < NELEMS(lengths); i++)
copy_binary(lengths[i]);
read_error(0);
read_error(1);
#ifdef __linux__
FILE *src = tmpfile();
FILE *dst = fopen("/dev/full", "w");
fail_unless(src && dst);
fail_unless(fputs("data", src) >= 0);
rewind(src);
fail_unless(setvbuf(dst, NULL, _IONBF, 0) == 0);
errno = 0;
fail_unless(fcopyfile(src, dst) == -1);
fail_unless(errno == ENOSPC);
fail_unless(ferror(dst));
fclose(src);
fclose(dst);
#endif
errno = 0;
fail_unless(fcopyfile(NULL, NULL) == -1 && errno == EINVAL);
return 0;
}
+34 -2
View File
@@ -50,6 +50,34 @@ int test_fmkpath(const char *subdir)
return ret;
}
/* mkpath() must apply mode as given, not mode & ~umask */
int test_umask(void)
{
const char *dirs[] = { BASEDIR "/um", BASEDIR "/um/sub", NULL };
mode_t old = umask(0077);
struct stat sb;
int i, ret;
ret = mkpath(dirs[1], 0755);
umask(old);
if (ret) {
perror("Failed");
return ret;
}
for (i = 0; dirs[i]; i++) {
if (checkpath(dirs[i]) || stat(dirs[i], &sb))
return 1;
if ((sb.st_mode & 07777) != 0755) {
fprintf(stderr, "Failed: %s mode %04o, expected 0755\n",
dirs[i], sb.st_mode & 07777);
return 1;
}
}
return 0;
}
int main(void)
{
int i, ret = 0;
@@ -66,14 +94,18 @@ int main(void)
mkdir(BASEDIR, 0755);
printf("Pass 1/2: makepath() =========================\n");
printf("Pass 1/3: makepath() =========================\n");
for (i = 0; list[i] && !ret; i++)
ret |= test_makepath(list[i]);
printf("Pass 2/2: fmkpath() ==========================\n");
printf("Pass 2/3: fmkpath() ==========================\n");
for (i = 0; list[i] && !ret; i++)
ret |= test_fmkpath(list[i] + strlen(BASEDIR) + 1);
printf("Pass 3/3: mkpath() with umask ================\n");
if (!ret)
ret |= test_umask();
printf("Done: Cleaning up ============================\n");
system("rm -rf " BASEDIR);