summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTavian Barnes <tavianator@tavianator.com>2019-02-01 12:57:00 -0500
committerTavian Barnes <tavianator@tavianator.com>2019-02-01 12:57:00 -0500
commit3a52c0e1f55d5dd03a00740c191569b9309c0ad6 (patch)
tree65785e4e2e4ff1a0a5ed4ec2f46895d559333616
parent23b55d03ddf69b5ab40dc338c419089e8c64930b (diff)
parent36330d7aab63ef2c202efb0d551f104ed6cefa17 (diff)
downloadbfs-3a52c0e1f55d5dd03a00740c191569b9309c0ad6.tar.xz
Merge branch 'improvements'
-rw-r--r--.gitignore1
-rw-r--r--.travis.yml35
-rw-r--r--Makefile24
-rw-r--r--dstring.c1
-rw-r--r--eval.c117
-rw-r--r--main.c40
-rw-r--r--opt.c287
-rw-r--r--parse.c9
-rw-r--r--posix1e.h2
-rw-r--r--stat.c7
-rwxr-xr-xtests.sh71
-rw-r--r--tests/mksock.c131
-rw-r--r--tests/test_closed_stdin.out19
-rw-r--r--tests/test_color.out1
-rw-r--r--tests/test_color_ext.out1
-rw-r--r--tests/test_color_ext0.out1
-rw-r--r--tests/test_color_mh.out1
-rw-r--r--tests/test_color_mh0.out1
-rw-r--r--tests/test_color_mi.out1
-rw-r--r--tests/test_color_missing_colon.out1
-rw-r--r--tests/test_color_or.out1
-rw-r--r--tests/test_color_or0_mi.out1
-rw-r--r--tests/test_color_or_mi.out1
-rw-r--r--tests/test_color_or_mi0.out1
-rw-r--r--tests/test_ok_closed_stdin.out0
-rw-r--r--tests/test_okdir_closed_stdin.out0
-rw-r--r--util.c10
-rw-r--r--util.h41
28 files changed, 594 insertions, 212 deletions
diff --git a/.gitignore b/.gitignore
index b251086..99776af 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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
diff --git a/Makefile b/Makefile
index 05665f7..c1249fa 100644
--- a/Makefile
+++ b/Makefile
@@ -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)
diff --git a/dstring.c b/dstring.c
index 98678c0..f4a865a 100644
--- a/dstring.c
+++ b/dstring.c
@@ -43,6 +43,7 @@ char *dstralloc(size_t capacity) {
header->capacity = capacity;
header->length = 0;
+ header->data[0] = '\0';
return header->data;
}
diff --git a/eval.c b/eval.c
index 84e68c2..de57b9f 100644
--- a/eval.c
+++ b/eval.c
@@ -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));
diff --git a/main.c b/main.c
index 2734aaf..3c19092 100644
--- a/main.c
+++ b/main.c
@@ -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;
}
diff --git a/opt.c b/opt.c
index 2ded162..b3abf37 100644
--- a/opt.c
+++ b/opt.c
@@ -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;
diff --git a/parse.c b/parse.c
index ac0894a..8fe65b3 100644
--- a/parse.c
+++ b/parse.c
@@ -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");
diff --git a/posix1e.h b/posix1e.h
index ad6a9e9..8950498 100644
--- a/posix1e.h
+++ b/posix1e.h
@@ -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
diff --git a/stat.c b/stat.c
index 47585b8..d223ff8 100644
--- a/stat.c
+++ b/stat.c
@@ -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
diff --git a/tests.sh b/tests.sh
index 5905d38..57b19de 100755
--- a/tests.sh
+++ b/tests.sh
@@ -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 @@
rainbow
rainbow/exec.sh
+rainbow/socket
rainbow/link.txt
rainbow/sticky_ow
rainbow/sgid
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 @@
rainbow
rainbow/exec.sh
+rainbow/socket
rainbow/link.txt
rainbow/file.txt
rainbow/sticky_ow
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 @@
rainbow
rainbow/file.txt
rainbow/exec.sh
+rainbow/socket
rainbow/link.txt
rainbow/sticky_ow
rainbow/sgid
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 @@
rainbow
rainbow/exec.sh
+rainbow/socket
rainbow/link.txt
rainbow/mh1
rainbow/mh2
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 @@
rainbow
rainbow/exec.sh
+rainbow/socket
rainbow/link.txt
rainbow/sticky_ow
rainbow/sgid
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 @@
rainbow
rainbow/exec.sh
+rainbow/socket
rainbow/link.txt
rainbow/sticky_ow
rainbow/sgid
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 @@
rainbow
rainbow/exec.sh
+rainbow/socket
rainbow/link.txt
rainbow/file.txt
rainbow/sticky_ow
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 @@
rainbow
rainbow/exec.sh
+rainbow/socket
rainbow/link.txt
rainbow/broken
rainbow/sticky_ow
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 @@
rainbow
rainbow/exec.sh
+rainbow/socket
rainbow/broken
rainbow/link.txt
rainbow/sticky_ow
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 @@
rainbow
rainbow/broken
rainbow/exec.sh
+rainbow/socket
rainbow/link.txt
rainbow/sticky_ow
rainbow/sgid
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 @@
rainbow
rainbow/broken
rainbow/exec.sh
+rainbow/socket
rainbow/link.txt
rainbow/sticky_ow
rainbow/sgid
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
diff --git a/util.c b/util.c
index b708527..1e5ebdb 100644
--- a/util.c
+++ b/util.c
@@ -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;
diff --git a/util.h b/util.h
index d96b79d..4f70c1b 100644
--- a/util.h
+++ b/util.h
@@ -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