diff --git a/ChangeLog.md b/ChangeLog.md index f83a311..8edc447 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -7,7 +7,14 @@ 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 `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 diff --git a/doc/API.md b/doc/API.md index e28102a..2808235 100644 --- a/doc/API.md +++ b/doc/API.md @@ -244,6 +244,12 @@ 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. 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 diff --git a/src/lite.h b/src/lite.h index 2d03290..bf704e4 100644 --- a/src/lite.h +++ b/src/lite.h @@ -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 */ diff --git a/src/rsync.c b/src/rsync.c index 47fd5fc..9869e67 100644 --- a/src/rsync.c +++ b/src/rsync.c @@ -45,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 @@ -60,6 +61,13 @@ 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. * * The filter callback, @p filter, if provided, is used to determine * what files to include from the source directory when backing up. If @@ -87,7 +95,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; @@ -137,7 +145,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++; } @@ -162,8 +170,12 @@ 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; - copyfile(src, dst, 0, opt | LITE_FOPT_COPYFILE_SYM); + if (opt & LITE_FOPT_RSYNC_LINKS) + copt |= LITE_FOPT_COPYFILE_SYM; + + copyfile(src, dst, 0, copt); if (errno != EEXIST) return errno ? 1 : 0; @@ -186,7 +198,7 @@ static int copy(char *src, char *dst, int opt) if (asprintf(&file, "%s/link", temp) < 0) goto exit; - copyfile(src, file, 0, opt | LITE_FOPT_COPYFILE_SYM); + copyfile(src, file, 0, copt); if (errno) goto exit; result = rename(file, dest) != 0; diff --git a/test/copyfile-symlink.c b/test/copyfile-symlink.c index d4c55c6..58de5ee 100644 --- a/test/copyfile-symlink.c +++ b/test/copyfile-symlink.c @@ -65,29 +65,33 @@ int main(void) check_link("copied", "file"); fail_unless(lstat("copied", &dst_st) == 0 && dst_st.st_mtime == times[1].tv_sec); fail_unless(unlink("copied") == 0); - /* Exercise the existing rsync file caller, which requests SYM. */ + /* 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", 0, NULL) == 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", 0, NULL) == 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", 0, NULL) == 0); + 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", 0, NULL) == 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", 0, NULL) != 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); @@ -95,7 +99,7 @@ int main(void) fail_unless(mkdir("protected", 0700) == 0); fail_unless(symlink("original", "protected/link") == 0); fail_unless(chmod("protected", 0500) == 0); - fail_unless(rsync("link", "protected", 0, NULL) != 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);