mirror of
https://github.com/troglobit/libite.git
synced 2026-11-14 19:03:05 +00:00
Compare commits
7
Commits
b69d8a6013
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
798d70e2a2 | ||
|
|
2f6a7f1595 | ||
|
|
ef8b17f438 | ||
|
|
4609c53ddd | ||
|
|
666a8ef7cd | ||
|
|
dff88a622d | ||
|
|
2dfc02e67d |
No files matched your search
@@ -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
@@ -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
|
||||||
|
|||||||
+31
-12
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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 */
|
||||||
|
|||||||
+94
-13
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 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)
|
static int copy(char *src, char *dst, int opt)
|
||||||
{
|
{
|
||||||
copyfile(src, dst, 0, opt | LITE_FOPT_COPYFILE_SYM);
|
char *dest = NULL, *temp = NULL, *file = NULL;
|
||||||
if (errno) {
|
int result = 1, saved_errno, made_temp = 0;
|
||||||
if (errno != EEXIST)
|
int copt = opt & LITE_FOPT_KEEP_MTIME;
|
||||||
return 1;
|
|
||||||
|
|
||||||
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. */
|
/* 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;
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
.deps/
|
.deps/
|
||||||
/chomp
|
/chomp
|
||||||
/copyfile
|
/copyfile
|
||||||
|
/fcopyfile
|
||||||
|
/copyfile-symlink
|
||||||
/dir
|
/dir
|
||||||
/fexist
|
/fexist
|
||||||
/fisdir
|
/fisdir
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
Reference in new issue
Block a user