summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--bfs.h24
-rw-r--r--eval.c25
-rw-r--r--parse.c122
-rwxr-xr-xtests.sh18
4 files changed, 172 insertions, 17 deletions
diff --git a/bfs.h b/bfs.h
index 1e38869..f0aaada 100644
--- a/bfs.h
+++ b/bfs.h
@@ -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);
diff --git a/eval.c b/eval.c
index aae82cd..05499db 100644
--- a/eval.c
+++ b/eval.c
@@ -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) {
diff --git a/parse.c b/parse.c
index 154d566..53d42eb 100644
--- a/parse.c
+++ b/parse.c
@@ -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;
diff --git a/tests.sh b/tests.sh
index a6c40b7..006496b 100755
--- a/tests.sh
+++ b/tests.sh
@@ -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=$?