To use this patch, run these commands for a successful build: patch -p1 modtime; + if (atimes_ndx && !S_ISDIR(mode)) { + time_t file_atime = F_ATIME(file); + if (file_atime == atime) + xflags |= XMIT_SAME_ATIME; + else + atime = file_atime; + } #ifdef SUPPORT_HARD_LINKS if (tmp_dev != 0) { @@ -517,6 +526,8 @@ static void send_file_entry(int f, struc } if (!(xflags & XMIT_SAME_MODE)) write_int(f, to_wire_mode(mode)); + if (atimes_ndx && !S_ISDIR(mode) && !(xflags & XMIT_SAME_ATIME)) + write_varlong(f, atime, 4); if (uid_ndx && !(xflags & XMIT_SAME_UID)) { if (protocol_version < 30) write_int(f, uid); @@ -603,7 +614,7 @@ static void send_file_entry(int f, struc static struct file_struct *recv_file_entry(struct file_list *flist, int xflags, int f) { - static int64 modtime; + static int64 modtime, atime; static mode_t mode; #ifdef SUPPORT_HARD_LINKS static int64 dev; @@ -736,6 +747,16 @@ static struct file_struct *recv_file_ent } if (!(xflags & XMIT_SAME_MODE)) mode = from_wire_mode(read_int(f)); + if (atimes_ndx && !S_ISDIR(mode) && !(xflags & XMIT_SAME_ATIME)) { + atime = read_varlong(f, 4); +#if SIZEOF_TIME_T < SIZEOF_INT64 + if ((atime > INT_MAX || atime < INT_MIN) && !am_generator) { + rprintf(FERROR, + "Access time value of %s truncated on receiver.\n", + lastname); + } +#endif + } if (chmod_modes && !S_ISLNK(mode)) mode = tweak_mode(mode, chmod_modes); @@ -864,6 +885,8 @@ static struct file_struct *recv_file_ent F_GROUP(file) = gid; file->flags |= gid_flags; } + if (atimes_ndx) + F_ATIME(file) = (uint32)atime; if (unsort_ndx) F_NDX(file) = flist->used + flist->ndx_start; @@ -1186,6 +1209,8 @@ struct file_struct *make_file(const char F_OWNER(file) = st.st_uid; if (gid_ndx) F_GROUP(file) = st.st_gid; + if (atimes_ndx) + F_ATIME(file) = (uint32)st.st_atime; if (basename != thisname) file->dirname = lastdir; --- old/generator.c +++ new/generator.c @@ -43,6 +43,7 @@ extern int preserve_specials; extern int preserve_hard_links; extern int preserve_perms; extern int preserve_times; +extern int preserve_atimes; extern int uid_ndx; extern int gid_ndx; extern int delete_mode; @@ -568,6 +569,9 @@ void itemize(const char *fnamecmp, struc && (!(iflags & ITEM_XNAME_FOLLOWS) || *xname)) || (keep_time && cmp_time(file->modtime, sxp->st.st_mtime) != 0)) iflags |= ITEM_REPORT_TIME; + if (preserve_atimes && !S_ISDIR(file->mode) && !S_ISLNK(file->mode) + && cmp_time(F_ATIME(file), sxp->st.st_atime) != 0) + iflags |= ITEM_REPORT_ATIME; #if !defined HAVE_LCHMOD && !defined HAVE_SETATTRLIST if (S_ISLNK(file->mode)) { ; @@ -923,6 +927,8 @@ static int try_dests_reg(struct file_str if (link_dest) { if (!hard_link_one(file, fname, cmpbuf, 1)) goto try_a_copy; + if (preserve_atimes) + set_file_attrs(fname, file, sxp, NULL, 0); if (preserve_hard_links && F_IS_HLINKED(file)) finish_hard_link(file, fname, ndx, &sxp->st, itemizing, code, j); if (itemizing && (verbose > 1 || stdout_format_has_i > 1)) { @@ -1113,6 +1119,7 @@ static int try_dests_non(struct file_str static void list_file_entry(struct file_struct *f) { char permbuf[PERMSTRING_SIZE]; + time_t atime = atimes_ndx ? F_ATIME(f) : 0; double len; if (!F_IS_ACTIVE(f)) { @@ -1127,14 +1134,16 @@ static void list_file_entry(struct file_ #ifdef SUPPORT_LINKS if (preserve_links && S_ISLNK(f->mode)) { - rprintf(FINFO, "%s %11.0f %s %s -> %s\n", + rprintf(FINFO, "%s %11.0f %s %s %s -> %s\n", permbuf, len, timestring(f->modtime), + atimes_ndx ? timestring(atime) : "", f_name(f, NULL), F_SYMLINK(f)); } else #endif { - rprintf(FINFO, "%s %11.0f %s %s\n", + rprintf(FINFO, "%s %11.0f %s %s %s\n", permbuf, len, timestring(f->modtime), + atimes_ndx ? timestring(atime) : "", f_name(f, NULL)); } } @@ -1884,7 +1893,7 @@ static void touch_up_dirs(struct file_li if (!(file->mode & S_IWUSR)) do_chmod(fname, file->mode); if (need_retouch_dir_times) - set_modtime(fname, file->modtime, file->mode); + set_times(fname, file->modtime, file->modtime, file->mode); if (allowed_lull && !(counter % lull_mod)) maybe_send_keepalive(); else if (!(counter & 0xFF)) --- old/log.c +++ new/log.c @@ -631,7 +631,8 @@ static void log_formatted(enum logcode c c[5] = !(iflags & ITEM_REPORT_PERMS) ? '.' : 'p'; c[6] = !(iflags & ITEM_REPORT_OWNER) ? '.' : 'o'; c[7] = !(iflags & ITEM_REPORT_GROUP) ? '.' : 'g'; - c[8] = !(iflags & ITEM_REPORT_ATIME) ? '.' : 'u'; + c[8] = !(iflags & ITEM_REPORT_ATIME) ? '.' + : S_ISLNK(file->mode) ? 'U' : 'u'; c[9] = !(iflags & ITEM_REPORT_ACL) ? '.' : 'a'; c[10] = !(iflags & ITEM_REPORT_XATTR) ? '.' : 'x'; c[11] = '\0'; --- old/options.c +++ new/options.c @@ -57,6 +57,7 @@ int preserve_specials = 0; int preserve_uid = 0; int preserve_gid = 0; int preserve_times = 0; +int preserve_atimes = 0; int update_only = 0; int cvs_exclude = 0; int dry_run = 0; @@ -346,6 +347,7 @@ void usage(enum logcode F) rprintf(F," -D same as --devices --specials\n"); rprintf(F," -t, --times preserve modification times\n"); rprintf(F," -O, --omit-dir-times omit directories from --times\n"); + rprintf(F," -U, --atimes preserve access (last-used) times\n"); rprintf(F," --super receiver attempts super-user activities\n"); #ifdef SUPPORT_XATTRS rprintf(F," --fake-super store/recover privileged attrs using xattrs\n"); @@ -480,6 +482,9 @@ static struct poptOption long_options[] {"times", 't', POPT_ARG_VAL, &preserve_times, 2, 0, 0 }, {"no-times", 0, POPT_ARG_VAL, &preserve_times, 0, 0, 0 }, {"no-t", 0, POPT_ARG_VAL, &preserve_times, 0, 0, 0 }, + {"atimes", 'U', POPT_ARG_VAL, &preserve_atimes, 1, 0, 0 }, + {"no-atimes", 0, POPT_ARG_VAL, &preserve_atimes, 0, 0, 0 }, + {"no-U", 0, POPT_ARG_VAL, &preserve_atimes, 0, 0, 0 }, {"omit-dir-times", 'O', POPT_ARG_VAL, &omit_dir_times, 1, 0, 0 }, {"no-omit-dir-times",0, POPT_ARG_VAL, &omit_dir_times, 0, 0, 0 }, {"no-O", 0, POPT_ARG_VAL, &omit_dir_times, 0, 0, 0 }, @@ -1713,6 +1718,8 @@ void server_options(char **args, int *ar argstr[x++] = 'D'; if (preserve_times) argstr[x++] = 't'; + if (preserve_atimes) + argstr[x++] = 'U'; if (preserve_perms) argstr[x++] = 'p'; else if (preserve_executability && am_sender) --- old/rsync.c +++ new/rsync.c @@ -33,6 +33,7 @@ extern int preserve_acls; extern int preserve_xattrs; extern int preserve_perms; extern int preserve_executability; +extern int preserve_atimes; extern int preserve_times; extern int am_root; extern int am_server; @@ -343,6 +344,7 @@ int set_file_attrs(const char *fname, st int updated = 0; stat_x sx2; int change_uid, change_gid; + time_t atime, mtime; mode_t new_mode = file->mode; int inherit; @@ -383,18 +385,36 @@ int set_file_attrs(const char *fname, st set_stat_xattr(fname, file); #endif + /* This code must be the first update in the function due to + * how it uses the "updated" variable. */ if (!preserve_times || (S_ISDIR(sxp->st.st_mode) && preserve_times == 1)) flags |= ATTRS_SKIP_MTIME; + if (!preserve_atimes || S_ISDIR(sxp->st.st_mode)) + flags |= ATTRS_SKIP_ATIME; if (!(flags & ATTRS_SKIP_MTIME) && cmp_time(sxp->st.st_mtime, file->modtime) != 0) { - int ret = set_modtime(fname, file->modtime, sxp->st.st_mode); + mtime = file->modtime; + updated = 1; + } else + mtime = sxp->st.st_mtime; + if (!(flags & ATTRS_SKIP_ATIME)) { + time_t file_atime = F_ATIME(file); + if (cmp_time(sxp->st.st_atime, file_atime) != 0) { + atime = file_atime; + updated = 1; + } else + atime = sxp->st.st_atime; + } else + atime = sxp->st.st_atime; + if (updated) { + int ret = set_times(fname, mtime, atime, sxp->st.st_mode); if (ret < 0) { rsyserr(FERROR, errno, "failed to set times on %s", full_fname(fname)); goto cleanup; } - if (ret == 0) /* ret == 1 if symlink could not be set */ - updated = 1; + if (ret > 0) /* ret == 1 if symlink could not be set */ + updated = 0; } change_uid = am_root && uid_ndx && sxp->st.st_uid != (uid_t)F_OWNER(file); --- old/rsync.h +++ new/rsync.h @@ -57,6 +57,7 @@ #define XMIT_RDEV_MINOR_8_pre30 (1<<11) /* protocols 28 - 29 */ #define XMIT_GROUP_NAME_FOLLOWS (1<<11) /* protocols 30 - now */ #define XMIT_HLINK_FIRST (1<<12) /* protocols 30 - now (HLINKED files only) */ +#define XMIT_SAME_ATIME (1<<13) /* protocols ?? - now */ /* These flags are used in the live flist data. */ @@ -145,6 +146,7 @@ #define ATTRS_REPORT (1<<0) #define ATTRS_SKIP_MTIME (1<<1) +#define ATTRS_SKIP_ATIME (1<<2) #define FULL_FLUSH 1 #define NORMAL_FLUSH 0 @@ -608,6 +610,7 @@ extern int file_extra_cnt; extern int inc_recurse; extern int uid_ndx; extern int gid_ndx; +extern int atimes_ndx; extern int acls_ndx; extern int xattrs_ndx; @@ -645,6 +648,7 @@ extern int xattrs_ndx; /* When the associated option is on, all entries will have these present: */ #define F_OWNER(f) REQ_EXTRA(f, uid_ndx)->unum #define F_GROUP(f) REQ_EXTRA(f, gid_ndx)->unum +#define F_ATIME(f) REQ_EXTRA(f, atimes_ndx)->unum #define F_ACL(f) REQ_EXTRA(f, acls_ndx)->num #define F_XATTR(f) REQ_EXTRA(f, xattrs_ndx)->num #define F_NDX(f) REQ_EXTRA(f, unsort_ndx)->num --- old/rsync.yo +++ new/rsync.yo @@ -349,6 +349,7 @@ to the detailed description below for a -D same as --devices --specials -t, --times preserve modification times -O, --omit-dir-times omit directories from --times + -U, --atimes preserve access (use) times --super receiver attempts super-user activities --fake-super store/recover privileged attrs using xattrs -S, --sparse handle sparse files efficiently @@ -973,6 +974,12 @@ it is preserving modification times (see the directories on the receiving side, it is a good idea to use bf(-O). This option is inferred if you use bf(--backup) without bf(--backup-dir). +dit(bf(-U, --atimes)) This tells rsync to set the access (use) times of the +destination files to the same value as the source files. Note that the +reading of the source file may update the atime of the source files, so +repeated rsync runs with --atimes may be needed if you want to force the +access-time values to be 100% identical on the two systems. + dit(bf(--super)) This tells the receiving side to attempt super-user activities even if the receiving rsync wasn't run by the super-user. These activities include: preserving users via the bf(--owner) option, preserving @@ -1654,8 +1661,10 @@ quote(itemization( sender's value (requires bf(--owner) and super-user privileges). it() A bf(g) means the group is different and is being updated to the sender's value (requires bf(--group) and the authority to set the group). - it() The bf(u) slot is reserved for reporting update (access) time changes - (a feature that is not yet released). + it() A bf(u) means the access (use) time is different and is being updated to + the sender's value (requires bf(--atimes)). An alternate value of bf(U) + means that the access time will be set to the transfer time, which happens + when a symlink or directory is updated. it() The bf(a) means that the ACL information changed. it() The bf(x) slot is reserved for reporting extended attribute changes (a feature that is not yet released). --- old/sender.c +++ new/sender.c @@ -43,6 +43,7 @@ extern int do_progress; extern int inplace; extern int batch_fd; extern int write_batch; +extern unsigned int file_struct_len; extern struct stats stats; extern struct file_list *cur_flist, *first_flist, *dir_flist; --- old/testsuite/atimes.test +++ new/testsuite/atimes.test @@ -0,0 +1,19 @@ +#! /bin/sh + +# Test rsync copying atimes + +. "$suitedir/rsync.fns" + +set -x + +mkdir "$fromdir" + +touch "$fromdir/foo" +touch -a -t 200102031717.42 "$fromdir/foo" + +TLS_ARGS=--atime + +checkit "$RSYNC -rtUgvvv \"$fromdir/\" \"$todir/\"" "$fromdir" "$todir" + +# The script would have aborted on error, so getting here means we've won. +exit 0 --- old/testsuite/rsync.fns +++ new/testsuite/rsync.fns @@ -187,6 +187,10 @@ checkit() { # We can just write everything to stdout/stderr, because the # wrapper hides it unless there is a problem. + if test x$TLS_ARGS = x--atime; then + ( cd "$2" && rsync_ls_lR . ) > "$tmpdir/ls-from" + fi + echo "Running: \"$1\"" eval "$1" status=$? @@ -194,10 +198,13 @@ checkit() { failed="YES"; fi + if test x$TLS_ARGS != x--atime; then + ( cd "$2" && rsync_ls_lR . ) > "$tmpdir/ls-from" + fi + echo "-------------" echo "check how the directory listings compare with diff:" echo "" - ( cd "$2" && rsync_ls_lR . ) > "$tmpdir/ls-from" ( cd "$3" && rsync_ls_lR . ) > "$tmpdir/ls-to" diff $diffopt "$tmpdir/ls-from" "$tmpdir/ls-to" || failed=YES --- old/tls.c +++ new/tls.c @@ -104,6 +104,8 @@ static int stat_xattr(const char *fname, #endif +static int display_atime = 0; + static void failed(char const *what, char const *where) { fprintf(stderr, PROGRAM ": %s %s: %s\n", @@ -111,12 +113,29 @@ static void failed(char const *what, cha exit(1); } +static void storetime(char *dest, time_t t, size_t destsize) +{ + if (t) { + struct tm *mt = gmtime(&t); + + snprintf(dest, destsize, + "%04d-%02d-%02d %02d:%02d:%02d ", + (int)mt->tm_year + 1900, + (int)mt->tm_mon + 1, + (int)mt->tm_mday, + (int)mt->tm_hour, + (int)mt->tm_min, + (int)mt->tm_sec); + } else + strlcpy(dest, " ", destsize); +} + static void list_file(const char *fname) { STRUCT_STAT buf; char permbuf[PERMSTRING_SIZE]; - struct tm *mt; - char datebuf[50]; + char mtimebuf[50]; + char atimebuf[50]; char linkbuf[4096]; if (do_lstat(fname, &buf) < 0) @@ -153,19 +172,8 @@ static void list_file(const char *fname) permstring(permbuf, buf.st_mode); - if (buf.st_mtime) { - mt = gmtime(&buf.st_mtime); - - snprintf(datebuf, sizeof datebuf, - "%04d-%02d-%02d %02d:%02d:%02d", - (int)mt->tm_year + 1900, - (int)mt->tm_mon + 1, - (int)mt->tm_mday, - (int)mt->tm_hour, - (int)mt->tm_min, - (int)mt->tm_sec); - } else - strlcpy(datebuf, " ", sizeof datebuf); + storetime(mtimebuf, buf.st_mtime, sizeof mtimebuf); + storetime(atimebuf, buf.st_atime, sizeof atimebuf); /* TODO: Perhaps escape special characters in fname? */ @@ -176,13 +184,15 @@ static void list_file(const char *fname) (long)minor(buf.st_rdev)); } else /* NB: use double for size since it might not fit in a long. */ printf("%12.0f", (double)buf.st_size); - printf(" %6ld.%-6ld %6ld %s %s%s\n", + printf(" %6ld.%-6ld %6ld %s%s%s%s\n", (long)buf.st_uid, (long)buf.st_gid, (long)buf.st_nlink, - datebuf, fname, linkbuf); + mtimebuf, display_atime && !S_ISDIR(buf.st_mode) ? atimebuf : "", + fname, linkbuf); } static struct poptOption long_options[] = { /* longName, shortName, argInfo, argPtr, value, descrip, argDesc */ + {"atime", 'u', POPT_ARG_NONE, &display_atime, 0, 0, 0}, #ifdef SUPPORT_XATTRS {"fake-super", 'f', POPT_ARG_VAL, &am_root, -1, 0, 0 }, #endif @@ -196,6 +206,7 @@ static void tls_usage(int ret) fprintf(F,"usage: " PROGRAM " [OPTIONS] FILE ...\n"); fprintf(F,"Trivial file listing program for portably checking rsync\n"); fprintf(F,"\nOptions:\n"); + rprintf(F," -U, --atimes display access (last-used) times\n"); #ifdef SUPPORT_XATTRS fprintf(F," -f, --fake-super display attributes including fake-super xattrs\n"); #endif --- old/util.c +++ new/util.c @@ -122,7 +122,7 @@ NORETURN void overflow_exit(const char * exit_cleanup(RERR_MALLOC); } -int set_modtime(const char *fname, time_t modtime, mode_t mode) +int set_times(const char *fname, time_t modtime, time_t atime, mode_t mode) { #if !defined HAVE_LUTIMES || !defined HAVE_UTIMES if (S_ISLNK(mode)) @@ -130,9 +130,13 @@ int set_modtime(const char *fname, time_ #endif if (verbose > 2) { - rprintf(FINFO, "set modtime of %s to (%ld) %s", + char mtimebuf[200]; + + strlcpy(mtimebuf, timestring(modtime), sizeof mtimebuf); + rprintf(FINFO, + "set modtime, atime of %s to (%ld) %s, (%ld) %s\n", fname, (long)modtime, - asctime(localtime(&modtime))); + mtimebuf, (long)atime, timestring(atime)); } if (dry_run) @@ -141,7 +145,7 @@ int set_modtime(const char *fname, time_ { #ifdef HAVE_UTIMES struct timeval t[2]; - t[0].tv_sec = time(NULL); + t[0].tv_sec = atime; t[0].tv_usec = 0; t[1].tv_sec = modtime; t[1].tv_usec = 0; @@ -154,12 +158,12 @@ int set_modtime(const char *fname, time_ return utimes(fname, t); #elif defined HAVE_STRUCT_UTIMBUF struct utimbuf tbuf; - tbuf.actime = time(NULL); + tbuf.actime = atime; tbuf.modtime = modtime; return utime(fname,&tbuf); #elif defined HAVE_UTIME time_t t[2]; - t[0] = time(NULL); + t[0] = atime; t[1] = modtime; return utime(fname,t); #else