summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/codeql.yml2
-rw-r--r--.github/workflows/ci.yml21
-rw-r--r--.github/workflows/codecov.yml2
-rw-r--r--.github/workflows/codeql.yml2
-rw-r--r--Makefile114
-rw-r--r--README.md2
-rw-r--r--bench/bench.sh7
-rwxr-xr-xbuild/cc.sh30
-rw-r--r--build/config.mk15
-rwxr-xr-xbuild/define-if.sh4
-rw-r--r--build/deps.mk18
-rwxr-xr-xbuild/embed.sh2
-rwxr-xr-xbuild/flags-if.sh28
-rw-r--r--build/flags.mk67
-rw-r--r--build/flags/Wformat.c9
-rw-r--r--build/flags/Wimplicit-fallthrough.c9
-rw-r--r--build/flags/Wimplicit.c9
-rw-r--r--build/flags/Wmissing-decls.c9
-rw-r--r--build/flags/Wmissing-var-decls.c9
-rw-r--r--build/flags/Wshadow.c9
-rw-r--r--build/flags/Wsign-compare.c9
-rw-r--r--build/flags/Wstrict-prototypes.c9
-rw-r--r--build/flags/bind-now.c8
-rw-r--r--build/flags/deps.c8
-rw-r--r--build/has/_Fork.c8
-rw-r--r--build/has/tcgetwinsize.c9
-rw-r--r--build/header.mk22
-rwxr-xr-xbuild/pkgconf.sh10
-rw-r--r--build/prelude.mk44
-rwxr-xr-xbuild/version.sh18
-rw-r--r--completions/bfs.bash1
-rw-r--r--completions/bfs.fish3
-rw-r--r--completions/bfs.zsh3
-rwxr-xr-xconfigure18
-rw-r--r--docs/BUILDING.md8
-rw-r--r--docs/CHANGELOG.md97
-rw-r--r--docs/SECURITY.md126
-rw-r--r--docs/bfs.129
-rw-r--r--src/alloc.c6
-rw-r--r--src/alloc.h22
-rw-r--r--src/atomic.h6
-rw-r--r--src/bar.c57
-rw-r--r--src/bfs.h214
-rw-r--r--src/bfstd.c20
-rw-r--r--src/bfstd.h19
-rw-r--r--src/bftw.c10
-rw-r--r--src/bftw.h1
-rw-r--r--src/bit.h5
-rw-r--r--src/color.c204
-rw-r--r--src/color.h7
-rw-r--r--src/ctx.c10
-rw-r--r--src/ctx.h17
-rw-r--r--src/diag.c10
-rw-r--r--src/diag.h55
-rw-r--r--src/dir.c4
-rw-r--r--src/dir.h3
-rw-r--r--src/dstring.c20
-rw-r--r--src/dstring.h26
-rw-r--r--src/eval.c56
-rw-r--r--src/eval.h2
-rw-r--r--src/exec.c6
-rw-r--r--src/expr.c7
-rw-r--r--src/expr.h27
-rw-r--r--src/fsade.c26
-rw-r--r--src/fsade.h6
-rw-r--r--src/ioq.c33
-rw-r--r--src/ioq.h2
-rw-r--r--src/list.h1
-rw-r--r--src/main.c5
-rw-r--r--src/mtab.c6
-rw-r--r--src/mtab.h2
-rw-r--r--src/opt.c156
-rw-r--r--src/parse.c526
-rw-r--r--src/prelude.h402
-rw-r--r--src/printf.c12
-rw-r--r--src/pwcache.c5
-rw-r--r--src/sanity.h19
-rw-r--r--src/sighook.c295
-rw-r--r--src/stat.c4
-rw-r--r--src/stat.h5
-rw-r--r--src/thread.c3
-rw-r--r--src/thread.h9
-rw-r--r--src/trie.c24
-rw-r--r--src/trie.h1
-rw-r--r--src/typo.c3
-rw-r--r--src/version.c2
-rw-r--r--src/xregex.c4
-rw-r--r--src/xspawn.c84
-rw-r--r--src/xspawn.h11
-rw-r--r--src/xtime.c21
-rw-r--r--src/xtime.h10
-rw-r--r--tests/alloc.c26
-rw-r--r--tests/bfs/L_noerror.out11
-rw-r--r--tests/bfs/L_noerror.sh1
-rw-r--r--tests/bfs/color_ext_case_flipflop.out27
-rw-r--r--tests/bfs/color_ext_case_flipflop.sh1
-rw-r--r--tests/bfs/color_ext_case_priority.out27
-rw-r--r--tests/bfs/color_ext_case_priority.sh1
-rw-r--r--tests/bfs/noerror.out4
-rw-r--r--tests/bfs/noerror.sh1
-rw-r--r--tests/bfs/noerror_nowarn.sh2
-rw-r--r--tests/bfs/noerror_warn.sh2
-rw-r--r--tests/bfs/perm_leading_plus_symbolic.out3
-rw-r--r--tests/bfs/warn_without_noerror.sh2
-rw-r--r--tests/bfstd.c105
-rw-r--r--tests/bit.c185
-rw-r--r--tests/bsd/perm_000_plus.out37
-rw-r--r--tests/bsd/perm_222_plus.out24
-rw-r--r--tests/bsd/perm_644_plus.out33
-rw-r--r--tests/common/L_mount.out5
-rw-r--r--tests/common/amin.sh2
-rw-r--r--tests/common/gid.out (renamed from tests/gnu/gid.out)0
-rw-r--r--tests/common/gid.sh (renamed from tests/gnu/gid.sh)0
-rw-r--r--tests/common/gid_invalid_id.sh1
-rw-r--r--tests/common/gid_invalid_name.sh1
-rw-r--r--tests/common/gid_minus.out (renamed from tests/gnu/gid_minus.out)0
-rw-r--r--tests/common/gid_minus.sh (renamed from tests/gnu/gid_minus.sh)0
-rw-r--r--tests/common/gid_minus_plus.out (renamed from tests/gnu/gid_minus_plus.out)0
-rw-r--r--tests/common/gid_minus_plus.sh (renamed from tests/gnu/gid_minus_plus.sh)0
-rw-r--r--tests/common/gid_plus.out (renamed from tests/gnu/gid_plus.out)0
-rw-r--r--tests/common/gid_plus.sh (renamed from tests/gnu/gid_plus.sh)0
-rw-r--r--tests/common/gid_plus_plus.out (renamed from tests/gnu/gid_plus_plus.out)0
-rw-r--r--tests/common/gid_plus_plus.sh (renamed from tests/gnu/gid_plus_plus.sh)0
-rw-r--r--tests/common/iname.sh2
-rw-r--r--tests/common/mmin.sh2
-rw-r--r--tests/common/name_slash.sh1
-rw-r--r--tests/common/name_slashes.sh1
-rw-r--r--tests/common/uid.out (renamed from tests/gnu/uid.out)0
-rw-r--r--tests/common/uid.sh (renamed from tests/gnu/uid.sh)0
-rw-r--r--tests/common/uid_invalid_id.sh1
-rw-r--r--tests/common/uid_invalid_name.sh1
-rw-r--r--tests/common/uid_minus.out (renamed from tests/gnu/uid_minus.out)0
-rw-r--r--tests/common/uid_minus.sh (renamed from tests/gnu/uid_minus.sh)0
-rw-r--r--tests/common/uid_minus_plus.out (renamed from tests/gnu/uid_minus_plus.out)0
-rw-r--r--tests/common/uid_minus_plus.sh (renamed from tests/gnu/uid_minus_plus.sh)0
-rw-r--r--tests/common/uid_plus.out (renamed from tests/gnu/uid_plus.out)0
-rw-r--r--tests/common/uid_plus.sh (renamed from tests/gnu/uid_plus.sh)0
-rw-r--r--tests/common/uid_plus_plus.out (renamed from tests/gnu/uid_plus_plus.out)0
-rw-r--r--tests/common/uid_plus_plus.sh (renamed from tests/gnu/uid_plus_plus.sh)0
-rw-r--r--tests/gnu/executable.out23
-rw-r--r--tests/gnu/fprint_unreached_error.sh (renamed from tests/gnu/fprint_noerror.sh)0
-rw-r--r--tests/gnu/not_comma.out34
-rw-r--r--tests/gnu/not_comma.sh2
-rw-r--r--tests/gnu/perm_000_slash.out37
-rw-r--r--tests/gnu/perm_222_slash.out24
-rw-r--r--tests/gnu/perm_644_slash.out33
-rw-r--r--tests/gnu/perm_leading_plus_symbolic_slash.out35
-rw-r--r--tests/gnu/perm_symbolic_slash.out31
-rw-r--r--tests/gnu/readable.out24
-rw-r--r--tests/gnu/writable.out24
-rw-r--r--tests/ioq.c10
-rw-r--r--tests/list.c10
-rw-r--r--tests/main.c22
-rw-r--r--tests/mksock.c1
-rw-r--r--tests/posix/L_mount.out2
-rw-r--r--tests/posix/L_mount.sh (renamed from tests/common/L_mount.sh)0
-rw-r--r--tests/posix/atime.out (renamed from tests/common/atime.out)0
-rw-r--r--tests/posix/atime.sh (renamed from tests/common/atime.sh)8
-rw-r--r--tests/posix/group_invalid_id.sh1
-rw-r--r--tests/posix/group_invalid_name.sh1
-rw-r--r--tests/posix/iname.out (renamed from tests/common/iname.out)0
-rw-r--r--tests/posix/iname.sh1
-rw-r--r--tests/posix/mount.out (renamed from tests/common/mount.out)1
-rw-r--r--tests/posix/mount.sh (renamed from tests/common/mount.sh)0
-rw-r--r--tests/posix/mtime.out (renamed from tests/common/mtime.out)0
-rw-r--r--tests/posix/mtime.sh (renamed from tests/common/mtime.sh)8
-rw-r--r--tests/posix/name_slash.out (renamed from tests/common/name_slash.out)0
-rw-r--r--tests/posix/name_slash.sh1
-rw-r--r--tests/posix/name_slashes.out (renamed from tests/common/name_slashes.out)0
-rw-r--r--tests/posix/name_slashes.sh1
-rw-r--r--tests/posix/perm_000.out2
-rw-r--r--tests/posix/perm_000_minus.out37
-rw-r--r--tests/posix/perm_222.out2
-rw-r--r--tests/posix/perm_222_minus.out6
-rw-r--r--tests/posix/perm_644.out2
-rw-r--r--tests/posix/perm_644_minus.out12
-rw-r--r--tests/posix/perm_leading_plus_symbolic_minus.out7
-rw-r--r--tests/posix/perm_leading_plus_umask.out10
-rw-r--r--tests/posix/perm_leading_plus_umask.sh3
-rw-r--r--tests/posix/perm_symbolic_minus.out12
-rw-r--r--tests/posix/permcopy.out2
-rw-r--r--tests/posix/print0.out (renamed from tests/gnu/print0.out)bin16 -> 16 bytes
-rw-r--r--tests/posix/print0.sh (renamed from tests/gnu/print0.sh)0
-rw-r--r--tests/posix/user_invalid_id.sh1
-rw-r--r--tests/posix/user_invalid_name.sh1
-rw-r--r--tests/run.sh9
-rw-r--r--tests/sighook.c121
-rw-r--r--tests/stddirs.sh32
-rw-r--r--tests/tests.h33
-rw-r--r--tests/trie.c53
-rw-r--r--tests/xspawn.c102
-rw-r--r--tests/xtime.c123
-rw-r--r--tests/xtouch.c6
193 files changed, 2805 insertions, 1903 deletions
diff --git a/.github/codeql.yml b/.github/codeql.yml
index b61c30e..a4271ec 100644
--- a/.github/codeql.yml
+++ b/.github/codeql.yml
@@ -9,3 +9,5 @@ query-filters:
id: cpp/loop-variable-changed
- exclude:
id: cpp/poorly-documented-function
+ - exclude:
+ id: cpp/constant-comparison
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 8011224..20d3797 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -6,7 +6,7 @@ jobs:
linux:
name: Linux
- runs-on: ubuntu-22.04
+ runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
@@ -17,6 +17,7 @@ jobs:
sudo apt-get update -y
sudo apt-get install -y \
expect \
+ mandoc \
gcc-multilib \
libgcc-s1:i386 \
acl \
@@ -68,16 +69,16 @@ jobs:
freebsd:
name: FreeBSD
- runs-on: ubuntu-22.04
+ runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Run tests
- uses: cross-platform-actions/action@v0.24.0
+ uses: cross-platform-actions/action@v0.25.0
with:
operating_system: freebsd
- version: "14.0"
+ version: "14.1"
run: |
sudo pkg install -y \
@@ -97,13 +98,13 @@ jobs:
openbsd:
name: OpenBSD
- runs-on: ubuntu-22.04
+ runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Run tests
- uses: cross-platform-actions/action@v0.24.0
+ uses: cross-platform-actions/action@v0.25.0
with:
operating_system: openbsd
version: "7.5"
@@ -126,13 +127,13 @@ jobs:
netbsd:
name: NetBSD
- runs-on: ubuntu-22.04
+ runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Run tests
- uses: cross-platform-actions/action@v0.24.0
+ uses: cross-platform-actions/action@v0.25.0
with:
operating_system: netbsd
version: "10.0"
@@ -156,7 +157,7 @@ jobs:
dragonflybsd:
name: DragonFly BSD
- runs-on: ubuntu-22.04
+ runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
@@ -192,7 +193,7 @@ jobs:
omnios:
name: OmniOS
- runs-on: ubuntu-22.04
+ runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml
index 6aaace6..e2df25a 100644
--- a/.github/workflows/codecov.yml
+++ b/.github/workflows/codecov.yml
@@ -4,7 +4,7 @@ on: [push]
jobs:
build:
- runs-on: ubuntu-22.04
+ runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index a0b8fe3..1f2041c 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -13,7 +13,7 @@ on:
jobs:
analyze:
name: Analyze
- runs-on: ubuntu-22.04
+ runs-on: ubuntu-24.04
permissions:
actions: read
contents: read
diff --git a/Makefile b/Makefile
index d21cbb3..85029d9 100644
--- a/Makefile
+++ b/Makefile
@@ -27,10 +27,11 @@ asan lsan msan tsan ubsan gcov lint release::
# Print an error if `make` is run before `./configure`
gen/config.mk::
- @if ! [ -e $@ ]; then \
+ if ! [ -e $@ ]; then \
printf 'error: You must run `./configure` before `%s`.\n' "${MAKE}" >&2; \
false; \
fi
+.SILENT: gen/config.mk
## Build phase (`make`)
@@ -49,8 +50,43 @@ BINS := \
all: ${BINS}
.PHONY: all
+# All object files except the entry point
+LIBBFS := \
+ obj/src/alloc.o \
+ obj/src/bar.o \
+ obj/src/bfstd.o \
+ obj/src/bftw.o \
+ obj/src/color.o \
+ obj/src/ctx.o \
+ obj/src/diag.o \
+ obj/src/dir.o \
+ obj/src/dstring.o \
+ obj/src/eval.o \
+ obj/src/exec.o \
+ obj/src/expr.o \
+ obj/src/fsade.o \
+ obj/src/ioq.o \
+ obj/src/mtab.o \
+ obj/src/opt.o \
+ obj/src/parse.o \
+ obj/src/printf.o \
+ obj/src/pwcache.o \
+ obj/src/sighook.o \
+ obj/src/stat.o \
+ 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
+
+# All object files
+OBJS := ${LIBBFS}
+
# The main binary
-bin/bfs: ${LIBBFS} obj/src/main.o
+bin/bfs: obj/src/main.o ${LIBBFS}
+OBJS += obj/src/main.o
${BINS}:
@${MKDIR} ${@D}
@@ -60,24 +96,15 @@ ${BINS}:
# Get the .c file for a .o file
CSRC = ${@:obj/%.o=%.c}
-# Rebuild when the configuration changes
-${OBJS}: gen/config.mk
- @${MKDIR} ${@D}
- ${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.i.new::
- @${MKDIR} ${@D}
- @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.3.1"; \
- fi | tr -d '\n' | build/embed.sh >$@
+ ${MKDIR} ${@D}
+ build/version.sh | tr -d '\n' | build/embed.sh >$@
+.SILENT: gen/version.i.new
gen/version.i: gen/version.i.new
- @test -e $@ && cmp -s $@ ${.ALLSRC} && ${RM} ${.ALLSRC} || mv ${.ALLSRC} $@
+ test -e $@ && cmp -s $@ ${.ALLSRC} && ${RM} ${.ALLSRC} || mv ${.ALLSRC} $@
+.SILENT: gen/version.i
obj/src/version.o: gen/version.i
@@ -106,12 +133,24 @@ unit-tests: ${UTEST_BINS}
${MSG} "[TEST] tests/units" bin/tests/units
.PHONY: unit-tests
-bin/tests/units: \
- ${UNIT_OBJS} \
- ${LIBBFS}
-
-bin/tests/xspawnee: \
- obj/tests/xspawnee.o
+# Unit test objects
+UNIT_OBJS := \
+ obj/tests/alloc.o \
+ 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 \
+ obj/tests/xspawn.o \
+ obj/tests/xtime.o
+
+bin/tests/units: ${UNIT_OBJS} ${LIBBFS}
+OBJS += ${UNIT_OBJS}
+
+bin/tests/xspawnee: obj/tests/xspawnee.o
+OBJS += obj/tests/xspawnee.o
# The different flag combinations we check
INTEGRATIONS := default dfs ids eds j1 j2 j3 s
@@ -136,13 +175,11 @@ check-j1 check-j2 check-j3 check-s: bin/bfs ${ITEST_BINS}
integration-tests: ${INTEGRATION_TESTS}
.PHONY: integration-tests
-bin/tests/mksock: \
- obj/tests/mksock.o \
- ${LIBBFS}
+bin/tests/mksock: obj/tests/mksock.o ${LIBBFS}
+OBJS += obj/tests/mksock.o
-bin/tests/xtouch: \
- obj/tests/xtouch.o \
- ${LIBBFS}
+bin/tests/xtouch: obj/tests/xtouch.o ${LIBBFS}
+OBJS += obj/tests/xtouch.o
# `make distcheck` configurations
DISTCHECKS := \
@@ -159,6 +196,8 @@ distcheck:
@+test "$$(uname)" = FreeBSD || ${MAKE} distcheck-tsan
@+test "$$(uname)-$$(uname -m)" != Linux-x86_64 || ${MAKE} distcheck-m32
@+${MAKE} distcheck-release
+ @+${MAKE} -C distcheck-release check-install
+ @+test "$$(uname)" != Linux || ${MAKE} check-man
.PHONY: distcheck
# Per-distcheck configuration
@@ -171,9 +210,19 @@ DISTCHECK_CONFIG_release := --enable-release
${DISTCHECKS}::
@${MKDIR} $@
@+cd $@ \
- && ../configure ${DISTCHECK_CONFIG_${@:distcheck-%=%}} \
+ && ../configure MAKE="${MAKE}" ${DISTCHECK_CONFIG_${@:distcheck-%=%}} \
&& ${MAKE} check TEST_FLAGS="--sudo --verbose=skipped"
+## Automatic dependency tracking
+
+# Rebuild when the configuration changes
+${OBJS}: gen/config.mk
+ @${MKDIR} ${@D}
+ ${MSG} "[ CC ] ${CSRC}" ${CC} ${_CPPFLAGS} ${_CFLAGS} -c ${CSRC} -o $@
+
+# Include any generated dependency files
+-include ${OBJS:.o=.d}
+
## Packaging (`make install`)
DEST_PREFIX := ${DESTDIR}${PREFIX}
@@ -217,8 +266,9 @@ check-install::
# Check man page markup
check-man::
- ${MSG} "[LINT] docs/bfs.1" \
- groff -man -rCHECKSTYLE=3 -ww -b -z docs/bfs.1
+ ${MSG} "[LINT] docs/bfs.1"
+ ${Q}groff -man -rCHECKSTYLE=3 -ww -b -z docs/bfs.1
+ ${Q}mandoc -Tlint -Wwarning docs/bfs.1
## Cleanup (`make clean`)
@@ -229,6 +279,6 @@ clean::
# Clean everything, including generated files
distclean: clean
- ${MSG} "[ RM ] gen" \
+ ${MSG} "[ RM ] gen distcheck-*" \
${RM} -r gen ${DISTCHECKS}
.PHONY: distclean
diff --git a/README.md b/README.md
index b9aad82..ad1fd09 100644
--- a/README.md
+++ b/README.md
@@ -34,7 +34,7 @@ It is otherwise compatible with many versions of `find`, including
**[POSIX]   •   [GNU]   •   [FreeBSD]   •   [OpenBSD]   •   [NetBSD]   •   [macOS]**
-[POSIX]: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/find.html
+[POSIX]: https://pubs.opengroup.org/onlinepubs/9799919799/utilities/find.html
[GNU]: https://www.gnu.org/software/findutils/
[FreeBSD]: https://www.freebsd.org/cgi/man.cgi?find(1)
[OpenBSD]: https://man.openbsd.org/find.1
diff --git a/bench/bench.sh b/bench/bench.sh
index cf1ae49..b6682a2 100644
--- a/bench/bench.sh
+++ b/bench/bench.sh
@@ -269,12 +269,7 @@ setup() {
)
done
- # $SETUP_DIR contains `:` so it won't work in $PATH
- # Work around this with a symlink
- tmp=$(as-user mktemp)
- as-user ln -sf "$bin" "$tmp"
- defer rm "$tmp"
- export PATH="$tmp:$PATH"
+ export PATH="$bin:$PATH"
fi
export_array BFS
diff --git a/build/cc.sh b/build/cc.sh
index 45d51ca..e1d2b0b 100755
--- a/build/cc.sh
+++ b/build/cc.sh
@@ -3,14 +3,32 @@
# Copyright © Tavian Barnes <tavianator@tavianator.com>
# SPDX-License-Identifier: 0BSD
-# Run the compiler and check if it succeeded
+# Run the compiler and check if it succeeded. Usage:
+#
+# $ build/cc.sh [-q] path/to/file.c [-flags -Warnings ...]
set -eu
-TMP=$(mktemp)
-trap 'rm -f "$TMP"' EXIT
+QUIET=
+if [ "$1" = "-q" ]; then
+ QUIET=y
+ shift
+fi
-(
+# Source files can specify their own flags with lines like
+#
+# /// _CFLAGS += -Wmissing-variable-declarations
+#
+# which will be added to the makefile on success, or lines like
+#
+# /// -Werror
+#
+# which are just used for the current file.
+EXTRA_FLAGS=$(sed -n '\|^///|{s|^/// ||; s|[^=]*= ||; p;}' "$1")
+
+# Without -q, print the executed command for config.log
+if [ -z "$QUIET" ]; then
set -x
- $XCC $XCPPFLAGS $XCFLAGS $XLDFLAGS "$@" $XLDLIBS -o "$TMP"
-)
+fi
+
+$XCC $XCPPFLAGS $XCFLAGS $XLDFLAGS "$@" $EXTRA_FLAGS $XLDLIBS
diff --git a/build/config.mk b/build/config.mk
index 24873ec..6296168 100644
--- a/build/config.mk
+++ b/build/config.mk
@@ -7,21 +7,20 @@ include build/prelude.mk
include build/exports.mk
# All configuration steps
-config: gen/config.mk gen/config.h
+config: gen/config.mk
.PHONY: config
# Makefile fragments generated by `./configure`
MKS := \
gen/vars.mk \
gen/flags.mk \
- gen/deps.mk \
gen/pkgs.mk
# The main configuration file, which includes the others
-gen/config.mk: ${MKS}
+gen/config.mk: ${MKS} gen/config.h
${MSG} "[ GEN] $@"
@printf '# %s\n' "$@" >$@
- @printf 'include %s\n' ${.ALLSRC} >>$@
+ @printf 'include %s\n' ${MKS} >>$@
${VCAT} gen/config.mk
.PHONY: gen/config.mk
@@ -38,6 +37,7 @@ gen/vars.mk::
@printf 'MKDIR := %s\n' "$$XMKDIR" >>$@
@printf 'PKG_CONFIG := %s\n' "$$XPKG_CONFIG" >>$@
@printf 'RM := %s\n' "$$XRM" >>$@
+ @test -z "$$VERSION" || printf 'export VERSION=%s\n' "$$VERSION" >>$@
${VCAT} $@
# Sets the build flags. This depends on vars.mk and uses a recursive make so
@@ -46,17 +46,12 @@ gen/flags.mk: gen/vars.mk
@+XMAKEFLAGS="$$MAKEFLAGS" ${MAKE} -sf build/flags.mk $@
.PHONY: gen/flags.mk
-# Check for dependency generation support
-gen/deps.mk: gen/flags.mk
- @+XMAKEFLAGS="$$MAKEFLAGS" ${MAKE} -sf build/deps.mk $@
-.PHONY: gen/deps.mk
-
# Auto-detect dependencies and their build flags
gen/pkgs.mk: gen/flags.mk
@+XMAKEFLAGS="$$MAKEFLAGS" ${MAKE} -sf build/pkgs.mk $@
.PHONY: gen/pkgs.mk
# Compile-time feature detection
-gen/config.h: gen/config.mk
+gen/config.h: gen/pkgs.mk
@+XMAKEFLAGS="$$MAKEFLAGS" ${MAKE} -sf build/header.mk $@
.PHONY: gen/config.h
diff --git a/build/define-if.sh b/build/define-if.sh
index 295ead8..204cfa4 100755
--- a/build/define-if.sh
+++ b/build/define-if.sh
@@ -7,9 +7,7 @@
set -eu
-SLUG="${1#build/}"
-SLUG="${SLUG%.c}"
-MACRO="BFS_$(printf '%s' "$SLUG" | tr '/a-z-' '_A-Z_')"
+MACRO=$(printf 'BFS_%s' "$1" | tr '/a-z-' '_A-Z_')
shift
if "$@"; then
diff --git a/build/deps.mk b/build/deps.mk
deleted file mode 100644
index d382f5d..0000000
--- a/build/deps.mk
+++ /dev/null
@@ -1,18 +0,0 @@
-# Copyright © Tavian Barnes <tavianator@tavianator.com>
-# SPDX-License-Identifier: 0BSD
-
-# Makefile that generates gen/deps.mk
-
-include build/prelude.mk
-include gen/vars.mk
-include gen/flags.mk
-include build/exports.mk
-
-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'; \
- fi >>$@ 2>$@.log
- ${VCAT} $@
- @printf -- '-include %s\n' ${OBJS:.o=.d} >>$@
diff --git a/build/embed.sh b/build/embed.sh
index 8d7d0f1..c0744f6 100755
--- a/build/embed.sh
+++ b/build/embed.sh
@@ -9,4 +9,4 @@ set -eu
{ cat; printf '\0'; } \
| od -An -tx1 \
- | sed 's/\([^ ][^ ]*\)/0x\1,/g'
+ | sed 's/[^ ][^ ]*/0x&,/g'
diff --git a/build/flags-if.sh b/build/flags-if.sh
new file mode 100755
index 0000000..81eb345
--- /dev/null
+++ b/build/flags-if.sh
@@ -0,0 +1,28 @@
+#!/bin/sh
+
+# Copyright © Tavian Barnes <tavianator@tavianator.com>
+# SPDX-License-Identifier: 0BSD
+
+# Add flags to a makefile if a build succeeds
+
+set -eu
+
+build/cc.sh "$@" || exit 1
+
+# If the build succeeded, print any lines like
+#
+# /// _CFLAGS += -foo
+#
+# (unless they're already set)
+OLD_FLAGS="$XCC $XCPPFLAGS $XCFLAGS $XLDFLAGS $XLDLIBS"
+
+while IFS="" read -r line; do
+ case "$line" in
+ ///*=*)
+ flag="${line#*= }"
+ if [ "${OLD_FLAGS#*"$flag"}" = "$OLD_FLAGS" ]; then
+ printf '%s\n' "${line#/// }"
+ fi
+ ;;
+ esac
+done <"$1"
diff --git a/build/flags.mk b/build/flags.mk
index c50b077..1421ce6 100644
--- a/build/flags.mk
+++ b/build/flags.mk
@@ -7,19 +7,7 @@ include build/prelude.mk
include gen/vars.mk
# Internal flags
-_CPPFLAGS := \
- -Isrc \
- -Igen \
- -D__EXTENSIONS__ \
- -D_ATFILE_SOURCE \
- -D_BSD_SOURCE \
- -D_DARWIN_C_SOURCE \
- -D_DEFAULT_SOURCE \
- -D_GNU_SOURCE \
- -D_POSIX_PTHREAD_SEMANTICS \
- -D_FILE_OFFSET_BITS=64 \
- -D_TIME_BITS=64
-
+_CPPFLAGS := -Isrc -Igen -include src/prelude.h
_CFLAGS := -std=c17 -pthread
_LDFLAGS :=
_LDLIBS :=
@@ -41,10 +29,6 @@ _GCOV := ${TRUTHY,${GCOV}}
_LINT := ${TRUTHY,${LINT}}
_RELEASE := ${TRUTHY,${RELEASE}}
-# https://github.com/google/sanitizers/issues/342
-TSAN_CPPFLAGS,y := -DBFS_USE_TARGET_CLONES=0
-_CPPFLAGS += ${TSAN_CPPFLAGS,${_TSAN}}
-
ASAN_CFLAGS,y := -fsanitize=address
LSAN_CFLAGS,y := -fsanitize=leak
MSAN_CFLAGS,y := -fsanitize=memory -fsanitize-memory-track-origins
@@ -83,16 +67,7 @@ _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
+CFLAGS ?= -g -Wall
# Add the configurable flags last so they can override ours
_CPPFLAGS += ${CPPFLAGS} ${EXTRA_CPPFLAGS}
@@ -103,7 +78,20 @@ _LDLIBS := ${LDLIBS} ${EXTRA_LDLIBS} ${_LDLIBS}
include build/exports.mk
-gen/flags.mk::
+# Conditionally-supported flags
+AUTO_FLAGS := \
+ gen/flags/Wformat.mk \
+ gen/flags/Wimplicit-fallthrough.mk \
+ gen/flags/Wimplicit.mk \
+ gen/flags/Wmissing-decls.mk \
+ gen/flags/Wmissing-var-decls.mk \
+ gen/flags/Wshadow.mk \
+ gen/flags/Wsign-compare.mk \
+ gen/flags/Wstrict-prototypes.mk \
+ gen/flags/bind-now.mk \
+ gen/flags/deps.mk
+
+gen/flags.mk: ${AUTO_FLAGS}
${MSG} "[ GEN] $@"
@printf '# %s\n' "$@" >$@
@printf '_CPPFLAGS := %s\n' "$$XCPPFLAGS" >>$@
@@ -112,4 +100,27 @@ gen/flags.mk::
@printf '_LDLIBS := %s\n' "$$XLDLIBS" >>$@
@printf 'NOLIBS := %s\n' "$$XNOLIBS" >>$@
@test "${OS}-${SAN}" != FreeBSD-y || printf 'POSTLINK = elfctl -e +noaslr $$@\n' >>$@
+ @cat ${.ALLSRC} >>$@
+ @cat ${.ALLSRC:%=%.log} >gen/flags.log
${VCAT} $@
+.PHONY: gen/flags.mk
+
+# Check that the C compiler works at all
+cc::
+ @build/cc.sh -q build/empty.c -o gen/.cc.out; \
+ ret=$$?; \
+ build/msg-if.sh "[ CC ] build/empty.c" test $$ret -eq 0; \
+ exit $$ret
+
+# The short name of the config test
+SLUG = ${@:gen/%.mk=%}
+# The source file to build
+CSRC = build/${SLUG}.c
+# The hidden output file name
+OUT = ${SLUG:flags/%=gen/flags/.%.out}
+
+${AUTO_FLAGS}: cc
+ @${MKDIR} ${@D}
+ @build/flags-if.sh ${CSRC} -o ${OUT} >$@ 2>$@.log; \
+ build/msg-if.sh "[ CC ] ${SLUG}.c" test $$? -eq 0
+.PHONY: ${AUTO_FLAGS}
diff --git a/build/flags/Wformat.c b/build/flags/Wformat.c
new file mode 100644
index 0000000..287b209
--- /dev/null
+++ b/build/flags/Wformat.c
@@ -0,0 +1,9 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+/// _CFLAGS += -Wformat=2
+/// -Werror
+
+int main(void) {
+ return 0;
+}
diff --git a/build/flags/Wimplicit-fallthrough.c b/build/flags/Wimplicit-fallthrough.c
new file mode 100644
index 0000000..c32058d
--- /dev/null
+++ b/build/flags/Wimplicit-fallthrough.c
@@ -0,0 +1,9 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+/// _CFLAGS += -Wimplicit-fallthrough
+/// -Werror
+
+int main(void) {
+ return 0;
+}
diff --git a/build/flags/Wimplicit.c b/build/flags/Wimplicit.c
new file mode 100644
index 0000000..3ea2b90
--- /dev/null
+++ b/build/flags/Wimplicit.c
@@ -0,0 +1,9 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+/// _CFLAGS += -Werror=implicit
+/// -Werror
+
+int main(void) {
+ return 0;
+}
diff --git a/build/flags/Wmissing-decls.c b/build/flags/Wmissing-decls.c
new file mode 100644
index 0000000..5ef3e96
--- /dev/null
+++ b/build/flags/Wmissing-decls.c
@@ -0,0 +1,9 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+/// _CFLAGS += -Wmissing-declarations
+/// -Werror
+
+int main(void) {
+ return 0;
+}
diff --git a/build/flags/Wmissing-var-decls.c b/build/flags/Wmissing-var-decls.c
new file mode 100644
index 0000000..5c20cc6
--- /dev/null
+++ b/build/flags/Wmissing-var-decls.c
@@ -0,0 +1,9 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+/// _CFLAGS += -Wmissing-variable-declarations
+/// -Werror
+
+int main(void) {
+ return 0;
+}
diff --git a/build/flags/Wshadow.c b/build/flags/Wshadow.c
new file mode 100644
index 0000000..28f6ef3
--- /dev/null
+++ b/build/flags/Wshadow.c
@@ -0,0 +1,9 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+/// _CFLAGS += -Wshadow
+/// -Werror
+
+int main(void) {
+ return 0;
+}
diff --git a/build/flags/Wsign-compare.c b/build/flags/Wsign-compare.c
new file mode 100644
index 0000000..f083083
--- /dev/null
+++ b/build/flags/Wsign-compare.c
@@ -0,0 +1,9 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+/// _CFLAGS += -Wsign-compare
+/// -Werror
+
+int main(void) {
+ return 0;
+}
diff --git a/build/flags/Wstrict-prototypes.c b/build/flags/Wstrict-prototypes.c
new file mode 100644
index 0000000..9614bee
--- /dev/null
+++ b/build/flags/Wstrict-prototypes.c
@@ -0,0 +1,9 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+/// _CFLAGS += -Wstrict-prototypes
+/// -Werror
+
+int main(void) {
+ return 0;
+}
diff --git a/build/flags/bind-now.c b/build/flags/bind-now.c
new file mode 100644
index 0000000..08bb4f2
--- /dev/null
+++ b/build/flags/bind-now.c
@@ -0,0 +1,8 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+/// _LDFLAGS += -Wl,-z,now
+
+int main(void) {
+ return 0;
+}
diff --git a/build/flags/deps.c b/build/flags/deps.c
new file mode 100644
index 0000000..1c8c309
--- /dev/null
+++ b/build/flags/deps.c
@@ -0,0 +1,8 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+/// _CPPFLAGS += -MD -MP
+
+int main(void) {
+ return 0;
+}
diff --git a/build/has/_Fork.c b/build/has/_Fork.c
new file mode 100644
index 0000000..4d7fbd3
--- /dev/null
+++ b/build/has/_Fork.c
@@ -0,0 +1,8 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+#include <unistd.h>
+
+int main(void) {
+ return _Fork();
+}
diff --git a/build/has/tcgetwinsize.c b/build/has/tcgetwinsize.c
new file mode 100644
index 0000000..d25d12b
--- /dev/null
+++ b/build/has/tcgetwinsize.c
@@ -0,0 +1,9 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+#include <termios.h>
+
+int main(void) {
+ struct winsize ws;
+ return tcgetwinsize(0, &ws);
+}
diff --git a/build/header.mk b/build/header.mk
index 632afb6..abec00f 100644
--- a/build/header.mk
+++ b/build/header.mk
@@ -4,12 +4,15 @@
# Makefile that generates gen/config.h
include build/prelude.mk
-include gen/config.mk
+include gen/vars.mk
+include gen/flags.mk
+include gen/pkgs.mk
include build/exports.mk
# All header fragments we generate
HEADERS := \
gen/has/--st-birthtim.h \
+ gen/has/_Fork.h \
gen/has/acl-get-entry.h \
gen/has/acl-get-file.h \
gen/has/acl-get-tag-type.h \
@@ -47,6 +50,7 @@ HEADERS := \
gen/has/strerror-r-posix.h \
gen/has/string-to-flags.h \
gen/has/strtofflags.h \
+ gen/has/tcgetwinsize.h \
gen/has/timegm.h \
gen/has/tm-gmtoff.h \
gen/has/uselocale.h
@@ -61,7 +65,7 @@ gen/config.h: ${PKG_HEADERS} ${HEADERS}
@printf '#define BFS_CONFIG_H\n' >>$@
@cat ${.ALLSRC} >>$@
@printf '#endif // BFS_CONFIG_H\n' >>$@
- @cat gen/cc.log ${.ALLSRC:%=%.log} >gen/config.log
+ @cat gen/flags.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
@@ -73,16 +77,10 @@ gen/config.h: ${PKG_HEADERS} ${HEADERS}
# The short name of the config test
SLUG = ${@:gen/%.h=%}
+# The hidden output file name
+OUT = ${SLUG:has/%=gen/has/.%.out}
-${HEADERS}: cc
+${HEADERS}::
@${MKDIR} ${@D}
- @build/define-if.sh ${SLUG} build/cc.sh build/${SLUG}.c >$@ 2>$@.log; \
+ @build/define-if.sh ${SLUG} build/cc.sh build/${SLUG}.c -o ${OUT} >$@ 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/pkgconf.sh b/build/pkgconf.sh
index 244c95d..decf706 100755
--- a/build/pkgconf.sh
+++ b/build/pkgconf.sh
@@ -34,10 +34,10 @@ if [ -z "$MODE" ]; then
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/with/$LIB.c $LDLIBS || exit 1
+ XCFLAGS="$XCFLAGS $("$0" --cflags "$LIB")" || exit 1
+ XLDFLAGS="$XLDFLAGS $("$0" --ldflags "$LIB")" || exit 1
+ XLDLIBS="$("$0" --ldlibs "$LIB") $XLDLIBS" || exit 1
+ build/cc.sh "build/with/$LIB.c" -o "gen/with/.$LIB.out" || exit 1
done
fi
@@ -92,5 +92,5 @@ done
case "$MODE" in
--ldlibs)
printf '%s\n' "$LDLIBS"
- ;;
+ ;;
esac
diff --git a/build/prelude.mk b/build/prelude.mk
index 76fbce8..b8726d7 100644
--- a/build/prelude.mk
+++ b/build/prelude.mk
@@ -71,50 +71,6 @@ ALL_PKGS := \
# List all object files here, as they're needed by both `./configure` and `make`
-# All object files except the entry point
-LIBBFS := \
- obj/src/alloc.o \
- obj/src/bar.o \
- obj/src/bfstd.o \
- obj/src/bftw.o \
- obj/src/color.o \
- obj/src/ctx.o \
- obj/src/diag.o \
- obj/src/dir.o \
- obj/src/dstring.o \
- obj/src/eval.o \
- obj/src/exec.o \
- obj/src/expr.o \
- obj/src/fsade.o \
- obj/src/ioq.o \
- obj/src/mtab.o \
- obj/src/opt.o \
- obj/src/parse.o \
- obj/src/printf.o \
- obj/src/pwcache.o \
- obj/src/sighook.o \
- obj/src/stat.o \
- 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 \
-
-# Unit test objects
-UNIT_OBJS := \
- obj/tests/alloc.o \
- 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 \
- obj/tests/xspawn.o \
- obj/tests/xtime.o
-
# All object files
OBJS := \
obj/src/main.o \
diff --git a/build/version.sh b/build/version.sh
new file mode 100755
index 0000000..afcb865
--- /dev/null
+++ b/build/version.sh
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+# Copyright © Tavian Barnes <tavianator@tavianator.com>
+# SPDX-License-Identifier: 0BSD
+
+# Print the version number
+
+set -eu
+
+DIR="$(dirname -- "$0")/.."
+
+if [ "${VERSION-}" ]; then
+ printf '%s' "$VERSION"
+elif [ -e "$DIR/.git" ] && command -v git >/dev/null 2>&1; then
+ git -C "$DIR" describe --always --dirty
+else
+ echo "4.0.2"
+fi
diff --git a/completions/bfs.bash b/completions/bfs.bash
index 6fd82c8..0dd39f4 100644
--- a/completions/bfs.bash
+++ b/completions/bfs.bash
@@ -99,6 +99,7 @@ _bfs() {
-ignore_readdir_race
-mount
-nocolor
+ -noerror
-noignore_readdir_race
-noleaf
-nowarn
diff --git a/completions/bfs.fish b/completions/bfs.fish
index 24b0ad9..7182bee 100644
--- a/completions/bfs.fish
+++ b/completions/bfs.fish
@@ -42,7 +42,8 @@ complete -c bfs -o ignore_readdir_race -d "Don't report an error if the file tre
complete -c bfs -o noignore_readdir_race -d "Report an error if the file tree is modified during the search"
complete -c bfs -o maxdepth -d "Ignore files deeper than specified number" -x
complete -c bfs -o mindepth -d "Ignore files shallower than specified number" -x
-complete -c bfs -o mount -d "Don't descend into other mount points"
+complete -c bfs -o mount -d "Exclude mount points"
+complete -c bfs -o noerror -d "Ignore any errors that occur during traversal"
complete -c bfs -o nohidden -d "Exclude hidden files and directories"
complete -c bfs -o noleaf -d "Ignored; for compatibility with GNU find"
complete -c bfs -o regextype -d "Use specified flavored regex" -a $regex_type_comp -x
diff --git a/completions/bfs.zsh b/completions/bfs.zsh
index 432ab8c..3825e09 100644
--- a/completions/bfs.zsh
+++ b/completions/bfs.zsh
@@ -41,7 +41,8 @@ args=(
'*-noignore_readdir_race[do not report an error if bfs detects file tree is modified during search]'
'*-maxdepth[ignore files deeper than N]:maximum search depth'
'*-mindepth[ignore files shallower than N]:minimum search depth'
- "*-mount[don't descend into other mount points]"
+ "*-mount[exclude mount points]"
+ '*-noerror[ignore any errors that occur during traversal]'
'*-nohidden[exclude hidden files]'
'*-noleaf[ignored, for compatibility with GNU find]'
'-regextype[type of regex to use, default posix-basic]:regexp syntax:(help posix-basic posix-extended ed emacs grep sed)'
diff --git a/configure b/configure
index 8cdb7d8..5156c91 100755
--- a/configure
+++ b/configure
@@ -7,13 +7,16 @@
set -eu
-# Print the help message()
+# Get the relative path to the source tree based on how the script was run
+DIR=$(dirname -- "$0")
+
+# Print the help message
help() {
cat <<EOF
Usage:
\$ $0 [--enable-*|--disable-*] [--with-*|--without-*] [CC=...] [...]
- \$ $MAKE $j
+ \$ $MAKE -j$(nproc)
Variables set in the environment or on the command line will be picked up:
@@ -57,11 +60,13 @@ Packaging:
Set the installation prefix (default: /usr)
--mandir=/path
Set the man page directory (default: \$PREFIX/share/man)
+ --version=X.Y.Z
+ Set the version string (default: $("$DIR/build/version.sh"))
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
+ \$ $0 -j$(nproc) V=1
EOF
}
@@ -134,7 +139,7 @@ for arg; do
printf 'warning: Treating "%s" like "%s"\n' "$old" "$arg" >&2
;;
esac
- ;;
+ ;;
esac
case "$arg" in
@@ -165,7 +170,7 @@ for arg; do
esac
;;
- --prefix=*|--mandir=*)
+ --prefix=*|--mandir=*|--version=*)
set -- "$@" "$NAME=$value"
;;
@@ -188,9 +193,6 @@ for arg; do
esac
done
-# Get the relative path to the source tree based on how the script was run
-DIR=$(dirname -- "$0")
-
# Set up symbolic links for out-of-tree builds
for f in Makefile build completions docs src tests; do
test -e "$f" || ln -s "$DIR/$f" "$f"
diff --git a/docs/BUILDING.md b/docs/BUILDING.md
index 025dadf..cb26059 100644
--- a/docs/BUILDING.md
+++ b/docs/BUILDING.md
@@ -178,11 +178,11 @@ It can be handy to generate the snapshot with a different `find` implementation
But keep in mind, other `find` implementations may not be correct.
To my knowledge, no other implementation passes even the POSIX-compatible subset of the tests:
- $ ./tests/tests.sh --bfs=find --posix
+ $ ./tests/tests.sh --bfs=find --sudo --posix
...
- tests passed: 90
- tests skipped: 3
- tests failed: 6
+ [PASS] 104 / 119
+ [SKIP] 1 / 119
+ [FAIL] 14 / 119
Run
diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index db978b8..ec3daab 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -1,3 +1,100 @@
+4.*
+===
+
+4.0.2
+-----
+
+**September 17, 2024**
+
+### New features
+
+- Implemented `./configure --version=X.Y.Z`, mainly for packagers to override the version number
+ ([`4a278d3`](https://github.com/tavianator/bfs/commit/4a278d3e39a685379711727eac7bfaa83679e0e4))
+
+### Changes
+
+- Minor refactoring of the build system
+
+### Bug fixes
+
+- Fixed `./configure --help`, which was broken since `bfs` 4.0
+ ([`07ae989`](https://github.com/tavianator/bfs/commit/07ae98906dbb0caaac2f758d72e88dd0975b2a81))
+
+- Fixed compiler flag auto-detection on systems with non-GNU `sed`.
+ This fixes a potential race condition on FreeBSD since `bfs` 4.0 due to the [switch to `_Fork()`](https://github.com/tavianator/bfs/commit/085bb402c7b2c2f96624fb0523ff3f9686fe26d9) without passing `-z now` to the linker.
+ ([`34e6081`](https://github.com/tavianator/bfs/commit/34e60816adb0ea8ddb155a454676a99ab225dc8a))
+
+- Fixed `$MAKE distcheck` when `$MAKE` is not `make`, e.g. `gmake distcheck` on BSD
+ ([`2135b00`](https://github.com/tavianator/bfs/commit/2135b00d215efc5c2c38e1abd3254baf31229ad4))
+
+- Fixed some roff syntax issues in the `bfs` manpage
+ ([`812ecd1`](https://github.com/tavianator/bfs/commit/812ecd1feeb002252dd4d732b395d31c4179afaf))
+
+- Fixed an assertion failure optimizing expressions like `bfs -not \( -prune , -type f \)` since `bfs` 3.1.
+ Release builds were not affected, since their assertions are disabled and the behaviour was otherwise correct.
+ ([`b1a9998`](https://github.com/tavianator/bfs/commit/b1a999892b9e13181ddd9a7d895f3d1c65fbb449))
+
+
+4.0.1
+-----
+
+**August 19, 2024**
+
+### Bug fixes
+
+- `bfs` no longer prints a "suppressed errors" warning unless `-noerror` is actually suppressing errors
+ ([`5d03c9d`](https://github.com/tavianator/bfs/commit/5d03c9d460d1c1afcdf062d494537986ce96a690))
+
+
+4.0
+---
+
+**August 16, 2024**
+
+### New features
+
+- To match BSD `find` (and the POSIX Utility Syntax Guidelines), multiple flags can now be given in a single argument like `-LEXO2`.
+ Previously, you would have had to write `-L -E -X -O2`.
+ ([`c0fd33a`](https://github.com/tavianator/bfs/commit/c0fd33aaef5f345566a41c7c2558f27adf05558b))
+
+- Explicit timestamps can now be written as `@SECONDS_SINCE_EPOCH`.
+ For example, `bfs -newermt @946684800` will print files modified since January 1, 2000 (UTC).
+ ([`c6bb003`](https://github.com/tavianator/bfs/commit/c6bb003b8882e9a16941f5803d072ec1cb728318))
+
+- The new `-noerror` option suppresses all error messages during traversal.
+ ([#142](https://github.com/tavianator/bfs/issues/142))
+
+### Changes
+
+- `-mount` now excludes mount points entirely, to comply with the recently published POSIX 2024 standard.
+ Use `-xdev` to include the mount point itself, but not its contents.
+ `bfs` has been warning about this change since version 1.5.1 (September 2019).
+ ([`33b85e1`](https://github.com/tavianator/bfs/commit/33b85e1f8769e7f75721887638ae454d109a034f))
+
+- `-perm` now takes the current file creation mask into account when parsing a symbolic mode like `+rw`, as clarified by [POSIX defect 1392](https://www.austingroupbugs.net/view.php?id=1392).
+ This matches the behaviour of BSD `find`, contrary to the behaviour of GNU `find`.
+ ([`6290ce4`](https://github.com/tavianator/bfs/commit/6290ce41f3ec1f889abb881cf90ca91da869b5b2))
+
+### Bug fixes
+
+- Fixed commands like `./configure CC=clang --enable-release` that set variables before other options
+ ([`49a5d48`](https://github.com/tavianator/bfs/commit/49a5d48d0a43bac313c8b8d1b167e60da9eaadf6))
+
+- Fixed the build on RISC-V with GCC versions older than 14
+ ([`e93a1dc`](https://github.com/tavianator/bfs/commit/e93a1dccd82f831a2f0d2cc382d8af5e1fda55ed))
+
+- Fixed running `bfs` under Valgrind
+ ([`a01cfac`](https://github.com/tavianator/bfs/commit/a01cfacd423af28af6b7c13ba51e2395f3a52ee7))
+
+- Fixed the exit code when failing to execute a non-existent command with `-exec`/`-ok` on some platforms including OpenBSD and HPPA
+ ([`8c130ca`](https://github.com/tavianator/bfs/commit/8c130ca0117fd225c24569be2ec16c7dc2150a13))
+
+- Fixed `$LS_COLORS` case-sensitivity to match GNU ls more closely when the same extension is specified multiple times
+ ([`08030ae`](https://github.com/tavianator/bfs/commit/08030aea919039165c02805e8c637a9ec1ad0d70))
+
+- Fixed the `-status` bar on Solaris/Illumos
+
+
3.*
===
diff --git a/docs/SECURITY.md b/docs/SECURITY.md
new file mode 100644
index 0000000..7bcb5e6
--- /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 be 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 cc9504e..13a5303 100644
--- a/docs/bfs.1
+++ b/docs/bfs.1
@@ -1,4 +1,4 @@
-.TH BFS 1 2024-06-17 "bfs 3.3.1"
+.TH BFS 1 2024-09-17 "bfs 4.0.1"
.SH NAME
bfs \- breadth-first search for your files
.SH SYNOPSIS
@@ -41,7 +41,7 @@ For example,
.PP
.nf
.RS
-.B bfs \\\( \-name '*.txt' \-or \-lname '*.txt' \\\\) \-and \-print
+.B bfs \e( \-name '*.txt' \-or \-lname '*.txt' \e) \-and \-print
.RE
.fi
.PP
@@ -51,7 +51,7 @@ is implied between two consecutive expressions, so this is equivalent:
.PP
.nf
.RS
-.B bfs \\\( \-name '*.txt' \-or \-lname '*.txt' \\\\) \-print
+.B bfs \e( \-name '*.txt' \-or \-lname '*.txt' \e) \-print
.RE
.fi
.PP
@@ -115,7 +115,6 @@ Don't descend into other mount points (same as \fB\-xdev\fR).
Treat
.I PATH
as a path to search (useful if it begins with a dash).
-.PP
.TP
\fB\-D \fIFLAG\fR
Turn on a debugging flag (see
@@ -188,9 +187,9 @@ threads in parallel (default: number of CPUs, up to
\fB( \fIexpression \fB)\fR
Parentheses are used for grouping expressions together.
You'll probably have to write
-.B \\\\(
+.B \e(
.I expression
-.B \\\\)
+.B \e)
to avoid the parentheses being interpreted by the shell.
.PP
\fB! \fIexpression\fR
@@ -199,7 +198,7 @@ to avoid the parentheses being interpreted by the shell.
.RS
The "not" operator: returns the negation of the truth value of the
.IR expression .
-You may have to write \fB\\! \fIexpression\fR to avoid \fB!\fR being interpreted by the shell.
+You may have to write \fB\e! \fIexpression\fR to avoid \fB!\fR being interpreted by the shell.
.RE
.PP
\fIexpression\fR \fIexpression\fR
@@ -266,7 +265,6 @@ Print usage information, and exit immediately (without parsing the rest of the c
Print version information, and exit immediately.
.RE
.SH OPTIONS
-.PP
.B \-color
.br
.B \-nocolor
@@ -289,7 +287,7 @@ Follow all symbolic links (same as
.BR \-L ).
.TP
\fB\-files0\-from \fIFILE\fR
-Treat the NUL ('\\0')-separated paths in
+Treat the NUL ('\e0')-separated paths in
.I FILE
as starting points for the search.
Pass
@@ -316,9 +314,10 @@ Ignore files deeper/shallower than
.RE
.TP
.B \-mount
-Don't descend into other mount points (same as
-.B \-xdev
-for now, but will skip mount points entirely in the future).
+Exclude mount points entirely from the results.
+.TP
+.B \-noerror
+Ignore any errors that occur during traversal.
.TP
.B \-nohidden
Exclude hidden files and directories.
@@ -381,6 +380,9 @@ Turn on or off warnings about the command line.
.TP
.B \-xdev
Don't descend into other mount points.
+Unlike
+.BR \-mount ,
+the mount point itself is still included.
.SH TESTS
.TP
.B \-acl
@@ -685,7 +687,6 @@ Find files of the given type, following links when
.B \-type
would not, and vice versa.
.SH ACTIONS
-.PP
.B \-delete
.br
.B \-rm
@@ -749,7 +750,7 @@ Print the path to the found file.
.B \-print0
Like
.BR \-print ,
-but use the null character ('\\0') as a separator rather than newlines.
+but use the null character ('\e0') as a separator rather than newlines.
Useful in conjunction with
.B xargs
.IR \-0 .
diff --git a/src/alloc.c b/src/alloc.c
index ebaff38..223d737 100644
--- a/src/alloc.c
+++ b/src/alloc.c
@@ -1,11 +1,13 @@
// Copyright © Tavian Barnes <tavianator@tavianator.com>
// SPDX-License-Identifier: 0BSD
-#include "prelude.h"
#include "alloc.h"
+
+#include "bfs.h"
#include "bit.h"
#include "diag.h"
#include "sanity.h"
+
#include <errno.h>
#include <stdlib.h>
#include <stdint.h>
@@ -176,7 +178,7 @@ void arena_init(struct arena *arena, size_t align, size_t size) {
}
/** Allocate a new slab. */
-attr(cold)
+_cold
static int slab_alloc(struct arena *arena) {
// Make the initial allocation size ~4K
size_t size = 4096;
diff --git a/src/alloc.h b/src/alloc.h
index 095134a..7b97b8c 100644
--- a/src/alloc.h
+++ b/src/alloc.h
@@ -8,7 +8,8 @@
#ifndef BFS_ALLOC_H
#define BFS_ALLOC_H
-#include "prelude.h"
+#include "bfs.h"
+
#include <errno.h>
#include <stddef.h>
#include <stdlib.h>
@@ -115,7 +116,8 @@ static inline size_t flex_size(size_t align, size_t min, size_t offset, size_t s
* @return
* The allocated memory, or NULL on failure.
*/
-attr(malloc(free, 1), aligned_alloc(1, 2))
+_malloc(free, 1)
+_aligned_alloc(1, 2)
void *alloc(size_t align, size_t size);
/**
@@ -128,7 +130,8 @@ void *alloc(size_t align, size_t size);
* @return
* The allocated memory, or NULL on failure.
*/
-attr(malloc(free, 1), aligned_alloc(1, 2))
+_malloc(free, 1)
+_aligned_alloc(1, 2)
void *zalloc(size_t align, size_t size);
/** Allocate memory for the given type. */
@@ -169,7 +172,8 @@ void *zalloc(size_t align, size_t size);
* @return
* The reallocated memory, or NULL on failure.
*/
-attr(nodiscard, aligned_alloc(2, 4))
+_aligned_alloc(2, 4)
+_nodiscard
void *xrealloc(void *ptr, size_t align, size_t old_size, size_t new_size);
/** Reallocate memory for an array. */
@@ -195,7 +199,7 @@ void *xrealloc(void *ptr, size_t align, size_t old_size, size_t new_size);
* for (count + 1) elements. On failure, errno will be non-zero, and
* ptr will returned unchanged.
*/
-attr(nodiscard)
+_nodiscard
void *reserve(void *ptr, size_t align, size_t size, size_t count);
/**
@@ -253,7 +257,7 @@ void arena_free(struct arena *arena, void *ptr);
/**
* Allocate an object out of the arena.
*/
-attr(malloc(arena_free, 2))
+_malloc(arena_free, 2)
void *arena_alloc(struct arena *arena);
/**
@@ -335,7 +339,7 @@ void varena_free(struct varena *varena, void *ptr, size_t count);
* @return
* The allocated struct, or NULL on failure.
*/
-attr(malloc(varena_free, 2))
+_malloc(varena_free, 2)
void *varena_alloc(struct varena *varena, size_t count);
/**
@@ -352,7 +356,7 @@ void *varena_alloc(struct varena *varena, size_t count);
* @return
* The resized struct, or NULL on failure.
*/
-attr(nodiscard)
+_nodiscard
void *varena_realloc(struct varena *varena, void *ptr, size_t old_count, size_t new_count);
/**
@@ -367,7 +371,7 @@ void *varena_realloc(struct varena *varena, void *ptr, size_t old_count, size_t
* @return
* The resized struct, or NULL on failure.
*/
-attr(nodiscard)
+_nodiscard
void *varena_grow(struct varena *varena, void *ptr, size_t *count);
/**
diff --git a/src/atomic.h b/src/atomic.h
index ad5303b..2682c29 100644
--- a/src/atomic.h
+++ b/src/atomic.h
@@ -8,8 +8,8 @@
#ifndef BFS_ATOMIC_H
#define BFS_ATOMIC_H
-#include "prelude.h"
-#include "sanity.h"
+#include "bfs.h"
+
#include <stdatomic.h>
/**
@@ -87,7 +87,7 @@
/**
* Shorthand for atomic_thread_fence().
*/
-#if SANITIZE_THREAD
+#if __SANITIZE_THREAD__
// TSan doesn't support fences: https://github.com/google/sanitizers/issues/1415
# define thread_fence(obj, order) \
fetch_add(obj, 0, order)
diff --git a/src/bar.c b/src/bar.c
index be77694..3258df0 100644
--- a/src/bar.c
+++ b/src/bar.c
@@ -1,21 +1,26 @@
// Copyright © Tavian Barnes <tavianator@tavianator.com>
// SPDX-License-Identifier: 0BSD
-#include "prelude.h"
#include "bar.h"
+
#include "alloc.h"
#include "atomic.h"
+#include "bfs.h"
#include "bfstd.h"
#include "bit.h"
#include "dstring.h"
#include "sighook.h"
+
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
+#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
+#include <termios.h>
+#include <unistd.h>
struct bfs_bar {
int fd;
@@ -28,10 +33,16 @@ struct bfs_bar {
/** Get the terminal size, if possible. */
static int bfs_bar_getsize(struct bfs_bar *bar) {
-#ifdef TIOCGWINSZ
+#if BFS_HAS_TCGETWINSIZE || defined(TIOCGWINSZ)
struct winsize ws;
- if (ioctl(bar->fd, TIOCGWINSZ, &ws) != 0) {
- return -1;
+
+# if BFS_HAS_TCGETWINSIZE
+ int ret = tcgetwinsize(bar->fd, &ws);
+# else
+ int ret = ioctl(bar->fd, TIOCGWINSZ, &ws);
+# endif
+ if (ret != 0) {
+ return ret;
}
store(&bar->width, ws.ws_col, relaxed);
@@ -72,8 +83,26 @@ static char *ass_itoa(char *str, unsigned int n) {
return str + len;
}
+/** Reset the scrollable region and hide the bar. */
+static int bfs_bar_reset(struct bfs_bar *bar) {
+ return bfs_bar_puts(bar,
+ "\0337" // DECSC: Save cursor
+ "\033[r" // DECSTBM: Reset scrollable region
+ "\0338" // DECRC: Restore cursor
+ "\033[J" // ED: Erase display from cursor to end
+ );
+}
+
+/** Hide the bar if the terminal is shorter than this. */
+#define BFS_BAR_MIN_HEIGHT 3
+
/** Update the size of the scrollable region. */
static int bfs_bar_resize(struct bfs_bar *bar) {
+ unsigned int height = load(&bar->height, relaxed);
+ if (height < BFS_BAR_MIN_HEIGHT) {
+ return bfs_bar_reset(bar);
+ }
+
static const char PREFIX[] =
"\033D" // IND: Line feed, possibly scrolling
"\033[1A" // CUU: Move cursor up 1 row
@@ -87,10 +116,8 @@ static int bfs_bar_resize(struct bfs_bar *bar) {
char esc_seq[sizeof(PREFIX) + ITOA_DIGITS + sizeof(SUFFIX)];
// DECSTBM takes the height as the second argument
- unsigned int height = load(&bar->height, relaxed) - 1;
-
char *cur = stpcpy(esc_seq, PREFIX);
- cur = ass_itoa(cur, height);
+ cur = ass_itoa(cur, height - 1);
cur = stpcpy(cur, SUFFIX);
return bfs_bar_write(bar, esc_seq, cur - esc_seq);
@@ -105,16 +132,6 @@ static void bfs_bar_sigwinch(int sig, siginfo_t *info, void *arg) {
}
#endif
-/** Reset the scrollable region and hide the bar. */
-static int bfs_bar_reset(struct bfs_bar *bar) {
- return bfs_bar_puts(bar,
- "\0337" // DECSC: Save cursor
- "\033[r" // DECSTBM: Reset scrollable region
- "\0338" // DECRC: Restore cursor
- "\033[J" // ED: Erase display from cursor to end
- );
-}
-
/** Signal handler for process-terminating signals. */
static void bfs_bar_sigexit(int sig, siginfo_t *info, void *arg) {
struct bfs_bar *bar = arg;
@@ -122,7 +139,7 @@ static void bfs_bar_sigexit(int sig, siginfo_t *info, void *arg) {
}
/** printf() to the status bar with a single write(). */
-attr(printf(2, 3))
+_printf(2, 3)
static int bfs_bar_printf(struct bfs_bar *bar, const char *format, ...) {
va_list args;
va_start(args, format);
@@ -183,6 +200,10 @@ unsigned int bfs_bar_width(const struct bfs_bar *bar) {
int bfs_bar_update(struct bfs_bar *bar, const char *str) {
unsigned int height = load(&bar->height, relaxed);
+ if (height < BFS_BAR_MIN_HEIGHT) {
+ return 0;
+ }
+
return bfs_bar_printf(bar,
"\0337" // DECSC: Save cursor
"\033[%u;0f" // HVP: Move cursor to row, column
diff --git a/src/bfs.h b/src/bfs.h
new file mode 100644
index 0000000..91c6540
--- /dev/null
+++ b/src/bfs.h
@@ -0,0 +1,214 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+/**
+ * Configuration and fundamental utilities.
+ */
+
+#ifndef BFS_H
+#define BFS_H
+
+// Standard versions
+
+/** Possible __STDC_VERSION__ values. */
+#define C95 199409L
+#define C99 199901L
+#define C11 201112L
+#define C17 201710L
+#define C23 202311L
+
+/** Possible _POSIX_C_SOURCE and _POSIX_<OPTION> values. */
+#define POSIX_1990 1
+#define POSIX_1992 2
+#define POSIX_1993 199309L
+#define POSIX_1995 199506L
+#define POSIX_2001 200112L
+#define POSIX_2008 200809L
+#define POSIX_2024 202405L
+
+// Build configuration
+
+#include "config.h"
+
+#ifndef BFS_COMMAND
+# define BFS_COMMAND "bfs"
+#endif
+#ifndef BFS_HOMEPAGE
+# define BFS_HOMEPAGE "https://tavianator.com/projects/bfs.html"
+#endif
+
+// This is a symbol instead of a literal so we don't have to rebuild everything
+// 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[];
+
+// Get __GLIBC__
+#include <assert.h>
+
+// Fundamental utilities
+
+/**
+ * Get the length of an array.
+ */
+#define countof(...) (sizeof(__VA_ARGS__) / sizeof(0[__VA_ARGS__]))
+
+/**
+ * False sharing/destructive interference/largest cache line size.
+ */
+#ifdef __GCC_DESTRUCTIVE_SIZE
+# define FALSE_SHARING_SIZE __GCC_DESTRUCTIVE_SIZE
+#else
+# define FALSE_SHARING_SIZE 64
+#endif
+
+/**
+ * True sharing/constructive interference/smallest cache line size.
+ */
+#ifdef __GCC_CONSTRUCTIVE_SIZE
+# define TRUE_SHARING_SIZE __GCC_CONSTRUCTIVE_SIZE
+#else
+# define TRUE_SHARING_SIZE 64
+#endif
+
+/**
+ * Alignment specifier that avoids false sharing.
+ */
+#define cache_align alignas(FALSE_SHARING_SIZE)
+
+// Wrappers for attributes
+
+/**
+ * Silence warnings about switch/case fall-throughs.
+ */
+#if __has_attribute(fallthrough)
+# define _fallthrough __attribute__((fallthrough))
+#else
+# define _fallthrough ((void)0)
+#endif
+
+/**
+ * Silence warnings about unused declarations.
+ */
+#if __has_attribute(unused)
+# define _maybe_unused __attribute__((unused))
+#else
+# define _maybe_unused
+#endif
+
+/**
+ * Warn if a value is unused.
+ */
+#if __has_attribute(warn_unused_result)
+# define _nodiscard __attribute__((warn_unused_result))
+#else
+# define _nodiscard
+#endif
+
+/**
+ * Hint to avoid inlining a function.
+ */
+#if __has_attribute(noinline)
+# define _noinline __attribute__((noinline))
+#else
+# define _noinline
+#endif
+
+/**
+ * Marks a non-returning function.
+ */
+#if __STDC_VERSION__ >= C23
+# define _noreturn [[noreturn]]
+#else
+# define _noreturn _Noreturn
+#endif
+
+/**
+ * Hint that a function is unlikely to be called.
+ */
+#if __has_attribute(cold)
+# define _cold _noinline __attribute__((cold))
+#else
+# define _cold _noinline
+#endif
+
+/**
+ * Adds compiler warnings for bad printf()-style function calls, if supported.
+ */
+#if __has_attribute(format)
+# define _printf(fmt, args) __attribute__((format(printf, fmt, args)))
+#else
+# define _printf(fmt, args)
+#endif
+
+/**
+ * Annotates functions that potentially modify and return format strings.
+ */
+#if __has_attribute(format_arg)
+# define _format_arg(arg) __attribute__((format_arg(arg)))
+#else
+# define _format_arg(arg)
+#endif
+
+/**
+ * Annotates allocator-like functions.
+ */
+#if __has_attribute(malloc)
+# if __GNUC__ >= 11 && !__OPTIMIZE__ // malloc(deallocator) disables inlining on GCC
+# define _malloc(...) _nodiscard __attribute__((malloc(__VA_ARGS__)))
+# else
+# define _malloc(...) _nodiscard __attribute__((malloc))
+# endif
+#else
+# define _malloc(...) _nodiscard
+#endif
+
+/**
+ * Specifies that a function returns allocations with a given alignment.
+ */
+#if __has_attribute(alloc_align)
+# define _alloc_align(param) __attribute__((alloc_align(param)))
+#else
+# define _alloc_align(param)
+#endif
+
+/**
+ * Specifies that a function returns allocations with a given size.
+ */
+#if __has_attribute(alloc_size)
+# define _alloc_size(...) __attribute__((alloc_size(__VA_ARGS__)))
+#else
+# define _alloc_size(...)
+#endif
+
+/**
+ * Shorthand for _alloc_align() and _alloc_size().
+ */
+#define _aligned_alloc(align, ...) _alloc_align(align) _alloc_size(__VA_ARGS__)
+
+/**
+ * Check if function multiversioning via GNU indirect functions (ifunc) is supported.
+ *
+ * Disabled on TSan due to https://github.com/google/sanitizers/issues/342.
+ */
+#ifndef BFS_USE_TARGET_CLONES
+# if __has_attribute(target_clones) && (__GLIBC__ || __FreeBSD__) && !__SANITIZE_THREAD__
+# define BFS_USE_TARGET_CLONES true
+# endif
+#endif
+
+/**
+ * Apply the target_clones attribute, if available.
+ */
+#if BFS_USE_TARGET_CLONES
+# define _target_clones(...) __attribute__((target_clones(__VA_ARGS__)))
+#else
+# define _target_clones(...)
+#endif
+
+#endif // BFS_H
diff --git a/src/bfstd.c b/src/bfstd.c
index 6d244ca..b29fb7b 100644
--- a/src/bfstd.c
+++ b/src/bfstd.c
@@ -1,13 +1,15 @@
// Copyright © Tavian Barnes <tavianator@tavianator.com>
// SPDX-License-Identifier: 0BSD
-#include "prelude.h"
#include "bfstd.h"
+
+#include "bfs.h"
#include "bit.h"
#include "diag.h"
#include "sanity.h"
#include "thread.h"
#include "xregex.h"
+
#include <errno.h>
#include <fcntl.h>
#include <langinfo.h>
@@ -27,13 +29,13 @@
#include <unistd.h>
#include <wchar.h>
-#if BFS_USE_SYS_SYSMACROS_H
+#if __has_include(<sys/sysmacros.h>)
# include <sys/sysmacros.h>
-#elif BFS_USE_SYS_MKDEV_H
+#elif __has_include(<sys/mkdev.h>)
# include <sys/mkdev.h>
#endif
-#if BFS_USE_UTIL_H
+#if __has_include(<util.h>)
# include <util.h>
#endif
@@ -324,7 +326,7 @@ const char *xstrerror(int errnum) {
// On FreeBSD with MemorySanitizer, duplocale() triggers
// https://github.com/llvm/llvm-project/issues/65532
-#if BFS_HAS_STRERROR_L && !(__FreeBSD__ && SANITIZE_MEMORY)
+#if BFS_HAS_STRERROR_L && !(__FreeBSD__ && __SANITIZE_MEMORY__)
# if BFS_HAS_USELOCALE
locale_t loc = uselocale((locale_t)0);
# else
@@ -715,14 +717,14 @@ int xstrtofflags(const char **str, unsigned long long *set, unsigned long long *
}
long xsysconf(int name) {
-#if __FreeBSD__ && SANITIZE_MEMORY
+#if __FreeBSD__ && __SANITIZE_MEMORY__
// Work around https://github.com/llvm/llvm-project/issues/88163
__msan_scoped_disable_interceptor_checks();
#endif
long ret = sysconf(name);
-#if __FreeBSD__ && SANITIZE_MEMORY
+#if __FreeBSD__ && __SANITIZE_MEMORY__
__msan_scoped_enable_interceptor_checks();
#endif
@@ -965,14 +967,14 @@ static char *dollar_quote(char *dest, char *end, const char *str, size_t len, en
/** How much of this string is safe as a bare word? */
static size_t bare_len(const char *str, size_t len) {
- // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02
+ // https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap02.html#tag_19_02
size_t ret = strcspn(str, "|&;<>()$`\\\"' *?[#~=%!{}");
return ret < len ? ret : len;
}
/** How much of this string is safe to double-quote? */
static size_t quotable_len(const char *str, size_t len) {
- // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02_03
+ // https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap02.html#tag_19_02_03
size_t ret = strcspn(str, "`$\\\"!");
return ret < len ? ret : len;
}
diff --git a/src/bfstd.h b/src/bfstd.h
index afb5282..557f253 100644
--- a/src/bfstd.h
+++ b/src/bfstd.h
@@ -8,8 +8,8 @@
#ifndef BFS_BFSTD_H
#define BFS_BFSTD_H
-#include "prelude.h"
-#include "sanity.h"
+#include "bfs.h"
+
#include <stddef.h>
#include <ctype.h>
@@ -18,7 +18,7 @@
* Work around https://github.com/llvm/llvm-project/issues/65532 by forcing a
* function, not a macro, to be called.
*/
-#if __FreeBSD__ && SANITIZE_MEMORY
+#if __FreeBSD__ && __SANITIZE_MEMORY__
# define BFS_INTERCEPT(fn) (fn)
#else
# define BFS_INTERCEPT(fn) fn
@@ -447,6 +447,19 @@ int xstrtofflags(const char **str, unsigned long long *set, unsigned long long *
*/
long xsysconf(int name);
+/**
+ * Check for a POSIX option[1] at runtime.
+ *
+ * [1]: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/V1_chap02.html#tag_02_01_06
+ *
+ * @param name
+ * The symbolic name of the POSIX option (e.g. SPAWN).
+ * @return
+ * The value of the option, either -1 or a date like 202405.
+ */
+#define sysoption(name) \
+ (_POSIX_##name == 0 ? xsysconf(_SC_##name) : _POSIX_##name)
+
#include <wchar.h>
/**
diff --git a/src/bftw.c b/src/bftw.c
index f9ef2a1..64991b0 100644
--- a/src/bftw.c
+++ b/src/bftw.c
@@ -18,9 +18,10 @@
* various helper functions to take fewer parameters.
*/
-#include "prelude.h"
#include "bftw.h"
+
#include "alloc.h"
+#include "bfs.h"
#include "bfstd.h"
#include "diag.h"
#include "dir.h"
@@ -30,6 +31,7 @@
#include "mtab.h"
#include "stat.h"
#include "trie.h"
+
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
@@ -1281,8 +1283,8 @@ static int bftw_pin_parent(struct bftw_state *state, struct bftw_file *file) {
int fd = parent->fd;
if (fd < 0) {
- bfs_static_assert((int)AT_FDCWD != -1);
- return -1;
+ // Don't confuse failures with AT_FDCWD
+ return (int)AT_FDCWD == -1 ? -2 : -1;
}
bftw_cache_pin(&state->cache, parent);
@@ -1431,7 +1433,7 @@ static bool bftw_must_stat(const struct bftw_state *state, size_t depth, enum bf
if (!(bftw_stat_flags(state, depth) & BFS_STAT_NOFOLLOW)) {
return true;
}
- fallthru;
+ _fallthrough;
default:
#if __linux__
diff --git a/src/bftw.h b/src/bftw.h
index a2a201c..28e0679 100644
--- a/src/bftw.h
+++ b/src/bftw.h
@@ -10,6 +10,7 @@
#include "dir.h"
#include "stat.h"
+
#include <stddef.h>
/**
diff --git a/src/bit.h b/src/bit.h
index c494130..c7530f1 100644
--- a/src/bit.h
+++ b/src/bit.h
@@ -8,11 +8,12 @@
#ifndef BFS_BIT_H
#define BFS_BIT_H
-#include "prelude.h"
+#include "bfs.h"
+
#include <limits.h>
#include <stdint.h>
-#if BFS_HAS_STDBIT_H
+#if __has_include(<stdbit.h>)
# include <stdbit.h>
#endif
diff --git a/src/color.c b/src/color.c
index 701a89c..7f653b0 100644
--- a/src/color.c
+++ b/src/color.c
@@ -1,9 +1,10 @@
// Copyright © Tavian Barnes <tavianator@tavianator.com>
// SPDX-License-Identifier: 0BSD
-#include "prelude.h"
#include "color.h"
+
#include "alloc.h"
+#include "bfs.h"
#include "bfstd.h"
#include "bftw.h"
#include "diag.h"
@@ -13,6 +14,7 @@
#include "fsade.h"
#include "stat.h"
#include "trie.h"
+
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
@@ -214,57 +216,26 @@ static void ext_tolower(char *ext, size_t len) {
}
}
-/**
- * The "smart case" algorithm.
- *
- * @param ext
- * The current extension being added.
- * @param prev
- * The previous case-sensitive match, if any, for the same extension.
- * @param iprev
- * The previous case-insensitive match, if any, for the same extension.
- * @return
- * Whether this extension should become case-sensitive.
- */
-static bool ext_case_sensitive(struct ext_color *ext, struct ext_color *prev, struct ext_color *iprev) {
- // This is the first case-insensitive occurrence of this extension, e.g.
- //
- // *.gz=01;31:*.tar.gz=01;33
- if (!iprev) {
- bfs_assert(!prev);
- return false;
- }
-
- // If the last version of this extension is already case-sensitive,
- // this one should be too, e.g.
- //
- // *.tar.gz=01;31:*.TAR.GZ=01;32:*.TAR.GZ=01;33
- if (iprev->case_sensitive) {
- return true;
- }
-
- // The case matches the last occurrence exactly, e.g.
- //
- // *.tar.gz=01;31:*.tar.gz=01;33
- if (iprev == prev) {
- return false;
+/** Insert an extension into a trie. */
+static int insert_ext(struct trie *trie, struct ext_color *ext) {
+ // A later *.x should override any earlier *.x, *.y.x, etc.
+ struct trie_leaf *leaf;
+ while ((leaf = trie_find_postfix(trie, ext->ext))) {
+ trie_remove(trie, leaf);
}
- // Different case, but same value, e.g.
- //
- // *.tar.gz=01;31:*.TAR.GZ=01;31
- if (esc_eq(iprev->esc, ext->esc->seq, ext->esc->len)) {
- return false;
+ size_t len = ext->len + 1;
+ leaf = trie_insert_mem(trie, ext->ext, len);
+ if (!leaf) {
+ return -1;
}
- // Different case, different value, e.g.
- //
- // *.tar.gz=01;31:*.TAR.GZ=01;33
- return true;
+ leaf->value = ext;
+ return 0;
}
/** Set the color for an extension. */
-static int set_ext(struct colors *colors, char *key, char *value) {
+static int set_ext(struct colors *colors, dchar *key, dchar *value) {
size_t len = dstrlen(key);
struct ext_color *ext = varena_alloc(&colors->ext_arena, len + 1);
if (!ext) {
@@ -279,46 +250,20 @@ static int set_ext(struct colors *colors, char *key, char *value) {
goto fail;
}
- key = memcpy(ext->ext, key, len + 1);
+ memcpy(ext->ext, key, len + 1);
// Reverse the extension (`*.y.x` -> `x.y.*`) so we can use trie_find_prefix()
- ext_reverse(key, len);
-
- // Find any pre-existing exact match
- struct ext_color *prev = NULL;
- struct trie_leaf *leaf = trie_find_str(&colors->ext_trie, key);
- if (leaf) {
- prev = leaf->value;
- trie_remove(&colors->ext_trie, leaf);
- }
-
- // A later *.x should override any earlier *.x, *.y.x, etc.
- while ((leaf = trie_find_postfix(&colors->ext_trie, key))) {
- trie_remove(&colors->ext_trie, leaf);
- }
+ ext_reverse(ext->ext, len);
// Insert the extension into the case-sensitive trie
- leaf = trie_insert_str(&colors->ext_trie, key);
- if (!leaf) {
+ if (insert_ext(&colors->ext_trie, ext) != 0) {
goto fail;
}
- leaf->value = ext;
- // "Smart case": if the same extension is given with two different
- // capitalizations (e.g. `*.y.x=31:*.Y.Z=32:`), make it case-sensitive
- ext_tolower(key, len);
- leaf = trie_insert_str(&colors->iext_trie, key);
- if (!leaf) {
- goto fail;
+ if (colors->ext_len < len) {
+ colors->ext_len = len;
}
- struct ext_color *iprev = leaf->value;
- if (ext_case_sensitive(ext, prev, iprev)) {
- iprev->case_sensitive = true;
- ext->case_sensitive = true;
- }
- leaf->value = ext;
-
return 0;
fail:
@@ -329,32 +274,83 @@ fail:
return -1;
}
-/** Rebuild the case-insensitive trie after all extensions have been parsed. */
-static int build_iext_trie(struct colors *colors) {
- trie_clear(&colors->iext_trie);
+/**
+ * The "smart case" algorithm.
+ *
+ * @param ext
+ * The current extension being added.
+ * @param iext
+ * The previous case-insensitive match, if any, for the same extension.
+ * @return
+ * Whether this extension should become case-sensitive.
+ */
+static bool ext_case_sensitive(struct ext_color *ext, struct ext_color *iext) {
+ // This is the first case-insensitive occurrence of this extension, e.g.
+ //
+ // *.gz=01;31:*.tar.gz=01;33
+ if (!iext) {
+ return false;
+ }
+ // If the last version of this extension is already case-sensitive,
+ // this one should be too, e.g.
+ //
+ // *.tar.gz=01;31:*.TAR.GZ=01;32:*.TAR.GZ=01;33
+ if (iext->case_sensitive) {
+ return true;
+ }
+
+ // Different case, but same value, e.g.
+ //
+ // *.tar.gz=01;31:*.TAR.GZ=01;31
+ if (esc_eq(iext->esc, ext->esc->seq, ext->esc->len)) {
+ return false;
+ }
+
+ // Different case, different value, e.g.
+ //
+ // *.tar.gz=01;31:*.TAR.GZ=01;33
+ return true;
+}
+
+/** Build the case-insensitive trie, after all extensions have been parsed. */
+static int build_iext_trie(struct colors *colors) {
+ // Find which extensions should be case-sensitive
for_trie (leaf, &colors->ext_trie) {
- size_t len = leaf->length - 1;
- if (colors->ext_len < len) {
- colors->ext_len = len;
+ struct ext_color *ext = leaf->value;
+
+ // "Smart case": if the same extension is given with two different
+ // capitalizations (e.g. `*.y.x=31:*.Y.Z=32:`), make it case-sensitive
+ ext_tolower(ext->ext, ext->len);
+
+ size_t len = ext->len + 1;
+ struct trie_leaf *ileaf = trie_insert_mem(&colors->iext_trie, ext->ext, len);
+ if (!ileaf) {
+ return -1;
+ }
+
+ struct ext_color *iext = ileaf->value;
+ if (ext_case_sensitive(ext, iext)) {
+ ext->case_sensitive = true;
+ iext->case_sensitive = true;
}
+ ileaf->value = ext;
+ }
+
+ // Rebuild the trie with only the case-insensitive ones
+ trie_clear(&colors->iext_trie);
+
+ for_trie (leaf, &colors->ext_trie) {
struct ext_color *ext = leaf->value;
if (ext->case_sensitive) {
continue;
}
- // set_ext() already reversed and lowercased the extension
- struct trie_leaf *ileaf;
- while ((ileaf = trie_find_postfix(&colors->iext_trie, ext->ext))) {
- trie_remove(&colors->iext_trie, ileaf);
- }
-
- ileaf = trie_insert_str(&colors->iext_trie, ext->ext);
- if (!ileaf) {
+ // We already lowercased the extension above
+ if (insert_ext(&colors->iext_trie, ext) != 0) {
return -1;
}
- ileaf->value = ext;
}
return 0;
@@ -1125,7 +1121,7 @@ static int print_link_target(CFILE *cfile, const struct BFTW *ftwbuf) {
}
/** Format some colored output to the buffer. */
-attr(printf(2, 3))
+_printf(2, 3)
static int cbuff(CFILE *cfile, const char *format, ...);
/** Dump a parsed expression tree, for debugging. */
@@ -1142,14 +1138,20 @@ static int print_expr(CFILE *cfile, const struct bfs_expr *expr, bool verbose, i
return -1;
}
- if (bfs_expr_is_parent(expr)) {
- if (cbuff(cfile, "${red}%pq${rs}", expr->argv[0]) < 0) {
- return -1;
- }
- } else {
- if (cbuff(cfile, "${blu}%pq${rs}", expr->argv[0]) < 0) {
- return -1;
- }
+ int ret;
+ switch (expr->kind) {
+ case BFS_FLAG:
+ ret = cbuff(cfile, "${cyn}%pq${rs}", expr->argv[0]);
+ break;
+ case BFS_OPERATOR:
+ ret = cbuff(cfile, "${red}%pq${rs}", expr->argv[0]);
+ break;
+ default:
+ ret = cbuff(cfile, "${blu}%pq${rs}", expr->argv[0]);
+ break;
+ }
+ if (ret < 0) {
+ return -1;
}
for (size_t i = 1; i < expr->argc; ++i) {
@@ -1194,7 +1196,7 @@ static int print_expr(CFILE *cfile, const struct bfs_expr *expr, bool verbose, i
return 0;
}
-attr(printf(2, 0))
+_printf(2, 0)
static int cvbuff(CFILE *cfile, const char *format, va_list args) {
const struct colors *colors = cfile->colors;
diff --git a/src/color.h b/src/color.h
index 8582eb3..38f5ad7 100644
--- a/src/color.h
+++ b/src/color.h
@@ -8,8 +8,9 @@
#ifndef BFS_COLOR_H
#define BFS_COLOR_H
-#include "prelude.h"
+#include "bfs.h"
#include "dstring.h"
+
#include <stdio.h>
/**
@@ -100,13 +101,13 @@ int cfclose(CFILE *cfile);
* @return
* 0 on success, -1 on failure.
*/
-attr(printf(2, 3))
+_printf(2, 3)
int cfprintf(CFILE *cfile, const char *format, ...);
/**
* cfprintf() variant that takes a va_list.
*/
-attr(printf(2, 0))
+_printf(2, 0)
int cvfprintf(CFILE *cfile, const char *format, va_list args);
/**
diff --git a/src/ctx.c b/src/ctx.c
index 0f619c2..2c55a35 100644
--- a/src/ctx.c
+++ b/src/ctx.c
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: 0BSD
#include "ctx.h"
+
#include "alloc.h"
#include "bfstd.h"
#include "color.h"
@@ -13,12 +14,14 @@
#include "sighook.h"
#include "stat.h"
#include "trie.h"
-#include "xtime.h"
+
#include <errno.h>
#include <limits.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
+#include <sys/stat.h>
+#include <time.h>
#include <unistd.h>
/** Get the initial value for ctx->threads (-j). */
@@ -52,6 +55,9 @@ struct bfs_ctx *bfs_ctx_new(void) {
trie_init(&ctx->files);
+ ctx->umask = umask(0);
+ umask(ctx->umask);
+
if (getrlimit(RLIMIT_NOFILE, &ctx->orig_nofile) != 0) {
goto fail;
}
@@ -68,7 +74,7 @@ struct bfs_ctx *bfs_ctx_new(void) {
goto fail;
}
- if (xgettime(&ctx->now) != 0) {
+ if (clock_gettime(CLOCK_REALTIME, &ctx->now) != 0) {
goto fail;
}
diff --git a/src/ctx.h b/src/ctx.h
index b28a63c..e532550 100644
--- a/src/ctx.h
+++ b/src/ctx.h
@@ -8,14 +8,15 @@
#ifndef BFS_CTX_H
#define BFS_CTX_H
-#include "prelude.h"
#include "alloc.h"
#include "bftw.h"
#include "diag.h"
#include "expr.h"
#include "trie.h"
+
#include <stddef.h>
#include <sys/resource.h>
+#include <sys/types.h>
#include <time.h>
struct CFILE;
@@ -67,11 +68,18 @@ struct bfs_ctx {
bool status;
/** Whether to only return unique files (-unique). */
bool unique;
- /** Whether to print warnings (-warn/-nowarn). */
- bool warn;
/** Whether to only handle paths with xargs-safe characters (-X). */
bool xargs_safe;
+ /** Whether bfs was run interactively. */
+ bool interactive;
+ /** Whether to print warnings (-warn/-nowarn). */
+ bool warn;
+ /** Whether to report errors (-noerror). */
+ bool ignore_errors;
+ /** Whether any dangerous actions (-delete/-exec) are present. */
+ bool dangerous;
+
/** Color data. */
struct colors *colors;
/** The error that occurred parsing the color table, if any. */
@@ -98,6 +106,9 @@ struct bfs_ctx {
/** The number of files owned by the context. */
int nfiles;
+ /** The current file creation mask. */
+ mode_t umask;
+
/** The initial RLIMIT_NOFILE limits. */
struct rlimit orig_nofile;
/** The current RLIMIT_NOFILE limits. */
diff --git a/src/diag.c b/src/diag.c
index ccafd98..4909cf5 100644
--- a/src/diag.c
+++ b/src/diag.c
@@ -1,21 +1,22 @@
// Copyright © Tavian Barnes <tavianator@tavianator.com>
// SPDX-License-Identifier: 0BSD
-#include "prelude.h"
#include "diag.h"
+
#include "alloc.h"
+#include "bfs.h"
#include "bfstd.h"
#include "color.h"
#include "ctx.h"
#include "dstring.h"
#include "expr.h"
-#include <errno.h>
+
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
/** bfs_diagf() implementation. */
-attr(printf(2, 0))
+_printf(2, 0)
static void bfs_vdiagf(const struct bfs_loc *loc, const char *format, va_list args) {
fprintf(stderr, "%s: %s@%s:%d: ", xgetprogname(), loc->func, loc->file, loc->line);
vfprintf(stderr, format, args);
@@ -29,7 +30,8 @@ void bfs_diagf(const struct bfs_loc *loc, const char *format, ...) {
va_end(args);
}
-noreturn void bfs_abortf(const struct bfs_loc *loc, const char *format, ...) {
+_noreturn
+void bfs_abortf(const struct bfs_loc *loc, const char *format, ...) {
va_list args;
va_start(args, format);
bfs_vdiagf(loc, format, args);
diff --git a/src/diag.h b/src/diag.h
index da22f3a..3bea9b2 100644
--- a/src/diag.h
+++ b/src/diag.h
@@ -8,19 +8,10 @@
#ifndef BFS_DIAG_H
#define BFS_DIAG_H
-#include "prelude.h"
+#include "bfs.h"
#include "bfstd.h"
-#include <stdarg.h>
-/**
- * static_assert() with an optional second argument.
- */
-#if __STDC_VERSION__ >= C23
-# define bfs_static_assert static_assert
-#else
-# define bfs_static_assert(...) bfs_static_assert_(__VA_ARGS__, #__VA_ARGS__, )
-# define bfs_static_assert_(expr, msg, ...) _Static_assert(expr, msg)
-#endif
+#include <stdarg.h>
/**
* A source code location.
@@ -47,7 +38,7 @@ struct bfs_loc {
*
* bfs: func@src/file.c:0: Message
*/
-attr(printf(2, 3))
+_printf(2, 3)
void bfs_diagf(const struct bfs_loc *loc, const char *format, ...);
/**
@@ -67,8 +58,10 @@ void bfs_diagf(const struct bfs_loc *loc, const char *format, ...);
/**
* Print a message to standard error and abort.
*/
-attr(cold, printf(2, 3))
-noreturn void bfs_abortf(const struct bfs_loc *loc, const char *format, ...);
+_cold
+_printf(2, 3)
+_noreturn
+void bfs_abortf(const struct bfs_loc *loc, const char *format, ...);
/**
* Unconditional abort with a message.
@@ -166,13 +159,14 @@ const char *debug_flag_name(enum debug_flags flag);
/**
* Like perror(), but decorated like bfs_error().
*/
-attr(cold)
+_cold
void bfs_perror(const struct bfs_ctx *ctx, const char *str);
/**
* Shorthand for printing error messages.
*/
-attr(cold, printf(2, 3))
+_cold
+_printf(2, 3)
void bfs_error(const struct bfs_ctx *ctx, const char *format, ...);
/**
@@ -180,7 +174,8 @@ void bfs_error(const struct bfs_ctx *ctx, const char *format, ...);
*
* @return Whether a warning was printed.
*/
-attr(cold, printf(2, 3))
+_cold
+_printf(2, 3)
bool bfs_warning(const struct bfs_ctx *ctx, const char *format, ...);
/**
@@ -188,67 +183,71 @@ bool bfs_warning(const struct bfs_ctx *ctx, const char *format, ...);
*
* @return Whether a debug message was printed.
*/
-attr(cold, printf(3, 4))
+_cold
+_printf(3, 4)
bool bfs_debug(const struct bfs_ctx *ctx, enum debug_flags flag, const char *format, ...);
/**
* bfs_error() variant that takes a va_list.
*/
-attr(cold, printf(2, 0))
+_cold
+_printf(2, 0)
void bfs_verror(const struct bfs_ctx *ctx, const char *format, va_list args);
/**
* bfs_warning() variant that takes a va_list.
*/
-attr(cold, printf(2, 0))
+_cold
+_printf(2, 0)
bool bfs_vwarning(const struct bfs_ctx *ctx, const char *format, va_list args);
/**
* bfs_debug() variant that takes a va_list.
*/
-attr(cold, printf(3, 0))
+_cold
+_printf(3, 0)
bool bfs_vdebug(const struct bfs_ctx *ctx, enum debug_flags flag, const char *format, va_list args);
/**
* Print the error message prefix.
*/
-attr(cold)
+_cold
void bfs_error_prefix(const struct bfs_ctx *ctx);
/**
* Print the warning message prefix.
*/
-attr(cold)
+_cold
bool bfs_warning_prefix(const struct bfs_ctx *ctx);
/**
* Print the debug message prefix.
*/
-attr(cold)
+_cold
bool bfs_debug_prefix(const struct bfs_ctx *ctx, enum debug_flags flag);
/**
* Highlight parts of the command line in an error message.
*/
-attr(cold)
+_cold
void bfs_argv_error(const struct bfs_ctx *ctx, const bool args[]);
/**
* Highlight parts of an expression in an error message.
*/
-attr(cold)
+_cold
void bfs_expr_error(const struct bfs_ctx *ctx, const struct bfs_expr *expr);
/**
* Highlight parts of the command line in a warning message.
*/
-attr(cold)
+_cold
bool bfs_argv_warning(const struct bfs_ctx *ctx, const bool args[]);
/**
* Highlight parts of an expression in a warning message.
*/
-attr(cold)
+_cold
bool bfs_expr_warning(const struct bfs_ctx *ctx, const struct bfs_expr *expr);
#endif // BFS_DIAG_H
diff --git a/src/dir.c b/src/dir.c
index fadf1c0..4bf72a1 100644
--- a/src/dir.c
+++ b/src/dir.c
@@ -1,13 +1,15 @@
// Copyright © Tavian Barnes <tavianator@tavianator.com>
// SPDX-License-Identifier: 0BSD
-#include "prelude.h"
#include "dir.h"
+
#include "alloc.h"
+#include "bfs.h"
#include "bfstd.h"
#include "diag.h"
#include "sanity.h"
#include "trie.h"
+
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
diff --git a/src/dir.h b/src/dir.h
index bbba071..4482003 100644
--- a/src/dir.h
+++ b/src/dir.h
@@ -8,7 +8,8 @@
#ifndef BFS_DIR_H
#define BFS_DIR_H
-#include "prelude.h"
+#include "bfs.h"
+
#include <sys/types.h>
/**
diff --git a/src/dstring.c b/src/dstring.c
index 86ab646..af499fd 100644
--- a/src/dstring.c
+++ b/src/dstring.c
@@ -1,11 +1,12 @@
// Copyright © Tavian Barnes <tavianator@tavianator.com>
// SPDX-License-Identifier: 0BSD
-#include "prelude.h"
#include "dstring.h"
+
#include "alloc.h"
#include "bit.h"
#include "diag.h"
+
#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
@@ -277,3 +278,20 @@ void dstrfree(dchar *dstr) {
free(dstrheader(dstr));
}
}
+
+dchar *dstrepeat(const char *str, size_t n) {
+ size_t len = strlen(str);
+ dchar *ret = dstralloc(n * len);
+ if (!ret) {
+ return NULL;
+ }
+
+ for (size_t i = 0; i < n; ++i) {
+ if (dstrxcat(&ret, str, len) < 0) {
+ dstrfree(ret);
+ return NULL;
+ }
+ }
+
+ return ret;
+}
diff --git a/src/dstring.h b/src/dstring.h
index 14e1d3e..79458e4 100644
--- a/src/dstring.h
+++ b/src/dstring.h
@@ -8,8 +8,9 @@
#ifndef BFS_DSTRING_H
#define BFS_DSTRING_H
-#include "prelude.h"
+#include "bfs.h"
#include "bfstd.h"
+
#include <stdarg.h>
#include <stddef.h>
@@ -41,7 +42,7 @@ void dstrfree(dchar *dstr);
* @param cap
* The initial capacity of the string.
*/
-attr(malloc(dstrfree, 1))
+_malloc(dstrfree, 1)
dchar *dstralloc(size_t cap);
/**
@@ -50,7 +51,7 @@ dchar *dstralloc(size_t cap);
* @param str
* The NUL-terminated string to copy.
*/
-attr(malloc(dstrfree, 1))
+_malloc(dstrfree, 1)
dchar *dstrdup(const char *str);
/**
@@ -61,7 +62,7 @@ dchar *dstrdup(const char *str);
* @param n
* The maximum number of characters to copy from str.
*/
-attr(malloc(dstrfree, 1))
+_malloc(dstrfree, 1)
dchar *dstrndup(const char *str, size_t n);
/**
@@ -70,7 +71,7 @@ dchar *dstrndup(const char *str, size_t n);
* @param dstr
* The dynamic string to copy.
*/
-attr(malloc(dstrfree, 1))
+_malloc(dstrfree, 1)
dchar *dstrddup(const dchar *dstr);
/**
@@ -81,7 +82,7 @@ dchar *dstrddup(const dchar *dstr);
* @param len
* The length of the string, which may include internal NUL bytes.
*/
-attr(malloc(dstrfree, 1))
+_malloc(dstrfree, 1)
dchar *dstrxdup(const char *str, size_t len);
/**
@@ -243,7 +244,7 @@ int dstrxcpy(dchar **dest, const char *str, size_t len);
* @return
* The created string, or NULL on failure.
*/
-attr(printf(1, 2))
+_printf(1, 2)
dchar *dstrprintf(const char *format, ...);
/**
@@ -256,7 +257,7 @@ dchar *dstrprintf(const char *format, ...);
* @return
* The created string, or NULL on failure.
*/
-attr(printf(1, 0))
+_printf(1, 0)
dchar *dstrvprintf(const char *format, va_list args);
/**
@@ -271,7 +272,7 @@ dchar *dstrvprintf(const char *format, va_list args);
* @return
* 0 on success, -1 on failure.
*/
-attr(printf(2, 3))
+_printf(2, 3)
int dstrcatf(dchar **str, const char *format, ...);
/**
@@ -286,7 +287,7 @@ int dstrcatf(dchar **str, const char *format, ...);
* @return
* 0 on success, -1 on failure.
*/
-attr(printf(2, 0))
+_printf(2, 0)
int dstrvcatf(dchar **str, const char *format, va_list args);
/**
@@ -319,4 +320,9 @@ int dstrescat(dchar **dest, const char *str, enum wesc_flags flags);
*/
int dstrnescat(dchar **dest, const char *str, size_t n, enum wesc_flags flags);
+/**
+ * Repeat a string n times.
+ */
+dchar *dstrepeat(const char *str, size_t n);
+
#endif // BFS_DSTRING_H
diff --git a/src/eval.c b/src/eval.c
index 0b5992e..9789b5d 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -5,10 +5,11 @@
* Implementation of all the primary expressions.
*/
-#include "prelude.h"
#include "eval.h"
+
#include "atomic.h"
#include "bar.h"
+#include "bfs.h"
#include "bfstd.h"
#include "bftw.h"
#include "color.h"
@@ -27,11 +28,13 @@
#include "stat.h"
#include "trie.h"
#include "xregex.h"
+
#include <errno.h>
#include <fcntl.h>
#include <fnmatch.h>
#include <grp.h>
#include <pwd.h>
+#include <signal.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
@@ -53,6 +56,8 @@ struct bfs_eval {
enum bftw_action action;
/** The bfs_eval() return value. */
int *ret;
+ /** The number of errors that have occurred. */
+ size_t *nerrors;
/** Whether to quit immediately. */
bool quit;
};
@@ -60,12 +65,18 @@ struct bfs_eval {
/**
* Print an error message.
*/
-attr(printf(2, 3))
+_printf(2, 3)
static void eval_error(struct bfs_eval *state, const char *format, ...) {
+ const struct bfs_ctx *ctx = state->ctx;
+
+ ++*state->nerrors;
+ if (ctx->ignore_errors) {
+ return;
+ }
+
// By POSIX, any errors should be accompanied by a non-zero exit status
*state->ret = EXIT_FAILURE;
- const struct bfs_ctx *ctx = state->ctx;
CFILE *cerr = ctx->cerr;
bfs_error(ctx, "%pP: ", state->ftwbuf);
@@ -269,10 +280,10 @@ bool eval_time(const struct bfs_expr *expr, struct bfs_eval *state) {
switch (expr->time_unit) {
case BFS_DAYS:
diff /= 60 * 24;
- fallthru;
+ _fallthrough;
case BFS_MINUTES:
diff /= 60;
- fallthru;
+ _fallthrough;
case BFS_SECONDS:
break;
}
@@ -1014,25 +1025,23 @@ bool eval_xtype(const struct bfs_expr *expr, struct bfs_eval *state) {
}
}
-#if _POSIX_MONOTONIC_CLOCK > 0
-# define BFS_CLOCK CLOCK_MONOTONIC
-#elif _POSIX_TIMERS > 0
-# define BFS_CLOCK CLOCK_REALTIME
-#endif
-
/**
- * Call clock_gettime(), if available.
+ * clock_gettime() wrapper.
*/
static int eval_gettime(struct bfs_eval *state, struct timespec *ts) {
-#ifdef BFS_CLOCK
- int ret = clock_gettime(BFS_CLOCK, ts);
+ clockid_t clock = CLOCK_REALTIME;
+
+#if defined(_POSIX_MONOTONIC_CLOCK) && _POSIX_MONOTONIC_CLOCK >= 0
+ if (sysoption(MONOTONIC_CLOCK) > 0) {
+ clock = CLOCK_MONOTONIC;
+ }
+#endif
+
+ int ret = clock_gettime(clock, ts);
if (ret != 0) {
bfs_warning(state->ctx, "%pP: clock_gettime(): %s.\n", state->ftwbuf, errstr());
}
return ret;
-#else
- return -1;
-#endif
}
/**
@@ -1382,8 +1391,6 @@ 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;
@@ -1393,6 +1400,8 @@ struct callback_args {
/** The set of seen files. */
struct trie *seen;
+ /** The number of errors that have occurred. */
+ size_t nerrors;
/** Eventual return value from bfs_eval(). */
int ret;
};
@@ -1411,6 +1420,7 @@ static enum bftw_action eval_callback(const struct BFTW *ftwbuf, void *ptr) {
state.ctx = ctx;
state.action = BFTW_CONTINUE;
state.ret = &args->ret;
+ state.nerrors = &args->nerrors;
state.quit = false;
// Check whether SIGINFO was delivered and show/hide the bar
@@ -1672,7 +1682,7 @@ int bfs_eval(struct bfs_ctx *ctx) {
#else
int siginfo = SIGUSR1;
#endif
- args.info_hook = sighook(siginfo, eval_siginfo, &args, SH_CONTINUE);
+ struct sighook *info_hook = sighook(siginfo, eval_siginfo, &args, SH_CONTINUE);
struct trie seen;
if (ctx->unique) {
@@ -1741,8 +1751,12 @@ int bfs_eval(struct bfs_ctx *ctx) {
trie_destroy(&seen);
}
- sigunhook(args.info_hook);
+ sigunhook(info_hook);
bfs_bar_hide(args.bar);
+ if (ctx->ignore_errors && args.nerrors > 0) {
+ bfs_warning(ctx, "Suppressed errors: %zu\n", args.nerrors);
+ }
+
return args.ret;
}
diff --git a/src/eval.h b/src/eval.h
index 4dd7996..48184c7 100644
--- a/src/eval.h
+++ b/src/eval.h
@@ -9,8 +9,6 @@
#ifndef BFS_EVAL_H
#define BFS_EVAL_H
-#include "prelude.h"
-
struct bfs_ctx;
struct bfs_expr;
diff --git a/src/exec.c b/src/exec.c
index 2faa731..45c9f1d 100644
--- a/src/exec.c
+++ b/src/exec.c
@@ -1,9 +1,10 @@
// Copyright © Tavian Barnes <tavianator@tavianator.com>
// SPDX-License-Identifier: 0BSD
-#include "prelude.h"
#include "exec.h"
+
#include "alloc.h"
+#include "bfs.h"
#include "bfstd.h"
#include "bftw.h"
#include "color.h"
@@ -11,6 +12,7 @@
#include "diag.h"
#include "dstring.h"
#include "xspawn.h"
+
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
@@ -22,7 +24,7 @@
#include <unistd.h>
/** Print some debugging info. */
-attr(printf(2, 3))
+_printf(2, 3)
static void bfs_exec_debug(const struct bfs_exec *execbuf, const char *format, ...) {
const struct bfs_ctx *ctx = execbuf->ctx;
diff --git a/src/expr.c b/src/expr.c
index 5784220..110e9b7 100644
--- a/src/expr.c
+++ b/src/expr.c
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: 0BSD
#include "expr.h"
+
#include "alloc.h"
#include "ctx.h"
#include "diag.h"
@@ -10,9 +11,12 @@
#include "list.h"
#include "printf.h"
#include "xregex.h"
+
#include <string.h>
-struct bfs_expr *bfs_expr_new(struct bfs_ctx *ctx, bfs_eval_fn *eval_fn, size_t argc, char **argv) {
+struct bfs_expr *bfs_expr_new(struct bfs_ctx *ctx, bfs_eval_fn *eval_fn, size_t argc, char **argv, enum bfs_kind kind) {
+ bfs_assert(kind != BFS_PATH);
+
struct bfs_expr *expr = arena_alloc(&ctx->expr_arena);
if (!expr) {
return NULL;
@@ -22,6 +26,7 @@ struct bfs_expr *bfs_expr_new(struct bfs_ctx *ctx, bfs_eval_fn *eval_fn, size_t
expr->eval_fn = eval_fn;
expr->argc = argc;
expr->argv = argv;
+ expr->kind = kind;
expr->probability = 0.5;
SLIST_PREPEND(&ctx->expr_list, expr, freelist);
diff --git a/src/expr.h b/src/expr.h
index 60b298d..f14b936 100644
--- a/src/expr.h
+++ b/src/expr.h
@@ -8,14 +8,35 @@
#ifndef BFS_EXPR_H
#define BFS_EXPR_H
-#include "prelude.h"
#include "color.h"
#include "eval.h"
#include "stat.h"
+
#include <sys/types.h>
#include <time.h>
/**
+ * Argument/token/expression kinds.
+ */
+enum bfs_kind {
+ /** A flag (-H, -L, etc.). */
+ BFS_FLAG,
+
+ /** A root path. */
+ BFS_PATH,
+
+ /** An option (-follow, -mindepth, etc.). */
+ BFS_OPTION,
+ /** A test (-name, -size, etc.). */
+ BFS_TEST,
+ /** An action (-print, -exec, etc.). */
+ BFS_ACTION,
+
+ /** An operator (-and, -or, etc.). */
+ BFS_OPERATOR,
+};
+
+/**
* Integer comparison modes.
*/
enum bfs_int_cmp {
@@ -97,6 +118,8 @@ struct bfs_expr {
size_t argc;
/** The command line arguments comprising this expression. */
char **argv;
+ /** The kind of expression this is. */
+ enum bfs_kind kind;
/** The number of files this expression keeps open between evaluations. */
int persistent_fds;
@@ -207,7 +230,7 @@ struct bfs_ctx;
/**
* Create a new expression.
*/
-struct bfs_expr *bfs_expr_new(struct bfs_ctx *ctx, bfs_eval_fn *eval, size_t argc, char **argv);
+struct bfs_expr *bfs_expr_new(struct bfs_ctx *ctx, bfs_eval_fn *eval, size_t argc, char **argv, enum bfs_kind kind);
/**
* @return Whether this type of expression has children.
diff --git a/src/fsade.c b/src/fsade.c
index 7310141..30b3018 100644
--- a/src/fsade.c
+++ b/src/fsade.c
@@ -1,14 +1,16 @@
// Copyright © Tavian Barnes <tavianator@tavianator.com>
// SPDX-License-Identifier: 0BSD
-#include "prelude.h"
#include "fsade.h"
+
#include "atomic.h"
+#include "bfs.h"
#include "bfstd.h"
#include "bftw.h"
#include "dir.h"
#include "dstring.h"
#include "sanity.h"
+
#include <errno.h>
#include <fcntl.h>
#include <stddef.h>
@@ -26,17 +28,19 @@
# include <selinux/selinux.h>
#endif
-#if BFS_USE_SYS_EXTATTR_H
+#if __has_include(<sys/extattr.h>)
# include <sys/extattr.h>
-#elif BFS_USE_SYS_XATTR_H
+# define BFS_USE_EXTATTR true
+#elif __has_include(<sys/xattr.h>)
# include <sys/xattr.h>
+# define BFS_USE_XATTR true
#endif
/**
* Many of the APIs used here don't have *at() variants, but we can try to
* emulate something similar if /proc/self/fd is available.
*/
-attr(maybe_unused)
+_maybe_unused
static const char *fake_at(const struct BFTW *ftwbuf) {
static atomic int proc_works = -1;
@@ -70,7 +74,7 @@ fail:
return ftwbuf->path;
}
-attr(maybe_unused)
+_maybe_unused
static void free_fake_at(const struct BFTW *ftwbuf, const char *path) {
if (path != ftwbuf->path) {
dstrfree((dchar *)path);
@@ -80,7 +84,7 @@ static void free_fake_at(const struct BFTW *ftwbuf, const char *path) {
/**
* Check if an error was caused by the absence of support or data for a feature.
*/
-attr(maybe_unused)
+_maybe_unused
static bool is_absence_error(int error) {
// If the OS doesn't support the feature, it's obviously not enabled for
// any files
@@ -160,7 +164,7 @@ static int bfs_acl_entry(acl_t acl, int which, acl_entry_t *entry) {
}
/** Unified interface for acl_get_tag_type(). */
-attr(maybe_unused)
+_maybe_unused
static int bfs_acl_tag_type(acl_entry_t entry, acl_tag_t *tag) {
#if BFS_HAS_ACL_GET_TAG_TYPE
return acl_get_tag_type(entry, tag);
@@ -344,7 +348,7 @@ int bfs_check_capabilities(const struct BFTW *ftwbuf) {
#if BFS_CAN_CHECK_XATTRS
-#if BFS_USE_SYS_EXTATTR_H
+#if BFS_USE_EXTATTR
/** Wrapper for extattr_list_{file,link}. */
static ssize_t bfs_extattr_list(const char *path, enum bfs_type type, int namespace) {
@@ -390,13 +394,13 @@ static ssize_t bfs_extattr_get(const char *path, enum bfs_type type, int namespa
#endif
}
-#endif // BFS_USE_SYS_EXTATTR_H
+#endif // BFS_USE_EXTATTR
int bfs_check_xattrs(const struct BFTW *ftwbuf) {
const char *path = fake_at(ftwbuf);
ssize_t len;
-#if BFS_USE_SYS_EXTATTR_H
+#if BFS_USE_EXTATTR
len = bfs_extattr_list(path, ftwbuf->type, EXTATTR_NAMESPACE_SYSTEM);
if (len <= 0) {
len = bfs_extattr_list(path, ftwbuf->type, EXTATTR_NAMESPACE_USER);
@@ -432,7 +436,7 @@ int bfs_check_xattr_named(const struct BFTW *ftwbuf, const char *name) {
const char *path = fake_at(ftwbuf);
ssize_t len;
-#if BFS_USE_SYS_EXTATTR_H
+#if BFS_USE_EXTATTR
len = bfs_extattr_get(path, ftwbuf->type, EXTATTR_NAMESPACE_SYSTEM, name);
if (len < 0) {
len = bfs_extattr_get(path, ftwbuf->type, EXTATTR_NAMESPACE_USER, name);
diff --git a/src/fsade.h b/src/fsade.h
index 4465017..77cf82a 100644
--- a/src/fsade.h
+++ b/src/fsade.h
@@ -9,7 +9,7 @@
#ifndef BFS_FSADE_H
#define BFS_FSADE_H
-#include "prelude.h"
+#include "bfs.h"
#define BFS_CAN_CHECK_ACL (BFS_HAS_ACL_GET_FILE || BFS_HAS_ACL_TRIVIAL)
@@ -17,7 +17,9 @@
#define BFS_CAN_CHECK_CONTEXT BFS_WITH_LIBSELINUX
-#define BFS_CAN_CHECK_XATTRS (BFS_USE_SYS_EXTATTR_H || BFS_USE_SYS_XATTR_H)
+#if __has_include(<sys/extattr.h>) || __has_include(<sys/xattr.h>)
+# define BFS_CAN_CHECK_XATTRS true
+#endif
struct BFTW;
diff --git a/src/ioq.c b/src/ioq.c
index 4057a5e..f936ff8 100644
--- a/src/ioq.c
+++ b/src/ioq.c
@@ -118,16 +118,18 @@
* [1]: https://arxiv.org/abs/2201.02179
*/
-#include "prelude.h"
#include "ioq.h"
+
#include "alloc.h"
#include "atomic.h"
+#include "bfs.h"
#include "bfstd.h"
#include "bit.h"
#include "diag.h"
#include "dir.h"
#include "stat.h"
#include "thread.h"
+
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
@@ -180,8 +182,7 @@ typedef atomic uintptr_t ioq_slot;
/** Amount to add for an additional skip. */
#define IOQ_SKIP_ONE (~IOQ_BLOCKED)
-// Need room for two flag bits
-bfs_static_assert(alignof(struct ioq_ent) >= (1 << 2));
+static_assert(alignof(struct ioq_ent) >= (1 << 2), "struct ioq_ent is underaligned");
/**
* An MPMC queue of I/O commands.
@@ -263,7 +264,7 @@ static struct ioq_monitor *ioq_slot_monitor(struct ioqq *ioqq, ioq_slot *slot) {
}
/** Atomically wait for a slot to change. */
-attr(noinline)
+_noinline
static uintptr_t ioq_slot_wait(struct ioqq *ioqq, ioq_slot *slot, uintptr_t value) {
struct ioq_monitor *monitor = ioq_slot_monitor(ioqq, slot);
mutex_lock(&monitor->mutex);
@@ -293,7 +294,7 @@ done:
}
/** Wake up any threads waiting on a slot. */
-attr(noinline)
+_noinline
static void ioq_slot_wake(struct ioqq *ioqq, ioq_slot *slot) {
struct ioq_monitor *monitor = ioq_slot_monitor(ioqq, slot);
@@ -313,9 +314,11 @@ static void ioq_slot_wake(struct ioqq *ioqq, ioq_slot *slot) {
cond_broadcast(&monitor->cond);
}
-/** Branch-free (slot & IOQ_SKIP) ? ~IOQ_BLOCKED : 0 */
-static uintptr_t ioq_skip_mask(uintptr_t slot) {
- return -(slot >> IOQ_SKIP_BIT) << 1;
+/** Branch-free ((slot & IOQ_SKIP) ? skip : full) & ~IOQ_BLOCKED */
+static uintptr_t ioq_slot_blend(uintptr_t slot, uintptr_t skip, uintptr_t full) {
+ uintptr_t mask = -(slot >> IOQ_SKIP_BIT);
+ uintptr_t ret = (skip & mask) | (full & ~mask);
+ return ret & ~IOQ_BLOCKED;
}
/** Push an entry into a slot. */
@@ -323,19 +326,18 @@ static bool ioq_slot_push(struct ioqq *ioqq, ioq_slot *slot, struct ioq_ent *ent
uintptr_t prev = load(slot, relaxed);
while (true) {
- size_t skip_mask = ioq_skip_mask(prev);
- size_t full_mask = ~skip_mask & ~IOQ_BLOCKED;
- if (prev & full_mask) {
+ uintptr_t full = ioq_slot_blend(prev, 0, prev);
+ if (full) {
// full(ptr) → wait
prev = ioq_slot_wait(ioqq, slot, prev);
continue;
}
// empty → full(ptr)
- uintptr_t next = ((uintptr_t)ent >> 1) & full_mask;
+ uintptr_t next = (uintptr_t)ent >> 1;
// skip(1) → empty
// skip(n) → skip(n - 1)
- next |= (prev - IOQ_SKIP_ONE) & skip_mask;
+ next = ioq_slot_blend(prev, prev - IOQ_SKIP_ONE, next);
if (compare_exchange_weak(slot, &prev, next, release, relaxed)) {
break;
@@ -357,9 +359,8 @@ static struct ioq_ent *ioq_slot_pop(struct ioqq *ioqq, ioq_slot *slot, bool bloc
// skip(n) → skip(n + 1)
// full(ptr) → full(ptr - 1)
uintptr_t next = prev + IOQ_SKIP_ONE;
- // skip(n) → ~IOQ_BLOCKED
// full(ptr) → 0
- next &= ioq_skip_mask(next);
+ next = ioq_slot_blend(next, next, 0);
if (block && next) {
prev = ioq_slot_wait(ioqq, slot, prev);
@@ -378,7 +379,7 @@ static struct ioq_ent *ioq_slot_pop(struct ioqq *ioqq, ioq_slot *slot, bool bloc
// empty → 0
// skip(n) → 0
// full(ptr) → ptr
- prev &= ioq_skip_mask(~prev);
+ prev = ioq_slot_blend(prev, 0, prev);
return (struct ioq_ent *)(prev << 1);
}
diff --git a/src/ioq.h b/src/ioq.h
index d8e1573..458c9e7 100644
--- a/src/ioq.h
+++ b/src/ioq.h
@@ -8,9 +8,9 @@
#ifndef BFS_IOQ_H
#define BFS_IOQ_H
-#include "prelude.h"
#include "dir.h"
#include "stat.h"
+
#include <stddef.h>
/**
diff --git a/src/list.h b/src/list.h
index d95483f..b30a96e 100644
--- a/src/list.h
+++ b/src/list.h
@@ -83,6 +83,7 @@
#define BFS_LIST_H
#include "diag.h"
+
#include <stddef.h>
#include <string.h>
diff --git a/src/main.c b/src/main.c
index 6f23034..da07508 100644
--- a/src/main.c
+++ b/src/main.c
@@ -20,13 +20,14 @@
* - bftw.[ch] (an extended version of nftw(3))
*
* - Utilities:
+ * - prelude.h (feature test macros; automatically included)
* - alloc.[ch] (memory allocation)
* - atomic.h (atomic operations)
* - bar.[ch] (a terminal status bar)
* - bit.h (bit manipulation)
+ * - bfs.h (configuration and fundamental utilities)
* - bfstd.[ch] (standard library wrappers/polyfills)
* - color.[ch] (for pretty terminal colors)
- * - prelude.h (configuration and feature/platform detection)
* - diag.[ch] (formats diagnostic messages)
* - dir.[ch] (a directory API facade)
* - dstring.[ch] (a dynamic string library)
@@ -47,12 +48,12 @@
* - xtime.[ch] (date/time handling utilities)
*/
-#include "prelude.h"
#include "bfstd.h"
#include "ctx.h"
#include "diag.h"
#include "eval.h"
#include "parse.h"
+
#include <errno.h>
#include <fcntl.h>
#include <locale.h>
diff --git a/src/mtab.c b/src/mtab.c
index 0377fea..79f3100 100644
--- a/src/mtab.c
+++ b/src/mtab.c
@@ -1,12 +1,14 @@
// Copyright © Tavian Barnes <tavianator@tavianator.com>
// SPDX-License-Identifier: 0BSD
-#include "prelude.h"
#include "mtab.h"
+
#include "alloc.h"
+#include "bfs.h"
#include "bfstd.h"
#include "stat.h"
#include "trie.h"
+
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
@@ -65,7 +67,7 @@ struct bfs_mtab {
/**
* Add an entry to the mount table.
*/
-attr(maybe_unused)
+_maybe_unused
static int bfs_mtab_add(struct bfs_mtab *mtab, const char *path, const char *type) {
size_t path_size = strlen(path) + 1;
size_t type_size = strlen(type) + 1;
diff --git a/src/mtab.h b/src/mtab.h
index 67290c2..6c7d5cb 100644
--- a/src/mtab.h
+++ b/src/mtab.h
@@ -8,8 +8,6 @@
#ifndef BFS_MTAB_H
#define BFS_MTAB_H
-#include "prelude.h"
-
struct bfs_stat;
/**
diff --git a/src/opt.c b/src/opt.c
index 9b091bd..9d4323d 100644
--- a/src/opt.c
+++ b/src/opt.c
@@ -25,8 +25,10 @@
* effects are reachable at all, skipping the traversal if not.
*/
-#include "prelude.h"
#include "opt.h"
+
+#include "bfs.h"
+#include "bfstd.h"
#include "bftw.h"
#include "bit.h"
#include "color.h"
@@ -38,6 +40,8 @@
#include "expr.h"
#include "list.h"
#include "pwcache.h"
+#include "xspawn.h"
+
#include <errno.h>
#include <limits.h>
#include <stdarg.h>
@@ -173,11 +177,17 @@ static void constrain_min(struct df_range *range, long long value) {
range->min = max_value(range->min, value);
}
-/** Contrain the maximum of a range. */
+/** Constrain the maximum of a range. */
static void constrain_max(struct df_range *range, long long value) {
range->max = min_value(range->max, value);
}
+/** Constrain a range to a single value. */
+static void constrain_range(struct df_range *range, long long value) {
+ constrain_min(range, value);
+ constrain_max(range, value);
+}
+
/** Remove a single value from a range. */
static void range_remove(struct df_range *range, long long value) {
if (range->min == value) {
@@ -364,7 +374,7 @@ struct bfs_opt {
};
/** Log an optimization. */
-attr(printf(2, 3))
+_printf(2, 3)
static bool opt_debug(struct bfs_opt *opt, const char *format, ...) {
if (bfs_debug_prefix(opt->ctx, DEBUG_OPT)) {
for (int i = 0; i < opt->depth; ++i) {
@@ -382,7 +392,7 @@ static bool opt_debug(struct bfs_opt *opt, const char *format, ...) {
}
/** Log a recursive call. */
-attr(printf(2, 3))
+_printf(2, 3)
static bool opt_enter(struct bfs_opt *opt, const char *format, ...) {
int depth = opt->depth;
if (depth > 0) {
@@ -402,7 +412,7 @@ static bool opt_enter(struct bfs_opt *opt, const char *format, ...) {
}
/** Log a recursive return. */
-attr(printf(2, 3))
+_printf(2, 3)
static bool opt_leave(struct bfs_opt *opt, const char *format, ...) {
bool debug = false;
int depth = opt->depth;
@@ -426,7 +436,7 @@ static bool opt_leave(struct bfs_opt *opt, const char *format, ...) {
}
/** Log a shallow visit. */
-attr(printf(2, 3))
+_printf(2, 3)
static bool opt_visit(struct bfs_opt *opt, const char *format, ...) {
int depth = opt->depth;
if (depth > 0) {
@@ -446,7 +456,7 @@ static bool opt_visit(struct bfs_opt *opt, const char *format, ...) {
}
/** Log the deletion of an expression. */
-attr(printf(2, 3))
+_printf(2, 3)
static bool opt_delete(struct bfs_opt *opt, const char *format, ...) {
int depth = opt->depth;
@@ -608,22 +618,26 @@ static bool is_const(const struct bfs_expr *expr) {
}
/** Warn about an expression. */
-attr(printf(3, 4))
-static void opt_warning(const struct bfs_opt *opt, const struct bfs_expr *expr, const char *format, ...) {
+_printf(3, 4)
+static bool opt_warning(const struct bfs_opt *opt, const struct bfs_expr *expr, const char *format, ...) {
if (!opt->warn) {
- return;
+ return false;
}
if (bfs_expr_is_parent(expr) || is_const(expr)) {
- return;
+ return false;
}
- if (bfs_expr_warning(opt->ctx, expr)) {
- va_list args;
- va_start(args, format);
- bfs_vwarning(opt->ctx, format, args);
- va_end(args);
+ if (!bfs_expr_warning(opt->ctx, expr)) {
+ return false;
}
+
+ va_list args;
+ va_start(args, format);
+ bfs_vwarning(opt->ctx, format, args);
+ va_end(args);
+
+ return true;
}
/** Remove and return an expression's children. */
@@ -1327,7 +1341,7 @@ static struct bfs_expr *opt_const(struct bfs_opt *opt, bool value) {
static bfs_eval_fn *const fns[] = {eval_false, eval_true};
static char *fake_args[] = {"-false", "-true"};
- struct bfs_expr *expr = bfs_expr_new(opt->ctx, fns[value], 1, &fake_args[value]);
+ struct bfs_expr *expr = bfs_expr_new(opt->ctx, fns[value], 1, &fake_args[value], BFS_TEST);
return visit_shallow(opt, expr, &annotate);
}
@@ -1341,7 +1355,7 @@ static struct bfs_expr *negate_expr(struct bfs_opt *opt, struct bfs_expr *expr,
return opt_const(opt, true);
}
- struct bfs_expr *ret = bfs_expr_new(opt->ctx, eval_not, 1, argv);
+ struct bfs_expr *ret = bfs_expr_new(opt->ctx, eval_not, 1, argv, BFS_OPERATOR);
if (!ret) {
return NULL;
}
@@ -1389,12 +1403,11 @@ static struct bfs_expr *sink_not_andor(struct bfs_opt *opt, struct bfs_expr *exp
/** Sink a negation into a comma expression. */
static struct bfs_expr *sink_not_comma(struct bfs_opt *opt, struct bfs_expr *expr) {
- bfs_assert(expr->eval_fn == eval_comma);
-
- opt_enter(opt, "%pe\n", expr);
-
char **argv = expr->argv;
expr = only_child(expr);
+ opt_enter(opt, "%pe\n", expr);
+
+ bfs_assert(expr->eval_fn == eval_comma);
struct bfs_exprs children;
foster_children(expr, &children);
@@ -1601,8 +1614,7 @@ static void data_flow_icmp(struct bfs_opt *opt, const struct bfs_expr *expr, enu
switch (expr->int_cmp) {
case BFS_INT_EQUAL:
- constrain_min(true_range, value);
- constrain_max(true_range, value);
+ constrain_range(true_range, value);
range_remove(false_range, value);
break;
@@ -1635,6 +1647,18 @@ static struct bfs_expr *data_flow_access(struct bfs_opt *opt, struct bfs_expr *e
return expr;
}
+/** Transfer function for -empty. */
+static struct bfs_expr *data_flow_empty(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor) {
+ opt->after_true.types &= (1 << BFS_REG) | (1 << BFS_DIR);
+
+ if (opt->before.types == (1 << BFS_REG)) {
+ constrain_range(&opt->after_true.ranges[SIZE_RANGE], 0);
+ range_remove(&opt->after_false.ranges[SIZE_RANGE], 0);
+ }
+
+ return expr;
+}
+
/** Transfer function for -gid. */
static struct bfs_expr *data_flow_gid(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor) {
struct df_range *range = &opt->after_true.ranges[GID_RANGE];
@@ -1673,11 +1697,16 @@ static struct bfs_expr *data_flow_links(struct bfs_opt *opt, struct bfs_expr *ex
return expr;
}
+/** Transfer function for -lname. */
+static struct bfs_expr *data_flow_lname(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor) {
+ opt->after_true.types &= 1 << BFS_LNK;
+ return expr;
+}
+
/** Transfer function for -samefile. */
static struct bfs_expr *data_flow_samefile(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor) {
struct df_range *true_range = &opt->after_true.ranges[INUM_RANGE];
- constrain_min(true_range, expr->ino);
- constrain_max(true_range, expr->ino);
+ constrain_range(true_range, expr->ino);
struct df_range *false_range = &opt->after_false.ranges[INUM_RANGE];
range_remove(false_range, expr->ino);
@@ -1785,12 +1814,45 @@ static struct bfs_expr *data_flow_leave(struct bfs_opt *opt, struct bfs_expr *ex
return visit_leave(opt, expr, visitor);
}
-/** Data flow visitor function. */
-static struct bfs_expr *data_flow_visit(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor) {
- if (opt->ignore_result && expr->pure) {
+/** Ignore an expression (and possibly warn/prompt). */
+static struct bfs_expr *opt_ignore(struct bfs_opt *opt, struct bfs_expr *expr, bool delete) {
+ if (delete) {
+ opt_delete(opt, "%pe [ignored result]\n", expr);
+ } else {
opt_debug(opt, "ignored result\n");
- opt_warning(opt, expr, "The result of this expression is ignored.\n\n");
+ }
+
+ if (expr->kind != BFS_TEST) {
+ goto done;
+ }
+
+ if (!opt_warning(opt, expr, "The result of this expression is ignored.\n")) {
+ goto done;
+ }
+
+ struct bfs_ctx *ctx = opt->ctx;
+ if (ctx->interactive && ctx->dangerous) {
+ bfs_warning(ctx, "Do you want to continue? ");
+ if (ynprompt() <= 0) {
+ errno = 0;
+ return NULL;
+ }
+ }
+
+ fprintf(stderr, "\n");
+
+done:
+ if (!delete && expr->pure) {
+ // If we're not deleting the expression entirely, replace it with -false
expr = opt_const(opt, false);
+ }
+ return expr;
+}
+
+/** Data flow visitor function. */
+static struct bfs_expr *data_flow_visit(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor) {
+ if (opt->ignore_result) {
+ expr = opt_ignore(opt, expr, false);
if (!expr) {
return NULL;
}
@@ -1860,9 +1922,11 @@ static const struct visitor data_flow = {
.leave = data_flow_leave,
.table = (const struct visitor_table[]) {
{eval_access, data_flow_access},
+ {eval_empty, data_flow_empty},
{eval_gid, data_flow_gid},
{eval_inum, data_flow_inum},
{eval_links, data_flow_links},
+ {eval_lname, data_flow_lname},
{eval_samefile, data_flow_samefile},
{eval_size, data_flow_size},
{eval_type, data_flow_type},
@@ -1925,6 +1989,10 @@ static struct bfs_expr *lift_andor_not(struct bfs_opt *opt, struct bfs_expr *exp
}
expr = visit_shallow(opt, expr, &annotate);
+ if (!expr) {
+ return NULL;
+ }
+
return negate_expr(opt, expr, &fake_not_arg);
}
@@ -1962,8 +2030,9 @@ static struct bfs_expr *simplify_and(struct bfs_opt *opt, struct bfs_expr *expr,
}
if (ignore) {
- opt_delete(opt, "%pe [ignored result]\n", child);
- opt_warning(opt, child, "The result of this expression is ignored.\n\n");
+ if (!opt_ignore(opt, child, true)) {
+ return NULL;
+ }
continue;
}
@@ -2010,8 +2079,9 @@ static struct bfs_expr *simplify_or(struct bfs_opt *opt, struct bfs_expr *expr,
}
if (ignore) {
- opt_delete(opt, "%pe [ignored result]\n", child);
- opt_warning(opt, child, "The result of this expression is ignored.\n\n");
+ if (!opt_ignore(opt, child, true)) {
+ return NULL;
+ }
continue;
}
@@ -2051,8 +2121,9 @@ static struct bfs_expr *simplify_comma(struct bfs_opt *opt, struct bfs_expr *exp
struct bfs_expr *child = SLIST_POP(&children);
if (opt->level >= 2 && child->pure && !SLIST_EMPTY(&children)) {
- opt_delete(opt, "%pe [ignored result]\n", child);
- opt_warning(opt, child, "The result of this expression is ignored.\n\n");
+ if (!opt_ignore(opt, child, true)) {
+ return NULL;
+ }
continue;
}
@@ -2103,6 +2174,8 @@ static struct bfs_expr *optimize(struct bfs_opt *opt, struct bfs_expr *expr) {
};
struct df_domain impure;
+ df_init_top(&opt->after_true);
+ df_init_top(&opt->after_false);
for (int i = 0; i < 3; ++i) {
struct bfs_opt nested = *opt;
@@ -2116,9 +2189,11 @@ static struct bfs_expr *optimize(struct bfs_opt *opt, struct bfs_expr *expr) {
continue;
}
+ const struct visitor *visitor = passes[j].visitor;
+
// Skip reordering the first time through the passes, to
// make warnings more understandable
- if (passes[j].visitor == &reorder) {
+ if (visitor == &reorder) {
if (i == 0) {
continue;
} else {
@@ -2126,10 +2201,15 @@ static struct bfs_expr *optimize(struct bfs_opt *opt, struct bfs_expr *expr) {
}
}
- expr = visit(&nested, expr, passes[j].visitor);
+ expr = visit(&nested, expr, visitor);
if (!expr) {
return NULL;
}
+
+ if (visitor == &data_flow) {
+ opt->after_true = nested.after_true;
+ opt->after_false = nested.after_false;
+ }
}
opt_leave(&nested, NULL);
diff --git a/src/parse.c b/src/parse.c
index 86ce72c..42f71cc 100644
--- a/src/parse.c
+++ b/src/parse.c
@@ -8,9 +8,10 @@
* flags like always-true options, and skipping over paths wherever they appear.
*/
-#include "prelude.h"
#include "parse.h"
+
#include "alloc.h"
+#include "bfs.h"
#include "bfstd.h"
#include "bftw.h"
#include "color.h"
@@ -31,6 +32,7 @@
#include "xregex.h"
#include "xspawn.h"
#include "xtime.h"
+
#include <errno.h>
#include <fcntl.h>
#include <fnmatch.h>
@@ -78,8 +80,6 @@ struct bfs_parser {
/** Whether stdout is a terminal. */
bool stdout_tty;
- /** Whether this session is interactive (stdin and stderr are each a terminal). */
- bool interactive;
/** Whether -color or -nocolor has been passed. */
enum use_color use_color;
/** Whether a -print action is implied. */
@@ -115,31 +115,6 @@ struct bfs_parser {
};
/**
- * Token types and flags.
- */
-enum token_info {
- /** A flag. */
- T_FLAG = 1,
- /** A root path. */
- T_PATH = 2,
- /** An option. */
- T_OPTION = 3,
- /** A test. */
- T_TEST = 4,
- /** An action. */
- T_ACTION = 5,
- /** An 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,
-};
-
-/**
* Print a low-level error message during parsing.
*/
static void parse_perror(const struct bfs_parser *parser, const char *str) {
@@ -165,7 +140,7 @@ static void highlight_args(const struct bfs_ctx *ctx, char **argv, size_t argc,
/**
* Print an error message during parsing.
*/
-attr(printf(2, 3))
+_printf(2, 3)
static void parse_error(const struct bfs_parser *parser, const char *format, ...) {
const struct bfs_ctx *ctx = parser->ctx;
@@ -183,7 +158,7 @@ static void parse_error(const struct bfs_parser *parser, const char *format, ...
/**
* Print an error about some command line arguments.
*/
-attr(printf(4, 5))
+_printf(4, 5)
static void parse_argv_error(const struct bfs_parser *parser, char **argv, size_t argc, const char *format, ...) {
const struct bfs_ctx *ctx = parser->ctx;
@@ -201,7 +176,7 @@ static void parse_argv_error(const struct bfs_parser *parser, char **argv, size_
/**
* Print an error about conflicting command line arguments.
*/
-attr(printf(6, 7))
+_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, ...) {
const struct bfs_ctx *ctx = parser->ctx;
@@ -220,7 +195,7 @@ static void parse_conflict_error(const struct bfs_parser *parser, char **argv1,
/**
* Print an error about an expression.
*/
-attr(printf(3, 4))
+_printf(3, 4)
static void parse_expr_error(const struct bfs_parser *parser, const struct bfs_expr *expr, const char *format, ...) {
const struct bfs_ctx *ctx = parser->ctx;
@@ -235,7 +210,7 @@ static void parse_expr_error(const struct bfs_parser *parser, const struct bfs_e
/**
* Print a warning message during parsing.
*/
-attr(printf(2, 3))
+_printf(2, 3)
static bool parse_warning(const struct bfs_parser *parser, const char *format, ...) {
const struct bfs_ctx *ctx = parser->ctx;
@@ -256,7 +231,7 @@ static bool parse_warning(const struct bfs_parser *parser, const char *format, .
/**
* Print a warning about conflicting command line arguments.
*/
-attr(printf(6, 7))
+_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, ...) {
const struct bfs_ctx *ctx = parser->ctx;
@@ -278,7 +253,7 @@ static bool parse_conflict_warning(const struct bfs_parser *parser, char **argv1
/**
* Print a warning about an expression.
*/
-attr(printf(3, 4))
+_printf(3, 4)
static bool parse_expr_warning(const struct bfs_parser *parser, const struct bfs_expr *expr, const char *format, ...) {
const struct bfs_ctx *ctx = parser->ctx;
@@ -296,8 +271,8 @@ static bool parse_expr_warning(const struct bfs_parser *parser, const struct bfs
/**
* Allocate a new expression.
*/
-static struct bfs_expr *parse_new_expr(const struct bfs_parser *parser, bfs_eval_fn *eval_fn, size_t argc, char **argv) {
- struct bfs_expr *expr = bfs_expr_new(parser->ctx, eval_fn, argc, argv);
+static struct bfs_expr *parse_new_expr(const struct bfs_parser *parser, bfs_eval_fn *eval_fn, size_t argc, char **argv, enum bfs_kind kind) {
+ struct bfs_expr *expr = bfs_expr_new(parser->ctx, eval_fn, argc, argv, kind);
if (!expr) {
parse_perror(parser, "bfs_expr_new()");
}
@@ -308,7 +283,7 @@ static struct bfs_expr *parse_new_expr(const struct bfs_parser *parser, bfs_eval
* Create a new unary expression.
*/
static struct bfs_expr *new_unary_expr(const struct bfs_parser *parser, bfs_eval_fn *eval_fn, struct bfs_expr *rhs, char **argv) {
- struct bfs_expr *expr = parse_new_expr(parser, eval_fn, 1, argv);
+ struct bfs_expr *expr = parse_new_expr(parser, eval_fn, 1, argv, BFS_OPERATOR);
if (!expr) {
return NULL;
}
@@ -322,7 +297,7 @@ static struct bfs_expr *new_unary_expr(const struct bfs_parser *parser, bfs_eval
* Create a new binary expression.
*/
static struct bfs_expr *new_binary_expr(const struct bfs_parser *parser, bfs_eval_fn *eval_fn, struct bfs_expr *lhs, struct bfs_expr *rhs, char **argv) {
- struct bfs_expr *expr = parse_new_expr(parser, eval_fn, 1, argv);
+ struct bfs_expr *expr = parse_new_expr(parser, eval_fn, 1, argv, BFS_OPERATOR);
if (!expr) {
return NULL;
}
@@ -407,14 +382,12 @@ 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_info type, size_t argc) {
- bfs_assert(type == (type & T_TYPE));
-
- if (type != T_FLAG && type != T_PATH) {
+static char **parser_advance(struct bfs_parser *parser, enum bfs_kind kind, size_t argc) {
+ if (kind != BFS_FLAG && kind != BFS_PATH) {
parser->expr_started = true;
}
- if (type != T_PATH) {
+ if (kind != BFS_PATH) {
parser->last_arg = parser->argv;
}
@@ -460,7 +433,7 @@ static int skip_paths(struct bfs_parser *parser) {
// find uses -- to separate flags from the rest
// of the command line. We allow mixing flags
// and paths/predicates, so we just ignore --.
- parser_advance(parser, T_FLAG, 1);
+ parser_advance(parser, BFS_FLAG, 1);
continue;
}
if (strcmp(arg, "-") != 0) {
@@ -492,7 +465,7 @@ static int skip_paths(struct bfs_parser *parser) {
return -1;
}
- parser_advance(parser, T_PATH, 1);
+ parser_advance(parser, BFS_PATH, 1);
}
}
@@ -623,8 +596,8 @@ static bool looks_like_icmp(const char *str) {
* Parse a single flag.
*/
static struct bfs_expr *parse_flag(struct bfs_parser *parser, size_t argc) {
- char **argv = parser_advance(parser, T_FLAG, argc);
- return parse_new_expr(parser, eval_true, argc, argv);
+ char **argv = parser_advance(parser, BFS_FLAG, argc);
+ return parse_new_expr(parser, eval_true, argc, argv, BFS_FLAG);
}
/**
@@ -677,8 +650,8 @@ static struct bfs_expr *parse_prefix_flag(struct bfs_parser *parser, char flag,
* Parse a single option.
*/
static struct bfs_expr *parse_option(struct bfs_parser *parser, size_t argc) {
- char **argv = parser_advance(parser, T_OPTION, argc);
- return parse_new_expr(parser, eval_true, argc, argv);
+ char **argv = parser_advance(parser, BFS_OPTION, argc);
+ return parse_new_expr(parser, eval_true, argc, argv, BFS_OPTION);
}
/**
@@ -706,8 +679,8 @@ static struct bfs_expr *parse_unary_option(struct bfs_parser *parser) {
* Parse a single test.
*/
static struct bfs_expr *parse_test(struct bfs_parser *parser, bfs_eval_fn *eval_fn, size_t argc) {
- char **argv = parser_advance(parser, T_TEST, argc);
- return parse_new_expr(parser, eval_fn, argc, argv);
+ char **argv = parser_advance(parser, BFS_TEST, argc);
+ return parse_new_expr(parser, eval_fn, argc, argv, BFS_TEST);
}
/**
@@ -735,7 +708,7 @@ static struct bfs_expr *parse_unary_test(struct bfs_parser *parser, bfs_eval_fn
* Parse a single action.
*/
static struct bfs_expr *parse_action(struct bfs_parser *parser, bfs_eval_fn *eval_fn, size_t argc) {
- char **argv = parser_advance(parser, T_ACTION, argc);
+ char **argv = parser_advance(parser, BFS_ACTION, argc);
if (parser->excluding) {
parse_argv_error(parser, argv, argc, "This action is not supported within ${red}-exclude${rs}.\n");
@@ -746,7 +719,7 @@ static struct bfs_expr *parse_action(struct bfs_parser *parser, bfs_eval_fn *eva
parser->implicit_print = false;
}
- return parse_new_expr(parser, eval_fn, argc, argv);
+ return parse_new_expr(parser, eval_fn, argc, argv, BFS_ACTION);
}
/**
@@ -1005,16 +978,16 @@ static struct bfs_expr *parse_time(struct bfs_parser *parser, int field, int arg
switch (*tail) {
case 'w':
time *= 7;
- fallthru;
+ _fallthrough;
case 'd':
time *= 24;
- fallthru;
+ _fallthrough;
case 'h':
time *= 60;
- fallthru;
+ _fallthrough;
case 'm':
time *= 60;
- fallthru;
+ _fallthrough;
case 's':
break;
default:
@@ -1126,7 +1099,7 @@ static struct bfs_expr *parse_fnmatch(const struct bfs_parser *parser, struct bf
// strcmp() can be much faster than fnmatch() since it doesn't have to
// parse the pattern, so special-case patterns with no wildcards.
//
- // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_13_01
+ // https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap02.html#tag_19_14_01
expr->literal = strcspn(expr->pattern, "?*\\[") == len;
return expr;
@@ -1185,8 +1158,12 @@ static struct bfs_expr *parse_daystart(struct bfs_parser *parser, int arg1, int
* Parse -delete.
*/
static struct bfs_expr *parse_delete(struct bfs_parser *parser, int arg1, int arg2) {
- parser->ctx->flags |= BFTW_POST_ORDER;
+ struct bfs_ctx *ctx = parser->ctx;
+ ctx->flags |= BFTW_POST_ORDER;
+ ctx->dangerous = true;
+
parser->depth_arg = parser->argv;
+
return parse_nullary_action(parser, eval_delete);
}
@@ -1246,7 +1223,9 @@ static struct bfs_expr *parse_empty(struct bfs_parser *parser, int arg1, int arg
* Parse -exec(dir)?/-ok(dir)?.
*/
static struct bfs_expr *parse_exec(struct bfs_parser *parser, int flags, int arg2) {
- struct bfs_exec *execbuf = bfs_exec_parse(parser->ctx, parser->argv, flags);
+ struct bfs_ctx *ctx = parser->ctx;
+
+ struct bfs_exec *execbuf = bfs_exec_parse(ctx, parser->argv, flags);
if (!execbuf) {
return NULL;
}
@@ -1295,6 +1274,8 @@ static struct bfs_expr *parse_exec(struct bfs_parser *parser, int flags, int arg
if (execbuf->flags & BFS_EXEC_CONFIRM) {
parser->ok_expr = expr;
+ } else {
+ ctx->dangerous = true;
}
return expr;
@@ -1694,10 +1675,7 @@ static struct bfs_expr *parse_mount(struct bfs_parser *parser, int arg1, int arg
return NULL;
}
- parse_expr_warning(parser, expr, "In the future, ${blu}%s${rs} will skip mount points entirely, unlike\n", expr->argv[0]);
- bfs_warning(parser->ctx, "${blu}-xdev${rs}, due to http://austingroupbugs.net/view.php?id=1133.\n\n");
-
- parser->ctx->flags |= BFTW_PRUNE_MOUNTS;
+ parser->ctx->flags |= BFTW_SKIP_MOUNTS;
parser->mount_arg = expr->argv;
return expr;
}
@@ -1841,6 +1819,14 @@ static struct bfs_expr *parse_newerxy(struct bfs_parser *parser, int arg1, int a
}
/**
+ * Parse -noerror.
+ */
+static struct bfs_expr *parse_noerror(struct bfs_parser *parser, int arg1, int arg2) {
+ parser->ctx->ignore_errors = true;
+ return parse_nullary_option(parser);
+}
+
+/**
* Parse -nogroup.
*/
static struct bfs_expr *parse_nogroup(struct bfs_parser *parser, int arg1, int arg2) {
@@ -1856,7 +1842,7 @@ static struct bfs_expr *parse_nogroup(struct bfs_parser *parser, int arg1, int a
* Parse -nohidden.
*/
static struct bfs_expr *parse_nohidden(struct bfs_parser *parser, int arg1, int arg2) {
- struct bfs_expr *hidden = parse_new_expr(parser, eval_hidden, 1, &fake_hidden_arg);
+ struct bfs_expr *hidden = parse_new_expr(parser, eval_hidden, 1, &fake_hidden_arg, BFS_TEST);
if (!hidden) {
return NULL;
}
@@ -1904,6 +1890,8 @@ static int parse_mode(const struct bfs_parser *parser, const char *mode, struct
return 0;
}
+ mode_t umask = parser->ctx->umask;
+
expr->file_mode = 0;
expr->dir_mode = 0;
@@ -1924,7 +1912,7 @@ static int parse_mode(const struct bfs_parser *parser, const char *mode, struct
//
// PERMCOPY : "u" | "g" | "o"
- // Parser machine parser
+ // State machine state
enum {
MODE_CLAUSE,
MODE_WHO,
@@ -1932,7 +1920,7 @@ static int parse_mode(const struct bfs_parser *parser, const char *mode, struct
MODE_ACTION_APPLY,
MODE_OP,
MODE_PERM,
- } mparser = MODE_CLAUSE;
+ } state = MODE_CLAUSE;
enum {
MODE_PLUS,
@@ -1941,16 +1929,18 @@ static int parse_mode(const struct bfs_parser *parser, const char *mode, struct
} op uninit(MODE_EQUALS);
mode_t who uninit(0);
+ mode_t mask uninit(0);
mode_t file_change uninit(0);
mode_t dir_change uninit(0);
const char *i = mode;
while (true) {
- switch (mparser) {
+ switch (state) {
case MODE_CLAUSE:
who = 0;
- mparser = MODE_WHO;
- fallthru;
+ mask = 0777;
+ state = MODE_WHO;
+ _fallthrough;
case MODE_WHO:
switch (*i) {
@@ -1967,7 +1957,7 @@ static int parse_mode(const struct bfs_parser *parser, const char *mode, struct
who |= 0777;
break;
default:
- mparser = MODE_ACTION;
+ state = MODE_ACTION;
continue;
}
break;
@@ -1977,7 +1967,7 @@ static int parse_mode(const struct bfs_parser *parser, const char *mode, struct
case MODE_EQUALS:
expr->file_mode &= ~who;
expr->dir_mode &= ~who;
- fallthru;
+ _fallthrough;
case MODE_PLUS:
expr->file_mode |= file_change;
expr->dir_mode |= dir_change;
@@ -1987,37 +1977,40 @@ static int parse_mode(const struct bfs_parser *parser, const char *mode, struct
expr->dir_mode &= ~dir_change;
break;
}
- fallthru;
+ _fallthrough;
case MODE_ACTION:
if (who == 0) {
who = 0777;
+ mask = who & ~umask;
+ } else {
+ mask = who;
}
switch (*i) {
case '+':
op = MODE_PLUS;
- mparser = MODE_OP;
+ state = MODE_OP;
break;
case '-':
op = MODE_MINUS;
- mparser = MODE_OP;
+ state = MODE_OP;
break;
case '=':
op = MODE_EQUALS;
- mparser = MODE_OP;
+ state = MODE_OP;
break;
case ',':
- if (mparser == MODE_ACTION_APPLY) {
- mparser = MODE_CLAUSE;
+ if (state == MODE_ACTION_APPLY) {
+ state = MODE_CLAUSE;
} else {
goto fail;
}
break;
case '\0':
- if (mparser == MODE_ACTION_APPLY) {
+ if (state == MODE_ACTION_APPLY) {
goto done;
} else {
goto fail;
@@ -2046,32 +2039,32 @@ static int parse_mode(const struct bfs_parser *parser, const char *mode, struct
default:
file_change = 0;
dir_change = 0;
- mparser = MODE_PERM;
+ state = MODE_PERM;
continue;
}
file_change |= (file_change << 6) | (file_change << 3);
- file_change &= who;
+ file_change &= mask;
dir_change |= (dir_change << 6) | (dir_change << 3);
- dir_change &= who;
- mparser = MODE_ACTION_APPLY;
+ dir_change &= mask;
+ state = MODE_ACTION_APPLY;
break;
case MODE_PERM:
switch (*i) {
case 'r':
- file_change |= who & 0444;
- dir_change |= who & 0444;
+ file_change |= mask & 0444;
+ dir_change |= mask & 0444;
break;
case 'w':
- file_change |= who & 0222;
- dir_change |= who & 0222;
+ file_change |= mask & 0222;
+ dir_change |= mask & 0222;
break;
case 'x':
- file_change |= who & 0111;
- fallthru;
+ file_change |= mask & 0111;
+ _fallthrough;
case 'X':
- dir_change |= who & 0111;
+ dir_change |= mask & 0111;
break;
case 's':
if (who & 0700) {
@@ -2090,7 +2083,7 @@ static int parse_mode(const struct bfs_parser *parser, const char *mode, struct
}
break;
default:
- mparser = MODE_ACTION_APPLY;
+ state = MODE_ACTION_APPLY;
continue;
}
break;
@@ -2132,7 +2125,7 @@ static struct bfs_expr *parse_perm(struct bfs_parser *parser, int field, int arg
++mode;
break;
}
- fallthru;
+ _fallthrough;
default:
expr->mode_cmp = BFS_MODE_EQUAL;
break;
@@ -2774,8 +2767,9 @@ static struct bfs_expr *parse_help(struct bfs_parser *parser, int arg1, int arg2
cfprintf(cout, " ${blu}-mindepth${rs} ${bld}N${rs}\n");
cfprintf(cout, " Ignore files deeper/shallower than ${bld}N${rs}\n");
cfprintf(cout, " ${blu}-mount${rs}\n");
- cfprintf(cout, " Don't descend into other mount points (same as ${blu}-xdev${rs} for now, but will\n");
- cfprintf(cout, " skip mount points entirely in the future)\n");
+ cfprintf(cout, " Exclude mount points entirely from the results\n");
+ cfprintf(cout, " ${blu}-noerror${rs}\n");
+ cfprintf(cout, " Ignore any errors that occur during traversal\n");
cfprintf(cout, " ${blu}-nohidden${rs}\n");
cfprintf(cout, " Exclude hidden files\n");
cfprintf(cout, " ${blu}-noleaf${rs}\n");
@@ -2952,11 +2946,42 @@ static struct bfs_expr *parse_help(struct bfs_parser *parser, int arg1, int arg2
return NULL;
}
+/** Print the bfs "logo". */
+static void print_logo(CFILE *cout) {
+ if (!cout->colors) {
+ goto boring;
+ }
+
+ size_t vwidth = xstrwidth(bfs_version);
+ dchar *spaces = dstrepeat(" ", vwidth);
+ dchar *lines = dstrepeat("─", vwidth);
+ if (!spaces || !lines) {
+ dstrfree(lines);
+ dstrfree(spaces);
+ goto boring;
+ }
+
+ // We do ----\r<emoji> rather than <emoji>--- so we don't have to assume
+ // anything about the width of the emoji
+ cfprintf(cout, "╭─────%s╮\r📂\n", lines);
+ cfprintf(cout, "├${ex}b${rs} %s │\n", spaces);
+ cfprintf(cout, "╰├${ex}f${rs} ${bld}%s${rs} │\n", bfs_version);
+ cfprintf(cout, " ╰├${ex}s${rs} %s │\n", spaces);
+ cfprintf(cout, " ╰──%s─╯\n\n", lines);
+
+ dstrfree(lines);
+ dstrfree(spaces);
+ return;
+
+boring:
+ printf("%s %s\n\n", BFS_COMMAND, bfs_version);
+}
+
/**
* "Parse" -version.
*/
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);
+ print_logo(parser->ctx->cout);
printf("Copyright © Tavian Barnes and the bfs contributors\n");
printf("No rights reserved (https://opensource.org/license/0BSD)\n\n");
@@ -2982,136 +3007,139 @@ typedef struct bfs_expr *parse_fn(struct bfs_parser *parser, int arg1, int arg2)
*/
struct table_entry {
char *arg;
- enum token_info info;
+ enum bfs_kind kind;
parse_fn *parse;
int arg1;
int arg2;
+ bool prefix;
+ bool needs_arg;
};
/**
* The parse table for primary expressions.
*/
static const struct table_entry parse_table[] = {
- {"--", T_FLAG},
- {"--help", T_ACTION, parse_help},
- {"--version", T_ACTION, parse_version},
- {"-Bmin", T_TEST, parse_min, BFS_STAT_BTIME},
- {"-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 | 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 | T_PREFIX, parse_optlevel},
- {"-P", T_FLAG, parse_follow, 0, false},
- {"-S", T_FLAG | T_PREFIX, parse_search_strategy},
- {"-X", T_FLAG, parse_xargs_safe},
- {"-a", T_OPERATOR},
- {"-acl", T_TEST, parse_acl},
- {"-amin", T_TEST, parse_min, BFS_STAT_ATIME},
- {"-and", T_OPERATOR},
- {"-anewer", T_TEST, parse_newer, BFS_STAT_ATIME},
- {"-asince", T_TEST, parse_since, BFS_STAT_ATIME},
- {"-atime", T_TEST, parse_time, BFS_STAT_ATIME},
- {"-capable", T_TEST, parse_capable},
- {"-cmin", T_TEST, parse_min, BFS_STAT_CTIME},
- {"-cnewer", T_TEST, parse_newer, BFS_STAT_CTIME},
- {"-color", T_OPTION, parse_color, true},
- {"-context", T_TEST, parse_context, true},
- {"-csince", T_TEST, parse_since, BFS_STAT_CTIME},
- {"-ctime", T_TEST, parse_time, BFS_STAT_CTIME},
- {"-d", T_FLAG, parse_depth},
- {"-daystart", T_OPTION, parse_daystart},
- {"-delete", T_ACTION, parse_delete},
- {"-depth", T_OPTION, parse_depth_n},
- {"-empty", T_TEST, parse_empty},
- {"-exclude", T_OPERATOR},
- {"-exec", T_ACTION, parse_exec, 0},
- {"-execdir", T_ACTION, parse_exec, BFS_EXEC_CHDIR},
- {"-executable", T_TEST, parse_access, X_OK},
- {"-exit", T_ACTION, parse_exit},
- {"-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},
- {"-fls", T_ACTION, parse_fls},
- {"-follow", T_OPTION, parse_follow, BFTW_FOLLOW_ALL, true},
- {"-fprint", T_ACTION, parse_fprint},
- {"-fprint0", T_ACTION, parse_fprint0},
- {"-fprintf", T_ACTION, parse_fprintf},
- {"-fstype", T_TEST, parse_fstype},
- {"-gid", T_TEST, parse_group},
- {"-group", T_TEST, parse_group},
- {"-help", T_ACTION, parse_help},
- {"-hidden", T_TEST, parse_hidden},
- {"-ignore_readdir_race", T_OPTION, parse_ignore_races, true},
- {"-ilname", T_TEST, parse_lname, true},
- {"-iname", T_TEST, parse_name, true},
- {"-inum", T_TEST, parse_inum},
- {"-ipath", T_TEST, parse_path, true},
- {"-iregex", T_TEST, parse_regex, BFS_REGEX_ICASE},
- {"-iwholename", T_TEST, parse_path, true},
- {"-j", T_FLAG | T_PREFIX, parse_jobs},
- {"-limit", T_ACTION, parse_limit},
- {"-links", T_TEST, parse_links},
- {"-lname", T_TEST, parse_lname, false},
- {"-ls", T_ACTION, parse_ls},
- {"-maxdepth", T_OPTION, parse_depth_limit, false},
- {"-mindepth", T_OPTION, parse_depth_limit, true},
- {"-mmin", T_TEST, parse_min, BFS_STAT_MTIME},
- {"-mnewer", T_TEST, parse_newer, BFS_STAT_MTIME},
- {"-mount", T_OPTION, parse_mount},
- {"-msince", T_TEST, parse_since, BFS_STAT_MTIME},
- {"-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 | T_PREFIX, parse_newerxy},
- {"-nocolor", T_OPTION, parse_color, false},
- {"-nogroup", T_TEST, parse_nogroup},
- {"-nohidden", T_TEST, parse_nohidden},
- {"-noignore_readdir_race", T_OPTION, parse_ignore_races, false},
- {"-noleaf", T_OPTION, parse_noleaf},
- {"-not", T_OPERATOR},
- {"-nouser", T_TEST, parse_nouser},
- {"-nowarn", T_OPTION, parse_warn, false},
- {"-o", T_OPERATOR},
- {"-ok", T_ACTION, parse_exec, BFS_EXEC_CONFIRM},
- {"-okdir", T_ACTION, parse_exec, BFS_EXEC_CONFIRM | BFS_EXEC_CHDIR},
- {"-or", T_OPERATOR},
- {"-path", T_TEST, parse_path, false},
- {"-perm", T_TEST, parse_perm},
- {"-print", T_ACTION, parse_print},
- {"-print0", T_ACTION, parse_print0},
- {"-printf", T_ACTION, parse_printf},
- {"-printx", T_ACTION, parse_printx},
- {"-prune", T_ACTION, parse_prune},
- {"-quit", T_ACTION, parse_quit},
- {"-readable", T_TEST, parse_access, R_OK},
- {"-regex", T_TEST, parse_regex, 0},
- {"-regextype", T_OPTION, parse_regextype},
- {"-rm", T_ACTION, parse_delete},
- {"-s", T_FLAG, parse_s},
- {"-samefile", T_TEST, parse_samefile},
- {"-since", T_TEST, parse_since, BFS_STAT_MTIME},
- {"-size", T_TEST, parse_size},
- {"-sparse", T_TEST, parse_sparse},
- {"-status", T_OPTION, parse_status},
- {"-true", T_TEST, parse_const, true},
- {"-type", T_TEST, parse_type, false},
- {"-uid", T_TEST, parse_user},
- {"-unique", T_OPTION, parse_unique},
- {"-used", T_TEST, parse_used},
- {"-user", T_TEST, parse_user},
- {"-version", T_ACTION, parse_version},
- {"-warn", T_OPTION, parse_warn, true},
- {"-wholename", T_TEST, parse_path, false},
- {"-writable", T_TEST, parse_access, W_OK},
- {"-x", T_FLAG, parse_xdev},
- {"-xattr", T_TEST, parse_xattr},
- {"-xattrname", T_TEST, parse_xattrname},
- {"-xdev", T_OPTION, parse_xdev},
- {"-xtype", T_TEST, parse_type, true},
+ {"--", BFS_FLAG},
+ {"--help", BFS_ACTION, parse_help},
+ {"--version", BFS_ACTION, parse_version},
+ {"-Bmin", BFS_TEST, parse_min, BFS_STAT_BTIME},
+ {"-Bnewer", BFS_TEST, parse_newer, BFS_STAT_BTIME},
+ {"-Bsince", BFS_TEST, parse_since, BFS_STAT_BTIME},
+ {"-Btime", BFS_TEST, parse_time, BFS_STAT_BTIME},
+ {"-D", BFS_FLAG, parse_debug, .prefix = true},
+ {"-E", BFS_FLAG, parse_regex_extended},
+ {"-H", BFS_FLAG, parse_follow, BFTW_FOLLOW_ROOTS, false},
+ {"-L", BFS_FLAG, parse_follow, BFTW_FOLLOW_ALL, false},
+ {"-O", BFS_FLAG, parse_optlevel, .prefix = true},
+ {"-P", BFS_FLAG, parse_follow, 0, false},
+ {"-S", BFS_FLAG, parse_search_strategy, .prefix = true},
+ {"-X", BFS_FLAG, parse_xargs_safe},
+ {"-a", BFS_OPERATOR},
+ {"-acl", BFS_TEST, parse_acl},
+ {"-amin", BFS_TEST, parse_min, BFS_STAT_ATIME},
+ {"-and", BFS_OPERATOR},
+ {"-anewer", BFS_TEST, parse_newer, BFS_STAT_ATIME},
+ {"-asince", BFS_TEST, parse_since, BFS_STAT_ATIME},
+ {"-atime", BFS_TEST, parse_time, BFS_STAT_ATIME},
+ {"-capable", BFS_TEST, parse_capable},
+ {"-cmin", BFS_TEST, parse_min, BFS_STAT_CTIME},
+ {"-cnewer", BFS_TEST, parse_newer, BFS_STAT_CTIME},
+ {"-color", BFS_OPTION, parse_color, true},
+ {"-context", BFS_TEST, parse_context, true},
+ {"-csince", BFS_TEST, parse_since, BFS_STAT_CTIME},
+ {"-ctime", BFS_TEST, parse_time, BFS_STAT_CTIME},
+ {"-d", BFS_FLAG, parse_depth},
+ {"-daystart", BFS_OPTION, parse_daystart},
+ {"-delete", BFS_ACTION, parse_delete},
+ {"-depth", BFS_OPTION, parse_depth_n},
+ {"-empty", BFS_TEST, parse_empty},
+ {"-exclude", BFS_OPERATOR},
+ {"-exec", BFS_ACTION, parse_exec, 0},
+ {"-execdir", BFS_ACTION, parse_exec, BFS_EXEC_CHDIR},
+ {"-executable", BFS_TEST, parse_access, X_OK},
+ {"-exit", BFS_ACTION, parse_exit},
+ {"-f", BFS_FLAG, parse_f, .needs_arg = true},
+ {"-false", BFS_TEST, parse_const, false},
+ {"-files0-from", BFS_OPTION, parse_files0_from},
+ {"-flags", BFS_TEST, parse_flags},
+ {"-fls", BFS_ACTION, parse_fls},
+ {"-follow", BFS_OPTION, parse_follow, BFTW_FOLLOW_ALL, true},
+ {"-fprint", BFS_ACTION, parse_fprint},
+ {"-fprint0", BFS_ACTION, parse_fprint0},
+ {"-fprintf", BFS_ACTION, parse_fprintf},
+ {"-fstype", BFS_TEST, parse_fstype},
+ {"-gid", BFS_TEST, parse_group},
+ {"-group", BFS_TEST, parse_group},
+ {"-help", BFS_ACTION, parse_help},
+ {"-hidden", BFS_TEST, parse_hidden},
+ {"-ignore_readdir_race", BFS_OPTION, parse_ignore_races, true},
+ {"-ilname", BFS_TEST, parse_lname, true},
+ {"-iname", BFS_TEST, parse_name, true},
+ {"-inum", BFS_TEST, parse_inum},
+ {"-ipath", BFS_TEST, parse_path, true},
+ {"-iregex", BFS_TEST, parse_regex, BFS_REGEX_ICASE},
+ {"-iwholename", BFS_TEST, parse_path, true},
+ {"-j", BFS_FLAG, parse_jobs, .prefix = true},
+ {"-limit", BFS_ACTION, parse_limit},
+ {"-links", BFS_TEST, parse_links},
+ {"-lname", BFS_TEST, parse_lname, false},
+ {"-ls", BFS_ACTION, parse_ls},
+ {"-maxdepth", BFS_OPTION, parse_depth_limit, false},
+ {"-mindepth", BFS_OPTION, parse_depth_limit, true},
+ {"-mmin", BFS_TEST, parse_min, BFS_STAT_MTIME},
+ {"-mnewer", BFS_TEST, parse_newer, BFS_STAT_MTIME},
+ {"-mount", BFS_OPTION, parse_mount},
+ {"-msince", BFS_TEST, parse_since, BFS_STAT_MTIME},
+ {"-mtime", BFS_TEST, parse_time, BFS_STAT_MTIME},
+ {"-name", BFS_TEST, parse_name, false},
+ {"-newer", BFS_TEST, parse_newer, BFS_STAT_MTIME},
+ {"-newer", BFS_TEST, parse_newerxy, .prefix = true},
+ {"-nocolor", BFS_OPTION, parse_color, false},
+ {"-noerror", BFS_OPTION, parse_noerror},
+ {"-nogroup", BFS_TEST, parse_nogroup},
+ {"-nohidden", BFS_TEST, parse_nohidden},
+ {"-noignore_readdir_race", BFS_OPTION, parse_ignore_races, false},
+ {"-noleaf", BFS_OPTION, parse_noleaf},
+ {"-not", BFS_OPERATOR},
+ {"-nouser", BFS_TEST, parse_nouser},
+ {"-nowarn", BFS_OPTION, parse_warn, false},
+ {"-o", BFS_OPERATOR},
+ {"-ok", BFS_ACTION, parse_exec, BFS_EXEC_CONFIRM},
+ {"-okdir", BFS_ACTION, parse_exec, BFS_EXEC_CONFIRM | BFS_EXEC_CHDIR},
+ {"-or", BFS_OPERATOR},
+ {"-path", BFS_TEST, parse_path, false},
+ {"-perm", BFS_TEST, parse_perm},
+ {"-print", BFS_ACTION, parse_print},
+ {"-print0", BFS_ACTION, parse_print0},
+ {"-printf", BFS_ACTION, parse_printf},
+ {"-printx", BFS_ACTION, parse_printx},
+ {"-prune", BFS_ACTION, parse_prune},
+ {"-quit", BFS_ACTION, parse_quit},
+ {"-readable", BFS_TEST, parse_access, R_OK},
+ {"-regex", BFS_TEST, parse_regex, 0},
+ {"-regextype", BFS_OPTION, parse_regextype},
+ {"-rm", BFS_ACTION, parse_delete},
+ {"-s", BFS_FLAG, parse_s},
+ {"-samefile", BFS_TEST, parse_samefile},
+ {"-since", BFS_TEST, parse_since, BFS_STAT_MTIME},
+ {"-size", BFS_TEST, parse_size},
+ {"-sparse", BFS_TEST, parse_sparse},
+ {"-status", BFS_OPTION, parse_status},
+ {"-true", BFS_TEST, parse_const, true},
+ {"-type", BFS_TEST, parse_type, false},
+ {"-uid", BFS_TEST, parse_user},
+ {"-unique", BFS_OPTION, parse_unique},
+ {"-used", BFS_TEST, parse_used},
+ {"-user", BFS_TEST, parse_user},
+ {"-version", BFS_ACTION, parse_version},
+ {"-warn", BFS_OPTION, parse_warn, true},
+ {"-wholename", BFS_TEST, parse_path, false},
+ {"-writable", BFS_TEST, parse_access, W_OK},
+ {"-x", BFS_FLAG, parse_xdev},
+ {"-xattr", BFS_TEST, parse_xattr},
+ {"-xattrname", BFS_TEST, parse_xattrname},
+ {"-xdev", BFS_OPTION, parse_xdev},
+ {"-xtype", BFS_TEST, parse_type, true},
{0},
};
@@ -3119,7 +3147,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->info & T_PREFIX) {
+ if (entry->prefix) {
match = strncmp(arg, entry->arg, strlen(entry->arg)) == 0;
} else {
match = strcmp(arg, entry->arg) == 0;
@@ -3135,8 +3163,8 @@ static const struct table_entry *table_lookup(const char *arg) {
/** 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]) {
+ enum bfs_kind kind = entry->kind;
+ if (kind == BFS_FLAG && entry->arg[1] == flag && !entry->arg[2]) {
return entry;
}
}
@@ -3168,14 +3196,12 @@ static bool is_flag_group(const char *arg) {
return false;
}
- if (entry->info & T_PREFIX) {
+ if (entry->prefix) {
// The rest is the flag's argument
break;
}
- if (entry->info & T_NEEDS_ARG) {
- needs_arg = true;
- }
+ needs_arg |= entry->needs_arg;
}
return has_upper;
@@ -3199,7 +3225,7 @@ static struct bfs_expr *parse_flag_group(struct bfs_parser *parser) {
end = parser->argv;
}
- if (!expr || entry->info & T_PREFIX) {
+ if (!expr || entry->prefix) {
break;
}
}
@@ -3233,6 +3259,8 @@ static const struct table_entry *table_lookup_fuzzy(const char *arg) {
* | ACTION
*/
static struct bfs_expr *parse_primary(struct bfs_parser *parser) {
+ struct bfs_ctx *ctx = parser->ctx;
+
// Paths are already skipped at this point
const char *arg = parser->argv[0];
@@ -3255,13 +3283,13 @@ static struct bfs_expr *parse_primary(struct bfs_parser *parser) {
match = table_lookup_fuzzy(arg);
- CFILE *cerr = parser->ctx->cerr;
+ CFILE *cerr = ctx->cerr;
parse_error(parser, "Unknown argument; did you mean ");
- switch (match->info & T_TYPE) {
- case T_FLAG:
+ switch (match->kind) {
+ case BFS_FLAG:
cfprintf(cerr, "${cyn}%s${rs}?", match->arg);
break;
- case T_OPERATOR:
+ case BFS_OPERATOR:
cfprintf(cerr, "${red}%s${rs}?", match->arg);
break;
default:
@@ -3269,7 +3297,7 @@ static struct bfs_expr *parse_primary(struct bfs_parser *parser) {
break;
}
- if (!parser->interactive || !match->parse) {
+ if (!ctx->interactive || !match->parse) {
fprintf(stderr, "\n");
goto unmatched;
}
@@ -3311,7 +3339,7 @@ static struct bfs_expr *parse_factor(struct bfs_parser *parser) {
}
if (strcmp(arg, "(") == 0) {
- parser_advance(parser, T_OPERATOR, 1);
+ parser_advance(parser, BFS_OPERATOR, 1);
struct bfs_expr *expr = parse_expr(parser);
if (!expr) {
@@ -3328,7 +3356,7 @@ static struct bfs_expr *parse_factor(struct bfs_parser *parser) {
return NULL;
}
- parser_advance(parser, T_OPERATOR, 1);
+ parser_advance(parser, BFS_OPERATOR, 1);
return expr;
} else if (strcmp(arg, "-exclude") == 0) {
if (parser->excluding) {
@@ -3336,7 +3364,7 @@ static struct bfs_expr *parse_factor(struct bfs_parser *parser) {
return NULL;
}
- char **argv = parser_advance(parser, T_OPERATOR, 1);
+ char **argv = parser_advance(parser, BFS_OPERATOR, 1);
parser->excluding = true;
struct bfs_expr *factor = parse_factor(parser);
@@ -3347,9 +3375,9 @@ static struct bfs_expr *parse_factor(struct bfs_parser *parser) {
parser->excluding = false;
bfs_expr_append(parser->ctx->exclude, factor);
- return parse_new_expr(parser, eval_true, parser->argv - argv, argv);
+ return parse_new_expr(parser, eval_true, parser->argv - argv, argv, BFS_OPERATOR);
} else if (strcmp(arg, "!") == 0 || strcmp(arg, "-not") == 0) {
- char **argv = parser_advance(parser, T_OPERATOR, 1);
+ char **argv = parser_advance(parser, BFS_OPERATOR, 1);
struct bfs_expr *factor = parse_factor(parser);
if (!factor) {
@@ -3389,7 +3417,7 @@ static struct bfs_expr *parse_term(struct bfs_parser *parser) {
char **argv = &fake_and_arg;
if (strcmp(arg, "-a") == 0 || strcmp(arg, "-and") == 0) {
- argv = parser_advance(parser, T_OPERATOR, 1);
+ argv = parser_advance(parser, BFS_OPERATOR, 1);
}
struct bfs_expr *lhs = term;
@@ -3426,7 +3454,7 @@ static struct bfs_expr *parse_clause(struct bfs_parser *parser) {
break;
}
- char **argv = parser_advance(parser, T_OPERATOR, 1);
+ char **argv = parser_advance(parser, BFS_OPERATOR, 1);
struct bfs_expr *lhs = clause;
struct bfs_expr *rhs = parse_term(parser);
@@ -3461,7 +3489,7 @@ static struct bfs_expr *parse_expr(struct bfs_parser *parser) {
break;
}
- char **argv = parser_advance(parser, T_OPERATOR, 1);
+ char **argv = parser_advance(parser, BFS_OPERATOR, 1);
struct bfs_expr *lhs = expr;
struct bfs_expr *rhs = parse_clause(parser);
@@ -3479,6 +3507,8 @@ static struct bfs_expr *parse_expr(struct bfs_parser *parser) {
* Parse the top-level expression.
*/
static struct bfs_expr *parse_whole_expr(struct bfs_parser *parser) {
+ struct bfs_ctx *ctx = parser->ctx;
+
if (skip_paths(parser) != 0) {
return NULL;
}
@@ -3487,7 +3517,7 @@ static struct bfs_expr *parse_whole_expr(struct bfs_parser *parser) {
if (parser->argv[0]) {
expr = parse_expr(parser);
} else {
- expr = parse_new_expr(parser, eval_true, 1, &fake_true_arg);
+ expr = parse_new_expr(parser, eval_true, 1, &fake_true_arg, BFS_TEST);
}
if (!expr) {
return NULL;
@@ -3507,7 +3537,7 @@ static struct bfs_expr *parse_whole_expr(struct bfs_parser *parser) {
return NULL;
}
- struct bfs_expr *print = parse_new_expr(parser, eval_fprint, 1, &fake_print_arg);
+ struct bfs_expr *print = parse_new_expr(parser, eval_fprint, 1, &fake_print_arg, BFS_ACTION);
if (!print) {
return NULL;
}
@@ -3525,14 +3555,14 @@ static struct bfs_expr *parse_whole_expr(struct bfs_parser *parser) {
parser->xdev_arg[0], parser->mount_arg[0]);
}
- if (parser->ctx->warn && parser->depth_arg && parser->prune_arg) {
+ if (ctx->warn && parser->depth_arg && parser->prune_arg) {
parse_conflict_warning(parser, parser->depth_arg, 1, parser->prune_arg, 1,
"${blu}%s${rs} does not work in the presence of ${blu}%s${rs}.\n",
parser->prune_arg[0], parser->depth_arg[0]);
- if (parser->interactive) {
- bfs_warning(parser->ctx, "Do you want to continue? ");
- if (ynprompt() == 0) {
+ if (ctx->interactive) {
+ bfs_warning(ctx, "Do you want to continue? ");
+ if (ynprompt() <= 0) {
return NULL;
}
}
@@ -3766,6 +3796,7 @@ struct bfs_ctx *bfs_parse_cmdline(int argc, char *argv[]) {
} else {
ctx->warn = stdin_tty;
}
+ ctx->interactive = stdin_tty && stderr_tty;
struct bfs_parser parser = {
.ctx = ctx,
@@ -3773,7 +3804,6 @@ struct bfs_ctx *bfs_parse_cmdline(int argc, char *argv[]) {
.command = ctx->argv[0],
.regex_type = BFS_REGEX_POSIX_BASIC,
.stdout_tty = stdout_tty,
- .interactive = stdin_tty && stderr_tty,
.use_color = use_color,
.implicit_print = true,
.implicit_root = true,
@@ -3789,7 +3819,7 @@ struct bfs_ctx *bfs_parse_cmdline(int argc, char *argv[]) {
.now = ctx->now,
};
- ctx->exclude = parse_new_expr(&parser, eval_or, 1, &fake_or_arg);
+ ctx->exclude = parse_new_expr(&parser, eval_or, 1, &fake_or_arg, BFS_OPERATOR);
if (!ctx->exclude) {
goto fail;
}
@@ -3808,7 +3838,9 @@ struct bfs_ctx *bfs_parse_cmdline(int argc, char *argv[]) {
}
if (bfs_optimize(ctx) != 0) {
- bfs_perror(ctx, "bfs_optimize()");
+ if (errno != 0) {
+ bfs_perror(ctx, "bfs_optimize()");
+ }
goto fail;
}
diff --git a/src/prelude.h b/src/prelude.h
index 34ce803..a0cc2a1 100644
--- a/src/prelude.h
+++ b/src/prelude.h
@@ -2,366 +2,124 @@
// SPDX-License-Identifier: 0BSD
/**
- * Configuration and feature/platform detection.
+ * Praeludium.
+ *
+ * This header is automatically included in every translation unit, before any
+ * other headers, so it can set feature test macros[1][2]. This sets up our own
+ * mini-dialect of C, which includes
+ *
+ * - Standard C17 and POSIX.1 2024 features
+ * - Portable and platform-specific extensions
+ * - Convenience macros like `bool`, `alignof`, etc.
+ * - Common compiler extensions like __has_include()
+ *
+ * Further bfs-specific utilities are defined in "bfs.h".
+ *
+ * [1]: https://www.gnu.org/software/libc/manual/html_node/Feature-Test-Macros.html
+ * [2]: https://pubs.opengroup.org/onlinepubs/9799919799/functions/V2_chap02.html
*/
#ifndef BFS_PRELUDE_H
#define BFS_PRELUDE_H
-// Possible __STDC_VERSION__ values
-
-#define C95 199409L
-#define C99 199901L
-#define C11 201112L
-#define C17 201710L
-#define C23 202311L
-
-// Get the static_assert() definition as well as __GLIBC__
-#include <assert.h>
-
-#if __STDC_VERSION__ < C23
-# include <stdalign.h>
-# include <stdbool.h>
-# include <stdnoreturn.h>
-#endif
-
-// bfs packaging configuration
-
-#include "config.h"
-
-#ifndef BFS_COMMAND
-# define BFS_COMMAND "bfs"
-#endif
-#ifndef BFS_HOMEPAGE
-# define BFS_HOMEPAGE "https://tavianator.com/projects/bfs.html"
-#endif
-
-// This is a symbol instead of a literal so we don't have to rebuild everything
-// when the version number changes
-extern const char bfs_version[];
+// Feature test macros
-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
-
-#if __has_include(<mntent.h>)
-# define BFS_HAS_MNTENT_H true
-#endif
-#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
-#if __has_include(<sys/mkdev.h>)
-# define BFS_HAS_SYS_MKDEV_H true
-#endif
-#if __has_include(<sys/param.h>)
-# define BFS_HAS_SYS_PARAM_H true
-#endif
-#if __has_include(<sys/sysmacros.h>)
-# define BFS_HAS_SYS_SYSMACROS_H true
-#endif
-#if __has_include(<sys/xattr.h>)
-# define BFS_HAS_SYS_XATTR_H true
-#endif
-#if __has_include(<threads.h>)
-# define BFS_HAS_THREADS_H true
-#endif
-#if __has_include(<util.h>)
-# define BFS_HAS_UTIL_H true
-#endif
+/**
+ * Linux and BSD handle _POSIX_C_SOURCE differently: on Linux, it enables POSIX
+ * interfaces that are not visible by default. On BSD, it also *disables* most
+ * extensions, giving a strict POSIX environment. Since we want the extensions,
+ * we don't set _POSIX_C_SOURCE.
+ */
+// #define _POSIX_C_SOURCE 202405L
-#else // !__has_include
+/** openat() etc. */
+#define _ATFILE_SOURCE 1
-#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
-#define BFS_HAS_SYS_SYSMACROS_H __GLIBC__
-#define BFS_HAS_SYS_XATTR_H __linux__
-#define BFS_HAS_THREADS_H (!__STDC_NO_THREADS__)
-#define BFS_HAS_UTIL_H __NetBSD__
+/** BSD-derived extensions. */
+#define _BSD_SOURCE 1
-#endif // !__has_include
+/** glibc successor to _BSD_SOURCE. */
+#define _DEFAULT_SOURCE 1
-#ifndef BFS_USE_MNTENT_H
-# define BFS_USE_MNTENT_H BFS_HAS_MNTENT_H
-#endif
-#ifndef BFS_USE_PATHS_H
-# define BFS_USE_PATHS_H BFS_HAS_PATHS_H
-#endif
-#ifndef BFS_USE_SYS_EXTATTR_H
-# define BFS_USE_SYS_EXTATTR_H BFS_HAS_SYS_EXTATTR_H
-#endif
-#ifndef BFS_USE_SYS_MKDEV_H
-# define BFS_USE_SYS_MKDEV_H BFS_HAS_SYS_MKDEV_H
-#endif
-#ifndef BFS_USE_SYS_PARAM_H
-# define BFS_USE_SYS_PARAM_H BFS_HAS_SYS_PARAM_H
-#endif
-#ifndef BFS_USE_SYS_SYSMACROS_H
-# define BFS_USE_SYS_SYSMACROS_H BFS_HAS_SYS_SYSMACROS_H
-#endif
-#ifndef BFS_USE_SYS_XATTR_H
-# define BFS_USE_SYS_XATTR_H BFS_HAS_SYS_XATTR_H
-#endif
-#ifndef BFS_USE_THREADS_H
-# define BFS_USE_THREADS_H BFS_HAS_THREADS_H
-#endif
-#ifndef BFS_USE_UTIL_H
-# define BFS_USE_UTIL_H BFS_HAS_UTIL_H
-#endif
+/** GNU extensions. */
+#define _GNU_SOURCE 1
-// Stub out feature detection on old/incompatible compilers
+/** Use 64-bit off_t. */
+#define _FILE_OFFSET_BITS 64
-#ifndef __has_feature
-# define __has_feature(feat) false
-#endif
+/** Use 64-bit time_t. */
+#define _TIME_BITS 64
-#ifndef __has_c_attribute
-# define __has_c_attribute(attr) false
+/** macOS extensions. */
+#if __APPLE__
+# define _DARWIN_C_SOURCE 1
#endif
-#ifndef __has_attribute
-# define __has_attribute(attr) false
+/** Solaris extensions. */
+#if __sun
+# define __EXTENSIONS__ 1
+// https://illumos.org/man/3C/getpwnam#standard-conforming
+# define _POSIX_PTHREAD_SEMANTICS 1
#endif
-// Fundamental utilities
+// Get the convenience macros that became standard spellings in C23
+#if __STDC_VERSION__ < 202311L
-/**
- * Get the length of an array.
- */
-#define countof(...) (sizeof(__VA_ARGS__) / sizeof(0[__VA_ARGS__]))
+/** _Static_assert() => static_assert() */
+#include <assert.h>
+/** _Alignas(), _Alignof() => alignas(), alignof() */
+#include <stdalign.h>
+/** _Bool => bool, true, false */
+#include <stdbool.h>
/**
- * False sharing/destructive interference/largest cache line size.
+ * C23 deprecates `noreturn void` in favour of `[[noreturn]] void`, so we expose
+ * _noreturn instead with the other attributes in "bfs.h".
*/
-#ifdef __GCC_DESTRUCTIVE_SIZE
-# define FALSE_SHARING_SIZE __GCC_DESTRUCTIVE_SIZE
-#else
-# define FALSE_SHARING_SIZE 64
-#endif
+// #include <stdnoreturn.h>
-/**
- * True sharing/constructive interference/smallest cache line size.
- */
-#ifdef __GCC_CONSTRUCTIVE_SIZE
-# define TRUE_SHARING_SIZE __GCC_CONSTRUCTIVE_SIZE
-#else
-# define TRUE_SHARING_SIZE 64
-#endif
+/** Part of <threads.h>, but we don't use anything else from it. */
+#define thread_local _Thread_local
-/**
- * Alignment specifier that avoids false sharing.
- */
-#define cache_align alignas(FALSE_SHARING_SIZE)
+#endif // !C23
-// Wrappers for attributes
+// Feature detection
-/**
- * Silence warnings about switch/case fall-throughs.
- */
-#if __has_attribute(fallthrough)
-# define fallthru __attribute__((fallthrough))
-#else
-# define fallthru ((void)0)
-#endif
-
-/**
- * Silence warnings about unused declarations.
- */
-#if __has_attribute(unused)
-# define attr_maybe_unused __attribute__((unused))
-#else
-# define attr_maybe_unused
+// https://clang.llvm.org/docs/LanguageExtensions.html#has-attribute
+#ifndef __has_attribute
+# define __has_attribute(attr) false
#endif
-/**
- * Warn if a value is unused.
- */
-#if __has_attribute(warn_unused_result)
-# define attr_nodiscard __attribute__((warn_unused_result))
-#else
-# define attr_nodiscard
+// https://clang.llvm.org/docs/LanguageExtensions.html#has-builtin
+#ifndef __has_builtin
+# define __has_builtin(builtin) false
#endif
-/**
- * Hint to avoid inlining a function.
- */
-#if __has_attribute(noinline)
-# define attr_noinline __attribute__((noinline))
-#else
-# define attr_noinline
+// https://en.cppreference.com/w/c/language/attributes#Attribute_testing
+#ifndef __has_c_attribute
+# define __has_c_attribute(attr) false
#endif
-/**
- * Hint that a function is unlikely to be called.
- */
-#if __has_attribute(cold)
-# define attr_cold attr_noinline __attribute__((cold))
-#else
-# define attr_cold attr_noinline
+// https://clang.llvm.org/docs/LanguageExtensions.html#has-feature-and-has-extension
+#ifndef __has_feature
+# define __has_feature(feat) false
#endif
-/**
- * Adds compiler warnings for bad printf()-style function calls, if supported.
- */
-#if __has_attribute(format)
-# define attr_printf(fmt, args) __attribute__((format(printf, fmt, args)))
-#else
-# define attr_printf(fmt, args)
+// https://en.cppreference.com/w/c/preprocessor/include
+#ifndef __has_include
+# define __has_include(header) false
#endif
-/**
- * Annotates functions that potentially modify and return format strings.
- */
-#if __has_attribute(format_arg)
-# define attr_format_arg(arg) __attribute__((format_arg(arg)))
-#else
-# define attr_format_arg(args)
-#endif
+// Sanitizer macros (GCC defines these but Clang does not)
-/**
- * Annotates allocator-like functions.
- */
-#if __has_attribute(malloc)
-# if __GNUC__ >= 11 && !__OPTIMIZE__ // malloc(deallocator) disables inlining on GCC
-# define attr_malloc(...) attr_nodiscard __attribute__((malloc(__VA_ARGS__)))
-# else
-# define attr_malloc(...) attr_nodiscard __attribute__((malloc))
-# endif
-#else
-# define attr_malloc(...) attr_nodiscard
+#if __has_feature(address_sanitizer) && !defined(__SANITIZE_ADDRESS__)
+# define __SANITIZE_ADDRESS__ true
#endif
-
-/**
- * Specifies that a function returns allocations with a given alignment.
- */
-#if __has_attribute(alloc_align)
-# define attr_alloc_align(param) __attribute__((alloc_align(param)))
-#else
-# define attr_alloc_align(param)
+#if __has_feature(memory_sanitizer) && !defined(__SANITIZE_MEMORY__)
+# define __SANITIZE_MEMORY__ true
#endif
-
-/**
- * Specifies that a function returns allocations with a given size.
- */
-#if __has_attribute(alloc_size)
-# define attr_alloc_size(...) __attribute__((alloc_size(__VA_ARGS__)))
-#else
-# define attr_alloc_size(...)
+#if __has_feature(thread_sanitizer) && !defined(__SANITIZE_THREAD__)
+# define __SANITIZE_THREAD__ true
#endif
-/**
- * Shorthand for attr_alloc_align() and attr_alloc_size().
- */
-#define attr_aligned_alloc(align, ...) \
- attr_alloc_align(align) \
- attr_alloc_size(__VA_ARGS__)
-
-/**
- * Check if function multiversioning via GNU indirect functions (ifunc) is supported.
- */
-#ifndef BFS_USE_TARGET_CLONES
-# if __has_attribute(target_clones) && (__GLIBC__ || __FreeBSD__)
-# define BFS_USE_TARGET_CLONES true
-# endif
-#endif
-
-/**
- * Apply the target_clones attribute, if available.
- */
-#if BFS_USE_TARGET_CLONES
-# define attr_target_clones(...) __attribute__((target_clones(__VA_ARGS__)))
-#else
-# define attr_target_clones(...)
-#endif
-
-/**
- * Shorthand for multiple attributes at once. attr(a, b(c), d) is equivalent to
- *
- * attr_a
- * attr_b(c)
- * attr_d
- */
-#define attr(...) \
- attr__(attr_##__VA_ARGS__, none, none, none, none, none, none, none, none, none, )
-
-/**
- * attr() helper. For exposition, pretend we support only 2 args, instead of 9.
- * There are a few cases:
- *
- * attr()
- * => attr__(attr_, none, none)
- * => attr_ =>
- * attr_none =>
- * attr_too_many_none() =>
- *
- * attr(a)
- * => attr__(attr_a, none, none)
- * => attr_a => __attribute__((a))
- * attr_none =>
- * attr_too_many_none() =>
- *
- * attr(a, b(c))
- * => attr__(attr_a, b(c), none, none)
- * => attr_a => __attribute__((a))
- * attr_b(c) => __attribute__((b(c)))
- * attr_too_many_none(none) =>
- *
- * attr(a, b(c), d)
- * => attr__(attr_a, b(c), d, none, none)
- * => attr_a => __attribute__((a))
- * attr_b(c) => __attribute__((b(c)))
- * attr_too_many_d(none, none) => error
- *
- * Some attribute names are the same as standard library functions, e.g. printf.
- * Standard libraries are permitted to define these functions as macros, like
- *
- * #define printf(...) __builtin_printf(__VA_ARGS__)
- *
- * The token paste in
- *
- * #define attr(...) attr__(attr_##__VA_ARGS__, none, none)
- *
- * is necessary to prevent macro expansion before evaluating attr__().
- * Otherwise, we could get
- *
- * attr(printf(1, 2))
- * => attr__(__builtin_printf(1, 2), none, none)
- * => attr____builtin_printf(1, 2)
- * => error
- */
-#define attr__(a1, a2, a3, a4, a5, a6, a7, a8, a9, none, ...) \
- a1 \
- attr_##a2 \
- attr_##a3 \
- attr_##a4 \
- attr_##a5 \
- attr_##a6 \
- attr_##a7 \
- attr_##a8 \
- attr_##a9 \
- attr_too_many_##none(__VA_ARGS__)
-
-// Ignore `attr_none` from expanding 1-9 argument attr(a1, a2, ...)
-#define attr_none
-// Ignore `attr_` from expanding 0-argument attr()
-#define attr_
-// Only trigger an error on more than 9 arguments
-#define attr_too_many_none(...)
-
#endif // BFS_PRELUDE_H
diff --git a/src/printf.c b/src/printf.c
index 6b07c54..30ec201 100644
--- a/src/printf.c
+++ b/src/printf.c
@@ -1,9 +1,10 @@
// Copyright © Tavian Barnes <tavianator@tavianator.com>
// SPDX-License-Identifier: 0BSD
-#include "prelude.h"
#include "printf.h"
+
#include "alloc.h"
+#include "bfs.h"
#include "bfstd.h"
#include "bftw.h"
#include "color.h"
@@ -16,6 +17,7 @@
#include "mtab.h"
#include "pwcache.h"
#include "stat.h"
+
#include <errno.h>
#include <grp.h>
#include <pwd.h>
@@ -89,7 +91,7 @@ static bool should_color(CFILE *cfile, const struct bfs_fmt *fmt) {
(void)ret
/** Return a dynamic format string. */
-attr(format_arg(2))
+_format_arg(2)
static const char *dyn_fmt(const char *str, const char *fake) {
bfs_assert(strcmp(str + strlen(str) - strlen(fake) + 1, fake + 1) == 0,
"Mismatched format specifiers: '%s' vs. '%s'", str, fake);
@@ -97,7 +99,7 @@ static const char *dyn_fmt(const char *str, const char *fake) {
}
/** Wrapper for fprintf(). */
-attr(printf(3, 4))
+_printf(3, 4)
static int bfs_fprintf(CFILE *cfile, const struct bfs_fmt *fmt, const char *fake, ...) {
va_list args;
va_start(args, fake);
@@ -560,7 +562,7 @@ static int bfs_printf_Y(CFILE *cfile, const struct bfs_fmt *fmt, const struct BF
}
/** %Z: SELinux context */
-attr(maybe_unused)
+_maybe_unused
static int bfs_printf_Z(CFILE *cfile, const struct bfs_fmt *fmt, const struct BFTW *ftwbuf) {
char *con = bfs_getfilecon(ftwbuf);
if (!con) {
@@ -706,7 +708,7 @@ int bfs_printf_parse(const struct bfs_ctx *ctx, struct bfs_expr *expr, const cha
case '+':
case ' ':
must_be_numeric = true;
- fallthru;
+ _fallthrough;
case '-':
if (strchr(fmt.str, c)) {
bfs_expr_error(ctx, expr);
diff --git a/src/pwcache.c b/src/pwcache.c
index af8c237..fa19dad 100644
--- a/src/pwcache.c
+++ b/src/pwcache.c
@@ -1,16 +1,15 @@
// Copyright © Tavian Barnes <tavianator@tavianator.com>
// SPDX-License-Identifier: 0BSD
-#include "prelude.h"
#include "pwcache.h"
+
#include "alloc.h"
#include "trie.h"
+
#include <errno.h>
#include <grp.h>
#include <pwd.h>
#include <stdlib.h>
-#include <string.h>
-#include <unistd.h>
/** Represents cache hits for negative results. */
static void *MISSING = &MISSING;
diff --git a/src/sanity.h b/src/sanity.h
index e168b8f..0b770cf 100644
--- a/src/sanity.h
+++ b/src/sanity.h
@@ -8,21 +8,8 @@
#ifndef BFS_SANITY_H
#define BFS_SANITY_H
-#include "prelude.h"
#include <stddef.h>
-#if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__)
-# define SANITIZE_ADDRESS true
-#endif
-
-#if __has_feature(memory_sanitizer) || defined(__SANITIZE_MEMORY__)
-# define SANITIZE_MEMORY true
-#endif
-
-#if __has_feature(thread_sanitizer) || defined(__SANITIZE_THREAD__)
-# define SANITIZE_THREAD true
-#endif
-
// Call macro(ptr, size) or macro(ptr, sizeof(*ptr))
#define SANITIZE_CALL(...) \
SANITIZE_CALL_(__VA_ARGS__, )
@@ -33,7 +20,7 @@
#define SANITIZE_CALL__(macro, ptr, size, ...) \
macro(ptr, size)
-#if SANITIZE_ADDRESS
+#if __SANITIZE_ADDRESS__
# include <sanitizer/asan_interface.h>
/**
@@ -55,7 +42,7 @@
# define sanitize_free sanitize_uninit
#endif
-#if SANITIZE_MEMORY
+#if __SANITIZE_MEMORY__
# include <sanitizer/msan_interface.h>
/**
@@ -85,7 +72,7 @@
/**
* Initialize a variable, unless sanitizers would detect uninitialized uses.
*/
-#if SANITIZE_MEMORY
+#if __SANITIZE_MEMORY__
# define uninit(value)
#else
# define uninit(value) = value
diff --git a/src/sighook.c b/src/sighook.c
index 6d6ff01..4356fdb 100644
--- a/src/sighook.c
+++ b/src/sighook.c
@@ -17,19 +17,29 @@
* drop to zero. Once it does, the old pointer can be safely freed.
*/
-#include "prelude.h"
#include "sighook.h"
+
#include "alloc.h"
#include "atomic.h"
+#include "bfs.h"
#include "bfstd.h"
#include "diag.h"
#include "thread.h"
+
#include <errno.h>
+#include <pthread.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
-#if _POSIX_SEMAPHORES > 0
+// NetBSD opens a file descriptor for each sem_init()
+#if defined(_POSIX_SEMAPHORES) && !__NetBSD__
+# define BFS_POSIX_SEMAPHORES _POSIX_SEMAPHORES
+#else
+# define BFS_POSIX_SEMAPHORES (-1)
+#endif
+
+#if BFS_POSIX_SEMAPHORES >= 0
# include <semaphore.h>
#endif
@@ -42,8 +52,8 @@ struct arc {
/** The reference itself. */
void *ptr;
-#if _POSIX_SEMAPHORES > 0
- /** A semaphore for arc_wake(). */
+#if BFS_POSIX_SEMAPHORES >= 0
+ /** A semaphore for arc_wait(). */
sem_t sem;
/** sem_init() result. */
int sem_status;
@@ -57,8 +67,12 @@ static void arc_init(struct arc *arc) {
atomic_init(&arc->refs, 0);
arc->ptr = NULL;
-#if _POSIX_SEMAPHORES > 0
- arc->sem_status = sem_init(&arc->sem, false, 0);
+#if BFS_POSIX_SEMAPHORES >= 0
+ if (sysoption(SEMAPHORES) > 0) {
+ arc->sem_status = sem_init(&arc->sem, false, 0);
+ } else {
+ arc->sem_status = -1;
+ }
#endif
}
@@ -93,7 +107,7 @@ static void arc_put(struct arc *arc) {
size_t refs = fetch_sub(&arc->refs, 1, release);
if (refs == 1) {
-#if _POSIX_SEMAPHORES > 0
+#if BFS_POSIX_SEMAPHORES >= 0
if (arc->sem_status == 0 && sem_post(&arc->sem) != 0) {
abort();
}
@@ -103,7 +117,7 @@ static void arc_put(struct arc *arc) {
/** Wait on the semaphore. */
static int arc_sem_wait(struct arc *arc) {
-#if _POSIX_SEMAPHORES > 0
+#if BFS_POSIX_SEMAPHORES >= 0
if (arc->sem_status == 0) {
while (sem_wait(&arc->sem) != 0) {
bfs_everify(errno == EINTR, "sem_wait()");
@@ -144,9 +158,9 @@ done:;
/** Destroy an arc. */
static void arc_destroy(struct arc *arc) {
- bfs_assert(arc_refs(arc) <= 1);
+ bfs_assert(arc_refs(arc) == 0);
-#if _POSIX_SEMAPHORES > 0
+#if BFS_POSIX_SEMAPHORES >= 0
if (arc->sem_status == 0) {
bfs_everify(sem_destroy(&arc->sem) == 0, "sem_destroy()");
}
@@ -166,14 +180,25 @@ struct rcu {
/** Sentinel value for RCU, since arc uses NULL already. */
static void *RCU_NULL = &RCU_NULL;
+/** Map NULL -> RCU_NULL. */
+static void *rcu_encode(void *ptr) {
+ return ptr ? ptr : RCU_NULL;
+}
+
+/** Map RCU_NULL -> NULL. */
+static void *rcu_decode(void *ptr) {
+ bfs_assert(ptr != NULL);
+ return ptr == RCU_NULL ? NULL : ptr;
+}
+
/** Initialize an RCU block. */
-static void rcu_init(struct rcu *rcu) {
+static void rcu_init(struct rcu *rcu, void *ptr) {
bfs_verify(atomic_is_lock_free(&rcu->active));
atomic_init(&rcu->active, 0);
arc_init(&rcu->slots[0]);
arc_init(&rcu->slots[1]);
- arc_set(&rcu->slots[0], RCU_NULL);
+ arc_set(&rcu->slots[0], rcu_encode(ptr));
}
/** Get the active slot. */
@@ -182,15 +207,20 @@ static struct arc *rcu_active(struct rcu *rcu) {
return &rcu->slots[i];
}
+/** Destroy an RCU block. */
+static void rcu_destroy(struct rcu *rcu) {
+ arc_wait(rcu_active(rcu));
+ arc_destroy(&rcu->slots[1]);
+ arc_destroy(&rcu->slots[0]);
+}
+
/** Read an RCU-protected pointer. */
static void *rcu_read(struct rcu *rcu, struct arc **slot) {
while (true) {
*slot = rcu_active(rcu);
void *ptr = arc_get(*slot);
- if (ptr == RCU_NULL) {
- return NULL;
- } else if (ptr) {
- return ptr;
+ if (ptr) {
+ return rcu_decode(ptr);
}
// Otherwise, the other slot became active; retry
}
@@ -199,12 +229,7 @@ static void *rcu_read(struct rcu *rcu, struct arc **slot) {
/** Get the RCU-protected pointer without acquiring a reference. */
static void *rcu_peek(struct rcu *rcu) {
struct arc *arc = rcu_active(rcu);
- void *ptr = arc->ptr;
- if (ptr == RCU_NULL) {
- return NULL;
- } else {
- return ptr;
- }
+ return rcu_decode(arc->ptr);
}
/** Update an RCU-protected pointer, and return the old one. */
@@ -215,126 +240,66 @@ static void *rcu_update(struct rcu *rcu, void *ptr) {
size_t j = i ^ 1;
struct arc *next = &rcu->slots[j];
- arc_set(next, ptr ? ptr : RCU_NULL);
+ arc_set(next, rcu_encode(ptr));
store(&rcu->active, j, relaxed);
- return arc_wait(prev);
+ return rcu_decode(arc_wait(prev));
}
struct sighook {
+ /** The signal being hooked, or 0 for atsigexit(). */
int sig;
+ /** Signal hook flags. */
+ enum sigflags flags;
+ /** The function to call. */
sighook_fn *fn;
+ /** An argument to pass to the function. */
void *arg;
- enum sigflags flags;
+
+ /** The RCU pointer to this hook. */
+ struct rcu *self;
+ /** The next hook in the list. */
+ struct rcu next;
};
/**
- * A table of signal hooks.
+ * An RCU-protected linked list of signal hooks.
*/
-struct sigtable {
- /** The number of filled slots. */
- size_t filled;
- /** The length of the array. */
- size_t size;
- /** An array of signal hooks. */
- struct arc hooks[];
+struct siglist {
+ /** The first hook in the list. */
+ struct rcu head;
+ /** &last->next */
+ struct rcu *tail;
};
-/** Add a hook to a table. */
-static int sigtable_add(struct sigtable *table, struct sighook *hook) {
- if (!table || table->filled == table->size) {
- return -1;
- }
-
- for (size_t i = 0; i < table->size; ++i) {
- struct arc *arc = &table->hooks[i];
- if (arc_refs(arc) == 0) {
- arc_set(arc, hook);
- ++table->filled;
- return 0;
- }
- }
-
- return -1;
-}
-
-/** Delete a hook from a table. */
-static int sigtable_del(struct sigtable *table, struct sighook *hook) {
- for (size_t i = 0; i < table->size; ++i) {
- struct arc *arc = &table->hooks[i];
- if (arc->ptr == hook) {
- arc_wait(arc);
- --table->filled;
- return 0;
- }
- }
-
- return -1;
-}
-
-/** Create a bigger copy of a signal table. */
-static struct sigtable *sigtable_grow(struct sigtable *prev) {
- size_t old_size = prev ? prev->size : 0;
- size_t new_size = old_size ? 2 * old_size : 1;
- struct sigtable *table = ALLOC_FLEX(struct sigtable, hooks, new_size);
- if (!table) {
- return NULL;
- }
-
- table->filled = 0;
- table->size = new_size;
- for (size_t i = 0; i < new_size; ++i) {
- arc_init(&table->hooks[i]);
- }
-
- for (size_t i = 0; i < old_size; ++i) {
- struct sighook *hook = prev->hooks[i].ptr;
- if (hook) {
- bfs_verify(sigtable_add(table, hook) == 0);
- }
- }
-
- return table;
+/** Initialize a siglist. */
+static void siglist_init(struct siglist *list) {
+ rcu_init(&list->head, NULL);
+ list->tail = &list->head;
}
-/** Free a signal table. */
-static void sigtable_free(struct sigtable *table) {
- if (!table) {
- return;
- }
-
- for (size_t i = 0; i < table->size; ++i) {
- struct arc *arc = &table->hooks[i];
- arc_destroy(arc);
- }
- free(table);
+/** Append a hook to a linked list. */
+static void sigpush(struct siglist *list, struct sighook *hook) {
+ hook->self = list->tail;
+ list->tail = &hook->next;
+ rcu_init(&hook->next, NULL);
+ rcu_update(hook->self, hook);
}
-/** Add a hook to a signal table, growing it if necessary. */
-static int rcu_sigtable_add(struct rcu *rcu, struct sighook *hook) {
- struct sigtable *prev = rcu_peek(rcu);
- if (sigtable_add(prev, hook) == 0) {
- return 0;
+/** Remove a hook from the linked list. */
+static void sigpop(struct siglist *list, struct sighook *hook) {
+ struct sighook *next = rcu_peek(&hook->next);
+ rcu_update(hook->self, next);
+ if (next) {
+ next->self = hook->self;
}
-
- struct sigtable *next = sigtable_grow(prev);
- if (!next) {
- return -1;
- }
-
- bfs_verify(sigtable_add(next, hook) == 0);
- rcu_update(rcu, next);
- sigtable_free(prev);
- return 0;
}
-/** The sharded table of signal hooks. */
-static struct rcu rcu_sighooks[64];
-/** The table of atsigexit() hooks. */
-static struct rcu rcu_exithooks;
+/** The lists of signal hooks. */
+static struct siglist sighooks[64];
-/** Get the table for a particular signal. */
-static struct rcu *sigshard(int sig) {
- return &rcu_sighooks[sig % countof(rcu_sighooks)];
+/** Get the hook list for a particular signal. */
+static struct siglist *siglist(int sig) {
+ return &sighooks[sig % countof(sighooks)];
}
/** Mutex for initialization and RCU writer exclusion. */
@@ -342,7 +307,7 @@ static pthread_mutex_t sigmutex = PTHREAD_MUTEX_INITIALIZER;
/** Check if a signal was generated by userspace. */
static bool is_user_generated(const siginfo_t *info) {
- // https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_04_03
+ // https://pubs.opengroup.org/onlinepubs/9799919799/functions/V2_chap02.html#tag_16_04_03_03
//
// If si_code is SI_USER or SI_QUEUE, or any value less than or
// equal to 0, then the signal was generated by a process ...
@@ -360,7 +325,7 @@ static bool is_fault(const siginfo_t *info) {
}
}
-// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/signal.h.html
+// https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/signal.h.html
static const int FATAL_SIGNALS[] = {
SIGABRT,
SIGALRM,
@@ -401,7 +366,7 @@ static bool is_fatal(int sig) {
}
#ifdef SIGRTMIN
- // https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_04_03
+ // https://pubs.opengroup.org/onlinepubs/9799919799/functions/V2_chap02.html#tag_16_04_03_01
//
// The default actions for the realtime signals in the range
// SIGRTMIN to SIGRTMAX shall be to terminate the process
@@ -415,7 +380,8 @@ static bool is_fatal(int sig) {
}
/** Reraise a fatal signal. */
-static noreturn void reraise(int sig) {
+_noreturn
+static void reraise(int sig) {
// Restore the default signal action
if (signal(sig, SIG_DFL) == SIG_ERR) {
goto fail;
@@ -435,36 +401,29 @@ fail:
}
/** Find any matching hooks and run them. */
-static enum sigflags run_hooks(struct rcu *rcu, int sig, siginfo_t *info) {
+static enum sigflags run_hooks(struct siglist *list, int sig, siginfo_t *info) {
enum sigflags ret = 0;
- struct arc *slot;
- struct sigtable *table = rcu_read(rcu, &slot);
- if (!table) {
- goto done;
- }
-
- for (size_t i = 0; i < table->size; ++i) {
- struct arc *arc = &table->hooks[i];
- struct sighook *hook = arc_get(arc);
- if (!hook) {
- continue;
- }
+ struct arc *slot = NULL;
+ struct sighook *hook = rcu_read(&list->head, &slot);
+ while (hook) {
if (hook->sig == sig || hook->sig == 0) {
hook->fn(sig, info, hook->arg);
ret |= hook->flags;
}
- arc_put(arc);
+
+ struct arc *prev = slot;
+ hook = rcu_read(&hook->next, &slot);
+ arc_put(prev);
}
-done:
arc_put(slot);
return ret;
}
/** Dispatches a signal to the registered handlers. */
static void sigdispatch(int sig, siginfo_t *info, void *context) {
- // https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_04_03
+ // https://pubs.opengroup.org/onlinepubs/9799919799/functions/V2_chap02.html#tag_16_04_03_03
//
// The behavior of a process is undefined after it returns normally
// from a signal-catching function for a SIGBUS, SIGFPE, SIGILL, or
@@ -474,7 +433,7 @@ static void sigdispatch(int sig, siginfo_t *info, void *context) {
reraise(sig);
}
- // https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_04_03
+ // https://pubs.opengroup.org/onlinepubs/9799919799/functions/V2_chap02.html#tag_16_04_04
//
// After returning from a signal-catching function, the value of
// errno is unspecified if the signal-catching function or any
@@ -484,12 +443,13 @@ static void sigdispatch(int sig, siginfo_t *info, void *context) {
int error = errno;
// Run the normal hooks
- struct rcu *shard = sigshard(sig);
- enum sigflags flags = run_hooks(shard, sig, info);
+ struct siglist *list = siglist(sig);
+ enum sigflags flags = run_hooks(list, sig, info);
// Run the atsigexit() hooks, if we're exiting
if (!(flags & SH_CONTINUE) && is_fatal(sig)) {
- run_hooks(&rcu_exithooks, sig, info);
+ list = siglist(0);
+ run_hooks(list, sig, info);
reraise(sig);
}
@@ -512,10 +472,9 @@ static int siginit(int sig) {
return -1;
}
- for (size_t i = 0; i < countof(rcu_sighooks); ++i) {
- rcu_init(&rcu_sighooks[i]);
+ for (size_t i = 0; i < countof(sighooks); ++i) {
+ siglist_init(&sighooks[i]);
}
- rcu_init(&rcu_exithooks);
initialized = true;
}
@@ -539,36 +498,32 @@ static int siginit(int sig) {
}
/** Shared sighook()/atsigexit() implementation. */
-static struct sighook *sighook_impl(struct rcu *rcu, int sig, sighook_fn *fn, void *arg, enum sigflags flags) {
+static struct sighook *sighook_impl(int sig, sighook_fn *fn, void *arg, enum sigflags flags) {
struct sighook *hook = ALLOC(struct sighook);
if (!hook) {
return NULL;
}
hook->sig = sig;
+ hook->flags = flags;
hook->fn = fn;
hook->arg = arg;
- hook->flags = flags;
-
- if (rcu_sigtable_add(rcu, hook) != 0) {
- free(hook);
- return NULL;
- }
+ struct siglist *list = siglist(sig);
+ sigpush(list, hook);
return hook;
}
struct sighook *sighook(int sig, sighook_fn *fn, void *arg, enum sigflags flags) {
+ bfs_assert(sig > 0);
+
mutex_lock(&sigmutex);
struct sighook *ret = NULL;
- if (siginit(sig) != 0) {
- goto done;
+ if (siginit(sig) == 0) {
+ ret = sighook_impl(sig, fn, arg, flags);
}
- struct rcu *shard = sigshard(sig);
- ret = sighook_impl(shard, sig, fn, arg, flags);
-done:
mutex_unlock(&sigmutex);
return ret;
}
@@ -589,7 +544,7 @@ struct sighook *atsigexit(sighook_fn *fn, void *arg) {
}
#endif
- struct sighook *ret = sighook_impl(&rcu_exithooks, 0, fn, arg, 0);
+ struct sighook *ret = sighook_impl(0, fn, arg, 0);
mutex_unlock(&sigmutex);
return ret;
}
@@ -601,21 +556,11 @@ void sigunhook(struct sighook *hook) {
mutex_lock(&sigmutex);
- 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);
-
- if (table->filled == 0) {
- rcu_update(rcu, NULL);
- sigtable_free(table);
- }
+ struct siglist *list = siglist(hook->sig);
+ sigpop(list, hook);
mutex_unlock(&sigmutex);
+
+ rcu_destroy(&hook->next);
free(hook);
}
diff --git a/src/stat.c b/src/stat.c
index 1e340d0..e99e711 100644
--- a/src/stat.c
+++ b/src/stat.c
@@ -1,12 +1,14 @@
// Copyright © Tavian Barnes <tavianator@tavianator.com>
// SPDX-License-Identifier: 0BSD
-#include "prelude.h"
#include "stat.h"
+
#include "atomic.h"
+#include "bfs.h"
#include "bfstd.h"
#include "diag.h"
#include "sanity.h"
+
#include <errno.h>
#include <fcntl.h>
#include <string.h>
diff --git a/src/stat.h b/src/stat.h
index 8d7144d..8d0bd9b 100644
--- a/src/stat.h
+++ b/src/stat.h
@@ -12,7 +12,8 @@
#ifndef BFS_STAT_H
#define BFS_STAT_H
-#include "prelude.h"
+#include "bfs.h"
+
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
@@ -25,7 +26,7 @@
# define BFS_USE_STATX (BFS_HAS_STATX || BFS_HAS_STATX_SYSCALL)
#endif
-#if BFS_USE_SYS_PARAM_H
+#if __has_include(<sys/param.h>)
# include <sys/param.h>
#endif
diff --git a/src/thread.c b/src/thread.c
index 3793896..d61ff8c 100644
--- a/src/thread.c
+++ b/src/thread.c
@@ -1,10 +1,11 @@
// Copyright © Tavian Barnes <tavianator@tavianator.com>
// SPDX-License-Identifier: 0BSD
-#include "prelude.h"
#include "thread.h"
+
#include "bfstd.h"
#include "diag.h"
+
#include <errno.h>
#include <pthread.h>
diff --git a/src/thread.h b/src/thread.h
index db11bd8..7d12468 100644
--- a/src/thread.h
+++ b/src/thread.h
@@ -8,17 +8,8 @@
#ifndef BFS_THREAD_H
#define BFS_THREAD_H
-#include "prelude.h"
#include <pthread.h>
-#if __STDC_VERSION__ < C23 && !defined(thread_local)
-# if BFS_USE_THREADS_H
-# include <threads.h>
-# else
-# define thread_local _Thread_local
-# endif
-#endif
-
/** Thread entry point type. */
typedef void *thread_fn(void *arg);
diff --git a/src/trie.c b/src/trie.c
index cc5db09..a92950b 100644
--- a/src/trie.c
+++ b/src/trie.c
@@ -81,21 +81,23 @@
* and insert intermediate singleton "jump" nodes when necessary.
*/
-#include "prelude.h"
#include "trie.h"
+
#include "alloc.h"
+#include "bfs.h"
#include "bit.h"
#include "diag.h"
#include "list.h"
+
#include <stdint.h>
#include <string.h>
-bfs_static_assert(CHAR_WIDTH == 8);
+static_assert(CHAR_WIDTH == 8, "This trie implementation assumes 8-bit bytes.");
#if __i386__ || __x86_64__
-# define trie_clones attr(target_clones("popcnt", "default"))
+# define _trie_clones _target_clones("popcnt", "default")
#else
-# define trie_clones
+# define _trie_clones
#endif
/** Number of bits for the sparse array bitmap, aka the range of a nibble. */
@@ -190,7 +192,7 @@ static unsigned char trie_key_nibble(const void *key, size_t offset) {
* that case, the first mismatch between the key and the representative will be
* the depth at which to make a new branch to insert the key.
*/
-trie_clones
+_trie_clones
static struct trie_leaf *trie_representative(const struct trie *trie, const void *key, size_t length) {
uintptr_t ptr = trie->root;
if (!ptr) {
@@ -221,7 +223,7 @@ struct trie_leaf *trie_find_str(const struct trie *trie, const char *key) {
return trie_find_mem(trie, key, strlen(key) + 1);
}
-trie_clones
+_trie_clones
static struct trie_leaf *trie_find_mem_impl(const struct trie *trie, const void *key, size_t length) {
struct trie_leaf *rep = trie_representative(trie, key, length);
if (rep && rep->length == length && memcmp(rep->key, key, length) == 0) {
@@ -235,7 +237,7 @@ struct trie_leaf *trie_find_mem(const struct trie *trie, const void *key, size_t
return trie_find_mem_impl(trie, key, length);
}
-trie_clones
+_trie_clones
static struct trie_leaf *trie_find_postfix_impl(const struct trie *trie, const char *key) {
size_t length = strlen(key);
struct trie_leaf *rep = trie_representative(trie, key, length + 1);
@@ -280,7 +282,7 @@ static bool trie_check_prefix(struct trie_leaf *leaf, size_t skip, const char *k
}
}
-trie_clones
+_trie_clones
static struct trie_leaf *trie_find_prefix_impl(const struct trie *trie, const char *key) {
uintptr_t ptr = trie->root;
if (!ptr) {
@@ -438,7 +440,7 @@ static size_t trie_mismatch(const struct trie_leaf *rep, const void *key, size_t
* | Z
* +--->...
*/
-trie_clones
+_trie_clones
static struct trie_leaf *trie_node_insert(struct trie *trie, uintptr_t *ptr, struct trie_leaf *leaf, unsigned char nibble) {
struct trie_node *node = trie_decode_node(*ptr);
unsigned int size = count_ones(node->bitmap);
@@ -561,7 +563,7 @@ struct trie_leaf *trie_insert_str(struct trie *trie, const char *key) {
return trie_insert_mem(trie, key, strlen(key) + 1);
}
-trie_clones
+_trie_clones
static struct trie_leaf *trie_insert_mem_impl(struct trie *trie, const void *key, size_t length) {
struct trie_leaf *rep = trie_representative(trie, key, length);
size_t mismatch = trie_mismatch(rep, key, length);
@@ -663,7 +665,7 @@ static int trie_collapse_node(struct trie *trie, uintptr_t *parent, struct trie_
return 0;
}
-trie_clones
+_trie_clones
static void trie_remove_impl(struct trie *trie, struct trie_leaf *leaf) {
uintptr_t *child = &trie->root;
uintptr_t *parent = NULL;
diff --git a/src/trie.h b/src/trie.h
index 4288d76..8f7f94b 100644
--- a/src/trie.h
+++ b/src/trie.h
@@ -6,6 +6,7 @@
#include "alloc.h"
#include "list.h"
+
#include <stddef.h>
#include <stdint.h>
diff --git a/src/typo.c b/src/typo.c
index cb826fc..7b359c4 100644
--- a/src/typo.c
+++ b/src/typo.c
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: 0BSD
#include "typo.h"
+
#include <limits.h>
#include <stdint.h>
#include <stdlib.h>
@@ -125,7 +126,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 = (40 + 12 + 1) / 2;
+ const int insert_cost = 12;
size_t rows = strlen(actual) + 1;
size_t cols = strlen(expected) + 1;
diff --git a/src/version.c b/src/version.c
index 8383350..7479a9f 100644
--- a/src/version.c
+++ b/src/version.c
@@ -1,7 +1,7 @@
// Copyright © Tavian Barnes <tavianator@tavianator.com>
// SPDX-License-Identifier: 0BSD
-#include "prelude.h"
+#include "bfs.h"
const char bfs_version[] = {
#include "version.i"
diff --git a/src/xregex.c b/src/xregex.c
index 2d089b2..796544e 100644
--- a/src/xregex.c
+++ b/src/xregex.c
@@ -1,13 +1,15 @@
// Copyright © Tavian Barnes <tavianator@tavianator.com>
// SPDX-License-Identifier: 0BSD
-#include "prelude.h"
#include "xregex.h"
+
#include "alloc.h"
+#include "bfs.h"
#include "bfstd.h"
#include "diag.h"
#include "sanity.h"
#include "thread.h"
+
#include <errno.h>
#include <pthread.h>
#include <stdlib.h>
diff --git a/src/xspawn.c b/src/xspawn.c
index 2c64011..b3eed79 100644
--- a/src/xspawn.c
+++ b/src/xspawn.c
@@ -1,12 +1,14 @@
// Copyright © Tavian Barnes <tavianator@tavianator.com>
// SPDX-License-Identifier: 0BSD
-#include "prelude.h"
#include "xspawn.h"
+
#include "alloc.h"
+#include "bfs.h"
#include "bfstd.h"
#include "diag.h"
#include "list.h"
+
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
@@ -16,11 +18,11 @@
#include <sys/types.h>
#include <unistd.h>
-#if BFS_USE_PATHS_H
+#if __has_include(<paths.h>)
# include <paths.h>
#endif
-#if _POSIX_SPAWN > 0
+#if BFS_POSIX_SPAWN >= 0
# include <spawn.h>
#endif
@@ -70,29 +72,42 @@ int bfs_spawn_init(struct bfs_spawn *ctx) {
ctx->flags = 0;
SLIST_INIT(ctx);
-#if _POSIX_SPAWN > 0
- ctx->flags |= BFS_SPAWN_USE_POSIX;
+#if BFS_POSIX_SPAWN >= 0
+ if (sysoption(SPAWN) > 0) {
+ ctx->flags |= BFS_SPAWN_USE_POSIX;
- errno = posix_spawn_file_actions_init(&ctx->actions);
- if (errno != 0) {
- return -1;
- }
+ errno = posix_spawn_file_actions_init(&ctx->actions);
+ if (errno != 0) {
+ return -1;
+ }
- errno = posix_spawnattr_init(&ctx->attr);
- if (errno != 0) {
- posix_spawn_file_actions_destroy(&ctx->actions);
- return -1;
+ errno = posix_spawnattr_init(&ctx->attr);
+ if (errno != 0) {
+ posix_spawn_file_actions_destroy(&ctx->actions);
+ return -1;
+ }
}
#endif
return 0;
}
-int bfs_spawn_destroy(struct bfs_spawn *ctx) {
-#if _POSIX_SPAWN > 0
- posix_spawnattr_destroy(&ctx->attr);
- posix_spawn_file_actions_destroy(&ctx->actions);
+/**
+ * Clear the BFS_SPAWN_USE_POSIX flag and free the attributes.
+ */
+static void bfs_spawn_clear_posix(struct bfs_spawn *ctx) {
+ if (ctx->flags & BFS_SPAWN_USE_POSIX) {
+ ctx->flags &= ~BFS_SPAWN_USE_POSIX;
+
+#if BFS_POSIX_SPAWN >= 0
+ posix_spawnattr_destroy(&ctx->attr);
+ posix_spawn_file_actions_destroy(&ctx->actions);
#endif
+ }
+}
+
+int bfs_spawn_destroy(struct bfs_spawn *ctx) {
+ bfs_spawn_clear_posix(ctx);
for_slist (struct bfs_spawn_action, action, ctx) {
free(action);
@@ -101,9 +116,9 @@ int bfs_spawn_destroy(struct bfs_spawn *ctx) {
return 0;
}
-#if _POSIX_SPAWN > 0
+#if BFS_POSIX_SPAWN >= 0
/** Set some posix_spawnattr flags. */
-attr(maybe_unused)
+_maybe_unused
static int bfs_spawn_addflags(struct bfs_spawn *ctx, short flags) {
short prev;
errno = posix_spawnattr_getflags(&ctx->attr, &prev);
@@ -121,7 +136,7 @@ static int bfs_spawn_addflags(struct bfs_spawn *ctx, short flags) {
return 0;
}
-#endif // _POSIX_SPAWN > 0
+#endif
/** Allocate a spawn action. */
static struct bfs_spawn_action *bfs_spawn_action(enum bfs_spawn_op op) {
@@ -143,7 +158,7 @@ int bfs_spawn_addopen(struct bfs_spawn *ctx, int fd, const char *path, int flags
return -1;
}
-#if _POSIX_SPAWN > 0
+#if BFS_POSIX_SPAWN >= 0
if (ctx->flags & BFS_SPAWN_USE_POSIX) {
errno = posix_spawn_file_actions_addopen(&ctx->actions, fd, path, flags, mode);
if (errno != 0) {
@@ -167,7 +182,7 @@ int bfs_spawn_addclose(struct bfs_spawn *ctx, int fd) {
return -1;
}
-#if _POSIX_SPAWN > 0
+#if BFS_POSIX_SPAWN >= 0
if (ctx->flags & BFS_SPAWN_USE_POSIX) {
errno = posix_spawn_file_actions_addclose(&ctx->actions, fd);
if (errno != 0) {
@@ -188,7 +203,7 @@ int bfs_spawn_adddup2(struct bfs_spawn *ctx, int oldfd, int newfd) {
return -1;
}
-#if _POSIX_SPAWN > 0
+#if BFS_POSIX_SPAWN >= 0
if (ctx->flags & BFS_SPAWN_USE_POSIX) {
errno = posix_spawn_file_actions_adddup2(&ctx->actions, oldfd, newfd);
if (errno != 0) {
@@ -228,7 +243,7 @@ int bfs_spawn_addfchdir(struct bfs_spawn *ctx, int fd) {
# define BFS_POSIX_SPAWN_ADDFCHDIR posix_spawn_file_actions_addfchdir_np
#endif
-#if _POSIX_SPAWN > 0 && defined(BFS_POSIX_SPAWN_FCHDIR)
+#if BFS_POSIX_SPAWN >= 0 && defined(BFS_POSIX_SPAWN_ADDFCHDIR)
if (ctx->flags & BFS_SPAWN_USE_POSIX) {
errno = BFS_POSIX_SPAWN_ADDFCHDIR(&ctx->actions, fd);
if (errno != 0) {
@@ -237,7 +252,7 @@ int bfs_spawn_addfchdir(struct bfs_spawn *ctx, int fd) {
}
}
#else
- ctx->flags &= ~BFS_SPAWN_USE_POSIX;
+ bfs_spawn_clear_posix(ctx);
#endif
action->in_fd = fd;
@@ -261,7 +276,7 @@ int bfs_spawn_setrlimit(struct bfs_spawn *ctx, int resource, const struct rlimit
goto fail;
}
#else
- ctx->flags &= ~BFS_SPAWN_USE_POSIX;
+ bfs_spawn_clear_posix(ctx);
#endif
action->resource = resource;
@@ -482,7 +497,7 @@ fail:
return -1;
}
-#if _POSIX_SPAWN > 0
+#if BFS_POSIX_SPAWN >= 0
/** bfs_spawn() implementation using posix_spawn(). */
static pid_t bfs_posix_spawn(struct bfs_resolver *res, const struct bfs_spawn *ctx, char **argv, char **envp) {
@@ -516,10 +531,11 @@ static bool bfs_use_posix_spawn(const struct bfs_resolver *res, const struct bfs
return true;
}
-#endif // _POSIX_SPAWN > 0
+#endif // BFS_POSIX_SPAWN >= 0
/** Actually exec() the new process. */
-static noreturn void bfs_spawn_exec(struct bfs_resolver *res, const struct bfs_spawn *ctx, char **argv, char **envp, int pipefd[2]) {
+_noreturn
+static void bfs_spawn_exec(struct bfs_resolver *res, const struct bfs_spawn *ctx, char **argv, char **envp, int pipefd[2]) {
xclose(pipefd[0]);
for_slist (const struct bfs_spawn_action, action, ctx) {
@@ -612,15 +628,19 @@ static pid_t bfs_fork_spawn(struct bfs_resolver *res, const struct bfs_spawn *ct
goto fail;
}
+#if BFS_HAS__FORK
+ pid_t pid = _Fork();
+#else
pid_t pid = fork();
+#endif
if (pid == 0) {
// Child
bfs_spawn_exec(res, ctx, argv, envp, pipefd);
}
// Restore the original signal mask
- int ret = pthread_sigmask(SIG_SETMASK, &old_mask, NULL);
- bfs_everify(ret == 0, "pthread_sigmask()");
+ errno = pthread_sigmask(SIG_SETMASK, &old_mask, NULL);
+ bfs_everify(errno == 0, "pthread_sigmask()");
if (pid < 0) {
// fork() failed
@@ -648,7 +668,7 @@ fail:
/** Call the right bfs_spawn() implementation. */
static pid_t bfs_spawn_impl(struct bfs_resolver *res, const struct bfs_spawn *ctx, char **argv, char **envp) {
-#if _POSIX_SPAWN > 0
+#if BFS_POSIX_SPAWN >= 0
if (bfs_use_posix_spawn(res, ctx)) {
return bfs_posix_spawn(res, ctx, argv, envp);
}
diff --git a/src/xspawn.h b/src/xspawn.h
index 6a8f54a..9cfbaf7 100644
--- a/src/xspawn.h
+++ b/src/xspawn.h
@@ -8,12 +8,17 @@
#ifndef BFS_XSPAWN_H
#define BFS_XSPAWN_H
-#include "prelude.h"
#include <sys/resource.h>
#include <sys/types.h>
#include <unistd.h>
-#if _POSIX_SPAWN > 0
+#ifdef _POSIX_SPAWN
+# define BFS_POSIX_SPAWN _POSIX_SPAWN
+#else
+# define BFS_POSIX_SPAWN (-1)
+#endif
+
+#if BFS_POSIX_SPAWN >= 0
# include <spawn.h>
#endif
@@ -38,7 +43,7 @@ struct bfs_spawn {
struct bfs_spawn_action *head;
struct bfs_spawn_action **tail;
-#if _POSIX_SPAWN > 0
+#if BFS_POSIX_SPAWN >= 0
/** posix_spawn() context, for when we can use it. */
posix_spawn_file_actions_t actions;
posix_spawnattr_t attr;
diff --git a/src/xtime.c b/src/xtime.c
index 186651b..dd9b60f 100644
--- a/src/xtime.c
+++ b/src/xtime.c
@@ -1,11 +1,12 @@
// Copyright © Tavian Barnes <tavianator@tavianator.com>
// SPDX-License-Identifier: 0BSD
-#include "prelude.h"
#include "xtime.h"
+
+#include "bfs.h"
#include "bfstd.h"
#include "diag.h"
-#include "sanity.h"
+
#include <errno.h>
#include <limits.h>
#include <sys/time.h>
@@ -36,7 +37,7 @@ int xmktime(struct tm *tm, time_t *timep) {
}
// FreeBSD is missing an interceptor
-#if BFS_HAS_TIMEGM && !(__FreeBSD__ && SANITIZE_MEMORY)
+#if BFS_HAS_TIMEGM && !(__FreeBSD__ && __SANITIZE_MEMORY__)
int xtimegm(struct tm *tm, time_t *timep) {
time_t time = timegm(tm);
@@ -350,17 +351,3 @@ invalid:
error:
return -1;
}
-
-int xgettime(struct timespec *result) {
-#if _POSIX_TIMERS > 0
- return clock_gettime(CLOCK_REALTIME, result);
-#else
- struct timeval tv;
- int ret = gettimeofday(&tv, NULL);
- if (ret == 0) {
- result->tv_sec = tv.tv_sec;
- result->tv_nsec = tv.tv_usec * 1000L;
- }
- return ret;
-#endif
-}
diff --git a/src/xtime.h b/src/xtime.h
index fb60ae4..a3547a7 100644
--- a/src/xtime.h
+++ b/src/xtime.h
@@ -46,14 +46,4 @@ int xtimegm(struct tm *tm, time_t *timep);
*/
int xgetdate(const char *str, struct timespec *result);
-/**
- * Get the current time.
- *
- * @param[out] result
- * A pointer to the result.
- * @return
- * 0 on success, -1 on failure.
- */
-int xgettime(struct timespec *result);
-
#endif // BFS_XTIME_H
diff --git a/tests/alloc.c b/tests/alloc.c
index 6c0defd..f91b428 100644
--- a/tests/alloc.c
+++ b/tests/alloc.c
@@ -1,41 +1,40 @@
// Copyright © Tavian Barnes <tavianator@tavianator.com>
// SPDX-License-Identifier: 0BSD
-#include "prelude.h"
#include "tests.h"
+
#include "alloc.h"
#include "diag.h"
+
#include <errno.h>
#include <stdlib.h>
#include <stdint.h>
-bool check_alloc(void) {
- bool ret = true;
-
+void check_alloc(void) {
// Check sizeof_flex()
struct flexible {
alignas(64) int foo[8];
int bar[];
};
- ret &= bfs_check(sizeof_flex(struct flexible, bar, 0) >= sizeof(struct flexible));
- ret &= bfs_check(sizeof_flex(struct flexible, bar, 16) % alignof(struct flexible) == 0);
+ bfs_check(sizeof_flex(struct flexible, bar, 0) >= sizeof(struct flexible));
+ bfs_check(sizeof_flex(struct flexible, bar, 16) % alignof(struct flexible) == 0);
size_t too_many = SIZE_MAX / sizeof(int) + 1;
- ret &= bfs_check(sizeof_flex(struct flexible, bar, too_many) == align_floor(alignof(struct flexible), SIZE_MAX));
+ bfs_check(sizeof_flex(struct flexible, bar, too_many) == align_floor(alignof(struct flexible), SIZE_MAX));
// Corner case: sizeof(type) > align_ceil(alignof(type), offsetof(type, member))
// Doesn't happen in typical ABIs
- ret &= bfs_check(flex_size(8, 16, 4, 4, 1) == 16);
+ bfs_check(flex_size(8, 16, 4, 4, 1) == 16);
// Make sure we detect allocation size overflows
#if __GNUC__ && !__clang__
# pragma GCC diagnostic ignored "-Walloc-size-larger-than="
#endif
- ret &= bfs_check(ALLOC_ARRAY(int, too_many) == NULL && errno == EOVERFLOW);
- ret &= bfs_check(ZALLOC_ARRAY(int, too_many) == NULL && errno == EOVERFLOW);
- ret &= bfs_check(ALLOC_FLEX(struct flexible, bar, too_many) == NULL && errno == EOVERFLOW);
- ret &= bfs_check(ZALLOC_FLEX(struct flexible, bar, too_many) == NULL && errno == EOVERFLOW);
+ bfs_check(ALLOC_ARRAY(int, too_many) == NULL && errno == EOVERFLOW);
+ bfs_check(ZALLOC_ARRAY(int, too_many) == NULL && errno == EOVERFLOW);
+ bfs_check(ALLOC_FLEX(struct flexible, bar, too_many) == NULL && errno == EOVERFLOW);
+ bfs_check(ZALLOC_FLEX(struct flexible, bar, too_many) == NULL && errno == EOVERFLOW);
// varena tests
struct varena varena;
@@ -44,9 +43,8 @@ bool check_alloc(void) {
for (size_t i = 0; i < 256; ++i) {
bfs_verify(varena_alloc(&varena, i));
struct arena *arena = &varena.arenas[varena.narenas - 1];
- ret &= bfs_check(arena->size >= sizeof_flex(struct flexible, bar, i));
+ bfs_check(arena->size >= sizeof_flex(struct flexible, bar, i));
}
varena_destroy(&varena);
- return ret;
}
diff --git a/tests/bfs/L_noerror.out b/tests/bfs/L_noerror.out
new file mode 100644
index 0000000..a514555
--- /dev/null
+++ b/tests/bfs/L_noerror.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/bfs/L_noerror.sh b/tests/bfs/L_noerror.sh
new file mode 100644
index 0000000..7db2a4d
--- /dev/null
+++ b/tests/bfs/L_noerror.sh
@@ -0,0 +1 @@
+bfs_diff -L loops -noerror
diff --git a/tests/bfs/color_ext_case_flipflop.out b/tests/bfs/color_ext_case_flipflop.out
new file mode 100644
index 0000000..f4cc53c
--- /dev/null
+++ b/tests/bfs/color_ext_case_flipflop.out
@@ -0,0 +1,27 @@
+$'rainbow/\e[1m'
+$'rainbow/\e[1m/'$'\e[0m'
+rainbow
+rainbow/exec.sh
+rainbow/lower.tar.gz
+rainbow/lu.tar.GZ
+rainbow/ul.TAR.gz
+rainbow/upper.TAR.GZ
+rainbow/socket
+rainbow/broken
+rainbow/chardev_link
+rainbow/link.txt
+rainbow/sticky_ow
+rainbow/sgid
+rainbow/pipe
+rainbow/ow
+rainbow/sugid
+rainbow/suid
+rainbow/sticky
+rainbow/file.dat
+rainbow/file.txt
+rainbow/lower.gz
+rainbow/lower.tar
+rainbow/mh1
+rainbow/mh2
+rainbow/upper.GZ
+rainbow/upper.TAR
diff --git a/tests/bfs/color_ext_case_flipflop.sh b/tests/bfs/color_ext_case_flipflop.sh
new file mode 100644
index 0000000..4d6f615
--- /dev/null
+++ b/tests/bfs/color_ext_case_flipflop.sh
@@ -0,0 +1 @@
+LS_COLORS="*.tar.gz=01;31:*.TAR.GZ=01;32:*.TAR.GZ=01;33:*.tar.gz=01;33:" bfs_diff rainbow -color
diff --git a/tests/bfs/color_ext_case_priority.out b/tests/bfs/color_ext_case_priority.out
new file mode 100644
index 0000000..4a6c9a0
--- /dev/null
+++ b/tests/bfs/color_ext_case_priority.out
@@ -0,0 +1,27 @@
+$'rainbow/\e[1m'
+$'rainbow/\e[1m/'$'\e[0m'
+rainbow
+rainbow/lower.tar.gz
+rainbow/exec.sh
+rainbow/upper.TAR.GZ
+rainbow/lower.gz
+rainbow/lu.tar.GZ
+rainbow/ul.TAR.gz
+rainbow/upper.GZ
+rainbow/socket
+rainbow/broken
+rainbow/chardev_link
+rainbow/link.txt
+rainbow/sticky_ow
+rainbow/sgid
+rainbow/pipe
+rainbow/ow
+rainbow/sugid
+rainbow/suid
+rainbow/sticky
+rainbow/file.dat
+rainbow/file.txt
+rainbow/lower.tar
+rainbow/mh1
+rainbow/mh2
+rainbow/upper.TAR
diff --git a/tests/bfs/color_ext_case_priority.sh b/tests/bfs/color_ext_case_priority.sh
new file mode 100644
index 0000000..f178c56
--- /dev/null
+++ b/tests/bfs/color_ext_case_priority.sh
@@ -0,0 +1 @@
+LS_COLORS="*.gz=01;33:*.tar.gz=01;31:*.TAR.GZ=01;32:" bfs_diff rainbow -color
diff --git a/tests/bfs/noerror.out b/tests/bfs/noerror.out
new file mode 100644
index 0000000..c4f8ce4
--- /dev/null
+++ b/tests/bfs/noerror.out
@@ -0,0 +1,4 @@
+inaccessible
+inaccessible/dir
+inaccessible/file
+inaccessible/link
diff --git a/tests/bfs/noerror.sh b/tests/bfs/noerror.sh
new file mode 100644
index 0000000..e334f8f
--- /dev/null
+++ b/tests/bfs/noerror.sh
@@ -0,0 +1 @@
+bfs_diff inaccessible -noerror
diff --git a/tests/bfs/noerror_nowarn.sh b/tests/bfs/noerror_nowarn.sh
new file mode 100644
index 0000000..26e7e68
--- /dev/null
+++ b/tests/bfs/noerror_nowarn.sh
@@ -0,0 +1,2 @@
+stderr=$(invoke_bfs inaccessible -noerror -nowarn 2>&1 >/dev/null)
+[ -z "$stderr" ]
diff --git a/tests/bfs/noerror_warn.sh b/tests/bfs/noerror_warn.sh
new file mode 100644
index 0000000..ec85f4c
--- /dev/null
+++ b/tests/bfs/noerror_warn.sh
@@ -0,0 +1,2 @@
+stderr=$(invoke_bfs inaccessible -noerror -warn 2>&1 >/dev/null)
+[ -n "$stderr" ]
diff --git a/tests/bfs/perm_leading_plus_symbolic.out b/tests/bfs/perm_leading_plus_symbolic.out
index e69de29..09bc88f 100644
--- a/tests/bfs/perm_leading_plus_symbolic.out
+++ b/tests/bfs/perm_leading_plus_symbolic.out
@@ -0,0 +1,3 @@
+perms
+perms/drwxr-xr-x
+perms/frwxr-xr-x
diff --git a/tests/bfs/warn_without_noerror.sh b/tests/bfs/warn_without_noerror.sh
new file mode 100644
index 0000000..5167309
--- /dev/null
+++ b/tests/bfs/warn_without_noerror.sh
@@ -0,0 +1,2 @@
+# bfs shouldn't print "warning: Suppressed errors" without -noerror
+! invoke_bfs inaccessible -warn 2>&1 >/dev/null | grep warning >&2
diff --git a/tests/bfstd.c b/tests/bfstd.c
index f0f61ce..a43783a 100644
--- a/tests/bfstd.c
+++ b/tests/bfstd.c
@@ -1,93 +1,86 @@
// Copyright © Tavian Barnes <tavianator@tavianator.com>
// SPDX-License-Identifier: 0BSD
-#include "prelude.h"
#include "tests.h"
+
#include "bfstd.h"
#include "diag.h"
-#include <errno.h>
+
#include <langinfo.h>
#include <stdlib.h>
#include <string.h>
/** Check the result of xdirname()/xbasename(). */
-static bool check_base_dir(const char *path, const char *dir, const char *base) {
- bool ret = true;
-
+static void check_base_dir(const char *path, const char *dir, const char *base) {
char *xdir = xdirname(path);
bfs_everify(xdir, "xdirname()");
- ret &= bfs_check(strcmp(xdir, dir) == 0, "xdirname('%s') == '%s' (!= '%s')", path, xdir, dir);
+ bfs_check(strcmp(xdir, dir) == 0, "xdirname('%s') == '%s' (!= '%s')", path, xdir, dir);
free(xdir);
char *xbase = xbasename(path);
bfs_everify(xbase, "xbasename()");
- ret &= bfs_check(strcmp(xbase, base) == 0, "xbasename('%s') == '%s' (!= '%s')", path, xbase, base);
+ bfs_check(strcmp(xbase, base) == 0, "xbasename('%s') == '%s' (!= '%s')", path, xbase, base);
free(xbase);
-
- return ret;
}
/** Check the result of wordesc(). */
-static bool check_wordesc(const char *str, const char *exp, enum wesc_flags flags) {
+static void check_wordesc(const char *str, const char *exp, enum wesc_flags flags) {
char buf[256];
char *end = buf + sizeof(buf);
char *esc = wordesc(buf, end, str, flags);
- return bfs_check(esc != end)
- && bfs_check(strcmp(buf, exp) == 0, "wordesc('%s') == '%s' (!= '%s')", str, buf, exp);
+ if (bfs_check(esc != end)) {
+ bfs_check(strcmp(buf, exp) == 0, "wordesc('%s') == '%s' (!= '%s')", str, buf, exp);
+ }
}
-bool check_bfstd(void) {
- bool ret = true;
-
- ret &= bfs_check(asciilen("") == 0);
- ret &= bfs_check(asciilen("@") == 1);
- ret &= bfs_check(asciilen("@@") == 2);
- ret &= bfs_check(asciilen("\xFF@") == 0);
- ret &= bfs_check(asciilen("@\xFF") == 1);
- ret &= bfs_check(asciilen("@@@@@@@@") == 8);
- ret &= bfs_check(asciilen("@@@@@@@@@@@@@@@@") == 16);
- ret &= bfs_check(asciilen("@@@@@@@@@@@@@@@@@@@@@@@@") == 24);
- ret &= bfs_check(asciilen("@@@@@@@@@@@@@@a\xFF@@@@@@@") == 15);
- ret &= bfs_check(asciilen("@@@@@@@@@@@@@@@@\xFF@@@@@@@") == 16);
- ret &= bfs_check(asciilen("@@@@@@@@@@@@@@@@a\xFF@@@@@@") == 17);
- ret &= bfs_check(asciilen("@@@@@@@\xFF@@@@@@a\xFF@@@@@@@") == 7);
- ret &= bfs_check(asciilen("@@@@@@@@\xFF@@@@@a\xFF@@@@@@@") == 8);
- ret &= bfs_check(asciilen("@@@@@@@@@\xFF@@@@a\xFF@@@@@@@") == 9);
+void check_bfstd(void) {
+ bfs_check(asciilen("") == 0);
+ bfs_check(asciilen("@") == 1);
+ bfs_check(asciilen("@@") == 2);
+ bfs_check(asciilen("\xFF@") == 0);
+ bfs_check(asciilen("@\xFF") == 1);
+ bfs_check(asciilen("@@@@@@@@") == 8);
+ bfs_check(asciilen("@@@@@@@@@@@@@@@@") == 16);
+ bfs_check(asciilen("@@@@@@@@@@@@@@@@@@@@@@@@") == 24);
+ bfs_check(asciilen("@@@@@@@@@@@@@@a\xFF@@@@@@@") == 15);
+ bfs_check(asciilen("@@@@@@@@@@@@@@@@\xFF@@@@@@@") == 16);
+ bfs_check(asciilen("@@@@@@@@@@@@@@@@a\xFF@@@@@@") == 17);
+ bfs_check(asciilen("@@@@@@@\xFF@@@@@@a\xFF@@@@@@@") == 7);
+ bfs_check(asciilen("@@@@@@@@\xFF@@@@@a\xFF@@@@@@@") == 8);
+ bfs_check(asciilen("@@@@@@@@@\xFF@@@@a\xFF@@@@@@@") == 9);
// From man 3p basename
- ret &= check_base_dir("usr", ".", "usr");
- ret &= check_base_dir("usr/", ".", "usr");
- ret &= check_base_dir("", ".", ".");
- ret &= check_base_dir("/", "/", "/");
+ check_base_dir("usr", ".", "usr");
+ check_base_dir("usr/", ".", "usr");
+ check_base_dir("", ".", ".");
+ check_base_dir("/", "/", "/");
// check_base_dir("//", "/" or "//", "/" or "//");
- ret &= check_base_dir("///", "/", "/");
- ret &= check_base_dir("/usr/", "/", "usr");
- ret &= check_base_dir("/usr/lib", "/usr", "lib");
- ret &= check_base_dir("//usr//lib//", "//usr", "lib");
- ret &= check_base_dir("/home//dwc//test", "/home//dwc", "test");
+ check_base_dir("///", "/", "/");
+ check_base_dir("/usr/", "/", "usr");
+ check_base_dir("/usr/lib", "/usr", "lib");
+ check_base_dir("//usr//lib//", "//usr", "lib");
+ check_base_dir("/home//dwc//test", "/home//dwc", "test");
- ret &= check_wordesc("", "\"\"", WESC_SHELL);
- ret &= check_wordesc("word", "word", WESC_SHELL);
- ret &= check_wordesc("two words", "\"two words\"", WESC_SHELL);
- ret &= check_wordesc("word's", "\"word's\"", WESC_SHELL);
- ret &= check_wordesc("\"word\"", "'\"word\"'", WESC_SHELL);
- ret &= check_wordesc("\"word's\"", "'\"word'\\''s\"'", WESC_SHELL);
- ret &= check_wordesc("\033[1mbold's\033[0m", "$'\\e[1mbold\\'s\\e[0m'", WESC_SHELL | WESC_TTY);
- ret &= check_wordesc("\x7F", "$'\\x7F'", WESC_SHELL | WESC_TTY);
- ret &= check_wordesc("~user", "\"~user\"", WESC_SHELL);
+ check_wordesc("", "\"\"", WESC_SHELL);
+ check_wordesc("word", "word", WESC_SHELL);
+ check_wordesc("two words", "\"two words\"", WESC_SHELL);
+ check_wordesc("word's", "\"word's\"", WESC_SHELL);
+ check_wordesc("\"word\"", "'\"word\"'", WESC_SHELL);
+ check_wordesc("\"word's\"", "'\"word'\\''s\"'", WESC_SHELL);
+ check_wordesc("\033[1mbold's\033[0m", "$'\\e[1mbold\\'s\\e[0m'", WESC_SHELL | WESC_TTY);
+ check_wordesc("\x7F", "$'\\x7F'", WESC_SHELL | WESC_TTY);
+ check_wordesc("~user", "\"~user\"", WESC_SHELL);
const char *charmap = nl_langinfo(CODESET);
if (strcmp(charmap, "UTF-8") == 0) {
- ret &= check_wordesc("\xF0", "$'\\xF0'", WESC_SHELL | WESC_TTY);
- ret &= check_wordesc("\xF0\x9F", "$'\\xF0\\x9F'", WESC_SHELL | WESC_TTY);
- ret &= check_wordesc("\xF0\x9F\x98", "$'\\xF0\\x9F\\x98'", WESC_SHELL | WESC_TTY);
- ret &= check_wordesc("\xF0\x9F\x98\x80", "\xF0\x9F\x98\x80", WESC_SHELL | WESC_TTY);
- ret &= check_wordesc("\xCB\x9Cuser", "\xCB\x9Cuser", WESC_SHELL);
+ check_wordesc("\xF0", "$'\\xF0'", WESC_SHELL | WESC_TTY);
+ check_wordesc("\xF0\x9F", "$'\\xF0\\x9F'", WESC_SHELL | WESC_TTY);
+ check_wordesc("\xF0\x9F\x98", "$'\\xF0\\x9F\\x98'", WESC_SHELL | WESC_TTY);
+ check_wordesc("\xF0\x9F\x98\x80", "\xF0\x9F\x98\x80", WESC_SHELL | WESC_TTY);
+ check_wordesc("\xCB\x9Cuser", "\xCB\x9Cuser", WESC_SHELL);
}
- ret &= bfs_check(xstrwidth("Hello world") == 11);
- ret &= bfs_check(xstrwidth("Hello\1world") == 10);
-
- return ret;
+ bfs_check(xstrwidth("Hello world") == 11);
+ bfs_check(xstrwidth("Hello\1world") == 10);
}
diff --git a/tests/bit.c b/tests/bit.c
index 49e167d..cc95785 100644
--- a/tests/bit.c
+++ b/tests/bit.c
@@ -1,141 +1,144 @@
// Copyright © Tavian Barnes <tavianator@tavianator.com>
// SPDX-License-Identifier: 0BSD
-#include "prelude.h"
#include "tests.h"
+
+#include "bfs.h"
#include "bit.h"
-#include "diag.h"
+
#include <limits.h>
#include <stdint.h>
#include <string.h>
-bfs_static_assert(UMAX_WIDTH(0x1) == 1);
-bfs_static_assert(UMAX_WIDTH(0x3) == 2);
-bfs_static_assert(UMAX_WIDTH(0x7) == 3);
-bfs_static_assert(UMAX_WIDTH(0xF) == 4);
-bfs_static_assert(UMAX_WIDTH(0xFF) == 8);
-bfs_static_assert(UMAX_WIDTH(0xFFF) == 12);
-bfs_static_assert(UMAX_WIDTH(0xFFFF) == 16);
+// Polyfill C23's one-argument static_assert()
+#if __STDC_VERSION__ < C23
+# undef static_assert
+# define static_assert(...) _Static_assert(__VA_ARGS__, #__VA_ARGS__)
+#endif
+
+static_assert(UMAX_WIDTH(0x1) == 1);
+static_assert(UMAX_WIDTH(0x3) == 2);
+static_assert(UMAX_WIDTH(0x7) == 3);
+static_assert(UMAX_WIDTH(0xF) == 4);
+static_assert(UMAX_WIDTH(0xFF) == 8);
+static_assert(UMAX_WIDTH(0xFFF) == 12);
+static_assert(UMAX_WIDTH(0xFFFF) == 16);
#define UWIDTH_MAX(n) (2 * ((UINTMAX_C(1) << ((n) - 1)) - 1) + 1)
#define IWIDTH_MAX(n) UWIDTH_MAX((n) - 1)
#define IWIDTH_MIN(n) (-(intmax_t)IWIDTH_MAX(n) - 1)
-bfs_static_assert(UCHAR_MAX == UWIDTH_MAX(UCHAR_WIDTH));
-bfs_static_assert(SCHAR_MIN == IWIDTH_MIN(SCHAR_WIDTH));
-bfs_static_assert(SCHAR_MAX == IWIDTH_MAX(SCHAR_WIDTH));
+static_assert(UCHAR_MAX == UWIDTH_MAX(UCHAR_WIDTH));
+static_assert(SCHAR_MIN == IWIDTH_MIN(SCHAR_WIDTH));
+static_assert(SCHAR_MAX == IWIDTH_MAX(SCHAR_WIDTH));
-bfs_static_assert(USHRT_MAX == UWIDTH_MAX(USHRT_WIDTH));
-bfs_static_assert(SHRT_MIN == IWIDTH_MIN(SHRT_WIDTH));
-bfs_static_assert(SHRT_MAX == IWIDTH_MAX(SHRT_WIDTH));
+static_assert(USHRT_MAX == UWIDTH_MAX(USHRT_WIDTH));
+static_assert(SHRT_MIN == IWIDTH_MIN(SHRT_WIDTH));
+static_assert(SHRT_MAX == IWIDTH_MAX(SHRT_WIDTH));
-bfs_static_assert(UINT_MAX == UWIDTH_MAX(UINT_WIDTH));
-bfs_static_assert(INT_MIN == IWIDTH_MIN(INT_WIDTH));
-bfs_static_assert(INT_MAX == IWIDTH_MAX(INT_WIDTH));
+static_assert(UINT_MAX == UWIDTH_MAX(UINT_WIDTH));
+static_assert(INT_MIN == IWIDTH_MIN(INT_WIDTH));
+static_assert(INT_MAX == IWIDTH_MAX(INT_WIDTH));
-bfs_static_assert(ULONG_MAX == UWIDTH_MAX(ULONG_WIDTH));
-bfs_static_assert(LONG_MIN == IWIDTH_MIN(LONG_WIDTH));
-bfs_static_assert(LONG_MAX == IWIDTH_MAX(LONG_WIDTH));
+static_assert(ULONG_MAX == UWIDTH_MAX(ULONG_WIDTH));
+static_assert(LONG_MIN == IWIDTH_MIN(LONG_WIDTH));
+static_assert(LONG_MAX == IWIDTH_MAX(LONG_WIDTH));
-bfs_static_assert(ULLONG_MAX == UWIDTH_MAX(ULLONG_WIDTH));
-bfs_static_assert(LLONG_MIN == IWIDTH_MIN(LLONG_WIDTH));
-bfs_static_assert(LLONG_MAX == IWIDTH_MAX(LLONG_WIDTH));
+static_assert(ULLONG_MAX == UWIDTH_MAX(ULLONG_WIDTH));
+static_assert(LLONG_MIN == IWIDTH_MIN(LLONG_WIDTH));
+static_assert(LLONG_MAX == IWIDTH_MAX(LLONG_WIDTH));
-bfs_static_assert(SIZE_MAX == UWIDTH_MAX(SIZE_WIDTH));
-bfs_static_assert(PTRDIFF_MIN == IWIDTH_MIN(PTRDIFF_WIDTH));
-bfs_static_assert(PTRDIFF_MAX == IWIDTH_MAX(PTRDIFF_WIDTH));
+static_assert(SIZE_MAX == UWIDTH_MAX(SIZE_WIDTH));
+static_assert(PTRDIFF_MIN == IWIDTH_MIN(PTRDIFF_WIDTH));
+static_assert(PTRDIFF_MAX == IWIDTH_MAX(PTRDIFF_WIDTH));
-bfs_static_assert(UINTPTR_MAX == UWIDTH_MAX(UINTPTR_WIDTH));
-bfs_static_assert(INTPTR_MIN == IWIDTH_MIN(INTPTR_WIDTH));
-bfs_static_assert(INTPTR_MAX == IWIDTH_MAX(INTPTR_WIDTH));
+static_assert(UINTPTR_MAX == UWIDTH_MAX(UINTPTR_WIDTH));
+static_assert(INTPTR_MIN == IWIDTH_MIN(INTPTR_WIDTH));
+static_assert(INTPTR_MAX == IWIDTH_MAX(INTPTR_WIDTH));
-bfs_static_assert(UINTMAX_MAX == UWIDTH_MAX(UINTMAX_WIDTH));
-bfs_static_assert(INTMAX_MIN == IWIDTH_MIN(INTMAX_WIDTH));
-bfs_static_assert(INTMAX_MAX == IWIDTH_MAX(INTMAX_WIDTH));
+static_assert(UINTMAX_MAX == UWIDTH_MAX(UINTMAX_WIDTH));
+static_assert(INTMAX_MIN == IWIDTH_MIN(INTMAX_WIDTH));
+static_assert(INTMAX_MAX == IWIDTH_MAX(INTMAX_WIDTH));
#define check_eq(a, b) \
bfs_check((a) == (b), "(0x%jX) %s != %s (0x%jX)", (uintmax_t)(a), #a, #b, (uintmax_t)(b))
-bool check_bit(void) {
- bool ret = true;
-
+void check_bit(void) {
const char *str = "\x1\x2\x3\x4";
uint32_t word;
memcpy(&word, str, sizeof(word));
#if ENDIAN_NATIVE == ENDIAN_LITTLE
- ret &= check_eq(word, 0x04030201);
+ check_eq(word, 0x04030201);
#elif ENDIAN_NATIVE == ENDIAN_BIG
- ret &= check_eq(word, 0x01020304);
+ check_eq(word, 0x01020304);
#else
# warning "Skipping byte order tests on mixed/unknown-endian machine"
#endif
- ret &= check_eq(bswap((uint8_t)0x12), 0x12);
- ret &= check_eq(bswap((uint16_t)0x1234), 0x3412);
- ret &= check_eq(bswap((uint32_t)0x12345678), 0x78563412);
- ret &= check_eq(bswap((uint64_t)0x1234567812345678), 0x7856341278563412);
-
- 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);
- ret &= check_eq(count_zeros((uint64_t)0), 64);
-
- ret &= check_eq(rotate_left((uint8_t)0xA1, 4), 0x1A);
- ret &= check_eq(rotate_left((uint16_t)0x1234, 12), 0x4123);
- ret &= check_eq(rotate_left((uint32_t)0x12345678, 20), 0x67812345);
- ret &= check_eq(rotate_left((uint32_t)0x12345678, 0), 0x12345678);
-
- ret &= check_eq(rotate_right((uint8_t)0xA1, 4), 0x1A);
- ret &= check_eq(rotate_right((uint16_t)0x1234, 12), 0x2341);
- ret &= check_eq(rotate_right((uint32_t)0x12345678, 20), 0x45678123);
- ret &= check_eq(rotate_right((uint32_t)0x12345678, 0), 0x12345678);
+ check_eq(bswap((uint8_t)0x12), 0x12);
+ check_eq(bswap((uint16_t)0x1234), 0x3412);
+ check_eq(bswap((uint32_t)0x12345678), 0x78563412);
+ check_eq(bswap((uint64_t)0x1234567812345678), 0x7856341278563412);
+
+ check_eq(count_ones(0x0U), 0);
+ check_eq(count_ones(0x1U), 1);
+ check_eq(count_ones(0x2U), 1);
+ check_eq(count_ones(0x3U), 2);
+ check_eq(count_ones(0x137FU), 10);
+
+ check_eq(count_zeros(0U), UINT_WIDTH);
+ check_eq(count_zeros(0UL), ULONG_WIDTH);
+ check_eq(count_zeros(0ULL), ULLONG_WIDTH);
+ check_eq(count_zeros((uint8_t)0), 8);
+ check_eq(count_zeros((uint16_t)0), 16);
+ check_eq(count_zeros((uint32_t)0), 32);
+ check_eq(count_zeros((uint64_t)0), 64);
+
+ check_eq(rotate_left((uint8_t)0xA1, 4), 0x1A);
+ check_eq(rotate_left((uint16_t)0x1234, 12), 0x4123);
+ check_eq(rotate_left((uint32_t)0x12345678, 20), 0x67812345);
+ check_eq(rotate_left((uint32_t)0x12345678, 0), 0x12345678);
+
+ check_eq(rotate_right((uint8_t)0xA1, 4), 0x1A);
+ check_eq(rotate_right((uint16_t)0x1234, 12), 0x2341);
+ check_eq(rotate_right((uint32_t)0x12345678, 20), 0x45678123);
+ check_eq(rotate_right((uint32_t)0x12345678, 0), 0x12345678);
for (unsigned int i = 0; i < 16; ++i) {
uint16_t n = (uint16_t)1 << i;
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), 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);
+ check_eq(count_ones(nm), 1 + (n != m));
+ check_eq(count_zeros(nm), 15 - (n != m));
+ check_eq(leading_zeros(nm), 15 - j);
+ check_eq(trailing_zeros(nm), i);
+ check_eq(first_leading_one(nm), 16 - j);
+ check_eq(first_trailing_one(nm), i + 1);
+ check_eq(bit_width(nm), j + 1);
+ check_eq(bit_floor(nm), m);
if (n == m) {
- ret &= check_eq(bit_ceil(nm), m);
- ret &= bfs_check(has_single_bit(nm));
+ check_eq(bit_ceil(nm), m);
+ bfs_check(has_single_bit(nm));
} else {
if (j < 15) {
- ret &= check_eq(bit_ceil(nm), (m << 1));
+ check_eq(bit_ceil(nm), (m << 1));
}
- ret &= bfs_check(!has_single_bit(nm));
+ bfs_check(!has_single_bit(nm));
}
}
}
- ret &= check_eq(leading_zeros((uint16_t)0), 16);
- ret &= check_eq(trailing_zeros((uint16_t)0), 16);
- 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(0U));
- ret &= bfs_check(!has_single_bit(UINT32_MAX));
- ret &= bfs_check(has_single_bit((uint32_t)1 << (UINT_WIDTH - 1)));
-
- return ret;
+ check_eq(leading_zeros((uint16_t)0), 16);
+ check_eq(trailing_zeros((uint16_t)0), 16);
+ check_eq(first_leading_one(0U), 0);
+ check_eq(first_trailing_one(0U), 0);
+ check_eq(bit_width(0U), 0);
+ check_eq(bit_floor(0U), 0);
+ check_eq(bit_ceil(0U), 1);
+
+ bfs_check(!has_single_bit(0U));
+ bfs_check(!has_single_bit(UINT32_MAX));
+ bfs_check(has_single_bit((uint32_t)1 << (UINT_WIDTH - 1)));
}
diff --git a/tests/bsd/perm_000_plus.out b/tests/bsd/perm_000_plus.out
index 42f2fed..e279684 100644
--- a/tests/bsd/perm_000_plus.out
+++ b/tests/bsd/perm_000_plus.out
@@ -1,10 +1,29 @@
perms
-perms/dr-x
-perms/drwx
-perms/f---
-perms/f-w-
-perms/f-wx
-perms/fr--
-perms/fr-x
-perms/frw-
-perms/frwx
+perms/dr-x------
+perms/dr-xr-xr-x
+perms/drwx------
+perms/drwxr-xr-x
+perms/drwxrwxr-x
+perms/drwxrwxrwx
+perms/f---------
+perms/f--x------
+perms/f--x--x--x
+perms/f-w-------
+perms/f-w--w----
+perms/f-w--w--w-
+perms/f-wx------
+perms/f-wx--x--x
+perms/f-wx-wx--x
+perms/f-wx-wx-wx
+perms/fr--------
+perms/fr--r--r--
+perms/fr-x------
+perms/fr-xr-xr-x
+perms/frw-------
+perms/frw-r--r--
+perms/frw-rw-r--
+perms/frw-rw-rw-
+perms/frwxr-----
+perms/frwxr-xr-x
+perms/frwxrwxr-x
+perms/frwxrwxrwx
diff --git a/tests/bsd/perm_222_plus.out b/tests/bsd/perm_222_plus.out
index 5c78ecc..1b6d885 100644
--- a/tests/bsd/perm_222_plus.out
+++ b/tests/bsd/perm_222_plus.out
@@ -1,6 +1,20 @@
perms
-perms/drwx
-perms/f-w-
-perms/f-wx
-perms/frw-
-perms/frwx
+perms/drwx------
+perms/drwxr-xr-x
+perms/drwxrwxr-x
+perms/drwxrwxrwx
+perms/f-w-------
+perms/f-w--w----
+perms/f-w--w--w-
+perms/f-wx------
+perms/f-wx--x--x
+perms/f-wx-wx--x
+perms/f-wx-wx-wx
+perms/frw-------
+perms/frw-r--r--
+perms/frw-rw-r--
+perms/frw-rw-rw-
+perms/frwxr-----
+perms/frwxr-xr-x
+perms/frwxrwxr-x
+perms/frwxrwxrwx
diff --git a/tests/bsd/perm_644_plus.out b/tests/bsd/perm_644_plus.out
index 774c0ea..eef88ca 100644
--- a/tests/bsd/perm_644_plus.out
+++ b/tests/bsd/perm_644_plus.out
@@ -1,9 +1,26 @@
perms
-perms/dr-x
-perms/drwx
-perms/f-w-
-perms/f-wx
-perms/fr--
-perms/fr-x
-perms/frw-
-perms/frwx
+perms/dr-x------
+perms/dr-xr-xr-x
+perms/drwx------
+perms/drwxr-xr-x
+perms/drwxrwxr-x
+perms/drwxrwxrwx
+perms/f-w-------
+perms/f-w--w----
+perms/f-w--w--w-
+perms/f-wx------
+perms/f-wx--x--x
+perms/f-wx-wx--x
+perms/f-wx-wx-wx
+perms/fr--------
+perms/fr--r--r--
+perms/fr-x------
+perms/fr-xr-xr-x
+perms/frw-------
+perms/frw-r--r--
+perms/frw-rw-r--
+perms/frw-rw-rw-
+perms/frwxr-----
+perms/frwxr-xr-x
+perms/frwxrwxr-x
+perms/frwxrwxrwx
diff --git a/tests/common/L_mount.out b/tests/common/L_mount.out
deleted file mode 100644
index 788579d..0000000
--- a/tests/common/L_mount.out
+++ /dev/null
@@ -1,5 +0,0 @@
-.
-./foo
-./foo/bar
-./foo/qux
-./mnt
diff --git a/tests/common/amin.sh b/tests/common/amin.sh
index c4d53fb..92c3531 100644
--- a/tests/common/amin.sh
+++ b/tests/common/amin.sh
@@ -9,7 +9,7 @@ now=$(epoch_time)
"$XTOUCH" -at "@$((now + 60))" in_one_minute
"$XTOUCH" -at "@$((now + 60 * 60))" in_one_hour
-bfs_diff -mindepth 1 \
+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/gnu/gid.out b/tests/common/gid.out
index a7ccfe4..a7ccfe4 100644
--- a/tests/gnu/gid.out
+++ b/tests/common/gid.out
diff --git a/tests/gnu/gid.sh b/tests/common/gid.sh
index 2707b4a..2707b4a 100644
--- a/tests/gnu/gid.sh
+++ b/tests/common/gid.sh
diff --git a/tests/common/gid_invalid_id.sh b/tests/common/gid_invalid_id.sh
new file mode 100644
index 0000000..74f0055
--- /dev/null
+++ b/tests/common/gid_invalid_id.sh
@@ -0,0 +1 @@
+! invoke_bfs -gid 1eW6f5RM9Qi
diff --git a/tests/common/gid_invalid_name.sh b/tests/common/gid_invalid_name.sh
new file mode 100644
index 0000000..0e2e5f5
--- /dev/null
+++ b/tests/common/gid_invalid_name.sh
@@ -0,0 +1 @@
+! invoke_bfs -gid eW6f5RM9Qi
diff --git a/tests/gnu/gid_minus.out b/tests/common/gid_minus.out
index a7ccfe4..a7ccfe4 100644
--- a/tests/gnu/gid_minus.out
+++ b/tests/common/gid_minus.out
diff --git a/tests/gnu/gid_minus.sh b/tests/common/gid_minus.sh
index e3822f0..e3822f0 100644
--- a/tests/gnu/gid_minus.sh
+++ b/tests/common/gid_minus.sh
diff --git a/tests/gnu/gid_minus_plus.out b/tests/common/gid_minus_plus.out
index a7ccfe4..a7ccfe4 100644
--- a/tests/gnu/gid_minus_plus.out
+++ b/tests/common/gid_minus_plus.out
diff --git a/tests/gnu/gid_minus_plus.sh b/tests/common/gid_minus_plus.sh
index 4ff0877..4ff0877 100644
--- a/tests/gnu/gid_minus_plus.sh
+++ b/tests/common/gid_minus_plus.sh
diff --git a/tests/gnu/gid_plus.out b/tests/common/gid_plus.out
index a7ccfe4..a7ccfe4 100644
--- a/tests/gnu/gid_plus.out
+++ b/tests/common/gid_plus.out
diff --git a/tests/gnu/gid_plus.sh b/tests/common/gid_plus.sh
index ccba0e6..ccba0e6 100644
--- a/tests/gnu/gid_plus.sh
+++ b/tests/common/gid_plus.sh
diff --git a/tests/gnu/gid_plus_plus.out b/tests/common/gid_plus_plus.out
index a7ccfe4..a7ccfe4 100644
--- a/tests/gnu/gid_plus_plus.out
+++ b/tests/common/gid_plus_plus.out
diff --git a/tests/gnu/gid_plus_plus.sh b/tests/common/gid_plus_plus.sh
index ec7ae86..ec7ae86 100644
--- a/tests/gnu/gid_plus_plus.sh
+++ b/tests/common/gid_plus_plus.sh
diff --git a/tests/common/iname.sh b/tests/common/iname.sh
deleted file mode 100644
index c25a646..0000000
--- a/tests/common/iname.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-invoke_bfs -quit -iname PATTERN || skip
-bfs_diff basic -iname '*F*'
diff --git a/tests/common/mmin.sh b/tests/common/mmin.sh
index 38e3337..4e1d19c 100644
--- a/tests/common/mmin.sh
+++ b/tests/common/mmin.sh
@@ -9,7 +9,7 @@ now=$(epoch_time)
"$XTOUCH" -mt "@$((now + 60))" in_one_minute
"$XTOUCH" -mt "@$((now + 60 * 60))" in_one_hour
-bfs_diff -mindepth 1 \
+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/name_slash.sh b/tests/common/name_slash.sh
deleted file mode 100644
index 8d89623..0000000
--- a/tests/common/name_slash.sh
+++ /dev/null
@@ -1 +0,0 @@
-bfs_diff / -maxdepth 0 -name /
diff --git a/tests/common/name_slashes.sh b/tests/common/name_slashes.sh
deleted file mode 100644
index 78d0a84..0000000
--- a/tests/common/name_slashes.sh
+++ /dev/null
@@ -1 +0,0 @@
-bfs_diff /// -maxdepth 0 -name /
diff --git a/tests/gnu/uid.out b/tests/common/uid.out
index a7ccfe4..a7ccfe4 100644
--- a/tests/gnu/uid.out
+++ b/tests/common/uid.out
diff --git a/tests/gnu/uid.sh b/tests/common/uid.sh
index fb3cd93..fb3cd93 100644
--- a/tests/gnu/uid.sh
+++ b/tests/common/uid.sh
diff --git a/tests/common/uid_invalid_id.sh b/tests/common/uid_invalid_id.sh
new file mode 100644
index 0000000..f5b952d
--- /dev/null
+++ b/tests/common/uid_invalid_id.sh
@@ -0,0 +1 @@
+! invoke_bfs -uid 1eW6f5RM9Qi
diff --git a/tests/common/uid_invalid_name.sh b/tests/common/uid_invalid_name.sh
new file mode 100644
index 0000000..a2c359f
--- /dev/null
+++ b/tests/common/uid_invalid_name.sh
@@ -0,0 +1 @@
+! invoke_bfs -uid eW6f5RM9Qi
diff --git a/tests/gnu/uid_minus.out b/tests/common/uid_minus.out
index a7ccfe4..a7ccfe4 100644
--- a/tests/gnu/uid_minus.out
+++ b/tests/common/uid_minus.out
diff --git a/tests/gnu/uid_minus.sh b/tests/common/uid_minus.sh
index 6d371f2..6d371f2 100644
--- a/tests/gnu/uid_minus.sh
+++ b/tests/common/uid_minus.sh
diff --git a/tests/gnu/uid_minus_plus.out b/tests/common/uid_minus_plus.out
index a7ccfe4..a7ccfe4 100644
--- a/tests/gnu/uid_minus_plus.out
+++ b/tests/common/uid_minus_plus.out
diff --git a/tests/gnu/uid_minus_plus.sh b/tests/common/uid_minus_plus.sh
index e7a0496..e7a0496 100644
--- a/tests/gnu/uid_minus_plus.sh
+++ b/tests/common/uid_minus_plus.sh
diff --git a/tests/gnu/uid_plus.out b/tests/common/uid_plus.out
index a7ccfe4..a7ccfe4 100644
--- a/tests/gnu/uid_plus.out
+++ b/tests/common/uid_plus.out
diff --git a/tests/gnu/uid_plus.sh b/tests/common/uid_plus.sh
index 22b2c8e..22b2c8e 100644
--- a/tests/gnu/uid_plus.sh
+++ b/tests/common/uid_plus.sh
diff --git a/tests/gnu/uid_plus_plus.out b/tests/common/uid_plus_plus.out
index a7ccfe4..a7ccfe4 100644
--- a/tests/gnu/uid_plus_plus.out
+++ b/tests/common/uid_plus_plus.out
diff --git a/tests/gnu/uid_plus_plus.sh b/tests/common/uid_plus_plus.sh
index e021888..e021888 100644
--- a/tests/gnu/uid_plus_plus.sh
+++ b/tests/common/uid_plus_plus.sh
diff --git a/tests/gnu/executable.out b/tests/gnu/executable.out
index 08965bf..e256554 100644
--- a/tests/gnu/executable.out
+++ b/tests/gnu/executable.out
@@ -1,6 +1,19 @@
perms
-perms/dr-x
-perms/drwx
-perms/f-wx
-perms/fr-x
-perms/frwx
+perms/dr-x------
+perms/dr-xr-xr-x
+perms/drwx------
+perms/drwxr-xr-x
+perms/drwxrwxr-x
+perms/drwxrwxrwx
+perms/f--x------
+perms/f--x--x--x
+perms/f-wx------
+perms/f-wx--x--x
+perms/f-wx-wx--x
+perms/f-wx-wx-wx
+perms/fr-x------
+perms/fr-xr-xr-x
+perms/frwxr-----
+perms/frwxr-xr-x
+perms/frwxrwxr-x
+perms/frwxrwxrwx
diff --git a/tests/gnu/fprint_noerror.sh b/tests/gnu/fprint_unreached_error.sh
index f13a62b..f13a62b 100644
--- a/tests/gnu/fprint_noerror.sh
+++ b/tests/gnu/fprint_unreached_error.sh
diff --git a/tests/gnu/not_comma.out b/tests/gnu/not_comma.out
new file mode 100644
index 0000000..b90468e
--- /dev/null
+++ b/tests/gnu/not_comma.out
@@ -0,0 +1,34 @@
+basic
+basic
+basic/a
+basic/a
+basic/b
+basic/b
+basic/c
+basic/c
+basic/c/d
+basic/c/d
+basic/e
+basic/e
+basic/e/f
+basic/g
+basic/g
+basic/g/h
+basic/g/h
+basic/i
+basic/i
+basic/j
+basic/j
+basic/j/foo
+basic/k
+basic/k
+basic/k/foo
+basic/k/foo/bar
+basic/k/foo/bar
+basic/l
+basic/l
+basic/l/foo
+basic/l/foo/bar
+basic/l/foo/bar
+basic/l/foo/bar/baz
+basic/l/foo/bar/baz
diff --git a/tests/gnu/not_comma.sh b/tests/gnu/not_comma.sh
new file mode 100644
index 0000000..04c0195
--- /dev/null
+++ b/tests/gnu/not_comma.sh
@@ -0,0 +1,2 @@
+# Regression test: assertion failure in sink_not_comma()
+bfs_diff basic -not \( -print , -name '*f*' \) -print
diff --git a/tests/gnu/perm_000_slash.out b/tests/gnu/perm_000_slash.out
index 42f2fed..e279684 100644
--- a/tests/gnu/perm_000_slash.out
+++ b/tests/gnu/perm_000_slash.out
@@ -1,10 +1,29 @@
perms
-perms/dr-x
-perms/drwx
-perms/f---
-perms/f-w-
-perms/f-wx
-perms/fr--
-perms/fr-x
-perms/frw-
-perms/frwx
+perms/dr-x------
+perms/dr-xr-xr-x
+perms/drwx------
+perms/drwxr-xr-x
+perms/drwxrwxr-x
+perms/drwxrwxrwx
+perms/f---------
+perms/f--x------
+perms/f--x--x--x
+perms/f-w-------
+perms/f-w--w----
+perms/f-w--w--w-
+perms/f-wx------
+perms/f-wx--x--x
+perms/f-wx-wx--x
+perms/f-wx-wx-wx
+perms/fr--------
+perms/fr--r--r--
+perms/fr-x------
+perms/fr-xr-xr-x
+perms/frw-------
+perms/frw-r--r--
+perms/frw-rw-r--
+perms/frw-rw-rw-
+perms/frwxr-----
+perms/frwxr-xr-x
+perms/frwxrwxr-x
+perms/frwxrwxrwx
diff --git a/tests/gnu/perm_222_slash.out b/tests/gnu/perm_222_slash.out
index 5c78ecc..1b6d885 100644
--- a/tests/gnu/perm_222_slash.out
+++ b/tests/gnu/perm_222_slash.out
@@ -1,6 +1,20 @@
perms
-perms/drwx
-perms/f-w-
-perms/f-wx
-perms/frw-
-perms/frwx
+perms/drwx------
+perms/drwxr-xr-x
+perms/drwxrwxr-x
+perms/drwxrwxrwx
+perms/f-w-------
+perms/f-w--w----
+perms/f-w--w--w-
+perms/f-wx------
+perms/f-wx--x--x
+perms/f-wx-wx--x
+perms/f-wx-wx-wx
+perms/frw-------
+perms/frw-r--r--
+perms/frw-rw-r--
+perms/frw-rw-rw-
+perms/frwxr-----
+perms/frwxr-xr-x
+perms/frwxrwxr-x
+perms/frwxrwxrwx
diff --git a/tests/gnu/perm_644_slash.out b/tests/gnu/perm_644_slash.out
index 774c0ea..eef88ca 100644
--- a/tests/gnu/perm_644_slash.out
+++ b/tests/gnu/perm_644_slash.out
@@ -1,9 +1,26 @@
perms
-perms/dr-x
-perms/drwx
-perms/f-w-
-perms/f-wx
-perms/fr--
-perms/fr-x
-perms/frw-
-perms/frwx
+perms/dr-x------
+perms/dr-xr-xr-x
+perms/drwx------
+perms/drwxr-xr-x
+perms/drwxrwxr-x
+perms/drwxrwxrwx
+perms/f-w-------
+perms/f-w--w----
+perms/f-w--w--w-
+perms/f-wx------
+perms/f-wx--x--x
+perms/f-wx-wx--x
+perms/f-wx-wx-wx
+perms/fr--------
+perms/fr--r--r--
+perms/fr-x------
+perms/fr-xr-xr-x
+perms/frw-------
+perms/frw-r--r--
+perms/frw-rw-r--
+perms/frw-rw-rw-
+perms/frwxr-----
+perms/frwxr-xr-x
+perms/frwxrwxr-x
+perms/frwxrwxrwx
diff --git a/tests/gnu/perm_leading_plus_symbolic_slash.out b/tests/gnu/perm_leading_plus_symbolic_slash.out
index 774c0ea..fcbf49e 100644
--- a/tests/gnu/perm_leading_plus_symbolic_slash.out
+++ b/tests/gnu/perm_leading_plus_symbolic_slash.out
@@ -1,9 +1,28 @@
perms
-perms/dr-x
-perms/drwx
-perms/f-w-
-perms/f-wx
-perms/fr--
-perms/fr-x
-perms/frw-
-perms/frwx
+perms/dr-x------
+perms/dr-xr-xr-x
+perms/drwx------
+perms/drwxr-xr-x
+perms/drwxrwxr-x
+perms/drwxrwxrwx
+perms/f--x------
+perms/f--x--x--x
+perms/f-w-------
+perms/f-w--w----
+perms/f-w--w--w-
+perms/f-wx------
+perms/f-wx--x--x
+perms/f-wx-wx--x
+perms/f-wx-wx-wx
+perms/fr--------
+perms/fr--r--r--
+perms/fr-x------
+perms/fr-xr-xr-x
+perms/frw-------
+perms/frw-r--r--
+perms/frw-rw-r--
+perms/frw-rw-rw-
+perms/frwxr-----
+perms/frwxr-xr-x
+perms/frwxrwxr-x
+perms/frwxrwxrwx
diff --git a/tests/gnu/perm_symbolic_slash.out b/tests/gnu/perm_symbolic_slash.out
index 774c0ea..5a21321 100644
--- a/tests/gnu/perm_symbolic_slash.out
+++ b/tests/gnu/perm_symbolic_slash.out
@@ -1,9 +1,24 @@
perms
-perms/dr-x
-perms/drwx
-perms/f-w-
-perms/f-wx
-perms/fr--
-perms/fr-x
-perms/frw-
-perms/frwx
+perms/dr-x------
+perms/dr-xr-xr-x
+perms/drwx------
+perms/drwxr-xr-x
+perms/drwxrwxr-x
+perms/drwxrwxrwx
+perms/f-w-------
+perms/f-w--w----
+perms/f-w--w--w-
+perms/f-wx------
+perms/f-wx--x--x
+perms/f-wx-wx--x
+perms/f-wx-wx-wx
+perms/fr--r--r--
+perms/fr-xr-xr-x
+perms/frw-------
+perms/frw-r--r--
+perms/frw-rw-r--
+perms/frw-rw-rw-
+perms/frwxr-----
+perms/frwxr-xr-x
+perms/frwxrwxr-x
+perms/frwxrwxrwx
diff --git a/tests/gnu/readable.out b/tests/gnu/readable.out
index 285aa43..56d1f52 100644
--- a/tests/gnu/readable.out
+++ b/tests/gnu/readable.out
@@ -1,7 +1,19 @@
perms
-perms/dr-x
-perms/drwx
-perms/fr--
-perms/fr-x
-perms/frw-
-perms/frwx
+perms/dr-x------
+perms/dr-xr-xr-x
+perms/drwx------
+perms/drwxr-xr-x
+perms/drwxrwxr-x
+perms/drwxrwxrwx
+perms/fr--------
+perms/fr--r--r--
+perms/fr-x------
+perms/fr-xr-xr-x
+perms/frw-------
+perms/frw-r--r--
+perms/frw-rw-r--
+perms/frw-rw-rw-
+perms/frwxr-----
+perms/frwxr-xr-x
+perms/frwxrwxr-x
+perms/frwxrwxrwx
diff --git a/tests/gnu/writable.out b/tests/gnu/writable.out
index 5c78ecc..1b6d885 100644
--- a/tests/gnu/writable.out
+++ b/tests/gnu/writable.out
@@ -1,6 +1,20 @@
perms
-perms/drwx
-perms/f-w-
-perms/f-wx
-perms/frw-
-perms/frwx
+perms/drwx------
+perms/drwxr-xr-x
+perms/drwxrwxr-x
+perms/drwxrwxrwx
+perms/f-w-------
+perms/f-w--w----
+perms/f-w--w--w-
+perms/f-wx------
+perms/f-wx--x--x
+perms/f-wx-wx--x
+perms/f-wx-wx-wx
+perms/frw-------
+perms/frw-r--r--
+perms/frw-rw-r--
+perms/frw-rw-rw-
+perms/frwxr-----
+perms/frwxr-xr-x
+perms/frwxrwxr-x
+perms/frwxrwxrwx
diff --git a/tests/ioq.c b/tests/ioq.c
index 99c98a2..f067436 100644
--- a/tests/ioq.c
+++ b/tests/ioq.c
@@ -1,13 +1,12 @@
// Copyright © Tavian Barnes <tavianator@tavianator.com>
// SPDX-License-Identifier: 0BSD
-#include "prelude.h"
#include "tests.h"
-#include "ioq.h"
-#include "bfstd.h"
+
#include "diag.h"
#include "dir.h"
-#include <errno.h>
+#include "ioq.h"
+
#include <fcntl.h>
#include <stdlib.h>
@@ -71,7 +70,6 @@ static void check_ioq_push_block(void) {
ioq_destroy(ioq);
}
-bool check_ioq(void) {
+void check_ioq(void) {
check_ioq_push_block();
- return true;
}
diff --git a/tests/list.c b/tests/list.c
index e14570f..5d0403f 100644
--- a/tests/list.c
+++ b/tests/list.c
@@ -1,10 +1,14 @@
// Copyright © Tavian Barnes <tavianator@tavianator.com>
// SPDX-License-Identifier: 0BSD
-#include "prelude.h"
#include "tests.h"
+
+#include "bfs.h"
+#include "diag.h"
#include "list.h"
+#include <stddef.h>
+
struct item {
int n;
struct item *next;
@@ -41,7 +45,7 @@ static bool check_list_items(struct list *list, int *array, size_t size) {
#define ARRAY(...) (int[]){ __VA_ARGS__ }, countof((int[]){ __VA_ARGS__ })
#define EMPTY() NULL, 0
-bool check_list(void) {
+void check_list(void) {
struct list l1;
SLIST_INIT(&l1);
bfs_verify(check_list_items(&l1, EMPTY()));
@@ -92,6 +96,4 @@ bool check_list(void) {
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 bef2e37..81c2311 100644
--- a/tests/main.c
+++ b/tests/main.c
@@ -5,18 +5,29 @@
* Entry point for unit tests.
*/
-#include "prelude.h"
#include "tests.h"
-#include "bfstd.h"
+
#include "color.h"
+
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
+/** Result of the current test. */
+static thread_local bool pass;
+
+bool bfs_check_impl(bool result) {
+ pass &= result;
+ return result;
+}
+
+/** Unit test function type. */
+typedef void test_fn(void);
+
/**
- * Test context.
+ * Global test context.
*/
struct test_ctx {
/** Number of command line arguments. */
@@ -80,7 +91,10 @@ static bool should_run(const struct test_ctx *ctx, const char *test) {
/** Run a test if it's enabled. */
static void run_test(struct test_ctx *ctx, const char *test, test_fn *fn) {
if (should_run(ctx, test)) {
- if (fn()) {
+ pass = true;
+ fn();
+
+ if (pass) {
cfprintf(ctx->cout, "${grn}[PASS]${rs} ${bld}%s${rs}\n", test);
} else {
cfprintf(ctx->cout, "${red}[FAIL]${rs} ${bld}%s${rs}\n", test);
diff --git a/tests/mksock.c b/tests/mksock.c
index 5786cb6..f46df96 100644
--- a/tests/mksock.c
+++ b/tests/mksock.c
@@ -7,6 +7,7 @@
*/
#include "bfstd.h"
+
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
diff --git a/tests/posix/L_mount.out b/tests/posix/L_mount.out
new file mode 100644
index 0000000..7ed5f0d
--- /dev/null
+++ b/tests/posix/L_mount.out
@@ -0,0 +1,2 @@
+.
+./foo
diff --git a/tests/common/L_mount.sh b/tests/posix/L_mount.sh
index fd8042a..fd8042a 100644
--- a/tests/common/L_mount.sh
+++ b/tests/posix/L_mount.sh
diff --git a/tests/common/atime.out b/tests/posix/atime.out
index 5ed206b..5ed206b 100644
--- a/tests/common/atime.out
+++ b/tests/posix/atime.out
diff --git a/tests/common/atime.sh b/tests/posix/atime.sh
index c2e4031..25dfd7e 100644
--- a/tests/common/atime.sh
+++ b/tests/posix/atime.sh
@@ -9,7 +9,7 @@ now=$(epoch_time)
"$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 \)
+bfs_diff . \! -name . \
+ \( -atime -1 -exec printf -- '-atime -1: %s\n' {} \; -o -prune \) \
+ \( -atime 1 -exec printf -- '-atime 1: %s\n' {} \; -o -prune \) \
+ \( -atime +1 -exec printf -- '-atime +1: %s\n' {} \; -o -prune \)
diff --git a/tests/posix/group_invalid_id.sh b/tests/posix/group_invalid_id.sh
new file mode 100644
index 0000000..1a89747
--- /dev/null
+++ b/tests/posix/group_invalid_id.sh
@@ -0,0 +1 @@
+! invoke_bfs -group 1eW6f5RM9Qi
diff --git a/tests/posix/group_invalid_name.sh b/tests/posix/group_invalid_name.sh
new file mode 100644
index 0000000..a08dc72
--- /dev/null
+++ b/tests/posix/group_invalid_name.sh
@@ -0,0 +1 @@
+! invoke_bfs -group eW6f5RM9Qi
diff --git a/tests/common/iname.out b/tests/posix/iname.out
index a9e5d42..a9e5d42 100644
--- a/tests/common/iname.out
+++ b/tests/posix/iname.out
diff --git a/tests/posix/iname.sh b/tests/posix/iname.sh
new file mode 100644
index 0000000..a9297ac
--- /dev/null
+++ b/tests/posix/iname.sh
@@ -0,0 +1 @@
+bfs_diff basic -iname '*F*'
diff --git a/tests/common/mount.out b/tests/posix/mount.out
index 6253434..b0ad937 100644
--- a/tests/common/mount.out
+++ b/tests/posix/mount.out
@@ -1,4 +1,3 @@
.
./foo
./foo/bar
-./mnt
diff --git a/tests/common/mount.sh b/tests/posix/mount.sh
index c9abde5..c9abde5 100644
--- a/tests/common/mount.sh
+++ b/tests/posix/mount.sh
diff --git a/tests/common/mtime.out b/tests/posix/mtime.out
index 91f0114..91f0114 100644
--- a/tests/common/mtime.out
+++ b/tests/posix/mtime.out
diff --git a/tests/common/mtime.sh b/tests/posix/mtime.sh
index 7ba127e..8367631 100644
--- a/tests/common/mtime.sh
+++ b/tests/posix/mtime.sh
@@ -9,7 +9,7 @@ now=$(epoch_time)
"$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 \)
+bfs_diff . \! -name . \
+ \( -mtime -1 -exec printf -- '-mtime -1: %s\n' {} \; -o -prune \) \
+ \( -mtime 1 -exec printf -- '-mtime 1: %s\n' {} \; -o -prune \) \
+ \( -mtime +1 -exec printf -- '-mtime +1: %s\n' {} \; -o -prune \)
diff --git a/tests/common/name_slash.out b/tests/posix/name_slash.out
index b498fd4..b498fd4 100644
--- a/tests/common/name_slash.out
+++ b/tests/posix/name_slash.out
diff --git a/tests/posix/name_slash.sh b/tests/posix/name_slash.sh
new file mode 100644
index 0000000..b42b145
--- /dev/null
+++ b/tests/posix/name_slash.sh
@@ -0,0 +1 @@
+bfs_diff / -prune -name /
diff --git a/tests/common/name_slashes.out b/tests/posix/name_slashes.out
index 187b81f..187b81f 100644
--- a/tests/common/name_slashes.out
+++ b/tests/posix/name_slashes.out
diff --git a/tests/posix/name_slashes.sh b/tests/posix/name_slashes.sh
new file mode 100644
index 0000000..45a39d3
--- /dev/null
+++ b/tests/posix/name_slashes.sh
@@ -0,0 +1 @@
+bfs_diff /// -prune -name /
diff --git a/tests/posix/perm_000.out b/tests/posix/perm_000.out
index b46af62..9df7f46 100644
--- a/tests/posix/perm_000.out
+++ b/tests/posix/perm_000.out
@@ -1 +1 @@
-perms/f---
+perms/f---------
diff --git a/tests/posix/perm_000_minus.out b/tests/posix/perm_000_minus.out
index 42f2fed..e279684 100644
--- a/tests/posix/perm_000_minus.out
+++ b/tests/posix/perm_000_minus.out
@@ -1,10 +1,29 @@
perms
-perms/dr-x
-perms/drwx
-perms/f---
-perms/f-w-
-perms/f-wx
-perms/fr--
-perms/fr-x
-perms/frw-
-perms/frwx
+perms/dr-x------
+perms/dr-xr-xr-x
+perms/drwx------
+perms/drwxr-xr-x
+perms/drwxrwxr-x
+perms/drwxrwxrwx
+perms/f---------
+perms/f--x------
+perms/f--x--x--x
+perms/f-w-------
+perms/f-w--w----
+perms/f-w--w--w-
+perms/f-wx------
+perms/f-wx--x--x
+perms/f-wx-wx--x
+perms/f-wx-wx-wx
+perms/fr--------
+perms/fr--r--r--
+perms/fr-x------
+perms/fr-xr-xr-x
+perms/frw-------
+perms/frw-r--r--
+perms/frw-rw-r--
+perms/frw-rw-rw-
+perms/frwxr-----
+perms/frwxr-xr-x
+perms/frwxrwxr-x
+perms/frwxrwxrwx
diff --git a/tests/posix/perm_222.out b/tests/posix/perm_222.out
index 4876193..bdc5590 100644
--- a/tests/posix/perm_222.out
+++ b/tests/posix/perm_222.out
@@ -1 +1 @@
-perms/f-w-
+perms/f-w--w--w-
diff --git a/tests/posix/perm_222_minus.out b/tests/posix/perm_222_minus.out
index 4876193..342b285 100644
--- a/tests/posix/perm_222_minus.out
+++ b/tests/posix/perm_222_minus.out
@@ -1 +1,5 @@
-perms/f-w-
+perms/drwxrwxrwx
+perms/f-w--w--w-
+perms/f-wx-wx-wx
+perms/frw-rw-rw-
+perms/frwxrwxrwx
diff --git a/tests/posix/perm_644.out b/tests/posix/perm_644.out
index 4598cc1..9f77ce6 100644
--- a/tests/posix/perm_644.out
+++ b/tests/posix/perm_644.out
@@ -1 +1 @@
-perms/frw-
+perms/frw-r--r--
diff --git a/tests/posix/perm_644_minus.out b/tests/posix/perm_644_minus.out
index 9e041c3..84f69f5 100644
--- a/tests/posix/perm_644_minus.out
+++ b/tests/posix/perm_644_minus.out
@@ -1,4 +1,10 @@
perms
-perms/drwx
-perms/frw-
-perms/frwx
+perms/drwxr-xr-x
+perms/drwxrwxr-x
+perms/drwxrwxrwx
+perms/frw-r--r--
+perms/frw-rw-r--
+perms/frw-rw-rw-
+perms/frwxr-xr-x
+perms/frwxrwxr-x
+perms/frwxrwxrwx
diff --git a/tests/posix/perm_leading_plus_symbolic_minus.out b/tests/posix/perm_leading_plus_symbolic_minus.out
index e69de29..38d0e1c 100644
--- a/tests/posix/perm_leading_plus_symbolic_minus.out
+++ b/tests/posix/perm_leading_plus_symbolic_minus.out
@@ -0,0 +1,7 @@
+perms
+perms/drwxr-xr-x
+perms/drwxrwxr-x
+perms/drwxrwxrwx
+perms/frwxr-xr-x
+perms/frwxrwxr-x
+perms/frwxrwxrwx
diff --git a/tests/posix/perm_leading_plus_umask.out b/tests/posix/perm_leading_plus_umask.out
new file mode 100644
index 0000000..6ed4b7f
--- /dev/null
+++ b/tests/posix/perm_leading_plus_umask.out
@@ -0,0 +1,10 @@
+perms/drwxrwxr-x
+perms/drwxrwxrwx
+perms/f-w--w----
+perms/f-w--w--w-
+perms/f-wx-wx--x
+perms/f-wx-wx-wx
+perms/frw-rw-r--
+perms/frw-rw-rw-
+perms/frwxrwxr-x
+perms/frwxrwxrwx
diff --git a/tests/posix/perm_leading_plus_umask.sh b/tests/posix/perm_leading_plus_umask.sh
new file mode 100644
index 0000000..948b4ad
--- /dev/null
+++ b/tests/posix/perm_leading_plus_umask.sh
@@ -0,0 +1,3 @@
+# Test for https://www.austingroupbugs.net/view.php?id=1392
+umask 002
+bfs_diff perms -perm -+w
diff --git a/tests/posix/perm_symbolic_minus.out b/tests/posix/perm_symbolic_minus.out
index 9e041c3..84f69f5 100644
--- a/tests/posix/perm_symbolic_minus.out
+++ b/tests/posix/perm_symbolic_minus.out
@@ -1,4 +1,10 @@
perms
-perms/drwx
-perms/frw-
-perms/frwx
+perms/drwxr-xr-x
+perms/drwxrwxr-x
+perms/drwxrwxrwx
+perms/frw-r--r--
+perms/frw-rw-r--
+perms/frw-rw-rw-
+perms/frwxr-xr-x
+perms/frwxrwxr-x
+perms/frwxrwxrwx
diff --git a/tests/posix/permcopy.out b/tests/posix/permcopy.out
index 4598cc1..9f77ce6 100644
--- a/tests/posix/permcopy.out
+++ b/tests/posix/permcopy.out
@@ -1 +1 @@
-perms/frw-
+perms/frw-r--r--
diff --git a/tests/gnu/print0.out b/tests/posix/print0.out
index 1347444..1347444 100644
--- a/tests/gnu/print0.out
+++ b/tests/posix/print0.out
Binary files differ
diff --git a/tests/gnu/print0.sh b/tests/posix/print0.sh
index b916172..b916172 100644
--- a/tests/gnu/print0.sh
+++ b/tests/posix/print0.sh
diff --git a/tests/posix/user_invalid_id.sh b/tests/posix/user_invalid_id.sh
new file mode 100644
index 0000000..c378f7e
--- /dev/null
+++ b/tests/posix/user_invalid_id.sh
@@ -0,0 +1 @@
+! invoke_bfs -user 1eW6f5RM9Qi
diff --git a/tests/posix/user_invalid_name.sh b/tests/posix/user_invalid_name.sh
new file mode 100644
index 0000000..bbf3031
--- /dev/null
+++ b/tests/posix/user_invalid_name.sh
@@ -0,0 +1 @@
+! invoke_bfs -user eW6f5RM9Qi
diff --git a/tests/run.sh b/tests/run.sh
index 115a036..612e11a 100644
--- a/tests/run.sh
+++ b/tests/run.sh
@@ -94,9 +94,10 @@ reap_test() {
# Wait for a background test to finish
wait_test() {
- local pid
+ local pid line ret
while true; do
+ line=$((LINENO + 1))
wait -n -ppid
ret=$?
@@ -106,7 +107,7 @@ wait_test() {
# Interrupted by signal
continue
else
- debug "${BASH_SOURCE[0]}" $((LINENO - 3)) "${RED}error $ret${RST}" >&$DUPERR
+ debug "${BASH_SOURCE[0]}" $line "${RED}error $ret${RST}" >&$DUPERR
exit 1
fi
done
@@ -120,7 +121,9 @@ wait_ready() {
# We'd like to parse the output of jobs -n, but we can't run it in a
# subshell or we won't get the right output
jobs -n >"$TMP/jobs"
- while read -r job status ret foo; do
+
+ local job status ret rest
+ while read -r job status ret rest; do
case "$status" in
Done)
reap_test 0
diff --git a/tests/sighook.c b/tests/sighook.c
index c94526e..aa01c36 100644
--- a/tests/sighook.c
+++ b/tests/sighook.c
@@ -1,97 +1,112 @@
// Copyright © Tavian Barnes <tavianator@tavianator.com>
// SPDX-License-Identifier: 0BSD
-#include "prelude.h"
#include "tests.h"
-#include "sighook.h"
+
#include "atomic.h"
#include "thread.h"
+#include "sighook.h"
+
+#include <stddef.h>
+#include <errno.h>
#include <pthread.h>
#include <signal.h>
#include <sys/time.h>
+/** Counts SIGALRM deliveries. */
static atomic size_t count = 0;
+/** Keeps the background thread alive. */
+static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
+static bool done = false;
+
/** SIGALRM handler. */
static void alrm_hook(int sig, siginfo_t *info, void *arg) {
fetch_add(&count, 1, relaxed);
}
-/** Swap out an old hook for a new hook. */
-static int swap_hooks(struct sighook **hook) {
- struct sighook *next = sighook(SIGALRM, alrm_hook, NULL, SH_CONTINUE);
- if (!bfs_echeck(next, "sighook(SIGALRM)")) {
- return -1;
+/** Background thread that receives signals. */
+static void *hook_thread(void *ptr) {
+ mutex_lock(&mutex);
+ while (!done) {
+ cond_wait(&cond, &mutex);
}
-
- sigunhook(*hook);
- *hook = next;
- return 0;
+ mutex_unlock(&mutex);
+ return NULL;
}
-/** Background thread that rapidly (un)registers signal hooks. */
-static void *hook_thread(void *ptr) {
- struct sighook *hook = sighook(SIGALRM, alrm_hook, NULL, SH_CONTINUE);
- if (!bfs_echeck(hook, "sighook(SIGALRM)")) {
- return NULL;
+/** Block a signal in this thread. */
+static int block_signal(int sig, sigset_t *old) {
+ sigset_t set;
+ if (sigemptyset(&set) != 0) {
+ return -1;
+ }
+ if (sigaddset(&set, sig) != 0) {
+ return -1;
}
- while (load(&count, relaxed) < 1000) {
- if (swap_hooks(&hook) != 0) {
- sigunhook(hook);
- return NULL;
- }
+ errno = pthread_sigmask(SIG_BLOCK, &set, old);
+ if (errno != 0) {
+ return -1;
}
- sigunhook(hook);
- return &count;
+ return 0;
}
-bool check_sighook(void) {
- bool ret = true;
-
+void check_sighook(void) {
struct sighook *hook = sighook(SIGALRM, alrm_hook, NULL, SH_CONTINUE);
- ret &= bfs_echeck(hook, "sighook(SIGALRM)");
- if (!ret) {
- goto done;
+ if (!bfs_echeck(hook, "sighook(SIGALRM)")) {
+ return;
}
- struct itimerval ival = {
- .it_value = {
- .tv_usec = 100,
- },
- .it_interval = {
- .tv_usec = 100,
- },
- };
- ret &= bfs_echeck(setitimer(ITIMER_REAL, &ival, NULL) == 0);
- if (!ret) {
+ // Create a timer that sends SIGALRM every 100 microseconds
+ struct itimerval ival = {0};
+ ival.it_value.tv_usec = 100;
+ ival.it_interval.tv_usec = 100;
+ if (!bfs_echeck(setitimer(ITIMER_REAL, &ival, NULL) == 0)) {
goto unhook;
}
+ // Create a background thread to receive signals
pthread_t thread;
- ret &= bfs_echeck(thread_create(&thread, NULL, hook_thread, NULL) == 0);
- if (!ret) {
+ if (!bfs_echeck(thread_create(&thread, NULL, hook_thread, NULL) == 0)) {
goto untime;
}
- while (ret && load(&count, relaxed) < 1000) {
- ret &= swap_hooks(&hook) == 0;
+ // Block SIGALRM in this thread so the handler runs concurrently with
+ // sighook()/sigunhook()
+ sigset_t mask;
+ if (!bfs_echeck(block_signal(SIGALRM, &mask) == 0)) {
+ goto untime;
}
- void *ptr;
- thread_join(thread, &ptr);
- ret &= bfs_check(ptr);
+ // Rapidly register/unregister SIGALRM hooks
+ while (load(&count, relaxed) < 1000) {
+ struct sighook *next = sighook(SIGALRM, alrm_hook, NULL, SH_CONTINUE);
+ if (!bfs_echeck(next, "sighook(SIGALRM)")) {
+ break;
+ }
-untime:
- ival.it_value.tv_usec = 0;
- ret &= bfs_echeck(setitimer(ITIMER_REAL, &ival, NULL) == 0);
- if (!ret) {
- goto unhook;
+ sigunhook(hook);
+ hook = next;
}
+ // Quit the background thread
+ mutex_lock(&mutex);
+ done = true;
+ mutex_unlock(&mutex);
+ cond_signal(&cond);
+ thread_join(thread, NULL);
+
+ // Restore the old signal mask
+ errno = pthread_sigmask(SIG_SETMASK, &mask, NULL);
+ bfs_echeck(errno == 0, "pthread_sigmask()");
+untime:
+ // Stop the timer
+ ival.it_value.tv_usec = 0;
+ bfs_echeck(setitimer(ITIMER_REAL, &ival, NULL) == 0);
unhook:
+ // Unregister the SIGALRM hook
sigunhook(hook);
-done:
- return ret;
}
diff --git a/tests/stddirs.sh b/tests/stddirs.sh
index b3cd521..1183970 100644
--- a/tests/stddirs.sh
+++ b/tests/stddirs.sh
@@ -14,13 +14,31 @@ make_basic() {
# Creates a file+directory structure with various permissions for tests
make_perms() {
- "$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/"
+ "$XTOUCH" -p -M000 "$1/f---------"
+ "$XTOUCH" -p -M111 "$1/f--x--x--x"
+ "$XTOUCH" -p -M222 "$1/f-w--w--w-"
+ "$XTOUCH" -p -M333 "$1/f-wx-wx-wx"
+ "$XTOUCH" -p -M444 "$1/fr--r--r--"
+ "$XTOUCH" -p -M555 "$1/fr-xr-xr-x" "$1/dr-xr-xr-x/"
+ "$XTOUCH" -p -M666 "$1/frw-rw-rw-"
+ "$XTOUCH" -p -M777 "$1/frwxrwxrwx" "$1/drwxrwxrwx/"
+
+ "$XTOUCH" -p -M220 "$1/f-w--w----"
+ "$XTOUCH" -p -M331 "$1/f-wx-wx--x"
+ "$XTOUCH" -p -M664 "$1/frw-rw-r--"
+ "$XTOUCH" -p -M775 "$1/frwxrwxr-x" "$1/drwxrwxr-x/"
+
+ "$XTOUCH" -p -M311 "$1/f-wx--x--x"
+ "$XTOUCH" -p -M644 "$1/frw-r--r--"
+ "$XTOUCH" -p -M755 "$1/frwxr-xr-x" "$1/drwxr-xr-x/"
+
+ "$XTOUCH" -p -M100 "$1/f--x------"
+ "$XTOUCH" -p -M200 "$1/f-w-------"
+ "$XTOUCH" -p -M300 "$1/f-wx------"
+ "$XTOUCH" -p -M400 "$1/fr--------"
+ "$XTOUCH" -p -M500 "$1/fr-x------" "$1/dr-x------/"
+ "$XTOUCH" -p -M600 "$1/frw-------"
+ "$XTOUCH" -p -M700 "$1/frwxr-----" "$1/drwx------/"
}
# Creates a file+directory structure with various symbolic and hard links
diff --git a/tests/tests.h b/tests/tests.h
index 2958fe1..4c6b3d2 100644
--- a/tests/tests.h
+++ b/tests/tests.h
@@ -8,51 +8,44 @@
#ifndef BFS_TESTS_H
#define BFS_TESTS_H
-#include "prelude.h"
#include "bfstd.h"
#include "diag.h"
-/** Unit test function type. */
-typedef bool test_fn(void);
-
/** Memory allocation tests. */
-bool check_alloc(void);
+void check_alloc(void);
/** Standard library wrapper tests. */
-bool check_bfstd(void);
+void check_bfstd(void);
/** Bit manipulation tests. */
-bool check_bit(void);
+void check_bit(void);
/** I/O queue tests. */
-bool check_ioq(void);
+void check_ioq(void);
/** Linked list tests. */
-bool check_list(void);
+void check_list(void);
/** Signal hook tests. */
-bool check_sighook(void);
+void check_sighook(void);
/** Trie tests. */
-bool check_trie(void);
+void check_trie(void);
/** Process spawning tests. */
-bool check_xspawn(void);
+void check_xspawn(void);
/** Time tests. */
-bool check_xtime(void);
+void check_xtime(void);
-/** Don't ignore the bfs_check() return value. */
-attr(nodiscard)
-static inline bool bfs_check(bool ret) {
- return ret;
-}
+/** Record a single check and return the result. */
+bool bfs_check_impl(bool result);
/**
* Check a condition, logging a message on failure but continuing.
*/
#define bfs_check(...) \
- bfs_check(bfs_check_(#__VA_ARGS__, __VA_ARGS__, "", ""))
+ bfs_check_impl(bfs_check_(#__VA_ARGS__, __VA_ARGS__, "", ""))
#define bfs_check_(str, cond, format, ...) \
((cond) ? true : (bfs_diag( \
@@ -65,7 +58,7 @@ static inline bool bfs_check(bool ret) {
* Check a condition, logging the current error string on failure.
*/
#define bfs_echeck(...) \
- bfs_echeck_(#__VA_ARGS__, __VA_ARGS__, "", errstr())
+ bfs_check_impl(bfs_echeck_(#__VA_ARGS__, __VA_ARGS__, "", errstr()))
#define bfs_echeck_(str, cond, format, ...) \
((cond) ? true : (bfs_diag( \
diff --git a/tests/trie.c b/tests/trie.c
index ebaae5d..9e9a294 100644
--- a/tests/trie.c
+++ b/tests/trie.c
@@ -1,10 +1,12 @@
// Copyright © Tavian Barnes <tavianator@tavianator.com>
// SPDX-License-Identifier: 0BSD
-#include "prelude.h"
#include "tests.h"
-#include "trie.h"
+
+#include "bfs.h"
#include "diag.h"
+#include "trie.h"
+
#include <stdlib.h>
#include <string.h>
@@ -39,14 +41,12 @@ static const char *keys[] = {
static const size_t nkeys = countof(keys);
-bool check_trie(void) {
- bool ret = true;
-
+void check_trie(void) {
struct trie trie;
trie_init(&trie);
for (size_t i = 0; i < nkeys; ++i) {
- ret &= bfs_check(!trie_find_str(&trie, keys[i]));
+ bfs_check(!trie_find_str(&trie, keys[i]));
const char *prefix = NULL;
for (size_t j = 0; j < i; ++j) {
@@ -60,37 +60,37 @@ bool check_trie(void) {
struct trie_leaf *leaf = trie_find_prefix(&trie, keys[i]);
if (prefix) {
bfs_verify(leaf);
- ret &= bfs_check(strcmp(prefix, leaf->key) == 0);
+ bfs_check(strcmp(prefix, leaf->key) == 0);
} else {
- ret &= bfs_check(!leaf);
+ bfs_check(!leaf);
}
leaf = trie_insert_str(&trie, keys[i]);
bfs_verify(leaf);
- ret &= bfs_check(strcmp(keys[i], leaf->key) == 0);
- ret &= bfs_check(leaf->length == strlen(keys[i]) + 1);
+ bfs_check(strcmp(keys[i], leaf->key) == 0);
+ bfs_check(leaf->length == strlen(keys[i]) + 1);
}
{
size_t i = 0;
for_trie (leaf, &trie) {
- ret &= bfs_check(leaf == trie_find_str(&trie, keys[i]));
- ret &= bfs_check(!leaf->prev || leaf->prev->next == leaf);
- ret &= bfs_check(!leaf->next || leaf->next->prev == leaf);
+ bfs_check(leaf == trie_find_str(&trie, keys[i]));
+ bfs_check(!leaf->prev || leaf->prev->next == leaf);
+ bfs_check(!leaf->next || leaf->next->prev == leaf);
++i;
}
- ret &= bfs_check(i == nkeys);
+ bfs_check(i == nkeys);
}
for (size_t i = 0; i < nkeys; ++i) {
struct trie_leaf *leaf = trie_find_str(&trie, keys[i]);
bfs_verify(leaf);
- ret &= bfs_check(strcmp(keys[i], leaf->key) == 0);
- ret &= bfs_check(leaf->length == strlen(keys[i]) + 1);
+ bfs_check(strcmp(keys[i], leaf->key) == 0);
+ bfs_check(leaf->length == strlen(keys[i]) + 1);
trie_remove(&trie, leaf);
leaf = trie_find_str(&trie, keys[i]);
- ret &= bfs_check(!leaf);
+ bfs_check(!leaf);
const char *postfix = NULL;
for (size_t j = i + 1; j < nkeys; ++j) {
@@ -104,14 +104,14 @@ bool check_trie(void) {
leaf = trie_find_postfix(&trie, keys[i]);
if (postfix) {
bfs_verify(leaf);
- ret &= bfs_check(strcmp(postfix, leaf->key) == 0);
+ bfs_check(strcmp(postfix, leaf->key) == 0);
} else {
- ret &= bfs_check(!leaf);
+ bfs_check(!leaf);
}
}
for_trie (leaf, &trie) {
- ret &= bfs_check(false, "trie should be empty");
+ bfs_check(false, "trie should be empty");
}
// This tests the "jump" node handling on 32-bit platforms
@@ -120,18 +120,17 @@ bool check_trie(void) {
bfs_verify(longstr);
memset(longstr, 0xAC, longsize);
- ret &= bfs_check(!trie_find_mem(&trie, longstr, longsize));
- ret &= bfs_check(trie_insert_mem(&trie, longstr, longsize));
+ bfs_check(!trie_find_mem(&trie, longstr, longsize));
+ bfs_check(trie_insert_mem(&trie, longstr, longsize));
memset(longstr + longsize / 2, 0xAB, longsize / 2);
- ret &= bfs_check(!trie_find_mem(&trie, longstr, longsize));
- ret &= bfs_check(trie_insert_mem(&trie, longstr, longsize));
+ bfs_check(!trie_find_mem(&trie, longstr, longsize));
+ bfs_check(trie_insert_mem(&trie, longstr, longsize));
memset(longstr, 0xAA, longsize / 2);
- ret &= bfs_check(!trie_find_mem(&trie, longstr, longsize));
- ret &= bfs_check(trie_insert_mem(&trie, longstr, longsize));
+ bfs_check(!trie_find_mem(&trie, longstr, longsize));
+ bfs_check(trie_insert_mem(&trie, longstr, longsize));
free(longstr);
trie_destroy(&trie);
- return ret;
}
diff --git a/tests/xspawn.c b/tests/xspawn.c
index f48e220..3194adc 100644
--- a/tests/xspawn.c
+++ b/tests/xspawn.c
@@ -1,12 +1,13 @@
// Copyright © Tavian Barnes <tavianator@tavianator.com>
// SPDX-License-Identifier: 0BSD
-#include "prelude.h"
#include "tests.h"
+
#include "alloc.h"
#include "bfstd.h"
#include "dstring.h"
#include "xspawn.h"
+
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
@@ -50,13 +51,10 @@ fail:
}
/** Check that we resolve executables in $PATH correctly. */
-static bool check_use_path(bool use_posix) {
- bool ret = true;
-
+static void check_use_path(bool use_posix) {
struct bfs_spawn spawn;
- ret &= bfs_echeck(bfs_spawn_init(&spawn) == 0);
- if (!ret) {
- goto out;
+ if (!bfs_echeck(bfs_spawn_init(&spawn) == 0)) {
+ return;
}
spawn.flags |= BFS_SPAWN_USE_PATH;
@@ -64,19 +62,18 @@ static bool check_use_path(bool use_posix) {
spawn.flags &= ~BFS_SPAWN_USE_POSIX;
}
- ret &= bfs_echeck(bfs_spawn_addopen(&spawn, 10, "bin", O_RDONLY | O_DIRECTORY, 0) == 0);
- ret &= bfs_echeck(bfs_spawn_adddup2(&spawn, 10, 11) == 0);
- ret &= bfs_echeck(bfs_spawn_addclose(&spawn, 10) == 0);
- ret &= bfs_echeck(bfs_spawn_addfchdir(&spawn, 11) == 0);
- ret &= bfs_echeck(bfs_spawn_addclose(&spawn, 11) == 0);
- if (!ret) {
+ bool init = bfs_echeck(bfs_spawn_addopen(&spawn, 10, "bin", O_RDONLY | O_DIRECTORY, 0) == 0)
+ && bfs_echeck(bfs_spawn_adddup2(&spawn, 10, 11) == 0)
+ && bfs_echeck(bfs_spawn_addclose(&spawn, 10) == 0)
+ && bfs_echeck(bfs_spawn_addfchdir(&spawn, 11) == 0)
+ && bfs_echeck(bfs_spawn_addclose(&spawn, 11) == 0);
+ if (!init) {
goto destroy;
}
// Check that $PATH is resolved in the parent's environment
- char **envp;
- ret &= bfs_echeck(envp = envdup());
- if (!ret) {
+ char **envp = envdup();
+ if (!bfs_echeck(envp, "envdup()")) {
goto destroy;
}
@@ -84,44 +81,41 @@ static bool check_use_path(bool use_posix) {
char *old_path = getenv("PATH");
dchar *new_path = NULL;
if (old_path) {
- ret &= bfs_echeck(old_path = strdup(old_path));
- if (!ret) {
+ old_path = strdup(old_path);
+ if (!bfs_echeck(old_path, "strdup()")) {
goto env;
}
new_path = dstrprintf("tests:%s", old_path);
} else {
new_path = dstrdup("tests");
}
- ret &= bfs_check(new_path);
- if (!ret) {
+ if (!bfs_check(new_path)) {
goto path;
}
- ret &= bfs_echeck(setenv("PATH", new_path, true) == 0);
- if (!ret) {
+ if (!bfs_echeck(setenv("PATH", new_path, true) == 0)) {
goto path;
}
char *argv[] = {"xspawnee", old_path, NULL};
pid_t pid = bfs_spawn("xspawnee", &spawn, argv, envp);
- ret &= bfs_echeck(pid >= 0, "bfs_spawn()");
- if (!ret) {
+ if (!bfs_echeck(pid >= 0, "bfs_spawn()")) {
goto unset;
}
int wstatus;
- ret &= bfs_echeck(xwaitpid(pid, &wstatus, 0) == pid)
+ bool exited = bfs_echeck(xwaitpid(pid, &wstatus, 0) == pid)
&& bfs_check(WIFEXITED(wstatus));
- if (ret) {
+ if (exited) {
int wexit = WEXITSTATUS(wstatus);
- ret &= bfs_check(wexit == EXIT_SUCCESS, "xspawnee: exit(%d)", wexit);
+ bfs_check(wexit == EXIT_SUCCESS, "xspawnee: exit(%d)", wexit);
}
unset:
if (old_path) {
- ret &= bfs_echeck(setenv("PATH", old_path, true) == 0);
+ bfs_echeck(setenv("PATH", old_path, true) == 0);
} else {
- ret &= bfs_echeck(unsetenv("PATH") == 0);
+ bfs_echeck(unsetenv("PATH") == 0);
}
path:
dstrfree(new_path);
@@ -132,19 +126,14 @@ env:
}
free(envp);
destroy:
- ret &= bfs_echeck(bfs_spawn_destroy(&spawn) == 0);
-out:
- return ret;
+ bfs_echeck(bfs_spawn_destroy(&spawn) == 0);
}
/** Check path resolution of non-existent executables. */
-static bool check_enoent(bool use_posix) {
- bool ret = true;
-
+static void check_enoent(bool use_posix) {
struct bfs_spawn spawn;
- ret &= bfs_echeck(bfs_spawn_init(&spawn) == 0);
- if (!ret) {
- goto out;
+ if (!bfs_echeck(bfs_spawn_init(&spawn) == 0)) {
+ return;
}
spawn.flags |= BFS_SPAWN_USE_PATH;
@@ -154,46 +143,37 @@ static bool check_enoent(bool use_posix) {
char *argv[] = {"eW6f5RM9Qi", NULL};
pid_t pid = bfs_spawn("eW6f5RM9Qi", &spawn, argv, NULL);
- ret &= bfs_echeck(pid < 0 && errno == ENOENT, "bfs_spawn()");
+ bfs_echeck(pid < 0 && errno == ENOENT, "bfs_spawn()");
- ret &= bfs_echeck(bfs_spawn_destroy(&spawn) == 0);
-out:
- return ret;
+ bfs_echeck(bfs_spawn_destroy(&spawn) == 0);
}
-static bool check_resolve(void) {
- bool ret = true;
+static void check_resolve(void) {
char *exe;
exe = bfs_spawn_resolve("sh");
- ret &= bfs_echeck(exe, "bfs_spawn_resolve('sh')");
+ bfs_echeck(exe, "bfs_spawn_resolve('sh')");
free(exe);
exe = bfs_spawn_resolve("/bin/sh");
- ret &= bfs_echeck(exe && strcmp(exe, "/bin/sh") == 0);
+ bfs_echeck(exe && strcmp(exe, "/bin/sh") == 0);
free(exe);
exe = bfs_spawn_resolve("bin/tests/xspawnee");
- ret &= bfs_echeck(exe && strcmp(exe, "bin/tests/xspawnee") == 0);
+ bfs_echeck(exe && strcmp(exe, "bin/tests/xspawnee") == 0);
free(exe);
- ret &= bfs_echeck(!bfs_spawn_resolve("eW6f5RM9Qi") && errno == ENOENT);
+ bfs_echeck(!bfs_spawn_resolve("eW6f5RM9Qi") && errno == ENOENT);
- ret &= bfs_echeck(!bfs_spawn_resolve("bin/eW6f5RM9Qi") && errno == ENOENT);
-
- return ret;
+ bfs_echeck(!bfs_spawn_resolve("bin/eW6f5RM9Qi") && errno == ENOENT);
}
-bool check_xspawn(void) {
- bool ret = true;
-
- ret &= check_use_path(true);
- ret &= check_use_path(false);
-
- ret &= check_enoent(true);
- ret &= check_enoent(false);
+void check_xspawn(void) {
+ check_use_path(true);
+ check_use_path(false);
- ret &= check_resolve();
+ check_enoent(true);
+ check_enoent(false);
- return ret;
+ check_resolve();
}
diff --git a/tests/xtime.c b/tests/xtime.c
index 1907e26..3472bea 100644
--- a/tests/xtime.c
+++ b/tests/xtime.c
@@ -1,11 +1,12 @@
// Copyright © Tavian Barnes <tavianator@tavianator.com>
// SPDX-License-Identifier: 0BSD
-#include "prelude.h"
#include "tests.h"
-#include "xtime.h"
-#include "bfstd.h"
+
+#include "bfs.h"
#include "diag.h"
+#include "xtime.h"
+
#include <errno.h>
#include <limits.h>
#include <stdint.h>
@@ -39,35 +40,31 @@ static bool check_one_xgetdate(const char *str, int error, time_t expected) {
}
/** xgetdate() tests. */
-static bool check_xgetdate(void) {
- bool ret = true;
-
- ret &= check_one_xgetdate("", EINVAL, 0);
- ret &= check_one_xgetdate("????", EINVAL, 0);
- ret &= check_one_xgetdate("1991", EINVAL, 0);
- ret &= check_one_xgetdate("1991-??", EINVAL, 0);
- ret &= check_one_xgetdate("1991-12", EINVAL, 0);
- ret &= check_one_xgetdate("1991-12-", EINVAL, 0);
- ret &= check_one_xgetdate("1991-12-??", EINVAL, 0);
- ret &= check_one_xgetdate("1991-12-14", 0, 692668800);
- ret &= check_one_xgetdate("1991-12-14-", EINVAL, 0);
- ret &= check_one_xgetdate("1991-12-14T", EINVAL, 0);
- ret &= check_one_xgetdate("1991-12-14T??", EINVAL, 0);
- ret &= check_one_xgetdate("1991-12-14T10", 0, 692704800);
- ret &= check_one_xgetdate("1991-12-14T10:??", EINVAL, 0);
- ret &= check_one_xgetdate("1991-12-14T10:11", 0, 692705460);
- ret &= check_one_xgetdate("1991-12-14T10:11:??", EINVAL, 0);
- ret &= check_one_xgetdate("1991-12-14T10:11:12", 0, 692705472);
- ret &= check_one_xgetdate("1991-12-14T10Z", 0, 692704800);
- ret &= check_one_xgetdate("1991-12-14T10:11Z", 0, 692705460);
- ret &= check_one_xgetdate("1991-12-14T10:11:12Z", 0, 692705472);
- ret &= check_one_xgetdate("1991-12-14T10:11:12?", EINVAL, 0);
- ret &= check_one_xgetdate("1991-12-14T03-07", 0, 692704800);
- ret &= check_one_xgetdate("1991-12-14T06:41-03:30", 0, 692705460);
- ret &= check_one_xgetdate("1991-12-14T03:11:12-07:00", 0, 692705472);
- ret &= check_one_xgetdate("19911214 031112-0700", 0, 692705472);;
-
- return ret;
+static void check_xgetdate(void) {
+ check_one_xgetdate("", EINVAL, 0);
+ check_one_xgetdate("????", EINVAL, 0);
+ check_one_xgetdate("1991", EINVAL, 0);
+ check_one_xgetdate("1991-??", EINVAL, 0);
+ check_one_xgetdate("1991-12", EINVAL, 0);
+ check_one_xgetdate("1991-12-", EINVAL, 0);
+ check_one_xgetdate("1991-12-??", EINVAL, 0);
+ check_one_xgetdate("1991-12-14", 0, 692668800);
+ check_one_xgetdate("1991-12-14-", EINVAL, 0);
+ check_one_xgetdate("1991-12-14T", EINVAL, 0);
+ check_one_xgetdate("1991-12-14T??", EINVAL, 0);
+ check_one_xgetdate("1991-12-14T10", 0, 692704800);
+ check_one_xgetdate("1991-12-14T10:??", EINVAL, 0);
+ check_one_xgetdate("1991-12-14T10:11", 0, 692705460);
+ check_one_xgetdate("1991-12-14T10:11:??", EINVAL, 0);
+ check_one_xgetdate("1991-12-14T10:11:12", 0, 692705472);
+ check_one_xgetdate("1991-12-14T10Z", 0, 692704800);
+ check_one_xgetdate("1991-12-14T10:11Z", 0, 692705460);
+ check_one_xgetdate("1991-12-14T10:11:12Z", 0, 692705472);
+ check_one_xgetdate("1991-12-14T10:11:12?", EINVAL, 0);
+ check_one_xgetdate("1991-12-14T03-07", 0, 692704800);
+ check_one_xgetdate("1991-12-14T06:41-03:30", 0, 692705460);
+ check_one_xgetdate("1991-12-14T03:11:12-07:00", 0, 692705472);
+ check_one_xgetdate("19911214 031112-0700", 0, 692705472);;
}
#define TM_FORMAT "%04d-%02d-%02d %02d:%02d:%02d (%d/7, %d/365%s)"
@@ -92,11 +89,9 @@ static bool check_one_xmktime(time_t expected) {
}
/** xmktime() tests. */
-static bool check_xmktime(void) {
- bool ret = true;
-
+static void check_xmktime(void) {
for (time_t time = -10; time <= 10; ++time) {
- ret &= check_one_xmktime(time);
+ check_one_xmktime(time);
}
// Attempt to trigger overflow (but don't test for it, since it's not mandatory)
@@ -111,12 +106,10 @@ static bool check_xmktime(void) {
};
time_t time;
xmktime(&tm, &time);
-
- return ret;
}
/** Check one xtimegm() result. */
-static bool check_one_xtimegm(const struct tm *tm) {
+static void check_one_xtimegm(const struct tm *tm) {
struct tm tma = *tm, tmb = *tm;
time_t ta, tb;
ta = mktime(&tma);
@@ -124,43 +117,39 @@ static bool check_one_xtimegm(const struct tm *tm) {
tb = -1;
}
- bool ret = true;
- ret &= bfs_check(ta == tb, "%jd != %jd", (intmax_t)ta, (intmax_t)tb);
- ret &= bfs_check(ta == -1 || tm_equal(&tma, &tmb));
+ bool pass = true;
+ pass &= bfs_check(ta == tb, "%jd != %jd", (intmax_t)ta, (intmax_t)tb);
+ if (ta != -1) {
+ pass &= bfs_check(tm_equal(&tma, &tmb));
+ }
- if (!ret) {
+ if (!pass) {
bfs_diag("mktime(): " TM_FORMAT, TM_PRINTF(tma));
bfs_diag("xtimegm(): " TM_FORMAT, TM_PRINTF(tmb));
bfs_diag("(input): " TM_FORMAT, TM_PRINTF(*tm));
}
-
- return ret;
}
#if !BFS_HAS_TIMEGM
/** Check an overflowing xtimegm() call. */
-static bool check_xtimegm_overflow(const struct tm *tm) {
+static void check_xtimegm_overflow(const struct tm *tm) {
struct tm copy = *tm;
time_t time = 123;
- bool ret = true;
- ret &= bfs_check(xtimegm(&copy, &time) == -1 && errno == EOVERFLOW);
- ret &= bfs_check(tm_equal(&copy, tm));
- ret &= bfs_check(time == 123);
+ bool pass = true;
+ pass &= bfs_check(xtimegm(&copy, &time) == -1 && errno == EOVERFLOW);
+ pass &= bfs_check(tm_equal(&copy, tm));
+ pass &= bfs_check(time == 123);
- if (!ret) {
+ if (!pass) {
bfs_diag("xtimegm(): " TM_FORMAT, TM_PRINTF(copy));
bfs_diag("(input): " TM_FORMAT, TM_PRINTF(*tm));
}
-
- return ret;
}
#endif
/** xtimegm() tests. */
-static bool check_xtimegm(void) {
- bool ret = true;
-
+static void check_xtimegm(void) {
struct tm tm = {
.tm_isdst = -1,
};
@@ -172,24 +161,20 @@ static bool check_xtimegm(void) {
for (tm.tm_hour = -1; tm.tm_hour <= 24; tm.tm_hour += 5)
for (tm.tm_min = -1; tm.tm_min <= 60; tm.tm_min += 31)
for (tm.tm_sec = -60; tm.tm_sec <= 120; tm.tm_sec += 5) {
- ret &= check_one_xtimegm(&tm);
+ check_one_xtimegm(&tm);
}
#if !BFS_HAS_TIMEGM
// Check integer overflow cases
- ret &= check_xtimegm_overflow(&(struct tm) { .tm_sec = INT_MAX, .tm_min = INT_MAX });
- ret &= check_xtimegm_overflow(&(struct tm) { .tm_min = INT_MAX, .tm_hour = INT_MAX });
- ret &= check_xtimegm_overflow(&(struct tm) { .tm_hour = INT_MAX, .tm_mday = INT_MAX });
- ret &= check_xtimegm_overflow(&(struct tm) { .tm_mon = INT_MAX, .tm_year = INT_MAX });
+ check_xtimegm_overflow(&(struct tm) { .tm_sec = INT_MAX, .tm_min = INT_MAX });
+ check_xtimegm_overflow(&(struct tm) { .tm_min = INT_MAX, .tm_hour = INT_MAX });
+ check_xtimegm_overflow(&(struct tm) { .tm_hour = INT_MAX, .tm_mday = INT_MAX });
+ check_xtimegm_overflow(&(struct tm) { .tm_mon = INT_MAX, .tm_year = INT_MAX });
#endif
-
- return ret;
}
-bool check_xtime(void) {
- bool ret = true;
- ret &= check_xgetdate();
- ret &= check_xmktime();
- ret &= check_xtimegm();
- return ret;
+void check_xtime(void) {
+ check_xgetdate();
+ check_xmktime();
+ check_xtimegm();
}
diff --git a/tests/xtouch.c b/tests/xtouch.c
index 427e3e0..e7c2e00 100644
--- a/tests/xtouch.c
+++ b/tests/xtouch.c
@@ -1,10 +1,10 @@
// Copyright © Tavian Barnes <tavianator@tavianator.com>
// SPDX-License-Identifier: 0BSD
-#include "prelude.h"
#include "bfstd.h"
#include "sanity.h"
#include "xtime.h"
+
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
@@ -247,8 +247,8 @@ int main(int argc, char *argv[]) {
times[1] = times[0];
} else {
// Don't use UTIME_NOW, so that multiple paths all get the same timestamp
- if (xgettime(&times[0]) != 0) {
- perror("xgettime()");
+ if (clock_gettime(CLOCK_REALTIME, &times[0]) != 0) {
+ perror("clock_gettime()");
return EXIT_FAILURE;
}
times[1] = times[0];