From ceee081c850df8353db54530dcc0d76887485ee3 Mon Sep 17 00:00:00 2001 From: twojstaryzdomu Date: Sat, 3 Jul 2021 19:59:34 +0200 Subject: [PATCH 01/15] Added independent update mode for symbolic links --- flist.c | 8 +++++- generator.c | 59 +++++++++++++++++++++++++++++++++++++++++ options.c | 14 +++++++++- rsync.1.md | 75 ++++++++++++++++++++++++++++++++++++++++++++++++----- 4 files changed, 147 insertions(+), 9 deletions(-) diff --git a/flist.c b/flist.c index 17832533e..cc9b44227 100644 --- a/flist.c +++ b/flist.c @@ -62,6 +62,7 @@ extern int implied_dirs; extern int ignore_perishable; extern int non_perishable_cnt; extern int prune_empty_dirs; +extern int update_links; extern int copy_links; extern int copy_unsafe_links; extern int protocol_version; @@ -234,10 +235,15 @@ static int readlink_stat(const char *path, STRUCT_STAT *stp, char *linkbuf) int link_stat(const char *path, STRUCT_STAT *stp, int follow_dirlinks) { #ifdef SUPPORT_LINKS - if (copy_links) + if (copy_links && update_links == 0) return x_stat(path, stp, NULL); if (x_lstat(path, stp, NULL) < 0) return -1; + if (update_links && S_ISLNK(stp->st_mode)) { + STRUCT_STAT st; + if (x_stat(path, &st, NULL) == 0 && !S_ISDIR(st.st_mode)) + return x_lstat(path, stp, NULL); + } if (follow_dirlinks && S_ISLNK(stp->st_mode)) { STRUCT_STAT st; if (x_stat(path, &st, NULL) == 0 && S_ISDIR(st.st_mode)) diff --git a/generator.c b/generator.c index b56fa569a..e56ed3cf3 100644 --- a/generator.c +++ b/generator.c @@ -57,6 +57,9 @@ extern int ignore_errors; extern int remove_source_files; extern int delay_updates; extern int update_only; +extern int update_links; +extern int copy_links; +extern int allow_link_update_dir; extern int human_readable; extern int ignore_existing; extern int ignore_non_existing; @@ -1540,6 +1543,8 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx, && hard_link_check(file, ndx, fname, statret, &sx, itemizing, code)) goto cleanup; #endif + if (DEBUG_GTE(GENR, 1)) + rprintf(FINFO, "%s src mtime=%ld dest mtime=%ld modify_window=%d statret=%d copy_links=%d update_links=%d allow_link_update_dir=%d\n", fname, file->modtime, (long)sx.st.st_mtime, modify_window, statret, copy_links, update_links, allow_link_update_dir); if (preserve_links && ftype == FT_SYMLINK) { #ifdef SUPPORT_LINKS @@ -1588,6 +1593,60 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx, fnamecmp = fnamecmpbuf; } } + if (statret == 0) { + if (update_links > 0) { + if (S_ISDIR(sx.st.st_mode) && allow_link_update_dir == 0) { + if (INFO_GTE(SKIP, 1)) + rprintf(FINFO, "symlink \"%s\" is a directory on destination and allow-link-update-dir isn't enabled, skipping\n", fname); + goto cleanup; + } + else { + int mtime_offset = sx.st.st_mtime - file->modtime; + char *st_mode = S_ISDIR(sx.st.st_mode) + ? "directory" + : S_ISLNK(sx.st.st_mode) + ? "symlink" + : S_ISCHR(sx.st.st_mode) + ? "character device" + : S_ISBLK(sx.st.st_mode) + ? "block device" + : S_ISFIFO(sx.st.st_mode) + ? "named pipe" + : S_ISSOCK(sx.st.st_mode) + ? "socket" + : "file"; + if (mtime_offset > modify_window) { + if (INFO_GTE(SKIP, 1)) + rprintf(FINFO, "%s \"%s\" is newer by %d sec, skipping\n", st_mode, fname, mtime_offset - modify_window); + goto cleanup; + } + else if (mtime_offset < modify_window) { + if (INFO_GTE(SKIP, 1)) + rprintf(FINFO, "%s \"%s\" is older by %d sec, updating\n", st_mode, fname, - mtime_offset - modify_window); + } + else if (S_ISLNK(sx.st.st_mode)) { + char lnk[MAXPATHLEN]; + int llen = do_readlink(fname, lnk, MAXPATHLEN - 1); + lnk[llen] = '\0'; + if (strcmp(lnk, F_SYMLINK(file)) == 0) { + if (INFO_GTE(SKIP, 1)) + rprintf(FINFO, "symlink \"%s\" points to the same referent %s, skipping\n", fname, lnk); + goto cleanup; + } + else if (INFO_GTE(SKIP, 1)) + rprintf(FINFO, "symlink \"%s\" points to a different referent on source (%s) than destination (%s), updating\n", fname, F_SYMLINK(file), lnk); + } + else { + if (INFO_GTE(SKIP, 1)) + rprintf(FINFO, "symlink \"%s\" more recent than %s on destination, updating\n", fname, st_mode); + } + } + } + else { + if (DEBUG_GTE(GENR, 1)) + rprintf(FINFO, "update-links not enabled for \"%s\"\n, skipping", fname); + } + } if (atomic_create(file, fname, sl, NULL, MAKEDEV(0, 0), &sx, statret == 0 ? DEL_FOR_SYMLINK : 0)) { set_file_attrs(fname, file, NULL, NULL, 0); if (itemizing) { diff --git a/options.c b/options.c index 578507c6e..277a0542a 100644 --- a/options.c +++ b/options.c @@ -48,6 +48,8 @@ int whole_file = -1; int append_mode = 0; int keep_dirlinks = 0; int copy_dirlinks = 0; +int update_links = 0; +int allow_link_update_dir = 0; int copy_links = 0; int copy_devices = 0; int write_devices = 0; @@ -693,6 +695,8 @@ static struct poptOption long_options[] = { {"no-one-file-system",0, POPT_ARG_VAL, &one_file_system, 0, 0, 0 }, {"no-x", 0, POPT_ARG_VAL, &one_file_system, 0, 0, 0 }, {"update", 'u', POPT_ARG_NONE, &update_only, 0, 0, 0 }, + {"update-links", 0, POPT_ARG_NONE, &update_links, 0, 0, 0 }, + {"allow-link-update-dir",0,POPT_ARG_NONE, &allow_link_update_dir, 0, 0, 0 }, {"existing", 0, POPT_ARG_NONE, &ignore_non_existing, 0, 0, 0 }, {"ignore-non-existing",0,POPT_ARG_NONE, &ignore_non_existing, 0, 0, 0 }, {"ignore-existing", 0, POPT_ARG_NONE, &ignore_existing, 0, 0, 0 }, @@ -2321,6 +2325,9 @@ int parse_arguments(int *argc_p, const char ***argv_p) parse_filter_str(&filter_list, backup_dir_buf, rule_template(0), 0); } + if (update_links) + preserve_links = 1; + if (make_backups && !backup_dir) omit_dir_times = -1; /* Implied, so avoid -O to sender. */ @@ -2615,7 +2622,7 @@ void server_options(char **args, int *argc_p) argstr[x++] = 'u'; if (!do_xfers) /* Note: NOT "dry_run"! */ argstr[x++] = 'n'; - if (preserve_links) + if (preserve_links || update_links) argstr[x++] = 'l'; if ((xfer_dirs >= 2 && xfer_dirs < 4) || (xfer_dirs && !recurse && (list_only || (delete_mode && am_sender)))) @@ -2634,6 +2641,11 @@ void server_options(char **args, int *argc_p) if (fuzzy_basis > 1) argstr[x++] = 'y'; } + if (update_links) { + args[ac++] = "--update-links"; + if (allow_link_update_dir) + args[ac++] = "--allow-link-update-dir"; + } } else { if (copy_links) argstr[x++] = 'L'; diff --git a/rsync.1.md b/rsync.1.md index 7e40e3617..35677ccfe 100644 --- a/rsync.1.md +++ b/rsync.1.md @@ -431,6 +431,8 @@ has its own detailed description later in this manpage. --backup-dir=DIR make backups into hierarchy based in DIR --suffix=SUFFIX backup suffix (default ~ w/o --backup-dir) --update, -u skip files that are newer on the receiver +--update-links skip symlinks that are newer on the receiver +--allow-link-update-dir newer symlinks may replace older directories --inplace update destination files in-place --append append data onto shorter files --append-verify --append w/old data in file checksum @@ -1034,11 +1036,12 @@ expand it. will be updated if the sizes are different.) Note that this does not affect the copying of dirs, symlinks, or other - special files. Also, a difference of file format between the sender and - receiver is always considered to be important enough for an update, no - matter what date is on the objects. In other words, if the source has a - directory where the destination has a file, the transfer would occur - regardless of the timestamps. + special files unless the `--update-links` option is enabled. Also, + a difference of file format between the sender and receiver is always + considered to be important enough for an update, no matter what date is + on the objects. In other words, if the source has a directory where + the destination has a file, the transfer would occur regardless of + the timestamps. This option is a [TRANSFER RULE](#TRANSFER_RULES), so don't expect any exclude side effects. @@ -1050,6 +1053,45 @@ expand it. is usually best to avoid combining this with[ `--inplace`](#opt) unless you have implemented manual steps to handle any interrupted in-progress files. +0. `--update-links` + + This option controls rsync behaviour when a symbolic link on the source + encounters a destination file in the way of the symlink during transfer. + The setting forces rsync to skip any files which exist on the destination + and have a modified time that is newer than the symbolic link on the + source. A file in the way of the symlink can be a regular file, a symlink, + a named pipe, or a device that exists on the destination. Such a file with + an identical or less recent modification time than the source symlink will + be deleted and a symlink pointing to the same item (the referent) as the + source symlink on the destination created in its place. A destination + symlink with an identical modification time as the source symlink will + only be replaced if the referent is different. By default, any directory + in the way of the source symlink is exempted from removal by a symlink, + regardless of its modification time. To extend the behaviour of this + option to directories, see the [`--allow-link-update-dir`](#opt) option + below. + + This option may be specified independently of the [`--update`](#opt) + ([`-u`](#opt)) option mentioned above, which acts on regular source files + only. + +0. `--allow-link-update-dir` + + Enabling this option allows rsync to replace a less recent directory on the + destination with a more recent symbolic link from the source. This option + extends the [`--update-links`](#opt) option, which by default preserves any + existing directory on the destination from being replaced by a more recent + symbolic link. The modification time of the symbolic link on the source + needs to be more recent than the modification time of the directory + existing on the destination for this option to take effect. An exisitng + directory with a more recent modification time on the destination than the + symlink on the source will not be removed and replaced by the symlink. A + destination directory with an identical modification time as the source + symbolic link will be removed and a symlink will be recreated with same + referent as the source symlink. + + This option requires the [`--update-links`](#opt) option to be enabled. + 0. `--inplace` This option changes how rsync transfers a file when its data needs to be @@ -1179,7 +1221,9 @@ expand it. alternately silence the warning by specifying [`--info=nonreg0`](#opt). The default handling of symlinks is to recreate each symlink's unchanged - value on the receiving side. + value on the receiving side. To exempt more recent destination + files from being replaced by a link, refer to the [`--update-links`](#opt) + option. See the [SYMBOLIC LINKS](#) section for multi-option info. @@ -4568,7 +4612,7 @@ version uses a new implementation. ## SYMBOLIC LINKS -Three basic behaviors are possible when rsync encounters a symbolic +Four basic behaviors are possible when rsync encounters a symbolic link in the source directory. By default, symbolic links are not transferred at all. A message "skipping @@ -4582,6 +4626,15 @@ implies [`--links`](#opt). If [`--copy-links`](#opt) is specified, then symlinks are "collapsed" by copying their referent, rather than the symlink. +If [`--update-links`](#opt) is specified, then symlinks are recreated with +the same target on the destination unless the existing destination file +in the way of the symlink is newer or is a directory. +Unlike [`--links`](#opt), this option preserves all more recent files on the +destination from being replaced by a symlink. It is possible to extend having +an existing less recent directory on the destination replaced by a more recent +symlink by specifying [`--allow-link-update-dir`](#opt) in addition to +[`--update-links`](#opt). + Rsync can also distinguish "safe" and "unsafe" symbolic links. An example where this might be used is a web site mirror that wishes to ensure that the rsync module that is copied does not include symbolic links to `/etc/passwd` in @@ -4610,6 +4663,14 @@ first line that is a complete subset of your options: 0. `--links --safe-links` The receiver skips creating unsafe symlinks found in the transfer and creates the safe ones. 0. `--links` Create all symlinks. +0. `--update-links` Create symlinks, replace less recent files or files + with an identical modification time. Skip any more recent destination + files. Unconditionally exclude any existing directories on the destination + from deletion and replacement by a symlink when one would otherwise + be transferred. +0. `--update-links --allow-link-update-dir` Create symlinks, skip any more + recent files existing on the destination, allow a less recent + directory to be replaced by a more recent symlink. For the effect of [`--munge-links`](#opt), see the discussion in that option's section. From 15785e0d52f2e864a6bf416e07251bd69bb21d2c Mon Sep 17 00:00:00 2001 From: twojstaryzdomu Date: Sat, 3 Jul 2021 20:09:39 +0200 Subject: [PATCH 02/15] Added test for symlink update mode --- testsuite/update-links.test | 560 ++++++++++++++++++++++++++++++++++++ 1 file changed, 560 insertions(+) create mode 100644 testsuite/update-links.test diff --git a/testsuite/update-links.test b/testsuite/update-links.test new file mode 100644 index 000000000..07e3cae74 --- /dev/null +++ b/testsuite/update-links.test @@ -0,0 +1,560 @@ +#! /bin/sh + +SELF="$(basename "$0")" +LSRC="$(readlink -e "$(realpath $0)")" +LSELF="$(basename "$LSRC")" +DSELF="$(dirname "$LSRC")" + +. "${testsuite:-${DSELF}}/rsync.fns" + +log(){ + echo "${SELF}: $@" +} + +debug(){ + [ -n "$DEBUG" ] && echo "$@" 1>&2 || : +} + +error(){ + test_fail "$@" +} + +get_ctime(){ + stat -c "%Y" "$1" || test_fail "Unable to get ctime of $1" +} + +get_mtime(){ + stat -c "%Y" "$1" || test_fail "Unable to get mtime of $1" +} + +get_size(){ + stat -c "%s" "$1" || test_fail "Unable to get size of $1" +} + +get_referent(){ + readlink "$1" || test_fail "Unable to dereference $1" +} + +get_type(){ + file -b "$1" || test_fail "Unable to get type of $1" +} + +exists(){ + [ -h "$1" ] || [ -e "$1" ] +} + +is_a_link(){ + [ -h "$1" ] +} + +is_a_file(){ + [ -h "$1" ] +} + +is_a_pipe(){ + [ -p "$1" ] +} + +points_to_pipe(){ + is_a_link "$1" && is_a_pipe "$1" +} + +is_a_directory(){ + ! is_a_link "$1" && [ -d "$1" ] +} + +points_to_dir(){ + is_a_link "$1" && [ -d "$1" ] +} + +is_newer(){ + [ $(get_ctime "$1") -gt $(get_ctime "$2") ] || error "$1 is not newer than $2" + #DO NOT USE, DEREFERENCES LINKS! [ "$1" -nt "$2" ] +} + +is_older(){ + [ $(get_ctime "$1") -lt $(get_ctime "$2") ] || error "$1 is not older than $2" + #DO NOT USE, DEREFERENCES LINKS! [ "$1" -ot "$2" ] +} + +is_defined(){ + type "$1" >/dev/null 2>&1 +} + +compare(){ + local s + local d + s=$($1 "$2") + debug "compare $1 $2: output = $s; exit status = $?" + d=$($1 "$3") + debug "compare $1 $3: output = $d; exit status = $?" + case "$s" in + "$d") + : + ;; + *) + return 1 + ;; + esac +} + +# The size of a symlink equals the length of the link target path +# Links pointing at /dev/zero & /dev/null will thus have the same size +is_same_size(){ + compare get_size "$1" "$2" || test_fail "Files $1 & $2 are not same size" +} + +is_diff_size(){ + compare get_size "$1" "$2" && test_fail "Files $1 & $2 are supposed to be of different size" || : +} + +is_same_mtime(){ + compare get_mtime "$1" "$2" || test_fail "Files $1 & $2 have different mtimes" +} + +is_diff_mtime(){ + compare get_mtime "$1" "$2" && test_fail "Files $1 & $2 are supposed to have different mtimes" || : +} + +is_any_mtime(){ + compare get_mtime "$1" "$2" || : +} + +is_same_ctime(){ + compare get_ctime "$1" "$2" || test_fail "Files $1 & $2 have different ctimes" +} + +is_diff_ctime(){ + compare get_ctime "$1" "$2" && test_fail "Files $1 & $2 are supposed to have different ctimes" || : +} + +is_any_ctime(){ + compare get_ctime "$1" "$2" || : +} + +is_same_referent(){ + compare get_referent "$1" "$2" || test_fail "Links $1 & $2 point at a different referent each" +} + +is_diff_referent(){ + compare get_referent "$1" "$2" && test_fail "Links $1 & $2 should point at a different referent each" || : +} + +touch_links(){ + debug "touch_links: # touch -h $@" + touch -h "$@" +} + +shift_mtime(){ + local offset + local f + local epoch + local rc + offset=$1 + shift + for f in $@; do + epoch=$(stat -c%Y "$f") + rc=$? + debug "shift_mtime: $f; offset = $offset; rc = $rc; touch -hd @$((epoch${offset})) $f" + [ $rc -eq 0 ] && touch -hd @$((epoch${offset})) "$f" || error "Unable to stat $f" + done +} + +add_a_second(){ + shift_mtime +1 $@ +} + +take_a_second(){ + shift_mtime -1 $@ +} + +show_files(){ + stat -c "%Y %Z %N" $@ 1>&2 || test_fail "Unable to stat $@" +} + +count_words(){ + echo $@ | wc -w +} + +get_word(){ + local i + local words + i=$1 + shift + words=$@ + while [ $i -gt 0 ]; do + words=${words#* } + i=$(($i-1)) + done + echo ${words%% *} +} + +# Sets the referent of $1 to something else than the existing one and the referent of $2 +# choice of out of two variables, purposely defined to be of different length +# size checks should pass +cycle_referent(){ + local links + local referent + local current + local n + local next + links="/dev/urandom /dev/random" + referent=$(get_referent "$1") + [ -n "$2" ] && compare_referent=$(get_referent "$2") + n=0; while [ $n -le $(count_words $links) ]; do + current=$(get_word $n $links) + n=$((n+1)) + [ -n "$2" ] || next="$(get_word $n $links)" + case "$current" in + ${compare_referent}|${referent}) + : + ;; + *) + ln -nsf "$current" "$1" && return + ;; + esac + done || test_fail "Unable to cycle referent for $1" +} + +build_symlinks(){ + rm -rf "$fromdir" + mkdir "$fromdir" + mkdir "$fromdir/emptydir" + mkfifo "$fromdir/fifo" + date >"$fromdir/referent" + ln -s referent "$fromdir/relative" + ln -s "$fromdir/referent" "$fromdir/absolute" + ln -s nonexistent "$fromdir/dangling" + ln -s "$srcdir/rsync.c" "$fromdir/unsafe" + ln -s emptydir "$fromdir/dirlink" + ln -s /dev/null "$fromdir/devlink" +} + +# Unused yet +is_set(){ + read opt opts << _EOT_ +$@ +_EOT_ + case "${opts}" in + "${opt}"|"${opt} "*|*" ${opt}"*) + : + ;; + *) + false + ;; + esac +} + +list_contains(){ + read opt opts << _EOT_ +$@ +_EOT_ + debug "list_contains: opt = $opt; opts = $opts" + echo "$opts" | grep -qPo -- "${opt}(?=$|\s)" 1>/dev/null +} + +# Various scenarios that modify either side before re-running rsync & performing a check +default(){ + read fromdir todir file scenario opts << _EOT_ +$@ +_EOT_ + show_files $fromdir/$file + exists $todir/$file && show_files $todir/$file || : +} + +newer_dest_link(){ + read fromdir todir file scenario opts << _EOT_ +$@ +_EOT_ + if exists $todir/$file; then + if is_a_link $todir/$file; then + take_a_second "$fromdir/$file" + add_a_second "$todir/$file" + show_files "$todir/$file" "$fromdir/$file" + fi + else + show_files "$fromdir/$file" + log "$todir/$file did not get transferred in scenario $scenario with rsync opts $opts" + fi +} + +newer_source_link(){ + read fromdir todir file scenario opts << _EOT_ +$@ +_EOT_ + is_a_link $fromdir/$file && touch_links $fromdir/$file $todir/$file && add_a_second $fromdir/$file || : +} + +cycle_dest_link(){ + read fromdir todir file scenario opts << _EOT_ +$@ +_EOT_ + if is_a_link $todir/$file; then + cycle_referent $todir/$file $fromdir/$file + # Simulate a time difference, making dest_link more recent + take_a_second $fromdir/$file + add_a_second $todir/$file + #show_files $fromdir/$file $todir/$file + fi +} + +cycle_dest_newer_source_link(){ + read fromdir todir file scenario opts << _EOT_ +$@ +_EOT_ + cycle_dest_link $fromdir $todir $file $scenario $opts && newer_source_link $fromdir $todir $file $scenario $opts + if exists $todir/$file; then + show_files "$todir/$file" "$fromdir/$file" + else + show_files "$fromdir/$file" + log "$todir/$file did not get transferred in scenario $scenario with rsync opts $opts" + fi +} + +link_update_dir(){ + read fromdir todir file scenario opts << _EOT_ +$@ +_EOT_ + debug "link_update_dir: $todir/$f" + # Make all target links directories + local f + for f in $FILES; do + rm $todir/$f 2>/dev/null || log "${scenario}: $f didn't exist" + mkdir -p $todir/$f + # Make source link more recent than dir just created + add_a_second $fromdir/$f + show_files $todir/$f + done +} + +test_failure(){ + read fromdir todir file scenario opts << _EOT_ +$@ +_EOT_ + debug "${scenario}: has predictably failed" + false +} + +test_success(){ + read fromdir todir file scenario opts << _EOT_ +$@ +_EOT_ + debug "${scenario}: has predictably completed" + true +} + +# Various checks after a scenario has run, these rules should all pass +run_checks(){ + read type fromdir todir file scenario opts << _EOT_ +$@ +_EOT_ + local times + list_contains "--times" "$opts" || times=any + for check in ${times:-${type}}_mtime ${times:-${type}}_ctime ${type}_referent ${type}_size; do + debug "check_${scenario}: is_${check} $fromdir/$file $todir/$file" + is_${check} $fromdir/$file $todir/$file + done +} + +check_existing_dest(){ + read fromdir todir file scenario opts << _EOT_ +$@ +_EOT_ + run_checks same $fromdir $todir $file $scenario $opts +} + +check_default(){ + read fromdir todir file scenario opts << _EOT_ +$@ +_EOT_ + exists $todir/$file && check_existing_dest $fromdir $todir $file $scenario $opts || debug "$todir/$file does not exist, no more checks" +} + +check_newer_dest_link(){ + read fromdir todir file scenario opts << _EOT_ +$@ +_EOT_ + if is_a_link $todir/$file; then + if list_contains "--update-links" "$opts"; then + # Newer dest links are supposed to be preserved + is_same_referent $fromdir/$file $todir/$file + # Times will get updated if times is in effect + if list_contains "--times" "$opts"; then + is_same_mtime $fromdir/$file $todir/$file + is_same_ctime $fromdir/$file $todir/$file + else + is_older $fromdir/$file $todir/$file + fi + fi + fi +} + +check_newer_source_link(){ + read fromdir todir file scenario opts << _EOT_ +$@ +_EOT_ + if is_a_link $todir/$file; then + if list_contains "--update-links" "$opts"; then + # Newer source links are supposed to be transfered + run_checks same $fromdir $todir $file $scenario $opts + fi + fi +} + +check_cycle_dest_link(){ + read fromdir todir file scenario opts << _EOT_ +$@ +_EOT_ + if is_a_link $todir/$file; then + if list_contains "--update-links" "$opts"; then + # Newer source links are supposed to be transfered + run_checks diff $fromdir $todir $file $scenario $opts + fi + fi +} + +check_cycle_dest_newer_source_link(){ + read fromdir todir file scenario opts << _EOT_ +$@ +_EOT_ + check_newer_source_link $fromdir $todir $file $scenario $opts +} + +check_link_update_dir(){ + read fromdir todir file scenario opts << _EOT_ +$@ +_EOT_ + if list_contains "--update-links" "$opts"; then + if list_contains "--allow-link-update-dir" "$opts"; then + if is_a_link $todir/$file; then + # Newer source links are supposed to be transfered + run_checks same $fromdir $todir $file $scenario $opts + else + is_a_directory $todir/$file && error "$todir/$file has not been updated by a source link in spite of --allow-link-update-dir" + fi + fi + fi +} + +check_test_failure(){ + read fromdir todir file scenario opts << _EOT_ +$@ +_EOT_ + debug "check_${scenario}: $scenario should have failed, this notice should never be displayed" + exit 1 +} + +check_test_success(){ + read fromdir todir file scenario opts << _EOT_ +$@ +_EOT_ + debug "check_${scenario}: $scenario should have completed, this notice should always be displayed" +} + +run_rsync(){ + read fromdir todir file opts << _EOT_ +$@ +_EOT_ + debug "run_rsync: # $RSYNC $RSYNC_COMMON_OPTS $opts $fromdir/$file $todir/" + $RSYNC $RSYNC_COMMON_OPTS $opts $fromdir/$file $todir/ 1>&2 || test_fail "$RSYNC $RSYNC_COMMON_OPTS $opts $fromdir/$file $todir/ failed" +} + +run_scenario(){ + read fromdir todir file scenario opts << _EOT_ +$@ +_EOT_ + cd $tmpdir && build_symlinks || error "Unable to change directory to $tmpdir" + debug "run_scenario: scenario = $scenario; file = $file; type = $(get_type $fromdir/$file); opts = $opts" + run_rsync $fromdir $todir $file $opts + is_defined $scenario && $scenario $fromdir $todir $file $scenario $opts + run_rsync $fromdir $todir $file $opts + check_${scenario} $fromdir $todir $file $scenario $opts + rm -rf $fromdir $todir +} + +run_scenarios(){ + read fromdir todir file opts << _EOT_ +$@ +_EOT_ + local scenario + for scenario in $SCENARIOS; do + for t in '' check_; do + is_defined ${t}${scenario} || error "No ${t}${scenario} scenario defined" + done + log "Scenario $scenario running on $file with rsync opts $opts" + run_scenario $fromdir $todir $file $scenario $opts + done +} + +process_files(){ + read fromdir todir opts << _EOT_ +$@ +_EOT_ + local file + for file in $FILES; do + run_scenarios $fromdir $todir $file $opts + done +} + +sort_opts(){ + echo "$@" | tr ' ' '\n' | sort | xargs echo +} + +strip_whitespace(){ + echo "${@}" | sed -e 's/ //g' +} + +process_opts(){ + read fromdir todir opts << _EOT_ +$@ +_EOT_ + local current + local opt + debug "process_opts: opts = $opts" + for opt in $opts; do + debug "process_opts: opt = $opt; done = $done" + current="$(sort_opts $current $opt)" + if list_contains "$(strip_whitespace ${current})" "$done"; then + # Skip item if found on the done list + debug "process_opts: $current already processed" + continue + else + debug "process_opts: current = $current" + process_files $fromdir $todir $current + done="${done:+${done} }$(strip_whitespace ${current})" + fi + debug "process_opts: opt = $opt; opts = $opts; current = $current" + done +} + +process_all_opts(){ + read fromdir todir all_opts << _EOT_ +$@ +_EOT_ + local remaining + local first + remaining=${all_opts} + debug "process_all_opts: remaining = ${remaining}" + first=${remaining%% *} + while [ -n "$remaining" ]; do + debug "process_all_opts: remaining before run = ${remaining}" + process_opts $fromdir $todir ${remaining} + # Remove first parameter for next iteration + remaining="${remaining##${remaining%% *} } ${remaining%% *}" + debug "process_all_opts: remaining after run = ${remaining}" + case ${remaining%% *} in ${first}) break;; esac + done + log "Completed scenarios with status $?" + #echo -e $n | wc -c +} + +# All of the following variables may be overriden at command line +FILES="${FILES:-relative dangling absolute unsafe devlink dirlink}" +# For sanity testing only +#SCENARIOS="test_success test_failure" +SCENARIOS="${SCENARIOS:-default newer_source_link newer_dest_link cycle_dest_link cycle_dest_newer_source_link link_update_dir}" +RSYNC=${RSYNC:-${DSELF}/../rsync} +[ -n "$DEBUG" ] && RSYNC_COMMON_OPTS=${RSYNC_COMMON_OPTS:--i} +RSYNC_OPTS="--update-links --times --allow-link-update-dir" + +process_all_opts $fromdir $todir ${RSYNC_OPTS} From 8498dbd0f35e151b31a728806fa13dbdc7202cc9 Mon Sep 17 00:00:00 2001 From: twojstaryzdomu Date: Sat, 3 Jul 2021 21:49:11 +0200 Subject: [PATCH 03/15] Clarify directory replaced by symlink has to be empty --- rsync.1.md | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/rsync.1.md b/rsync.1.md index 35677ccfe..c271db3dc 100644 --- a/rsync.1.md +++ b/rsync.1.md @@ -1077,18 +1077,19 @@ expand it. 0. `--allow-link-update-dir` - Enabling this option allows rsync to replace a less recent directory on the - destination with a more recent symbolic link from the source. This option - extends the [`--update-links`](#opt) option, which by default preserves any - existing directory on the destination from being replaced by a more recent - symbolic link. The modification time of the symbolic link on the source - needs to be more recent than the modification time of the directory - existing on the destination for this option to take effect. An exisitng - directory with a more recent modification time on the destination than the - symlink on the source will not be removed and replaced by the symlink. A - destination directory with an identical modification time as the source - symbolic link will be removed and a symlink will be recreated with same - referent as the source symlink. + Enabling this option allows rsync to replace a less recent empty directory + on the destination with a more recent symbolic link from the source. This + option extends the [`--update-links`](#opt) option, which by default + preserves any existing directory on the destination from being replaced + by a more recent symbolic link. The modification time of the symbolic link + on the source needs to be more recent than the modification time of the + directory existing on the destination for this option to take effect. An + exisitng directory with a more recent modification time on the destination + than the symlink on the source will not be removed and replaced by + the symlink. A destination directory with an identical modification time + as the source symbolic link will be removed and a symlink will be recreated + with same referent as the source symlink. Non-empty directories are not + unlinked under any circumstances. This option requires the [`--update-links`](#opt) option to be enabled. From 3f8bfa695e6f3fab7a130b121e449eb7254b9635 Mon Sep 17 00:00:00 2001 From: twojstaryzdomu Date: Sat, 3 Jul 2021 21:50:15 +0200 Subject: [PATCH 04/15] Include non-empty directory scenario check --- testsuite/update-links.test | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/testsuite/update-links.test b/testsuite/update-links.test index 07e3cae74..71b2673cb 100644 --- a/testsuite/update-links.test +++ b/testsuite/update-links.test @@ -59,10 +59,14 @@ points_to_pipe(){ is_a_link "$1" && is_a_pipe "$1" } -is_a_directory(){ +is_a_dir(){ ! is_a_link "$1" && [ -d "$1" ] } +is_empty_dir(){ + is_a_dir && [ $(find "$1" | wc -l) -eq 1 ] +} + points_to_dir(){ is_a_link "$1" && [ -d "$1" ] } @@ -220,6 +224,9 @@ build_symlinks(){ rm -rf "$fromdir" mkdir "$fromdir" mkdir "$fromdir/emptydir" + mkdir "$fromdir/nonemptydir" + touch "$fromdir/nonemptydir/file" + mkfifo "$fromdir/fifo" mkfifo "$fromdir/fifo" date >"$fromdir/referent" ln -s referent "$fromdir/relative" @@ -427,10 +434,17 @@ _EOT_ if list_contains "--update-links" "$opts"; then if list_contains "--allow-link-update-dir" "$opts"; then if is_a_link $todir/$file; then - # Newer source links are supposed to be transfered - run_checks same $fromdir $todir $file $scenario $opts + case $file in + nonemptydir) + error "$file should never be replaced by a link" + ;; + *) + # Newer source links are supposed to be transfered + run_checks same $fromdir $todir $file $scenario $opts + ;; + esac else - is_a_directory $todir/$file && error "$todir/$file has not been updated by a source link in spite of --allow-link-update-dir" + is_empty_dir $todir/$file && error "$todir/$file has not been updated by a source link in spite of --allow-link-update-dir" fi fi fi From 1e117dd21e384a2db66cf939806442060c764a32 Mon Sep 17 00:00:00 2001 From: twojstaryzdomu Date: Sat, 3 Jul 2021 22:46:38 +0200 Subject: [PATCH 05/15] Fixed readlink OpenBSD compatibility --- testsuite/update-links.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testsuite/update-links.test b/testsuite/update-links.test index 71b2673cb..c6b89a134 100644 --- a/testsuite/update-links.test +++ b/testsuite/update-links.test @@ -1,7 +1,7 @@ #! /bin/sh SELF="$(basename "$0")" -LSRC="$(readlink -e "$(realpath $0)")" +LSRC="$(readlink -f "$(realpath $0)")" LSELF="$(basename "$LSRC")" DSELF="$(dirname "$LSRC")" From 791c7cfbe9fddd35d9ea7564afa871d788a9a76f Mon Sep 17 00:00:00 2001 From: twojstaryzdomu Date: Sun, 4 Jul 2021 00:59:41 +0200 Subject: [PATCH 06/15] Fixed stat & grep FreeBSD compatibility --- testsuite/update-links.test | 39 ++++++++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/testsuite/update-links.test b/testsuite/update-links.test index c6b89a134..dd7ad14a8 100644 --- a/testsuite/update-links.test +++ b/testsuite/update-links.test @@ -7,6 +7,28 @@ DSELF="$(dirname "$LSRC")" . "${testsuite:-${DSELF}}/rsync.fns" +set_os_opts(){ + os=$(uname -s) + case ${os} in + Linux) + stat_opt=-c + size=%s + mtime=%Y + ctime=%Z + ;; + FreeBSD) + stat_opt=-f + size=%z + ctime=%Sc + mtime=%Sm + bsd="-t %s" + ;; + *) + test_fail "OS ${os} unsupported" + ;; + esac +} + log(){ echo "${SELF}: $@" } @@ -20,15 +42,15 @@ error(){ } get_ctime(){ - stat -c "%Y" "$1" || test_fail "Unable to get ctime of $1" + stat ${stat_opt} ${ctime} ${bsd} "$1" || test_fail "Unable to get ctime of $1" } get_mtime(){ - stat -c "%Y" "$1" || test_fail "Unable to get mtime of $1" + stat ${stat_opt} ${mtime} ${bsd} "$1" || test_fail "Unable to get mtime of $1" } get_size(){ - stat -c "%s" "$1" || test_fail "Unable to get size of $1" + stat ${stat_opt} ${size} "$1" || test_fail "Unable to get size of $1" } get_referent(){ @@ -154,13 +176,15 @@ shift_mtime(){ local f local epoch local rc + local time offset=$1 shift for f in $@; do - epoch=$(stat -c%Y "$f") + epoch=$(stat ${stat_opt} ${mtime} ${bsd} "$f") rc=$? debug "shift_mtime: $f; offset = $offset; rc = $rc; touch -hd @$((epoch${offset})) $f" - [ $rc -eq 0 ] && touch -hd @$((epoch${offset})) "$f" || error "Unable to stat $f" + case ${os} in FreeBSD) time=$(date -r $((epoch${offset})) +%Y-%m-%dT%H:%M:%S) ;; *) time=@$((epoch${offset})) ;; esac + [ $rc -eq 0 ] && touch -hd "${time}" "$f" || error "Unable to stat $f" done } @@ -173,7 +197,7 @@ take_a_second(){ } show_files(){ - stat -c "%Y %Z %N" $@ 1>&2 || test_fail "Unable to stat $@" + stat ${stat_opt} "${mtime} ${ctime} %N" ${bsd} $@ 1>&2 || test_fail "Unable to stat $@" } count_words(){ @@ -257,7 +281,7 @@ list_contains(){ $@ _EOT_ debug "list_contains: opt = $opt; opts = $opts" - echo "$opts" | grep -qPo -- "${opt}(?=$|\s)" 1>/dev/null + echo "$opts" | egrep -q -- " ${opt} |^${opt} | ${opt}$" 1>/dev/null } # Various scenarios that modify either side before re-running rsync & performing a check @@ -571,4 +595,5 @@ RSYNC=${RSYNC:-${DSELF}/../rsync} [ -n "$DEBUG" ] && RSYNC_COMMON_OPTS=${RSYNC_COMMON_OPTS:--i} RSYNC_OPTS="--update-links --times --allow-link-update-dir" +set_os_opts process_all_opts $fromdir $todir ${RSYNC_OPTS} From 95048a16b3a21d5ccc8157db5e97414d87644c25 Mon Sep 17 00:00:00 2001 From: twojstaryzdomu Date: Sun, 4 Jul 2021 01:22:02 +0200 Subject: [PATCH 07/15] ctime defaults to mtime due to inabilty to set it by non-root users --- testsuite/update-links.test | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/testsuite/update-links.test b/testsuite/update-links.test index dd7ad14a8..4b6f74540 100644 --- a/testsuite/update-links.test +++ b/testsuite/update-links.test @@ -13,13 +13,15 @@ set_os_opts(){ Linux) stat_opt=-c size=%s + # ctime is not normally modifiable by user, defaults to mtime + ctime=%Y mtime=%Y - ctime=%Z ;; FreeBSD) stat_opt=-f size=%z - ctime=%Sc + # ctime is not normally modifiable by user, defaults to mtime + ctime=%Sm mtime=%Sm bsd="-t %s" ;; From d4ef4612472bfb74d18492af6fe4899e71d48ea7 Mon Sep 17 00:00:00 2001 From: twojstaryzdomu Date: Tue, 6 Jul 2021 18:15:38 +0200 Subject: [PATCH 08/15] Added explicit checks for empty directory --- generator.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/generator.c b/generator.c index e56ed3cf3..beec8df53 100644 --- a/generator.c +++ b/generator.c @@ -1196,6 +1196,19 @@ static BOOL is_below(struct file_struct *file, struct file_struct *subtree) && (!implied_dirs_are_missing || f_name_has_prefix(file, subtree)); } +static BOOL dir_empty(char *dirname) { + int n = 3; + DIR *dir = opendir(dirname); + if (dir == NULL) + return -1; + while (readdir(dir)) { + if (!--n) + break; + } + closedir(dir); + return n; +} + /* Acts on the indicated item in cur_flist whose name is fname. If a dir, * make sure it exists, and has the right permissions/timestamp info. For * all other non-regular files (symlinks, etc.) we create them here. For @@ -1621,6 +1634,12 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx, goto cleanup; } else if (mtime_offset < modify_window) { + if (S_ISDIR(sx.st.st_mode)) { + if (!dir_empty(fname)) { + rprintf(FINFO, "directory %s not empty, skipping\n", fname); + goto cleanup; + } + } if (INFO_GTE(SKIP, 1)) rprintf(FINFO, "%s \"%s\" is older by %d sec, updating\n", st_mode, fname, - mtime_offset - modify_window); } From 9fc294a51962028ca57e2ff01176f3c66899e3df Mon Sep 17 00:00:00 2001 From: twojstaryzdomu Date: Tue, 6 Jul 2021 18:18:53 +0200 Subject: [PATCH 09/15] Fixed update-links checks --- testsuite/update-links.test | 39 +++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/testsuite/update-links.test b/testsuite/update-links.test index 4b6f74540..c81565f60 100644 --- a/testsuite/update-links.test +++ b/testsuite/update-links.test @@ -88,7 +88,7 @@ is_a_dir(){ } is_empty_dir(){ - is_a_dir && [ $(find "$1" | wc -l) -eq 1 ] + is_a_dir "$1" && [ $(find "$1" | wc -l) -eq 1 ] } points_to_dir(){ @@ -348,12 +348,17 @@ link_update_dir(){ read fromdir todir file scenario opts << _EOT_ $@ _EOT_ - debug "link_update_dir: $todir/$f" - # Make all target links directories + # Make all target files directories local f for f in $FILES; do + debug "link_update_dir: $todir/$f" rm $todir/$f 2>/dev/null || log "${scenario}: $f didn't exist" mkdir -p $todir/$f + case $f in + nonemptydir) + touch $todir/$f/testfile + ;; + esac # Make source link more recent than dir just created add_a_second $fromdir/$f show_files $todir/$f @@ -457,22 +462,26 @@ check_link_update_dir(){ read fromdir todir file scenario opts << _EOT_ $@ _EOT_ - if list_contains "--update-links" "$opts"; then - if list_contains "--allow-link-update-dir" "$opts"; then - if is_a_link $todir/$file; then - case $file in + if is_a_link $todir/$file; then + if list_contains "--update-links" "$opts"; then + if list_contains "--allow-link-update-dir" "$opts"; then + case $file in nonemptydir) - error "$file should never be replaced by a link" - ;; - *) - # Newer source links are supposed to be transfered - run_checks same $fromdir $todir $file $scenario $opts + error "$file should not have been replaced by a link" ;; esac else - is_empty_dir $todir/$file && error "$todir/$file has not been updated by a source link in spite of --allow-link-update-dir" + case $file in + *dir) + error "$file should not have been replaced by a link without --allow-link-update-dir enabled" + ;; + esac fi + else + error "$file should not have been replaced by a link without --update-links enabled" fi + # Newer source links are supposed to be transfered + run_checks same $fromdir $todir $file $scenario $opts fi } @@ -589,12 +598,12 @@ _EOT_ } # All of the following variables may be overriden at command line -FILES="${FILES:-relative dangling absolute unsafe devlink dirlink}" +FILES="${FILES:-relative dangling absolute unsafe devlink dirlink emptydir nonemptydir}" # For sanity testing only #SCENARIOS="test_success test_failure" SCENARIOS="${SCENARIOS:-default newer_source_link newer_dest_link cycle_dest_link cycle_dest_newer_source_link link_update_dir}" RSYNC=${RSYNC:-${DSELF}/../rsync} -[ -n "$DEBUG" ] && RSYNC_COMMON_OPTS=${RSYNC_COMMON_OPTS:--i} +[ -n "$DEBUG" ] && RSYNC_COMMON_OPTS=${RSYNC_COMMON_OPTS:--r -i} RSYNC_OPTS="--update-links --times --allow-link-update-dir" set_os_opts From 3c57a64e223de8450fac4ae35bab661309a50027 Mon Sep 17 00:00:00 2001 From: twojstaryzdomu Date: Thu, 15 Jul 2021 17:46:51 +0200 Subject: [PATCH 10/15] Avoid a redundant macro call --- flist.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/flist.c b/flist.c index cc9b44227..670f1b60d 100644 --- a/flist.c +++ b/flist.c @@ -239,15 +239,17 @@ int link_stat(const char *path, STRUCT_STAT *stp, int follow_dirlinks) return x_stat(path, stp, NULL); if (x_lstat(path, stp, NULL) < 0) return -1; - if (update_links && S_ISLNK(stp->st_mode)) { - STRUCT_STAT st; - if (x_stat(path, &st, NULL) == 0 && !S_ISDIR(st.st_mode)) - return x_lstat(path, stp, NULL); - } - if (follow_dirlinks && S_ISLNK(stp->st_mode)) { - STRUCT_STAT st; - if (x_stat(path, &st, NULL) == 0 && S_ISDIR(st.st_mode)) - *stp = st; + if (S_ISLNK(stp->st_mode)) { + if (update_links) { + STRUCT_STAT st; + if (x_stat(path, &st, NULL) == 0 && !S_ISDIR(st.st_mode)) + return x_lstat(path, stp, NULL); + } + if (follow_dirlinks) { + STRUCT_STAT st; + if (x_stat(path, &st, NULL) == 0 && S_ISDIR(st.st_mode)) + *stp = st; + } } return 0; #else From 8b5226e90d2faa39c81b4a6eb6ec24f8fffb3402 Mon Sep 17 00:00:00 2001 From: twojstaryzdomu Date: Sat, 8 Jan 2022 16:18:56 +0100 Subject: [PATCH 11/15] Revert "Avoid a redundant macro call" This reverts commit c3437c114dff2dd65e41fd30df62065253ce2530, the redundant macro call is intended to avoid running the macro if neither update_links nor follow_dirlinks are enabled. --- flist.c | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/flist.c b/flist.c index 670f1b60d..cc9b44227 100644 --- a/flist.c +++ b/flist.c @@ -239,17 +239,15 @@ int link_stat(const char *path, STRUCT_STAT *stp, int follow_dirlinks) return x_stat(path, stp, NULL); if (x_lstat(path, stp, NULL) < 0) return -1; - if (S_ISLNK(stp->st_mode)) { - if (update_links) { - STRUCT_STAT st; - if (x_stat(path, &st, NULL) == 0 && !S_ISDIR(st.st_mode)) - return x_lstat(path, stp, NULL); - } - if (follow_dirlinks) { - STRUCT_STAT st; - if (x_stat(path, &st, NULL) == 0 && S_ISDIR(st.st_mode)) - *stp = st; - } + if (update_links && S_ISLNK(stp->st_mode)) { + STRUCT_STAT st; + if (x_stat(path, &st, NULL) == 0 && !S_ISDIR(st.st_mode)) + return x_lstat(path, stp, NULL); + } + if (follow_dirlinks && S_ISLNK(stp->st_mode)) { + STRUCT_STAT st; + if (x_stat(path, &st, NULL) == 0 && S_ISDIR(st.st_mode)) + *stp = st; } return 0; #else From ca89b1e6bd6e03e085fcffbc05a96146785299f7 Mon Sep 17 00:00:00 2001 From: twojstaryzdomu Date: Tue, 11 Jan 2022 17:26:41 +0100 Subject: [PATCH 12/15] Avoid an unnecessary lstat call --- flist.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flist.c b/flist.c index cc9b44227..d163798f6 100644 --- a/flist.c +++ b/flist.c @@ -242,7 +242,7 @@ int link_stat(const char *path, STRUCT_STAT *stp, int follow_dirlinks) if (update_links && S_ISLNK(stp->st_mode)) { STRUCT_STAT st; if (x_stat(path, &st, NULL) == 0 && !S_ISDIR(st.st_mode)) - return x_lstat(path, stp, NULL); + return 0; } if (follow_dirlinks && S_ISLNK(stp->st_mode)) { STRUCT_STAT st; From 04026dc3616b7beb9ab59696cdc5691702dd3988 Mon Sep 17 00:00:00 2001 From: twojstaryzdomu Date: Wed, 27 Apr 2022 11:06:13 +0200 Subject: [PATCH 13/15] Documented order of precedence for symlink handling options --- rsync.1.md | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/rsync.1.md b/rsync.1.md index c271db3dc..a9fe9dcac 100644 --- a/rsync.1.md +++ b/rsync.1.md @@ -1075,6 +1075,9 @@ expand it. ([`-u`](#opt)) option mentioned above, which acts on regular source files only. + If specified in tandem, `--update-links` prevails over + [`--copy-links`](#opt) and [`--links`](#opt). + 0. `--allow-link-update-dir` Enabling this option allows rsync to replace a less recent empty directory @@ -1226,6 +1229,9 @@ expand it. files from being replaced by a link, refer to the [`--update-links`](#opt) option. + If specified in tandem, [`--update-links`](#opt) or [`--copy-links`](#opt) + prevail over `--links`. + See the [SYMBOLIC LINKS](#) section for multi-option info. 0. `--copy-links`, `-L` @@ -1235,8 +1241,8 @@ expand it. references. If a symlink chain is broken, an error is output and the file is dropped from the transfer. - This option supersedes any other options that affect symlinks in the - transfer, since there are no symlinks left in the transfer. + This option supersedes any other option that affect symlinks in the + transfer but [`--update-links`](#opt), which takes precedence. This option does not change the handling of existing symlinks on the receiving side, unlike versions of rsync prior to 2.6.3 which had the @@ -4673,6 +4679,14 @@ first line that is a complete subset of your options: recent files existing on the destination, allow a less recent directory to be replaced by a more recent symlink. +Note that some options are mutually exclusive. The first option listed +below prevails over the others, when multiple symlink mode options are +specified. + +0. `--update-links` +0. `--copy-links` +0. `--links` + For the effect of [`--munge-links`](#opt), see the discussion in that option's section. From 7a1227e98a8d2fc2df542f16783dbf5d75a867b6 Mon Sep 17 00:00:00 2001 From: twojstaryzdomu Date: Wed, 27 Apr 2022 14:29:34 +0200 Subject: [PATCH 14/15] Added Debian package build configuration --- debian/compat | 1 + debian/control | 16 +++++++++ debian/patches/disable_reconfigure_req.diff | 38 +++++++++++++++++++++ debian/patches/series | 1 + debian/rules | 3 ++ debian/watch | 3 ++ options.c | 2 ++ 7 files changed, 64 insertions(+) create mode 100644 debian/compat create mode 100644 debian/control create mode 100644 debian/patches/disable_reconfigure_req.diff create mode 100644 debian/patches/series create mode 100644 debian/rules create mode 100644 debian/watch diff --git a/debian/compat b/debian/compat new file mode 100644 index 000000000..b4de39476 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +11 diff --git a/debian/control b/debian/control new file mode 100644 index 000000000..39d8275bc --- /dev/null +++ b/debian/control @@ -0,0 +1,16 @@ +Source: rsync +Section: unknown +Priority: optional +Maintainer: unknown +Build-Depends: debhelper (>= 11) +Standards-Version: 4.1.3 +Homepage: https://github.com/twojstaryzdomu/rsync +Vcs-Git: https://github.com/twojstaryzdomu/rsync.git +Vcs-Browser: https://github.com/twojstaryzdomu/rsync + + +Package: rsync +Section: unknown +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends} +Description: rsync description diff --git a/debian/patches/disable_reconfigure_req.diff b/debian/patches/disable_reconfigure_req.diff new file mode 100644 index 000000000..1dce71069 --- /dev/null +++ b/debian/patches/disable_reconfigure_req.diff @@ -0,0 +1,38 @@ +diff --git a/Makefile.in b/Makefile.in +index 3cde955..ef71d7e 100644 +--- a/Makefile.in ++++ b/Makefile.in +@@ -210,15 +210,6 @@ configure.sh config.h.in: configure.ac aclocal.m4 + else \ + echo "config.h.in has CHANGED."; \ + fi +- @if test -f configure.sh.old || test -f config.h.in.old; then \ +- if test "$(MAKECMDGOALS)" = reconfigure; then \ +- echo 'Continuing with "make reconfigure".'; \ +- else \ +- echo 'You may need to run:'; \ +- echo ' make reconfigure'; \ +- exit 1; \ +- fi \ +- fi + + .PHONY: reconfigure + reconfigure: configure.sh +@@ -232,17 +223,6 @@ restatus: + Makefile: Makefile.in config.status configure.sh config.h.in + @if test -f Makefile; then cp -p Makefile Makefile.old; else touch Makefile.old; fi + @./config.status +- @if diff Makefile Makefile.old >/dev/null 2>&1; then \ +- echo "Makefile is unchanged."; \ +- rm Makefile.old; \ +- else \ +- if test "$(MAKECMDGOALS)" = reconfigure; then \ +- echo 'Continuing with "make reconfigure".'; \ +- else \ +- echo "Makefile updated -- rerun your make command."; \ +- exit 1; \ +- fi \ +- fi + + stunnel-rsyncd.conf: $(srcdir)/stunnel-rsyncd.conf.in Makefile + sed 's;\@bindir\@;$(bindir);g' <$(srcdir)/stunnel-rsyncd.conf.in >stunnel-rsyncd.conf diff --git a/debian/patches/series b/debian/patches/series new file mode 100644 index 000000000..0a5931483 --- /dev/null +++ b/debian/patches/series @@ -0,0 +1 @@ +disable_reconfigure_req.diff diff --git a/debian/rules b/debian/rules new file mode 100644 index 000000000..cbe925d75 --- /dev/null +++ b/debian/rules @@ -0,0 +1,3 @@ +#!/usr/bin/make -f +%: + dh $@ diff --git a/debian/watch b/debian/watch new file mode 100644 index 000000000..6c67a454c --- /dev/null +++ b/debian/watch @@ -0,0 +1,3 @@ +version=4 +opts=filenamemangle=s/.+\/v?(\d\S+)\.tar\.gz/rsync-$1\.tar\.gz/ \ + https://github.com/twojstaryzdomu/rsync/releases .*/v?(\d\S+)\.tar\.gz diff --git a/options.c b/options.c index 277a0542a..521be4adc 100644 --- a/options.c +++ b/options.c @@ -1384,6 +1384,8 @@ int parse_arguments(int *argc_p, const char ***argv_p) * only special cases are returned and listed here. */ switch (opt) { + case 'L': + break; case 'V': version_opt_cnt++; break; From aff59bb50b523185b03a3b5a6cb003d9240f12cf Mon Sep 17 00:00:00 2001 From: twojstaryzdomu Date: Wed, 27 Apr 2022 14:31:09 +0200 Subject: [PATCH 15/15] Added Debian package build & publish workflow using github actions --- .github/workflows/build-publish.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 .github/workflows/build-publish.yml diff --git a/.github/workflows/build-publish.yml b/.github/workflows/build-publish.yml new file mode 100644 index 000000000..f8d6233d3 --- /dev/null +++ b/.github/workflows/build-publish.yml @@ -0,0 +1,27 @@ +name: Build & publish +on: {push: {branches: [master]}, pull_request: {branches: [master]}, workflow_dispatch} + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: '0' + - id: debianise + uses: twojstaryzdomu/debianise@HEAD + with: + create_changelog: true + install_build_depends: false + debug: true + - id: action-gh-release + uses: softprops/action-gh-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + files: ${{ steps.debianise.outputs.files }} + name: ${{ steps.debianise.outputs.release_name }} + tag_name: ${{ steps.debianise.outputs.tag_name }} + fail_on_unmatched_files: true + draft: true + prerelease: true