diff options
-rw-r--r-- | bfs.h | 24 | ||||
-rw-r--r-- | eval.c | 25 | ||||
-rw-r--r-- | parse.c | 122 | ||||
-rwxr-xr-x | tests.sh | 18 |
4 files changed, 172 insertions, 17 deletions
@@ -125,6 +125,24 @@ enum timeunit { DAYS, }; +/** + * Possible file size units. + */ +enum sizeunit { + /** 512-byte blocks. */ + SIZE_BLOCKS, + /** Single bytes. */ + SIZE_BYTES, + /** Two-byte words. */ + SIZE_WORDS, + /** Kibibytes. */ + SIZE_KB, + /** Mebibytes. */ + SIZE_MB, + /** Gibibytes. */ + SIZE_GB, +}; + struct expr { /** The function that evaluates this expression. */ eval_fn *eval; @@ -152,13 +170,16 @@ struct expr { /** The optional time unit. */ enum timeunit timeunit; + /** The optional size unit. */ + enum sizeunit sizeunit; + /** Optional device number for a target file. */ dev_t dev; /** Optional inode number for a target file. */ ino_t ino; /** Optional integer data for this expression. */ - int idata; + long long idata; /** Optional string data for this expression. */ const char *sdata; @@ -197,6 +218,7 @@ bool eval_hidden(const struct expr *expr, struct eval_state *state); bool eval_inum(const struct expr *expr, struct eval_state *state); bool eval_links(const struct expr *expr, struct eval_state *state); bool eval_samefile(const struct expr *expr, struct eval_state *state); +bool eval_size(const struct expr *expr, struct eval_state *state); bool eval_type(const struct expr *expr, struct eval_state *state); bool eval_xtype(const struct expr *expr, struct eval_state *state); @@ -75,7 +75,7 @@ static time_t timespec_diff(const struct timespec *lhs, const struct timespec *r /** * Perform a comparison. */ -static bool do_cmp(const struct expr *expr, int n) { +static bool do_cmp(const struct expr *expr, long long n) { switch (expr->cmp) { case CMP_EXACT: return n == expr->idata; @@ -449,6 +449,29 @@ bool eval_samefile(const struct expr *expr, struct eval_state *state) { } /** + * -size test. + */ +bool eval_size(const struct expr *expr, struct eval_state *state) { + const struct stat *statbuf = fill_statbuf(state); + if (!statbuf) { + return false; + } + + static off_t scales[] = { + [SIZE_BLOCKS] = 512, + [SIZE_BYTES] = 1, + [SIZE_WORDS] = 2, + [SIZE_KB] = 1024, + [SIZE_MB] = 1024*1024, + [SIZE_GB] = 1024*1024*1024, + }; + + off_t scale = scales[expr->sizeunit]; + off_t size = (statbuf->st_size + scale - 1)/scale; // Round up + return do_cmp(expr, size); +} + +/** * -type test. */ bool eval_type(const struct expr *expr, struct eval_state *state) { @@ -213,34 +213,72 @@ static const char *skip_paths(struct parser_state *state) { } } +/** Integer parsing flags. */ +enum intflags { + IF_INT = 0, + IF_LONG = 1, + IF_LONG_LONG = 2, + IF_SIZE_MASK = 0x3, + IF_UNSIGNED = 1 << 2, + IF_PARTIAL_OK = 1 << 3, +}; + /** * Parse an integer. */ -static bool parse_int(const struct parser_state *state, const char *str, int *value) { +static const char *parse_int(const struct parser_state *state, const char *str, void *result, enum intflags flags) { char *endptr; - long result = strtol(str, &endptr, 10); - if (*str == '\0' || *endptr != '\0') { + errno = 0; + long long value = strtoll(str, &endptr, 10); + if (errno != 0) { goto bad; } - if (result < INT_MIN || result > INT_MAX) { + if (endptr == str) { goto bad; } - *value = result; - return true; + if (!(flags & IF_PARTIAL_OK) && *endptr != '\0') { + goto bad; + } + + if ((flags & IF_UNSIGNED) && value < 0) { + goto bad; + } + + switch (flags & IF_SIZE_MASK) { + case IF_INT: + if (value < INT_MIN || value > INT_MAX) { + goto bad; + } + *(int *)result = value; + break; + + case IF_LONG: + if (value < LONG_MIN || value > LONG_MAX) { + goto bad; + } + *(long *)result = value; + break; + + case IF_LONG_LONG: + *(long long *)result = value; + break; + } + + return endptr; bad: pretty_error(state->cmdline->stderr_colors, "error: '%s' is not a valid integer.\n", str); - return false; + return NULL; } /** * Parse an integer and a comparison flag. */ -static bool parse_icmp(const struct parser_state *state, const char *str, struct expr *expr) { +static const char *parse_icmp(const struct parser_state *state, const char *str, struct expr *expr, enum intflags flags) { switch (str[0]) { case '-': expr->cmp = CMP_LESS; @@ -255,7 +293,7 @@ static bool parse_icmp(const struct parser_state *state, const char *str, struct break; } - return parse_int(state, str, &expr->idata); + return parse_int(state, str, &expr->idata, flags | IF_LONG_LONG | IF_UNSIGNED); } /** @@ -383,7 +421,7 @@ static struct expr *parse_test_icmp(struct parser_state *state, eval_fn *eval) { return NULL; } - if (!parse_icmp(state, expr->sdata, expr)) { + if (!parse_icmp(state, expr->sdata, expr, 0)) { free_expr(expr); return NULL; } @@ -430,7 +468,7 @@ static struct expr *parse_debug(struct parser_state *state) { * Parse -On. */ static struct expr *parse_optlevel(struct parser_state *state) { - if (!parse_int(state, state->args[0] + 2, &state->cmdline->optlevel)) { + if (!parse_int(state, state->args[0] + 2, &state->cmdline->optlevel, IF_INT)) { return NULL; } @@ -525,7 +563,7 @@ static struct expr *parse_depth(struct parser_state *state, int *depth) { return NULL; } - if (!parse_int(state, value, depth)) { + if (!parse_int(state, value, depth, IF_INT)) { return NULL; } @@ -553,7 +591,7 @@ static struct expr *parse_group(struct parser_state *state) { error = strerror(errno); goto error; } else if (isdigit(expr->sdata[0])) { - if (!parse_int(state, expr->sdata, &expr->idata)) { + if (!parse_int(state, expr->sdata, &expr->idata, IF_LONG_LONG)) { goto fail; } } else { @@ -594,7 +632,7 @@ static struct expr *parse_user(struct parser_state *state) { error = strerror(errno); goto error; } else if (isdigit(expr->sdata[0])) { - if (!parse_int(state, expr->sdata, &expr->idata)) { + if (!parse_int(state, expr->sdata, &expr->idata, IF_LONG_LONG)) { goto fail; } } else { @@ -772,6 +810,60 @@ static struct expr *parse_samefile(struct parser_state *state) { } /** + * Parse -size N[bcwkMG]?. + */ +static struct expr *parse_size(struct parser_state *state) { + struct expr *expr = parse_unary_test(state, eval_size); + if (!expr) { + return NULL; + } + + const char *unit = parse_icmp(state, expr->sdata, expr, IF_PARTIAL_OK); + if (!unit) { + goto fail; + } + + if (strlen(unit) > 1) { + goto bad_unit; + } + + switch (*unit) { + case '\0': + case 'b': + expr->sizeunit = SIZE_BLOCKS; + break; + case 'c': + expr->sizeunit = SIZE_BYTES; + break; + case 'w': + expr->sizeunit = SIZE_WORDS; + break; + case 'k': + expr->sizeunit = SIZE_KB; + break; + case 'M': + expr->sizeunit = SIZE_MB; + break; + case 'G': + expr->sizeunit = SIZE_GB; + break; + + default: + goto bad_unit; + } + + return expr; + +bad_unit: + pretty_error(state->cmdline->stderr_colors, + "error: %s %s: Expected a size unit of 'b', 'c', 'w', 'k', 'M', or 'G'; found %s.\n", + expr->args[0], expr->args[1], unit); +fail: + free(expr); + return NULL; +} + +/** * Parse -x?type [bcdpfls]. */ static struct expr *parse_type(struct parser_state *state, eval_fn *eval) { @@ -1050,6 +1142,8 @@ static struct expr *parse_literal(struct parser_state *state) { case 's': if (strcmp(arg, "-samefile") == 0) { return parse_samefile(state); + } else if (strcmp(arg, "-size") == 0) { + return parse_size(state); } break; @@ -267,7 +267,23 @@ function test_0049() { find_diff "$basic" -newermc "$basic/e/f" } -for i in {1..49}; do +function test_0050() { + find_diff "$basic" -size 0 +} + +function test_0051() { + find_diff "$basic" -size +0 +} + +function test_0052() { + find_diff "$basic" -size +0c +} + +function test_0053() { + find_diff "$basic" -size 9223372036854775807 +} + +for i in {1..53}; do test="test_$(printf '%04d' $i)" "$test" "$dir" status=$? |