--- rsync.h.orig 2016-05-14 12:58:20.563512507 -0600 +++ rsync.h 2016-05-14 13:00:41.597669903 -0600 @@ -1247,8 +1247,9 @@ #define INFO_SKIP (INFO_REMOVE+1) #define INFO_STATS (INFO_SKIP+1) #define INFO_SYMSAFE (INFO_STATS+1) +#define INFO_RENAME (INFO_SYMSAFE+1) -#define COUNT_INFO (INFO_SYMSAFE+1) +#define COUNT_INFO (INFO_RENAME+1) #define DEBUG_ACL 0 #define DEBUG_BACKUP (DEBUG_ACL+1) @@ -1273,8 +1274,9 @@ #define DEBUG_RECV (DEBUG_PROTO+1) #define DEBUG_SEND (DEBUG_RECV+1) #define DEBUG_TIME (DEBUG_SEND+1) +#define DEBUG_RENAME (DEBUG_TIME+1) -#define COUNT_DEBUG (DEBUG_TIME+1) +#define COUNT_DEBUG (DEBUG_RENAME+1) #ifndef HAVE_INET_NTOP const char *inet_ntop(int af, const void *src, char *dst, size_t size); --- options.c.orig 2016-05-14 12:58:24.983674942 -0600 +++ options.c 2016-05-14 13:00:41.598669940 -0600 @@ -113,6 +113,7 @@ int fuzzy_basis = 0; size_t bwlimit_writemax = 0; int ignore_existing = 0; +int rename_existing = 0; int ignore_non_existing = 0; int need_messages_from_generator = 0; int max_delete = INT_MIN; @@ -207,7 +208,7 @@ /*0*/ NULL, /*1*/ NULL, /*2*/ "BIND,CMD,CONNECT,DEL,DELTASUM,DUP,FILTER,FLIST,ICONV", - /*3*/ "ACL,BACKUP,CONNECT2,DELTASUM2,DEL2,EXIT,FILTER2,FLIST2,FUZZY,GENR,OWN,RECV,SEND,TIME", + /*3*/ "ACL,BACKUP,CONNECT2,DELTASUM2,DEL2,EXIT,FILTER2,FLIST2,FUZZY,GENR,OWN,RECV,SEND,TIME,RENAME", /*4*/ "CMD2,DELTASUM3,DEL3,EXIT2,FLIST3,ICONV2,OWN2,PROTO,TIME2", /*5*/ "CHDIR,DELTASUM4,FLIST4,FUZZY2,HASH,HLINK", }; @@ -216,7 +217,7 @@ static const char *info_verbosity[1+MAX_VERBOSITY] = { /*0*/ NULL, - /*1*/ "COPY,DEL,FLIST,MISC,NAME,STATS,SYMSAFE", + /*1*/ "COPY,DEL,FLIST,MISC,NAME,STATS,SYMSAFE,RENAME", /*2*/ "BACKUP,MISC2,MOUNT,NAME2,REMOVE,SKIP", }; @@ -258,6 +259,7 @@ INFO_WORD(SKIP, W_REC, "Mention files that are skipped due to options used"), INFO_WORD(STATS, W_CLI|W_SRV, "Mention statistics at end of run (levels 1-3)"), INFO_WORD(SYMSAFE, W_SND|W_REC, "Mention symlinks that are unsafe"), + INFO_WORD(RENAME, W_SND|W_REC, "Mention files renamed for rename-existing"), { NULL, "--info", 0, 0, 0, 0 } }; @@ -287,6 +289,7 @@ DEBUG_WORD(RECV, W_REC, "Debug receiver functions"), DEBUG_WORD(SEND, W_SND, "Debug sender functions"), DEBUG_WORD(TIME, W_REC, "Debug setting of modified times (levels 1-2)"), + DEBUG_WORD(RENAME, W_REC, "Debug rename-existing functions in generator"), { NULL, "--debug", 0, 0, 0, 0 } }; @@ -727,6 +730,7 @@ rprintf(F," --rsync-path=PROGRAM specify the rsync to run on the remote machine\n"); rprintf(F," --existing skip creating new files on receiver\n"); rprintf(F," --ignore-existing skip updating files that already exist on receiver\n"); + rprintf(F," --rename-existing rename files on receiver that already exist on receiver\n"); rprintf(F," --remove-source-files sender removes synchronized files (non-dirs)\n"); rprintf(F," --del an alias for --delete-during\n"); rprintf(F," --delete delete extraneous files from destination dirs\n"); @@ -917,6 +921,7 @@ {"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 }, + {"rename-existing", 0, POPT_ARG_NONE, &rename_existing, 0, 0, 0 }, {"max-size", 0, POPT_ARG_STRING, &max_size_arg, OPT_MAX_SIZE, 0, 0 }, {"min-size", 0, POPT_ARG_STRING, &min_size_arg, OPT_MIN_SIZE, 0, 0 }, {"sparse", 'S', POPT_ARG_VAL, &sparse_files, 1, 0, 0 }, @@ -2702,6 +2707,9 @@ if (ignore_existing) args[ac++] = "--ignore-existing"; + if (rename_existing) + args[ac++] = "--rename-existing"; + /* Backward compatibility: send --existing, not --ignore-non-existing. */ if (ignore_non_existing) args[ac++] = "--existing"; --- generator.c.orig 2016-05-14 12:58:31.258905542 -0600 +++ generator.c 2016-05-14 13:00:49.130944468 -0600 @@ -56,6 +56,7 @@ extern int update_only; extern int human_readable; extern int ignore_existing; +extern int rename_existing; extern int ignore_non_existing; extern int want_xattr_optim; extern int inplace; @@ -1191,6 +1192,7 @@ STRUCT_STAT partial_st; struct file_struct *back_file = NULL; int statret, real_ret, stat_errno; + int renamed = 0; char *fnamecmp, *partialptr, *backupptr = NULL; char fnamecmpbuf[MAXPATHLEN]; uchar fnamecmp_type; @@ -1345,7 +1347,105 @@ && !am_root && sx.st.st_uid == our_uid) del_opts |= DEL_NO_UID_WRITE; - if (ignore_existing > 0 && statret == 0 + /* --rename-existing */ + /* If rename is not possible, revert to --ignore-existing behavior */ + if (rename_existing > 0 && statret == 0 && S_ISREG(sx.st.st_mode) + && (!is_dir || !S_ISDIR(sx.st.st_mode))) { + char *new_fname, *ext, *slash = NULL; + STRUCT_STAT st; + int count, malloc_len, fname_len, ext_len, ret, i = 0; + + if (DEBUG_GTE(RENAME, 1)) + rprintf(FINFO, "starting rename-existing processing for fname %s\n", fname); + + fname_len = strlen(fname); + slash = strrchr(fname, '/'); + ext = strrchr(fname, '.'); + if (slash > ext) + ext = NULL; + if (ext == NULL) + ext_len = 0; + else + ext_len = strlen(ext); + + /* alloc new filename mem, fname length +6 (+1 char for _, +4 chars for 1-9999 unique number, and +1 for \0) */ + malloc_len = fname_len + 6; + new_fname = malloc(malloc_len); + if (new_fname == NULL) { + rsyserr(FERROR, errno, "new_fname malloc failed"); + goto cleanup; + } + + count = 1; + renamed = 0; + while (renamed != 1 && count <= 9999) { + /* build new filename */ + snprintf(new_fname, malloc_len, "%.*s_%d%s", + fname_len - ext_len, fname, + count, + ext ? ext : ""); + + if (DEBUG_GTE(RENAME, 1)) + rprintf(FINFO, "attempting to use new_fname of %s\n", new_fname); + + /* verify MAXPATHLEN */ + i = strlen(new_fname); + if (DEBUG_GTE(RENAME, 1)) + rprintf(FINFO, "checking MAXPATHLEN: new_fname fullpath length = %d, MAXPATHLEN = %d\n", i, MAXPATHLEN); + if (i > MAXPATHLEN) { + rsyserr(FERROR, ENAMETOOLONG, "error new_fname fullpath length (%d) is larger than MAXPATHLEN (%d)", i, MAXPATHLEN); + break; + } + + /* verify NAME_MAX, i is already set to strlen(new_fname), don't re-set if no slash found */ + slash = strrchr(new_fname, '/'); + if (slash != NULL) + i = strlen(slash) - 1; /* subtract 1 for the slash itself */ + if (DEBUG_GTE(RENAME, 1)) + rprintf(FINFO, "checking NAME_MAX: new_fname filename length = %d, NAME_MAX = %d\n", i, NAME_MAX); + if (i > NAME_MAX) { + rsyserr(FERROR, ENAMETOOLONG, "error new_fname filename length (%d) is larger than NAME_MAX (%d)", i, NAME_MAX); + break; + } + + /* attempt to move the file */ + ret = link_stat(new_fname, &st, 0); + if (ret != 0 && errno == ENOENT) { + if (DEBUG_GTE(RENAME, 1)) + rprintf(FINFO, "new_fname %s does not exist\n", new_fname); + if (do_rename(fname, new_fname) == 0) { + if (DEBUG_GTE(RENAME, 1)) + rprintf(FINFO, "do_rename from %s to %s succeeded\n", fname, new_fname); + renamed = 1; + break; + } else { + rsyserr(FERROR, errno, "do_rename from %s to %s failed", fname, new_fname); + } + } else if (ret == 0) { + if (DEBUG_GTE(RENAME, 1)) + rprintf(FINFO, "new_fname %s exists\n", new_fname); + } else { + rsyserr(FERROR, errno, "link_stat new_fname failed"); + } + count++; + } + + if (renamed == 1) { + if (INFO_GTE(RENAME,1)) + rprintf(FINFO, "rename-existing on receiver success (%s renamed to %s)\n", fname, new_fname); + } else { + if (INFO_GTE(RENAME,1)) + rprintf(FERROR, "rename-existing on receiver did not succeed for %s\n", fname); + } + + free(new_fname); + + /* Redo statret, since the file may have moved */ + statret = link_stat(fname, &sx.st, keep_dirlinks && is_dir); + stat_errno = errno; + } + + if ((ignore_existing > 0 || rename_existing > 0) && statret == 0 && (!is_dir || !S_ISDIR(sx.st.st_mode))) { if (INFO_GTE(SKIP, 1) && is_dir >= 0) rprintf(FINFO, "%s exists\n", fname);