pax_global_header00006660000000000000000000000064147576402450014530gustar00rootroot0000000000000052 comment=a561d782265c2157f270f0ead6b88db24e433d92 bfs-4.0.6/000077500000000000000000000000001475764024500123115ustar00rootroot00000000000000bfs-4.0.6/LICENSE000066400000000000000000000012721475764024500133200ustar00rootroot00000000000000Copyright © 2015-2025 Tavian Barnes and the bfs contributors Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. bfs-4.0.6/Makefile000066400000000000000000000177321475764024500137630ustar00rootroot00000000000000# Copyright © Tavian Barnes # SPDX-License-Identifier: 0BSD # To build bfs, run # # $ ./configure # $ make # Utilities and GNU/BSD portability include build/prelude.mk # The default build target default: bfs .PHONY: default # 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. Use `./configure --enable-%s` instead.\n' \ "${MAKE}" $@ $@ >&2 @false # Print an error if `make` is run before `./configure` gen/config.mk:: if ! [ -e $@ ]; then \ printf 'error: You must run `./configure` before `%s`.\n' "${MAKE}" >&2; \ false; \ fi .SILENT: gen/config.mk ## Build phase (`make`) # The main binary bfs: bin/bfs .PHONY: bfs # All binaries BINS := \ bin/bfs \ bin/tests/mksock \ bin/tests/units \ bin/tests/xspawnee \ bin/tests/xtouch \ bin/bench/ioq all: ${BINS} .PHONY: all # All object files except the entry point LIBBFS := \ obj/src/alloc.o \ obj/src/bar.o \ obj/src/bfstd.o \ obj/src/bftw.o \ obj/src/color.o \ obj/src/ctx.o \ obj/src/diag.o \ obj/src/dir.o \ obj/src/dstring.o \ obj/src/eval.o \ obj/src/exec.o \ obj/src/expr.o \ obj/src/fsade.o \ obj/src/ioq.o \ obj/src/mtab.o \ obj/src/opt.o \ obj/src/parse.o \ obj/src/printf.o \ obj/src/pwcache.o \ obj/src/sighook.o \ obj/src/stat.o \ obj/src/thread.o \ obj/src/trie.o \ obj/src/typo.o \ obj/src/version.o \ obj/src/xregex.o \ obj/src/xspawn.o \ obj/src/xtime.o # All object files OBJS := ${LIBBFS} # The main binary bin/bfs: obj/src/main.o ${LIBBFS} OBJS += obj/src/main.o ${BINS}: @${MKDIR} ${@D} +${MSG} "[ LD ] $@" ${CC} ${_CFLAGS} ${_LDFLAGS} ${.ALLSRC} ${_LDLIBS} -o $@ ${POSTLINK} # Get the .c file for a .o file CSRC = ${@:obj/%.o=%.c} # Save the version number to this file, but only update version.c if it changes gen/version.i.new:: ${MKDIR} ${@D} build/version.sh | tr -d '\n' | build/embed.sh >$@ .SILENT: gen/version.i.new gen/version.i: gen/version.i.new test -e $@ && cmp -s $@ ${.ALLSRC} && ${RM} ${.ALLSRC} || mv ${.ALLSRC} $@ .SILENT: gen/version.i obj/src/version.o: gen/version.i ## Test phase (`make check`) # Unit test binaries UTEST_BINS := \ bin/tests/units \ bin/tests/xspawnee # Integration test binaries ITEST_BINS := \ bin/tests/mksock \ bin/tests/xtouch # Build (but don't run) test binaries tests: ${UTEST_BINS} ${ITEST_BINS} .PHONY: tests # Run all the tests check: unit-tests integration-tests .PHONY: check # Run the unit tests unit-tests: ${UTEST_BINS} ${MSG} "[TEST] tests/units" bin/tests/units .PHONY: unit-tests # Unit test objects UNIT_OBJS := \ obj/tests/alloc.o \ obj/tests/bfstd.o \ obj/tests/bit.o \ obj/tests/ioq.o \ obj/tests/list.o \ obj/tests/main.o \ obj/tests/sighook.o \ obj/tests/trie.o \ obj/tests/xspawn.o \ obj/tests/xtime.o bin/tests/units: ${UNIT_OBJS} ${LIBBFS} OBJS += ${UNIT_OBJS} bin/tests/xspawnee: obj/tests/xspawnee.o OBJS += obj/tests/xspawnee.o # The different flag combinations we check INTEGRATIONS := default dfs ids eds j1 j2 j3 s INTEGRATION_TESTS := ${INTEGRATIONS:%=check-%} # Check just `bfs` check-default: bin/bfs ${ITEST_BINS} +${MSG} "[TEST] bfs" \ ./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} +${MSG} "[TEST] bfs -S ${@:check-%=%}" \ ./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} +${MSG} "[TEST] bfs -${@:check-%=%}" \ ./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 ${LIBBFS} OBJS += obj/tests/mksock.o bin/tests/xtouch: obj/tests/xtouch.o ${LIBBFS} OBJS += obj/tests/xtouch.o # `make distcheck` configurations DISTCHECKS := \ distcheck-asan \ distcheck-msan \ distcheck-tsan \ distcheck-m32 \ distcheck-release # Test multiple configurations distcheck: @+${MAKE} distcheck-asan @+test "$$(uname)" = Darwin || ${MAKE} distcheck-msan @+test "$$(uname)" = FreeBSD || ${MAKE} distcheck-tsan @+test "$$(uname)-$$(uname -m)" != Linux-x86_64 || ${MAKE} distcheck-m32 @+${MAKE} distcheck-release @+${MAKE} -C distcheck-release check-install @+test "$$(uname)" != Linux || ${MAKE} check-man .PHONY: distcheck # Per-distcheck configuration 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 := --enable-release ${DISTCHECKS}:: @${MKDIR} $@ @test "$${GITHUB_ACTIONS-}" != true || printf '::group::%s\n' $@ @+cd $@ \ && ../configure MAKE="${MAKE}" ${DISTCHECK_CONFIG_${@:distcheck-%=%}} \ && ${MAKE} check TEST_FLAGS="--sudo --verbose=skipped" @test "$${GITHUB_ACTIONS-}" != true || printf '::endgroup::\n' ## Benchmarks (`make bench`) bench: bin/bench/ioq .PHONY: bench bin/bench/ioq: obj/bench/ioq.o ${LIBBFS} OBJS += obj/bench/ioq.o ## Automatic dependency tracking # Rebuild when the configuration changes ${OBJS}: gen/config.mk @${MKDIR} ${@D} ${MSG} "[ CC ] ${CSRC}" ${CC} ${_CPPFLAGS} ${_CFLAGS} -c ${CSRC} -o $@ # Include any generated dependency files -include ${OBJS:.o=.d} ## Packaging (`make dist`, `make install`) TARBALL = bfs-$$(build/version.sh).tar.gz dist: ${MSG} "[DIST] ${TARBALL}" git archive HEAD -o ${TARBALL} distsign: dist ${MSG} "[SIGN] ${TARBALL}" ssh-keygen -Y sign -q -f $$(git config user.signingkey) -n file ${TARBALL} .PHONY: dist distsign DEST_PREFIX := ${DESTDIR}${PREFIX} DEST_MANDIR := ${DESTDIR}${MANDIR} install:: ${Q}${MKDIR} ${DEST_PREFIX}/bin ${MSG} "[INST] bin/bfs" \ ${INSTALL} -m755 bin/bfs ${DEST_PREFIX}/bin/bfs ${Q}${MKDIR} ${DEST_MANDIR}/man1 ${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} "[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} "[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} "[INST] completions/bfs.fish" \ ${INSTALL} -m644 completions/bfs.fish ${DEST_PREFIX}/share/fish/vendor_completions.d/bfs.fish uninstall:: ${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=pkg +${MAKE} uninstall DESTDIR=pkg bin/bfs pkg -not -type d -print -exit 1 ${RM} -r pkg # Check man page markup check-man:: ${MSG} "[LINT] docs/bfs.1" ${Q}groff -man -rCHECKSTYLE=3 -ww -b -z docs/bfs.1 ${Q}mandoc -Tlint -Wwarning docs/bfs.1 ## Cleanup (`make clean`) # Clean all build products clean:: ${MSG} "[ RM ] bin obj" \ ${RM} -r bin obj # Clean everything, including generated files distclean: clean ${MSG} "[ RM ] gen distcheck-*" \ ${RM} -r gen ${DISTCHECKS} .PHONY: distclean bfs-4.0.6/README.md000066400000000000000000000222421475764024500135720ustar00rootroot00000000000000

bfs
Version License CI Status Code coverage

**[Features]   •   [Installation]   •   [Usage]   •   [Building]   •   [Contributing]   •   [Changelog]** [Features]: #features [Installation]: #installation [Usage]: /docs/USAGE.md [Building]: /docs/BUILDING.md [Contributing]: /docs/CONTRIBUTING.md [Changelog]: /docs/CHANGELOG.md Screencast

