}
}
- if (cleanup_got_literal && cleanup_fname && keep_partial) {
+ if (cleanup_got_literal && cleanup_fname && keep_partial
+ && handle_partial_dir(cleanup_new_fname, PDIR_CREATE)) {
char *fname = cleanup_fname;
cleanup_fname = NULL;
if (cleanup_fd_r != -1)
extern int io_timeout;
extern int protocol_version;
extern int always_checksum;
+extern char *partial_dir;
extern char *compare_dest;
extern int link_dest;
extern int whole_file;
statret = link_stat(fname, &st, keep_dirlinks && S_ISDIR(file->mode));
stat_errno = errno;
- if (only_existing && statret == -1 && errno == ENOENT) {
+ if (only_existing && statret == -1 && stat_errno == ENOENT) {
/* we only want to update existing files */
if (verbose > 1) {
rprintf(FINFO, "not creating new file \"%s\"\n",
return;
}
+ if (partial_dir) {
+ STRUCT_STAT st2;
+ char *partialptr = partial_dir_fname(fname);
+ if (partialptr && link_stat(partialptr, &st2, 0) == 0
+ && S_ISREG(st2.st_mode)) {
+ st = st2;
+ fnamecmp = partialptr;
+ }
+ }
+
/* open the file */
fd = do_open(fnamecmp, O_RDONLY, 0);
char *backup_suffix = NULL;
char *tmpdir = NULL;
+char *partial_dir = NULL;
char *compare_dest = NULL;
char *config_file = NULL;
char *shell_cmd = NULL;
rprintf(F," --ignore-errors delete even if there are I/O errors\n");
rprintf(F," --max-delete=NUM don't delete more than NUM files\n");
rprintf(F," --partial keep partially transferred files\n");
+ rprintf(F," --partial-dir=DIR put a partially transferred file into DIR\n");
rprintf(F," --force force deletion of directories even if not empty\n");
rprintf(F," --numeric-ids don't map uid/gid values by user/group name\n");
rprintf(F," --timeout=TIME set I/O timeout in seconds\n");
{"stats", 0, POPT_ARG_NONE, &do_stats, 0, 0, 0 },
{"progress", 0, POPT_ARG_NONE, &do_progress, 0, 0, 0 },
{"partial", 0, POPT_ARG_NONE, &keep_partial, 0, 0, 0 },
+ {"partial-dir", 0, POPT_ARG_STRING, &partial_dir, 0, 0, 0 },
{"ignore-errors", 0, POPT_ARG_NONE, &ignore_errors, 0, 0, 0 },
{"blocking-io", 0, POPT_ARG_VAL, &blocking_io, 1, 0, 0 },
{"no-blocking-io", 0, POPT_ARG_VAL, &blocking_io, 0, 0, 0 },
(*argv)[i] = alloc_sanitize_path((*argv)[i], NULL);
if (tmpdir)
tmpdir = alloc_sanitize_path(tmpdir, curr_dir);
+ if (partial_dir)
+ partial_dir = alloc_sanitize_path(partial_dir, curr_dir);
if (compare_dest)
compare_dest = alloc_sanitize_path(compare_dest, curr_dir);
if (backup_dir)
if (inplace) {
#if HAVE_FTRUNCATE
+ if (partial_dir) {
+ snprintf(err_buf, sizeof err_buf,
+ "--inplace cannot be used with --partial-dir\n");
+ return 0;
+ }
keep_partial = 0;
#else
snprintf(err_buf, sizeof err_buf,
am_server ? "server" : "client");
return 0;
#endif
+ } else if (partial_dir) {
+ if (strcmp(partial_dir, ".") == 0)
+ partial_dir = NULL;
+ keep_partial = 1;
}
if (files_from) {
args[ac++] = arg;
}
- if (keep_partial)
+ if (partial_dir && am_sender) {
+ args[ac++] = "--partial-dir";
+ args[ac++] = partial_dir;
+ } else if (keep_partial)
args[ac++] = "--partial";
if (force_delete)
extern int cvs_exclude;
extern int io_error;
extern char *tmpdir;
+extern char *partial_dir;
extern char *compare_dest;
extern int make_backups;
extern int do_progress;
char *fname, fbuf[MAXPATHLEN];
char template[MAXPATHLEN];
char fnametmp[MAXPATHLEN];
- char *fnamecmp;
+ char *fnamecmp, *partialptr;
char fnamecmpbuf[MAXPATHLEN];
struct file_struct *file;
struct stats initial_stats;
if (verbose > 2)
rprintf(FINFO, "recv_files(%s)\n", safe_fname(fname));
- fnamecmp = fname;
-
if (read_batch) {
while (i > next_gen_i) {
next_gen_i = read_int(batch_gen_fd);
continue;
}
+ if (partial_dir) {
+ if ((partialptr = partial_dir_fname(fname)) != NULL)
+ fnamecmp = partialptr;
+ else
+ fnamecmp = fname;
+ } else
+ fnamecmp = partialptr = fname;
+
/* open the file */
fd1 = do_open(fnamecmp, O_RDONLY, 0);
+ if (fd1 == -1 && fnamecmp != fname) {
+ fnamecmp = fname;
+ fd1 = do_open(fnamecmp, O_RDONLY, 0);
+ }
+
if (fd1 == -1 && compare_dest != NULL) {
/* try the file at compare_dest instead */
pathjoin(fnamecmpbuf, sizeof fnamecmpbuf,
continue;
}
- cleanup_set(fnametmp, fname, file, fd1, fd2);
+ if (partialptr)
+ cleanup_set(fnametmp, partialptr, file, fd1, fd2);
}
if (!am_server && verbose) /* log the transfer */
exit_cleanup(RERR_FILEIO);
}
- if (recv_ok || keep_partial || inplace)
+ if (recv_ok || inplace)
finish_transfer(fname, fnametmp, file, recv_ok);
- else
+ else if (keep_partial && partialptr
+ && handle_partial_dir(partialptr, PDIR_CREATE))
+ finish_transfer(partialptr, fnametmp, file, 0);
+ else {
+ partialptr = NULL;
do_unlink(fnametmp);
+ }
+
+ if (partialptr != fname && fnamecmp == partialptr && recv_ok) {
+ do_unlink(partialptr);
+ handle_partial_dir(partialptr, PDIR_DELETE);
+ }
cleanup_disable();
int msgtype = csum_length == SUM_LENGTH || read_batch ?
FERROR : FINFO;
if (msgtype == FERROR || verbose) {
- char *errstr, *redostr;
- char *keptstr = keep_partial || inplace ?
- "retain" : "discard";
+ char *errstr, *redostr, *keptstr;
+ if (!(keep_partial && partialptr) && !inplace)
+ keptstr = "discarded";
+ else if (partial_dir)
+ keptstr = "put into partial-dir";
+ else
+ keptstr = "retained";
if (msgtype == FERROR) {
errstr = "ERROR";
redostr = "";
redostr = " (will try again)";
}
rprintf(msgtype,
- "%s: %s failed verification -- update %sed%s.\n",
+ "%s: %s failed verification -- update %s%s.\n",
errstr, safe_fname(fname),
keptstr, redostr);
}
#define FULL_FLUSH 1
#define NORMAL_FLUSH 0
+#define PDIR_CREATE 1
+#define PDIR_DELETE 0
+
/* Log-message categories. FLOG is only used on the daemon side to
* output messages to the log file. */
int modify_window = 0;
int module_id = -1;
+char *partial_dir;
struct exclude_list_struct server_exclude_list;
void rprintf(UNUSED(enum logcode code), const char *format, ...)
extern int dry_run;
extern int module_id;
extern int modify_window;
+extern char *partial_dir;
extern struct exclude_list_struct server_exclude_list;
int sanitize_paths = 0;
return result;
}
+static char partial_fname[MAXPATHLEN];
+
+char *partial_dir_fname(const char *fname)
+{
+ char *t = partial_fname;
+ int sz = sizeof partial_fname;
+ const char *fn;
+
+ if ((fn = strrchr(fname, '/')) != NULL) {
+ fn++;
+ if (*partial_dir != '/') {
+ int len = fn - fname;
+ strncpy(t, fname, len); /* safe */
+ t += len;
+ sz -= len;
+ }
+ } else
+ fn = fname;
+ if ((int)pathjoin(t, sz, partial_dir, fn) >= sz)
+ return NULL;
+
+ return partial_fname;
+}
+
+/* If no --partial-dir option was specified, we don't need to do anything
+ * (the partial-dir is essentially '.'), so just return success. */
+int handle_partial_dir(const char *fname, int create)
+{
+ char *fn, *dir;
+
+ if (fname != partial_fname)
+ return 1;
+ if (!create && *partial_dir == '/')
+ return 1;
+ if (!(fn = strrchr(partial_fname, '/')))
+ return 1;
+
+ *fn = '\0';
+ dir = partial_fname;
+ if (create) {
+ STRUCT_STAT st;
+#if SUPPORT_LINKS
+ int statret = do_lstat(dir, &st);
+#else
+ int statret = do_stat(dir, &st);
+#endif
+ if (statret == 0 && !S_ISDIR(st.st_mode)) {
+ if (do_unlink(dir) < 0)
+ return 0;
+ statret = -1;
+ }
+ if (statret < 0 && do_mkdir(dir, 0700) < 0)
+ return 0;
+ } else
+ do_rmdir(dir);
+ *fn = '/';
+
+ return 1;
+}
+
/** We need to supply our own strcmp function for file list comparisons
to ensure that signed/unsigned usage is consistent between machines. */
int u_strcmp(const char *cs1, const char *cs2)