extern int preserve_xattrs;
extern int need_messages_from_generator;
extern int delete_mode, delete_before, delete_during, delete_after;
-extern char *shell_cmd; /* contains VER.SUB string if client is a pre-release */
+extern char *shell_cmd;
extern char *partial_dir;
extern char *dest_option;
extern char *files_from;
/* These index values are for the file-list's extra-attribute array. */
int uid_ndx, gid_ndx, acls_ndx, xattrs_ndx, unsort_ndx;
+int receiver_symlink_times = 0; /* receiver can set the time on a symlink */
+
#ifdef ICONV_OPTION
int filesfrom_convert = 0;
#endif
+#define CF_INC_RECURSE (1<<0)
+#define CF_SYMLINK_TIMES (1<<1)
+
+static const char *client_info;
+
/* The server makes sure that if either side only supports a pre-release
* version of a protocol, that both sides must speak a compatible version
* of that protocol for it to be advertised as available. */
int our_sub = 0;
#endif
- if (!shell_cmd || !(dot = strchr(shell_cmd, '.'))
- || !(their_protocol = atoi(shell_cmd))
+ /* client_info starts with VER.SUB string if client is a pre-release. */
+ if (!(their_protocol = atoi(client_info))
+ || !(dot = strchr(client_info, '.'))
|| !(their_sub = atoi(dot+1))) {
#if SUBPROTOCOL_VERSION != 0
if (our_sub)
void set_allow_inc_recurse(void)
{
+ client_info = shell_cmd ? shell_cmd : "";
+
if (!recurse || use_qsort)
allow_inc_recurse = 0;
else if (!am_sender
|| delay_updates || prune_empty_dirs))
allow_inc_recurse = 0;
else if (am_server && !local_server
- && (!shell_cmd || strchr(shell_cmd, 'i') == NULL))
+ && (strchr(client_info, 'i') == NULL))
allow_inc_recurse = 0;
}
exit_cleanup(RERR_PROTOCOL);
}
} else if (protocol_version >= 30) {
- /* The inc_recurse var MUST be set to 0 or 1. */
+ int compat_flags;
if (am_server) {
- inc_recurse = allow_inc_recurse ? 1 : 0;
- write_byte(f_out, inc_recurse);
+ compat_flags = allow_inc_recurse ? CF_INC_RECURSE : 0;
+#if defined HAVE_LUTIMES && defined HAVE_UTIMES
+ compat_flags |= CF_SYMLINK_TIMES;
+#endif
+ write_byte(f_out, compat_flags);
} else
- inc_recurse = read_byte(f_in) ? 1 : 0;
+ compat_flags = read_byte(f_in);
+ /* The inc_recurse var MUST be set to 0 or 1. */
+ inc_recurse = compat_flags & CF_INC_RECURSE ? 1 : 0;
+ if (am_sender) {
+ receiver_symlink_times = am_server
+ ? strchr(client_info, 'L') != NULL
+ : !!(compat_flags & CF_SYMLINK_TIMES);
+ }
+#if defined HAVE_LUTIMES && defined HAVE_UTIMES
+ else
+ receiver_symlink_times = 1;
+#endif
if (inc_recurse && !allow_inc_recurse) {
/* This should only be able to happen in a batch. */
fprintf(stderr,
extern int do_xfers;
extern int stdout_format_has_i;
extern int logfile_format_has_i;
+extern int receiver_symlink_times;
extern int am_root;
extern int am_server;
extern int am_daemon;
int unchanged_attrs(const char *fname, struct file_struct *file, stat_x *sxp)
{
-#ifndef HAVE_LUTIMES
+#if !defined HAVE_LUTIMES || !defined HAVE_UTIMES
if (S_ISLNK(file->mode)) {
;
} else
{
if (statret >= 0) { /* A from-dest-dir statret can == 1! */
int keep_time = !preserve_times ? 0
- : S_ISDIR(file->mode) ? preserve_times > 1
- : !S_ISLNK(file->mode);
+ : S_ISDIR(file->mode) ? preserve_times > 1 :
+#if defined HAVE_LUTIMES && defined HAVE_UTIMES
+ (receiver_symlink_times && !(file->flags & FLAG_TIME_FAILED)) ||
+#endif
+ !S_ISLNK(file->mode);
if (S_ISREG(file->mode) && F_LENGTH(file) != sxp->st.st_size)
iflags |= ITEM_REPORT_SIZE;
extern int stdout_format_has_o_or_i;
extern int logfile_format_has_i;
extern int logfile_format_has_o_or_i;
+extern int receiver_symlink_times;
extern mode_t orig_umask;
extern char *auth_user;
extern char *stdout_format;
c[2] = !(iflags & ITEM_REPORT_CHECKSUM) ? '.' : 'c';
c[3] = !(iflags & ITEM_REPORT_SIZE) ? '.' : 's';
c[4] = !(iflags & ITEM_REPORT_TIME) ? '.'
- : !preserve_times || S_ISLNK(file->mode) ? 'T' : 't';
+ : !preserve_times || (!receiver_symlink_times && S_ISLNK(file->mode))
+ ? 'T' : 't';
c[5] = !(iflags & ITEM_REPORT_PERMS) ? '.' : 'p';
c[6] = !(iflags & ITEM_REPORT_OWNER) ? '.' : 'o';
c[7] = !(iflags & ITEM_REPORT_GROUP) ? '.' : 'g';
char const *got_socketpair = "no ";
char const *have_inplace = "no ";
char const *hardlinks = "no ";
+ char const *symtimes = "no ";
char const *acls = "no ";
char const *xattrs = "no ";
char const *links = "no ";
#ifdef ICONV_OPTION
iconv = "";
#endif
+#if defined HAVE_LUTIMES && defined HAVE_UTIMES
+ symtimes = "";
+#endif
rprintf(f, "%s version %s protocol version %d%s\n",
RSYNC_NAME, RSYNC_VERSION, PROTOCOL_VERSION, subprotocol);
(int)(sizeof (int64) * 8));
rprintf(f, " %ssocketpairs, %shardlinks, %ssymlinks, %sIPv6, batchfiles, %sinplace,\n",
got_socketpair, hardlinks, links, ipv6, have_inplace);
- rprintf(f, " %sappend, %sACLs, %sxattrs, %siconv\n",
- have_inplace, acls, xattrs, iconv);
+ rprintf(f, " %sappend, %sACLs, %sxattrs, %siconv, %ssymtimes\n",
+ have_inplace, acls, xattrs, iconv, symtimes);
#ifdef MAINTAINER_MODE
rprintf(f, "Panic Action: \"%s\"\n", get_panic_action());
argstr[x++] = 'z';
/* We make use of the -e option to let the server know about any
- * pre-release protocol version && our allow_inc_recurse status. */
- set_allow_inc_recurse();
+ * pre-release protocol version && some behavior flags. */
+ argstr[x++] = 'e';
#if SUBPROTOCOL_VERSION != 0
if (protocol_version == PROTOCOL_VERSION) {
x += snprintf(argstr+x, sizeof argstr - x,
- "e%d.%d%s", PROTOCOL_VERSION, SUBPROTOCOL_VERSION,
- allow_inc_recurse ? "i" : "");
+ "%d.%d", PROTOCOL_VERSION, SUBPROTOCOL_VERSION);
} else
#endif
- if (allow_inc_recurse) {
- argstr[x++] = 'e';
+ argstr[x++] = '.';
+ set_allow_inc_recurse();
+ if (allow_inc_recurse)
argstr[x++] = 'i';
- }
-
+#if defined HAVE_LUTIMES && defined HAVE_UTIMES
+ argstr[x++] = 'L';
+#endif
argstr[x] = '\0';
- if (x != 1)
- args[ac++] = argstr;
+ args[ac++] = argstr;
#ifdef ICONV_OPTION
if (iconv_opt) {
}
if (ret == 0) /* ret == 1 if symlink could not be set */
updated = 1;
+ else
+ file->flags |= FLAG_TIME_FAILED;
}
change_uid = am_root && uid_ndx && sxp->st.st_uid != (uid_t)F_OWNER(file);
#define FLAG_HLINK_DONE (1<<8) /* receiver/generator (checked on all types) */
#define FLAG_LENGTH64 (1<<9) /* sender/receiver/generator */
#define FLAG_SKIP_GROUP (1<<10) /* receiver/generator */
+#define FLAG_TIME_FAILED (1<<11)/* generator */
/* These flags are passed to functions but not stored. */
/* This is used when working on a new protocol version in CVS, and should
* be a new non-zero value for each CVS change that affects the protocol.
* It must ALWAYS be 0 when the protocol goes final! */
-#define SUBPROTOCOL_VERSION 16
+#define SUBPROTOCOL_VERSION 17
/* We refuse to interoperate with versions that are not in this range.
* Note that we assume we'll work with later versions: the onus is on
it() A bf(t) means the modification time is different and is being updated
to the sender's value (requires bf(--times)). An alternate value of bf(T)
means that the modification time will be set to the transfer time, which happens
- anytime a symlink is transferred, or when a regular file or device is
- transferred without bf(--times).
+ when a file/symlink/device is updated without bf(--times) and when a
+ symlink is changed and the receiver can't set its time.
it() A bf(p) means the permissions are different and are being updated to
the sender's value (requires bf(--perms)).
it() An bf(o) means the owner is different and is being updated to the
ln "$fromdir/foo/config1" "$fromdir/foo/extra"
# Check if the OS can hard-link symlinks or not
-ln -s no-such-dir "$to2dir"
-if ln "$to2dir" "$to2dir.test" 2>/dev/null; then
+if ln "$fromdir/foo/sym" "$to2dir" 2>/dev/null; then
L=hL
else
L=cL
fi
-rm -f "$to2dir" "$to2dir.test"
+rm -f "$to2dir"
+
+# Check if rsync can preserve time on symlinks
+case "$RSYNC" in
+*protocol=2*)
+ T=.T
+ ;;
+*)
+ if $RSYNC --version | grep ", symtimes" >/dev/null; then
+ T=.t
+ else
+ T=.T
+ fi
+ ;;
+esac
$RSYNC -iplr "$fromdir/" "$todir/" \
| tee "$outfile"
.d..t...... foo/
.f..t...... foo/config1
>fcstp..... foo/config2
-cL..T...... foo/sym -> ../bar/baz/rsync
+cL.$T...... foo/sym -> ../bar/baz/rsync
EOT
diff $diffopt "$chkfile" "$outfile" || test_fail "test 3 failed"
$RSYNC -ivvplrtH --copy-dest=../to "$fromdir/" "$to2dir/" \
| tee "$outfile"
filter_outfile
+case `tail -1 "$outfile"` in
+cL..t*)
+ sym_dots='..t......'
+ L_sym_dots='cL..t......'
+ is_uptodate='-> ../bar/baz/rsync'
+ echo "cL$sym_dots foo/sym $is_uptodate" >"$chkfile.extra"
+ L=cL
+ ;;
+*)
+ sym_dots=' '
+ L_sym_dots='.L '
+ is_uptodate='is uptodate'
+ touch "$chkfile.extra"
+ ;;
+esac
cat <<EOT >"$chkfile"
cd ./
cd bar/
cf foo/config1
cf foo/config2
hf foo/extra => foo/config1
-cL foo/sym -> ../bar/baz/rsync
+cL$sym_dots foo/sym -> ../bar/baz/rsync
EOT
diff $diffopt "$chkfile" "$outfile" || test_fail "test 8 failed"
rm -rf "$to2dir"
$RSYNC -iplrtH --copy-dest=../to "$fromdir/" "$to2dir/" \
| tee "$outfile"
-cat <<EOT >"$chkfile"
+cat - "$chkfile.extra" <<EOT >"$chkfile"
hf foo/extra => foo/config1
EOT
diff $diffopt "$chkfile" "$outfile" || test_fail "test 9 failed"
foo/config1 is uptodate
foo/config2 is uptodate
foo/extra => foo/config1
-foo/sym is uptodate
+foo/sym $is_uptodate
EOT
diff $diffopt "$chkfile" "$outfile" || test_fail "test 10 failed"
hf foo/config1
hf foo/config2
hf foo/extra => foo/config1
-$L foo/sym -> ../bar/baz/rsync
+$L$sym_dots foo/sym -> ../bar/baz/rsync
EOT
diff $diffopt "$chkfile" "$outfile" || test_fail "test 11 failed"
rm -rf "$to2dir"
$RSYNC -iplrtH --dry-run --link-dest=../to "$fromdir/" "$to2dir/" \
| tee "$outfile"
-cat <<EOT >"$chkfile"
+cat - "$chkfile.extra" <<EOT >"$chkfile"
EOT
diff $diffopt "$chkfile" "$outfile" || test_fail "test 12 failed"
rm -rf "$to2dir"
$RSYNC -iplrtH --link-dest=../to "$fromdir/" "$to2dir/" \
| tee "$outfile"
-cat <<EOT >"$chkfile"
+cat - "$chkfile.extra" <<EOT >"$chkfile"
EOT
diff $diffopt "$chkfile" "$outfile" || test_fail "test 13 failed"
foo/config1 is uptodate
foo/config2 is uptodate
foo/extra is uptodate
-foo/sym is uptodate
+foo/sym $is_uptodate
EOT
diff $diffopt "$chkfile" "$outfile" || test_fail "test 14 failed"
.f foo/config1
.f foo/config2
.f foo/extra
-.L foo/sym -> ../bar/baz/rsync
+$L_sym_dots foo/sym -> ../bar/baz/rsync
EOT
diff $diffopt "$chkfile" "$outfile" || test_fail "test 15 failed"
rm -rf "$to2dir"
$RSYNC -iplrtH --compare-dest="$todir" "$fromdir/" "$to2dir/" \
| tee "$outfile"
-cat <<EOT >"$chkfile"
+cat - "$chkfile.extra" <<EOT >"$chkfile"
EOT
diff $diffopt "$chkfile" "$outfile" || test_fail "test 16 failed"
foo/config1 is uptodate
foo/config2 is uptodate
foo/extra is uptodate
-foo/sym is uptodate
+foo/sym $is_uptodate
EOT
diff $diffopt "$chkfile" "$outfile" || test_fail "test 17 failed"