diff options
-rw-r--r-- | .github/codeql.yml | 9 | ||||
-rw-r--r-- | .github/dependabot.yml | 6 | ||||
-rwxr-xr-x | .github/diag.sh | 16 | ||||
-rw-r--r-- | .github/workflows/ci.yml | 107 | ||||
-rw-r--r-- | .github/workflows/codecov.yml | 4 | ||||
-rw-r--r-- | .github/workflows/codeql.yml | 6 | ||||
-rw-r--r-- | Makefile | 167 | ||||
-rw-r--r-- | README.md | 4 | ||||
-rw-r--r-- | bench/bench.sh | 10 | ||||
-rwxr-xr-x | build/cc.sh (renamed from config/cc.sh) | 0 | ||||
-rw-r--r-- | build/config.mk | 62 | ||||
-rwxr-xr-x | build/define-if.sh (renamed from config/cc-define.sh) | 11 | ||||
-rw-r--r-- | build/deps.mk | 18 | ||||
-rw-r--r-- | build/empty.c (renamed from config/empty.c) | 0 | ||||
-rw-r--r-- | build/exports.mk (renamed from config/exports.mk) | 0 | ||||
-rw-r--r-- | build/flags.mk (renamed from config/flags.mk) | 27 | ||||
-rw-r--r-- | build/has/--st-birthtim.c | 9 | ||||
-rw-r--r-- | build/has/acl-get-entry.c | 11 | ||||
-rw-r--r-- | build/has/acl-get-file.c | 11 | ||||
-rw-r--r-- | build/has/acl-get-tag-type.c | 13 | ||||
-rw-r--r-- | build/has/acl-is-trivial-np.c (renamed from config/acl-is-trivial-np.c) | 1 | ||||
-rw-r--r-- | build/has/acl-trivial.c | 8 | ||||
-rw-r--r-- | build/has/aligned-alloc.c (renamed from config/aligned-alloc.c) | 0 | ||||
-rw-r--r-- | build/has/attribute-format-syslog.c | 13 | ||||
-rw-r--r-- | build/has/confstr.c (renamed from config/confstr.c) | 0 | ||||
-rw-r--r-- | build/has/extattr-get-file.c | 10 | ||||
-rw-r--r-- | build/has/extattr-get-link.c | 10 | ||||
-rw-r--r-- | build/has/extattr-list-file.c | 10 | ||||
-rw-r--r-- | build/has/extattr-list-link.c | 10 | ||||
-rw-r--r-- | build/has/fdclosedir.c (renamed from config/fdclosedir.c) | 0 | ||||
-rw-r--r-- | build/has/getdents.c (renamed from config/getdents.c) | 5 | ||||
-rw-r--r-- | build/has/getdents64-syscall.c (renamed from config/getdents64-syscall.c) | 5 | ||||
-rw-r--r-- | build/has/getdents64.c (renamed from config/getdents64.c) | 5 | ||||
-rw-r--r-- | build/has/getmntent-1.c | 9 | ||||
-rw-r--r-- | build/has/getmntent-2.c | 10 | ||||
-rw-r--r-- | build/has/getmntinfo.c | 10 | ||||
-rw-r--r-- | build/has/getprogname-gnu.c (renamed from config/getprogname-gnu.c) | 0 | ||||
-rw-r--r-- | build/has/getprogname.c (renamed from config/getprogname.c) | 0 | ||||
-rw-r--r-- | build/has/max-align-t.c | 8 | ||||
-rw-r--r-- | build/has/pipe2.c (renamed from config/pipe2.c) | 0 | ||||
-rw-r--r-- | build/has/posix-getdents.c | 9 | ||||
-rw-r--r-- | build/has/posix-spawn-addfchdir-np.c (renamed from config/posix-spawn-addfchdir-np.c) | 0 | ||||
-rw-r--r-- | build/has/posix-spawn-addfchdir.c (renamed from config/posix-spawn-addfchdir.c) | 0 | ||||
-rw-r--r-- | build/has/st-acmtim.c (renamed from config/st-acmtim.c) | 0 | ||||
-rw-r--r-- | build/has/st-acmtimespec.c (renamed from config/st-acmtimespec.c) | 0 | ||||
-rw-r--r-- | build/has/st-birthtim.c (renamed from config/st-birthtim.c) | 0 | ||||
-rw-r--r-- | build/has/st-birthtimespec.c (renamed from config/st-birthtimespec.c) | 0 | ||||
-rw-r--r-- | build/has/st-flags.c (renamed from config/st-flags.c) | 0 | ||||
-rw-r--r-- | build/has/statx-syscall.c (renamed from config/statx-syscall.c) | 0 | ||||
-rw-r--r-- | build/has/statx.c (renamed from config/statx.c) | 0 | ||||
-rw-r--r-- | build/has/strerror-l.c (renamed from config/strerror-l.c) | 0 | ||||
-rw-r--r-- | build/has/strerror-r-gnu.c (renamed from config/strerror-r-gnu.c) | 0 | ||||
-rw-r--r-- | build/has/strerror-r-posix.c (renamed from config/strerror-r-posix.c) | 0 | ||||
-rw-r--r-- | build/has/string-to-flags.c | 9 | ||||
-rw-r--r-- | build/has/strtofflags.c | 9 | ||||
-rw-r--r-- | build/has/timegm.c | 9 | ||||
-rw-r--r-- | build/has/tm-gmtoff.c (renamed from config/tm-gmtoff.c) | 0 | ||||
-rw-r--r-- | build/has/uselocale.c (renamed from config/uselocale.c) | 0 | ||||
-rw-r--r-- | build/header.mk | 74 | ||||
-rwxr-xr-x | build/msg-if.sh | 21 | ||||
-rwxr-xr-x | build/msg.sh | 62 | ||||
-rwxr-xr-x | build/pkgconf.sh (renamed from config/pkgconf.sh) | 14 | ||||
-rw-r--r-- | build/pkgs.mk | 33 | ||||
-rw-r--r-- | build/prelude.mk | 124 | ||||
-rw-r--r-- | build/use/libacl.c (renamed from config/libacl.c) | 0 | ||||
-rw-r--r-- | build/use/libcap.c (renamed from config/libcap.c) | 0 | ||||
-rw-r--r-- | build/use/libselinux.c (renamed from config/libselinux.c) | 0 | ||||
-rw-r--r-- | build/use/liburing.c (renamed from config/liburing.c) | 0 | ||||
-rw-r--r-- | build/use/oniguruma.c (renamed from config/oniguruma.c) | 0 | ||||
-rw-r--r-- | config/config.mk | 78 | ||||
-rw-r--r-- | config/deps.mk | 18 | ||||
-rw-r--r-- | config/header.mk | 60 | ||||
-rw-r--r-- | config/pkg.mk | 23 | ||||
-rwxr-xr-x | config/pkg.sh | 26 | ||||
-rw-r--r-- | config/pkgs.mk | 17 | ||||
-rw-r--r-- | config/prelude.mk | 170 | ||||
-rwxr-xr-x | configure | 137 | ||||
-rw-r--r-- | docs/BUILDING.md | 211 | ||||
-rw-r--r-- | docs/CHANGELOG.md | 30 | ||||
-rw-r--r-- | src/atomic.h | 33 | ||||
-rw-r--r-- | src/bar.c | 137 | ||||
-rw-r--r-- | src/bfstd.c | 99 | ||||
-rw-r--r-- | src/bfstd.h | 5 | ||||
-rw-r--r-- | src/bftw.c | 6 | ||||
-rw-r--r-- | src/bftw.h | 4 | ||||
-rw-r--r-- | src/color.c | 50 | ||||
-rw-r--r-- | src/color.h | 7 | ||||
-rw-r--r-- | src/ctx.c | 104 | ||||
-rw-r--r-- | src/diag.c | 4 | ||||
-rw-r--r-- | src/diag.h | 43 | ||||
-rw-r--r-- | src/dir.c | 16 | ||||
-rw-r--r-- | src/dir.h | 9 | ||||
-rw-r--r-- | src/dstring.c | 5 | ||||
-rw-r--r-- | src/dstring.h | 4 | ||||
-rw-r--r-- | src/eval.c | 42 | ||||
-rw-r--r-- | src/exec.c | 6 | ||||
-rw-r--r-- | src/fsade.c | 127 | ||||
-rw-r--r-- | src/fsade.h | 9 | ||||
-rw-r--r-- | src/ioq.c | 5 | ||||
-rw-r--r-- | src/main.c | 1 | ||||
-rw-r--r-- | src/mtab.c | 9 | ||||
-rw-r--r-- | src/opt.c | 6 | ||||
-rw-r--r-- | src/prelude.h | 59 | ||||
-rw-r--r-- | src/printf.c | 118 | ||||
-rw-r--r-- | src/sighook.c | 600 | ||||
-rw-r--r-- | src/sighook.h | 73 | ||||
-rw-r--r-- | src/stat.c | 37 | ||||
-rw-r--r-- | src/xspawn.c | 37 | ||||
-rw-r--r-- | src/xtime.c | 40 | ||||
-rw-r--r-- | tests/bfstd.c | 4 | ||||
-rw-r--r-- | tests/common/empty.out (renamed from tests/gnu/empty.out) | 0 | ||||
-rw-r--r-- | tests/common/empty.sh (renamed from tests/gnu/empty.sh) | 0 | ||||
-rw-r--r-- | tests/common/empty_error.out | 3 | ||||
-rw-r--r-- | tests/common/empty_error.sh | 7 | ||||
-rw-r--r-- | tests/common/empty_special.out (renamed from tests/gnu/empty_special.out) | 0 | ||||
-rw-r--r-- | tests/common/empty_special.sh (renamed from tests/gnu/empty_special.sh) | 0 | ||||
-rw-r--r-- | tests/ioq.c | 6 | ||||
-rw-r--r-- | tests/main.c | 6 | ||||
-rw-r--r-- | tests/sighook.c | 97 | ||||
-rw-r--r-- | tests/tests.h | 18 | ||||
-rw-r--r-- | tests/util.sh | 9 | ||||
-rw-r--r-- | tests/xspawn.c | 53 | ||||
-rw-r--r-- | tests/xtime.c | 20 | ||||
-rw-r--r-- | tests/xtouch.c | 2 |
124 files changed, 2557 insertions, 1102 deletions
diff --git a/.github/codeql.yml b/.github/codeql.yml new file mode 100644 index 0000000..6ff8337 --- /dev/null +++ b/.github/codeql.yml @@ -0,0 +1,9 @@ +query-filters: + - exclude: + id: cpp/commented-out-code + - exclude: + id: cpp/long-switch + - exclude: + id: cpp/loop-variable-changed + - exclude: + id: cpp/poorly-documented-function diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..5ace460 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/diag.sh b/.github/diag.sh new file mode 100755 index 0000000..942487a --- /dev/null +++ b/.github/diag.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +# Copyright © Tavian Barnes <tavianator@tavianator.com> +# SPDX-License-Identifier: 0BSD + +# Convert compiler diagnostics to GitHub Actions messages +# https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-a-warning-message + +set -eu + +filter() { + sed -E 's/^(([^:]*):([^:]*):([^:]*): (warning|error): (.*))$/::\5 file=\2,line=\3,col=\4,title=Compiler \5::\6\ +\1/' +} + +"$@" 2> >(filter >&2) | filter diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3ad924f..8011224 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,7 +39,12 @@ jobs: - name: Run tests run: | - make -j$(nproc) distcheck + .github/diag.sh make -j$(nproc) distcheck + + - uses: actions/upload-artifact@v4 + with: + name: linux-config.log + path: distcheck-*/gen/config.log macos: name: macOS @@ -58,7 +63,7 @@ jobs: - name: Run tests run: | jobs=$(sysctl -n hw.ncpu) - make -j$jobs distcheck + .github/diag.sh make -j$jobs distcheck freebsd: name: FreeBSD @@ -69,27 +74,25 @@ jobs: - uses: actions/checkout@v4 - name: Run tests - uses: vmactions/freebsd-vm@v1 + uses: cross-platform-actions/action@v0.24.0 with: - release: "14.0" - usesh: true - copyback: false + operating_system: freebsd + version: "14.0" - prepare: | - pkg install -y \ + run: | + sudo pkg install -y \ bash \ expect \ oniguruma \ pkgconf \ - sudo \ tcl-wrapper - pw useradd -n action -m -G wheel -s /usr/local/bin/bash - echo "%wheel ALL=(ALL) NOPASSWD: ALL" >>/usr/local/etc/sudoers - mount -t fdescfs none /dev/fd + sudo mount -t fdescfs none /dev/fd + .github/diag.sh make -j$(nproc) distcheck - run: | - chown -R action:action . - sudo -u action make -j$(nproc) distcheck + - uses: actions/upload-artifact@v4 + with: + name: freebsd-config.log + path: distcheck-*/gen/config.log openbsd: name: OpenBSD @@ -100,27 +103,25 @@ jobs: - uses: actions/checkout@v4 - name: Run tests - uses: vmactions/openbsd-vm@v1 + uses: cross-platform-actions/action@v0.24.0 with: - release: "7.5" - usesh: true - copyback: false + operating_system: openbsd + version: "7.5" - prepare: | - pkg_add \ + run: | + sudo pkg_add \ bash \ expect \ gmake \ oniguruma - adduser -group USER -batch action wheel </dev/null - cp /etc/examples/doas.conf /etc/doas.conf - echo "permit nopass keepenv :wheel" >>/etc/doas.conf - - run: | - chown -R action:action . jobs=$(sysctl -n hw.ncpu) - doas -u action gmake config - doas -u action gmake -j$jobs check TEST_FLAGS="--sudo=doas --verbose=skipped" + ./configure MAKE=gmake + .github/diag.sh gmake -j$jobs check TEST_FLAGS="--sudo --verbose=skipped" + + - uses: actions/upload-artifact@v4 + with: + name: openbsd-config.log + path: gen/config.log netbsd: name: NetBSD @@ -131,30 +132,26 @@ jobs: - uses: actions/checkout@v4 - name: Run tests - uses: vmactions/netbsd-vm@v1 + uses: cross-platform-actions/action@v0.24.0 with: - release: "10.0" - usesh: true - copyback: false + operating_system: netbsd + version: "10.0" - prepare: | + run: | PATH="/sbin:/usr/sbin:$PATH" - pkg_add \ + sudo pkgin -y install \ bash \ - clang \ oniguruma \ pkgconf \ - sudo \ tcl-expect - useradd -m -G wheel -g =uid action - echo "%wheel ALL=(ALL) NOPASSWD: ALL" >>/usr/pkg/etc/sudoers - - run: | - PATH="/sbin:/usr/sbin:$PATH" - chown -R action:action . jobs=$(sysctl -n hw.ncpu) - sudo -u action make config CC=clang - sudo -u action make -j$jobs check TEST_FLAGS="--sudo --verbose=skipped" + ./configure + .github/diag.sh make -j$jobs check TEST_FLAGS="--sudo --verbose=skipped" + + - uses: actions/upload-artifact@v4 + with: + name: netbsd-config.log + path: gen/config.log dragonflybsd: name: DragonFly BSD @@ -169,7 +166,6 @@ jobs: with: release: "6.4.0" usesh: true - copyback: false prepare: | pkg install -y \ @@ -185,8 +181,13 @@ jobs: run: | chown -R action:action . jobs=$(sysctl -n hw.ncpu) - sudo -u action make config - sudo -u action make -j$jobs check TEST_FLAGS="--sudo --verbose=skipped" + sudo -u action ./configure + sudo -u action .github/diag.sh make -j$jobs check TEST_FLAGS="--sudo --verbose=skipped" + + - uses: actions/upload-artifact@v4 + with: + name: dragonfly-config.log + path: gen/config.log omnios: name: OmniOS @@ -201,7 +202,6 @@ jobs: with: release: "r151048" usesh: true - copyback: false prepare: | pkg install \ @@ -218,5 +218,10 @@ jobs: PATH="/usr/xpg4/bin:$PATH" chown -R action:staff . jobs=$(getconf NPROCESSORS_ONLN) - sudo -u action gmake config - sudo -u action gmake -j$jobs check TEST_FLAGS="--sudo --verbose=skipped" + sudo -u action ./configure MAKE=gmake + sudo -u action .github/diag.sh gmake -j$jobs check TEST_FLAGS="--sudo --verbose=skipped" + + - uses: actions/upload-artifact@v4 + with: + name: omnios-config.log + path: gen/config.log diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index 2abe531..6aaace6 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -25,11 +25,11 @@ jobs: - name: Generate coverage run: | - make config GCOV=y + ./configure --enable-gcov make -j$(nproc) check TEST_FLAGS="--sudo" gcov -abcfpu obj/*/*.o - - uses: codecov/codecov-action@v3 + - uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: true diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index a2c224a..a0b8fe3 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -39,15 +39,19 @@ jobs: libonig-dev \ liburing-dev + - name: Configure + run: | + ./configure + - name: Initialize CodeQL uses: github/codeql-action/init@v3 with: languages: cpp queries: +security-and-quality + config-file: .github/codeql.yml - name: Build run: | - make config make -j$(nproc) all - name: Perform CodeQL Analysis @@ -1,111 +1,99 @@ # Copyright © Tavian Barnes <tavianator@tavianator.com> # SPDX-License-Identifier: 0BSD -# This Makefile implements the configuration and build steps for bfs. It is -# portable to both GNU make and most BSD make implementations. To build bfs, -# run +# To build bfs, run # -# $ make config +# $ ./configure # $ make # Utilities and GNU/BSD portability -include config/prelude.mk +include build/prelude.mk # The default build target default: bfs .PHONY: default # Include the generated build config, if it exists --include ${CONFIG} +-include gen/config.mk -## Configuration phase (`make config`) - -# The configuration goal itself -config:: - @+${MAKE} -sf config/config.mk +## Configuration phase (`./configure`) # bfs used to have flag-like targets (`make release`, `make asan ubsan`, etc.). # Direct users to the new configuration system. asan lsan msan tsan ubsan gcov lint release:: - @printf 'error: `%s %s` is no longer supported. ' "${MAKE}" $@ >&2 - @printf 'Use `%s config %s=y` instead.\n' "${MAKE}" $$(echo $@ | tr 'a-z' 'A-Z') >&2 + @printf 'error: `%s %s` is no longer supported. Use `./configure --enable-%s` instead.\n' \ + "${MAKE}" $@ $@ >&2 @false -# Print an error if `make` is run before `make config` -${CONFIG}:: +# Print an error if `make` is run before `./configure` +gen/config.mk:: @if ! [ -e $@ ]; then \ - printf 'error: You must run `%s config` before `%s`.\n' "${MAKE}" "${MAKE}" >&2; \ + printf 'error: You must run `./configure` before `%s`.\n' "${MAKE}" >&2; \ false; \ fi ## Build phase (`make`) # The main binary -bfs: ${BIN}/bfs +bfs: bin/bfs .PHONY: bfs # All binaries BINS := \ - ${BIN}/bfs \ - ${BIN}/tests/mksock \ - ${BIN}/tests/units \ - ${BIN}/tests/xspawnee \ - ${BIN}/tests/xtouch + bin/bfs \ + bin/tests/mksock \ + bin/tests/units \ + bin/tests/xspawnee \ + bin/tests/xtouch all: ${BINS} .PHONY: all -# Group relevant flags together -ALL_CFLAGS = ${CPPFLAGS} ${CFLAGS} ${DEPFLAGS} -ALL_LDFLAGS = ${CFLAGS} ${LDFLAGS} - # The main binary -${BIN}/bfs: ${LIBBFS} ${OBJ}/src/main.o +bin/bfs: ${LIBBFS} obj/src/main.o ${BINS}: @${MKDIR} ${@D} - +${MSG} "[ LD ] ${TGT}" ${CC} ${ALL_LDFLAGS} ${.ALLSRC} ${LDLIBS} -o $@ + +${MSG} "[ LD ] $@" ${CC} ${CFLAGS} ${LDFLAGS} ${.ALLSRC} ${LDLIBS} -o $@ ${POSTLINK} # Get the .c file for a .o file -_CSRC = ${@:${OBJ}/%.o=%.c} -CSRC = ${_CSRC:gen/%=${GEN}/%} +CSRC = ${@:obj/%.o=%.c} -# Depend on ${CONFIG} to make sure `make config` runs first, and to rebuild when -# the configuration changes -${OBJS}: ${CONFIG} +# Rebuild when the configuration changes +${OBJS}: gen/config.mk @${MKDIR} ${@D} - ${MSG} "[ CC ] ${_CSRC}" ${CC} ${ALL_CFLAGS} -c ${CSRC} -o $@ + ${MSG} "[ CC ] ${CSRC}" ${CC} ${CPPFLAGS} ${CFLAGS} -c ${CSRC} -o $@ # Save the version number to this file, but only update version.c if it changes -${GEN}/version.c.new:: +gen/version.c.new:: @${MKDIR} ${@D} @printf 'const char bfs_version[] = "' >$@ @if [ "$$VERSION" ]; then \ printf '%s' "$$VERSION"; \ - elif test -d .git && command -v git >/dev/null 2>&1; then \ - git describe --always --dirty; \ + elif test -e src/../.git && command -v git >/dev/null 2>&1; then \ + git -C src/.. describe --always --dirty; \ else \ - echo "3.1.3"; \ + echo "3.2"; \ fi | tr -d '\n' >>$@ @printf '";\n' >>$@ -${GEN}/version.c: ${GEN}/version.c.new +gen/version.c: gen/version.c.new @test -e $@ && cmp -s $@ ${.ALLSRC} && rm ${.ALLSRC} || mv ${.ALLSRC} $@ -${OBJ}/gen/version.o: ${GEN}/version.c +obj/gen/version.o: gen/version.c ## Test phase (`make check`) # Unit test binaries UTEST_BINS := \ - ${BIN}/tests/units \ - ${BIN}/tests/xspawnee + bin/tests/units \ + bin/tests/xspawnee # Integration test binaries ITEST_BINS := \ - ${BIN}/tests/mksock \ - ${BIN}/tests/xtouch + bin/tests/mksock \ + bin/tests/xtouch # Build (but don't run) test binaries tests: ${UTEST_BINS} ${ITEST_BINS} @@ -117,45 +105,45 @@ check: unit-tests integration-tests # Run the unit tests unit-tests: ${UTEST_BINS} - ${MSG} "[TEST] tests/units" ${BIN}/tests/units + ${MSG} "[TEST] tests/units" bin/tests/units .PHONY: unit-tests -${BIN}/tests/units: \ +bin/tests/units: \ ${UNIT_OBJS} \ ${LIBBFS} -${BIN}/tests/xspawnee: \ - ${OBJ}/tests/xspawnee.o +bin/tests/xspawnee: \ + obj/tests/xspawnee.o # The different flag combinations we check INTEGRATIONS := default dfs ids eds j1 j2 j3 s INTEGRATION_TESTS := ${INTEGRATIONS:%=check-%} # Check just `bfs` -check-default: ${BIN}/bfs ${ITEST_BINS} +check-default: bin/bfs ${ITEST_BINS} +${MSG} "[TEST] bfs" \ - ./tests/tests.sh --make="${MAKE}" --bfs="${BIN}/bfs" ${TEST_FLAGS} + ./tests/tests.sh --make="${MAKE}" --bfs="bin/bfs" ${TEST_FLAGS} # Check the different search strategies -check-dfs check-ids check-eds: ${BIN}/bfs ${ITEST_BINS} +check-dfs check-ids check-eds: bin/bfs ${ITEST_BINS} +${MSG} "[TEST] bfs -S ${@:check-%=%}" \ - ./tests/tests.sh --make="${MAKE}" --bfs="${BIN}/bfs -S ${@:check-%=%}" ${TEST_FLAGS} + ./tests/tests.sh --make="${MAKE}" --bfs="bin/bfs -S ${@:check-%=%}" ${TEST_FLAGS} # Check various flags -check-j1 check-j2 check-j3 check-s: ${BIN}/bfs ${ITEST_BINS} +check-j1 check-j2 check-j3 check-s: bin/bfs ${ITEST_BINS} +${MSG} "[TEST] bfs -${@:check-%=%}" \ - ./tests/tests.sh --make="${MAKE}" --bfs="${BIN}/bfs -${@:check-%=%}" ${TEST_FLAGS} + ./tests/tests.sh --make="${MAKE}" --bfs="bin/bfs -${@:check-%=%}" ${TEST_FLAGS} # Run the integration tests integration-tests: ${INTEGRATION_TESTS} .PHONY: integration-tests -${BIN}/tests/mksock: \ - ${OBJ}/tests/mksock.o \ +bin/tests/mksock: \ + obj/tests/mksock.o \ ${LIBBFS} -${BIN}/tests/xtouch: \ - ${OBJ}/tests/xtouch.o \ +bin/tests/xtouch: \ + obj/tests/xtouch.o \ ${LIBBFS} # `make distcheck` configurations @@ -168,23 +156,25 @@ DISTCHECKS := \ # Test multiple configurations distcheck: - @+${MAKE} -s distcheck-asan - @+test "$$(uname)" = Darwin || ${MAKE} -s distcheck-msan - @+${MAKE} -s distcheck-tsan - @+test "$$(uname)-$$(uname -m)" != Linux-x86_64 || ${MAKE} -s distcheck-m32 - @+${MAKE} -s distcheck-release + @+${MAKE} distcheck-asan + @+test "$$(uname)" = Darwin || ${MAKE} distcheck-msan + @+test "$$(uname)" = FreeBSD || ${MAKE} distcheck-tsan + @+test "$$(uname)-$$(uname -m)" != Linux-x86_64 || ${MAKE} distcheck-m32 + @+${MAKE} distcheck-release .PHONY: distcheck # Per-distcheck configuration -DISTCHECK_CONFIG_asan := ASAN=y UBSAN=y -DISTCHECK_CONFIG_msan := MSAN=y UBSAN=y CC=clang -DISTCHECK_CONFIG_tsan := TSAN=y UBSAN=y CC=clang +DISTCHECK_CONFIG_asan := --enable-asan --enable-ubsan +DISTCHECK_CONFIG_msan := --enable-msan --enable-ubsan CC=clang +DISTCHECK_CONFIG_tsan := --enable-tsan --enable-ubsan CC=clang DISTCHECK_CONFIG_m32 := EXTRA_CFLAGS="-m32" PKG_CONFIG_LIBDIR=/usr/lib32/pkgconfig -DISTCHECK_CONFIG_release := RELEASE=y +DISTCHECK_CONFIG_release := --enable-release ${DISTCHECKS}:: - +${MAKE} -rs BUILDDIR=${BUILDDIR}/$@ config ${DISTCHECK_CONFIG_${@:distcheck-%=%}} - +${MAKE} -s BUILDDIR=${BUILDDIR}/$@ check TEST_FLAGS="--sudo --verbose=skipped" + @${MKDIR} $@ + @+cd $@ \ + && ../configure ${DISTCHECK_CONFIG_${@:distcheck-%=%}} \ + && ${MAKE} check TEST_FLAGS="--sudo --verbose=skipped" ## Packaging (`make install`) @@ -193,44 +183,49 @@ DEST_MANDIR := ${DESTDIR}${MANDIR} install:: ${Q}${MKDIR} ${DEST_PREFIX}/bin - ${MSG} "[INSTALL] bin/bfs" \ - ${INSTALL} -m755 ${BIN}/bfs ${DEST_PREFIX}/bin/bfs + ${MSG} "[INST] bin/bfs" \ + ${INSTALL} -m755 bin/bfs ${DEST_PREFIX}/bin/bfs ${Q}${MKDIR} ${DEST_MANDIR}/man1 - ${MSG} "[INSTALL] man/man1/bfs.1" \ + ${MSG} "[INST] man/man1/bfs.1" \ ${INSTALL} -m644 docs/bfs.1 ${DEST_MANDIR}/man1/bfs.1 ${Q}${MKDIR} ${DEST_PREFIX}/share/bash-completion/completions - ${MSG} "[INSTALL] completions/bfs.bash" \ + ${MSG} "[INST] completions/bfs.bash" \ ${INSTALL} -m644 completions/bfs.bash ${DEST_PREFIX}/share/bash-completion/completions/bfs ${Q}${MKDIR} ${DEST_PREFIX}/share/zsh/site-functions - ${MSG} "[INSTALL] completions/bfs.zsh" \ + ${MSG} "[INST] completions/bfs.zsh" \ ${INSTALL} -m644 completions/bfs.zsh ${DEST_PREFIX}/share/zsh/site-functions/_bfs ${Q}${MKDIR} ${DEST_PREFIX}/share/fish/vendor_completions.d - ${MSG} "[INSTALL] completions/bfs.fish" \ + ${MSG} "[INST] completions/bfs.fish" \ ${INSTALL} -m644 completions/bfs.fish ${DEST_PREFIX}/share/fish/vendor_completions.d/bfs.fish uninstall:: - ${RM} ${DEST_PREFIX}/share/bash-completion/completions/bfs - ${RM} ${DEST_PREFIX}/share/zsh/site-functions/_bfs - ${RM} ${DEST_PREFIX}/share/fish/vendor_completions.d/bfs.fish - ${RM} ${DEST_MANDIR}/man1/bfs.1 - ${RM} ${DEST_PREFIX}/bin/bfs + ${MSG} "[ RM ] completions/bfs.bash" \ + ${RM} ${DEST_PREFIX}/share/bash-completion/completions/bfs + ${MSG} "[ RM ] completions/bfs.zsh" \ + ${RM} ${DEST_PREFIX}/share/zsh/site-functions/_bfs + ${MSG} "[ RM ] completions/bfs.fish" \ + ${RM} ${DEST_PREFIX}/share/fish/vendor_completions.d/bfs.fish + ${MSG} "[ RM ] man/man1/bfs.1" \ + ${RM} ${DEST_MANDIR}/man1/bfs.1 + ${MSG} "[ RM ] bin/bfs" \ + ${RM} ${DEST_PREFIX}/bin/bfs # Check that `make install` works and `make uninstall` removes everything check-install:: - +${MAKE} install DESTDIR=${BUILDDIR}/pkg - +${MAKE} uninstall DESTDIR=${BUILDDIR}/pkg - ${BIN}/bfs ${BUILDDIR}/pkg -not -type d -print -exit 1 - ${RM} -r ${BUILDDIR}/pkg + +${MAKE} install DESTDIR=pkg + +${MAKE} uninstall DESTDIR=pkg + bin/bfs pkg -not -type d -print -exit 1 + ${RM} -r pkg ## Cleanup (`make clean`) # Clean all build products clean:: ${MSG} "[ RM ] bin obj" \ - ${RM} -r ${BIN} ${OBJ} + ${RM} -r bin obj # Clean everything, including generated files distclean: clean ${MSG} "[ RM ] gen" \ - ${RM} -r ${GEN} ${DISTCHECKS} + ${RM} -r gen ${DISTCHECKS} .PHONY: distclean @@ -333,7 +333,7 @@ Once you have the dependencies, you can build <code>bfs</code>. Download one of the [releases](https://github.com/tavianator/bfs/releases) or clone the [git repo](https://github.com/tavianator/bfs). Then run - $ make config + $ ./configure $ make This will build the `./bin/bfs` binary. @@ -343,7 +343,7 @@ Run the test suite to make sure it works correctly: If you're interested in speed, you may want to build the release version instead: - $ make config RELEASE=y + $ ./configure --enable-release $ make Finally, if you want to install it globally, run diff --git a/bench/bench.sh b/bench/bench.sh index e4b5511..cf1ae49 100644 --- a/bench/bench.sh +++ b/bench/bench.sh @@ -221,7 +221,8 @@ setup() { fi echo "Building bfs ..." - as-user make -s -j"$nproc" release all + as-user ./configure --enable-release + as-user make -s -j"$nproc" all as-user mkdir -p bench/corpus @@ -253,7 +254,12 @@ setup() { echo "Building bfs $commit ..." cd "$worktree" as-user git checkout -qd "$commit" -- - as-user make -s -j"$nproc" release + if [ -e configure ]; then + as-user ./configure --enable-release + as-user make -s -j"$nproc" + else + as-user make -s -j"$nproc" release + fi if [ -e ./bin/bfs ]; then as-user cp ./bin/bfs "$bin/bfs-$commit" else diff --git a/config/cc.sh b/build/cc.sh index 45d51ca..45d51ca 100755 --- a/config/cc.sh +++ b/build/cc.sh diff --git a/build/config.mk b/build/config.mk new file mode 100644 index 0000000..24873ec --- /dev/null +++ b/build/config.mk @@ -0,0 +1,62 @@ +# Copyright © Tavian Barnes <tavianator@tavianator.com> +# SPDX-License-Identifier: 0BSD + +# Makefile that implements `./configure` + +include build/prelude.mk +include build/exports.mk + +# All configuration steps +config: gen/config.mk gen/config.h +.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} + ${MSG} "[ GEN] $@" + @printf '# %s\n' "$@" >$@ + @printf 'include %s\n' ${.ALLSRC} >>$@ + ${VCAT} gen/config.mk +.PHONY: gen/config.mk + +# Saves the configurable variables +gen/vars.mk:: + @${MKDIR} ${@D} + ${MSG} "[ GEN] $@" + @printf '# %s\n' "$@" >$@ + @printf 'PREFIX := %s\n' "$$XPREFIX" >>$@ + @printf 'MANDIR := %s\n' "$$XMANDIR" >>$@ + @printf 'OS := %s\n' "$${OS:-$$(uname)}" >>$@ + @printf 'CC := %s\n' "$$XCC" >>$@ + @printf 'INSTALL := %s\n' "$$XINSTALL" >>$@ + @printf 'MKDIR := %s\n' "$$XMKDIR" >>$@ + @printf 'PKG_CONFIG := %s\n' "$$XPKG_CONFIG" >>$@ + @printf 'RM := %s\n' "$$XRM" >>$@ + ${VCAT} $@ + +# Sets the build flags. This depends on vars.mk and uses a recursive make so +# that the default flags can depend on variables like ${OS}. +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 + @+XMAKEFLAGS="$$MAKEFLAGS" ${MAKE} -sf build/header.mk $@ +.PHONY: gen/config.h diff --git a/config/cc-define.sh b/build/define-if.sh index edb5c87..295ead8 100755 --- a/config/cc-define.sh +++ b/build/define-if.sh @@ -3,17 +3,18 @@ # Copyright © Tavian Barnes <tavianator@tavianator.com> # SPDX-License-Identifier: 0BSD -# Output a C preprocessor definition based on whether a C source file could be -# compiled successfully +# Output a C preprocessor definition based on whether a command succeeds set -eu -SLUG="${1#config/}" +SLUG="${1#build/}" SLUG="${SLUG%.c}" -MACRO="BFS_HAS_$(printf '%s' "$SLUG" | tr 'a-z-' 'A-Z_')" +MACRO="BFS_$(printf '%s' "$SLUG" | tr '/a-z-' '_A-Z_')" +shift -if config/cc.sh "$1"; then +if "$@"; then printf '#define %s true\n' "$MACRO" else printf '#define %s false\n' "$MACRO" + exit 1 fi diff --git a/build/deps.mk b/build/deps.mk new file mode 100644 index 0000000..3db62b6 --- /dev/null +++ b/build/deps.mk @@ -0,0 +1,18 @@ +# 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/config/empty.c b/build/empty.c index 4fa9a5b..4fa9a5b 100644 --- a/config/empty.c +++ b/build/empty.c diff --git a/config/exports.mk b/build/exports.mk index ed19134..ed19134 100644 --- a/config/exports.mk +++ b/build/exports.mk diff --git a/config/flags.mk b/build/flags.mk index 8a2e50e..c911b22 100644 --- a/config/flags.mk +++ b/build/flags.mk @@ -3,8 +3,8 @@ # Makefile that generates gen/flags.mk -include config/prelude.mk -include ${GEN}/vars.mk +include build/prelude.mk +include gen/vars.mk # Configurable flags CPPFLAGS ?= @@ -29,7 +29,7 @@ export XLDLIBS=${LDLIBS} # Immutable flags export BFS_CPPFLAGS= \ -Isrc \ - -I${GEN} \ + -Igen \ -D__EXTENSIONS__ \ -D_ATFILE_SOURCE \ -D_BSD_SOURCE \ @@ -45,7 +45,7 @@ export BFS_CFLAGS= -std=c17 -pthread LDLIBS,DragonFly := -lposix1e LDLIBS,Linux := -lrt LDLIBS,NetBSD := -lutil -LDLIBS,SunOS := -lsocket -lnsl +LDLIBS,SunOS := -lsec -lsocket -lnsl export BFS_LDLIBS=${LDLIBS,${OS}} # Build profiles @@ -75,12 +75,13 @@ export TSAN_CFLAGS=${TSAN_CFLAGS,${_TSAN}} export UBSAN_CFLAGS=${UBSAN_CFLAGS,${_UBSAN}} SAN_CFLAGS,y := -fno-sanitize-recover=all -NO_SAN := ${NOR,${_ASAN},${_LSAN},${_MSAN},${_TSAN},${_UBSAN}} -SAN := ${NOT,${NO_SAN}} +INSANE := ${NOT,${_ASAN}${_LSAN}${_MSAN}${_TSAN}${_UBSAN}} +SAN := ${NOT,${INSANE}} export SAN_CFLAGS=${SAN_CFLAGS,${SAN}} # MSAN and TSAN both need all code to be instrumented -NOLIBS ?= ${NOT,${NOR,${_MSAN},${_TSAN}}} +YESLIBS := ${NOT,${_MSAN}${_TSAN}} +NOLIBS ?= ${NOT,${YESLIBS}} export XNOLIBS=${NOLIBS} # gcov only intercepts fork()/exec() with -std=gnu* @@ -100,14 +101,14 @@ export RELEASE_CPPFLAGS=${RELEASE_CPPFLAGS,${_RELEASE}} export RELEASE_CFLAGS=${RELEASE_CFLAGS,${_RELEASE}} # Set a variable -SETVAR = printf '%s := %s\n' >>$@ +SETVAR = @printf '%s := %s\n' >>$@ # Append to a variable, if non-empty -APPEND = append() { test -z "$$2" || printf '%s += %s\n' "$$1" "$$2" >>$@; }; append +APPEND = @append() { test -z "$$2" || printf '%s += %s\n' "$$1" "$$2" >>$@; }; append -${GEN}/flags.mk:: - ${MSG} "[ GEN] ${TGT}" - printf '# %s\n' "${TGT}" >$@ +gen/flags.mk:: + ${MSG} "[ GEN] $@" + @printf '# %s\n' "$@" >$@ ${SETVAR} CPPFLAGS "$$BFS_CPPFLAGS" ${APPEND} CPPFLAGS "$$TSAN_CPPFLAGS" ${APPEND} CPPFLAGS "$$LINT_CPPFLAGS" @@ -131,5 +132,5 @@ ${GEN}/flags.mk:: ${APPEND} LDLIBS "$$EXTRA_LDLIBS" ${APPEND} LDLIBS "$$BFS_LDLIBS" ${SETVAR} NOLIBS "$$XNOLIBS" - test "${OS}-${SAN}" != FreeBSD-y || printf 'POSTLINK = elfctl -e +noaslr $$@\n' >>$@ + @test "${OS}-${SAN}" != FreeBSD-y || printf 'POSTLINK = elfctl -e +noaslr $$@\n' >>$@ ${VCAT} $@ diff --git a/build/has/--st-birthtim.c b/build/has/--st-birthtim.c new file mode 100644 index 0000000..4da621f --- /dev/null +++ b/build/has/--st-birthtim.c @@ -0,0 +1,9 @@ +// Copyright © Tavian Barnes <tavianator@tavianator.com> +// SPDX-License-Identifier: 0BSD + +#include <sys/stat.h> + +int main(void) { + struct stat sb = {0}; + return sb.__st_birthtim.tv_sec; +} diff --git a/build/has/acl-get-entry.c b/build/has/acl-get-entry.c new file mode 100644 index 0000000..1e7f473 --- /dev/null +++ b/build/has/acl-get-entry.c @@ -0,0 +1,11 @@ +// Copyright © Tavian Barnes <tavianator@tavianator.com> +// SPDX-License-Identifier: 0BSD + +#include <sys/types.h> +#include <sys/acl.h> + +int main(void) { + acl_t acl = acl_get_file(".", ACL_TYPE_DEFAULT); + acl_entry_t entry; + return acl_get_entry(acl, ACL_FIRST_ENTRY, &entry); +} diff --git a/build/has/acl-get-file.c b/build/has/acl-get-file.c new file mode 100644 index 0000000..0b76ee2 --- /dev/null +++ b/build/has/acl-get-file.c @@ -0,0 +1,11 @@ +// Copyright © Tavian Barnes <tavianator@tavianator.com> +// SPDX-License-Identifier: 0BSD + +#include <stddef.h> +#include <sys/types.h> +#include <sys/acl.h> + +int main(void) { + acl_t acl = acl_get_file(".", ACL_TYPE_DEFAULT); + return acl == (acl_t)NULL; +} diff --git a/build/has/acl-get-tag-type.c b/build/has/acl-get-tag-type.c new file mode 100644 index 0000000..67b7d37 --- /dev/null +++ b/build/has/acl-get-tag-type.c @@ -0,0 +1,13 @@ +// Copyright © Tavian Barnes <tavianator@tavianator.com> +// SPDX-License-Identifier: 0BSD + +#include <string.h> +#include <sys/types.h> +#include <sys/acl.h> + +int main(void) { + acl_entry_t entry; + memset(&entry, 0, sizeof(entry)); + acl_tag_t tag; + return acl_get_tag_type(entry, &tag); +} diff --git a/config/acl-is-trivial-np.c b/build/has/acl-is-trivial-np.c index 4178238..9ca9fc7 100644 --- a/config/acl-is-trivial-np.c +++ b/build/has/acl-is-trivial-np.c @@ -1,6 +1,7 @@ // Copyright © Tavian Barnes <tavianator@tavianator.com> // SPDX-License-Identifier: 0BSD +#include <sys/types.h> #include <sys/acl.h> int main(void) { diff --git a/build/has/acl-trivial.c b/build/has/acl-trivial.c new file mode 100644 index 0000000..7efc838 --- /dev/null +++ b/build/has/acl-trivial.c @@ -0,0 +1,8 @@ +// Copyright © Tavian Barnes <tavianator@tavianator.com> +// SPDX-License-Identifier: 0BSD + +#include <sys/acl.h> + +int main(void) { + return acl_trivial("."); +} diff --git a/config/aligned-alloc.c b/build/has/aligned-alloc.c index 4460038..4460038 100644 --- a/config/aligned-alloc.c +++ b/build/has/aligned-alloc.c diff --git a/build/has/attribute-format-syslog.c b/build/has/attribute-format-syslog.c new file mode 100644 index 0000000..ce988e5 --- /dev/null +++ b/build/has/attribute-format-syslog.c @@ -0,0 +1,13 @@ +// Copyright © Tavian Barnes <tavianator@tavianator.com> +// SPDX-License-Identifier: 0BSD + +#include <stdio.h> + +__attribute__((format(syslog, 1, 2))) +static int foo(const char *format, ...) { + return 0; +} + +int main(void) { + return foo("%s: %m\n", "main()"); +} diff --git a/config/confstr.c b/build/has/confstr.c index 58280b4..58280b4 100644 --- a/config/confstr.c +++ b/build/has/confstr.c diff --git a/build/has/extattr-get-file.c b/build/has/extattr-get-file.c new file mode 100644 index 0000000..ac9cf96 --- /dev/null +++ b/build/has/extattr-get-file.c @@ -0,0 +1,10 @@ +// Copyright © Tavian Barnes <tavianator@tavianator.com> +// SPDX-License-Identifier: 0BSD + +#include <stddef.h> +#include <sys/types.h> +#include <sys/extattr.h> + +int main(void) { + return extattr_get_file("file", EXTATTR_NAMESPACE_USER, "xattr", NULL, 0); +} diff --git a/build/has/extattr-get-link.c b/build/has/extattr-get-link.c new file mode 100644 index 0000000..c35be5b --- /dev/null +++ b/build/has/extattr-get-link.c @@ -0,0 +1,10 @@ +// Copyright © Tavian Barnes <tavianator@tavianator.com> +// SPDX-License-Identifier: 0BSD + +#include <stddef.h> +#include <sys/types.h> +#include <sys/extattr.h> + +int main(void) { + return extattr_get_link("link", EXTATTR_NAMESPACE_USER, "xattr", NULL, 0); +} diff --git a/build/has/extattr-list-file.c b/build/has/extattr-list-file.c new file mode 100644 index 0000000..e68a8bb --- /dev/null +++ b/build/has/extattr-list-file.c @@ -0,0 +1,10 @@ +// Copyright © Tavian Barnes <tavianator@tavianator.com> +// SPDX-License-Identifier: 0BSD + +#include <stddef.h> +#include <sys/types.h> +#include <sys/extattr.h> + +int main(void) { + return extattr_list_file("file", EXTATTR_NAMESPACE_USER, NULL, 0); +} diff --git a/build/has/extattr-list-link.c b/build/has/extattr-list-link.c new file mode 100644 index 0000000..49f0ec2 --- /dev/null +++ b/build/has/extattr-list-link.c @@ -0,0 +1,10 @@ +// Copyright © Tavian Barnes <tavianator@tavianator.com> +// SPDX-License-Identifier: 0BSD + +#include <stddef.h> +#include <sys/types.h> +#include <sys/extattr.h> + +int main(void) { + return extattr_list_link("link", EXTATTR_NAMESPACE_USER, NULL, 0); +} diff --git a/config/fdclosedir.c b/build/has/fdclosedir.c index f4ad1f5..f4ad1f5 100644 --- a/config/fdclosedir.c +++ b/build/has/fdclosedir.c diff --git a/config/getdents.c b/build/has/getdents.c index d0d4228..579898f 100644 --- a/config/getdents.c +++ b/build/has/getdents.c @@ -4,7 +4,6 @@ #include <dirent.h> int main(void) { - struct dirent de; - getdents(3, &de, 1024); - return 0; + char buf[1024]; + return getdents(3, (void *)buf, sizeof(buf)); } diff --git a/config/getdents64-syscall.c b/build/has/getdents64-syscall.c index 4838c14..7642d93 100644 --- a/config/getdents64-syscall.c +++ b/build/has/getdents64-syscall.c @@ -6,7 +6,6 @@ #include <unistd.h> int main(void) { - struct dirent64 de; - syscall(SYS_getdents64, 3, &de, 1024); - return 0; + char buf[1024]; + return syscall(SYS_getdents64, 3, (void *)buf, sizeof(buf)); } diff --git a/config/getdents64.c b/build/has/getdents64.c index 1abf36d..d8e8062 100644 --- a/config/getdents64.c +++ b/build/has/getdents64.c @@ -4,7 +4,6 @@ #include <dirent.h> int main(void) { - struct dirent64 de; - getdents64(3, &de, 1024); - return 0; + char buf[1024]; + return getdents64(3, (void *)buf, sizeof(buf)); } diff --git a/build/has/getmntent-1.c b/build/has/getmntent-1.c new file mode 100644 index 0000000..9854dcd --- /dev/null +++ b/build/has/getmntent-1.c @@ -0,0 +1,9 @@ +// Copyright © Tavian Barnes <tavianator@tavianator.com> +// SPDX-License-Identifier: 0BSD + +#include <mntent.h> +#include <stdio.h> + +int main(void) { + return !getmntent(stdin); +} diff --git a/build/has/getmntent-2.c b/build/has/getmntent-2.c new file mode 100644 index 0000000..71f0220 --- /dev/null +++ b/build/has/getmntent-2.c @@ -0,0 +1,10 @@ +// Copyright © Tavian Barnes <tavianator@tavianator.com> +// SPDX-License-Identifier: 0BSD + +#include <stdio.h> +#include <sys/mnttab.h> + +int main(void) { + struct mnttab mnt; + return getmntent(stdin, &mnt); +} diff --git a/build/has/getmntinfo.c b/build/has/getmntinfo.c new file mode 100644 index 0000000..90ef5fb --- /dev/null +++ b/build/has/getmntinfo.c @@ -0,0 +1,10 @@ +// Copyright © Tavian Barnes <tavianator@tavianator.com> +// SPDX-License-Identifier: 0BSD + +#include <stddef.h> +#include <sys/types.h> +#include <sys/mount.h> + +int main(void) { + return getmntinfo(NULL, MNT_WAIT); +} diff --git a/config/getprogname-gnu.c b/build/has/getprogname-gnu.c index 6b97c5e..6b97c5e 100644 --- a/config/getprogname-gnu.c +++ b/build/has/getprogname-gnu.c diff --git a/config/getprogname.c b/build/has/getprogname.c index 83dc8e8..83dc8e8 100644 --- a/config/getprogname.c +++ b/build/has/getprogname.c diff --git a/build/has/max-align-t.c b/build/has/max-align-t.c new file mode 100644 index 0000000..96165ce --- /dev/null +++ b/build/has/max-align-t.c @@ -0,0 +1,8 @@ +// Copyright © Tavian Barnes <tavianator@tavianator.com> +// SPDX-License-Identifier: 0BSD + +#include <stddef.h> + +int main(void) { + return _Alignof(max_align_t); +} diff --git a/config/pipe2.c b/build/has/pipe2.c index 4cb43b5..4cb43b5 100644 --- a/config/pipe2.c +++ b/build/has/pipe2.c diff --git a/build/has/posix-getdents.c b/build/has/posix-getdents.c new file mode 100644 index 0000000..f74bbe5 --- /dev/null +++ b/build/has/posix-getdents.c @@ -0,0 +1,9 @@ +// Copyright © Tavian Barnes <tavianator@tavianator.com> +// SPDX-License-Identifier: 0BSD + +#include <dirent.h> + +int main(void) { + char buf[1024]; + return posix_getdents(3, (void *)buf, sizeof(buf), 0); +} diff --git a/config/posix-spawn-addfchdir-np.c b/build/has/posix-spawn-addfchdir-np.c index b870a53..b870a53 100644 --- a/config/posix-spawn-addfchdir-np.c +++ b/build/has/posix-spawn-addfchdir-np.c diff --git a/config/posix-spawn-addfchdir.c b/build/has/posix-spawn-addfchdir.c index c52ff81..c52ff81 100644 --- a/config/posix-spawn-addfchdir.c +++ b/build/has/posix-spawn-addfchdir.c diff --git a/config/st-acmtim.c b/build/has/st-acmtim.c index d687ab0..d687ab0 100644 --- a/config/st-acmtim.c +++ b/build/has/st-acmtim.c diff --git a/config/st-acmtimespec.c b/build/has/st-acmtimespec.c index f747bc0..f747bc0 100644 --- a/config/st-acmtimespec.c +++ b/build/has/st-acmtimespec.c diff --git a/config/st-birthtim.c b/build/has/st-birthtim.c index 4964571..4964571 100644 --- a/config/st-birthtim.c +++ b/build/has/st-birthtim.c diff --git a/config/st-birthtimespec.c b/build/has/st-birthtimespec.c index 91a613f..91a613f 100644 --- a/config/st-birthtimespec.c +++ b/build/has/st-birthtimespec.c diff --git a/config/st-flags.c b/build/has/st-flags.c index b1d0c32..b1d0c32 100644 --- a/config/st-flags.c +++ b/build/has/st-flags.c diff --git a/config/statx-syscall.c b/build/has/statx-syscall.c index 87ec869..87ec869 100644 --- a/config/statx-syscall.c +++ b/build/has/statx-syscall.c diff --git a/config/statx.c b/build/has/statx.c index 65f1674..65f1674 100644 --- a/config/statx.c +++ b/build/has/statx.c diff --git a/config/strerror-l.c b/build/has/strerror-l.c index 3dcc4d7..3dcc4d7 100644 --- a/config/strerror-l.c +++ b/build/has/strerror-l.c diff --git a/config/strerror-r-gnu.c b/build/has/strerror-r-gnu.c index 26ca0ee..26ca0ee 100644 --- a/config/strerror-r-gnu.c +++ b/build/has/strerror-r-gnu.c diff --git a/config/strerror-r-posix.c b/build/has/strerror-r-posix.c index 41b2d30..41b2d30 100644 --- a/config/strerror-r-posix.c +++ b/build/has/strerror-r-posix.c diff --git a/build/has/string-to-flags.c b/build/has/string-to-flags.c new file mode 100644 index 0000000..027d72c --- /dev/null +++ b/build/has/string-to-flags.c @@ -0,0 +1,9 @@ +// Copyright © Tavian Barnes <tavianator@tavianator.com> +// SPDX-License-Identifier: 0BSD + +#include <stddef.h> +#include <util.h> + +int main(void) { + return string_to_flags(NULL, NULL, NULL); +} diff --git a/build/has/strtofflags.c b/build/has/strtofflags.c new file mode 100644 index 0000000..73ecbcb --- /dev/null +++ b/build/has/strtofflags.c @@ -0,0 +1,9 @@ +// Copyright © Tavian Barnes <tavianator@tavianator.com> +// SPDX-License-Identifier: 0BSD + +#include <stddef.h> +#include <unistd.h> + +int main(void) { + return strtofflags(NULL, NULL, NULL); +} diff --git a/build/has/timegm.c b/build/has/timegm.c new file mode 100644 index 0000000..6e2d155 --- /dev/null +++ b/build/has/timegm.c @@ -0,0 +1,9 @@ +// Copyright © Tavian Barnes <tavianator@tavianator.com> +// SPDX-License-Identifier: 0BSD + +#include <time.h> + +int main(void) { + struct tm tm = {0}; + return (int)timegm(&tm); +} diff --git a/config/tm-gmtoff.c b/build/has/tm-gmtoff.c index 543df48..543df48 100644 --- a/config/tm-gmtoff.c +++ b/build/has/tm-gmtoff.c diff --git a/config/uselocale.c b/build/has/uselocale.c index a712ff8..a712ff8 100644 --- a/config/uselocale.c +++ b/build/has/uselocale.c diff --git a/build/header.mk b/build/header.mk new file mode 100644 index 0000000..a449b77 --- /dev/null +++ b/build/header.mk @@ -0,0 +1,74 @@ +# Copyright © Tavian Barnes <tavianator@tavianator.com> +# SPDX-License-Identifier: 0BSD + +# Makefile that generates gen/config.h + +include build/prelude.mk +include gen/config.mk +include build/exports.mk + +# All header fragments we generate +HEADERS := \ + gen/has/--st-birthtim.h \ + gen/has/acl-get-entry.h \ + gen/has/acl-get-file.h \ + gen/has/acl-get-tag-type.h \ + gen/has/acl-is-trivial-np.h \ + gen/has/acl-trivial.h \ + gen/has/aligned-alloc.h \ + gen/has/confstr.h \ + gen/has/extattr-get-file.h \ + gen/has/extattr-get-link.h \ + gen/has/extattr-list-file.h \ + gen/has/extattr-list-link.h \ + gen/has/fdclosedir.h \ + gen/has/getdents.h \ + gen/has/getdents64-syscall.h \ + gen/has/getdents64.h \ + gen/has/getmntent-1.h \ + gen/has/getmntent-2.h \ + gen/has/getmntinfo.h \ + gen/has/getprogname-gnu.h \ + gen/has/getprogname.h \ + gen/has/max-align-t.h \ + gen/has/pipe2.h \ + gen/has/posix-getdents.h \ + gen/has/posix-spawn-addfchdir-np.h \ + gen/has/posix-spawn-addfchdir.h \ + gen/has/st-acmtim.h \ + gen/has/st-acmtimespec.h \ + gen/has/st-birthtim.h \ + gen/has/st-birthtimespec.h \ + gen/has/st-flags.h \ + gen/has/statx-syscall.h \ + gen/has/statx.h \ + gen/has/strerror-l.h \ + gen/has/strerror-r-gnu.h \ + gen/has/strerror-r-posix.h \ + gen/has/string-to-flags.h \ + gen/has/strtofflags.h \ + gen/has/timegm.h \ + gen/has/tm-gmtoff.h \ + gen/has/uselocale.h + +# Previously generated by pkgs.mk +PKG_HEADERS := ${ALL_PKGS:%=gen/use/%.h} + +gen/config.h: ${PKG_HEADERS} ${HEADERS} + ${MSG} "[ GEN] $@" + @printf '// %s\n' "$@" >$@ + @printf '#ifndef BFS_CONFIG_H\n' >>$@ + @printf '#define BFS_CONFIG_H\n' >>$@ + @cat ${.ALLSRC} >>$@ + @printf '#endif // BFS_CONFIG_H\n' >>$@ + @cat ${.ALLSRC:%=%.log} >gen/config.log + ${VCAT} $@ +.PHONY: gen/config.h + +# The short name of the config test +SLUG = ${@:gen/%.h=%} + +${HEADERS}:: + @${MKDIR} ${@D} + @build/define-if.sh ${SLUG} build/cc.sh build/${SLUG}.c >$@ 2>$@.log; \ + build/msg-if.sh "[ CC ] ${SLUG}.c" test $$? -eq 0 diff --git a/build/msg-if.sh b/build/msg-if.sh new file mode 100755 index 0000000..8112aea --- /dev/null +++ b/build/msg-if.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +# Copyright © Tavian Barnes <tavianator@tavianator.com> +# SPDX-License-Identifier: 0BSD + +# Print a success/failure indicator from a makefile: +# +# $ ./configure +# [ CC ] use/liburing.c ✘ +# [ CC ] use/oniguruma.c ✔ + +set -eu + +MSG="$1" +shift + +if "$@"; then + build/msg.sh "$(printf '%-37s ✔' "$MSG")" +else + build/msg.sh "$(printf '%-37s ✘' "$MSG")" +fi diff --git a/build/msg.sh b/build/msg.sh new file mode 100755 index 0000000..a7da31b --- /dev/null +++ b/build/msg.sh @@ -0,0 +1,62 @@ +#!/bin/sh + +# Copyright © Tavian Barnes <tavianator@tavianator.com> +# SPDX-License-Identifier: 0BSD + +# Print a message from a makefile: +# +# $ make -s +# $ make +# [ CC ] src/main.c +# $ make V=1 +# cc -Isrc -Igen -D... + +set -eu + +# Get the $MAKEFLAGS from the top-level make invocation +MFLAGS="${XMAKEFLAGS-${MAKEFLAGS-}}" + +# Check if make should be quiet (make -s) +is_quiet() { + # GNU make puts single-letter flags in the first word of $MAKEFLAGS, + # without a leading dash + case "${MFLAGS%% *}" in + -*) : ;; + *s*) return 0 ;; + esac + + # BSD make puts each flag separately like -r -s -j 48 + for flag in $MFLAGS; do + case "$flag" in + # Ignore things like --jobserver-auth + --*) continue ;; + # Skip variable assignments + *=*) break ;; + -*s*) return 0 ;; + esac + done + + return 1 +} + +# Check if make should be loud (make V=1) +is_loud() { + test "$XV" +} + +MSG="$1" +shift + +if ! is_quiet && ! is_loud; then + printf '%s\n' "$MSG" +fi + +if [ $# -eq 0 ]; then + exit +fi + +if is_loud; then + printf '%s\n' "$*" +fi + +"$@" diff --git a/config/pkgconf.sh b/build/pkgconf.sh index 2dbad76..96e4bf1 100755 --- a/config/pkgconf.sh +++ b/build/pkgconf.sh @@ -26,10 +26,22 @@ esac if [ -z "$MODE" ]; then # Check whether the libraries exist at all for LIB; do + # Check ${USE_$LIB} + USE_LIB="USE_$(printf '%s' "$LIB" | tr 'a-z-' 'A-Z_')" + eval "USE=\"\${$USE_LIB:-}\"" + case "$USE" in + y|1) + continue + ;; + n|0) + exit 1 + ;; + esac + CFLAGS=$("$0" --cflags "$LIB") || exit 1 LDFLAGS=$("$0" --ldflags "$LIB") || exit 1 LDLIBS=$("$0" --ldlibs "$LIB") || exit 1 - config/cc.sh $CFLAGS $LDFLAGS config/$LIB.c $LDLIBS || exit 1 + build/cc.sh $CFLAGS $LDFLAGS build/use/$LIB.c $LDLIBS || exit 1 done fi diff --git a/build/pkgs.mk b/build/pkgs.mk new file mode 100644 index 0000000..39b550d --- /dev/null +++ b/build/pkgs.mk @@ -0,0 +1,33 @@ +# Copyright © Tavian Barnes <tavianator@tavianator.com> +# SPDX-License-Identifier: 0BSD + +# Makefile that generates gen/pkgs.mk + +include build/prelude.mk +include gen/vars.mk +include gen/flags.mk +include build/exports.mk + +HEADERS := ${ALL_PKGS:%=gen/use/%.h} + +gen/pkgs.mk: ${HEADERS} + ${MSG} "[ GEN] $@" + @printf '# %s\n' "$@" >$@ + @gen() { \ + printf 'PKGS := %s\n' "$$*"; \ + printf 'CFLAGS += %s\n' "$$(build/pkgconf.sh --cflags "$$@")"; \ + printf 'LDFLAGS += %s\n' "$$(build/pkgconf.sh --ldflags "$$@")"; \ + printf 'LDLIBS := %s $${LDLIBS}\n' "$$(build/pkgconf.sh --ldlibs "$$@")"; \ + }; \ + gen $$(grep -l ' true$$' ${.ALLSRC} | sed 's|.*/\(.*\)\.h|\1|') >>$@ + ${VCAT} $@ + +.PHONY: gen/pkgs.mk + +# Convert gen/use/foo.h to foo +PKG = ${@:gen/use/%.h=%} + +${HEADERS}:: + @${MKDIR} ${@D} + @build/define-if.sh use/${PKG} build/pkgconf.sh ${PKG} >$@ 2>$@.log; \ + build/msg-if.sh "[ CC ] use/${PKG}.c" test $$? -eq 0; diff --git a/build/prelude.mk b/build/prelude.mk new file mode 100644 index 0000000..d0968ea --- /dev/null +++ b/build/prelude.mk @@ -0,0 +1,124 @@ +# Copyright © Tavian Barnes <tavianator@tavianator.com> +# SPDX-License-Identifier: 0BSD + +# Common makefile utilities. Compatible with both GNU make and most BSD makes. + +# BSD make will chdir into ${.OBJDIR} by default, unless we tell it not to +.OBJDIR: . + +# We don't use any suffix rules +.SUFFIXES: + +# GNU make has $^ for the full list of targets, while BSD make has $> and the +# long-form ${.ALLSRC}. We could write $^ $> to get them both, but that would +# break if one of them implemented support for the other. So instead, bring +# BSD's ${.ALLSRC} to GNU. +.ALLSRC ?= $^ + +# Installation paths +DESTDIR ?= +PREFIX ?= /usr +MANDIR ?= ${PREFIX}/share/man + +# Configurable executables +CC ?= cc +INSTALL ?= install +MKDIR ?= mkdir -p +PKG_CONFIG ?= pkg-config +RM ?= rm -f + +# GNU and BSD make have incompatible syntax for conditionals, but we can do a +# lot with just nested variable expansion. We use "y" as the canonical +# truthy value, and "" (the empty string) as the canonical falsey value. +# +# To normalize a boolean, use ${TRUTHY,${VAR}}, which expands like this: +# +# VAR=y ${TRUTHY,${VAR}} => ${TRUTHY,y} => y +# VAR=1 ${TRUTHY,${VAR}} => ${TRUTHY,1} => y +# VAR=n ${TRUTHY,${VAR}} => ${TRUTHY,n} => [empty] +# VAR=other ${TRUTHY,${VAR}} => ${TRUTHY,other} => [empty] +# VAR= ${TRUTHY,${VAR}} => ${TRUTHY,} => [emtpy] +# +# Inspired by https://github.com/wahern/autoguess +TRUTHY,y := y +TRUTHY,1 := y + +# Boolean operators are also implemented with nested expansion +NOT, := y + +# Normalize ${V} to either "y" or "" +export XV=${TRUTHY,${V}} + +# Suppress output unless V=1 +Q, := @ +Q := ${Q,${XV}} + +# Show full commands with `make V=1`, otherwise short summaries +MSG = @build/msg.sh + +# cat a file if V=1 +VCAT,y := @cat +VCAT, := @: +VCAT := ${VCAT,${XV}} + +# All external dependencies +ALL_PKGS := \ + libacl \ + libcap \ + libselinux \ + liburing \ + oniguruma + +# 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/xregex.o \ + obj/src/xspawn.o \ + obj/src/xtime.o \ + obj/gen/version.o + +# Unit test objects +UNIT_OBJS := \ + obj/tests/alloc.o \ + obj/tests/bfstd.o \ + obj/tests/bit.o \ + obj/tests/ioq.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 \ + obj/tests/mksock.o \ + obj/tests/xspawnee.o \ + obj/tests/xtouch.o \ + ${LIBBFS} \ + ${UNIT_OBJS} diff --git a/config/libacl.c b/build/use/libacl.c index de1fe50..de1fe50 100644 --- a/config/libacl.c +++ b/build/use/libacl.c diff --git a/config/libcap.c b/build/use/libcap.c index 58e832c..58e832c 100644 --- a/config/libcap.c +++ b/build/use/libcap.c diff --git a/config/libselinux.c b/build/use/libselinux.c index bca409d..bca409d 100644 --- a/config/libselinux.c +++ b/build/use/libselinux.c diff --git a/config/liburing.c b/build/use/liburing.c index bea499a..bea499a 100644 --- a/config/liburing.c +++ b/build/use/liburing.c diff --git a/config/oniguruma.c b/build/use/oniguruma.c index cb17596..cb17596 100644 --- a/config/oniguruma.c +++ b/build/use/oniguruma.c diff --git a/config/config.mk b/config/config.mk deleted file mode 100644 index 5750b49..0000000 --- a/config/config.mk +++ /dev/null @@ -1,78 +0,0 @@ -# Copyright © Tavian Barnes <tavianator@tavianator.com> -# SPDX-License-Identifier: 0BSD - -# Makefile fragment that implements `make config` - -include config/prelude.mk -include config/exports.mk - -# All configuration steps -config: ${CONFIG} ${GEN}/config.h -.PHONY: config - -# Makefile fragments generated by `make config` -MKS := \ - ${GEN}/vars.mk \ - ${GEN}/flags.mk \ - ${GEN}/deps.mk \ - ${GEN}/pkgs.mk - -# The main configuration file, which includes the others -${CONFIG}: ${MKS} - ${MSG} "[ GEN] ${TGT}" - @printf '# %s\n' "${TGT}" >$@ - @printf 'include $${GEN}/%s\n' ${MKS:${GEN}/%=%} >>$@ - ${VCAT} ${CONFIG} -.PHONY: ${CONFIG} - -# Saves the configurable variables -${GEN}/vars.mk:: - @${MKDIR} ${@D} - ${MSG} "[ GEN] ${TGT}" - @printf '# %s\n' "${TGT}" >$@ - @printf 'PREFIX := %s\n' "$$XPREFIX" >>$@ - @printf 'MANDIR := %s\n' "$$XMANDIR" >>$@ - @printf 'OS := %s\n' "$${OS:-$$(uname)}" >>$@ - @printf 'CC := %s\n' "$$XCC" >>$@ - @printf 'INSTALL := %s\n' "$$XINSTALL" >>$@ - @printf 'MKDIR := %s\n' "$$XMKDIR" >>$@ - @printf 'PKG_CONFIG := %s\n' "$$XPKG_CONFIG" >>$@ - @printf 'RM := %s\n' "$$XRM" >>$@ - @printf 'PKGS :=\n' >>$@ - ${VCAT} $@ - -# Sets the build flags. This depends on vars.mk and uses a recursive make so -# that the default flags can depend on variables like ${OS}. -${GEN}/flags.mk: ${GEN}/vars.mk - @+${MAKE} -sf config/flags.mk -.PHONY: ${GEN}/flags.mk - -# Check for dependency generation support -${GEN}/deps.mk: ${GEN}/flags.mk - @+${MAKE} -sf config/deps.mk -.PHONY: ${GEN}/deps.mk - -# External dependencies -PKG_MKS := \ - ${GEN}/libacl.mk \ - ${GEN}/libcap.mk \ - ${GEN}/libselinux.mk \ - ${GEN}/liburing.mk \ - ${GEN}/oniguruma.mk - -# Auto-detect dependencies and their build flags -${GEN}/pkgs.mk: ${PKG_MKS} - @printf '# %s\n' "${TGT}" >$@ - @printf 'include $${GEN}/%s\n' ${.ALLSRC:${GEN}/%=%} >>$@ - @+${MAKE} -sf config/pkgs.mk -.PHONY: ${GEN}/pkgs.mk - -# Auto-detect dependencies -${PKG_MKS}: ${GEN}/flags.mk - @+${MAKE} -sf config/pkg.mk TARGET=$@ -.PHONY: ${PKG_MKS} - -# Compile-time feature detection -${GEN}/config.h: ${CONFIG} - @+${MAKE} -sf config/header.mk $@ -.PHONY: ${GEN}/config.h diff --git a/config/deps.mk b/config/deps.mk deleted file mode 100644 index 2201f06..0000000 --- a/config/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 config/prelude.mk -include ${GEN}/vars.mk -include ${GEN}/flags.mk -include config/exports.mk - -${GEN}/deps.mk:: - ${MSG} "[ GEN] ${TGT}" - printf '# %s\n' "${TGT}" >$@ - if config/cc.sh -MD -MP -MF /dev/null config/empty.c; then \ - printf 'DEPFLAGS = -MD -MP\n'; \ - fi >>$@ 2>$@.log - ${VCAT} $@ - @printf -- '-include %s\n' ${OBJS:.o=.d} >>$@ diff --git a/config/header.mk b/config/header.mk deleted file mode 100644 index 86a4dc5..0000000 --- a/config/header.mk +++ /dev/null @@ -1,60 +0,0 @@ -# Copyright © Tavian Barnes <tavianator@tavianator.com> -# SPDX-License-Identifier: 0BSD - -# Makefile that generates gen/config.h - -include config/prelude.mk -include ${GEN}/config.mk -include config/exports.mk - -# All header fragments we generate -HEADERS := \ - ${GEN}/acl-is-trivial-np.h \ - ${GEN}/aligned-alloc.h \ - ${GEN}/confstr.h \ - ${GEN}/fdclosedir.h \ - ${GEN}/getdents.h \ - ${GEN}/getdents64.h \ - ${GEN}/getdents64-syscall.h \ - ${GEN}/getprogname.h \ - ${GEN}/getprogname-gnu.h \ - ${GEN}/pipe2.h \ - ${GEN}/posix-spawn-addfchdir.h \ - ${GEN}/posix-spawn-addfchdir-np.h \ - ${GEN}/st-acmtim.h \ - ${GEN}/st-acmtimespec.h \ - ${GEN}/st-birthtim.h \ - ${GEN}/st-birthtimespec.h \ - ${GEN}/st-flags.h \ - ${GEN}/statx.h \ - ${GEN}/statx-syscall.h \ - ${GEN}/strerror-l.h \ - ${GEN}/strerror-r-gnu.h \ - ${GEN}/strerror-r-posix.h \ - ${GEN}/tm-gmtoff.h \ - ${GEN}/uselocale.h - -${GEN}/config.h: ${HEADERS} - ${MSG} "[ GEN] ${TGT}" - printf '// %s\n' "${TGT}" >$@ - printf '#ifndef BFS_CONFIG_H\n' >>$@ - printf '#define BFS_CONFIG_H\n' >>$@ - cat ${.ALLSRC} >>$@ - printf '#endif // BFS_CONFIG_H\n' >>$@ - cat ${.ALLSRC:%=%.log} >$@.log - ${RM} ${.ALLSRC} ${.ALLSRC:%=%.log} - ${VCAT} $@ -.PHONY: ${GEN}/config.h - -# The C source file to attempt to compile -CSRC = ${@:${GEN}/%.h=config/%.c} - -${HEADERS}:: - config/cc-define.sh ${CSRC} >$@ 2>$@.log - if ! [ "${IS_V}" ]; then \ - if grep -q 'true$$' $@; then \ - printf '[ CC ] %-${MSG_WIDTH}s ✔\n' ${CSRC}; \ - else \ - printf '[ CC ] %-${MSG_WIDTH}s ✘\n' ${CSRC}; \ - fi; \ - fi diff --git a/config/pkg.mk b/config/pkg.mk deleted file mode 100644 index fafe562..0000000 --- a/config/pkg.mk +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright © Tavian Barnes <tavianator@tavianator.com> -# SPDX-License-Identifier: 0BSD - -# Makefile that generates gen/lib*.mk - -include config/prelude.mk -include ${GEN}/vars.mk -include ${GEN}/flags.mk -include config/exports.mk - -# Like ${TGT} but for ${TARGET}, not $@ -SHORT = ${TARGET:${BUILDDIR}/%=%} - -default:: - @printf '# %s\n' "${SHORT}" >${TARGET} - config/pkg.sh ${TARGET:${GEN}/%.mk=%} >>${TARGET} 2>${TARGET}.log - @if [ "${IS_V}" ]; then \ - cat ${TARGET}; \ - elif grep -q PKGS ${TARGET}; then \ - printf '[ GEN] %-${MSG_WIDTH}s ✔\n' ${SHORT}; \ - else \ - printf '[ GEN] %-${MSG_WIDTH}s ✘\n' ${SHORT}; \ - fi diff --git a/config/pkg.sh b/config/pkg.sh deleted file mode 100755 index 4ebea64..0000000 --- a/config/pkg.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/sh - -# Copyright © Tavian Barnes <tavianator@tavianator.com> -# SPDX-License-Identifier: 0BSD - -# pkg-config wrapper that outputs a makefile fragment - -set -eu - -NAME=$(printf '%s' "$1" | tr 'a-z' 'A-Z') -eval "XUSE=\"\${USE_$NAME:-}\"" - -if [ "$XUSE" ]; then - USE="$XUSE" -elif config/pkgconf.sh "$1"; then - USE=y -else - USE=n -fi - -if [ "$USE" = y ]; then - printf 'PKGS += %s\n' "$1" - printf 'CPPFLAGS += -DBFS_USE_%s=1\n' "$NAME" -else - printf 'CPPFLAGS += -DBFS_USE_%s=0\n' "$NAME" -fi diff --git a/config/pkgs.mk b/config/pkgs.mk deleted file mode 100644 index 3a18289..0000000 --- a/config/pkgs.mk +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright © Tavian Barnes <tavianator@tavianator.com> -# SPDX-License-Identifier: 0BSD - -# Makefile that generates gen/pkgs.mk - -include config/prelude.mk -include ${GEN}/vars.mk -include ${GEN}/flags.mk -include ${GEN}/pkgs.mk -include config/exports.mk - -${GEN}/pkgs.mk:: - ${MSG} "[ GEN] ${TGT}" - printf 'CFLAGS += %s\n' "$$(config/pkgconf.sh --cflags ${PKGS})" >>$@ 2>>$@.log - printf 'LDFLAGS += %s\n' "$$(config/pkgconf.sh --ldflags ${PKGS})" >>$@ 2>>$@.log - printf 'LDLIBS := %s $${LDLIBS}\n' "$$(config/pkgconf.sh --ldlibs ${PKGS})" >>$@ 2>>$@.log - ${VCAT} $@ diff --git a/config/prelude.mk b/config/prelude.mk deleted file mode 100644 index b9bc61b..0000000 --- a/config/prelude.mk +++ /dev/null @@ -1,170 +0,0 @@ -# Copyright © Tavian Barnes <tavianator@tavianator.com> -# SPDX-License-Identifier: 0BSD - -# Common makefile utilities. Compatible with both GNU make and most BSD makes. - -# BSD make will chdir into ${.OBJDIR} by default, unless we tell it not to -.OBJDIR: . - -# We don't use any suffix rules -.SUFFIXES: - -# GNU make has $^ for the full list of targets, while BSD make has $> and the -# long-form ${.ALLSRC}. We could write $^ $> to get them both, but that would -# break if one of them implemented support for the other. So instead, bring -# BSD's ${.ALLSRC} to GNU. -.ALLSRC ?= $^ - -# For out-of-tree builds, e.g. -# -# $ make config BUILDDIR=/path/to/build/dir -# $ make BUILDDIR=/path/to/build/dir -BUILDDIR ?= . - -# Shorthand for build subdirectories -BIN := ${BUILDDIR}/bin -GEN := ${BUILDDIR}/gen -OBJ := ${BUILDDIR}/obj - -# GNU make strips a leading ./ from target names, so do the same for BSD make -BIN := ${BIN:./%=%} -GEN := ${GEN:./%=%} -OBJ := ${OBJ:./%=%} - -# The configuration file generated by `make config` -CONFIG := ${GEN}/config.mk - -# Installation paths -DESTDIR ?= -PREFIX ?= /usr -MANDIR ?= ${PREFIX}/share/man - -# GNU make supports `export VAR`, but BSD make requires `export VAR=value`. -# Sadly, GNU make gives a recursion error on `export VAR=${VAR}`. -_BUILDDIR := ${BUILDDIR} -export BUILDDIR=${_BUILDDIR} - -# Configurable executables; can be overridden with -# -# $ make config CC=clang -CC ?= cc -INSTALL ?= install -MKDIR ?= mkdir -p -PKG_CONFIG ?= pkg-config -RM ?= rm -f - -# GNU and BSD make have incompatible syntax for conditionals, but we can do a -# lot with just nested variable expansion. We use "y" as the canonical -# truthy value, and "" (the empty string) as the canonical falsey value. -# -# To normalize a boolean, use ${TRUTHY,${VAR}}, which expands like this: -# -# VAR=y ${TRUTHY,${VAR}} => ${TRUTHY,y} => y -# VAR=1 ${TRUTHY,${VAR}} => ${TRUTHY,1} => y -# VAR=n ${TRUTHY,${VAR}} => ${TRUTHY,n} => [empty] -# VAR=other ${TRUTHY,${VAR}} => ${TRUTHY,other} => [empty] -# VAR= ${TRUTHY,${VAR}} => ${TRUTHY,} => [emtpy] -# -# Inspired by https://github.com/wahern/autoguess -TRUTHY,y := y -TRUTHY,1 := y - -# Boolean operators are also implemented with nested expansion -NOT,y := -NOT, := y - -# Support up to 5 arguments -AND,y := y -AND,y,y := y -AND,y,y,y := y -AND,y,y,y,y := y -AND,y,y,y,y,y := y - -# NOR can be defined without combinatorial explosion. -# OR is just ${NOT,${NOR,...}} -NOR, := y -NOR,, := y -NOR,,, := y -NOR,,,, := y -NOR,,,,, := y - -# Normalize ${V} to either "y" or "" -IS_V := ${TRUTHY,${V}} - -# Suppress output unless V=1 -Q, := @ -Q := ${Q,${IS_V}} - -# The current target, with ${BUILDDIR} stripped for shorter messages -TGT = ${@:${BUILDDIR}/%=%} - -# Show full commands with `make V=1`, otherwise short summaries -MSG = @msg() { \ - MSG="$$1"; \ - shift; \ - test "${IS_V}" || printf '%s\n' "$$MSG"; \ - test "$${1:-}" || return 0; \ - test "${IS_V}" && printf '%s\n' "$$*"; \ - "$$@"; \ - }; \ - msg - -# Maximum width of a short message, to align the [X] -MSG_WIDTH := 33 - -# cat a file if V=1 -VCAT,y := @cat -VCAT, := @: -VCAT := ${VCAT,${IS_V}} - -# List all object files here, as they're needed by both `make config` 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/stat.o \ - ${OBJ}/src/thread.o \ - ${OBJ}/src/trie.o \ - ${OBJ}/src/typo.o \ - ${OBJ}/src/xregex.o \ - ${OBJ}/src/xspawn.o \ - ${OBJ}/src/xtime.o \ - ${OBJ}/gen/version.o - -# Unit test objects -UNIT_OBJS := \ - ${OBJ}/tests/alloc.o \ - ${OBJ}/tests/bfstd.o \ - ${OBJ}/tests/bit.o \ - ${OBJ}/tests/ioq.o \ - ${OBJ}/tests/main.o \ - ${OBJ}/tests/trie.o \ - ${OBJ}/tests/xspawn.o \ - ${OBJ}/tests/xtime.o - -# All object files -OBJS := \ - ${OBJ}/src/main.o \ - ${OBJ}/tests/mksock.o \ - ${OBJ}/tests/xspawnee.o \ - ${OBJ}/tests/xtouch.o \ - ${LIBBFS} \ - ${UNIT_OBJS} diff --git a/configure b/configure new file mode 100755 index 0000000..d42dec3 --- /dev/null +++ b/configure @@ -0,0 +1,137 @@ +#!/bin/sh + +# Copyright © Tavian Barnes <tavianator@tavianator.com> +# SPDX-License-Identifier: 0BSD + +# bfs build configuration script + +set -eu + +# Default to `make` +MAKE="${MAKE:-make}" + +# Pass -j$(nproc) unless MAKEFLAGS is set +if [ "${MAKEFLAGS+y}" ]; then + j="" +else + j="-j$({ nproc || sysctl -n hw.ncpu || getconf _NPROCESSORS_ONLN || echo 1; } 2>/dev/null)" +fi + +for arg; do + case "$arg" in + -h|--help) + cat <<EOF +Usage: + + \$ $0 [--enable-*|--disable-*] [CC=...] [CFLAGS=...] [...] + \$ $MAKE $j + +Variables set in the environment or on the command line will be picked up: + + MAKE + The make implementation to use + CC + The C compiler to use + + CPPFLAGS="-I... -D..." + CFLAGS="-W... -f..." + LDFLAGS="-L... -Wl,..." + Preprocessor/compiler/linker flags + + LDLIBS="-l... -l..." + Dynamic libraries to link + + EXTRA_{CPPFLAGS,CFLAGS,LDFLAGS,LDLIBS} + Adds to the default flags, instead of replacing them + +The default flags result in a plain debug build. Other build profiles include: + + --enable-release + Enable optimizations, disable assertions + --enable-{asan,lsan,msan,tsan,ubsan} + Enable sanitizers + --enable-gcov + Enable code coverage instrumentation + +External dependencies are auto-detected by default, but you can --enable or +--disable them manually: + + --enable-libacl --disable-libacl + --enable-libcap --disable-libcap + --enable-libselinux --disable-libselinux + --enable-liburing --disable-liburing + --enable-oniguruma --disable-oniguruma + +Packaging: + + --prefix=/path + Set the installation prefix (default: /usr) + +This script is a thin wrapper around a makefile-based configuration system. +Any other arguments will be passed directly to the $MAKE invocation, e.g. + + \$ $0 $j V=1 +EOF + exit 0 + ;; + + --enable-*|--disable-*) + case "$arg" in + --enable-*) yn=y ;; + --disable-*) yn=n ;; + esac + + name="${arg#--*able-}" + NAME=$(printf '%s' "$name" | tr 'a-z-' 'A-Z_') + case "$name" in + libacl|libcap|libselinux|liburing|oniguruma) + shift + set -- "$@" "USE_$NAME=$yn" + ;; + release|asan|lsan|msan|tsan|ubsan|lint|gcov) + shift + set -- "$@" "$NAME=$yn" + ;; + *) + printf 'error: Unrecognized option "%s"\n\n' "$arg" >&2 + printf 'Run %s --help for more information.\n' "$0" >&2 + exit 1 + ;; + esac + ;; + + --prefix=*) + shift + set -- "$@" "PREFIX=${arg#*=}" + ;; + + MAKE=*) + MAKE="${arg#*=}" + shift + ;; + + # make flag (-j2) or variable (CC=clang) + -*|*=*) + continue + ;; + + *) + printf 'error: Unrecognized option "%s"\n\n' "$arg" >&2 + printf 'Run %s --help for more information.\n' "$0" >&2 + exit 1 + ;; + 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" +done + +# Set MAKEFLAGS to -j$(nproc) if it's unset +export MAKEFLAGS="${MAKEFLAGS-$j}" + +$MAKE -rf build/config.mk "$@" diff --git a/docs/BUILDING.md b/docs/BUILDING.md index 4ed139c..cb33c51 100644 --- a/docs/BUILDING.md +++ b/docs/BUILDING.md @@ -1,110 +1,157 @@ Building `bfs` ============== -Compiling ---------- - -`bfs` uses [GNU Make](https://www.gnu.org/software/make/) as its build system. A simple invocation of - $ make config + $ ./configure $ make should build `bfs` successfully. -As usual with `make`, you can run a [parallel build](https://www.gnu.org/software/make/manual/html_node/Parallel.html) with `-j`. -For example, to use all your cores, run `make -j$(nproc)`. -### Targets -| Command | Description | -|------------------|---------------------------------------------------------------| -| `make config` | Configures the build system | -| `make` | Builds just the `bfs` binary | -| `make all` | Builds everything, including the tests (but doesn't run them) | -| `make check` | Builds everything, and runs the tests | -| `make install` | Installs `bfs` (with man page, shell completions, etc.) | -| `make uninstall` | Uninstalls `bfs` | -| `make clean` | Delete the build products | -| `make distclean` | Delete all generated files, including the build configuration | +Configuration +------------- + +```console +$ ./configure --help +Usage: + + $ ./configure [--enable-*|--disable-*] [CC=...] [CFLAGS=...] [...] + $ make + +... +``` + +### Variables + +Variables set in the environment or on the command line will be picked up: +These variables specify binaries to run during the configuration and build process: + +<pre> +<b>MAKE</b>=<i>make</i> + <a href="https://en.wikipedia.org/wiki/Make_(software)">make</a> implementation +<b>CC</b>=<i>cc</i> + C compiler +<b>INSTALL</b>=<i>install</i> + Copy files during <i>make install</i> +<b>MKDIR</b>="<i>mkdir -p</i>" + Create directories +<b>PKG_CONFIG</b>=<i>pkg-config</i> + Detect external libraries and required build flags +<b>RM</b>="<i>rm -f</i>" + Delete files +</pre> + +These flags will be used by the build process: + +<pre> +<b>CPPFLAGS</b>="<i>-I... -D...</i>" +<b>CFLAGS</b>="<i>-W... -f...</i>" +<b>LDFLAGS</b>="<i>-L... -Wl,...</i>" + Preprocessor/compiler/linker flags + +<b>LDLIBS</b>="<i>-l... -l...</i>" + Dynamic libraries to link + +<b>EXTRA_</b>{<b>CPPFLAGS</b>,<b>CFLAGS</b>,<b>LDFLAGS</b>,<b>LDLIBS</b>}="<i>...</i>" + Adds to the default flags, instead of replacing them +</pre> ### Build profiles -The configuration system provides a few shorthand flags for handy configurations: - -| Command | Description | -|-------------------------|-------------------------------------------------------------| -| `make config RELEASE=y` | Build `bfs` with optimizations, LTO, and without assertions | -| `make config ASAN=y` | Enable [AddressSanitizer] | -| `make config LSAN=y` | Enable [LeakSanitizer] | -| `make config MSAN=y` | Enable [MemorySanitizer] | -| `make config TSAN=y` | Enable [ThreadSanitizer] | -| `make config UBSAN=y` | Enable [UndefinedBehaviorSanitizer] | -| `make config GCOV=y` | Enable [code coverage] | - -[AddressSanitizer]: https://github.com/google/sanitizers/wiki/AddressSanitizer -[LeakSanitizer]: https://github.com/google/sanitizers/wiki/AddressSanitizerLeakSanitizer#stand-alone-mode -[MemorySanitizer]: https://github.com/google/sanitizers/wiki/MemorySanitizer -[ThreadSanitizer]: https://github.com/google/sanitizers/wiki/ThreadSanitizerCppManual -[UndefinedBehaviorSanitizer]: https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html -[code coverage]: https://gcc.gnu.org/onlinedocs/gcc/Gcov.html - -You can combine multiple profiles (e.g. `make config ASAN=y UBSAN=y`), but not all of them will work together. - -### Flags - -Other flags can be specified on the `make config` command line or in the environment. -Here are some of the common ones; check the [`Makefile`](/Makefile) for more. - -| Flag | Description | -|-------------------------------------|----------------------------------------------------| -| `CC` | The C compiler to use, e.g. `make config CC=clang` | -| `CFLAGS`<br>`EXTRA_CFLAGS` | Override/add to the default compiler flags | -| `LDFLAGS`<br>`EXTRA_LDFLAGS` | Override/add to the linker flags | -| `USE_LIBACL`<br>`USE_LIBCAP`<br>... | Enable/disable [optional dependencies] | -| `TEST_FLAGS` | `tests.sh` flags for `make check` | -| `BUILDDIR` | The build output directory (default: `.`) | -| `DESTDIR` | The root directory for `make install` | -| `PREFIX` | The installation prefix (default: `/usr`) | -| `MANDIR` | The man page installation directory | - -[optional dependencies]: #dependencies +The default flags result in a plain debug build. +Other build profiles can be enabled: + +<pre> +--enable-release + Enable optimizations, disable assertions + +--enable-<a href="https://github.com/google/sanitizers/wiki/AddressSanitizer">asan</a> +--enable-<a href="https://github.com/google/sanitizers/wiki/AddressSanitizerLeakSanitizer#stand-alone-mode">lsan</a> +--enable-<a href="https://github.com/google/sanitizers/wiki/MemorySanitizer">msan</a> +--enable-<a href="https://github.com/google/sanitizers/wiki/ThreadSanitizerCppManual">tsan</a> +--enable-<a href="https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html">ubsan</a> + Enable sanitizers + +--enable-<a href="https://gcc.gnu.org/onlinedocs/gcc/gcov/introduction-to-gcov.html">gcov</a> + Enable code coverage instrumentation +</pre> + +You can combine multiple profiles (e.g. `./configure --enable-asan --enable-ubsan`), but not all of them will work together. ### Dependencies `bfs` depends on some system libraries for some of its features. -These dependencies are optional, and can be turned off in `make config` if necessary by setting the appropriate variable to `n` (e.g. `make config USE_ONIGURUMA=n`). +External dependencies are auto-detected by default, but you can `--enable` or `--disable` them manually: -| Dependency | Platforms | `make config` flag | -|--------------|------------|--------------------| -| [libacl] | Linux only | `USE_LIBACL` | -| [libcap] | Linux only | `USE_LIBCAP` | -| [liburing] | Linux only | `USE_LIBURING` | -| [libselinux] | Linux only | `USE_LIBSELINUX` | -| [Oniguruma] | All | `USE_ONIGURUMA` | +<pre> +--enable-<a href="https://savannah.nongnu.org/projects/acl">libacl</a> --disable-libacl +--enable-<a href="https://sites.google.com/site/fullycapable/">libcap</a> --disable-libcap +--enable-<a href="https://github.com/SELinuxProject/selinux">libselinux</a> --disable-libselinux +--enable-<a href="https://github.com/axboe/liburing">liburing</a> --disable-liburing +--enable-<a href="https://github.com/kkos/oniguruma">oniguruma</a> --disable-oniguruma +</pre> -[libacl]: https://savannah.nongnu.org/projects/acl -[libcap]: https://sites.google.com/site/fullycapable/ -[libselinux]: https://github.com/SELinuxProject/selinux -[liburing]: https://github.com/axboe/liburing -[Oniguruma]: https://github.com/kkos/oniguruma +[`pkg-config`] is used, if available, to detect these libraries and any additional build flags they may require. +If this is undesireable, disable it by setting `PKG_CONFIG` to the empty string (`./configure PKG_CONFIG=""`). -### Dependency tracking +[`pkg-config`]: https://www.freedesktop.org/wiki/Software/pkg-config/ -The build system automatically tracks header dependencies with the `-M` family of compiler options (see `DEPFLAGS` in the [`Makefile`](/Makefile)). -So if you edit a header file, `make` will rebuild the necessary object files ensuring they don't go out of sync. +### Out-of-tree builds -We also add a dependency on the current configuration, so you can change configurations and rebuild without having to `make clean`. +You can set up an out-of-tree build by running the `configure` script from another directory, for example: -We go one step further than most build systems by tracking the flags that were used for the previous compilation. -That means you can change configurations without having to `make clean`. -For example, - - $ make config - $ make - $ make config RELEASE=y + $ mkdir out + $ cd out + $ ../configure $ make -will build the project in debug mode and then rebuild it in release mode. + +Building +-------- + +### Targets + +The [`Makefile`](/Makefile) supports several different build targets: + +<pre> +make + The default target; builds just the <i>bfs</i> binary +make <b>all</b> + Builds everything, including the tests (but doesn't run them) + +make <b>check</b> + Builds everything, and runs all tests +make <b>unit-tests</b> + Builds and runs the unit tests +make <b>integration-tests</b> + Builds and runs the integration tests +make <b>distcheck</b> + Builds and runs the tests in multiple different configurations + +make <b>install</b> + Installs bfs globally +make <b>uninstall</b> + Uninstalls bfs + +make <b>clean</b> + Deletes all built files +make <b>distclean</b> + Also deletes files generated by ./configure +</pre> + + +Troubleshooting +--------------- + +If the build fails or behaves unexpectedly, start by enabling verbose mode: + + $ ./configure V=1 + $ make V=1 + +This will print the generated configuration and the exact commands that are executed. + +You can also check the file `gen/config.log`, which contains any errors from commands run during the configuration phase. Testing diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 672c2b4..62b6480 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,6 +1,36 @@ 3.* === +3.2 +--- + +**May 2, 2024** + +### New features + +- New `-limit N` action that quits immediately after `N` results + +- Implemented `-context` (from GNU find) for matching SELinux contexts ([#27](https://github.com/tavianator/bfs/issues/27)) + +- Implemented `-printf %Z` for printing SELinux contexts + +### Changes + +- The build system has been rewritten, and there is now a configure step: + + $ ./configure + $ make + + See `./configure --help` or [docs/BUILDING.md](/docs/BUILDING.md) for more details. + +- Improved platform support + - Implemented `-acl` on Solaris/Illumos + - Implemented `-xattr` on DragonFly BSD + +### Bug fixes + +- Fixed some rarely-used code paths that clean up after allocation failures + 3.1.3 ----- diff --git a/src/atomic.h b/src/atomic.h index f1a6bea..360de20 100644 --- a/src/atomic.h +++ b/src/atomic.h @@ -8,6 +8,8 @@ #ifndef BFS_ATOMIC_H #define BFS_ATOMIC_H +#include "prelude.h" +#include "sanity.h" #include <stdatomic.h> /** @@ -82,4 +84,35 @@ #define fetch_and(obj, arg, order) \ atomic_fetch_and_explicit(obj, arg, memory_order_##order) +/** + * Shorthand for atomic_thread_fence(). + */ +#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) +#else +# define thread_fence(obj, order) \ + atomic_thread_fence(memory_order_##order) +#endif + +/** + * Shorthand for atomic_signal_fence(). + */ +#define signal_fence(order) \ + atomic_signal_fence(memory_order_##order) + +/** + * A hint to the CPU to relax while it spins. + */ +#if __has_builtin(__builtin_ia32_pause) +# define spin_loop() __builtin_ia32_pause() +#elif __has_builtin(__builtin_arm_yield) +# define spin_loop() __builtin_arm_yield() +#elif __has_builtin(__builtin_riscv_pause) +# define spin_loop() __builtin_riscv_pause() +#else +# define spin_loop() ((void)0) +#endif + #endif // BFS_ATOMIC_H @@ -3,10 +3,12 @@ #include "prelude.h" #include "bar.h" +#include "alloc.h" #include "atomic.h" #include "bfstd.h" #include "bit.h" #include "dstring.h" +#include "sighook.h" #include <errno.h> #include <fcntl.h> #include <signal.h> @@ -19,11 +21,9 @@ struct bfs_bar { int fd; atomic unsigned int width; atomic unsigned int height; -}; -/** The global status bar instance. */ -static struct bfs_bar the_bar = { - .fd = -1, + struct sighook *exit_hook; + struct sighook *winch_hook; }; /** Get the terminal size, if possible. */ @@ -43,10 +43,14 @@ static int bfs_bar_getsize(struct bfs_bar *bar) { #endif } -/** Async Signal Safe puts(). */ -static int ass_puts(int fd, const char *str) { - size_t len = strlen(str); - return xwrite(fd, str, len) == len ? 0 : -1; +/** Write a string to the status bar (async-signal-safe). */ +static int bfs_bar_write(struct bfs_bar *bar, const char *str, size_t len) { + return xwrite(bar->fd, str, len) == len ? 0 : -1; +} + +/** Write a string to the status bar (async-signal-safe). */ +static int bfs_bar_puts(struct bfs_bar *bar, const char *str) { + return bfs_bar_write(bar, str, strlen(str)); } /** Number of decimal digits needed for terminal sizes. */ @@ -70,39 +74,40 @@ static char *ass_itoa(char *str, unsigned int n) { /** Update the size of the scrollable region. */ static int bfs_bar_resize(struct bfs_bar *bar) { - char esc_seq[12 + ITOA_DIGITS] = + static const char PREFIX[] = + "\033D" // IND: Line feed, possibly scrolling + "\033[1A" // CUU: Move cursor up 1 row "\0337" // DECSC: Save cursor "\033[;"; // DECSTBM: Set scrollable region + static const char SUFFIX[] = + "r" // (end of DECSTBM) + "\0338" // DECRC: Restore the cursor + "\033[J"; // ED: Erase display from cursor to end + + char esc_seq[sizeof(PREFIX) + ITOA_DIGITS + sizeof(SUFFIX)]; // DECSTBM takes the height as the second argument - unsigned int height = load(&bar->height, relaxed); - char *ptr = esc_seq + strlen(esc_seq); - ptr = ass_itoa(ptr, height - 1); + unsigned int height = load(&bar->height, relaxed) - 1; - strcpy(ptr, - "r" // DECSTBM - "\0338" // DECRC: Restore the cursor - "\033[J" // ED: Erase display from cursor to end - ); + char *cur = stpcpy(esc_seq, PREFIX); + cur = ass_itoa(cur, height); + cur = stpcpy(cur, SUFFIX); - return ass_puts(bar->fd, esc_seq); + return bfs_bar_write(bar, esc_seq, cur - esc_seq); } #ifdef SIGWINCH /** SIGWINCH handler. */ -static void sighand_winch(int sig) { - int error = errno; - - bfs_bar_getsize(&the_bar); - bfs_bar_resize(&the_bar); - - errno = error; +static void bfs_bar_sigwinch(int sig, siginfo_t *info, void *arg) { + struct bfs_bar *bar = arg; + bfs_bar_getsize(bar); + bfs_bar_resize(bar); } #endif /** Reset the scrollable region and hide the bar. */ static int bfs_bar_reset(struct bfs_bar *bar) { - return ass_puts(bar->fd, + return bfs_bar_puts(bar, "\0337" // DECSC: Save cursor "\033[r" // DECSTBM: Reset scrollable region "\0338" // DECRC: Restore cursor @@ -111,19 +116,9 @@ static int bfs_bar_reset(struct bfs_bar *bar) { } /** Signal handler for process-terminating signals. */ -static void sighand_reset(int sig) { - bfs_bar_reset(&the_bar); - raise(sig); -} - -/** Register sighand_reset() for a signal. */ -static void reset_before_death_by(int sig) { - struct sigaction sa = { - .sa_handler = sighand_reset, - .sa_flags = SA_RESETHAND, - }; - sigemptyset(&sa.sa_mask); - sigaction(sig, &sa, NULL); +static void bfs_bar_sigexit(int sig, siginfo_t *info, void *arg) { + struct bfs_bar *bar = arg; + bfs_bar_reset(bar); } /** printf() to the status bar with a single write(). */ @@ -138,15 +133,15 @@ static int bfs_bar_printf(struct bfs_bar *bar, const char *format, ...) { return -1; } - int ret = ass_puts(bar->fd, str); + int ret = bfs_bar_write(bar, str, dstrlen(str)); dstrfree(str); return ret; } struct bfs_bar *bfs_bar_show(void) { - if (the_bar.fd >= 0) { - errno = EBUSY; - goto fail; + struct bfs_bar *bar = ALLOC(struct bfs_bar); + if (!bar) { + return NULL; } char term[L_ctermid]; @@ -156,46 +151,36 @@ struct bfs_bar *bfs_bar_show(void) { goto fail; } - the_bar.fd = open(term, O_RDWR | O_CLOEXEC); - if (the_bar.fd < 0) { + bar->fd = open(term, O_RDWR | O_CLOEXEC); + if (bar->fd < 0) { goto fail; } - if (bfs_bar_getsize(&the_bar) != 0) { + if (bfs_bar_getsize(bar) != 0) { goto fail_close; } - reset_before_death_by(SIGABRT); - reset_before_death_by(SIGINT); - reset_before_death_by(SIGPIPE); - reset_before_death_by(SIGQUIT); - reset_before_death_by(SIGTERM); + bar->exit_hook = atsigexit(bfs_bar_sigexit, bar); + if (!bar->exit_hook) { + goto fail_close; + } #ifdef SIGWINCH - struct sigaction sa = { - .sa_handler = sighand_winch, - .sa_flags = SA_RESTART, - }; - sigemptyset(&sa.sa_mask); - sigaction(SIGWINCH, &sa, NULL); + bar->winch_hook = sighook(SIGWINCH, bfs_bar_sigwinch, bar, 0); + if (!bar->winch_hook) { + goto fail_hook; + } #endif - unsigned int height = load(&the_bar.height, relaxed); - bfs_bar_printf(&the_bar, - "\n" // Make space for the bar - "\0337" // DECSC: Save cursor - "\033[;%ur" // DECSTBM: Set scrollable region - "\0338" // DECRC: Restore cursor - "\033[1A", // CUU: Move cursor up 1 row - height - 1 - ); - - return &the_bar; + bfs_bar_resize(bar); + return bar; +fail_hook: + sigunhook(bar->exit_hook); fail_close: - close_quietly(the_bar.fd); - the_bar.fd = -1; + close_quietly(bar->fd); fail: + free(bar); return NULL; } @@ -223,17 +208,11 @@ void bfs_bar_hide(struct bfs_bar *bar) { return; } - signal(SIGABRT, SIG_DFL); - signal(SIGINT, SIG_DFL); - signal(SIGPIPE, SIG_DFL); - signal(SIGQUIT, SIG_DFL); - signal(SIGTERM, SIG_DFL); -#ifdef SIGWINCH - signal(SIGWINCH, SIG_DFL); -#endif + sigunhook(bar->winch_hook); + sigunhook(bar->exit_hook); bfs_bar_reset(bar); xclose(bar->fd); - bar->fd = -1; + free(bar); } diff --git a/src/bfstd.c b/src/bfstd.c index 1144380..7680f17 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -252,40 +252,6 @@ int ynprompt(void) { return ret; } -/** Get the single character describing the given file type. */ -static char type_char(mode_t mode) { - switch (mode & S_IFMT) { - case S_IFREG: - return '-'; - case S_IFBLK: - return 'b'; - case S_IFCHR: - return 'c'; - case S_IFDIR: - return 'd'; - case S_IFLNK: - return 'l'; - case S_IFIFO: - return 'p'; - case S_IFSOCK: - return 's'; -#ifdef S_IFDOOR - case S_IFDOOR: - return 'D'; -#endif -#ifdef S_IFPORT - case S_IFPORT: - return 'P'; -#endif -#ifdef S_IFWHT - case S_IFWHT: - return 'w'; -#endif - } - - return '?'; -} - void *xmemdup(const void *src, size_t size) { void *ret = malloc(size); if (ret) { @@ -356,6 +322,40 @@ const char *xstrerror(int errnum) { return ret; } +/** Get the single character describing the given file type. */ +static char type_char(mode_t mode) { + switch (mode & S_IFMT) { + case S_IFREG: + return '-'; + case S_IFBLK: + return 'b'; + case S_IFCHR: + return 'c'; + case S_IFDIR: + return 'd'; + case S_IFLNK: + return 'l'; + case S_IFIFO: + return 'p'; + case S_IFSOCK: + return 's'; +#ifdef S_IFDOOR + case S_IFDOOR: + return 'D'; +#endif +#ifdef S_IFPORT + case S_IFPORT: + return 'P'; +#endif +#ifdef S_IFWHT + case S_IFWHT: + return 'w'; +#endif + } + + return '?'; +} + void xstrmode(mode_t mode, char str[11]) { strcpy(str, "----------"); @@ -637,8 +637,14 @@ error: return NULL; } +#if BFS_HAS_STRTOFFLAGS +# define BFS_STRTOFFLAGS strtofflags +#elif BFS_HAS_STRING_TO_FLAGS +# define BFS_STRTOFFLAGS string_to_flags +#endif + int xstrtofflags(const char **str, unsigned long long *set, unsigned long long *clear) { -#if BSD && !__GNU__ +#ifdef BFS_STRTOFFLAGS char *str_arg = (char *)*str; #if __OpenBSD__ @@ -649,11 +655,7 @@ int xstrtofflags(const char **str, unsigned long long *set, unsigned long long * bfs_fflags_t set_arg = 0; bfs_fflags_t clear_arg = 0; -#if __NetBSD__ - int ret = string_to_flags(&str_arg, &set_arg, &clear_arg); -#else - int ret = strtofflags(&str_arg, &set_arg, &clear_arg); -#endif + int ret = BFS_STRTOFFLAGS(&str_arg, &set_arg, &clear_arg); *str = str_arg; *set = set_arg; @@ -663,12 +665,27 @@ int xstrtofflags(const char **str, unsigned long long *set, unsigned long long * errno = EINVAL; } return ret; -#else // !BSD +#else // !BFS_STRTOFFLAGS errno = ENOTSUP; return -1; #endif } +long xsysconf(int name) { +#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 + __msan_scoped_enable_interceptor_checks(); +#endif + + return ret; +} + size_t asciilen(const char *str) { return asciinlen(str, strlen(str)); } diff --git a/src/bfstd.h b/src/bfstd.h index f91e380..d06bbd9 100644 --- a/src/bfstd.h +++ b/src/bfstd.h @@ -410,6 +410,11 @@ char *xconfstr(int name); */ int xstrtofflags(const char **str, unsigned long long *set, unsigned long long *clear); +/** + * Wrapper for sysconf() that works around an MSan bug. + */ +long xsysconf(int name); + #include <wchar.h> /** @@ -1281,7 +1281,7 @@ static int bftw_pin_parent(struct bftw_state *state, struct bftw_file *file) { int fd = parent->fd; if (fd < 0) { - bfs_static_assert(AT_FDCWD != -1); + bfs_static_assert((int)AT_FDCWD != -1); return -1; } @@ -1298,7 +1298,7 @@ static int bftw_ioq_opendir(struct bftw_state *state, struct bftw_file *file) { } int dfd = bftw_pin_parent(state, file); - if (dfd < 0 && dfd != AT_FDCWD) { + if (dfd < 0 && dfd != (int)AT_FDCWD) { goto fail; } @@ -1450,7 +1450,7 @@ static int bftw_ioq_stat(struct bftw_state *state, struct bftw_file *file) { } int dfd = bftw_pin_parent(state, file); - if (dfd < 0 && dfd != AT_FDCWD) { + if (dfd < 0 && dfd != (int)AT_FDCWD) { goto fail; } @@ -54,7 +54,7 @@ struct BFTW { /** The file type. */ enum bfs_type type; - /** The errno that occurred, if type == BFTW_ERROR. */ + /** The errno that occurred, if type == BFS_ERROR. */ int error; /** A parent file descriptor for the *at() family of calls. */ @@ -104,7 +104,7 @@ const struct bfs_stat *bftw_cached_stat(const struct BFTW *ftwbuf, enum bfs_stat * @param flags * flags for bfs_stat(). Pass ftwbuf->stat_flags for the default flags. * @return - * The type of the file, or BFTW_ERROR if an error occurred. + * The type of the file, or BFS_ERROR if an error occurred. */ enum bfs_type bftw_type(const struct BFTW *ftwbuf, enum bfs_stat_flags flags); diff --git a/src/color.c b/src/color.c index f004bf2..a0e553f 100644 --- a/src/color.c +++ b/src/color.c @@ -161,8 +161,13 @@ static struct esc_seq **get_esc(const struct colors *colors, const char *name) { return leaf ? leaf->value : NULL; } +/** Append an escape sequence to a string. */ +static int cat_esc(dchar **dstr, const struct esc_seq *seq) { + return dstrxcat(dstr, seq->seq, seq->len); +} + /** Set a named escape sequence. */ -static int set_esc(struct colors *colors, const char *name, char *value) { +static int set_esc(struct colors *colors, const char *name, dchar *value) { struct esc_seq **field = get_esc(colors, name); if (!field) { return 0; @@ -587,8 +592,8 @@ static int parse_gnu_ls_colors(struct colors *colors, const char *ls_colors) { // All-zero values should be treated like NULL, to fall // back on any other relevant coloring for that file - char *esc = value; - if (strspn(value, "0") == strlen(value) + dchar *esc = value; + if (strspn(value, "0") == dstrlen(value) && strcmp(key, "rs") != 0 && strcmp(key, "lc") != 0 && strcmp(key, "rc") != 0 @@ -693,6 +698,20 @@ struct colors *parse_colors(void) { colors->link->len = 0; } + // Pre-compute the reset escape sequence + if (!colors->endcode) { + dchar *ec = dstralloc(0); + if (!ec + || cat_esc(&ec, colors->leftcode) != 0 + || cat_esc(&ec, colors->reset) != 0 + || cat_esc(&ec, colors->rightcode) != 0 + || set_esc(colors, "ec", ec) != 0) { + dstrfree(ec); + goto fail; + } + dstrfree(ec); + } + return colors; fail: @@ -727,10 +746,11 @@ CFILE *cfwrap(FILE *file, const struct colors *colors, bool close) { } cfile->file = file; + cfile->fd = fileno(file); cfile->need_reset = false; cfile->close = close; - if (isatty(fileno(file))) { + if (isatty(cfile->fd)) { cfile->colors = colors; } else { cfile->colors = NULL; @@ -874,7 +894,7 @@ error: /** Print an escape sequence chunk. */ static int print_esc_chunk(CFILE *cfile, const struct esc_seq *esc) { - return dstrxcat(&cfile->buffer, esc->seq, esc->len); + return cat_esc(&cfile->buffer, esc); } /** Print an ANSI escape sequence. */ @@ -908,12 +928,7 @@ static int print_reset(CFILE *cfile) { } cfile->need_reset = false; - const struct colors *colors = cfile->colors; - if (colors->endcode) { - return print_esc_chunk(cfile, colors->endcode); - } else { - return print_esc(cfile, colors->reset); - } + return print_esc_chunk(cfile, cfile->colors->endcode); } /** Print a shell-escaped string. */ @@ -961,7 +976,7 @@ static ssize_t first_broken_offset(const char *path, const struct BFTW *ftwbuf, } else { // We're in print_link_target(), so resolve relative to the link's parent directory at_fd = ftwbuf->at_fd; - if (at_fd == AT_FDCWD && path[0] != '/') { + if (at_fd == (int)AT_FDCWD && path[0] != '/') { at_path = dstrndup(ftwbuf->path, ftwbuf->nameoff); if (at_path && dstrncat(&at_path, path, max) != 0) { ret = -1; @@ -1390,3 +1405,14 @@ int cfprintf(CFILE *cfile, const char *format, ...) { va_end(args); return ret; } + +int cfreset(CFILE *cfile) { + const struct colors *colors = cfile->colors; + if (!colors) { + return 0; + } + + const struct esc_seq *esc = colors->endcode; + size_t ret = xwrite(cfile->fd, esc->seq, esc->len); + return ret == esc->len ? 0 : -1; +} diff --git a/src/color.h b/src/color.h index 3278cd6..3550888 100644 --- a/src/color.h +++ b/src/color.h @@ -42,6 +42,8 @@ typedef struct CFILE { const struct colors *colors; /** A buffer for colored formatting. */ dchar *buffer; + /** Cached file descriptor number. */ + int fd; /** Whether the next ${rs} is actually necessary. */ bool need_reset; /** Whether to close the underlying stream. */ @@ -108,4 +110,9 @@ int cfprintf(CFILE *cfile, const char *format, ...); attr(printf(2, 0)) int cvfprintf(CFILE *cfile, const char *format, va_list args); +/** + * Reset the TTY state when terminating abnormally (async-signal-safe). + */ +int cfreset(CFILE *cfile); + #endif // BFS_COLOR_H @@ -3,24 +3,27 @@ #include "ctx.h" #include "alloc.h" +#include "bfstd.h" #include "color.h" #include "diag.h" #include "expr.h" #include "list.h" #include "mtab.h" #include "pwcache.h" +#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 <unistd.h> /** Get the initial value for ctx->threads (-j). */ static int bfs_nproc(void) { - long nproc = sysconf(_SC_NPROCESSORS_ONLN); + long nproc = xsysconf(_SC_NPROCESSORS_ONLN); if (nproc < 1) { nproc = 1; @@ -98,13 +101,20 @@ struct bfs_ctx_file { CFILE *cfile; /** The path to the file (for diagnostics). */ const char *path; + /** Signal hook to send a reset escape sequence. */ + struct sighook *hook; /** Remembers I/O errors, to propagate them to the exit status. */ int error; }; +/** Call cfreset() on a tracked file. */ +static void cfreset_hook(int sig, siginfo_t *info, void *arg) { + cfreset(arg); +} + CFILE *bfs_ctx_dedup(struct bfs_ctx *ctx, CFILE *cfile, const char *path) { struct bfs_stat sb; - if (bfs_stat(fileno(cfile->file), NULL, 0, &sb) != 0) { + if (bfs_stat(cfile->fd, NULL, 0, &sb) != 0) { return NULL; } @@ -124,19 +134,31 @@ CFILE *bfs_ctx_dedup(struct bfs_ctx *ctx, CFILE *cfile, const char *path) { leaf->value = ctx_file = ALLOC(struct bfs_ctx_file); if (!ctx_file) { - trie_remove(&ctx->files, leaf); - return NULL; + goto fail; } ctx_file->cfile = cfile; ctx_file->path = path; ctx_file->error = 0; + ctx_file->hook = NULL; + + if (cfile->colors) { + ctx_file->hook = atsigexit(cfreset_hook, cfile); + if (!ctx_file->hook) { + goto fail; + } + } if (cfile != ctx->cout && cfile != ctx->cerr) { ++ctx->nfiles; } return cfile; + +fail: + trie_remove(&ctx->files, leaf); + free(ctx_file); + return NULL; } void bfs_ctx_flush(const struct bfs_ctx *ctx) { @@ -156,7 +178,7 @@ void bfs_ctx_flush(const struct bfs_ctx *ctx) { const char *path = ctx_file->path; if (path) { - bfs_error(ctx, "'%s': %m.\n", path); + bfs_error(ctx, "%pq: %m.\n", path); } else if (cfile == ctx->cout) { bfs_error(ctx, "(standard output): %m.\n"); } @@ -188,30 +210,49 @@ static int bfs_ctx_fflush(CFILE *cfile) { static int bfs_ctx_fclose(struct bfs_ctx *ctx, struct bfs_ctx_file *ctx_file) { CFILE *cfile = ctx_file->cfile; - if (cfile == ctx->cout) { - // Will be checked later - return 0; - } else if (cfile == ctx->cerr) { - // Writes to stderr are allowed to fail silently, unless the same file was used by - // -fprint, -fls, etc. - if (ctx_file->path) { - return bfs_ctx_fflush(cfile); - } else { - return 0; - } - } - + // Writes to stderr are allowed to fail silently, unless the same file + // was used by -fprint, -fls, etc. + bool silent = cfile == ctx->cerr && !ctx_file->path; int ret = 0, error = 0; - if (ferror(cfile->file)) { + + if (ctx_file->error) { + // An error was previously reported during bfs_ctx_flush() ret = -1; - error = EIO; + error = ctx_file->error; } - if (cfclose(cfile) != 0) { + + // Flush the file just before we remove the hook, to maximize the chance + // we leave the TTY in a good state + if (bfs_ctx_fflush(cfile) != 0) { ret = -1; error = errno; } - errno = error; + if (ctx_file->hook) { + sigunhook(ctx_file->hook); + } + + // Close the CFILE, except for stdio streams, which are closed later + if (cfile != ctx->cout && cfile != ctx->cerr) { + if (cfclose(cfile) != 0) { + ret = -1; + error = errno; + } + } + + if (silent) { + ret = 0; + } + + if (ret != 0 && ctx->cerr) { + if (ctx_file->path) { + bfs_error(ctx, "%pq: %s.\n", ctx_file->path, xstrerror(error)); + } else if (cfile == ctx->cout) { + bfs_error(ctx, "(standard output): %s.\n", xstrerror(error)); + } + } + + free(ctx_file); return ret; } @@ -229,33 +270,14 @@ int bfs_ctx_free(struct bfs_ctx *ctx) { for_trie (leaf, &ctx->files) { struct bfs_ctx_file *ctx_file = leaf->value; - - if (ctx_file->error) { - // An error was previously reported during bfs_ctx_flush() - ret = -1; - } - if (bfs_ctx_fclose(ctx, ctx_file) != 0) { - if (cerr) { - bfs_error(ctx, "'%s': %m.\n", ctx_file->path); - } ret = -1; } - - free(ctx_file); } trie_destroy(&ctx->files); - if (cout && bfs_ctx_fflush(cout) != 0) { - if (cerr) { - bfs_error(ctx, "(standard output): %m.\n"); - } - ret = -1; - } - cfclose(cout); cfclose(cerr); - free_colors(ctx->colors); for_slist (struct bfs_expr, expr, &ctx->expr_list, freelist) { @@ -38,6 +38,10 @@ noreturn void bfs_abortf(const struct bfs_loc *loc, const char *format, ...) { abort(); } +const char *bfs_errstr(void) { + return xstrerror(errno); +} + const char *debug_flag_name(enum debug_flags flag) { switch (flag) { case DEBUG_COST: @@ -55,6 +55,20 @@ void bfs_diagf(const struct bfs_loc *loc, const char *format, ...); #define bfs_diag(...) bfs_diagf(bfs_location(), __VA_ARGS__) /** + * Get the last error message. + */ +const char *bfs_errstr(void); + +/** + * Print a diagnostic message including the last error. + */ +#define bfs_ediag(...) \ + bfs_ediag_("" __VA_ARGS__, bfs_errstr()) + +#define bfs_ediag_(format, ...) \ + bfs_diag(sizeof(format) > 1 ? format ": %s" : "%s", __VA_ARGS__) + +/** * Print a message to standard error and abort. */ attr(cold, printf(2, 3)) @@ -63,15 +77,27 @@ noreturn void bfs_abortf(const struct bfs_loc *loc, const char *format, ...); /** * Unconditional abort with a message. */ -#define bfs_abort(...) bfs_abortf(bfs_location(), __VA_ARGS__) +#define bfs_abort(...) \ + bfs_abortf(bfs_location(), __VA_ARGS__) + +/** + * Abort with a message including the last error. + */ +#define bfs_eabort(...) \ + bfs_eabort_("" __VA_ARGS__, bfs_errstr()) + +#define bfs_eabort_(format, ...) \ + bfs_abort(sizeof(format) > 1 ? format ": %s" : "%s", __VA_ARGS__) /** * Abort in debug builds; no-op in release builds. */ #ifdef NDEBUG # define bfs_bug(...) ((void)0) +# define bfs_ebug(...) ((void)0) #else # define bfs_bug bfs_abort +# define bfs_ebug bfs_eabort #endif /** @@ -88,12 +114,27 @@ noreturn void bfs_abortf(const struct bfs_loc *loc, const char *format, ...); str, __VA_ARGS__)) /** + * Unconditional assert, including the last error. + */ +#define bfs_everify(...) \ + bfs_everify_(#__VA_ARGS__, __VA_ARGS__, "", bfs_errstr()) + +#define bfs_everify_(str, cond, format, ...) \ + ((cond) ? (void)0 : bfs_abort( \ + sizeof(format) > 1 \ + ? "%.0s" format "%s: %s" \ + : "Assertion failed: `%s`: %s", \ + str, __VA_ARGS__)) + +/** * Assert in debug builds; no-op in release builds. */ #ifdef NDEBUG # define bfs_assert(...) ((void)0) +# define bfs_eassert(...) ((void)0) #else # define bfs_assert bfs_verify +# define bfs_eassert bfs_everify #endif struct bfs_ctx; @@ -25,7 +25,13 @@ static ssize_t bfs_getdents(int fd, void *buf, size_t size) { sanitize_uninit(buf, size); -#if BFS_HAS_GETDENTS +#if BFS_HAS_POSIX_GETDENTS + int flags = 0; +# ifdef DT_FORCE_TYPE + flags |= DT_FORCE_TYPE; +# endif + ssize_t ret = posix_getdents(fd, buf, size, flags); +#elif BFS_HAS_GETDENTS ssize_t ret = getdents(fd, buf, size); #elif BFS_HAS_GETDENTS64 ssize_t ret = getdents64(fd, buf, size); @@ -44,11 +50,13 @@ static ssize_t bfs_getdents(int fd, void *buf, size_t size) { #endif // BFS_USE_GETDENTS -#if BFS_USE_GETDENTS && !BFS_HAS_GETDENTS /** Directory entry type for bfs_getdents() */ -typedef struct dirent64 sys_dirent; -#else +#if !BFS_USE_GETDENTS || BFS_HAS_GETDENTS typedef struct dirent sys_dirent; +#elif BFS_HAS_POSIX_GETDENTS +typedef struct posix_dent sys_dirent; +#else +typedef struct dirent64 sys_dirent; #endif enum bfs_type bfs_mode_to_type(mode_t mode) { @@ -8,14 +8,19 @@ #ifndef BFS_DIR_H #define BFS_DIR_H +#include "prelude.h" #include <sys/types.h> /** * Whether the implementation uses the getdents() syscall directly, rather than * libc's readdir(). */ -#if !defined(BFS_USE_GETDENTS) && (__linux__ || __FreeBSD__) -# define BFS_USE_GETDENTS (BFS_HAS_GETDENTS || BFS_HAS_GETDENTS64 | BFS_HAS_GETDENTS64_SYSCALL) +#ifndef BFS_USE_GETDENTS +# if BFS_HAS_POSIX_GETDENTS +# define BFS_USE_GETDENTS true +# elif __linux__ || __FreeBSD__ +# define BFS_USE_GETDENTS (BFS_HAS_GETDENTS || BFS_HAS_GETDENTS64 | BFS_HAS_GETDENTS64_SYSCALL) +# endif #endif /** diff --git a/src/dstring.c b/src/dstring.c index 913dda8..86ab646 100644 --- a/src/dstring.c +++ b/src/dstring.c @@ -174,7 +174,7 @@ int dstrxcpy(dchar **dest, const char *src, size_t len) { return 0; } -char *dstrprintf(const char *format, ...) { +dchar *dstrprintf(const char *format, ...) { va_list args; va_start(args, format); @@ -184,7 +184,7 @@ char *dstrprintf(const char *format, ...) { return str; } -char *dstrvprintf(const char *format, va_list args) { +dchar *dstrvprintf(const char *format, va_list args) { // Guess a capacity to try to avoid reallocating dchar *str = dstralloc(2 * strlen(format)); if (!str) { @@ -245,6 +245,7 @@ int dstrvcatf(dchar **str, const char *format, va_list args) { return 0; fail: + va_end(copy); *tail = '\0'; return -1; } diff --git a/src/dstring.h b/src/dstring.h index 9ea7eb9..14e1d3e 100644 --- a/src/dstring.h +++ b/src/dstring.h @@ -244,7 +244,7 @@ int dstrxcpy(dchar **dest, const char *str, size_t len); * The created string, or NULL on failure. */ attr(printf(1, 2)) -char *dstrprintf(const char *format, ...); +dchar *dstrprintf(const char *format, ...); /** * Create a dynamic string from a format string and a va_list. @@ -257,7 +257,7 @@ char *dstrprintf(const char *format, ...); * The created string, or NULL on failure. */ attr(printf(1, 0)) -char *dstrvprintf(const char *format, va_list args); +dchar *dstrvprintf(const char *format, va_list args); /** * Format some text onto the end of a dynamic string. @@ -445,38 +445,42 @@ bool eval_depth(const struct bfs_expr *expr, struct bfs_eval *state) { * -empty test. */ bool eval_empty(const struct bfs_expr *expr, struct bfs_eval *state) { - bool ret = false; const struct BFTW *ftwbuf = state->ftwbuf; + const struct bfs_stat *statbuf; + struct bfs_dir *dir; + + switch (ftwbuf->type) { + case BFS_REG: + statbuf = eval_stat(state); + return statbuf && statbuf->size == 0; - if (ftwbuf->type == BFS_DIR) { - struct bfs_dir *dir = bfs_allocdir(); + case BFS_DIR: + dir = bfs_allocdir(); if (!dir) { - eval_report_error(state); - return ret; + goto error; } if (bfs_opendir(dir, ftwbuf->at_fd, ftwbuf->at_path, 0) != 0) { - eval_report_error(state); - return ret; + goto error; } int did_read = bfs_readdir(dir, NULL); + bfs_closedir(dir); + if (did_read < 0) { - eval_report_error(state); - } else { - ret = !did_read; + goto error; } - bfs_closedir(dir); free(dir); - } else if (ftwbuf->type == BFS_REG) { - const struct bfs_stat *statbuf = eval_stat(state); - if (statbuf) { - ret = statbuf->size == 0; - } - } + return did_read == 0; + error: + eval_report_error(state); + free(dir); + return false; - return ret; + default: + return false; + } } /** @@ -1266,7 +1270,7 @@ static void debug_stat(const struct bfs_ctx *ctx, const struct BFTW *ftwbuf, enu bfs_debug_prefix(ctx, DEBUG_STAT); fprintf(stderr, "bfs_stat("); - if (ftwbuf->at_fd == AT_FDCWD) { + if (ftwbuf->at_fd == (int)AT_FDCWD) { fprintf(stderr, "AT_FDCWD"); } else { size_t baselen = strlen(ftwbuf->path) - strlen(ftwbuf->at_path); @@ -56,7 +56,7 @@ static size_t bfs_exec_arg_size(const char *arg) { /** Determine the maximum argv size. */ static size_t bfs_exec_arg_max(const struct bfs_exec *execbuf) { - long arg_max = sysconf(_SC_ARG_MAX); + long arg_max = xsysconf(_SC_ARG_MAX); bfs_exec_debug(execbuf, "ARG_MAX: %ld according to sysconf()\n", arg_max); if (arg_max < 0) { arg_max = BFS_EXEC_ARG_MAX; @@ -82,7 +82,7 @@ static size_t bfs_exec_arg_max(const struct bfs_exec *execbuf) { // Assume arguments are counted with the granularity of a single page, // so allow a one page cushion to account for rounding up - long page_size = sysconf(_SC_PAGESIZE); + long page_size = xsysconf(_SC_PAGESIZE); if (page_size < 4096) { page_size = 4096; } @@ -268,7 +268,7 @@ static int bfs_exec_openwd(struct bfs_exec *execbuf, const struct BFTW *ftwbuf) bfs_assert(execbuf->wd_fd < 0); bfs_assert(!execbuf->wd_path); - if (ftwbuf->at_fd != AT_FDCWD) { + if (ftwbuf->at_fd != (int)AT_FDCWD) { // Rely on at_fd being the immediate parent bfs_assert(xbaseoff(ftwbuf->at_path) == 0); diff --git a/src/fsade.c b/src/fsade.c index 51b629f..7310141 100644 --- a/src/fsade.c +++ b/src/fsade.c @@ -41,7 +41,7 @@ static const char *fake_at(const struct BFTW *ftwbuf) { static atomic int proc_works = -1; dchar *path = NULL; - if (ftwbuf->at_fd == AT_FDCWD || load(&proc_works, relaxed) == 0) { + if (ftwbuf->at_fd == (int)AT_FDCWD || load(&proc_works, relaxed) == 0) { goto fail; } @@ -121,11 +121,23 @@ static bool is_absence_error(int error) { #if BFS_CAN_CHECK_ACL +#if BFS_HAS_ACL_GET_FILE + /** Unified interface for incompatible acl_get_entry() implementations. */ static int bfs_acl_entry(acl_t acl, int which, acl_entry_t *entry) { -#if __DragonFly__ && !defined(ACL_FIRST_ENTRY) && !defined(ACL_NEXT_ENTRY) -# define ACL_FIRST_ENTRY 0 -# define ACL_NEXT_ENTRY 1 +#if BFS_HAS_ACL_GET_ENTRY + int ret = acl_get_entry(acl, which, entry); +# if __APPLE__ + // POSIX.1e specifies a return value of 1 for success, but macOS returns 0 instead + return !ret; +# else + return ret; +# endif +#elif __DragonFly__ +# if !defined(ACL_FIRST_ENTRY) && !defined(ACL_NEXT_ENTRY) +# define ACL_FIRST_ENTRY 0 +# define ACL_NEXT_ENTRY 1 +# endif switch (which) { case ACL_FIRST_ENTRY: @@ -142,24 +154,22 @@ static int bfs_acl_entry(acl_t acl, int which, acl_entry_t *entry) { acl_entry_t last = &acl->acl_entry[acl->acl_cnt]; return *entry == last; #else - int ret = acl_get_entry(acl, which, entry); -# if __APPLE__ - // POSIX.1e specifies a return value of 1 for success, but macOS returns 0 instead - return !ret; -# else - return ret; -# endif + errno = ENOTSUP; + return -1; #endif } /** Unified interface for acl_get_tag_type(). */ attr(maybe_unused) static int bfs_acl_tag_type(acl_entry_t entry, acl_tag_t *tag) { -#if __DragonFly__ +#if BFS_HAS_ACL_GET_TAG_TYPE + return acl_get_tag_type(entry, tag); +#elif __DragonFly__ *tag = entry->ae_tag; return 0; #else - return acl_get_tag_type(entry, tag); + errno = ENOTSUP; + return -1; #endif } @@ -218,29 +228,35 @@ static int bfs_check_acl_type(acl_t acl, acl_type_t type) { #endif } +#endif // BFS_HAS_ACL_GET_FILE + int bfs_check_acl(const struct BFTW *ftwbuf) { + if (ftwbuf->type == BFS_LNK) { + return 0; + } + + const char *path = fake_at(ftwbuf); + +#if BFS_HAS_ACL_TRIVIAL + int ret = acl_trivial(path); + int error = errno; +#elif BFS_HAS_ACL_GET_FILE static const acl_type_t acl_types[] = { -#if __APPLE__ +# if __APPLE__ // macOS gives EINVAL for either of the two standard ACL types, // supporting only ACL_TYPE_EXTENDED ACL_TYPE_EXTENDED, -#else +# else // The two standard POSIX.1e ACL types ACL_TYPE_ACCESS, ACL_TYPE_DEFAULT, -#endif +# endif -#ifdef ACL_TYPE_NFS4 +# ifdef ACL_TYPE_NFS4 ACL_TYPE_NFS4, -#endif +# endif }; - if (ftwbuf->type == BFS_LNK) { - return 0; - } - - const char *path = fake_at(ftwbuf); - int ret = -1, error = 0; for (size_t i = 0; i < countof(acl_types) && ret <= 0; ++i) { acl_type_t type = acl_types[i]; @@ -264,6 +280,7 @@ int bfs_check_acl(const struct BFTW *ftwbuf) { error = errno; acl_free(acl); } +#endif free_fake_at(ftwbuf, path); errno = error; @@ -327,17 +344,62 @@ int bfs_check_capabilities(const struct BFTW *ftwbuf) { #if BFS_CAN_CHECK_XATTRS +#if BFS_USE_SYS_EXTATTR_H + +/** Wrapper for extattr_list_{file,link}. */ +static ssize_t bfs_extattr_list(const char *path, enum bfs_type type, int namespace) { + if (type == BFS_LNK) { +#if BFS_HAS_EXTATTR_LIST_LINK + return extattr_list_link(path, namespace, NULL, 0); +#elif BFS_HAS_EXTATTR_GET_LINK + return extattr_get_link(path, namespace, "", NULL, 0); +#else + return 0; +#endif + } + +#if BFS_HAS_EXTATTR_LIST_FILE + return extattr_list_file(path, namespace, NULL, 0); +#elif BFS_HAS_EXTATTR_GET_FILE + // From man extattr(2): + // + // In earlier versions of this API, passing an empty string for the + // attribute name to extattr_get_file() would return the list of attributes + // defined for the target object. This interface has been deprecated in + // preference to using the explicit list API, and should not be used. + return extattr_get_file(path, namespace, "", NULL, 0); +#else + return 0; +#endif +} + +/** Wrapper for extattr_get_{file,link}. */ +static ssize_t bfs_extattr_get(const char *path, enum bfs_type type, int namespace, const char *name) { + if (type == BFS_LNK) { +#if BFS_HAS_EXTATTR_GET_LINK + return extattr_get_link(path, namespace, name, NULL, 0); +#else + return 0; +#endif + } + +#if BFS_HAS_EXTATTR_GET_FILE + return extattr_get_file(path, namespace, name, NULL, 0); +#else + return 0; +#endif +} + +#endif // BFS_USE_SYS_EXTATTR_H + int bfs_check_xattrs(const struct BFTW *ftwbuf) { const char *path = fake_at(ftwbuf); ssize_t len; #if BFS_USE_SYS_EXTATTR_H - ssize_t (*extattr_list)(const char *, int, void *, size_t) = - ftwbuf->type == BFS_LNK ? extattr_list_link : extattr_list_file; - - len = extattr_list(path, EXTATTR_NAMESPACE_SYSTEM, NULL, 0); + len = bfs_extattr_list(path, ftwbuf->type, EXTATTR_NAMESPACE_SYSTEM); if (len <= 0) { - len = extattr_list(path, EXTATTR_NAMESPACE_USER, NULL, 0); + len = bfs_extattr_list(path, ftwbuf->type, EXTATTR_NAMESPACE_USER); } #elif __APPLE__ int options = ftwbuf->type == BFS_LNK ? XATTR_NOFOLLOW : 0; @@ -371,12 +433,9 @@ int bfs_check_xattr_named(const struct BFTW *ftwbuf, const char *name) { ssize_t len; #if BFS_USE_SYS_EXTATTR_H - ssize_t (*extattr_get)(const char *, int, const char *, void *, size_t) = - ftwbuf->type == BFS_LNK ? extattr_get_link : extattr_get_file; - - len = extattr_get(path, EXTATTR_NAMESPACE_SYSTEM, name, NULL, 0); + len = bfs_extattr_get(path, ftwbuf->type, EXTATTR_NAMESPACE_SYSTEM, name); if (len < 0) { - len = extattr_get(path, EXTATTR_NAMESPACE_USER, name, NULL, 0); + len = bfs_extattr_get(path, ftwbuf->type, EXTATTR_NAMESPACE_USER, name); } #elif __APPLE__ int options = ftwbuf->type == BFS_LNK ? XATTR_NOFOLLOW : 0; diff --git a/src/fsade.h b/src/fsade.h index 6300852..eefef9f 100644 --- a/src/fsade.h +++ b/src/fsade.h @@ -11,14 +11,9 @@ #include "prelude.h" -#define BFS_CAN_CHECK_ACL BFS_USE_SYS_ACL_H +#define BFS_CAN_CHECK_ACL (BFS_HAS_ACL_GET_FILE || BFS_HAS_ACL_TRIVIAL) -#if !defined(BFS_CAN_CHECK_CAPABILITIES) && BFS_USE_SYS_CAPABILITY_H -# include <sys/capability.h> -# ifdef CAP_CHOWN -# define BFS_CAN_CHECK_CAPABILITIES true -# endif -#endif +#define BFS_CAN_CHECK_CAPABILITIES BFS_USE_LIBCAP #define BFS_CAN_CHECK_CONTEXT BFS_USE_LIBSELINUX @@ -918,17 +918,14 @@ static void ioq_thread_join(struct ioq_thread *thread) { } struct ioq *ioq_create(size_t depth, size_t nthreads) { - struct ioq *ioq = ALLOC_FLEX(struct ioq, threads, nthreads); + struct ioq *ioq = ZALLOC_FLEX(struct ioq, threads, nthreads); if (!ioq) { goto fail; } ioq->depth = depth; - ioq->size = 0; - ioq->cancel = false; ARENA_INIT(&ioq->ents, struct ioq_ent); - #if BFS_USE_LIBURING && BFS_USE_STATX ARENA_INIT(&ioq->xbufs, struct statx); #endif @@ -36,6 +36,7 @@ * - mtab.[ch] (parses the system's mount table) * - pwcache.[ch] (a cache for the user/group tables) * - sanity.h (sanitizer interfaces) + * - sighook.[ch] (signal hooks) * - stat.[ch] (wraps stat(), or statx() on Linux) * - thread.h (multi-threading) * - trie.[ch] (a trie set/map implementation) @@ -13,11 +13,11 @@ #include <string.h> #include <sys/types.h> -#if !defined(BFS_USE_MNTENT) && BFS_USE_MNTENT_H +#if !defined(BFS_USE_MNTENT) && BFS_HAS_GETMNTENT_1 # define BFS_USE_MNTENT true -#elif !defined(BFS_USE_MNTINFO) && BSD +#elif !defined(BFS_USE_MNTINFO) && BFS_HAS_GETMNTINFO # define BFS_USE_MNTINFO true -#elif !defined(BFS_USE_MNTTAB) && __SVR4 +#elif !defined(BFS_USE_MNTTAB) && BFS_HAS_GETMNTENT_2 # define BFS_USE_MNTTAB true #endif @@ -27,7 +27,6 @@ # include <stdio.h> #elif BFS_USE_MNTINFO # include <sys/mount.h> -# include <sys/ucred.h> #elif BFS_USE_MNTTAB # include <stdio.h> # include <sys/mnttab.h> @@ -148,7 +147,7 @@ struct bfs_mtab *bfs_mtab_parse(void) { bfs_statfs *mntbuf; int size = getmntinfo(&mntbuf, MNT_WAIT); - if (size < 0) { + if (size <= 0) { error = errno; goto fail; } @@ -947,8 +947,12 @@ static struct bfs_expr *visit_shallow(struct bfs_opt *opt, struct bfs_expr *expr expr = general(opt, expr, visitor); } + if (!expr) { + return NULL; + } + visit_fn *specific = look_up_visitor(expr, visitor->table); - if (expr && specific) { + if (specific) { expr = specific(opt, expr, visitor); } diff --git a/src/prelude.h b/src/prelude.h index a23b167..72f88b0 100644 --- a/src/prelude.h +++ b/src/prelude.h @@ -49,12 +49,6 @@ extern const char bfs_version[]; #if __has_include(<paths.h>) # define BFS_HAS_PATHS_H true #endif -#if __has_include(<sys/acl.h>) -# define BFS_HAS_SYS_ACL_H true -#endif -#if __has_include(<sys/capability.h>) -# define BFS_HAS_SYS_CAPABILITY_H true -#endif #if __has_include(<sys/extattr.h>) # define BFS_HAS_SYS_EXTATTR_H true #endif @@ -81,8 +75,6 @@ extern const char bfs_version[]; #define BFS_HAS_MNTENT_H __GLIBC__ #define BFS_HAS_PATHS_H true -#define BFS_HAS_SYS_ACL_H true -#define BFS_HAS_SYS_CAPABILITY_H __linux__ #define BFS_HAS_SYS_EXTATTR_H __FreeBSD__ #define BFS_HAS_SYS_MKDEV_H false #define BFS_HAS_SYS_PARAM_H true @@ -99,14 +91,8 @@ extern const char bfs_version[]; #ifndef BFS_USE_PATHS_H # define BFS_USE_PATHS_H BFS_HAS_PATHS_H #endif -#ifndef BFS_USE_SYS_ACL_H -# define BFS_USE_SYS_ACL_H (BFS_HAS_SYS_ACL_H && !__illumos__ && (!__linux__ || BFS_USE_LIBACL)) -#endif -#ifndef BFS_USE_SYS_CAPABILITY_H -# define BFS_USE_SYS_CAPABILITY_H (BFS_HAS_SYS_CAPABILITY_H && !__FreeBSD__ && (!__linux__ || BFS_USE_LIBCAP)) -#endif #ifndef BFS_USE_SYS_EXTATTR_H -# define BFS_USE_SYS_EXTATTR_H (BFS_HAS_SYS_EXTATTR_H && !__DragonFly__) +# 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 @@ -141,21 +127,6 @@ extern const char bfs_version[]; # define __has_attribute(attr) false #endif -// Platform detection - -// Get the definition of BSD if available -#if BFS_USE_SYS_PARAM_H -# include <sys/param.h> -#endif - -#ifndef __GLIBC_PREREQ -# define __GLIBC_PREREQ(maj, min) false -#endif - -#ifndef __NetBSD_Prereq__ -# define __NetBSD_Prereq__(maj, min, patch) false -#endif - // Fundamental utilities /** @@ -182,14 +153,25 @@ extern const char bfs_version[]; #endif /** + * Polyfill max_align_t if we don't already have it. + */ +#if !BFS_HAS_MAX_ALIGN_T +typedef union { +# ifdef __BIGGEST_ALIGNMENT__ + alignas(__BIGGEST_ALIGNMENT__) char c; +# else + long double ld; + long long ll; + void *ptr; +# endif +} max_align_t; +#endif + +/** * Alignment specifier that avoids false sharing. */ #define cache_align alignas(FALSE_SHARING_SIZE) -#if __COSMOPOLITAN__ -typedef long double max_align_t; -#endif - // Wrappers for attributes /** @@ -247,6 +229,15 @@ typedef long double max_align_t; #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 + +/** * Annotates allocator-like functions. */ #if __has_attribute(malloc) diff --git a/src/printf.c b/src/printf.c index 4df399b..f8428f7 100644 --- a/src/printf.c +++ b/src/printf.c @@ -88,22 +88,20 @@ static bool should_color(CFILE *cfile, const struct bfs_fmt *fmt) { bfs_assert(ret >= 0 && (size_t)ret < sizeof(buf)); \ (void)ret -/** - * Common entry point for fprintf() with a dynamic format string. - */ -static int dyn_fprintf(FILE *file, const struct bfs_fmt *fmt, ...) { - va_list args; - va_start(args, fmt); - -#if __GNUC__ -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wformat-nonliteral" -#endif - int ret = vfprintf(file, fmt->str, args); -#if __GNUC__ -# pragma GCC diagnostic pop -#endif +/** Return a dynamic format string. */ +attr(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); + return str; +} +/** Wrapper for fprintf(). */ +attr(printf(3, 4)) +static int bfs_fprintf(CFILE *cfile, const struct bfs_fmt *fmt, const char *fake, ...) { + va_list args; + va_start(args, fake); + int ret = vfprintf(cfile->file, dyn_fmt(fmt->str, fake), args); va_end(args); return ret; } @@ -139,7 +137,7 @@ static int bfs_printf_ctime(CFILE *cfile, const struct bfs_fmt *fmt, const struc (long)ts->tv_nsec, 1900 + tm.tm_year); - return dyn_fprintf(cfile->file, fmt, buf); + return bfs_fprintf(cfile, fmt, "%s", buf); } /** %A, %B/%W, %C, %T: strftime() */ @@ -214,7 +212,7 @@ static int bfs_printf_strftime(CFILE *cfile, const struct bfs_fmt *fmt, const st bfs_assert(ret >= 0 && (size_t)ret < sizeof(buf)); (void)ret; - return dyn_fprintf(cfile->file, fmt, buf); + return bfs_fprintf(cfile, fmt, "%s", buf); } /** %b: blocks */ @@ -226,12 +224,12 @@ static int bfs_printf_b(CFILE *cfile, const struct bfs_fmt *fmt, const struct BF uintmax_t blocks = ((uintmax_t)statbuf->blocks * BFS_STAT_BLKSIZE + 511) / 512; BFS_PRINTF_BUF(buf, "%ju", blocks); - return dyn_fprintf(cfile->file, fmt, buf); + return bfs_fprintf(cfile, fmt, "%s", buf); } /** %d: depth */ static int bfs_printf_d(CFILE *cfile, const struct bfs_fmt *fmt, const struct BFTW *ftwbuf) { - return dyn_fprintf(cfile->file, fmt, (intmax_t)ftwbuf->depth); + return bfs_fprintf(cfile, fmt, "%jd", (intmax_t)ftwbuf->depth); } /** %D: device */ @@ -242,7 +240,7 @@ static int bfs_printf_D(CFILE *cfile, const struct bfs_fmt *fmt, const struct BF } BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)statbuf->dev); - return dyn_fprintf(cfile->file, fmt, buf); + return bfs_fprintf(cfile, fmt, "%s", buf); } /** %f: file name */ @@ -250,7 +248,7 @@ static int bfs_printf_f(CFILE *cfile, const struct bfs_fmt *fmt, const struct BF if (should_color(cfile, fmt)) { return cfprintf(cfile, "%pF", ftwbuf); } else { - return dyn_fprintf(cfile->file, fmt, ftwbuf->path + ftwbuf->nameoff); + return bfs_fprintf(cfile, fmt, "%s", ftwbuf->path + ftwbuf->nameoff); } } @@ -266,7 +264,7 @@ static int bfs_printf_F(CFILE *cfile, const struct bfs_fmt *fmt, const struct BF return -1; } - return dyn_fprintf(cfile->file, fmt, type); + return bfs_fprintf(cfile, fmt, "%s", type); } /** %G: gid */ @@ -277,7 +275,7 @@ static int bfs_printf_G(CFILE *cfile, const struct bfs_fmt *fmt, const struct BF } BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)statbuf->gid); - return dyn_fprintf(cfile->file, fmt, buf); + return bfs_fprintf(cfile, fmt, "%s", buf); } /** %g: group name */ @@ -293,7 +291,7 @@ static int bfs_printf_g(CFILE *cfile, const struct bfs_fmt *fmt, const struct BF return bfs_printf_G(cfile, fmt, ftwbuf); } - return dyn_fprintf(cfile->file, fmt, grp->gr_name); + return bfs_fprintf(cfile, fmt, "%s", grp->gr_name); } /** %h: leading directories */ @@ -322,7 +320,7 @@ static int bfs_printf_h(CFILE *cfile, const struct bfs_fmt *fmt, const struct BF if (should_color(cfile, fmt)) { ret = cfprintf(cfile, "${di}%pQ${rs}", buf); } else { - ret = dyn_fprintf(cfile->file, fmt, buf); + ret = bfs_fprintf(cfile, fmt, "%s", buf); } free(copy); @@ -338,7 +336,7 @@ static int bfs_printf_H(CFILE *cfile, const struct bfs_fmt *fmt, const struct BF return cfprintf(cfile, "${di}%pQ${rs}", ftwbuf->root); } } else { - return dyn_fprintf(cfile->file, fmt, ftwbuf->root); + return bfs_fprintf(cfile, fmt, "%s", ftwbuf->root); } } @@ -350,7 +348,7 @@ static int bfs_printf_i(CFILE *cfile, const struct bfs_fmt *fmt, const struct BF } BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)statbuf->ino); - return dyn_fprintf(cfile->file, fmt, buf); + return bfs_fprintf(cfile, fmt, "%s", buf); } /** %k: 1K blocks */ @@ -362,7 +360,7 @@ static int bfs_printf_k(CFILE *cfile, const struct bfs_fmt *fmt, const struct BF uintmax_t blocks = ((uintmax_t)statbuf->blocks * BFS_STAT_BLKSIZE + 1023) / 1024; BFS_PRINTF_BUF(buf, "%ju", blocks); - return dyn_fprintf(cfile->file, fmt, buf); + return bfs_fprintf(cfile, fmt, "%s", buf); } /** %l: link target */ @@ -384,7 +382,7 @@ static int bfs_printf_l(CFILE *cfile, const struct bfs_fmt *fmt, const struct BF } } - int ret = dyn_fprintf(cfile->file, fmt, target); + int ret = bfs_fprintf(cfile, fmt, "%s", target); free(buf); return ret; } @@ -396,7 +394,7 @@ static int bfs_printf_m(CFILE *cfile, const struct bfs_fmt *fmt, const struct BF return -1; } - return dyn_fprintf(cfile->file, fmt, (unsigned int)(statbuf->mode & 07777)); + return bfs_fprintf(cfile, fmt, "%o", (unsigned int)(statbuf->mode & 07777)); } /** %M: symbolic mode */ @@ -408,7 +406,7 @@ static int bfs_printf_M(CFILE *cfile, const struct bfs_fmt *fmt, const struct BF char buf[11]; xstrmode(statbuf->mode, buf); - return dyn_fprintf(cfile->file, fmt, buf); + return bfs_fprintf(cfile, fmt, "%s", buf); } /** %n: link count */ @@ -419,7 +417,7 @@ static int bfs_printf_n(CFILE *cfile, const struct bfs_fmt *fmt, const struct BF } BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)statbuf->nlink); - return dyn_fprintf(cfile->file, fmt, buf); + return bfs_fprintf(cfile, fmt, "%s", buf); } /** %p: full path */ @@ -427,7 +425,7 @@ static int bfs_printf_p(CFILE *cfile, const struct bfs_fmt *fmt, const struct BF if (should_color(cfile, fmt)) { return cfprintf(cfile, "%pP", ftwbuf); } else { - return dyn_fprintf(cfile->file, fmt, ftwbuf->path); + return bfs_fprintf(cfile, fmt, "%s", ftwbuf->path); } } @@ -448,7 +446,7 @@ static int bfs_printf_P(CFILE *cfile, const struct bfs_fmt *fmt, const struct BF copybuf.nameoff -= offset; return cfprintf(cfile, "%pP", ©buf); } else { - return dyn_fprintf(cfile->file, fmt, ftwbuf->path + offset); + return bfs_fprintf(cfile, fmt, "%s", ftwbuf->path + offset); } } @@ -460,7 +458,7 @@ static int bfs_printf_s(CFILE *cfile, const struct bfs_fmt *fmt, const struct BF } BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)statbuf->size); - return dyn_fprintf(cfile->file, fmt, buf); + return bfs_fprintf(cfile, fmt, "%s", buf); } /** %S: sparseness */ @@ -476,7 +474,7 @@ static int bfs_printf_S(CFILE *cfile, const struct bfs_fmt *fmt, const struct BF } else { sparsity = (double)BFS_STAT_BLKSIZE * statbuf->blocks / statbuf->size; } - return dyn_fprintf(cfile->file, fmt, sparsity); + return bfs_fprintf(cfile, fmt, "%g", sparsity); } /** %U: uid */ @@ -487,7 +485,7 @@ static int bfs_printf_U(CFILE *cfile, const struct bfs_fmt *fmt, const struct BF } BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)statbuf->uid); - return dyn_fprintf(cfile->file, fmt, buf); + return bfs_fprintf(cfile, fmt, "%s", buf); } /** %u: user name */ @@ -503,7 +501,7 @@ static int bfs_printf_u(CFILE *cfile, const struct bfs_fmt *fmt, const struct BF return bfs_printf_U(cfile, fmt, ftwbuf); } - return dyn_fprintf(cfile->file, fmt, pwd->pw_name); + return bfs_fprintf(cfile, fmt, "%s", pwd->pw_name); } static const char *bfs_printf_type(enum bfs_type type) { @@ -520,10 +518,14 @@ static const char *bfs_printf_type(enum bfs_type type) { return "p"; case BFS_LNK: return "l"; + case BFS_PORT: + return "P"; case BFS_REG: return "f"; case BFS_SOCK: return "s"; + case BFS_WHT: + return "w"; default: return "U"; } @@ -532,39 +534,29 @@ static const char *bfs_printf_type(enum bfs_type type) { /** %y: type */ static int bfs_printf_y(CFILE *cfile, const struct bfs_fmt *fmt, const struct BFTW *ftwbuf) { const char *type = bfs_printf_type(ftwbuf->type); - return dyn_fprintf(cfile->file, fmt, type); + return bfs_fprintf(cfile, fmt, "%s", type); } /** %Y: target type */ static int bfs_printf_Y(CFILE *cfile, const struct bfs_fmt *fmt, const struct BFTW *ftwbuf) { - int error = 0; - - if (ftwbuf->type != BFS_LNK) { - return bfs_printf_y(cfile, fmt, ftwbuf); - } + enum bfs_type type = bftw_type(ftwbuf, BFS_STAT_FOLLOW); + const char *str; - const char *type = "U"; - - const struct bfs_stat *statbuf = bftw_stat(ftwbuf, BFS_STAT_FOLLOW); - if (statbuf) { - type = bfs_printf_type(bfs_mode_to_type(statbuf->mode)); - } else { - switch (errno) { - case ELOOP: - type = "L"; - break; - case ENOENT: - case ENOTDIR: - type = "N"; - break; - default: - type = "?"; + int error = 0; + if (type == BFS_ERROR) { + if (errno_is_like(ELOOP)) { + str = "L"; + } else if (errno_is_like(ENOENT)) { + str = "N"; + } else { + str = "?"; error = errno; - break; } + } else { + str = bfs_printf_type(type); } - int ret = dyn_fprintf(cfile->file, fmt, type); + int ret = bfs_fprintf(cfile, fmt, "%s", str); if (error != 0) { ret = -1; errno = error; @@ -580,7 +572,7 @@ static int bfs_printf_Z(CFILE *cfile, const struct bfs_fmt *fmt, const struct BF return -1; } - int ret = dyn_fprintf(cfile->file, fmt, con); + int ret = bfs_fprintf(cfile, fmt, "%s", con); bfs_freecon(con); return ret; } diff --git a/src/sighook.c b/src/sighook.c new file mode 100644 index 0000000..ff5b96f --- /dev/null +++ b/src/sighook.c @@ -0,0 +1,600 @@ +// Copyright © Tavian Barnes <tavianator@tavianator.com> +// SPDX-License-Identifier: 0BSD + +/** + * Dynamic (un)registration of signal handlers. + * + * Because signal handlers can interrupt any thread at an arbitrary point, they + * must be lock-free or risk deadlock. Therefore, we implement the global table + * of signal "hooks" with a simple read-copy-update (RCU) scheme. Readers get a + * reference-counted pointer (struct arc) to the table in a lock-free way, and + * release the reference count when finished. + * + * Updates are managed by struct rcu, which has two slots: one active and one + * inactive. Readers acquire a reference to the active slot. A single writer + * can safely update it by initializing the inactive slot, atomically swapping + * the slots, and waiting for the reference count of the newly inactive slot to + * 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 "bfstd.h" +#include "diag.h" +#include "thread.h" +#include <errno.h> +#include <signal.h> +#include <stdlib.h> +#include <unistd.h> + +#if _POSIX_SEMAPHORES > 0 +# include <semaphore.h> +#endif + +/** + * An atomically reference-counted pointer. + */ +struct arc { + /** The current reference count (0 means empty). */ + atomic size_t refs; + /** The reference itself. */ + void *ptr; + +#if _POSIX_SEMAPHORES > 0 + /** A semaphore for arc_wake(). */ + sem_t sem; + /** sem_init() result. */ + int sem_status; +#endif +}; + +/** Initialize an arc. */ +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); +#endif +} + +/** Get the current refcount. */ +static size_t arc_refs(const struct arc *arc) { + return load(&arc->refs, relaxed); +} + +/** Set the pointer in an empty arc. */ +static void arc_set(struct arc *arc, void *ptr) { + bfs_assert(arc_refs(arc) == 0); + bfs_assert(ptr); + + arc->ptr = ptr; + store(&arc->refs, 1, release); +} + +/** Acquire a reference. */ +static void *arc_get(struct arc *arc) { + size_t refs = arc_refs(arc); + do { + if (refs < 1) { + return NULL; + } + } while (!compare_exchange_weak(&arc->refs, &refs, refs + 1, acquire, relaxed)); + + return arc->ptr; +} + +/** Release a reference. */ +static void arc_put(struct arc *arc) { + size_t refs = fetch_sub(&arc->refs, 1, release); + + if (refs == 1) { +#if _POSIX_SEMAPHORES > 0 + if (arc->sem_status == 0 && sem_post(&arc->sem) != 0) { + abort(); + } +#endif + } +} + +/** Wait on the semaphore. */ +static int arc_sem_wait(struct arc *arc) { +#if _POSIX_SEMAPHORES > 0 + if (arc->sem_status == 0) { + while (sem_wait(&arc->sem) != 0) { + bfs_everify(errno == EINTR, "sem_wait()"); + } + return 0; + } +#endif + + return -1; +} + +/** Wait for all references to be released. */ +static void *arc_wait(struct arc *arc) { + size_t refs = fetch_sub(&arc->refs, 1, relaxed); + bfs_assert(refs > 0); + + --refs; + while (refs > 0) { + if (arc_sem_wait(arc) == 0) { + bfs_assert(arc_refs(arc) == 0); + // sem_wait() provides enough ordering, so we can skip the fence + goto done; + } + + // Some platforms (like macOS) don't support unnamed semaphores, + // but we can always busy-wait + spin_loop(); + refs = arc_refs(arc); + } + + thread_fence(&arc->refs, acquire); + +done:; + void *ptr = arc->ptr; + arc->ptr = NULL; + return ptr; +} + +/** Destroy an arc. */ +static void arc_destroy(struct arc *arc) { + bfs_assert(arc_refs(arc) <= 1); + +#if _POSIX_SEMAPHORES > 0 + if (arc->sem_status == 0) { + bfs_everify(sem_destroy(&arc->sem) == 0, "sem_destroy()"); + } +#endif +} + +/** + * A simple read-copy-update memory reclamation scheme. + */ +struct rcu { + /** The currently active slot. */ + atomic size_t active; + /** The two slots. */ + struct arc slots[2]; +}; + +/** Sentinel value for RCU, since arc uses NULL already. */ +static void *RCU_NULL = &RCU_NULL; + +/** Initialize an RCU block. */ +static void rcu_init(struct rcu *rcu) { + atomic_init(&rcu->active, 0); + arc_init(&rcu->slots[0]); + arc_init(&rcu->slots[1]); + arc_set(&rcu->slots[0], RCU_NULL); +} + +/** Get the active slot. */ +static struct arc *rcu_active(struct rcu *rcu) { + size_t i = load(&rcu->active, relaxed); + return &rcu->slots[i]; +} + +/** 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; + } + // Otherwise, the other slot became active; retry + } +} + +/** 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; + } +} + +/** Update an RCU-protected pointer, and return the old one. */ +static void *rcu_update(struct rcu *rcu, void *ptr) { + size_t i = load(&rcu->active, relaxed); + struct arc *prev = &rcu->slots[i]; + + size_t j = i ^ 1; + struct arc *next = &rcu->slots[j]; + + arc_set(next, ptr ? ptr : RCU_NULL); + store(&rcu->active, j, relaxed); + return arc_wait(prev); +} + +struct sighook { + int sig; + sighook_fn *fn; + void *arg; + enum sigflags flags; +}; + +/** + * A table 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[]; +}; + +/** 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; +} + +/** 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); +} + +/** 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; + } + + 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 global table of signal hooks. */ +static struct rcu rcu_sighooks; +/** The global table of atsigexit() hooks. */ +static struct rcu rcu_exithooks; + +/** Mutex for initialization and RCU writer exclusion. */ +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 + // + // 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 ... + int code = info->si_code; + return code == SI_USER || code == SI_QUEUE || code <= 0; +} + +/** Check if a signal is caused by a fault. */ +static bool is_fault(const siginfo_t *info) { + int sig = info->si_signo; + if (sig == SIGBUS || sig == SIGFPE || sig == SIGILL || sig == SIGSEGV) { + return !is_user_generated(info); + } else { + return false; + } +} + +// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/signal.h.html +static const int FATAL_SIGNALS[] = { + SIGABRT, + SIGALRM, + SIGBUS, + SIGFPE, + SIGHUP, + SIGILL, + SIGINT, + SIGPIPE, + SIGQUIT, + SIGSEGV, + SIGTERM, + SIGUSR1, + SIGUSR2, +#ifdef SIGPOLL + SIGPOLL, +#endif +#ifdef SIGPROF + SIGPROF, +#endif +#ifdef SIGSYS + SIGSYS, +#endif + SIGTRAP, +#ifdef SIGVTALRM + SIGVTALRM, +#endif + SIGXCPU, + SIGXFSZ, +}; + +/** Check if a signal's default action is to terminate the process. */ +static bool is_fatal(int sig) { + for (size_t i = 0; i < countof(FATAL_SIGNALS); ++i) { + if (sig == FATAL_SIGNALS[i]) { + return true; + } + } + +#ifdef SIGRTMIN + // https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_04_03 + // + // The default actions for the realtime signals in the range + // SIGRTMIN to SIGRTMAX shall be to terminate the process + // abnormally. + if (sig >= SIGRTMIN && sig <= SIGRTMAX) { + return true; + } +#endif + + return false; +} + +/** Reraise a fatal signal. */ +static noreturn void reraise(int sig) { + // Restore the default signal action + if (signal(sig, SIG_DFL) == SIG_ERR) { + goto fail; + } + + // Unblock the signal, since we didn't set SA_NODEFER + sigset_t mask; + if (sigemptyset(&mask) != 0 + || sigaddset(&mask, sig) != 0 + || pthread_sigmask(SIG_UNBLOCK, &mask, NULL) != 0) { + goto fail; + } + + raise(sig); +fail: + abort(); +} + +/** Find any matching hooks and run them. */ +static enum sigflags run_hooks(struct rcu *rcu, 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; + } + + if (hook->sig == sig || hook->sig == 0) { + hook->fn(sig, info, hook->arg); + ret |= hook->flags; + } + arc_put(arc); + } + +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 + // + // The behavior of a process is undefined after it returns normally + // from a signal-catching function for a SIGBUS, SIGFPE, SIGILL, or + // SIGSEGV signal that was not generated by kill(), sigqueue(), or + // raise(). + if (is_fault(info)) { + reraise(sig); + } + + // https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_04_03 + // + // After returning from a signal-catching function, the value of + // errno is unspecified if the signal-catching function or any + // function it called assigned a value to errno and the signal- + // catching function did not save and restore the original value of + // errno. + int error = errno; + + // Run the normal hooks + enum sigflags flags = run_hooks(&rcu_sighooks, sig, info); + + // Run the atsigexit() hooks, if we're exiting + if (!(flags & SH_CONTINUE) && is_fatal(sig)) { + run_hooks(&rcu_exithooks, sig, info); + reraise(sig); + } + + errno = error; +} + +/** Make sure our signal handler is installed for a given signal. */ +static int siginit(int sig) { + static struct sigaction action = { + .sa_sigaction = sigdispatch, + .sa_flags = SA_RESTART | SA_SIGINFO, + }; + + static sigset_t signals; + static bool initialized = false; + + if (!initialized) { + if (sigemptyset(&signals) != 0 + || sigemptyset(&action.sa_mask) != 0) { + return -1; + } + rcu_init(&rcu_sighooks); + rcu_init(&rcu_exithooks); + initialized = true; + } + + int installed = sigismember(&signals, sig); + if (installed < 0) { + return -1; + } else if (installed) { + return 0; + } + + if (sigaction(sig, &action, NULL) != 0) { + return -1; + } + + if (sigaddset(&signals, sig) != 0) { + return -1; + } + + return 0; +} + +/** Shared sighook()/atsigexit() implementation. */ +static struct sighook *sighook_impl(struct rcu *rcu, int sig, sighook_fn *fn, void *arg, enum sigflags flags) { + struct sighook *hook = ALLOC(struct sighook); + if (!hook) { + return NULL; + } + + hook->sig = sig; + hook->fn = fn; + hook->arg = arg; + hook->flags = flags; + + if (rcu_sigtable_add(rcu, hook) != 0) { + free(hook); + return NULL; + } + + return hook; +} + +struct sighook *sighook(int sig, sighook_fn *fn, void *arg, enum sigflags flags) { + mutex_lock(&sigmutex); + + struct sighook *ret = NULL; + if (siginit(sig) != 0) { + goto done; + } + + ret = sighook_impl(&rcu_sighooks, sig, fn, arg, flags); +done: + mutex_unlock(&sigmutex); + return ret; +} + +struct sighook *atsigexit(sighook_fn *fn, void *arg) { + mutex_lock(&sigmutex); + + struct sighook *ret = NULL; + + for (size_t i = 0; i < countof(FATAL_SIGNALS); ++i) { + if (siginit(FATAL_SIGNALS[i]) != 0) { + goto done; + } + } + +#ifdef SIGRTMIN + for (int i = SIGRTMIN; i <= SIGRTMAX; ++i) { + if (siginit(i) != 0) { + goto done; + } + } +#endif + + ret = sighook_impl(&rcu_exithooks, 0, fn, arg, 0); +done: + mutex_unlock(&sigmutex); + return ret; +} + +void sigunhook(struct sighook *hook) { + mutex_lock(&sigmutex); + + struct rcu *rcu = hook->sig ? &rcu_sighooks : &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); + } + + mutex_unlock(&sigmutex); + free(hook); +} diff --git a/src/sighook.h b/src/sighook.h new file mode 100644 index 0000000..74d18c0 --- /dev/null +++ b/src/sighook.h @@ -0,0 +1,73 @@ +// Copyright © Tavian Barnes <tavianator@tavianator.com> +// SPDX-License-Identifier: 0BSD + +/** + * Signal hooks. + */ + +#ifndef BFS_SIGHOOK_H +#define BFS_SIGHOOK_H + +#include <signal.h> + +/** + * A dynamic signal hook. + */ +struct sighook; + +/** + * Signal hook flags. + */ +enum sigflags { + /** Suppress the default action for this signal. */ + SH_CONTINUE = 1 << 0, +}; + +/** + * A signal hook callback. Hooks are executed from a signal handler, so must + * only call async-signal-safe functions. + * + * @param sig + * The signal number. + * @param info + * Additional information about the signal. + * @param arg + * An arbitrary pointer passed to the hook. + */ +typedef void sighook_fn(int sig, siginfo_t *info, void *arg); + +/** + * Install a hook for a signal. + * + * @param sig + * The signal to hook. + * @param fn + * The function to call. + * @param arg + * An argument passed to the function. + * @param flags + * Flags for the new hook. + * @return + * The installed hook, or NULL on failure. + */ +struct sighook *sighook(int sig, sighook_fn *fn, void *arg, enum sigflags flags); + +/** + * On a best-effort basis, invoke the given hook just before the program is + * abnormally terminated by a signal. + * + * @param fn + * The function to call. + * @param arg + * An argument passed to the function. + * @return + * The installed hook, or NULL on failure. + */ +struct sighook *atsigexit(sighook_fn *fn, void *arg); + +/** + * Remove a signal hook. + */ +void sigunhook(struct sighook *hook); + +#endif // BFS_SIGHOOK_H @@ -62,7 +62,7 @@ int bfs_fstatat_flags(enum bfs_stat_flags flags) { ret |= AT_SYMLINK_NOFOLLOW; } -#if defined(AT_NO_AUTOMOUNT) && (!__GNU__ || __GLIBC_PREREQ(2, 35)) +#ifdef AT_NO_AUTOMOUNT ret |= AT_NO_AUTOMOUNT; #endif @@ -116,6 +116,9 @@ void bfs_stat_convert(struct bfs_stat *dest, const struct stat *src) { #if BFS_HAS_ST_BIRTHTIM dest->btime = src->st_birthtim; dest->mask |= BFS_STAT_BTIME; +#elif BFS_HAS___ST_BIRTHTIM + dest->btime = src->__st_birthtim; + dest->mask |= BFS_STAT_BTIME; #elif BFS_HAS_ST_BIRTHTIMESPEC dest->btime = src->st_birthtimespec; dest->mask |= BFS_STAT_BTIME; @@ -297,27 +300,21 @@ int bfs_stat(int at_fd, const char *at_path, enum bfs_stat_flags flags, struct b return bfs_stat_tryfollow(at_fd, at_path, at_flags, flags, buf); } - // Check __GNU__ to work around https://lists.gnu.org/archive/html/bug-hurd/2021-12/msg00001.html -#if defined(AT_EMPTY_PATH) && !__GNU__ - static atomic bool has_at_ep = true; - if (load(&has_at_ep, relaxed)) { - at_flags |= AT_EMPTY_PATH; - int ret = bfs_stat_explicit(at_fd, "", at_flags, buf); - if (ret != 0 && errno == EINVAL) { - store(&has_at_ep, false, relaxed); - } else { - return ret; - } - } -#endif - - struct stat statbuf; - if (fstat(at_fd, &statbuf) == 0) { - bfs_stat_convert(buf, &statbuf); - return 0; - } else { +#if BFS_USE_STATX + // If we have statx(), use it with AT_EMPTY_PATH for its extra features + at_flags |= AT_EMPTY_PATH; + return bfs_stat_explicit(at_fd, "", at_flags, buf); +#else + // Otherwise, just use fstat() rather than fstatat(at_fd, ""), to save + // the kernel the trouble of copying in the empty string + struct stat sb; + if (fstat(at_fd, &sb) != 0) { return -1; } + + bfs_stat_convert(buf, &sb); + return 0; +#endif } const struct timespec *bfs_stat_time(const struct bfs_stat *buf, enum bfs_stat_field field) { diff --git a/src/xspawn.c b/src/xspawn.c index 0b0cea4..33e5a4a 100644 --- a/src/xspawn.c +++ b/src/xspawn.c @@ -5,9 +5,11 @@ #include "xspawn.h" #include "alloc.h" #include "bfstd.h" +#include "diag.h" #include "list.h" #include <errno.h> #include <fcntl.h> +#include <signal.h> #include <stdlib.h> #include <string.h> #include <sys/resource.h> @@ -590,30 +592,49 @@ static pid_t bfs_fork_spawn(struct bfs_resolver *res, const struct bfs_spawn *ct return -1; } + // Block signals before fork() so handlers don't run in the child + sigset_t new_mask; + if (sigfillset(&new_mask) != 0) { + goto fail; + } + sigset_t old_mask; + errno = pthread_sigmask(SIG_BLOCK, &new_mask, &old_mask); + if (errno != 0) { + goto fail; + } + pid_t pid = fork(); - if (pid < 0) { - close_quietly(pipefd[1]); - close_quietly(pipefd[0]); - return -1; - } else if (pid == 0) { + if (pid == 0) { // Child bfs_spawn_exec(res, ctx, argv, envp, pipefd); } - // Parent + // Restore the original signal mask + int ret = pthread_sigmask(SIG_SETMASK, &old_mask, NULL); + bfs_everify(ret == 0, "pthread_sigmask()"); + + if (pid < 0) { + // fork() failed + goto fail; + } + xclose(pipefd[1]); int error; ssize_t nbytes = xread(pipefd[0], &error, sizeof(error)); xclose(pipefd[0]); if (nbytes == sizeof(error)) { - int wstatus; - xwaitpid(pid, &wstatus, 0); + xwaitpid(pid, NULL, 0); errno = error; return -1; } return pid; + +fail: + close_quietly(pipefd[1]); + close_quietly(pipefd[0]); + return -1; } /** Call the right bfs_spawn() implementation. */ diff --git a/src/xtime.c b/src/xtime.c index 91ed915..2808455 100644 --- a/src/xtime.c +++ b/src/xtime.c @@ -5,6 +5,7 @@ #include "xtime.h" #include "bfstd.h" #include "diag.h" +#include "sanity.h" #include <errno.h> #include <limits.h> #include <sys/time.h> @@ -12,14 +13,14 @@ #include <unistd.h> int xmktime(struct tm *tm, time_t *timep) { - *timep = mktime(tm); + time_t time = mktime(tm); - if (*timep == -1) { + if (time == -1) { int error = errno; struct tm tmp; - if (!localtime_r(timep, &tmp)) { - bfs_bug("localtime_r(-1): %s", xstrerror(errno)); + if (!localtime_r(&time, &tmp)) { + bfs_ebug("localtime_r(-1)"); return -1; } @@ -30,9 +31,38 @@ int xmktime(struct tm *tm, time_t *timep) { } } + *timep = time; + return 0; +} + +// FreeBSD is missing an interceptor +#if BFS_HAS_TIMEGM && !(__FreeBSD__ && SANITIZE_MEMORY) + +int xtimegm(struct tm *tm, time_t *timep) { + time_t time = timegm(tm); + + if (time == -1) { + int error = errno; + + struct tm tmp; + if (!gmtime_r(&time, &tmp)) { + bfs_ebug("gmtime_r(-1)"); + return -1; + } + + if (tm->tm_year != tmp.tm_year || tm->tm_yday != tmp.tm_yday + || tm->tm_hour != tmp.tm_hour || tm->tm_min != tmp.tm_min || tm->tm_sec != tmp.tm_sec) { + errno = error; + return -1; + } + } + + *timep = time; return 0; } +#else + static int safe_add(int *value, int delta) { if (*value >= 0) { if (delta > INT_MAX - *value) { @@ -147,6 +177,8 @@ overflow: return -1; } +#endif // !BFS_HAS_TIMEGM + /** Parse a decimal digit. */ static int xgetdigit(char c) { int ret = c - '0'; diff --git a/tests/bfstd.c b/tests/bfstd.c index 07b68b0..f0f61ce 100644 --- a/tests/bfstd.c +++ b/tests/bfstd.c @@ -15,12 +15,12 @@ static bool check_base_dir(const char *path, const char *dir, const char *base) bool ret = true; char *xdir = xdirname(path); - bfs_verify(xdir, "xdirname(): %s", xstrerror(errno)); + bfs_everify(xdir, "xdirname()"); ret &= bfs_check(strcmp(xdir, dir) == 0, "xdirname('%s') == '%s' (!= '%s')", path, xdir, dir); free(xdir); char *xbase = xbasename(path); - bfs_verify(xbase, "xbasename(): %s", xstrerror(errno)); + bfs_everify(xbase, "xbasename()"); ret &= bfs_check(strcmp(xbase, base) == 0, "xbasename('%s') == '%s' (!= '%s')", path, xbase, base); free(xbase); diff --git a/tests/gnu/empty.out b/tests/common/empty.out index a0f4b76..a0f4b76 100644 --- a/tests/gnu/empty.out +++ b/tests/common/empty.out diff --git a/tests/gnu/empty.sh b/tests/common/empty.sh index 95ee988..95ee988 100644 --- a/tests/gnu/empty.sh +++ b/tests/common/empty.sh diff --git a/tests/common/empty_error.out b/tests/common/empty_error.out new file mode 100644 index 0000000..da45e23 --- /dev/null +++ b/tests/common/empty_error.out @@ -0,0 +1,3 @@ +./bar +./baz +./qux diff --git a/tests/common/empty_error.sh b/tests/common/empty_error.sh new file mode 100644 index 0000000..7c8049c --- /dev/null +++ b/tests/common/empty_error.sh @@ -0,0 +1,7 @@ +cd "$TEST" + +"$XTOUCH" -p foo/ bar/ baz qux +chmod -r foo baz +defer chmod +r foo baz + +! bfs_diff . -empty diff --git a/tests/gnu/empty_special.out b/tests/common/empty_special.out index fa35478..fa35478 100644 --- a/tests/gnu/empty_special.out +++ b/tests/common/empty_special.out diff --git a/tests/gnu/empty_special.sh b/tests/common/empty_special.sh index 31e9d2e..31e9d2e 100644 --- a/tests/gnu/empty_special.sh +++ b/tests/common/empty_special.sh diff --git a/tests/ioq.c b/tests/ioq.c index ef5ee3b..99c98a2 100644 --- a/tests/ioq.c +++ b/tests/ioq.c @@ -40,15 +40,15 @@ static void check_ioq_push_block(void) { const size_t depth = 2; struct ioq *ioq = ioq_create(depth, 1); - bfs_verify(ioq, "ioq_create(): %s", xstrerror(errno)); + bfs_everify(ioq, "ioq_create()"); // Push enough operations to fill the queue for (size_t i = 0; i < depth; ++i) { struct bfs_dir *dir = bfs_allocdir(); - bfs_verify(dir, "bfs_allocdir(): %s", xstrerror(errno)); + bfs_everify(dir, "bfs_allocdir()"); int ret = ioq_opendir(ioq, dir, AT_FDCWD, ".", 0, NULL); - bfs_verify(ret == 0, "ioq_opendir(): %s", xstrerror(errno)); + bfs_everify(ret == 0, "ioq_opendir()"); } bfs_verify(ioq_capacity(ioq) == 0); diff --git a/tests/main.c b/tests/main.c index 429772b..aef0583 100644 --- a/tests/main.c +++ b/tests/main.c @@ -9,7 +9,6 @@ #include "tests.h" #include "bfstd.h" #include "color.h" -#include <errno.h> #include <locale.h> #include <stdio.h> #include <stdlib.h> @@ -90,10 +89,6 @@ static void run_test(struct test_ctx *ctx, const char *test, test_fn *fn) { } } -const char *bfs_errstr(void) { - return xstrerror(errno); -} - int main(int argc, char *argv[]) { // Try to set a UTF-8 locale if (!setlocale(LC_ALL, "C.UTF-8")) { @@ -116,6 +111,7 @@ int main(int argc, char *argv[]) { run_test(&ctx, "bfstd", check_bfstd); run_test(&ctx, "bit", check_bit); run_test(&ctx, "ioq", check_ioq); + run_test(&ctx, "sighook", check_sighook); run_test(&ctx, "trie", check_trie); run_test(&ctx, "xspawn", check_xspawn); run_test(&ctx, "xtime", check_xtime); diff --git a/tests/sighook.c b/tests/sighook.c new file mode 100644 index 0000000..c94526e --- /dev/null +++ b/tests/sighook.c @@ -0,0 +1,97 @@ +// 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 <pthread.h> +#include <signal.h> +#include <sys/time.h> + +static atomic size_t count = 0; + +/** 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; + } + + sigunhook(*hook); + *hook = next; + return 0; +} + +/** 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; + } + + while (load(&count, relaxed) < 1000) { + if (swap_hooks(&hook) != 0) { + sigunhook(hook); + return NULL; + } + } + + sigunhook(hook); + return &count; +} + +bool check_sighook(void) { + bool ret = true; + + struct sighook *hook = sighook(SIGALRM, alrm_hook, NULL, SH_CONTINUE); + ret &= bfs_echeck(hook, "sighook(SIGALRM)"); + if (!ret) { + goto done; + } + + struct itimerval ival = { + .it_value = { + .tv_usec = 100, + }, + .it_interval = { + .tv_usec = 100, + }, + }; + ret &= bfs_echeck(setitimer(ITIMER_REAL, &ival, NULL) == 0); + if (!ret) { + goto unhook; + } + + pthread_t thread; + ret &= bfs_echeck(thread_create(&thread, NULL, hook_thread, NULL) == 0); + if (!ret) { + goto untime; + } + + while (ret && load(&count, relaxed) < 1000) { + ret &= swap_hooks(&hook) == 0; + } + + void *ptr; + thread_join(thread, &ptr); + ret &= bfs_check(ptr); + +untime: + ival.it_value.tv_usec = 0; + ret &= bfs_echeck(setitimer(ITIMER_REAL, &ival, NULL) == 0); + if (!ret) { + goto unhook; + } + +unhook: + sigunhook(hook); +done: + return ret; +} diff --git a/tests/tests.h b/tests/tests.h index 9078938..19b7f5e 100644 --- a/tests/tests.h +++ b/tests/tests.h @@ -26,6 +26,9 @@ bool check_bit(void); /** I/O queue tests. */ bool check_ioq(void); +/** Signal hook tests. */ +bool check_sighook(void); + /** Trie tests. */ bool check_trie(void); @@ -54,20 +57,17 @@ static inline bool bfs_check(bool ret) { : "Check failed: `%s`%s", \ str, __VA_ARGS__), false)) -/** Get a string description of the last error. */ -const char *bfs_errstr(void); - /** * Check a condition, logging the current error string on failure. */ -#define bfs_pcheck(...) \ - bfs_pcheck_(#__VA_ARGS__, __VA_ARGS__, "", "") +#define bfs_echeck(...) \ + bfs_echeck_(#__VA_ARGS__, __VA_ARGS__, "", bfs_errstr()) -#define bfs_pcheck_(str, cond, format, ...) \ +#define bfs_echeck_(str, cond, format, ...) \ ((cond) ? true : (bfs_diag( \ sizeof(format) > 1 \ - ? "%.0s" format "%s%s: %s" \ - : "Check failed: `%s`%s: %s", \ - str, __VA_ARGS__, bfs_errstr()), false)) + ? "%.0s" format "%s: %s" \ + : "Check failed: `%s`: %s", \ + str, __VA_ARGS__), false)) #endif // BFS_TESTS_H diff --git a/tests/util.sh b/tests/util.sh index 7dba9fb..3969db5 100644 --- a/tests/util.sh +++ b/tests/util.sh @@ -12,12 +12,9 @@ _realpath() ( ) # Globals -TESTS=$(_realpath "$TESTS") -if [ "${BUILDDIR-}" ]; then - BIN=$(_realpath "$BUILDDIR/bin") -else - BIN=$(_realpath "$TESTS/../bin") -fi +ROOT=$(_realpath "$(dirname -- "$TESTS")") +TESTS="$ROOT/tests" +BIN="$ROOT/bin" MKSOCK="$BIN/tests/mksock" XTOUCH="$BIN/tests/xtouch" UNAME=$(uname) diff --git a/tests/xspawn.c b/tests/xspawn.c index 7362aa5..b1d6dc1 100644 --- a/tests/xspawn.c +++ b/tests/xspawn.c @@ -54,7 +54,7 @@ static bool check_use_path(bool use_posix) { bool ret = true; struct bfs_spawn spawn; - ret &= bfs_pcheck(bfs_spawn_init(&spawn) == 0); + ret &= bfs_echeck(bfs_spawn_init(&spawn) == 0); if (!ret) { goto out; } @@ -64,34 +64,27 @@ static bool check_use_path(bool use_posix) { spawn.flags &= ~BFS_SPAWN_USE_POSIX; } - const char *builddir = getenv("BUILDDIR"); - dchar *bin = dstrprintf("%s/bin", builddir ? builddir : "."); - ret &= bfs_pcheck(bin, "dstrprintf()"); + 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) { goto destroy; } - ret &= bfs_pcheck(bfs_spawn_addopen(&spawn, 10, bin, O_RDONLY | O_DIRECTORY, 0) == 0); - ret &= bfs_pcheck(bfs_spawn_adddup2(&spawn, 10, 11) == 0); - ret &= bfs_pcheck(bfs_spawn_addclose(&spawn, 10) == 0); - ret &= bfs_pcheck(bfs_spawn_addfchdir(&spawn, 11) == 0); - ret &= bfs_pcheck(bfs_spawn_addclose(&spawn, 11) == 0); - if (!ret) { - goto bin; - } - // Check that $PATH is resolved in the parent's environment char **envp; - ret &= bfs_pcheck(envp = envdup()); + ret &= bfs_echeck(envp = envdup()); if (!ret) { - goto bin; + goto destroy; } // Check that $PATH is resolved after the file actions char *old_path = getenv("PATH"); dchar *new_path = NULL; if (old_path) { - ret &= bfs_pcheck(old_path = strdup(old_path)); + ret &= bfs_echeck(old_path = strdup(old_path)); if (!ret) { goto env; } @@ -104,20 +97,20 @@ static bool check_use_path(bool use_posix) { goto path; } - ret &= bfs_pcheck(setenv("PATH", new_path, true) == 0); + ret &= bfs_echeck(setenv("PATH", new_path, true) == 0); if (!ret) { goto path; } char *argv[] = {"xspawnee", old_path, NULL}; pid_t pid = bfs_spawn("xspawnee", &spawn, argv, envp); - ret &= bfs_pcheck(pid >= 0, "bfs_spawn()"); + ret &= bfs_echeck(pid >= 0, "bfs_spawn()"); if (!ret) { goto unset; } int wstatus; - ret &= bfs_pcheck(xwaitpid(pid, &wstatus, 0) == pid) + ret &= bfs_echeck(xwaitpid(pid, &wstatus, 0) == pid) && bfs_check(WIFEXITED(wstatus)); if (ret) { int wexit = WEXITSTATUS(wstatus); @@ -126,9 +119,9 @@ static bool check_use_path(bool use_posix) { unset: if (old_path) { - ret &= bfs_pcheck(setenv("PATH", old_path, true) == 0); + ret &= bfs_echeck(setenv("PATH", old_path, true) == 0); } else { - ret &= bfs_pcheck(unsetenv("PATH") == 0); + ret &= bfs_echeck(unsetenv("PATH") == 0); } path: dstrfree(new_path); @@ -138,10 +131,8 @@ env: free(*var); } free(envp); -bin: - dstrfree(bin); destroy: - ret &= bfs_pcheck(bfs_spawn_destroy(&spawn) == 0); + ret &= bfs_echeck(bfs_spawn_destroy(&spawn) == 0); out: return ret; } @@ -151,7 +142,7 @@ static bool check_enoent(bool use_posix) { bool ret = true; struct bfs_spawn spawn; - ret &= bfs_pcheck(bfs_spawn_init(&spawn) == 0); + ret &= bfs_echeck(bfs_spawn_init(&spawn) == 0); if (!ret) { goto out; } @@ -163,9 +154,9 @@ static bool check_enoent(bool use_posix) { char *argv[] = {"eW6f5RM9Qi", NULL}; pid_t pid = bfs_spawn("eW6f5RM9Qi", &spawn, argv, NULL); - ret &= bfs_pcheck(pid < 0 && errno == ENOENT, "bfs_spawn()"); + ret &= bfs_echeck(pid < 0 && errno == ENOENT, "bfs_spawn()"); - ret &= bfs_pcheck(bfs_spawn_destroy(&spawn) == 0); + ret &= bfs_echeck(bfs_spawn_destroy(&spawn) == 0); out: return ret; } @@ -175,18 +166,18 @@ static bool check_resolve(void) { char *exe; exe = bfs_spawn_resolve("sh"); - ret &= bfs_pcheck(exe, "bfs_spawn_resolve('sh')"); + ret &= bfs_echeck(exe, "bfs_spawn_resolve('sh')"); free(exe); exe = bfs_spawn_resolve("/bin/sh"); - ret &= bfs_pcheck(exe && strcmp(exe, "/bin/sh") == 0); + ret &= bfs_echeck(exe && strcmp(exe, "/bin/sh") == 0); free(exe); exe = bfs_spawn_resolve("bin/tests/xspawnee"); - ret &= bfs_pcheck(exe && strcmp(exe, "bin/tests/xspawnee") == 0); + ret &= bfs_echeck(exe && strcmp(exe, "bin/tests/xspawnee") == 0); free(exe); - ret &= bfs_pcheck(!bfs_spawn_resolve("eW6f5RM9Qi") && errno == ENOENT); + ret &= bfs_echeck(!bfs_spawn_resolve("eW6f5RM9Qi") && errno == ENOENT); return ret; } diff --git a/tests/xtime.c b/tests/xtime.c index a7c63d2..1907e26 100644 --- a/tests/xtime.c +++ b/tests/xtime.c @@ -29,9 +29,9 @@ static bool check_one_xgetdate(const char *str, int error, time_t expected) { int ret = xgetdate(str, &ts); if (error) { - return bfs_pcheck(ret == -1 && errno == error, "xgetdate('%s')", str); + return bfs_echeck(ret == -1 && errno == error, "xgetdate('%s')", str); } else { - return bfs_pcheck(ret == 0, "xgetdate('%s')", str) + return bfs_echeck(ret == 0, "xgetdate('%s')", str) && bfs_check(ts.tv_sec == expected && ts.tv_nsec == 0, "xgetdate('%s'): %jd.%09jd != %jd", str, (intmax_t)ts.tv_sec, (intmax_t)ts.tv_nsec, (intmax_t)expected); @@ -82,12 +82,12 @@ static bool check_xgetdate(void) { static bool check_one_xmktime(time_t expected) { struct tm tm; if (!localtime_r(&expected, &tm)) { - bfs_diag("localtime_r(%jd): %s", (intmax_t)expected, xstrerror(errno)); + bfs_ediag("localtime_r(%jd)", (intmax_t)expected); return false; } time_t actual; - return bfs_pcheck(xmktime(&tm, &actual) == 0, "xmktime(" TM_FORMAT ")", TM_PRINTF(tm)) + return bfs_echeck(xmktime(&tm, &actual) == 0, "xmktime(" TM_FORMAT ")", TM_PRINTF(tm)) && bfs_check(actual == expected, "xmktime(" TM_FORMAT "): %jd != %jd", TM_PRINTF(tm), (intmax_t)actual, (intmax_t)expected); } @@ -137,6 +137,7 @@ static bool check_one_xtimegm(const struct tm *tm) { return ret; } +#if !BFS_HAS_TIMEGM /** Check an overflowing xtimegm() call. */ static bool check_xtimegm_overflow(const struct tm *tm) { struct tm copy = *tm; @@ -154,6 +155,7 @@ static bool check_xtimegm_overflow(const struct tm *tm) { return ret; } +#endif /** xtimegm() tests. */ static bool check_xtimegm(void) { @@ -173,11 +175,13 @@ static bool check_xtimegm(void) { ret &= check_one_xtimegm(&tm); } +#if !BFS_HAS_TIMEGM // Check integer overflow cases - 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 }); + 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 }); +#endif return ret; } diff --git a/tests/xtouch.c b/tests/xtouch.c index cd41842..427e3e0 100644 --- a/tests/xtouch.c +++ b/tests/xtouch.c @@ -120,7 +120,7 @@ static int at_flags(const struct args *args) { /** Touch one path. */ static int xtouch(const struct args *args, const char *path) { int dfd = open_parent(args, &path); - if (dfd < 0 && dfd != AT_FDCWD) { + if (dfd < 0 && dfd != (int)AT_FDCWD) { return -1; } |