diff options
143 files changed, 2207 insertions, 1309 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/workflows/ci.yml b/.github/workflows/ci.yml index 78aa196..1d4196e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,6 +41,11 @@ jobs: run: | make -j$(nproc) distcheck + - uses: actions/upload-artifact@v4 + with: + name: linux-config.log + path: distcheck-*/gen/config.log + macos: name: macOS @@ -53,13 +58,12 @@ jobs: run: | brew install \ bash \ - expect \ - make + expect - name: Run tests run: | jobs=$(sysctl -n hw.ncpu) - gmake -j$jobs distcheck + make -j$jobs distcheck freebsd: name: FreeBSD @@ -70,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 + 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 @@ -101,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 + 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 @@ -132,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 + 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 @@ -170,7 +166,6 @@ jobs: with: release: "6.4.0" usesh: true - copyback: false prepare: | pkg install -y \ @@ -186,9 +181,14 @@ jobs: run: | chown -R action:action . jobs=$(sysctl -n hw.ncpu) - sudo -u action make config + sudo -u action ./configure sudo -u action 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 @@ -202,7 +202,6 @@ jobs: with: release: "r151048" usesh: true - copyback: false prepare: | pkg install \ @@ -219,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 ./configure MAKE=gmake sudo -u action 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,408 +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 the BSD make implementations (how that works -# is documented below). To build bfs, run +# To build bfs, run # -# $ make config +# $ ./configure # $ make +# Utilities and GNU/BSD portability +include build/prelude.mk + # The default build target default: bfs .PHONY: default -# 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 ?= $^ - -# GNU and BSD make have incompatible syntax for conditionals, but we can do a -# lot with just recursive variable expansion. Inspired by -# https://github.com/wahern/autoguess -TRUTHY,y := y -TRUTHY,1 := y - -# Normalize ${V} to either "y" or "" -IS_V := ${TRUTHY,${V}} - -# Suppress output unless V=1 -Q, := @ -Q := ${Q,${IS_V}} - -# 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 - -# Platform detection -OS != uname -ARCH != uname -m - -# 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:./%=%} - -# Installation paths -DESTDIR ?= -PREFIX ?= /usr -MANDIR ?= ${PREFIX}/share/man - -# Configurable executables; can be overridden with -# -# $ make config CC=clang -CC ?= cc -INSTALL ?= install -MKDIR ?= mkdir -p -PKG_CONFIG ?= pkg-config -RM ?= rm -f - -# Configurable flags - -CPPFLAGS ?= -CFLAGS ?= \ - -g \ - -Wall \ - -Wformat=2 \ - -Werror=implicit \ - -Wimplicit-fallthrough \ - -Wmissing-declarations \ - -Wshadow \ - -Wsign-compare \ - -Wstrict-prototypes -LDFLAGS ?= -LDLIBS ?= - -EXTRA_CPPFLAGS ?= -EXTRA_CFLAGS ?= -EXTRA_LDFLAGS ?= -EXTRA_LDLIBS ?= - -GIT_VERSION != test -d .git && command -v git >/dev/null 2>&1 && git describe --always --dirty || echo 3.1.3 -VERSION ?= ${GIT_VERSION} - -# Immutable flags -export BFS_CPPFLAGS= \ - -D__EXTENSIONS__ \ - -D_ATFILE_SOURCE \ - -D_BSD_SOURCE \ - -D_DARWIN_C_SOURCE \ - -D_DEFAULT_SOURCE \ - -D_GNU_SOURCE \ - -D_LARGEFILE64_SOURCE \ - -D_POSIX_PTHREAD_SEMANTICS \ - -D_FILE_OFFSET_BITS=64 \ - -D_TIME_BITS=64 -export BFS_CFLAGS= -std=c17 -pthread - -# Platform-specific system libraries -LDLIBS,DragonFly := -lposix1e -LDLIBS,Linux := -lrt -LDLIBS,NetBSD := -lutil -LDLIBS,SunOS := -lsocket -lnsl -_BFS_LDLIBS := ${LDLIBS,${OS}} -export BFS_LDLIBS=${_BFS_LDLIBS} - -# Build profiles -ASAN ?= n -LSAN ?= n -MSAN ?= n -TSAN ?= n -UBSAN ?= n -GCOV ?= n -LINT ?= n -RELEASE ?= n - -export ASAN_CFLAGS= -fsanitize=address -export LSAN_CFLAGS= -fsanitize=leak -export MSAN_CFLAGS= -fsanitize=memory -fsanitize-memory-track-origins -export UBSAN_CFLAGS= -fsanitize=undefined - -# https://github.com/google/sanitizers/issues/342 -export TSAN_CPPFLAGS= -DBFS_USE_TARGET_CLONES=0 -export TSAN_CFLAGS= -fsanitize=thread - -export SAN=${ASAN}${LSAN}${MSAN}${TSAN}${UBSAN} -export SAN_CFLAGS= -fno-sanitize-recover=all - -# MSAN and TSAN both need all code to be instrumented -export NOLIBS= ${MSAN}${TSAN} - -# gcov only intercepts fork()/exec() with -std=gnu* -export GCOV_CFLAGS= --coverage -std=gnu17 - -export LINT_CPPFLAGS= -D_FORTIFY_SOURCE=3 -DBFS_LINT -export LINT_CFLAGS= -Werror -O2 - -export RELEASE_CPPFLAGS= -DNDEBUG -export RELEASE_CFLAGS= -O3 -flto=auto - -# Save the new value of these variables, before they potentially get overridden -# by `-include ${CONFIG}` below - -_XPREFIX := ${PREFIX} -_XMANDIR := ${MANDIR} - -_XOS := ${OS} -_XARCH := ${ARCH} - -_XCC := ${CC} -_XINSTALL := ${INSTALL} -_XMKDIR := ${MKDIR} -_XRM := ${RM} - -_XCPPFLAGS := ${CPPFLAGS} -_XCFLAGS := ${CFLAGS} -_XLDFLAGS := ${LDFLAGS} -_XLDLIBS := ${LDLIBS} - -# 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} -_PKG_CONFIG := ${PKG_CONFIG} - -export BUILDDIR=${_BUILDDIR} -export PKG_CONFIG=${_PKG_CONFIG} - -export XPREFIX=${_XPREFIX} -export XMANDIR=${_XMANDIR} - -export XOS=${_XOS} -export XARCH=${_XARCH} - -export XCC=${_XCC} -export XINSTALL=${_XINSTALL} -export XMKDIR=${_XMKDIR} -export XRM=${_XRM} - -export XCPPFLAGS=${_XCPPFLAGS} -export XCFLAGS=${_XCFLAGS} -export XLDFLAGS=${_XLDFLAGS} -export XLDLIBS=${_XLDLIBS} - -# The configuration file generated by `make config` -CONFIG := ${GEN}/config.mk --include ${CONFIG} - -## Configuration phase (`make config`) - -# Makefile fragments generated by `make config` -MKS := \ - ${GEN}/vars.mk \ - ${GEN}/deps.mk \ - ${GEN}/objs.mk \ - ${GEN}/pkgs.mk - -# cat a file if V=1 -VCAT,y := @cat -VCAT, := @: -VCAT := ${VCAT,${IS_V}} - -# The configuration goal itself -config: ${MKS} - ${MSG} "[ GEN] ${CONFIG}" - @printf 'include $${GEN}/%s\n' ${.ALLSRC:${GEN}/%=%} >${CONFIG} - ${VCAT} ${CONFIG} -.PHONY: config - -# Saves the configurable variables -${GEN}/vars.mk: - @${XMKDIR} ${@D} - ${MSG} "[ GEN] $@" - @config/vars.sh >$@ - ${VCAT} $@ -.PHONY: ${GEN}/vars.mk - -# Check for dependency generation support -${GEN}/deps.mk: ${GEN}/vars.mk - ${MSG} "[ GEN] $@" - @+${MAKE} -rs -f config/deps.mk TARGET=$@ - ${VCAT} $@ - @printf -- '-include %s\n' ${OBJS:.o=.d} >>$@ -.PHONY: ${GEN}/deps.mk - -# Lists file.o: file.c dependencies -${GEN}/objs.mk: - @${XMKDIR} ${@D} - ${MSG} "[ GEN] $@" - @for obj in ${OBJS:${OBJ}/%.o=%}; do printf '$${OBJ}/%s.o: %s.c\n' "$$obj" "$$obj"; done >$@ -.PHONY: ${GEN}/objs.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} - ${MSG} "[ GEN] $@" - @printf 'include $${GEN}/%s\n' ${.ALLSRC:${GEN}/%=%} >$@ - @+${MAKE} -rs -f config/pkgs.mk TARGET=$@ - ${VCAT} $@ -.PHONY: ${GEN}/pkgs.mk - -# Auto-detect dependencies -${PKG_MKS}: ${GEN}/vars.mk - @+${MAKE} -rs -f config/pkg.mk TARGET=$@ - @if [ "${IS_V}" ]; then \ - cat $@; \ - elif grep -q PKGS $@; then \ - printf '[ GEN] %-18s [y]\n' $@; \ - else \ - printf '[ GEN] %-18s [n]\n' $@; \ - fi -.PHONY: ${PKG_MKS} +# Include the generated build config, if it exists +-include gen/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 -# 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/version.o \ - ${OBJ}/src/xregex.o \ - ${OBJ}/src/xspawn.o \ - ${OBJ}/src/xtime.o - -# 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] $@" ${CC} ${ALL_LDFLAGS} ${.ALLSRC} ${LDLIBS} -o $@ + +${MSG} "[ LD ] $@" ${CC} ${CFLAGS} ${LDFLAGS} ${.ALLSRC} ${LDLIBS} -o $@ ${POSTLINK} -# All object files -OBJS := \ - ${OBJ}/src/main.o \ - ${OBJ}/tests/alloc.o \ - ${OBJ}/tests/bfstd.o \ - ${OBJ}/tests/bit.o \ - ${OBJ}/tests/ioq.o \ - ${OBJ}/tests/main.o \ - ${OBJ}/tests/mksock.o \ - ${OBJ}/tests/trie.o \ - ${OBJ}/tests/xspawn.o \ - ${OBJ}/tests/xspawnee.o \ - ${OBJ}/tests/xtime.o \ - ${OBJ}/tests/xtouch.o \ - ${LIBBFS} - # Get the .c file for a .o file -CSRC = ${@:${OBJ}/%.o=%.c} +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 if it changes -${GEN}/NEWVERSION:: +# Save the version number to this file, but only update version.c if it changes +gen/version.c.new:: @${MKDIR} ${@D} - @printf '%s\n' '${VERSION}' >$@ + @printf 'const char bfs_version[] = "' >$@ + @if [ "$$VERSION" ]; then \ + printf '%s' "$$VERSION"; \ + elif test -e src/../.git && command -v git >/dev/null 2>&1; then \ + git -C src/.. describe --always --dirty; \ + else \ + echo "3.2"; \ + fi | tr -d '\n' >>$@ + @printf '";\n' >>$@ -${GEN}/VERSION: ${GEN}/NEWVERSION +gen/version.c: gen/version.c.new @test -e $@ && cmp -s $@ ${.ALLSRC} && rm ${.ALLSRC} || mv ${.ALLSRC} $@ -# Rebuild version.c whenever the version number changes -${OBJ}/src/version.o: ${GEN}/VERSION -${OBJ}/src/version.o: CPPFLAGS := ${CPPFLAGS} -DBFS_VERSION='"${VERSION}"' +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} @@ -414,81 +105,76 @@ 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: \ - ${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 \ +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 -DISTCHECKS := distcheck-asan distcheck-tsan distcheck-release - -# Don't use msan on macOS -IS_DARWIN,Darwin := y -IS_DARWIN := ${IS_DARWIN,${OS}} -DISTCHECK_MSAN, := distcheck-msan -DISTCHECKS += ${DISTCHECK_MSAN,${IS_DARWIN}} - -# Only add a 32-bit build on 64-bit Linux -DISTCHECK_M32,Linux,x86_64 := distcheck-m32 -DISTCHECKS += ${DISTCHECK_M32,${OS},${ARCH}} +DISTCHECKS := \ + distcheck-asan \ + distcheck-msan \ + distcheck-tsan \ + distcheck-m32 \ + distcheck-release # Test multiple configurations -distcheck: ${DISTCHECKS} +distcheck: + @+${MAKE} distcheck-asan + @+test "$$(uname)" = Darwin || ${MAKE} distcheck-msan + @+${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 posix/basic" + @${MKDIR} $@ + @+cd $@ \ + && ../configure ${DISTCHECK_CONFIG_${@:distcheck-%=%}} \ + && ${MAKE} check TEST_FLAGS="--sudo --verbose=skipped" ## Packaging (`make install`) @@ -497,42 +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:: - ${RM} -r ${BIN} ${OBJ} + ${MSG} "[ RM ] bin obj" \ + ${RM} -r bin obj # Clean everything, including generated files distclean: clean - ${RM} -r ${GEN} ${DISTCHECKS} + ${MSG} "[ RM ] gen" \ + ${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 2b340c0..45d51ca 100755 --- a/config/cc.sh +++ b/build/cc.sh @@ -1,10 +1,16 @@ -#!/usr/bin/env bash +#!/bin/sh # Copyright © Tavian Barnes <tavianator@tavianator.com> # SPDX-License-Identifier: 0BSD # Run the compiler and check if it succeeded -set -eux +set -eu -$CC $CPPFLAGS $CFLAGS $LDFLAGS "$@" $LDLIBS -o /dev/null +TMP=$(mktemp) +trap 'rm -f "$TMP"' EXIT + +( + set -x + $XCC $XCPPFLAGS $XCFLAGS $XLDFLAGS "$@" $XLDLIBS -o "$TMP" +) 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/build/define-if.sh b/build/define-if.sh new file mode 100755 index 0000000..295ead8 --- /dev/null +++ b/build/define-if.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +# Copyright © Tavian Barnes <tavianator@tavianator.com> +# SPDX-License-Identifier: 0BSD + +# Output a C preprocessor definition based on whether a command succeeds + +set -eu + +SLUG="${1#build/}" +SLUG="${SLUG%.c}" +MACRO="BFS_$(printf '%s' "$SLUG" | tr '/a-z-' '_A-Z_')" +shift + +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/build/exports.mk b/build/exports.mk new file mode 100644 index 0000000..ed19134 --- /dev/null +++ b/build/exports.mk @@ -0,0 +1,20 @@ +# Copyright © Tavian Barnes <tavianator@tavianator.com> +# SPDX-License-Identifier: 0BSD + +# Makefile fragment that exports variables used by configuration scripts + +export XPREFIX=${PREFIX} +export XMANDIR=${MANDIR} + +export XCC=${CC} +export XINSTALL=${INSTALL} +export XMKDIR=${MKDIR} +export XPKG_CONFIG=${PKG_CONFIG} +export XRM=${RM} + +export XCPPFLAGS=${CPPFLAGS} +export XCFLAGS=${CFLAGS} +export XLDFLAGS=${LDFLAGS} +export XLDLIBS=${LDLIBS} + +export XNOLIBS=${NOLIBS} diff --git a/build/flags.mk b/build/flags.mk new file mode 100644 index 0000000..c911b22 --- /dev/null +++ b/build/flags.mk @@ -0,0 +1,136 @@ +# Copyright © Tavian Barnes <tavianator@tavianator.com> +# SPDX-License-Identifier: 0BSD + +# Makefile that generates gen/flags.mk + +include build/prelude.mk +include gen/vars.mk + +# Configurable flags +CPPFLAGS ?= +CFLAGS ?= \ + -g \ + -Wall \ + -Wformat=2 \ + -Werror=implicit \ + -Wimplicit-fallthrough \ + -Wmissing-declarations \ + -Wshadow \ + -Wsign-compare \ + -Wstrict-prototypes +LDFLAGS ?= +LDLIBS ?= + +export XCPPFLAGS=${CPPFLAGS} +export XCFLAGS=${CFLAGS} +export XLDFLAGS=${LDFLAGS} +export XLDLIBS=${LDLIBS} + +# Immutable flags +export BFS_CPPFLAGS= \ + -Isrc \ + -Igen \ + -D__EXTENSIONS__ \ + -D_ATFILE_SOURCE \ + -D_BSD_SOURCE \ + -D_DARWIN_C_SOURCE \ + -D_DEFAULT_SOURCE \ + -D_GNU_SOURCE \ + -D_POSIX_PTHREAD_SEMANTICS \ + -D_FILE_OFFSET_BITS=64 \ + -D_TIME_BITS=64 +export BFS_CFLAGS= -std=c17 -pthread + +# Platform-specific system libraries +LDLIBS,DragonFly := -lposix1e +LDLIBS,Linux := -lrt +LDLIBS,NetBSD := -lutil +LDLIBS,SunOS := -lsec -lsocket -lnsl +export BFS_LDLIBS=${LDLIBS,${OS}} + +# Build profiles +_ASAN := ${TRUTHY,${ASAN}} +_LSAN := ${TRUTHY,${LSAN}} +_MSAN := ${TRUTHY,${MSAN}} +_TSAN := ${TRUTHY,${TSAN}} +_UBSAN := ${TRUTHY,${UBSAN}} +_GCOV := ${TRUTHY,${GCOV}} +_LINT := ${TRUTHY,${LINT}} +_RELEASE := ${TRUTHY,${RELEASE}} + +# https://github.com/google/sanitizers/issues/342 +TSAN_CPPFLAGS,y := -DBFS_USE_TARGET_CLONES=0 +export TSAN_CPPFLAGS=${TSAN_CPPFLAGS,${_TSAN}} + +ASAN_CFLAGS,y := -fsanitize=address +LSAN_CFLAGS,y := -fsanitize=leak +MSAN_CFLAGS,y := -fsanitize=memory -fsanitize-memory-track-origins +TSAN_CFLAGS,y := -fsanitize=thread +UBSAN_CFLAGS.y := -fsanitize=undefined + +export ASAN_CFLAGS=${ASAN_CFLAGS,${_ASAN}} +export LSAN_CFLAGS=${LSAN_CFLAGS,${_LSAN}} +export MSAN_CFLAGS=${MSAN_CFLAGS,${_MSAN}} +export TSAN_CFLAGS=${TSAN_CFLAGS,${_TSAN}} +export UBSAN_CFLAGS=${UBSAN_CFLAGS,${_UBSAN}} + +SAN_CFLAGS,y := -fno-sanitize-recover=all +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 +YESLIBS := ${NOT,${_MSAN}${_TSAN}} +NOLIBS ?= ${NOT,${YESLIBS}} +export XNOLIBS=${NOLIBS} + +# gcov only intercepts fork()/exec() with -std=gnu* +GCOV_CFLAGS,y := -std=gnu17 --coverage +export GCOV_CFLAGS=${GCOV_CFLAGS,${_GCOV}} + +LINT_CPPFLAGS,y := -D_FORTIFY_SOURCE=3 -DBFS_LINT +LINT_CFLAGS,y := -Werror -O2 + +export LINT_CPPFLAGS=${LINT_CPPFLAGS,${_LINT}} +export LINT_CFLAGS=${LINT_CFLAGS,${_LINT}} + +RELEASE_CPPFLAGS,y := -DNDEBUG +RELEASE_CFLAGS,y := -O3 -flto=auto + +export RELEASE_CPPFLAGS=${RELEASE_CPPFLAGS,${_RELEASE}} +export RELEASE_CFLAGS=${RELEASE_CFLAGS,${_RELEASE}} + +# Set a variable +SETVAR = @printf '%s := %s\n' >>$@ + +# Append to a variable, if non-empty +APPEND = @append() { test -z "$$2" || printf '%s += %s\n' "$$1" "$$2" >>$@; }; append + +gen/flags.mk:: + ${MSG} "[ GEN] $@" + @printf '# %s\n' "$@" >$@ + ${SETVAR} CPPFLAGS "$$BFS_CPPFLAGS" + ${APPEND} CPPFLAGS "$$TSAN_CPPFLAGS" + ${APPEND} CPPFLAGS "$$LINT_CPPFLAGS" + ${APPEND} CPPFLAGS "$$RELEASE_CPPFLAGS" + ${APPEND} CPPFLAGS "$$XCPPFLAGS" + ${APPEND} CPPFLAGS "$$EXTRA_CPPFLAGS" + ${SETVAR} CFLAGS "$$BFS_CFLAGS" + ${APPEND} CFLAGS "$$ASAN_CFLAGS" + ${APPEND} CFLAGS "$$LSAN_CFLAGS" + ${APPEND} CFLAGS "$$MSAN_CFLAGS" + ${APPEND} CFLAGS "$$TSAN_CFLAGS" + ${APPEND} CFLAGS "$$UBSAN_CFLAGS" + ${APPEND} CFLAGS "$$SAN_CFLAGS" + ${APPEND} CFLAGS "$$GCOV_CFLAGS" + ${APPEND} CFLAGS "$$LINT_CFLAGS" + ${APPEND} CFLAGS "$$RELEASE_CFLAGS" + ${APPEND} CFLAGS "$$XCFLAGS" + ${APPEND} CFLAGS "$$EXTRA_CFLAGS" + ${SETVAR} LDFLAGS "$$XLDFLAGS" + ${SETVAR} LDLIBS "$$XLDLIBS" + ${APPEND} LDLIBS "$$EXTRA_LDLIBS" + ${APPEND} LDLIBS "$$BFS_LDLIBS" + ${SETVAR} NOLIBS "$$XNOLIBS" + @test "${OS}-${SAN}" != FreeBSD-y || printf 'POSTLINK = elfctl -e +noaslr $$@\n' >>$@ + ${VCAT} $@ diff --git a/build/has/acl-get-entry.c b/build/has/acl-get-entry.c new file mode 100644 index 0000000..3cce771 --- /dev/null +++ b/build/has/acl-get-entry.c @@ -0,0 +1,8 @@ +#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..89fbf23 --- /dev/null +++ b/build/has/acl-get-file.c @@ -0,0 +1,8 @@ +#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..2901956 --- /dev/null +++ b/build/has/acl-get-tag-type.c @@ -0,0 +1,10 @@ +#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/build/has/acl-is-trivial-np.c b/build/has/acl-is-trivial-np.c new file mode 100644 index 0000000..9ca9fc7 --- /dev/null +++ b/build/has/acl-is-trivial-np.c @@ -0,0 +1,12 @@ +// 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_fd(3); + int trivial; + acl_is_trivial_np(acl, &trivial); + return 0; +} 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/build/has/aligned-alloc.c b/build/has/aligned-alloc.c new file mode 100644 index 0000000..4460038 --- /dev/null +++ b/build/has/aligned-alloc.c @@ -0,0 +1,8 @@ +// Copyright © Tavian Barnes <tavianator@tavianator.com> +// SPDX-License-Identifier: 0BSD + +#include <stdlib.h> + +int main(void) { + return !aligned_alloc(_Alignof(void *), sizeof(void *)); +} diff --git a/build/has/confstr.c b/build/has/confstr.c new file mode 100644 index 0000000..58280b4 --- /dev/null +++ b/build/has/confstr.c @@ -0,0 +1,9 @@ +// Copyright © Tavian Barnes <tavianator@tavianator.com> +// SPDX-License-Identifier: 0BSD + +#include <unistd.h> + +int main(void) { + confstr(_CS_PATH, NULL, 0); + return 0; +} 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/build/has/fdclosedir.c b/build/has/fdclosedir.c new file mode 100644 index 0000000..f4ad1f5 --- /dev/null +++ b/build/has/fdclosedir.c @@ -0,0 +1,8 @@ +// Copyright © Tavian Barnes <tavianator@tavianator.com> +// SPDX-License-Identifier: 0BSD + +#include <dirent.h> + +int main(void) { + return fdclosedir(opendir(".")); +} diff --git a/build/has/getdents.c b/build/has/getdents.c new file mode 100644 index 0000000..579898f --- /dev/null +++ b/build/has/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 getdents(3, (void *)buf, sizeof(buf)); +} diff --git a/build/has/getdents64-syscall.c b/build/has/getdents64-syscall.c new file mode 100644 index 0000000..7642d93 --- /dev/null +++ b/build/has/getdents64-syscall.c @@ -0,0 +1,11 @@ +// Copyright © Tavian Barnes <tavianator@tavianator.com> +// SPDX-License-Identifier: 0BSD + +#include <dirent.h> +#include <sys/syscall.h> +#include <unistd.h> + +int main(void) { + char buf[1024]; + return syscall(SYS_getdents64, 3, (void *)buf, sizeof(buf)); +} diff --git a/build/has/getdents64.c b/build/has/getdents64.c new file mode 100644 index 0000000..d8e8062 --- /dev/null +++ b/build/has/getdents64.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 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/build/has/getprogname-gnu.c b/build/has/getprogname-gnu.c new file mode 100644 index 0000000..6b97c5e --- /dev/null +++ b/build/has/getprogname-gnu.c @@ -0,0 +1,9 @@ +// Copyright © Tavian Barnes <tavianator@tavianator.com> +// SPDX-License-Identifier: 0BSD + +#include <errno.h> + +int main(void) { + const char *str = program_invocation_short_name; + return str[0]; +} diff --git a/build/has/getprogname.c b/build/has/getprogname.c new file mode 100644 index 0000000..83dc8e8 --- /dev/null +++ b/build/has/getprogname.c @@ -0,0 +1,9 @@ +// Copyright © Tavian Barnes <tavianator@tavianator.com> +// SPDX-License-Identifier: 0BSD + +#include <stdlib.h> + +int main(void) { + const char *str = getprogname(); + return str[0]; +} 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/build/has/pipe2.c b/build/has/pipe2.c new file mode 100644 index 0000000..4cb43b5 --- /dev/null +++ b/build/has/pipe2.c @@ -0,0 +1,10 @@ +// Copyright © Tavian Barnes <tavianator@tavianator.com> +// SPDX-License-Identifier: 0BSD + +#include <fcntl.h> +#include <unistd.h> + +int main(void) { + int fds[2]; + return pipe2(fds, O_CLOEXEC); +} diff --git a/build/has/posix-spawn-addfchdir-np.c b/build/has/posix-spawn-addfchdir-np.c new file mode 100644 index 0000000..b870a53 --- /dev/null +++ b/build/has/posix-spawn-addfchdir-np.c @@ -0,0 +1,11 @@ +// Copyright © Tavian Barnes <tavianator@tavianator.com> +// SPDX-License-Identifier: 0BSD + +#include <spawn.h> + +int main(void) { + posix_spawn_file_actions_t actions; + posix_spawn_file_actions_init(&actions); + posix_spawn_file_actions_addfchdir_np(&actions, 3); + return 0; +} diff --git a/build/has/posix-spawn-addfchdir.c b/build/has/posix-spawn-addfchdir.c new file mode 100644 index 0000000..c52ff81 --- /dev/null +++ b/build/has/posix-spawn-addfchdir.c @@ -0,0 +1,11 @@ +// Copyright © Tavian Barnes <tavianator@tavianator.com> +// SPDX-License-Identifier: 0BSD + +#include <spawn.h> + +int main(void) { + posix_spawn_file_actions_t actions; + posix_spawn_file_actions_init(&actions); + posix_spawn_file_actions_addfchdir(&actions, 3); + return 0; +} diff --git a/build/has/st-acmtim.c b/build/has/st-acmtim.c new file mode 100644 index 0000000..d687ab0 --- /dev/null +++ b/build/has/st-acmtim.c @@ -0,0 +1,12 @@ +// Copyright © Tavian Barnes <tavianator@tavianator.com> +// SPDX-License-Identifier: 0BSD + +#include <sys/stat.h> + +int main(void) { + struct stat sb = {0}; + unsigned int a = sb.st_atim.tv_sec; + unsigned int c = sb.st_ctim.tv_sec; + unsigned int m = sb.st_mtim.tv_sec; + return a + c + m; +} diff --git a/build/has/st-acmtimespec.c b/build/has/st-acmtimespec.c new file mode 100644 index 0000000..f747bc0 --- /dev/null +++ b/build/has/st-acmtimespec.c @@ -0,0 +1,12 @@ +// Copyright © Tavian Barnes <tavianator@tavianator.com> +// SPDX-License-Identifier: 0BSD + +#include <sys/stat.h> + +int main(void) { + struct stat sb = {0}; + unsigned int a = sb.st_atimespec.tv_sec; + unsigned int c = sb.st_ctimespec.tv_sec; + unsigned int m = sb.st_mtimespec.tv_sec; + return a + c + m; +} diff --git a/build/has/st-birthtim.c b/build/has/st-birthtim.c new file mode 100644 index 0000000..4964571 --- /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/st-birthtimespec.c b/build/has/st-birthtimespec.c new file mode 100644 index 0000000..91a613f --- /dev/null +++ b/build/has/st-birthtimespec.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_birthtimespec.tv_sec; +} diff --git a/build/has/st-flags.c b/build/has/st-flags.c new file mode 100644 index 0000000..b1d0c32 --- /dev/null +++ b/build/has/st-flags.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_flags; +} diff --git a/build/has/statx-syscall.c b/build/has/statx-syscall.c new file mode 100644 index 0000000..87ec869 --- /dev/null +++ b/build/has/statx-syscall.c @@ -0,0 +1,13 @@ +// Copyright © Tavian Barnes <tavianator@tavianator.com> +// SPDX-License-Identifier: 0BSD + +#include <fcntl.h> +#include <linux/stat.h> +#include <sys/syscall.h> +#include <unistd.h> + +int main(void) { + struct statx sb; + syscall(SYS_statx, AT_FDCWD, ".", 0, STATX_BASIC_STATS, &sb); + return 0; +} diff --git a/build/has/statx.c b/build/has/statx.c new file mode 100644 index 0000000..65f1674 --- /dev/null +++ b/build/has/statx.c @@ -0,0 +1,11 @@ +// Copyright © Tavian Barnes <tavianator@tavianator.com> +// SPDX-License-Identifier: 0BSD + +#include <fcntl.h> +#include <sys/stat.h> + +int main(void) { + struct statx sb; + statx(AT_FDCWD, ".", 0, STATX_BASIC_STATS, &sb); + return 0; +} diff --git a/build/has/strerror-l.c b/build/has/strerror-l.c new file mode 100644 index 0000000..3dcc4d7 --- /dev/null +++ b/build/has/strerror-l.c @@ -0,0 +1,11 @@ +// Copyright © Tavian Barnes <tavianator@tavianator.com> +// SPDX-License-Identifier: 0BSD + +#include <errno.h> +#include <locale.h> +#include <string.h> + +int main(void) { + locale_t locale = duplocale(LC_GLOBAL_LOCALE); + return !strerror_l(ENOMEM, locale); +} diff --git a/build/has/strerror-r-gnu.c b/build/has/strerror-r-gnu.c new file mode 100644 index 0000000..26ca0ee --- /dev/null +++ b/build/has/strerror-r-gnu.c @@ -0,0 +1,11 @@ +// Copyright © Tavian Barnes <tavianator@tavianator.com> +// SPDX-License-Identifier: 0BSD + +#include <errno.h> +#include <string.h> + +int main(void) { + char buf[256]; + // Check that strerror_r() returns a pointer + return *strerror_r(ENOMEM, buf, sizeof(buf)); +} diff --git a/build/has/strerror-r-posix.c b/build/has/strerror-r-posix.c new file mode 100644 index 0000000..41b2d30 --- /dev/null +++ b/build/has/strerror-r-posix.c @@ -0,0 +1,11 @@ +// Copyright © Tavian Barnes <tavianator@tavianator.com> +// SPDX-License-Identifier: 0BSD + +#include <errno.h> +#include <string.h> + +int main(void) { + char buf[256]; + // Check that strerror_r() returns an integer + return 2 * strerror_r(ENOMEM, buf, sizeof(buf)); +} 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/build/has/tm-gmtoff.c b/build/has/tm-gmtoff.c new file mode 100644 index 0000000..543df48 --- /dev/null +++ b/build/has/tm-gmtoff.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 tm.tm_gmtoff; +} diff --git a/build/has/uselocale.c b/build/has/uselocale.c new file mode 100644 index 0000000..a712ff8 --- /dev/null +++ b/build/has/uselocale.c @@ -0,0 +1,9 @@ +// Copyright © Tavian Barnes <tavianator@tavianator.com> +// SPDX-License-Identifier: 0BSD + +#include <locale.h> + +int main(void) { + locale_t locale = uselocale((locale_t)0); + return locale == LC_GLOBAL_LOCALE; +} diff --git a/build/header.mk b/build/header.mk new file mode 100644 index 0000000..8412fd5 --- /dev/null +++ b/build/header.mk @@ -0,0 +1,72 @@ +# 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/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-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/build/pkgconf.sh b/build/pkgconf.sh new file mode 100755 index 0000000..96e4bf1 --- /dev/null +++ b/build/pkgconf.sh @@ -0,0 +1,100 @@ +#!/bin/sh + +# Copyright © Tavian Barnes <tavianator@tavianator.com> +# SPDX-License-Identifier: 0BSD + +# pkg-config wrapper with hardcoded fallbacks + +set -eu + +MODE= +case "${1:-}" in + --*) + MODE="$1" + shift +esac + +if [ $# -lt 1 ]; then + exit +fi + +case "$XNOLIBS" in + y|1) + exit 1 +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 + build/cc.sh $CFLAGS $LDFLAGS build/use/$LIB.c $LDLIBS || exit 1 + done +fi + +# Defer to pkg-config if possible +if command -v "${XPKG_CONFIG:-}" >/dev/null 2>&1; then + case "$MODE" in + --cflags) + "$XPKG_CONFIG" --cflags "$@" + ;; + --ldflags) + "$XPKG_CONFIG" --libs-only-L --libs-only-other "$@" + ;; + --ldlibs) + "$XPKG_CONFIG" --libs-only-l "$@" + ;; + esac + + exit +fi + +# pkg-config unavailable, emulate it ourselves +CFLAGS="" +LDFLAGS="" +LDLIBS="" + +for LIB; do + case "$LIB" in + libacl) + LDLIB=-lacl + ;; + libcap) + LDLIB=-lcap + ;; + libselinux) + LDLIB=-lselinux + ;; + liburing) + LDLIB=-luring + ;; + oniguruma) + LDLIB=-lonig + ;; + *) + printf 'error: Unknown package %s\n' "$LIB" >&2 + exit 1 + ;; + esac + + LDLIBS="$LDLIBS$LDLIB " +done + +case "$MODE" in + --ldlibs) + printf '%s\n' "$LDLIBS" + ;; +esac 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..5be26cb --- /dev/null +++ b/build/prelude.mk @@ -0,0 +1,122 @@ +# 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/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/src/version.c b/build/use/libacl.c index 736f3d5..de1fe50 100644 --- a/src/version.c +++ b/build/use/libacl.c @@ -1,6 +1,9 @@ // Copyright © Tavian Barnes <tavianator@tavianator.com> // SPDX-License-Identifier: 0BSD -#include "config.h" +#include <sys/acl.h> -const char bfs_version[] = BFS_VERSION; +int main(void) { + acl_free(0); + return 0; +} diff --git a/build/use/libcap.c b/build/use/libcap.c new file mode 100644 index 0000000..58e832c --- /dev/null +++ b/build/use/libcap.c @@ -0,0 +1,9 @@ +// Copyright © Tavian Barnes <tavianator@tavianator.com> +// SPDX-License-Identifier: 0BSD + +#include <sys/capability.h> + +int main(void) { + cap_free(0); + return 0; +} diff --git a/build/use/libselinux.c b/build/use/libselinux.c new file mode 100644 index 0000000..bca409d --- /dev/null +++ b/build/use/libselinux.c @@ -0,0 +1,9 @@ +// Copyright © Tavian Barnes <tavianator@tavianator.com> +// SPDX-License-Identifier: 0BSD + +#include <selinux/selinux.h> + +int main(void) { + freecon(0); + return 0; +} diff --git a/build/use/liburing.c b/build/use/liburing.c new file mode 100644 index 0000000..bea499a --- /dev/null +++ b/build/use/liburing.c @@ -0,0 +1,9 @@ +// Copyright © Tavian Barnes <tavianator@tavianator.com> +// SPDX-License-Identifier: 0BSD + +#include <liburing.h> + +int main(void) { + io_uring_free_probe(0); + return 0; +} diff --git a/build/use/oniguruma.c b/build/use/oniguruma.c new file mode 100644 index 0000000..cb17596 --- /dev/null +++ b/build/use/oniguruma.c @@ -0,0 +1,9 @@ +// Copyright © Tavian Barnes <tavianator@tavianator.com> +// SPDX-License-Identifier: 0BSD + +#include <oniguruma.h> + +int main(void) { + onig_free(0); + return 0; +} diff --git a/config/deps.mk b/config/deps.mk deleted file mode 100644 index 7d991ab..0000000 --- a/config/deps.mk +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright © Tavian Barnes <tavianator@tavianator.com> -# SPDX-License-Identifier: 0BSD - -# Makefile that generates gen/deps.mk - -.OBJDIR: . - -include config/vars.mk - -default:: - if config/cc.sh -MD -MP -MF /dev/null config/empty.c; then \ - printf 'DEPFLAGS = -MD -MP -MF $${@:.o=.d}\n'; \ - fi >${TARGET} 2>${TARGET}.log diff --git a/config/libacl.c b/config/libacl.c deleted file mode 100644 index 877cb69..0000000 --- a/config/libacl.c +++ /dev/null @@ -1,6 +0,0 @@ -#include <sys/acl.h> - -int main(void) { - acl_free(0); - return 0; -} diff --git a/config/libcap.c b/config/libcap.c deleted file mode 100644 index 64188ac..0000000 --- a/config/libcap.c +++ /dev/null @@ -1,6 +0,0 @@ -#include <sys/capability.h> - -int main(void) { - cap_free(0); - return 0; -} diff --git a/config/libselinux.c b/config/libselinux.c deleted file mode 100644 index 72f5d33..0000000 --- a/config/libselinux.c +++ /dev/null @@ -1,6 +0,0 @@ -#include <selinux/selinux.h> - -int main(void) { - freecon(0); - return 0; -} diff --git a/config/liburing.c b/config/liburing.c deleted file mode 100644 index 456059c..0000000 --- a/config/liburing.c +++ /dev/null @@ -1,6 +0,0 @@ -#include <liburing.h> - -int main(void) { - io_uring_free_probe(0); - return 0; -} diff --git a/config/oniguruma.c b/config/oniguruma.c deleted file mode 100644 index b834fac..0000000 --- a/config/oniguruma.c +++ /dev/null @@ -1,6 +0,0 @@ -#include <oniguruma.h> - -int main(void) { - onig_free(0); - return 0; -} diff --git a/config/pkg.mk b/config/pkg.mk deleted file mode 100644 index 9b32b42..0000000 --- a/config/pkg.mk +++ /dev/null @@ -1,11 +0,0 @@ -# Copyright © Tavian Barnes <tavianator@tavianator.com> -# SPDX-License-Identifier: 0BSD - -# Makefile that generates gen/lib*.mk - -.OBJDIR: . - -include config/vars.mk - -default:: - config/pkg.sh ${TARGET:${GEN}/%.mk=%} >${TARGET} 2>${TARGET}.log diff --git a/config/pkg.sh b/config/pkg.sh deleted file mode 100755 index 2ca533e..0000000 --- a/config/pkg.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/env bash - -# Copyright © Tavian Barnes <tavianator@tavianator.com> -# SPDX-License-Identifier: 0BSD - -# pkg-config wrapper that outputs a makefile fragment - -set -eu - -NAME="${1^^}" -declare -n 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/pkgconf.sh b/config/pkgconf.sh deleted file mode 100755 index a13b30f..0000000 --- a/config/pkgconf.sh +++ /dev/null @@ -1,86 +0,0 @@ -#!/usr/bin/env bash - -# Copyright © Tavian Barnes <tavianator@tavianator.com> -# SPDX-License-Identifier: 0BSD - -# pkg-config wrapper with hardcoded fallbacks - -set -eu - -MODE= -if [[ "${1:-}" == --* ]]; then - MODE="$1" - shift -fi - -if (($# < 1)); then - exit -fi - -if [[ "$NOLIBS" == *y* ]]; then - exit 1 -fi - -if command -v "${PKG_CONFIG:-}" &>/dev/null; then - case "$MODE" in - "") - "$PKG_CONFIG" "$@" - ;; - --cflags) - OUT=$("$PKG_CONFIG" --cflags "$@") - if [ "$OUT" ]; then - printf 'CFLAGS += %s\n' "$OUT" - fi - ;; - --ldflags) - OUT=$("$PKG_CONFIG" --libs-only-L --libs-only-other "$@") - if [ "$OUT" ]; then - printf 'LDFLAGS += %s\n' "$OUT" - fi - ;; - --ldlibs) - OUT=$("$PKG_CONFIG" --libs-only-l "$@") - if [ "$OUT" ]; then - printf 'LDLIBS := %s ${LDLIBS}\n' "$OUT" - fi - ;; - esac -else - LDLIBS="" - for LIB; do - case "$LIB" in - libacl) - LDLIB=-lacl - ;; - libcap) - LDLIB=-lcap - ;; - libselinux) - LDLIB=-lselinux - ;; - liburing) - LDLIB=-luring - ;; - oniguruma) - LDLIB=-lonig - ;; - *) - printf 'error: Unknown package %s\n' "$LIB" >&2 - exit 1 - ;; - esac - - case "$MODE" in - "") - config/cc.sh "config/$LIB.c" "$LDLIB" || exit $? - ;; - --ldlibs) - LDLIBS="$LDLIBS $LDLIB" - ;; - esac - done - - if [ "$MODE" = "--ldlibs" ] && [ "$LDLIBS" ]; then - printf 'LDLIBS :=%s ${LDLIBS}\n' "$LDLIBS" - fi -fi diff --git a/config/pkgs.mk b/config/pkgs.mk deleted file mode 100644 index 54024b2..0000000 --- a/config/pkgs.mk +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright © Tavian Barnes <tavianator@tavianator.com> -# SPDX-License-Identifier: 0BSD - -# Makefile that generates gen/pkgs.mk - -.OBJDIR: . - -include config/vars.mk -include ${GEN}/pkgs.mk - -default:: - config/pkgconf.sh --cflags ${PKGS} >>${TARGET} 2>>${TARGET}.log - config/pkgconf.sh --ldflags ${PKGS} >>${TARGET} 2>>${TARGET}.log - config/pkgconf.sh --ldlibs ${PKGS} >>${TARGET} 2>>${TARGET}.log diff --git a/config/vars.mk b/config/vars.mk deleted file mode 100644 index a8fae9d..0000000 --- a/config/vars.mk +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright © Tavian Barnes <tavianator@tavianator.com> -# SPDX-License-Identifier: 0BSD - -# Makefile fragment loads and exports variables for config steps - -GEN := ${BUILDDIR}/gen -GEN := ${GEN:./%=%} - -include ${GEN}/vars.mk - -_CC := ${CC} -_CPPFLAGS := ${CPPFLAGS} -_CFLAGS := ${CFLAGS} -_LDFLAGS := ${LDFLAGS} -_LDLIBS := ${LDLIBS} - -export CC=${_CC} -export CPPFLAGS=${_CPPFLAGS} -export CFLAGS=${_CFLAGS} -export LDFLAGS=${_LDFLAGS} -export LDLIBS=${_LDLIBS} diff --git a/config/vars.sh b/config/vars.sh deleted file mode 100755 index 8a781bb..0000000 --- a/config/vars.sh +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/env bash - -# Copyright © Tavian Barnes <tavianator@tavianator.com> -# SPDX-License-Identifier: 0BSD - -# Writes the saved variables to gen/vars.mk - -set -eu - -print() { - NAME="$1" - OP="${2:-:=}" - - if (($# >= 3)); then - printf '# %s\n' "${3#X}" - declare -n VAR="$3" - VALUE="${VAR:-}" - else - # Try X$NAME, $NAME, "" - local -n XVAR="X$NAME" - local -n VAR="$NAME" - VALUE="${XVAR:-${VAR:-}}" - fi - - printf '%s %s %s\n' "$NAME" "$OP" "$VALUE" -} - -cond_flags() { - local -n COND="$1" - - if [[ "${COND:-}" == *y* ]]; then - print "$2" += "${1}_${2}" - fi -} - -print PREFIX -print MANDIR - -print OS -print ARCH - -print CC -print INSTALL -print MKDIR -print RM - -print CPPFLAGS := BFS_CPPFLAGS -cond_flags TSAN CPPFLAGS -cond_flags LINT CPPFLAGS -cond_flags RELEASE CPPFLAGS -print CPPFLAGS += XCPPFLAGS -print CPPFLAGS += EXTRA_CPPFLAGS - -print CFLAGS := BFS_CFLAGS -cond_flags ASAN CFLAGS -cond_flags LSAN CFLAGS -cond_flags MSAN CFLAGS -cond_flags TSAN CFLAGS -cond_flags UBSAN CFLAGS -cond_flags SAN CFLAGS -cond_flags GCOV CFLAGS -cond_flags LINT CFLAGS -cond_flags RELEASE CFLAGS -print CFLAGS += XCFLAGS -print CFLAGS += EXTRA_CFLAGS - -print LDFLAGS := XLDFLAGS -print LDFLAGS += EXTRA_LDFLAGS - -print LDLIBS := XLDLIBS -print LDLIBS += EXTRA_LDLIBS -print LDLIBS += BFS_LDLIBS - -print PKGS - -# Disable ASLR on FreeBSD when sanitizers are enabled -case "$XOS-$SAN" in - FreeBSD-*y*) - printf 'POSTLINK = elfctl -e +noaslr $@\n' - ;; -esac 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/alloc.c b/src/alloc.c index b65d0c5..ebaff38 100644 --- a/src/alloc.c +++ b/src/alloc.c @@ -1,9 +1,9 @@ // Copyright © Tavian Barnes <tavianator@tavianator.com> // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "alloc.h" #include "bit.h" -#include "config.h" #include "diag.h" #include "sanity.h" #include <errno.h> @@ -24,12 +24,12 @@ static void *xmemalign(size_t align, size_t size) { bfs_assert(align >= sizeof(void *)); bfs_assert(is_aligned(align, size)); -#if __APPLE__ +#if BFS_HAS_ALIGNED_ALLOC + return aligned_alloc(align, size); +#else void *ptr = NULL; errno = posix_memalign(&ptr, align, size); return ptr; -#else - return aligned_alloc(align, size); #endif } diff --git a/src/alloc.h b/src/alloc.h index ae055bc..095134a 100644 --- a/src/alloc.h +++ b/src/alloc.h @@ -8,7 +8,7 @@ #ifndef BFS_ALLOC_H #define BFS_ALLOC_H -#include "config.h" +#include "prelude.h" #include <errno.h> #include <stddef.h> #include <stdlib.h> @@ -1,11 +1,12 @@ // Copyright © Tavian Barnes <tavianator@tavianator.com> // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "bar.h" #include "atomic.h" #include "bfstd.h" #include "bit.h" -#include "config.h" +#include "diag.h" #include "dstring.h" #include <errno.h> #include <fcntl.h> @@ -15,9 +16,30 @@ #include <string.h> #include <sys/ioctl.h> +/** + * Sentinel values for bfs_bar::refcount. + */ +enum { + /** The bar is currently uninitialized. */ + BFS_BAR_UNINIT = 0, + /** The bar is being initialized/destroyed. */ + BFS_BAR_BUSY = 1, + /** Values >= this are valid refcounts. */ + BFS_BAR_READY = 2, +}; + struct bfs_bar { - int fd; + /** + * A file descriptor to the TTY. Must be atomic because signal handlers + * can race with bfs_bar_hide(). + */ + atomic int fd; + /** A reference count protecting `fd`. */ + atomic unsigned int refcount; + + /** The width of the TTY. */ atomic unsigned int width; + /** The height of the TTY. */ atomic unsigned int height; }; @@ -26,27 +48,71 @@ static struct bfs_bar the_bar = { .fd = -1, }; +/** Try to acquire a reference to bar->fd. */ +static int bfs_bar_getfd(struct bfs_bar *bar) { + unsigned int refs = load(&bar->refcount, relaxed); + do { + if (refs < BFS_BAR_READY) { + errno = EAGAIN; + return -1; + } + } while (!compare_exchange_weak(&bar->refcount, &refs, refs + 1, acquire, relaxed)); + + return load(&bar->fd, relaxed); +} + +/** Release a reference to bar->fd. */ +static void bfs_bar_putfd(struct bfs_bar *bar) { + int fd = load(&bar->fd, relaxed); + + unsigned int refs = fetch_sub(&bar->refcount, 1, release); + bfs_assert(refs >= BFS_BAR_READY); + + if (refs == 2) { + close_quietly(fd); + store(&bar->fd, -1, relaxed); + store(&bar->refcount, BFS_BAR_UNINIT, release); + } +} + /** Get the terminal size, if possible. */ static int bfs_bar_getsize(struct bfs_bar *bar) { #ifdef TIOCGWINSZ - struct winsize ws; - if (ioctl(bar->fd, TIOCGWINSZ, &ws) != 0) { + int fd = bfs_bar_getfd(bar); + if (fd < 0) { return -1; } - store(&bar->width, ws.ws_col, relaxed); - store(&bar->height, ws.ws_row, relaxed); - return 0; + struct winsize ws; + int ret = ioctl(fd, TIOCGWINSZ, &ws); + if (ret == 0) { + store(&bar->width, ws.ws_col, relaxed); + store(&bar->height, ws.ws_row, relaxed); + } + + bfs_bar_putfd(bar); + return ret; #else errno = ENOTSUP; return -1; #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) { + int fd = bfs_bar_getfd(bar); + if (fd < 0) { + return -1; + } + + int ret = xwrite(fd, str, len) == len ? 0 : -1; + bfs_bar_putfd(bar); + return ret; +} + +/** 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,22 +136,24 @@ 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] = - "\0337" // DECSC: Save cursor - "\033[;"; // DECSTBM: Set scrollable region + char esc_seq[12 + ITOA_DIGITS]; + + char *cur = stpcpy(esc_seq, + "\0337" // DECSC: Save cursor + "\033[;" // DECSTBM: Set scrollable region + ); // 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); + cur = ass_itoa(cur, height - 1); - strcpy(ptr, + cur = stpcpy(cur, "r" // DECSTBM "\0338" // DECRC: Restore the cursor "\033[J" // ED: Erase display from cursor to end ); - return ass_puts(bar->fd, esc_seq); + return bfs_bar_write(bar, esc_seq, cur - esc_seq); } #ifdef SIGWINCH @@ -102,7 +170,7 @@ static void sighand_winch(int sig) { /** 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 @@ -138,13 +206,14 @@ 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) { + unsigned int refs = BFS_BAR_UNINIT; + if (!compare_exchange_strong(&the_bar.refcount, &refs, BFS_BAR_BUSY, acq_rel, relaxed)) { errno = EBUSY; goto fail; } @@ -153,16 +222,18 @@ struct bfs_bar *bfs_bar_show(void) { ctermid(term); if (strlen(term) == 0) { errno = ENOTTY; - goto fail; + goto unref; } - the_bar.fd = open(term, O_RDWR | O_CLOEXEC); - if (the_bar.fd < 0) { - goto fail; + int fd = open(term, O_RDWR | O_CLOEXEC); + if (fd < 0) { + goto unref; } + store(&the_bar.fd, fd, relaxed); + store(&the_bar.refcount, BFS_BAR_READY, release); if (bfs_bar_getsize(&the_bar) != 0) { - goto fail_close; + goto put; } reset_before_death_by(SIGABRT); @@ -192,9 +263,11 @@ struct bfs_bar *bfs_bar_show(void) { return &the_bar; -fail_close: - close_quietly(the_bar.fd); - the_bar.fd = -1; +put: + bfs_bar_putfd(&the_bar); + return NULL; +unref: + store(&the_bar.refcount, 0, release); fail: return NULL; } @@ -233,7 +306,5 @@ void bfs_bar_hide(struct bfs_bar *bar) { #endif bfs_bar_reset(bar); - - xclose(bar->fd); - bar->fd = -1; + bfs_bar_putfd(bar); } diff --git a/src/bfstd.c b/src/bfstd.c index 2499f00..0ac3a72 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -1,9 +1,9 @@ // Copyright © Tavian Barnes <tavianator@tavianator.com> // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "bfstd.h" #include "bit.h" -#include "config.h" #include "diag.h" #include "sanity.h" #include "thread.h" @@ -186,10 +186,10 @@ char *xgetdelim(FILE *file, char delim) { const char *xgetprogname(void) { const char *cmd = NULL; -#if __GLIBC__ - cmd = program_invocation_short_name; -#elif BSD +#if BFS_HAS_GETPROGNAME cmd = getprogname(); +#elif BFS_HAS_GETPROGNAME_GNU + cmd = program_invocation_short_name; #endif if (!cmd) { @@ -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) { @@ -317,35 +283,33 @@ const char *xstrerror(int errnum) { const char *ret = NULL; static thread_local char buf[256]; - // - __APPLE__ - // - __COSMOPOLITAN__ - // - No strerror_l() - // - __FreeBSD__ && SANITIZE_MEMORY - // - duplocale() triggers https://github.com/llvm/llvm-project/issues/65532 -#if __APPLE__ || __COSMOPOLITAN__ || (__FreeBSD__ && SANITIZE_MEMORY) - if (strerror_r(errnum, buf, sizeof(buf)) == 0) { - ret = buf; - } -#else -# if __NetBSD__ - // NetBSD has no thread-specific locales - locale_t loc = LC_GLOBAL_LOCALE; -# else + // On FreeBSD with MemorySanitizer, duplocale() triggers + // https://github.com/llvm/llvm-project/issues/65532 +#if BFS_HAS_STRERROR_L && !(__FreeBSD__ && SANITIZE_MEMORY) +# if BFS_HAS_USELOCALE locale_t loc = uselocale((locale_t)0); +# else + locale_t loc = LC_GLOBAL_LOCALE; # endif - locale_t copy = loc; - if (copy == LC_GLOBAL_LOCALE) { - copy = duplocale(copy); + bool free_loc = false; + if (loc == LC_GLOBAL_LOCALE) { + loc = duplocale(loc); + free_loc = true; } - if (copy != (locale_t)0) { - ret = strerror_l(errnum, copy); - - if (loc == LC_GLOBAL_LOCALE) { - freelocale(copy); + if (loc != (locale_t)0) { + ret = strerror_l(errnum, loc); + if (free_loc) { + freelocale(loc); } } +#elif BFS_HAS_STRERROR_R_POSIX + if (strerror_r(errnum, buf, sizeof(buf)) == 0) { + ret = buf; + } +#elif BFS_HAS_STRERROR_R_GNU + ret = strerror_r(errnum, buf, sizeof(buf)); #endif if (!ret) { @@ -358,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, "----------"); @@ -489,7 +487,7 @@ int dup_cloexec(int fd) { } int pipe_cloexec(int pipefd[2]) { -#if __linux__ || (BSD && !__APPLE__) +#if BFS_HAS_PIPE2 return pipe2(pipefd, O_CLOEXEC); #else if (pipe(pipefd) != 0) { @@ -581,10 +579,7 @@ int xfaccessat(int fd, const char *path, int amode) { } char *xconfstr(int name) { -#if __ANDROID__ - errno = ENOTSUP; - return NULL; -#else +#if BFS_HAS_CONFSTR size_t len = confstr(name, NULL, 0); if (len == 0) { return NULL; @@ -601,7 +596,10 @@ char *xconfstr(int name) { } return str; -#endif // !__ANDROID__ +#else + errno = ENOTSUP; + return NULL; +#endif } char *xreadlinkat(int fd, const char *path, size_t size) { @@ -639,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__ @@ -651,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; @@ -665,7 +665,7 @@ 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 diff --git a/src/bfstd.h b/src/bfstd.h index fc22971..f91e380 100644 --- a/src/bfstd.h +++ b/src/bfstd.h @@ -8,7 +8,7 @@ #ifndef BFS_BFSTD_H #define BFS_BFSTD_H -#include "config.h" +#include "prelude.h" #include "sanity.h" #include <stddef.h> @@ -283,11 +283,21 @@ int xminor(dev_t dev); // #include <sys/stat.h> -#if __APPLE__ -# define st_atim st_atimespec -# define st_ctim st_ctimespec -# define st_mtim st_mtimespec -# define st_birthtim st_birthtimespec +/** + * Get the access/change/modification time from a struct stat. + */ +#if BFS_HAS_ST_ACMTIM +# define ST_ATIM(sb) (sb).st_atim +# define ST_CTIM(sb) (sb).st_ctim +# define ST_MTIM(sb) (sb).st_mtim +#elif BFS_HAS_ST_ACMTIMESPEC +# define ST_ATIM(sb) (sb).st_atimespec +# define ST_CTIM(sb) (sb).st_ctimespec +# define ST_MTIM(sb) (sb).st_mtimespec +#else +# define ST_ATIM(sb) ((struct timespec) { .tv_sec = (sb).st_atime }) +# define ST_CTIM(sb) ((struct timespec) { .tv_sec = (sb).st_ctime }) +# define ST_MTIM(sb) ((struct timespec) { .tv_sec = (sb).st_mtime }) #endif // #include <sys/wait.h> @@ -18,10 +18,10 @@ * various helper functions to take fewer parameters. */ +#include "prelude.h" #include "bftw.h" #include "alloc.h" #include "bfstd.h" -#include "config.h" #include "diag.h" #include "dir.h" #include "dstring.h" @@ -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); @@ -8,7 +8,7 @@ #ifndef BFS_BIT_H #define BFS_BIT_H -#include "config.h" +#include "prelude.h" #include <limits.h> #include <stdint.h> diff --git a/src/color.c b/src/color.c index 8c32a68..f004bf2 100644 --- a/src/color.c +++ b/src/color.c @@ -1,11 +1,11 @@ // Copyright © Tavian Barnes <tavianator@tavianator.com> // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "color.h" #include "alloc.h" #include "bfstd.h" #include "bftw.h" -#include "config.h" #include "diag.h" #include "dir.h" #include "dstring.h" diff --git a/src/color.h b/src/color.h index e3e7973..3278cd6 100644 --- a/src/color.h +++ b/src/color.h @@ -8,7 +8,7 @@ #ifndef BFS_COLOR_H #define BFS_COLOR_H -#include "config.h" +#include "prelude.h" #include "dstring.h" #include <stdio.h> @@ -237,7 +237,7 @@ int bfs_ctx_free(struct bfs_ctx *ctx) { if (bfs_ctx_fclose(ctx, ctx_file) != 0) { if (cerr) { - bfs_error(ctx, "'%s': %m.\n", ctx_file->path); + bfs_error(ctx, "%pq: %m.\n", ctx_file->path); } ret = -1; } @@ -8,9 +8,9 @@ #ifndef BFS_CTX_H #define BFS_CTX_H +#include "prelude.h" #include "alloc.h" #include "bftw.h" -#include "config.h" #include "diag.h" #include "expr.h" #include "trie.h" @@ -1,11 +1,11 @@ // Copyright © Tavian Barnes <tavianator@tavianator.com> // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "diag.h" #include "alloc.h" #include "bfstd.h" #include "color.h" -#include "config.h" #include "ctx.h" #include "dstring.h" #include "expr.h" @@ -8,7 +8,7 @@ #ifndef BFS_DIAG_H #define BFS_DIAG_H -#include "config.h" +#include "prelude.h" #include <stdarg.h> /** @@ -1,10 +1,10 @@ // Copyright © Tavian Barnes <tavianator@tavianator.com> // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "dir.h" #include "alloc.h" #include "bfstd.h" -#include "config.h" #include "diag.h" #include "sanity.h" #include "trie.h" @@ -17,7 +17,7 @@ #include <unistd.h> #if BFS_USE_GETDENTS -# if __linux__ +# if BFS_HAS_GETDENTS64_SYSCALL # include <sys/syscall.h> # endif @@ -25,12 +25,14 @@ static ssize_t bfs_getdents(int fd, void *buf, size_t size) { sanitize_uninit(buf, size); -#if (__linux__ && __GLIBC__ && !__GLIBC_PREREQ(2, 30)) || __ANDROID__ - ssize_t ret = syscall(SYS_getdents64, fd, buf, size); -#elif __linux__ +#if BFS_HAS_GETDENTS + ssize_t ret = getdents(fd, buf, size); +#elif BFS_HAS_GETDENTS64 ssize_t ret = getdents64(fd, buf, size); +#elif BFS_HAS_GETDENTS64_SYSCALL + ssize_t ret = syscall(SYS_getdents64, fd, buf, size); #else - ssize_t ret = getdents(fd, buf, size); +# error "No getdents() implementation" #endif if (ret > 0) { @@ -42,7 +44,7 @@ static ssize_t bfs_getdents(int fd, void *buf, size_t size) { #endif // BFS_USE_GETDENTS -#if BFS_USE_GETDENTS && __linux__ +#if BFS_USE_GETDENTS && !BFS_HAS_GETDENTS /** Directory entry type for bfs_getdents() */ typedef struct dirent64 sys_dirent; #else @@ -351,7 +353,7 @@ int bfs_closedir(struct bfs_dir *dir) { int bfs_unwrapdir(struct bfs_dir *dir) { #if BFS_USE_GETDENTS int ret = dir->fd; -#elif __FreeBSD__ +#elif BFS_HAS_FDCLOSEDIR int ret = fdclosedir(dir->dir); #endif @@ -8,14 +8,15 @@ #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(). */ -#ifndef BFS_USE_GETDENTS -# define BFS_USE_GETDENTS (__linux__ || __FreeBSD__) +#if !defined(BFS_USE_GETDENTS) && (__linux__ || __FreeBSD__) +# define BFS_USE_GETDENTS (BFS_HAS_GETDENTS || BFS_HAS_GETDENTS64 | BFS_HAS_GETDENTS64_SYSCALL) #endif /** @@ -152,7 +153,7 @@ int bfs_closedir(struct bfs_dir *dir); * Whether the bfs_unwrapdir() function is supported. */ #ifndef BFS_USE_UNWRAPDIR -# define BFS_USE_UNWRAPDIR (BFS_USE_GETDENTS || __FreeBSD__) +# define BFS_USE_UNWRAPDIR (BFS_USE_GETDENTS || BFS_HAS_FDCLOSEDIR) #endif #if BFS_USE_UNWRAPDIR diff --git a/src/dstring.c b/src/dstring.c index 10b0fad..86ab646 100644 --- a/src/dstring.c +++ b/src/dstring.c @@ -1,10 +1,10 @@ // Copyright © Tavian Barnes <tavianator@tavianator.com> // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "dstring.h" #include "alloc.h" #include "bit.h" -#include "config.h" #include "diag.h" #include <stdarg.h> #include <stddef.h> @@ -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 6006199..14e1d3e 100644 --- a/src/dstring.h +++ b/src/dstring.h @@ -8,8 +8,8 @@ #ifndef BFS_DSTRING_H #define BFS_DSTRING_H +#include "prelude.h" #include "bfstd.h" -#include "config.h" #include <stdarg.h> #include <stddef.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. @@ -5,12 +5,12 @@ * Implementation of all the primary expressions. */ +#include "prelude.h" #include "eval.h" #include "bar.h" #include "bfstd.h" #include "bftw.h" #include "color.h" -#include "config.h" #include "ctx.h" #include "diag.h" #include "dir.h" @@ -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; + } } /** @@ -9,7 +9,7 @@ #ifndef BFS_EVAL_H #define BFS_EVAL_H -#include "config.h" +#include "prelude.h" struct bfs_ctx; struct bfs_expr; @@ -1,12 +1,12 @@ // Copyright © Tavian Barnes <tavianator@tavianator.com> // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "exec.h" #include "alloc.h" #include "bfstd.h" #include "bftw.h" #include "color.h" -#include "config.h" #include "ctx.h" #include "diag.h" #include "dstring.h" @@ -8,8 +8,8 @@ #ifndef BFS_EXPR_H #define BFS_EXPR_H +#include "prelude.h" #include "color.h" -#include "config.h" #include "eval.h" #include "stat.h" #include <sys/types.h> diff --git a/src/fsade.c b/src/fsade.c index 0810c7f..d56fb07 100644 --- a/src/fsade.c +++ b/src/fsade.c @@ -1,11 +1,11 @@ // Copyright © Tavian Barnes <tavianator@tavianator.com> // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "fsade.h" #include "atomic.h" #include "bfstd.h" #include "bftw.h" -#include "config.h" #include "dir.h" #include "dstring.h" #include "sanity.h" @@ -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 } @@ -199,7 +209,7 @@ static int bfs_check_acl_type(acl_t acl, acl_type_t type) { return bfs_check_posix1e_acl(acl, false); } -#if __FreeBSD__ +#if BFS_HAS_ACL_IS_TRIVIAL_NP int trivial; int ret = acl_is_trivial_np(acl, &trivial); @@ -213,34 +223,40 @@ static int bfs_check_acl_type(acl_t acl, acl_type_t type) { } else { return 1; } -#else // !__FreeBSD__ +#else return bfs_check_posix1e_acl(acl, true); #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 1f1dbfc..eefef9f 100644 --- a/src/fsade.h +++ b/src/fsade.h @@ -9,16 +9,11 @@ #ifndef BFS_FSADE_H #define BFS_FSADE_H -#include "config.h" +#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 @@ -118,12 +118,12 @@ * [1]: https://arxiv.org/abs/2201.02179 */ +#include "prelude.h" #include "ioq.h" #include "alloc.h" #include "atomic.h" #include "bfstd.h" #include "bit.h" -#include "config.h" #include "diag.h" #include "dir.h" #include "stat.h" @@ -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 @@ -8,7 +8,7 @@ #ifndef BFS_IOQ_H #define BFS_IOQ_H -#include "config.h" +#include "prelude.h" #include "dir.h" #include "stat.h" #include <stddef.h> @@ -26,7 +26,7 @@ * - bit.h (bit manipulation) * - bfstd.[ch] (standard library wrappers/polyfills) * - color.[ch] (for pretty terminal colors) - * - config.h (configuration and feature/platform detection) + * - prelude.h (configuration and feature/platform detection) * - diag.[ch] (formats diagnostic messages) * - dir.[ch] (a directory API facade) * - dstring.[ch] (a dynamic string library) @@ -40,14 +40,13 @@ * - thread.h (multi-threading) * - trie.[ch] (a trie set/map implementation) * - typo.[ch] (fuzzy matching for typos) - * - version.c (defines the version number) * - xregex.[ch] (regular expression support) * - xspawn.[ch] (spawns processes) * - xtime.[ch] (date/time handling utilities) */ +#include "prelude.h" #include "bfstd.h" -#include "config.h" #include "ctx.h" #include "diag.h" #include "eval.h" @@ -1,10 +1,10 @@ // Copyright © Tavian Barnes <tavianator@tavianator.com> // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "mtab.h" #include "alloc.h" #include "bfstd.h" -#include "config.h" #include "stat.h" #include "trie.h" #include <errno.h> @@ -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> @@ -41,11 +40,16 @@ struct bfs_mount { char *path; /** The filesystem type. */ char *type; + /** Buffer for the strings. */ + char buf[]; }; struct bfs_mtab { + /** Mount point arena. */ + struct varena varena; + /** The array of mount points. */ - struct bfs_mount *mounts; + struct bfs_mount **mounts; /** The number of mount points. */ size_t nmounts; @@ -63,28 +67,37 @@ struct bfs_mtab { */ attr(maybe_unused) static int bfs_mtab_add(struct bfs_mtab *mtab, const char *path, const char *type) { - struct bfs_mount *mount = RESERVE(struct bfs_mount, &mtab->mounts, &mtab->nmounts); + size_t path_size = strlen(path) + 1; + size_t type_size = strlen(type) + 1; + size_t size = path_size + type_size; + struct bfs_mount *mount = varena_alloc(&mtab->varena, size); if (!mount) { return -1; } - mount->path = strdup(path); - mount->type = strdup(type); - if (!mount->path || !mount->type) { - goto fail; + struct bfs_mount **ptr = RESERVE(struct bfs_mount *, &mtab->mounts, &mtab->nmounts); + if (!ptr) { + goto free; } + *ptr = mount; + + mount->path = mount->buf; + memcpy(mount->path, path, path_size); + + mount->type = mount->buf + path_size; + memcpy(mount->type, type, type_size); const char *name = path + xbaseoff(path); if (!trie_insert_str(&mtab->names, name)) { - goto fail; + goto shrink; } return 0; -fail: - free(mount->type); - free(mount->path); +shrink: --mtab->nmounts; +free: + varena_free(&mtab->varena, mount, size); return -1; } @@ -94,6 +107,8 @@ struct bfs_mtab *bfs_mtab_parse(void) { return NULL; } + VARENA_INIT(&mtab->varena, struct bfs_mount, buf); + trie_init(&mtab->names); trie_init(&mtab->types); @@ -132,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; } @@ -195,7 +210,7 @@ static int bfs_mtab_fill_types(struct bfs_mtab *mtab) { struct bfs_stat parent_stat; for (size_t i = 0; i < mtab->nmounts; ++i) { - struct bfs_mount *mount = &mtab->mounts[i]; + struct bfs_mount *mount = mtab->mounts[i]; const char *path = mount->path; int fd = AT_FDCWD; @@ -280,11 +295,8 @@ void bfs_mtab_free(struct bfs_mtab *mtab) { trie_destroy(&mtab->types); trie_destroy(&mtab->names); - for (size_t i = 0; i < mtab->nmounts; ++i) { - free(mtab->mounts[i].type); - free(mtab->mounts[i].path); - } free(mtab->mounts); + varena_destroy(&mtab->varena); free(mtab); } @@ -8,7 +8,7 @@ #ifndef BFS_MTAB_H #define BFS_MTAB_H -#include "config.h" +#include "prelude.h" struct bfs_stat; @@ -25,11 +25,11 @@ * effects are reachable at all, skipping the traversal if not. */ +#include "prelude.h" #include "opt.h" #include "bftw.h" #include "bit.h" #include "color.h" -#include "config.h" #include "ctx.h" #include "diag.h" #include "dir.h" @@ -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/parse.c b/src/parse.c index a3e32fe..a1155c0 100644 --- a/src/parse.c +++ b/src/parse.c @@ -8,12 +8,12 @@ * flags like always-true options, and skipping over paths wherever they appear. */ +#include "prelude.h" #include "parse.h" #include "alloc.h" #include "bfstd.h" #include "bftw.h" #include "color.h" -#include "config.h" #include "ctx.h" #include "diag.h" #include "dir.h" @@ -1755,7 +1755,7 @@ static int parse_reftime(const struct bfs_parser *parser, struct bfs_expr *expr) fprintf(stderr, " - %04d-%02d-%02d\n", year, month, tm.tm_mday); fprintf(stderr, " - %04d-%02d-%02dT%02d:%02d:%02d\n", year, month, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); -#if __FreeBSD__ +#if BFS_HAS_TM_GMTOFF int gmtoff = tm.tm_gmtoff; #else int gmtoff = -timezone; diff --git a/src/config.h b/src/prelude.h index 2eff1fc..72f88b0 100644 --- a/src/config.h +++ b/src/prelude.h @@ -5,8 +5,8 @@ * Configuration and feature/platform detection. */ -#ifndef BFS_CONFIG_H -#define BFS_CONFIG_H +#ifndef BFS_PRELUDE_H +#define BFS_PRELUDE_H // Possible __STDC_VERSION__ values @@ -26,6 +26,8 @@ // bfs packaging configuration +#include "config.h" + #ifndef BFS_COMMAND # define BFS_COMMAND "bfs" #endif @@ -47,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 @@ -79,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 @@ -97,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 @@ -139,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 /** @@ -180,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 /** @@ -245,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) @@ -374,4 +367,4 @@ typedef long double max_align_t; // Only trigger an error on more than 9 arguments #define attr_too_many_none(...) -#endif // BFS_CONFIG_H +#endif // BFS_PRELUDE_H diff --git a/src/printf.c b/src/printf.c index 3b8269e..f8428f7 100644 --- a/src/printf.c +++ b/src/printf.c @@ -1,12 +1,12 @@ // Copyright © Tavian Barnes <tavianator@tavianator.com> // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "printf.h" #include "alloc.h" #include "bfstd.h" #include "bftw.h" #include "color.h" -#include "config.h" #include "ctx.h" #include "diag.h" #include "dir.h" @@ -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/pwcache.c b/src/pwcache.c index 79437d8..af8c237 100644 --- a/src/pwcache.c +++ b/src/pwcache.c @@ -1,9 +1,9 @@ // Copyright © Tavian Barnes <tavianator@tavianator.com> // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "pwcache.h" #include "alloc.h" -#include "config.h" #include "trie.h" #include <errno.h> #include <grp.h> diff --git a/src/sanity.h b/src/sanity.h index 423e6ff..e168b8f 100644 --- a/src/sanity.h +++ b/src/sanity.h @@ -8,7 +8,7 @@ #ifndef BFS_SANITY_H #define BFS_SANITY_H -#include "config.h" +#include "prelude.h" #include <stddef.h> #if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__) @@ -1,10 +1,10 @@ // Copyright © Tavian Barnes <tavianator@tavianator.com> // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "stat.h" #include "atomic.h" #include "bfstd.h" -#include "config.h" #include "diag.h" #include "sanity.h" #include <errno.h> @@ -13,7 +13,7 @@ #include <sys/stat.h> #include <sys/types.h> -#if BFS_USE_STATX && !BFS_HAS_LIBC_STATX +#if BFS_USE_STATX && !BFS_HAS_STATX # include <linux/stat.h> # include <sys/syscall.h> # include <unistd.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 @@ -99,23 +99,26 @@ void bfs_stat_convert(struct bfs_stat *dest, const struct stat *src) { dest->rdev = src->st_rdev; dest->mask |= BFS_STAT_RDEV; -#if BSD +#if BFS_HAS_ST_FLAGS dest->attrs = src->st_flags; dest->mask |= BFS_STAT_ATTRS; #endif - dest->atime = src->st_atim; + dest->atime = ST_ATIM(*src); dest->mask |= BFS_STAT_ATIME; - dest->ctime = src->st_ctim; + dest->ctime = ST_CTIM(*src); dest->mask |= BFS_STAT_CTIME; - dest->mtime = src->st_mtim; + dest->mtime = ST_MTIM(*src); dest->mask |= BFS_STAT_MTIME; -#if __APPLE__ || __FreeBSD__ || __NetBSD__ +#if 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; #endif } @@ -137,7 +140,7 @@ static int bfs_stat_impl(int at_fd, const char *at_path, int at_flags, struct bf * Wrapper for the statx() system call, which had no glibc wrapper prior to 2.28. */ static int bfs_statx(int at_fd, const char *at_path, int at_flags, unsigned int mask, struct statx *buf) { -#if BFS_HAS_LIBC_STATX +#if BFS_HAS_STATX int ret = statx(at_fd, at_path, at_flags, mask, buf); #else int ret = syscall(SYS_statx, at_fd, at_path, at_flags, mask, buf); @@ -294,27 +297,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) { @@ -12,21 +12,17 @@ #ifndef BFS_STAT_H #define BFS_STAT_H -#include "config.h" +#include "prelude.h" #include <sys/stat.h> #include <sys/types.h> #include <time.h> -#if defined(STATX_BASIC_STATS) && (!__ANDROID__ || __ANDROID_API__ >= 30) -# define BFS_HAS_LIBC_STATX true -#elif __linux__ +#if !BFS_HAS_STATX && BFS_HAS_STATX_SYSCALL # include <linux/stat.h> #endif #ifndef BFS_USE_STATX -# ifdef STATX_BASIC_STATS -# define BFS_USE_STATX true -# endif +# define BFS_USE_STATX (BFS_HAS_STATX || BFS_HAS_STATX_SYSCALL) #endif #if BFS_USE_SYS_PARAM_H diff --git a/src/thread.c b/src/thread.c index 200d8c3..3793896 100644 --- a/src/thread.c +++ b/src/thread.c @@ -1,9 +1,9 @@ // Copyright © Tavian Barnes <tavianator@tavianator.com> // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "thread.h" #include "bfstd.h" -#include "config.h" #include "diag.h" #include <errno.h> #include <pthread.h> diff --git a/src/thread.h b/src/thread.h index 8174fe4..db11bd8 100644 --- a/src/thread.h +++ b/src/thread.h @@ -8,7 +8,7 @@ #ifndef BFS_THREAD_H #define BFS_THREAD_H -#include "config.h" +#include "prelude.h" #include <pthread.h> #if __STDC_VERSION__ < C23 && !defined(thread_local) @@ -81,10 +81,10 @@ * and insert intermediate singleton "jump" nodes when necessary. */ +#include "prelude.h" #include "trie.h" #include "alloc.h" #include "bit.h" -#include "config.h" #include "diag.h" #include "list.h" #include <stdint.h> diff --git a/src/xregex.c b/src/xregex.c index 3df27f0..c2711bc 100644 --- a/src/xregex.c +++ b/src/xregex.c @@ -1,10 +1,10 @@ // Copyright © Tavian Barnes <tavianator@tavianator.com> // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "xregex.h" #include "alloc.h" #include "bfstd.h" -#include "config.h" #include "diag.h" #include "sanity.h" #include "thread.h" diff --git a/src/xspawn.c b/src/xspawn.c index 347625d..0b0cea4 100644 --- a/src/xspawn.c +++ b/src/xspawn.c @@ -1,10 +1,10 @@ // Copyright © Tavian Barnes <tavianator@tavianator.com> // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "xspawn.h" #include "alloc.h" #include "bfstd.h" -#include "config.h" #include "list.h" #include <errno.h> #include <fcntl.h> @@ -212,7 +212,7 @@ int bfs_spawn_adddup2(struct bfs_spawn *ctx, int oldfd, int newfd) { * but macOS and NetBSD resolve the PATH *before* file_actions (because there * posix_spawn() is its own syscall). */ -#define BFS_POSIX_SPAWNP_AFTER_FCHDIR !(__APPLE__ || __NetBSD_Prereq__(10, 0, 0)) +#define BFS_POSIX_SPAWNP_AFTER_FCHDIR !(__APPLE__ || __NetBSD__) int bfs_spawn_addfchdir(struct bfs_spawn *ctx, int fd) { struct bfs_spawn_action *action = bfs_spawn_action(BFS_SPAWN_FCHDIR); @@ -220,29 +220,15 @@ int bfs_spawn_addfchdir(struct bfs_spawn *ctx, int fd) { return -1; } -#ifndef BFS_HAS_POSIX_SPAWN_FCHDIR -# define BFS_HAS_POSIX_SPAWN_FCHDIR __NetBSD_Prereq__(10, 0, 0) -#endif - -#ifndef BFS_HAS_POSIX_SPAWN_FCHDIR_NP -# if __GLIBC__ -# define BFS_HAS_POSIX_SPAWN_FCHDIR_NP __GLIBC_PREREQ(2, 29) -# elif __ANDROID__ -# define BFS_HAS_POSIX_SPAWN_FCHDIR_NP (__ANDROID_API__ >= 34) -# else -# define BFS_HAS_POSIX_SPAWN_FCHDIR_NP (__linux__ || __FreeBSD__ || __APPLE__) -# endif -#endif - -#if BFS_HAS_POSIX_SPAWN_FCHDIR -# define BFS_POSIX_SPAWN_FCHDIR posix_spawn_file_actions_addfchdir -#elif BFS_HAS_POSIX_SPAWN_FCHDIR_NP -# define BFS_POSIX_SPAWN_FCHDIR posix_spawn_file_actions_addfchdir_np +#if BFS_HAS_POSIX_SPAWN_ADDFCHDIR +# define BFS_POSIX_SPAWN_ADDFCHDIR posix_spawn_file_actions_addfchdir +#elif BFS_HAS_POSIX_SPAWN_ADDFCHDIR_NP +# define BFS_POSIX_SPAWN_ADDFCHDIR posix_spawn_file_actions_addfchdir_np #endif #if _POSIX_SPAWN > 0 && defined(BFS_POSIX_SPAWN_FCHDIR) if (ctx->flags & BFS_SPAWN_USE_POSIX) { - errno = BFS_POSIX_SPAWN_FCHDIR(&ctx->actions, fd); + errno = BFS_POSIX_SPAWN_ADDFCHDIR(&ctx->actions, fd); if (errno != 0) { free(action); return -1; diff --git a/src/xspawn.h b/src/xspawn.h index a20cbd0..6a8f54a 100644 --- a/src/xspawn.h +++ b/src/xspawn.h @@ -8,7 +8,7 @@ #ifndef BFS_XSPAWN_H #define BFS_XSPAWN_H -#include "config.h" +#include "prelude.h" #include <sys/resource.h> #include <sys/types.h> #include <unistd.h> diff --git a/src/xtime.c b/src/xtime.c index bcf6dd3..eb11afa 100644 --- a/src/xtime.c +++ b/src/xtime.c @@ -1,10 +1,11 @@ // Copyright © Tavian Barnes <tavianator@tavianator.com> // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "xtime.h" #include "bfstd.h" -#include "config.h" #include "diag.h" +#include "sanity.h" #include <errno.h> #include <limits.h> #include <sys/time.h> @@ -12,13 +13,13 @@ #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)) { + if (!localtime_r(&time, &tmp)) { bfs_bug("localtime_r(-1): %s", xstrerror(errno)); 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_bug("gmtime_r(-1): %s", xstrerror(errno)); + 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/alloc.c b/tests/alloc.c index 9f08111..6c0defd 100644 --- a/tests/alloc.c +++ b/tests/alloc.c @@ -1,10 +1,10 @@ // Copyright © Tavian Barnes <tavianator@tavianator.com> // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "tests.h" -#include "../src/alloc.h" -#include "../src/config.h" -#include "../src/diag.h" +#include "alloc.h" +#include "diag.h" #include <errno.h> #include <stdlib.h> #include <stdint.h> diff --git a/tests/bfstd.c b/tests/bfstd.c index dc5ceaa..07b68b0 100644 --- a/tests/bfstd.c +++ b/tests/bfstd.c @@ -1,10 +1,10 @@ // Copyright © Tavian Barnes <tavianator@tavianator.com> // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "tests.h" -#include "../src/bfstd.h" -#include "../src/config.h" -#include "../src/diag.h" +#include "bfstd.h" +#include "diag.h" #include <errno.h> #include <langinfo.h> #include <stdlib.h> diff --git a/tests/bit.c b/tests/bit.c index 6548c30..674d1b2 100644 --- a/tests/bit.c +++ b/tests/bit.c @@ -1,10 +1,10 @@ // Copyright © Tavian Barnes <tavianator@tavianator.com> // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "tests.h" -#include "../src/bit.h" -#include "../src/config.h" -#include "../src/diag.h" +#include "bit.h" +#include "diag.h" #include <limits.h> #include <stdint.h> #include <string.h> 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 1ce8f75..ef5ee3b 100644 --- a/tests/ioq.c +++ b/tests/ioq.c @@ -1,12 +1,12 @@ // Copyright © Tavian Barnes <tavianator@tavianator.com> // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "tests.h" -#include "../src/ioq.h" -#include "../src/bfstd.h" -#include "../src/config.h" -#include "../src/diag.h" -#include "../src/dir.h" +#include "ioq.h" +#include "bfstd.h" +#include "diag.h" +#include "dir.h" #include <errno.h> #include <fcntl.h> #include <stdlib.h> diff --git a/tests/main.c b/tests/main.c index 69903d4..429772b 100644 --- a/tests/main.c +++ b/tests/main.c @@ -5,10 +5,10 @@ * Entry point for unit tests. */ +#include "prelude.h" #include "tests.h" -#include "../src/bfstd.h" -#include "../src/color.h" -#include "../src/config.h" +#include "bfstd.h" +#include "color.h" #include <errno.h> #include <locale.h> #include <stdio.h> diff --git a/tests/mksock.c b/tests/mksock.c index f3b61da..5786cb6 100644 --- a/tests/mksock.c +++ b/tests/mksock.c @@ -6,7 +6,7 @@ * program does the job. */ -#include "../src/bfstd.h" +#include "bfstd.h" #include <errno.h> #include <stdio.h> #include <stdlib.h> diff --git a/tests/run.sh b/tests/run.sh index ab1ed6d..ad9c0be 100644 --- a/tests/run.sh +++ b/tests/run.sh @@ -430,7 +430,7 @@ make_xattrs() { EX_DIFF=20 # Detect colored diff support -if diff --color /dev/null /dev/null 2>/dev/null; then +if diff --color /dev/null /dev/null &>/dev/null; then DIFF="diff --color" else DIFF="diff" @@ -447,7 +447,7 @@ diff_output() { if ((UPDATE)); then cp "$OUT" "$GOLD" - else + elif ! cmp -s "$GOLD" "$OUT"; then $DIFF -u "$GOLD" "$OUT" >&$DUPERR fi } diff --git a/tests/tests.h b/tests/tests.h index 351badb..9078938 100644 --- a/tests/tests.h +++ b/tests/tests.h @@ -8,8 +8,8 @@ #ifndef BFS_TESTS_H #define BFS_TESTS_H -#include "../src/config.h" -#include "../src/diag.h" +#include "prelude.h" +#include "diag.h" /** Unit test function type. */ typedef bool test_fn(void); diff --git a/tests/trie.c b/tests/trie.c index fec0de2..4667322 100644 --- a/tests/trie.c +++ b/tests/trie.c @@ -1,10 +1,10 @@ // Copyright © Tavian Barnes <tavianator@tavianator.com> // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "tests.h" -#include "../src/trie.h" -#include "../src/config.h" -#include "../src/diag.h" +#include "trie.h" +#include "diag.h" #include <stdlib.h> #include <string.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 c1bac36..785ea48 100644 --- a/tests/xspawn.c +++ b/tests/xspawn.c @@ -1,12 +1,12 @@ // Copyright © Tavian Barnes <tavianator@tavianator.com> // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "tests.h" -#include "../src/alloc.h" -#include "../src/bfstd.h" -#include "../src/config.h" -#include "../src/dstring.h" -#include "../src/xspawn.h" +#include "alloc.h" +#include "bfstd.h" +#include "dstring.h" +#include "xspawn.h" #include <stdlib.h> #include <string.h> #include <sys/wait.h> @@ -64,27 +64,20 @@ 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()"); - if (!ret) { - goto destroy; - } - - ret &= bfs_pcheck(bfs_spawn_addopen(&spawn, 10, bin, O_RDONLY | O_DIRECTORY, 0) == 0); + 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; + goto destroy; } // Check that $PATH is resolved in the parent's environment char **envp; ret &= bfs_pcheck(envp = envdup()); if (!ret) { - goto bin; + goto destroy; } // Check that $PATH is resolved after the file actions @@ -138,8 +131,6 @@ env: free(*var); } free(envp); -bin: - dstrfree(bin); destroy: ret &= bfs_pcheck(bfs_spawn_destroy(&spawn) == 0); out: diff --git a/tests/xtime.c b/tests/xtime.c index f85402e..d9d6c5c 100644 --- a/tests/xtime.c +++ b/tests/xtime.c @@ -1,11 +1,11 @@ // Copyright © Tavian Barnes <tavianator@tavianator.com> // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "tests.h" -#include "../src/xtime.h" -#include "../src/bfstd.h" -#include "../src/config.h" -#include "../src/diag.h" +#include "xtime.h" +#include "bfstd.h" +#include "diag.h" #include <errno.h> #include <limits.h> #include <stdint.h> @@ -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 fad272f..cd41842 100644 --- a/tests/xtouch.c +++ b/tests/xtouch.c @@ -1,10 +1,10 @@ // Copyright © Tavian Barnes <tavianator@tavianator.com> // SPDX-License-Identifier: 0BSD -#include "../src/bfstd.h" -#include "../src/config.h" -#include "../src/sanity.h" -#include "../src/xtime.h" +#include "prelude.h" +#include "bfstd.h" +#include "sanity.h" +#include "xtime.h" #include <errno.h> #include <fcntl.h> #include <stdio.h> @@ -237,8 +237,8 @@ int main(int argc, char *argv[]) { fprintf(stderr, "%s: '%s': %s\n", cmd, rarg, xstrerror(errno)); return EXIT_FAILURE; } - times[0] = buf.st_atim; - times[1] = buf.st_mtim; + times[0] = ST_ATIM(buf); + times[1] = ST_MTIM(buf); } else if (darg) { if (xgetdate(darg, ×[0]) != 0) { fprintf(stderr, "%s: Parsing time '%s' failed: %s\n", cmd, darg, xstrerror(errno)); |