`bfs` is a variant of the UNIX `find` command that operates [**breadth-first**](https://en.wikipedia.org/wiki/Breadth-first_search) rather than [**depth-first**](https://en.wikipedia.org/wiki/Depth-first_search). It is otherwise compatible with many versions of `find`, including
**[POSIX]   •   [GNU]   •   [FreeBSD]   •   [OpenBSD]   •   [NetBSD]   •   [macOS]** [POSIX]: https://pubs.opengroup.org/onlinepubs/9799919799/utilities/find.html [GNU]: https://www.gnu.org/software/findutils/ [FreeBSD]: https://www.freebsd.org/cgi/man.cgi?find(1) [OpenBSD]: https://man.openbsd.org/find.1 [NetBSD]: https://man.netbsd.org/find.1 [macOS]: https://ss64.com/osx/find.html
If you're not familiar with `find`, the [GNU find manual](https://www.gnu.org/software/findutils/manual/html_mono/find.html) provides a good introduction. Features --------
bfs operates breadth-first, which typically finds the file(s) you're looking for faster.

Imagine the following directory tree:
haystack
├── deep
│   └── 1
│       └── 2
│           └── 3
│               └── 4
│                   └── ...
└── shallow
    └── needle
`find` will explore the entire `deep` directory tree before it ever gets to the `shallow` one that contains what you're looking for. On the other hand, `bfs` lists files from shallowest to deepest, so you never have to wait for it to explore an entire unrelated subtree.
bfsfind
```console $ bfs haystack haystack haystack/deep haystack/shallow haystack/deep/1 haystack/shallow/needle ... ``` ```console $ find haystack haystack haystack/deep haystack/deep/1 haystack/deep/1/2 haystack/deep/1/2/3 haystack/deep/1/2/3/4 ... haystack/shallow haystack/shallow/needle ```
bfs tries to be easier to use than find, while remaining compatible.

For example, `bfs` is less picky about where you put its arguments:
bfsfind
```console $ bfs -L -name 'needle' haystack haystack/needle $ bfs haystack -L -name 'needle' haystack/needle $ bfs -L haystack -name 'needle' haystack/needle ``` ```console $ find -L -name 'needle' haystack find: paths must precede expression: haystack $ find haystack -L -name 'needle' find: unknown predicate `-L' $ find -L haystack -name 'needle' haystack/needle ```
bfs gives helpful errors and warnings.

For example, `bfs` will detect and suggest corrections for typos: ```console $ bfs -nam needle bfs: error: bfs -nam needle bfs: error: ~~~~ bfs: error: Unknown argument; did you mean -name? ``` `bfs` also includes a powerful static analysis to help catch mistakes: ```console $ bfs -print -name 'needle' bfs: warning: bfs -print -name needle bfs: warning: ~~~~~~~~~~~~ bfs: warning: The result of this expression is ignored. ```
bfs adds some options that make common tasks easier.

For example, the `-exclude` operator skips over entire subtrees whenever an expression matches. `-exclude` is both more powerful and easier to use than the standard `-prune` action; compare
$ bfs -name config -exclude -name .git
to the equivalent
$ find ! \( -name .git -prune \) -name config
As an additional shorthand, `-nohidden` skips over all hidden files and directories. See the [usage documentation](/docs/USAGE.md#extensions) for more about the extensions provided by `bfs`.
Installation ------------
bfs may already be packaged for your operating system.

LinuxmacOS
Alpine Linux
# apk add bfs

Arch Linux
# pacman -S bfs

Debian/Ubuntu
# apt install bfs

Fedora Linux
# dnf install bfs

Gentoo
# emerge sys-apps/bfs

GNU Guix
# guix install bfs

NixOS
# nix-env -i bfs

Void Linux
# xbps-install -S bfs
Homebrew
$ brew install bfs

MacPorts
# port install bfs
BSD
FreeBSD
# pkg install bfs

OpenBSD
# pkg_add bfs
To build bfs from source, you may need to install some dependencies.

The only absolute requirements for building `bfs` are a C compiler, [GNU make](https://www.gnu.org/software/make/), and [Bash](https://www.gnu.org/software/bash/). These are installed by default on many systems, and easy to install on most others. Refer to your operating system's documentation on building software. `bfs` also depends on some system libraries for some of its features. Here's how to install them on some common platforms:
Alpine Linux
# apk add acl{,-dev} attr libcap{,-dev} liburing-dev oniguruma-dev

Arch Linux
# pacman -S acl attr libcap liburing oniguruma

Debian/Ubuntu
# apt install acl libacl1-dev attr libattr1-dev libcap2-bin libcap-dev liburing-dev libonig-dev

Fedora
# dnf install acl libacl-devel attr libcap-devel liburing-devel oniguruma-devel

NixOS
# nix-env -i acl attr libcap liburing oniguruma

Void Linux
# xbps-install -S acl-{devel,progs} attr-progs libcap-{devel,progs} liburing-devel oniguruma-devel

Homebrew
$ brew install oniguruma

MacPorts
# port install oniguruma6

FreeBSD
# pkg install oniguruma
These dependencies are technically optional, though strongly recommended. See the [build documentation](/docs/BUILDING.md#dependencies) for how to disable them.
Once you have the dependencies, you can build bfs.

Download one of the [releases](https://github.com/tavianator/bfs/releases) or clone the [git repo](https://github.com/tavianator/bfs). Then run $ ./configure $ make This will build the `./bin/bfs` binary. Run the test suite to make sure it works correctly: $ make check If you're interested in speed, you may want to build the release version instead: $ ./configure --enable-release $ make Finally, if you want to install it globally, run # make install
bfs-4.0.6/bench/000077500000000000000000000000001475764024500133705ustar00rootroot00000000000000bfs-4.0.6/bench/README.md000066400000000000000000000035761475764024500146620ustar00rootroot00000000000000This directory contains a suite of benchmarks used to evaluate `bfs` and detect performance regressions. To run them, you'll need the [tailfin] benchmark harness. You can read the full usage information with [tailfin]: https://github.com/tavianator/tailfin ```console $ tailfin -n run bench/bench.sh --help Usage: tailfin run bench/bench.sh [--default] [--complete] [--early-quit] [--print] [--strategies] [--build=...] [--bfs] [--find] [--fd] [--no-clean] [--help] ... ``` The benchmarks use various git repositories to have a realistic and reproducible directory structure as a corpus. Currently, those are the [Linux], [Rust], and [Chromium] repos. The scripts will automatically clone those repos using [partial clone] filters to avoid downloading the actual file contents, saving bandwidth and space. [Linux]: https://github.com/torvalds/linux.git [Rust]: https://github.com/rust-lang/rust.git [Chromium]: https://chromium.googlesource.com/chromium/src.git [partial clone]: https://git-scm.com/docs/partial-clone You can try out a quick benchmark by running ```console $ tailfin run bench/bench.sh --build=main --complete=linux ``` This will build the `main` branch, and measure the complete traversal of the Linux repo. Results will be both printed to the console and saved in a Markdown file, which you can find by running ```console $ tailfin latest results/2023/09/29/15:32:49 $ cat results/2023/09/29/15:32:49/runs/1/bench.md ## Complete traversal ... ``` To measure performance improvements/regressions of a change, compare the `main` branch to the topic branch on the full benchmark suite: ```console $ tailfin run bench/bench.sh --build=main --build=branch --default ``` This will take a few minutes. Results from the full benchmark suite can be seen in performance-related pull requests, for example [#126]. [#126]: https://github.com/tavianator/bfs/pull/126 bfs-4.0.6/bench/bench.sh000066400000000000000000000417751475764024500150210ustar00rootroot00000000000000#!/hint/bash # Copyright © Tavian Barnes # SPDX-License-Identifier: 0BSD declare -gA URLS=( [chromium]="https://chromium.googlesource.com/chromium/src.git" [linux]="https://github.com/torvalds/linux.git" [rust]="https://github.com/rust-lang/rust.git" ) declare -gA TAGS=( [chromium]=119.0.6036.2 [linux]=v6.5 [rust]=1.72.1 ) COMPLETE_DEFAULT=(linux rust chromium) EARLY_QUIT_DEFAULT=(chromium) STAT_DEFAULT=(rust) PRINT_DEFAULT=(linux) STRATEGIES_DEFAULT=(rust) JOBS_DEFAULT=(rust) EXEC_DEFAULT=(linux) usage() { printf 'Usage: tailfin run %s\n' "${BASH_SOURCE[0]}" printf ' [--default] [-- [--...]]\n' printf ' [--build=...] [--bfs] [--find] [--fd]\n' printf ' [--no-clean] [--help]\n\n' printf ' --default\n' printf ' Run the default set of benchmarks\n\n' printf ' --complete[=CORPUS]\n' printf ' Complete traversal benchmark.\n' printf ' Default corpus is --complete="%s"\n\n' "${COMPLETE_DEFAULT[*]}" printf ' --early-quit[=CORPUS]\n' printf ' Early quitting benchmark.\n' printf ' Default corpus is --early-quit=%s\n\n' "${EARLY_QUIT_DEFAULT[*]}" printf ' --stat[=CORPUS]\n' printf ' Traversal with stat().\n' printf ' Default corpus is --stat=%s\n\n' "${STAT_DEFAULT[*]}" printf ' --print[=CORPUS]\n' printf ' Path printing benchmark.\n' printf ' Default corpus is --print=%s\n\n' "${PRINT_DEFAULT[*]}" printf ' --strategies[=CORPUS]\n' printf ' Search strategy benchmark.\n' printf ' Default corpus is --strategies=%s\n\n' "${STRATEGIES_DEFAULT[*]}" printf ' --jobs[=CORPUS]\n' printf ' Parallelism benchmark.\n' printf ' Default corpus is --jobs=%s\n\n' "${JOBS_DEFAULT[*]}" printf ' --exec[=CORPUS]\n' printf ' Process spawning benchmark.\n' printf ' Default corpus is --exec=%s\n\n' "${EXEC_DEFAULT[*]}" printf ' --build=COMMIT\n' printf ' Build this bfs commit and benchmark it. Specify multiple times to\n' printf ' compare, e.g. --build=3.0.1 --build=3.0.2\n\n' printf ' --bfs[=COMMAND]\n' printf ' Benchmark an existing build of bfs\n\n' printf ' --find[=COMMAND]\n' printf ' Compare against find\n\n' printf ' --fd[=COMMAND]\n' printf ' Compare against fd\n\n' printf ' --no-clean\n' printf ' Use any existing corpora as-is\n\n' printf ' --help\n' printf ' This message\n\n' } # Hack to export an array export_array() { local str=$(declare -p "$1" | sed 's/ -a / -ga /') unset "$1" export "$1=$str" } # Hack to import an array import_array() { local cmd="${!1}" unset "$1" eval "$cmd" } # Set up the benchmarks setup() { ROOT=$(realpath -- "$(dirname -- "${BASH_SOURCE[0]}")/..") if ! [ "$PWD" -ef "$ROOT" ]; then printf 'error: Please run this script from %s\n\n' "$ROOT" >&2 usage >&2 exit $EX_USAGE fi nproc=$(nproc) # Options CLEAN=1 BUILD=() BFS=() FIND=() FD=() COMPLETE=() EARLY_QUIT=() STAT=() PRINT=() STRATEGIES=() JOBS=() EXEC=() for arg; do case "$arg" in # Flags --no-clean) CLEAN=0 ;; # bfs commits/tags to benchmark --build=*) BUILD+=("${arg#*=}") BFS+=("bfs-${arg#*=}") ;; # Utilities to benchmark against --bfs) BFS+=(bfs) ;; --bfs=*) BFS+=("${arg#*=}") ;; --find) FIND+=(find) ;; --find=*) FIND+=("${arg#*=}") ;; --fd) FD+=(fd) ;; --fd=*) FD+=("${arg#*=}") ;; # Benchmark groups --complete) COMPLETE=("${COMPLETE_DEFAULT[@]}") ;; --complete=*) read -ra COMPLETE <<<"${arg#*=}" ;; --early-quit) EARLY_QUIT=("${EARLY_QUIT_DEFAULT[@]}") ;; --early-quit=*) read -ra EARLY_QUIT <<<"${arg#*=}" ;; --stat) STAT=("${STAT_DEFAULT[@]}") ;; --stat=*) read -ra STAT <<<"${arg#*=}" ;; --print) PRINT=("${PRINT_DEFAULT[@]}") ;; --print=*) read -ra PRINT <<<"${arg#*=}" ;; --strategies) STRATEGIES=("${STRATEGIES_DEFAULT[@]}") ;; --strategies=*) read -ra STRATEGIES <<<"${arg#*=}" ;; --jobs) JOBS=("${JOBS_DEFAULT[@]}") ;; --jobs=*) read -ra JOBS <<<"${arg#*=}" ;; --exec) EXEC=("${EXEC_DEFAULT[@]}") ;; --exec=*) read -ra EXEC <<<"${arg#*=}" ;; --default) COMPLETE=("${COMPLETE_DEFAULT[@]}") EARLY_QUIT=("${EARLY_QUIT_DEFAULT[@]}") STAT=("${STAT_DEFAULT[@]}") PRINT=("${PRINT_DEFAULT[@]}") STRATEGIES=("${STRATEGIES_DEFAULT[@]}") JOBS=("${JOBS_DEFAULT[@]}") EXEC=("${EXEC_DEFAULT[@]}") ;; --help) usage exit ;; *) printf 'error: Unknown option %q\n\n' "$arg" >&2 usage >&2 exit $EX_USAGE ;; esac done if ((UID == 0)); then max-freq fi echo "Building bfs ..." as-user ./configure --enable-release as-user make -s -j"$nproc" all as-user mkdir -p bench/corpus declare -A cloned=() for corpus in "${COMPLETE[@]}" "${EARLY_QUIT[@]}" "${STAT[@]}" "${PRINT[@]}" "${STRATEGIES[@]}" "${JOBS[@]}" "${EXEC[@]}"; do if ((cloned["$corpus"])); then continue fi cloned["$corpus"]=1 dir="bench/corpus/$corpus" if ((CLEAN)) || ! [ -e "$dir" ]; then as-user ./bench/clone-tree.sh "${URLS[$corpus]}" "${TAGS[$corpus]}" "$dir"{,.git} fi done if ((${#BUILD[@]} > 0)); then echo "Creating bfs worktree ..." worktree="bench/worktree" as-user git worktree add -qd "$worktree" defer as-user git worktree remove "$worktree" bin="$(realpath -- "$SETUP_DIR")/bin" as-user mkdir "$bin" for commit in "${BUILD[@]}"; do ( echo "Building bfs $commit ..." cd "$worktree" as-user git checkout -qd "$commit" -- 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 as-user cp ./bfs "$bin/bfs-$commit" fi as-user make -s clean ) done export PATH="$bin:$PATH" fi export_array BFS export_array FIND export_array FD export_array COMPLETE export_array EARLY_QUIT export_array STAT export_array PRINT export_array STRATEGIES export_array JOBS export_array EXEC if ((UID == 0)); then turbo-off fi sync } # Runs hyperfine and saves the output do-hyperfine() { local tmp_md="$BENCH_DIR/.bench.md" local md="$BENCH_DIR/bench.md" local tmp_json="$BENCH_DIR/.bench.json" local json="$BENCH_DIR/bench.json" if (($# == 0)); then printf 'Nothing to do\n\n' | tee -a "$md" return 1 fi hyperfine -w2 -M20 --export-markdown="$tmp_md" --export-json="$tmp_json" "$@" &>/dev/tty cat "$tmp_md" >>"$md" cat "$tmp_json" >>"$json" rm "$tmp_md" "$tmp_json" printf '\n' | tee -a "$md" } # Print the header for a benchmark group group() { printf "## $1\\n\\n" "${@:2}" | tee -a "$BENCH_DIR/bench.md" } # Print the header for a benchmark subgroup subgroup() { printf "### $1\\n\\n" "${@:2}" | tee -a "$BENCH_DIR/bench.md" } # Print the header for a benchmark sub-subgroup subsubgroup() { printf "#### $1\\n\\n" "${@:2}" | tee -a "$BENCH_DIR/bench.md" } # Benchmark the complete traversal of a directory tree # (without printing anything) bench-complete-corpus() { total=$(./bin/bfs "$2" -printf '.' | wc -c) subgroup "%s (%'d files)" "$1" "$total" cmds=() for bfs in "${BFS[@]}"; do cmds+=("$bfs $2 -false") done for find in "${FIND[@]}"; do cmds+=("$find $2 -false") done for fd in "${FD[@]}"; do cmds+=("$fd -u '^$' $2") done do-hyperfine "${cmds[@]}" } # All complete traversal benchmarks bench-complete() { if (($#)); then group "Complete traversal" for corpus; do bench-complete-corpus "$corpus ${TAGS[$corpus]}" "bench/corpus/$corpus" done fi } # Benchmark quitting as soon as a file is seen bench-early-quit-corpus() { dir="$2" max_depth=$(./bin/bfs "$dir" -printf '%d\n' | sort -rn | head -n1) subgroup '%s (depth %d)' "$1" "$max_depth" # Save the list of unique filenames, along with their depth UNIQ="$BENCH_DIR/uniq" ./bin/bfs "$dir" -printf '%d %f\n' | sort -k2 | uniq -uf1 >"$UNIQ" for ((i = 2; i <= max_depth; i *= 2)); do subsubgroup 'Depth %d' "$i" # Sample random uniquely-named files at depth $i export FILES="$BENCH_DIR/uniq-$i" sed -n "s/^$i //p" "$UNIQ" | shuf -n20 >"$FILES" if ! [ -s "$FILES" ]; then continue fi cmds=() for bfs in "${BFS[@]}"; do cmds+=("$bfs $dir -name \$(shuf -n1 \$FILES) -print -quit") done for find in "${FIND[@]}"; do cmds+=("$find $dir -name \$(shuf -n1 \$FILES) -print -quit") done for fd in "${FD[@]}"; do cmds+=("$fd -usg1 \$(shuf -n1 \$FILES) $dir") done do-hyperfine "${cmds[@]}" done } # All early-quitting benchmarks bench-early-quit() { if (($#)); then group "Early termination" for corpus; do bench-early-quit-corpus "$corpus ${TAGS[$corpus]}" "bench/corpus/$corpus" done fi } # Benchmark traversal with stat() bench-stat-corpus() { total=$(./bin/bfs "$2" -printf '.' | wc -c) subgroup "%s (%'d files)" "$1" "$total" cmds=() for bfs in "${BFS[@]}"; do cmds+=("$bfs $2 -size 1024G") done for find in "${FIND[@]}"; do cmds+=("$find $2 -size 1024G") done for fd in "${FD[@]}"; do cmds+=("$fd -u --search-path $2 --size 1024Gi") done do-hyperfine "${cmds[@]}" } # stat() benchmarks bench-stat() { if (($#)); then group "Traversal with stat()" for corpus; do bench-stat-corpus "$corpus ${TAGS[$corpus]}" "bench/corpus/$corpus" done fi } # Benchmark printing paths without colors bench-print-nocolor() { subsubgroup '%s' "$1" cmds=() for bfs in "${BFS[@]}"; do cmds+=("$bfs $2") done for find in "${FIND[@]}"; do cmds+=("$find $2") done for fd in "${FD[@]}"; do cmds+=("$fd -u --search-path $2") done do-hyperfine "${cmds[@]}" } # Benchmark printing paths with colors bench-print-color() { subsubgroup '%s' "$1" cmds=() for bfs in "${BFS[@]}"; do cmds+=("$bfs $2 -color") done for fd in "${FD[@]}"; do cmds+=("$fd -u --search-path $2 --color=always") done do-hyperfine "${cmds[@]}" } # All printing benchmarks bench-print() { if (($#)); then group "Printing paths" subgroup "Without colors" for corpus; do bench-print-nocolor "$corpus ${TAGS[$corpus]}" "bench/corpus/$corpus" done subgroup "With colors" for corpus; do bench-print-color "$corpus ${TAGS[$corpus]}" "bench/corpus/$corpus" done fi } # Benchmark search strategies bench-strategies-corpus() { subgroup '%s' "$1" if ((${#BFS[@]} == 1)); then cmds=("$BFS -S "{bfs,dfs,ids,eds}" $2 -false") do-hyperfine "${cmds[@]}" else for S in bfs dfs ids eds; do subsubgroup '`-S %s`' "$S" cmds=() for bfs in "${BFS[@]}"; do cmds+=("$bfs -S $S $2 -false") done do-hyperfine "${cmds[@]}" done fi } # All search strategy benchmarks bench-strategies() { if (($#)); then group "Search strategies" for corpus; do bench-strategies-corpus "$corpus ${TAGS[$corpus]}" "bench/corpus/$corpus" done fi } # Benchmark parallelism bench-jobs-corpus() { subgroup '%s' "$1" if ((${#BFS[@]} + ${#FD[@]} == 1)); then cmds=() for bfs in "${BFS[@]}"; do if "$bfs" -j1 -quit &>/dev/null; then cmds+=("$bfs -j"{1,2,3,4,6,8,12,16}" $2 -false") else cmds+=("$bfs $2 -false") fi done for fd in "${FD[@]}"; do cmds+=("$fd -j"{1,2,3,4,6,8,12,16}" -u '^$' $2") done do-hyperfine "${cmds[@]}" else for j in 1 2 3 4 6 8 12 16; do subsubgroup '`-j%d`' $j cmds=() for bfs in "${BFS[@]}"; do if "$bfs" -j1 -quit &>/dev/null; then cmds+=("$bfs -j$j $2 -false") elif ((j == 1)); then cmds+=("$bfs $2 -false") fi done for fd in "${FD[@]}"; do cmds+=("$fd -j$j -u '^$' $2") done if ((${#cmds[@]})); then do-hyperfine "${cmds[@]}" fi done fi } # All parallelism benchmarks bench-jobs() { if (($#)); then group "Parallelism" for corpus; do bench-jobs-corpus "$corpus ${TAGS[$corpus]}" "bench/corpus/$corpus" done fi } # One file/process bench-exec-single() { subsubgroup "One file per process" cmds=() for cmd in "${BFS[@]}" "${FIND[@]}"; do cmds+=("$cmd $1 -maxdepth 2 -exec true -- {} \;") done for fd in "${FD[@]}"; do cmds+=("$fd -u --search-path $1 --max-depth=2 -x true --") # Without -j1, fd runs multiple processes in parallel, which is unfair cmds+=("$fd -j1 -u --search-path $1 --max-depth=2 -x true --") done do-hyperfine "${cmds[@]}" } # Many files/process bench-exec-multi() { subsubgroup "Many files per process" cmds=() for cmd in "${BFS[@]}" "${FIND[@]}"; do cmds+=("$cmd $1 -exec true -- {} +") done for fd in "${FD[@]}"; do cmds+=("$fd -u --search-path $1 -X true --") done do-hyperfine "${cmds[@]}" } # Many files, same dir bench-exec-chdir() { if ((${#BFS[@]} + ${#FIND[@]} == 0)); then return fi subsubgroup "Spawn in parent directory" cmds=() for cmd in "${BFS[@]}" "${FIND[@]}"; do cmds+=("$cmd $1 -maxdepth 3 -execdir true -- {} +") done do-hyperfine "${cmds[@]}" } # Benchmark process spawning bench-exec-corpus() { subgroup '%s' "$1" bench-exec-single "$2" bench-exec-multi "$2" bench-exec-chdir "$2" } # All process spawning benchmarks bench-exec() { if (($#)); then group "Process spawning" for corpus; do bench-exec-corpus "$corpus ${TAGS[$corpus]}" "bench/corpus/$corpus" done fi } # Print benchmarked versions bench-versions() { subgroup "Versions" local md="$BENCH_DIR/bench.md" printf '```console\n' >>"$md" { for bfs in "${BFS[@]}"; do printf '$ %s --version | head -n1\n' "$bfs" "$bfs" --version | head -n1 done for find in "${FIND[@]}"; do printf '$ %s --version | head -n1\n' "$find" "$find" --version | head -n1 done for fd in "${FD[@]}"; do printf '$ %s --version\n' "$fd" "$fd" --version done } | tee -a "$md" printf '```' >>"$md" } # Print benchmark details bench-details() { group "Details" bench-versions } # Run all the benchmarks bench() { import_array BFS import_array FIND import_array FD import_array COMPLETE import_array EARLY_QUIT import_array STAT import_array PRINT import_array STRATEGIES import_array JOBS import_array EXEC bench-complete "${COMPLETE[@]}" bench-early-quit "${EARLY_QUIT[@]}" bench-stat "${STAT[@]}" bench-print "${PRINT[@]}" bench-strategies "${STRATEGIES[@]}" bench-jobs "${JOBS[@]}" bench-exec "${EXEC[@]}" bench-details } bfs-4.0.6/bench/clone-tree.sh000077500000000000000000000072341475764024500157720ustar00rootroot00000000000000#!/usr/bin/env bash # Copyright © Tavian Barnes # SPDX-License-Identifier: 0BSD # Creates a directory tree that matches a git repo, but with empty files. E.g. # # $ ./bench/clone-tree.sh "https://.../linux.git" v6.5 ./linux ./linux.git # # will create or update a shallow clone at ./linux.git, then create a directory # tree at ./linux with the same directory tree as the tag v6.5, except all files # will be empty. set -eu if (($# != 4)); then printf 'Usage: %s https://url/of/repo.git path/to/checkout path/to/repo.git\n' "$0" >&2 exit 1 fi URL="$1" TAG="$2" DIR="$3" REPO="$4" BENCH=$(dirname -- "${BASH_SOURCE[0]}") BIN=$(realpath -- "$BENCH/../bin") BFS="$BIN/bfs" XTOUCH="$BIN/tests/xtouch" if [ "${NPROC-}" ]; then # Use fewer cores in recursive calls export NPROC=$(((NPROC + 1) / 2)) else export NPROC=$(nproc) fi JOBS=$((NPROC < 8 ? NPROC : 8)) do-git() { git -C "$REPO" "$@" } if ! [ -e "$REPO" ]; then mkdir -p -- "$REPO" do-git init -q --bare fi has-ref() { do-git rev-list --quiet -1 --missing=allow-promisor "$1" &>/dev/null } sparse-fetch() { do-git -c fetch.negotiationAlgorithm=noop fetch -q --filter=blob:none --depth=1 --no-tags --no-write-fetch-head --no-auto-gc "$@" } if ! has-ref "$TAG"; then printf 'Fetching %s ...\n' "$TAG" >&2 do-git config remote.origin.url "$URL" if ((${#TAG} >= 40)); then sparse-fetch origin "$TAG" else sparse-fetch origin tag "$TAG" fi fi # Delete a tree in parallel clean() { local d=5 "$BFS" -f "$1" -mindepth $d -maxdepth $d -type d -print0 \ | xargs -0r -n1 -P$JOBS -- "$BFS" -j1 -mindepth 1 -delete -f "$BFS" -f "$1" -delete } if [ -e "$DIR" ]; then printf 'Cleaning old directory tree %s ...\n' "$DIR" >&2 TMP=$(mktemp -dp "$(dirname -- "$DIR")") mv -- "$DIR" "$TMP" clean "$TMP" & fi # List gitlinks (submodule references) in the tree ls-gitlinks() { do-git ls-tree -zr "$TAG" \ | sed -zn 's/.* commit //p' } # Get the submodule ID for a path submodule-for-path() { do-git config --blob "$TAG:.gitmodules" \ --name-only \ --fixed-value \ --get-regexp 'submodule\..**\.path' "$1" \ | sed -En 's/submodule\.(.*)\.path/\1/p' } # Get the URL for a submodule submodule-url() { # - https://chrome-internal.googlesource.com/ # - not publicly accessible # - https://chromium.googlesource.com/external/github.com/WebKit/webkit.git # - is accessible, but the commit (59e9de61b7b3) isn't # - https://android.googlesource.com/ # - is accessible, but you need an account do-git config --blob "$TAG:.gitmodules" \ --get "submodule.$1.url" \ | sed -E \ -e '\|^https://chrome-internal.googlesource.com/|Q1' \ -e '\|^https://chromium.googlesource.com/external/github.com/WebKit/webkit.git|Q1' \ -e '\|^https://android.googlesource.com/|Q1' } # Recursively checkout submodules while read -rd '' SUBREF SUBDIR; do SUBNAME=$(submodule-for-path "$SUBDIR") SUBURL=$(submodule-url "$SUBNAME") || continue if (($(jobs -pr | wc -w) >= JOBS)); then wait -n fi "$0" "$SUBURL" "$SUBREF" "$DIR/$SUBDIR" "$REPO/modules/$SUBNAME" & done < <(ls-gitlinks) # Touch files in parallel xtouch() ( cd "$DIR" if ((JOBS > 1)); then xargs -0r -n4096 -P$JOBS -- "$XTOUCH" -p -- else xargs -0r -- "$XTOUCH" -p -- fi ) # Check out files printf 'Checking out %s ...\n' "$DIR" >&2 mkdir -p -- "$DIR" do-git ls-tree -zr "$TAG"\ | sed -zn 's/.* blob .*\t//p' \ | xtouch # Wait for cleaning/submodules wait bfs-4.0.6/bench/ioq.c000066400000000000000000000170531475764024500143320ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD #include "atomic.h" #include "bfs.h" #include "bfstd.h" #include "diag.h" #include "ioq.h" #include "sighook.h" #include "xtime.h" #include #include #include #include #include #include #include /** Which clock to use for benchmarking. */ static clockid_t clockid = CLOCK_REALTIME; /** Get a current time measurement. */ static void gettime(struct timespec *tp) { int ret = clock_gettime(clockid, tp); bfs_everify(ret == 0, "clock_gettime(%d)", (int)clockid); } /** * Time measurements. */ struct times { /** The start time. */ struct timespec start; /** Total requests started. */ size_t pushed; /** Total requests finished. */ size_t popped; /** Number of timed requests (latency). */ size_t timed_reqs; /** The start time for the currently tracked request. */ struct timespec req_start; /** Whether a timed request is currently in flight. */ bool timing; /** Latency measurements. */ struct { struct timespec min; struct timespec max; struct timespec sum; } latency; }; /** Initialize a timer. */ static void times_init(struct times *times) { *times = (struct times) { .latency = { .min = { .tv_sec = 1000 }, }, }; gettime(×->start); } /** Start timing a single request. */ static void start_request(struct times *times) { gettime(×->req_start); times->timing = true; } /** Finish timing a request. */ static void finish_request(struct times *times) { struct timespec elapsed; gettime(&elapsed); timespec_sub(&elapsed, ×->req_start); timespec_min(×->latency.min, &elapsed); timespec_max(×->latency.max, &elapsed); timespec_add(×->latency.sum, &elapsed); bfs_assert(times->timing); times->timing = false; ++times->timed_reqs; } /** Add times to the totals, and reset the lap times. */ static void times_lap(struct times *total, struct times *lap) { total->pushed += lap->pushed; total->popped += lap->popped; total->timed_reqs += lap->timed_reqs; timespec_min(&total->latency.min, &lap->latency.min); timespec_max(&total->latency.max, &lap->latency.max); timespec_add(&total->latency.sum, &lap->latency.sum); times_init(lap); } /** Print some times. */ static void times_print(const struct times *times, long seconds) { struct timespec elapsed; gettime(&elapsed); timespec_sub(&elapsed, ×->start); double fsec = timespec_ns(&elapsed) / 1.0e9; double iops = times->popped / fsec; double mean = timespec_ns(×->latency.sum) / times->timed_reqs; double min = timespec_ns(×->latency.min); double max = timespec_ns(×->latency.max); if (seconds > 0) { printf("%9ld", seconds); } else if (elapsed.tv_nsec >= 10 * 1000 * 1000) { printf("%9.2f", fsec); } else { printf("%9.0f", fsec); } printf(" │ %'17.0f │ %'15.0f ∈ [%'6.0f .. %'7.0f]\n", iops, mean, min, max); fflush(stdout); } /** Push an ioq request. */ static bool push(struct ioq *ioq, enum ioq_nop_type type, struct times *lap) { void *ptr = NULL; // Track latency for a small fraction of requests if (!lap->timing && (lap->pushed + 1) % 128 == 0) { start_request(lap); ptr = lap; } int ret = ioq_nop(ioq, type, ptr); if (ret != 0) { bfs_everify(errno == EAGAIN, "ioq_nop(%d)", (int)type); return false; } ++lap->pushed; return true; } /** Pop an ioq request. */ static bool pop(struct ioq *ioq, struct times *lap, bool block) { struct ioq_ent *ent = ioq_pop(ioq, block); if (!ent) { return false; } if (ent->ptr) { finish_request(lap); } ioq_free(ioq, ent); ++lap->popped; return true; } /** ^C flag. */ static atomic bool quit = false; /** ^C hook. */ static void ctrlc(int sig, siginfo_t *info, void *arg) { store(&quit, true, relaxed); } int main(int argc, char *argv[]) { // Use CLOCK_MONOTONIC if available #if defined(_POSIX_MONOTONIC_CLOCK) && _POSIX_MONOTONIC_CLOCK >= 0 if (sysoption(MONOTONIC_CLOCK) > 0) { clockid = CLOCK_MONOTONIC; } #endif // Enable thousands separators setlocale(LC_ALL, ""); // -d: queue depth long depth = 4096; // -j: threads long threads = 0; // -t: timeout double timeout = 5.0; // -L|-H: ioq_nop() type enum ioq_nop_type type = IOQ_NOP_LIGHT; const char *cmd = argc > 0 ? argv[0] : "ioq"; int c; while (c = getopt(argc, argv, ":d:j:t:LH"), c != -1) { switch (c) { case 'd': if (xstrtol(optarg, NULL, 10, &depth) != 0) { fprintf(stderr, "%s: Bad depth '%s': %s\n", cmd, optarg, errstr()); return EXIT_FAILURE; } break; case 'j': if (xstrtol(optarg, NULL, 10, &threads) != 0) { fprintf(stderr, "%s: Bad thread count '%s': %s\n", cmd, optarg, errstr()); return EXIT_FAILURE; } break; case 't': if (xstrtod(optarg, NULL, &timeout) != 0) { fprintf(stderr, "%s: Bad timeout '%s': %s\n", cmd, optarg, errstr()); return EXIT_FAILURE; } break; case 'L': type = IOQ_NOP_LIGHT; break; case 'H': type = IOQ_NOP_HEAVY; break; case ':': fprintf(stderr, "%s: Missing argument to -%c\n", cmd, optopt); return EXIT_FAILURE; case '?': fprintf(stderr, "%s: Unrecognized option -%c\n", cmd, optopt); return EXIT_FAILURE; } } if (threads <= 0) { threads = xsysconf(_SC_NPROCESSORS_ONLN); if (threads > 8) { threads = 8; } } if (threads < 2) { threads = 2; } --threads; // Listen for ^C to print the summary struct sighook *hook = sighook(SIGINT, ctrlc, NULL, SH_CONTINUE | SH_ONESHOT); printf("I/O queue benchmark (%s)\n\n", bfs_version); printf("[-d] depth: %ld\n", depth); printf("[-j] threads: %ld (including main)\n", threads + 1); if (type == IOQ_NOP_HEAVY) { printf("[-H] type: heavy (with syscalls)\n"); } else { printf("[-L] type: light (no syscalls)\n"); } printf("\n"); printf(" Time (s) │ Throughput (IO/s) │ Latency (ns/IO)\n"); printf("══════════╪═══════════════════╪═════════════════\n"); fflush(stdout); struct ioq *ioq = ioq_create(depth, threads); bfs_everify(ioq, "ioq_create(%ld, %ld)", depth, threads); // Pre-allocate all the requests while (ioq_capacity(ioq) > 0) { int ret = ioq_nop(ioq, type, NULL); bfs_everify(ret == 0, "ioq_nop(%d)", (int)type); } while (true) { struct ioq_ent *ent = ioq_pop(ioq, true); if (!ent) { break; } ioq_free(ioq, ent); } struct times total, lap; times_init(&total); lap = total; long seconds = 0; while (!load(&quit, relaxed)) { bool was_timing = lap.timing; for (int i = 0; i < 16; ++i) { bool block = ioq_capacity(ioq) == 0; if (!pop(ioq, &lap, block)) { break; } } if (was_timing && !lap.timing) { struct timespec elapsed; gettime(&elapsed); timespec_sub(&elapsed, &total.start); if (elapsed.tv_sec > seconds) { seconds = elapsed.tv_sec; times_print(&lap, seconds); times_lap(&total, &lap); } double ns = timespec_ns(&elapsed); if (timeout > 0 && ns >= timeout * 1.0e9) { break; } } for (int i = 0; i < 8; ++i) { if (!push(ioq, type, &lap)) { break; } } ioq_submit(ioq); } while (pop(ioq, &lap, true)); times_lap(&total, &lap); if (load(&quit, relaxed)) { printf("\r────^C────┼───────────────────┼─────────────────\n"); } else { printf("──────────┼───────────────────┼─────────────────\n"); } times_print(&total, 0); ioq_destroy(ioq); sigunhook(hook); return 0; } bfs-4.0.6/build/000077500000000000000000000000001475764024500134105ustar00rootroot00000000000000bfs-4.0.6/build/cc.sh000077500000000000000000000013661475764024500143420ustar00rootroot00000000000000#!/bin/sh # Copyright © Tavian Barnes # SPDX-License-Identifier: 0BSD # Run the compiler and check if it succeeded. Usage: # # $ build/cc.sh [-q] path/to/file.c [-flags -Warnings ...] set -eu QUIET= if [ "$1" = "-q" ]; then QUIET=y shift fi # Source files can specify their own flags with lines like # # /// _CFLAGS += -Wmissing-variable-declarations # # which will be added to the makefile on success, or lines like # # /// -Werror # # which are just used for the current file. EXTRA_FLAGS=$(sed -n '\|^///|{s|^/// ||; s|[^=]*= ||; p;}' "$1") # Without -q, print the executed command for config.log if [ -z "$QUIET" ]; then set -x fi $XCC $XCPPFLAGS $XCFLAGS $XLDFLAGS "$@" $EXTRA_FLAGS $XLDLIBS bfs-4.0.6/build/config.mk000066400000000000000000000031651475764024500152130ustar00rootroot00000000000000# Copyright © Tavian Barnes # SPDX-License-Identifier: 0BSD # Makefile that implements `./configure` include build/prelude.mk include build/exports.mk # All configuration steps config: gen/config.mk .PHONY: config # Makefile fragments generated by `./configure` MKS := \ gen/vars.mk \ gen/flags.mk \ gen/pkgs.mk # The main configuration file, which includes the others gen/config.mk: ${MKS} gen/config.h ${MSG} "[ GEN] $@" @printf '# %s\n' "$@" >$@ @printf 'include %s\n' ${MKS} >>$@ ${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" >>$@ @test -z "$$VERSION" || printf 'export VERSION=%s\n' "$$VERSION" >>$@ ${VCAT} $@ # Sets the build flags. This depends on vars.mk and uses a recursive make so # 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 # 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/pkgs.mk @+XMAKEFLAGS="$$MAKEFLAGS" ${MAKE} -sf build/header.mk $@ .PHONY: gen/config.h bfs-4.0.6/build/define-if.sh000077500000000000000000000005441475764024500156000ustar00rootroot00000000000000#!/bin/sh # Copyright © Tavian Barnes # SPDX-License-Identifier: 0BSD # Output a C preprocessor definition based on whether a command succeeds set -eu MACRO=$(printf 'BFS_%s' "$1" | tr '/a-z-' '_A-Z_') shift if "$@"; then printf '#define %s true\n' "$MACRO" else printf '#define %s false\n' "$MACRO" exit 1 fi bfs-4.0.6/build/embed.sh000077500000000000000000000003441475764024500150240ustar00rootroot00000000000000#!/bin/sh # Copyright © Tavian Barnes # SPDX-License-Identifier: 0BSD # Convert data into a C array like #embed set -eu { cat; printf '\0'; } \ | od -An -tx1 \ | sed 's/[^ ][^ ]*/0x&,/g' bfs-4.0.6/build/empty.c000066400000000000000000000001721475764024500147120ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD int main(void) { return 0; } bfs-4.0.6/build/exports.mk000066400000000000000000000007251475764024500154510ustar00rootroot00000000000000# Copyright © Tavian Barnes # 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} bfs-4.0.6/build/flags-if.sh000077500000000000000000000011361475764024500154400ustar00rootroot00000000000000#!/bin/sh # Copyright © Tavian Barnes # SPDX-License-Identifier: 0BSD # Add flags to a makefile if a build succeeds set -eu build/cc.sh "$@" || exit 1 # If the build succeeded, print any lines like # # /// _CFLAGS += -foo # # (unless they're already set) OLD_FLAGS="$XCC $XCPPFLAGS $XCFLAGS $XLDFLAGS $XLDLIBS" while IFS="" read -r line; do case "$line" in ///*=*) flag="${line#*= }" if [ "${OLD_FLAGS#*"$flag"}" = "$OLD_FLAGS" ]; then printf '%s\n' "${line#/// }" fi ;; esac done <"$1" bfs-4.0.6/build/flags.mk000066400000000000000000000072171475764024500150440ustar00rootroot00000000000000# Copyright © Tavian Barnes # SPDX-License-Identifier: 0BSD # Makefile that generates gen/flags.mk include build/prelude.mk include gen/vars.mk # Internal flags _CPPFLAGS := -Isrc -Igen -include src/prelude.h _CFLAGS := -std=c17 _LDFLAGS := _LDLIBS := # Platform-specific system libraries LDLIBS,DragonFly := -lposix1e LDLIBS,Linux := -lrt LDLIBS,NetBSD := -lutil LDLIBS,QNX := -lregex -lsocket LDLIBS,SunOS := -lsec -lsocket -lnsl _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}} LTO ?= ${RELEASE} _LTO := ${TRUTHY,${LTO}} 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 _CFLAGS += ${ASAN_CFLAGS,${_ASAN}} _CFLAGS += ${LSAN_CFLAGS,${_LSAN}} _CFLAGS += ${MSAN_CFLAGS,${_MSAN}} _CFLAGS += ${TSAN_CFLAGS,${_TSAN}} _CFLAGS += ${UBSAN_CFLAGS,${_UBSAN}} SAN_CFLAGS,y := -fno-sanitize-recover=all INSANE := ${NOT,${_ASAN}${_LSAN}${_MSAN}${_TSAN}${_UBSAN}} SAN := ${NOT,${INSANE}} _CFLAGS += ${SAN_CFLAGS,${SAN}} # MSAN and TSAN both need all code to be instrumented YESLIBS := ${NOT,${_MSAN}${_TSAN}} NOLIBS ?= ${NOT,${YESLIBS}} # gcov only intercepts fork()/exec() with -std=gnu* GCOV_CFLAGS,y := -std=gnu17 --coverage _CFLAGS += ${GCOV_CFLAGS,${_GCOV}} LINT_CPPFLAGS,y := -D_FORTIFY_SOURCE=3 -DBFS_LINT LINT_CFLAGS,y := -Werror -O2 _CPPFLAGS += ${LINT_CPPFLAGS,${_LINT}} _CFLAGS += ${LINT_CFLAGS,${_LINT}} RELEASE_CPPFLAGS,y := -DNDEBUG RELEASE_CFLAGS,y := -O3 _CPPFLAGS += ${RELEASE_CPPFLAGS,${_RELEASE}} _CFLAGS += ${RELEASE_CFLAGS,${_RELEASE}} LTO_CFLAGS,y := -flto=auto _CFLAGS += ${LTO_CFLAGS,${_LTO}} # Configurable flags CFLAGS ?= -g -Wall # Add the configurable flags last so they can override ours _CPPFLAGS += ${CPPFLAGS} ${EXTRA_CPPFLAGS} _CFLAGS += ${CFLAGS} ${EXTRA_CFLAGS} _LDFLAGS += ${LDFLAGS} ${EXTRA_LDFLAGS} # (except LDLIBS, as earlier libs override later ones) _LDLIBS := ${LDLIBS} ${EXTRA_LDLIBS} ${_LDLIBS} include build/exports.mk # Conditionally-supported flags AUTO_FLAGS := \ gen/flags/Wformat.mk \ gen/flags/Wimplicit-fallthrough.mk \ gen/flags/Wimplicit.mk \ gen/flags/Wmissing-decls.mk \ gen/flags/Wmissing-var-decls.mk \ gen/flags/Wshadow.mk \ gen/flags/Wsign-compare.mk \ gen/flags/Wstrict-prototypes.mk \ gen/flags/Wundef-prefix.mk \ gen/flags/bind-now.mk \ gen/flags/deps.mk \ gen/flags/pthread.mk gen/flags.mk: ${AUTO_FLAGS} ${MSG} "[ GEN] $@" @printf '# %s\n' "$@" >$@ @printf '_CPPFLAGS := %s\n' "$$XCPPFLAGS" >>$@ @printf '_CFLAGS := %s\n' "$$XCFLAGS" >>$@ @printf '_LDFLAGS := %s\n' "$$XLDFLAGS" >>$@ @printf '_LDLIBS := %s\n' "$$XLDLIBS" >>$@ @printf 'NOLIBS := %s\n' "$$XNOLIBS" >>$@ @test "${OS}-${SAN}" != FreeBSD-y || printf 'POSTLINK = elfctl -e +noaslr $$@\n' >>$@ @cat ${.ALLSRC} >>$@ @cat ${.ALLSRC:%=%.log} >gen/flags.log ${VCAT} $@ .PHONY: gen/flags.mk # Check that the C compiler works at all cc:: @build/cc.sh -q build/empty.c -o gen/.cc.out; \ ret=$$?; \ build/msg-if.sh "[ CC ] build/empty.c" test $$ret -eq 0; \ exit $$ret # The short name of the config test SLUG = ${@:gen/%.mk=%} # The source file to build CSRC = build/${SLUG}.c # The hidden output file name OUT = ${SLUG:flags/%=gen/flags/.%.out} ${AUTO_FLAGS}: cc @${MKDIR} ${@D} @build/flags-if.sh ${CSRC} -o ${OUT} >$@ 2>$@.log; \ build/msg-if.sh "[ CC ] ${SLUG}.c" test $$? -eq 0 .PHONY: ${AUTO_FLAGS} bfs-4.0.6/build/flags/000077500000000000000000000000001475764024500145045ustar00rootroot00000000000000bfs-4.0.6/build/flags/Wformat.c000066400000000000000000000002411475764024500162640ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD /// _CFLAGS += -Wformat=2 /// -Werror int main(void) { return 0; } bfs-4.0.6/build/flags/Wimplicit-fallthrough.c000066400000000000000000000002551475764024500211300ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD /// _CFLAGS += -Wimplicit-fallthrough /// -Werror int main(void) { return 0; } bfs-4.0.6/build/flags/Wimplicit.c000066400000000000000000000002471475764024500166140ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD /// _CFLAGS += -Werror=implicit /// -Werror int main(void) { return 0; } bfs-4.0.6/build/flags/Wmissing-decls.c000066400000000000000000000002551475764024500175420ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD /// _CFLAGS += -Wmissing-declarations /// -Werror int main(void) { return 0; } bfs-4.0.6/build/flags/Wmissing-var-decls.c000066400000000000000000000002661475764024500203320ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD /// _CFLAGS += -Wmissing-variable-declarations /// -Werror int main(void) { return 0; } bfs-4.0.6/build/flags/Wshadow.c000066400000000000000000000002371475764024500162660ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD /// _CFLAGS += -Wshadow /// -Werror int main(void) { return 0; } bfs-4.0.6/build/flags/Wsign-compare.c000066400000000000000000000002451475764024500173640ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD /// _CFLAGS += -Wsign-compare /// -Werror int main(void) { return 0; } bfs-4.0.6/build/flags/Wstrict-prototypes.c000066400000000000000000000002521475764024500205340ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD /// _CFLAGS += -Wstrict-prototypes /// -Werror int main(void) { return 0; } bfs-4.0.6/build/flags/Wundef-prefix.c000066400000000000000000000002541475764024500173740ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD /// _CPPFLAGS += -Wundef-prefix=BFS_ /// -Werror int main(void) { return 0; } bfs-4.0.6/build/flags/bind-now.c000066400000000000000000000002261475764024500163650ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD /// _LDFLAGS += -Wl,-z,now int main(void) { return 0; } bfs-4.0.6/build/flags/deps.c000066400000000000000000000002241475764024500156010ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD /// _CPPFLAGS += -MD -MP int main(void) { return 0; } bfs-4.0.6/build/flags/pthread.c000066400000000000000000000002231475764024500162740ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD /// _CFLAGS += -pthread int main(void) { return 0; } bfs-4.0.6/build/has/000077500000000000000000000000001475764024500141635ustar00rootroot00000000000000bfs-4.0.6/build/has/--st-birthtim.c000066400000000000000000000002761475764024500167340ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD #include int main(void) { struct stat sb = {0}; return sb.__st_birthtim.tv_sec; } bfs-4.0.6/build/has/_Fork.c000066400000000000000000000002251475764024500153660ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD #include int main(void) { return _Fork(); } bfs-4.0.6/build/has/acl-get-entry.c000066400000000000000000000004271475764024500170050ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD #include #include 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); } bfs-4.0.6/build/has/acl-get-file.c000066400000000000000000000003761475764024500165660ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD #include #include #include int main(void) { acl_t acl = acl_get_file(".", ACL_TYPE_DEFAULT); return acl == (acl_t)NULL; } bfs-4.0.6/build/has/acl-get-tag-type.c000066400000000000000000000004361475764024500173760ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD #include #include #include int main(void) { acl_entry_t entry; memset(&entry, 0, sizeof(entry)); acl_tag_t tag; return acl_get_tag_type(entry, &tag); } bfs-4.0.6/build/has/acl-is-trivial-np.c000066400000000000000000000003641475764024500175650ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD #include #include int main(void) { acl_t acl = acl_get_fd(3); int trivial; acl_is_trivial_np(acl, &trivial); return 0; } bfs-4.0.6/build/has/acl-trivial.c000066400000000000000000000002371475764024500165400ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD #include int main(void) { return acl_trivial("."); } bfs-4.0.6/build/has/builtin-riscv-pause.c000066400000000000000000000002241475764024500202320ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD int main(void) { __builtin_riscv_pause(); return 0; } bfs-4.0.6/build/has/compound-literal-storage.c000066400000000000000000000002101475764024500212400ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD int main(void) { return (static int){0}; } bfs-4.0.6/build/has/confstr.c000066400000000000000000000002541475764024500160060ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD #include int main(void) { confstr(_CS_PATH, NULL, 0); return 0; } bfs-4.0.6/build/has/dprintf.c000066400000000000000000000002571475764024500160010ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD #include int main(void) { return dprintf(1, "%s\n", "Hello world!"); } bfs-4.0.6/build/has/extattr-get-file.c000066400000000000000000000004001475764024500175060ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD #include #include #include int main(void) { return extattr_get_file("file", EXTATTR_NAMESPACE_USER, "xattr", NULL, 0); } bfs-4.0.6/build/has/extattr-get-link.c000066400000000000000000000004001475764024500175240ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD #include #include #include int main(void) { return extattr_get_link("link", EXTATTR_NAMESPACE_USER, "xattr", NULL, 0); } bfs-4.0.6/build/has/extattr-list-file.c000066400000000000000000000003701475764024500177100ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD #include #include #include int main(void) { return extattr_list_file("file", EXTATTR_NAMESPACE_USER, NULL, 0); } bfs-4.0.6/build/has/extattr-list-link.c000066400000000000000000000003701475764024500177260ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD #include #include #include int main(void) { return extattr_list_link("link", EXTATTR_NAMESPACE_USER, NULL, 0); } bfs-4.0.6/build/has/fdclosedir.c000066400000000000000000000002461475764024500164470ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD #include int main(void) { return fdclosedir(opendir(".")); } bfs-4.0.6/build/has/getdents.c000066400000000000000000000003041475764024500161410ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD #include int main(void) { char buf[1024]; return getdents(3, (void *)buf, sizeof(buf)); } bfs-4.0.6/build/has/getdents64-syscall.c000066400000000000000000000004001475764024500177600ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD #include #include #include int main(void) { char buf[1024]; return syscall(SYS_getdents64, 3, (void *)buf, sizeof(buf)); } bfs-4.0.6/build/has/getdents64.c000066400000000000000000000003061475764024500163150ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD #include int main(void) { char buf[1024]; return getdents64(3, (void *)buf, sizeof(buf)); } bfs-4.0.6/build/has/getmntent-1.c000066400000000000000000000002621475764024500164720ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD #include #include int main(void) { return !getmntent(stdin); } bfs-4.0.6/build/has/getmntent-2.c000066400000000000000000000003171475764024500164740ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD #include #include int main(void) { struct mnttab mnt; return getmntent(stdin, &mnt); } bfs-4.0.6/build/has/getmntinfo.c000066400000000000000000000003261475764024500165020ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD #include #include #include int main(void) { return getmntinfo(NULL, MNT_WAIT); } bfs-4.0.6/build/has/getprogname-gnu.c000066400000000000000000000003051475764024500174240ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD #include int main(void) { const char *str = program_invocation_short_name; return str[0]; } bfs-4.0.6/build/has/getprogname.c000066400000000000000000000002661475764024500166430ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD #include int main(void) { const char *str = getprogname(); return str[0]; } bfs-4.0.6/build/has/io-uring-max-workers.c000066400000000000000000000004341475764024500203360ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD #include int main(void) { struct io_uring ring; io_uring_queue_init(1, &ring, 0); unsigned int values[] = {0, 0}; return io_uring_register_iowq_max_workers(&ring, values); } bfs-4.0.6/build/has/pipe2.c000066400000000000000000000003031475764024500153420ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD #include #include int main(void) { int fds[2]; return pipe2(fds, O_CLOEXEC); } bfs-4.0.6/build/has/posix-getdents.c000066400000000000000000000003151475764024500173030ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD #include int main(void) { char buf[1024]; return posix_getdents(3, (void *)buf, sizeof(buf), 0); } bfs-4.0.6/build/has/posix-spawn-addfchdir-np.c000066400000000000000000000004221475764024500211360ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD #include 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; } bfs-4.0.6/build/has/posix-spawn-addfchdir.c000066400000000000000000000004171475764024500205270ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD #include int main(void) { posix_spawn_file_actions_t actions; posix_spawn_file_actions_init(&actions); posix_spawn_file_actions_addfchdir(&actions, 3); return 0; } bfs-4.0.6/build/has/pragma-nounroll.c000066400000000000000000000002701475764024500174430ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD /// -Werror int main(void) { #pragma nounroll for (int i = 0; i < 100; ++i); return 0; } bfs-4.0.6/build/has/pthread-set-name-np.c000066400000000000000000000003261475764024500201010ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD #include #include int main(void) { pthread_set_name_np(pthread_self(), "name"); return 0; } bfs-4.0.6/build/has/pthread-setname-np.c000066400000000000000000000002711475764024500200230ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD #include int main(void) { return pthread_setname_np(pthread_self(), "name"); } bfs-4.0.6/build/has/st-acmtim.c000066400000000000000000000004371475764024500162310ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD #include 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; } bfs-4.0.6/build/has/st-acmtimespec.c000066400000000000000000000004561475764024500172520ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD #include 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; } bfs-4.0.6/build/has/st-birthtim.c000066400000000000000000000002741475764024500166000ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD #include int main(void) { struct stat sb = {0}; return sb.st_birthtim.tv_sec; } bfs-4.0.6/build/has/st-birthtimespec.c000066400000000000000000000003011475764024500176070ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD #include int main(void) { struct stat sb = {0}; return sb.st_birthtimespec.tv_sec; } bfs-4.0.6/build/has/st-flags.c000066400000000000000000000002621475764024500160470ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD #include int main(void) { struct stat sb = {0}; return sb.st_flags; } bfs-4.0.6/build/has/statx-syscall.c000066400000000000000000000004441475764024500171440ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD #include #include #include #include int main(void) { struct statx sb; syscall(SYS_statx, AT_FDCWD, ".", 0, STATX_BASIC_STATS, &sb); return 0; } bfs-4.0.6/build/has/statx.c000066400000000000000000000003501475764024500154700ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD #include #include int main(void) { struct statx sb; statx(AT_FDCWD, ".", 0, STATX_BASIC_STATS, &sb); return 0; } bfs-4.0.6/build/has/strerror-l.c000066400000000000000000000004001475764024500164340ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD #include #include #include int main(void) { locale_t locale = duplocale(LC_GLOBAL_LOCALE); return !strerror_l(ENOMEM, locale); } bfs-4.0.6/build/has/strerror-r-gnu.c000066400000000000000000000004041475764024500172350ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD #include #include int main(void) { char buf[256]; // Check that strerror_r() returns a pointer return *strerror_r(ENOMEM, buf, sizeof(buf)); } bfs-4.0.6/build/has/strerror-r-posix.c000066400000000000000000000004101475764024500176030ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD #include #include int main(void) { char buf[256]; // Check that strerror_r() returns an integer return 2 * strerror_r(ENOMEM, buf, sizeof(buf)); } bfs-4.0.6/build/has/string-to-flags.c000066400000000000000000000003011475764024500173410ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD #include #include int main(void) { return string_to_flags(NULL, NULL, NULL); } bfs-4.0.6/build/has/strtofflags.c000066400000000000000000000002771475764024500166730ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD #include #include int main(void) { return strtofflags(NULL, NULL, NULL); } bfs-4.0.6/build/has/tcgetwinsize.c000066400000000000000000000002671475764024500170530ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD #include int main(void) { struct winsize ws; return tcgetwinsize(0, &ws); } bfs-4.0.6/build/has/timegm.c000066400000000000000000000002611475764024500156100ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD #include int main(void) { struct tm tm = {0}; return (int)timegm(&tm); } bfs-4.0.6/build/has/timer-create.c000066400000000000000000000003061475764024500167070ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD #include int main(void) { timer_t timer; return timer_create(CLOCK_REALTIME, NULL, &timer); } bfs-4.0.6/build/has/tm-gmtoff.c000066400000000000000000000002551475764024500162310ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD #include int main(void) { struct tm tm = {0}; return tm.tm_gmtoff; } bfs-4.0.6/build/has/uselocale.c000066400000000000000000000003231475764024500163010ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD #include int main(void) { locale_t locale = uselocale((locale_t)0); return locale == LC_GLOBAL_LOCALE; } bfs-4.0.6/build/header.mk000066400000000000000000000053521475764024500151760ustar00rootroot00000000000000# Copyright © Tavian Barnes # SPDX-License-Identifier: 0BSD # Makefile that generates gen/config.h include build/prelude.mk include gen/vars.mk include gen/flags.mk include gen/pkgs.mk include build/exports.mk # All header fragments we generate HEADERS := \ gen/has/--st-birthtim.h \ gen/has/_Fork.h \ gen/has/acl-get-entry.h \ gen/has/acl-get-file.h \ gen/has/acl-get-tag-type.h \ gen/has/acl-is-trivial-np.h \ gen/has/acl-trivial.h \ gen/has/builtin-riscv-pause.h \ gen/has/compound-literal-storage.h \ gen/has/confstr.h \ gen/has/dprintf.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/io-uring-max-workers.h \ gen/has/pipe2.h \ gen/has/pragma-nounroll.h \ gen/has/posix-getdents.h \ gen/has/posix-spawn-addfchdir-np.h \ gen/has/posix-spawn-addfchdir.h \ gen/has/pthread-set-name-np.h \ gen/has/pthread-setname-np.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/tcgetwinsize.h \ gen/has/timegm.h \ gen/has/timer-create.h \ gen/has/tm-gmtoff.h \ gen/has/uselocale.h # Previously generated by pkgs.mk PKG_HEADERS := ${ALL_PKGS:%=gen/with/%.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 gen/flags.log ${.ALLSRC:%=%.log} >gen/config.log ${VCAT} $@ @printf '%s' "$$CONFFLAGS" | build/embed.sh >gen/confflags.i @printf '%s' "$$XCC" | build/embed.sh >gen/cc.i @printf '%s' "$$XCPPFLAGS" | build/embed.sh >gen/cppflags.i @printf '%s' "$$XCFLAGS" | build/embed.sh >gen/cflags.i @printf '%s' "$$XLDFLAGS" | build/embed.sh >gen/ldflags.i @printf '%s' "$$XLDLIBS" | build/embed.sh >gen/ldlibs.i .PHONY: gen/config.h # The short name of the config test SLUG = ${@:gen/%.h=%} # The hidden output file name OUT = ${SLUG:has/%=gen/has/.%.out} ${HEADERS}:: @${MKDIR} ${@D} @build/define-if.sh ${SLUG} build/cc.sh build/${SLUG}.c -o ${OUT} >$@ 2>$@.log; \ build/msg-if.sh "[ CC ] ${SLUG}.c" test $$? -eq 0 bfs-4.0.6/build/msg-if.sh000077500000000000000000000010171475764024500151300ustar00rootroot00000000000000#!/bin/sh # Copyright © Tavian Barnes # SPDX-License-Identifier: 0BSD # Print a success/failure indicator from a makefile: # # $ ./configure # [ CC ] with/liburing.c ✘ # [ CC ] with/oniguruma.c ✔ set -eu MSG="$1" shift if [ -z "${NO_COLOR:-}" ] && [ -t 1 ]; then Y='\033[1;32m✔\033[0m' N='\033[1;31m✘\033[0m' else Y='✔' N='✘' fi if "$@"; then YN="$Y" else YN="$N" fi build/msg.sh "$(printf "%-37s $YN" "$MSG")" bfs-4.0.6/build/msg.sh000077500000000000000000000021631475764024500145370ustar00rootroot00000000000000#!/bin/sh # Copyright © Tavian Barnes # 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 exec "$@" bfs-4.0.6/build/pkgconf.sh000077500000000000000000000035661475764024500154100ustar00rootroot00000000000000#!/bin/sh # Copyright © Tavian Barnes # 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 ${WITH_$LIB} WITH_LIB="WITH_$(printf '%s' "$LIB" | tr 'a-z-' 'A-Z_')" eval "WITH=\"\${$WITH_LIB:-}\"" case "$WITH" in y|1) continue ;; n|0) exit 1 ;; esac XCFLAGS="$XCFLAGS $("$0" --cflags "$LIB")" || exit 1 XLDFLAGS="$XLDFLAGS $("$0" --ldflags "$LIB")" || exit 1 XLDLIBS="$("$0" --ldlibs "$LIB") $XLDLIBS" || exit 1 build/cc.sh "build/with/$LIB.c" -o "gen/with/.$LIB.out" || exit 1 done fi # 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 bfs-4.0.6/build/pkgs.mk000066400000000000000000000016521475764024500147110ustar00rootroot00000000000000# Copyright © Tavian Barnes # 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/with/%.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/with/foo.h to foo PKG = ${@:gen/with/%.h=%} ${HEADERS}:: @${MKDIR} ${@D} @build/define-if.sh with/${PKG} build/pkgconf.sh ${PKG} >$@ 2>$@.log; \ build/msg-if.sh "[ CC ] with/${PKG}.c" test $$? -eq 0; bfs-4.0.6/build/prelude.mk000066400000000000000000000036061475764024500154060ustar00rootroot00000000000000# Copyright © Tavian Barnes # 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,} => [empty] # # 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 bfs-4.0.6/build/version.sh000077500000000000000000000005421475764024500154350ustar00rootroot00000000000000#!/bin/sh # Copyright © Tavian Barnes # SPDX-License-Identifier: 0BSD # Print the version number set -eu DIR="$(dirname -- "$0")/.." if [ "${VERSION-}" ]; then printf '%s' "$VERSION" elif [ -e "$DIR/.git" ] && command -v git >/dev/null 2>&1; then git -C "$DIR" describe --always --dirty else echo "4.0.6" fi bfs-4.0.6/build/with/000077500000000000000000000000001475764024500143635ustar00rootroot00000000000000bfs-4.0.6/build/with/libacl.c000066400000000000000000000002361475764024500157560ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD #include int main(void) { acl_free(0); return 0; } bfs-4.0.6/build/with/libcap.c000066400000000000000000000002451475764024500157620ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD #include int main(void) { cap_free(0); return 0; } bfs-4.0.6/build/with/libselinux.c000066400000000000000000000002451475764024500167060ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD #include int main(void) { freecon(0); return 0; } bfs-4.0.6/build/with/liburing.c000066400000000000000000000002521475764024500163410ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD #include int main(void) { io_uring_free_probe(0); return 0; } bfs-4.0.6/build/with/oniguruma.c000066400000000000000000000002411475764024500165320ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD #include int main(void) { onig_free(0); return 0; } bfs-4.0.6/completions/000077500000000000000000000000001475764024500146455ustar00rootroot00000000000000bfs-4.0.6/completions/bfs.bash000066400000000000000000000144431475764024500162640ustar00rootroot00000000000000# Copyright © Benjamin Mundt # Copyright © Tavian Barnes # SPDX-License-Identifier: 0BSD # bash completion script for bfs _bfs() { local cur prev words cword _init_completion || return # Options with a special completion procedure local special=( -D -S -exec -execdir -fprintf -fstype -gid -group -j -ok -okdir -regextype -type -uid -user -xtype ) # Options whose values should not be completed # (e.g. because they are numeric, glob, regexp, time, etc.) local nocomp=( -{a,B,c,m}{min,since,time} -context -ilname -iname -inum -ipath -iregex -iwholename -limit -links -lname -maxdepth -mindepth -name -newer{a,B,c,m}t -path -perm -printf -regex -since -size -used -wholename -xattrname ) # Options whose value is a filename local filecomp=( -{a,B,c,m}newer -f -fls -fprint -fprint0 -newer -newer{a,B,c,m}{a,B,c,m} -samefile ) local operators=( -a -and -exclude -not -o -or ) # Flags that take no arguments local nullary_flags=( -E -H -L -O{0,1,2,3,4,fast} -P -X -d -x ) # Options that take no arguments local nullary_options=( -color -daystart -depth -follow -ignore_readdir_race -mount -nocolor -noerror -noignore_readdir_race -noleaf -nowarn -status -unique -warn -xdev ) # Tests that take no arguments local nullary_tests=( -capable -empty -executable -false -hidden -nogroup -nohidden -nouser -readable -sparse -true -writable -xattr -xattrname ) # Actions that take no arguments local nullary_actions=( --help --version -delete -exit -help -ls -print -print0 -printx -prune -quit -rm -version ) local everything=( "${special[@]}" "${nocomp[@]}" "${filecomp[@]}" "${operators[@]}" "${nullary_flags[@]}" "${nullary_options[@]}" "${nullary_tests[@]}" "${nullary_actions[@]}" ) # Completing -exec requires matching the whole command line local i offset for i in "${!words[@]}"; do if ((i >= cword)); then break fi case "${words[i]}" in -exec|-execdir|-ok|-okdir) offset=$((i + 1)) ;; \\\;|+) offset= ;; esac done if [[ -n "$offset" ]]; then _command_offset "$offset" COMPREPLY+=($(compgen -W "{} + '\\;'" -- "$cur")) return fi # Completions with 2-word lookbehind if ((cword > 1)); then case "${words[cword-2]}" in -fprintf) # -fprintf FORMAT FILE # Like -ls/-print/-print0/-printf, but write to FILE instead of standard # output # when -fprintf is prev2, current word is FILE; perform file completion _filedir return ;; esac fi # No completion for numbers, globs, regexes, times, etc. if [[ " ${nocomp[@]} " =~ " $prev " ]]; then COMPREPLY=() return fi # Complete filenames if [[ " ${filecomp[@]} " =~ " $prev " ]]; then _filedir return fi # Special completions with 1-word lookbehind case "$prev" in -D) # -D FLAG # Turn on a debugging flag (see -D help) COMPREPLY=($(compgen -W 'help cost exec opt rates search stat tree all' -- "$cur")) return ;; -S) # -S bfs|dfs|ids|eds # Use breadth-first/depth-first/iterative/exponential deepening search # (default: -S bfs) COMPREPLY=($(compgen -W 'bfs dfs ids eds' -- "$cur")) return ;; -fstype) # -fstype TYPE # Find files on file systems with the given TYPE _fstypes return ;; -gid) # -gid [-+]N # Find files owned by group ID N _gids return ;; -group) # -group NAME # Find files owned by the group NAME COMPREPLY=($(compgen -g -- "$cur")) return ;; -uid) # -uid [-+]N # Find files owned by auser ID N _uids return ;; -user) # -user NAME # Find files owned by the user NAME COMPREPLY=($(compgen -u -- "$cur")) return ;; -regextype) # -regextype TYPE # Use TYPE-flavored regexes (default: posix-basic; see -regextype help) COMPREPLY=($(compgen -W 'help posix-basic posix-extended' -- "$cur")) return ;; -type|-xtype) # -type [bcdlpfswD] # Find files of the given type # -xtype [bcdlpfswD] # Find files of the given type, following links when -type would not, and # vice versa COMPREPLY=() if [[ -n $cur ]] && ! [[ $cur =~ ,$ ]]; then COMPREPLY+=("$cur") cur+=, fi COMPREPLY+=("$cur"{b,c,d,l,p,f,s,w,D}) return ;; esac # Completions with no lookbehind if [[ "$cur" == -* ]]; then # complete all options COMPREPLY=($(compgen -W "${everything[*]}" -- "$cur")) return fi # default completion _filedir COMPREPLY+=($(compgen -W "- ! , '\\(' '\\)'" -- "$cur")) } && complete -F _bfs bfs bfs-4.0.6/completions/bfs.fish000066400000000000000000000236051475764024500163000ustar00rootroot00000000000000# Completions for the 'bfs' command set -l debug_flag_comp 'help\t"Print help message" cost\t"Show cost estimates" exec\t"Print executed command details" opt\t"Print optimization details" rates\t"Print predicate success rates" search\t"Trace the filesystem traversal" stat\t"Trace all stat() calls" tree\t"Print the parse tree" all\t"All debug flags at once"' set -l optimization_comp '0\t"Disable all optimizations" 1\t"Basic logical simplifications" 2\t"-O1, plus dead code elimination and data flow analysis" 3\t"-02, plus re-order expressions to reduce expected cost" 4\t"All optimizations, including aggressive optimizations" fast\t"Same as -O4"' set -l strategy_comp 'bfs\t"Breadth-first search" dfs\t"Depth-first search" ids\t"Iterative deepening search" eds\t"Exponential deepening search"' set -l regex_type_comp 'help\t"Print help message" posix-basic\t"POSIX basic regular expressions" posix-extended\t"POSIX extended regular expressions" ed\t"Like ed" emacs\t"Like emacs" grep\t"Like grep" sed\t"Like sed"' set -l type_comp 'b\t"Block device" c\t"Character device" d\t"Directory" l\t"Symbolic link" p\t"Pipe" f\t"Regular file" s\t"Socket" w\t"Whiteout" D\t"Door"' # Flags complete -c bfs -o H -d "Follow symbolic links only on the command line" complete -c bfs -o L -o follow -d "Follow all symbolic links" complete -c bfs -o P -d "Never follow symbolic links" complete -c bfs -o E -d "Use extended regular expressions" complete -c bfs -o X -d "Filter out files with non-xargs(1)-safe names" complete -c bfs -o d -o depth -d "Search in post-order" complete -c bfs -o s -d "Visit directory entries in sorted order" complete -c bfs -o x -o xdev -d "Don't descend into other mount points" complete -c bfs -o f -d "Treat specified path as a path to search" -a "(__fish_complete_directories)" -x complete -c bfs -o D -d "Turn on a debugging flag" -a $debug_flag_comp -x complete -c bfs -s O -d "Enable specified optimization level" -a $optimization_comp -x complete -c bfs -o S -d "Choose the search strategy" -a $strategy_comp -x complete -c bfs -s j -d "Use this many threads" -x # Operators complete -c bfs -o not -d "Negate result of expression" complete -c bfs -o a -o and -d "Result is only true if both previous and next expression are true" complete -c bfs -o o -o or -d "Result is true if either previous or next expression are true" # Special forms complete -c bfs -o exclude -d "Exclude all paths matching the expression from the search" -x # Options complete -c bfs -o color -d "Turn colors on" complete -c bfs -o nocolor -d "Turn colors off" complete -c bfs -o daystart -d "Measure time relative to the start of today" complete -c bfs -o files0-from -d "Treat the NUL-separated paths in specified file as starting points for the search" -F complete -c bfs -o ignore_readdir_race -d "Don't report an error if the file tree is modified during the search" complete -c bfs -o noignore_readdir_race -d "Report an error if the file tree is modified during the search" complete -c bfs -o maxdepth -d "Ignore files deeper than specified number" -x complete -c bfs -o mindepth -d "Ignore files shallower than specified number" -x complete -c bfs -o mount -d "Exclude mount points" complete -c bfs -o noerror -d "Ignore any errors that occur during traversal" complete -c bfs -o nohidden -d "Exclude hidden files and directories" complete -c bfs -o noleaf -d "Ignored; for compatibility with GNU find" complete -c bfs -o regextype -d "Use specified flavored regex" -a $regex_type_comp -x complete -c bfs -o status -d "Display a status bar while searching" complete -c bfs -o unique -d "Skip any files that have already been seen" complete -c bfs -o warn -d "Turn on warnings about the command line" complete -c bfs -o nowarn -d "Turn off warnings about the command line" # Tests complete -c bfs -o acl -d "Find files with a non-trivial Access Control List" complete -c bfs -o amin -d "Find files accessed specified number of minutes ago" -x complete -c bfs -o Bmin -d "Find files birthed specified number of minutes ago" -x complete -c bfs -o cmin -d "Find files changed specified number of minutes ago" -x complete -c bfs -o mmin -d "Find files modified specified number of minutes ago" -x complete -c bfs -o anewer -d "Find files accessed more recently than specified file was modified" -F complete -c bfs -o Bnewer -d "Find files birthed more recently than specified file was modified" -F complete -c bfs -o cnewer -d "Find files changed more recently than specified file was modified" -F complete -c bfs -o mnewer -d "Find files modified more recently than specified file was modified" -F complete -c bfs -o asince -d "Find files accessed more recently than specified time" -x complete -c bfs -o Bsince -d "Find files birthed more recently than specified time" -x complete -c bfs -o csince -d "Find files changed more recently than specified time" -x complete -c bfs -o msince -d "Find files modified more recently than specified time" -x complete -c bfs -o atime -d "Find files accessed specified number of days ago" -x complete -c bfs -o Btime -d "Find files birthed specified number of days ago" -x complete -c bfs -o ctime -d "Find files changed specified number of days ago" -x complete -c bfs -o mtime -d "Find files modified specified number of days ago" -x complete -c bfs -o capable -d "Find files with capabilities set" complete -c bfs -o context -d "Find files by SELinux context" -x complete -c bfs -o depth -d "Find files with specified number of depth" -x complete -c bfs -o empty -d "Find empty files/directories" complete -c bfs -o executable -d "Find files the current user can execute" complete -c bfs -o readable -d "Find files the current user can read" complete -c bfs -o writable -d "Find files the current user can write" complete -c bfs -o false -d "Always false" complete -c bfs -o true -d "Always true" complete -c bfs -o fstype -d "Find files on file systems with the given type" -a "(__fish_print_filesystems)" -x complete -c bfs -o gid -d "Find files owned by group ID" -a "(__fish_complete_group_ids)" -x complete -c bfs -o uid -d "Find files owned by user ID" -a "(__fish_complete_user_ids)" -x complete -c bfs -o group -d "Find files owned by the group" -a "(__fish_complete_groups)" -x complete -c bfs -o user -d "Find files owned by the user" -a "(__fish_complete_users)" -x complete -c bfs -o hidden -d "Find hidden files" complete -c bfs -o ilname -d "Case-insensitive versions of -lname" -x complete -c bfs -o iname -d "Case-insensitive versions of -name" -x complete -c bfs -o ipath -d "Case-insensitive versions of -path" -x complete -c bfs -o iregex -d "Case-insensitive versions of -regex" -x complete -c bfs -o iwholename -d "Case-insensitive versions of -wholename" -x complete -c bfs -o inum -d "Find files with specified inode number" -x complete -c bfs -o links -d "Find files with specified number of hard links" -x complete -c bfs -o lname -d "Find symbolic links whose target matches specified glob" -x complete -c bfs -o name -d "Find files whose name matches specified glob" -x complete -c bfs -o newer -d "Find files newer than specified file" -F # handle -newer{a,B,c,m}{a,B,c,m} FILE for x in {a,B,c,m} for y in {a,B,c,m} complete -c bfs -o newer$x$y -d "Find files whose $x""time is newer than the $y""time of specified file" -F end end # handle -newer{a,B,c,m}t TIMESTAMP for x in {a,B,c,m} complete -c bfs -o newer$x"t" -d "Find files whose $x""time is newer than specified timestamp" -x end complete -c bfs -o nogroup -d "Find files owned by nonexistent groups" complete -c bfs -o nouser -d "Find files owned by nonexistent users" complete -c bfs -o path -o wholename -d "Find files whose entire path matches specified glob" -x complete -c bfs -o perm -d "Find files with a matching mode" -x complete -c bfs -o regex -d "Find files whose entire path matches the regular expression" -x complete -c bfs -o samefile -d "Find hard links to specified file" -F complete -c bfs -o since -d "Find files modified since specified time" -x complete -c bfs -o size -d "Find files with the given size" -x complete -c bfs -o sparse -d "Find files that occupy fewer disk blocks than expected" complete -c bfs -o type -d "Find files of the given type" -a $type_comp -x complete -c bfs -o used -d "Find files last accessed specified number of days after they were changed" -x complete -c bfs -o xattr -d "Find files with extended attributes" complete -c bfs -o xattrname -d "Find files with the specified extended attribute name" -x complete -c bfs -o xtype -d "Find files of the given type, following links when -type would not, and vice versa" -a $type_comp -x # Actions complete -c bfs -o rm -o delete -d "Delete any found files" complete -c bfs -o exec -d "Execute a command" -r complete -c bfs -o ok -d "Prompt the user whether to execute a command" -r complete -c bfs -o execdir -d "Like -exec, but run the command in the same directory as the found file(s)" -r complete -c bfs -o okdir -d "Like -ok, but run the command in the same directory as the found file(s)" -r complete -c bfs -o exit -d "Exit immediately with the given status" -x complete -c bfs -o fls -d "Like -ls, but write to specified file" -F complete -c bfs -o fprint -d "Like -print, but write to specified file" -F complete -c bfs -o fprint0 -d "Like -print0, but write to specified file" -F complete -c bfs -o fprintf -d "Like -printf, but write to specified file" -F complete -c bfs -o limit -d "Limit the number of results" -x complete -c bfs -o ls -d "List files like ls -dils" complete -c bfs -o print -d "Print the path to the found file" complete -c bfs -o print0 -d "Like -print, but use the null character as a separator rather than newlines" complete -c bfs -o printf -d "Print according to a format string" -x complete -c bfs -o printx -d "Like -print, but escape whitespace and quotation characters" complete -c bfs -o prune -d "Don't descend into this directory" complete -c bfs -o quit -d "Quit immediately" complete -c bfs -o version -l version -d "Print version information" complete -c bfs -o help -l help -d "Print usage information" bfs-4.0.6/completions/bfs.zsh000066400000000000000000000231341475764024500161500ustar00rootroot00000000000000#compdef bfs # Based on standard zsh find completion and bfs bash completion. local curcontext="$curcontext" state_descr variant default ret=1 local -a state line args alts disp smatch args=( # Flags '(-depth)-d[search in post-order (descendents first)]' '-D[print diagnostics]:debug option:(cost exec opt rates search stat time tree all help)' '-E[use extended regular expressions with -regex/-iregex]' '-f[specify file hierarchy to traverse]:path:_directories' '-O+[enable query optimisation]:level:(0 1 2 3 4 fast)' '-s[traverse directories in sorted order]' '-X[warn if filename contains characters special to xargs]' "-x[don't span filesystems]" '(-H -L)-P[never follow symlinks]' '(-H -P)-L[follow symlinks]' '(-L -P)-H[only follow symlinks when resolving command-line arguments]' "-S[select search method]:value:(bfs dfs ids eds)" '-f[treat path as path to search]:path:_files -/' '-j+[use this many threads]:threads:' # Operators '*-and' '*-not' '*-or' '*-a' '*-o' # Special forms '*-exclude[exclude paths matching EXPRESSION from search]' # Options '(-nocolor)-color[turn on colors]' '(-color)-nocolor[turn off colors]' '*-daystart[measure times relative to start of today]' '(-d)*-depth[search in post-order (descendents first)]' '-files0-from[search NUL separated paths from FILE]:file:_files' '*-follow[follow all symbolic links (same as -L)]' '*-ignore_readdir_race[report an error if bfs detects file tree is modified during search]' '*-noignore_readdir_race[do not report an error if bfs detects file tree is modified during search]' '*-maxdepth[ignore files deeper than N]:maximum search depth' '*-mindepth[ignore files shallower than N]:minimum search depth' "*-mount[exclude mount points]" '*-noerror[ignore any errors that occur during traversal]' '*-nohidden[exclude hidden files]' '*-noleaf[ignored, for compatibility with GNU find]' '-regextype[type of regex to use, default posix-basic]:regexp syntax:(help posix-basic posix-extended ed emacs grep sed)' '*-status[display a status bar while searching]' '-unique[skip any files that have already been seen]' '*-warn[turn on warnings about the command line]' '*-nowarn[turn off warnings about the command line]' "*-xdev[don't descend into other mount points]" # Tests '*-acl[find files with a non-trivial Access Control List]' '*-amin[find files accessed N minutes ago]:access time (minutes):' '*-anewer[find files accessed more recently than FILE was modified]:file to compare (access time):_files' '*-asince[find files accessed more recently than TIME]:time:' '*-atime[find files accessed N days ago]:access time (days):->times' '*-Bmin[find files birthed N minutes ago]:birth time (minutes):' '*-Bnewer[find files birthed more recently than FILE was modified]:file to compare (birth time):_files' '*-Bsince[find files birthed more recently than TIME]:time:' '*-Btime[find files birthed N days ago]:birth time (days):->times' '*-cmin[find files changed N minutes ago]:inode change time (minutes):' '*-cnewer[find files changed more recently than FILE was modified]:file to compare (inode change time):_files' '*-csince[find files changed more recently than TIME]:time:' '*-ctime[find files changed N days ago]:inode change time (days):->times' '*-mmin[find files modified N minutes ago]:modification time (minutes):' '*-mnewer[find files modified more recently than FILE was modified]:file to compare (modification time):_files' '*-msince[find files modified more recently than TIME]:time:' '*-mtime[find files modified N days ago]:modification time (days):->times' '*-capable[find files with POSIX.1e capabilities set]' '*-context[find files by SELinux context]:pattern' # -depth without parameters exist above. I don't know how to handle this gracefully '*-empty[find empty files/directories]' '*-executable[find files the current user can execute]' '*-readable[find files the current user can read]' '*-writable[find files the current user can write]' '*-false[always false]' '*-true[always true]' '*-fstype[find files on file systems with the given type]:file system type:_file_systems' '*-gid[find files owned by group ID N]:numeric group ID:' '*-group[find files owned by group NAME]:group:_groups' '*-uid[find files owned by user ID N]:numeric user ID' '*-user[find files owned by user NAME]:user:_users' '*-hidden[find hidden files (those beginning with .)]' '*-ilname[find symbolic links whose target matches GLOB (case insensitive)]:link pattern to search (case insensitive):' '*-iname[find files whose name matches GLOB (case insensitive)]:name pattern to match (case insensitive):' '*-inum[find files with inode number N]:inode number:' '*-ipath[find files whose entire path matches GLOB (case insensitive)]:path pattern to search (case insensitive):' '*-iregex[find files whose entire path matches REGEX (case insensitive)]:regular expression to search (case insensitive):' '*-iwholename[find files whose entire path matches GLOB (case insensitive)]:full path pattern to search (case insensitive):' '*-links[find files with N hard links]:number of links:' '*-lname[find symbolic links whose target matches GLOB]:link pattern to search' '*-name[find files whose name matches GLOB]:name pattern' '*-newer[find files newer than FILE]:file to compare (modification time):_files' '*-newer'{a,B,c,m}{a,B,c,m}'[find files where timestamp 1 is newer than timestamp 2 of reference FILE]:reference file:_files' '*-newer'{a,B,c,m}t'[find files where timestamp is newer than timestamp given as parameter]:timestamp:' '*-nogroup[find files with nonexistent owning group]' '*-nouser[find files with nonexistent owning user]' '*-path[find files whose entire path matches GLOB]:path pattern to search:' '*-wholename[find files whose entire path matches GLOB]:full path pattern to search:' '*-perm[find files with a matching mode]: :_file_modes' '*-regex[find files whose entire path matches REGEX]:regular expression to search:' '*-samefile[find hard links to FILE]:file to compare inode:_files' '*-since[files modified since TIME]:time:' '*-size[find files with the given size]:file size (blocks):' '*-sparse[find files that occupy fewer disk blocks than expected]' '*-type[find files of the given type]:file type:((b\:block\ device c\:character\ device d\:directory p\:named\ pipe f\:normal\ file l\:symbolic\ link s\:socket w\:whiteout D\:Door))' '*-used[find files last accessed N days after they were changed]:access after inode change (days)' '*-xattr[find files with extended attributes]' '*-xattrname[find files with extended attribute NAME]:name:' '*-xtype[find files of the given type following links when -type would not, and vice versa]:file type:((b\:block\ device c\:character\ device d\:directory p\:named\ pipe f\:normal\ file l\:symbolic\ link s\:socket w\:whiteout D\:Door))' # Actions '*-delete[delete any found files (-implies -depth)]' '*-rm[delete any found files (-implies -depth)]' '*-exec[execute a command]:program: _command_names -e:*(\;|+)::program arguments: _normal' '*-execdir[execute a command in the same directory as the found files]:program: _command_names -e:*(\;|+)::program arguments: _normal' '*-ok[prompt the user whether to execute a command]:program: _command_names -e:*(\;|+)::program arguments: _normal' '*-okdir[prompt the user whether to execute a command in the same directory as the found files]:program: _command_names -e:*(\;|+)::program arguments: _normal' '-exit[exit with status if found, default 0]' '*-fls[list files like ls -dils, but write to FILE instead of standard output]:output file:_files' '*-fprint[print the path to the found file, but write to FILE instead of standard output]:output file:_files' '*-fprint0[print the path to the found file using null character as separator, but write to FILE instead of standard output]:output file:_files' '*-fprintf[print according to format string, but write to FILE instead of standard output]:output file:_files:output format' '*-limit[quit after N results]:maximum result count' '*-ls[list files like ls -dils]' '*-print[print the path to the found file]' '*-print0[print the path to the found file using null character as separator]' '*-printf[print according to format string]:output format' '*-printx[like -print but escapes whitespace and quotation marks]' "*-prune[don't descend into this directory]" '*-quit[quit immediately]' '(- *)-help[print usage information]' '(-)--help[print usage information]' '(- *)-version[print version information]' '(-)--version[print version information]' '(--help --version)*:other:{_alternative "directories:directory:_files -/" "logic:logic:(, ! \( \) )"}' ) _arguments -C $args && ret=0 if [[ $state = times ]]; then if ! compset -P '[+-]' || [[ -prefix '[0-9]' ]]; then compstate[list]+=' packed' if zstyle -t ":completion:${curcontext}:senses" verbose; then zstyle -s ":completion:${curcontext}:senses" list-separator sep || sep=-- default=" [default exactly]" disp=( "- $sep before" "+ $sep since" ) smatch=( - + ) else disp=( before exactly since ) smatch=( - '' + ) fi alts=( "senses:sense${default}:compadd -V times -S '' -d disp -a smatch" ) fi alts+=( "times:${state_descr}:_dates -f d" ) _alternative $alts && ret=0 fi return ret bfs-4.0.6/configure000077500000000000000000000137121475764024500142240ustar00rootroot00000000000000#!/bin/sh # Copyright © Tavian Barnes # SPDX-License-Identifier: 0BSD # bfs build configuration script set -eu # Get the relative path to the source tree based on how the script was run DIR=$(dirname -- "$0") # Print the help message help() { cat <&2 } # Report an argument parsing error invalid() { printf '%s: error: Unrecognized option "%s"\n\n' "$0" "$1" >&2 printf 'Run %s --help for more information.\n' "$0" >&2 exit 1 } # Get the number of cores to use nproc() { { command nproc \ || sysctl -n hw.ncpu \ || getconf _NPROCESSORS_ONLN \ || echo 1 } 2>/dev/null } # Save the ./configure command line for bfs --version export CONFFLAGS="" # Default to `make` MAKE="${MAKE-make}" # Parse the command-line arguments for arg; do shift # Only add --options to CONFFLAGS, so we don't print FLAG=values twice in bfs --version case "$arg" in -*) CONFFLAGS="${CONFFLAGS}${CONFFLAGS:+ }${arg}" ;; esac # --[(enable|disable|with|without)-]$name[=$value] value="${arg#*=}" name="${arg%%=*}" name="${name#--}" case "$arg" in --enable-*|--disable-*|--with-*|--without-*) name="${name#*-}" ;; esac NAME=$(printf '%s' "$name" | tr 'a-z-' 'A-Z_') # y/n modality case "$arg" in --enable-*|--with-*) case "$arg" in *=y|*=yes) yn=y ;; *=n|*=no) yn=n ;; *=*) invalid "$arg" ;; *) yn=y ;; esac ;; --disable-*|--without-*) case "$arg" in *=*) invalid "arg" ;; *) yn=n ;; esac ;; esac # Fix up --enable-lib* to --with-lib* case "$arg" in --enable-*|--disable-*) case "$name" in libacl|libcap|libselinux|liburing|oniguruma) old="$arg" case "$arg" in --enable-*) arg="--with-${arg#--*-}" ;; --disable-*) arg="--without-${arg#--*-}" ;; esac warn 'Treating "%s" like "%s"' "$old" "$arg" ;; esac ;; esac case "$arg" in -h|--help) help exit 0 ;; --enable-*|--disable-*) case "$name" in release|lto|asan|lsan|msan|tsan|ubsan|lint|gcov) set -- "$@" "$NAME=$yn" ;; *) invalid "$arg" ;; esac ;; --with-*|--without-*) case "$name" in libacl|libcap|libselinux|liburing|oniguruma) set -- "$@" "WITH_$NAME=$yn" ;; *) invalid "$arg" ;; esac ;; --prefix=*|--mandir=*|--version=*) set -- "$@" "$NAME=$value" ;; --infodir=*|--build=*|--host=*|--target=*) warn 'Ignoring option "%s"' "$arg" ;; MAKE=*) MAKE="$value" ;; # Warn about MAKE variables that have documented configure flags RELEASE=*|LTO=*|ASAN=*|LSAN=*|MSAN=*|TSAN=*|UBSAN=*|LINT=*|GCOV=*) name=$(printf '%s' "$NAME" | tr 'A-Z_' 'a-z-') warn '"%s" is deprecated; use --enable-%s' "$arg" "$name" set -- "$@" "$arg" ;; PREFIX=*|MANDIR=*|VERSION=*) name=$(printf '%s' "$NAME" | tr 'A-Z_' 'a-z-') warn '"%s" is deprecated; use --%s=%s' "$arg" "$name" "$value" set -- "$@" "$arg" ;; WITH_*=*) name=$(printf '%s' "$NAME" | tr 'A-Z_' 'a-z-') warn '"%s" is deprecated; use --%s' "$arg" "$name" set -- "$@" "$arg" ;; # make flag (-j2) or variable (CC=clang) -*|*=*) set -- "$@" "$arg" ;; *) invalid "$arg" ;; esac done # 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$(nproc)}" $MAKE -rf build/config.mk "$@" bfs-4.0.6/docs/000077500000000000000000000000001475764024500132415ustar00rootroot00000000000000bfs-4.0.6/docs/BUILDING.md000066400000000000000000000135001475764024500147570ustar00rootroot00000000000000Building `bfs` ============== A simple invocation of $ ./configure $ make should build `bfs` successfully. Configuration ------------- ```console $ ./configure --help Usage: $ ./configure [--enable-*|--disable-*] [--with-*|--without-*] [CC=...] [...] $ 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:
MAKE=make
    make implementation
CC=cc
    C compiler
INSTALL=install
    Copy files during make install
MKDIR="mkdir -p"
    Create directories
PKG_CONFIG=pkg-config
    Detect external libraries and required build flags
RM="rm -f"
    Delete files
These flags will be used by the build process:
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
### Build profiles The default flags result in a plain debug build. Other build profiles can be enabled:
--enable-release
    Enable optimizations, disable assertions

--enable-asan
--enable-lsan
--enable-msan
--enable-tsan
--enable-ubsan
    Enable sanitizers

--enable-gcov
    Enable code coverage instrumentation
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. External dependencies are auto-detected by default, but you can build `--with` or `--without` them explicitly:
--with-libacl      --without-libacl
--with-libcap      --without-libcap
--with-libselinux  --without-libselinux
--with-liburing    --without-liburing
--with-oniguruma   --without-oniguruma
[`pkg-config`] is used, if available, to detect these libraries and any additional build flags they may require. If this is undesirable, disable it by setting `PKG_CONFIG` to the empty string (`./configure PKG_CONFIG=""`). [`pkg-config`]: https://www.freedesktop.org/wiki/Software/pkg-config/ ### Out-of-tree builds You can set up an out-of-tree build by running the `configure` script from another directory, for example: $ mkdir out $ cd out $ ../configure $ make Building -------- ### Targets The [`Makefile`](/Makefile) supports several different build targets:
make
    The default target; builds just the bfs binary
make all
    Builds everything, including the tests (but doesn't run them)

make check
    Builds everything, and runs all tests
make unit-tests
    Builds and runs the unit tests
make integration-tests
    Builds and runs the integration tests
make distcheck
    Builds and runs the tests in multiple different configurations

make install
    Installs bfs globally
make uninstall
    Uninstalls bfs

make clean
    Deletes all built files
make distclean
    Also deletes files generated by ./configure
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 ------- `bfs` comes with an extensive test suite which can be run with $ make check The test harness is implemented in the file [`tests/tests.sh`](/tests/tests.sh). Individual test cases are found in `tests/*/*.sh`. Most of them are *snapshot tests* which compare `bfs`'s output to a known-good copy saved under the matching `tests/*/*.out`. You can pass the name of a particular test case (or a few) to run just those tests. For example: $ ./tests/tests.sh posix/basic If you need to update the reference snapshot, pass `--update`. It can be handy to generate the snapshot with a different `find` implementation to ensure the output is correct, for example: $ ./tests/tests.sh posix/basic --bfs=find --update But keep in mind, other `find` implementations may not be correct. To my knowledge, no other implementation passes even the POSIX-compatible subset of the tests: $ ./tests/tests.sh --bfs=find --sudo --posix ... [PASS] 104 / 119 [SKIP] 1 / 119 [FAIL] 14 / 119 Run $ ./tests/tests.sh --help for more details. ### Validation A more thorough testsuite is run by the [CI](https://github.com/tavianator/bfs/actions) and to validate releases. It builds `bfs` in multiple configurations to test for latent bugs, memory leaks, 32-bit compatibility, etc. You can run it yourself with $ make distcheck Some of these tests require `sudo`, and will prompt for your password if necessary. bfs-4.0.6/docs/CHANGELOG.md000066400000000000000000001073001475764024500150530ustar00rootroot000000000000004.* === 4.0.6 ----- **February 26, 2025** ### Bug fixes - Fixed `-fstype` with btrfs subvolumes (requires Linux 5.8+) ([`0dccdae`](https://github.com/tavianator/bfs/commit/0dccdae4510ff5603247be871e64a6119647ea2a)) - Fixed `-ls` with timestamps very far in the future ([`dd5df1f`](https://github.com/tavianator/bfs/commit/dd5df1f8997550c5bf49205578027715b957bd01)) - Fixed the `posix/exec_sigmask` test on mips64el Linux ([`532dec0`](https://github.com/tavianator/bfs/commit/532dec0849dcdc3e15e530ac40a8168f146a41cd)) - Fixed time-related tests with `mawk 1.3.4 20250131` ([#152](https://github.com/tavianator/bfs/issues/152)) 4.0.5 ----- **January 18, 2025** ### Bug fixes - Fixed a bug that could cause child processes (e.g. from `-exec`) to run with all signals blocked. The bug was introduced in version 3.3. ([`af207e7`](https://github.com/tavianator/bfs/commit/af207e702148e5c9ae08047d7a2dce6394653b62)) ### Changes - Fixed the build against old liburing versions ([#147](https://github.com/tavianator/bfs/issues/147)) - Async I/O performance optimizations 4.0.4 ----- **October 31, 2024** ## Bug fixes - Fixed a man page typo ([#144](https://github.com/tavianator/bfs/pull/144)) - Fixed the build on PowerPC macOS ([#145](https://github.com/tavianator/bfs/issues/145)) - Fixed a bug introduced in bfs 4.0.3 that colorized every file as if it had capabilities on non-Linux systems ([#146](https://github.com/tavianator/bfs/pull/146)) 4.0.3 ----- **October 22, 2024** ### Bug fixes - Fixed an assertion failure when `$LS_COLORS` contained escaped NUL bytes like `*\0.gz=` ([`f5eaadb9`](https://github.com/tavianator/bfs/commit/f5eaadb96fb94b2d3666e53a99495840a3099aec)) - Fixed a use-after-free bug introduced in bfs 4.0 when unregistering and re-registering signal hooks. This could be reproduced with `bfs -nocolor` by repeatedly sending `SIGINFO`/`SIGUSR1` to toggle the status bar. ([`39ff273`](https://github.com/tavianator/bfs/commit/39ff273df97e51b1285358b9e6808b117ea8adb1)) - Fixed a hang present since bfs 3.0 colorizing paths like `notdir/file`, where `notdir` is a symlink pointing to a non-directory file. ([`b89f22cb`](https://github.com/tavianator/bfs/commit/b89f22cbf250958a802915eb7b6bf0e5f38376ca)) 4.0.2 ----- **September 17, 2024** ### New features - Implemented `./configure --version=X.Y.Z`, mainly for packagers to override the version number ([`4a278d3`](https://github.com/tavianator/bfs/commit/4a278d3e39a685379711727eac7bfaa83679e0e4)) ### Changes - Minor refactoring of the build system ### Bug fixes - Fixed `./configure --help`, which was broken since `bfs` 4.0 ([`07ae989`](https://github.com/tavianator/bfs/commit/07ae98906dbb0caaac2f758d72e88dd0975b2a81)) - Fixed compiler flag auto-detection on systems with non-GNU `sed`. This fixes a potential race condition on FreeBSD since `bfs` 4.0 due to the [switch to `_Fork()`](https://github.com/tavianator/bfs/commit/085bb402c7b2c2f96624fb0523ff3f9686fe26d9) without passing `-z now` to the linker. ([`34e6081`](https://github.com/tavianator/bfs/commit/34e60816adb0ea8ddb155a454676a99ab225dc8a)) - Fixed `$MAKE distcheck` when `$MAKE` is not `make`, e.g. `gmake distcheck` on BSD ([`2135b00`](https://github.com/tavianator/bfs/commit/2135b00d215efc5c2c38e1abd3254baf31229ad4)) - Fixed some roff syntax issues in the `bfs` manpage ([`812ecd1`](https://github.com/tavianator/bfs/commit/812ecd1feeb002252dd4d732b395d31c4179afaf)) - Fixed an assertion failure optimizing expressions like `bfs -not \( -prune , -type f \)` since `bfs` 3.1. Release builds were not affected, since their assertions are disabled and the behaviour was otherwise correct. ([`b1a9998`](https://github.com/tavianator/bfs/commit/b1a999892b9e13181ddd9a7d895f3d1c65fbb449)) 4.0.1 ----- **August 19, 2024** ### Bug fixes - `bfs` no longer prints a "suppressed errors" warning unless `-noerror` is actually suppressing errors ([`5d03c9d`](https://github.com/tavianator/bfs/commit/5d03c9d460d1c1afcdf062d494537986ce96a690)) 4.0 --- **August 16, 2024** ### New features - To match BSD `find` (and the POSIX Utility Syntax Guidelines), multiple flags can now be given in a single argument like `-LEXO2`. Previously, you would have had to write `-L -E -X -O2`. ([`c0fd33a`](https://github.com/tavianator/bfs/commit/c0fd33aaef5f345566a41c7c2558f27adf05558b)) - Explicit timestamps can now be written as `@SECONDS_SINCE_EPOCH`. For example, `bfs -newermt @946684800` will print files modified since January 1, 2000 (UTC). ([`c6bb003`](https://github.com/tavianator/bfs/commit/c6bb003b8882e9a16941f5803d072ec1cb728318)) - The new `-noerror` option suppresses all error messages during traversal. ([#142](https://github.com/tavianator/bfs/issues/142)) ### Changes - `-mount` now excludes mount points entirely, to comply with the recently published POSIX 2024 standard. Use `-xdev` to include the mount point itself, but not its contents. `bfs` has been warning about this change since version 1.5.1 (September 2019). ([`33b85e1`](https://github.com/tavianator/bfs/commit/33b85e1f8769e7f75721887638ae454d109a034f)) - `-perm` now takes the current file creation mask into account when parsing a symbolic mode like `+rw`, as clarified by [POSIX defect 1392](https://www.austingroupbugs.net/view.php?id=1392). This matches the behaviour of BSD `find`, contrary to the behaviour of GNU `find`. ([`6290ce4`](https://github.com/tavianator/bfs/commit/6290ce41f3ec1f889abb881cf90ca91da869b5b2)) ### Bug fixes - Fixed commands like `./configure CC=clang --enable-release` that set variables before other options ([`49a5d48`](https://github.com/tavianator/bfs/commit/49a5d48d0a43bac313c8b8d1b167e60da9eaadf6)) - Fixed the build on RISC-V with GCC versions older than 14 ([`e93a1dc`](https://github.com/tavianator/bfs/commit/e93a1dccd82f831a2f0d2cc382d8af5e1fda55ed)) - Fixed running `bfs` under Valgrind ([`a01cfac`](https://github.com/tavianator/bfs/commit/a01cfacd423af28af6b7c13ba51e2395f3a52ee7)) - Fixed the exit code when failing to execute a non-existent command with `-exec`/`-ok` on some platforms including OpenBSD and HPPA ([`8c130ca`](https://github.com/tavianator/bfs/commit/8c130ca0117fd225c24569be2ec16c7dc2150a13)) - Fixed `$LS_COLORS` case-sensitivity to match GNU ls more closely when the same extension is specified multiple times ([`08030ae`](https://github.com/tavianator/bfs/commit/08030aea919039165c02805e8c637a9ec1ad0d70)) - Fixed the `-status` bar on Solaris/Illumos 3.* === 3.3.1 ----- **June 3, 2024** ### Bug fixes - Reduced the scope of the symbolic link loop change in version 3.3. `-xtype l` remains true for symbolic link loops, matching a change in GNU findutils 4.10.0. However, `-L` will report an error, just like `bfs` prior to 3.3 and other `find` implementations, as required by POSIX. 3.3 --- **May 28, 2024** ### New features - The `-status` bar can now be toggled by `SIGINFO` (Ctrl+T) on systems that support it, and `SIGUSR1` on other systems - `-regextype` now supports all regex types from GNU find ([#21](https://github.com/tavianator/bfs/issues/21)) - File birth times are now supported on OpenBSD ### Changes - Symbolic link loops are now treated like other broken links, rather than an error - `./configure` now expects `--with-libacl`, `--without-libcap`, etc. rather than `--enable-`/`--disable-` - The ` ` (space) flag is now restricted to numeric `-printf` specifiers ### Bug fixes - `-regextype emacs` now supports [shy](https://www.gnu.org/software/emacs/manual/html_node/elisp/Regexp-Backslash.html#index-shy-groups) (non-capturing) groups - Fixed `-status` bar visual corruption when the terminal is resized - `bfs` now prints a reset escape sequence when terminated by a signal in the middle of colored output ([#138](https://github.com/tavianator/bfs/issues/138)) - `./configure CFLAGS=...` no longer overrides flags from `pkg-config` during configuration 3.2 --- **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 ----- **March 6, 2024** ### Bug fixes - On Linux, the `io_uring` feature probing introduced in `bfs` 3.1.2 only applied to one thread, causing all other threads to avoid using io_uring entirely. The probe results are now copied to all threads correctly. ([`f64f76b`](https://github.com/tavianator/bfs/commit/f64f76b55400b71e8576ed7e4a377eb5ef9576aa)) 3.1.2 ----- **February 29, 2024** ### Bug fixes - On Linux, we now check for supported `io_uring` operations before using them, which should fix `bfs` on 5.X series kernels that support `io_uring` but not all of `openat()`/`close()`/`statx()` ([`8bc72d6`](https://github.com/tavianator/bfs/commit/8bc72d6c20c5e38783c4956c4d9fde9b3ee9140c)) - Fixed a test failure triggered by certain filesystem types for `/tmp` ([#131](https://github.com/tavianator/bfs/issues/131)) - Fixed parsing and interpretation of timezone offsets for explicit reference times used in `-*since` and `-newerXt` ([`a9f3cde`](https://github.com/tavianator/bfs/commit/a9f3cde30426b546ba6e3172e1a7951213a72049)) - Fixed the build on m68k ([`c749c11`](https://github.com/tavianator/bfs/commit/c749c11b04444ca40941dd2ddc5802faed148f6a)) 3.1.1 ----- **February 16, 2024** ### Changes - Performance and scalability improvements - The file count in `bfs -status` now has a thousands separator 3.1 --- **February 6, 2024** ### New features - On Linux, `bfs` now uses [io_uring](https://en.wikipedia.org/wiki/Io_uring) for async I/O - On all platforms, `bfs` can now perform `stat()` calls in parallel, accelerating queries like `-links`, `-newer`, and `-size`, as well as colorized output - On FreeBSD, `-type w` now works to find whiteouts like the system `find` ### Changes - Improved `bfs -j2` performance ([`b2ab7a1`](https://github.com/tavianator/bfs/commit/b2ab7a151fca517f4879e76e626ec85ad3de97c7)) - Optimized `-exec` by using `posix_spawn()` when possible, which can avoid the overhead of `fork()` ([`95fbde1`](https://github.com/tavianator/bfs/commit/95fbde17a66377b6fbe7ff1f014301dbbf09270d)) - `-execdir` and `-okdir` are now rejected if `$PATH` contains a relative path, matching the behaviour of GNU find ([`163baf1`](https://github.com/tavianator/bfs/commit/163baf1c9af13be0ce705b133e41e0c3d6427398)) - Leading whitespace is no longer accepted in integer command line arguments like `-links ' 1'` ([`e0d7dc5`](https://github.com/tavianator/bfs/commit/e0d7dc5dfd7bdaa62b6bc18e9c1cce00bbe08577)) ### Bug fixes - `-quit` and `-exit` could be ignored in the iterative deepening modes (`-S {ids,eds}`). This is now fixed ([`670ebd9`](https://github.com/tavianator/bfs/commit/670ebd97fb431e830b1500b2e7e8013b121fb2c5)). The bug was introduced in version 3.0.3 (commit [`5f16169`]). - Fixed two possible errors in sort mode (`-s`): - Too many open files ([`710c083`](https://github.com/tavianator/bfs/commit/710c083ff02eb1cc5b8daa6778784f3d1cd3c08d)) - Out of memory ([`76ffc8d`](https://github.com/tavianator/bfs/commit/76ffc8d30cb1160d55d855d8ac630a2b9075fbcf)) - Fixed handling of FreeBSD union mounts ([`3ac3bee`](https://github.com/tavianator/bfs/commit/3ac3bee7b0d9c9be693415206efa664bf4a7d4a7)) - Fixed `NO_COLOR` handling when it's set to the empty string ([`79aee58`](https://github.com/tavianator/bfs/commit/79aee58a4621d01c4b1e98c332775f3b87213ddb)) - Fixed some portability issues: - [OpenBSD](https://github.com/tavianator/bfs/compare/ee200c07643801c8b53e5b80df704ecbf77a884e...79f1521b0e628be72bed3a648f0ae90b62fc69b8) - [NetBSD](https://github.com/tavianator/bfs/compare/683f2c41c72efcb82ce866e3dcc311ac9bd8b66d...6435684a7d515e18247ae1b3dd9ec8681fee22d0) - [DragonFly BSD](https://github.com/tavianator/bfs/compare/08867473e75e8e20ca76c7fb181204839e28b271...45fb1d952c3b262278a3b22e9c7d60cca19a5407) - [Illumos](https://github.com/tavianator/bfs/compare/4010140cb748cc4f7f57b0a3d514485796c665ce...ae94cdc00136685abe61d55e1e357caaa636d785) 3.0.4 ----- **October 12, 2023** ### Bug fixes - Fixed a segfault when reporting errors under musl ([`d40eb87`]) [`d40eb87`]: https://github.com/tavianator/bfs/commit/d40eb87cc00f50a5debb8899eacb7fcf1065badf 3.0.3 ----- **October 12, 2023** ### Changes - Iterative deepening modes (`-S {ids,eds}`) were optimized by delaying teardown until the very end ([`5f16169`]) - Parallel depth-first search (`-S dfs`) was optimized to avoid enqueueing every file separately ([`2572273`]) ### Bug fixes - Iterative deepening modes (`-S {ids,eds}`) were performing iterative *breadth*-first searches since `bfs` 3.0, negating any advantages they may have had over normal breadth-first search. They now do iterative *depth*-first searches as expected. ([`a029d95`]) - Fixed a linked-list corruption that could lead to an infinite loop on macOS and other non-Linux, non-FreeBSD platforms ([`773f4a4`]) [`5f16169`]: https://github.com/tavianator/bfs/commit/5f1616912ba3a7a23ce6bce02df3791b73da38ab [`2572273`]: https://github.com/tavianator/bfs/commit/257227326fe60fe70e80433fd34d1ebcb2f9f623 [`a029d95`]: https://github.com/tavianator/bfs/commit/a029d95b5736a74879f32089514a5a6b63d6efbc [`773f4a4`]: https://github.com/tavianator/bfs/commit/773f4a446f03da62d88e6d17be49fdc0a3e38465 3.0.2 ----- **September 6, 2023** ### Changes - `-files0-from` now allows an empty set of paths to be given, matching GNU findutils 4.9.0 - Reduced memory consumption in multi-threaded searches - Many man page updates ### Bug fixes - Fixed an out-of-bounds memory read that could occur when escaping a string containing an incomplete multi-byte character 3.0.1 ----- **July 18, 2023** ### Bug fixes - Traversal fixes that mostly affect large directory trees ([#107]) - `bfs` could encounter `EMFILE`, close a file, and retry many times, particularly with `-j1` - Breadth-first search could become highly unbalanced, negating many of the benefits of `bfs` - On non-{Linux,FreeBSD} platforms, directories could stay open longer than necessary, consuming extra memory [#107]: https://github.com/tavianator/bfs/pull/107 3.0 --- **July 13, 2023** ### New features - `bfs` now reads directories asynchronously and in parallel ([#101]). Performance is significantly improved as a result. Parallelism is controlled by the new `-j` flag, e.g. `-j1`, `-j2`, etc. [#101]: https://github.com/tavianator/bfs/issues/101 ### Changes - `bfs` now uses the [C17] standard version, up from C11 - Due to [#101], `bfs` now requires some additional C and POSIX features: - [Standard C atomics] (``) - [POSIX threads] (``) - `$LS_COLORS` extensions written in different cases (e.g. `*.jpg=35:*.JPG=01;35`) are now matched case-sensitively, to match the new behaviour of GNU ls since coreutils version 9.2 - Added a warning/error if `$LS_COLORS` can't be parsed, depending on whether `-color` is requested explicitly - Filenames with control characters are now escaped when printing with `-color` - Build flags like `WITH_ONIGURUMA` have been renamed to `USE_ONIGURUMA` [C17]: https://en.cppreference.com/w/c/17 [Standard C atomics]: https://en.cppreference.com/w/c/atomic [POSIX threads]: https://pubs.opengroup.org/onlinepubs/9699919799/idx/threads.html ### Bug fixes - Fixed handling of the "normal text" color (`no` in `$LS_COLORS`) to match GNU ls 2.* === 2.6.3 ----- **January 31, 2023** - Fixed running the tests as root on Linux [`8b24de3`] - Fixed some tests on Android [`2724dfb`] [`0a5a80c`] - Stopped relying on non-POSIX touch(1) features in the tests. This should fix the tests on at least OpenBSD. [`2d5edb3`] - User/group caches are now filled lazily instead of eagerly [`b41dca5`] - More caches and I/O streams are flushed before -exec/-ok [`f98a1c4`] - Fixed various memory safety issues found by fuzzing \ [`712b137`] [`5ce883d`] [`da02def`] [`c55e855`] - Fixed a test failure on certain macOS versions [`8b24de3`] - Mitigated a race condition when determining filesystem types ([#97]) - Lots of refactoring and optimization [`8b24de3`]: https://github.com/tavianator/bfs/commit/8b24de3882ff5a3e33b82ab20bb4eadf134cf559 [`2724dfb`]: https://github.com/tavianator/bfs/commit/2724dfbd17552f892a0d8b39b96cbe9e49d66fdb [`0a5a80c`]: https://github.com/tavianator/bfs/commit/0a5a80c98cc7e5d8735b615fa197a6cff2bb08cc [`2d5edb3`]: https://github.com/tavianator/bfs/commit/2d5edb37b924715b4fbee4d917ac334c773fca61 [`b41dca5`]: https://github.com/tavianator/bfs/commit/b41dca52762c5188638236ae81b9f4597bb29ac9 [`f98a1c4`]: https://github.com/tavianator/bfs/commit/f98a1c4a1cf61ff7d6483388ca1fac365fb0b31b [`712b137`]: https://github.com/tavianator/bfs/commit/712b13756a09014ef730c8f9b96da4dc2f09b762 [`5ce883d`]: https://github.com/tavianator/bfs/commit/5ce883daaafc69f83b01dac5db0647e9662a6e87 [`da02def`]: https://github.com/tavianator/bfs/commit/da02defb91c3a1bda0ea7e653d81f997f1c8884a [`c55e855`]: https://github.com/tavianator/bfs/commit/c55e85580df10c5afdc6fc0710e756a456aa8e93 [`8b24de3`]: https://github.com/tavianator/bfs/commit/8b24de3882ff5a3e33b82ab20bb4eadf134cf559 [#97]: https://github.com/tavianator/bfs/issues/97 2.6.2 ----- **October 21, 2022** - Fixed use of uninitialized memory on parsing errors involving `-fprintf` - Fixed Android build issues ([#96]) - Refactored the test suite [#96]: https://github.com/tavianator/bfs/issues/96 2.6.1 ----- **July 7, 2022** - Fix `stat()` errors on GNU Hurd systems with glibc older than 2.35 - Added fish shell tab completion ([#94]). Thanks @xfgusta! [#94]: https://github.com/tavianator/bfs/pull/94 2.6 --- **May 21, 2022** - Fixed deleting large NFS directories on FreeBSD ([#67]). - Added support for a `bfs`-specific `BFS_COLORS` environment variable. - Refactored the build system, directory structure, and documentation ([#88], [#89], [#91]). Thanks @ElectronicsArchiver! - Added `zsh` completion ([#86]). Thanks @VorpalBlade! - Updated the default color scheme to match GNU coreutils 9.1. Files with capabilities set are no longer colored differently by default, resulting in a significant performance improvement. - Became less aggressive at triggering automounts - Added support for out-of-tree builds with `BUILDDIR` [#67]: https://github.com/tavianator/bfs/issues/67 [#86]: https://github.com/tavianator/bfs/issues/86 [#88]: https://github.com/tavianator/bfs/issues/88 [#89]: https://github.com/tavianator/bfs/issues/89 [#91]: https://github.com/tavianator/bfs/issues/91 2.5 --- **March 27, 2022** - Added compiler-style context for errors and warnings. Errors look like this: $ bfs -nam needle bfs: error: bfs -nam needle bfs: error: ~~~~ bfs: error: Unknown argument; did you mean -name? and warnings look like this: $ bfs -print -name 'needle' bfs: warning: bfs -print -name needle bfs: warning: ~~~~~~~~~~~~ bfs: warning: The result of this expression is ignored. - Updated from C99 to C11 - Fixed the tests when built against musl - Fixed a build error reported on Manjaro 2.4.1 ----- **February 24, 2022** - Fixed the build when Oniguruma is not installed in the default search paths ([#82]) - Fixed string encoding bugs with Oniguruma enabled - Fixed regex error reporting bugs [#82]: https://github.com/tavianator/bfs/issues/82 2.4 --- **February 22, 2022** - Added the Oniguruma regular expression library as an (optional, but enabled by default) dependency ([#81]). Oniguruma supports more regular expression syntax types than the POSIX regex API, and often performs better. To build `bfs` without this new dependency, do `make WITH_ONIGURUMA=` to disable it. Thanks @data-man! - Added support for the `ed`, `emacs`, `grep`, and `sed` regular expression types ([#21]) - Before executing a process with `-exec[dir]`/`-ok[dir]`, `bfs` now ensures all output streams are flushed. Previously, I/O from subprocesses could be interleaved unpredictably with buffered I/O from `bfs` itself. [#81]: https://github.com/tavianator/bfs/pull/81 [#21]: https://github.com/tavianator/bfs/issues/21 2.3.1 ----- **January 21, 2022** - Fixed the build on Debian kFreeBSD - Fixed a crash on GNU Hurd when piping bfs's output - Fixed a double-`close()` on non-Linux platforms if `fdopendir()` fails - Reduced memory allocations on startup 2.3 --- **November 25, 2021** - More tweaks to `PAGER` and `LESS` handling for `bfs -help` ([#76]) - Use 512-byte blocks for `-ls` when `POSIXLY_CORRECT` is set ([#77]) - Implemented `-files0-from FILE` to take a list of `'\0'`-separated starting paths. GNU find will implement the same feature in an upcoming release. - Added colors to `-printf` output ([#62]) - Faster recovery from `E2BIG` during `-exec` [#76]: https://github.com/tavianator/bfs/issues/76 [#77]: https://github.com/tavianator/bfs/issues/77 [#62]: https://github.com/tavianator/bfs/issues/62 2.2.1 ----- **June 2, 2021** - Fixed some incorrect coloring of broken links when links are being followed (`-L`) - Made the tests work when run as root by dropping privileges. This may be helpful for certain packaging or CI environments, but is not recommended. - Treat empty `PAGER` and `LESS` environment variables like they're unset, for `bfs -help` ([#71]). Thanks @markus-oberhumer! - The soft `RLIMIT_NOFILE` is now raised automatically to a fairly large value when possible. This provides a minor performance benefit for large directory trees. - Implemented time units for `-mtime` as found in FreeBSD find ([#75]) [#71]: https://github.com/tavianator/bfs/issues/71 [#75]: https://github.com/tavianator/bfs/issues/75 2.2 --- **March 6, 2021** - Fixed `-hidden` on hidden start paths - Added a Bash completion script. Thanks @bmundt6! - Fixed rounding in `-used`. Corresponding fixes were made to GNU find in version 4.8.0. - Optimized the open directory representation. On Linux, much libc overhead is bypassed by issuing syscalls directly. On all platforms, a few fewer syscalls and open file descriptors will be used. - Implemented `-flags` from BSD find 2.1 --- **November 11, 2020** - Added a new `-status` option that displays the search progress in a bar at the bottom of the terminal - Fixed an optimizer bug introduced in version 2.0 that affected some combinations of `-user`/`-group` and `-nouser`/`-nogroup` 2.0 --- **October 14, 2020** - [#8]: New `-exclude ` syntax to more easily and reliably filter out paths. For example: bfs -name config -exclude -name .git will find all files named `config`, without searching any directories (or files) named `.git`. In this case, the same effect could have been achieved (more awkwardly) with `-prune`: bfs ! \( -name .git -prune \) -name config But `-exclude` will work in more cases: # -exclude works with -depth, while -prune doesn't: bfs -depth -name config -exclude -name .git # -exclude applies even to paths below the minimum depth: bfs -mindepth 3 -name config -exclude -name .git - [#30]: `-nohidden` is now equivalent to `-exclude -hidden`. This changes the behavior of command lines like bfs -type f -nohidden to do what was intended. - Optimized the iterative deepening (`-S ids`) implementation - Added a new search strategy: exponential deepening search (`-S eds`). This strategy provides many of the benefits of iterative deepening, but much faster due to fewer re-traversals. - Fixed an optimizer bug that could skip `-empty`/`-xtype` if they didn't always lead to an action - Implemented `-xattrname` to find files with a particular extended attribute (from macOS find) - Made `-printf %l` still respect the width specifier (e.g. `%10l`) for non-links, to match GNU find - Made `bfs` fail if `-color` is given explicitly and `LS_COLORS` can't be parsed, rather than falling back to non-colored output [#8]: https://github.com/tavianator/bfs/issues/8 [#30]: https://github.com/tavianator/bfs/issues/30 1.* === 1.7 --- **April 22, 2020** - Fixed `-ls` printing numeric IDs instead of user/group names in large directory trees - Cached the user and group tables for a performance boost - Fixed interpretation of "default" ACLs - Implemented `-s` flag to sort results 1.6 --- **February 25, 2020** - Implemented `-newerXt` (explicit reference times), `-since`, `-asince`, etc. - Fixed `-empty` to skip special files (pipes, devices, sockets, etc.) 1.5.2 ----- **January 9, 2020** - Fixed the build on NetBSD - Added support for NFSv4 ACLs on FreeBSD - Added a `+` after the file mode for files with ACLs in `-ls` - Supported more file types (whiteouts, doors) in symbolic modes for `-ls`/`-printf %M` - Implemented `-xattr` on FreeBSD 1.5.1 ----- **September 14, 2019** - Added a warning to `-mount`, since it will change behaviour in the next POSIX revision - Added a workaround for environments that block `statx()` with `seccomp()`, like older Docker - Fixed coloring of nonexistent leading directories - Avoided calling `stat()` on all mount points at startup 1.5 --- **June 27, 2019** - New `-xattr` predicate to find files with extended attributes - Fixed the `-acl` implementation on macOS - Implemented depth-first (`-S dfs`) and iterative deepening search (`-S ids`) - Piped `-help` output into `$PAGER` by default - Fixed crashes on some invalid `LS_COLORS` values 1.4.1 ----- **April 5, 2019** - Added a nicer error message when the tests are run as root - Fixed detection of comparison expressions with signs, to match GNU find for things like `-uid ++10` - Added support for https://no-color.org/ - Decreased the number of `stat()` calls necessary in some cases 1.4 --- **April 15, 2019** - New `-unique` option that filters out duplicate files ([#48]) - Optimized the file coloring implementation - Fixed the coloring implementation to match GNU ls more closely in many corner cases - Implemented escape sequence parsing for `LS_COLORS` - Implemented `ln=target` for coloring links like their targets - Fixed the order of fallbacks used when some color keys are unset - Add a workaround for incorrect file types for bind-mounted files on Linux ([#37]) [#48]: https://github.com/tavianator/bfs/issues/48 [#37]: https://github.com/tavianator/bfs/issues/37 1.3.3 ----- **February 10, 2019** - Fixed unpredictable behaviour for empty responses to `-ok`/`-okdir` caused by an uninitialized string - Writing to standard output now causes `bfs` to fail if the descriptor was closed - Fixed incomplete file coloring in error messages - Added some data flow optimizations - Fixed `-nogroup`/`-nouser` in big directory trees - Added `-type w` for whiteouts, as supported by FreeBSD `find` - Re-wrote the `-help` message and manual page 1.3.2 ----- **January 11, 2019** - Fixed an out-of-bounds read if LS_COLORS doesn't end with a `:` - Allowed multiple debug flags to be specified like `-D opt,tree` 1.3.1 ----- **January 3, 2019** - Fixed some portability problems affecting FreeBSD 1.3 --- **January 2, 2019** New features: - `-acl` finds files with non-trivial Access Control Lists (from FreeBSD) - `-capable` finds files with capabilities set - `-D all` turns on all debugging flags at once Fixes: - `LS_COLORS` handling has been improved: - Extension colors are now case-insensitive like GNU `ls` - `or` (orphan) and `mi` (missing) files are now treated differently - Default colors can be unset with `di=00` or similar - Specific colors fall back to more general colors when unspecified in more places - `LS_COLORS` no longer needs a trailing colon - `-ls`/`-fls` now prints the major/minor numbers for device nodes - `-exec ;` is rejected rather than segfaulting - `bfs` now builds on old Linux versions that require `-lrt` for POSIX timers - For files whose access/change/modification times can't be read, `bfs` no longer fails unless those times are needed for tests - The testsuite is now more correct and portable 1.2.4 ----- **September 24, 2018** - GNU find compatibility fixes for `-printf`: - `%Y` now prints `?` if an error occurs resolving the link - `%B` is now supported for birth/creation time (as well as `%W`/`%w`) - All standard `strftime()` formats are supported, not just the ones from the GNU find manual - Optimizations are now re-run if any expressions are reordered - `-exec` and friends no longer leave zombie processes around when `exec()` fails 1.2.3 ----- **July 15, 2018** - Fixed `test_depth_error` on filesystems that don't fill in `d_type` - Fixed the build on Linux architectures that don't have the `statx()` syscall (ia64, sh4) - Fixed use of AT_EMPTY_PATH for fstatat on systems that don't support it (Hurd) - Fixed `ARG_MAX` accounting on architectures with large pages (ppc64le) - Fixed the build against the upcoming glibc 2.28 release that includes its own `statx()` wrapper 1.2.2 ----- **June 23, 2018** - Minor bug fixes: - Fixed `-exec ... '{}' +` argument size tracking after recovering from `E2BIG` - Fixed `-fstype` if `/proc` is available but `/etc/mtab` is not - Fixed an uninitialized variable when given `-perm +rw...` - Fixed some potential "error: 'path': Success" messages - Reduced reliance on GNU coreutils in the testsuite - Refactored and simplified the internals of `bftw()` 1.2.1 ----- **February 8, 2018** - Performance optimizations 1.2 --- **January 20, 2018** - Added support for the `-perm +7777` syntax deprecated by GNU find (equivalent to `-perm /7777`), for compatibility with BSD finds - Added support for file birth/creation times on platforms that report it - `-Bmin`/`-Btime`/`-Bnewer` - `B` flag for `-newerXY` - `%w` and `%Wk` directives for `-printf` - Uses the `statx(2)` system call on new enough Linux kernels - More robustness to `E2BIG` added to the `-exec` implementation 1.1.4 ----- **October 27, 2017** - Added a man page - Fixed cases where multiple actions write to the same file - Report errors that occur when closing files/flushing streams - Fixed "argument list too long" errors with `-exec ... '{}' +` 1.1.3 ----- **October 4, 2017** - Refactored the optimizer - Implemented data flow optimizations 1.1.2 ----- **September 10, 2017** - Fixed `-samefile` and similar predicates when passed broken symbolic links - Implemented `-fstype` on Solaris - Fixed `-fstype` under musl - Implemented `-D search` - Implemented a cost-based optimizer 1.1.1 ----- **August 10, 2017** - Re-licensed under the BSD Zero Clause License - Fixed some corner cases with `-exec` and `-ok` parsing 1.1 --- **July 22, 2017** - Implemented some primaries from NetBSD `find`: - `-exit [STATUS]` (like `-quit`, but with an optional explicit exit status) - `-printx` (escape special characters for `xargs`) - `-rm` (alias for `-delete`) - Warn if `-prune` will have no effect due to `-depth` - Handle y/n prompts according to the user's locale - Prompt the user to correct typos without having to re-run `bfs` - Fixed handling of paths longer than `PATH_MAX` - Fixed spurious "Inappropriate ioctl for device" errors when redirecting `-exec ... +` output - Fixed the handling of paths that treat a file as a directory (e.g. `a/b/c` where `a/b` is a regular file) - Fixed an expression optimizer bug that broke command lines like `bfs -name '*' -o -print` 1.0.2 ----- **June 15, 2017** Bugfix release. - Fixed handling of \0 inside -printf format strings - Fixed `-perm` interpretation of permcopy actions (e.g. `u=rw,g=r`) 1.0.1 ----- **May 17, 2017** Bugfix release. - Portability fixes that mostly affect GNU Hurd - Implemented `-D exec` - Made `-quit` not disable the implicit `-print` 1.0 --- **April 24, 2017** This is the first release of bfs with support for all of GNU find's primitives. Changes since 0.96: - Implemented `-fstype` - Implemented `-exec/-execdir ... +` - Implemented BSD's `-X` - Fixed the tests under Bash 3 (mostly for macOS) - Some minor optimizations and fixes 0.* === 0.96 ---- **March 11, 2017** 73/76 GNU find features supported. - Implemented -nouser and -nogroup - Implemented -printf and -fprintf - Implemented -ls and -fls - Implemented -type with multiple types at once (e.g. -type f,d,l) - Fixed 32-bit builds - Fixed -lname on "symlinks" in Linux /proc - Fixed -quit to take effect as soon as it's reached - Stopped redirecting standard input from /dev/null for -ok and -okdir, as that violates POSIX - Many test suite improvements 0.88 ---- **December 20, 2016** 67/76 GNU find features supported. - Fixed the build on macOS, and some other UNIXes - Implemented `-regex`, `-iregex`, `-regextype`, and BSD's `-E` - Implemented `-x` (same as `-mount`/`-xdev`) from BSD - Implemented `-mnewer` (same as `-newer`) from BSD - Implemented `-depth N` from BSD - Implemented `-sparse` from FreeBSD - Implemented the `T` and `P` suffices for `-size`, for BSD compatibility - Added support for `-gid NAME` and `-uid NAME` as in BSD 0.84.1 ------ **November 24, 2016** Bugfix release. - Fixed [#7] again - Like GNU find, don't print warnings by default if standard input is not a terminal - Redirect standard input from /dev/null for -ok and -okdir - Skip . when -delete'ing - Fixed -execdir when the root path has no slashes - Fixed -execdir in / - Support -perm +MODE for symbolic modes - Fixed the build on FreeBSD [#7]: https://github.com/tavianator/bfs/issues/7 0.84 ---- **October 29, 2016** 64/76 GNU find features supported. - Spelling suggestion improvements - Handle `--` - (Untested) support for exotic file types like doors, ports, and whiteouts - Improved robustness in the face of closed std{in,out,err} - Fixed the build on macOS - Implement `-ignore_readdir_race`, `-noignore_readdir_race` - Implement `-perm` 0.82 ---- **September 4, 2016** 62/76 GNU find features supported. - Rework optimization levels - `-O1` - Simple boolean simplification - `-O2` - Purity-based optimizations, allowing side-effect-free tests like `-name` or `-type` to be moved or removed - `-O3` (**default**): - Re-order tests to reduce the expected cost (TODO) - `-O4` - Aggressive optimizations that may have surprising effects on warning/error messages and runtime, but should not otherwise affect the results - `-Ofast`: - Always the highest level, currently the same as `-O4` - Color files with multiple hard links correctly - Treat `-`, `)`, and `,` as paths when required to by POSIX - `)` and `,` are only supported before the expression begins - Implement `-D opt` - Implement `-D rates` - Implement `-fprint` - Implement `-fprint0` - Implement BSD's `-f` - Suggest fixes for typo'd arguments 0.79 ---- **May 27, 2016** 60/76 GNU find features supported. - Remove an errant debug `printf()` from `-used` - Implement the `{} ;` variants of `-exec`, `-execdir`, `-ok`, and `-okdir` 0.74 ---- **March 12, 2016** 56/76 GNU find features supported. - Color broken symlinks correctly - Fix [#7] - Fix `-daystart`'s rounding of midnight - Implement (most of) `-newerXY` - Implement `-used` - Implement `-size` [#7]: https://github.com/tavianator/bfs/issues/7 0.70 ---- **February 23, 2016** 53/76 GNU find features supported. - New `make install` and `make uninstall` targets - Squelch non-positional warnings for `-follow` - Reduce memory footprint by as much as 64% by closing `DIR*`s earlier - Speed up `bfs` by ~5% by using a better FD cache eviction policy - Fix infinite recursion when evaluating `! expr` - Optimize unused pure expressions (e.g. `-empty -a -false`) - Optimize double-negation (e.g. `! ! -name foo`) - Implement `-D stat` and `-D tree` - Implement `-O` 0.67 ---- **February 14, 2016** Initial release. 51/76 GNU find features supported. bfs-4.0.6/docs/CONTRIBUTING.md000066400000000000000000000051051475764024500154730ustar00rootroot00000000000000Contributing to `bfs` ===================== License ------- `bfs` is licensed under the [Zero-Clause BSD License](https://opensource.org/licenses/0BSD), a maximally permissive license. Contributions must use the same license. Individual files contain the following tag instead of the full license text: SPDX-License-Identifier: 0BSD This enables machine processing of license information based on the SPDX License Identifiers that are available here: https://spdx.org/licenses/ Implementation -------------- `bfs` is written in [C](https://en.wikipedia.org/wiki/C_(programming_language)), specifically [C17](https://en.wikipedia.org/wiki/C17_(C_standard_revision)). You can get a feel for the coding style by skimming the source code. [`main.c`](/src/main.c) contains an overview of the rest of source files. A quick summary: - Tabs for indentation, spaces for alignment. - Most types and functions should be namespaced with `bfs_`. Exceptions are made for things that could be generally useful outside of `bfs`. - Error handling follows the C standard library conventions: return a nonzero `int` or a `NULL` pointer, with the error code in `errno`. All failure cases should be handled, including `malloc()` failures. - `goto` is not considered harmful for cleaning up in error paths. Tests ----- `bfs` includes an extensive test suite. See the [build documentation](BUILDING.md#testing) for details on running the tests. Test cases are grouped by the standard or `find` implementation that supports the tested feature(s): | Group | Description | |---------------------------------|---------------------------------------| | [`tests/posix`](/tests/posix) | POSIX compatibility tests | | [`tests/bsd`](/tests/bsd) | BSD `find` features | | [`tests/gnu`](/tests/gnu) | GNU `find` features | | [`tests/common`](/tests/common) | Features common to BSD and GNU `find` | | [`tests/bfs`](/tests/bfs) | `bfs`-specific tests | Both new features and bug fixes should have associated tests. To add a test, create a new `*.sh` file in the appropriate group. Snapshot tests use the `bfs_diff` function to automatically compare the generated and expected outputs. For example, ```bash # posix/something.sh bfs_diff basic -name something ``` `basic` is one of the directory trees generated for test cases; others include `links`, `loops`, `deep`, and `rainbow`. Run `./tests/tests.sh posix/something --update` to generate the reference snapshot (and don't forget to `git add` it). bfs-4.0.6/docs/RELATED.md000066400000000000000000000060361475764024500146500ustar00rootroot00000000000000# Related utilities There are many tools that can be used to find files. This is a catalogue of some of the most important/interesting ones. ## `find`-compatible ### System `find` implementations These `find` implementations are commonly installed as the system `find` utility in UNIX-like operating systems: - [GNU findutils](https://www.gnu.org/software/findutils/) ([manual](https://www.gnu.org/software/findutils/manual/html_node/find_html/index.html), [source](https://git.savannah.gnu.org/cgit/findutils.git)) - BSD `find` - FreeBSD `find` ([manual](https://www.freebsd.org/cgi/man.cgi?find(1)), [source](https://cgit.freebsd.org/src/tree/usr.bin/find)) - OpenBSD `find` ([manual](https://man.openbsd.org/find.1), [source](https://cvsweb.openbsd.org/src/usr.bin/find/)) - NetBSD `find` ([manual](https://man.netbsd.org/find.1), [source](http://cvsweb.netbsd.org/bsdweb.cgi/src/usr.bin/find/)) - macOS `find` ([manual](https://ss64.com/osx/find.html), [source](https://github.com/apple-oss-distributions/shell_cmds/tree/main/find)) - Solaris `find` - [Illumos](https://illumos.org/) `find` ([manual](https://illumos.org/man/1/find), [source](https://github.com/illumos/illumos-gate/blob/master/usr/src/cmd/find/find.c)) ### Alternative `find` implementations These are not usually installed as the system `find`, but are designed to be `find`-compatible - [`bfs`](https://tavianator.com/projects/bfs.html) ([manual](https://man.archlinux.org/man/bfs.1), [source](https://github.com/tavianator/bfs)) - [schilytools](https://codeberg.org/schilytools/schilytools) `sfind` ([source](https://codeberg.org/schilytools/schilytools/src/branch/master/sfind)) - [BusyBox](https://busybox.net/) `find` ([manual](https://busybox.net/downloads/BusyBox.html#find), [source](https://git.busybox.net/busybox/tree/findutils/find.c)) - [ToyBox](https://landley.net/toybox/) `find` ([manual](http://landley.net/toybox/help.html#find), [source](https://github.com/landley/toybox/blob/master/toys/posix/find.c)) - [Heirloom Project](https://heirloom.sourceforge.net/) `find` ([manual](https://heirloom.sourceforge.net/man/find.1.html), [source](https://github.com/eunuchs/heirloom-project/blob/master/heirloom/heirloom/find/find.c)) - [uutils](https://uutils.github.io/) `find` ([source](https://github.com/uutils/findutils)) ## `find` alternatives These utilities are not `find`-compatible, but serve a similar purpose: - [`fd`](https://github.com/sharkdp/fd): A simple, fast and user-friendly alternative to 'find' - `locate` - [GNU `locate`](https://www.gnu.org/software/findutils/locate) - [`mlocate`](https://pagure.io/mlocate) ([manual](), [source](https://pagure.io/mlocate/tree/master)) - [`plocate`](https://plocate.sesse.net/) ([manual](https://plocate.sesse.net/plocate.1.html), [source](https://git.sesse.net/?p=plocate)) - [`walk`](https://github.com/google/walk): Plan 9 style utilities to replace find(1) - [fselect](https://github.com/jhspetersson/fselect): Find files with SQL-like queries - [rawhide](https://github.com/raforg/rawhide): find files using pretty C expressions bfs-4.0.6/docs/SECURITY.md000066400000000000000000000115661475764024500150430ustar00rootroot00000000000000Security ======== Threat model ------------ `bfs` is a command line program running on multi-user operating systems. Those other users may be malicious, but `bfs` should not allow them to do anything they couldn't already do. That includes situations where one user (especially `root`) is running `bfs` on files owned or controlled by another user. On the other hand, `bfs` implicitly trusts the user running it. Anyone with enough control over the command line of `bfs` or any `find`-compatible tool can wreak havoc with dangerous actions like `-exec`, `-delete`, etc. > [!CAUTION] > The only untrusted input that should *ever* be passed on the `bfs` command line are **file paths**. > It is *always* unsafe to allow *any* other part of the command line to be affected by untrusted input. > Use the `-f` flag, or `-files0-from`, to ensure that the input is interpreted as a path. This still has security implications, including: - **Information disclosure:** an attacker may learn whether particular files exist by observing `bfs`'s output, exit status, or even side channels like execution time. - **Denial of service:** large directory trees or slow/network storage may cause `bfs` to consume excessive system resources. > [!TIP] > When in doubt, do not pass any untrusted input to `bfs`. Executing commands ------------------ The `-exec` family of actions execute commands, passing the matched paths as arguments. File names that begin with a dash may be misinterpreted as options, so `bfs` adds a leading `./` in some instances: ```console user@host$ bfs -execdir echo {} \; ./-rf ``` This might save you from accidentally running `rm -rf` (for example) when you didn't mean to. This mitigation applies to `-execdir`, but not `-exec`, because the full path typically does not begin with a dash. But it is possible, so be careful: ```console user@host$ bfs -f -rf -exec echo {} \; -rf ``` Race conditions --------------- Like many programs that interface with the file system, `bfs` can be affected by race conditions—in particular, "[time-of-check to time-of-use](https://en.wikipedia.org/wiki/Time-of-check_to_time-of-use)" (TOCTTOU) issues. For example, ```console user@host$ bfs / -user user -exec dangerous_command {} \; ``` is not guaranteed to only run `dangerous_command` on files you own, because another user may run ```console evil@host$ mv /path/to/file /path/to/exile evil@host$ mv ~/malicious /path/to/file ``` in between checking `-user user` and executing the command. > [!WARNING] > Be careful when running `bfs` on directories that other users have write access to, because they can modify the directory tree while `bfs` is running, leading to unpredictable results and possible TOCTTOU issues. Output sanitization ------------------- In general, printing arbitrary data to a terminal may have [security](https://hdm.io/writing/termulation.txt) [implications](https://dgl.cx/2023/09/ansi-terminal-security#vulnerabilities-using-known-replies). On many platforms, file paths may be completely arbitrary data (except for NUL (`\0`) bytes). Therefore, when `bfs` is writing output to a terminal, it will escape non-printable characters:
user@host$ touch $'\e[1mBOLD\e[0m'
user@host$ bfs
.
./$'\e[1mBOLD\e[0m'
However, this is fragile as it only applies when outputting directly to a terminal:
user@host$ bfs | grep BOLD
BOLD
Code quality ------------ Every correctness issue in `bfs` is a potential security issue, because acting on the wrong path may do arbitrarily bad things. For example: ```console root@host# bfs /etc -name passwd -exec cat {} \; ``` should print `/etc/passwd` but not `/etc/shadow`. `bfs` tries to ensure correct behavior through careful programming practice, an extensive testsuite, and static analysis. `bfs` is written in C, which is a memory unsafe language. Bugs that lead to memory corruption are likely to be exploitable due to the nature of C. We use [sanitizers](https://github.com/google/sanitizers) to try to detect these bugs. Fuzzing has also been applied in the past, and deploying continuous fuzzing is a work in progress. Supported versions ------------------ `bfs` comes with [no warranty](/LICENSE), and is maintained by [me](https://tavianator.com/) and [other volunteers](https://github.com/tavianator/bfs/graphs/contributors) in our spare time. In that sense, there are no *supported* versions. However, as long as I maintain `bfs` I will attempt to address any security issues swiftly. In general, security fixes will be part of the latest release, though for significant issues I may backport fixes to older release series. Reporting a vulnerability ------------------------- If you think you have found a sensitive security issue in `bfs`, you can [report it privately](https://github.com/tavianator/bfs/security/advisories/new). Or you can [report it publicly](https://github.com/tavianator/bfs/issues/new); I won't judge you. bfs-4.0.6/docs/USAGE.md000066400000000000000000000112471475764024500144340ustar00rootroot00000000000000Using `bfs` =========== `bfs` has the same command line syntax as `find`, and almost any `find` command that works with a major `find` implementation will also work with `bfs`. When invoked with no arguments, `bfs` will list everything under the current directory recursively, breadth-first: ```console $ bfs . ./LICENSE ./Makefile ./README.md ./completions ./docs ./src ./tests ./completions/bfs.bash ./completions/bfs.zsh ./docs/BUILDING.md ./docs/CHANGELOG.md ./docs/CONTRIBUTING.md ./docs/USAGE.md ./docs/bfs.1 ... ``` Paths ----- Arguments that don't begin with `-` are treated as paths to search. If one or more paths are specified, they are used instead of the current directory: ```console $ bfs /usr/bin /usr/lib /usr/bin /usr/lib /usr/bin/bfs ... /usr/lib/libc.so ... ``` Expressions ----------- Arguments that start with `-` form an *expression* which `bfs` evaluates to filter the matched files, and to do things with the files that match. The most common expression is probably `-name`, which matches filenames against a glob pattern: ```console $ bfs -name '*.md' ./README.md ./docs/BUILDING.md ./docs/CHANGELOG.md ./docs/CONTRIBUTING.md ./docs/USAGE.md ``` ### Operators When you put multiple expressions next to each other, both of them must match: ```console $ bfs -name '*.md' -name '*ING*' ./docs/BUILDING.md ./docs/CONTRIBUTING.md ``` This works because the expressions are implicitly combined with *logical and*. You could be explicit by writing ```console $ bfs -name '*.md' -and -name '*ING'` ``` There are other operators like `-or`: ```console $ bfs -name '*.md' -or -name 'bfs.*' ./README.md ./completions/bfs.bash ./completions/bfs.fish ./completions/bfs.zsh ./docs/BUILDING.md ./docs/CHANGELOG.md ./docs/CONTRIBUTING.md ./docs/USAGE.md ./docs/bfs.1 ``` and `-not`: ```console $ bfs -name '*.md' -and -not -name '*ING*' ./README.md ./docs/CHANGELOG.md ./docs/USAGE.md ``` ### Actions Every `bfs` expression returns either `true` or `false`. For expressions like `-name`, that's all they do. But some expressions, called *actions*, have other side effects. If no actions are included in the expression, `bfs` adds the `-print` action automatically, which is why the above examples actually print any output. The default `-print` is suppressed if any actions are given explicitly. Available actions include printing with alternate formats (`-ls`, `-printf`, etc.), executing commands (`-exec`, `-execdir`, etc.), deleting files (`-delete`), and stopping the search (`-quit`, `-exit`). Extensions ---------- `bfs` implements a few extensions not found in other `find` implementations. ### `-exclude` The `-exclude` operator skips an entire subtree whenever an expression matches. For example, `-exclude -name .git` will exclude any files or directories named `.git` from the search results. `-exclude` is easier to use than the standard `-prune` action; compare bfs -name config -exclude -name .git to the equivalent find ! \( -name .git -prune \) -name config Unlike `-prune`, `-exclude` even works in combination with `-depth`/`-delete`. --- ### `-limit` The `-limit N` action makes `bfs` quit once it gets evaluated `N` times. Placing it after an action like `-print` limits the number of results that get printed, for example: ```console $ bfs -s -type f -name '*.txt' ./1.txt ./2.txt ./3.txt ./4.txt $ bfs -s -type f -name '*.txt' -print -limit 2 ./1.txt ./2.txt ``` This is similar to ```console $ bfs -s -type f -name '*.txt' | head -n2 ``` but more powerful because you can apply separate limits to different expressions: ```console $ bfs \( -name '*.txt' -print -limit 3 -o -name '*.log' -print -limit 4 \) -limit 5 [At most 3 .txt files, at most 4 .log files, and at most 5 in total] ``` and more efficient because it will quit immediately. When piping to `head`, `bfs` will only quit *after* it tries to output too many results. --- ### `-hidden`/`-nohidden` `-hidden` matches "hidden" files (dotfiles). `bfs -hidden` is effectively shorthand for find \( -name '.*' -not -name . -not -name .. \) `-nohidden` is equivalent to `-exclude -hidden`. --- ### `-unique` This option ensures that `bfs` only visits each file once, even if it's reachable through multiple hard or symbolic links. It's particularly useful when following symbolic links (`-L`). --- ### `-color`/`-nocolor` When printing to a terminal, `bfs` automatically colors paths like GNU `ls`, according to the `LS_COLORS` environment variable. The `-color` and `-nocolor` options override the automatic behavior, which may be handy when you want to preserve colors through a pipe: bfs -color | less -R If the [`NO_COLOR`](https://no-color.org/) environment variable is set, colors will be disabled by default. bfs-4.0.6/docs/bfs.1000066400000000000000000000411651475764024500141040ustar00rootroot00000000000000.\" Copyright © Tavian Barnes .\" SPDX-License-Identifier: 0BSD .TH BFS 1 2025-02-26 "bfs 4.0.6" .SH NAME bfs \- breadth-first search for your files .SH SYNOPSIS .B bfs .RB [ flags ...] .RI [ paths ...] .RB [ expression ...] .PP flags .RB ( \-H / \-L / \-P etc.), .IR paths , and .B expressions may be freely mixed in any order. .SH DESCRIPTION .B bfs is a breadth-first version of the UNIX .BR find (1) command. .PP .B bfs supports almost every feature from every major .BR find (1) implementation, so your existing command lines should work as-is. It also adds some features of its own, such as a more forgiving command line parser and some additional options. .PP Each .I path specified on the command line is treated as a starting path to search through. If no paths are specified, the current directory .RI ( . ) is searched by default. .PP Like .BR find (1), .B bfs interprets its arguments as a short-circuiting Boolean expression. For example, .PP .nf .RS .B bfs \e( \-name '*.txt' \-or \-lname '*.txt' \e) \-and \-print .RE .fi .PP will print all the paths that are either .txt files or symbolic links to .txt files. .B \-and is implied between two consecutive expressions, so this is equivalent: .PP .nf .RS .B bfs \e( \-name '*.txt' \-or \-lname '*.txt' \e) \-print .RE .fi .PP Finally, .B \-print is implied if no actions are specified, so this too is equivalent: .PP .nf .RS .B bfs \-name '*.txt' \-or \-lname '*.txt' .RE .fi .PP Most options that take a numeric argument .I N will also accept .I \-N or .IR +N . .I \-N means "less than .IR N ," and .I +N means "greater than .IR N ." .SH FLAGS .TP .B \-H Follow symbolic links on the command line, but not while searching. .TP .B \-L Follow all symbolic links. .TP .B \-P Never follow symbolic links (the default). .TP .B \-E Use extended regular expressions (same as .B \-regextype .IR posix-extended ). .TP .B \-X Filter out files with .RB non- xargs (1)-safe names. .TP .B \-d Search in post-order (same as .BR \-depth ). .TP .B \-s Visit directory entries in sorted order. The sorting takes place within each directory separately, which makes it different from .B bfs ... | .BR sort , but still provides a deterministic ordering. .TP .B \-x Don't descend into other mount points (same as .BR \-xdev ). .TP .BI "\-f " PATH Treat .I PATH as a path to search (useful if it begins with a dash). .TP .BI "\-D " FLAG Turn on a debugging flag (see .B \-D .IR help ). .PP .BI \-O N .RS Enable optimization level .I N (default: .IR 3 ). .TP .BI \-O 0 Disable all optimizations. .TP .BI \-O 1 Basic logical simplifications. .TP .BI \-O 2 All .BI \-O 1 optimizations, plus dead code elimination and data flow analysis. .TP .BI \-O 3 All .BI \-O 2 optimizations, plus re-order expressions to reduce expected cost. .TP \fB\-O\fI4\fR/\fB\-O\fIfast\fR All optimizations, including aggressive optimizations that may alter the observed behavior in corner cases. .RE .PP \fB\-S \fIbfs\fR|\fIdfs\fR|\fIids\fR|\fIeds\fR .RS Choose the search strategy. .TP .I bfs Breadth-first search (the default). .TP .I dfs Depth-first search. Uses less memory than breadth-first search, but is typically slower to return relevant results. .TP .I ids Iterative deepening search. Performs repeated depth-first searches with increasing depth limits. This gives results in the same order as breadth-first search, but with the reduced memory consumption of depth-first search. Tends to be very slow in practice, so use it only if you absolutely need breadth-first ordering, but .B \-S .I bfs consumes too much memory. .TP .I eds Exponential deepening search. A compromise between breadth- and depth-first search, which searches exponentially increasing depth ranges (e.g. 0-1, 1-2, 2-4, 4-8, etc.). Provides many of the benefits of breadth-first search with depth-first's reduced memory consumption. Typically far faster than .B \-S .IR ids . .RE .TP .BI \-j N Search with .I N threads in parallel (default: number of CPUs, up to .IR 8 ). .SH OPERATORS .TP .BI "( " expression " )" Parentheses are used for grouping expressions together. You'll probably have to write .B \e( .I expression .B \e) to avoid the parentheses being interpreted by the shell. .PP \fB! \fIexpression\fR .br .B \-not .I expression .RS The "not" operator: returns the negation of the truth value of the .IR expression . You may have to write \fB\e! \fIexpression\fR to avoid .B ! being interpreted by the shell. .RE .PP .I expression expression .br .I expression .B \-a .I expression .br .I expression .B \-and .I expression .RS Short-circuiting "and" operator: if the left-hand .I expression is .BR true , returns the right-hand .IR expression ; otherwise, returns .BR false . .RE .PP .I expression .B \-o .I expression .br .I expression .B \-or .I expression .RS Short-circuiting "or" operator: if the left-hand .I expression is .BR false , returns the right-hand .IR expression ; otherwise, returns .BR true . .RE .TP .IB "expression " , " expression" The "comma" operator: evaluates the left-hand .I expression but discards the result, returning the right-hand .IR expression . .SH SPECIAL FORMS .TP .BI "\-exclude " expression Exclude all paths matching the .I expression from the search. This is more powerful than .BR \-prune , because it applies even when the expression wouldn't otherwise be evaluated, due to .B \-depth or .B \-mindepth for example. Exclusions are always applied before other expressions, so it may be least confusing to put them first on the command line. .PP .B \-help .br .B \-\-help .RS Print usage information, and exit immediately (without parsing the rest of the command line or processing any files). .RE .PP .B \-version .br .B \-\-version .RS Print version information, and exit immediately. .RE .SH OPTIONS .B \-color .br .B \-nocolor .RS Turn colors on or off (default: .B \-color if outputting to a terminal, .B \-nocolor otherwise). .RE .TP .B \-daystart Measure time relative to the start of today. .TP .B \-depth Search in post-order (descendents first). .TP .B \-follow Follow all symbolic links (same as .BR \-L ). .TP .BI "\-files0\-from " FILE Treat the NUL ('\e0')-separated paths in .I FILE as starting points for the search. Pass .B \-files0\-from .I \- to read the paths from standard input. .PP .B \-ignore_readdir_race .br .B \-noignore_readdir_race .RS Whether to report an error if .B bfs detects that the file tree is modified during the search (default: .BR \-noignore_readdir_race ). .RE .PP .B \-maxdepth .I N .br .B \-mindepth .I N .RS Ignore files deeper/shallower than .IR N . .RE .TP .B \-mount Exclude mount points entirely from the results. .TP .B \-noerror Ignore any errors that occur during traversal. .TP .B \-nohidden Exclude hidden files and directories. .TP .B \-noleaf Ignored; for compatibility with GNU find. .TP .BI "\-regextype " TYPE Use .IR TYPE -flavored regular expressions. The possible types are .RS .TP .I posix-basic POSIX basic regular expressions (the default). .TP .I posix-extended POSIX extended regular expressions. .TP .I ed Like .BR ed (1) (same as .IR posix-basic ). .TP .I emacs Like .BR emacs (1). .TP .I grep Like .BR grep (1). .TP .I sed Like .BR sed (1) (same as .IR posix-basic ). .PP See .BR regex (7) for a description of regular expression syntax. .RE .TP .B \-status Display a status bar while searching. .TP .B \-unique Skip any files that have already been seen. Particularly useful along with .BR \-L . .PP .B \-warn .br .B \-nowarn .RS Turn on or off warnings about the command line. .RE .TP .B \-xdev Don't descend into other mount points. Unlike .BR \-mount , the mount point itself is still included. .SH TESTS .TP .B \-acl Find files with a non-trivial Access Control List .RB ( acl (5)). .PP \fB\-amin\fR [\fI\-+\fR]\fIN\fR .br \fB\-Bmin\fR [\fI\-+\fR]\fIN\fR .br \fB\-cmin\fR [\fI\-+\fR]\fIN\fR .br \fB\-mmin\fR [\fI\-+\fR]\fIN\fR .RS Find files .BR a ccessed/ B irthed/ c hanged/ m odified .I N minutes ago. .RE .PP .B \-anewer .I FILE .br .B \-Bnewer .I FILE .br .B \-cnewer .I FILE .br .B \-mnewer .I FILE .RS Find files .BR a ccessed/ B irthed/ c hanged/ m odified more recently than .I FILE was modified. .RE .PP .B \-asince .I TIME .br .B \-Bsince .I TIME .br .B \-csince .I TIME .br .B \-msince .I TIME .RS Find files .BR a ccessed/ B irthed/ c hanged/ m odified more recently than the ISO 8601-style timestamp .IR TIME . See .BI \-newer XY for examples of the timestamp format. .RE .PP \fB\-atime\fR [\fI\-+\fR]\fIN\fR .br \fB\-Btime\fR [\fI\-+\fR]\fIN\fR .br \fB\-ctime\fR [\fI\-+\fR]\fIN\fR .br \fB\-mtime\fR [\fI\-+\fR]\fIN\fR .RS Find files .BR a ccessed/ B irthed/ c hanged/ m odified .I N days ago. .RE .TP .B \-capable Find files with POSIX.1e .BR capabilities (7) set. .TP .BI "\-context " GLOB Find files whose SELinux context matches the .IR GLOB . .TP \fB\-depth\fR [\fI\-+\fR]\fIN\fR Find files with depth .IR N . .TP .B \-empty Find empty files/directories. .PP .B \-executable .br .B \-readable .br .B \-writable .RS Find files the current user can execute/read/write. .RE .PP .B \-false .br .B \-true .RS Always false/true. .RE .TP \fB\-flags\fR [\fI\-+\fR]\fIFLAGS\fR Find files with matching inode .BR FLAGS . .TP .BI "\-fstype " TYPE Find files on file systems with the given .IR TYPE . .PP \fB\-gid\fR [\fI\-+\fR]\fIN\fR .br \fB\-uid\fR [\fI\-+\fR]\fIN\fR .RS Find files owned by group/user ID .IR N . .RE .PP .B \-group .I NAME .br .B \-user .I NAME .RS Find files owned by the group/user .IR NAME . .RE .TP .B \-hidden Find hidden files (those beginning with .IR . ). .PP .B \-ilname .I GLOB .br .B \-iname .I GLOB .br .B \-ipath .I GLOB .br .B \-iregex .I REGEX .br .B \-iwholename .I GLOB .RS Case-insensitive versions of .BR \-lname / \-name / \-path / \-regex / \-wholename . .RE .TP \fB\-inum\fR [\fI\-+\fR]\fIN\fR Find files with inode number .IR N . .TP \fB\-links\fR [\fI\-+\fR]\fIN\fR Find files with .I N hard links. .TP .BI "\-lname " GLOB Find symbolic links whose target matches the .IR GLOB . .TP .BI "\-name " GLOB Find files whose name matches the .IR GLOB . .TP .BI "\-newer " FILE Find files newer than .IR FILE . .TP .BI \-newer "XY REFERENCE" Find files whose .I X time is newer than the .I Y time of .IR REFERENCE . .I X and .I Y can be any of .RI [ aBcm ] .RI ( a ccess/ B irth/ c hange/ m odification). .I Y may also be .I t to parse .I REFERENCE as an ISO 8601-style timestamp. For example: .PP .RS .nf \(bu \fI1991-12-14\fR \(bu \fI1991-12-14T03:00\fR \(bu \fI1991-12-14T03:00-07:00\fR \(bu '\fI1991-12-14 10:00Z\fR' .fi .RE .PP .B \-nogroup .br .B \-nouser .RS Find files owned by nonexistent groups/users. .RE .PP .B \-path .I GLOB .br .B \-wholename .I GLOB .RS Find files whose entire path matches the .IR GLOB . .RE .TP \fB\-perm\fR [\fI\-+/\fR]\fIMODE\fR Find files with a matching mode. .TP .BI "\-regex " REGEX Find files whose entire path matches the regular expression .IR REGEX . .TP .BI "\-samefile " FILE Find hard links to .IR FILE . .TP .BI "\-since " TIME Find files modified since the ISO 8601-style timestamp .IR TIME . See .BI \-newer XY for examples of the timestamp format. .TP \fB\-size\fR [\fI\-+\fR]\fIN\fR[\fIcwbkMGTP\fR] Find files with the given size. The unit can be one of .PP .RS .nf \(bu \fIc\fRhars (1 byte) \(bu \fIw\fRords (2 bytes) \(bu \fIb\fRlocks (512 bytes, the default) \(bu \fIk\fRiB (1024 bytes) \(bu \fIM\fRiB (1024 kiB) \(bu \fIG\fRiB (1024 MiB) \(bu \fIT\fRiB (1024 GiB) \(bu \fIP\fRiB (1024 TiB) .fi .RE .TP .B \-sparse Find files that occupy fewer disk blocks than expected. .TP \fB\-type\fR [\fIbcdlpfswD\fR] Find files of the given type. The possible types are .PP .RS \(bu .IR b lock device .br \(bu .IR c haracter device .br \(bu .IR d irectory .br \(bu .IR l ink (symbolic) .br \(bu .IR p ipe .br \(bu .IR f ile (regular) .br \(bu .IR s ocket .br \(bu .IR w hiteout .br \(bu .IR D oor .PP Multiple types can be given at once, separated by commas. For example, .B \-type .I d,f matches both directories and regular files. .RE .TP \fB\-used\fR [\fI\-+\fR]\fIN\fR Find files last accessed .I N days after they were changed. .TP .B \-xattr Find files with extended attributes .RB ( xattr (7)). .TP .BI "\-xattrname " NAME Find files with the extended attribute .IR NAME . .TP \fB\-xtype\fR [\fIbcdlpfswD\fR] Find files of the given type, following links when .B \-type would not, and vice versa. .SH ACTIONS .B \-delete .br .B \-rm .RS Delete any found files (implies .BR \-depth ). .RE .TP .BI "\-exec " "command ... {} ;" Execute a command. .TP .BI "\-exec " "command ... {} +" Execute a command with multiple files at once. .TP .BI "\-ok " "command ... {} ;" Prompt the user whether to execute a command. .PP .B \-execdir .I command ... {} ; .br .B \-execdir .I command ... {} + .br .B \-okdir .I command ... {} ; .RS Like .BR \-exec / \-ok , but run the command in the same directory as the found file(s). .RE .TP \fB\-exit\fR [\fISTATUS\fR] Exit immediately with the given status .RI ( 0 if unspecified). .PP .B \-fls .I FILE .br .B \-fprint .I FILE .br .B \-fprint0 .I FILE .br .B \-fprintf .I FILE FORMAT .RS Like .BR \-ls / \-print / \-print0 / \-printf , but write to .I FILE instead of standard output. .RE .TP .BI "\-limit " N Quit once this action is evaluated .I N times. .TP .B \-ls List files like .B ls .IR \-dils . .TP .B \-print Print the path to the found file. .TP .B \-print0 Like .BR \-print , but use the null character ('\e0') as a separator rather than newlines. Useful in conjunction with .B xargs .IR \-0 . .TP .BI "\-printf " FORMAT Print according to a format string (see .BR find (1)). These additional format directives are supported: .RS .TP %w The file's birth time, in the same format as %a/%c/%t. .TP .RI %W k Field .I k of the file's birth time, in the same format as .RI %A k /%C k /%T k . .RE .TP .B \-printx Like .BR \-print , but escape whitespace and quotation characters, to make the output safe for .BR xargs (1). Consider using .B \-print0 and .B xargs .I \-0 instead. .TP .B \-prune Don't descend into this directory. This has no effect if .B \-depth is enabled (either explicitly, or implicitly by .BR \-delete ). Use .B \-exclude instead in that case. .TP .B \-quit Quit immediately. .SH ENVIRONMENT Certain environment variables affect the behavior of .BR bfs . .PP .B LANG .br .B LC_* .RS Specifies the .BR locale (7) in use for various things. .B bfs is not (yet) translated to any languages except English, but the locale will still affect the format of printed values. Yes/no prompts (e.g. from .BR \-ok ) will also be interpreted according to the current locale. .RE .PP .B LS_COLORS .br .B BFS_COLORS .RS Controls the colors used when displaying file paths if .B \-color is enabled. .B bfs interprets .B LS_COLORS the same way GNU .BR ls (1) does (see .BR dir_colors (5)). .B BFS_COLORS can be used to customize .B bfs without affecting other commands. .RE .TP .B NO_COLOR Causes .B bfs to default to .B \-nocolor if it is set (see https://no-color.org/). .TP .B PAGER Specifies the pager used for .B \-help output. Defaults to .BR less (1), if found on the current .BR PATH , otherwise .BR more (1). .TP .B PATH Used to resolve executables for .BR \-exec [ dir ] and .BR \-ok [ dir ]. .TP .B POSIXLY_CORRECT Makes .B bfs conform more strictly to the POSIX.1-2017 specification for .BR find (1). Currently this has two effects: .RS .IP \(bu Disables warnings by default, because POSIX prohibits writing to standard error (except for the .B \-ok prompt), unless the command also fails with a non-zero exit status. .IP \(bu Makes .B \-ls and .B \-fls use 512-byte blocks instead of 1024-byte blocks. (POSIX does not specify these actions, but BSD .BR find (1) implementations use 512-byte blocks, while GNU .BR find (1) uses 1024-byte blocks by default.) .PP It does not disable .BR bfs 's various extensions to the base POSIX functionality. .B POSIXLY_CORRECT has the same effects on GNU .BR find (1). .RE .SH EXAMPLES .TP .B bfs With no arguments, .B bfs prints all files under the current directory in breadth-first order. .TP .B bfs \-name '*.txt' Prints all the .txt files under the current directory. .B *.txt is quoted to ensure the glob is processed by .B bfs rather than the shell. .TP .BI "bfs \-name access_log \-L " /var Finds all files named .B access_log under .IR /var , following symbolic links. .B bfs allows flags and paths to appear anywhere on the command line. .TP .BI "bfs " ~ " \-not \-user $USER" Prints all files in your home directory not owned by you. .TP .B bfs \-xtype l Finds broken symbolic links. .TP .B bfs \-name config \-exclude \-name .git Finds all files named .BR config , skipping every .B .git directory. .TP .B bfs \-type f \-executable \-exec strip {} + Runs .BR strip (1) on all executable files it finds, passing it multiple files at a time. .SH BUGS https://github.com/tavianator/bfs/issues .SH AUTHOR Tavian Barnes .PP https://tavianator.com/projects/bfs.html .SH SEE ALSO .BR find (1), .BR locate (1), .BR xargs (1) bfs-4.0.6/src/000077500000000000000000000000001475764024500131005ustar00rootroot00000000000000bfs-4.0.6/src/alloc.c000066400000000000000000000227041475764024500143430ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD #include "alloc.h" #include "bfs.h" #include "bit.h" #include "diag.h" #include "sanity.h" #include #include #include #include /** The largest possible allocation size. */ #if PTRDIFF_MAX < SIZE_MAX / 2 # define ALLOC_MAX ((size_t)PTRDIFF_MAX) #else # define ALLOC_MAX (SIZE_MAX / 2) #endif /** posix_memalign() wrapper. */ static void *xmemalign(size_t align, size_t size) { bfs_assert(has_single_bit(align)); bfs_assert(align >= sizeof(void *)); // Since https://www.open-std.org/jtc1/sc22/wg14/www/docs/n2072.htm, // aligned_alloc() doesn't require the size to be a multiple of align. // But the sanitizers don't know about that yet, so always use // posix_memalign(). void *ptr = NULL; errno = posix_memalign(&ptr, align, size); return ptr; } void *alloc(size_t align, size_t size) { bfs_assert(has_single_bit(align)); if (size > ALLOC_MAX) { errno = EOVERFLOW; return NULL; } if (align <= alignof(max_align_t)) { return malloc(size); } else { return xmemalign(align, size); } } void *zalloc(size_t align, size_t size) { bfs_assert(has_single_bit(align)); if (size > ALLOC_MAX) { errno = EOVERFLOW; return NULL; } if (align <= alignof(max_align_t)) { return calloc(1, size); } void *ret = xmemalign(align, size); if (ret) { memset(ret, 0, size); } return ret; } void *xrealloc(void *ptr, size_t align, size_t old_size, size_t new_size) { bfs_assert(has_single_bit(align)); if (new_size == 0) { free(ptr); return NULL; } else if (new_size > ALLOC_MAX) { errno = EOVERFLOW; return NULL; } if (align <= alignof(max_align_t)) { return realloc(ptr, new_size); } // There is no aligned_realloc(), so reallocate and copy manually void *ret = xmemalign(align, new_size); if (!ret) { return NULL; } size_t min_size = old_size < new_size ? old_size : new_size; if (min_size) { memcpy(ret, ptr, min_size); } free(ptr); return ret; } void *reserve(void *ptr, size_t align, size_t size, size_t count) { // No need to overflow-check the current size size_t old_size = size * count; // Capacity is doubled every power of two, from 0→1, 1→2, 2→4, etc. // If we stayed within the same size class, reuse ptr. if (count & (count - 1)) { // Tell sanitizers about the new array element sanitize_resize(ptr, old_size, old_size + size, bit_ceil(count) * size); errno = 0; return ptr; } // No need to overflow-check; xrealloc() will fail before we overflow size_t new_size = count ? 2 * old_size : size; void *ret = xrealloc(ptr, align, old_size, new_size); if (!ret) { // errno is used to communicate success/failure to the RESERVE() macro bfs_assert(errno != 0); return ptr; } // Pretend we only allocated one more element sanitize_resize(ret, new_size, old_size + size, new_size); errno = 0; return ret; } /** * An arena allocator chunk. */ union chunk { /** * Free chunks are stored in a singly linked list. The pointer to the * next chunk is represented by an offset from the chunk immediately * after this one in memory, so that zalloc() correctly initializes a * linked list of chunks (except for the last one). */ uintptr_t next; // char object[]; }; /** Decode the next chunk. */ static union chunk *chunk_next(const struct arena *arena, const union chunk *chunk) { uintptr_t base = (uintptr_t)chunk + arena->size; return (union chunk *)(base + chunk->next); } /** Encode the next chunk. */ static void chunk_set_next(const struct arena *arena, union chunk *chunk, union chunk *next) { uintptr_t base = (uintptr_t)chunk + arena->size; chunk->next = (uintptr_t)next - base; } void arena_init(struct arena *arena, size_t align, size_t size) { bfs_assert(has_single_bit(align)); bfs_assert(is_aligned(align, size)); if (align < alignof(union chunk)) { align = alignof(union chunk); } if (size < sizeof(union chunk)) { size = sizeof(union chunk); } bfs_assert(is_aligned(align, size)); arena->chunks = NULL; arena->nslabs = 0; arena->slabs = NULL; arena->align = align; arena->size = size; } /** Allocate a new slab. */ _cold static int slab_alloc(struct arena *arena) { // Make the initial allocation size ~4K size_t size = 4096; if (size < arena->size) { size = arena->size; } // Trim off the excess size -= size % arena->size; // Double the size for every slab size <<= arena->nslabs; // Allocate the slab void *slab = zalloc(arena->align, size); if (!slab) { return -1; } // Grow the slab array void **pslab = RESERVE(void *, &arena->slabs, &arena->nslabs); if (!pslab) { free(slab); return -1; } // Fix the last chunk->next offset void *last = (char *)slab + size - arena->size; chunk_set_next(arena, last, arena->chunks); // We can rely on zero-initialized slabs, but others shouldn't sanitize_uninit(slab, size); arena->chunks = *pslab = slab; return 0; } void *arena_alloc(struct arena *arena) { if (!arena->chunks && slab_alloc(arena) != 0) { return NULL; } union chunk *chunk = arena->chunks; sanitize_alloc(chunk, arena->size); sanitize_init(chunk); arena->chunks = chunk_next(arena, chunk); sanitize_uninit(chunk, arena->size); return chunk; } void arena_free(struct arena *arena, void *ptr) { union chunk *chunk = ptr; chunk_set_next(arena, chunk, arena->chunks); arena->chunks = chunk; sanitize_uninit(chunk, arena->size); sanitize_free(chunk, arena->size); } void arena_clear(struct arena *arena) { for (size_t i = 0; i < arena->nslabs; ++i) { free(arena->slabs[i]); } free(arena->slabs); arena->chunks = NULL; arena->nslabs = 0; arena->slabs = NULL; } void arena_destroy(struct arena *arena) { arena_clear(arena); sanitize_uninit(arena); } void varena_init(struct varena *varena, size_t align, size_t offset, size_t size) { varena->align = align; varena->offset = offset; varena->size = size; varena->narenas = 0; varena->arenas = NULL; // The smallest size class is at least as many as fit in the smallest // aligned allocation size size_t min_count = (flex_size(align, offset, size, 1) - offset + size - 1) / size; varena->shift = bit_width(min_count - 1); } /** Get the size class for the given array length. */ static size_t varena_size_class(struct varena *varena, size_t count) { // Since powers of two are common array lengths, make them the // (inclusive) upper bound for each size class return bit_width((count - !!count) >> varena->shift); } /** Get the exact size of a flexible struct. */ static size_t varena_exact_size(const struct varena *varena, size_t count) { return flex_size(varena->align, varena->offset, varena->size, count); } /** Get the arena for the given array length. */ static struct arena *varena_get(struct varena *varena, size_t count) { size_t i = varena_size_class(varena, count); while (i >= varena->narenas) { size_t j = varena->narenas; struct arena *arena = RESERVE(struct arena, &varena->arenas, &varena->narenas); if (!arena) { return NULL; } size_t shift = j + varena->shift; size_t size = varena_exact_size(varena, (size_t)1 << shift); arena_init(arena, varena->align, size); } return &varena->arenas[i]; } void *varena_alloc(struct varena *varena, size_t count) { struct arena *arena = varena_get(varena, count); if (!arena) { return NULL; } void *ret = arena_alloc(arena); if (!ret) { return NULL; } // Tell the sanitizers the exact size of the allocated struct sanitize_resize(ret, arena->size, varena_exact_size(varena, count), arena->size); return ret; } void *varena_realloc(struct varena *varena, void *ptr, size_t old_count, size_t new_count) { struct arena *new_arena = varena_get(varena, new_count); struct arena *old_arena = varena_get(varena, old_count); if (!new_arena) { return NULL; } size_t old_size = old_arena->size; size_t new_size = new_arena->size; if (new_arena == old_arena) { sanitize_resize(ptr, varena_exact_size(varena, old_count), varena_exact_size(varena, new_count), new_size); return ptr; } void *ret = arena_alloc(new_arena); if (!ret) { return NULL; } // Non-sanitized builds don't bother computing exact sizes, and just use // the potentially-larger arena size for each size class instead. To // allow the below memcpy() to work with the less-precise sizes, expand // the old allocation to its full capacity. sanitize_resize(ptr, varena_exact_size(varena, old_count), old_size, old_size); size_t min_size = new_size < old_size ? new_size : old_size; memcpy(ret, ptr, min_size); arena_free(old_arena, ptr); sanitize_resize(ret, new_size, varena_exact_size(varena, new_count), new_size); return ret; } void *varena_grow(struct varena *varena, void *ptr, size_t *count) { size_t old_count = *count; // Round up to the limit of the current size class. If we're already at // the limit, go to the next size class. size_t new_shift = varena_size_class(varena, old_count + 1) + varena->shift; size_t new_count = (size_t)1 << new_shift; ptr = varena_realloc(varena, ptr, old_count, new_count); if (ptr) { *count = new_count; } return ptr; } void varena_free(struct varena *varena, void *ptr, size_t count) { struct arena *arena = varena_get(varena, count); arena_free(arena, ptr); } void varena_clear(struct varena *varena) { for (size_t i = 0; i < varena->narenas; ++i) { arena_clear(&varena->arenas[i]); } } void varena_destroy(struct varena *varena) { for (size_t i = 0; i < varena->narenas; ++i) { arena_destroy(&varena->arenas[i]); } free(varena->arenas); sanitize_uninit(varena); } bfs-4.0.6/src/alloc.h000066400000000000000000000242151475764024500143470ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD /** * Memory allocation. */ #ifndef BFS_ALLOC_H #define BFS_ALLOC_H #include "bfs.h" #include #include #include #define IS_ALIGNED(align, size) \ (((size) & ((align) - 1)) == 0) /** Check if a size is properly aligned. */ static inline bool is_aligned(size_t align, size_t size) { return IS_ALIGNED(align, size); } #define ALIGN_FLOOR(align, size) \ ((size) & ~((align) - 1)) /** Round down to a multiple of an alignment. */ static inline size_t align_floor(size_t align, size_t size) { return ALIGN_FLOOR(align, size); } #define ALIGN_CEIL(align, size) \ ((((size) - 1) | ((align) - 1)) + 1) /** Round up to a multiple of an alignment. */ static inline size_t align_ceil(size_t align, size_t size) { return ALIGN_CEIL(align, size); } /** * Saturating size addition. */ static inline size_t size_add(size_t lhs, size_t rhs) { size_t ret = lhs + rhs; return ret >= lhs ? ret : (size_t)-1; } /** * Saturating size multiplication. */ static inline size_t size_mul(size_t size, size_t count) { size_t ret = size * count; return ret / size == count ? ret : (size_t)-1; } /** Saturating array sizeof. */ #define sizeof_array(type, count) \ size_mul(sizeof(type), count) /** Size of a struct/union field. */ #define sizeof_member(type, member) \ sizeof(((type *)NULL)->member) /** * @internal * Our flexible struct size calculations assume that structs have the minimum * trailing padding to align the type properly. A pathological ABI that adds * extra padding would result in us under-allocating space for those structs, * so we static_assert() that no such padding exists. */ #define ASSERT_FLEX_ABI(type, member) \ ASSERT_FLEX_ABI_( \ ALIGN_CEIL(alignof(type), offsetof(type, member)) >= sizeof(type), \ "Unexpected tail padding in " #type) /** * @internal * The contortions here allow static_assert() to be used in expressions, rather * than just declarations. */ #define ASSERT_FLEX_ABI_(...) \ ((void)sizeof(struct { char _; static_assert(__VA_ARGS__); })) /** * Saturating flexible struct size. * * @align * Struct alignment. * @offset * Flexible array member offset. * @size * Flexible array element size. * @count * Flexible array element count. * @return * The size of the struct with count flexible array elements. Saturates * to the maximum aligned value on overflow. */ static inline size_t flex_size(size_t align, size_t offset, size_t size, size_t count) { size_t ret = size_mul(size, count); ret = size_add(ret, offset + align - 1); ret = align_floor(align, ret); return ret; } /** * Computes the size of a flexible struct. * * @type * The type of the struct containing the flexible array. * @member * The name of the flexible array member. * @count * The length of the flexible array. * @return * The size of the struct with count flexible array elements. Saturates * to the maximum aligned value on overflow. */ #define sizeof_flex(type, member, count) \ (ASSERT_FLEX_ABI(type, member), flex_size( \ alignof(type), offsetof(type, member), sizeof_member(type, member[0]), count)) /** * General memory allocator. * * @align * The required alignment. * @size * The size of the allocation. * @return * The allocated memory, or NULL on failure. */ _malloc(free, 1) _aligned_alloc(1, 2) void *alloc(size_t align, size_t size); /** * Zero-initialized memory allocator. * * @align * The required alignment. * @size * The size of the allocation. * @return * The allocated memory, or NULL on failure. */ _malloc(free, 1) _aligned_alloc(1, 2) void *zalloc(size_t align, size_t size); /** Allocate memory for the given type. */ #define ALLOC(type) \ (type *)alloc(alignof(type), sizeof(type)) /** Allocate zeroed memory for the given type. */ #define ZALLOC(type) \ (type *)zalloc(alignof(type), sizeof(type)) /** Allocate memory for an array. */ #define ALLOC_ARRAY(type, count) \ (type *)alloc(alignof(type), sizeof_array(type, count)) /** Allocate zeroed memory for an array. */ #define ZALLOC_ARRAY(type, count) \ (type *)zalloc(alignof(type), sizeof_array(type, count)) /** Allocate memory for a flexible struct. */ #define ALLOC_FLEX(type, member, count) \ (type *)alloc(alignof(type), sizeof_flex(type, member, count)) /** Allocate zeroed memory for a flexible struct. */ #define ZALLOC_FLEX(type, member, count) \ (type *)zalloc(alignof(type), sizeof_flex(type, member, count)) /** * Alignment-aware realloc(). * * @ptr * The pointer to reallocate. * @align * The required alignment. * @old_size * The previous allocation size. * @new_size * The new allocation size. * @return * The reallocated memory, or NULL on failure. */ _aligned_alloc(2, 4) _nodiscard void *xrealloc(void *ptr, size_t align, size_t old_size, size_t new_size); /** Reallocate memory for an array. */ #define REALLOC_ARRAY(type, ptr, old_count, new_count) \ (type *)xrealloc((ptr), alignof(type), sizeof_array(type, old_count), sizeof_array(type, new_count)) /** Reallocate memory for a flexible struct. */ #define REALLOC_FLEX(type, member, ptr, old_count, new_count) \ (type *)xrealloc((ptr), alignof(type), sizeof_flex(type, member, old_count), sizeof_flex(type, member, new_count)) /** * Reserve space for one more element in a dynamic array. * * @ptr * The pointer to reallocate. * @align * The required alignment. * @count * The current size of the array. * @return * The reallocated memory, on both success *and* failure. On success, * errno will be set to zero, and the returned pointer will have room * for (count + 1) elements. On failure, errno will be non-zero, and * ptr will returned unchanged. */ _nodiscard void *reserve(void *ptr, size_t align, size_t size, size_t count); /** * Convenience macro to grow a dynamic array. * * @type * The array element type. * @type **ptr * A pointer to the array. * @size_t *count * A pointer to the array's size. * @return * On success, a pointer to the newly reserved array element, i.e. * `*ptr + *count++`. On failure, NULL is returned, and both *ptr and * *count remain unchanged. */ #define RESERVE(type, ptr, count) \ ((*ptr) = reserve((*ptr), alignof(type), sizeof(type), (*count)), \ errno ? NULL : (*ptr) + (*count)++) /** * An arena allocator for fixed-size types. * * Arena allocators are intentionally not thread safe. */ struct arena { /** The list of free chunks. */ void *chunks; /** The number of allocated slabs. */ size_t nslabs; /** The array of slabs. */ void **slabs; /** Chunk alignment. */ size_t align; /** Chunk size. */ size_t size; }; /** * Initialize an arena for chunks of the given size and alignment. */ void arena_init(struct arena *arena, size_t align, size_t size); /** * Initialize an arena for the given type. */ #define ARENA_INIT(arena, type) \ arena_init((arena), alignof(type), sizeof(type)) /** * Free an object from the arena. */ void arena_free(struct arena *arena, void *ptr); /** * Allocate an object out of the arena. */ _malloc(arena_free, 2) void *arena_alloc(struct arena *arena); /** * Free all allocations from an arena. */ void arena_clear(struct arena *arena); /** * Destroy an arena, freeing all allocations. */ void arena_destroy(struct arena *arena); /** * An arena allocator for flexibly-sized types. */ struct varena { /** The alignment of the struct. */ size_t align; /** The offset of the flexible array. */ size_t offset; /** The size of the flexible array elements. */ size_t size; /** Shift amount for the smallest size class. */ size_t shift; /** The number of arenas of different sizes. */ size_t narenas; /** The array of differently-sized arenas. */ struct arena *arenas; }; /** * Initialize a varena for a struct with the given layout. * * @varena * The varena to initialize. * @align * alignof(type) * @offset * offsetof(type, flexible_array) * @size * sizeof(flexible_array[i]) */ void varena_init(struct varena *varena, size_t align, size_t offset, size_t size); /** * Initialize a varena for the given type and flexible array. * * @varena * The varena to initialize. * @type * A struct type containing a flexible array. * @member * The name of the flexible array member. */ #define VARENA_INIT(varena, type, member) \ (ASSERT_FLEX_ABI(type, member), varena_init( \ varena, alignof(type), offsetof(type, member), sizeof_member(type, member[0]))) /** * Free an arena-allocated flexible struct. * * @varena * The that allocated the object. * @ptr * The object to free. * @count * The length of the flexible array. */ void varena_free(struct varena *varena, void *ptr, size_t count); /** * Arena-allocate a flexible struct. * * @varena * The varena to allocate from. * @count * The length of the flexible array. * @return * The allocated struct, or NULL on failure. */ _malloc(varena_free, 2) void *varena_alloc(struct varena *varena, size_t count); /** * Resize a flexible struct. * * @varena * The varena to allocate from. * @ptr * The object to resize. * @old_count * The old array length. * @new_count * The new array length. * @return * The resized struct, or NULL on failure. */ _nodiscard void *varena_realloc(struct varena *varena, void *ptr, size_t old_count, size_t new_count); /** * Grow a flexible struct by an arbitrary amount. * * @varena * The varena to allocate from. * @ptr * The object to resize. * @count * Pointer to the flexible array length. * @return * The resized struct, or NULL on failure. */ _nodiscard void *varena_grow(struct varena *varena, void *ptr, size_t *count); /** * Free all allocations from a varena. */ void varena_clear(struct varena *varena); /** * Destroy a varena, freeing all allocations. */ void varena_destroy(struct varena *varena); #endif // BFS_ALLOC_H bfs-4.0.6/src/atomic.h000066400000000000000000000055731475764024500145370ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD /** * Shorthand for standard C atomic operations. */ #ifndef BFS_ATOMIC_H #define BFS_ATOMIC_H #include "bfs.h" #include /** * Prettier spelling of _Atomic. */ #define atomic _Atomic /** * Shorthand for atomic_load_explicit(). * * @obj * A pointer to the atomic object. * @order * The memory ordering to use, without the memory_order_ prefix. * @return * The loaded value. */ #define load(obj, order) \ atomic_load_explicit(obj, memory_order_##order) /** * Shorthand for atomic_store_explicit(). */ #define store(obj, value, order) \ atomic_store_explicit(obj, value, memory_order_##order) /** * Shorthand for atomic_exchange_explicit(). */ #define exchange(obj, value, order) \ atomic_exchange_explicit(obj, value, memory_order_##order) /** * Shorthand for atomic_compare_exchange_weak_explicit(). */ #define compare_exchange_weak(obj, expected, desired, succ, fail) \ atomic_compare_exchange_weak_explicit(obj, expected, desired, memory_order_##succ, memory_order_##fail) /** * Shorthand for atomic_compare_exchange_strong_explicit(). */ #define compare_exchange_strong(obj, expected, desired, succ, fail) \ atomic_compare_exchange_strong_explicit(obj, expected, desired, memory_order_##succ, memory_order_##fail) /** * Shorthand for atomic_fetch_add_explicit(). */ #define fetch_add(obj, arg, order) \ atomic_fetch_add_explicit(obj, arg, memory_order_##order) /** * Shorthand for atomic_fetch_sub_explicit(). */ #define fetch_sub(obj, arg, order) \ atomic_fetch_sub_explicit(obj, arg, memory_order_##order) /** * Shorthand for atomic_fetch_or_explicit(). */ #define fetch_or(obj, arg, order) \ atomic_fetch_or_explicit(obj, arg, memory_order_##order) /** * Shorthand for atomic_fetch_xor_explicit(). */ #define fetch_xor(obj, arg, order) \ atomic_fetch_xor_explicit(obj, arg, memory_order_##order) /** * Shorthand for atomic_fetch_and_explicit(). */ #define fetch_and(obj, arg, order) \ atomic_fetch_and_explicit(obj, arg, memory_order_##order) /** * Shorthand for atomic_thread_fence(). */ #if __SANITIZE_THREAD__ // TSan doesn't support fences: https://github.com/google/sanitizers/issues/1415 # define thread_fence(obj, order) \ fetch_add(obj, 0, order) #else # define thread_fence(obj, order) \ atomic_thread_fence(memory_order_##order) #endif /** * Shorthand for atomic_signal_fence(). */ #define signal_fence(order) \ atomic_signal_fence(memory_order_##order) /** * A hint to the CPU to relax while it spins. */ #if __has_builtin(__builtin_ia32_pause) # define spin_loop() __builtin_ia32_pause() #elif __has_builtin(__builtin_arm_yield) # define spin_loop() __builtin_arm_yield() #elif BFS_HAS_BUILTIN_RISCV_PAUSE # define spin_loop() __builtin_riscv_pause() #else # define spin_loop() ((void)0) #endif #endif // BFS_ATOMIC_H bfs-4.0.6/src/bar.c000066400000000000000000000120011475764024500140020ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD #include "bar.h" #include "alloc.h" #include "atomic.h" #include "bfs.h" #include "bfstd.h" #include "bit.h" #include "dstring.h" #include "sighook.h" #include #include #include #include #include #include #include #include #include #include struct bfs_bar { int fd; atomic unsigned int width; atomic unsigned int height; struct sighook *exit_hook; struct sighook *winch_hook; }; /** Get the terminal size, if possible. */ static int bfs_bar_getsize(struct bfs_bar *bar) { #if BFS_HAS_TCGETWINSIZE || defined(TIOCGWINSZ) struct winsize ws; # if BFS_HAS_TCGETWINSIZE int ret = tcgetwinsize(bar->fd, &ws); # else int ret = ioctl(bar->fd, TIOCGWINSZ, &ws); # endif if (ret != 0) { return ret; } store(&bar->width, ws.ws_col, relaxed); store(&bar->height, ws.ws_row, relaxed); return 0; #else errno = ENOTSUP; return -1; #endif } /** Write a string to the status bar (async-signal-safe). */ static int bfs_bar_write(struct bfs_bar *bar, const char *str, size_t len) { return xwrite(bar->fd, str, len) == len ? 0 : -1; } /** Write a string to the status bar (async-signal-safe). */ static int bfs_bar_puts(struct bfs_bar *bar, const char *str) { return bfs_bar_write(bar, str, strlen(str)); } /** Number of decimal digits needed for terminal sizes. */ #define ITOA_DIGITS ((USHRT_WIDTH + 2) / 3) /** Async Signal Safe itoa(). */ static char *ass_itoa(char *str, unsigned int n) { char *end = str + ITOA_DIGITS; *end = '\0'; char *c = end; do { *--c = '0' + (n % 10); n /= 10; } while (n); size_t len = end - c; memmove(str, c, len + 1); return str + len; } /** Reset the scrollable region and hide the bar. */ static int bfs_bar_reset(struct bfs_bar *bar) { return bfs_bar_puts(bar, "\0337" // DECSC: Save cursor "\033[r" // DECSTBM: Reset scrollable region "\0338" // DECRC: Restore cursor "\033[J" // ED: Erase display from cursor to end ); } /** Hide the bar if the terminal is shorter than this. */ #define BFS_BAR_MIN_HEIGHT 3 /** Update the size of the scrollable region. */ static int bfs_bar_resize(struct bfs_bar *bar) { unsigned int height = load(&bar->height, relaxed); if (height < BFS_BAR_MIN_HEIGHT) { return bfs_bar_reset(bar); } static const char PREFIX[] = "\033D" // IND: Line feed, possibly scrolling "\033[1A" // CUU: Move cursor up 1 row "\0337" // DECSC: Save cursor "\033[;"; // DECSTBM: Set scrollable region static const char SUFFIX[] = "r" // (end of DECSTBM) "\0338" // DECRC: Restore the cursor "\033[J"; // ED: Erase display from cursor to end char esc_seq[sizeof(PREFIX) + ITOA_DIGITS + sizeof(SUFFIX)]; // DECSTBM takes the height as the second argument char *cur = stpcpy(esc_seq, PREFIX); cur = ass_itoa(cur, height - 1); cur = stpcpy(cur, SUFFIX); return bfs_bar_write(bar, esc_seq, cur - esc_seq); } #ifdef SIGWINCH /** SIGWINCH handler. */ static void bfs_bar_sigwinch(int sig, siginfo_t *info, void *arg) { struct bfs_bar *bar = arg; bfs_bar_getsize(bar); bfs_bar_resize(bar); } #endif /** Signal handler for process-terminating signals. */ static void bfs_bar_sigexit(int sig, siginfo_t *info, void *arg) { struct bfs_bar *bar = arg; bfs_bar_reset(bar); } /** printf() to the status bar with a single write(). */ _printf(2, 3) static int bfs_bar_printf(struct bfs_bar *bar, const char *format, ...) { va_list args; va_start(args, format); dchar *str = dstrvprintf(format, args); va_end(args); if (!str) { return -1; } int ret = bfs_bar_write(bar, str, dstrlen(str)); dstrfree(str); return ret; } struct bfs_bar *bfs_bar_show(void) { struct bfs_bar *bar = ALLOC(struct bfs_bar); if (!bar) { return NULL; } bar->fd = open_cterm(O_RDWR | O_CLOEXEC); if (bar->fd < 0) { goto fail; } if (bfs_bar_getsize(bar) != 0) { goto fail_close; } bar->exit_hook = atsigexit(bfs_bar_sigexit, bar); if (!bar->exit_hook) { goto fail_close; } #ifdef SIGWINCH bar->winch_hook = sighook(SIGWINCH, bfs_bar_sigwinch, bar, 0); if (!bar->winch_hook) { goto fail_hook; } #endif bfs_bar_resize(bar); return bar; fail_hook: sigunhook(bar->exit_hook); fail_close: close_quietly(bar->fd); fail: free(bar); return NULL; } unsigned int bfs_bar_width(const struct bfs_bar *bar) { return load(&bar->width, relaxed); } int bfs_bar_update(struct bfs_bar *bar, const char *str) { unsigned int height = load(&bar->height, relaxed); if (height < BFS_BAR_MIN_HEIGHT) { return 0; } return bfs_bar_printf(bar, "\0337" // DECSC: Save cursor "\033[%u;0f" // HVP: Move cursor to row, column "\033[K" // EL: Erase line "\033[7m" // SGR reverse video "%s" "\033[27m" // SGR reverse video off "\0338", // DECRC: Restore cursor height, str ); } void bfs_bar_hide(struct bfs_bar *bar) { if (!bar) { return; } sigunhook(bar->winch_hook); sigunhook(bar->exit_hook); bfs_bar_reset(bar); xclose(bar->fd); free(bar); } bfs-4.0.6/src/bar.h000066400000000000000000000015221475764024500140150ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD /** * A terminal status bar. */ #ifndef BFS_BAR_H #define BFS_BAR_H /** A terminal status bar. */ struct bfs_bar; /** * Create a terminal status bar. Only one status bar is supported at a time. * * @return * A pointer to the new status bar, or NULL on failure. */ struct bfs_bar *bfs_bar_show(void); /** * Get the width of the status bar. */ unsigned int bfs_bar_width(const struct bfs_bar *bar); /** * Update the status bar message. * * @bar * The status bar to update. * @str * The string to display. * @return * 0 on success, -1 on failure. */ int bfs_bar_update(struct bfs_bar *bar, const char *str); /** * Hide the status bar. */ void bfs_bar_hide(struct bfs_bar *status); #endif // BFS_BAR_H bfs-4.0.6/src/bfs.h000066400000000000000000000117751475764024500140360ustar00rootroot00000000000000// Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD /** * Configuration and fundamental utilities. */ #ifndef BFS_H #define BFS_H // Standard versions /** Possible __STDC_VERSION__ values. */ #define C95 199409L #define C99 199901L #define C11 201112L #define C17 201710L #define C23 202311L /** Possible _POSIX_C_SOURCE and _POSIX_