Compare commits

..
7 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
9 changed files with 394 additions and 25 deletions

No files matched your search

+9
View File
@@ -7,7 +7,16 @@ All notable changes to the project are documented in this file.
[UNRELEASED][] [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 ### 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 - Fix `copyfile()` not preserving the source file mode when the process
umask is more restrictive umask is more restrictive
- Fix `mkpath()`, and `fmkpath()`, not applying the given mode when the - Fix `mkpath()`, and `fmkpath()`, not applying the given mode when the
+10
View File
@@ -244,6 +244,16 @@ Generic Functions
- `LITE_FOPT_RSYNC_DELETE (0x01)` - `LITE_FOPT_RSYNC_DELETE (0x01)`
- `LITE_FOPT_KEEP_MTIME (0x02)` - `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 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 `delete`, it is now called `opt`. The APIs are 100% compatible if the
+30 -11
View File
@@ -157,7 +157,13 @@ ssize_t copyfile(const char *src, const char *dst, int len, int opt)
if (!buffer) if (!buffer)
return 0; 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 */ saved_errno = EISDIR; /* Error: source is a directory */
goto exit; 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. */ /* Check if target is a directory, then append src filename. */
isdir = adjust_target(src, &dest); isdir = adjust_target(src, &dest);
/* Check if the source file is a symlink ... */ /* Only try readlink() if symlink and @sym is set. */
if (stat(src, &st)) {
size = -1;
goto exit;
}
/* ... only try readlink() if symlink and @sym is set. */
if (sym && (st.st_mode & S_IFMT) == S_IFLNK) { if (sym && (st.st_mode & S_IFMT) == S_IFLNK) {
size = readlink(src, buffer, BUFSIZ); size = readlink(src, buffer, BUFSIZ);
if (size > 0) { if (size > 0) {
@@ -181,8 +181,16 @@ ssize_t copyfile(const char *src, const char *dst, int len, int opt)
} else { } else {
buffer[size] = 0; buffer[size] = 0;
size = !symlink(buffer, dest); 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 /* Don't fall back to copy, that is a potentially
* dangerous race condition, see CWE-367. */ * dangerous race condition, see CWE-367. */
@@ -286,6 +294,8 @@ int movefile(const char *src, const char *dst)
int fcopyfile(FILE *src, FILE *dst) int fcopyfile(FILE *src, FILE *dst)
{ {
char *buf; char *buf;
size_t len;
int rc, saved_errno;
if (!src || !dst) { if (!src || !dst) {
errno = EINVAL; errno = EINVAL;
@@ -296,12 +306,21 @@ int fcopyfile(FILE *src, FILE *dst)
if (!buf) if (!buf)
return -1; return -1;
while (fgets(buf, BUFSIZ, src)) rc = 0;
fputs(buf, dst); while ((len = fread(buf, 1, BUFSIZ, src)) > 0) {
if (fwrite(buf, 1, len, dst) != len) {
rc = -1;
break;
}
}
if (ferror(src))
rc = -1;
saved_errno = errno;
free(buf); free(buf);
errno = saved_errno;
return 0; 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_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_COPYFILE_SYM 0x01 /**< Recreate symlink or follow to copy target */
#define LITE_FOPT_KEEP_MTIME 0x02 /**< Preserve modification time */ #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 lfile lfile_t; /**< Opqaue context struct for lfile APIs */
typedef struct sdbuf sdbuf_t; /**< Opqaue context struct for telnet APIs */ typedef struct sdbuf sdbuf_t; /**< Opqaue context struct for telnet APIs */
+95 -14
View File
@@ -24,6 +24,7 @@
#include <errno.h> #include <errno.h>
#include <fcntl.h> /* AT_* macros */ #include <fcntl.h> /* AT_* macros */
#include <libgen.h> /* dirname() */
#include <stdlib.h> /* NULL, free() */ #include <stdlib.h> /* NULL, free() */
#include <string.h> /* strlen() */ #include <string.h> /* strlen() */
#include <strings.h> /* rindex() */ #include <strings.h> /* rindex() */
@@ -35,7 +36,7 @@
#include "lite.h" #include "lite.h"
static int copy(char *src, char *dst, int opt); 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 prune(char *dst, char **new_files, int new_num);
static int set_mtime(char *fn, struct stat *st); 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 * Synchronize contents and optionally remove non-existing backups
* @param src Source directory * @param src Source directory
* @param dst Destination 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. * @param filter Optional filtering function for source directory.
* *
* This is a miniature implementation of the famous rsync for local use * 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_RSYNC_DELETE: Prune files from @p dst that no longer exist in @p src.
* %LITE_FOPT_KEEP_MTIME: Preserve modification time * %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 * The filter callback, @p filter, if provided, is used to determine
* what files to include from the source directory when backing up. If * 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)) if (!fexist(src))
return 1; return 1;
if (copy(src, dst, keep_mtim)) if (copy(src, dst, opt))
return 1; return 1;
return 0; return 0;
@@ -104,7 +117,7 @@ int rsync(char *src, char *dst, int opt, int (*filter)(const char *file))
if (stat(src, &st)) if (stat(src, &st))
return 1; return 1;
if (mdir(dest, sizeof(dest), dst, ptr, &st)) if (mdir(dest, sizeof(dest), dst, ptr, &st, opt))
return 1; return 1;
dst = dest; dst = dest;
do_mtim = keep_mtim; do_mtim = keep_mtim;
@@ -124,7 +137,7 @@ int rsync(char *src, char *dst, int opt, int (*filter)(const char *file))
continue; continue;
} }
if (mdir(dst2, sizeof(dst2), dst, files[i], &sb)) { if (mdir(dst2, sizeof(dst2), dst, files[i], &sb, opt)) {
result++; result++;
continue; 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... */ continue; /* Next file/dir in @src to copy... */
} }
if (copy(source, dst, keep_mtim)) if (copy(source, dst, opt))
result++; result++;
} }
@@ -157,23 +170,91 @@ int rsync(char *src, char *dst, int opt, int (*filter)(const char *file))
return result; return result;
} }
static int copy(char *src, char *dst, int opt) /* Leaves errno alone, a missing @path is not an error to rsync() */
static int islink(const char *path)
{ {
copyfile(src, dst, 0, opt | LITE_FOPT_COPYFILE_SYM); int rc, saved_errno = errno;
if (errno) { struct stat st;
if (errno != EEXIST)
return 1;
errno = 0; rc = !lstat(path, &st) && S_ISLNK(st.st_mode);
errno = saved_errno;
return rc;
} }
return 0; static int copy(char *src, char *dst, int opt)
{
char *dest = NULL, *temp = NULL, *file = NULL;
int result = 1, saved_errno, made_temp = 0;
int copt = opt & LITE_FOPT_KEEP_MTIME;
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;
}
/*
* 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. */ /* 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); 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 (mkdir(buf, st->st_mode)) {
if (EEXIST != errno) if (EEXIST != errno)
return 1; return 1;
+2
View File
@@ -4,6 +4,8 @@
.deps/ .deps/
/chomp /chomp
/copyfile /copyfile
/fcopyfile
/copyfile-symlink
/dir /dir
/fexist /fexist
/fisdir /fisdir
+3
View File
@@ -19,6 +19,8 @@ clean-local:
TESTS = TESTS =
TESTS += chomp TESTS += chomp
TESTS += copyfile TESTS += copyfile
TESTS += fcopyfile
TESTS += copyfile-symlink
TESTS += dir TESTS += dir
TESTS += fexist TESTS += fexist
TESTS += fisdir TESTS += fisdir
@@ -40,3 +42,4 @@ TESTS += which
TESTS += yorn TESTS += yorn
check_PROGRAMS = $(TESTS) 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;
}
+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;
}