From 4a08c0300cf6b0ee3bb5f755007641635ba21f9d Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sat, 21 Oct 2017 16:02:30 -0400 Subject: parse: Keep track of what files are already open Fixes #22 --- cmdline.h | 9 ++- color.c | 2 - expr.h | 4 +- parse.c | 139 ++++++++++++++++++++++++++++------------ tests.sh | 24 +++++-- tests/test_fprint_duplicate.out | 57 ++++++++++++++++ 6 files changed, 185 insertions(+), 50 deletions(-) create mode 100644 tests/test_fprint_duplicate.out diff --git a/cmdline.h b/cmdline.h index 9a46f7f..7c1891c 100644 --- a/cmdline.h +++ b/cmdline.h @@ -49,6 +49,11 @@ struct root { struct root *next; }; +/** + * An open file for the command line. + */ +struct open_file; + /** * The parsed command line. */ @@ -89,7 +94,9 @@ struct cmdline { /** The command line expression. */ struct expr *expr; - /** The number of open files used by the expression tree. */ + /** All the open files owned by the command line. */ + struct open_file *open_files; + /** The number of open files owned by the command line. */ int nopen_files; }; diff --git a/color.c b/color.c index 1025c9e..8df6a1f 100644 --- a/color.c +++ b/color.c @@ -288,8 +288,6 @@ int cfclose(CFILE *cfile) { if (cfile) { if (cfile->close) { ret = fclose(cfile->file); - } else if (cfile->file) { - ret = fflush(cfile->file); } free(cfile); } diff --git a/expr.h b/expr.h index c0e2b92..0eba0da 100644 --- a/expr.h +++ b/expr.h @@ -219,9 +219,7 @@ void dump_expr(CFILE *cfile, const struct expr *expr, bool verbose); /** * Free an expression tree. - * - * @return 0 if successful, -1 on error. */ -int free_expr(struct expr *expr); +void free_expr(struct expr *expr); #endif // EXPR_H diff --git a/parse.c b/parse.c index b4b7ddc..2708b9a 100644 --- a/parse.c +++ b/parse.c @@ -80,36 +80,23 @@ struct expr expr_false = { /** * Free an expression. */ -int free_expr(struct expr *expr) { - int ret = 0; - - if (expr && expr != &expr_true && expr != &expr_false) { - if (expr->cfile && expr->cfile->close) { - if (cfclose(expr->cfile) != 0) { - perror("cfclose()"); - ret = -1; - } - } - - if (expr->regex) { - regfree(expr->regex); - free(expr->regex); - } +void free_expr(struct expr *expr) { + if (!expr || expr == &expr_true || expr == &expr_false) { + return; + } - free_bfs_printf(expr->printf); - free_bfs_exec(expr->execbuf); + if (expr->regex) { + regfree(expr->regex); + free(expr->regex); + } - if (free_expr(expr->lhs) != 0) { - ret = -1; - } - if (free_expr(expr->rhs) != 0) { - ret = -1; - } + free_bfs_printf(expr->printf); + free_bfs_exec(expr->execbuf); - free(expr); - } + free_expr(expr->lhs); + free_expr(expr->rhs); - return ret; + free(expr); } struct expr *new_expr(eval_fn *eval, size_t argc, char **argv) { @@ -231,6 +218,22 @@ void dump_expr(CFILE *cfile, const struct expr *expr, bool verbose) { fputs(")", cfile->file); } +/** + * An open file for the command line. + */ +struct open_file { + /** The file itself. */ + CFILE *cfile; + /** The path to the file (for diagnostics). */ + const char *path; + /** Device number (for deduplication). */ + dev_t dev; + /** Inode number (for deduplication). */ + ino_t ino; + /** The next open file in the chain. */ + struct open_file *next; +}; + /** * Free the parsed command line. */ @@ -238,21 +241,38 @@ int free_cmdline(struct cmdline *cmdline) { int ret = 0; if (cmdline) { - if (free_expr(cmdline->expr) != 0) { - ret = -1; - } + CFILE *cout = cmdline->cout; + CFILE *cerr = cmdline->cerr; + + free_expr(cmdline->expr); free_bfs_mtab(cmdline->mtab); - if (cfclose(cmdline->cerr) != 0) { - perror("cfclose()"); - ret = -1; + struct open_file *ofile = cmdline->open_files; + while (ofile) { + struct open_file *next = ofile->next; + + if (cfclose(ofile->cfile) != 0) { + if (cerr) { + cfprintf(cerr, "%{er}error: '%s': %s%{rs}\n", ofile->path, strerror(errno)); + } + ret = -1; + } + + free(ofile); + ofile = next; } - if (cfclose(cmdline->cout) != 0) { - perror("cfclose()"); + + if (cout && fflush(cout->file) != 0) { + if (cerr) { + cfprintf(cerr, "%{er}error: standard output: %s%{rs}\n", strerror(errno)); + } ret = -1; } + cfclose(cout); + cfclose(cerr); + free_colors(cmdline->colors); struct root *root = cmdline->roots; @@ -349,14 +369,52 @@ static void init_print_expr(struct parser_state *state, struct expr *expr) { * Open a file for an expression. */ static int expr_open(struct parser_state *state, struct expr *expr, const char *path) { - expr->cfile = cfopen(path, state->use_color ? state->cmdline->colors : NULL); - if (!expr->cfile) { - cfprintf(state->cmdline->cerr, "%{er}error: '%s': %s%{rs}\n", path, strerror(errno)); - return -1; + int ret = -1; + + struct cmdline *cmdline = state->cmdline; + + CFILE *cfile = cfopen(path, state->use_color ? cmdline->colors : NULL); + if (!cfile) { + cfprintf(cmdline->cerr, "%{er}error: '%s': %s%{rs}\n", path, strerror(errno)); + goto out; } - ++state->cmdline->nopen_files; - return 0; + struct stat sb; + if (fstat(fileno(cfile->file), &sb) != 0) { + cfprintf(cmdline->cerr, "%{er}error: '%s': %s%{rs}\n", path, strerror(errno)); + goto out_close; + } + + for (struct open_file *ofile = cmdline->open_files; ofile; ofile = ofile->next) { + if (ofile->dev == sb.st_dev && ofile->ino == sb.st_ino) { + expr->cfile = ofile->cfile; + ret = 0; + goto out_close; + } + } + + struct open_file *ofile = malloc(sizeof(*ofile)); + if (!ofile) { + goto out_close; + } + + ofile->cfile = cfile; + ofile->path = path; + ofile->dev = sb.st_dev; + ofile->ino = sb.st_ino; + ofile->next = cmdline->open_files; + cmdline->open_files = ofile; + ++cmdline->nopen_files; + + expr->cfile = cfile; + + ret = 0; + goto out; + +out_close: + cfclose(cfile); +out: + return ret; } /** @@ -2932,6 +2990,7 @@ struct cmdline *parse_cmdline(int argc, char *argv[]) { cmdline->xargs_safe = false; cmdline->ignore_races = false; cmdline->expr = &expr_true; + cmdline->open_files = NULL; cmdline->nopen_files = 0; cmdline->argv = malloc((argc + 1)*sizeof(*cmdline->argv)); diff --git a/tests.sh b/tests.sh index 0f52581..9775292 100755 --- a/tests.sh +++ b/tests.sh @@ -330,6 +330,7 @@ gnu_tests=( test_flag_weird_names test_follow_comma test_fprint + test_fprint_duplicate test_double_dash test_flag_double_dash test_ignore_readdir_race @@ -931,16 +932,31 @@ function test_follow_comma() { } function test_fprint() { + invoke_bfs basic -fprint scratch/test_fprint.out + sort -o scratch/test_fprint.out scratch/test_fprint.out + if [ "$UPDATE" ]; then - invoke_bfs basic -fprint "$TESTS/test_fprint.out" - sort -o "$TESTS/test_fprint.out" "$TESTS/test_fprint.out" + cp scratch/test_fprint.out "$TESTS/test_fprint.out" else - invoke_bfs basic -fprint scratch/test_fprint.out - sort -o scratch/test_fprint.out scratch/test_fprint.out diff -u scratch/test_fprint.out "$TESTS/test_fprint.out" fi } +function test_fprint_duplicate() { + touchp scratch/test_fprint_duplicate.out + ln scratch/test_fprint_duplicate.out scratch/test_fprint_duplicate.hard + ln -s test_fprint_duplicate.out scratch/test_fprint_duplicate.soft + + invoke_bfs basic -fprint scratch/test_fprint_duplicate.out -fprint scratch/test_fprint_duplicate.hard -fprint scratch/test_fprint_duplicate.soft + sort -o scratch/test_fprint_duplicate.out scratch/test_fprint_duplicate.out + + if [ "$UPDATE" ]; then + cp scratch/test_fprint_duplicate.out "$TESTS/test_fprint_duplicate.out" + else + diff -u scratch/test_fprint_duplicate.out "$TESTS/test_fprint_duplicate.out" + fi +} + function test_double_dash() { cd basic bfs_diff -- . -type f diff --git a/tests/test_fprint_duplicate.out b/tests/test_fprint_duplicate.out new file mode 100644 index 0000000..2575f35 --- /dev/null +++ b/tests/test_fprint_duplicate.out @@ -0,0 +1,57 @@ +basic +basic +basic +basic/a +basic/a +basic/a +basic/b +basic/b +basic/b +basic/c +basic/c +basic/c +basic/c/d +basic/c/d +basic/c/d +basic/e +basic/e +basic/e +basic/e/f +basic/e/f +basic/e/f +basic/g +basic/g +basic/g +basic/g/h +basic/g/h +basic/g/h +basic/i +basic/i +basic/i +basic/j +basic/j +basic/j +basic/j/foo +basic/j/foo +basic/j/foo +basic/k +basic/k +basic/k +basic/k/foo +basic/k/foo +basic/k/foo +basic/k/foo/bar +basic/k/foo/bar +basic/k/foo/bar +basic/l +basic/l +basic/l +basic/l/foo +basic/l/foo +basic/l/foo +basic/l/foo/bar +basic/l/foo/bar +basic/l/foo/bar +basic/l/foo/bar/baz +basic/l/foo/bar/baz +basic/l/foo/bar/baz -- cgit v1.2.3