Add safety check for local --remove-source-files.
authorWayne Davison <wayne@opencoder.net>
Sun, 21 Aug 2022 16:56:33 +0000 (09:56 -0700)
committerWayne Davison <wayne@opencoder.net>
Sun, 21 Aug 2022 17:19:23 +0000 (10:19 -0700)
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
generator.c
hlink.c
io.c
receiver.c
rsync.1.md
sender.c

diff --git a/NEWS.md b/NEWS.md
index b5883ecf9d64c5e50bc272a8ae83d962096fa0cc..80b458e200d1ee1bb25e9a324a7de393883bf065 100644 (file)
--- a/NEWS.md
+++ b/NEWS.md
 - 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).
 
index 278e2a6f68ff03df1253cfddc034295712093dfd..935c84f9c4bd8974c5e78477d80422ea4d8906d0 100644 (file)
@@ -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 66810a3eb7d3654cbbeedd92668a30f69f7f5e0b..7602ec38d5e2e7de94509d568047f5dafdf635c9 100644 (file)
--- 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 3f605d74f7c726e035f3fe14ee38aea40714fe42..f3d802ecafaf87360471f1c38572561a7e4009c7 100644 (file)
--- 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);
index 93cf8efdb3efed1bb9cc3096ff58968c15c242f3..0f5d92d213fe0c35b4af7937182607ac1903189d 100644 (file)
@@ -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;
index 1a3e8607e95e53af49b5ce4b817c4998c2a5bf3c..866a9e4ff102949b2bfdceaa5060748303cfb7b2 100644 (file)
@@ -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
index 9159da4db80ac598b50dda7ea76178624200b832..3d4f052e9bdbeaf7e41a39315907bddcbe69ed15 100644 (file)
--- 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))