diff options
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 @@ -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 @@ -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)' @@ -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—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. @@ -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) @@ -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> /** @@ -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__ @@ -10,6 +10,7 @@ #include "dir.h" #include "stat.h" + #include <stddef.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); /** @@ -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; } @@ -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. */ @@ -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); @@ -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 @@ -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> @@ -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 @@ -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; } @@ -9,8 +9,6 @@ #ifndef BFS_EVAL_H #define BFS_EVAL_H -#include "prelude.h" - struct bfs_ctx; struct bfs_expr; @@ -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; @@ -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); @@ -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; @@ -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); } @@ -8,9 +8,9 @@ #ifndef BFS_IOQ_H #define BFS_IOQ_H -#include "prelude.h" #include "dir.h" #include "stat.h" + #include <stddef.h> /** @@ -83,6 +83,7 @@ #define BFS_LIST_H #include "diag.h" + #include <stddef.h> #include <string.h> @@ -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> @@ -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; @@ -8,8 +8,6 @@ #ifndef BFS_MTAB_H #define BFS_MTAB_H -#include "prelude.h" - struct bfs_stat; /** @@ -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); } @@ -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> @@ -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); @@ -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; @@ -6,6 +6,7 @@ #include "alloc.h" #include "list.h" + #include <stddef.h> #include <stdint.h> @@ -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 @@ +[01;34m$'rainbow/\e[1m'[0m +[01;34m$'rainbow/\e[1m/'[0m$'\e[0m' +[01;34mrainbow[0m +[01;34mrainbow/[0m[01;32mexec.sh[0m +[01;34mrainbow/[0m[01;33mlower.tar.gz[0m +[01;34mrainbow/[0m[01;33mlu.tar.GZ[0m +[01;34mrainbow/[0m[01;33mul.TAR.gz[0m +[01;34mrainbow/[0m[01;33mupper.TAR.GZ[0m +[01;34mrainbow/[0m[01;35msocket[0m +[01;34mrainbow/[0m[01;36mbroken[0m +[01;34mrainbow/[0m[01;36mchardev_link[0m +[01;34mrainbow/[0m[01;36mlink.txt[0m +[01;34mrainbow/[0m[30;42msticky_ow[0m +[01;34mrainbow/[0m[30;43msgid[0m +[01;34mrainbow/[0m[33mpipe[0m +[01;34mrainbow/[0m[34;42mow[0m +[01;34mrainbow/[0m[37;41msugid[0m +[01;34mrainbow/[0m[37;41msuid[0m +[01;34mrainbow/[0m[37;44msticky[0m +[01;34mrainbow/[0mfile.dat +[01;34mrainbow/[0mfile.txt +[01;34mrainbow/[0mlower.gz +[01;34mrainbow/[0mlower.tar +[01;34mrainbow/[0mmh1 +[01;34mrainbow/[0mmh2 +[01;34mrainbow/[0mupper.GZ +[01;34mrainbow/[0mupper.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 @@ +[01;34m$'rainbow/\e[1m'[0m +[01;34m$'rainbow/\e[1m/'[0m$'\e[0m' +[01;34mrainbow[0m +[01;34mrainbow/[0m[01;31mlower.tar.gz[0m +[01;34mrainbow/[0m[01;32mexec.sh[0m +[01;34mrainbow/[0m[01;32mupper.TAR.GZ[0m +[01;34mrainbow/[0m[01;33mlower.gz[0m +[01;34mrainbow/[0m[01;33mlu.tar.GZ[0m +[01;34mrainbow/[0m[01;33mul.TAR.gz[0m +[01;34mrainbow/[0m[01;33mupper.GZ[0m +[01;34mrainbow/[0m[01;35msocket[0m +[01;34mrainbow/[0m[01;36mbroken[0m +[01;34mrainbow/[0m[01;36mchardev_link[0m +[01;34mrainbow/[0m[01;36mlink.txt[0m +[01;34mrainbow/[0m[30;42msticky_ow[0m +[01;34mrainbow/[0m[30;43msgid[0m +[01;34mrainbow/[0m[33mpipe[0m +[01;34mrainbow/[0m[34;42mow[0m +[01;34mrainbow/[0m[37;41msugid[0m +[01;34mrainbow/[0m[37;41msuid[0m +[01;34mrainbow/[0m[37;44msticky[0m +[01;34mrainbow/[0mfile.dat +[01;34mrainbow/[0mfile.txt +[01;34mrainbow/[0mlower.tar +[01;34mrainbow/[0mmh1 +[01;34mrainbow/[0mmh2 +[01;34mrainbow/[0mupper.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 Binary files differindex 1347444..1347444 100644 --- a/tests/gnu/print0.out +++ b/tests/posix/print0.out 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(©, &time) == -1 && errno == EOVERFLOW); - ret &= bfs_check(tm_equal(©, tm)); - ret &= bfs_check(time == 123); + bool pass = true; + pass &= bfs_check(xtimegm(©, &time) == -1 && errno == EOVERFLOW); + pass &= bfs_check(tm_equal(©, 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(×[0]) != 0) { - perror("xgettime()"); + if (clock_gettime(CLOCK_REALTIME, ×[0]) != 0) { + perror("clock_gettime()"); return EXIT_FAILURE; } times[1] = times[0]; |