From e07cbd10f59a8197a0b5fc789ea3781a863548e8 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sat, 29 Oct 2016 16:24:23 -0400 Subject: Implement -perm. --- bfs.h | 20 +++++ eval.c | 31 +++++++ parse.c | 277 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- tests.sh | 51 +++++++++++- 4 files changed, 366 insertions(+), 13 deletions(-) diff --git a/bfs.h b/bfs.h index b504f0c..55aeda2 100644 --- a/bfs.h +++ b/bfs.h @@ -128,6 +128,18 @@ enum cmp_flag { CMP_GREATER, }; +/** + * Possible types of mode comparison. + */ +enum mode_cmp { + /** Mode is an exact match (MODE). */ + MODE_EXACT, + /** Mode has all these bits (-MODE). */ + MODE_ALL, + /** Mode has any of these bits (/MODE). */ + MODE_ANY, +}; + /** * Available struct stat time fields. */ @@ -207,6 +219,13 @@ struct expr { /** The optional comparison flag. */ enum cmp_flag cmp_flag; + /** The mode comparison flag. */ + enum mode_cmp mode_cmp; + /** Mode to use for files. */ + mode_t file_mode; + /** Mode to use for directories (different due to X). */ + mode_t dir_mode; + /** The optional reference time. */ struct timespec reftime; /** The optional time field. */ @@ -260,6 +279,7 @@ bool eval_true(const struct expr *expr, struct eval_state *state); bool eval_false(const struct expr *expr, struct eval_state *state); bool eval_access(const struct expr *expr, struct eval_state *state); +bool eval_perm(const struct expr *expr, struct eval_state *state); bool eval_acmtime(const struct expr *expr, struct eval_state *state); bool eval_acnewer(const struct expr *expr, struct eval_state *state); diff --git a/eval.c b/eval.c index f930bac..54d9a7c 100644 --- a/eval.c +++ b/eval.c @@ -635,6 +635,37 @@ bool eval_path(const struct expr *expr, struct eval_state *state) { return fnmatch(expr->sdata, ftwbuf->path, expr->idata) == 0; } +/** + * -perm test. + */ +bool eval_perm(const struct expr *expr, struct eval_state *state) { + const struct stat *statbuf = fill_statbuf(state); + if (!statbuf) { + return false; + } + + mode_t mode = statbuf->st_mode; + mode_t target; + if (state->ftwbuf->typeflag == BFTW_DIR) { + target = expr->dir_mode; + } else { + target = expr->file_mode; + } + + switch (expr->mode_cmp) { + case MODE_EXACT: + return (mode & 07777) == target; + + case MODE_ALL: + return (mode & target) == target; + + case MODE_ANY: + return !(mode & target) == !target; + } + + return false; +} + /** * -print action. */ diff --git a/parse.c b/parse.c index 20f256c..ca8a00f 100644 --- a/parse.c +++ b/parse.c @@ -370,12 +370,14 @@ static const char *skip_paths(struct parser_state *state) { /** Integer parsing flags. */ enum int_flags { - IF_INT = 0, - IF_LONG = 1, - IF_LONG_LONG = 2, - IF_SIZE_MASK = 0x3, - IF_UNSIGNED = 1 << 2, - IF_PARTIAL_OK = 1 << 3, + IF_BASE_MASK = 0x03F, + IF_INT = 0x040, + IF_LONG = 0x080, + IF_LONG_LONG = 0x0C0, + IF_SIZE_MASK = 0x0C0, + IF_UNSIGNED = 0x100, + IF_PARTIAL_OK = 0x200, + IF_QUIET = 0x400, }; /** @@ -384,8 +386,13 @@ enum int_flags { static const char *parse_int(const struct parser_state *state, const char *str, void *result, enum int_flags flags) { char *endptr; + int base = flags & IF_BASE_MASK; + if (base == 0) { + base = 10; + } + errno = 0; - long long value = strtoll(str, &endptr, 10); + long long value = strtoll(str, &endptr, base); if (errno != 0) { goto bad; } @@ -425,8 +432,10 @@ static const char *parse_int(const struct parser_state *state, const char *str, return endptr; bad: - pretty_error(state->cmdline->stderr_colors, - "error: '%s' is not a valid integer.\n", str); + if (!(flags & IF_QUIET)) { + pretty_error(state->cmdline->stderr_colors, + "error: '%s' is not a valid integer.\n", str); + } return NULL; } @@ -1208,6 +1217,255 @@ static struct expr *parse_noleaf(struct parser_state *state, int arg1, int arg2) return parse_nullary_option(state); } +/** + * Parse a permission mode like chmod(1). + */ +static int parse_mode(const struct parser_state *state, const char *mode, struct expr *expr) { + if (mode[0] >= '0' && mode[0] <= '9') { + unsigned int parsed; + if (!parse_int(state, mode, &parsed, 8 | IF_INT | IF_UNSIGNED | IF_QUIET)) { + goto fail; + } + if (parsed > 07777) { + goto fail; + } + + expr->file_mode = parsed; + expr->dir_mode = parsed; + return 0; + } + + expr->file_mode = 0; + expr->dir_mode = 0; + + // Parse the same grammar as chmod(1), which looks like this: + // + // MODE : CLAUSE ["," CLAUSE]* + // + // CLAUSE : WHO* ACTION+ + // + // WHO : "u" | "g" | "o" | "a" + // + // ACTION : OP PERM* + // | OP PERMCOPY + // + // OP : "+" | "-" | "=" + // + // PERM : "r" | "w" | "x" | "X" | "s" | "t" + // + // PERMCOPY : "u" | "g" | "o" + + // State machine state + enum { + MODE_CLAUSE, + MODE_WHO, + MODE_ACTION, + MODE_ACTION_APPLY, + MODE_OP, + MODE_PERM, + } mstate = MODE_CLAUSE; + + enum { + MODE_PLUS, + MODE_MINUS, + MODE_EQUALS, + } op; + + mode_t who; + mode_t file_change; + mode_t dir_change; + + const char *i = mode; + while (true) { + switch (mstate) { + case MODE_CLAUSE: + who = 0; + mstate = MODE_WHO; + // Fallthrough + + case MODE_WHO: + switch (*i) { + case 'u': + who |= 0700; + break; + case 'g': + who |= 0070; + break; + case 'o': + who |= 0007; + break; + case 'a': + who |= 0777; + break; + default: + mstate = MODE_ACTION; + continue; + } + break; + + case MODE_ACTION_APPLY: + switch (op) { + case MODE_EQUALS: + expr->file_mode &= ~who; + expr->dir_mode &= ~who; + // Fallthrough + case MODE_PLUS: + expr->file_mode |= file_change; + expr->dir_mode |= dir_change; + break; + case MODE_MINUS: + expr->file_mode &= ~file_change; + expr->dir_mode &= ~dir_change; + break; + } + // Fallthrough + + case MODE_ACTION: + if (who == 0) { + who = 0777; + } + + switch (*i) { + case '+': + op = MODE_PLUS; + mstate = MODE_OP; + break; + case '-': + op = MODE_MINUS; + mstate = MODE_OP; + break; + case '=': + op = MODE_EQUALS; + mstate = MODE_OP; + break; + + case ',': + if (mstate == MODE_ACTION_APPLY) { + mstate = MODE_CLAUSE; + } else { + goto fail; + } + break; + + case '\0': + if (mstate == MODE_ACTION_APPLY) { + goto done; + } else { + goto fail; + } + + default: + goto fail; + } + break; + + case MODE_OP: + file_change = 0; + dir_change = 0; + + switch (*i) { + case 'u': + case 'g': + case 'o': + // PERMCOPY (e.g. u=g) has no effect for -perm + mstate = MODE_ACTION_APPLY; + break; + + default: + mstate = MODE_PERM; + continue; + } + break; + + case MODE_PERM: + switch (*i) { + case 'r': + file_change |= who & 0444; + dir_change |= who & 0444; + break; + case 'w': + file_change |= who & 0222; + dir_change |= who & 0222; + break; + case 'x': + file_change |= who & 0111; + // Fallthrough + case 'X': + dir_change |= who & 0111; + break; + case 's': + if (who & 0700) { + file_change |= S_ISUID; + dir_change |= S_ISUID; + } + if (who & 0070) { + file_change |= S_ISGID; + dir_change |= S_ISGID; + } + break; + case 't': + file_change |= S_ISVTX; + dir_change |= S_ISVTX; + break; + default: + mstate = MODE_ACTION_APPLY; + continue; + } + break; + } + + ++i; + } + +done: + return 0; + +fail: + pretty_error(state->cmdline->stderr_colors, + "error: '%s' is an invalid mode.\n\n", + mode); + return -1; +} + +/** + * Parse -perm MODE. + */ +static struct expr *parse_perm(struct parser_state *state, int field, int arg2) { + struct expr *expr = parse_unary_test(state, eval_perm); + if (!expr) { + return NULL; + } + + const char *mode = expr->sdata; + switch (mode[0]) { + case '-': + expr->mode_cmp = MODE_ALL; + ++mode; + break; + case '/': + expr->mode_cmp = MODE_ANY; + ++mode; + break; + case '+': + pretty_error(state->cmdline->stderr_colors, + "error: -perm +mode is not supported.\n\n"); + goto fail; + default: + expr->mode_cmp = MODE_EXACT; + break; + } + + if (parse_mode(state, mode, expr) != 0) { + goto fail; + } + + return expr; + +fail: + free_expr(expr); + return NULL; +} + /** * Parse -print. */ @@ -1478,6 +1736,7 @@ static const struct table_entry parse_table[] = { {"okdir", false, parse_exec, EXEC_CONFIRM | EXEC_CHDIR}, {"or"}, {"path", false, parse_path, false}, + {"perm", false, parse_perm}, {"print", false, parse_print}, {"print0", false, parse_print0}, {"prune", false, parse_prune}, diff --git a/tests.sh b/tests.sh index a615346..51f891c 100755 --- a/tests.sh +++ b/tests.sh @@ -391,7 +391,50 @@ function test_0069() { "$BFS" "$scratch" -mindepth 1 -ignore_readdir_race -links 1 -exec ./tests/remove-sibling.sh '{}' ';' } -for i in {1..69}; do +function test_0070() { + find_diff "$perms" -perm 222 && \ + find_diff "$perms" -perm -222 && \ + find_diff "$perms" -perm /222 +} + +function test_0071() { + find_diff "$perms" -perm 644 && \ + find_diff "$perms" -perm -644 && \ + find_diff "$perms" -perm /644 +} + +function test_0072() { + find_diff "$perms" -perm a+r,u=wX,g+wX-w && \ + find_diff "$perms" -perm -a+r,u=wX,g+wX-w && \ + find_diff "$perms" -perm /a+r,u=wX,g+wX-w +} + +function test_0073() { + ! "$BFS" "$perms" -perm a+r, 2>/dev/null +} + +function test_0074() { + ! "$BFS" "$perms" -perm a+r,,u+w 2>/dev/null +} + +function test_0075() { + ! "$BFS" "$perms" -perm a 2>/dev/null +} + +function test_0076() { + find_diff "$perms" -perm -+rwx && \ + find_diff "$perms" -perm /+rwx +} + +function test_0077() { + ! "$BFS" "$perms" -perm +rwx 2>/dev/null +} + +function test_0078() { + ! "$BFS" "$perms" -perm +777 2>/dev/null +} + +for i in {1..78}; do test="test_$(printf '%04d' $i)" if [ -t 1 ]; then @@ -403,14 +446,14 @@ for i in {1..69}; do if [ $status -ne 0 ]; then if [ -t 1 ]; then - echo " failed!" + printf '\r%s failed!\n' "$test" else - echo "$test failed!" + printf '%s failed!\n' "$test" fi exit $status fi done if [ -t 1 ]; then - echo + printf '\n' fi -- cgit v1.2.3