diff options
-rw-r--r-- | NEWS | 6 | ||||
-rw-r--r-- | generator.c | 110 | ||||
-rw-r--r-- | hlink.c | 15 | ||||
-rw-r--r-- | receiver.c | 80 | ||||
-rw-r--r-- | testsuite/devices.test | 4 |
5 files changed, 146 insertions, 69 deletions
@@ -63,6 +63,12 @@ Changes since 3.0.4: file. Also made explicitly-set uid/gid values no longer ignored by a daemon that was not run by a super-user. + - When replacing a non-dir with a symlink/hard-link/device/special-file, + the update should now be done in an atomic manner. + + - When backing up a file, try to hard-link the file into place so that the + upcoming replacement of the destination file will be atomic. + EXTRAS: - Added an "instant-rsyncd" script to the support directory, which makes diff --git a/generator.c b/generator.c index 6f4fa4f0..df2434f5 100644 --- a/generator.c +++ b/generator.c @@ -95,6 +95,7 @@ extern struct stats stats; extern dev_t filesystem_dev; extern mode_t orig_umask; extern uid_t our_uid; +extern char *tmpdir; extern char *basis_dir[MAX_BASIS_DIRS+1]; extern struct file_list *cur_flist, *first_flist, *dir_flist; extern filter_rule_list filter_list, daemon_filter_list; @@ -131,7 +132,7 @@ static int start_delete_delay_temp(void) int save_dry_run = dry_run; dry_run = 0; - if (!get_tmpname(fnametmp, "deldelay") + if (!get_tmpname(fnametmp, "deldelay", False) || (deldelay_fd = do_mkstemp(fnametmp, 0600)) < 0) { rprintf(FINFO, "NOTE: Unable to create delete-delay temp file%s.\n", inc_recurse ? "" : " -- switching to --delete-after"); @@ -1360,10 +1361,9 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx, char lnk[MAXPATHLEN]; int len; - if (!S_ISLNK(sx.st.st_mode)) - statret = -1; - else if ((len = readlink(fname, lnk, MAXPATHLEN-1)) > 0 - && strncmp(lnk, sl, len) == 0 && sl[len] == '\0') { + if (S_ISLNK(sx.st.st_mode) + && (len = readlink(fname, lnk, MAXPATHLEN-1)) > 0 + && strncmp(lnk, sl, len) == 0 && sl[len] == '\0') { /* The link is pointing to the right place. */ set_file_attrs(fname, file, &sx, NULL, maybe_ATTRS_REPORT); if (itemizing) @@ -1376,10 +1376,6 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx, goto return_with_success; goto cleanup; } - /* Not the right symlink (or not a symlink), so - * delete it. */ - if (delete_item(fname, sx.st.st_mode, del_opts | DEL_FOR_SYMLINK) != 0) - goto cleanup; } else if (basis_dir[0] != NULL) { int j = try_dests_non(file, fname, ndx, fnamecmpbuf, &sx, itemizing, code); @@ -1396,18 +1392,11 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx, } else if (j >= 0) statret = 1; } -#ifdef SUPPORT_HARD_LINKS - if (preserve_hard_links && F_HLINK_NOT_LAST(file)) { - cur_flist->in_progress++; - goto cleanup; - } -#endif - if (do_symlink(sl, fname) != 0) { - rsyserr(FERROR_XFER, errno, "symlink %s -> \"%s\" failed", - full_fname(fname), sl); - } else { + if (atomic_create(file, fname, sl, MAKEDEV(0, 0), &sx, statret == 0 ? DEL_FOR_SYMLINK : 0)) { set_file_attrs(fname, file, NULL, NULL, 0); if (itemizing) { + if (statret == 0 && !S_ISLNK(sx.st.st_mode)) + statret = -1; itemize(fname, file, ndx, statret, &sx, ITEM_LOCAL_CHANGE|ITEM_REPORT_CHANGE, 0, NULL); } @@ -1430,13 +1419,13 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx, if ((am_root && preserve_devices && IS_DEVICE(file->mode)) || (preserve_specials && IS_SPECIAL(file->mode))) { dev_t rdev; + int del_for_flag = 0; if (IS_DEVICE(file->mode)) { uint32 *devp = F_RDEV_P(file); rdev = MAKEDEV(DEV_MAJOR(devp), DEV_MINOR(devp)); } else rdev = 0; if (statret == 0) { - int del_for_flag; if (IS_DEVICE(file->mode)) { if (!IS_DEVICE(sx.st.st_mode)) statret = -1; @@ -1461,8 +1450,6 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx, goto return_with_success; goto cleanup; } - if (delete_item(fname, sx.st.st_mode, del_opts | del_for_flag) != 0) - goto cleanup; } else if (basis_dir[0] != NULL) { int j = try_dests_non(file, fname, ndx, fnamecmpbuf, &sx, itemizing, code); @@ -1479,21 +1466,12 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx, } else if (j >= 0) statret = 1; } -#ifdef SUPPORT_HARD_LINKS - if (preserve_hard_links && F_HLINK_NOT_LAST(file)) { - cur_flist->in_progress++; - goto cleanup; - } -#endif if (DEBUG_GTE(GENR, 1)) { rprintf(FINFO, "mknod(%s, 0%o, [%ld,%ld])\n", fname, (int)file->mode, (long)major(rdev), (long)minor(rdev)); } - if (do_mknod(fname, file->mode, rdev) < 0) { - rsyserr(FERROR_XFER, errno, "mknod %s failed", - full_fname(fname)); - } else { + if (atomic_create(file, fname, NULL, rdev, &sx, del_for_flag)) { set_file_attrs(fname, file, NULL, NULL, 0); if (itemizing) { itemize(fname, file, ndx, statret, &sx, @@ -1813,6 +1791,74 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx, return; } +/* If we are replacing an existing hard link, symlink, device, or special file, + * create a temp-name item and rename it into place. Only a symlink or hard + * link puts a non-NULL value into the lnk arg. Only a device puts a non-0 + * value into the rdev arg. Specify 0 for the del_for_flag if there is not a + * file to replace. This returns 1 on success and 0 on failure. */ +int atomic_create(struct file_struct *file, char *fname, const char *lnk, + dev_t rdev, stat_x *sxp, int del_for_flag) +{ + char tmpname[MAXPATHLEN]; + const char *create_name; + int skip_atomic, dir_in_the_way = del_for_flag && S_ISDIR(sxp->st.st_mode); + + if (!del_for_flag || dir_in_the_way || tmpdir || !get_tmpname(tmpname, fname, True)) + skip_atomic = 1; + else + skip_atomic = 0; + + if (del_for_flag) { + if (make_backups > 0 && !dir_in_the_way) { + if (!make_backup(fname, skip_atomic)) + return 0; + } else if (skip_atomic) { + int del_opts = delete_mode || force_delete ? DEL_RECURSE : 0; + if (delete_item(fname, sxp->st.st_mode, del_opts | del_for_flag) != 0) + return 0; + } + } + + create_name = skip_atomic ? fname : tmpname; + + if (lnk) { +#ifdef SUPPORT_LINKS + if (S_ISLNK(file->mode) +#ifdef SUPPORT_HARD_LINKS /* The first symlink in a hard-linked cluster is always created. */ + && (!F_IS_HLINKED(file) || file->flags & FLAG_HLINK_FIRST) +#endif + ) { + if (do_symlink(lnk, create_name) < 0) { + rsyserr(FERROR_XFER, errno, "symlink %s -> \"%s\" failed", + full_fname(create_name), lnk); + return 0; + } + } else +#endif +#ifdef SUPPORT_HARD_LINKS + if (!hard_link_one(file, create_name, lnk, 0)) + return 0; +#endif + } else { + if (do_mknod(create_name, file->mode, rdev) < 0) { + rsyserr(FERROR_XFER, errno, "mknod %s failed", + full_fname(create_name)); + return 0; + } + } + + if (!skip_atomic) { + if (do_rename(tmpname, fname) < 0) { + rsyserr(FERROR_XFER, errno, "rename %s -> \"%s\" failed", + full_fname(tmpname), full_fname(fname)); + do_unlink(tmpname); + return 0; + } + } + + return 1; +} + #ifdef SUPPORT_HARD_LINKS static void handle_skipped_hlink(struct file_struct *file, int itemizing, enum logcode code, int f_out) @@ -212,12 +212,11 @@ void match_hard_links(struct file_list *flist) } static int maybe_hard_link(struct file_struct *file, int ndx, - const char *fname, int statret, stat_x *sxp, + char *fname, int statret, stat_x *sxp, const char *oldname, STRUCT_STAT *old_stp, const char *realname, int itemizing, enum logcode code) { if (statret == 0) { - int ok = 0; if (sxp->st.st_dev == old_stp->st_dev && sxp->st.st_ino == old_stp->st_ino) { if (itemizing) { @@ -230,16 +229,9 @@ static int maybe_hard_link(struct file_struct *file, int ndx, file->flags |= FLAG_HLINK_DONE; return 0; } - if (make_backups > 0 && (ok = make_backup(fname, True)) == 0) - return -1; - if (ok != 1 && robust_unlink(fname) && errno != ENOENT) { - rsyserr(FERROR_XFER, errno, "unlink %s failed", - full_fname(fname)); - return -1; - } } - if (hard_link_one(file, fname, oldname, 0)) { + if (atomic_create(file, fname, oldname, MAKEDEV(0, 0), sxp, statret == 0 ? DEL_FOR_FILE : 0)) { if (itemizing) { itemize(fname, file, ndx, statret, sxp, ITEM_LOCAL_CHANGE | ITEM_XNAME_FOLLOWS, 0, @@ -249,6 +241,7 @@ static int maybe_hard_link(struct file_struct *file, int ndx, rprintf(code, "%s => %s\n", fname, realname); return 0; } + return -1; } @@ -294,7 +287,7 @@ static char *check_prior(struct file_struct *file, int gnum, /* Only called if FLAG_HLINKED is set and FLAG_HLINK_FIRST is not. Returns: * 0 = process the file, 1 = skip the file, -1 = error occurred. */ -int hard_link_check(struct file_struct *file, int ndx, const char *fname, +int hard_link_check(struct file_struct *file, int ndx, char *fname, int statret, stat_x *sxp, int itemizing, enum logcode code) { @@ -64,31 +64,36 @@ static flist_ndx_list batch_redo_list; /* We're either updating the basis file or an identical copy: */ static int updating_basis_or_equiv; -/* - * get_tmpname() - create a tmp filename for a given filename +#define TMPNAME_SUFFIX ".XXXXXX" +#define TMPNAME_SUFFIX_LEN ((int)sizeof TMPNAME_SUFFIX - 1) +#define MAX_UNIQUE_NUMBER 999999 +#define MAX_UNIQUE_LOOP 100 + +/* get_tmpname() - create a tmp filename for a given filename * - * If a tmpdir is defined, use that as the directory to - * put it in. Otherwise, the tmp filename is in the same - * directory as the given name. Note that there may be no - * directory at all in the given name! + * If a tmpdir is defined, use that as the directory to put it in. Otherwise, + * the tmp filename is in the same directory as the given name. Note that + * there may be no directory at all in the given name! * - * The tmp filename is basically the given filename with a - * dot prepended, and .XXXXXX appended (for mkstemp() to - * put its unique gunk in). Take care to not exceed - * either the MAXPATHLEN or NAME_MAX, esp. the last, as - * the basename basically becomes 8 chars longer. In that - * case, the original name is shortened sufficiently to - * make it all fit. + * The tmp filename is basically the given filename with a dot prepended, and + * .XXXXXX appended (for mkstemp() to put its unique gunk in). We take care + * to not exceed either the MAXPATHLEN or NAME_MAX, especially the last, as + * the basename basically becomes 8 characters longer. In such a case, the + * original name is shortened sufficiently to make it all fit. * - * Of course, there's no real reason for the tmp name to - * look like the original, except to satisfy us humans. - * As long as it's unique, rsync will work. - */ - -int get_tmpname(char *fnametmp, const char *fname) + * If the make_unique arg is True, the XXXXXX string is replaced with a unique + * string that doesn't exist at the time of the check. This is intended to be + * used for creating hard links, symlinks, devices, and special files, since + * normal files should be handled by mkstemp() for safety. + * + * Of course, the only reason the file is based on the original name is to + * make it easier to figure out what purpose a temp file is serving when a + * transfer is in progress. */ +int get_tmpname(char *fnametmp, const char *fname, BOOL make_unique) { int maxname, added, length = 0; const char *f; + char *suf; if (tmpdir) { /* Note: this can't overflow, so the return value is safe */ @@ -108,8 +113,9 @@ int get_tmpname(char *fnametmp, const char *fname) fnametmp[length++] = '.'; /* The maxname value is bufsize, and includes space for the '\0'. - * (Note that NAME_MAX get -8 for the leading '.' above.) */ - maxname = MIN(MAXPATHLEN - 7 - length, NAME_MAX - 8); + * NAME_MAX needs an extra -1 for the name's leading dot. */ + maxname = MIN(MAXPATHLEN - length - TMPNAME_SUFFIX_LEN, + NAME_MAX - 1 - TMPNAME_SUFFIX_LEN); if (maxname < 1) { rprintf(FERROR_XFER, "temporary filename too long: %s\n", fname); @@ -120,7 +126,33 @@ int get_tmpname(char *fnametmp, const char *fname) added = strlcpy(fnametmp + length, f, maxname); if (added >= maxname) added = maxname - 1; - memcpy(fnametmp + length + added, ".XXXXXX", 8); + suf = fnametmp + length + added; + + if (make_unique) { + static unsigned counter_limit; + unsigned counter; + + if (!counter_limit) { + counter_limit = (unsigned)getpid() + MAX_UNIQUE_LOOP; + if (counter_limit > MAX_UNIQUE_NUMBER || counter_limit < MAX_UNIQUE_LOOP) + counter_limit = MAX_UNIQUE_LOOP; + } + counter = counter_limit - MAX_UNIQUE_LOOP; + + /* This doesn't have to be very good because we don't need + * to worry about someone trying to guess the values: all + * a conflict will do is cause a device, special file, hard + * link, or symlink to fail to be created. Also: avoid + * using mktemp() due to gcc's annoying warning. */ + while (1) { + snprintf(suf, TMPNAME_SUFFIX_LEN+1, ".%d", counter); + if (access(fnametmp, 0) < 0) + break; + if (++counter >= counter_limit) + return 0; + } + } else + memcpy(suf, TMPNAME_SUFFIX, TMPNAME_SUFFIX_LEN+1); return 1; } @@ -133,7 +165,7 @@ int open_tmpfile(char *fnametmp, const char *fname, struct file_struct *file) { int fd; - if (!get_tmpname(fnametmp, fname)) + if (!get_tmpname(fnametmp, fname, False)) return -1; /* We initially set the perms without the setuid/setgid bits or group @@ -149,7 +181,7 @@ int open_tmpfile(char *fnametmp, const char *fname, struct file_struct *file) if (fd == -1 && relative_paths && errno == ENOENT && make_path(fnametmp, MKP_SKIP_SLASH | MKP_DROP_NAME) == 0) { /* Get back to name with XXXXXX in it. */ - get_tmpname(fnametmp, fname); + get_tmpname(fnametmp, fname, False); fd = do_mkstemp(fnametmp, file->mode & INITACCESSPERMS); } #endif diff --git a/testsuite/devices.test b/testsuite/devices.test index 23a8e5b9..51400550 100644 --- a/testsuite/devices.test +++ b/testsuite/devices.test @@ -109,8 +109,8 @@ cat <<EOT >"$chkfile" .d..t.$dots ./ cDc.t.$dots block cDc...$dots block2 -cD$all_plus block3 -hD$all_plus block2.5 => block3 +cD$all_plus block2.5 +hD$all_plus block3 => block2.5 cD$all_plus char cD$all_plus char2 cD$all_plus char3 |