From dbf435f4555ee3a1f2f7c929866f63dc5a48662b Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 31 Jan 2019 23:17:23 -0500 Subject: dstring: Initialize freshly-allocated strings Previously, a string allocated with dstralloc() had length 0 but no terminating NUL byte there. This was problematic if such a string was used without being modified. In particular, this was reproducible with bfs -ok by not typing any response to the prompt. In that case, uninitialized memory was being tested for a y/n response, with unpredictable results. --- dstring.c | 1 + 1 file changed, 1 insertion(+) 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; } -- cgit v1.2.3 From deef4028ecd70d5b0006b83da07d170c38efb3aa Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 31 Jan 2019 23:39:27 -0500 Subject: tests: Fail if bfs fails For tests that expect bfs to fail, the return value disambiguates whether bfs itself failed or whether the output was unexpected. --- tests.sh | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/tests.sh b/tests.sh index 5905d38..775a254 100755 --- a/tests.sh +++ b/tests.sh @@ -787,6 +787,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 +807,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 +902,7 @@ function test_depth_error() { chmod +r scratch/foo rm -rf scratch/* - return $ret + [ $ret -eq $EX_BFS ] } function test_name() { @@ -1046,10 +1058,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 +1722,7 @@ function test_printf_Y_error() { chmod +x scratch/foo rm -rf scratch/* - return $ret + [ $ret -eq $EX_BFS ] } function test_fstype() { -- cgit v1.2.3 From 185026706d926d1f94bd1c42a11dd83e6b8e74ec Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 31 Jan 2019 23:50:25 -0500 Subject: stat: Work around msan not knowing about statx() --- stat.c | 7 +++++++ util.h | 8 +++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/stat.c b/stat.c index 47585b8..d223ff8 100644 --- a/stat.c +++ b/stat.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -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/util.h b/util.h index d96b79d..6a51806 100644 --- a/util.h +++ b/util.h @@ -1,6 +1,6 @@ /**************************************************************************** * bfs * - * Copyright (C) 2016-2018 Tavian Barnes * + * Copyright (C) 2016-2019 Tavian Barnes * * * * Permission to use, copy, modify, and/or distribute this software for any * * purpose with or without fee is hereby granted. * @@ -27,6 +27,12 @@ // 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 -- cgit v1.2.3 From 33d320ab80aebb89dc63931684ef2e748b5fb91c Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 1 Feb 2019 11:01:42 -0500 Subject: util: Allow configuration macros to be specified with -D In case we need to override them at build time. --- posix1e.h | 2 +- util.h | 29 +++++++++++++++++++++++------ 2 files changed, 24 insertions(+), 7 deletions(-) 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 -#if BFS_HAS_SYS_CAPABILITY && !__FreeBSD__ +#if !defined(BFS_HAS_POSIX1E_CAPABILITIES) && BFS_HAS_SYS_CAPABILITY && !__FreeBSD__ # include # ifdef CAP_CHOWN # define BFS_HAS_POSIX1E_CAPABILITIES true diff --git a/util.h b/util.h index 6a51806..332fb79 100644 --- a/util.h +++ b/util.h @@ -39,12 +39,29 @@ # define BFS_HAS_INCLUDE(header, fallback) fallback #endif -#define BFS_HAS_MNTENT BFS_HAS_INCLUDE(, __GLIBC__) -#define BFS_HAS_SYS_ACL BFS_HAS_INCLUDE(, true) -#define BFS_HAS_SYS_CAPABILITY BFS_HAS_INCLUDE(, __linux__) -#define BFS_HAS_SYS_MKDEV BFS_HAS_INCLUDE(, false) -#define BFS_HAS_SYS_PARAM BFS_HAS_INCLUDE(, true) -#define BFS_HAS_SYS_SYSMACROS BFS_HAS_INCLUDE(, __GLIBC__) +#ifndef BFS_HAS_MNTENT +# define BFS_HAS_MNTENT BFS_HAS_INCLUDE(, __GLIBC__) +#endif + +#ifndef BFS_HAS_SYS_ACL +# define BFS_HAS_SYS_ACL BFS_HAS_INCLUDE(, true) +#endif + +#ifndef BFS_HAS_SYS_CAPABILITY +# define BFS_HAS_SYS_CAPABILITY BFS_HAS_INCLUDE(, __linux__) +#endif + +#ifndef BFS_HAS_SYS_MKDEV +# define BFS_HAS_SYS_MKDEV BFS_HAS_INCLUDE(, false) +#endif + +#ifndef BFS_HAS_SYS_PARAM +# define BFS_HAS_SYS_PARAM BFS_HAS_INCLUDE(, true) +#endif + +#ifndef BFS_HAS_SYS_SYSMACROS +# define BFS_HAS_SYS_SYSMACROS BFS_HAS_INCLUDE(, __GLIBC__) +#endif #if !defined(FNM_CASEFOLD) && defined(FNM_IGNORECASE) # define FNM_CASEFOLD FNM_IGNORECASE -- cgit v1.2.3 From 02132c2efb6daa213aa07b805ccb0c20241ca2f7 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 31 Jan 2019 23:43:19 -0500 Subject: Makefile: New distcheck target To catch more errors automatically, this new target runs the tests in multiple configurations, including various sanitizers and with/without optimization. --- .travis.yml | 35 +++++++++++------------------------ Makefile | 20 ++++++++++++++++---- 2 files changed, 27 insertions(+), 28 deletions(-) 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..2349727 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,18 +85,27 @@ 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 ./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 @@ -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) -- cgit v1.2.3 From aa5f0f5745e4e7392c078d1fe28825c941f52f7c Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 31 Jan 2019 23:46:56 -0500 Subject: main: Fix closed standard stream handling bfs >&- should complain about a missing file descriptor, rather than silently succeeding. --- main.c | 40 ++++++++++++++++++++++++--------------- tests.sh | 29 ++++++++++++++++++++++++++++ tests/test_closed_stdin.out | 19 +++++++++++++++++++ tests/test_ok_closed_stdin.out | 0 tests/test_okdir_closed_stdin.out | 0 util.c | 10 +++------- 6 files changed, 76 insertions(+), 22 deletions(-) create mode 100644 tests/test_closed_stdin.out create mode 100644 tests/test_ok_closed_stdin.out create mode 100644 tests/test_okdir_closed_stdin.out 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 * + * Copyright (C) 2015-2019 Tavian Barnes * * * * 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 /** - * 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/tests.sh b/tests.sh index 775a254..302a4c2 100755 --- a/tests.sh +++ b/tests.sh @@ -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 @@ -1929,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/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_ok_closed_stdin.out b/tests/test_ok_closed_stdin.out new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_okdir_closed_stdin.out b/tests/test_okdir_closed_stdin.out new file mode 100644 index 0000000..e69de29 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; -- cgit v1.2.3 From 6a5c4c1161291a55b35151562a2d86c0a7a06606 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 31 Jan 2019 23:56:06 -0500 Subject: eval: Fix wrong colors in error messages When reporting an error, we should try to stat the file first so the message can have the right colors. --- eval.c | 117 ++++++++++++++++++++++++++++++++++++++++------------------------- 1 file changed, 72 insertions(+), 45 deletions(-) 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,19 +107,82 @@ 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. */ @@ -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)); -- cgit v1.2.3 From 30012351f6cadc25bf800c86f1431eade4ee0af5 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 1 Feb 2019 00:00:08 -0500 Subject: tests: New utility for making and testing socket files --- .gitignore | 1 + Makefile | 4 +- tests.sh | 22 +++---- tests/mksock.c | 131 +++++++++++++++++++++++++++++++++++++ tests/test_color.out | 1 + tests/test_color_ext.out | 1 + tests/test_color_ext0.out | 1 + tests/test_color_mh.out | 1 + tests/test_color_mh0.out | 1 + tests/test_color_mi.out | 1 + tests/test_color_missing_colon.out | 1 + tests/test_color_or.out | 1 + tests/test_color_or0_mi.out | 1 + tests/test_color_or_mi.out | 1 + tests/test_color_or_mi0.out | 1 + 15 files changed, 156 insertions(+), 13 deletions(-) create mode 100644 tests/mksock.c 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/Makefile b/Makefile index 2349727..c1249fa 100644 --- a/Makefile +++ b/Makefile @@ -94,7 +94,7 @@ tests/mksock: tests/mksock.o %.o: %.c $(CC) $(ALL_CFLAGS) -c $< -o $@ -check: all +check: all tests/mksock ./tests.sh distcheck: @@ -107,7 +107,7 @@ endif +$(MAKE) -Bs check clean: - $(RM) bfs *.o *.d + $(RM) bfs *.[od] tests/mksock tests/*.[od] install: $(MKDIR) $(DESTDIR)$(PREFIX)/bin diff --git a/tests.sh b/tests.sh index 302a4c2..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 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 * + * * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * 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_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 -- cgit v1.2.3 From 58919515be142df8dd341d4814767701923f71c4 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 1 Feb 2019 00:00:59 -0500 Subject: parse: Remove the recommendation to check find -help or man find bfs has had a comprehensive help text and man page for a while now. --- parse.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) 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"); -- cgit v1.2.3 From 5d1c2af7d1ca2cf77e0be712ea1e20a3e7188a94 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 1 Feb 2019 00:02:31 -0500 Subject: opt: Apply data flow optimizations to more numeric ranges --- opt.c | 287 ++++++++++++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 208 insertions(+), 79 deletions(-) diff --git a/opt.c b/opt.c index 2ded162..b3abf37 100644 --- a/opt.c +++ b/opt.c @@ -26,46 +26,158 @@ 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; -- cgit v1.2.3 From 36330d7aab63ef2c202efb0d551f104ed6cefa17 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 1 Feb 2019 00:02:59 -0500 Subject: util: Remove some unused macros --- util.h | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/util.h b/util.h index 332fb79..4f70c1b 100644 --- a/util.h +++ b/util.h @@ -67,18 +67,6 @@ # define FNM_CASEFOLD FNM_IGNORECASE #endif -#ifndef S_ISDOOR -# define S_ISDOOR(mode) false -#endif - -#ifndef S_ISPORT -# define S_ISPORT(mode) false -#endif - -#ifndef S_ISWHT -# define S_ISWHT(mode) false -#endif - #ifndef O_DIRECTORY # define O_DIRECTORY 0 #endif -- cgit v1.2.3