diff options
author | Tavian Barnes <tavianator@tavianator.com> | 2019-02-01 12:57:00 -0500 |
---|---|---|
committer | Tavian Barnes <tavianator@tavianator.com> | 2019-02-01 12:57:00 -0500 |
commit | 3a52c0e1f55d5dd03a00740c191569b9309c0ad6 (patch) | |
tree | 65785e4e2e4ff1a0a5ed4ec2f46895d559333616 | |
parent | 23b55d03ddf69b5ab40dc338c419089e8c64930b (diff) | |
parent | 36330d7aab63ef2c202efb0d551f104ed6cefa17 (diff) | |
download | bfs-3a52c0e1f55d5dd03a00740c191569b9309c0ad6.tar.xz |
Merge branch 'improvements'
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | .travis.yml | 35 | ||||
-rw-r--r-- | Makefile | 24 | ||||
-rw-r--r-- | dstring.c | 1 | ||||
-rw-r--r-- | eval.c | 117 | ||||
-rw-r--r-- | main.c | 40 | ||||
-rw-r--r-- | opt.c | 287 | ||||
-rw-r--r-- | parse.c | 9 | ||||
-rw-r--r-- | posix1e.h | 2 | ||||
-rw-r--r-- | stat.c | 7 | ||||
-rwxr-xr-x | tests.sh | 71 | ||||
-rw-r--r-- | tests/mksock.c | 131 | ||||
-rw-r--r-- | tests/test_closed_stdin.out | 19 | ||||
-rw-r--r-- | tests/test_color.out | 1 | ||||
-rw-r--r-- | tests/test_color_ext.out | 1 | ||||
-rw-r--r-- | tests/test_color_ext0.out | 1 | ||||
-rw-r--r-- | tests/test_color_mh.out | 1 | ||||
-rw-r--r-- | tests/test_color_mh0.out | 1 | ||||
-rw-r--r-- | tests/test_color_mi.out | 1 | ||||
-rw-r--r-- | tests/test_color_missing_colon.out | 1 | ||||
-rw-r--r-- | tests/test_color_or.out | 1 | ||||
-rw-r--r-- | tests/test_color_or0_mi.out | 1 | ||||
-rw-r--r-- | tests/test_color_or_mi.out | 1 | ||||
-rw-r--r-- | tests/test_color_or_mi0.out | 1 | ||||
-rw-r--r-- | tests/test_ok_closed_stdin.out | 0 | ||||
-rw-r--r-- | tests/test_okdir_closed_stdin.out | 0 | ||||
-rw-r--r-- | util.c | 10 | ||||
-rw-r--r-- | util.h | 41 |
28 files changed, 594 insertions, 212 deletions
@@ -1,3 +1,4 @@ *.o *.d /bfs +/tests/mksock diff --git a/.travis.yml b/.travis.yml index 393a947..1acc520 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,37 +1,24 @@ language: c -script: make check +script: make distcheck addons: apt: packages: - - acl-dev + - gcc-multilib + - libacl1-dev + - libacl1:i386 - libcap-dev + - libcap2:i386 matrix: include: - os: linux - dist: trusty - sudo: false - compiler: gcc - - - os: linux - dist: trusty - sudo: false - compiler: gcc - env: - - CFLAGS="-m32 -g -Wall" - addons: - apt: - packages: - - gcc-multilib - - acl-dev:i386 - - libcap-dev:i386 - - - os: linux - dist: trusty - sudo: false - compiler: clang + dist: xenial + before_script: + # Ubuntu doesn't let you install the -dev packages for both amd64 and + # i386 at once, so we make our own symlinks to fix -m32 -lacl -lcap + - sudo ln -s libacl.so.1 /lib/i386-linux-gnu/libacl.so + - sudo ln -s libcap.so.2 /lib/i386-linux-gnu/libcap.so - os: osx - compiler: clang @@ -54,6 +54,9 @@ LOCAL_LDLIBS := ifeq ($(OS),Linux) LOCAL_LDFLAGS += -Wl,--as-needed LOCAL_LDLIBS += -lacl -lcap -lrt + +# These libraries are not build with msan, so disable them +MSAN_CFLAGS := -DBFS_HAS_SYS_ACL=0 -DBFS_HAS_SYS_CAPABILITY=0 endif ALL_CPPFLAGS = $(LOCAL_CPPFLAGS) $(CPPFLAGS) @@ -82,20 +85,29 @@ bfs: \ util.o $(CC) $(ALL_LDFLAGS) $^ $(ALL_LDLIBS) -o $@ -sanitized: CFLAGS := -g $(WFLAGS) -fsanitize=address -fsanitize=undefined -sanitized: bfs - release: CFLAGS := -g $(WFLAGS) -O3 -flto -DNDEBUG release: bfs +tests/mksock: tests/mksock.o + $(CC) $(ALL_LDFLAGS) $^ -o $@ + %.o: %.c $(CC) $(ALL_CFLAGS) -c $< -o $@ -check: all +check: all tests/mksock ./tests.sh +distcheck: + +$(MAKE) -Bs check CFLAGS="$(CFLAGS) -fsanitize=undefined -fsanitize=address" +ifneq ($(OS),Darwin) + +$(MAKE) -Bs check CC=clang CFLAGS="$(CFLAGS) $(MSAN_CFLAGS) -fsanitize=memory" + +$(MAKE) -Bs check CFLAGS="$(CFLAGS) -m32" +endif + +$(MAKE) -Bs release check + +$(MAKE) -Bs check + clean: - $(RM) bfs *.o *.d + $(RM) bfs *.[od] tests/mksock tests/*.[od] install: $(MKDIR) $(DESTDIR)$(PREFIX)/bin @@ -107,6 +119,6 @@ uninstall: $(RM) $(DESTDIR)$(PREFIX)/bin/bfs $(RM) $(DESTDIR)$(PREFIX)/share/man/man1/bfs.1 -.PHONY: all release check clean install uninstall +.PHONY: all release check distcheck clean install uninstall -include $(wildcard *.d) @@ -43,6 +43,7 @@ char *dstralloc(size_t capacity) { header->capacity = capacity; header->length = 0; + header->data[0] = '\0'; return header->data; } @@ -60,42 +60,6 @@ struct eval_state { struct bfs_stat xstatbuf; }; -/** - * Print an error message. - */ -BFS_FORMATTER(2, 3) -static void eval_error(const struct eval_state *state, const char *format, ...) { - int error = errno; - const struct cmdline *cmdline = state->cmdline; - - bfs_error(cmdline, "%pP: ", state->ftwbuf); - - va_list args; - va_start(args, format); - errno = error; - cvfprintf(cmdline->cerr, format, args); - va_end(args); -} - -/** - * Check if an error should be ignored. - */ -static bool eval_should_ignore(const struct eval_state *state, int error) { - return state->cmdline->ignore_races - && is_nonexistence_error(error) - && state->ftwbuf->depth > 0; -} - -/** - * Report an error that occurs during evaluation. - */ -static void eval_report_error(struct eval_state *state) { - if (!eval_should_ignore(state, errno)) { - eval_error(state, "%m.\n"); - *state->ret = EXIT_FAILURE; - } -} - #define DEBUG_FLAG(flags, flag) \ do { \ if ((flags & flag) || flags == flag) { \ @@ -143,20 +107,83 @@ static void debug_stat(const struct eval_state *state, int at_flags, enum bfs_st /** * Perform a bfs_stat() call if necessary. */ -static const struct bfs_stat *eval_stat(struct eval_state *state) { +static const struct bfs_stat *eval_try_stat(struct eval_state *state) { struct BFTW *ftwbuf = state->ftwbuf; - if (!ftwbuf->statbuf) { - debug_stat(state, ftwbuf->at_flags, BFS_STAT_BROKEN_OK); - if (bfs_stat(ftwbuf->at_fd, ftwbuf->at_path, ftwbuf->at_flags, BFS_STAT_BROKEN_OK, &state->statbuf) == 0) { - ftwbuf->statbuf = &state->statbuf; - } else { - eval_report_error(state); - } + + if (ftwbuf->statbuf) { + goto done; + } + + if (ftwbuf->error) { + errno = ftwbuf->error; + goto done; + } + + debug_stat(state, ftwbuf->at_flags, BFS_STAT_BROKEN_OK); + + if (bfs_stat(ftwbuf->at_fd, ftwbuf->at_path, ftwbuf->at_flags, BFS_STAT_BROKEN_OK, &state->statbuf) == 0) { + ftwbuf->statbuf = &state->statbuf; + } else { + ftwbuf->error = errno; } + +done: return ftwbuf->statbuf; } /** + * Print an error message. + */ +BFS_FORMATTER(2, 3) +static void eval_error(struct eval_state *state, const char *format, ...) { + int error = errno; + const struct cmdline *cmdline = state->cmdline; + CFILE *cerr = cmdline->cerr; + + if (cerr->colors) { + eval_try_stat(state); + } + + bfs_error(cmdline, "%pP: ", state->ftwbuf); + + va_list args; + va_start(args, format); + errno = error; + cvfprintf(cerr, format, args); + va_end(args); +} + +/** + * Check if an error should be ignored. + */ +static bool eval_should_ignore(const struct eval_state *state, int error) { + return state->cmdline->ignore_races + && is_nonexistence_error(error) + && state->ftwbuf->depth > 0; +} + +/** + * Report an error that occurs during evaluation. + */ +static void eval_report_error(struct eval_state *state) { + if (!eval_should_ignore(state, errno)) { + eval_error(state, "%m.\n"); + *state->ret = EXIT_FAILURE; + } +} + +/** + * Perform a bfs_stat() call if necessary. + */ +static const struct bfs_stat *eval_stat(struct eval_state *state) { + const struct bfs_stat *ret = eval_try_stat(state); + if (!ret) { + eval_report_error(state); + } + return ret; +} + +/** * Perform a bfs_stat() call for tests that flip the follow flag, like -xtype. */ static const struct bfs_stat *eval_xstat(struct eval_state *state) { @@ -235,7 +262,7 @@ bool eval_capable(const struct expr *expr, struct eval_state *state) { /** * Get the given timespec field out of a stat buffer. */ -static const struct timespec *eval_stat_time(const struct bfs_stat *statbuf, enum bfs_stat_field field, const struct eval_state *state) { +static const struct timespec *eval_stat_time(const struct bfs_stat *statbuf, enum bfs_stat_field field, struct eval_state *state) { const struct timespec *ret = bfs_stat_time(statbuf, field); if (!ret) { eval_error(state, "Couldn't get file %s: %m.\n", bfs_stat_field_name(field)); @@ -1,6 +1,6 @@ /**************************************************************************** * bfs * - * Copyright (C) 2015-2017 Tavian Barnes <tavianator@tavianator.com> * + * Copyright (C) 2015-2019 Tavian Barnes <tavianator@tavianator.com> * * * * Permission to use, copy, modify, and/or distribute this software for any * * purpose with or without fee is hereby granted. * @@ -24,16 +24,31 @@ #include <unistd.h> /** - * Ensure that a file descriptor is open. + * Make sure the standard streams std{in,out,err} are open. If they are not, + * future open() calls may use those file descriptors, and std{in,out,err} will + * use them unintentionally. */ -static int ensure_fd_open(int fd, int flags) { - if (isopen(fd)) { - return 0; - } else if (redirect(fd, "/dev/null", flags) >= 0) { - return 0; - } else { +static int open_std_streams() { +#ifdef O_PATH + const int inflags = O_PATH, outflags = O_PATH; +#else + // These are intentionally backwards so that bfs >&- still fails with EBADF + const int inflags = O_WRONLY, outflags = O_RDONLY; +#endif + + if (!isopen(STDERR_FILENO) && redirect(STDERR_FILENO, "/dev/null", outflags) < 0) { + return -1; + } + if (!isopen(STDOUT_FILENO) && redirect(STDOUT_FILENO, "/dev/null", outflags) < 0) { + perror("redirect()"); + return -1; + } + if (!isopen(STDIN_FILENO) && redirect(STDIN_FILENO, "/dev/null", inflags) < 0) { + perror("redirect()"); return -1; } + + return 0; } /** @@ -42,13 +57,8 @@ static int ensure_fd_open(int fd, int flags) { int main(int argc, char *argv[]) { int ret = EXIT_FAILURE; - if (ensure_fd_open(STDIN_FILENO, O_RDONLY) != 0) { - goto done; - } - if (ensure_fd_open(STDOUT_FILENO, O_WRONLY) != 0) { - goto done; - } - if (ensure_fd_open(STDERR_FILENO, O_WRONLY) != 0) { + // Make sure the standard streams are open + if (open_std_streams() != 0) { goto done; } @@ -27,45 +27,157 @@ static char *fake_and_arg = "-a"; static char *fake_or_arg = "-o"; /** + * A contrained integer range. + */ +struct range { + /** The (inclusive) minimum value. */ + long long min; + /** The (inclusive) maximum value. */ + long long max; +}; + +/** Compute the minimum of two values. */ +static long long min_value(long long a, long long b) { + if (a < b) { + return a; + } else { + return b; + } +} + +/** Compute the maximum of two values. */ +static long long max_value(long long a, long long b) { + if (a > b) { + return a; + } else { + return b; + } +} + +/** Constrain the minimum of a range. */ +static void constrain_min(struct range *range, long long value) { + range->min = max_value(range->min, value); +} + +/** Contrain the maximum of a range. */ +static void constrain_max(struct range *range, long long value) { + range->max = min_value(range->max, value); +} + +/** Remove a single value from a range. */ +static void range_remove(struct range *range, long long value) { + if (range->min == value) { + if (range->min == LLONG_MAX) { + range->max = LLONG_MIN; + } else { + ++range->min; + } + } + + if (range->max == value) { + if (range->max == LLONG_MIN) { + range->min = LLONG_MAX; + } else { + --range->max; + } + } +} + +/** Compute the union of two ranges. */ +static void range_union(struct range *result, const struct range *lhs, const struct range *rhs) { + result->min = min_value(lhs->min, rhs->min); + result->max = max_value(lhs->max, rhs->max); +} + +/** Check if a range contains no values. */ +static bool range_impossible(const struct range *range) { + return range->min > range->max; +} + +/** Set a range to contain no values. */ +static void set_range_impossible(struct range *range) { + range->min = LLONG_MAX; + range->max = LLONG_MIN; +} + +/** + * Types of ranges we track. + */ +enum range_type { + /** Search tree depth. */ + DEPTH_RANGE, + /** Group ID. */ + GID_RANGE, + /** Inode number. */ + INUM_RANGE, + /** Hard link count. */ + LINKS_RANGE, + /** File size. */ + SIZE_RANGE, + /** User ID. */ + UID_RANGE, + /** The number of range_types. */ + MAX_RANGE, +}; + +/** * Data flow facts about an evaluation point. */ struct opt_facts { - /** Minimum possible depth at this point. */ - int mindepth; - /** Maximum possible depth at this point. */ - int maxdepth; + /** The value ranges we track. */ + struct range ranges[MAX_RANGE]; - /** Bitmask of possible file types at this point. */ + /** Bitmask of possible file types. */ enum bftw_typeflag types; + /** Bitmask of possible link target types. */ + enum bftw_typeflag xtypes; }; -/** Compute the union of two fact sets. */ -static void facts_union(struct opt_facts *result, const struct opt_facts *lhs, const struct opt_facts *rhs) { - if (lhs->mindepth < rhs->mindepth) { - result->mindepth = lhs->mindepth; - } else { - result->mindepth = rhs->mindepth; +/** Initialize some data flow facts. */ +static void facts_init(struct opt_facts *facts) { + for (int i = 0; i < MAX_RANGE; ++i) { + struct range *range = facts->ranges + i; + range->min = 0; // All ranges we currently track are non-negative + range->max = LLONG_MAX; } - if (lhs->maxdepth > rhs->maxdepth) { - result->maxdepth = lhs->maxdepth; - } else { - result->maxdepth = rhs->maxdepth; + facts->types = ~0; + facts->xtypes = ~0; +} + +/** Compute the union of two fact sets. */ +static void facts_union(struct opt_facts *result, const struct opt_facts *lhs, const struct opt_facts *rhs) { + for (int i = 0; i < MAX_RANGE; ++i) { + range_union(result->ranges + i, lhs->ranges + i, rhs->ranges + i); } result->types = lhs->types | rhs->types; + result->xtypes = lhs->xtypes | rhs->xtypes; } /** Determine whether a fact set is impossible. */ static bool facts_impossible(const struct opt_facts *facts) { - return facts->mindepth > facts->maxdepth || !facts->types; + for (int i = 0; i < MAX_RANGE; ++i) { + if (range_impossible(facts->ranges + i)) { + return true; + } + } + + if (!facts->types || !facts->xtypes) { + return true; + } + + return false; } /** Set some facts to be impossible. */ static void set_facts_impossible(struct opt_facts *facts) { - facts->mindepth = INT_MAX; - facts->maxdepth = -1; + for (int i = 0; i < MAX_RANGE; ++i) { + set_range_impossible(facts->ranges + i); + } + facts->types = 0; + facts->xtypes = 0; } /** @@ -110,6 +222,10 @@ static void debug_opt(const struct opt_state *state, const char *format, ...) { case 'g': cfprintf(cerr, "${ylw}%g${rs}", va_arg(args, double)); break; + + default: + assert(false); + break; } } else { fputc(*i, stderr); @@ -119,55 +235,6 @@ static void debug_opt(const struct opt_state *state, const char *format, ...) { va_end(args); } -/** Update the inferred mindepth. */ -static void update_mindepth(struct opt_facts *facts, long long mindepth) { - if (mindepth > facts->mindepth) { - if (mindepth > INT_MAX) { - facts->maxdepth = -1; - } else { - facts->mindepth = mindepth; - } - } -} - -/** Update the inferred maxdepth. */ -static void update_maxdepth(struct opt_facts *facts, long long maxdepth) { - if (maxdepth < facts->maxdepth) { - facts->maxdepth = maxdepth; - } -} - -/** Infer data flow facts about a -depth N expression. */ -static void infer_depth_facts(struct opt_state *state, const struct expr *expr) { - switch (expr->cmp_flag) { - case CMP_EXACT: - update_mindepth(&state->facts_when_true, expr->idata); - update_maxdepth(&state->facts_when_true, expr->idata); - break; - - case CMP_LESS: - update_maxdepth(&state->facts_when_true, expr->idata - 1); - update_mindepth(&state->facts_when_false, expr->idata); - break; - - case CMP_GREATER: - if (expr->idata == LLONG_MAX) { - // Avoid overflow - state->facts_when_true.maxdepth = -1; - } else { - update_mindepth(&state->facts_when_true, expr->idata + 1); - } - update_maxdepth(&state->facts_when_false, expr->idata); - break; - } -} - -/** Infer data flow facts about a -type expression. */ -static void infer_type_facts(struct opt_state *state, const struct expr *expr) { - state->facts_when_true.types &= expr->idata; - state->facts_when_false.types &= ~expr->idata; -} - /** Extract a child expression, freeing the outer expression. */ static struct expr *extract_child_expr(struct expr *expr, struct expr **child) { struct expr *ret = *child; @@ -525,14 +592,65 @@ fail: return NULL; } +/** Infer data flow facts about an icmp-style ([+-]N) expression */ +static void infer_icmp_facts(struct opt_state *state, const struct expr *expr, enum range_type type) { + struct range *range_when_true = state->facts_when_true.ranges + type; + struct range *range_when_false = state->facts_when_false.ranges + type; + long long value = expr->idata; + + switch (expr->cmp_flag) { + case CMP_EXACT: + constrain_min(range_when_true, value); + constrain_max(range_when_true, value); + range_remove(range_when_false, value); + break; + + case CMP_LESS: + constrain_min(range_when_false, value); + constrain_max(range_when_true, value); + range_remove(range_when_true, value); + break; + + case CMP_GREATER: + constrain_max(range_when_false, value); + constrain_min(range_when_true, value); + range_remove(range_when_true, value); + break; + } +} + +/** Infer data flow facts about a -type expression. */ +static void infer_type_facts(struct opt_state *state, const struct expr *expr) { + state->facts_when_true.types &= expr->idata; + state->facts_when_false.types &= ~expr->idata; +} + +/** Infer data flow facts about an -xtype expression. */ +static void infer_xtype_facts(struct opt_state *state, const struct expr *expr) { + state->facts_when_true.xtypes &= expr->idata; + state->facts_when_false.xtypes &= ~expr->idata; +} + static struct expr *optimize_expr_recursive(struct opt_state *state, struct expr *expr) { state->facts_when_true = state->facts; state->facts_when_false = state->facts; if (expr->eval == eval_depth) { - infer_depth_facts(state, expr); + infer_icmp_facts(state, expr, DEPTH_RANGE); + } else if (expr->eval == eval_gid) { + infer_icmp_facts(state, expr, GID_RANGE); + } else if (expr->eval == eval_inum) { + infer_icmp_facts(state, expr, INUM_RANGE); + } else if (expr->eval == eval_links) { + infer_icmp_facts(state, expr, LINKS_RANGE); + } else if (expr->eval == eval_size) { + infer_icmp_facts(state, expr, SIZE_RANGE); } else if (expr->eval == eval_type) { infer_type_facts(state, expr); + } else if (expr->eval == eval_uid) { + infer_icmp_facts(state, expr, UID_RANGE); + } else if (expr->eval == eval_xtype) { + infer_xtype_facts(state, expr); } else if (expr->eval == eval_not) { expr = optimize_not_expr_recursive(state, expr); } else if (expr->eval == eval_and) { @@ -649,13 +767,13 @@ int optimize_cmdline(struct cmdline *cmdline) { struct opt_state state = { .cmdline = cmdline, - .facts = { - .mindepth = cmdline->mindepth, - .maxdepth = cmdline->maxdepth, - .types = ~0, - }, .facts_when_impure = &facts_when_impure, }; + facts_init(&state.facts); + + struct range *depth = state.facts.ranges + DEPTH_RANGE; + depth->min = cmdline->mindepth; + depth->max = cmdline->maxdepth; int optlevel = cmdline->optlevel; @@ -675,13 +793,24 @@ int optimize_cmdline(struct cmdline *cmdline) { cmdline->expr = ignore_result(&state, cmdline->expr); - if (optlevel >= 2 && facts_when_impure.mindepth > cmdline->mindepth) { - debug_opt(&state, "-O2: data flow: mindepth --> %d\n", facts_when_impure.mindepth); - cmdline->mindepth = facts_when_impure.mindepth; + const struct range *depth_when_impure = facts_when_impure.ranges + DEPTH_RANGE; + long long mindepth = depth_when_impure->min; + long long maxdepth = depth_when_impure->max; + + if (optlevel >= 2 && mindepth > cmdline->mindepth) { + if (mindepth > INT_MAX) { + mindepth = INT_MAX; + } + cmdline->mindepth = mindepth; + debug_opt(&state, "-O2: data flow: mindepth --> %d\n", cmdline->mindepth); } - if (optlevel >= 4 && facts_when_impure.maxdepth < cmdline->maxdepth) { - debug_opt(&state, "-O4: data flow: maxdepth --> %d\n", facts_when_impure.maxdepth); - cmdline->maxdepth = facts_when_impure.maxdepth; + + if (optlevel >= 4 && maxdepth < cmdline->maxdepth) { + if (maxdepth < INT_MIN) { + maxdepth = INT_MIN; + } + cmdline->maxdepth = maxdepth; + debug_opt(&state, "-O4: data flow: maxdepth --> %d\n", cmdline->maxdepth); } return 0; @@ -2323,12 +2323,9 @@ static struct expr *parse_help(struct parser_state *state, int arg1, int arg2) { cfprintf(cout, "Usage: ${ex}%s${rs} [${cyn}flags${rs}...] [${mag}paths${rs}...] [${blu}expression${rs}...]\n\n", state->command); - cfprintf(cout, "${ex}bfs${rs} is compatible with ${ex}find${rs}; see ${ex}find${rs} ${blu}-help${rs} or" - " ${ex}man${rs} ${bld}find${rs} for help with ${ex}find${rs}-\n" - "compatible options :)\n\n"); - - cfprintf(cout, "${cyn}flags${rs} (${cyn}-H${rs}/${cyn}-L${rs}/${cyn}-P${rs} etc.), ${mag}paths${rs}, and" - " ${blu}expressions${rs} may be freely mixed in any order.\n\n"); + cfprintf(cout, "${ex}bfs${rs} is compatible with ${ex}find${rs}, with some extensions. " + "${cyn}Flags${rs} (${cyn}-H${rs}/${cyn}-L${rs}/${cyn}-P${rs} etc.), ${mag}paths${rs},\n" + "and ${blu}expressions${rs} may be freely mixed in any order.\n\n"); cfprintf(cout, "${bld}POSIX find features:${rs}\n\n"); @@ -21,7 +21,7 @@ #include "util.h" #include <stdbool.h> -#if BFS_HAS_SYS_CAPABILITY && !__FreeBSD__ +#if !defined(BFS_HAS_POSIX1E_CAPABILITIES) && BFS_HAS_SYS_CAPABILITY && !__FreeBSD__ # include <sys/capability.h> # ifdef CAP_CHOWN # define BFS_HAS_POSIX1E_CAPABILITIES true @@ -20,6 +20,7 @@ #include <errno.h> #include <fcntl.h> #include <stdbool.h> +#include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> @@ -161,6 +162,12 @@ static int bfs_stat_impl(int at_fd, const char *at_path, int at_flags, enum bfs_ * Wrapper for the statx() system call, which had no glibc wrapper prior to 2.28. */ static int bfs_statx(int at_fd, const char *at_path, int at_flags, unsigned int mask, struct statx *buf) { + // -fsanitize=memory doesn't know about statx(), so tell it the memory + // got initialized +#if BFS_HAS_FEATURE(memory_sanitizer, false) + memset(buf, 0, sizeof(*buf)); +#endif + #if HAVE_STATX return statx(at_fd, at_path, at_flags, mask, buf); #else @@ -22,6 +22,16 @@ umask 022 export LC_ALL=C export TZ=UTC +function _realpath() { + ( + cd "${1%/*}" + echo "$PWD/${1##*/}" + ) +} + +BFS="$(_realpath ./bfs)" +TESTS="$(_realpath ./tests)" + # The temporary directory that will hold our test data TMP="$(mktemp -d "${TMPDIR:-/tmp}"/bfs.XXXXXXXXXX)" chown "$(id -u):$(id -g)" "$TMP" @@ -177,7 +187,7 @@ function make_rainbow() { # TODO: block # TODO: chardev ln -s nowhere "$1/broken" - # TODO: socket + "$TESTS/mksock" "$1/socket" touchp "$1"/s{u,g,ug}id chmod u+s "$1"/su{,g}id chmod g+s "$1"/s{u,}gid @@ -195,16 +205,6 @@ function make_scratch() { } make_scratch "$TMP/scratch" -function _realpath() { - ( - cd "${1%/*}" - echo "$PWD/${1##*/}" - ) -} - -BFS="$(_realpath ./bfs)" -TESTS="$(_realpath ./tests)" - posix_tests=( # General parsing test_basic @@ -290,6 +290,11 @@ posix_tests=( test_user_name test_user_id + # Closed file descriptors + test_closed_stdin + test_closed_stdout + test_closed_stderr + # PATH_MAX handling test_deep @@ -385,8 +390,10 @@ bsd_tests=( test_nouser test_ok_stdin + test_ok_closed_stdin test_okdir_stdin + test_okdir_closed_stdin test_perm_000_plus test_perm_222_plus @@ -525,8 +532,10 @@ gnu_tests=( test_nouser + test_ok_closed_stdin test_ok_nothing + test_okdir_closed_stdin test_okdir_plus_semicolon test_perm_000_slash @@ -787,6 +796,11 @@ function invoke_bfs() { $BFS "$@" } +# Return value when bfs fails +EX_BFS=10 +# Return value when a difference is detected +EX_DIFF=20 + function bfs_diff() ( bfs_verbose "$@" @@ -802,9 +816,16 @@ function bfs_diff() ( fi $BFS "$@" | bfs_sort >"$ACTUAL" + local STATUS="${PIPESTATUS[0]}" if [ ! "$UPDATE" ]; then - diff -u "$EXPECTED" "$ACTUAL" + diff -u "$EXPECTED" "$ACTUAL" || return $EX_DIFF + fi + + if [ "$STATUS" -eq 0 ]; then + return 0 + else + return $EX_BFS fi ) @@ -890,7 +911,7 @@ function test_depth_error() { chmod +r scratch/foo rm -rf scratch/* - return $ret + [ $ret -eq $EX_BFS ] } function test_name() { @@ -1046,10 +1067,12 @@ function test_L_loops() { function test_L_loops_continue() { bfs_diff -L loops 2>/dev/null + [ $? -eq $EX_BFS ] } function test_X() { bfs_diff -X weirdnames 2>/dev/null + [ $? -eq $EX_BFS ] } function test_follow() { @@ -1708,7 +1731,7 @@ function test_printf_Y_error() { chmod +x scratch/foo rm -rf scratch/* - return $ret + [ $ret -eq $EX_BFS ] } function test_fstype() { @@ -1915,6 +1938,26 @@ function test_fprint_error() { fi } +function test_closed_stdin() { + bfs_diff basic <&- +} + +function test_ok_closed_stdin() { + bfs_diff basic -ok echo \; <&- 2>/dev/null +} + +function test_okdir_closed_stdin() { + bfs_diff basic -okdir echo {} \; <&- 2>/dev/null +} + +function test_closed_stdout() { + ! invoke_bfs basic >&- 2>/dev/null +} + +function test_closed_stderr() { + ! invoke_bfs basic >&- 2>&- +} + if [ -t 1 -a ! "$VERBOSE" ]; then in_place=yes fi diff --git a/tests/mksock.c b/tests/mksock.c new file mode 100644 index 0000000..a996d58 --- /dev/null +++ b/tests/mksock.c @@ -0,0 +1,131 @@ +/**************************************************************************** + * bfs * + * Copyright (C) 2019 Tavian Barnes <tavianator@tavianator.com> * + * * + * Permission to use, copy, modify, and/or distribute this software for any * + * purpose with or without fee is hereby granted. * + * * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * + ****************************************************************************/ + +/** + * There's no standard Unix utility that creates a socket file, so this small + * program does the job. + */ + +#include <errno.h> +#include <fcntl.h> +#include <libgen.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/un.h> +#include <unistd.h> + +/** + * Print an error message. + */ +static void errmsg(const char *cmd, const char *path) { + fprintf(stderr, "%s: '%s': %s.\n", cmd, path, strerror(errno)); +} + +/** + * struct sockaddr_un::sun_path is very short, so we chdir() into the target + * directory before creating sockets in case the full path is too long but the + * file name is not. + */ +static int chdir_parent(const char *path) { + char *copy = strdup(path); + if (!copy) { + return -1; + } + const char *dir = dirname(copy); + + int ret = chdir(dir); + + int error = errno; + free(copy); + errno = error; + + return ret; +} + +/** + * Initialize a struct sockaddr_un with the right filename. + */ +static int init_sun(struct sockaddr_un *sock, const char *path) { + size_t len = strlen(path); + if (len == 0 || path[len - 1] == '/') { + errno = ENOENT; + return -1; + } + + char *copy = strdup(path); + if (!copy) { + return -1; + } + const char *base = basename(copy); + + len = strlen(base); + if (len >= sizeof(sock->sun_path)) { + free(copy); + errno = ENAMETOOLONG; + return -1; + } + + sock->sun_family = AF_UNIX; + memcpy(sock->sun_path, base, len + 1); + free(copy); + return 0; +} + +int main(int argc, char *argv[]) { + const char *cmd = argc > 0 ? argv[0] : "mksock"; + + if (argc != 2) { + fprintf(stderr, "Usage: %s NAME\n", cmd); + return EXIT_FAILURE; + } + + const char *path = argv[1]; + + if (chdir_parent(path) != 0) { + errmsg(cmd, path); + return EXIT_FAILURE; + } + + struct sockaddr_un sock; + if (init_sun(&sock, path) != 0) { + errmsg(cmd, path); + return EXIT_FAILURE; + } + + int fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) { + errmsg(cmd, path); + return EXIT_FAILURE; + } + + int ret = EXIT_SUCCESS; + + if (bind(fd, (struct sockaddr *)&sock, sizeof(sock)) != 0) { + errmsg(cmd, path); + ret = EXIT_FAILURE; + } + + if (close(fd) != 0) { + errmsg(cmd, path); + ret = EXIT_FAILURE; + } + + return ret; +} diff --git a/tests/test_closed_stdin.out b/tests/test_closed_stdin.out new file mode 100644 index 0000000..bb3cd8d --- /dev/null +++ b/tests/test_closed_stdin.out @@ -0,0 +1,19 @@ +basic +basic/a +basic/b +basic/c +basic/e +basic/g +basic/i +basic/j +basic/k +basic/l +basic/c/d +basic/e/f +basic/g/h +basic/j/foo +basic/k/foo +basic/l/foo +basic/k/foo/bar +basic/l/foo/bar +basic/l/foo/bar/baz diff --git a/tests/test_color.out b/tests/test_color.out index e267da8..40e09b4 100644 --- a/tests/test_color.out +++ b/tests/test_color.out @@ -1,5 +1,6 @@ [01;34mrainbow[0m [01;34mrainbow/[0m[01;32mexec.sh[0m +[01;34mrainbow/[0m[01;35msocket[0m [01;34mrainbow/[0m[01;36mlink.txt[0m [01;34mrainbow/[0m[30;42msticky_ow[0m [01;34mrainbow/[0m[30;43msgid[0m diff --git a/tests/test_color_ext.out b/tests/test_color_ext.out index d2e2a70..f8c4d28 100644 --- a/tests/test_color_ext.out +++ b/tests/test_color_ext.out @@ -1,5 +1,6 @@ [01;34mrainbow[0m [01;34mrainbow/[0m[01;32mexec.sh[0m +[01;34mrainbow/[0m[01;35msocket[0m [01;34mrainbow/[0m[01;36mlink.txt[0m [01;34mrainbow/[0m[01mfile.txt[0m [01;34mrainbow/[0m[30;42msticky_ow[0m diff --git a/tests/test_color_ext0.out b/tests/test_color_ext0.out index 3bc8dc1..8710fc8 100644 --- a/tests/test_color_ext0.out +++ b/tests/test_color_ext0.out @@ -1,6 +1,7 @@ [01;34mrainbow[0m [01;34mrainbow/[0m[00mfile.txt[0m [01;34mrainbow/[0m[01;32mexec.sh[0m +[01;34mrainbow/[0m[01;35msocket[0m [01;34mrainbow/[0m[01;36mlink.txt[0m [01;34mrainbow/[0m[30;42msticky_ow[0m [01;34mrainbow/[0m[30;43msgid[0m diff --git a/tests/test_color_mh.out b/tests/test_color_mh.out index 600451e..32e2b95 100644 --- a/tests/test_color_mh.out +++ b/tests/test_color_mh.out @@ -1,5 +1,6 @@ [01;34mrainbow[0m [01;34mrainbow/[0m[01;32mexec.sh[0m +[01;34mrainbow/[0m[01;35msocket[0m [01;34mrainbow/[0m[01;36mlink.txt[0m [01;34mrainbow/[0m[01mmh1[0m [01;34mrainbow/[0m[01mmh2[0m diff --git a/tests/test_color_mh0.out b/tests/test_color_mh0.out index e267da8..40e09b4 100644 --- a/tests/test_color_mh0.out +++ b/tests/test_color_mh0.out @@ -1,5 +1,6 @@ [01;34mrainbow[0m [01;34mrainbow/[0m[01;32mexec.sh[0m +[01;34mrainbow/[0m[01;35msocket[0m [01;34mrainbow/[0m[01;36mlink.txt[0m [01;34mrainbow/[0m[30;42msticky_ow[0m [01;34mrainbow/[0m[30;43msgid[0m diff --git a/tests/test_color_mi.out b/tests/test_color_mi.out index e267da8..40e09b4 100644 --- a/tests/test_color_mi.out +++ b/tests/test_color_mi.out @@ -1,5 +1,6 @@ [01;34mrainbow[0m [01;34mrainbow/[0m[01;32mexec.sh[0m +[01;34mrainbow/[0m[01;35msocket[0m [01;34mrainbow/[0m[01;36mlink.txt[0m [01;34mrainbow/[0m[30;42msticky_ow[0m [01;34mrainbow/[0m[30;43msgid[0m diff --git a/tests/test_color_missing_colon.out b/tests/test_color_missing_colon.out index d2e2a70..f8c4d28 100644 --- a/tests/test_color_missing_colon.out +++ b/tests/test_color_missing_colon.out @@ -1,5 +1,6 @@ [01;34mrainbow[0m [01;34mrainbow/[0m[01;32mexec.sh[0m +[01;34mrainbow/[0m[01;35msocket[0m [01;34mrainbow/[0m[01;36mlink.txt[0m [01;34mrainbow/[0m[01mfile.txt[0m [01;34mrainbow/[0m[30;42msticky_ow[0m diff --git a/tests/test_color_or.out b/tests/test_color_or.out index 6e94bc6..e132546 100644 --- a/tests/test_color_or.out +++ b/tests/test_color_or.out @@ -1,5 +1,6 @@ [01;34mrainbow[0m [01;34mrainbow/[0m[01;32mexec.sh[0m +[01;34mrainbow/[0m[01;35msocket[0m [01;34mrainbow/[0m[01;36mlink.txt[0m [01;34mrainbow/[0m[01mbroken[0m [01;34mrainbow/[0m[30;42msticky_ow[0m diff --git a/tests/test_color_or0_mi.out b/tests/test_color_or0_mi.out index cafa798..8b92ded 100644 --- a/tests/test_color_or0_mi.out +++ b/tests/test_color_or0_mi.out @@ -1,5 +1,6 @@ [01;34mrainbow[0m [01;34mrainbow/[0m[01;32mexec.sh[0m +[01;34mrainbow/[0m[01;35msocket[0m [01;34mrainbow/[0m[01;36mbroken[0m [01;34mrainbow/[0m[01;36mlink.txt[0m [01;34mrainbow/[0m[30;42msticky_ow[0m diff --git a/tests/test_color_or_mi.out b/tests/test_color_or_mi.out index 7e57688..8a095a8 100644 --- a/tests/test_color_or_mi.out +++ b/tests/test_color_or_mi.out @@ -1,6 +1,7 @@ [01;34mrainbow[0m [01;34mrainbow/[0m[01;31mbroken[0m [01;34mrainbow/[0m[01;32mexec.sh[0m +[01;34mrainbow/[0m[01;35msocket[0m [01;34mrainbow/[0m[01;36mlink.txt[0m [01;34mrainbow/[0m[30;42msticky_ow[0m [01;34mrainbow/[0m[30;43msgid[0m diff --git a/tests/test_color_or_mi0.out b/tests/test_color_or_mi0.out index 7e57688..8a095a8 100644 --- a/tests/test_color_or_mi0.out +++ b/tests/test_color_or_mi0.out @@ -1,6 +1,7 @@ [01;34mrainbow[0m [01;34mrainbow/[0m[01;31mbroken[0m [01;34mrainbow/[0m[01;32mexec.sh[0m +[01;34mrainbow/[0m[01;35msocket[0m [01;34mrainbow/[0m[01;36mlink.txt[0m [01;34mrainbow/[0m[30;42msticky_ow[0m [01;34mrainbow/[0m[30;43msgid[0m diff --git a/tests/test_ok_closed_stdin.out b/tests/test_ok_closed_stdin.out new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/test_ok_closed_stdin.out diff --git a/tests/test_okdir_closed_stdin.out b/tests/test_okdir_closed_stdin.out new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/test_okdir_closed_stdin.out @@ -85,8 +85,6 @@ bool isopen(int fd) { } int redirect(int fd, const char *path, int flags, ...) { - close(fd); - mode_t mode = 0; if (flags & O_CREAT) { va_list args; @@ -102,11 +100,9 @@ int redirect(int fd, const char *path, int flags, ...) { int ret = open(path, flags, mode); if (ret >= 0 && ret != fd) { - int other = ret; - ret = dup2(other, fd); - if (close(other) != 0) { - ret = -1; - } + int orig = ret; + ret = dup2(orig, fd); + close(orig); } return ret; @@ -1,6 +1,6 @@ /**************************************************************************** * bfs * - * Copyright (C) 2016-2018 Tavian Barnes <tavianator@tavianator.com> * + * Copyright (C) 2016-2019 Tavian Barnes <tavianator@tavianator.com> * * * * Permission to use, copy, modify, and/or distribute this software for any * * purpose with or without fee is hereby granted. * @@ -27,33 +27,44 @@ // Some portability concerns +#ifdef __has_feature +# define BFS_HAS_FEATURE(feature, fallback) __has_feature(feature) +#else +# define BFS_HAS_FEATURE(feature, fallback) fallback +#endif + #ifdef __has_include # define BFS_HAS_INCLUDE(header, fallback) __has_include(header) #else # define BFS_HAS_INCLUDE(header, fallback) fallback #endif -#define BFS_HAS_MNTENT BFS_HAS_INCLUDE(<mntent.h>, __GLIBC__) -#define BFS_HAS_SYS_ACL BFS_HAS_INCLUDE(<sys/acl.h>, true) -#define BFS_HAS_SYS_CAPABILITY BFS_HAS_INCLUDE(<sys/capability.h>, __linux__) -#define BFS_HAS_SYS_MKDEV BFS_HAS_INCLUDE(<sys/mkdev.h>, false) -#define BFS_HAS_SYS_PARAM BFS_HAS_INCLUDE(<sys/param.h>, true) -#define BFS_HAS_SYS_SYSMACROS BFS_HAS_INCLUDE(<sys/sysmacros.h>, __GLIBC__) +#ifndef BFS_HAS_MNTENT +# define BFS_HAS_MNTENT BFS_HAS_INCLUDE(<mntent.h>, __GLIBC__) +#endif -#if !defined(FNM_CASEFOLD) && defined(FNM_IGNORECASE) -# define FNM_CASEFOLD FNM_IGNORECASE +#ifndef BFS_HAS_SYS_ACL +# define BFS_HAS_SYS_ACL BFS_HAS_INCLUDE(<sys/acl.h>, true) #endif -#ifndef S_ISDOOR -# define S_ISDOOR(mode) false +#ifndef BFS_HAS_SYS_CAPABILITY +# define BFS_HAS_SYS_CAPABILITY BFS_HAS_INCLUDE(<sys/capability.h>, __linux__) #endif -#ifndef S_ISPORT -# define S_ISPORT(mode) false +#ifndef BFS_HAS_SYS_MKDEV +# define BFS_HAS_SYS_MKDEV BFS_HAS_INCLUDE(<sys/mkdev.h>, false) #endif -#ifndef S_ISWHT -# define S_ISWHT(mode) false +#ifndef BFS_HAS_SYS_PARAM +# define BFS_HAS_SYS_PARAM BFS_HAS_INCLUDE(<sys/param.h>, true) +#endif + +#ifndef BFS_HAS_SYS_SYSMACROS +# define BFS_HAS_SYS_SYSMACROS BFS_HAS_INCLUDE(<sys/sysmacros.h>, __GLIBC__) +#endif + +#if !defined(FNM_CASEFOLD) && defined(FNM_IGNORECASE) +# define FNM_CASEFOLD FNM_IGNORECASE #endif #ifndef O_DIRECTORY |