aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorElliott Hughes <enh@google.com>2020-12-03 14:48:16 -0800
committerRob Landley <rob@landley.net>2020-12-04 01:39:22 -0600
commit60e8974e215801f6c29de9f4b786f45323e9af89 (patch)
treeaa09c155caccb82078c137fc7f2c89d59c34d71e
parent5fddea07aa15b9ff0d5f25c3a9a2b6a6b4db47b0 (diff)
downloadtoybox-60e8974e215801f6c29de9f4b786f45323e9af89.tar.gz
chmod: support complex modes.
Change 5109da9b3e6a898c8e0ad647303a1b375e3d97d3 caused test.test to call chmod with mode `u+s+s` which passes on the host (where you have a toybox test but a GNU chmod) but fails on Android where chmod is toybox too. Add the missing loop to string_to_mode(), which means this will also affect other toys, but that seems like a feature (and, for example, GNU mkdir also accepts a mode like `a=r+w+x`).
-rw-r--r--lib/lib.c76
-rwxr-xr-xtests/chmod.test1
2 files changed, 41 insertions, 36 deletions
diff --git a/lib/lib.c b/lib/lib.c
index c4e70dfe..ebf85ce0 100644
--- a/lib/lib.c
+++ b/lib/lib.c
@@ -970,48 +970,52 @@ mode_t string_to_mode(char *modestr, mode_t mode)
umask(amask = umask(0));
}
- if (!*str || !(s = strchr(hows, *str))) goto barf;
- if (!(dohow = *(str++))) goto barf;
+ // Repeated "hows" are allowed; something like "a=r+w+s" is valid.
+ if (!*str) goto barf;
+ while (*str) {
+ if (!strchr(hows, *str)) goto barf;
+ if (!(dohow = *(str++))) goto barf;
+
+ while (*str && (s = strchr(whats, *str))) {
+ dowhat |= 1<<(s-whats);
+ str++;
+ }
- while (*str && (s = strchr(whats, *str))) {
- dowhat |= 1<<(s-whats);
- str++;
- }
+ // Convert X to x for directory or if already executable somewhere
+ if ((dowhat&32) && (S_ISDIR(mode) || (mode&0111))) dowhat |= 1;
- // Convert X to x for directory or if already executable somewhere
- if ((dowhat&32) && (S_ISDIR(mode) || (mode&0111))) dowhat |= 1;
+ // Copy mode from another category?
+ if (!dowhat && *str && (s = strchr(whys, *str))) {
+ dowhat = (mode>>(3*(s-whys)))&7;
+ str++;
+ }
- // Copy mode from another category?
- if (!dowhat && *str && (s = strchr(whys, *str))) {
- dowhat = (mode>>(3*(s-whys)))&7;
- str++;
- }
+ // Are we ready to do a thing yet?
+ if (*str && (str[1] != ',' && !strchr(hows, *str))) goto barf;
- // Are we ready to do a thing yet?
- if (*str && *(str++) != ',') goto barf;
-
- // Loop through what=xwrs and who=ogu to apply bits to the mode.
- for (i=0; i<4; i++) {
- for (j=0; j<3; j++) {
- mode_t bit = 0;
- int where = 1<<((3*i)+j);
-
- if (amask & where) continue;
-
- // Figure out new value at this location
- if (i == 3) {
- // suid and sticky
- if (!j) bit = dowhat&16; // o+s = t
- else if ((dowhat&8) && (dowho&(8|(1<<j)))) bit++;
- } else {
- if (!(dowho&(8|(1<<i)))) continue;
- else if (dowhat&(1<<j)) bit++;
- }
+ // Loop through what=xwrs and who=ogu to apply bits to the mode.
+ for (i=0; i<4; i++) {
+ for (j=0; j<3; j++) {
+ mode_t bit = 0;
+ int where = 1<<((3*i)+j);
+
+ if (amask & where) continue;
- // When selection active, modify bit
+ // Figure out new value at this location
+ if (i == 3) {
+ // suid and sticky
+ if (!j) bit = dowhat&16; // o+s = t
+ else if ((dowhat&8) && (dowho&(8|(1<<j)))) bit++;
+ } else {
+ if (!(dowho&(8|(1<<i)))) continue;
+ else if (dowhat&(1<<j)) bit++;
+ }
- if (dohow == '=' || (bit && dohow == '-')) mode &= ~where;
- if (bit && dohow != '-') mode |= where;
+ // When selection active, modify bit
+
+ if (dohow == '=' || (bit && dohow == '-')) mode &= ~where;
+ if (bit && dohow != '-') mode |= where;
+ }
}
}
diff --git a/tests/chmod.test b/tests/chmod.test
index 9dcf796e..2c8c6b91 100755
--- a/tests/chmod.test
+++ b/tests/chmod.test
@@ -112,6 +112,7 @@ chtest u+s "drwsr-xr-x\n-rwSr--r--\n"
chtest +s "drwsr-sr-x\n-rwSr-Sr--\n"
chtest o+s "drwxr-xr-x\n-rw-r--r--\n"
chtest +t "drwxr-xr-t\n-rw-r--r-T\n"
+chtest a=r+w+x "drwxrwxrwx\n-rwxrwxrwx\n"
mkdir foo
ln -s bar foo/baz