From 5183c0d6f0bf6786d5e9fa149d0d00f664533441 Mon Sep 17 00:00:00 2001 From: Wayne Davison Date: Sun, 21 Aug 2022 09:56:33 -0700 Subject: [PATCH] Add safety check for local --remove-source-files. A local_server copy now includes the dev+ino info from the destination file so that the sender can make sure that it is not going to delete the destination file. Fixes mistakes such as: rsync -aiv --remove-source-files dir . --- NEWS.md | 5 +++++ generator.c | 2 +- hlink.c | 4 ++-- io.c | 34 ++++++++++++++++++++++++++++++---- receiver.c | 12 +++++------- rsync.1.md | 4 ++++ sender.c | 9 +++++++++ 7 files changed, 56 insertions(+), 14 deletions(-) diff --git a/NEWS.md b/NEWS.md index b5883ecf..80b458e2 100644 --- a/NEWS.md +++ b/NEWS.md @@ -10,6 +10,11 @@ - Fixed a bug with the new file-list validation code when the last line of the [`--files-from`](rsync.1#opt) list is not terminated by a newline. +- Added a safety check that prevents the sender from removing destination files + when a local copy using [`--remove-source-files`](rsync.1#opt) has some + content that is shared between the sending & receiving hierarchies, including + the case where the source dir & destination dir are identical. + - Fixed a bug in the internal MD4 checksum code that could cause the digest to be sporadically incorrect (the openssl version was/is fine). diff --git a/generator.c b/generator.c index 278e2a6f..935c84f9 100644 --- a/generator.c +++ b/generator.c @@ -1819,7 +1819,7 @@ static void recv_generator(char *fname, struct file_struct *file, int ndx, goto cleanup; return_with_success: if (!dry_run) - send_msg_int(MSG_SUCCESS, ndx); + send_msg_success(fname, ndx); goto cleanup; } diff --git a/hlink.c b/hlink.c index 66810a3e..7602ec38 100644 --- a/hlink.c +++ b/hlink.c @@ -446,7 +446,7 @@ int hard_link_check(struct file_struct *file, int ndx, char *fname, return -1; if (remove_source_files == 1 && do_xfers) - send_msg_int(MSG_SUCCESS, ndx); + send_msg_success(fname, ndx); return 1; } @@ -519,7 +519,7 @@ void finish_hard_link(struct file_struct *file, const char *fname, int fin_ndx, if (val < 0) continue; if (remove_source_files == 1 && do_xfers) - send_msg_int(MSG_SUCCESS, ndx); + send_msg_success(fname, ndx); } if (inc_recurse) { diff --git a/io.c b/io.c index 3f605d74..f3d802ec 100644 --- a/io.c +++ b/io.c @@ -41,6 +41,7 @@ extern int am_server; extern int am_sender; extern int am_receiver; extern int am_generator; +extern int local_server; extern int msgs2stderr; extern int inc_recurse; extern int io_error; @@ -84,6 +85,8 @@ int sock_f_out = -1; int64 total_data_read = 0; int64 total_data_written = 0; +char num_dev_ino_buf[4 + 8 + 8]; + static struct { xbuf in, out, msg; int in_fd; @@ -1064,6 +1067,24 @@ void send_msg_int(enum msgcode code, int num) send_msg(code, numbuf, 4, -1); } +void send_msg_success(const char *fname, int num) +{ + if (local_server) { + STRUCT_STAT st; + + if (DEBUG_GTE(IO, 1)) + rprintf(FINFO, "[%s] send_msg_success(%d)\n", who_am_i(), num); + + if (stat(fname, &st) < 0) + memset(&st, 0, sizeof (STRUCT_STAT)); + SIVAL(num_dev_ino_buf, 0, num); + SIVAL64(num_dev_ino_buf, 4, st.st_dev); + SIVAL64(num_dev_ino_buf, 4+8, st.st_ino); + send_msg(MSG_SUCCESS, num_dev_ino_buf, sizeof num_dev_ino_buf, -1); + } else + send_msg_int(MSG_SUCCESS, num); +} + static void got_flist_entry_status(enum festatus status, int ndx) { struct file_list *flist = flist_for_ndx(ndx, "got_flist_entry_status"); @@ -1078,8 +1099,12 @@ static void got_flist_entry_status(enum festatus status, int ndx) switch (status) { case FES_SUCCESS: - if (remove_source_files) - send_msg_int(MSG_SUCCESS, ndx); + if (remove_source_files) { + if (local_server) + send_msg(MSG_SUCCESS, num_dev_ino_buf, sizeof num_dev_ino_buf, -1); + else + send_msg_int(MSG_SUCCESS, ndx); + } /* FALL THROUGH */ case FES_NO_SEND: #ifdef SUPPORT_HARD_LINKS @@ -1574,14 +1599,15 @@ static void read_a_msg(void) } break; case MSG_SUCCESS: - if (msg_bytes != 4) { + if (msg_bytes != (local_server ? 4+8+8 : 4)) { invalid_msg: rprintf(FERROR, "invalid multi-message %d:%lu [%s%s]\n", tag, (unsigned long)msg_bytes, who_am_i(), inc_recurse ? "/inc" : ""); exit_cleanup(RERR_STREAMIO); } - val = raw_read_int(); + raw_read_buf(num_dev_ino_buf, msg_bytes); + val = IVAL(num_dev_ino_buf, 0); iobuf.in_multiplexed = 1; if (am_generator) got_flist_entry_status(FES_SUCCESS, val); diff --git a/receiver.c b/receiver.c index 93cf8efd..0f5d92d2 100644 --- a/receiver.c +++ b/receiver.c @@ -439,9 +439,8 @@ static void handle_delayed_updates(char *local_name) "rename failed for %s (from %s)", full_fname(fname), partialptr); } else { - if (remove_source_files - || (preserve_hard_links && F_IS_HLINKED(file))) - send_msg_int(MSG_SUCCESS, ndx); + if (remove_source_files || (preserve_hard_links && F_IS_HLINKED(file))) + send_msg_success(fname, ndx); handle_partial_dir(partialptr, PDIR_DELETE); } } @@ -698,7 +697,7 @@ int recv_files(int f_in, int f_out, char *local_name) if (!am_server) discard_receive_data(f_in, file); if (inc_recurse) - send_msg_int(MSG_SUCCESS, ndx); + send_msg_success(fname, ndx); continue; } @@ -926,9 +925,8 @@ int recv_files(int f_in, int f_out, char *local_name) case 2: break; case 1: - if (remove_source_files || inc_recurse - || (preserve_hard_links && F_IS_HLINKED(file))) - send_msg_int(MSG_SUCCESS, ndx); + if (remove_source_files || inc_recurse || (preserve_hard_links && F_IS_HLINKED(file))) + send_msg_success(fname, ndx); break; case 0: { enum logcode msgtype = redoing ? FERROR_XFER : FWARNING; diff --git a/rsync.1.md b/rsync.1.md index 1a3e8607..866a9e4f 100644 --- a/rsync.1.md +++ b/rsync.1.md @@ -1808,6 +1808,10 @@ expand it. Starting with 3.1.0, rsync will skip the sender-side removal (and output an error) if the file's size or modify time has not stayed unchanged. + Starting with 3.2.6, a local rsync copy will ensure that the sender does + not remove a file the receiver just verified, such as when the user + accidentally makes the source and destination directory the same path. + 0. `--delete` This tells rsync to delete extraneous files from the receiving side (ones diff --git a/sender.c b/sender.c index 9159da4d..3d4f052e 100644 --- a/sender.c +++ b/sender.c @@ -25,6 +25,7 @@ extern int do_xfers; extern int am_server; extern int am_daemon; +extern int local_server; extern int inc_recurse; extern int log_before_transfer; extern int stdout_format_has_i; @@ -51,6 +52,7 @@ extern int file_old_total; extern BOOL want_progress_now; extern struct stats stats; extern struct file_list *cur_flist, *first_flist, *dir_flist; +extern char num_dev_ino_buf[4 + 8 + 8]; BOOL extra_flist_sending_enabled; @@ -144,6 +146,13 @@ void successful_send(int ndx) goto failed; } + if (local_server + && (int64)st.st_dev == IVAL64(num_dev_ino_buf, 4) + && (int64)st.st_ino == IVAL64(num_dev_ino_buf, 4 + 8)) { + rprintf(FERROR_XFER, "ERROR: Skipping sender remove of destination file: %s\n", fname); + return; + } + if (st.st_size != F_LENGTH(file) || st.st_mtime != file->modtime #ifdef ST_MTIME_NSEC || (NSEC_BUMP(file) && (uint32)st.ST_MTIME_NSEC != F_MOD_NSEC(file)) -- 2.34.1