]> git.ipfire.org Git - thirdparty/rsync.git/commitdiff
chmod: clear special bits on copy assignment
authorZen Dodd <mail@steadytao.com>
Sat, 6 Jun 2026 05:00:57 +0000 (15:00 +1000)
committerAndrew Tridgell <andrew@tridgell.net>
Tue, 9 Jun 2026 04:18:29 +0000 (14:18 +1000)
chmod.c
testsuite/chmod-option_test.py

diff --git a/chmod.c b/chmod.c
index 8538d182b0014956218cbde3177b520212ef3e07..8368e5b3197c2604b7469a15bdcc6d038effa687 100644 (file)
--- a/chmod.c
+++ b/chmod.c
@@ -43,6 +43,20 @@ struct chmod_mode_struct {
 #define STATE_2ND_HALF 2
 #define STATE_OCTAL_NUM 3
 
+static int mode_dest_special_bits(int where)
+{
+       int bits = 0;
+
+       if (where & 0100)
+               bits |= S_ISUID;
+       if (where & 0010)
+               bits |= S_ISGID;
+       if (where & 0001)
+               bits |= S_ISVTX;
+
+       return bits;
+}
+
 /* Parse a chmod-style argument, and break it down into one or more AND/OR
  * pairs in a linked list.  We return a pointer to new items on success
  * (appending the items to the specified list), or NULL on error. */
@@ -96,7 +110,8 @@ struct chmod_mode_struct *parse_chmod(const char *modestr,
                                curr_mode->ModeOP = op;
                                break;
                        case CHMOD_EQ:
-                               curr_mode->ModeAND = CHMOD_BITS - (where * 7) - (topoct ? topbits : 0);
+                               curr_mode->ModeAND = CHMOD_BITS - (where * 7) - (topoct ? topbits : 0)
+                                                   - (copybits ? mode_dest_special_bits(where) : 0);
                                curr_mode->ModeOR  = bits + topoct;
                                curr_mode->ModeCOPY_SRC = copybits;
                                curr_mode->ModeCOPY_DST = where;
index b1cf714e251533d2e3782e6ad4c8134b21c01811..ad0fc77be25762ec53bb3146c74f80044053d79c 100644 (file)
@@ -62,12 +62,16 @@ for d in (checkdir, checkdir / 'dir1', checkdir / 'dir2'):
 checkit(['-avv', '--chmod', 'ug-s,a+rX,D+w', f'{FROMDIR}/', f'{TODIR}/'],
         checkdir, TODIR)
 
-def check_permcopy(chmod_arg, start_mode, expected):
+def check_permcopy(chmod_arg, start_mode, expected, is_dir=False):
     rmtree(FROMDIR)
     rmtree(TODIR)
     makepath(FROMDIR)
-    (FROMDIR / 'permcopy').write_text('permcopy\n')
-    os.chmod(FROMDIR / 'permcopy', start_mode)
+    permcopy = FROMDIR / 'permcopy'
+    if is_dir:
+        permcopy.mkdir()
+    else:
+        permcopy.write_text('permcopy\n')
+    os.chmod(permcopy, start_mode)
     run_rsync('-avv', f'--chmod={chmod_arg}', f'{FROMDIR}/', f'{TODIR}/')
     check_perms(TODIR / 'permcopy', expected)
 
@@ -76,6 +80,9 @@ def check_permcopy(chmod_arg, start_mode, expected):
 check_permcopy('g=o,o=', 0o647, 'rw-rwx---')
 check_permcopy('g=u', 0o741, 'rwxrwx--x')
 check_permcopy('g-o', 0o775, 'rwx-w-r-x')
+check_permcopy('u=g', 0o4755, 'r-xr-xr-x')
+check_permcopy('g=u', 0o2755, 'rwxrwxr-x')
+check_permcopy('o=u', 0o1750, 'rwxr-xrwx', is_dir=True)
 
 rmtree(FROMDIR)
 rmtree(TODIR)