mirror of
https://github.com/RsyncProject/rsync.git
synced 2026-06-08 06:05:57 -04:00
Compare commits
3 Commits
v34-stable
...
fix/chmod-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
55b68225e5 | ||
|
|
b2fc33868f | ||
|
|
7371c898e4 |
95
chmod.c
95
chmod.c
@@ -29,7 +29,7 @@ extern mode_t orig_umask;
|
||||
|
||||
struct chmod_mode_struct {
|
||||
struct chmod_mode_struct *next;
|
||||
int ModeAND, ModeOR;
|
||||
int ModeAND, ModeOR, ModeCOPY_SRC, ModeCOPY_DST, ModeCOPY_AND, ModeOP;
|
||||
char flags;
|
||||
};
|
||||
|
||||
@@ -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. */
|
||||
@@ -50,13 +64,13 @@ struct chmod_mode_struct *parse_chmod(const char *modestr,
|
||||
struct chmod_mode_struct **root_mode_ptr)
|
||||
{
|
||||
int state = STATE_1ST_HALF;
|
||||
int where = 0, what = 0, op = 0, topbits = 0, topoct = 0, flags = 0;
|
||||
int where = 0, what = 0, op = 0, topbits = 0, topoct = 0, flags = 0, copybits = 0;
|
||||
struct chmod_mode_struct *first_mode = NULL, *curr_mode = NULL,
|
||||
*prev_mode = NULL;
|
||||
|
||||
while (state != STATE_ERROR) {
|
||||
if (!*modestr || *modestr == ',') {
|
||||
int bits;
|
||||
int bits, where_specified;
|
||||
|
||||
if (!op) {
|
||||
state = STATE_ERROR;
|
||||
@@ -70,9 +84,10 @@ struct chmod_mode_struct *parse_chmod(const char *modestr,
|
||||
first_mode = curr_mode;
|
||||
curr_mode->next = NULL;
|
||||
|
||||
if (where)
|
||||
where_specified = where;
|
||||
if (where) {
|
||||
bits = where * what;
|
||||
else {
|
||||
} else {
|
||||
where = 0111;
|
||||
bits = (where * what) & ~orig_umask;
|
||||
}
|
||||
@@ -81,18 +96,35 @@ struct chmod_mode_struct *parse_chmod(const char *modestr,
|
||||
case CHMOD_ADD:
|
||||
curr_mode->ModeAND = CHMOD_BITS;
|
||||
curr_mode->ModeOR = bits + topoct;
|
||||
curr_mode->ModeCOPY_SRC = copybits;
|
||||
curr_mode->ModeCOPY_DST = where;
|
||||
curr_mode->ModeCOPY_AND = where_specified ? CHMOD_BITS : ~orig_umask;
|
||||
curr_mode->ModeOP = op;
|
||||
break;
|
||||
case CHMOD_SUB:
|
||||
curr_mode->ModeAND = CHMOD_BITS - bits - topoct;
|
||||
curr_mode->ModeOR = 0;
|
||||
curr_mode->ModeCOPY_SRC = copybits;
|
||||
curr_mode->ModeCOPY_DST = where;
|
||||
curr_mode->ModeCOPY_AND = where_specified ? CHMOD_BITS : ~orig_umask;
|
||||
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;
|
||||
curr_mode->ModeCOPY_AND = where_specified ? CHMOD_BITS : ~orig_umask;
|
||||
curr_mode->ModeOP = op;
|
||||
break;
|
||||
case CHMOD_SET:
|
||||
curr_mode->ModeAND = 0;
|
||||
curr_mode->ModeOR = bits;
|
||||
curr_mode->ModeCOPY_SRC = 0;
|
||||
curr_mode->ModeCOPY_DST = 0;
|
||||
curr_mode->ModeCOPY_AND = CHMOD_BITS;
|
||||
curr_mode->ModeOP = op;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -103,7 +135,7 @@ struct chmod_mode_struct *parse_chmod(const char *modestr,
|
||||
modestr++;
|
||||
|
||||
state = STATE_1ST_HALF;
|
||||
where = what = op = topoct = topbits = flags = 0;
|
||||
where = what = op = topoct = topbits = flags = copybits = 0;
|
||||
}
|
||||
|
||||
switch (state) {
|
||||
@@ -159,26 +191,53 @@ struct chmod_mode_struct *parse_chmod(const char *modestr,
|
||||
case STATE_2ND_HALF:
|
||||
switch (*modestr) {
|
||||
case 'r':
|
||||
if (copybits)
|
||||
state = STATE_ERROR;
|
||||
what |= 4;
|
||||
break;
|
||||
case 'w':
|
||||
if (copybits)
|
||||
state = STATE_ERROR;
|
||||
what |= 2;
|
||||
break;
|
||||
case 'X':
|
||||
if (copybits)
|
||||
state = STATE_ERROR;
|
||||
flags |= FLAG_X_KEEP;
|
||||
/* FALL THROUGH */
|
||||
case 'x':
|
||||
if (copybits)
|
||||
state = STATE_ERROR;
|
||||
what |= 1;
|
||||
break;
|
||||
case 's':
|
||||
if (copybits)
|
||||
state = STATE_ERROR;
|
||||
if (topbits)
|
||||
topoct |= topbits;
|
||||
else
|
||||
topoct = 04000;
|
||||
break;
|
||||
case 't':
|
||||
if (copybits)
|
||||
state = STATE_ERROR;
|
||||
topoct |= 01000;
|
||||
break;
|
||||
case 'u':
|
||||
if (what || topoct || copybits)
|
||||
state = STATE_ERROR;
|
||||
copybits = 0100;
|
||||
break;
|
||||
case 'g':
|
||||
if (what || topoct || copybits)
|
||||
state = STATE_ERROR;
|
||||
copybits = 0010;
|
||||
break;
|
||||
case 'o':
|
||||
if (what || topoct || copybits)
|
||||
state = STATE_ERROR;
|
||||
copybits = 0001;
|
||||
break;
|
||||
default:
|
||||
state = STATE_ERROR;
|
||||
break;
|
||||
@@ -212,6 +271,20 @@ struct chmod_mode_struct *parse_chmod(const char *modestr,
|
||||
return first_mode;
|
||||
}
|
||||
|
||||
static int mode_copy_bits(int mode, int copy_src, int copy_dst, int copy_and)
|
||||
{
|
||||
int copy_bits = 0;
|
||||
|
||||
if (copy_src & 0100)
|
||||
copy_bits |= (mode >> 6) & 7;
|
||||
if (copy_src & 0010)
|
||||
copy_bits |= (mode >> 3) & 7;
|
||||
if (copy_src & 0001)
|
||||
copy_bits |= mode & 7;
|
||||
|
||||
return (copy_dst * copy_bits) & copy_and;
|
||||
}
|
||||
|
||||
|
||||
/* Takes an existing file permission and a list of AND/OR changes, and
|
||||
* create a new permissions. */
|
||||
@@ -219,17 +292,25 @@ int tweak_mode(int mode, struct chmod_mode_struct *chmod_modes)
|
||||
{
|
||||
int IsX = mode & 0111;
|
||||
int NonPerm = mode & ~CHMOD_BITS;
|
||||
int copy_bits;
|
||||
|
||||
for ( ; chmod_modes; chmod_modes = chmod_modes->next) {
|
||||
if ((chmod_modes->flags & FLAG_DIRS_ONLY) && !S_ISDIR(NonPerm))
|
||||
continue;
|
||||
if ((chmod_modes->flags & FLAG_FILES_ONLY) && S_ISDIR(NonPerm))
|
||||
continue;
|
||||
copy_bits = mode_copy_bits(mode, chmod_modes->ModeCOPY_SRC,
|
||||
chmod_modes->ModeCOPY_DST,
|
||||
chmod_modes->ModeCOPY_AND);
|
||||
mode &= chmod_modes->ModeAND;
|
||||
if ((chmod_modes->flags & FLAG_X_KEEP) && !IsX && !S_ISDIR(NonPerm))
|
||||
mode |= chmod_modes->ModeOR & ~0111;
|
||||
else
|
||||
mode |= chmod_modes->ModeOR;
|
||||
if (chmod_modes->ModeOP == CHMOD_SUB)
|
||||
mode &= CHMOD_BITS - copy_bits;
|
||||
else
|
||||
mode |= copy_bits;
|
||||
}
|
||||
|
||||
return mode | NonPerm;
|
||||
|
||||
10
rsync.1.md
10
rsync.1.md
@@ -1513,6 +1513,16 @@ expand it.
|
||||
|
||||
> --chmod=D2775,F664
|
||||
|
||||
Symbolic permission-copy modes are also allowed, such as `g=u`, `o=g` or
|
||||
`g-o`. A permission-copy item may copy from one class only (`u`, `g` or
|
||||
`o`) and cannot be combined with `rwxXst` permission letters in the same
|
||||
item. Use comma-separated items when you need both behaviours, such as
|
||||
`g=o,o=`.
|
||||
|
||||
A permission-copy `=` item also clears the special bit for each destination
|
||||
class it updates (`u` clears setuid, `g` clears setgid, and `o` clears
|
||||
sticky), matching GNU **chmod** behaviour.
|
||||
|
||||
It is also legal to specify multiple `--chmod` options, as each additional
|
||||
option is just appended to the list of changes to make.
|
||||
|
||||
|
||||
@@ -11,8 +11,8 @@ import shutil
|
||||
|
||||
from rsyncfns import (
|
||||
FROMDIR, SCRATCHDIR, TODIR,
|
||||
build_rsyncd_conf, checkit, makepath, rmtree,
|
||||
run_rsync, start_test_daemon,
|
||||
build_rsyncd_conf, check_perms, checkit, makepath, rmtree,
|
||||
run_rsync, start_test_daemon, test_fail,
|
||||
)
|
||||
|
||||
|
||||
@@ -62,6 +62,37 @@ 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, is_dir=False):
|
||||
rmtree(FROMDIR)
|
||||
rmtree(TODIR)
|
||||
makepath(FROMDIR)
|
||||
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)
|
||||
|
||||
|
||||
# Exercise chmod(1)-style permission copies.
|
||||
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)
|
||||
makepath(FROMDIR)
|
||||
(FROMDIR / 'permcopy').write_text('permcopy\n')
|
||||
proc = run_rsync('-avv', '--chmod=g=ur', f'{FROMDIR}/', f'{TODIR}/',
|
||||
check=False, capture_output=True)
|
||||
if proc.returncode == 0:
|
||||
test_fail('--chmod=g=ur was not rejected')
|
||||
|
||||
# Now exercise the F-only chmod path.
|
||||
rmtree(FROMDIR)
|
||||
rmtree(checkdir)
|
||||
|
||||
Reference in New Issue
Block a user