summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/codeql.yml2
-rwxr-xr-x.github/diag.sh2
-rw-r--r--LICENSE2
-rw-r--r--Makefile19
-rw-r--r--build/deps.mk2
-rwxr-xr-xbuild/embed.sh12
-rw-r--r--build/flags.mk107
-rw-r--r--build/has/builtin-riscv-pause.c7
-rw-r--r--build/header.mk21
-rwxr-xr-xbuild/msg-if.sh18
-rwxr-xr-xbuild/msg.sh2
-rwxr-xr-xbuild/pkgconf.sh18
-rw-r--r--build/pkgs.mk10
-rw-r--r--build/prelude.mk3
-rw-r--r--build/with/libacl.c (renamed from build/use/libacl.c)0
-rw-r--r--build/with/libcap.c (renamed from build/use/libcap.c)0
-rw-r--r--build/with/libselinux.c (renamed from build/use/libselinux.c)0
-rw-r--r--build/with/liburing.c (renamed from build/use/liburing.c)0
-rw-r--r--build/with/oniguruma.c (renamed from build/use/oniguruma.c)0
-rwxr-xr-xconfigure153
-rw-r--r--docs/BUILDING.md14
-rw-r--r--docs/CHANGELOG.md44
-rw-r--r--docs/RELATED.md5
-rw-r--r--docs/SECURITY.md126
-rw-r--r--docs/bfs.114
-rw-r--r--src/atomic.h2
-rw-r--r--src/bar.c9
-rw-r--r--src/bfstd.c39
-rw-r--r--src/bfstd.h29
-rw-r--r--src/bftw.c45
-rw-r--r--src/bftw.h2
-rw-r--r--src/bit.h100
-rw-r--r--src/color.c14
-rw-r--r--src/ctx.c5
-rw-r--r--src/ctx.h2
-rw-r--r--src/diag.c2
-rw-r--r--src/eval.c72
-rw-r--r--src/exec.c2
-rw-r--r--src/expr.h6
-rw-r--r--src/fsade.h4
-rw-r--r--src/ioq.c26
-rw-r--r--src/list.h24
-rw-r--r--src/main.c1
-rw-r--r--src/opt.c173
-rw-r--r--src/parse.c261
-rw-r--r--src/prelude.h13
-rw-r--r--src/printf.c43
-rw-r--r--src/sighook.c55
-rw-r--r--src/stat.c2
-rw-r--r--src/trie.c2
-rw-r--r--src/typo.c2
-rw-r--r--src/version.c32
-rw-r--r--src/xregex.c63
-rw-r--r--src/xregex.h4
-rw-r--r--src/xspawn.c13
-rw-r--r--src/xtime.c18
-rw-r--r--tests/bfs/Dmulti.out19
-rw-r--r--tests/bfs/Dmulti.sh1
-rw-r--r--tests/bfs/LD_stat.out17
-rw-r--r--tests/bfs/LD_stat.sh1
-rw-r--r--tests/bfs/LDstat.out17
-rw-r--r--tests/bfs/LDstat.sh1
-rw-r--r--tests/bfs/O_3.sh1
-rw-r--r--tests/bfs/Sbfs.out19
-rw-r--r--tests/bfs/Sbfs.sh2
-rw-r--r--tests/bfs/color_ext_case.sh10
-rw-r--r--tests/bfs/color_ext_override.sh2
-rw-r--r--tests/bfs/nohidden.out8
-rw-r--r--tests/bfs/nohidden_depth.out8
-rw-r--r--tests/bfs/xtype_depth.sh2
-rw-r--r--tests/bfs/xtype_reorder.sh3
-rw-r--r--tests/bit.c36
-rw-r--r--tests/bsd/Hf.out1
-rw-r--r--tests/bsd/Hf.sh1
-rw-r--r--tests/bsd/X.out4
-rw-r--r--tests/bsd/perm_000_plus.out16
-rw-r--r--tests/bsd/perm_222_plus.out9
-rw-r--r--tests/bsd/perm_644_plus.out14
-rw-r--r--tests/bsd/printx.out8
-rw-r--r--tests/bsd/s.out4
-rw-r--r--tests/color.sh116
-rw-r--r--tests/common/HLP.out1
-rw-r--r--tests/common/HLP.sh1
-rw-r--r--tests/common/amin.out6
-rw-r--r--tests/common/amin.sh15
-rw-r--r--tests/common/atime.out6
-rw-r--r--tests/common/atime.sh15
-rw-r--r--tests/common/empty_error.out4
-rw-r--r--tests/common/empty_error.sh8
-rw-r--r--tests/common/mmin.out6
-rw-r--r--tests/common/mmin.sh15
-rw-r--r--tests/common/mtime.out6
-rw-r--r--tests/common/mtime.sh15
-rw-r--r--tests/gnu/executable.out8
-rw-r--r--tests/gnu/files0_from_file.out12
-rw-r--r--tests/gnu/files0_from_stdin.out12
-rw-r--r--tests/gnu/follow_comma.out8
-rw-r--r--tests/gnu/ignore_readdir_race_loop.out11
-rw-r--r--tests/gnu/ignore_readdir_race_loop.sh2
-rw-r--r--tests/gnu/perm_000_slash.out16
-rw-r--r--tests/gnu/perm_222_slash.out9
-rw-r--r--tests/gnu/perm_644_slash.out14
-rw-r--r--tests/gnu/perm_leading_plus_symbolic_slash.out14
-rw-r--r--tests/gnu/perm_symbolic_slash.out14
-rw-r--r--tests/gnu/readable.out10
-rw-r--r--tests/gnu/regextype_awk.out2
-rw-r--r--tests/gnu/regextype_awk.sh3
-rw-r--r--tests/gnu/regextype_egrep.out0
-rw-r--r--tests/gnu/regextype_egrep.sh3
-rw-r--r--tests/gnu/regextype_emacs.sh2
-rw-r--r--tests/gnu/regextype_findutils_default.out3
-rw-r--r--tests/gnu/regextype_findutils_default.sh3
-rw-r--r--tests/gnu/regextype_gnu_awk.out2
-rw-r--r--tests/gnu/regextype_gnu_awk.sh3
-rw-r--r--tests/gnu/regextype_posix_awk.out2
-rw-r--r--tests/gnu/regextype_posix_awk.sh3
-rw-r--r--tests/gnu/regextype_posix_minimal_basic.out1
-rw-r--r--tests/gnu/regextype_posix_minimal_basic.sh2
-rw-r--r--tests/gnu/used.sh31
-rw-r--r--tests/gnu/writable.out9
-rw-r--r--tests/gnu/xtype_l_loops.out3
-rw-r--r--tests/gnu/xtype_l_loops.sh1
-rw-r--r--tests/list.c97
-rw-r--r--tests/main.c1
-rw-r--r--tests/posix/HL.out17
-rw-r--r--tests/posix/HL.sh1
-rw-r--r--tests/posix/LH.out1
-rw-r--r--tests/posix/LH.sh1
-rw-r--r--tests/posix/depth_error.out6
-rw-r--r--tests/posix/depth_error.sh8
-rw-r--r--tests/posix/perm_000.out2
-rw-r--r--tests/posix/perm_000_minus.out16
-rw-r--r--tests/posix/perm_222.out2
-rw-r--r--tests/posix/perm_222_minus.out2
-rw-r--r--tests/posix/perm_644.out2
-rw-r--r--tests/posix/perm_644_minus.out5
-rw-r--r--tests/posix/perm_symbolic_minus.out5
-rw-r--r--tests/posix/permcopy.out2
-rw-r--r--tests/posix/prune_error.out1
-rw-r--r--tests/posix/prune_error.sh1
-rw-r--r--tests/run.sh96
-rw-r--r--tests/stddirs.sh25
-rw-r--r--tests/tests.h3
-rw-r--r--tests/trie.c4
-rw-r--r--tests/util.sh9
-rw-r--r--tests/xspawn.c2
146 files changed, 1874 insertions, 702 deletions
diff --git a/.github/codeql.yml b/.github/codeql.yml
index 6ff8337..b61c30e 100644
--- a/.github/codeql.yml
+++ b/.github/codeql.yml
@@ -2,6 +2,8 @@ query-filters:
- exclude:
id: cpp/commented-out-code
- exclude:
+ id: cpp/include-non-header
+ - exclude:
id: cpp/long-switch
- exclude:
id: cpp/loop-variable-changed
diff --git a/.github/diag.sh b/.github/diag.sh
index 942487a..fe78be8 100755
--- a/.github/diag.sh
+++ b/.github/diag.sh
@@ -13,4 +13,4 @@ filter() {
\1/'
}
-"$@" 2> >(filter >&2) | filter
+exec "$@" > >(filter) 2> >(filter >&2)
diff --git a/LICENSE b/LICENSE
index 240d2ac..9fd4f17 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright © 2015-2023 Tavian Barnes <tavianator@tavianator.com> and the bfs contributors
+Copyright © 2015-2024 Tavian Barnes <tavianator@tavianator.com> and the bfs contributors
Permission to use, copy, modify, and/or distribute this software for any purpose with or
without fee is hereby granted.
diff --git a/Makefile b/Makefile
index f33a604..d21cbb3 100644
--- a/Makefile
+++ b/Makefile
@@ -66,22 +66,20 @@ ${OBJS}: gen/config.mk
${MSG} "[ CC ] ${CSRC}" ${CC} ${_CPPFLAGS} ${_CFLAGS} -c ${CSRC} -o $@
# Save the version number to this file, but only update version.c if it changes
-gen/version.c.new::
+gen/version.i.new::
@${MKDIR} ${@D}
- @printf 'const char bfs_version[] = "' >$@
@if [ "$$VERSION" ]; then \
printf '%s' "$$VERSION"; \
elif test -e src/../.git && command -v git >/dev/null 2>&1; then \
git -C src/.. describe --always --dirty; \
else \
- echo "3.2"; \
- fi | tr -d '\n' >>$@
- @printf '";\n' >>$@
+ echo "3.3.1"; \
+ fi | tr -d '\n' | build/embed.sh >$@
-gen/version.c: gen/version.c.new
- @test -e $@ && cmp -s $@ ${.ALLSRC} && rm ${.ALLSRC} || mv ${.ALLSRC} $@
+gen/version.i: gen/version.i.new
+ @test -e $@ && cmp -s $@ ${.ALLSRC} && ${RM} ${.ALLSRC} || mv ${.ALLSRC} $@
-obj/gen/version.o: gen/version.c
+obj/src/version.o: gen/version.i
## Test phase (`make check`)
@@ -217,6 +215,11 @@ check-install::
bin/bfs pkg -not -type d -print -exit 1
${RM} -r pkg
+# Check man page markup
+check-man::
+ ${MSG} "[LINT] docs/bfs.1" \
+ groff -man -rCHECKSTYLE=3 -ww -b -z docs/bfs.1
+
## Cleanup (`make clean`)
# Clean all build products
diff --git a/build/deps.mk b/build/deps.mk
index 3db62b6..d382f5d 100644
--- a/build/deps.mk
+++ b/build/deps.mk
@@ -12,7 +12,7 @@ gen/deps.mk::
${MSG} "[ GEN] $@"
@printf '# %s\n' "$@" >$@
@if build/cc.sh -MD -MP -MF /dev/null build/empty.c; then \
- printf 'CPPFLAGS += -MD -MP\n'; \
+ printf '_CPPFLAGS += -MD -MP\n'; \
fi >>$@ 2>$@.log
${VCAT} $@
@printf -- '-include %s\n' ${OBJS:.o=.d} >>$@
diff --git a/build/embed.sh b/build/embed.sh
new file mode 100755
index 0000000..8d7d0f1
--- /dev/null
+++ b/build/embed.sh
@@ -0,0 +1,12 @@
+#!/bin/sh
+
+# Copyright © Tavian Barnes <tavianator@tavianator.com>
+# SPDX-License-Identifier: 0BSD
+
+# Convert data into a C array like #embed
+
+set -eu
+
+{ cat; printf '\0'; } \
+ | od -An -tx1 \
+ | sed 's/\([^ ][^ ]*\)/0x\1,/g'
diff --git a/build/flags.mk b/build/flags.mk
index fa793b8..c50b077 100644
--- a/build/flags.mk
+++ b/build/flags.mk
@@ -6,28 +6,8 @@
include build/prelude.mk
include gen/vars.mk
-# Configurable flags
-CPPFLAGS ?=
-CFLAGS ?= \
- -g \
- -Wall \
- -Wformat=2 \
- -Werror=implicit \
- -Wimplicit-fallthrough \
- -Wmissing-declarations \
- -Wshadow \
- -Wsign-compare \
- -Wstrict-prototypes
-LDFLAGS ?=
-LDLIBS ?=
-
-export XCPPFLAGS=${CPPFLAGS}
-export XCFLAGS=${CFLAGS}
-export XLDFLAGS=${LDFLAGS}
-export XLDLIBS=${LDLIBS}
-
-# Immutable flags
-export BFS_CPPFLAGS= \
+# Internal flags
+_CPPFLAGS := \
-Isrc \
-Igen \
-D__EXTENSIONS__ \
@@ -39,14 +19,17 @@ export BFS_CPPFLAGS= \
-D_POSIX_PTHREAD_SEMANTICS \
-D_FILE_OFFSET_BITS=64 \
-D_TIME_BITS=64
-export BFS_CFLAGS= -std=c17 -pthread
+
+_CFLAGS := -std=c17 -pthread
+_LDFLAGS :=
+_LDLIBS :=
# Platform-specific system libraries
LDLIBS,DragonFly := -lposix1e
LDLIBS,Linux := -lrt
LDLIBS,NetBSD := -lutil
LDLIBS,SunOS := -lsec -lsocket -lnsl
-export BFS_LDLIBS=${LDLIBS,${OS}}
+_LDLIBS += ${LDLIBS,${OS}}
# Build profiles
_ASAN := ${TRUTHY,${ASAN}}
@@ -60,7 +43,7 @@ _RELEASE := ${TRUTHY,${RELEASE}}
# https://github.com/google/sanitizers/issues/342
TSAN_CPPFLAGS,y := -DBFS_USE_TARGET_CLONES=0
-export TSAN_CPPFLAGS=${TSAN_CPPFLAGS,${_TSAN}}
+_CPPFLAGS += ${TSAN_CPPFLAGS,${_TSAN}}
ASAN_CFLAGS,y := -fsanitize=address
LSAN_CFLAGS,y := -fsanitize=leak
@@ -68,69 +51,65 @@ MSAN_CFLAGS,y := -fsanitize=memory -fsanitize-memory-track-origins
TSAN_CFLAGS,y := -fsanitize=thread
UBSAN_CFLAGS,y := -fsanitize=undefined
-export ASAN_CFLAGS=${ASAN_CFLAGS,${_ASAN}}
-export LSAN_CFLAGS=${LSAN_CFLAGS,${_LSAN}}
-export MSAN_CFLAGS=${MSAN_CFLAGS,${_MSAN}}
-export TSAN_CFLAGS=${TSAN_CFLAGS,${_TSAN}}
-export UBSAN_CFLAGS=${UBSAN_CFLAGS,${_UBSAN}}
+_CFLAGS += ${ASAN_CFLAGS,${_ASAN}}
+_CFLAGS += ${LSAN_CFLAGS,${_LSAN}}
+_CFLAGS += ${MSAN_CFLAGS,${_MSAN}}
+_CFLAGS += ${TSAN_CFLAGS,${_TSAN}}
+_CFLAGS += ${UBSAN_CFLAGS,${_UBSAN}}
SAN_CFLAGS,y := -fno-sanitize-recover=all
INSANE := ${NOT,${_ASAN}${_LSAN}${_MSAN}${_TSAN}${_UBSAN}}
SAN := ${NOT,${INSANE}}
-export SAN_CFLAGS=${SAN_CFLAGS,${SAN}}
+_CFLAGS += ${SAN_CFLAGS,${SAN}}
# MSAN and TSAN both need all code to be instrumented
YESLIBS := ${NOT,${_MSAN}${_TSAN}}
NOLIBS ?= ${NOT,${YESLIBS}}
-export XNOLIBS=${NOLIBS}
# gcov only intercepts fork()/exec() with -std=gnu*
GCOV_CFLAGS,y := -std=gnu17 --coverage
-export GCOV_CFLAGS=${GCOV_CFLAGS,${_GCOV}}
+_CFLAGS += ${GCOV_CFLAGS,${_GCOV}}
LINT_CPPFLAGS,y := -D_FORTIFY_SOURCE=3 -DBFS_LINT
LINT_CFLAGS,y := -Werror -O2
-export LINT_CPPFLAGS=${LINT_CPPFLAGS,${_LINT}}
-export LINT_CFLAGS=${LINT_CFLAGS,${_LINT}}
+_CPPFLAGS += ${LINT_CPPFLAGS,${_LINT}}
+_CFLAGS += ${LINT_CFLAGS,${_LINT}}
RELEASE_CPPFLAGS,y := -DNDEBUG
RELEASE_CFLAGS,y := -O3 -flto=auto
-export RELEASE_CPPFLAGS=${RELEASE_CPPFLAGS,${_RELEASE}}
-export RELEASE_CFLAGS=${RELEASE_CFLAGS,${_RELEASE}}
+_CPPFLAGS += ${RELEASE_CPPFLAGS,${_RELEASE}}
+_CFLAGS += ${RELEASE_CFLAGS,${_RELEASE}}
+
+# Configurable flags
+CFLAGS ?= \
+ -g \
+ -Wall \
+ -Wformat=2 \
+ -Werror=implicit \
+ -Wimplicit-fallthrough \
+ -Wmissing-declarations \
+ -Wshadow \
+ -Wsign-compare \
+ -Wstrict-prototypes
-# Set a variable
-SETVAR = @printf '%s := %s\n' >>$@
+# Add the configurable flags last so they can override ours
+_CPPFLAGS += ${CPPFLAGS} ${EXTRA_CPPFLAGS}
+_CFLAGS += ${CFLAGS} ${EXTRA_CFLAGS}
+_LDFLAGS += ${LDFLAGS} ${EXTRA_LDFLAGS}
+# (except LDLIBS, as earlier libs override later ones)
+_LDLIBS := ${LDLIBS} ${EXTRA_LDLIBS} ${_LDLIBS}
-# Append to a variable, if non-empty
-APPEND = @append() { test -z "$$2" || printf '%s += %s\n' "$$1" "$$2" >>$@; }; append
+include build/exports.mk
gen/flags.mk::
${MSG} "[ GEN] $@"
@printf '# %s\n' "$@" >$@
- ${SETVAR} _CPPFLAGS "$$BFS_CPPFLAGS"
- ${APPEND} _CPPFLAGS "$$TSAN_CPPFLAGS"
- ${APPEND} _CPPFLAGS "$$LINT_CPPFLAGS"
- ${APPEND} _CPPFLAGS "$$RELEASE_CPPFLAGS"
- ${APPEND} _CPPFLAGS "$$XCPPFLAGS"
- ${APPEND} _CPPFLAGS "$$EXTRA_CPPFLAGS"
- ${SETVAR} _CFLAGS "$$BFS_CFLAGS"
- ${APPEND} _CFLAGS "$$ASAN_CFLAGS"
- ${APPEND} _CFLAGS "$$LSAN_CFLAGS"
- ${APPEND} _CFLAGS "$$MSAN_CFLAGS"
- ${APPEND} _CFLAGS "$$TSAN_CFLAGS"
- ${APPEND} _CFLAGS "$$UBSAN_CFLAGS"
- ${APPEND} _CFLAGS "$$SAN_CFLAGS"
- ${APPEND} _CFLAGS "$$GCOV_CFLAGS"
- ${APPEND} _CFLAGS "$$LINT_CFLAGS"
- ${APPEND} _CFLAGS "$$RELEASE_CFLAGS"
- ${APPEND} _CFLAGS "$$XCFLAGS"
- ${APPEND} _CFLAGS "$$EXTRA_CFLAGS"
- ${SETVAR} _LDFLAGS "$$XLDFLAGS"
- ${SETVAR} _LDLIBS "$$XLDLIBS"
- ${APPEND} _LDLIBS "$$EXTRA_LDLIBS"
- ${APPEND} _LDLIBS "$$BFS_LDLIBS"
- ${SETVAR} NOLIBS "$$XNOLIBS"
+ @printf '_CPPFLAGS := %s\n' "$$XCPPFLAGS" >>$@
+ @printf '_CFLAGS := %s\n' "$$XCFLAGS" >>$@
+ @printf '_LDFLAGS := %s\n' "$$XLDFLAGS" >>$@
+ @printf '_LDLIBS := %s\n' "$$XLDLIBS" >>$@
+ @printf 'NOLIBS := %s\n' "$$XNOLIBS" >>$@
@test "${OS}-${SAN}" != FreeBSD-y || printf 'POSTLINK = elfctl -e +noaslr $$@\n' >>$@
${VCAT} $@
diff --git a/build/has/builtin-riscv-pause.c b/build/has/builtin-riscv-pause.c
new file mode 100644
index 0000000..24b0675
--- /dev/null
+++ b/build/has/builtin-riscv-pause.c
@@ -0,0 +1,7 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+int main(void) {
+ __builtin_riscv_pause();
+ return 0;
+}
diff --git a/build/header.mk b/build/header.mk
index fb8246d..632afb6 100644
--- a/build/header.mk
+++ b/build/header.mk
@@ -16,6 +16,7 @@ HEADERS := \
gen/has/acl-is-trivial-np.h \
gen/has/acl-trivial.h \
gen/has/aligned-alloc.h \
+ gen/has/builtin-riscv-pause.h \
gen/has/confstr.h \
gen/has/extattr-get-file.h \
gen/has/extattr-get-link.h \
@@ -51,7 +52,7 @@ HEADERS := \
gen/has/uselocale.h
# Previously generated by pkgs.mk
-PKG_HEADERS := ${ALL_PKGS:%=gen/use/%.h}
+PKG_HEADERS := ${ALL_PKGS:%=gen/with/%.h}
gen/config.h: ${PKG_HEADERS} ${HEADERS}
${MSG} "[ GEN] $@"
@@ -60,14 +61,28 @@ gen/config.h: ${PKG_HEADERS} ${HEADERS}
@printf '#define BFS_CONFIG_H\n' >>$@
@cat ${.ALLSRC} >>$@
@printf '#endif // BFS_CONFIG_H\n' >>$@
- @cat ${.ALLSRC:%=%.log} >gen/config.log
+ @cat gen/cc.log ${.ALLSRC:%=%.log} >gen/config.log
${VCAT} $@
+ @printf '%s' "$$CONFFLAGS" | build/embed.sh >gen/confflags.i
+ @printf '%s' "$$XCC" | build/embed.sh >gen/cc.i
+ @printf '%s' "$$XCPPFLAGS" | build/embed.sh >gen/cppflags.i
+ @printf '%s' "$$XCFLAGS" | build/embed.sh >gen/cflags.i
+ @printf '%s' "$$XLDFLAGS" | build/embed.sh >gen/ldflags.i
+ @printf '%s' "$$XLDLIBS" | build/embed.sh >gen/ldlibs.i
.PHONY: gen/config.h
# The short name of the config test
SLUG = ${@:gen/%.h=%}
-${HEADERS}::
+${HEADERS}: cc
@${MKDIR} ${@D}
@build/define-if.sh ${SLUG} build/cc.sh build/${SLUG}.c >$@ 2>$@.log; \
build/msg-if.sh "[ CC ] ${SLUG}.c" test $$? -eq 0
+.PHONY: ${HEADERS}
+
+# Check that the C compiler works at all
+cc::
+ @build/cc.sh build/empty.c 2>gen/cc.log; \
+ ret=$$?; \
+ build/msg-if.sh "[ CC ] build/empty.c" test $$ret -eq 0; \
+ exit $$ret
diff --git a/build/msg-if.sh b/build/msg-if.sh
index 8112aea..afb478c 100755
--- a/build/msg-if.sh
+++ b/build/msg-if.sh
@@ -6,16 +6,26 @@
# Print a success/failure indicator from a makefile:
#
# $ ./configure
-# [ CC ] use/liburing.c ✘
-# [ CC ] use/oniguruma.c ✔
+# [ CC ] with/liburing.c ✘
+# [ CC ] with/oniguruma.c ✔
set -eu
MSG="$1"
shift
+if [ -z "${NO_COLOR:-}" ] && [ -t 1 ]; then
+ Y='\033[1;32m✔\033[0m'
+ N='\033[1;31m✘\033[0m'
+else
+ Y='✔'
+ N='✘'
+fi
+
if "$@"; then
- build/msg.sh "$(printf '%-37s ✔' "$MSG")"
+ YN="$Y"
else
- build/msg.sh "$(printf '%-37s ✘' "$MSG")"
+ YN="$N"
fi
+
+build/msg.sh "$(printf "%-37s $YN" "$MSG")"
diff --git a/build/msg.sh b/build/msg.sh
index a7da31b..2249125 100755
--- a/build/msg.sh
+++ b/build/msg.sh
@@ -59,4 +59,4 @@ if is_loud; then
printf '%s\n' "$*"
fi
-"$@"
+exec "$@"
diff --git a/build/pkgconf.sh b/build/pkgconf.sh
index 96e4bf1..244c95d 100755
--- a/build/pkgconf.sh
+++ b/build/pkgconf.sh
@@ -26,22 +26,18 @@ esac
if [ -z "$MODE" ]; then
# Check whether the libraries exist at all
for LIB; do
- # Check ${USE_$LIB}
- USE_LIB="USE_$(printf '%s' "$LIB" | tr 'a-z-' 'A-Z_')"
- eval "USE=\"\${$USE_LIB:-}\""
- case "$USE" in
- y|1)
- continue
- ;;
- n|0)
- exit 1
- ;;
+ # Check ${WITH_$LIB}
+ WITH_LIB="WITH_$(printf '%s' "$LIB" | tr 'a-z-' 'A-Z_')"
+ eval "WITH=\"\${$WITH_LIB:-}\""
+ case "$WITH" in
+ y|1) continue ;;
+ n|0) exit 1 ;;
esac
CFLAGS=$("$0" --cflags "$LIB") || exit 1
LDFLAGS=$("$0" --ldflags "$LIB") || exit 1
LDLIBS=$("$0" --ldlibs "$LIB") || exit 1
- build/cc.sh $CFLAGS $LDFLAGS build/use/$LIB.c $LDLIBS || exit 1
+ build/cc.sh $CFLAGS $LDFLAGS build/with/$LIB.c $LDLIBS || exit 1
done
fi
diff --git a/build/pkgs.mk b/build/pkgs.mk
index 5a26548..5de9ac2 100644
--- a/build/pkgs.mk
+++ b/build/pkgs.mk
@@ -8,7 +8,7 @@ include gen/vars.mk
include gen/flags.mk
include build/exports.mk
-HEADERS := ${ALL_PKGS:%=gen/use/%.h}
+HEADERS := ${ALL_PKGS:%=gen/with/%.h}
gen/pkgs.mk: ${HEADERS}
${MSG} "[ GEN] $@"
@@ -24,10 +24,10 @@ gen/pkgs.mk: ${HEADERS}
.PHONY: gen/pkgs.mk
-# Convert gen/use/foo.h to foo
-PKG = ${@:gen/use/%.h=%}
+# Convert gen/with/foo.h to foo
+PKG = ${@:gen/with/%.h=%}
${HEADERS}::
@${MKDIR} ${@D}
- @build/define-if.sh use/${PKG} build/pkgconf.sh ${PKG} >$@ 2>$@.log; \
- build/msg-if.sh "[ CC ] use/${PKG}.c" test $$? -eq 0;
+ @build/define-if.sh with/${PKG} build/pkgconf.sh ${PKG} >$@ 2>$@.log; \
+ build/msg-if.sh "[ CC ] with/${PKG}.c" test $$? -eq 0;
diff --git a/build/prelude.mk b/build/prelude.mk
index d0968ea..76fbce8 100644
--- a/build/prelude.mk
+++ b/build/prelude.mk
@@ -97,10 +97,10 @@ LIBBFS := \
obj/src/thread.o \
obj/src/trie.o \
obj/src/typo.o \
+ obj/src/version.o \
obj/src/xregex.o \
obj/src/xspawn.o \
obj/src/xtime.o \
- obj/gen/version.o
# Unit test objects
UNIT_OBJS := \
@@ -108,6 +108,7 @@ UNIT_OBJS := \
obj/tests/bfstd.o \
obj/tests/bit.o \
obj/tests/ioq.o \
+ obj/tests/list.o \
obj/tests/main.o \
obj/tests/sighook.o \
obj/tests/trie.o \
diff --git a/build/use/libacl.c b/build/with/libacl.c
index de1fe50..de1fe50 100644
--- a/build/use/libacl.c
+++ b/build/with/libacl.c
diff --git a/build/use/libcap.c b/build/with/libcap.c
index 58e832c..58e832c 100644
--- a/build/use/libcap.c
+++ b/build/with/libcap.c
diff --git a/build/use/libselinux.c b/build/with/libselinux.c
index bca409d..bca409d 100644
--- a/build/use/libselinux.c
+++ b/build/with/libselinux.c
diff --git a/build/use/liburing.c b/build/with/liburing.c
index bea499a..bea499a 100644
--- a/build/use/liburing.c
+++ b/build/with/liburing.c
diff --git a/build/use/oniguruma.c b/build/with/oniguruma.c
index cb17596..cb17596 100644
--- a/build/use/oniguruma.c
+++ b/build/with/oniguruma.c
diff --git a/configure b/configure
index d42dec3..8cdb7d8 100755
--- a/configure
+++ b/configure
@@ -7,23 +7,12 @@
set -eu
-# Default to `make`
-MAKE="${MAKE:-make}"
-
-# Pass -j$(nproc) unless MAKEFLAGS is set
-if [ "${MAKEFLAGS+y}" ]; then
- j=""
-else
- j="-j$({ nproc || sysctl -n hw.ncpu || getconf _NPROCESSORS_ONLN || echo 1; } 2>/dev/null)"
-fi
-
-for arg; do
- case "$arg" in
- -h|--help)
- cat <<EOF
+# Print the help message()
+help() {
+ cat <<EOF
Usage:
- \$ $0 [--enable-*|--disable-*] [CC=...] [CFLAGS=...] [...]
+ \$ $0 [--enable-*|--disable-*] [--with-*|--without-*] [CC=...] [...]
\$ $MAKE $j
Variables set in the environment or on the command line will be picked up:
@@ -53,72 +42,148 @@ The default flags result in a plain debug build. Other build profiles include:
--enable-gcov
Enable code coverage instrumentation
-External dependencies are auto-detected by default, but you can --enable or
---disable them manually:
+External dependencies are auto-detected by default, but you can build --with or
+--without them explicitly:
- --enable-libacl --disable-libacl
- --enable-libcap --disable-libcap
- --enable-libselinux --disable-libselinux
- --enable-liburing --disable-liburing
- --enable-oniguruma --disable-oniguruma
+ --with-libacl --without-libacl
+ --with-libcap --without-libcap
+ --with-libselinux --without-libselinux
+ --with-liburing --without-liburing
+ --with-oniguruma --without-oniguruma
Packaging:
--prefix=/path
Set the installation prefix (default: /usr)
+ --mandir=/path
+ Set the man page directory (default: \$PREFIX/share/man)
This script is a thin wrapper around a makefile-based configuration system.
Any other arguments will be passed directly to the $MAKE invocation, e.g.
\$ $0 $j V=1
EOF
- exit 0
+}
+
+# Report an argument parsing error
+invalid() {
+ printf 'error: Unrecognized option "%s"\n\n' "$1" >&2
+ printf 'Run %s --help for more information.\n' "$0" >&2
+ exit 1
+}
+
+# Get the number of cores to use
+nproc() {
+ {
+ command nproc \
+ || sysctl -n hw.ncpu \
+ || getconf _NPROCESSORS_ONLN \
+ || echo 1
+ } 2>/dev/null
+}
+
+# Save the ./configure command line for bfs --version
+export CONFFLAGS="$*"
+
+# Default to `make`
+MAKE="${MAKE-make}"
+
+# Parse the command-line arguments
+for arg; do
+ shift
+
+ # --[(enable|disable|with|without)-]$name[=$value]
+ value="${arg#*=}"
+ name="${arg%%=*}"
+ name="${name#--}"
+ case "$arg" in
+ --enable-*|--disable-*|--with-*|--without-*)
+ name="${name#*-}"
;;
+ esac
+ NAME=$(printf '%s' "$name" | tr 'a-z-' 'A-Z_')
- --enable-*|--disable-*)
+ # y/n modality
+ case "$arg" in
+ --enable-*|--with-*)
case "$arg" in
- --enable-*) yn=y ;;
- --disable-*) yn=n ;;
+ *=y|*=yes) yn=y ;;
+ *=n|*=no) yn=n ;;
+ *=*) invalid "$arg" ;;
+ *) yn=y ;;
esac
+ ;;
+ --disable-*|--without-*)
+ case "$arg" in
+ *=*) invalid "arg" ;;
+ *) yn=n ;;
+ esac
+ ;;
+ esac
- name="${arg#--*able-}"
- NAME=$(printf '%s' "$name" | tr 'a-z-' 'A-Z_')
+ # Fix up --enable-lib* to --with-lib*
+ case "$arg" in
+ --enable-*|--disable-*)
case "$name" in
libacl|libcap|libselinux|liburing|oniguruma)
- shift
- set -- "$@" "USE_$NAME=$yn"
+ old="$arg"
+ case "$arg" in
+ --enable-*) arg="--with-${arg#--*-}" ;;
+ --disable-*) arg="--without-${arg#--*-}" ;;
+ esac
+ printf 'warning: Treating "%s" like "%s"\n' "$old" "$arg" >&2
;;
+ esac
+ ;;
+ esac
+
+ case "$arg" in
+ -h|--help)
+ help
+ exit 0
+ ;;
+
+ --enable-*|--disable-*)
+ case "$name" in
release|asan|lsan|msan|tsan|ubsan|lint|gcov)
- shift
set -- "$@" "$NAME=$yn"
;;
*)
- printf 'error: Unrecognized option "%s"\n\n' "$arg" >&2
- printf 'Run %s --help for more information.\n' "$0" >&2
- exit 1
+ invalid "$arg"
;;
esac
;;
- --prefix=*)
- shift
- set -- "$@" "PREFIX=${arg#*=}"
+ --with-*|--without-*)
+ case "$name" in
+ libacl|libcap|libselinux|liburing|oniguruma)
+ set -- "$@" "WITH_$NAME=$yn"
+ ;;
+ *)
+ invalid "$arg"
+ ;;
+ esac
+ ;;
+
+ --prefix=*|--mandir=*)
+ set -- "$@" "$NAME=$value"
+ ;;
+
+ --infodir=*|--build=*|--host=*|--target=*)
+ printf 'warning: Ignoring option "%s"\n' "$arg" >&2
;;
MAKE=*)
- MAKE="${arg#*=}"
- shift
+ MAKE="$value"
;;
# make flag (-j2) or variable (CC=clang)
-*|*=*)
- continue
+ set -- "$@" "$arg"
;;
*)
- printf 'error: Unrecognized option "%s"\n\n' "$arg" >&2
- printf 'Run %s --help for more information.\n' "$0" >&2
- exit 1
+ invalid "$arg"
;;
esac
done
@@ -132,6 +197,6 @@ for f in Makefile build completions docs src tests; do
done
# Set MAKEFLAGS to -j$(nproc) if it's unset
-export MAKEFLAGS="${MAKEFLAGS-$j}"
+export MAKEFLAGS="${MAKEFLAGS--j$(nproc)}"
$MAKE -rf build/config.mk "$@"
diff --git a/docs/BUILDING.md b/docs/BUILDING.md
index cb33c51..025dadf 100644
--- a/docs/BUILDING.md
+++ b/docs/BUILDING.md
@@ -16,7 +16,7 @@ Configuration
$ ./configure --help
Usage:
- $ ./configure [--enable-*|--disable-*] [CC=...] [CFLAGS=...] [...]
+ $ ./configure [--enable-*|--disable-*] [--with-*|--without-*] [CC=...] [...]
$ make
...
@@ -82,14 +82,14 @@ You can combine multiple profiles (e.g. `./configure --enable-asan --enable-ubsa
### Dependencies
`bfs` depends on some system libraries for some of its features.
-External dependencies are auto-detected by default, but you can `--enable` or `--disable` them manually:
+External dependencies are auto-detected by default, but you can build `--with` or `--without` them explicitly:
<pre>
---enable-<a href="https://savannah.nongnu.org/projects/acl">libacl</a> --disable-libacl
---enable-<a href="https://sites.google.com/site/fullycapable/">libcap</a> --disable-libcap
---enable-<a href="https://github.com/SELinuxProject/selinux">libselinux</a> --disable-libselinux
---enable-<a href="https://github.com/axboe/liburing">liburing</a> --disable-liburing
---enable-<a href="https://github.com/kkos/oniguruma">oniguruma</a> --disable-oniguruma
+--with-<a href="https://savannah.nongnu.org/projects/acl">libacl</a> --without-libacl
+--with-<a href="https://sites.google.com/site/fullycapable/">libcap</a> --without-libcap
+--with-<a href="https://github.com/SELinuxProject/selinux">libselinux</a> --without-libselinux
+--with-<a href="https://github.com/axboe/liburing">liburing</a> --without-liburing
+--with-<a href="https://github.com/kkos/oniguruma">oniguruma</a> --without-oniguruma
</pre>
[`pkg-config`] is used, if available, to detect these libraries and any additional build flags they may require.
diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index 62b6480..db978b8 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -1,6 +1,50 @@
3.*
===
+3.3.1
+-----
+
+**June 3, 2024**
+
+### Bug fixes
+
+- Reduced the scope of the symbolic link loop change in version 3.3.
+ `-xtype l` remains true for symbolic link loops, matching a change in GNU findutils 4.10.0.
+ However, `-L` will report an error, just like `bfs` prior to 3.3 and other `find` implementations, as required by POSIX.
+
+
+3.3
+---
+
+**May 28, 2024**
+
+### New features
+
+- The `-status` bar can now be toggled by `SIGINFO` (<kbd>Ctrl</kbd>+<kbd>T</kbd>) on systems that support it, and `SIGUSR1` on other systems
+
+- `-regextype` now supports all regex types from GNU find ([#21](https://github.com/tavianator/bfs/issues/21))
+
+- File birth times are now supported on OpenBSD
+
+### Changes
+
+- Symbolic link loops are now treated like other broken links, rather than an error
+
+- `./configure` now expects `--with-libacl`, `--without-libcap`, etc. rather than `--enable-`/`--disable-`
+
+- The ` ` (space) flag is now restricted to numeric `-printf` specifiers
+
+### Bug fixes
+
+- `-regextype emacs` now supports [shy](https://www.gnu.org/software/emacs/manual/html_node/elisp/Regexp-Backslash.html#index-shy-groups) (non-capturing) groups
+
+- Fixed `-status` bar visual corruption when the terminal is resized
+
+- `bfs` now prints a reset escape sequence when terminated by a signal in the middle of colored output ([#138](https://github.com/tavianator/bfs/issues/138))
+
+- `./configure CFLAGS=...` no longer overrides flags from `pkg-config` during configuration
+
+
3.2
---
diff --git a/docs/RELATED.md b/docs/RELATED.md
index cf52b70..6e7bd38 100644
--- a/docs/RELATED.md
+++ b/docs/RELATED.md
@@ -25,8 +25,9 @@ These are not usually installed as the system `find`, but are designed to be `fi
- [`bfs`](https://tavianator.com/projects/bfs.html) ([manual](https://man.archlinux.org/man/bfs.1), [source](https://github.com/tavianator/bfs))
- [schilytools](https://codeberg.org/schilytools/schilytools) `sfind` ([source](https://codeberg.org/schilytools/schilytools/src/branch/master/sfind))
- [BusyBox](https://busybox.net/) `find` ([manual](https://busybox.net/downloads/BusyBox.html#find), [source](https://git.busybox.net/busybox/tree/findutils/find.c))
-- [ToyBox](http://landley.net/toybox/) `find` ([manual](http://landley.net/toybox/help.html#find), [source](https://github.com/landley/toybox/blob/master/toys/posix/find.c))
-- uutils `find` ([source](https://github.com/uutils/findutils))
+- [ToyBox](https://landley.net/toybox/) `find` ([manual](http://landley.net/toybox/help.html#find), [source](https://github.com/landley/toybox/blob/master/toys/posix/find.c))
+- [Heirloom Project](https://heirloom.sourceforge.net/) `find` ([manual](https://heirloom.sourceforge.net/man/find.1.html), [source](https://github.com/eunuchs/heirloom-project/blob/master/heirloom/heirloom/find/find.c))
+- [uutils](https://uutils.github.io/) `find` ([source](https://github.com/uutils/findutils))
## `find` alternatives
diff --git a/docs/SECURITY.md b/docs/SECURITY.md
new file mode 100644
index 0000000..f26efc5
--- /dev/null
+++ b/docs/SECURITY.md
@@ -0,0 +1,126 @@
+Security
+========
+
+Threat model
+------------
+
+`bfs` is a command line program running on multi-user operating systems.
+Those other users may be malicious, but `bfs` should not allow them to do anything they couldn't already do.
+That includes situations where one user (especially `root`) is running `bfs` on files owned or controlled by another user.
+
+On the other hand, `bfs` implicitly trusts the user running it.
+Anyone with enough control over the command line of `bfs` or any `find`-compatible tool can wreak havoc with dangerous actions like `-exec`, `-delete`, etc.
+
+> [!CAUTION]
+> The only untrusted input that should *ever* be passed on the `bfs` command line are **file paths**.
+> It is *always* unsafe to allow *any* other part of the command line to be affected by untrusted input.
+> Use the `-f` flag, or `-files0-from`, to ensure that the input is interpreted as a path.
+
+This still has security implications, incuding:
+
+- **Information disclosure:** an attacker may learn whether particular files exist by observing `bfs`'s output, exit status, or even side channels like execution time.
+- **Denial of service:** large directory trees or slow/network storage may cause `bfs` to consume excessive system resources.
+
+> [!TIP]
+> When in doubt, do not pass any untrusted input to `bfs`.
+
+
+Executing commands
+------------------
+
+The `-exec` family of actions execute commands, passing the matched paths as arguments.
+File names that begin with a dash may be misinterpreted as options, so `bfs` adds a leading `./` in some instances:
+
+```console
+user@host$ bfs -execdir echo {} \;
+./-rf
+```
+
+This might save you from accidentally running `rm -rf` (for example) when you didn't mean to.
+This mitigation applies to `-execdir`, but not `-exec`, because the full path typically does not begin with a dash.
+But it is possible, so be careful:
+
+```console
+user@host$ bfs -f -rf -exec echo {} \;
+-rf
+```
+
+
+Race conditions
+---------------
+
+Like many programs that interface with the file system, `bfs` can be affected by race conditions&mdash;in particular, "[time-of-check to time-of-use](https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use)" (TOCTTOU) issues.
+For example,
+
+```console
+user@host$ bfs / -user user -exec dangerous_command {} \;
+```
+
+is not guaranteed to only run `dangerous_command` on files you own, because another user may run
+
+```console
+evil@host$ mv /path/to/file /path/to/exile
+evil@host$ mv ~/malicious /path/to/file
+```
+
+in between checking `-user user` and executing the command.
+
+> [!WARNING]
+> Be careful when running `bfs` on directories that other users have write access to, because they can modify the directory tree while `bfs` is running, leading to unpredictable results and possible TOCTTOU issues.
+
+
+Output sanitization
+-------------------
+
+In general, printing arbitrary data to a terminal may have [security](https://hdm.io/writing/termulation.txt) [implications](https://dgl.cx/2023/09/ansi-terminal-security#vulnerabilities-using-known-replies).
+On many platforms, file paths may be completely arbitrary data (except for NUL (`\0`) bytes).
+Therefore, when `bfs` is writing output to a terminal, it will escape non-printable characters:
+
+<pre>
+user@host$ touch $'\e[1mBOLD\e[0m'
+user@host$ bfs
+.
+./$'\e[1mBOLD\e[0m'
+</pre>
+
+However, this is fragile as it only applies when outputting directly to a terminal:
+
+<pre>
+user@host$ bfs | grep BOLD
+<strong>BOLD</strong>
+</pre>
+
+
+Code quality
+------------
+
+Every correctness issue in `bfs` is a potential security issue, because acting on the wrong path may do arbitrarily bad things.
+For example:
+
+```console
+root@host# bfs /etc -name passwd -exec cat {} \;
+```
+
+should print `/etc/passwd` but not `/etc/shadow`.
+`bfs` tries to ensure correct behavior through careful programming practice, an extensive testsuite, and static analysis.
+
+`bfs` is written in C, which is a memory unsafe language.
+Bugs that lead to memory corruption are likely to be exploitable due to the nature of C.
+We use [sanitizers](https://github.com/google/sanitizers) to try to detect these bugs.
+Fuzzing has also been applied in the past, and deploying continuous fuzzing is a work in progress.
+
+
+Supported versions
+------------------
+
+`bfs` comes with [no warranty](/LICENSE), and is maintained by [me](https://tavianator.com/) and [other volunteers](https://github.com/tavianator/bfs/graphs/contributors) in our spare time.
+In that sense, there are no *supported* versions.
+However, as long as I maintain `bfs` I will attempt to address any security issues swiftly.
+In general, security fixes will we part of the latest release, though for significant issues I may backport fixes to older release series.
+
+
+Reporting a vulnerability
+-------------------------
+
+If you think you have found a sensitive security issue in `bfs`, you can [report it privately](https://github.com/tavianator/bfs/security/advisories/new).
+Or you can [report it publicly](https://github.com/tavianator/bfs/issues/new); I won't judge you.
diff --git a/docs/bfs.1 b/docs/bfs.1
index 54166ab..cc9504e 100644
--- a/docs/bfs.1
+++ b/docs/bfs.1
@@ -1,4 +1,4 @@
-.TH BFS 1
+.TH BFS 1 2024-06-17 "bfs 3.3.1"
.SH NAME
bfs \- breadth-first search for your files
.SH SYNOPSIS
@@ -45,7 +45,7 @@ For example,
.RE
.fi
.PP
-will print the all the paths that are either .txt files or symbolic links to .txt files.
+will print all the paths that are either .txt files or symbolic links to .txt files.
.B \-and
is implied between two consecutive expressions, so this is equivalent:
.PP
@@ -71,7 +71,7 @@ will also accept
.I \-N
or
.IR +N .
-.IR \-N
+.I \-N
means "less than
.IR N ,"
and
@@ -479,6 +479,10 @@ Find files the current user can execute/read/write.
Always false/true.
.RE
.TP
+\fB\-flags\fR [\fI\-+\fR]\fIFLAGS\fR
+Find files with matching inode
+.BR FLAGS .
+.TP
\fB\-fstype \fITYPE\fR
Find files on file systems with the given
.IR TYPE .
@@ -582,7 +586,7 @@ Find files whose entire path matches the
.IR GLOB .
.RE
.TP
-\fB\-perm\fR [\fI\-\fR]\fIMODE\fR
+\fB\-perm\fR [\fI\-+/\fR]\fIMODE\fR
Find files with a matching mode.
.TP
\fB\-regex \fIREGEX\fR
@@ -913,7 +917,7 @@ Finds broken symbolic links.
.TP
.B bfs \-name config \-exclude \-name .git
Finds all files named
-.BR config,
+.BR config ,
skipping every
.B .git
directory.
diff --git a/src/atomic.h b/src/atomic.h
index 360de20..ad5303b 100644
--- a/src/atomic.h
+++ b/src/atomic.h
@@ -109,7 +109,7 @@
# define spin_loop() __builtin_ia32_pause()
#elif __has_builtin(__builtin_arm_yield)
# define spin_loop() __builtin_arm_yield()
-#elif __has_builtin(__builtin_riscv_pause)
+#elif BFS_HAS_BUILTIN_RISCV_PAUSE
# define spin_loop() __builtin_riscv_pause()
#else
# define spin_loop() ((void)0)
diff --git a/src/bar.c b/src/bar.c
index b928373..be77694 100644
--- a/src/bar.c
+++ b/src/bar.c
@@ -144,14 +144,7 @@ struct bfs_bar *bfs_bar_show(void) {
return NULL;
}
- char term[L_ctermid];
- ctermid(term);
- if (strlen(term) == 0) {
- errno = ENOTTY;
- goto fail;
- }
-
- bar->fd = open(term, O_RDWR | O_CLOEXEC);
+ bar->fd = open_cterm(O_RDWR | O_CLOEXEC);
if (bar->fd < 0) {
goto fail;
}
diff --git a/src/bfstd.c b/src/bfstd.c
index ce2218c..6d244ca 100644
--- a/src/bfstd.c
+++ b/src/bfstd.c
@@ -184,6 +184,16 @@ char *xgetdelim(FILE *file, char delim) {
}
}
+int open_cterm(int flags) {
+ char path[L_ctermid];
+ if (ctermid(path) == NULL || strlen(path) == 0) {
+ errno = ENOTTY;
+ return -1;
+ }
+
+ return open(path, flags);
+}
+
const char *xgetprogname(void) {
const char *cmd = NULL;
#if BFS_HAS_GETPROGNAME
@@ -199,6 +209,35 @@ const char *xgetprogname(void) {
return cmd;
}
+int xstrtoll(const char *str, char **end, int base, long long *value) {
+ // strtoll() skips leading spaces, but we want to reject them
+ if (xisspace(str[0])) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ // If end is NULL, make sure the entire string is valid
+ bool entire = !end;
+ char *endp;
+ if (!end) {
+ end = &endp;
+ }
+
+ errno = 0;
+ long long result = strtoll(str, end, base);
+ if (errno != 0) {
+ return -1;
+ }
+
+ if (*end == str || (entire && **end != '\0')) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ *value = result;
+ return 0;
+}
+
/** Compile and execute a regular expression for xrpmatch(). */
static int xrpregex(nl_item item, const char *response) {
const char *pattern = nl_langinfo(item);
diff --git a/src/bfstd.h b/src/bfstd.h
index 8055c55..afb5282 100644
--- a/src/bfstd.h
+++ b/src/bfstd.h
@@ -158,6 +158,16 @@ FILE *xfopen(const char *path, int flags);
*/
char *xgetdelim(FILE *file, char delim);
+/**
+ * Open the controlling terminal.
+ *
+ * @param flags
+ * The open() flags.
+ * @return
+ * An open file descriptor, or -1 on failure.
+ */
+int open_cterm(int flags);
+
// #include <stdlib.h>
/**
@@ -169,6 +179,23 @@ char *xgetdelim(FILE *file, char delim);
const char *xgetprogname(void);
/**
+ * Wrapper for strtoll() that forbids leading spaces.
+ *
+ * @param str
+ * The string to parse.
+ * @param end
+ * If non-NULL, will hold a pointer to the first invalid character.
+ * If NULL, the entire string must be valid.
+ * @param base
+ * The base for the conversion, or 0 to auto-detect.
+ * @param value
+ * Will hold the parsed integer value, on success.
+ * @return
+ * 0 on success, -1 on failure.
+ */
+int xstrtoll(const char *str, char **end, int base, long long *value);
+
+/**
* Process a yes/no prompt.
*
* @return 1 for yes, 0 for no, and -1 for unknown.
@@ -349,7 +376,7 @@ size_t xread(int fd, void *buf, size_t nbytes);
* writes.
*
* @return
- The number of bytes written. A value != nbytes indicates an error.
+ * The number of bytes written. A value != nbytes indicates an error.
*/
size_t xwrite(int fd, const void *buf, size_t nbytes);
diff --git a/src/bftw.c b/src/bftw.c
index 5322181..f9ef2a1 100644
--- a/src/bftw.c
+++ b/src/bftw.c
@@ -913,7 +913,7 @@ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *arg
size_t qdepth = 4096;
size_t nthreads = args->nthreads;
-#if BFS_USE_LIBURING
+#if BFS_WITH_LIBURING
// io_uring uses one fd per ring, ioq uses one ring per thread
if (nthreads >= nopenfd - 1) {
nthreads = nopenfd - 2;
@@ -1529,11 +1529,28 @@ static bool bftw_pop_file(struct bftw_state *state) {
return bftw_pop(state, &state->fileq);
}
+/** Add a path component to the path. */
+static void bftw_prepend_path(char *path, size_t nameoff, size_t namelen, const char *name) {
+ if (nameoff > 0) {
+ path[nameoff - 1] = '/';
+ }
+ memcpy(path + nameoff, name, namelen);
+}
+
/** Build the path to the current file. */
static int bftw_build_path(struct bftw_state *state, const char *name) {
const struct bftw_file *file = state->file;
- size_t pathlen = file ? file->nameoff + file->namelen : 0;
+ size_t nameoff, namelen;
+ if (name) {
+ nameoff = file ? bftw_child_nameoff(file) : 0;
+ namelen = strlen(name);
+ } else {
+ nameoff = file->nameoff;
+ namelen = file->namelen;
+ }
+
+ size_t pathlen = nameoff + namelen;
if (dstresize(&state->path, pathlen) != 0) {
state->error = errno;
return -1;
@@ -1546,11 +1563,11 @@ static int bftw_build_path(struct bftw_state *state, const char *name) {
}
// Build the path backwards
+ if (name) {
+ bftw_prepend_path(state->path, nameoff, namelen, name);
+ }
while (file && file != ancestor) {
- if (file->nameoff > 0) {
- state->path[file->nameoff - 1] = '/';
- }
- memcpy(state->path + file->nameoff, file->name, file->namelen);
+ bftw_prepend_path(state->path, file->nameoff, file->namelen, file->name);
if (ancestor && ancestor->depth == file->depth) {
ancestor = ancestor->parent;
@@ -1559,20 +1576,6 @@ static int bftw_build_path(struct bftw_state *state, const char *name) {
}
state->previous = state->file;
-
- if (name) {
- if (pathlen > 0 && state->path[pathlen - 1] != '/') {
- if (dstrapp(&state->path, '/') != 0) {
- state->error = errno;
- return -1;
- }
- }
- if (dstrcat(&state->path, name) != 0) {
- state->error = errno;
- return -1;
- }
- }
-
return 0;
}
@@ -1676,6 +1679,7 @@ static void bftw_init_ftwbuf(struct bftw_state *state, enum bftw_visit visit) {
ftwbuf->visit = visit;
ftwbuf->type = BFS_UNKNOWN;
ftwbuf->error = state->direrror;
+ ftwbuf->loopoff = 0;
ftwbuf->at_fd = AT_FDCWD;
ftwbuf->at_path = ftwbuf->path;
bftw_stat_init(&ftwbuf->stat_bufs, &state->stat_buf, &state->lstat_buf);
@@ -1733,6 +1737,7 @@ static void bftw_init_ftwbuf(struct bftw_state *state, enum bftw_visit visit) {
if (ancestor->dev == statbuf->dev && ancestor->ino == statbuf->ino) {
ftwbuf->type = BFS_ERROR;
ftwbuf->error = ELOOP;
+ ftwbuf->loopoff = ancestor->nameoff + ancestor->namelen;
return;
}
}
diff --git a/src/bftw.h b/src/bftw.h
index 8656ca7..a2a201c 100644
--- a/src/bftw.h
+++ b/src/bftw.h
@@ -56,6 +56,8 @@ struct BFTW {
enum bfs_type type;
/** The errno that occurred, if type == BFS_ERROR. */
int error;
+ /** For filesystem loops, the length of the loop prefix. */
+ size_t loopoff;
/** A parent file descriptor for the *at() family of calls. */
int at_fd;
diff --git a/src/bit.h b/src/bit.h
index 17cfbcf..c494130 100644
--- a/src/bit.h
+++ b/src/bit.h
@@ -12,7 +12,7 @@
#include <limits.h>
#include <stdint.h>
-#if __STDC_VERSION__ >= C23
+#if BFS_HAS_STDBIT_H
# include <stdbit.h>
#endif
@@ -173,11 +173,7 @@
# define ENDIAN_NATIVE 0
#endif
-#if __STDC_VERSION__ >= C23
-# define bswap_u16 stdc_memreverse8u16
-# define bswap_u32 stdc_memreverse8u32
-# define bswap_u64 stdc_memreverse8u64
-#elif __GNUC__
+#if __GNUC__
# define bswap_u16 __builtin_bswap16
# define bswap_u32 __builtin_bswap32
# define bswap_u64 __builtin_bswap64
@@ -222,25 +218,17 @@ static inline uint8_t bswap_u8(uint8_t n) {
// Select an overload based on an unsigned integer type
#define UINT_SELECT(n, name) \
_Generic((n), \
- char: name##_uc, \
- signed char: name##_uc, \
unsigned char: name##_uc, \
- signed short: name##_us, \
unsigned short: name##_us, \
- signed int: name##_ui, \
unsigned int: name##_ui, \
- signed long: name##_ul, \
unsigned long: name##_ul, \
- signed long long: name##_ull, \
unsigned long long: name##_ull)
// C23 polyfill: bit utilities
-#if __STDC_VERSION__ >= C23
+#if __STDC_VERSION_STDBIT_H__ >= C23
# define count_ones stdc_count_ones
# define count_zeros stdc_count_zeros
-# define rotate_left stdc_rotate_left
-# define rotate_right stdc_rotate_right
# define leading_zeros stdc_leading_zeros
# define leading_ones stdc_leading_ones
# define trailing_zeros stdc_trailing_zeros
@@ -273,31 +261,31 @@ static inline uint8_t bswap_u8(uint8_t n) {
#define BUILTIN_WIDTH(suffix) BUILTIN_WIDTH##suffix
#define COUNT_ONES(type, suffix, width) \
- static inline int count_ones##suffix(type n) { \
+ static inline unsigned int count_ones##suffix(type n) { \
return UINT_BUILTIN(popcount, suffix)(n); \
}
#define LEADING_ZEROS(type, suffix, width) \
- static inline int leading_zeros##suffix(type n) { \
+ static inline unsigned int leading_zeros##suffix(type n) { \
return n \
? UINT_BUILTIN(clz, suffix)(n) - (BUILTIN_WIDTH(suffix) - width) \
: width; \
}
#define TRAILING_ZEROS(type, suffix, width) \
- static inline int trailing_zeros##suffix(type n) { \
+ static inline unsigned int trailing_zeros##suffix(type n) { \
return n ? UINT_BUILTIN(ctz, suffix)(n) : (int)width; \
}
#define FIRST_TRAILING_ONE(type, suffix, width) \
- static inline int first_trailing_one##suffix(type n) { \
+ static inline unsigned int first_trailing_one##suffix(type n) { \
return UINT_BUILTIN(ffs, suffix)(n); \
}
#else // !__GNUC__
#define COUNT_ONES(type, suffix, width) \
- static inline int count_ones##suffix(type n) { \
+ static inline unsigned int count_ones##suffix(type n) { \
int ret; \
for (ret = 0; n; ++ret) { \
n &= n - 1; \
@@ -306,7 +294,7 @@ static inline uint8_t bswap_u8(uint8_t n) {
}
#define LEADING_ZEROS(type, suffix, width) \
- static inline int leading_zeros##suffix(type n) { \
+ static inline unsigned int leading_zeros##suffix(type n) { \
type bit = (type)1 << (width - 1); \
int ret; \
for (ret = 0; bit && !(n & bit); ++ret, bit >>= 1); \
@@ -314,7 +302,7 @@ static inline uint8_t bswap_u8(uint8_t n) {
}
#define TRAILING_ZEROS(type, suffix, width) \
- static inline int trailing_zeros##suffix(type n) { \
+ static inline unsigned int trailing_zeros##suffix(type n) { \
type bit = 1; \
int ret; \
for (ret = 0; bit && !(n & bit); ++ret, bit <<= 1); \
@@ -322,7 +310,7 @@ static inline uint8_t bswap_u8(uint8_t n) {
}
#define FIRST_TRAILING_ONE(type, suffix, width) \
- static inline int first_trailing_one##suffix(type n) { \
+ static inline unsigned int first_trailing_one##suffix(type n) { \
return n ? trailing_zeros##suffix(n) + 1 : 0; \
}
@@ -333,19 +321,9 @@ UINT_OVERLOADS(LEADING_ZEROS)
UINT_OVERLOADS(TRAILING_ZEROS)
UINT_OVERLOADS(FIRST_TRAILING_ONE)
-#define ROTATE_LEFT(type, suffix, width) \
- static inline type rotate_left##suffix(type n, int c) { \
- return (n << c) | (n >> ((width - c) % width)); \
- }
-
-#define ROTATE_RIGHT(type, suffix, width) \
- static inline type rotate_right##suffix(type n, int c) { \
- return (n >> c) | (n << ((width - c) % width)); \
- }
-
#define FIRST_LEADING_ONE(type, suffix, width) \
- static inline int first_leading_one##suffix(type n) { \
- return width - leading_zeros##suffix(n); \
+ static inline unsigned int first_leading_one##suffix(type n) { \
+ return n ? leading_zeros##suffix(n) + 1 : 0; \
}
#define HAS_SINGLE_BIT(type, suffix, width) \
@@ -354,17 +332,30 @@ UINT_OVERLOADS(FIRST_TRAILING_ONE)
return n - 1 < (n ^ (n - 1)); \
}
-UINT_OVERLOADS(ROTATE_LEFT)
-UINT_OVERLOADS(ROTATE_RIGHT)
+#define BIT_WIDTH(type, suffix, width) \
+ static inline unsigned int bit_width##suffix(type n) { \
+ return width - leading_zeros##suffix(n); \
+ }
+
+#define BIT_FLOOR(type, suffix, width) \
+ static inline type bit_floor##suffix(type n) { \
+ return n ? (type)1 << (bit_width##suffix(n) - 1) : 0; \
+ }
+
+#define BIT_CEIL(type, suffix, width) \
+ static inline type bit_ceil##suffix(type n) { \
+ return (type)1 << bit_width##suffix(n - !!n); \
+ }
+
UINT_OVERLOADS(FIRST_LEADING_ONE)
UINT_OVERLOADS(HAS_SINGLE_BIT)
+UINT_OVERLOADS(BIT_WIDTH)
+UINT_OVERLOADS(BIT_FLOOR)
+UINT_OVERLOADS(BIT_CEIL)
#define count_ones(n) UINT_SELECT(n, count_ones)(n)
#define count_zeros(n) UINT_SELECT(n, count_ones)(~(n))
-#define rotate_left(n, c) UINT_SELECT(n, rotate_left)(n, c)
-#define rotate_right(n, c) UINT_SELECT(n, rotate_right)(n, c)
-
#define leading_zeros(n) UINT_SELECT(n, leading_zeros)(n)
#define leading_ones(n) UINT_SELECT(n, leading_zeros)(~(n))
@@ -379,23 +370,26 @@ UINT_OVERLOADS(HAS_SINGLE_BIT)
#define has_single_bit(n) UINT_SELECT(n, has_single_bit)(n)
-#define BIT_FLOOR(type, suffix, width) \
- static inline type bit_floor##suffix(type n) { \
- return n ? (type)1 << (first_leading_one##suffix(n) - 1) : 0; \
- }
+#define bit_width(n) UINT_SELECT(n, bit_width)(n)
+#define bit_floor(n) UINT_SELECT(n, bit_floor)(n)
+#define bit_ceil(n) UINT_SELECT(n, bit_ceil)(n)
-#define BIT_CEIL(type, suffix, width) \
- static inline type bit_ceil##suffix(type n) { \
- return (type)1 << first_leading_one##suffix(n - !!n); \
+#endif // __STDC_VERSION_STDBIT_H__ < C23
+
+#define ROTATE_LEFT(type, suffix, width) \
+ static inline type rotate_left##suffix(type n, int c) { \
+ return (n << c) | (n >> ((width - c) % width)); \
}
-UINT_OVERLOADS(BIT_FLOOR)
-UINT_OVERLOADS(BIT_CEIL)
+#define ROTATE_RIGHT(type, suffix, width) \
+ static inline type rotate_right##suffix(type n, int c) { \
+ return (n >> c) | (n << ((width - c) % width)); \
+ }
-#define bit_width(n) first_leading_one(n)
-#define bit_floor(n) UINT_SELECT(n, bit_floor)(n)
-#define bit_ceil(n) UINT_SELECT(n, bit_ceil)(n)
+UINT_OVERLOADS(ROTATE_LEFT)
+UINT_OVERLOADS(ROTATE_RIGHT)
-#endif // __STDC_VERSION__ < C23
+#define rotate_left(n, c) UINT_SELECT(n, rotate_left)(n, c)
+#define rotate_right(n, c) UINT_SELECT(n, rotate_right)(n, c)
#endif // BFS_BIT_H
diff --git a/src/color.c b/src/color.c
index 137d795..701a89c 100644
--- a/src/color.c
+++ b/src/color.c
@@ -583,7 +583,7 @@ static int parse_gnu_ls_colors(struct colors *colors, const char *ls_colors) {
break;
}
- if (dstrncpy(&key, chunk, equals - chunk) != 0) {
+ if (dstrxcpy(&key, chunk, equals - chunk) != 0) {
goto fail;
}
if (unescape(&value, equals + 1, ':', &next) != 0) {
@@ -968,7 +968,7 @@ static ssize_t first_broken_offset(const char *path, const struct BFTW *ftwbuf,
if (path == ftwbuf->path) {
if (ftwbuf->depth == 0) {
at_fd = AT_FDCWD;
- at_path = dstrndup(path, max);
+ at_path = dstrxdup(path, max);
} else {
// The parent must have existed to get here
goto out;
@@ -977,13 +977,13 @@ static ssize_t first_broken_offset(const char *path, const struct BFTW *ftwbuf,
// We're in print_link_target(), so resolve relative to the link's parent directory
at_fd = ftwbuf->at_fd;
if (at_fd == (int)AT_FDCWD && path[0] != '/') {
- at_path = dstrndup(ftwbuf->path, ftwbuf->nameoff);
- if (at_path && dstrncat(&at_path, path, max) != 0) {
+ at_path = dstrxdup(ftwbuf->path, ftwbuf->nameoff);
+ if (at_path && dstrxcat(&at_path, path, max) != 0) {
ret = -1;
goto out_path;
}
} else {
- at_path = dstrndup(path, max);
+ at_path = dstrxdup(path, max);
}
}
@@ -1171,7 +1171,7 @@ static int print_expr(CFILE *cfile, const struct bfs_expr *expr, bool verbose, i
}
int count = 0;
- for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) {
+ for_expr (child, expr) {
if (dstrcat(&cfile->buffer, " ") != 0) {
return -1;
}
@@ -1206,7 +1206,7 @@ static int cvbuff(CFILE *cfile, const char *format, va_list args) {
for (const char *i = format; *i; ++i) {
size_t verbatim = strcspn(i, "%$");
- if (dstrncat(&cfile->buffer, i, verbatim) != 0) {
+ if (dstrxcat(&cfile->buffer, i, verbatim) != 0) {
return -1;
}
i += verbatim;
diff --git a/src/ctx.c b/src/ctx.c
index 71fef98..0f619c2 100644
--- a/src/ctx.c
+++ b/src/ctx.c
@@ -56,6 +56,7 @@ struct bfs_ctx *bfs_ctx_new(void) {
goto fail;
}
ctx->cur_nofile = ctx->orig_nofile;
+ ctx->raise_nofile = true;
ctx->users = bfs_users_new();
if (!ctx->users) {
@@ -228,9 +229,7 @@ static int bfs_ctx_fclose(struct bfs_ctx *ctx, struct bfs_ctx_file *ctx_file) {
error = errno;
}
- if (ctx_file->hook) {
- sigunhook(ctx_file->hook);
- }
+ sigunhook(ctx_file->hook);
// Close the CFILE, except for stdio streams, which are closed later
if (cfile != ctx->cout && cfile != ctx->cerr) {
diff --git a/src/ctx.h b/src/ctx.h
index fc3020c..b28a63c 100644
--- a/src/ctx.h
+++ b/src/ctx.h
@@ -102,6 +102,8 @@ struct bfs_ctx {
struct rlimit orig_nofile;
/** The current RLIMIT_NOFILE limits. */
struct rlimit cur_nofile;
+ /** Whether the fd limit should be raised. */
+ bool raise_nofile;
/** The current time. */
struct timespec now;
diff --git a/src/diag.c b/src/diag.c
index 3594b84..ccafd98 100644
--- a/src/diag.c
+++ b/src/diag.c
@@ -159,7 +159,7 @@ static bool highlight_expr_recursive(const struct bfs_ctx *ctx, const struct bfs
}
}
- for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) {
+ for_expr (child, expr) {
ret |= highlight_expr_recursive(ctx, child, args);
}
diff --git a/src/eval.c b/src/eval.c
index 3d75f80..0b5992e 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -7,6 +7,7 @@
#include "prelude.h"
#include "eval.h"
+#include "atomic.h"
#include "bar.h"
#include "bfstd.h"
#include "bftw.h"
@@ -22,6 +23,7 @@
#include "printf.h"
#include "pwcache.h"
#include "sanity.h"
+#include "sighook.h"
#include "stat.h"
#include "trie.h"
#include "xregex.h"
@@ -402,7 +404,7 @@ static int eval_exec_finish(const struct bfs_expr *expr, const struct bfs_ctx *c
}
}
- for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) {
+ for_expr (child, expr) {
if (eval_exec_finish(child, ctx) != 0) {
ret = -1;
}
@@ -997,6 +999,13 @@ bool eval_xtype(const struct bfs_expr *expr, struct bfs_eval *state) {
const struct BFTW *ftwbuf = state->ftwbuf;
enum bfs_stat_flags flags = ftwbuf->stat_flags ^ (BFS_STAT_NOFOLLOW | BFS_STAT_TRYFOLLOW);
enum bfs_type type = bftw_type(ftwbuf, flags);
+
+ // GNU find treats ELOOP as a broken symbolic link for -xtype l
+ // (but not -L -type l)
+ if ((flags & BFS_STAT_TRYFOLLOW) && type == BFS_ERROR && errno == ELOOP) {
+ type = BFS_LNK;
+ }
+
if (type == BFS_ERROR) {
eval_report_error(state);
return false;
@@ -1089,7 +1098,7 @@ bool eval_not(const struct bfs_expr *expr, struct bfs_eval *state) {
* Evaluate a conjunction.
*/
bool eval_and(const struct bfs_expr *expr, struct bfs_eval *state) {
- for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) {
+ for_expr (child, expr) {
if (!eval_expr(child, state) || state->quit) {
return false;
}
@@ -1102,7 +1111,7 @@ bool eval_and(const struct bfs_expr *expr, struct bfs_eval *state) {
* Evaluate a disjunction.
*/
bool eval_or(const struct bfs_expr *expr, struct bfs_eval *state) {
- for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) {
+ for_expr (child, expr) {
if (eval_expr(child, state) || state->quit) {
return true;
}
@@ -1117,7 +1126,7 @@ bool eval_or(const struct bfs_expr *expr, struct bfs_eval *state) {
bool eval_comma(const struct bfs_expr *expr, struct bfs_eval *state) {
bool ret uninit(false);
- for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) {
+ for_expr (child, expr) {
ret = eval_expr(child, state);
if (state->quit) {
break;
@@ -1284,7 +1293,7 @@ static void debug_stat(const struct bfs_ctx *ctx, const struct BFTW *ftwbuf, enu
DEBUG_FLAG(flags, BFS_STAT_TRYFOLLOW);
DEBUG_FLAG(flags, BFS_STAT_NOSYNC);
- fprintf(stderr, ") == %d", err ? 0 : -1);
+ fprintf(stderr, ") == %d", err == 0 ? 0 : -1);
if (err) {
fprintf(stderr, " [%d]", err);
@@ -1373,6 +1382,11 @@ struct callback_args {
struct bfs_bar *bar;
/** The time of the last status update. */
struct timespec last_status;
+ /** SIGINFO hook. */
+ struct sighook *info_hook;
+ /** Flag set by SIGINFO hook. */
+ atomic bool info_flag;
+
/** The number of files visited so far. */
size_t count;
@@ -1399,15 +1413,38 @@ static enum bftw_action eval_callback(const struct BFTW *ftwbuf, void *ptr) {
state.ret = &args->ret;
state.quit = false;
+ // Check whether SIGINFO was delivered and show/hide the bar
+ if (exchange(&args->info_flag, false, relaxed)) {
+ if (args->bar) {
+ bfs_bar_hide(args->bar);
+ args->bar = NULL;
+ } else {
+ args->bar = bfs_bar_show();
+ if (!args->bar) {
+ bfs_warning(ctx, "Couldn't show status bar: %s.\n", errstr());
+ }
+ }
+ }
+
if (args->bar) {
eval_status(&state, args->bar, &args->last_status, args->count);
}
if (ftwbuf->type == BFS_ERROR) {
- if (!eval_should_ignore(&state, ftwbuf->error)) {
- eval_error(&state, "%s.\n", xstrerror(ftwbuf->error));
- }
state.action = BFTW_PRUNE;
+
+ if (ftwbuf->error == ELOOP && ftwbuf->loopoff > 0) {
+ char *loop = strndup(ftwbuf->path, ftwbuf->loopoff);
+ if (loop) {
+ eval_error(&state, "Filesystem loop back to ${di}%pq${rs}\n", loop);
+ free(loop);
+ goto done;
+ }
+ } else if (eval_should_ignore(&state, ftwbuf->error)) {
+ goto done;
+ }
+
+ eval_error(&state, "%s.\n", xstrerror(ftwbuf->error));
goto done;
}
@@ -1462,10 +1499,19 @@ done:
return state.action;
}
+/** Show/hide the bar in response to SIGINFO. */
+static void eval_siginfo(int sig, siginfo_t *info, void *ptr) {
+ struct callback_args *args = ptr;
+ store(&args->info_flag, true, relaxed);
+}
+
/** Raise RLIMIT_NOFILE if possible, and return the new limit. */
static int raise_fdlimit(struct bfs_ctx *ctx) {
rlim_t cur = ctx->orig_nofile.rlim_cur;
rlim_t max = ctx->orig_nofile.rlim_max;
+ if (!ctx->raise_nofile) {
+ max = cur;
+ }
rlim_t target = 64 << 10;
if (rlim_cmp(target, max) > 0) {
@@ -1594,7 +1640,7 @@ static bool eval_must_buffer(const struct bfs_expr *expr) {
return true;
}
- for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) {
+ for_expr (child, expr) {
if (eval_must_buffer(child)) {
return true;
}
@@ -1621,6 +1667,13 @@ int bfs_eval(struct bfs_ctx *ctx) {
}
}
+#ifdef SIGINFO
+ int siginfo = SIGINFO;
+#else
+ int siginfo = SIGUSR1;
+#endif
+ args.info_hook = sighook(siginfo, eval_siginfo, &args, SH_CONTINUE);
+
struct trie seen;
if (ctx->unique) {
trie_init(&seen);
@@ -1688,6 +1741,7 @@ int bfs_eval(struct bfs_ctx *ctx) {
trie_destroy(&seen);
}
+ sigunhook(args.info_hook);
bfs_bar_hide(args.bar);
return args.ret;
diff --git a/src/exec.c b/src/exec.c
index cd73d6c..2faa731 100644
--- a/src/exec.c
+++ b/src/exec.c
@@ -234,7 +234,7 @@ static char *bfs_exec_format_arg(char *arg, const char *path) {
char *last = arg;
do {
- if (dstrncat(&ret, last, match - last) != 0) {
+ if (dstrxcat(&ret, last, match - last) != 0) {
goto err;
}
if (dstrcat(&ret, path) != 0) {
diff --git a/src/expr.h b/src/expr.h
index 7bcace7..60b298d 100644
--- a/src/expr.h
+++ b/src/expr.h
@@ -244,4 +244,10 @@ bool bfs_expr_cmp(const struct bfs_expr *expr, long long n);
*/
void bfs_expr_clear(struct bfs_expr *expr);
+/**
+ * Iterate over the children of an expression.
+ */
+#define for_expr(child, expr) \
+ for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next)
+
#endif // BFS_EXPR_H
diff --git a/src/fsade.h b/src/fsade.h
index eefef9f..4465017 100644
--- a/src/fsade.h
+++ b/src/fsade.h
@@ -13,9 +13,9 @@
#define BFS_CAN_CHECK_ACL (BFS_HAS_ACL_GET_FILE || BFS_HAS_ACL_TRIVIAL)
-#define BFS_CAN_CHECK_CAPABILITIES BFS_USE_LIBCAP
+#define BFS_CAN_CHECK_CAPABILITIES BFS_WITH_LIBCAP
-#define BFS_CAN_CHECK_CONTEXT BFS_USE_LIBSELINUX
+#define BFS_CAN_CHECK_CONTEXT BFS_WITH_LIBSELINUX
#define BFS_CAN_CHECK_XATTRS (BFS_USE_SYS_EXTATTR_H || BFS_USE_SYS_XATTR_H)
diff --git a/src/ioq.c b/src/ioq.c
index 43a1b35..4057a5e 100644
--- a/src/ioq.c
+++ b/src/ioq.c
@@ -135,7 +135,7 @@
#include <stdlib.h>
#include <sys/stat.h>
-#if BFS_USE_LIBURING
+#if BFS_WITH_LIBURING
# include <liburing.h>
#endif
@@ -459,7 +459,7 @@ static void ioq_batch_push(struct ioqq *ioqq, struct ioq_batch *batch, struct io
/** Sentinel stop command. */
static struct ioq_ent IOQ_STOP;
-#if BFS_USE_LIBURING
+#if BFS_WITH_LIBURING
/**
* Supported io_uring operations.
*/
@@ -477,7 +477,7 @@ struct ioq_thread {
/** Pointer back to the I/O queue. */
struct ioq *parent;
-#if BFS_USE_LIBURING
+#if BFS_WITH_LIBURING
/** io_uring instance. */
struct io_uring ring;
/** Any error that occurred initializing the ring. */
@@ -497,7 +497,7 @@ struct ioq {
/** ioq_ent arena. */
struct arena ents;
-#if BFS_USE_LIBURING && BFS_USE_STATX
+#if BFS_WITH_LIBURING && BFS_USE_STATX
/** struct statx arena. */
struct arena xbufs;
#endif
@@ -559,7 +559,7 @@ static void ioq_dispatch_sync(struct ioq *ioq, struct ioq_ent *ent) {
ent->result = -ENOSYS;
}
-#if BFS_USE_LIBURING
+#if BFS_WITH_LIBURING
/** io_uring worker state. */
struct ioq_ring_state {
@@ -775,7 +775,7 @@ static void ioq_ring_work(struct ioq_thread *thread) {
}
}
-#endif // BFS_USE_LIBURING
+#endif // BFS_WITH_LIBURING
/** Synchronous syscall loop. */
static void ioq_sync_work(struct ioq_thread *thread) {
@@ -811,7 +811,7 @@ static void ioq_sync_work(struct ioq_thread *thread) {
static void *ioq_work(void *ptr) {
struct ioq_thread *thread = ptr;
-#if BFS_USE_LIBURING
+#if BFS_WITH_LIBURING
if (thread->ring_err == 0) {
ioq_ring_work(thread);
return NULL;
@@ -824,7 +824,7 @@ static void *ioq_work(void *ptr) {
/** Initialize io_uring thread state. */
static int ioq_ring_init(struct ioq *ioq, struct ioq_thread *thread) {
-#if BFS_USE_LIBURING
+#if BFS_WITH_LIBURING
struct ioq_thread *prev = NULL;
if (thread > ioq->threads) {
prev = thread - 1;
@@ -890,7 +890,7 @@ static int ioq_ring_init(struct ioq *ioq, struct ioq_thread *thread) {
/** Destroy an io_uring. */
static void ioq_ring_exit(struct ioq_thread *thread) {
-#if BFS_USE_LIBURING
+#if BFS_WITH_LIBURING
if (thread->ring_err == 0) {
io_uring_queue_exit(&thread->ring);
}
@@ -926,7 +926,7 @@ struct ioq *ioq_create(size_t depth, size_t nthreads) {
ioq->depth = depth;
ARENA_INIT(&ioq->ents, struct ioq_ent);
-#if BFS_USE_LIBURING && BFS_USE_STATX
+#if BFS_WITH_LIBURING && BFS_USE_STATX
ARENA_INIT(&ioq->xbufs, struct statx);
#endif
@@ -1036,7 +1036,7 @@ int ioq_stat(struct ioq *ioq, int dfd, const char *path, enum bfs_stat_flags fla
args->flags = flags;
args->buf = buf;
-#if BFS_USE_LIBURING && BFS_USE_STATX
+#if BFS_WITH_LIBURING && BFS_USE_STATX
args->xbuf = arena_alloc(&ioq->xbufs);
if (!args->xbuf) {
ioq_free(ioq, ent);
@@ -1060,7 +1060,7 @@ void ioq_free(struct ioq *ioq, struct ioq_ent *ent) {
bfs_assert(ioq->size > 0);
--ioq->size;
-#if BFS_USE_LIBURING && BFS_USE_STATX
+#if BFS_WITH_LIBURING && BFS_USE_STATX
if (ent->op == IOQ_STAT && ent->stat.xbuf) {
arena_free(&ioq->xbufs, ent->stat.xbuf);
}
@@ -1091,7 +1091,7 @@ void ioq_destroy(struct ioq *ioq) {
ioqq_destroy(ioq->ready);
ioqq_destroy(ioq->pending);
-#if BFS_USE_LIBURING && BFS_USE_STATX
+#if BFS_WITH_LIBURING && BFS_USE_STATX
arena_destroy(&ioq->xbufs);
#endif
arena_destroy(&ioq->ents);
diff --git a/src/list.h b/src/list.h
index 61d0e5b..d95483f 100644
--- a/src/list.h
+++ b/src/list.h
@@ -324,6 +324,25 @@
LIST_VOID_(SLIST_INSERT_(list, &(list)->head, item, __VA_ARGS__))
/**
+ * Splice a singly-linked list into another.
+ *
+ * @param dest
+ * The destination list.
+ * @param cursor
+ * A pointer to the item to splice after, e.g. &list->head or list->tail.
+ * @param src
+ * The source list.
+ */
+#define SLIST_SPLICE(dest, cursor, src) \
+ LIST_VOID_(SLIST_SPLICE_((dest), (cursor), (src)))
+
+#define SLIST_SPLICE_(dest, cursor, src) \
+ *src->tail = *cursor, \
+ *cursor = src->head, \
+ dest->tail = *dest->tail ? src->tail : dest->tail, \
+ SLIST_INIT(src)
+
+/**
* Add an entire singly-linked list to the tail of another.
*
* @param dest
@@ -332,10 +351,7 @@
* The source list.
*/
#define SLIST_EXTEND(dest, src) \
- SLIST_EXTEND_((dest), (src))
-
-#define SLIST_EXTEND_(dest, src) \
- (src->head ? (*dest->tail = src->head, dest->tail = src->tail, SLIST_INIT(src)) : (void)0)
+ SLIST_SPLICE(dest, (dest)->tail, src)
/**
* Remove an item from a singly-linked list.
diff --git a/src/main.c b/src/main.c
index 5dd88e4..6f23034 100644
--- a/src/main.c
+++ b/src/main.c
@@ -41,6 +41,7 @@
* - thread.h (multi-threading)
* - trie.[ch] (a trie set/map implementation)
* - typo.[ch] (fuzzy matching for typos)
+ * - version.c (embeds version information)
* - xregex.[ch] (regular expression support)
* - xspawn.[ch] (spawns processes)
* - xtime.[ch] (date/time handling utilities)
diff --git a/src/opt.c b/src/opt.c
index 883d598..9b091bd 100644
--- a/src/opt.c
+++ b/src/opt.c
@@ -102,39 +102,20 @@ enum pred_type {
PRED_TYPES,
};
-/** Get the name of a predicate type. */
-static const char *pred_type_name(enum pred_type type) {
- switch (type) {
- case READABLE_PRED:
- return "-readable";
- case WRITABLE_PRED:
- return "-writable";
- case EXECUTABLE_PRED:
- return "-executable";
- case ACL_PRED:
- return "-acl";
- case CAPABLE_PRED:
- return "-capable";
- case EMPTY_PRED:
- return "-empty";
- case HIDDEN_PRED:
- return "-hidden";
- case NOGROUP_PRED:
- return "-nogroup";
- case NOUSER_PRED:
- return "-nouser";
- case SPARSE_PRED:
- return "-sparse";
- case XATTR_PRED:
- return "-xattr";
-
- case PRED_TYPES:
- break;
- }
-
- bfs_bug("Unknown predicate %d", (int)type);
- return "???";
-}
+/** Predicate type names. */
+static const char *const pred_names[] = {
+ [READABLE_PRED] = "-readable",
+ [WRITABLE_PRED] = "-writable",
+ [EXECUTABLE_PRED] = "-executable",
+ [ACL_PRED] = "-acl",
+ [CAPABLE_PRED] = "-capable",
+ [EMPTY_PRED] = "-empty",
+ [HIDDEN_PRED] = "-hidden",
+ [NOGROUP_PRED] = "-nogroup",
+ [NOUSER_PRED] = "-nouser",
+ [SPARSE_PRED] = "-sparse",
+ [XATTR_PRED] = "-xattr",
+};
/**
* A contrained integer range.
@@ -242,29 +223,15 @@ enum range_type {
RANGE_TYPES,
};
-/** Get the name of a range type. */
-static const char *range_type_name(enum range_type type) {
- switch (type) {
- case DEPTH_RANGE:
- return "-depth";
- case GID_RANGE:
- return "-gid";
- case INUM_RANGE:
- return "-inum";
- case LINKS_RANGE:
- return "-links";
- case SIZE_RANGE:
- return "-size";
- case UID_RANGE:
- return "-uid";
-
- case RANGE_TYPES:
- break;
- }
-
- bfs_bug("Unknown range %d", (int)type);
- return "???";
-}
+/** Range type names. */
+static const char *const range_names[] = {
+ [DEPTH_RANGE] = "-depth",
+ [GID_RANGE] = "-gid",
+ [INUM_RANGE] = "-inum",
+ [LINKS_RANGE] = "-links",
+ [SIZE_RANGE] = "-size",
+ [UID_RANGE] = "-uid",
+};
/**
* The data flow analysis domain.
@@ -333,27 +300,27 @@ static void df_init_top(struct df_domain *value) {
/** Check for the top element. */
static bool df_is_top(const struct df_domain *value) {
- for (int i = 0; i < PRED_TYPES; ++i) {
- if (value->preds[i] != PRED_TOP) {
- return false;
- }
- }
+ for (int i = 0; i < PRED_TYPES; ++i) {
+ if (value->preds[i] != PRED_TOP) {
+ return false;
+ }
+ }
- for (int i = 0; i < RANGE_TYPES; ++i) {
- if (!range_is_top(&value->ranges[i])) {
- return false;
- }
- }
+ for (int i = 0; i < RANGE_TYPES; ++i) {
+ if (!range_is_top(&value->ranges[i])) {
+ return false;
+ }
+ }
- if (value->types != ~0U) {
- return false;
- }
+ if (value->types != ~0U) {
+ return false;
+ }
- if (value->xtypes != ~0U) {
- return false;
- }
+ if (value->xtypes != ~0U) {
+ return false;
+ }
- return true;
+ return true;
}
/** Compute the union of two fact sets. */
@@ -503,7 +470,7 @@ typedef bool dump_fn(struct bfs_opt *opt, const char *format, ...);
/** Print a df_pred. */
static void pred_dump(dump_fn *dump, struct bfs_opt *opt, const struct df_domain *value, enum pred_type type) {
- dump(opt, "${blu}%s${rs}: ", pred_type_name(type));
+ dump(opt, "${blu}%s${rs}: ", pred_names[type]);
FILE *file = opt->ctx->cerr->file;
switch (value->preds[type]) {
@@ -524,7 +491,7 @@ static void pred_dump(dump_fn *dump, struct bfs_opt *opt, const struct df_domain
/** Print a df_range. */
static void range_dump(dump_fn *dump, struct bfs_opt *opt, const struct df_domain *value, enum range_type type) {
- dump(opt, "${blu}%s${rs}: ", range_type_name(type));
+ dump(opt, "${blu}%s${rs}: ", range_names[type]);
FILE *file = opt->ctx->cerr->file;
const struct df_range *range = &value->ranges[type];
@@ -1088,7 +1055,7 @@ static struct bfs_expr *annotate_and(struct bfs_opt *opt, struct bfs_expr *expr,
expr->cost = 0.0;
expr->probability = 1.0;
- for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) {
+ for_expr (child, expr) {
expr->pure &= child->pure;
expr->always_true &= child->always_true;
expr->always_false |= child->always_false;
@@ -1107,7 +1074,7 @@ static struct bfs_expr *annotate_or(struct bfs_opt *opt, struct bfs_expr *expr,
expr->cost = 0.0;
float false_prob = 1.0;
- for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) {
+ for_expr (child, expr) {
expr->pure &= child->pure;
expr->always_true |= child->always_true;
expr->always_false &= child->always_false;
@@ -1124,7 +1091,7 @@ static struct bfs_expr *annotate_comma(struct bfs_opt *opt, struct bfs_expr *exp
expr->pure = true;
expr->cost = 0.0;
- for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) {
+ for_expr (child, expr) {
expr->pure &= child->pure;
expr->always_true = child->always_true;
expr->always_false = child->always_false;
@@ -1790,7 +1757,7 @@ static struct bfs_expr *data_flow_leave(struct bfs_opt *opt, struct bfs_expr *ex
if (df_is_bottom(&opt->after_false)) {
if (!expr->pure) {
expr->always_true = true;
- expr->probability = 0.0;
+ expr->probability = 1.0;
} else if (expr->eval_fn != eval_true) {
opt_warning(opt, expr, "This expression is always true.\n\n");
opt_debug(opt, "pure, always true\n");
@@ -1919,7 +1886,7 @@ static struct bfs_expr *simplify_not(struct bfs_opt *opt, struct bfs_expr *expr,
static struct bfs_expr *lift_andor_not(struct bfs_opt *opt, struct bfs_expr *expr) {
// Only lift negations if it would reduce the number of (-not) expressions
size_t added = 0, removed = 0;
- for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) {
+ for_expr (child, expr) {
if (child->eval_fn == eval_not) {
++removed;
} else {
@@ -1968,7 +1935,7 @@ static struct bfs_expr *first_ignorable(struct bfs_opt *opt, struct bfs_expr *ex
}
struct bfs_expr *ret = NULL;
- for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) {
+ for_expr (child, expr) {
if (!child->pure) {
ret = NULL;
} else if (!ret) {
@@ -2176,17 +2143,20 @@ static struct bfs_expr *optimize(struct bfs_opt *opt, struct bfs_expr *expr) {
return expr;
}
-/** Estimate the odds of an expression calling stat(). */
-static float expr_stat_odds(struct bfs_expr *expr) {
- if (expr->calls_stat) {
+/** An expression predicate. */
+typedef bool expr_pred(const struct bfs_expr *expr);
+
+/** Estimate the odds that a matching expression will be evaluated. */
+static float estimate_odds(const struct bfs_expr *expr, expr_pred *pred) {
+ if (pred(expr)) {
return 1.0;
}
- float nostat_odds = 1.0;
+ float nonmatch_odds = 1.0;
float reached_odds = 1.0;
- for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) {
- float child_odds = expr_stat_odds(child);
- nostat_odds *= 1.0 - reached_odds * child_odds;
+ for_expr (child, expr) {
+ float child_odds = estimate_odds(child, pred);
+ nonmatch_odds *= 1.0 - reached_odds * child_odds;
if (expr->eval_fn == eval_and) {
reached_odds *= child->probability;
@@ -2195,7 +2165,12 @@ static float expr_stat_odds(struct bfs_expr *expr) {
}
}
- return 1.0 - nostat_odds;
+ return 1.0 - nonmatch_odds;
+}
+
+/** Whether an expression calls stat(). */
+static bool calls_stat(const struct bfs_expr *expr) {
+ return expr->calls_stat;
}
/** Estimate the odds of calling stat(). */
@@ -2204,15 +2179,20 @@ static float estimate_stat_odds(struct bfs_ctx *ctx) {
return 1.0;
}
- float nostat_odds = 1.0 - expr_stat_odds(ctx->exclude);
+ float nostat_odds = 1.0 - estimate_odds(ctx->exclude, calls_stat);
float reached_odds = 1.0 - ctx->exclude->probability;
- float expr_odds = expr_stat_odds(ctx->expr);
+ float expr_odds = estimate_odds(ctx->expr, calls_stat);
nostat_odds *= 1.0 - reached_odds * expr_odds;
return 1.0 - nostat_odds;
}
+/** Matches -(exec|ok) ... \; */
+static bool single_exec(const struct bfs_expr *expr) {
+ return expr->eval_fn == eval_exec && !(expr->exec->flags & BFS_EXEC_MULTI);
+}
+
int bfs_optimize(struct bfs_ctx *ctx) {
bfs_ctx_dump(ctx, DEBUG_OPT);
@@ -2291,6 +2271,17 @@ int bfs_optimize(struct bfs_ctx *ctx) {
opt_leave(&opt, "eager stat cost: ${ylw}%g${rs}\n", eager_cost);
}
+#ifndef POSIX_SPAWN_SETRLIMIT
+ // If bfs_spawn_setrlimit() would force us to use fork() over
+ // posix_spawn(), the extra cost may outweigh the benefit of a
+ // higher RLIMIT_NOFILE
+ float single_exec_odds = estimate_odds(ctx->expr, single_exec);
+ if (single_exec_odds >= 0.5) {
+ opt_enter(&opt, "single ${blu}-exec${rs} odds: ${ylw}%g${rs}\n", single_exec_odds);
+ ctx->raise_nofile = false;
+ opt_leave(&opt, "not raising RLIMIT_NOFILE\n");
+ }
+#endif
}
opt_leave(&opt, NULL);
diff --git a/src/parse.c b/src/parse.c
index 8a8eb41..86ce72c 100644
--- a/src/parse.c
+++ b/src/parse.c
@@ -115,21 +115,28 @@ struct bfs_parser {
};
/**
- * Possible token types.
+ * Token types and flags.
*/
-enum token_type {
+enum token_info {
/** A flag. */
- T_FLAG,
+ T_FLAG = 1,
/** A root path. */
- T_PATH,
+ T_PATH = 2,
/** An option. */
- T_OPTION,
+ T_OPTION = 3,
/** A test. */
- T_TEST,
+ T_TEST = 4,
/** An action. */
- T_ACTION,
+ T_ACTION = 5,
/** An operator. */
- T_OPERATOR,
+ T_OPERATOR = 6,
+ /** Mask for token types. */
+ T_TYPE = (1 << 3) - 1,
+
+ /** A token can match a prefix of an argument, like -On, -newerXY, etc. */
+ T_PREFIX = 1 << 3,
+ /** A flag that takes an argument. */
+ T_NEEDS_ARG = 1 << 4,
};
/**
@@ -160,7 +167,6 @@ static void highlight_args(const struct bfs_ctx *ctx, char **argv, size_t argc,
*/
attr(printf(2, 3))
static void parse_error(const struct bfs_parser *parser, const char *format, ...) {
- int error = errno;
const struct bfs_ctx *ctx = parser->ctx;
bool highlight[ctx->argc];
@@ -170,7 +176,6 @@ static void parse_error(const struct bfs_parser *parser, const char *format, ...
va_list args;
va_start(args, format);
- errno = error;
bfs_verror(parser->ctx, format, args);
va_end(args);
}
@@ -180,7 +185,6 @@ static void parse_error(const struct bfs_parser *parser, const char *format, ...
*/
attr(printf(4, 5))
static void parse_argv_error(const struct bfs_parser *parser, char **argv, size_t argc, const char *format, ...) {
- int error = errno;
const struct bfs_ctx *ctx = parser->ctx;
bool highlight[ctx->argc];
@@ -190,7 +194,6 @@ static void parse_argv_error(const struct bfs_parser *parser, char **argv, size_
va_list args;
va_start(args, format);
- errno = error;
bfs_verror(ctx, format, args);
va_end(args);
}
@@ -200,7 +203,6 @@ static void parse_argv_error(const struct bfs_parser *parser, char **argv, size_
*/
attr(printf(6, 7))
static void parse_conflict_error(const struct bfs_parser *parser, char **argv1, size_t argc1, char **argv2, size_t argc2, const char *format, ...) {
- int error = errno;
const struct bfs_ctx *ctx = parser->ctx;
bool highlight[ctx->argc];
@@ -211,7 +213,6 @@ static void parse_conflict_error(const struct bfs_parser *parser, char **argv1,
va_list args;
va_start(args, format);
- errno = error;
bfs_verror(ctx, format, args);
va_end(args);
}
@@ -221,14 +222,12 @@ static void parse_conflict_error(const struct bfs_parser *parser, char **argv1,
*/
attr(printf(3, 4))
static void parse_expr_error(const struct bfs_parser *parser, const struct bfs_expr *expr, const char *format, ...) {
- int error = errno;
const struct bfs_ctx *ctx = parser->ctx;
bfs_expr_error(ctx, expr);
va_list args;
va_start(args, format);
- errno = error;
bfs_verror(ctx, format, args);
va_end(args);
}
@@ -238,7 +237,6 @@ static void parse_expr_error(const struct bfs_parser *parser, const struct bfs_e
*/
attr(printf(2, 3))
static bool parse_warning(const struct bfs_parser *parser, const char *format, ...) {
- int error = errno;
const struct bfs_ctx *ctx = parser->ctx;
bool highlight[ctx->argc];
@@ -250,7 +248,6 @@ static bool parse_warning(const struct bfs_parser *parser, const char *format, .
va_list args;
va_start(args, format);
- errno = error;
bool ret = bfs_vwarning(parser->ctx, format, args);
va_end(args);
return ret;
@@ -261,7 +258,6 @@ static bool parse_warning(const struct bfs_parser *parser, const char *format, .
*/
attr(printf(6, 7))
static bool parse_conflict_warning(const struct bfs_parser *parser, char **argv1, size_t argc1, char **argv2, size_t argc2, const char *format, ...) {
- int error = errno;
const struct bfs_ctx *ctx = parser->ctx;
bool highlight[ctx->argc];
@@ -274,7 +270,6 @@ static bool parse_conflict_warning(const struct bfs_parser *parser, char **argv1
va_list args;
va_start(args, format);
- errno = error;
bool ret = bfs_vwarning(ctx, format, args);
va_end(args);
return ret;
@@ -285,7 +280,6 @@ static bool parse_conflict_warning(const struct bfs_parser *parser, char **argv1
*/
attr(printf(3, 4))
static bool parse_expr_warning(const struct bfs_parser *parser, const struct bfs_expr *expr, const char *format, ...) {
- int error = errno;
const struct bfs_ctx *ctx = parser->ctx;
if (!bfs_expr_warning(ctx, expr)) {
@@ -294,7 +288,6 @@ static bool parse_expr_warning(const struct bfs_parser *parser, const struct bfs
va_list args;
va_start(args, format);
- errno = error;
bool ret = bfs_vwarning(ctx, format, args);
va_end(args);
return ret;
@@ -414,7 +407,9 @@ static struct bfs_expr *parse_expr(struct bfs_parser *parser);
/**
* Advance by a single token.
*/
-static char **parser_advance(struct bfs_parser *parser, enum token_type type, size_t argc) {
+static char **parser_advance(struct bfs_parser *parser, enum token_info type, size_t argc) {
+ bfs_assert(type == (type & T_TYPE));
+
if (type != T_FLAG && type != T_PATH) {
parser->expr_started = true;
}
@@ -517,20 +512,14 @@ enum int_flags {
* Parse an integer.
*/
static const char *parse_int(const struct bfs_parser *parser, char **arg, const char *str, void *result, enum int_flags flags) {
- // strtoll() skips leading spaces, but we want to reject them
- if (xisspace(str[0])) {
- goto bad;
- }
-
int base = flags & IF_BASE_MASK;
if (base == 0) {
base = 10;
}
char *endptr;
- errno = 0;
- long long value = strtoll(str, &endptr, base);
- if (errno != 0) {
+ long long value;
+ if (xstrtoll(str, &endptr, base, &value) != 0) {
if (errno == ERANGE) {
goto range;
} else {
@@ -538,13 +527,6 @@ static const char *parse_int(const struct bfs_parser *parser, char **arg, const
}
}
- // https://github.com/llvm/llvm-project/issues/64946
- sanitize_init(&endptr);
-
- if (endptr == str) {
- goto bad;
- }
-
if (!(flags & IF_PARTIAL_OK) && *endptr != '\0') {
goto bad;
}
@@ -657,9 +639,11 @@ static struct bfs_expr *parse_nullary_flag(struct bfs_parser *parser) {
*/
static struct bfs_expr *parse_unary_flag(struct bfs_parser *parser) {
const char *arg = parser->argv[0];
+ char flag = arg[strlen(arg) - 1];
+
const char *value = parser->argv[1];
if (!value) {
- parse_error(parser, "${cyn}%s${rs} needs a value.\n", arg);
+ parse_error(parser, "${cyn}-%c${rs} needs a value.\n", flag);
return NULL;
}
@@ -667,6 +651,29 @@ static struct bfs_expr *parse_unary_flag(struct bfs_parser *parser) {
}
/**
+ * Parse a prefix flag like -O3, -j8, etc.
+ */
+static struct bfs_expr *parse_prefix_flag(struct bfs_parser *parser, char flag, bool allow_separate, const char **value) {
+ const char *arg = parser->argv[0];
+
+ const char *suffix = strchr(arg, flag) + 1;
+ if (*suffix) {
+ *value = suffix;
+ return parse_nullary_flag(parser);
+ }
+
+ suffix = parser->argv[1];
+ if (allow_separate && suffix) {
+ *value = suffix;
+ } else {
+ parse_error(parser, "${cyn}-%c${rs} needs a value.\n", flag);
+ return NULL;
+ }
+
+ return parse_unary_flag(parser);
+}
+
+/**
* Parse a single option.
*/
static struct bfs_expr *parse_option(struct bfs_parser *parser, size_t argc) {
@@ -811,7 +818,8 @@ static bool parse_debug_flag(const char *flag, size_t len, const char *expected)
static struct bfs_expr *parse_debug(struct bfs_parser *parser, int arg1, int arg2) {
struct bfs_ctx *ctx = parser->ctx;
- struct bfs_expr *expr = parse_unary_flag(parser);
+ const char *flags;
+ struct bfs_expr *expr = parse_prefix_flag(parser, 'D', true, &flags);
if (!expr) {
cfprintf(ctx->cerr, "\n");
debug_help(ctx->cerr);
@@ -820,7 +828,7 @@ static struct bfs_expr *parse_debug(struct bfs_parser *parser, int arg1, int arg
bool unrecognized = false;
- for (const char *flag = expr->argv[1], *next; flag; flag = next) {
+ for (const char *flag = flags, *next; flag; flag = next) {
size_t len = strcspn(flag, ",");
if (flag[len]) {
next = flag + len + 1;
@@ -868,21 +876,22 @@ static struct bfs_expr *parse_debug(struct bfs_parser *parser, int arg1, int arg
* Parse -On.
*/
static struct bfs_expr *parse_optlevel(struct bfs_parser *parser, int arg1, int arg2) {
- struct bfs_expr *expr = parse_nullary_flag(parser);
+ const char *arg;
+ struct bfs_expr *expr = parse_prefix_flag(parser, 'O', false, &arg);
if (!expr) {
return NULL;
}
int *optlevel = &parser->ctx->optlevel;
- if (strcmp(expr->argv[0], "-Ofast") == 0) {
+ if (strcmp(arg, "fast") == 0) {
*optlevel = 4;
- } else if (!parse_int(parser, expr->argv, expr->argv[0] + 2, optlevel, IF_INT | IF_UNSIGNED)) {
+ } else if (!parse_int(parser, expr->argv, arg, optlevel, IF_INT | IF_UNSIGNED)) {
return NULL;
}
if (*optlevel > 4) {
- parse_expr_warning(parser, expr, "${cyn}-O${bld}%s${rs} is the same as ${cyn}-O${bld}4${rs}.\n\n", expr->argv[0] + 2);
+ parse_expr_warning(parser, expr, "${cyn}-O${bld}%s${rs} is the same as ${cyn}-O${bld}4${rs}.\n\n", arg);
}
return expr;
@@ -1613,13 +1622,14 @@ static struct bfs_expr *parse_inum(struct bfs_parser *parser, int arg1, int arg2
* Parse -j<n>.
*/
static struct bfs_expr *parse_jobs(struct bfs_parser *parser, int arg1, int arg2) {
- struct bfs_expr *expr = parse_nullary_flag(parser);
+ const char *arg;
+ struct bfs_expr *expr = parse_prefix_flag(parser, 'j', false, &arg);
if (!expr) {
return NULL;
}
unsigned int n;
- if (!parse_int(parser, expr->argv, expr->argv[0] + 2, &n, IF_INT | IF_UNSIGNED)) {
+ if (!parse_int(parser, expr->argv, arg, &n, IF_INT | IF_UNSIGNED)) {
return NULL;
}
@@ -2253,16 +2263,27 @@ static struct bfs_expr *parse_regextype(struct bfs_parser *parser, int arg1, int
// See https://www.gnu.org/software/gnulib/manual/html_node/Predefined-Syntaxes.html
const char *type = expr->argv[1];
if (strcmp(type, "posix-basic") == 0
+ || strcmp(type, "posix-minimal-basic") == 0
|| strcmp(type, "ed") == 0
|| strcmp(type, "sed") == 0) {
parser->regex_type = BFS_REGEX_POSIX_BASIC;
} else if (strcmp(type, "posix-extended") == 0) {
parser->regex_type = BFS_REGEX_POSIX_EXTENDED;
-#if BFS_USE_ONIGURUMA
+#if BFS_WITH_ONIGURUMA
+ } else if (strcmp(type, "awk") == 0
+ || strcmp(type, "posix-awk") == 0) {
+ parser->regex_type = BFS_REGEX_AWK;
+ } else if (strcmp(type, "gnu-awk") == 0) {
+ parser->regex_type = BFS_REGEX_GNU_AWK;
} else if (strcmp(type, "emacs") == 0) {
parser->regex_type = BFS_REGEX_EMACS;
} else if (strcmp(type, "grep") == 0) {
parser->regex_type = BFS_REGEX_GREP;
+ } else if (strcmp(type, "egrep") == 0
+ || strcmp(type, "posix-egrep") == 0) {
+ parser->regex_type = BFS_REGEX_EGREP;
+ } else if (strcmp(type, "findutils-default") == 0) {
+ parser->regex_type = BFS_REGEX_GNU_FIND;
#endif
} else if (strcmp(type, "help") == 0) {
parser->just_info = true;
@@ -2277,14 +2298,23 @@ static struct bfs_expr *parse_regextype(struct bfs_parser *parser, int arg1, int
list_types:
cfprintf(cfile, "Supported types are:\n\n");
- cfprintf(cfile, " ${bld}posix-basic${rs}: POSIX basic regular expressions (BRE)\n");
- cfprintf(cfile, " ${bld}posix-extended${rs}: POSIX extended regular expressions (ERE)\n");
- cfprintf(cfile, " ${bld}ed${rs}: Like ${grn}ed${rs} (same as ${bld}posix-basic${rs})\n");
-#if BFS_USE_ONIGURUMA
- cfprintf(cfile, " ${bld}emacs${rs}: Like ${grn}emacs${rs}\n");
- cfprintf(cfile, " ${bld}grep${rs}: Like ${grn}grep${rs}\n");
+ cfprintf(cfile, " ${bld}posix-basic${rs}: POSIX basic regular expressions (BRE)\n");
+ cfprintf(cfile, " ${bld}ed${rs}: Like ${grn}ed${rs} (same as ${bld}posix-basic${rs})\n");
+ cfprintf(cfile, " ${bld}sed${rs}: Like ${grn}sed${rs} (same as ${bld}posix-basic${rs})\n\n");
+
+ cfprintf(cfile, " ${bld}posix-extended${rs}: POSIX extended regular expressions (ERE)\n\n");
+
+#if BFS_WITH_ONIGURUMA
+ cfprintf(cfile, " [${bld}posix-${rs}]${bld}awk${rs}: Like ${grn}awk${rs}\n");
+ cfprintf(cfile, " ${bld}gnu-awk${rs}: Like GNU ${grn}awk${rs}\n\n");
+
+ cfprintf(cfile, " ${bld}emacs${rs}: Like ${grn}emacs${rs}\n\n");
+
+ cfprintf(cfile, " ${bld}grep${rs}: Like ${grn}grep${rs}\n");
+ cfprintf(cfile, " [${bld}posix-${rs}]${bld}egrep${rs}: Like ${grn}grep${rs} ${cyn}-E${rs}\n\n");
+
+ cfprintf(cfile, " ${bld}findutils-default${rs}: Like GNU ${grn}find${rs}\n");
#endif
- cfprintf(cfile, " ${bld}sed${rs}: Like ${grn}sed${rs} (same as ${bld}posix-basic${rs})\n");
return NULL;
}
@@ -2322,13 +2352,13 @@ static struct bfs_expr *parse_search_strategy(struct bfs_parser *parser, int arg
struct bfs_ctx *ctx = parser->ctx;
CFILE *cfile = ctx->cerr;
- struct bfs_expr *expr = parse_unary_flag(parser);
+ const char *arg;
+ struct bfs_expr *expr = parse_prefix_flag(parser, 'S', true, &arg);
if (!expr) {
cfprintf(cfile, "\n");
goto list_strategies;
}
- const char *arg = expr->argv[1];
if (strcmp(arg, "bfs") == 0) {
ctx->strategy = BFTW_BFS;
} else if (strcmp(arg, "dfs") == 0) {
@@ -2928,12 +2958,23 @@ static struct bfs_expr *parse_help(struct bfs_parser *parser, int arg1, int arg2
static struct bfs_expr *parse_version(struct bfs_parser *parser, int arg1, int arg2) {
cfprintf(parser->ctx->cout, "${ex}%s${rs} ${bld}%s${rs}\n\n", BFS_COMMAND, bfs_version);
- printf("%s\n", BFS_HOMEPAGE);
+ printf("Copyright © Tavian Barnes and the bfs contributors\n");
+ printf("No rights reserved (https://opensource.org/license/0BSD)\n\n");
+
+ printf("CONFFLAGS := %s\n", bfs_confflags);
+ printf("CC := %s\n", bfs_cc);
+ printf("CPPFLAGS := %s\n", bfs_cppflags);
+ printf("CFLAGS := %s\n", bfs_cflags);
+ printf("LDFLAGS := %s\n", bfs_ldflags);
+ printf("LDLIBS := %s\n", bfs_ldlibs);
+
+ printf("\n%s\n", BFS_HOMEPAGE);
parser->just_info = true;
return NULL;
}
+/** Parser callback function type. */
typedef struct bfs_expr *parse_fn(struct bfs_parser *parser, int arg1, int arg2);
/**
@@ -2941,11 +2982,10 @@ typedef struct bfs_expr *parse_fn(struct bfs_parser *parser, int arg1, int arg2)
*/
struct table_entry {
char *arg;
- enum token_type type;
+ enum token_info info;
parse_fn *parse;
int arg1;
int arg2;
- bool prefix;
};
/**
@@ -2959,13 +2999,13 @@ static const struct table_entry parse_table[] = {
{"-Bnewer", T_TEST, parse_newer, BFS_STAT_BTIME},
{"-Bsince", T_TEST, parse_since, BFS_STAT_BTIME},
{"-Btime", T_TEST, parse_time, BFS_STAT_BTIME},
- {"-D", T_FLAG, parse_debug},
+ {"-D", T_FLAG | T_PREFIX, parse_debug},
{"-E", T_FLAG, parse_regex_extended},
{"-H", T_FLAG, parse_follow, BFTW_FOLLOW_ROOTS, false},
{"-L", T_FLAG, parse_follow, BFTW_FOLLOW_ALL, false},
- {"-O", T_FLAG, parse_optlevel, 0, 0, true},
+ {"-O", T_FLAG | T_PREFIX, parse_optlevel},
{"-P", T_FLAG, parse_follow, 0, false},
- {"-S", T_FLAG, parse_search_strategy},
+ {"-S", T_FLAG | T_PREFIX, parse_search_strategy},
{"-X", T_FLAG, parse_xargs_safe},
{"-a", T_OPERATOR},
{"-acl", T_TEST, parse_acl},
@@ -2991,7 +3031,7 @@ static const struct table_entry parse_table[] = {
{"-execdir", T_ACTION, parse_exec, BFS_EXEC_CHDIR},
{"-executable", T_TEST, parse_access, X_OK},
{"-exit", T_ACTION, parse_exit},
- {"-f", T_FLAG, parse_f},
+ {"-f", T_FLAG | T_NEEDS_ARG, parse_f},
{"-false", T_TEST, parse_const, false},
{"-files0-from", T_OPTION, parse_files0_from},
{"-flags", T_TEST, parse_flags},
@@ -3012,7 +3052,7 @@ static const struct table_entry parse_table[] = {
{"-ipath", T_TEST, parse_path, true},
{"-iregex", T_TEST, parse_regex, BFS_REGEX_ICASE},
{"-iwholename", T_TEST, parse_path, true},
- {"-j", T_FLAG, parse_jobs, 0, 0, true},
+ {"-j", T_FLAG | T_PREFIX, parse_jobs},
{"-limit", T_ACTION, parse_limit},
{"-links", T_TEST, parse_links},
{"-lname", T_TEST, parse_lname, false},
@@ -3026,7 +3066,7 @@ static const struct table_entry parse_table[] = {
{"-mtime", T_TEST, parse_time, BFS_STAT_MTIME},
{"-name", T_TEST, parse_name, false},
{"-newer", T_TEST, parse_newer, BFS_STAT_MTIME},
- {"-newer", T_TEST, parse_newerxy, 0, 0, true},
+ {"-newer", T_TEST | T_PREFIX, parse_newerxy},
{"-nocolor", T_OPTION, parse_color, false},
{"-nogroup", T_TEST, parse_nogroup},
{"-nohidden", T_TEST, parse_nohidden},
@@ -3079,7 +3119,7 @@ static const struct table_entry parse_table[] = {
static const struct table_entry *table_lookup(const char *arg) {
for (const struct table_entry *entry = parse_table; entry->arg; ++entry) {
bool match;
- if (entry->prefix) {
+ if (entry->info & T_PREFIX) {
match = strncmp(arg, entry->arg, strlen(entry->arg)) == 0;
} else {
match = strcmp(arg, entry->arg) == 0;
@@ -3092,6 +3132,85 @@ static const struct table_entry *table_lookup(const char *arg) {
return NULL;
}
+/** Look up a single-character flag in the parse table. */
+static const struct table_entry *flag_lookup(char flag) {
+ for (const struct table_entry *entry = parse_table; entry->arg; ++entry) {
+ enum token_info type = entry->info & T_TYPE;
+ if (type == T_FLAG && entry->arg[1] == flag && !entry->arg[2]) {
+ return entry;
+ }
+ }
+
+ return NULL;
+}
+
+/** Check for a multi-flag argument like -LEXO2. */
+static bool is_flag_group(const char *arg) {
+ // We enforce that at least one flag in a flag group must be a capital
+ // letter, to avoid ambiguity with primary expressions
+ bool has_upper = false;
+
+ // Flags that take an argument must appear last
+ bool needs_arg = false;
+
+ for (size_t i = 1; arg[i]; ++i) {
+ char c = arg[i];
+ if (c >= 'A' && c <= 'Z') {
+ has_upper = true;
+ }
+
+ if (needs_arg) {
+ return false;
+ }
+
+ const struct table_entry *entry = flag_lookup(c);
+ if (!entry || !entry->parse) {
+ return false;
+ }
+
+ if (entry->info & T_PREFIX) {
+ // The rest is the flag's argument
+ break;
+ }
+
+ if (entry->info & T_NEEDS_ARG) {
+ needs_arg = true;
+ }
+ }
+
+ return has_upper;
+}
+
+/** Parse a multi-flag argument. */
+static struct bfs_expr *parse_flag_group(struct bfs_parser *parser) {
+ struct bfs_expr *expr = NULL;
+
+ char **start = parser->argv;
+ char **end = start;
+ const char *arg = start[0];
+
+ for (size_t i = 1; arg[i]; ++i) {
+ parser->argv = start;
+
+ const struct table_entry *entry = flag_lookup(arg[i]);
+ expr = entry->parse(parser, entry->arg1, entry->arg2);
+
+ if (parser->argv > end) {
+ end = parser->argv;
+ }
+
+ if (!expr || entry->info & T_PREFIX) {
+ break;
+ }
+ }
+
+ if (expr) {
+ bfs_assert(parser->argv == end, "Didn't eat enough tokens");
+ }
+
+ return expr;
+}
+
/** Search for a fuzzy match in the parse table. */
static const struct table_entry *table_lookup_fuzzy(const char *arg) {
const struct table_entry *best = NULL;
@@ -3130,11 +3249,15 @@ static struct bfs_expr *parse_primary(struct bfs_parser *parser) {
}
}
+ if (is_flag_group(arg)) {
+ return parse_flag_group(parser);
+ }
+
match = table_lookup_fuzzy(arg);
CFILE *cerr = parser->ctx->cerr;
parse_error(parser, "Unknown argument; did you mean ");
- switch (match->type) {
+ switch (match->info & T_TYPE) {
case T_FLAG:
cfprintf(cerr, "${cyn}%s${rs}?", match->arg);
break;
@@ -3458,7 +3581,7 @@ static void dump_expr_multiline(const struct bfs_ctx *ctx, enum debug_flags flag
++rparens;
} else {
cfprintf(ctx->cerr, "(${red}%s${rs}\n", expr->argv[0]);
- for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) {
+ for_expr (child, expr) {
int parens = child->next ? 0 : rparens + 1;
dump_expr_multiline(ctx, flag, child, indent + 1, parens);
}
diff --git a/src/prelude.h b/src/prelude.h
index 3521fe8..34ce803 100644
--- a/src/prelude.h
+++ b/src/prelude.h
@@ -40,6 +40,13 @@
// when the version number changes
extern const char bfs_version[];
+extern const char bfs_confflags[];
+extern const char bfs_cc[];
+extern const char bfs_cppflags[];
+extern const char bfs_cflags[];
+extern const char bfs_ldflags[];
+extern const char bfs_ldlibs[];
+
// Check for system headers
#ifdef __has_include
@@ -50,6 +57,9 @@ extern const char bfs_version[];
#if __has_include(<paths.h>)
# define BFS_HAS_PATHS_H true
#endif
+#if __has_include(<stdbit.h>)
+# define BFS_HAS_STDBIT_H true
+#endif
#if __has_include(<sys/extattr.h>)
# define BFS_HAS_SYS_EXTATTR_H true
#endif
@@ -76,6 +86,7 @@ extern const char bfs_version[];
#define BFS_HAS_MNTENT_H __GLIBC__
#define BFS_HAS_PATHS_H true
+#define BFS_HAS_STDBIT_H (__STDC_VERSION__ >= C23)
#define BFS_HAS_SYS_EXTATTR_H __FreeBSD__
#define BFS_HAS_SYS_MKDEV_H false
#define BFS_HAS_SYS_PARAM_H true
@@ -133,7 +144,7 @@ extern const char bfs_version[];
/**
* Get the length of an array.
*/
-#define countof(array) (sizeof(array) / sizeof(0[array]))
+#define countof(...) (sizeof(__VA_ARGS__) / sizeof(0[__VA_ARGS__]))
/**
* False sharing/destructive interference/largest cache line size.
diff --git a/src/printf.c b/src/printf.c
index be09ebd..6b07c54 100644
--- a/src/printf.c
+++ b/src/printf.c
@@ -505,30 +505,25 @@ static int bfs_printf_u(CFILE *cfile, const struct bfs_fmt *fmt, const struct BF
}
static const char *bfs_printf_type(enum bfs_type type) {
- switch (type) {
- case BFS_BLK:
- return "b";
- case BFS_CHR:
- return "c";
- case BFS_DIR:
- return "d";
- case BFS_DOOR:
- return "D";
- case BFS_FIFO:
- return "p";
- case BFS_LNK:
- return "l";
- case BFS_PORT:
- return "P";
- case BFS_REG:
- return "f";
- case BFS_SOCK:
- return "s";
- case BFS_WHT:
- return "w";
- default:
- return "U";
+ const char *const names[] = {
+ [BFS_BLK] = "b",
+ [BFS_CHR] = "c",
+ [BFS_DIR] = "d",
+ [BFS_DOOR] = "D",
+ [BFS_FIFO] = "p",
+ [BFS_LNK] = "l",
+ [BFS_PORT] = "P",
+ [BFS_REG] = "f",
+ [BFS_SOCK] = "s",
+ [BFS_WHT] = "w",
+ };
+
+ const char *name = NULL;
+ if ((size_t)type < countof(names)) {
+ name = names[type];
}
+
+ return name ? name : "U";
}
/** %y: type */
@@ -544,7 +539,7 @@ static int bfs_printf_Y(CFILE *cfile, const struct bfs_fmt *fmt, const struct BF
int error = 0;
if (type == BFS_ERROR) {
- if (errno_is_like(ELOOP)) {
+ if (errno == ELOOP) {
str = "L";
} else if (errno_is_like(ENOENT)) {
str = "N";
diff --git a/src/sighook.c b/src/sighook.c
index ff5b96f..6d6ff01 100644
--- a/src/sighook.c
+++ b/src/sighook.c
@@ -52,6 +52,8 @@ struct arc {
/** Initialize an arc. */
static void arc_init(struct arc *arc) {
+ bfs_verify(atomic_is_lock_free(&arc->refs));
+
atomic_init(&arc->refs, 0);
arc->ptr = NULL;
@@ -166,6 +168,8 @@ static void *RCU_NULL = &RCU_NULL;
/** Initialize an RCU block. */
static void rcu_init(struct rcu *rcu) {
+ bfs_verify(atomic_is_lock_free(&rcu->active));
+
atomic_init(&rcu->active, 0);
arc_init(&rcu->slots[0]);
arc_init(&rcu->slots[1]);
@@ -323,11 +327,16 @@ static int rcu_sigtable_add(struct rcu *rcu, struct sighook *hook) {
return 0;
}
-/** The global table of signal hooks. */
-static struct rcu rcu_sighooks;
-/** The global table of atsigexit() hooks. */
+/** The sharded table of signal hooks. */
+static struct rcu rcu_sighooks[64];
+/** The table of atsigexit() hooks. */
static struct rcu rcu_exithooks;
+/** Get the table for a particular signal. */
+static struct rcu *sigshard(int sig) {
+ return &rcu_sighooks[sig % countof(rcu_sighooks)];
+}
+
/** Mutex for initialization and RCU writer exclusion. */
static pthread_mutex_t sigmutex = PTHREAD_MUTEX_INITIALIZER;
@@ -475,7 +484,8 @@ static void sigdispatch(int sig, siginfo_t *info, void *context) {
int error = errno;
// Run the normal hooks
- enum sigflags flags = run_hooks(&rcu_sighooks, sig, info);
+ struct rcu *shard = sigshard(sig);
+ enum sigflags flags = run_hooks(shard, sig, info);
// Run the atsigexit() hooks, if we're exiting
if (!(flags & SH_CONTINUE) && is_fatal(sig)) {
@@ -501,8 +511,12 @@ static int siginit(int sig) {
|| sigemptyset(&action.sa_mask) != 0) {
return -1;
}
- rcu_init(&rcu_sighooks);
+
+ for (size_t i = 0; i < countof(rcu_sighooks); ++i) {
+ rcu_init(&rcu_sighooks[i]);
+ }
rcu_init(&rcu_exithooks);
+
initialized = true;
}
@@ -552,7 +566,8 @@ struct sighook *sighook(int sig, sighook_fn *fn, void *arg, enum sigflags flags)
goto done;
}
- ret = sighook_impl(&rcu_sighooks, sig, fn, arg, flags);
+ struct rcu *shard = sigshard(sig);
+ ret = sighook_impl(shard, sig, fn, arg, flags);
done:
mutex_unlock(&sigmutex);
return ret;
@@ -561,32 +576,38 @@ done:
struct sighook *atsigexit(sighook_fn *fn, void *arg) {
mutex_lock(&sigmutex);
- struct sighook *ret = NULL;
-
for (size_t i = 0; i < countof(FATAL_SIGNALS); ++i) {
- if (siginit(FATAL_SIGNALS[i]) != 0) {
- goto done;
- }
+ // Ignore errors; atsigexit() is best-effort anyway and things
+ // like sanitizer runtimes or valgrind may reserve signals for
+ // their own use
+ siginit(FATAL_SIGNALS[i]);
}
#ifdef SIGRTMIN
for (int i = SIGRTMIN; i <= SIGRTMAX; ++i) {
- if (siginit(i) != 0) {
- goto done;
- }
+ siginit(i);
}
#endif
- ret = sighook_impl(&rcu_exithooks, 0, fn, arg, 0);
-done:
+ struct sighook *ret = sighook_impl(&rcu_exithooks, 0, fn, arg, 0);
mutex_unlock(&sigmutex);
return ret;
}
void sigunhook(struct sighook *hook) {
+ if (!hook) {
+ return;
+ }
+
mutex_lock(&sigmutex);
- struct rcu *rcu = hook->sig ? &rcu_sighooks : &rcu_exithooks;
+ struct rcu *rcu;
+ if (hook->sig) {
+ rcu = sigshard(hook->sig);
+ } else {
+ rcu = &rcu_exithooks;
+ }
+
struct sigtable *table = rcu_peek(rcu);
bfs_verify(sigtable_del(table, hook) == 0);
diff --git a/src/stat.c b/src/stat.c
index 2044564..1e340d0 100644
--- a/src/stat.c
+++ b/src/stat.c
@@ -51,7 +51,7 @@ const char *bfs_stat_field_name(enum bfs_stat_field field) {
return "modification time";
}
- bfs_bug("Unrecognized stat field");
+ bfs_bug("Unrecognized stat field %d", (int)field);
return "???";
}
diff --git a/src/trie.c b/src/trie.c
index 7504d53..cc5db09 100644
--- a/src/trie.c
+++ b/src/trie.c
@@ -621,7 +621,7 @@ static void trie_free_singletons(struct trie *trie, uintptr_t ptr) {
struct trie_node *node = trie_decode_node(ptr);
// Make sure the bitmap is a power of two, i.e. it has just one child
- bfs_assert(has_single_bit(node->bitmap));
+ bfs_assert(has_single_bit((size_t)node->bitmap));
ptr = node->children[0];
trie_node_free(trie, node, 1);
diff --git a/src/typo.c b/src/typo.c
index b1c5c44..cb826fc 100644
--- a/src/typo.c
+++ b/src/typo.c
@@ -125,7 +125,7 @@ int typo_distance(const char *actual, const char *expected) {
// This is the Wagner-Fischer algorithm for Levenshtein distance, using
// Manhattan distance on the keyboard for individual characters.
- const int insert_cost = 12;
+ const int insert_cost = (40 + 12 + 1) / 2;
size_t rows = strlen(actual) + 1;
size_t cols = strlen(expected) + 1;
diff --git a/src/version.c b/src/version.c
new file mode 100644
index 0000000..8383350
--- /dev/null
+++ b/src/version.c
@@ -0,0 +1,32 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+#include "prelude.h"
+
+const char bfs_version[] = {
+#include "version.i"
+};
+
+const char bfs_confflags[] = {
+#include "confflags.i"
+};
+
+const char bfs_cc[] = {
+#include "cc.i"
+};
+
+const char bfs_cppflags[] = {
+#include "cppflags.i"
+};
+
+const char bfs_cflags[] = {
+#include "cflags.i"
+};
+
+const char bfs_ldflags[] = {
+#include "ldflags.i"
+};
+
+const char bfs_ldlibs[] = {
+#include "ldlibs.i"
+};
diff --git a/src/xregex.c b/src/xregex.c
index c2711bc..2d089b2 100644
--- a/src/xregex.c
+++ b/src/xregex.c
@@ -13,7 +13,7 @@
#include <stdlib.h>
#include <string.h>
-#if BFS_USE_ONIGURUMA
+#if BFS_WITH_ONIGURUMA
# include <langinfo.h>
# include <oniguruma.h>
#else
@@ -21,7 +21,7 @@
#endif
struct bfs_regex {
-#if BFS_USE_ONIGURUMA
+#if BFS_WITH_ONIGURUMA
unsigned char *pattern;
OnigRegex impl;
int err;
@@ -32,11 +32,17 @@ struct bfs_regex {
#endif
};
-#if BFS_USE_ONIGURUMA
+#if BFS_WITH_ONIGURUMA
static int bfs_onig_status;
static OnigEncoding bfs_onig_enc;
+static OnigSyntaxType bfs_onig_syntax_awk;
+static OnigSyntaxType bfs_onig_syntax_gnu_awk;
+static OnigSyntaxType bfs_onig_syntax_emacs;
+static OnigSyntaxType bfs_onig_syntax_egrep;
+static OnigSyntaxType bfs_onig_syntax_gnu_find;
+
/** pthread_once() callback. */
static void bfs_onig_once(void) {
// Fall back to ASCII by default
@@ -103,6 +109,35 @@ static void bfs_onig_once(void) {
if (bfs_onig_status != ONIG_NORMAL) {
bfs_onig_enc = NULL;
}
+
+ // Compute the GNU extensions
+ OnigSyntaxType *ere = ONIG_SYNTAX_POSIX_EXTENDED;
+ OnigSyntaxType *gnu = ONIG_SYNTAX_GNU_REGEX;
+ unsigned int gnu_op = gnu->op & ~ere->op;
+ unsigned int gnu_op2 = gnu->op2 & ~ere->op2;
+ unsigned int gnu_behavior = gnu->behavior & ~ere->behavior;
+
+ onig_copy_syntax(&bfs_onig_syntax_awk, ONIG_SYNTAX_POSIX_EXTENDED);
+ bfs_onig_syntax_awk.behavior |= ONIG_SYN_ALLOW_INVALID_INTERVAL;
+ bfs_onig_syntax_awk.behavior |= ONIG_SYN_BACKSLASH_ESCAPE_IN_CC;
+
+ onig_copy_syntax(&bfs_onig_syntax_gnu_awk, &bfs_onig_syntax_awk);
+ bfs_onig_syntax_gnu_awk.op |= gnu_op;
+ bfs_onig_syntax_gnu_awk.op2 |= gnu_op2;
+ bfs_onig_syntax_gnu_awk.behavior |= gnu_behavior;
+ bfs_onig_syntax_gnu_awk.behavior &= ~ONIG_SYN_CONTEXT_INDEP_REPEAT_OPS;
+ bfs_onig_syntax_gnu_awk.behavior &= ~ONIG_SYN_CONTEXT_INVALID_REPEAT_OPS;
+
+ // https://github.com/kkos/oniguruma/issues/296
+ onig_copy_syntax(&bfs_onig_syntax_emacs, ONIG_SYNTAX_EMACS);
+ bfs_onig_syntax_emacs.op2 |= ONIG_SYN_OP2_QMARK_GROUP_EFFECT;
+
+ onig_copy_syntax(&bfs_onig_syntax_egrep, ONIG_SYNTAX_POSIX_EXTENDED);
+ bfs_onig_syntax_egrep.behavior |= ONIG_SYN_ALLOW_INVALID_INTERVAL;
+ bfs_onig_syntax_egrep.behavior &= ~ONIG_SYN_CONTEXT_INVALID_REPEAT_OPS;
+
+ onig_copy_syntax(&bfs_onig_syntax_gnu_find, &bfs_onig_syntax_emacs);
+ bfs_onig_syntax_gnu_find.options |= ONIG_OPTION_MULTILINE;
}
/** Initialize Oniguruma. */
@@ -121,7 +156,7 @@ int bfs_regcomp(struct bfs_regex **preg, const char *pattern, enum bfs_regex_typ
return -1;
}
-#if BFS_USE_ONIGURUMA
+#if BFS_WITH_ONIGURUMA
// onig_error_code_to_str() says
//
// don't call this after the pattern argument of onig_new() is freed
@@ -143,12 +178,24 @@ int bfs_regcomp(struct bfs_regex **preg, const char *pattern, enum bfs_regex_typ
case BFS_REGEX_POSIX_EXTENDED:
syntax = ONIG_SYNTAX_POSIX_EXTENDED;
break;
+ case BFS_REGEX_AWK:
+ syntax = &bfs_onig_syntax_awk;
+ break;
+ case BFS_REGEX_GNU_AWK:
+ syntax = &bfs_onig_syntax_gnu_awk;
+ break;
case BFS_REGEX_EMACS:
- syntax = ONIG_SYNTAX_EMACS;
+ syntax = &bfs_onig_syntax_emacs;
break;
case BFS_REGEX_GREP:
syntax = ONIG_SYNTAX_GREP;
break;
+ case BFS_REGEX_EGREP:
+ syntax = &bfs_onig_syntax_egrep;
+ break;
+ case BFS_REGEX_GNU_FIND:
+ syntax = &bfs_onig_syntax_gnu_find;
+ break;
}
bfs_assert(syntax, "Invalid regex type");
@@ -204,7 +251,7 @@ fail:
int bfs_regexec(struct bfs_regex *regex, const char *str, enum bfs_regexec_flags flags) {
size_t len = strlen(str);
-#if BFS_USE_ONIGURUMA
+#if BFS_WITH_ONIGURUMA
const unsigned char *ustr = (const unsigned char *)str;
const unsigned char *end = ustr + len;
@@ -263,7 +310,7 @@ int bfs_regexec(struct bfs_regex *regex, const char *str, enum bfs_regexec_flags
void bfs_regfree(struct bfs_regex *regex) {
if (regex) {
-#if BFS_USE_ONIGURUMA
+#if BFS_WITH_ONIGURUMA
onig_free(regex->impl);
free(regex->pattern);
#else
@@ -278,7 +325,7 @@ char *bfs_regerror(const struct bfs_regex *regex) {
return strdup(xstrerror(ENOMEM));
}
-#if BFS_USE_ONIGURUMA
+#if BFS_WITH_ONIGURUMA
unsigned char *str = malloc(ONIG_MAX_ERROR_MESSAGE_LEN);
if (str) {
onig_error_code_to_str(str, regex->err, &regex->einfo);
diff --git a/src/xregex.h b/src/xregex.h
index 998a2b0..750db24 100644
--- a/src/xregex.h
+++ b/src/xregex.h
@@ -15,8 +15,12 @@ struct bfs_regex;
enum bfs_regex_type {
BFS_REGEX_POSIX_BASIC,
BFS_REGEX_POSIX_EXTENDED,
+ BFS_REGEX_AWK,
+ BFS_REGEX_GNU_AWK,
BFS_REGEX_EMACS,
BFS_REGEX_GREP,
+ BFS_REGEX_EGREP,
+ BFS_REGEX_GNU_FIND,
};
/**
diff --git a/src/xspawn.c b/src/xspawn.c
index 33e5a4a..2c64011 100644
--- a/src/xspawn.c
+++ b/src/xspawn.c
@@ -426,8 +426,17 @@ static int bfs_resolve_early(struct bfs_resolver *res, const char *exe, const st
};
if (bfs_can_skip_resolve(res, ctx)) {
- res->done = true;
- return 0;
+ // Do this check eagerly, even though posix_spawn()/execv() also
+ // would, because:
+ //
+ // - faccessat() is faster than fork()/clone() + execv()
+ // - posix_spawn() is not guaranteed to report ENOENT
+ if (xfaccessat(AT_FDCWD, exe, X_OK) == 0) {
+ res->done = true;
+ return 0;
+ } else {
+ return -1;
+ }
}
res->path = getenv("PATH");
diff --git a/src/xtime.c b/src/xtime.c
index 2808455..186651b 100644
--- a/src/xtime.c
+++ b/src/xtime.c
@@ -206,6 +206,23 @@ static int xgetpart(const char **str, size_t n, int *result) {
}
int xgetdate(const char *str, struct timespec *result) {
+ // Handle @epochseconds
+ if (str[0] == '@') {
+ long long value;
+ if (xstrtoll(str + 1, NULL, 10, &value) != 0) {
+ goto error;
+ }
+
+ time_t time = (time_t)value;
+ if ((long long)time != value) {
+ errno = ERANGE;
+ goto error;
+ }
+
+ result->tv_sec = time;
+ goto done;
+ }
+
struct tm tm = {
.tm_isdst = -1,
};
@@ -324,6 +341,7 @@ end:
}
}
+done:
result->tv_nsec = 0;
return 0;
diff --git a/tests/bfs/Dmulti.out b/tests/bfs/Dmulti.out
new file mode 100644
index 0000000..a7ccfe4
--- /dev/null
+++ b/tests/bfs/Dmulti.out
@@ -0,0 +1,19 @@
+basic
+basic/a
+basic/b
+basic/c
+basic/c/d
+basic/e
+basic/e/f
+basic/g
+basic/g/h
+basic/i
+basic/j
+basic/j/foo
+basic/k
+basic/k/foo
+basic/k/foo/bar
+basic/l
+basic/l/foo
+basic/l/foo/bar
+basic/l/foo/bar/baz
diff --git a/tests/bfs/Dmulti.sh b/tests/bfs/Dmulti.sh
new file mode 100644
index 0000000..35d64b1
--- /dev/null
+++ b/tests/bfs/Dmulti.sh
@@ -0,0 +1 @@
+bfs_diff -Dopt,tree,unknown basic
diff --git a/tests/bfs/LD_stat.out b/tests/bfs/LD_stat.out
new file mode 100644
index 0000000..ec9e861
--- /dev/null
+++ b/tests/bfs/LD_stat.out
@@ -0,0 +1,17 @@
+links
+links/broken
+links/deeply
+links/deeply/nested
+links/deeply/nested/broken
+links/deeply/nested/dir
+links/deeply/nested/file
+links/deeply/nested/link
+links/file
+links/hardlink
+links/notdir
+links/skip
+links/skip/broken
+links/skip/dir
+links/skip/file
+links/skip/link
+links/symlink
diff --git a/tests/bfs/LD_stat.sh b/tests/bfs/LD_stat.sh
new file mode 100644
index 0000000..d407de3
--- /dev/null
+++ b/tests/bfs/LD_stat.sh
@@ -0,0 +1 @@
+bfs_diff -LD stat links
diff --git a/tests/bfs/LDstat.out b/tests/bfs/LDstat.out
new file mode 100644
index 0000000..ec9e861
--- /dev/null
+++ b/tests/bfs/LDstat.out
@@ -0,0 +1,17 @@
+links
+links/broken
+links/deeply
+links/deeply/nested
+links/deeply/nested/broken
+links/deeply/nested/dir
+links/deeply/nested/file
+links/deeply/nested/link
+links/file
+links/hardlink
+links/notdir
+links/skip
+links/skip/broken
+links/skip/dir
+links/skip/file
+links/skip/link
+links/symlink
diff --git a/tests/bfs/LDstat.sh b/tests/bfs/LDstat.sh
new file mode 100644
index 0000000..ec6df0b
--- /dev/null
+++ b/tests/bfs/LDstat.sh
@@ -0,0 +1 @@
+bfs_diff -LDstat links
diff --git a/tests/bfs/O_3.sh b/tests/bfs/O_3.sh
new file mode 100644
index 0000000..f159852
--- /dev/null
+++ b/tests/bfs/O_3.sh
@@ -0,0 +1 @@
+! invoke_bfs -O 3 basic
diff --git a/tests/bfs/Sbfs.out b/tests/bfs/Sbfs.out
new file mode 100644
index 0000000..bb3cd8d
--- /dev/null
+++ b/tests/bfs/Sbfs.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/bfs/Sbfs.sh b/tests/bfs/Sbfs.sh
new file mode 100644
index 0000000..72d92c8
--- /dev/null
+++ b/tests/bfs/Sbfs.sh
@@ -0,0 +1,2 @@
+invoke_bfs -Sbfs -s basic >"$OUT"
+diff_output
diff --git a/tests/bfs/color_ext_case.sh b/tests/bfs/color_ext_case.sh
index 4adba69..4c14610 100644
--- a/tests/bfs/color_ext_case.sh
+++ b/tests/bfs/color_ext_case.sh
@@ -1,6 +1,6 @@
-# *.gz=01;31:*.GZ=01;32 -- case sensitive
-# *.tAr=01;33:*.TaR=01;33 -- case-insensitive
-# *.TAR.gz=01;34:*.tar.GZ=01;35 -- case-sensitive
-# *.txt=35:*TXT=36 -- case-insensitive
-export LS_COLORS="*.gz=01;31:*.GZ=01;32:*.tAr=01;33:*.TaR=01;33:*.TAR.gz=01;34:*.tar.GZ=01;35:*.txt=35:*TXT=36"
+# *.gz=01;30:*.gz=01;31:*.GZ=01;30:*.GZ=01;32 -- case sensitive
+# *.tAr=01;33:*.TaR=01;33 -- case-insensitive
+# *.TAR.gz=01;34:*.tar.GZ=01;35 -- case-sensitive
+# *.txt=35:*TXT=36 -- case-insensitive
+export LS_COLORS="*.gz=01;30:*.gz=01;31:*.GZ=01;30:*.GZ=01;32:*.tAr=01;33:*.TaR=01;33:*.TAR.gz=01;34:*.tar.GZ=01;35:*.txt=35:*TXT=36"
bfs_diff rainbow -color
diff --git a/tests/bfs/color_ext_override.sh b/tests/bfs/color_ext_override.sh
index ac4c7fb..9f818c9 100644
--- a/tests/bfs/color_ext_override.sh
+++ b/tests/bfs/color_ext_override.sh
@@ -1 +1 @@
-LS_COLORS="*.tar.gz=01;31:*.TAR=01;32:*.gz=01;33:" bfs_diff rainbow -color
+LS_COLORS="*.tar.gz=01;31:*.TAR=01;32:*.gz=01;30:*.gz=01;33:" bfs_diff rainbow -color
diff --git a/tests/bfs/nohidden.out b/tests/bfs/nohidden.out
index d3ec901..84e6bd2 100644
--- a/tests/bfs/nohidden.out
+++ b/tests/bfs/nohidden.out
@@ -1,4 +1,8 @@
+
+/n
weirdnames
+weirdnames/
+weirdnames/
weirdnames/
weirdnames/ /j
weirdnames/!
@@ -11,6 +15,8 @@ weirdnames/(-/c
weirdnames/(/b
weirdnames/)
weirdnames/)/g
+weirdnames/*
+weirdnames/*/m
weirdnames/,
weirdnames/,/f
weirdnames/-
@@ -19,3 +25,5 @@ weirdnames/[
weirdnames/[/k
weirdnames/\
weirdnames/\/i
+weirdnames/{
+weirdnames/{/l
diff --git a/tests/bfs/nohidden_depth.out b/tests/bfs/nohidden_depth.out
index d3ec901..84e6bd2 100644
--- a/tests/bfs/nohidden_depth.out
+++ b/tests/bfs/nohidden_depth.out
@@ -1,4 +1,8 @@
+
+/n
weirdnames
+weirdnames/
+weirdnames/
weirdnames/
weirdnames/ /j
weirdnames/!
@@ -11,6 +15,8 @@ weirdnames/(-/c
weirdnames/(/b
weirdnames/)
weirdnames/)/g
+weirdnames/*
+weirdnames/*/m
weirdnames/,
weirdnames/,/f
weirdnames/-
@@ -19,3 +25,5 @@ weirdnames/[
weirdnames/[/k
weirdnames/\
weirdnames/\/i
+weirdnames/{
+weirdnames/{/l
diff --git a/tests/bfs/xtype_depth.sh b/tests/bfs/xtype_depth.sh
index 02c8173..4683764 100644
--- a/tests/bfs/xtype_depth.sh
+++ b/tests/bfs/xtype_depth.sh
@@ -1,2 +1,2 @@
# Make sure -xtype is considered side-effecting for facts_when_impure
-! invoke_bfs loops -xtype l -depth 100
+! invoke_bfs inaccessible/link -xtype l -depth 100
diff --git a/tests/bfs/xtype_reorder.sh b/tests/bfs/xtype_reorder.sh
index 8d75d7d..c1d94f3 100644
--- a/tests/bfs/xtype_reorder.sh
+++ b/tests/bfs/xtype_reorder.sh
@@ -1,4 +1,3 @@
# Make sure -xtype is not reordered in front of anything -- if -xtype runs
# before -links 100, it will report an ELOOP error
-bfs_diff loops -links 100 -xtype l
-invoke_bfs loops -links 100 -xtype l
+bfs_diff inaccessible/link -links 100 -xtype l
diff --git a/tests/bit.c b/tests/bit.c
index 674d1b2..49e167d 100644
--- a/tests/bit.c
+++ b/tests/bit.c
@@ -76,15 +76,15 @@ bool check_bit(void) {
ret &= check_eq(bswap((uint32_t)0x12345678), 0x78563412);
ret &= check_eq(bswap((uint64_t)0x1234567812345678), 0x7856341278563412);
- ret &= check_eq(count_ones(0x0), 0);
- ret &= check_eq(count_ones(0x1), 1);
- ret &= check_eq(count_ones(0x2), 1);
- ret &= check_eq(count_ones(0x3), 2);
- ret &= check_eq(count_ones(0x137F), 10);
-
- ret &= check_eq(count_zeros(0), INT_WIDTH);
- ret &= check_eq(count_zeros(0L), LONG_WIDTH);
- ret &= check_eq(count_zeros(0LL), LLONG_WIDTH);
+ ret &= check_eq(count_ones(0x0U), 0);
+ ret &= check_eq(count_ones(0x1U), 1);
+ ret &= check_eq(count_ones(0x2U), 1);
+ ret &= check_eq(count_ones(0x3U), 2);
+ ret &= check_eq(count_ones(0x137FU), 10);
+
+ ret &= check_eq(count_zeros(0U), UINT_WIDTH);
+ ret &= check_eq(count_zeros(0UL), ULONG_WIDTH);
+ ret &= check_eq(count_zeros(0ULL), ULLONG_WIDTH);
ret &= check_eq(count_zeros((uint8_t)0), 8);
ret &= check_eq(count_zeros((uint16_t)0), 16);
ret &= check_eq(count_zeros((uint32_t)0), 32);
@@ -100,16 +100,16 @@ bool check_bit(void) {
ret &= check_eq(rotate_right((uint32_t)0x12345678, 20), 0x45678123);
ret &= check_eq(rotate_right((uint32_t)0x12345678, 0), 0x12345678);
- for (int i = 0; i < 16; ++i) {
+ for (unsigned int i = 0; i < 16; ++i) {
uint16_t n = (uint16_t)1 << i;
- for (int j = i; j < 16; ++j) {
+ for (unsigned int j = i; j < 16; ++j) {
uint16_t m = (uint16_t)1 << j;
uint16_t nm = n | m;
ret &= check_eq(count_ones(nm), 1 + (n != m));
ret &= check_eq(count_zeros(nm), 15 - (n != m));
ret &= check_eq(leading_zeros(nm), 15 - j);
ret &= check_eq(trailing_zeros(nm), i);
- ret &= check_eq(first_leading_one(nm), j + 1);
+ ret &= check_eq(first_leading_one(nm), 16 - j);
ret &= check_eq(first_trailing_one(nm), i + 1);
ret &= check_eq(bit_width(nm), j + 1);
ret &= check_eq(bit_floor(nm), m);
@@ -127,13 +127,13 @@ bool check_bit(void) {
ret &= check_eq(leading_zeros((uint16_t)0), 16);
ret &= check_eq(trailing_zeros((uint16_t)0), 16);
- ret &= check_eq(first_leading_one(0), 0);
- ret &= check_eq(first_trailing_one(0), 0);
- ret &= check_eq(bit_width(0), 0);
- ret &= check_eq(bit_floor(0), 0);
- ret &= check_eq(bit_ceil(0), 1);
+ ret &= check_eq(first_leading_one(0U), 0);
+ ret &= check_eq(first_trailing_one(0U), 0);
+ ret &= check_eq(bit_width(0U), 0);
+ ret &= check_eq(bit_floor(0U), 0);
+ ret &= check_eq(bit_ceil(0U), 1);
- ret &= bfs_check(!has_single_bit(0));
+ ret &= bfs_check(!has_single_bit(0U));
ret &= bfs_check(!has_single_bit(UINT32_MAX));
ret &= bfs_check(has_single_bit((uint32_t)1 << (UINT_WIDTH - 1)));
diff --git a/tests/bsd/Hf.out b/tests/bsd/Hf.out
new file mode 100644
index 0000000..ff635ff
--- /dev/null
+++ b/tests/bsd/Hf.out
@@ -0,0 +1 @@
+links/deeply/nested/dir
diff --git a/tests/bsd/Hf.sh b/tests/bsd/Hf.sh
new file mode 100644
index 0000000..333280c
--- /dev/null
+++ b/tests/bsd/Hf.sh
@@ -0,0 +1 @@
+bfs_diff -Hf links/deeply/nested/dir
diff --git a/tests/bsd/X.out b/tests/bsd/X.out
index afa84f7..dbe2408 100644
--- a/tests/bsd/X.out
+++ b/tests/bsd/X.out
@@ -9,6 +9,8 @@ weirdnames/(-/c
weirdnames/(/b
weirdnames/)
weirdnames/)/g
+weirdnames/*
+weirdnames/*/m
weirdnames/,
weirdnames/,/f
weirdnames/-
@@ -17,3 +19,5 @@ weirdnames/...
weirdnames/.../h
weirdnames/[
weirdnames/[/k
+weirdnames/{
+weirdnames/{/l
diff --git a/tests/bsd/perm_000_plus.out b/tests/bsd/perm_000_plus.out
index d7494b8..42f2fed 100644
--- a/tests/bsd/perm_000_plus.out
+++ b/tests/bsd/perm_000_plus.out
@@ -1,8 +1,10 @@
perms
-perms/0
-perms/r
-perms/rw
-perms/rwx
-perms/rx
-perms/w
-perms/wx
+perms/dr-x
+perms/drwx
+perms/f---
+perms/f-w-
+perms/f-wx
+perms/fr--
+perms/fr-x
+perms/frw-
+perms/frwx
diff --git a/tests/bsd/perm_222_plus.out b/tests/bsd/perm_222_plus.out
index 9a5b95a..5c78ecc 100644
--- a/tests/bsd/perm_222_plus.out
+++ b/tests/bsd/perm_222_plus.out
@@ -1,5 +1,6 @@
perms
-perms/rw
-perms/rwx
-perms/w
-perms/wx
+perms/drwx
+perms/f-w-
+perms/f-wx
+perms/frw-
+perms/frwx
diff --git a/tests/bsd/perm_644_plus.out b/tests/bsd/perm_644_plus.out
index 7e5ae98..774c0ea 100644
--- a/tests/bsd/perm_644_plus.out
+++ b/tests/bsd/perm_644_plus.out
@@ -1,7 +1,9 @@
perms
-perms/r
-perms/rw
-perms/rwx
-perms/rx
-perms/w
-perms/wx
+perms/dr-x
+perms/drwx
+perms/f-w-
+perms/f-wx
+perms/fr--
+perms/fr-x
+perms/frw-
+perms/frwx
diff --git a/tests/bsd/printx.out b/tests/bsd/printx.out
index 04bf9a9..034b2da 100644
--- a/tests/bsd/printx.out
+++ b/tests/bsd/printx.out
@@ -1,3 +1,5 @@
+
+/n
weirdnames
weirdnames/!
weirdnames/!-
@@ -9,6 +11,8 @@ weirdnames/(-/c
weirdnames/(/b
weirdnames/)
weirdnames/)/g
+weirdnames/*
+weirdnames/*/m
weirdnames/,
weirdnames/,/f
weirdnames/-
@@ -17,7 +21,11 @@ weirdnames/...
weirdnames/.../h
weirdnames/[
weirdnames/[/k
+weirdnames/\
+weirdnames/\
weirdnames/\
weirdnames/\ /j
weirdnames/\\
weirdnames/\\/i
+weirdnames/{
+weirdnames/{/l
diff --git a/tests/bsd/s.out b/tests/bsd/s.out
index 6b790c2..5c85ac8 100644
--- a/tests/bsd/s.out
+++ b/tests/bsd/s.out
@@ -1,12 +1,16 @@
weirdnames
+weirdnames/
+
weirdnames/
weirdnames/!
weirdnames/!-
weirdnames/(
weirdnames/(-
weirdnames/)
+weirdnames/*
weirdnames/,
weirdnames/-
weirdnames/...
weirdnames/[
weirdnames/\
+weirdnames/{
diff --git a/tests/color.sh b/tests/color.sh
index 805d2b8..4f4312e 100644
--- a/tests/color.sh
+++ b/tests/color.sh
@@ -35,3 +35,119 @@ color() {
"$@" | "$SED" $'s/\e\\[[^m]*m//g'
fi
}
+
+## Status bar
+
+# Show the terminal status bar
+show_bar() {
+ if [ -z "$TTY" ]; then
+ return 1
+ fi
+
+ # Name the pipe deterministically based on the ttyname, so that concurrent
+ # tests.sh runs on the same terminal (e.g. make -jN check) cooperate
+ local pipe
+ pipe=$(printf '%s' "$TTY" | tr '/' '-')
+ pipe="${TMPDIR:-/tmp}/bfs$pipe.bar"
+
+ if mkfifo "$pipe" 2>/dev/null; then
+ # We won the race, create the background process to manage the bar
+ bar_proc "$pipe" &
+ exec {BAR}>"$pipe"
+ elif [ -p "$pipe" ]; then
+ # We lost the race, connect to the existing process.
+ # There is a small TOCTTOU race here but I don't see how to avoid it.
+ exec {BAR}>"$pipe"
+ else
+ return 1
+ fi
+}
+
+# Print to the terminal status bar
+print_bar() {
+ printf 'PRINT:%d:%s\0' $$ "$1" >&$BAR
+}
+
+# Hide the terminal status bar
+hide_bar() {
+ printf 'HIDE:%d:\0' $$ >&$BAR
+ exec {BAR}>&-
+ unset BAR
+}
+
+# The background process that muxes multiple status bars for one TTY
+bar_proc() {
+ # Read from the pipe, write to the TTY
+ exec <"$1" >"$TTY"
+
+ # Delete the pipe when done
+ defer rm "$1"
+ # Reset the scroll region when done
+ defer printf '\e7\e[r\e8\e[J'
+
+ # Workaround for bash 4: checkwinsize is off by default. We can turn it
+ # on, but we also have to explicitly trigger a foreground job to finish
+ # so that it will update the window size before we use $LINES
+ shopt -s checkwinsize
+ (:)
+
+ BAR_HEIGHT=0
+ resize_bar
+ # Adjust the bar when the TTY size changes
+ trap resize_bar WINCH
+
+ # Map from PID to status bar
+ local -A pid2bar
+
+ # Read commands of the form "OP:PID:STRING\0"
+ while IFS=':' read -r -d '' op pid str; do
+ # Map the pid to a bar, creating a new one if necessary
+ if [ -z "${pid2bar[$pid]:-}" ]; then
+ pid2bar["$pid"]=$((BAR_HEIGHT++))
+ resize_bar
+ fi
+ bar="${pid2bar[$pid]}"
+
+ case "$op" in
+ PRINT)
+ printf '\e7\e[%d;0f\e[K%s\e8' $((TTY_HEIGHT - bar)) "$str"
+ ;;
+ HIDE)
+ bar="${pid2bar[$pid]}"
+ # Delete this status bar
+ unset 'pid2bar[$pid]'
+ # Shift all higher status bars down
+ for i in "${!pid2bar[@]}"; do
+ ibar="${pid2bar[$i]}"
+ if ((ibar > bar)); then
+ pid2bar["$i"]=$((ibar - 1))
+ fi
+ done
+ ((BAR_HEIGHT--))
+ resize_bar
+ ;;
+ esac
+ done
+}
+
+# Resize the status bar
+resize_bar() {
+ # Bash gets $LINES from stderr, so if it's redirected use tput instead
+ TTY_HEIGHT="${LINES:-$(tput lines 2>"$TTY")}"
+
+ if ((BAR_HEIGHT == 0)); then
+ return
+ fi
+
+ # Hide the bars temporarily
+ local seq='\e7\e[r\e8\e[J'
+ # Print \eD (IND) N times to ensure N blank lines at the bottom
+ for ((i = 0; i < BAR_HEIGHT; ++i)); do
+ seq="${seq}\\eD"
+ done
+ # Go back up N lines
+ seq="${seq}\\e[${BAR_HEIGHT}A"
+ # Create the new scroll region
+ seq="${seq}\\e7\\e[;$((TTY_HEIGHT - BAR_HEIGHT))r\\e8\\e[J"
+ printf "$seq"
+}
diff --git a/tests/common/HLP.out b/tests/common/HLP.out
new file mode 100644
index 0000000..ff635ff
--- /dev/null
+++ b/tests/common/HLP.out
@@ -0,0 +1 @@
+links/deeply/nested/dir
diff --git a/tests/common/HLP.sh b/tests/common/HLP.sh
new file mode 100644
index 0000000..4b6d631
--- /dev/null
+++ b/tests/common/HLP.sh
@@ -0,0 +1 @@
+bfs_diff -HLP links/deeply/nested/dir
diff --git a/tests/common/amin.out b/tests/common/amin.out
new file mode 100644
index 0000000..af57325
--- /dev/null
+++ b/tests/common/amin.out
@@ -0,0 +1,6 @@
+-amin 1: ./one_minute_ago
+-amin +1: ./one_hour_ago
+-amin +1: ./two_minutes_ago
+-amin -1: ./in_one_hour
+-amin -1: ./in_one_minute
+-amin -1: ./thirty_seconds_ago
diff --git a/tests/common/amin.sh b/tests/common/amin.sh
new file mode 100644
index 0000000..c4d53fb
--- /dev/null
+++ b/tests/common/amin.sh
@@ -0,0 +1,15 @@
+cd "$TEST"
+
+now=$(epoch_time)
+
+"$XTOUCH" -at "@$((now - 60 * 60))" one_hour_ago
+"$XTOUCH" -at "@$((now - 121))" two_minutes_ago
+"$XTOUCH" -at "@$((now - 61))" one_minute_ago
+"$XTOUCH" -at "@$((now - 30))" thirty_seconds_ago
+"$XTOUCH" -at "@$((now + 60))" in_one_minute
+"$XTOUCH" -at "@$((now + 60 * 60))" in_one_hour
+
+bfs_diff -mindepth 1 \
+ \( -amin -1 -exec printf -- '-amin -1: %s\n' {} \; -o -true \) \
+ \( -amin 1 -exec printf -- '-amin 1: %s\n' {} \; -o -true \) \
+ \( -amin +1 -exec printf -- '-amin +1: %s\n' {} \; -o -true \)
diff --git a/tests/common/atime.out b/tests/common/atime.out
new file mode 100644
index 0000000..5ed206b
--- /dev/null
+++ b/tests/common/atime.out
@@ -0,0 +1,6 @@
+-atime 1: ./yesterday
+-atime +1: ./last_week
+-atime +1: ./two_days_ago
+-atime -1: ./now
+-atime -1: ./one_hour_ago
+-atime -1: ./tomorrow
diff --git a/tests/common/atime.sh b/tests/common/atime.sh
new file mode 100644
index 0000000..c2e4031
--- /dev/null
+++ b/tests/common/atime.sh
@@ -0,0 +1,15 @@
+cd "$TEST"
+
+now=$(epoch_time)
+
+"$XTOUCH" -at "@$((now - 60 * 60 * 24 * 7))" last_week
+"$XTOUCH" -at "@$((now - 60 * 60 * 49))" two_days_ago
+"$XTOUCH" -at "@$((now - 60 * 60 * 25))" yesterday
+"$XTOUCH" -at "@$((now - 60 * 60))" one_hour_ago
+"$XTOUCH" -at "@$((now))" now
+"$XTOUCH" -at "@$((now + 60 * 60 * 24))" tomorrow
+
+bfs_diff -mindepth 1 \
+ \( -atime -1 -exec printf -- '-atime -1: %s\n' {} \; -o -true \) \
+ \( -atime 1 -exec printf -- '-atime 1: %s\n' {} \; -o -true \) \
+ \( -atime +1 -exec printf -- '-atime +1: %s\n' {} \; -o -true \)
diff --git a/tests/common/empty_error.out b/tests/common/empty_error.out
index da45e23..49f773d 100644
--- a/tests/common/empty_error.out
+++ b/tests/common/empty_error.out
@@ -1,3 +1 @@
-./bar
-./baz
-./qux
+inaccessible/file
diff --git a/tests/common/empty_error.sh b/tests/common/empty_error.sh
index 7c8049c..3438cca 100644
--- a/tests/common/empty_error.sh
+++ b/tests/common/empty_error.sh
@@ -1,7 +1 @@
-cd "$TEST"
-
-"$XTOUCH" -p foo/ bar/ baz qux
-chmod -r foo baz
-defer chmod +r foo baz
-
-! bfs_diff . -empty
+! bfs_diff inaccessible -empty
diff --git a/tests/common/mmin.out b/tests/common/mmin.out
new file mode 100644
index 0000000..4c79a16
--- /dev/null
+++ b/tests/common/mmin.out
@@ -0,0 +1,6 @@
+-mmin 1: ./one_minute_ago
+-mmin +1: ./one_hour_ago
+-mmin +1: ./two_minutes_ago
+-mmin -1: ./in_one_hour
+-mmin -1: ./in_one_minute
+-mmin -1: ./thirty_seconds_ago
diff --git a/tests/common/mmin.sh b/tests/common/mmin.sh
new file mode 100644
index 0000000..38e3337
--- /dev/null
+++ b/tests/common/mmin.sh
@@ -0,0 +1,15 @@
+cd "$TEST"
+
+now=$(epoch_time)
+
+"$XTOUCH" -mt "@$((now - 60 * 60))" one_hour_ago
+"$XTOUCH" -mt "@$((now - 121))" two_minutes_ago
+"$XTOUCH" -mt "@$((now - 61))" one_minute_ago
+"$XTOUCH" -mt "@$((now - 30))" thirty_seconds_ago
+"$XTOUCH" -mt "@$((now + 60))" in_one_minute
+"$XTOUCH" -mt "@$((now + 60 * 60))" in_one_hour
+
+bfs_diff -mindepth 1 \
+ \( -mmin -1 -exec printf -- '-mmin -1: %s\n' {} \; -o -true \) \
+ \( -mmin 1 -exec printf -- '-mmin 1: %s\n' {} \; -o -true \) \
+ \( -mmin +1 -exec printf -- '-mmin +1: %s\n' {} \; -o -true \)
diff --git a/tests/common/mtime.out b/tests/common/mtime.out
new file mode 100644
index 0000000..91f0114
--- /dev/null
+++ b/tests/common/mtime.out
@@ -0,0 +1,6 @@
+-mtime 1: ./yesterday
+-mtime +1: ./last_week
+-mtime +1: ./two_days_ago
+-mtime -1: ./now
+-mtime -1: ./one_hour_ago
+-mtime -1: ./tomorrow
diff --git a/tests/common/mtime.sh b/tests/common/mtime.sh
new file mode 100644
index 0000000..7ba127e
--- /dev/null
+++ b/tests/common/mtime.sh
@@ -0,0 +1,15 @@
+cd "$TEST"
+
+now=$(epoch_time)
+
+"$XTOUCH" -mt "@$((now - 60 * 60 * 24 * 7))" last_week
+"$XTOUCH" -mt "@$((now - 60 * 60 * 49))" two_days_ago
+"$XTOUCH" -mt "@$((now - 60 * 60 * 25))" yesterday
+"$XTOUCH" -mt "@$((now - 60 * 60))" one_hour_ago
+"$XTOUCH" -mt "@$((now))" now
+"$XTOUCH" -mt "@$((now + 60 * 60 * 24))" tomorrow
+
+bfs_diff -mindepth 1 \
+ \( -mtime -1 -exec printf -- '-mtime -1: %s\n' {} \; -o -true \) \
+ \( -mtime 1 -exec printf -- '-mtime 1: %s\n' {} \; -o -true \) \
+ \( -mtime +1 -exec printf -- '-mtime +1: %s\n' {} \; -o -true \)
diff --git a/tests/gnu/executable.out b/tests/gnu/executable.out
index 49c1b21..08965bf 100644
--- a/tests/gnu/executable.out
+++ b/tests/gnu/executable.out
@@ -1,4 +1,6 @@
perms
-perms/rwx
-perms/rx
-perms/wx
+perms/dr-x
+perms/drwx
+perms/f-wx
+perms/fr-x
+perms/frwx
diff --git a/tests/gnu/files0_from_file.out b/tests/gnu/files0_from_file.out
index 1d87e6b..0f6b00d 100644
--- a/tests/gnu/files0_from_file.out
+++ b/tests/gnu/files0_from_file.out
@@ -1,3 +1,7 @@
+
+
+
+
/j
/j
@@ -16,6 +20,9 @@
)
)/g
)/g
+*
+*/m
+*/m
,
,/f
,/f
@@ -25,9 +32,14 @@
...
.../h
.../h
+/n
+/n
[
[/k
[/k
\
\/i
\/i
+{
+{/l
+{/l
diff --git a/tests/gnu/files0_from_stdin.out b/tests/gnu/files0_from_stdin.out
index 1d87e6b..0f6b00d 100644
--- a/tests/gnu/files0_from_stdin.out
+++ b/tests/gnu/files0_from_stdin.out
@@ -1,3 +1,7 @@
+
+
+
+
/j
/j
@@ -16,6 +20,9 @@
)
)/g
)/g
+*
+*/m
+*/m
,
,/f
,/f
@@ -25,9 +32,14 @@
...
.../h
.../h
+/n
+/n
[
[/k
[/k
\
\/i
\/i
+{
+{/l
+{/l
diff --git a/tests/gnu/follow_comma.out b/tests/gnu/follow_comma.out
index 920b3d3..5e4b806 100644
--- a/tests/gnu/follow_comma.out
+++ b/tests/gnu/follow_comma.out
@@ -1,4 +1,7 @@
+
.
+./
+./
./
./ /j
./!
@@ -11,6 +14,8 @@
./(/b
./)
./)/g
+./*
+./*/m
./,
./,/f
./-
@@ -21,3 +26,6 @@
./[/k
./\
./\/i
+./{
+./{/l
+/n
diff --git a/tests/gnu/ignore_readdir_race_loop.out b/tests/gnu/ignore_readdir_race_loop.out
new file mode 100644
index 0000000..a514555
--- /dev/null
+++ b/tests/gnu/ignore_readdir_race_loop.out
@@ -0,0 +1,11 @@
+loops
+loops/broken
+loops/deeply
+loops/deeply/nested
+loops/deeply/nested/dir
+loops/file
+loops/notdir
+loops/skip
+loops/skip/dir
+loops/skip/loop
+loops/symlink
diff --git a/tests/gnu/ignore_readdir_race_loop.sh b/tests/gnu/ignore_readdir_race_loop.sh
new file mode 100644
index 0000000..3329169
--- /dev/null
+++ b/tests/gnu/ignore_readdir_race_loop.sh
@@ -0,0 +1,2 @@
+# Make sure -ignore_readdir_race doesn't suppress ELOOP from an actual filesystem loop
+! bfs_diff -L loops -ignore_readdir_race
diff --git a/tests/gnu/perm_000_slash.out b/tests/gnu/perm_000_slash.out
index d7494b8..42f2fed 100644
--- a/tests/gnu/perm_000_slash.out
+++ b/tests/gnu/perm_000_slash.out
@@ -1,8 +1,10 @@
perms
-perms/0
-perms/r
-perms/rw
-perms/rwx
-perms/rx
-perms/w
-perms/wx
+perms/dr-x
+perms/drwx
+perms/f---
+perms/f-w-
+perms/f-wx
+perms/fr--
+perms/fr-x
+perms/frw-
+perms/frwx
diff --git a/tests/gnu/perm_222_slash.out b/tests/gnu/perm_222_slash.out
index 9a5b95a..5c78ecc 100644
--- a/tests/gnu/perm_222_slash.out
+++ b/tests/gnu/perm_222_slash.out
@@ -1,5 +1,6 @@
perms
-perms/rw
-perms/rwx
-perms/w
-perms/wx
+perms/drwx
+perms/f-w-
+perms/f-wx
+perms/frw-
+perms/frwx
diff --git a/tests/gnu/perm_644_slash.out b/tests/gnu/perm_644_slash.out
index 7e5ae98..774c0ea 100644
--- a/tests/gnu/perm_644_slash.out
+++ b/tests/gnu/perm_644_slash.out
@@ -1,7 +1,9 @@
perms
-perms/r
-perms/rw
-perms/rwx
-perms/rx
-perms/w
-perms/wx
+perms/dr-x
+perms/drwx
+perms/f-w-
+perms/f-wx
+perms/fr--
+perms/fr-x
+perms/frw-
+perms/frwx
diff --git a/tests/gnu/perm_leading_plus_symbolic_slash.out b/tests/gnu/perm_leading_plus_symbolic_slash.out
index 7e5ae98..774c0ea 100644
--- a/tests/gnu/perm_leading_plus_symbolic_slash.out
+++ b/tests/gnu/perm_leading_plus_symbolic_slash.out
@@ -1,7 +1,9 @@
perms
-perms/r
-perms/rw
-perms/rwx
-perms/rx
-perms/w
-perms/wx
+perms/dr-x
+perms/drwx
+perms/f-w-
+perms/f-wx
+perms/fr--
+perms/fr-x
+perms/frw-
+perms/frwx
diff --git a/tests/gnu/perm_symbolic_slash.out b/tests/gnu/perm_symbolic_slash.out
index 7e5ae98..774c0ea 100644
--- a/tests/gnu/perm_symbolic_slash.out
+++ b/tests/gnu/perm_symbolic_slash.out
@@ -1,7 +1,9 @@
perms
-perms/r
-perms/rw
-perms/rwx
-perms/rx
-perms/w
-perms/wx
+perms/dr-x
+perms/drwx
+perms/f-w-
+perms/f-wx
+perms/fr--
+perms/fr-x
+perms/frw-
+perms/frwx
diff --git a/tests/gnu/readable.out b/tests/gnu/readable.out
index 386feba..285aa43 100644
--- a/tests/gnu/readable.out
+++ b/tests/gnu/readable.out
@@ -1,5 +1,7 @@
perms
-perms/r
-perms/rw
-perms/rwx
-perms/rx
+perms/dr-x
+perms/drwx
+perms/fr--
+perms/fr-x
+perms/frw-
+perms/frwx
diff --git a/tests/gnu/regextype_awk.out b/tests/gnu/regextype_awk.out
new file mode 100644
index 0000000..0f32fc4
--- /dev/null
+++ b/tests/gnu/regextype_awk.out
@@ -0,0 +1,2 @@
+weirdnames/*/m
+weirdnames/[/k
diff --git a/tests/gnu/regextype_awk.sh b/tests/gnu/regextype_awk.sh
new file mode 100644
index 0000000..3718473
--- /dev/null
+++ b/tests/gnu/regextype_awk.sh
@@ -0,0 +1,3 @@
+invoke_bfs -regextype awk -quit || skip
+
+bfs_diff weirdnames -regextype awk -regex '.*/[\[\*]/.*'
diff --git a/tests/gnu/regextype_egrep.out b/tests/gnu/regextype_egrep.out
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/gnu/regextype_egrep.out
diff --git a/tests/gnu/regextype_egrep.sh b/tests/gnu/regextype_egrep.sh
new file mode 100644
index 0000000..281d9c0
--- /dev/null
+++ b/tests/gnu/regextype_egrep.sh
@@ -0,0 +1,3 @@
+invoke_bfs -regextype egrep -quit || skip
+
+bfs_diff weirdnames -regextype egrep -regex '*.*/{l'
diff --git a/tests/gnu/regextype_emacs.sh b/tests/gnu/regextype_emacs.sh
index 3cc388c..164d17a 100644
--- a/tests/gnu/regextype_emacs.sh
+++ b/tests/gnu/regextype_emacs.sh
@@ -1,3 +1,3 @@
invoke_bfs -regextype emacs -quit || skip
-bfs_diff basic -regextype emacs -regex '.*/\(f+o?o?\|bar\)'
+bfs_diff basic -regextype emacs -regex '.*/\(?:f+o?o?\|bar\)'
diff --git a/tests/gnu/regextype_findutils_default.out b/tests/gnu/regextype_findutils_default.out
new file mode 100644
index 0000000..709a7ba
--- /dev/null
+++ b/tests/gnu/regextype_findutils_default.out
@@ -0,0 +1,3 @@
+/n
+weirdnames/
+weirdnames/*/m
diff --git a/tests/gnu/regextype_findutils_default.sh b/tests/gnu/regextype_findutils_default.sh
new file mode 100644
index 0000000..c870312
--- /dev/null
+++ b/tests/gnu/regextype_findutils_default.sh
@@ -0,0 +1,3 @@
+invoke_bfs -regextype findutils-default -quit || skip
+
+bfs_diff weirdnames -regextype findutils-default -regex '.*/./\(m\|n\)'
diff --git a/tests/gnu/regextype_gnu_awk.out b/tests/gnu/regextype_gnu_awk.out
new file mode 100644
index 0000000..0f32fc4
--- /dev/null
+++ b/tests/gnu/regextype_gnu_awk.out
@@ -0,0 +1,2 @@
+weirdnames/*/m
+weirdnames/[/k
diff --git a/tests/gnu/regextype_gnu_awk.sh b/tests/gnu/regextype_gnu_awk.sh
new file mode 100644
index 0000000..6b66496
--- /dev/null
+++ b/tests/gnu/regextype_gnu_awk.sh
@@ -0,0 +1,3 @@
+invoke_bfs -regextype gnu-awk -quit || skip
+
+bfs_diff weirdnames -regextype gnu-awk -regex '.*/[\[\*]/(\<.\>)'
diff --git a/tests/gnu/regextype_posix_awk.out b/tests/gnu/regextype_posix_awk.out
new file mode 100644
index 0000000..0f32fc4
--- /dev/null
+++ b/tests/gnu/regextype_posix_awk.out
@@ -0,0 +1,2 @@
+weirdnames/*/m
+weirdnames/[/k
diff --git a/tests/gnu/regextype_posix_awk.sh b/tests/gnu/regextype_posix_awk.sh
new file mode 100644
index 0000000..86377d7
--- /dev/null
+++ b/tests/gnu/regextype_posix_awk.sh
@@ -0,0 +1,3 @@
+invoke_bfs -regextype posix-awk -quit || skip
+
+bfs_diff weirdnames -regextype posix-awk -regex '.*/[\[\*]/.*'
diff --git a/tests/gnu/regextype_posix_minimal_basic.out b/tests/gnu/regextype_posix_minimal_basic.out
new file mode 100644
index 0000000..0f0971e
--- /dev/null
+++ b/tests/gnu/regextype_posix_minimal_basic.out
@@ -0,0 +1 @@
+./(
diff --git a/tests/gnu/regextype_posix_minimal_basic.sh b/tests/gnu/regextype_posix_minimal_basic.sh
new file mode 100644
index 0000000..ee324f3
--- /dev/null
+++ b/tests/gnu/regextype_posix_minimal_basic.sh
@@ -0,0 +1,2 @@
+cd weirdnames
+bfs_diff -regextype posix-minimal-basic -regex '\./\((\)'
diff --git a/tests/gnu/used.sh b/tests/gnu/used.sh
index 5e5d4e9..fe0a778 100644
--- a/tests/gnu/used.sh
+++ b/tests/gnu/used.sh
@@ -1,37 +1,18 @@
-iso8601() {
- printf '%04d-%02d-%02dT%02d:%02d:%02d\n' "$@"
-}
-
cd "$TEST"
-now=$(date '+%Y-%m-%dT%H:%M:%S')
-
-# Parse the current date
-[[ "$now" =~ ^([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})$ ]] || fail
-# Treat leading zeros as decimal, not octal
-YYYY=$((10#${BASH_REMATCH[1]}))
-MM=$((10#${BASH_REMATCH[2]}))
-DD=$((10#${BASH_REMATCH[3]}))
-hh=$((10#${BASH_REMATCH[4]}))
-mm=$((10#${BASH_REMATCH[5]}))
-ss=$((10#${BASH_REMATCH[6]}))
+now=$(epoch_time)
# -used is always false if atime < ctime
-yesterday=$(iso8601 $YYYY $MM $((DD - 1)) $hh $mm $ss)
-"$XTOUCH" -at "$yesterday" yesterday
+"$XTOUCH" -at "@$((now - 60 * 60 * 24))" yesterday
# -used rounds up
-tomorrow=$(iso8601 $YYYY $MM $DD $((hh + 1)) $mm $ss)
-"$XTOUCH" -at "$tomorrow" tomorrow
+"$XTOUCH" -at "@$((now + 60 * 60))" tomorrow
-dayafter=$(iso8601 $YYYY $MM $((DD + 1)) $((hh + 1)) $mm $ss)
-"$XTOUCH" -at "$dayafter" dayafter
+"$XTOUCH" -at "@$((now + 60 * 60 * 25))" dayafter
-nextweek=$(iso8601 $YYYY $MM $((DD + 6)) $((hh + 1)) $mm $ss)
-"$XTOUCH" -at "$nextweek" nextweek
+"$XTOUCH" -at "@$((now + 60 * 60 * (24 * 6 + 1)))" nextweek
-nextyear=$(iso8601 $((YYYY + 1)) $MM $DD $hh $mm $ss)
-"$XTOUCH" -at "$nextyear" nextyear
+"$XTOUCH" -at "@$((now + 60 * 60 * 24 * 365))" nextyear
bfs_diff -mindepth 1 \
-a -used 1 -printf '-used 1: %p\n' \
diff --git a/tests/gnu/writable.out b/tests/gnu/writable.out
index 9a5b95a..5c78ecc 100644
--- a/tests/gnu/writable.out
+++ b/tests/gnu/writable.out
@@ -1,5 +1,6 @@
perms
-perms/rw
-perms/rwx
-perms/w
-perms/wx
+perms/drwx
+perms/f-w-
+perms/f-wx
+perms/frw-
+perms/frwx
diff --git a/tests/gnu/xtype_l_loops.out b/tests/gnu/xtype_l_loops.out
new file mode 100644
index 0000000..fdaccab
--- /dev/null
+++ b/tests/gnu/xtype_l_loops.out
@@ -0,0 +1,3 @@
+loops/broken
+loops/loop
+loops/notdir
diff --git a/tests/gnu/xtype_l_loops.sh b/tests/gnu/xtype_l_loops.sh
new file mode 100644
index 0000000..6893134
--- /dev/null
+++ b/tests/gnu/xtype_l_loops.sh
@@ -0,0 +1 @@
+bfs_diff loops -xtype l
diff --git a/tests/list.c b/tests/list.c
new file mode 100644
index 0000000..e14570f
--- /dev/null
+++ b/tests/list.c
@@ -0,0 +1,97 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+#include "prelude.h"
+#include "tests.h"
+#include "list.h"
+
+struct item {
+ int n;
+ struct item *next;
+};
+
+struct list {
+ struct item *head;
+ struct item **tail;
+};
+
+static bool check_list_items(struct list *list, int *array, size_t size) {
+ struct item **cur = &list->head;
+ for (size_t i = 0; i < size; ++i) {
+ if (!bfs_check(*cur != NULL)) {
+ return false;
+ }
+ int n = (*cur)->n;
+ if (!bfs_check(n == array[i], "%d != %d", n, array[i])) {
+ return false;
+ }
+ cur = &(*cur)->next;
+ }
+
+ if (!bfs_check(*cur == NULL)) {
+ return false;
+ }
+ if (!bfs_check(list->tail == cur)) {
+ return false;
+ }
+
+ return true;
+}
+
+#define ARRAY(...) (int[]){ __VA_ARGS__ }, countof((int[]){ __VA_ARGS__ })
+#define EMPTY() NULL, 0
+
+bool check_list(void) {
+ struct list l1;
+ SLIST_INIT(&l1);
+ bfs_verify(check_list_items(&l1, EMPTY()));
+
+ struct list l2;
+ SLIST_INIT(&l2);
+ bfs_verify(check_list_items(&l2, EMPTY()));
+
+ SLIST_EXTEND(&l1, &l2);
+ bfs_verify(check_list_items(&l1, EMPTY()));
+
+ struct item i10 = { .n = 10 };
+ SLIST_APPEND(&l1, &i10);
+ bfs_verify(check_list_items(&l1, ARRAY(10)));
+
+ SLIST_EXTEND(&l1, &l2);
+ bfs_verify(check_list_items(&l1, ARRAY(10)));
+
+ SLIST_SPLICE(&l1, &l1.head, &l2);
+ bfs_verify(check_list_items(&l1, ARRAY(10)));
+
+ struct item i20 = { .n = 20 };
+ SLIST_PREPEND(&l2, &i20);
+ bfs_verify(check_list_items(&l2, ARRAY(20)));
+
+ SLIST_EXTEND(&l1, &l2);
+ bfs_verify(check_list_items(&l1, ARRAY(10, 20)));
+ bfs_verify(check_list_items(&l2, EMPTY()));
+
+ struct item i15 = { .n = 15 };
+ SLIST_APPEND(&l2, &i15);
+ SLIST_SPLICE(&l1, &i10.next, &l2);
+ bfs_verify(check_list_items(&l1, ARRAY(10, 15, 20)));
+ bfs_verify(check_list_items(&l2, EMPTY()));
+
+ SLIST_EXTEND(&l1, &l2);
+ bfs_verify(check_list_items(&l1, ARRAY(10, 15, 20)));
+
+ SLIST_SPLICE(&l1, &i10.next, &l2);
+ bfs_verify(check_list_items(&l1, ARRAY(10, 15, 20)));
+
+ SLIST_SPLICE(&l1, &l1.head, &l2);
+ bfs_verify(check_list_items(&l1, ARRAY(10, 15, 20)));
+
+ struct item i11 = { .n = 11 };
+ struct item i12 = { .n = 12 };
+ SLIST_APPEND(&l2, &i11);
+ SLIST_APPEND(&l2, &i12);
+ SLIST_SPLICE(&l1, &l1.head->next, &l2);
+ bfs_verify(check_list_items(&l1, ARRAY(10, 11, 12, 15, 20)));
+
+ return true;
+}
diff --git a/tests/main.c b/tests/main.c
index aef0583..bef2e37 100644
--- a/tests/main.c
+++ b/tests/main.c
@@ -111,6 +111,7 @@ int main(int argc, char *argv[]) {
run_test(&ctx, "bfstd", check_bfstd);
run_test(&ctx, "bit", check_bit);
run_test(&ctx, "ioq", check_ioq);
+ run_test(&ctx, "list", check_list);
run_test(&ctx, "sighook", check_sighook);
run_test(&ctx, "trie", check_trie);
run_test(&ctx, "xspawn", check_xspawn);
diff --git a/tests/posix/HL.out b/tests/posix/HL.out
new file mode 100644
index 0000000..ec9e861
--- /dev/null
+++ b/tests/posix/HL.out
@@ -0,0 +1,17 @@
+links
+links/broken
+links/deeply
+links/deeply/nested
+links/deeply/nested/broken
+links/deeply/nested/dir
+links/deeply/nested/file
+links/deeply/nested/link
+links/file
+links/hardlink
+links/notdir
+links/skip
+links/skip/broken
+links/skip/dir
+links/skip/file
+links/skip/link
+links/symlink
diff --git a/tests/posix/HL.sh b/tests/posix/HL.sh
new file mode 100644
index 0000000..1858982
--- /dev/null
+++ b/tests/posix/HL.sh
@@ -0,0 +1 @@
+bfs_diff -HL links
diff --git a/tests/posix/LH.out b/tests/posix/LH.out
new file mode 100644
index 0000000..ff635ff
--- /dev/null
+++ b/tests/posix/LH.out
@@ -0,0 +1 @@
+links/deeply/nested/dir
diff --git a/tests/posix/LH.sh b/tests/posix/LH.sh
new file mode 100644
index 0000000..ef1d980
--- /dev/null
+++ b/tests/posix/LH.sh
@@ -0,0 +1 @@
+bfs_diff -LH links/deeply/nested/dir
diff --git a/tests/posix/depth_error.out b/tests/posix/depth_error.out
index 7ed5f0d..c4f8ce4 100644
--- a/tests/posix/depth_error.out
+++ b/tests/posix/depth_error.out
@@ -1,2 +1,4 @@
-.
-./foo
+inaccessible
+inaccessible/dir
+inaccessible/file
+inaccessible/link
diff --git a/tests/posix/depth_error.sh b/tests/posix/depth_error.sh
index db414ba..9b29385 100644
--- a/tests/posix/depth_error.sh
+++ b/tests/posix/depth_error.sh
@@ -1,7 +1 @@
-cd "$TEST"
-"$XTOUCH" -p foo/bar
-
-chmod a-r foo
-defer chmod +r foo
-
-! bfs_diff . -depth
+! bfs_diff inaccessible -depth
diff --git a/tests/posix/perm_000.out b/tests/posix/perm_000.out
index 5fd30bc..b46af62 100644
--- a/tests/posix/perm_000.out
+++ b/tests/posix/perm_000.out
@@ -1 +1 @@
-perms/0
+perms/f---
diff --git a/tests/posix/perm_000_minus.out b/tests/posix/perm_000_minus.out
index d7494b8..42f2fed 100644
--- a/tests/posix/perm_000_minus.out
+++ b/tests/posix/perm_000_minus.out
@@ -1,8 +1,10 @@
perms
-perms/0
-perms/r
-perms/rw
-perms/rwx
-perms/rx
-perms/w
-perms/wx
+perms/dr-x
+perms/drwx
+perms/f---
+perms/f-w-
+perms/f-wx
+perms/fr--
+perms/fr-x
+perms/frw-
+perms/frwx
diff --git a/tests/posix/perm_222.out b/tests/posix/perm_222.out
index 1690e43..4876193 100644
--- a/tests/posix/perm_222.out
+++ b/tests/posix/perm_222.out
@@ -1 +1 @@
-perms/w
+perms/f-w-
diff --git a/tests/posix/perm_222_minus.out b/tests/posix/perm_222_minus.out
index 1690e43..4876193 100644
--- a/tests/posix/perm_222_minus.out
+++ b/tests/posix/perm_222_minus.out
@@ -1 +1 @@
-perms/w
+perms/f-w-
diff --git a/tests/posix/perm_644.out b/tests/posix/perm_644.out
index 4e64e49..4598cc1 100644
--- a/tests/posix/perm_644.out
+++ b/tests/posix/perm_644.out
@@ -1 +1 @@
-perms/rw
+perms/frw-
diff --git a/tests/posix/perm_644_minus.out b/tests/posix/perm_644_minus.out
index 2e2576b..9e041c3 100644
--- a/tests/posix/perm_644_minus.out
+++ b/tests/posix/perm_644_minus.out
@@ -1,3 +1,4 @@
perms
-perms/rw
-perms/rwx
+perms/drwx
+perms/frw-
+perms/frwx
diff --git a/tests/posix/perm_symbolic_minus.out b/tests/posix/perm_symbolic_minus.out
index 2e2576b..9e041c3 100644
--- a/tests/posix/perm_symbolic_minus.out
+++ b/tests/posix/perm_symbolic_minus.out
@@ -1,3 +1,4 @@
perms
-perms/rw
-perms/rwx
+perms/drwx
+perms/frw-
+perms/frwx
diff --git a/tests/posix/permcopy.out b/tests/posix/permcopy.out
index 4e64e49..4598cc1 100644
--- a/tests/posix/permcopy.out
+++ b/tests/posix/permcopy.out
@@ -1 +1 @@
-perms/rw
+perms/frw-
diff --git a/tests/posix/prune_error.out b/tests/posix/prune_error.out
new file mode 100644
index 0000000..436c48e
--- /dev/null
+++ b/tests/posix/prune_error.out
@@ -0,0 +1 @@
+inaccessible
diff --git a/tests/posix/prune_error.sh b/tests/posix/prune_error.sh
new file mode 100644
index 0000000..07a2523
--- /dev/null
+++ b/tests/posix/prune_error.sh
@@ -0,0 +1 @@
+! bfs_diff -L inaccessible -path '*/*' -prune -o -print
diff --git a/tests/run.sh b/tests/run.sh
index ad9c0be..115a036 100644
--- a/tests/run.sh
+++ b/tests/run.sh
@@ -5,23 +5,6 @@
## Running test cases
-# Beginning/end of line escape sequences
-BOL=$'\n'
-EOL=$'\n'
-
-# Update $EOL for the terminal size
-update_eol() {
- # Bash gets $COLUMNS from stderr, so if it's redirected use tput instead
- local cols="${COLUMNS-}"
- if [ -z "$cols" ]; then
- cols=$(tput cols 2>/dev/tty)
- fi
-
- # Put the cursor at the last column, then write a space so the next
- # character will wrap
- EOL=$'\e['"${cols}G "
-}
-
# ERR trap for tests
debug_err() {
local ret=$? line func file
@@ -64,19 +47,19 @@ run_test() {
case $ret in
0)
if ((VERBOSE_TESTS)); then
- color printf "${BOL}${GRN}[PASS]${RST} ${BLD}%s${RST}\n" "$TEST"
+ color printf "${GRN}[PASS]${RST} ${BLD}%s${RST}\n" "$TEST"
fi
;;
$EX_SKIP)
if ((VERBOSE_SKIPPED || VERBOSE_TESTS)); then
- color printf "${BOL}${CYN}[SKIP]${RST} ${BLD}%s${RST}\n" "$TEST"
+ color printf "${CYN}[SKIP]${RST} ${BLD}%s${RST}\n" "$TEST"
fi
;;
*)
if ((!VERBOSE_ERRORS)); then
cat "$TMP/$TEST.err" >&2
fi
- color printf "${BOL}${RED}[FAIL]${RST} ${BLD}%s${RST}\n" "$TEST"
+ color printf "${RED}[FAIL]${RST} ${BLD}%s${RST}\n" "$TEST"
;;
esac
@@ -112,12 +95,21 @@ reap_test() {
# Wait for a background test to finish
wait_test() {
local pid
- wait -n -ppid
- ret=$?
- if [ -z "${pid:-}" ]; then
- debug "${BASH_SOURCE[0]}" $((LINENO - 3)) "${RED}error $ret${RST}" >&$DUPERR
- exit 1
- fi
+
+ while true; do
+ wait -n -ppid
+ ret=$?
+
+ if [ "${pid:-}" ]; then
+ break
+ elif ((ret > 128)); then
+ # Interrupted by signal
+ continue
+ else
+ debug "${BASH_SOURCE[0]}" $((LINENO - 3)) "${RED}error $ret${RST}" >&$DUPERR
+ exit 1
+ fi
+ done
reap_test $ret
}
@@ -164,35 +156,24 @@ comake() {
exec {READY_PIPE}<&${COPROC[0]} {DONE_PIPE}>&${COPROC[1]}
}
-# Run all the tests
-run_tests() {
- if ((VERBOSE_TESTS)); then
- BOL=''
- elif ((COLOR_STDOUT)); then
- # Carriage return + clear line
- BOL=$'\r\e[K'
-
- # Workaround for bash 4: checkwinsize is off by default. We can turn it
- # on, but we also have to explicitly trigger a foreground job to finish
- # so that it will update the window size before we use $COLUMNS
- shopt -s checkwinsize
- (:)
-
- update_eol
- trap update_eol WINCH
+# Print the current test progess
+progress() {
+ if [ "${BAR:-}" ]; then
+ print_bar "$(printf "$@")"
+ elif ((VERBOSE_TESTS)); then
+ color printf "$@"
fi
+}
+# Run all the tests
+run_tests() {
passed=0
failed=0
skipped=0
ran=0
total=${#TEST_CASES[@]}
- if ((COLOR_STDOUT || VERBOSE_TESTS)); then
- TEST_FMT="${BOL}${YLW}[%3d%%]${RST} ${BLD}%s${RST}${EOL}"
- else
- TEST_FMT="."
- fi
+ TEST_FMT="${YLW}[%3d%%]${RST} ${BLD}%s${RST}\\n"
if ((${#MAKE[@]})); then
comake
@@ -201,6 +182,10 @@ run_tests() {
# Turn off set -e (but turn it back on in run_test)
set +e
+ if ((COLOR_STDOUT && !VERBOSE_TESTS)); then
+ show_bar
+ fi
+
for TEST in "${TEST_CASES[@]}"; do
wait_ready
if ((STOP && failed > 0)); then
@@ -208,7 +193,7 @@ run_tests() {
fi
percent=$((100 * ran / total))
- color printf "$TEST_FMT" $percent "$TEST"
+ progress "${YLW}[%3d%%]${RST} ${BLD}%s${RST}\\n" $percent "$TEST"
mkdir -p "$TMP/$TEST"
OUT="$TMP/$TEST.out"
@@ -221,7 +206,9 @@ run_tests() {
wait_test
done
- printf "${BOL}"
+ if [ "${BAR:-}" ]; then
+ hide_bar
+ fi
if ((passed > 0)); then
color printf "${GRN}[PASS]${RST} ${BLD}%3d${RST} / ${BLD}%d${RST}\n" $passed $total
@@ -253,7 +240,6 @@ skip() {
if ((VERBOSE_SKIPPED)); then
caller | {
read -r line file
- printf "${BOL}"
debug "$file" $line "" >&$DUPOUT
}
fi
@@ -424,14 +410,20 @@ make_xattrs() {
esac
}
+# Get the Unix epoch time in seconds
+epoch_time() {
+ # https://stackoverflow.com/a/12746260/502399
+ awk 'BEGIN { srand(); print srand(); }'
+}
+
## Snapshot testing
# Return value when a difference is detected
EX_DIFF=20
# Detect colored diff support
-if diff --color /dev/null /dev/null &>/dev/null; then
- DIFF="diff --color"
+if ((COLOR_STDERR)) && diff --color=always /dev/null /dev/null &>/dev/null; then
+ DIFF="diff --color=always"
else
DIFF="diff"
fi
diff --git a/tests/stddirs.sh b/tests/stddirs.sh
index e08e6bf..b3cd521 100644
--- a/tests/stddirs.sh
+++ b/tests/stddirs.sh
@@ -14,13 +14,13 @@ make_basic() {
# Creates a file+directory structure with various permissions for tests
make_perms() {
- "$XTOUCH" -p -M000 "$1/0"
- "$XTOUCH" -p -M444 "$1/r"
- "$XTOUCH" -p -M222 "$1/w"
- "$XTOUCH" -p -M644 "$1/rw"
- "$XTOUCH" -p -M555 "$1/rx"
- "$XTOUCH" -p -M311 "$1/wx"
- "$XTOUCH" -p -M755 "$1/rwx"
+ "$XTOUCH" -p -M000 "$1/f---"
+ "$XTOUCH" -p -M444 "$1/fr--"
+ "$XTOUCH" -p -M222 "$1/f-w-"
+ "$XTOUCH" -p -M644 "$1/frw-"
+ "$XTOUCH" -p -M311 "$1/f-wx"
+ "$XTOUCH" -p -M555 "$1/fr-x" "$1/dr-x/"
+ "$XTOUCH" -p -M755 "$1/frwx" "$1/drwx/"
}
# Creates a file+directory structure with various symbolic and hard links
@@ -48,6 +48,12 @@ make_loops() {
ln -s deeply/nested/loop/nested "$1/skip"
}
+# Creates a file+directory structure with inaccessible files
+make_inaccessible() {
+ "$XTOUCH" -p -M000 "$1/file" "$1/dir/"
+ ln -s dir/file "$1/link"
+}
+
# Creates a file+directory structure with varying timestamps
make_times() {
"$XTOUCH" -p -t "1991-12-14 00:00" "$1/a"
@@ -71,6 +77,9 @@ make_weirdnames() {
"$XTOUCH" -p "$1/\\/i"
"$XTOUCH" -p "$1/ /j"
"$XTOUCH" -p "$1/[/k"
+ "$XTOUCH" -p "$1/{/l"
+ "$XTOUCH" -p "$1/*/m"
+ "$XTOUCH" -p "$1/"$'\n/n'
}
# Creates a very deep directory structure for testing PATH_MAX handling
@@ -133,6 +142,7 @@ make_stddirs() {
make_perms "$TMP/perms"
make_links "$TMP/links"
make_loops "$TMP/loops"
+ make_inaccessible "$TMP/inaccessible"
make_times "$TMP/times"
make_weirdnames "$TMP/weirdnames"
make_deep "$TMP/deep"
@@ -148,5 +158,6 @@ clean_stddirs() {
fi
done
+ chmod -R +rwX "$TMP"
rm -rf "$TMP"
}
diff --git a/tests/tests.h b/tests/tests.h
index de95a49..2958fe1 100644
--- a/tests/tests.h
+++ b/tests/tests.h
@@ -27,6 +27,9 @@ bool check_bit(void);
/** I/O queue tests. */
bool check_ioq(void);
+/** Linked list tests. */
+bool check_list(void);
+
/** Signal hook tests. */
bool check_sighook(void);
diff --git a/tests/trie.c b/tests/trie.c
index 4667322..ebaae5d 100644
--- a/tests/trie.c
+++ b/tests/trie.c
@@ -8,7 +8,7 @@
#include <stdlib.h>
#include <string.h>
-const char *keys[] = {
+static const char *keys[] = {
"foo",
"bar",
"baz",
@@ -37,7 +37,7 @@ const char *keys[] = {
">>>",
};
-const size_t nkeys = countof(keys);
+static const size_t nkeys = countof(keys);
bool check_trie(void) {
bool ret = true;
diff --git a/tests/util.sh b/tests/util.sh
index 3969db5..76b72b9 100644
--- a/tests/util.sh
+++ b/tests/util.sh
@@ -59,6 +59,15 @@ stdenv() {
# Close stdin so bfs doesn't think we're interactive
# dup() the standard fds for logging even when redirected
exec </dev/null {DUPOUT}>&1 {DUPERR}>&2
+
+ # Get the ttyname
+ if [ -t $DUPOUT ]; then
+ TTY=$(tty <&$DUPOUT)
+ elif [ -t $DUPERR ]; then
+ TTY=$(tty <&$DUPERR)
+ else
+ TTY=
+ fi
}
# Drop root priviliges or bail
diff --git a/tests/xspawn.c b/tests/xspawn.c
index b1d6dc1..f48e220 100644
--- a/tests/xspawn.c
+++ b/tests/xspawn.c
@@ -179,6 +179,8 @@ static bool check_resolve(void) {
ret &= bfs_echeck(!bfs_spawn_resolve("eW6f5RM9Qi") && errno == ENOENT);
+ ret &= bfs_echeck(!bfs_spawn_resolve("bin/eW6f5RM9Qi") && errno == ENOENT);
+
return ret;
}