- Added `--crtimes` (`-N`) option for preserving the file's create time (on
an OS that supports that, such as macOS).
+ - Added `--mkpath` option to tell rsync that it should create a non-existing
+ path component of the destination arg.
+
- Added the ability to specify "@netgroup" names to the `hosts allow` and
`hosts deny` daemon parameters. This is a finalized version of the
netgroup-auth patch from the patches repo.
extern int keep_dirlinks;
extern int preserve_hard_links;
extern int protocol_version;
+extern int mkpath_dest_arg;
extern int file_total;
extern int recurse;
extern int xfer_dirs;
static char *get_local_name(struct file_list *flist, char *dest_path)
{
STRUCT_STAT st;
- int statret;
+ int statret, trailing_slash;
char *cp;
if (DEBUG_GTE(RECV, 1)) {
}
/* See what currently exists at the destination. */
- if ((statret = do_stat(dest_path, &st)) == 0) {
+ statret = do_stat(dest_path, &st);
+ cp = strrchr(dest_path, '/');
+ trailing_slash = cp && !cp[1];
+
+ if (mkpath_dest_arg && statret < 0 && (cp || file_total > 1)) {
+ int ret = make_path(dest_path, file_total > 1 && !trailing_slash ? 0 : MKP_DROP_NAME);
+ if (ret < 0)
+ goto mkdir_error;
+ if (INFO_GTE(NAME, 1)) {
+ if (file_total == 1 || trailing_slash)
+ *cp = '\0';
+ rprintf(FINFO, "created %s %s\n", ret == 1 ? "directory" : "path", dest_path);
+ if (file_total == 1 || trailing_slash)
+ *cp = '/';
+ }
+ if (file_total > 1 || trailing_slash)
+ statret = do_stat(dest_path, &st);
+ }
+
+ if (statret == 0) {
/* If the destination is a dir, enter it and use mode 1. */
if (S_ISDIR(st.st_mode)) {
if (!change_dir(dest_path, CD_NORMAL)) {
exit_cleanup(RERR_FILESELECT);
}
- cp = strrchr(dest_path, '/');
-
/* If we need a destination directory because the transfer is not
* of a single non-directory or the user has requested one via a
* destination path ending in a slash, create one and use mode 1. */
- if (file_total > 1 || (cp && !cp[1])) {
- /* Lop off the final slash (if any). */
- if (cp && !cp[1])
- *cp = '\0';
+ if (file_total > 1 || trailing_slash) {
+ if (trailing_slash)
+ *cp = '\0'; /* Lop off the final slash (if any). */
if (statret == 0) {
rprintf(FERROR, "ERROR: destination path is not a directory\n");
}
if (do_mkdir(dest_path, ACCESSPERMS) != 0) {
+ mkdir_error:
rsyserr(FERROR, errno, "mkdir %s failed",
full_fname(dest_path));
exit_cleanup(RERR_FILEIO);
int protect_args = -1;
int human_readable = 1;
int recurse = 0;
+int mkpath_dest_arg = 0;
int allow_inc_recurse = 1;
int xfer_dirs = -1;
int am_daemon = 0;
{"8-bit-output", '8', POPT_ARG_VAL, &allow_8bit_chars, 1, 0, 0 },
{"no-8-bit-output", 0, POPT_ARG_VAL, &allow_8bit_chars, 0, 0, 0 },
{"no-8", 0, POPT_ARG_VAL, &allow_8bit_chars, 0, 0, 0 },
+ {"mkpath", 0, POPT_ARG_VAL, &mkpath_dest_arg, 1, 0, 0 },
+ {"no-mkpath", 0, POPT_ARG_VAL, &mkpath_dest_arg, 0, 0, 0 },
{"qsort", 0, POPT_ARG_NONE, &use_qsort, 0, 0, 0 },
{"copy-as", 0, POPT_ARG_STRING, ©_as, 0, 0, 0 },
{"address", 0, POPT_ARG_STRING, &bind_address, 0, 0, 0 },
if (open_noatime && preserve_atimes <= 1)
args[ac++] = "--open-noatime";
+ if (mkpath_dest_arg && am_sender)
+ args[ac++] = "--mkpath";
+
if (ac > MAX_SERVER_ARGS) { /* Not possible... */
rprintf(FERROR, "argc overflow in server_options().\n");
exit_cleanup(RERR_MALLOC);
--append append data onto shorter files
--append-verify --append w/old data in file checksum
--dirs, -d transfer directories without recursing
+--mkpath create the destination's path component
--links, -l copy symlinks as symlinks
--copy-links, -L transform symlink into referent file/dir
--copy-unsafe-links only "unsafe" symlinks are transformed
`--old-d`) that tells rsync to use a hack of `-r --exclude='/*/*'` to get
an older rsync to list a single directory without recursing.
+0. `--mkpath`
+
+ Create a missing path component of the destination arg. This allows rsync
+ to create multiple levels of missing destination dirs and to create a path
+ in which to put a single renamed file. Keep in mind that you'll need to
+ supply a trailing slash if you want the entire destination path to be
+ treated as a directory when copying a single arg (making rsync behave the
+ same way that it would if the path component of the destination had already
+ existed).
+
+ For example, the following creates a copy of file foo as bar in the sub/dir
+ directory, creating dirs "sub" and "sub/dir" if either do not yet exist:
+
+ > rsync -ai --mkpath foo sub/dir/bar
+
+ If you instead ran the following, it would have created file foo in the
+ sub/dir/bar directory:
+
+ > rsync -ai --mkpath foo sub/dir/bar/
+
0. `--links`, `-l`
When symlinks are encountered, recreate the symlink on the destination.
--- /dev/null
+#!/bin/sh
+
+. "$suitedir/rsync.fns"
+
+makepath "$fromdir"
+makepath "$todir"
+
+cp_p "$srcdir/rsync.h" "$fromdir/text"
+cp_p "$srcdir/configure.ac" "$fromdir/extra"
+
+cd "$tmpdir"
+
+deep_dir=to/foo/bar/baz/down/deep
+
+# Check that we can create several levels of dest dir
+$RSYNC -aiv --mkpath from/text $deep_dir/new
+test -f $deep_dir/new || test_fail "'new' file not found in $deep_dir dir"
+rm -rf to/foo
+
+$RSYNC -aiv --mkpath from/text $deep_dir/
+test -f $deep_dir/text || test_fail "'text' file not found in $deep_dir dir"
+rm $deep_dir/text
+
+# Make sure we can handle an existing path
+mkdir $deep_dir/new
+$RSYNC -aiv --mkpath from/text $deep_dir/new
+test -f $deep_dir/new/text || test_fail "'text' file not found in $deep_dir/new dir"
+rm -rf to/foo
+
+# Try the tests again with multiple source args
+$RSYNC -aiv --mkpath from/ $deep_dir
+test -f $deep_dir/extra || test_fail "'extra' file not found in $deep_dir dir"
+rm -rf to/foo
+
+$RSYNC -aiv --mkpath from/ $deep_dir/
+test -f $deep_dir/text || test_fail "'text' file not found in $deep_dir dir"
+
+# Make sure that we can handle no path
+$RSYNC -aiv --mkpath from/text to_text
+test -f to_text || test_fail "'to_text' file not found in current dir"
+
+# The script would have aborted on error, so getting here means we've won.
+exit 0