pax_global_header00006660000000000000000000000064136056415400014516gustar00rootroot0000000000000052 comment=d6a8ad44c3716281f5ba5a395c91dfee0bcda09a bfs-1.5.2/000077500000000000000000000000001360564154000122755ustar00rootroot00000000000000bfs-1.5.2/COPYING000066400000000000000000000012421360564154000133270ustar00rootroot00000000000000Copyright (C) 2015-2020 Tavian Barnes 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-1.5.2/Makefile000066400000000000000000000073231360564154000137420ustar00rootroot00000000000000############################################################################ # bfs # # Copyright (C) 2015-2020 Tavian Barnes # # # # 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. # ############################################################################ ifeq ($(wildcard .git),) VERSION := 1.5.2 else VERSION := $(shell git describe --always) endif ifndef OS OS := $(shell uname) endif CC ?= gcc INSTALL ?= install MKDIR ?= mkdir -p RM ?= rm -f WFLAGS ?= -Wall -Wmissing-declarations -Wstrict-prototypes CFLAGS ?= -g $(WFLAGS) LDFLAGS ?= DEPFLAGS ?= -MD -MP -MF $(@:.o=.d) DESTDIR ?= PREFIX ?= /usr MANDIR ?= $(PREFIX)/share/man LOCAL_CPPFLAGS := \ -D__EXTENSIONS__ \ -D_ATFILE_SOURCE \ -D_BSD_SOURCE \ -D_DARWIN_C_SOURCE \ -D_DEFAULT_SOURCE \ -D_FILE_OFFSET_BITS=64 \ -D_GNU_SOURCE \ -DBFS_VERSION=\"$(VERSION)\" LOCAL_CFLAGS := -std=c99 LOCAL_LDFLAGS := LOCAL_LDLIBS := ASAN_CFLAGS := -fsanitize=address MSAN_CFLAGS := -fsanitize=memory UBSAN_CFLAGS := -fsanitize=undefined ifeq ($(OS),Linux) LOCAL_LDFLAGS += -Wl,--as-needed LOCAL_LDLIBS += -lacl -lcap -lattr -lrt # These libraries are not built with msan, so disable them MSAN_CFLAGS += -DBFS_HAS_SYS_ACL=0 -DBFS_HAS_SYS_CAPABILITY=0 -DBFS_HAS_SYS_XATTR=0 endif ALL_CPPFLAGS = $(LOCAL_CPPFLAGS) $(CPPFLAGS) ALL_CFLAGS = $(ALL_CPPFLAGS) $(LOCAL_CFLAGS) $(CFLAGS) $(DEPFLAGS) ALL_LDFLAGS = $(ALL_CFLAGS) $(LOCAL_LDFLAGS) $(LDFLAGS) ALL_LDLIBS = $(LOCAL_LDLIBS) $(LDLIBS) default: bfs all: bfs tests/mksock bfs: \ bftw.o \ color.o \ darray.o \ diag.o \ dstring.o \ eval.o \ exec.o \ fsade.o \ main.o \ mtab.o \ opt.o \ parse.o \ printf.o \ spawn.o \ stat.o \ trie.o \ typo.o \ util.o $(CC) $(ALL_LDFLAGS) $^ $(ALL_LDLIBS) -o $@ release: CFLAGS := -g $(WFLAGS) -O3 -flto -DNDEBUG release: bfs tests/mksock: tests/mksock.o $(CC) $(ALL_LDFLAGS) $^ -o $@ %.o: %.c $(CC) $(ALL_CFLAGS) -c $< -o $@ check: check-bfs check-dfs check-ids check-%: all ./tests.sh --bfs="$(CURDIR)/bfs -S $*" distcheck: +$(MAKE) -Bs check CFLAGS="$(CFLAGS) $(ASAN_CFLAGS) $(UBSAN_CFLAGS)" ifneq ($(OS),Darwin) +$(MAKE) -Bs check CC=clang CFLAGS="$(CFLAGS) $(MSAN_CFLAGS)" +$(MAKE) -Bs check CFLAGS="$(CFLAGS) -m32" endif +$(MAKE) -Bs release check +$(MAKE) -Bs check ifeq ($(OS),Linux) ./tests.sh --sudo --bfs="$(CURDIR)/bfs" ./tests.sh --sudo --bfs="$(CURDIR)/bfs -S dfs" ./tests.sh --sudo --bfs="$(CURDIR)/bfs -S ids" endif clean: $(RM) bfs *.[od] tests/mksock tests/*.[od] install: $(MKDIR) $(DESTDIR)$(PREFIX)/bin $(INSTALL) -m755 bfs $(DESTDIR)$(PREFIX)/bin/bfs $(MKDIR) $(DESTDIR)$(MANDIR)/man1 $(INSTALL) -m644 bfs.1 $(DESTDIR)$(MANDIR)/man1/bfs.1 uninstall: $(RM) $(DESTDIR)$(PREFIX)/bin/bfs $(RM) $(DESTDIR)$(MANDIR)/man1/bfs.1 .PHONY: all release check distcheck clean install uninstall -include $(wildcard *.d) bfs-1.5.2/README.md000066400000000000000000000103571360564154000135620ustar00rootroot00000000000000`bfs` ===== Screenshot [![License](http://img.shields.io/badge/license-0BSD-blue.svg)](https://github.com/tavianator/bfs/blob/master/COPYING) [![Version](https://img.shields.io/github/v/tag/tavianator/bfs?label=version)](https://github.com/tavianator/bfs/releases) [![Build Status](https://api.travis-ci.org/tavianator/bfs.svg?branch=master)](https://travis-ci.org/tavianator/bfs) Breadth-first search for your files. `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 intended to be compatible with many versions of `find`, including - [POSIX `find`](http://pubs.opengroup.org/onlinepubs/9699919799/utilities/find.html) - [GNU `find`](https://www.gnu.org/software/findutils/) - {[Free](https://www.freebsd.org/cgi/man.cgi?find(1)),[Open](https://man.openbsd.org/find.1),[Net](http://netbsd.gw.com/cgi-bin/man-cgi?find+1+NetBSD-current)}BSD `find` - [macOS `find`](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. Breadth vs. depth ----------------- The advantage of breadth-first over depth first search is that it usually 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.
$ 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
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.
$ bfs haystack
haystack
haystack/deep
haystack/shallow
haystack/deep/1
haystack/shallow/needle
haystack/deep/1/2
haystack/deep/1/2/3
haystack/deep/1/2/3/4
...
Easy ---- `bfs` tries to be easier to use than `find`, while remaining compatible. For example, `bfs` is less picky about where you put its arguments:
$ find -L -name 'needle' haystack
find: paths must precede expression: haystack
$ bfs -L -name 'needle' haystack
haystack/needle

$ find haystack -L -name 'needle'
find: unknown predicate `-L'
$ bfs haystack -L -name 'needle'
haystack/needle

$ find -L haystack -name 'needle'
haystack/needle
$ bfs -L haystack -name 'needle'
haystack/needle
`bfs` also adds some extra options that make some common tasks easier. Compare `bfs -nohidden` to find -name '.?*' -prune -o -print Try it! ------- Packaging status To get `bfs`, download one of the [releases](https://github.com/tavianator/bfs/releases) or clone the [git repo](https://github.com/tavianator/bfs). Then run $ make This will build the `bfs` binary in the current directory. You can test it out: $ ./bfs -nohidden If you're interested in speed, you may want to build the release version instead: $ make clean $ make release Finally, if you want to install it globally, run $ sudo make install Alternatively, `bfs` may already be packaged for your distribution of choice. For example:
Alpine Linux
# apk add bfs

Debian/Ubuntu
# apt install bfs

NixOS
# nix-env -i bfs

Void Linux
# xbps-install -S bfs

MacPorts
# port install bfs

Homebrew
$ brew install tavianator/tap/bfs
bfs-1.5.2/RELEASES.md000066400000000000000000000260221360564154000140240ustar00rootroot000000000000001.* === 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 (https://github.com/tavianator/bfs/issues/40) - 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 (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 https://github.com/tavianator/bfs/issues/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 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 https://github.com/tavianator/bfs/issues/7 - Fix `-daystart`'s rounding of midnight - Implement (most of) `-newerXY` - Implement `-used` - Implement `-size` 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-1.5.2/bfs.1000066400000000000000000000263751360564154000131460ustar00rootroot00000000000000.TH BFS 1 .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 \\\( \-name '*.txt' \-or \-lname '*.txt' \\\\) \-and \-print .RE .fi .PP will print the 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 \\\( \-name '*.txt' \-or \-lname '*.txt' \\\\) \-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 . .IR \-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 \fB\-regextype posix-extended\fR). .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 \-x Don't descend into other mount points (same as \fB\-xdev\fR). .TP \fB\-f \fIPATH\fR Treat .I PATH as a path to search (useful if it begins with a dash). .PP .TP \fB\-D \fIFLAG\fR Turn on a debugging flag (see .B \-D .IR help ). .PP \fB\-O\fIN\fR .RS Enable optimization level .I N (default: 3) .TP \fB\-O\fI0\fR Disable all optimizations. .TP \fB\-O\fI1\fR Basic logical simplifications. .TP \fB\-O\fI2\fR All .BI \-O 1 optimizations, plus dead code elimination and data flow analysis. .TP \fB\-O\fI3\fR 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 .TP \fB\-S \fIbfs\fR|\fIdfs\fR|\fIids\fR Use .IR b readth- f irst/ d epth- f irst/ i terative .IR d eepening .IR s earch (default: .B -S .IR bfs ). .RE .SH OPERATORS .TP \fB( \fIexpression \fB)\fR Parentheses are used for grouping expressions together. You'll probably have to write .B \\\\( .I expression .B \\\\) to avoid the parentheses being interpreted by the shell. .PP \fB! \fIexpression\fR .br \fB\-not \fIexpression\fR .RS The "not" operator: returns the negation of the truth value of the .IR expression . You may have to write \fB\\! \fIexpression\fR to avoid \fB!\fR being interpreted by the shell. .RE .PP \fIexpression\fR \fIexpression\fR .br \fIexpression \fB\-a \fIexpression\fR .br \fIexpression \fB\-and \fIexpression\fR .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 \fIexpression \fB\-o \fIexpression\fR .br \fIexpression \fB\-or \fIexpression\fR .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 \fIexpression \fB, \fIexpression\fR The "comma" operator: evaluates the left-hand .I expression but discards the result, returning the right-hand .IR expression . .SH OPTIONS .PP .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 ). .PP \fB\-ignore_readdir_race\fR .br \fB\-noignore_readdir_race\fR .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 \fB\-maxdepth \fIN\fR .br \fB\-mindepth \fIN\fR .RS Ignore files deeper/shallower than .IR N . .RE .TP .B \-mount Don't descend into other mount points (same as .B \-xdev for now, but will skip mount points entirely in the future). .TP .B \-noleaf Ignored; for compatibility with GNU find. .TP \fB\-regextype \fITYPE\fR Use .IR TYPE -flavored regexes (default: .IR posix-basic ; see .B \-regextype .IR help ). .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. .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 \fB\-anewer \fIFILE\fR .br \fB\-Bnewer \fIFILE\fR .br \fB\-cnewer \fIFILE\fR .br \fB\-mnewer \fIFILE\fR .RS Find files .BR a ccessed/ B irthed/ c hanged/ m odified more recently than .I FILE was modified. .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 \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 .B \-fstype TYPE Find files on file systems with the given .BR 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 \fB\-group \fINAME\fR .br \fB\-user \fINAME\fR .RS Find files owned by the group/user .IR NAME . .RE .TP .B \-hidden Find hidden files (those beginning with .IR . ). .PP \fB\-ilname \fIGLOB\fR .br \fB\-iname \fIGLOB\fR .br \fB\-ipath \fIGLOB\fR .br \fB\-iregex \fIREGEX\fR .br \fB\-iwholename \fIGLOB\fR .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 \fB\-lname \fIGLOB\fR Find symbolic links whose target matches the .IR GLOB . .TP \fB\-name \fIGLOB\fR Find files whose name matches the .IR GLOB . .TP \fB\-newer \fIFILE\fR Find files newer than .IR FILE . .TP \fB\-newer\fIXY \fIREFERENCE\fR 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). .PP .B \-nogroup .br .B \-nouser .RS Find files owned by nonexistent groups/users. .RE .PP \fB\-path \fIGLOB\fR .br \fB\-wholename \fIGLOB\fR .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 \fB\-regex \fIREGEX\fR Find files whose entire path matches the regular expression .IR REGEX . .TP \fB\-samefile \fIFILE\fR Find hard links to .IR FILE . .TP \fB\-size\fR [\fI\-+\fR]\fIN\fR[\fIcwbkMGTP\fR] Find files with the given size, in 1-byte .IR c haracters, 2-byte .IR w ords, 512-byte .IR b locks, (default) or .IR k iB/ M iB/ G iB/ T iB/ P iB. .TP .B \-sparse Find files that occupy fewer disk blocks than expected. .TP \fB\-type\fR [\fIbcdlpfswD\fR] Find files of the given type. Possible types are .IR b lock device, .IR c haracter device, .IR d irectory, symbolic .IR l ink, .IR p ipe, regular .IR f ile, .IR s ocket, .IR w hiteout, and .IR D oor. .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 \fB\-xtype\fR [\fIbcdlpfswD\fR] Find files of the given type, following links when .B \-type would not, and vice versa. .SH ACTIONS .PP .B \-delete .br .B \-rm .RS Delete any found files (implies \fB-depth\fR). .RE .TP \fB\-exec \fIcommand ... {} ;\fR Execute a command. .TP \fB\-exec \fIcommand ... {} +\fR Execute a command with multiple files at once. .TP \fB\-ok \fIcommand ... {} ;\fR Prompt the user whether to execute a command. .PP \fB\-execdir \fIcommand ... {} ;\fR .br \fB\-execdir \fIcommand ... {} +\fR .br \fB\-okdir \fIcommand ... {} ;\fR .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 (0 if unspecified). .PP \fB\-fls \fIFILE\fR .br \fB\-fprint \fIFILE\fR .br \fB\-fprint0 \fIFILE\fR .br \fB\-fprintf \fIFORMAT FILE\fR .RS Like .BR \-ls / \-print / \-print0 / \-printf , but write to .I FILE instead of standard output. .RE .TP .B \-ls List files like .B ls .IR \-dils . .TP .B \-nohidden Filter out hidden files and directories. .TP .B \-print Print the path to the found file. .TP .B \-print0 Like .BR \-print , but use the null character ('\\0') as a separator rather than newlines. Useful in conjunction with .B xargs .IR -0 . .TP \fB\-printf \fIFORMAT\fR 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. .TP .B \-quit Quit immediately. .TP .B \-version Print version information. .TP .B \-help Print usage information. .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 \fBbfs \-name access_log -L \fI/var\fR 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 \fBbfs \fI~ \fB\-not \-user $USER\fR Prints all files in your home directory not owned by you. .TP .B bfs \-xtype l Finds broken symbolic links. .TP .B bfs \-name .git \-prune \-false \-o \-name config 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://github.com/tavianator/bfs .SH SEE ALSO .BR find (1), .BR locate (1), .BR xargs (1) bfs-1.5.2/bfs.h000066400000000000000000000026051360564154000132230ustar00rootroot00000000000000/**************************************************************************** * bfs * * Copyright (C) 2015-2020 Tavian Barnes * * * * 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. * ****************************************************************************/ /** * Constants about the bfs program itself. */ #ifndef BFS_H #define BFS_H #ifndef BFS_VERSION # define BFS_VERSION "1.5.2" #endif #ifndef BFS_HOMEPAGE # define BFS_HOMEPAGE "https://github.com/tavianator/bfs" #endif #endif // BFS_H bfs-1.5.2/bftw.c000066400000000000000000001060601360564154000134060ustar00rootroot00000000000000/**************************************************************************** * bfs * * Copyright (C) 2015-2019 Tavian Barnes * * * * 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. * ****************************************************************************/ /** * The bftw() implementation consists of the following components: * * - struct bftw_file: A file that has been encountered during the traversal. * They have reference-counted links to their parents in the directory tree. * * - struct bftw_cache: Holds bftw_file's with open file descriptors, used for * openat() to minimize the amount of path re-traversals that need to happen. * Currently implemented as a priority queue based on depth and reference * count. * * - struct bftw_queue: The queue of bftw_file's left to explore. Implemented * as a simple circular buffer. * * - struct bftw_reader: A reader object that simplifies reading directories and * reporting errors. * * - struct bftw_state: Represents the current state of the traversal, allowing * various helper functions to take fewer parameters. */ #include "bftw.h" #include "dstring.h" #include "stat.h" #include "trie.h" #include "util.h" #include #include #include #include #include #include #include #include #include #include /** * A file. */ struct bftw_file { /** The parent directory, if any. */ struct bftw_file *parent; /** The root under which this file was found. */ struct bftw_file *root; /** The next file in the queue, if any. */ struct bftw_file *next; /** This file's depth in the walk. */ size_t depth; /** Reference count. */ size_t refcount; /** Index in the bftw_cache priority queue. */ size_t heap_index; /** An open descriptor to this file, or -1. */ int fd; /** This file's type, if known. */ enum bftw_typeflag typeflag; /** The device number, for cycle detection. */ dev_t dev; /** The inode number, for cycle detection. */ ino_t ino; /** The offset of this file in the full path. */ size_t nameoff; /** The length of the file's name. */ size_t namelen; /** The file's name. */ char name[]; }; /** * A cache of open directories. */ struct bftw_cache { /** A min-heap of open directories. */ struct bftw_file **heap; /** Current heap size. */ size_t size; /** Maximum heap size. */ size_t capacity; }; /** Initialize a cache. */ static int bftw_cache_init(struct bftw_cache *cache, size_t capacity) { cache->heap = malloc(capacity*sizeof(*cache->heap)); if (!cache->heap) { return -1; } cache->size = 0; cache->capacity = capacity; return 0; } /** Destroy a cache. */ static void bftw_cache_destroy(struct bftw_cache *cache) { assert(cache->size == 0); free(cache->heap); } /** Check if two heap entries are in heap order. */ static bool bftw_heap_check(const struct bftw_file *parent, const struct bftw_file *child) { if (parent->depth > child->depth) { return true; } else if (parent->depth < child->depth) { return false; } else { return parent->refcount <= child->refcount; } } /** Move a bftw_file to a particular place in the heap. */ static void bftw_heap_move(struct bftw_cache *cache, struct bftw_file *file, size_t i) { cache->heap[i] = file; file->heap_index = i; } /** Bubble an entry up the heap. */ static void bftw_heap_bubble_up(struct bftw_cache *cache, struct bftw_file *file) { size_t i = file->heap_index; while (i > 0) { size_t pi = (i - 1)/2; struct bftw_file *parent = cache->heap[pi]; if (bftw_heap_check(parent, file)) { break; } bftw_heap_move(cache, parent, i); i = pi; } bftw_heap_move(cache, file, i); } /** Bubble an entry down the heap. */ static void bftw_heap_bubble_down(struct bftw_cache *cache, struct bftw_file *file) { size_t i = file->heap_index; while (true) { size_t ci = 2*i + 1; if (ci >= cache->size) { break; } struct bftw_file *child = cache->heap[ci]; size_t ri = ci + 1; if (ri < cache->size) { struct bftw_file *right = cache->heap[ri]; if (!bftw_heap_check(child, right)) { ci = ri; child = right; } } if (bftw_heap_check(file, child)) { break; } bftw_heap_move(cache, child, i); i = ci; } bftw_heap_move(cache, file, i); } /** Bubble an entry up or down the heap. */ static void bftw_heap_bubble(struct bftw_cache *cache, struct bftw_file *file) { size_t i = file->heap_index; if (i > 0) { size_t pi = (i - 1)/2; struct bftw_file *parent = cache->heap[pi]; if (!bftw_heap_check(parent, file)) { bftw_heap_bubble_up(cache, file); return; } } bftw_heap_bubble_down(cache, file); } /** Increment a bftw_file's reference count. */ static size_t bftw_file_incref(struct bftw_cache *cache, struct bftw_file *file) { size_t ret = ++file->refcount; if (file->fd >= 0) { bftw_heap_bubble_down(cache, file); } return ret; } /** Decrement a bftw_file's reference count. */ static size_t bftw_file_decref(struct bftw_cache *cache, struct bftw_file *file) { size_t ret = --file->refcount; if (file->fd >= 0) { bftw_heap_bubble_up(cache, file); } return ret; } /** Add a bftw_file to the cache. */ static void bftw_cache_add(struct bftw_cache *cache, struct bftw_file *file) { assert(cache->size < cache->capacity); assert(file->fd >= 0); size_t size = cache->size++; file->heap_index = size; bftw_heap_bubble_up(cache, file); } /** Remove a bftw_file from the cache. */ static void bftw_cache_remove(struct bftw_cache *cache, struct bftw_file *file) { assert(cache->size > 0); assert(file->fd >= 0); size_t size = --cache->size; size_t i = file->heap_index; if (i != size) { struct bftw_file *end = cache->heap[size]; end->heap_index = i; bftw_heap_bubble(cache, end); } } /** Close a bftw_file. */ static void bftw_file_close(struct bftw_cache *cache, struct bftw_file *file) { assert(file->fd >= 0); bftw_cache_remove(cache, file); close(file->fd); file->fd = -1; } /** Pop a directory from the cache. */ static void bftw_cache_pop(struct bftw_cache *cache) { assert(cache->size > 0); bftw_file_close(cache, cache->heap[0]); } /** * Shrink the cache, to recover from EMFILE. * * @param cache * The cache in question. * @param saved * A bftw_file that must be preserved. * @return * 0 if successfully shrunk, otherwise -1. */ static int bftw_cache_shrink(struct bftw_cache *cache, const struct bftw_file *saved) { int ret = -1; struct bftw_file *file = NULL; if (cache->size >= 1) { file = cache->heap[0]; if (file == saved && cache->size >= 2) { file = cache->heap[1]; } } if (file && file != saved) { bftw_file_close(cache, file); ret = 0; } cache->capacity = cache->size; return ret; } /** Compute the name offset of a child path. */ static size_t bftw_child_nameoff(const struct bftw_file *parent) { size_t ret = parent->nameoff + parent->namelen; if (parent->name[parent->namelen - 1] != '/') { ++ret; } return ret; } /** Create a new bftw_file. */ static struct bftw_file *bftw_file_new(struct bftw_cache *cache, struct bftw_file *parent, const char *name) { size_t namelen = strlen(name); size_t size = sizeof(struct bftw_file) + namelen + 1; struct bftw_file *file = malloc(size); if (!file) { return NULL; } file->parent = parent; if (parent) { file->root = parent->root; file->depth = parent->depth + 1; file->nameoff = bftw_child_nameoff(parent); bftw_file_incref(cache, parent); } else { file->root = file; file->depth = 0; file->nameoff = 0; } file->next = NULL; file->refcount = 1; file->fd = -1; file->typeflag = BFTW_UNKNOWN; file->dev = -1; file->ino = -1; file->namelen = namelen; memcpy(file->name, name, namelen + 1); return file; } /** * Get the appropriate (fd, path) pair for the *at() family of functions. * * @param file * The file being accessed. * @param[out] at_fd * Will hold the appropriate file descriptor to use. * @param[in,out] at_path * Will hold the appropriate path to use. * @return The closest open ancestor file. */ static struct bftw_file *bftw_file_base(struct bftw_file *file, int *at_fd, const char **at_path) { struct bftw_file *base = file; do { base = base->parent; } while (base && base->fd < 0); if (base) { *at_fd = base->fd; *at_path += bftw_child_nameoff(base); } return base; } /** * Open a bftw_file relative to another one. * * @param cache * The cache to hold the file. * @param file * The file to open. * @param base * The base directory for the relative path (may be NULL). * @param at_fd * The base file descriptor, AT_FDCWD if base == NULL. * @param at_path * The relative path to the file. * @return * The opened file descriptor, or negative on error. */ static int bftw_file_openat(struct bftw_cache *cache, struct bftw_file *file, const struct bftw_file *base, int at_fd, const char *at_path) { assert(file->fd < 0); int flags = O_RDONLY | O_CLOEXEC | O_DIRECTORY; int fd = openat(at_fd, at_path, flags); if (fd < 0 && errno == EMFILE) { if (bftw_cache_shrink(cache, base) == 0) { fd = openat(base->fd, at_path, flags); } } if (fd >= 0) { if (cache->size == cache->capacity) { bftw_cache_pop(cache); } file->fd = fd; bftw_cache_add(cache, file); } return fd; } /** * Open a bftw_file. * * @param cache * The cache to hold the file. * @param file * The file to open. * @param path * The full path to the file. * @return * The opened file descriptor, or negative on error. */ static int bftw_file_open(struct bftw_cache *cache, struct bftw_file *file, const char *path) { int at_fd = AT_FDCWD; const char *at_path = path; struct bftw_file *base = bftw_file_base(file, &at_fd, &at_path); int fd = bftw_file_openat(cache, file, base, at_fd, at_path); if (fd >= 0 || errno != ENAMETOOLONG) { return fd; } // Handle ENAMETOOLONG by manually traversing the path component-by-component // -1 to include the root, which has depth == 0 size_t offset = base ? base->depth : -1; size_t levels = file->depth - offset; if (levels < 2) { return fd; } struct bftw_file **parents = malloc(levels * sizeof(*parents)); if (!parents) { return fd; } struct bftw_file *parent = file; for (size_t i = levels; i-- > 0;) { parents[i] = parent; parent = parent->parent; } for (size_t i = 0; i < levels; ++i) { fd = bftw_file_openat(cache, parents[i], base, at_fd, parents[i]->name); if (fd < 0) { break; } base = parents[i]; at_fd = fd; } free(parents); return fd; } /** * Open a DIR* for a bftw_file. * * @param cache * The cache to hold the file. * @param file * The directory to open. * @param path * The full path to the directory. * @return * The opened DIR *, or NULL on error. */ static DIR *bftw_file_opendir(struct bftw_cache *cache, struct bftw_file *file, const char *path) { int fd = bftw_file_open(cache, file, path); if (fd < 0) { return NULL; } // Now we dup() the fd and pass it to fdopendir(). This way we can // close the DIR* as soon as we're done with it, reducing the memory // footprint significantly, while keeping the fd around for future // openat() calls. int dfd = dup_cloexec(fd); if (dfd < 0 && errno == EMFILE) { if (bftw_cache_shrink(cache, file) == 0) { dfd = dup_cloexec(fd); } } if (dfd < 0) { return NULL; } DIR *ret = fdopendir(dfd); if (!ret) { int error = errno; close(dfd); errno = error; } return ret; } /** Free a bftw_file. */ static void bftw_file_free(struct bftw_cache *cache, struct bftw_file *file) { assert(file->refcount == 0); if (file->fd >= 0) { bftw_file_close(cache, file); } free(file); } /** * A queue of bftw_file's to examine. */ struct bftw_queue { /** The head of the queue. */ struct bftw_file *head; /** The tail of the queue. */ struct bftw_file *tail; }; /** Initialize a bftw_queue. */ static void bftw_queue_init(struct bftw_queue *queue) { queue->head = NULL; queue->tail = NULL; } /** Add a file to the tail of the bftw_queue. */ static void bftw_queue_push(struct bftw_queue *queue, struct bftw_file *file) { assert(file->next == NULL); if (!queue->head) { queue->head = file; } if (queue->tail) { queue->tail->next = file; } queue->tail = file; } /** Prepend a queue to the head of another one. */ static void bftw_queue_prepend(struct bftw_queue *head, struct bftw_queue *tail) { if (head->tail) { head->tail->next = tail->head; } if (head->head) { tail->head = head->head; } if (!tail->tail) { tail->tail = head->tail; } head->head = NULL; head->tail = NULL; } /** Pop the next file from the head of the queue. */ static struct bftw_file *bftw_queue_pop(struct bftw_queue *queue) { struct bftw_file *file = queue->head; queue->head = file->next; if (queue->tail == file) { queue->tail = NULL; } file->next = NULL; return file; } /** * A directory reader. */ struct bftw_reader { /** The open handle to the directory. */ DIR *dir; /** The current directory entry. */ struct dirent *de; /** Any error code that has occurred. */ int error; }; /** Initialize a reader. */ static void bftw_reader_init(struct bftw_reader *reader) { reader->dir = NULL; reader->de = NULL; reader->error = 0; } /** Open a directory for reading. */ static int bftw_reader_open(struct bftw_reader *reader, struct bftw_cache *cache, struct bftw_file *file, const char *path) { assert(!reader->dir); assert(!reader->de); reader->error = 0; reader->dir = bftw_file_opendir(cache, file, path); if (!reader->dir) { reader->error = errno; return -1; } return 0; } /** Read a directory entry. */ static int bftw_reader_read(struct bftw_reader *reader) { if (!reader->dir) { return -1; } if (xreaddir(reader->dir, &reader->de) != 0) { reader->error = errno; return -1; } else if (reader->de) { return 1; } else { return 0; } } /** Close a directory. */ static int bftw_reader_close(struct bftw_reader *reader) { int ret = 0; if (reader->dir && closedir(reader->dir) != 0) { reader->error = errno; ret = -1; } reader->de = NULL; reader->dir = NULL; return ret; } /** * Holds the current state of the bftw() traversal. */ struct bftw_state { /** bftw() callback. */ bftw_callback *callback; /** bftw() callback data. */ void *ptr; /** bftw() flags. */ enum bftw_flags flags; /** Search strategy. */ enum bftw_strategy strategy; /** The mount table. */ const struct bfs_mtab *mtab; /** The appropriate errno value, if any. */ int error; /** The cache of open directories. */ struct bftw_cache cache; /** The queue of directories left to explore. */ struct bftw_queue queue; /** An intermediate queue used for depth-first searches. */ struct bftw_queue prequeue; /** The current path. */ char *path; /** The current file. */ struct bftw_file *file; /** The previous file. */ struct bftw_file *previous; /** The reader for the current directory. */ struct bftw_reader reader; /** Extra data about the current file. */ struct BFTW ftwbuf; }; /** * Initialize the bftw() state. */ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *args) { state->callback = args->callback; state->ptr = args->ptr; state->flags = args->flags; state->strategy = args->strategy; state->mtab = args->mtab; state->error = 0; if (args->nopenfd < 2) { errno = EMFILE; goto err; } // Reserve 1 fd for the open DIR * if (bftw_cache_init(&state->cache, args->nopenfd - 1) != 0) { goto err; } bftw_queue_init(&state->queue); bftw_queue_init(&state->prequeue); state->path = dstralloc(0); if (!state->path) { goto err_cache; } state->file = NULL; state->previous = NULL; bftw_reader_init(&state->reader); return 0; err_cache: bftw_cache_destroy(&state->cache); err: return -1; } enum bftw_typeflag bftw_mode_typeflag(mode_t mode) { switch (mode & S_IFMT) { #ifdef S_IFBLK case S_IFBLK: return BFTW_BLK; #endif #ifdef S_IFCHR case S_IFCHR: return BFTW_CHR; #endif #ifdef S_IFDIR case S_IFDIR: return BFTW_DIR; #endif #ifdef S_IFDOOR case S_IFDOOR: return BFTW_DOOR; #endif #ifdef S_IFIFO case S_IFIFO: return BFTW_FIFO; #endif #ifdef S_IFLNK case S_IFLNK: return BFTW_LNK; #endif #ifdef S_IFPORT case S_IFPORT: return BFTW_PORT; #endif #ifdef S_IFREG case S_IFREG: return BFTW_REG; #endif #ifdef S_IFSOCK case S_IFSOCK: return BFTW_SOCK; #endif #ifdef S_IFWHT case S_IFWHT: return BFTW_WHT; #endif default: return BFTW_UNKNOWN; } } static enum bftw_typeflag bftw_dirent_typeflag(const struct dirent *de) { #if defined(_DIRENT_HAVE_D_TYPE) || defined(DT_UNKNOWN) switch (de->d_type) { #ifdef DT_BLK case DT_BLK: return BFTW_BLK; #endif #ifdef DT_CHR case DT_CHR: return BFTW_CHR; #endif #ifdef DT_DIR case DT_DIR: return BFTW_DIR; #endif #ifdef DT_DOOR case DT_DOOR: return BFTW_DOOR; #endif #ifdef DT_FIFO case DT_FIFO: return BFTW_FIFO; #endif #ifdef DT_LNK case DT_LNK: return BFTW_LNK; #endif #ifdef DT_PORT case DT_PORT: return BFTW_PORT; #endif #ifdef DT_REG case DT_REG: return BFTW_REG; #endif #ifdef DT_SOCK case DT_SOCK: return BFTW_SOCK; #endif #ifdef DT_WHT case DT_WHT: return BFTW_WHT; #endif } #endif return BFTW_UNKNOWN; } /** Cached bfs_stat(). */ static const struct bfs_stat *bftw_stat_impl(struct BFTW *ftwbuf, struct bftw_stat *cache, enum bfs_stat_flag flags) { if (!cache->buf) { if (cache->error) { errno = cache->error; } else if (bfs_stat(ftwbuf->at_fd, ftwbuf->at_path, flags, &cache->storage) == 0) { cache->buf = &cache->storage; } else { cache->error = errno; } } return cache->buf; } const struct bfs_stat *bftw_stat(const struct BFTW *ftwbuf, enum bfs_stat_flag flags) { struct BFTW *mutbuf = (struct BFTW *)ftwbuf; const struct bfs_stat *ret; if (flags & BFS_STAT_NOFOLLOW) { ret = bftw_stat_impl(mutbuf, &mutbuf->lstat_cache, BFS_STAT_NOFOLLOW); if (ret && !S_ISLNK(ret->mode) && !mutbuf->stat_cache.buf) { // Non-link, so share stat info mutbuf->stat_cache.buf = ret; } } else { ret = bftw_stat_impl(mutbuf, &mutbuf->stat_cache, BFS_STAT_FOLLOW); if (!ret && (flags & BFS_STAT_TRYFOLLOW) && is_nonexistence_error(errno)) { ret = bftw_stat_impl(mutbuf, &mutbuf->lstat_cache, BFS_STAT_NOFOLLOW); } } return ret; } enum bftw_typeflag bftw_typeflag(const struct BFTW *ftwbuf, enum bfs_stat_flag flags) { if (ftwbuf->stat_flags & BFS_STAT_NOFOLLOW) { if ((flags & BFS_STAT_NOFOLLOW) || ftwbuf->typeflag != BFTW_LNK) { return ftwbuf->typeflag; } } else if ((flags & (BFS_STAT_NOFOLLOW | BFS_STAT_TRYFOLLOW)) == BFS_STAT_TRYFOLLOW || ftwbuf->typeflag == BFTW_LNK) { return ftwbuf->typeflag; } const struct bfs_stat *statbuf = bftw_stat(ftwbuf, flags); if (statbuf) { return bftw_mode_typeflag(statbuf->mode); } else { return BFTW_ERROR; } } /** * Update the path for the current file. */ static int bftw_update_path(struct bftw_state *state, const char *name) { const struct bftw_file *file = state->file; size_t length = file ? file->nameoff + file->namelen : 0; assert(dstrlen(state->path) >= length); dstresize(&state->path, length); if (name) { if (length > 0 && state->path[length - 1] != '/') { if (dstrapp(&state->path, '/') != 0) { return -1; } } if (dstrcat(&state->path, name) != 0) { return -1; } } return 0; } /** Check if a stat() call is needed for this visit. */ static bool bftw_need_stat(const struct bftw_state *state) { if (state->flags & BFTW_STAT) { return true; } const struct BFTW *ftwbuf = &state->ftwbuf; if (ftwbuf->typeflag == BFTW_UNKNOWN) { return true; } if (ftwbuf->typeflag == BFTW_LNK && !(ftwbuf->stat_flags & BFS_STAT_NOFOLLOW)) { return true; } if (ftwbuf->typeflag == BFTW_DIR) { if (state->flags & (BFTW_DETECT_CYCLES | BFTW_MOUNT | BFTW_XDEV)) { return true; } #if __linux__ } else if (state->mtab) { // Linux fills in d_type from the underlying inode, even when // the directory entry is a bind mount point. In that case, we // need to stat() to get the correct type. We don't need to // check for directories because they can only be mounted over // by other directories. if (bfs_might_be_mount(state->mtab, ftwbuf->path)) { return true; } #endif } return false; } /** Initialize bftw_stat cache. */ static void bftw_stat_init(struct bftw_stat *cache) { cache->buf = NULL; cache->error = 0; } /** * Open a file if necessary. * * @param file * The file to open. * @param path * The path to that file or one of its descendants. * @return * The opened file descriptor, or -1 on error. */ static int bftw_ensure_open(struct bftw_cache *cache, struct bftw_file *file, const char *path) { int ret = file->fd; if (ret < 0) { char *copy = strndup(path, file->nameoff + file->namelen); if (!copy) { return -1; } ret = bftw_file_open(cache, file, copy); free(copy); } return ret; } /** * Initialize the buffers with data about the current path. */ static void bftw_init_ftwbuf(struct bftw_state *state, enum bftw_visit visit) { struct bftw_file *file = state->file; const struct bftw_reader *reader = &state->reader; const struct dirent *de = reader->de; struct BFTW *ftwbuf = &state->ftwbuf; ftwbuf->path = state->path; ftwbuf->root = file ? file->root->name : ftwbuf->path; ftwbuf->depth = 0; ftwbuf->visit = visit; ftwbuf->typeflag = BFTW_UNKNOWN; ftwbuf->error = reader->error; ftwbuf->at_fd = AT_FDCWD; ftwbuf->at_path = ftwbuf->path; ftwbuf->stat_flags = BFS_STAT_NOFOLLOW; bftw_stat_init(&ftwbuf->lstat_cache); bftw_stat_init(&ftwbuf->stat_cache); struct bftw_file *parent = NULL; if (de) { parent = file; ftwbuf->depth = file->depth + 1; ftwbuf->typeflag = bftw_dirent_typeflag(de); ftwbuf->nameoff = bftw_child_nameoff(file); } else if (file) { parent = file->parent; ftwbuf->depth = file->depth; ftwbuf->typeflag = file->typeflag; ftwbuf->nameoff = file->nameoff; } if (parent) { // Try to ensure the immediate parent is open, to avoid ENAMETOOLONG if (bftw_ensure_open(&state->cache, parent, state->path) >= 0) { ftwbuf->at_fd = parent->fd; ftwbuf->at_path += ftwbuf->nameoff; } else { ftwbuf->error = errno; } } if (ftwbuf->depth == 0) { // Compute the name offset for root paths like "foo/bar" ftwbuf->nameoff = xbasename(ftwbuf->path) - ftwbuf->path; } if (ftwbuf->error != 0) { ftwbuf->typeflag = BFTW_ERROR; return; } int follow_flags = BFTW_LOGICAL; if (ftwbuf->depth == 0) { follow_flags |= BFTW_COMFOLLOW; } bool follow = state->flags & follow_flags; if (follow) { ftwbuf->stat_flags = BFS_STAT_TRYFOLLOW; } const struct bfs_stat *statbuf = NULL; if (bftw_need_stat(state)) { statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); if (statbuf) { ftwbuf->typeflag = bftw_mode_typeflag(statbuf->mode); } else { ftwbuf->typeflag = BFTW_ERROR; ftwbuf->error = errno; return; } } if (ftwbuf->typeflag == BFTW_DIR && (state->flags & BFTW_DETECT_CYCLES)) { for (const struct bftw_file *ancestor = parent; ancestor; ancestor = ancestor->parent) { if (ancestor->dev == statbuf->dev && ancestor->ino == statbuf->ino) { ftwbuf->typeflag = BFTW_ERROR; ftwbuf->error = ELOOP; return; } } } } /** Check if the current file is a mount point. */ static bool bftw_is_mount(struct bftw_state *state, const char *name) { const struct bftw_file *file = state->file; if (!file) { return false; } const struct bftw_file *parent = name ? file : file->parent; if (!parent) { return false; } const struct BFTW *ftwbuf = &state->ftwbuf; const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); return statbuf && statbuf->dev != parent->dev; } /** Fill file identity information from an ftwbuf. */ static void bftw_fill_id(struct bftw_file *file, const struct BFTW *ftwbuf) { const struct bfs_stat *statbuf = ftwbuf->stat_cache.buf; if (!statbuf || (ftwbuf->stat_flags & BFS_STAT_NOFOLLOW)) { statbuf = ftwbuf->lstat_cache.buf; } if (statbuf) { file->dev = statbuf->dev; file->ino = statbuf->ino; } } /** * Visit a path, invoking the callback. */ static enum bftw_action bftw_visit(struct bftw_state *state, const char *name, enum bftw_visit visit) { if (bftw_update_path(state, name) != 0) { state->error = errno; return BFTW_STOP; } const struct BFTW *ftwbuf = &state->ftwbuf; bftw_init_ftwbuf(state, visit); // Never give the callback BFTW_ERROR unless BFTW_RECOVER is specified if (ftwbuf->typeflag == BFTW_ERROR && !(state->flags & BFTW_RECOVER)) { state->error = ftwbuf->error; return BFTW_STOP; } if ((state->flags & BFTW_MOUNT) && bftw_is_mount(state, name)) { return BFTW_PRUNE; } enum bftw_action ret = state->callback(ftwbuf, state->ptr); switch (ret) { case BFTW_CONTINUE: break; case BFTW_PRUNE: case BFTW_STOP: goto done; default: state->error = EINVAL; return BFTW_STOP; } if (visit != BFTW_PRE || ftwbuf->typeflag != BFTW_DIR) { ret = BFTW_PRUNE; goto done; } if ((state->flags & BFTW_XDEV) && bftw_is_mount(state, name)) { ret = BFTW_PRUNE; goto done; } done: if (state->file && !name) { bftw_fill_id(state->file, ftwbuf); } return ret; } /** * Push a new file onto the queue. */ static int bftw_push(struct bftw_state *state, const char *name, bool fill_id) { struct bftw_file *parent = state->file; struct bftw_file *file = bftw_file_new(&state->cache, parent, name); if (!file) { state->error = errno; return -1; } struct dirent *de = state->reader.de; if (de) { file->typeflag = bftw_dirent_typeflag(de); } if (fill_id) { bftw_fill_id(file, &state->ftwbuf); } if (state->strategy == BFTW_DFS) { bftw_queue_push(&state->prequeue, file); } else { bftw_queue_push(&state->queue, file); } return 0; } /** * Build the path to the current file. */ static int bftw_build_path(struct bftw_state *state) { const struct bftw_file *file = state->file; size_t pathlen = file->nameoff + file->namelen; if (dstresize(&state->path, pathlen) != 0) { state->error = errno; return -1; } // Try to find a common ancestor with the existing path const struct bftw_file *ancestor = state->previous; while (ancestor && ancestor->depth > file->depth) { ancestor = ancestor->parent; } // Build the path backwards while (file && file != ancestor) { if (file->nameoff > 0) { state->path[file->nameoff - 1] = '/'; } memcpy(state->path + file->nameoff, file->name, file->namelen); if (ancestor && ancestor->depth == file->depth) { ancestor = ancestor->parent; } file = file->parent; } state->previous = state->file; return 0; } /** * Pop the next file from the queue. */ static int bftw_pop(struct bftw_state *state) { if (state->strategy == BFTW_DFS) { bftw_queue_prepend(&state->prequeue, &state->queue); } if (!state->queue.head) { return 0; } state->file = bftw_queue_pop(&state->queue); if (bftw_build_path(state) != 0) { return -1; } return 1; } /** * Open a reader for the current directory. */ static struct bftw_reader *bftw_open(struct bftw_state *state) { struct bftw_reader *reader = &state->reader; bftw_reader_open(reader, &state->cache, state->file, state->path); return reader; } /** * Flags controlling which files get visited when releasing a reader/file. */ enum bftw_release_flags { /** Don't visit anything. */ BFTW_VISIT_NONE = 0, /** Visit the file itself. */ BFTW_VISIT_FILE = 1 << 0, /** Visit the file's ancestors. */ BFTW_VISIT_PARENTS = 1 << 1, /** Visit both the file and its ancestors. */ BFTW_VISIT_ALL = BFTW_VISIT_FILE | BFTW_VISIT_PARENTS, }; /** * Close and release the reader. */ static enum bftw_action bftw_release_reader(struct bftw_state *state, enum bftw_release_flags flags) { enum bftw_action ret = BFTW_CONTINUE; struct bftw_reader *reader = &state->reader; bftw_reader_close(reader); if (reader->error != 0) { if (flags & BFTW_VISIT_FILE) { ret = bftw_visit(state, NULL, BFTW_PRE); } else { state->error = reader->error; } reader->error = 0; } return ret; } /** * Finalize and free a file we're done with. */ static enum bftw_action bftw_release_file(struct bftw_state *state, enum bftw_release_flags flags) { enum bftw_action ret = BFTW_CONTINUE; if (!(state->flags & BFTW_DEPTH)) { flags = 0; } bool visit = flags & BFTW_VISIT_FILE; while (state->file) { if (bftw_file_decref(&state->cache, state->file) > 0) { state->file = NULL; break; } if (visit && bftw_visit(state, NULL, BFTW_POST) == BFTW_STOP) { ret = BFTW_STOP; flags &= ~BFTW_VISIT_PARENTS; } visit = flags & BFTW_VISIT_PARENTS; struct bftw_file *parent = state->file->parent; if (state->previous == state->file) { state->previous = parent; } bftw_file_free(&state->cache, state->file); state->file = parent; } return ret; } /** * Drain all the entries from a bftw_queue. */ static void bftw_drain_queue(struct bftw_state *state, struct bftw_queue *queue) { while (queue->head) { state->file = bftw_queue_pop(queue); bftw_release_file(state, BFTW_VISIT_NONE); } } /** * Dispose of the bftw() state. * * @return * The bftw() return value. */ static int bftw_state_destroy(struct bftw_state *state) { dstrfree(state->path); bftw_release_reader(state, BFTW_VISIT_NONE); bftw_release_file(state, BFTW_VISIT_NONE); bftw_drain_queue(state, &state->prequeue); bftw_drain_queue(state, &state->queue); bftw_cache_destroy(&state->cache); errno = state->error; return state->error ? -1 : 0; } /** * Breadth-first bftw() implementation. */ static int bftw_bfs(const struct bftw_args *args) { struct bftw_state state; if (bftw_state_init(&state, args) != 0) { return -1; } for (size_t i = 0; i < args->npaths; ++i) { const char *path = args->paths[i]; switch (bftw_visit(&state, path, BFTW_PRE)) { case BFTW_CONTINUE: break; case BFTW_PRUNE: continue; case BFTW_STOP: goto done; } if (bftw_push(&state, path, true) != 0) { goto done; } } while (bftw_pop(&state) > 0) { struct bftw_reader *reader = bftw_open(&state); while (bftw_reader_read(reader) > 0) { const char *name = reader->de->d_name; switch (bftw_visit(&state, name, BFTW_PRE)) { case BFTW_CONTINUE: break; case BFTW_PRUNE: continue; case BFTW_STOP: goto done; } if (bftw_push(&state, name, true) != 0) { goto done; } } if (bftw_release_reader(&state, BFTW_VISIT_ALL) == BFTW_STOP) { goto done; } if (bftw_release_file(&state, BFTW_VISIT_ALL) == BFTW_STOP) { goto done; } } done: return bftw_state_destroy(&state); } /** * Depth-first bftw() implementation. */ static int bftw_dfs(const struct bftw_args *args) { struct bftw_state state; if (bftw_state_init(&state, args) != 0) { return -1; } for (size_t i = 0; i < args->npaths; ++i) { if (bftw_push(&state, args->paths[i], false) != 0) { goto done; } } while (bftw_pop(&state) > 0) { enum bftw_release_flags relflags = BFTW_VISIT_ALL; switch (bftw_visit(&state, NULL, BFTW_PRE)) { case BFTW_CONTINUE: break; case BFTW_PRUNE: relflags &= ~BFTW_VISIT_FILE; goto next; case BFTW_STOP: goto done; } struct bftw_reader *reader = bftw_open(&state); while (bftw_reader_read(reader) > 0) { if (bftw_push(&state, reader->de->d_name, false) != 0) { goto done; } } if (bftw_release_reader(&state, relflags) == BFTW_STOP) { goto done; } next: if (bftw_release_file(&state, relflags) == BFTW_STOP) { goto done; } } done: return bftw_state_destroy(&state); } /** * Iterative deepening search state. */ struct bftw_ids_state { /** The wrapped callback. */ bftw_callback *delegate; /** The wrapped callback arguments. */ void *ptr; /** Which visit this search corresponds to. */ enum bftw_visit visit; /** The current target depth. */ size_t depth; /** The set of pruned paths. */ struct trie *pruned; /** An error code to report. */ int error; /** Whether the bottom has been found. */ bool bottom; /** Whether to quit the search. */ bool quit; }; /** Iterative deepening callback function. */ static enum bftw_action bftw_ids_callback(const struct BFTW *ftwbuf, void *ptr) { struct bftw_ids_state *state = ptr; struct BFTW *mutbuf = (struct BFTW *)ftwbuf; mutbuf->visit = state->visit; if (ftwbuf->typeflag == BFTW_ERROR) { if (state->depth - ftwbuf->depth <= 1) { return state->delegate(ftwbuf, state->ptr); } else { return BFTW_PRUNE; } } if (ftwbuf->depth < state->depth) { if (trie_find_str(state->pruned, ftwbuf->path)) { return BFTW_PRUNE; } else { return BFTW_CONTINUE; } } else if (state->visit == BFTW_POST) { if (trie_find_str(state->pruned, ftwbuf->path)) { return BFTW_PRUNE; } } state->bottom = false; enum bftw_action ret = state->delegate(ftwbuf, state->ptr); switch (ret) { case BFTW_CONTINUE: ret = BFTW_PRUNE; break; case BFTW_PRUNE: if (ftwbuf->typeflag == BFTW_DIR) { if (!trie_insert_str(state->pruned, ftwbuf->path)) { state->error = errno; state->quit = true; ret = BFTW_STOP; } } break; case BFTW_STOP: state->quit = true; break; } return ret; } /** * Iterative deepening bftw() wrapper. */ static int bftw_ids(const struct bftw_args *args) { struct trie pruned; trie_init(&pruned); struct bftw_ids_state state = { .delegate = args->callback, .ptr = args->ptr, .visit = BFTW_PRE, .depth = 0, .pruned = &pruned, .bottom = false, }; struct bftw_args ids_args = *args; ids_args.callback = bftw_ids_callback; ids_args.ptr = &state; ids_args.flags &= ~BFTW_DEPTH; ids_args.strategy = BFTW_DFS; int ret = 0; while (ret == 0 && !state.quit && !state.bottom) { state.bottom = true; // bftw_bfs() is more efficient than bftw_dfs() since it visits // directory entries as it reads them. With args->strategy == // BFTW_DFS, it gives a hybrid ordering that visits immediate // children first, then deeper descendants depth-first. This // doesn't matter for iterative deepening since we only visit // one level at a time. ret = bftw_bfs(&ids_args); ++state.depth; } if (args->flags & BFTW_DEPTH) { state.visit = BFTW_POST; while (ret == 0 && !state.quit && state.depth > 0) { --state.depth; ret = bftw_bfs(&ids_args); } } if (state.error) { ret = -1; } else { state.error = errno; } trie_destroy(&pruned); errno = state.error; return ret; } int bftw(const struct bftw_args *args) { switch (args->strategy) { case BFTW_BFS: return bftw_bfs(args); case BFTW_DFS: return bftw_dfs(args); case BFTW_IDS: return bftw_ids(args); } errno = EINVAL; return -1; } bfs-1.5.2/bftw.h000066400000000000000000000150221360564154000134100ustar00rootroot00000000000000/**************************************************************************** * bfs * * Copyright (C) 2015-2019 Tavian Barnes * * * * 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. * ****************************************************************************/ /** * A file-walking API based on nftw(). */ #ifndef BFS_BFTW_H #define BFS_BFTW_H #include "mtab.h" #include "stat.h" #include #include /** * Possible file types. */ enum bftw_typeflag { /** Unknown type. */ BFTW_UNKNOWN = 0, /** Block device. */ BFTW_BLK = 1 << 0, /** Character device. */ BFTW_CHR = 1 << 1, /** Directory. */ BFTW_DIR = 1 << 2, /** Solaris door. */ BFTW_DOOR = 1 << 3, /** Pipe. */ BFTW_FIFO = 1 << 4, /** Symbolic link. */ BFTW_LNK = 1 << 5, /** Solaris event port. */ BFTW_PORT = 1 << 6, /** Regular file. */ BFTW_REG = 1 << 7, /** Socket. */ BFTW_SOCK = 1 << 8, /** BSD whiteout. */ BFTW_WHT = 1 << 9, /** An error occurred for this file. */ BFTW_ERROR = 1 << 10, }; /** * Convert a bfs_stat() mode to a bftw() typeflag. */ enum bftw_typeflag bftw_mode_typeflag(mode_t mode); /** * Possible visit occurrences. */ enum bftw_visit { /** Pre-order visit. */ BFTW_PRE, /** Post-order visit. */ BFTW_POST, }; /** * Cached bfs_stat() info for a file. */ struct bftw_stat { /** A pointer to the bfs_stat() buffer, if available. */ const struct bfs_stat *buf; /** Storage for the bfs_stat() buffer, if needed. */ struct bfs_stat storage; /** The cached error code, if any. */ int error; }; /** * Data about the current file for the bftw() callback. */ struct BFTW { /** The path to the file. */ const char *path; /** The string offset of the filename. */ size_t nameoff; /** The root path passed to bftw(). */ const char *root; /** The depth of this file in the traversal. */ size_t depth; /** Which visit this is. */ enum bftw_visit visit; /** The file type. */ enum bftw_typeflag typeflag; /** The errno that occurred, if typeflag == BFTW_ERROR. */ int error; /** A parent file descriptor for the *at() family of calls. */ int at_fd; /** The path relative to at_fd for the *at() family of calls. */ const char *at_path; /** Flags for bfs_stat(). */ enum bfs_stat_flag stat_flags; /** Cached bfs_stat() info for BFS_STAT_NOFOLLOW. */ struct bftw_stat lstat_cache; /** Cached bfs_stat() info for BFS_STAT_FOLLOW. */ struct bftw_stat stat_cache; }; /** * Get bfs_stat() info for a file encountered during bftw(), caching the result * whenever possible. * * @param ftwbuf * bftw() data for the file to stat. * @param flags * flags for bfs_stat(). Pass ftwbuf->stat_flags for the default flags. * @return * A pointer to a bfs_stat() buffer, or NULL if the call failed. */ const struct bfs_stat *bftw_stat(const struct BFTW *ftwbuf, enum bfs_stat_flag flags); /** * Get the type of a file encountered during bftw(), with flags controlling * whether to follow links. This function will avoid calling bfs_stat() if * possible. * * @param ftwbuf * bftw() data for the file to check. * @param flags * flags for bfs_stat(). Pass ftwbuf->stat_flags for the default flags. * @return * The type of the file, or BFTW_ERROR if an error occurred. */ enum bftw_typeflag bftw_typeflag(const struct BFTW *ftwbuf, enum bfs_stat_flag flags); /** * Walk actions returned by the bftw() callback. */ enum bftw_action { /** Keep walking. */ BFTW_CONTINUE, /** Skip this path's children. */ BFTW_PRUNE, /** Stop walking. */ BFTW_STOP, }; /** * Callback function type for bftw(). * * @param ftwbuf * Data about the current file. * @param ptr * The pointer passed to bftw(). * @return * An action value. */ typedef enum bftw_action bftw_callback(const struct BFTW *ftwbuf, void *ptr); /** * Flags that control bftw() behavior. */ enum bftw_flags { /** stat() each encountered file. */ BFTW_STAT = 1 << 0, /** Attempt to recover from encountered errors. */ BFTW_RECOVER = 1 << 1, /** Visit directories in post-order as well as pre-order. */ BFTW_DEPTH = 1 << 2, /** If the initial path is a symbolic link, follow it. */ BFTW_COMFOLLOW = 1 << 3, /** Follow all symbolic links. */ BFTW_LOGICAL = 1 << 4, /** Detect directory cycles. */ BFTW_DETECT_CYCLES = 1 << 5, /** Skip mount points and their descendents. */ BFTW_MOUNT = 1 << 6, /** Skip the descendents of mount points. */ BFTW_XDEV = 1 << 7, }; /** * Tree search strategies for bftw(). */ enum bftw_strategy { /** Breadth-first search. */ BFTW_BFS, /** Depth-first search. */ BFTW_DFS, /** Iterative deepening search. */ BFTW_IDS, }; /** * Structure for holding the arguments passed to bftw(). */ struct bftw_args { /** The path(s) to start from. */ const char **paths; /** The number of starting paths. */ size_t npaths; /** The callback to invoke. */ bftw_callback *callback; /** A pointer which is passed to the callback. */ void *ptr; /** The maximum number of file descriptors to keep open. */ int nopenfd; /** Flags that control bftw() behaviour. */ enum bftw_flags flags; /** The search strategy to use. */ enum bftw_strategy strategy; /** The parsed mount table, if available. */ const struct bfs_mtab *mtab; }; /** * Breadth First Tree Walk (or Better File Tree Walk). * * Like ftw(3) and nftw(3), this function walks a directory tree recursively, * and invokes a callback for each path it encounters. * * @param args * The arguments that control the walk. * @return * 0 on success, or -1 on failure. */ int bftw(const struct bftw_args *args); #endif // BFS_BFTW_H bfs-1.5.2/cmdline.h000066400000000000000000000067441360564154000140740ustar00rootroot00000000000000/**************************************************************************** * bfs * * Copyright (C) 2015-2018 Tavian Barnes * * * * 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. * ****************************************************************************/ /** * Representation of the parsed command line. */ #ifndef BFS_CMDLINE_H #define BFS_CMDLINE_H #include "color.h" #include "trie.h" /** * Various debugging flags. */ enum debug_flags { /** Print cost estimates. */ DEBUG_COST = 1 << 0, /** Print executed command details. */ DEBUG_EXEC = 1 << 1, /** Print optimization details. */ DEBUG_OPT = 1 << 2, /** Print rate information. */ DEBUG_RATES = 1 << 3, /** Trace the filesystem traversal. */ DEBUG_SEARCH = 1 << 4, /** Trace all stat() calls. */ DEBUG_STAT = 1 << 5, /** Print the parse tree. */ DEBUG_TREE = 1 << 6, /** All debug flags. */ DEBUG_ALL = (1 << 7) - 1, }; /** * The parsed command line. */ struct cmdline { /** The unparsed command line arguments. */ char **argv; /** The root paths. */ const char **paths; /** Color data. */ struct colors *colors; /** Colored stdout. */ CFILE *cout; /** Colored stderr. */ CFILE *cerr; /** Table of mounted file systems. */ struct bfs_mtab *mtab; /** The error that occurred parsing the mount table, if any. */ int mtab_error; /** -mindepth option. */ int mindepth; /** -maxdepth option. */ int maxdepth; /** bftw() flags. */ enum bftw_flags flags; /** bftw() search strategy. */ enum bftw_strategy strategy; /** Optimization level. */ int optlevel; /** Debugging flags. */ enum debug_flags debug; /** Whether to only handle paths with xargs-safe characters. */ bool xargs_safe; /** Whether to ignore deletions that race with bfs. */ bool ignore_races; /** Whether to only return unique files. */ bool unique; /** The command line expression. */ struct expr *expr; /** All the open files owned by the command line. */ struct trie open_files; /** The number of open files owned by the command line. */ int nopen_files; }; /** * Parse the command line. */ struct cmdline *parse_cmdline(int argc, char *argv[]); /** * Dump the parsed command line. */ void dump_cmdline(const struct cmdline *cmdline, bool verbose); /** * Optimize the parsed command line. * * @return 0 if successful, -1 on error. */ int optimize_cmdline(struct cmdline *cmdline); /** * Evaluate the command line. */ int eval_cmdline(const struct cmdline *cmdline); /** * Free the parsed command line. * * @return 0 if successful, -1 on error. */ int free_cmdline(struct cmdline *cmdline); #endif // BFS_CMDLINE_H bfs-1.5.2/color.c000066400000000000000000000522141360564154000135630ustar00rootroot00000000000000/**************************************************************************** * bfs * * Copyright (C) 2015-2019 Tavian Barnes * * * * 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. * ****************************************************************************/ #include "color.h" #include "bftw.h" #include "dstring.h" #include "fsade.h" #include "stat.h" #include "trie.h" #include "util.h" #include #include #include #include #include #include #include #include #include /** * The parsed form of LS_COLORS. */ struct colors { char *reset; char *leftcode; char *rightcode; char *endcode; char *clear_to_eol; char *bold; char *gray; char *red; char *green; char *yellow; char *blue; char *magenta; char *cyan; char *white; char *warning; char *error; char *normal; char *file; char *multi_hard; char *executable; char *capable; char *setgid; char *setuid; char *directory; char *sticky; char *other_writable; char *sticky_other_writable; char *link; char *orphan; char *missing; bool link_as_target; char *blockdev; char *chardev; char *door; char *pipe; char *socket; /** A mapping from color names (fi, di, ln, etc.) to struct fields. */ struct trie names; /** A mapping from file extensions to colors. */ struct trie ext_colors; }; /** Initialize a color in the table. */ static int init_color(struct colors *colors, const char *name, const char *value, char **field) { if (value) { *field = dstrdup(value); if (!*field) { return -1; } } else { *field = NULL; } struct trie_leaf *leaf = trie_insert_str(&colors->names, name); if (leaf) { leaf->value = field; return 0; } else { return -1; } } /** Get a color from the table. */ static char **get_color(const struct colors *colors, const char *name) { const struct trie_leaf *leaf = trie_find_str(&colors->names, name); if (leaf) { return (char **)leaf->value; } else { return NULL; } } /** Set the value of a color. */ static void set_color(struct colors *colors, const char *name, char *value) { char **color = get_color(colors, name); if (color) { dstrfree(*color); *color = value; } } /** * Transform a file extension for fast lookups, by reversing and lowercasing it. */ static void extxfrm(char *ext) { size_t len = strlen(ext); for (size_t i = 0; i < len - i; ++i) { char a = ext[i]; char b = ext[len - i - 1]; // What's internationalization? Doesn't matter, this is what // GNU ls does. Luckily, since there's no standard C way to // casefold. Not using tolower() here since it respects the // current locale, which GNU ls doesn't do. if (a >= 'A' && a <= 'Z') { a += 'a' - 'A'; } if (b >= 'A' && b <= 'Z') { b += 'a' - 'A'; } ext[i] = b; ext[len - i - 1] = a; } } /** * Set the color for an extension. */ static int set_ext_color(struct colors *colors, char *key, const char *value) { extxfrm(key); // A later *.x should override any earlier *.x, *.y.x, etc. struct trie_leaf *match; while ((match = trie_find_postfix(&colors->ext_colors, key))) { dstrfree(match->value); trie_remove(&colors->ext_colors, match); } struct trie_leaf *leaf = trie_insert_str(&colors->ext_colors, key); if (leaf) { leaf->value = (char *)value; return 0; } else { return -1; } } /** * Find a color by an extension. */ static const char *get_ext_color(const struct colors *colors, const char *filename) { char *xfrm = strdup(filename); if (!xfrm) { return NULL; } extxfrm(xfrm); const struct trie_leaf *leaf = trie_find_prefix(&colors->ext_colors, xfrm); free(xfrm); if (leaf) { return leaf->value; } else { return NULL; } } /** * Parse a chunk of LS_COLORS that may have escape sequences. The supported * escapes are: * * \a, \b, \f, \n, \r, \t, \v: * As in C * \e: * ESC (\033) * \?: * DEL (\177) * \_: * ' ' (space) * \NNN: * Octal * \xNN: * Hex * ^C: * Control character. * * See man dir_colors. * * @param value * The value to parse. * @param end * The character that marks the end of the chunk. * @param[out] next * Will be set to the next chunk. * @return * The parsed chunk as a dstring. */ static char *unescape(const char *value, char end, const char **next) { if (!value) { goto fail; } char *str = dstralloc(0); if (!str) { goto fail_str; } const char *i; for (i = value; *i && *i != end; ++i) { unsigned char c = 0; switch (*i) { case '\\': switch (*++i) { case 'a': c = '\a'; break; case 'b': c = '\b'; break; case 'e': c = '\033'; break; case 'f': c = '\f'; break; case 'n': c = '\n'; break; case 'r': c = '\r'; break; case 't': c = '\t'; break; case 'v': c = '\v'; break; case '?': c = '\177'; break; case '_': c = ' '; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': while (i[1] >= '0' && i[1] <= '7') { c <<= 3; c |= *i++ - '0'; } c <<= 3; c |= *i - '0'; break; case 'X': case 'x': while (true) { if (i[1] >= '0' && i[1] <= '9') { c <<= 4; c |= i[1] - '0'; } else if (i[1] >= 'A' && i[1] <= 'F') { c <<= 4; c |= i[1] - 'A' + 0xA; } else if (i[1] >= 'a' && i[1] <= 'f') { c <<= 4; c |= i[1] - 'a' + 0xA; } else { break; } ++i; } break; case '\0': goto fail_str; default: c = *i; break; } break; case '^': switch (*++i) { case '?': c = '\177'; break; case '\0': goto fail_str; default: // CTRL masks bits 6 and 7 c = *i & 0x1F; break; } break; default: c = *i; break; } if (dstrapp(&str, c) != 0) { goto fail_str; } } if (*i) { *next = i + 1; } else { *next = NULL; } return str; fail_str: dstrfree(str); fail: *next = NULL; return NULL; } struct colors *parse_colors(const char *ls_colors) { struct colors *colors = malloc(sizeof(struct colors)); if (!colors) { return NULL; } trie_init(&colors->names); trie_init(&colors->ext_colors); int ret = 0; // From man console_codes ret |= init_color(colors, "rs", "0", &colors->reset); ret |= init_color(colors, "lc", "\033[", &colors->leftcode); ret |= init_color(colors, "rc", "m", &colors->rightcode); ret |= init_color(colors, "ec", NULL, &colors->endcode); ret |= init_color(colors, "cl", "\033[K", &colors->clear_to_eol); ret |= init_color(colors, "bld", "01", &colors->bold); ret |= init_color(colors, "gry", "01;30", &colors->gray); ret |= init_color(colors, "red", "01;31", &colors->red); ret |= init_color(colors, "grn", "01;32", &colors->green); ret |= init_color(colors, "ylw", "01;33", &colors->yellow); ret |= init_color(colors, "blu", "01;34", &colors->blue); ret |= init_color(colors, "mag", "01;35", &colors->magenta); ret |= init_color(colors, "cyn", "01;36", &colors->cyan); ret |= init_color(colors, "wht", "01;37", &colors->white); ret |= init_color(colors, "wr", "01;33", &colors->warning); ret |= init_color(colors, "er", "01;31", &colors->error); // Defaults from man dir_colors ret |= init_color(colors, "no", NULL, &colors->normal); ret |= init_color(colors, "fi", NULL, &colors->file); ret |= init_color(colors, "mh", NULL, &colors->multi_hard); ret |= init_color(colors, "ex", "01;32", &colors->executable); ret |= init_color(colors, "ca", "30;41", &colors->capable); ret |= init_color(colors, "sg", "30;43", &colors->setgid); ret |= init_color(colors, "su", "37;41", &colors->setuid); ret |= init_color(colors, "di", "01;34", &colors->directory); ret |= init_color(colors, "st", "37;44", &colors->sticky); ret |= init_color(colors, "ow", "34;42", &colors->other_writable); ret |= init_color(colors, "tw", "30;42", &colors->sticky_other_writable); ret |= init_color(colors, "ln", "01;36", &colors->link); ret |= init_color(colors, "or", NULL, &colors->orphan); ret |= init_color(colors, "mi", NULL, &colors->missing); colors->link_as_target = false; ret |= init_color(colors, "bd", "01;33", &colors->blockdev); ret |= init_color(colors, "cd", "01;33", &colors->chardev); ret |= init_color(colors, "do", "01;35", &colors->door); ret |= init_color(colors, "pi", "33", &colors->pipe); ret |= init_color(colors, "so", "01;35", &colors->socket); if (ret) { free_colors(colors); return NULL; } for (const char *chunk = ls_colors, *next; chunk; chunk = next) { if (chunk[0] == '*') { char *key = unescape(chunk + 1, '=', &next); if (!key) { continue; } char *value = unescape(next, ':', &next); if (value) { if (set_ext_color(colors, key, value) != 0) { dstrfree(value); } } dstrfree(key); } else { const char *equals = strchr(chunk, '='); if (!equals) { break; } char *value = unescape(equals + 1, ':', &next); if (!value) { continue; } char *key = strndup(chunk, equals - chunk); if (!key) { dstrfree(value); continue; } // All-zero values should be treated like NULL, to fall // back on any other relevant coloring for that file if (strspn(value, "0") == strlen(value) && strcmp(key, "rs") != 0 && strcmp(key, "lc") != 0 && strcmp(key, "rc") != 0 && strcmp(key, "ec") != 0) { dstrfree(value); value = NULL; } set_color(colors, key, value); free(key); } } if (colors->link && strcmp(colors->link, "target") == 0) { colors->link_as_target = true; dstrfree(colors->link); colors->link = NULL; } return colors; } void free_colors(struct colors *colors) { if (colors) { struct trie_leaf *leaf; while ((leaf = trie_first_leaf(&colors->ext_colors))) { dstrfree(leaf->value); trie_remove(&colors->ext_colors, leaf); } trie_destroy(&colors->ext_colors); while ((leaf = trie_first_leaf(&colors->names))) { char **field = leaf->value; dstrfree(*field); trie_remove(&colors->names, leaf); } trie_destroy(&colors->names); free(colors); } } CFILE *cfopen(const char *path, const struct colors *colors) { CFILE *cfile = malloc(sizeof(*cfile)); if (!cfile) { return NULL; } cfile->close = false; cfile->file = fopen(path, "wb"); if (!cfile->file) { cfclose(cfile); return NULL; } cfile->close = true; if (isatty(fileno(cfile->file))) { cfile->colors = colors; } else { cfile->colors = NULL; } return cfile; } CFILE *cfdup(FILE *file, const struct colors *colors) { CFILE *cfile = malloc(sizeof(*cfile)); if (!cfile) { return NULL; } cfile->close = false; cfile->file = file; if (isatty(fileno(file))) { cfile->colors = colors; } else { cfile->colors = NULL; } return cfile; } int cfclose(CFILE *cfile) { int ret = 0; if (cfile) { if (cfile->close) { ret = fclose(cfile->file); } free(cfile); } return ret; } /** Check if a symlink is broken. */ static bool is_link_broken(const struct BFTW *ftwbuf) { if (ftwbuf->stat_flags & BFS_STAT_NOFOLLOW) { return xfaccessat(ftwbuf->at_fd, ftwbuf->at_path, F_OK) != 0; } else { return true; } } /** Get the color for a file. */ static const char *file_color(const struct colors *colors, const char *filename, const struct BFTW *ftwbuf, enum bfs_stat_flag flags) { enum bftw_typeflag typeflag = bftw_typeflag(ftwbuf, flags); if (typeflag == BFTW_ERROR) { goto error; } const struct bfs_stat *statbuf = NULL; const char *color = NULL; switch (typeflag) { case BFTW_REG: if (colors->setuid || colors->setgid || colors->executable || colors->multi_hard) { statbuf = bftw_stat(ftwbuf, flags); if (!statbuf) { goto error; } } if (colors->setuid && (statbuf->mode & 04000)) { color = colors->setuid; } else if (colors->setgid && (statbuf->mode & 02000)) { color = colors->setgid; } else if (colors->capable && bfs_check_capabilities(ftwbuf) > 0) { color = colors->capable; } else if (colors->executable && (statbuf->mode & 00111)) { color = colors->executable; } else if (colors->multi_hard && statbuf->nlink > 1) { color = colors->multi_hard; } if (!color) { color = get_ext_color(colors, filename); } if (!color) { color = colors->file; } break; case BFTW_DIR: if (colors->sticky_other_writable || colors->other_writable || colors->sticky) { statbuf = bftw_stat(ftwbuf, flags); if (!statbuf) { goto error; } } if (colors->sticky_other_writable && (statbuf->mode & 01002) == 01002) { color = colors->sticky_other_writable; } else if (colors->other_writable && (statbuf->mode & 00002)) { color = colors->other_writable; } else if (colors->sticky && (statbuf->mode & 01000)) { color = colors->sticky; } else { color = colors->directory; } break; case BFTW_LNK: if (colors->orphan && is_link_broken(ftwbuf)) { color = colors->orphan; } else { color = colors->link; } break; case BFTW_BLK: color = colors->blockdev; break; case BFTW_CHR: color = colors->chardev; break; case BFTW_FIFO: color = colors->pipe; break; case BFTW_SOCK: color = colors->socket; break; case BFTW_DOOR: color = colors->door; break; default: break; } if (!color) { color = colors->normal; } return color; error: if (colors->missing) { return colors->missing; } else { return colors->orphan; } } /** Print a fixed-length string. */ static int print_strn(const char *str, size_t len, FILE *file) { if (fwrite(str, 1, len, file) == len) { return 0; } else { return -1; } } /** Print a dstring. */ static int print_dstr(const char *str, FILE *file) { return print_strn(str, dstrlen(str), file); } /** Print an ANSI escape sequence. */ static int print_esc(const struct colors *colors, const char *esc, FILE *file) { if (print_dstr(colors->leftcode, file) != 0) { return -1; } if (print_dstr(esc, file) != 0) { return -1; } if (print_dstr(colors->rightcode, file) != 0) { return -1; } return 0; } /** Reset after an ANSI escape sequence. */ static int print_reset(const struct colors *colors, FILE *file) { if (colors->endcode) { return print_dstr(colors->endcode, file); } else { return print_esc(colors, colors->reset, file); } } /** Print a string with an optional color. */ static int print_colored(const struct colors *colors, const char *esc, const char *str, size_t len, FILE *file) { if (esc) { if (print_esc(colors, esc, file) != 0) { return -1; } } if (print_strn(str, len, file) != 0) { return -1; } if (esc) { if (print_reset(colors, file) != 0) { return -1; } } return 0; } /** Find the offset of the first broken path component. */ static ssize_t first_broken_offset(const char *path, const struct BFTW *ftwbuf, enum bfs_stat_flag flags, size_t max) { ssize_t ret = max; if (bftw_typeflag(ftwbuf, flags) != BFTW_ERROR) { goto out; } char *at_path; int at_fd; if (path == ftwbuf->path) { if (ftwbuf->depth == 0) { at_fd = AT_FDCWD; at_path = dstrndup(path, max); } else { // The parent must have existed to get here goto out; } } else { // We're in print_link_target(), so resolve relative to the link's parent directory at_fd = ftwbuf->at_fd; if (at_fd == AT_FDCWD && path[0] != '/') { at_path = dstrndup(ftwbuf->path, ftwbuf->nameoff); if (at_path && dstrncat(&at_path, path, max) != 0) { ret = -1; goto out_path; } } else { at_path = dstrndup(path, max); } } if (!at_path) { ret = -1; goto out; } while (ret > 0) { if (xfaccessat(at_fd, at_path, F_OK) == 0) { break; } size_t len = dstrlen(at_path); while (ret && at_path[len - 1] == '/') { --len, --ret; } while (ret && at_path[len - 1] != '/') { --len, --ret; } dstresize(&at_path, len); } out_path: dstrfree(at_path); out: return ret; } /** Print the directories leading up to a file. */ static int print_dirs_colored(CFILE *cfile, const char *path, const struct BFTW *ftwbuf, enum bfs_stat_flag flags, size_t nameoff) { const struct colors *colors = cfile->colors; FILE *file = cfile->file; ssize_t broken = first_broken_offset(path, ftwbuf, flags, nameoff); if (broken < 0) { return -1; } if (broken > 0) { if (print_colored(colors, colors->directory, path, broken, file) != 0) { return -1; } } if (broken < nameoff) { const char *color = colors->missing; if (!color) { color = colors->orphan; } if (print_colored(colors, color, path + broken, nameoff - broken, file) != 0) { return -1; } } return 0; } /** Print a path with colors. */ static int print_path_colored(CFILE *cfile, const char *path, const struct BFTW *ftwbuf, enum bfs_stat_flag flags) { const struct colors *colors = cfile->colors; FILE *file = cfile->file; size_t nameoff; if (path == ftwbuf->path) { nameoff = ftwbuf->nameoff; } else { nameoff = xbasename(path) - path; } print_dirs_colored(cfile, path, ftwbuf, flags, nameoff); const char *filename = path + nameoff; const char *color = file_color(colors, filename, ftwbuf, flags); return print_colored(colors, color, filename, strlen(filename), file); } /** Print the path to a file with the appropriate colors. */ static int print_path(CFILE *cfile, const struct BFTW *ftwbuf) { const struct colors *colors = cfile->colors; if (!colors) { return fputs(ftwbuf->path, cfile->file) == EOF ? -1 : 0; } enum bfs_stat_flag flags = ftwbuf->stat_flags; if (colors && colors->link_as_target && ftwbuf->typeflag == BFTW_LNK) { flags = BFS_STAT_TRYFOLLOW; } return print_path_colored(cfile, ftwbuf->path, ftwbuf, flags); } /** Print a link target with the appropriate colors. */ static int print_link_target(CFILE *cfile, const struct BFTW *ftwbuf) { int ret = -1; size_t len = 0; const struct bfs_stat *statbuf = bftw_stat(ftwbuf, BFS_STAT_NOFOLLOW); if (statbuf) { len = statbuf->size; } char *target = xreadlinkat(ftwbuf->at_fd, ftwbuf->at_path, len); if (!target) { goto done; } if (!cfile->colors) { ret = fputs(target, cfile->file) == EOF ? -1 : 0; goto done; } ret = print_path_colored(cfile, target, ftwbuf, BFS_STAT_FOLLOW); done: free(target); return ret; } int cfprintf(CFILE *cfile, const char *format, ...) { va_list args; va_start(args, format); int ret = cvfprintf(cfile, format, args); va_end(args); return ret; } int cvfprintf(CFILE *cfile, const char *format, va_list args) { const struct colors *colors = cfile->colors; FILE *file = cfile->file; int error = errno; for (const char *i = format; *i; ++i) { size_t verbatim = strcspn(i, "%$"); if (fwrite(i, 1, verbatim, file) != verbatim) { return -1; } i += verbatim; switch (*i) { case '%': switch (*++i) { case '%': if (fputc('%', file) == EOF) { return -1; } break; case 'c': if (fputc(va_arg(args, int), file) == EOF) { return -1; } break; case 'd': if (fprintf(file, "%d", va_arg(args, int)) < 0) { return -1; } break; case 'g': if (fprintf(file, "%g", va_arg(args, double)) < 0) { return -1; } break; case 's': if (fputs(va_arg(args, const char *), file) == EOF) { return -1; } break; case 'z': ++i; if (*i != 'u') { goto invalid; } if (fprintf(file, "%zu", va_arg(args, size_t)) < 0) { return -1; } break; case 'm': if (fputs(strerror(error), file) == EOF) { return -1; } break; case 'p': switch (*++i) { case 'P': if (print_path(cfile, va_arg(args, const struct BFTW *)) != 0) { return -1; } break; case 'L': if (print_link_target(cfile, va_arg(args, const struct BFTW *)) != 0) { return -1; } break; default: goto invalid; } break; default: goto invalid; } break; case '$': switch (*++i) { case '$': if (fputc('$', file) == EOF) { return -1; } break; case '{': { ++i; const char *end = strchr(i, '}'); if (!end) { goto invalid; } if (!colors) { i = end; break; } size_t len = end - i; char name[len + 1]; memcpy(name, i, len); name[len] = '\0'; char **esc = get_color(colors, name); if (!esc) { goto invalid; } if (*esc) { if (print_esc(colors, *esc, file) != 0) { return -1; } } i = end; break; } default: goto invalid; } break; default: return 0; } } return 0; invalid: assert(false); errno = EINVAL; return -1; } bfs-1.5.2/color.h000066400000000000000000000070001360564154000135610ustar00rootroot00000000000000/**************************************************************************** * bfs * * Copyright (C) 2015-2018 Tavian Barnes * * * * 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. * ****************************************************************************/ /** * Utilities for colored output on ANSI terminals. */ #ifndef BFS_COLOR_H #define BFS_COLOR_H #include "bftw.h" #include "util.h" #include #include #include /** * A lookup table for colors. */ struct colors; /** * Parse a color table. * * @param ls_colors * A color table in the LS_COLORS environment variable format. * @return The parsed color table. */ struct colors *parse_colors(const char *ls_colors); /** * Free a color table. * * @param colors * The color table to free. */ void free_colors(struct colors *colors); /** * A file/stream with associated colors. */ typedef struct CFILE { /** The underlying file/stream. */ FILE *file; /** The color table to use, if any. */ const struct colors *colors; /** Whether to close the underlying stream. */ bool close; } CFILE; /** * Open a file for colored output. * * @param path * The path to the file to open. * @param colors * The color table to use if file is a TTY. * @return A colored file stream. */ CFILE *cfopen(const char *path, const struct colors *colors); /** * Make a colored copy of an open file. * * @param file * The underlying file. * @param colors * The color table to use if file is a TTY. * @return A colored wrapper around file. */ CFILE *cfdup(FILE *file, const struct colors *colors); /** * Close a colored file. * * @param cfile * The colored file to close. * @return 0 on success, -1 on failure. */ int cfclose(CFILE *cfile); /** * Colored, formatted output. * * @param cfile * The colored stream to print to. * @param format * A printf()-style format string, supporting these format specifiers: * * %c: A single character * %d: An integer * %g: A double * %s: A string * %zu: A size_t * %m: strerror(errno) * %pP: A colored file path, from a const struct BFTW * argument * %pL: A colored link target, from a const struct BFTW * argument * %%: A literal '%' * ${cc}: Change the color to 'cc' * $$: A literal '$' * @return 0 on success, -1 on failure. */ BFS_FORMATTER(2, 3) int cfprintf(CFILE *cfile, const char *format, ...); /** * cfprintf() variant that takes a va_list. */ int cvfprintf(CFILE *cfile, const char *format, va_list args); #endif // BFS_COLOR_H bfs-1.5.2/darray.c000066400000000000000000000057721360564154000137360ustar00rootroot00000000000000/**************************************************************************** * bfs * * Copyright (C) 2019 Tavian Barnes * * * * 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. * ****************************************************************************/ #include "darray.h" #include #include /** * The darray header. */ struct darray { /** The current capacity of the array, as a count of elements. */ size_t capacity; /** The current length of the array. */ size_t length; // The array elements are stored after this header in memory. Not using // a flexible array member to avoid worrying about strict aliasing. We // assume that 2*sizeof(size_t) keeps any memory allocation suitably // aligned for the element type. }; /** Get the header for a darray. */ static struct darray *darray_header(const void *da) { return (struct darray *)da - 1; } /** Get the array from a darray header. */ static char *darray_data(struct darray *header) { return (char *)(header + 1); } size_t darray_length(const void *da) { if (da) { return darray_header(da)->length; } else { return 0; } } void *darray_push(void *da, const void *item, size_t size) { struct darray *header; if (da) { header = darray_header(da); } else { header = malloc(sizeof(*header) + size); if (!header) { return NULL; } header->capacity = 1; header->length = 0; } size_t capacity = header->capacity; size_t i = header->length++; if (i >= capacity) { capacity *= 2; header = realloc(header, sizeof(*header) + capacity*size); if (!header) { // This failure will be detected by darray_check() return da; } header->capacity = capacity; } char *data = darray_data(header); memcpy(data + i*size, item, size); return data; } int darray_check(void *da) { if (!da) { return -1; } struct darray *header = darray_header(da); if (header->length <= header->capacity) { return 0; } else { // realloc() failed in darray_push(), so reset the length and report the failure header->length = header->capacity; return -1; } } void darray_free(void *da) { if (da) { free(darray_header(da)); } } bfs-1.5.2/darray.h000066400000000000000000000061771360564154000137430ustar00rootroot00000000000000/**************************************************************************** * bfs * * Copyright (C) 2019 Tavian Barnes * * * * 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. * ****************************************************************************/ /** * A dynamic array library. * * darrays are represented by a simple pointer to the array element type, like * any other array. Behind the scenes, the capacity and current length of the * array are stored along with it. NULL is a valid way to initialize an empty * darray: * * int *darray = NULL; * * To append an element to a darray, use the DARRAY_PUSH macro: * * int e = 42; * if (DARRAY_PUSH(&darray, &e) != 0) { * // Report the error... * } * * The length can be retrieved by darray_length(). Iterating over the array * works like normal arrays: * * for (size_t i = 0; i < darray_length(darray); ++i) { * printf("%d\n", darray[i]); * } * * To free a darray, use darray_free(): * * darray_free(darray); */ #ifndef BFS_DARRAY_H #define BFS_DARRAY_H #include /** * Get the length of a darray. * * @param da * The array in question. * @return * The length of the array. */ size_t darray_length(const void *da); /** * @internal Use DARRAY_PUSH(). * * Push an element into a darray. * * @param da * The array to append to. * @param item * The item to append. * @param size * The size of the item. * @return * The (new) location of the array. */ void *darray_push(void *da, const void *item, size_t size); /** * @internal Use DARRAY_PUSH(). * * Check if the last darray_push() call failed. * * @param da * The darray to check. * @return * 0 on success, -1 on failure. */ int darray_check(void *da); /** * Free a darray. * * @param da * The darray to free. */ void darray_free(void *da); /** * Push an item into a darray. * * @param da * The array to append to. * @param item * A pointer to the item to append. * @return * 0 on success, -1 on failure. */ #define DARRAY_PUSH(da, item) \ (darray_check(*(da) = darray_push(*(da), (item), sizeof(**(da) = *(item))))) #endif // BFS_DARRAY_H bfs-1.5.2/diag.c000066400000000000000000000044671360564154000133600ustar00rootroot00000000000000/**************************************************************************** * bfs * * Copyright (C) 2019 Tavian Barnes * * * * 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. * ****************************************************************************/ #include "diag.h" #include "cmdline.h" #include "color.h" #include "util.h" #include #include #include #include void bfs_error(const struct cmdline *cmdline, const char *format, ...) { va_list args; va_start(args, format); bfs_verror(cmdline, format, args); va_end(args); } void bfs_warning(const struct cmdline *cmdline, const char *format, ...) { va_list args; va_start(args, format); bfs_vwarning(cmdline, format, args); va_end(args); } void bfs_verror(const struct cmdline *cmdline, const char *format, va_list args) { int error = errno; bfs_error_prefix(cmdline); errno = error; cvfprintf(cmdline->cerr, format, args); } void bfs_vwarning(const struct cmdline *cmdline, const char *format, va_list args) { int error = errno; bfs_warning_prefix(cmdline); errno = error; cvfprintf(cmdline->cerr, format, args); } void bfs_error_prefix(const struct cmdline *cmdline) { cfprintf(cmdline->cerr, "${bld}%s:${rs} ${er}error:${rs} ", xbasename(cmdline->argv[0])); } void bfs_warning_prefix(const struct cmdline *cmdline) { cfprintf(cmdline->cerr, "${bld}%s:${rs} ${wr}warning:${rs} ", xbasename(cmdline->argv[0])); } bfs-1.5.2/diag.h000066400000000000000000000040721360564154000133550ustar00rootroot00000000000000/**************************************************************************** * bfs * * Copyright (C) 2019 Tavian Barnes * * * * 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. * ****************************************************************************/ /** * Formatters for diagnostic messages. */ #ifndef BFS_DIAG_H #define BFS_DIAG_H #include "cmdline.h" #include "util.h" #include /** * Shorthand for printing error messages. */ BFS_FORMATTER(2, 3) void bfs_error(const struct cmdline *cmdline, const char *format, ...); /** * Shorthand for printing warning messages. */ BFS_FORMATTER(2, 3) void bfs_warning(const struct cmdline *cmdline, const char *format, ...); /** * bfs_error() variant that takes a va_list. */ void bfs_verror(const struct cmdline *cmdline, const char *format, va_list args); /** * bfs_warning() variant that takes a va_list. */ void bfs_vwarning(const struct cmdline *cmdline, const char *format, va_list args); /** * Print the error message prefix. */ void bfs_error_prefix(const struct cmdline *cmdline); /** * Print the warning message prefix. */ void bfs_warning_prefix(const struct cmdline *cmdline); #endif // BFS_DIAG_H bfs-1.5.2/dstring.c000066400000000000000000000100311360564154000141060ustar00rootroot00000000000000/**************************************************************************** * bfs * * Copyright (C) 2016-2019 Tavian Barnes * * * * 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. * ****************************************************************************/ #include "dstring.h" #include #include #include #include #include /** * The memory representation of a dynamic string. Users get a pointer to data. */ struct dstring { size_t capacity; size_t length; char data[]; }; /** Get the string header from the string data pointer. */ static struct dstring *dstrheader(const char *dstr) { return (struct dstring *)(dstr - offsetof(struct dstring, data)); } /** Get the correct size for a dstring with the given capacity. */ static size_t dstrsize(size_t capacity) { return sizeof(struct dstring) + capacity + 1; } /** Allocate a dstring with the given contents. */ static char *dstralloc_impl(size_t capacity, size_t length, const char *data) { struct dstring *header = malloc(dstrsize(capacity)); if (!header) { return NULL; } header->capacity = capacity; header->length = length; memcpy(header->data, data, length); header->data[length] = '\0'; return header->data; } char *dstralloc(size_t capacity) { return dstralloc_impl(capacity, 0, ""); } char *dstrdup(const char *str) { size_t len = strlen(str); return dstralloc_impl(len, len, str); } char *dstrndup(const char *str, size_t n) { size_t len = strnlen(str, n); return dstralloc_impl(len, len, str); } size_t dstrlen(const char *dstr) { return dstrheader(dstr)->length; } int dstreserve(char **dstr, size_t capacity) { struct dstring *header = dstrheader(*dstr); if (capacity > header->capacity) { capacity *= 2; header = realloc(header, dstrsize(capacity)); if (!header) { return -1; } header->capacity = capacity; *dstr = header->data; } return 0; } int dstresize(char **dstr, size_t length) { if (dstreserve(dstr, length) != 0) { return -1; } struct dstring *header = dstrheader(*dstr); header->length = length; header->data[length] = '\0'; return 0; } /** Common implementation of dstr{cat,ncat,app}. */ static int dstrcat_impl(char **dest, const char *src, size_t srclen) { size_t oldlen = dstrlen(*dest); size_t newlen = oldlen + srclen; if (dstresize(dest, newlen) != 0) { return -1; } memcpy(*dest + oldlen, src, srclen); return 0; } int dstrcat(char **dest, const char *src) { return dstrcat_impl(dest, src, strlen(src)); } int dstrncat(char **dest, const char *src, size_t n) { return dstrcat_impl(dest, src, strnlen(src, n)); } int dstrapp(char **str, char c) { return dstrcat_impl(str, &c, 1); } char *dstrprintf(const char *format, ...) { va_list args; va_start(args, format); int len = vsnprintf(NULL, 0, format, args); va_end(args); assert(len > 0); char *str = dstralloc(len); if (!str) { return NULL; } va_start(args, format); len = vsnprintf(str, len + 1, format, args); va_end(args); struct dstring *header = dstrheader(str); assert(len == header->capacity); header->length = len; return str; } void dstrfree(char *dstr) { if (dstr) { free(dstrheader(dstr)); } } bfs-1.5.2/dstring.h000066400000000000000000000071641360564154000141300ustar00rootroot00000000000000/**************************************************************************** * bfs * * Copyright (C) 2016-2019 Tavian Barnes * * * * 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. * ****************************************************************************/ /** * A dynamic string library. */ #ifndef BFS_DSTRING_H #define BFS_DSTRING_H #include "util.h" #include /** * Allocate a dynamic string. * * @param capacity * The initial capacity of the string. */ char *dstralloc(size_t capacity); /** * Create a dynamic copy of a string. * * @param str * The NUL-terminated string to copy. */ char *dstrdup(const char *str); /** * Create a length-limited dynamic copy of a string. * * @param str * The string to copy. * @param n * The maximum number of characters to copy from str. */ char *dstrndup(const char *str, size_t n); /** * Get a dynamic string's length. * * @param dstr * The string to measure. * @return The length of dstr. */ size_t dstrlen(const char *dstr); /** * Reserve some capacity in a dynamic string. * * @param dstr * The dynamic string to preallocate. * @param capacity * The new capacity for the string. * @return 0 on success, -1 on failure. */ int dstreserve(char **dstr, size_t capacity); /** * Resize a dynamic string. * * @param dstr * The dynamic string to resize. * @param length * The new length for the dynamic string. * @return 0 on success, -1 on failure. */ int dstresize(char **dstr, size_t length); /** * Append to a dynamic string. * * @param dest * The destination dynamic string. * @param src * The string to append. * @return 0 on success, -1 on failure. */ int dstrcat(char **dest, const char *src); /** * Append to a dynamic string. * * @param dest * The destination dynamic string. * @param src * The string to append. * @param n * The maximum number of characters to take from src. * @return 0 on success, -1 on failure. */ int dstrncat(char **dest, const char *src, size_t n); /** * Append a single character to a dynamic string. * * @param str * The string to append to. * @param c * The character to append. * @return 0 on success, -1 on failure. */ int dstrapp(char **str, char c); /** * Create a dynamic string from a format string. * * @param format * The format string to fill in. * @param ... * Any arguments for the format string. * @return * The created string, or NULL on failure. */ BFS_FORMATTER(1, 2) char *dstrprintf(const char *format, ...); /** * Free a dynamic string. * * @param dstr * The string to free. */ void dstrfree(char *dstr); #endif // BFS_DSTRING_H bfs-1.5.2/eval.c000066400000000000000000000736641360564154000134100ustar00rootroot00000000000000/**************************************************************************** * bfs * * Copyright (C) 2015-2020 Tavian Barnes * * * * 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. * ****************************************************************************/ /** * Implementation of all the literal expressions. */ #include "eval.h" #include "bftw.h" #include "cmdline.h" #include "color.h" #include "darray.h" #include "diag.h" #include "dstring.h" #include "exec.h" #include "fsade.h" #include "mtab.h" #include "printf.h" #include "stat.h" #include "trie.h" #include "util.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct eval_state { /** Data about the current file. */ const struct BFTW *ftwbuf; /** The parsed command line. */ const struct cmdline *cmdline; /** The bftw() callback return value. */ enum bftw_action action; /** The eval_cmdline() return value. */ int *ret; /** Whether to quit immediately. */ bool quit; }; /** * Print an error message. */ BFS_FORMATTER(2, 3) static void eval_error(struct eval_state *state, const char *format, ...) { int error = errno; const struct cmdline *cmdline = state->cmdline; CFILE *cerr = cmdline->cerr; bfs_error(cmdline, "%pP: ", state->ftwbuf); va_list args; va_start(args, format); errno = error; cvfprintf(cerr, format, args); va_end(args); } /** * Check if an error should be ignored. */ static bool eval_should_ignore(const struct eval_state *state, int error) { return state->cmdline->ignore_races && is_nonexistence_error(error) && state->ftwbuf->depth > 0; } /** * Report an error that occurs during evaluation. */ static void eval_report_error(struct eval_state *state) { if (!eval_should_ignore(state, errno)) { eval_error(state, "%m.\n"); *state->ret = EXIT_FAILURE; } } /** * Perform a bfs_stat() call if necessary. */ static const struct bfs_stat *eval_stat(struct eval_state *state) { const struct BFTW *ftwbuf = state->ftwbuf; const struct bfs_stat *ret = bftw_stat(ftwbuf, ftwbuf->stat_flags); if (!ret) { eval_report_error(state); } return ret; } /** * Get the difference (in seconds) between two struct timespecs. */ static time_t timespec_diff(const struct timespec *lhs, const struct timespec *rhs) { time_t ret = lhs->tv_sec - rhs->tv_sec; if (lhs->tv_nsec < rhs->tv_nsec) { --ret; } return ret; } bool expr_cmp(const struct expr *expr, long long n) { switch (expr->cmp_flag) { case CMP_EXACT: return n == expr->idata; case CMP_LESS: return n < expr->idata; case CMP_GREATER: return n > expr->idata; } return false; } /** * -true test. */ bool eval_true(const struct expr *expr, struct eval_state *state) { return true; } /** * -false test. */ bool eval_false(const struct expr *expr, struct eval_state *state) { return false; } /** * -executable, -readable, -writable tests. */ bool eval_access(const struct expr *expr, struct eval_state *state) { const struct BFTW *ftwbuf = state->ftwbuf; return xfaccessat(ftwbuf->at_fd, ftwbuf->at_path, expr->idata) == 0; } /** * -acl test. */ bool eval_acl(const struct expr *expr, struct eval_state *state) { int ret = bfs_check_acl(state->ftwbuf); if (ret >= 0) { return ret; } else { eval_report_error(state); return false; } } /** * -capable test. */ bool eval_capable(const struct expr *expr, struct eval_state *state) { int ret = bfs_check_capabilities(state->ftwbuf); if (ret >= 0) { return ret; } else { eval_report_error(state); return false; } } /** * Get the given timespec field out of a stat buffer. */ static const struct timespec *eval_stat_time(const struct bfs_stat *statbuf, enum bfs_stat_field field, struct eval_state *state) { const struct timespec *ret = bfs_stat_time(statbuf, field); if (!ret) { eval_error(state, "Couldn't get file %s: %m.\n", bfs_stat_field_name(field)); *state->ret = EXIT_FAILURE; } return ret; } /** * -[aBcm]?newer tests. */ bool eval_newer(const struct expr *expr, struct eval_state *state) { const struct bfs_stat *statbuf = eval_stat(state); if (!statbuf) { return false; } const struct timespec *time = eval_stat_time(statbuf, expr->stat_field, state); if (!time) { return false; } return time->tv_sec > expr->reftime.tv_sec || (time->tv_sec == expr->reftime.tv_sec && time->tv_nsec > expr->reftime.tv_nsec); } /** * -[aBcm]{min,time} tests. */ bool eval_time(const struct expr *expr, struct eval_state *state) { const struct bfs_stat *statbuf = eval_stat(state); if (!statbuf) { return false; } const struct timespec *time = eval_stat_time(statbuf, expr->stat_field, state); if (!time) { return false; } time_t diff = timespec_diff(&expr->reftime, time); switch (expr->time_unit) { case MINUTES: diff /= 60; break; case DAYS: diff /= 60*60*24; break; } return expr_cmp(expr, diff); } /** * -used test. */ bool eval_used(const struct expr *expr, struct eval_state *state) { const struct bfs_stat *statbuf = eval_stat(state); if (!statbuf) { return false; } const struct timespec *atime = eval_stat_time(statbuf, BFS_STAT_ATIME, state); const struct timespec *ctime = eval_stat_time(statbuf, BFS_STAT_CTIME, state); if (!atime || !ctime) { return false; } time_t diff = timespec_diff(atime, ctime); diff /= 60*60*24; return expr_cmp(expr, diff); } /** * -gid test. */ bool eval_gid(const struct expr *expr, struct eval_state *state) { const struct bfs_stat *statbuf = eval_stat(state); if (!statbuf) { return false; } return expr_cmp(expr, statbuf->gid); } /** * -uid test. */ bool eval_uid(const struct expr *expr, struct eval_state *state) { const struct bfs_stat *statbuf = eval_stat(state); if (!statbuf) { return false; } return expr_cmp(expr, statbuf->uid); } /** * -nogroup test. */ bool eval_nogroup(const struct expr *expr, struct eval_state *state) { const struct bfs_stat *statbuf = eval_stat(state); if (!statbuf) { return false; } errno = 0; if (getgrgid(statbuf->gid) == NULL) { if (errno == 0) { return true; } else { eval_report_error(state); } } return false; } /** * -nouser test. */ bool eval_nouser(const struct expr *expr, struct eval_state *state) { const struct bfs_stat *statbuf = eval_stat(state); if (!statbuf) { return false; } errno = 0; if (getpwuid(statbuf->uid) == NULL) { if (errno == 0) { return true; } else { eval_report_error(state); } } return false; } /** * -delete action. */ bool eval_delete(const struct expr *expr, struct eval_state *state) { const struct BFTW *ftwbuf = state->ftwbuf; // Don't try to delete the current directory if (strcmp(ftwbuf->path, ".") == 0) { return true; } int flag = 0; // We need to know the actual type of the path, not what it points to enum bftw_typeflag type = bftw_typeflag(ftwbuf, BFS_STAT_NOFOLLOW); if (type == BFTW_DIR) { flag |= AT_REMOVEDIR; } else if (type == BFTW_ERROR) { eval_report_error(state); return false; } if (unlinkat(ftwbuf->at_fd, ftwbuf->at_path, flag) != 0) { eval_report_error(state); return false; } return true; } /** Finish any pending -exec ... + operations. */ static int eval_exec_finish(const struct expr *expr, const struct cmdline *cmdline) { int ret = 0; if (expr->execbuf && bfs_exec_finish(expr->execbuf) != 0) { if (errno != 0) { bfs_error(cmdline, "%s %s: %m.\n", expr->argv[0], expr->argv[1]); } ret = -1; } if (expr->lhs && eval_exec_finish(expr->lhs, cmdline) != 0) { ret = -1; } if (expr->rhs && eval_exec_finish(expr->rhs, cmdline) != 0) { ret = -1; } return ret; } /** * -exec[dir]/-ok[dir] actions. */ bool eval_exec(const struct expr *expr, struct eval_state *state) { bool ret = bfs_exec(expr->execbuf, state->ftwbuf) == 0; if (errno != 0) { eval_error(state, "%s %s: %m.\n", expr->argv[0], expr->argv[1]); *state->ret = EXIT_FAILURE; } return ret; } /** * -exit action. */ bool eval_exit(const struct expr *expr, struct eval_state *state) { state->action = BFTW_STOP; *state->ret = expr->idata; state->quit = true; return true; } /** * -depth N test. */ bool eval_depth(const struct expr *expr, struct eval_state *state) { return expr_cmp(expr, state->ftwbuf->depth); } /** * -empty test. */ bool eval_empty(const struct expr *expr, struct eval_state *state) { bool ret = false; const struct BFTW *ftwbuf = state->ftwbuf; if (ftwbuf->typeflag == BFTW_DIR) { int dfd = openat(ftwbuf->at_fd, ftwbuf->at_path, O_RDONLY | O_CLOEXEC | O_DIRECTORY); if (dfd < 0) { eval_report_error(state); goto done; } DIR *dir = fdopendir(dfd); if (!dir) { eval_report_error(state); close(dfd); goto done; } struct dirent *de; if (xreaddir(dir, &de) == 0) { ret = !de; } else { eval_report_error(state); } closedir(dir); } else { const struct bfs_stat *statbuf = eval_stat(state); if (statbuf) { ret = statbuf->size == 0; } } done: return ret; } /** * -fstype test. */ bool eval_fstype(const struct expr *expr, struct eval_state *state) { const struct bfs_stat *statbuf = eval_stat(state); if (!statbuf) { return false; } const char *type = bfs_fstype(state->cmdline->mtab, statbuf); return strcmp(type, expr->sdata) == 0; } /** * -hidden test. */ bool eval_hidden(const struct expr *expr, struct eval_state *state) { const struct BFTW *ftwbuf = state->ftwbuf; return ftwbuf->nameoff > 0 && ftwbuf->path[ftwbuf->nameoff] == '.'; } /** * -nohidden action. */ bool eval_nohidden(const struct expr *expr, struct eval_state *state) { if (eval_hidden(expr, state)) { eval_prune(expr, state); return false; } else { return true; } } /** * -inum test. */ bool eval_inum(const struct expr *expr, struct eval_state *state) { const struct bfs_stat *statbuf = eval_stat(state); if (!statbuf) { return false; } return expr_cmp(expr, statbuf->ino); } /** * -links test. */ bool eval_links(const struct expr *expr, struct eval_state *state) { const struct bfs_stat *statbuf = eval_stat(state); if (!statbuf) { return false; } return expr_cmp(expr, statbuf->nlink); } /** * -i?lname test. */ bool eval_lname(const struct expr *expr, struct eval_state *state) { bool ret = false; char *name = NULL; const struct BFTW *ftwbuf = state->ftwbuf; if (ftwbuf->typeflag != BFTW_LNK) { goto done; } const struct bfs_stat *statbuf = eval_stat(state); if (!statbuf) { goto done; } name = xreadlinkat(ftwbuf->at_fd, ftwbuf->at_path, statbuf->size); if (!name) { eval_report_error(state); goto done; } ret = fnmatch(expr->sdata, name, expr->idata) == 0; done: free(name); return ret; } /** * -i?name test. */ bool eval_name(const struct expr *expr, struct eval_state *state) { const struct BFTW *ftwbuf = state->ftwbuf; const char *name = ftwbuf->path + ftwbuf->nameoff; char *copy = NULL; if (ftwbuf->depth == 0) { // Any trailing slashes are not part of the name. This can only // happen for the root path. const char *slash = strchr(name, '/'); if (slash && slash > name) { copy = strndup(name, slash - name); if (!copy) { eval_report_error(state); return false; } name = copy; } } bool ret = fnmatch(expr->sdata, name, expr->idata) == 0; free(copy); return ret; } /** * -i?path test. */ bool eval_path(const struct expr *expr, struct eval_state *state) { const struct BFTW *ftwbuf = state->ftwbuf; return fnmatch(expr->sdata, ftwbuf->path, expr->idata) == 0; } /** * -perm test. */ bool eval_perm(const struct expr *expr, struct eval_state *state) { const struct bfs_stat *statbuf = eval_stat(state); if (!statbuf) { return false; } mode_t mode = statbuf->mode; mode_t target; if (state->ftwbuf->typeflag == BFTW_DIR) { target = expr->dir_mode; } else { target = expr->file_mode; } switch (expr->mode_cmp) { case MODE_EXACT: return (mode & 07777) == target; case MODE_ALL: return (mode & target) == target; case MODE_ANY: return !(mode & target) == !target; } return false; } /** * -f?ls action. */ bool eval_fls(const struct expr *expr, struct eval_state *state) { CFILE *cfile = expr->cfile; FILE *file = cfile->file; const struct BFTW *ftwbuf = state->ftwbuf; const struct bfs_stat *statbuf = eval_stat(state); if (!statbuf) { goto done; } uintmax_t ino = statbuf->ino; uintmax_t blocks = ((uintmax_t)statbuf->blocks*BFS_STAT_BLKSIZE + 1023)/1024; char mode[11]; format_mode(statbuf->mode, mode); char acl = bfs_check_acl(ftwbuf) > 0 ? '+' : ' '; uintmax_t nlink = statbuf->nlink; if (fprintf(file, "%9ju %6ju %s%c %2ju ", ino, blocks, mode, acl, nlink) < 0) { goto error; } uintmax_t uid = statbuf->uid; struct passwd *pwd = getpwuid(uid); if (pwd) { if (fprintf(file, " %-8s", pwd->pw_name) < 0) { goto error; } } else { if (fprintf(file, " %-8ju", uid) < 0) { goto error; } } uintmax_t gid = statbuf->gid; struct group *grp = getgrgid(gid); if (grp) { if (fprintf(file, " %-8s", grp->gr_name) < 0) { goto error; } } else { if (fprintf(file, " %-8ju", gid) < 0) { goto error; } } if (ftwbuf->typeflag & (BFTW_BLK | BFTW_CHR)) { int ma = bfs_major(statbuf->rdev); int mi = bfs_minor(statbuf->rdev); if (fprintf(file, " %3d, %3d", ma, mi) < 0) { goto error; } } else { uintmax_t size = statbuf->size; if (fprintf(file, " %8ju", size) < 0) { goto error; } } time_t time = statbuf->mtime.tv_sec; time_t now = expr->reftime.tv_sec; time_t six_months_ago = now - 6*30*24*60*60; time_t tomorrow = now + 24*60*60; struct tm tm; if (xlocaltime(&time, &tm) != 0) { goto error; } char time_str[256]; const char *time_format = "%b %e %H:%M"; if (time <= six_months_ago || time >= tomorrow) { time_format = "%b %e %Y"; } if (!strftime(time_str, sizeof(time_str), time_format, &tm)) { errno = EOVERFLOW; goto error; } if (fprintf(file, " %s", time_str) < 0) { goto error; } if (cfprintf(cfile, " %pP", ftwbuf) < 0) { goto error; } if (ftwbuf->typeflag == BFTW_LNK) { if (cfprintf(cfile, " -> %pL", ftwbuf) < 0) { goto error; } } if (fputc('\n', file) == EOF) { goto error; } done: return true; error: eval_report_error(state); return true; } /** * -f?print action. */ bool eval_fprint(const struct expr *expr, struct eval_state *state) { if (cfprintf(expr->cfile, "%pP\n", state->ftwbuf) < 0) { eval_report_error(state); } return true; } /** * -f?print0 action. */ bool eval_fprint0(const struct expr *expr, struct eval_state *state) { const char *path = state->ftwbuf->path; size_t length = strlen(path) + 1; if (fwrite(path, 1, length, expr->cfile->file) != length) { eval_report_error(state); } return true; } /** * -f?printf action. */ bool eval_fprintf(const struct expr *expr, struct eval_state *state) { if (bfs_printf(expr->cfile->file, expr->printf, state->ftwbuf) != 0) { eval_report_error(state); } return true; } /** * -printx action. */ bool eval_fprintx(const struct expr *expr, struct eval_state *state) { FILE *file = expr->cfile->file; const char *path = state->ftwbuf->path; while (true) { size_t span = strcspn(path, " \t\n\\$'\"`"); if (fwrite(path, 1, span, file) != span) { goto error; } path += span; char c = path[0]; if (!c) { break; } char escaped[] = {'\\', c}; if (fwrite(escaped, 1, sizeof(escaped), file) != sizeof(escaped)) { goto error; } ++path; } if (fputc('\n', file) == EOF) { goto error; } return true; error: eval_report_error(state); return true; } /** * -prune action. */ bool eval_prune(const struct expr *expr, struct eval_state *state) { state->action = BFTW_PRUNE; return true; } /** * -quit action. */ bool eval_quit(const struct expr *expr, struct eval_state *state) { state->action = BFTW_STOP; state->quit = true; return true; } /** * -i?regex test. */ bool eval_regex(const struct expr *expr, struct eval_state *state) { const char *path = state->ftwbuf->path; size_t len = strlen(path); regmatch_t match = { .rm_so = 0, .rm_eo = len, }; int flags = 0; #ifdef REG_STARTEND flags |= REG_STARTEND; #endif int err = regexec(expr->regex, path, 1, &match, flags); if (err == 0) { return match.rm_so == 0 && match.rm_eo == len; } else if (err != REG_NOMATCH) { char *str = xregerror(err, expr->regex); if (str) { eval_error(state, "%s.\n", str); free(str); } else { perror("xregerror()"); } *state->ret = EXIT_FAILURE; } return false; } /** * -samefile test. */ bool eval_samefile(const struct expr *expr, struct eval_state *state) { const struct bfs_stat *statbuf = eval_stat(state); if (!statbuf) { return false; } return statbuf->dev == expr->dev && statbuf->ino == expr->ino; } /** * -size test. */ bool eval_size(const struct expr *expr, struct eval_state *state) { const struct bfs_stat *statbuf = eval_stat(state); if (!statbuf) { return false; } static const off_t scales[] = { [SIZE_BLOCKS] = 512, [SIZE_BYTES] = 1, [SIZE_WORDS] = 2, [SIZE_KB] = 1024, [SIZE_MB] = 1024LL*1024, [SIZE_GB] = 1024LL*1024*1024, [SIZE_TB] = 1024LL*1024*1024*1024, [SIZE_PB] = 1024LL*1024*1024*1024*1024, }; off_t scale = scales[expr->size_unit]; off_t size = (statbuf->size + scale - 1)/scale; // Round up return expr_cmp(expr, size); } /** * -sparse test. */ bool eval_sparse(const struct expr *expr, struct eval_state *state) { const struct bfs_stat *statbuf = eval_stat(state); if (!statbuf) { return false; } blkcnt_t expected = (statbuf->size + BFS_STAT_BLKSIZE - 1)/BFS_STAT_BLKSIZE; return statbuf->blocks < expected; } /** * -type test. */ bool eval_type(const struct expr *expr, struct eval_state *state) { return state->ftwbuf->typeflag & expr->idata; } /** * -xattr test. */ bool eval_xattr(const struct expr *expr, struct eval_state *state) { int ret = bfs_check_xattrs(state->ftwbuf); if (ret >= 0) { return ret; } else { eval_report_error(state); return false; } } /** * -xtype test. */ bool eval_xtype(const struct expr *expr, struct eval_state *state) { const struct BFTW *ftwbuf = state->ftwbuf; enum bfs_stat_flag flags = ftwbuf->stat_flags ^ (BFS_STAT_NOFOLLOW | BFS_STAT_TRYFOLLOW); enum bftw_typeflag type = bftw_typeflag(ftwbuf, flags); if (type == BFTW_ERROR) { eval_report_error(state); return false; } else { return type & expr->idata; } } #if _POSIX_MONOTONIC_CLOCK > 0 # define BFS_CLOCK CLOCK_MONOTONIC #elif _POSIX_TIMERS > 0 # define BFS_CLOCK CLOCK_REALTIME #endif /** * Call clock_gettime(), if available. */ static int eval_gettime(struct timespec *ts) { #ifdef BFS_CLOCK int ret = clock_gettime(BFS_CLOCK, ts); if (ret != 0) { perror("clock_gettime()"); } return ret; #else return -1; #endif } /** * Record the time that elapsed evaluating an expression. */ static void add_elapsed(struct expr *expr, const struct timespec *start, const struct timespec *end) { expr->elapsed.tv_sec += end->tv_sec - start->tv_sec; expr->elapsed.tv_nsec += end->tv_nsec - start->tv_nsec; if (expr->elapsed.tv_nsec < 0) { expr->elapsed.tv_nsec += 1000000000L; --expr->elapsed.tv_sec; } else if (expr->elapsed.tv_nsec >= 1000000000L) { expr->elapsed.tv_nsec -= 1000000000L; ++expr->elapsed.tv_sec; } } /** * Evaluate an expression. */ static bool eval_expr(struct expr *expr, struct eval_state *state) { struct timespec start, end; bool time = state->cmdline->debug & DEBUG_RATES; if (time) { if (eval_gettime(&start) != 0) { time = false; } } assert(!state->quit); bool ret = expr->eval(expr, state); if (time) { if (eval_gettime(&end) == 0) { add_elapsed(expr, &start, &end); } } ++expr->evaluations; if (ret) { ++expr->successes; } if (expr_never_returns(expr)) { assert(state->quit); } else if (!state->quit) { assert(!expr->always_true || ret); assert(!expr->always_false || !ret); } return ret; } /** * Evaluate a negation. */ bool eval_not(const struct expr *expr, struct eval_state *state) { return !eval_expr(expr->rhs, state); } /** * Evaluate a conjunction. */ bool eval_and(const struct expr *expr, struct eval_state *state) { if (!eval_expr(expr->lhs, state)) { return false; } if (state->quit) { return false; } return eval_expr(expr->rhs, state); } /** * Evaluate a disjunction. */ bool eval_or(const struct expr *expr, struct eval_state *state) { if (eval_expr(expr->lhs, state)) { return true; } if (state->quit) { return false; } return eval_expr(expr->rhs, state); } /** * Evaluate the comma operator. */ bool eval_comma(const struct expr *expr, struct eval_state *state) { eval_expr(expr->lhs, state); if (state->quit) { return false; } return eval_expr(expr->rhs, state); } /** Check if we've seen a file before. */ static bool eval_file_unique(struct eval_state *state, struct trie *seen) { const struct bfs_stat *statbuf = eval_stat(state); if (!statbuf) { return false; } bfs_file_id id; bfs_stat_id(statbuf, &id); struct trie_leaf *leaf = trie_insert_mem(seen, id, sizeof(id)); if (!leaf) { eval_report_error(state); return false; } if (leaf->value) { state->action = BFTW_PRUNE; return false; } else { leaf->value = leaf; return true; } } #define DEBUG_FLAG(flags, flag) \ do { \ if ((flags & flag) || flags == flag) { \ fputs(#flag, stderr); \ flags ^= flag; \ if (flags) { \ fputs(" | ", stderr); \ } \ } \ } while (0) /** * Log a stat() call. */ static void debug_stat(const struct BFTW *ftwbuf, const struct bftw_stat *cache, enum bfs_stat_flag flags) { fprintf(stderr, "bfs_stat("); if (ftwbuf->at_fd == AT_FDCWD) { fprintf(stderr, "AT_FDCWD"); } else { size_t baselen = strlen(ftwbuf->path) - strlen(ftwbuf->at_path); fprintf(stderr, "\""); fwrite(ftwbuf->path, 1, baselen, stderr); fprintf(stderr, "\""); } fprintf(stderr, ", \"%s\", ", ftwbuf->at_path); DEBUG_FLAG(flags, BFS_STAT_FOLLOW); DEBUG_FLAG(flags, BFS_STAT_NOFOLLOW); DEBUG_FLAG(flags, BFS_STAT_TRYFOLLOW); fprintf(stderr, ") == %d", cache->buf ? 0 : -1); if (cache->error) { fprintf(stderr, " [%d]", cache->error); } fprintf(stderr, "\n"); } /** * Log any stat() calls that happened. */ static void debug_stats(const struct BFTW *ftwbuf) { const struct bfs_stat *statbuf = ftwbuf->stat_cache.buf; if (statbuf || ftwbuf->stat_cache.error) { debug_stat(ftwbuf, &ftwbuf->stat_cache, BFS_STAT_FOLLOW); } const struct bfs_stat *lstatbuf = ftwbuf->lstat_cache.buf; if ((lstatbuf && lstatbuf != statbuf) || ftwbuf->lstat_cache.error) { debug_stat(ftwbuf, &ftwbuf->lstat_cache, BFS_STAT_NOFOLLOW); } } /** * Dump the bftw_typeflag for -D search. */ static const char *dump_bftw_typeflag(enum bftw_typeflag type) { #define DUMP_BFTW_TYPEFLAG_CASE(flag) \ case flag: \ return #flag switch (type) { DUMP_BFTW_TYPEFLAG_CASE(BFTW_BLK); DUMP_BFTW_TYPEFLAG_CASE(BFTW_CHR); DUMP_BFTW_TYPEFLAG_CASE(BFTW_DIR); DUMP_BFTW_TYPEFLAG_CASE(BFTW_DOOR); DUMP_BFTW_TYPEFLAG_CASE(BFTW_FIFO); DUMP_BFTW_TYPEFLAG_CASE(BFTW_LNK); DUMP_BFTW_TYPEFLAG_CASE(BFTW_PORT); DUMP_BFTW_TYPEFLAG_CASE(BFTW_REG); DUMP_BFTW_TYPEFLAG_CASE(BFTW_SOCK); DUMP_BFTW_TYPEFLAG_CASE(BFTW_WHT); DUMP_BFTW_TYPEFLAG_CASE(BFTW_ERROR); default: DUMP_BFTW_TYPEFLAG_CASE(BFTW_UNKNOWN); } } #define DUMP_BFTW_MAP(value) [value] = #value /** * Dump the bftw_visit for -D search. */ static const char *dump_bftw_visit(enum bftw_visit visit) { static const char *visits[] = { DUMP_BFTW_MAP(BFTW_PRE), DUMP_BFTW_MAP(BFTW_POST), }; return visits[visit]; } /** * Dump the bftw_action for -D search. */ static const char *dump_bftw_action(enum bftw_action action) { static const char *actions[] = { DUMP_BFTW_MAP(BFTW_CONTINUE), DUMP_BFTW_MAP(BFTW_PRUNE), DUMP_BFTW_MAP(BFTW_STOP), }; return actions[action]; } /** * Type passed as the argument to the bftw() callback. */ struct callback_args { /** The parsed command line. */ const struct cmdline *cmdline; /** The set of seen files. */ struct trie *seen; /** Eventual return value from eval_cmdline(). */ int ret; }; /** * bftw() callback. */ static enum bftw_action cmdline_callback(const struct BFTW *ftwbuf, void *ptr) { struct callback_args *args = ptr; const struct cmdline *cmdline = args->cmdline; struct eval_state state; state.ftwbuf = ftwbuf; state.cmdline = cmdline; state.action = BFTW_CONTINUE; state.ret = &args->ret; state.quit = false; if (ftwbuf->typeflag == BFTW_ERROR) { if (!eval_should_ignore(&state, ftwbuf->error)) { args->ret = EXIT_FAILURE; eval_error(&state, "%s.\n", strerror(ftwbuf->error)); } state.action = BFTW_PRUNE; goto done; } if (cmdline->unique && ftwbuf->visit == BFTW_PRE) { if (!eval_file_unique(&state, args->seen)) { goto done; } } if (cmdline->xargs_safe && strpbrk(ftwbuf->path, " \t\n\'\"\\")) { args->ret = EXIT_FAILURE; eval_error(&state, "Path is not safe for xargs.\n"); state.action = BFTW_PRUNE; goto done; } if (cmdline->maxdepth < 0 || ftwbuf->depth >= cmdline->maxdepth) { state.action = BFTW_PRUNE; } // In -depth mode, only handle directories on the BFTW_POST visit enum bftw_visit expected_visit = BFTW_PRE; if ((cmdline->flags & BFTW_DEPTH) && (cmdline->strategy == BFTW_IDS || ftwbuf->typeflag == BFTW_DIR) && ftwbuf->depth < cmdline->maxdepth) { expected_visit = BFTW_POST; } if (ftwbuf->visit == expected_visit && ftwbuf->depth >= cmdline->mindepth && ftwbuf->depth <= cmdline->maxdepth) { eval_expr(cmdline->expr, &state); } done: if (cmdline->debug & DEBUG_STAT) { debug_stats(ftwbuf); } if (cmdline->debug & DEBUG_SEARCH) { fprintf(stderr, "cmdline_callback({\n"); fprintf(stderr, "\t.path = \"%s\",\n", ftwbuf->path); fprintf(stderr, "\t.root = \"%s\",\n", ftwbuf->root); fprintf(stderr, "\t.depth = %zu,\n", ftwbuf->depth); fprintf(stderr, "\t.visit = %s,\n", dump_bftw_visit(ftwbuf->visit)); fprintf(stderr, "\t.typeflag = %s,\n", dump_bftw_typeflag(ftwbuf->typeflag)); fprintf(stderr, "\t.error = %d,\n", ftwbuf->error); fprintf(stderr, "}) == %s\n", dump_bftw_action(state.action)); } return state.action; } /** * Infer the number of open file descriptors we're allowed to have. */ static int infer_fdlimit(const struct cmdline *cmdline) { int ret = 4096; struct rlimit rl; if (getrlimit(RLIMIT_NOFILE, &rl) == 0) { if (rl.rlim_cur != RLIM_INFINITY) { ret = rl.rlim_cur; } } // 3 for std{in,out,err} int nopen = 3 + cmdline->nopen_files; // Check /proc/self/fd for the current number of open fds, if possible // (we may have inherited more than just the standard ones) DIR *dir = opendir("/proc/self/fd"); if (!dir) { dir = opendir("/dev/fd"); } if (dir) { // Account for 'dir' itself nopen = -1; struct dirent *de; while (xreaddir(dir, &de) == 0 && de) { ++nopen; } closedir(dir); } ret -= nopen; ret -= cmdline->expr->persistent_fds; ret -= cmdline->expr->ephemeral_fds; // bftw() needs at least 2 available fds if (ret < 2) { ret = 2; } return ret; } /** * Dump the bftw() flags for -D search. */ static void dump_bftw_flags(enum bftw_flags flags) { DEBUG_FLAG(flags, 0); DEBUG_FLAG(flags, BFTW_STAT); DEBUG_FLAG(flags, BFTW_RECOVER); DEBUG_FLAG(flags, BFTW_DEPTH); DEBUG_FLAG(flags, BFTW_COMFOLLOW); DEBUG_FLAG(flags, BFTW_LOGICAL); DEBUG_FLAG(flags, BFTW_DETECT_CYCLES); DEBUG_FLAG(flags, BFTW_MOUNT); DEBUG_FLAG(flags, BFTW_XDEV); assert(!flags); } /** * Dump the bftw_strategy for -D search. */ static const char *dump_bftw_strategy(enum bftw_strategy strategy) { static const char *strategies[] = { DUMP_BFTW_MAP(BFTW_BFS), DUMP_BFTW_MAP(BFTW_DFS), DUMP_BFTW_MAP(BFTW_IDS), }; return strategies[strategy]; } /** * Evaluate the command line. */ int eval_cmdline(const struct cmdline *cmdline) { if (!cmdline->expr) { return EXIT_SUCCESS; } struct callback_args args = { .cmdline = cmdline, .ret = EXIT_SUCCESS, }; struct trie seen; if (cmdline->unique) { trie_init(&seen); args.seen = &seen; } struct bftw_args bftw_args = { .paths = cmdline->paths, .npaths = darray_length(cmdline->paths), .callback = cmdline_callback, .ptr = &args, .nopenfd = infer_fdlimit(cmdline), .flags = cmdline->flags, .strategy = cmdline->strategy, .mtab = cmdline->mtab, }; if (cmdline->debug & DEBUG_SEARCH) { fprintf(stderr, "bftw({\n"); fprintf(stderr, "\t.paths = {\n"); for (size_t i = 0; i < bftw_args.npaths; ++i) { fprintf(stderr, "\t\t\"%s\",\n", bftw_args.paths[i]); } fprintf(stderr, "\t},\n"); fprintf(stderr, "\t.npaths = %zu,\n", bftw_args.npaths); fprintf(stderr, "\t.callback = cmdline_callback,\n"); fprintf(stderr, "\t.ptr = &args,\n"); fprintf(stderr, "\t.nopenfd = %d,\n", bftw_args.nopenfd); fprintf(stderr, "\t.flags = "); dump_bftw_flags(bftw_args.flags); fprintf(stderr, ",\n\t.strategy = %s,\n", dump_bftw_strategy(bftw_args.strategy)); fprintf(stderr, "\t.mtab = "); if (bftw_args.mtab) { fprintf(stderr, "cmdline->mtab"); } else { fprintf(stderr, "NULL"); } fprintf(stderr, ",\n})\n"); } if (bftw(&bftw_args) != 0) { args.ret = EXIT_FAILURE; perror("bftw()"); } if (eval_exec_finish(cmdline->expr, cmdline) != 0) { args.ret = EXIT_FAILURE; } if (cmdline->debug & DEBUG_RATES) { dump_cmdline(cmdline, true); } if (cmdline->unique) { trie_destroy(&seen); } return args.ret; } bfs-1.5.2/eval.h000066400000000000000000000104761360564154000134050ustar00rootroot00000000000000/**************************************************************************** * bfs * * Copyright (C) 2015-2018 Tavian Barnes * * * * 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. * ****************************************************************************/ /** * The evaluation functions that implement literal expressions like -name, * -print, etc. */ #ifndef BFS_EVAL_H #define BFS_EVAL_H #include "expr.h" // Predicate evaluation functions bool eval_true(const struct expr *expr, struct eval_state *state); bool eval_false(const struct expr *expr, struct eval_state *state); bool eval_access(const struct expr *expr, struct eval_state *state); bool eval_acl(const struct expr *expr, struct eval_state *state); bool eval_capable(const struct expr *expr, struct eval_state *state); bool eval_perm(const struct expr *expr, struct eval_state *state); bool eval_xattr(const struct expr *expr, struct eval_state *state); bool eval_newer(const struct expr *expr, struct eval_state *state); bool eval_time(const struct expr *expr, struct eval_state *state); bool eval_used(const struct expr *expr, struct eval_state *state); bool eval_gid(const struct expr *expr, struct eval_state *state); bool eval_uid(const struct expr *expr, struct eval_state *state); bool eval_nogroup(const struct expr *expr, struct eval_state *state); bool eval_nouser(const struct expr *expr, struct eval_state *state); bool eval_depth(const struct expr *expr, struct eval_state *state); bool eval_empty(const struct expr *expr, struct eval_state *state); bool eval_fstype(const struct expr *expr, struct eval_state *state); bool eval_hidden(const struct expr *expr, struct eval_state *state); bool eval_inum(const struct expr *expr, struct eval_state *state); bool eval_links(const struct expr *expr, struct eval_state *state); bool eval_samefile(const struct expr *expr, struct eval_state *state); bool eval_size(const struct expr *expr, struct eval_state *state); bool eval_sparse(const struct expr *expr, struct eval_state *state); bool eval_type(const struct expr *expr, struct eval_state *state); bool eval_xtype(const struct expr *expr, struct eval_state *state); bool eval_lname(const struct expr *expr, struct eval_state *state); bool eval_name(const struct expr *expr, struct eval_state *state); bool eval_path(const struct expr *expr, struct eval_state *state); bool eval_regex(const struct expr *expr, struct eval_state *state); bool eval_delete(const struct expr *expr, struct eval_state *state); bool eval_exec(const struct expr *expr, struct eval_state *state); bool eval_exit(const struct expr *expr, struct eval_state *state); bool eval_nohidden(const struct expr *expr, struct eval_state *state); bool eval_fls(const struct expr *expr, struct eval_state *state); bool eval_fprint(const struct expr *expr, struct eval_state *state); bool eval_fprint0(const struct expr *expr, struct eval_state *state); bool eval_fprintf(const struct expr *expr, struct eval_state *state); bool eval_fprintx(const struct expr *expr, struct eval_state *state); bool eval_prune(const struct expr *expr, struct eval_state *state); bool eval_quit(const struct expr *expr, struct eval_state *state); // Operator evaluation functions bool eval_not(const struct expr *expr, struct eval_state *state); bool eval_and(const struct expr *expr, struct eval_state *state); bool eval_or(const struct expr *expr, struct eval_state *state); bool eval_comma(const struct expr *expr, struct eval_state *state); #endif // BFS_EVAL_H bfs-1.5.2/exec.c000066400000000000000000000364601360564154000133760ustar00rootroot00000000000000/**************************************************************************** * bfs * * Copyright (C) 2017-2018 Tavian Barnes * * * * 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. * ****************************************************************************/ #include "exec.h" #include "bftw.h" #include "cmdline.h" #include "color.h" #include "diag.h" #include "dstring.h" #include "spawn.h" #include "util.h" #include #include #include #include #include #include #include #include #include #include #include /** Print some debugging info. */ BFS_FORMATTER(2, 3) static void bfs_exec_debug(const struct bfs_exec *execbuf, const char *format, ...) { if (!(execbuf->flags & BFS_EXEC_DEBUG)) { return; } if (execbuf->flags & BFS_EXEC_CONFIRM) { fputs("-ok", stderr); } else { fputs("-exec", stderr); } if (execbuf->flags & BFS_EXEC_CHDIR) { fputs("dir", stderr); } fputs(": ", stderr); va_list args; va_start(args, format); vfprintf(stderr, format, args); va_end(args); } extern char **environ; /** Determine the size of a single argument, for comparison to arg_max. */ static size_t bfs_exec_arg_size(const char *arg) { return sizeof(arg) + strlen(arg) + 1; } /** Even if we can pass a bigger argument list, cap it here. */ #define BFS_EXEC_ARG_MAX (16*1024*1024) /** Determine the maximum argv size. */ static size_t bfs_exec_arg_max(const struct bfs_exec *execbuf) { long arg_max = sysconf(_SC_ARG_MAX); bfs_exec_debug(execbuf, "ARG_MAX: %ld according to sysconf()\n", arg_max); if (arg_max < 0) { arg_max = BFS_EXEC_ARG_MAX; bfs_exec_debug(execbuf, "ARG_MAX: %ld assumed\n", arg_max); } // We have to share space with the environment variables for (char **envp = environ; *envp; ++envp) { arg_max -= bfs_exec_arg_size(*envp); } // Account for the terminating NULL entry arg_max -= sizeof(char *); bfs_exec_debug(execbuf, "ARG_MAX: %ld remaining after environment variables\n", arg_max); // Account for the fixed arguments for (size_t i = 0; i < execbuf->tmpl_argc - 1; ++i) { arg_max -= bfs_exec_arg_size(execbuf->tmpl_argv[i]); } // Account for the terminating NULL entry arg_max -= sizeof(char *); bfs_exec_debug(execbuf, "ARG_MAX: %ld remaining after fixed arguments\n", arg_max); // Assume arguments are counted with the granularity of a single page, // so allow a one page cushion to account for rounding up long page_size = sysconf(_SC_PAGESIZE); if (page_size < 4096) { page_size = 4096; } arg_max -= page_size; bfs_exec_debug(execbuf, "ARG_MAX: %ld remaining after page cushion\n", arg_max); // POSIX recommends an additional 2048 bytes of headroom arg_max -= 2048; bfs_exec_debug(execbuf, "ARG_MAX: %ld remaining after headroom\n", arg_max); if (arg_max < 0) { arg_max = 0; } else if (arg_max > BFS_EXEC_ARG_MAX) { arg_max = BFS_EXEC_ARG_MAX; } bfs_exec_debug(execbuf, "ARG_MAX: %ld final value\n", arg_max); return arg_max; } struct bfs_exec *parse_bfs_exec(char **argv, enum bfs_exec_flags flags, const struct cmdline *cmdline) { struct bfs_exec *execbuf = malloc(sizeof(*execbuf)); if (!execbuf) { perror("malloc()"); goto fail; } execbuf->flags = flags; execbuf->argv = NULL; execbuf->argc = 0; execbuf->argv_cap = 0; execbuf->arg_size = 0; execbuf->arg_max = 0; execbuf->wd_fd = -1; execbuf->wd_path = NULL; execbuf->wd_len = 0; execbuf->ret = 0; if (cmdline->debug & DEBUG_EXEC) { execbuf->flags |= BFS_EXEC_DEBUG; } size_t i; for (i = 1; ; ++i) { const char *arg = argv[i]; if (!arg) { if (execbuf->flags & BFS_EXEC_CONFIRM) { bfs_error(cmdline, "%s: Expected '... ;'.\n", argv[0]); } else { bfs_error(cmdline, "%s: Expected '... ;' or '... {} +'.\n", argv[0]); } goto fail; } else if (strcmp(arg, ";") == 0) { break; } else if (strcmp(arg, "+") == 0) { if (!(execbuf->flags & BFS_EXEC_CONFIRM) && strcmp(argv[i - 1], "{}") == 0) { execbuf->flags |= BFS_EXEC_MULTI; break; } } } execbuf->tmpl_argv = argv + 1; execbuf->tmpl_argc = i - 1; if (execbuf->tmpl_argc == 0) { bfs_error(cmdline, "%s: Missing command.\n", argv[0]); goto fail; } execbuf->argv_cap = execbuf->tmpl_argc + 1; execbuf->argv = malloc(execbuf->argv_cap*sizeof(*execbuf->argv)); if (!execbuf->argv) { perror("malloc()"); goto fail; } if (execbuf->flags & BFS_EXEC_MULTI) { for (i = 0; i < execbuf->tmpl_argc - 1; ++i) { char *arg = execbuf->tmpl_argv[i]; if (strstr(arg, "{}")) { bfs_error(cmdline, "%s ... +: Only one '{}' is supported.\n", argv[0]); goto fail; } execbuf->argv[i] = arg; } execbuf->argc = execbuf->tmpl_argc - 1; execbuf->arg_max = bfs_exec_arg_max(execbuf); } return execbuf; fail: free_bfs_exec(execbuf); return NULL; } /** Format the current path for use as a command line argument. */ static char *bfs_exec_format_path(const struct bfs_exec *execbuf, const struct BFTW *ftwbuf) { if (!(execbuf->flags & BFS_EXEC_CHDIR)) { return strdup(ftwbuf->path); } const char *name = ftwbuf->path + ftwbuf->nameoff; if (name[0] == '/') { // Must be a root path ("/", "//", etc.) return strdup(name); } // For compatibility with GNU find, use './name' instead of just 'name' char *path = malloc(2 + strlen(name) + 1); if (!path) { return NULL; } strcpy(path, "./"); strcpy(path + 2, name); return path; } /** Format an argument, expanding "{}" to the current path. */ static char *bfs_exec_format_arg(char *arg, const char *path) { char *match = strstr(arg, "{}"); if (!match) { return arg; } char *ret = dstralloc(0); if (!ret) { return NULL; } char *last = arg; do { if (dstrncat(&ret, last, match - last) != 0) { goto err; } if (dstrcat(&ret, path) != 0) { goto err; } last = match + 2; match = strstr(last, "{}"); } while (match); if (dstrcat(&ret, last) != 0) { goto err; } return ret; err: dstrfree(ret); return NULL; } /** Free a formatted argument. */ static void bfs_exec_free_arg(char *arg, const char *tmpl) { if (arg != tmpl) { dstrfree(arg); } } /** Open a file to use as the working directory. */ static int bfs_exec_openwd(struct bfs_exec *execbuf, const struct BFTW *ftwbuf) { assert(execbuf->wd_fd < 0); assert(!execbuf->wd_path); if (ftwbuf->at_fd != AT_FDCWD) { // Rely on at_fd being the immediate parent assert(ftwbuf->at_path == xbasename(ftwbuf->at_path)); execbuf->wd_fd = ftwbuf->at_fd; if (!(execbuf->flags & BFS_EXEC_MULTI)) { return 0; } execbuf->wd_fd = dup_cloexec(execbuf->wd_fd); if (execbuf->wd_fd < 0) { return -1; } } execbuf->wd_len = ftwbuf->nameoff; if (execbuf->wd_len == 0) { if (ftwbuf->path[0] == '/') { ++execbuf->wd_len; } else { // The path is something like "foo", so we're already in the right directory return 0; } } execbuf->wd_path = strndup(ftwbuf->path, execbuf->wd_len); if (!execbuf->wd_path) { return -1; } if (execbuf->wd_fd < 0) { execbuf->wd_fd = open(execbuf->wd_path, O_RDONLY | O_CLOEXEC | O_DIRECTORY); } if (execbuf->wd_fd < 0) { return -1; } return 0; } /** Close the working directory. */ static int bfs_exec_closewd(struct bfs_exec *execbuf, const struct BFTW *ftwbuf) { int ret = 0; if (execbuf->wd_fd >= 0) { if (!ftwbuf || execbuf->wd_fd != ftwbuf->at_fd) { ret = close(execbuf->wd_fd); } execbuf->wd_fd = -1; } if (execbuf->wd_path) { free(execbuf->wd_path); execbuf->wd_path = NULL; execbuf->wd_len = 0; } return ret; } /** Actually spawn the process. */ static int bfs_exec_spawn(const struct bfs_exec *execbuf) { if (execbuf->flags & BFS_EXEC_CONFIRM) { for (size_t i = 0; i < execbuf->argc; ++i) { fprintf(stderr, "%s ", execbuf->argv[i]); } fprintf(stderr, "? "); if (ynprompt() <= 0) { errno = 0; return -1; } } if (execbuf->flags & BFS_EXEC_MULTI) { bfs_exec_debug(execbuf, "Executing '%s' ... [%zu arguments] (size %zu)\n", execbuf->argv[0], execbuf->argc - 1, execbuf->arg_size); } else { bfs_exec_debug(execbuf, "Executing '%s' ... [%zu arguments]\n", execbuf->argv[0], execbuf->argc - 1); } pid_t pid = -1; int error; struct bfs_spawn ctx; if (bfs_spawn_init(&ctx) != 0) { return -1; } if (bfs_spawn_setflags(&ctx, BFS_SPAWN_USEPATH) != 0) { goto fail; } if (execbuf->wd_fd >= 0) { if (bfs_spawn_addfchdir(&ctx, execbuf->wd_fd) != 0) { goto fail; } } pid = bfs_spawn(execbuf->argv[0], &ctx, execbuf->argv, environ); fail: error = errno; bfs_spawn_destroy(&ctx); if (pid < 0) { errno = error; return -1; } int wstatus; if (waitpid(pid, &wstatus, 0) < 0) { return -1; } int ret = -1; if (WIFEXITED(wstatus)) { int status = WEXITSTATUS(wstatus); if (status == EXIT_SUCCESS) { ret = 0; } else { bfs_exec_debug(execbuf, "Command '%s' failed with status %d\n", execbuf->argv[0], status); } } else if (WIFSIGNALED(wstatus)) { int sig = WTERMSIG(wstatus); bfs_exec_debug(execbuf, "Command '%s' terminated by signal %d\n", execbuf->argv[0], sig); } else { bfs_exec_debug(execbuf, "Command '%s' terminated abnormally\n", execbuf->argv[0]); } errno = 0; return ret; } /** exec() a command for a single file. */ static int bfs_exec_single(struct bfs_exec *execbuf, const struct BFTW *ftwbuf) { int ret = -1, error = 0; char *path = bfs_exec_format_path(execbuf, ftwbuf); if (!path) { goto out; } size_t i; for (i = 0; i < execbuf->tmpl_argc; ++i) { execbuf->argv[i] = bfs_exec_format_arg(execbuf->tmpl_argv[i], path); if (!execbuf->argv[i]) { goto out_free; } } execbuf->argv[i] = NULL; execbuf->argc = i; if (execbuf->flags & BFS_EXEC_CHDIR) { if (bfs_exec_openwd(execbuf, ftwbuf) != 0) { goto out_free; } } ret = bfs_exec_spawn(execbuf); out_free: error = errno; bfs_exec_closewd(execbuf, ftwbuf); for (size_t j = 0; j < i; ++j) { bfs_exec_free_arg(execbuf->argv[j], execbuf->tmpl_argv[j]); } free(path); errno = error; out: return ret; } /** Check if any arguments remain in the buffer. */ static bool bfs_exec_args_remain(const struct bfs_exec *execbuf) { return execbuf->argc >= execbuf->tmpl_argc; } /** Execute the pending command from a BFS_EXEC_MULTI execbuf. */ static int bfs_exec_flush(struct bfs_exec *execbuf) { int ret = 0, error = 0; size_t orig_argc = execbuf->argc; while (bfs_exec_args_remain(execbuf)) { execbuf->argv[execbuf->argc] = NULL; ret = bfs_exec_spawn(execbuf); error = errno; if (ret == 0 || error != E2BIG) { break; } // Try to recover from E2BIG by trying fewer and fewer arguments // until they fit bfs_exec_debug(execbuf, "Got E2BIG, shrinking argument list...\n"); execbuf->argv[execbuf->argc] = execbuf->argv[execbuf->argc - 1]; execbuf->arg_size -= bfs_exec_arg_size(execbuf->argv[execbuf->argc]); --execbuf->argc; } size_t new_argc = execbuf->argc; size_t new_size = execbuf->arg_size; for (size_t i = execbuf->tmpl_argc - 1; i < new_argc; ++i) { free(execbuf->argv[i]); } execbuf->argc = execbuf->tmpl_argc - 1; execbuf->arg_size = 0; if (new_argc < orig_argc) { execbuf->arg_max = new_size; bfs_exec_debug(execbuf, "ARG_MAX: %zu\n", execbuf->arg_max); // If we recovered from E2BIG, there are unused arguments at the // end of the list for (size_t i = new_argc + 1; i <= orig_argc; ++i) { if (error == 0) { execbuf->argv[execbuf->argc] = execbuf->argv[i]; execbuf->arg_size += bfs_exec_arg_size(execbuf->argv[execbuf->argc]); ++execbuf->argc; } else { free(execbuf->argv[i]); } } } errno = error; return ret; } /** Check if we need to flush the execbuf because we're changing directories. */ static bool bfs_exec_changed_dirs(const struct bfs_exec *execbuf, const struct BFTW *ftwbuf) { if (execbuf->flags & BFS_EXEC_CHDIR) { if (ftwbuf->nameoff > execbuf->wd_len || (execbuf->wd_path && strncmp(ftwbuf->path, execbuf->wd_path, execbuf->wd_len) != 0)) { bfs_exec_debug(execbuf, "Changed directories, executing buffered command\n"); return true; } } return false; } /** Check if we need to flush the execbuf because we're too big. */ static bool bfs_exec_would_overflow(const struct bfs_exec *execbuf, const char *arg) { size_t next_size = execbuf->arg_size + bfs_exec_arg_size(arg); if (next_size > execbuf->arg_max) { bfs_exec_debug(execbuf, "Command size (%zu) would exceed maximum (%zu), executing buffered command\n", next_size, execbuf->arg_max); return true; } return false; } /** Push a new argument to a BFS_EXEC_MULTI execbuf. */ static int bfs_exec_push(struct bfs_exec *execbuf, char *arg) { execbuf->argv[execbuf->argc] = arg; if (execbuf->argc + 1 >= execbuf->argv_cap) { size_t cap = 2*execbuf->argv_cap; char **argv = realloc(execbuf->argv, cap*sizeof(*argv)); if (!argv) { return -1; } execbuf->argv = argv; execbuf->argv_cap = cap; } ++execbuf->argc; execbuf->arg_size += bfs_exec_arg_size(arg); return 0; } /** Handle a new path for a BFS_EXEC_MULTI execbuf. */ static int bfs_exec_multi(struct bfs_exec *execbuf, const struct BFTW *ftwbuf) { int ret = 0; char *arg = bfs_exec_format_path(execbuf, ftwbuf); if (!arg) { ret = -1; goto out; } if (bfs_exec_changed_dirs(execbuf, ftwbuf)) { while (bfs_exec_args_remain(execbuf)) { ret |= bfs_exec_flush(execbuf); } bfs_exec_closewd(execbuf, ftwbuf); } else if (bfs_exec_would_overflow(execbuf, arg)) { ret |= bfs_exec_flush(execbuf); } if ((execbuf->flags & BFS_EXEC_CHDIR) && execbuf->wd_fd < 0) { if (bfs_exec_openwd(execbuf, ftwbuf) != 0) { ret = -1; goto out_arg; } } if (bfs_exec_push(execbuf, arg) != 0) { ret = -1; goto out_arg; } // arg will get cleaned up later by bfs_exec_flush() goto out; out_arg: free(arg); out: return ret; } int bfs_exec(struct bfs_exec *execbuf, const struct BFTW *ftwbuf) { if (execbuf->flags & BFS_EXEC_MULTI) { if (bfs_exec_multi(execbuf, ftwbuf) == 0) { errno = 0; } else { execbuf->ret = -1; } // -exec ... + never returns false return 0; } else { return bfs_exec_single(execbuf, ftwbuf); } } int bfs_exec_finish(struct bfs_exec *execbuf) { if (execbuf->flags & BFS_EXEC_MULTI) { bfs_exec_debug(execbuf, "Finishing execution, executing buffered command\n"); while (bfs_exec_args_remain(execbuf)) { execbuf->ret |= bfs_exec_flush(execbuf); } if (execbuf->ret != 0) { bfs_exec_debug(execbuf, "One or more executions of '%s' failed\n", execbuf->argv[0]); } } return execbuf->ret; } void free_bfs_exec(struct bfs_exec *execbuf) { if (execbuf) { bfs_exec_closewd(execbuf, NULL); free(execbuf->argv); free(execbuf); } } bfs-1.5.2/exec.h000066400000000000000000000071571360564154000134040ustar00rootroot00000000000000/**************************************************************************** * bfs * * Copyright (C) 2017 Tavian Barnes * * * * 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. * ****************************************************************************/ /** * Implementation of -exec/-execdir/-ok/-okdir. */ #ifndef BFS_EXEC_H #define BFS_EXEC_H #include "bftw.h" #include "color.h" struct cmdline; /** * Flags for the -exec actions. */ enum bfs_exec_flags { /** Prompt the user before executing (-ok, -okdir). */ BFS_EXEC_CONFIRM = 1 << 0, /** Run the command in the file's parent directory (-execdir, -okdir). */ BFS_EXEC_CHDIR = 1 << 1, /** Pass multiple files at once to the command (-exec ... {} +). */ BFS_EXEC_MULTI = 1 << 2, /** Print debugging information (-D exec). */ BFS_EXEC_DEBUG = 1 << 3, }; /** * Buffer for a command line to be executed. */ struct bfs_exec { /** Flags for this exec buffer. */ enum bfs_exec_flags flags; /** Command line template. */ char **tmpl_argv; /** Command line template size. */ size_t tmpl_argc; /** The built command line. */ char **argv; /** Number of command line arguments. */ size_t argc; /** Capacity of argv. */ size_t argv_cap; /** Current size of all arguments. */ size_t arg_size; /** Maximum arg_size before E2BIG. */ size_t arg_max; /** A file descriptor for the working directory, for BFS_EXEC_CHDIR. */ int wd_fd; /** The path to the working directory, for BFS_EXEC_CHDIR. */ char *wd_path; /** Length of the working directory path. */ size_t wd_len; /** The ultimate return value for bfs_exec_finish(). */ int ret; }; /** * Parse an exec action. * * @param argv * The (bfs) command line argument to parse. * @param flags * Any flags for this exec action. * @param cmdline * The command line. * @return The parsed exec action, or NULL on failure. */ struct bfs_exec *parse_bfs_exec(char **argv, enum bfs_exec_flags flags, const struct cmdline *cmdline); /** * Execute the command for a file. * * @param execbuf * The parsed exec action. * @param ftwbuf * The bftw() data for the current file. * @return 0 if the command succeeded, -1 if it failed. If the command could * be executed, -1 is returned, and errno will be non-zero. For * BFS_EXEC_MULTI, errors will not be reported until bfs_exec_finish(). */ int bfs_exec(struct bfs_exec *execbuf, const struct BFTW *ftwbuf); /** * Finish executing any commands. * * @param execbuf * The parsed exec action. * @return 0 on success, -1 if any errors were encountered. */ int bfs_exec_finish(struct bfs_exec *execbuf); /** * Free a parsed exec action. */ void free_bfs_exec(struct bfs_exec *execbuf); #endif // BFS_EXEC_H bfs-1.5.2/expr.h000066400000000000000000000124611360564154000134300ustar00rootroot00000000000000/**************************************************************************** * bfs * * Copyright (C) 2015-2018 Tavian Barnes * * * * 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. * ****************************************************************************/ /** * The expression tree representation. */ #ifndef BFS_EXPR_H #define BFS_EXPR_H #include "color.h" #include "exec.h" #include "printf.h" #include "stat.h" #include #include #include #include #include /** * A command line expression. */ struct expr; /** * Ephemeral state for evaluating an expression. */ struct eval_state; /** * Expression evaluation function. * * @param expr * The current expression. * @param state * The current evaluation state. * @return * The result of the test. */ typedef bool eval_fn(const struct expr *expr, struct eval_state *state); /** * Possible types of numeric comparison. */ enum cmp_flag { /** Exactly n. */ CMP_EXACT, /** Less than n. */ CMP_LESS, /** Greater than n. */ CMP_GREATER, }; /** * Possible types of mode comparison. */ enum mode_cmp { /** Mode is an exact match (MODE). */ MODE_EXACT, /** Mode has all these bits (-MODE). */ MODE_ALL, /** Mode has any of these bits (/MODE). */ MODE_ANY, }; /** * Possible time units. */ enum time_unit { /** Minutes. */ MINUTES, /** Days. */ DAYS, }; /** * Possible file size units. */ enum size_unit { /** 512-byte blocks. */ SIZE_BLOCKS, /** Single bytes. */ SIZE_BYTES, /** Two-byte words. */ SIZE_WORDS, /** Kibibytes. */ SIZE_KB, /** Mebibytes. */ SIZE_MB, /** Gibibytes. */ SIZE_GB, /** Tebibytes. */ SIZE_TB, /** Pebibytes. */ SIZE_PB, }; struct expr { /** The function that evaluates this expression. */ eval_fn *eval; /** The left hand side of the expression. */ struct expr *lhs; /** The right hand side of the expression. */ struct expr *rhs; /** Whether this expression has no side effects. */ bool pure; /** Whether this expression always evaluates to true. */ bool always_true; /** Whether this expression always evaluates to false. */ bool always_false; /** Estimated cost. */ double cost; /** Estimated probability of success. */ double probability; /** Number of times this predicate was executed. */ size_t evaluations; /** Number of times this predicate succeeded. */ size_t successes; /** Total time spent running this predicate. */ struct timespec elapsed; /** The number of command line arguments for this expression. */ size_t argc; /** The command line arguments comprising this expression. */ char **argv; /** The optional comparison flag. */ enum cmp_flag cmp_flag; /** The mode comparison flag. */ enum mode_cmp mode_cmp; /** Mode to use for files. */ mode_t file_mode; /** Mode to use for directories (different due to X). */ mode_t dir_mode; /** The optional stat field to look at. */ enum bfs_stat_field stat_field; /** The optional reference time. */ struct timespec reftime; /** The optional time unit. */ enum time_unit time_unit; /** The optional size unit. */ enum size_unit size_unit; /** Optional device number for a target file. */ dev_t dev; /** Optional inode number for a target file. */ ino_t ino; /** File to output to. */ CFILE *cfile; /** Optional compiled regex. */ regex_t *regex; /** Optional exec command. */ struct bfs_exec *execbuf; /** Optional printf command. */ struct bfs_printf *printf; /** Optional integer data for this expression. */ long long idata; /** Optional string data for this expression. */ const char *sdata; /** The number of files this expression keeps open between evaluations. */ int persistent_fds; /** The number of files this expression opens during evaluation. */ int ephemeral_fds; }; /** Singleton true expression instance. */ extern struct expr expr_true; /** Singleton false expression instance. */ extern struct expr expr_false; /** * Create a new expression. */ struct expr *new_expr(eval_fn *eval, size_t argc, char **argv); /** * @return Whether expr is known to always quit. */ bool expr_never_returns(const struct expr *expr); /** * @return The result of the comparison for this expression. */ bool expr_cmp(const struct expr *expr, long long n); /** * Dump a parsed expression. */ void dump_expr(CFILE *cfile, const struct expr *expr, bool verbose); /** * Free an expression tree. */ void free_expr(struct expr *expr); #endif // BFS_EXPR_H bfs-1.5.2/fsade.c000066400000000000000000000163431360564154000135320ustar00rootroot00000000000000/**************************************************************************** * bfs * * Copyright (C) 2019-2020 Tavian Barnes * * * * 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. * ****************************************************************************/ #include "fsade.h" #include "bftw.h" #include "dstring.h" #include "util.h" #include #include #include #if BFS_CAN_CHECK_ACL # include #endif #if BFS_CAN_CHECK_CAPABILITIES # include #endif #if BFS_HAS_SYS_EXTATTR # include #elif BFS_HAS_SYS_XATTR # include #endif #if BFS_CAN_CHECK_ACL || BFS_CAN_CHECK_CAPABILITIES || BFS_CAN_CHECK_XATTRS /** * Many of the APIs used here don't have *at() variants, but we can try to * emulate something similar if /proc/self/fd is available. */ static const char *fake_at(const struct BFTW *ftwbuf) { static bool proc_works = true; static bool proc_checked = false; char *path = NULL; if (!proc_works || ftwbuf->at_fd == AT_FDCWD) { goto fail; } path = dstrprintf("/proc/self/fd/%d/", ftwbuf->at_fd); if (!path) { goto fail; } if (!proc_checked) { proc_checked = true; if (xfaccessat(AT_FDCWD, path, F_OK) != 0) { proc_works = false; goto fail; } } if (dstrcat(&path, ftwbuf->at_path) != 0) { goto fail; } return path; fail: dstrfree(path); return ftwbuf->path; } static void free_fake_at(const struct BFTW *ftwbuf, const char *path) { if (path != ftwbuf->path) { dstrfree((char *)path); } } /** * Check if an error was caused by the absence of support or data for a feature. */ static bool is_absence_error(int error) { // If the OS doesn't support the feature, it's obviously not enabled for // any files if (error == ENOTSUP) { return true; } // On Linux, ACLs and capabilities are implemented in terms of extended // attributes, which report ENODATA/ENOATTR when missing #ifdef ENODATA if (error == ENODATA) { return true; } #endif #if defined(ENOATTR) && ENOATTR != ENODATA if (error == ENOATTR) { return true; } #endif // On at least FreeBSD and macOS, EINVAL is returned when the requested // ACL type is not supported for that file if (error == EINVAL) { return true; } #if __APPLE__ // On macOS, ENOENT can also signal that a file has no ACLs if (error == ENOENT) { return true; } #endif return false; } #endif // BFS_CAN_CHECK_ACL || BFS_CAN_CHECK_CAPABILITIES || BFS_CAN_CHECK_XATTRS #if BFS_CAN_CHECK_ACL /** Check if any ACLs of the given type are non-trivial. */ static int bfs_check_acl_type(const char *path, acl_type_t type) { acl_t acl = acl_get_file(path, type); if (!acl) { if (is_absence_error(errno)) { return 0; } else { return -1; } } int ret = 0; #if __FreeBSD__ int trivial; if (acl_is_trivial_np(acl, &trivial) < 0) { ret = -1; } else if (trivial) { ret = 0; } else { ret = 1; } #else acl_entry_t entry; for (int status = acl_get_entry(acl, ACL_FIRST_ENTRY, &entry); #if __APPLE__ // POSIX.1e specifies a return value of 1 for success, but macOS // returns 0 instead status == 0; #else status > 0; #endif status = acl_get_entry(acl, ACL_NEXT_ENTRY, &entry)) { #if defined(ACL_USER_OBJ) && defined(ACL_GROUP_OBJ) && defined(ACL_OTHER) acl_tag_t tag; if (acl_get_tag_type(entry, &tag) != 0) { continue; } if (tag != ACL_USER_OBJ && tag != ACL_GROUP_OBJ && tag != ACL_OTHER) { ret = 1; break; } #else ret = 1; break; #endif } #endif // !__FreeBSD__ int error = errno; acl_free(acl); errno = error; return ret; } int bfs_check_acl(const struct BFTW *ftwbuf) { static const acl_type_t acl_types[] = { #if __APPLE__ // macOS gives EINVAL for either of the two standard ACL types, // supporting only ACL_TYPE_EXTENDED ACL_TYPE_EXTENDED, #else // The two standard POSIX.1e ACL types ACL_TYPE_ACCESS, ACL_TYPE_DEFAULT, #endif #ifdef ACL_TYPE_NFS4 ACL_TYPE_NFS4, #endif }; static const size_t n_acl_types = sizeof(acl_types)/sizeof(acl_types[0]); if (ftwbuf->typeflag == BFTW_LNK) { return 0; } const char *path = fake_at(ftwbuf); int ret = -1; for (size_t i = 0; i < n_acl_types && ret <= 0; ++i) { if (acl_types[i] == ACL_TYPE_DEFAULT && ftwbuf->typeflag != BFTW_DIR) { // ACL_TYPE_DEFAULT is supported only for directories, // otherwise acl_get_file() gives EACCESS continue; } ret = bfs_check_acl_type(path, acl_types[i]); } int error = errno; free_fake_at(ftwbuf, path); errno = error; return ret; } #else // !BFS_CAN_CHECK_ACL int bfs_check_acl(const struct BFTW *ftwbuf) { errno = ENOTSUP; return -1; } #endif #if BFS_CAN_CHECK_CAPABILITIES int bfs_check_capabilities(const struct BFTW *ftwbuf) { if (ftwbuf->typeflag == BFTW_LNK) { return 0; } int ret = -1, error; const char *path = fake_at(ftwbuf); cap_t caps = cap_get_file(path); if (!caps) { error = errno; if (is_absence_error(error)) { ret = 0; } goto out_path; } // TODO: Any better way to check for a non-empty capability set? char *text = cap_to_text(caps, NULL); if (!text) { error = errno; goto out_caps; } ret = text[0] ? 1 : 0; error = errno; cap_free(text); out_caps: cap_free(caps); out_path: free_fake_at(ftwbuf, path); errno = error; return ret; } #else // !BFS_CAN_CHECK_CAPABILITIES int bfs_check_capabilities(const struct BFTW *ftwbuf) { errno = ENOTSUP; return -1; } #endif #if BFS_CAN_CHECK_XATTRS int bfs_check_xattrs(const struct BFTW *ftwbuf) { const char *path = fake_at(ftwbuf); ssize_t len; #if BFS_HAS_SYS_EXTATTR ssize_t (*extattr_list)(const char *, int, void*, size_t) = ftwbuf->typeflag == BFTW_LNK ? extattr_list_link : extattr_list_file; len = extattr_list(path, EXTATTR_NAMESPACE_SYSTEM, NULL, 0); if (len <= 0) { len = extattr_list(path, EXTATTR_NAMESPACE_USER, NULL, 0); } #elif __APPLE__ int options = ftwbuf->typeflag == BFTW_LNK ? XATTR_NOFOLLOW : 0; len = listxattr(path, NULL, 0, options); #else if (ftwbuf->typeflag == BFTW_LNK) { len = llistxattr(path, NULL, 0); } else { len = listxattr(path, NULL, 0); } #endif int error = errno; free_fake_at(ftwbuf, path); if (len > 0) { return 1; } else if (len == 0 || is_absence_error(error)) { return 0; } else if (error == E2BIG) { return 1; } else { errno = error; return -1; } } #else // !BFS_CAN_CHECK_XATTRS int bfs_check_xattrs(const struct BFTW *ftwbuf) { errno = ENOTSUP; return -1; } #endif bfs-1.5.2/fsade.h000066400000000000000000000046031360564154000135330ustar00rootroot00000000000000/**************************************************************************** * bfs * * Copyright (C) 2019-2020 Tavian Barnes * * * * 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. * ****************************************************************************/ /** * A facade over (file)system features that are (un)implemented differently * between platforms. */ #ifndef BFS_FSADE_H #define BFS_FSADE_H #include "bftw.h" #include "util.h" #include #define BFS_CAN_CHECK_ACL BFS_HAS_SYS_ACL #if !defined(BFS_CAN_CHECK_CAPABILITIES) && BFS_HAS_SYS_CAPABILITY && !__FreeBSD__ # include # ifdef CAP_CHOWN # define BFS_CAN_CHECK_CAPABILITIES true # endif #endif #define BFS_CAN_CHECK_XATTRS (BFS_HAS_SYS_EXTATTR || BFS_HAS_SYS_XATTR) /** * Check if a file has a non-trvial Access Control List. * * @param ftwbuf * The file to check. * @return * 1 if it does, 0 if it doesn't, or -1 if an error occurred. */ int bfs_check_acl(const struct BFTW *ftwbuf); /** * Check if a file has a non-trvial capability set. * * @param ftwbuf * The file to check. * @return * 1 if it does, 0 if it doesn't, or -1 if an error occurred. */ int bfs_check_capabilities(const struct BFTW *ftwbuf); /** * Check if a file has any extended attributes set. * * @param ftwbuf * The file to check. * @return * 1 if it does, 0 if it doesn't, or -1 if an error occurred. */ int bfs_check_xattrs(const struct BFTW *ftwbuf); #endif // BFS_FSADE_H bfs-1.5.2/main.c000066400000000000000000000076401360564154000133740ustar00rootroot00000000000000/**************************************************************************** * bfs * * Copyright (C) 2015-2019 Tavian Barnes * * * * 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. * ****************************************************************************/ /** * - main(): the entry point for bfs(1), a breadth-first version of find(1) * - main.c (this file) * * - parse_cmdline(): parses the command line into an expression tree * - cmdline.h (declares the parsed command line structure) * - expr.h (declares the expression tree nodes) * - parse.c (the parser itself) * - opt.c (the expression optimizer) * * - eval_cmdline(): runs the expression on every file it sees * - eval.[ch] (the main evaluation functions) * - exec.[ch] (implements -exec[dir]/-ok[dir]) * - printf.[ch] (implements -[f]printf) * * - bftw(): used by eval_cmdline() to walk the directory tree(s) * - bftw.[ch] (an extended version of nftw(3)) * * - Utilities: * - bfs.h (constants about bfs itself) * - color.[ch] (for pretty terminal colors) * - darray.[ch] (a dynamic array library) * - diag.[ch] (formats diagnostic messages) * - dstring.[ch] (a dynamic string library) * - fsade.[ch] (a facade over non-standard filesystem features) * - mtab.[ch] (parses the system's mount table) * - spawn.[ch] (spawns processes) * - stat.[ch] (wraps stat(), or statx() on Linux) * - trie.[ch] (a trie set/map implementation) * - typo.[ch] (fuzzy matching for typos) * - util.[ch] (everything else) */ #include "cmdline.h" #include "util.h" #include #include #include #include #include #include /** * Make sure the standard streams std{in,out,err} are open. If they are not, * future open() calls may use those file descriptors, and std{in,out,err} will * use them unintentionally. */ static int open_std_streams(void) { #ifdef O_PATH const int inflags = O_PATH, outflags = O_PATH; #else // These are intentionally backwards so that bfs >&- still fails with EBADF const int inflags = O_WRONLY, outflags = O_RDONLY; #endif if (!isopen(STDERR_FILENO) && redirect(STDERR_FILENO, "/dev/null", outflags) < 0) { return -1; } if (!isopen(STDOUT_FILENO) && redirect(STDOUT_FILENO, "/dev/null", outflags) < 0) { perror("redirect()"); return -1; } if (!isopen(STDIN_FILENO) && redirect(STDIN_FILENO, "/dev/null", inflags) < 0) { perror("redirect()"); return -1; } return 0; } /** * bfs entry point. */ int main(int argc, char *argv[]) { int ret = EXIT_FAILURE; // Make sure the standard streams are open if (open_std_streams() != 0) { goto done; } // Use the system locale instead of "C" setlocale(LC_ALL, ""); struct cmdline *cmdline = parse_cmdline(argc, argv); if (cmdline) { ret = eval_cmdline(cmdline); } if (free_cmdline(cmdline) != 0 && ret == EXIT_SUCCESS) { ret = EXIT_FAILURE; } done: return ret; } bfs-1.5.2/mtab.c000066400000000000000000000125621360564154000133720ustar00rootroot00000000000000/**************************************************************************** * bfs * * Copyright (C) 2017-2019 Tavian Barnes * * * * 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. * ****************************************************************************/ #include "mtab.h" #include "darray.h" #include "trie.h" #include "util.h" #include #include #include #include #include #include #include #if BFS_HAS_SYS_PARAM # include #endif #if BFS_HAS_MNTENT # define BFS_MNTENT 1 #elif BSD # define BFS_MNTINFO 1 #elif __SVR4 # define BFS_MNTTAB 1 #endif #if BFS_MNTENT # include # include # include #elif BFS_MNTINFO # include # include #elif BFS_MNTTAB # include # include #endif /** * A mount point in the table. */ struct bfs_mtab_entry { /** The path to the mount point. */ char *path; /** The filesystem type. */ char *type; }; struct bfs_mtab { /** The list of mount points. */ struct bfs_mtab_entry *entries; /** The basenames of every mount point. */ struct trie names; /** A map from device ID to fstype (populated lazily). */ struct trie types; /** Whether the types map has been populated. */ bool types_filled; }; /** * Add an entry to the mount table. */ static int bfs_mtab_add(struct bfs_mtab *mtab, const char *path, const char *type) { struct bfs_mtab_entry entry = { .path = strdup(path), .type = strdup(type), }; if (!entry.path || !entry.type) { goto fail_entry; } if (DARRAY_PUSH(&mtab->entries, &entry) != 0) { goto fail_entry; } if (!trie_insert_str(&mtab->names, xbasename(path))) { goto fail; } return 0; fail_entry: free(entry.type); free(entry.path); fail: return -1; } struct bfs_mtab *parse_bfs_mtab() { struct bfs_mtab *mtab = malloc(sizeof(*mtab)); if (!mtab) { return NULL; } mtab->entries = NULL; trie_init(&mtab->names); trie_init(&mtab->types); mtab->types_filled = false; int error = 0; #if BFS_MNTENT FILE *file = setmntent(_PATH_MOUNTED, "r"); if (!file) { // In case we're in a chroot or something with /proc but no /etc/mtab error = errno; file = setmntent("/proc/mounts", "r"); } if (!file) { goto fail; } struct mntent *mnt; while ((mnt = getmntent(file))) { if (bfs_mtab_add(mtab, mnt->mnt_dir, mnt->mnt_type) != 0) { error = errno; endmntent(file); goto fail; } } endmntent(file); #elif BFS_MNTINFO #if __NetBSD__ typedef struct statvfs bfs_statfs; #else typedef struct statfs bfs_statfs; #endif bfs_statfs *mntbuf; int size = getmntinfo(&mntbuf, MNT_WAIT); if (size < 0) { error = errno; goto fail; } for (bfs_statfs *mnt = mntbuf; mnt < mntbuf + size; ++mnt) { if (bfs_mtab_add(mtab, mnt->f_mntonname, mnt->f_fstypename) != 0) { error = errno; goto fail; } } #elif BFS_MNTTAB FILE *file = fopen(MNTTAB, "r"); if (!file) { error = errno; goto fail; } struct mnttab mnt; while (getmntent(file, &mnt) == 0) { if (bfs_mtab_add(mtab, mnt.mnt_mountp, mnt.mnt_fstype) != 0) { error = errno; fclose(file); goto fail; } } fclose(file); #else error = ENOTSUP; goto fail; #endif return mtab; fail: free_bfs_mtab(mtab); errno = error; return NULL; } static void bfs_mtab_fill_types(struct bfs_mtab *mtab) { for (size_t i = 0; i < darray_length(mtab->entries); ++i) { struct bfs_mtab_entry *entry = mtab->entries + i; struct bfs_stat sb; if (bfs_stat(AT_FDCWD, entry->path, BFS_STAT_NOFOLLOW | BFS_STAT_NOSYNC, &sb) != 0) { continue; } struct trie_leaf *leaf = trie_insert_mem(&mtab->types, &sb.dev, sizeof(sb.dev)); if (leaf) { leaf->value = entry->type; } } mtab->types_filled = true; } const char *bfs_fstype(const struct bfs_mtab *mtab, const struct bfs_stat *statbuf) { if (!mtab->types_filled) { bfs_mtab_fill_types((struct bfs_mtab *)mtab); } const struct trie_leaf *leaf = trie_find_mem(&mtab->types, &statbuf->dev, sizeof(statbuf->dev)); if (leaf) { return leaf->value; } else { return "unknown"; } } bool bfs_might_be_mount(const struct bfs_mtab *mtab, const char *path) { const char *name = xbasename(path); return trie_find_str(&mtab->names, name); } void free_bfs_mtab(struct bfs_mtab *mtab) { if (mtab) { trie_destroy(&mtab->types); trie_destroy(&mtab->names); for (size_t i = 0; i < darray_length(mtab->entries); ++i) { free(mtab->entries[i].type); free(mtab->entries[i].path); } darray_free(mtab->entries); free(mtab); } } bfs-1.5.2/mtab.h000066400000000000000000000044211360564154000133720ustar00rootroot00000000000000/**************************************************************************** * bfs * * Copyright (C) 2017-2019 Tavian Barnes * * * * 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. * ****************************************************************************/ /** * A facade over platform-specific APIs for enumerating mounted filesystems. */ #ifndef BFS_MTAB_H #define BFS_MTAB_H #include "stat.h" #include /** * A file system mount table. */ struct bfs_mtab; /** * Parse the mount table. * * @return * The parsed mount table, or NULL on error. */ struct bfs_mtab *parse_bfs_mtab(void); /** * Determine the file system type that a file is on. * * @param mtab * The current mount table. * @param statbuf * The bfs_stat() buffer for the file in question. * @return * The type of file system containing this file, "unknown" if not known, * or NULL on error. */ const char *bfs_fstype(const struct bfs_mtab *mtab, const struct bfs_stat *statbuf); /** * Check if a file could be a mount point. * * @param mtab * The current mount table. * @param path * The path to check. * @return * Whether the named file could be a mount point. */ bool bfs_might_be_mount(const struct bfs_mtab *mtab, const char *path); /** * Free a mount table. */ void free_bfs_mtab(struct bfs_mtab *mtab); #endif // BFS_MTAB_H bfs-1.5.2/opt.c000066400000000000000000000617631360564154000132600ustar00rootroot00000000000000/**************************************************************************** * bfs * * Copyright (C) 2017-2019 Tavian Barnes * * * * 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. * ****************************************************************************/ /** * The expression optimizer. Different optimization levels are supported: * * -O1: basic logical simplifications, like folding (-true -and -foo) to -foo. * * -O2: dead code elimination and data flow analysis. struct opt_facts is used * to record data flow facts that are true at various points of evaluation. * Specifically, struct opt_facts records the facts that must be true before an * expression is evaluated (state->facts), and those that must be true after the * expression is evaluated, given that it returns true (state->facts_when_true) * or false (state->facts_when_true). Additionally, state->facts_when_impure * records the possible data flow facts before any expressions with side effects * are evaluated. * * -O3: expression re-ordering to reduce expected cost. In an expression like * (-foo -and -bar), if both -foo and -bar are pure (no side effects), they can * be re-ordered to (-bar -and -foo). This is profitable if the expected cost * is lower for the re-ordered expression, for example if -foo is very slow or * -bar is likely to return false. * * -O4/-Ofast: aggressive optimizations that may affect correctness in corner * cases. The main effect is to use facts_when_impure to determine if any side- * effects are reachable at all, and skipping the traversal if not. */ #include "cmdline.h" #include "color.h" #include "eval.h" #include "expr.h" #include #include #include #include static char *fake_and_arg = "-a"; static char *fake_or_arg = "-o"; static char *fake_not_arg = "!"; /** * A contrained integer range. */ struct range { /** The (inclusive) minimum value. */ long long min; /** The (inclusive) maximum value. */ long long max; }; /** Compute the minimum of two values. */ static long long min_value(long long a, long long b) { if (a < b) { return a; } else { return b; } } /** Compute the maximum of two values. */ static long long max_value(long long a, long long b) { if (a > b) { return a; } else { return b; } } /** Constrain the minimum of a range. */ static void constrain_min(struct range *range, long long value) { range->min = max_value(range->min, value); } /** Contrain the maximum of a range. */ static void constrain_max(struct range *range, long long value) { range->max = min_value(range->max, value); } /** Remove a single value from a range. */ static void range_remove(struct range *range, long long value) { if (range->min == value) { if (range->min == LLONG_MAX) { range->max = LLONG_MIN; } else { ++range->min; } } if (range->max == value) { if (range->max == LLONG_MIN) { range->min = LLONG_MAX; } else { --range->max; } } } /** Compute the union of two ranges. */ static void range_union(struct range *result, const struct range *lhs, const struct range *rhs) { result->min = min_value(lhs->min, rhs->min); result->max = max_value(lhs->max, rhs->max); } /** Check if a range contains no values. */ static bool range_impossible(const struct range *range) { return range->min > range->max; } /** Set a range to contain no values. */ static void set_range_impossible(struct range *range) { range->min = LLONG_MAX; range->max = LLONG_MIN; } /** * Types of ranges we track. */ enum range_type { /** Search tree depth. */ DEPTH_RANGE, /** Group ID. */ GID_RANGE, /** Inode number. */ INUM_RANGE, /** Hard link count. */ LINKS_RANGE, /** File size. */ SIZE_RANGE, /** User ID. */ UID_RANGE, /** The number of range_types. */ MAX_RANGE, }; /** * Data flow facts about an evaluation point. */ struct opt_facts { /** The value ranges we track. */ struct range ranges[MAX_RANGE]; /** Bitmask of possible file types. */ enum bftw_typeflag types; /** Bitmask of possible link target types. */ enum bftw_typeflag xtypes; }; /** Initialize some data flow facts. */ static void facts_init(struct opt_facts *facts) { for (int i = 0; i < MAX_RANGE; ++i) { struct range *range = facts->ranges + i; range->min = 0; // All ranges we currently track are non-negative range->max = LLONG_MAX; } facts->types = ~0; facts->xtypes = ~0; } /** Compute the union of two fact sets. */ static void facts_union(struct opt_facts *result, const struct opt_facts *lhs, const struct opt_facts *rhs) { for (int i = 0; i < MAX_RANGE; ++i) { range_union(result->ranges + i, lhs->ranges + i, rhs->ranges + i); } result->types = lhs->types | rhs->types; result->xtypes = lhs->xtypes | rhs->xtypes; } /** Determine whether a fact set is impossible. */ static bool facts_impossible(const struct opt_facts *facts) { for (int i = 0; i < MAX_RANGE; ++i) { if (range_impossible(facts->ranges + i)) { return true; } } if (!facts->types || !facts->xtypes) { return true; } return false; } /** Set some facts to be impossible. */ static void set_facts_impossible(struct opt_facts *facts) { for (int i = 0; i < MAX_RANGE; ++i) { set_range_impossible(facts->ranges + i); } facts->types = 0; facts->xtypes = 0; } /** * Optimizer state. */ struct opt_state { /** The command line we're optimizing. */ const struct cmdline *cmdline; /** Data flow facts before this expression is evaluated. */ struct opt_facts facts; /** Data flow facts after this expression returns true. */ struct opt_facts facts_when_true; /** Data flow facts after this expression returns false. */ struct opt_facts facts_when_false; /** Data flow facts before any side-effecting expressions are evaluated. */ struct opt_facts *facts_when_impure; }; /** Log an optimization. */ static void debug_opt(const struct opt_state *state, const char *format, ...) { if (!(state->cmdline->debug & DEBUG_OPT)) { return; } CFILE *cerr = state->cmdline->cerr; va_list args; va_start(args, format); for (const char *i = format; *i != '\0'; ++i) { if (*i == '%') { switch (*++i) { case 'd': fprintf(cerr->file, "%d", va_arg(args, int)); break; case 'e': dump_expr(cerr, va_arg(args, const struct expr *), false); break; case 'g': cfprintf(cerr, "${ylw}%g${rs}", va_arg(args, double)); break; default: assert(false); break; } } else { fputc(*i, stderr); } } va_end(args); } /** Extract a child expression, freeing the outer expression. */ static struct expr *extract_child_expr(struct expr *expr, struct expr **child) { struct expr *ret = *child; *child = NULL; free_expr(expr); return ret; } /** * Negate an expression. */ static struct expr *negate_expr(struct expr *rhs, char **argv) { if (rhs->eval == eval_not) { return extract_child_expr(rhs, &rhs->rhs); } struct expr *expr = new_expr(eval_not, 1, argv); if (!expr) { free_expr(rhs); return NULL; } expr->rhs = rhs; return expr; } static struct expr *optimize_not_expr(const struct opt_state *state, struct expr *expr); static struct expr *optimize_and_expr(const struct opt_state *state, struct expr *expr); static struct expr *optimize_or_expr(const struct opt_state *state, struct expr *expr); /** * Apply De Morgan's laws. */ static struct expr *de_morgan(const struct opt_state *state, struct expr *expr, char **argv) { debug_opt(state, "-O1: De Morgan's laws: %e ", expr); struct expr *parent = negate_expr(expr, argv); if (!parent) { return NULL; } bool has_parent = true; if (parent->eval != eval_not) { expr = parent; has_parent = false; } if (expr->eval == eval_and) { expr->eval = eval_or; expr->argv = &fake_or_arg; } else { assert(expr->eval == eval_or); expr->eval = eval_and; expr->argv = &fake_and_arg; } expr->lhs = negate_expr(expr->lhs, argv); expr->rhs = negate_expr(expr->rhs, argv); if (!expr->lhs || !expr->rhs) { free_expr(parent); return NULL; } debug_opt(state, "<==> %e\n", parent); if (expr->lhs->eval == eval_not) { expr->lhs = optimize_not_expr(state, expr->lhs); } if (expr->rhs->eval == eval_not) { expr->rhs = optimize_not_expr(state, expr->rhs); } if (!expr->lhs || !expr->rhs) { free_expr(parent); return NULL; } if (expr->eval == eval_and) { expr = optimize_and_expr(state, expr); } else { expr = optimize_or_expr(state, expr); } if (!expr) { if (has_parent) { parent->rhs = NULL; free_expr(parent); } return NULL; } if (has_parent) { parent = optimize_not_expr(state, parent); } return parent; } /** Optimize an expression recursively. */ static struct expr *optimize_expr_recursive(struct opt_state *state, struct expr *expr); /** * Optimize a negation. */ static struct expr *optimize_not_expr(const struct opt_state *state, struct expr *expr) { assert(expr->eval == eval_not); struct expr *rhs = expr->rhs; int optlevel = state->cmdline->optlevel; if (optlevel >= 1) { if (rhs == &expr_true) { debug_opt(state, "-O1: constant propagation: %e <==> %e\n", expr, &expr_false); free_expr(expr); return &expr_false; } else if (rhs == &expr_false) { debug_opt(state, "-O1: constant propagation: %e <==> %e\n", expr, &expr_true); free_expr(expr); return &expr_true; } else if (rhs->eval == eval_not) { debug_opt(state, "-O1: double negation: %e <==> %e\n", expr, rhs->rhs); return extract_child_expr(expr, &rhs->rhs); } else if (expr_never_returns(rhs)) { debug_opt(state, "-O1: reachability: %e <==> %e\n", expr, rhs); return extract_child_expr(expr, &expr->rhs); } else if ((rhs->eval == eval_and || rhs->eval == eval_or) && (rhs->lhs->eval == eval_not || rhs->rhs->eval == eval_not)) { return de_morgan(state, expr, expr->argv); } } expr->pure = rhs->pure; expr->always_true = rhs->always_false; expr->always_false = rhs->always_true; expr->cost = rhs->cost; expr->probability = 1.0 - rhs->probability; return expr; } /** Optimize a negation recursively. */ static struct expr *optimize_not_expr_recursive(struct opt_state *state, struct expr *expr) { struct opt_state rhs_state = *state; expr->rhs = optimize_expr_recursive(&rhs_state, expr->rhs); if (!expr->rhs) { goto fail; } state->facts_when_true = rhs_state.facts_when_false; state->facts_when_false = rhs_state.facts_when_true; return optimize_not_expr(state, expr); fail: free_expr(expr); return NULL; } /** Optimize a conjunction. */ static struct expr *optimize_and_expr(const struct opt_state *state, struct expr *expr) { assert(expr->eval == eval_and); struct expr *lhs = expr->lhs; struct expr *rhs = expr->rhs; int optlevel = state->cmdline->optlevel; if (optlevel >= 1) { if (lhs == &expr_true) { debug_opt(state, "-O1: conjunction elimination: %e <==> %e\n", expr, rhs); return extract_child_expr(expr, &expr->rhs); } else if (rhs == &expr_true) { debug_opt(state, "-O1: conjunction elimination: %e <==> %e\n", expr, lhs); return extract_child_expr(expr, &expr->lhs); } else if (lhs->always_false) { debug_opt(state, "-O1: short-circuit: %e <==> %e\n", expr, lhs); return extract_child_expr(expr, &expr->lhs); } else if (lhs->always_true && rhs == &expr_false) { debug_opt(state, "-O1: strength reduction: %e <==> ", expr); struct expr *ret = extract_child_expr(expr, &expr->lhs); ret = negate_expr(ret, &fake_not_arg); if (ret) { debug_opt(state, "%e\n", ret); } return ret; } else if (optlevel >= 2 && lhs->pure && rhs == &expr_false) { debug_opt(state, "-O2: purity: %e <==> %e\n", expr, rhs); return extract_child_expr(expr, &expr->rhs); } else if (lhs->eval == eval_not && rhs->eval == eval_not) { return de_morgan(state, expr, expr->lhs->argv); } } expr->pure = lhs->pure && rhs->pure; expr->always_true = lhs->always_true && rhs->always_true; expr->always_false = lhs->always_false || rhs->always_false; expr->cost = lhs->cost + lhs->probability*rhs->cost; expr->probability = lhs->probability*rhs->probability; return expr; } /** Optimize a conjunction recursively. */ static struct expr *optimize_and_expr_recursive(struct opt_state *state, struct expr *expr) { struct opt_state lhs_state = *state; expr->lhs = optimize_expr_recursive(&lhs_state, expr->lhs); if (!expr->lhs) { goto fail; } struct opt_state rhs_state = *state; rhs_state.facts = lhs_state.facts_when_true; expr->rhs = optimize_expr_recursive(&rhs_state, expr->rhs); if (!expr->rhs) { goto fail; } state->facts_when_true = rhs_state.facts_when_true; facts_union(&state->facts_when_false, &lhs_state.facts_when_false, &rhs_state.facts_when_false); return optimize_and_expr(state, expr); fail: free_expr(expr); return NULL; } /** Optimize a disjunction. */ static struct expr *optimize_or_expr(const struct opt_state *state, struct expr *expr) { assert(expr->eval == eval_or); struct expr *lhs = expr->lhs; struct expr *rhs = expr->rhs; int optlevel = state->cmdline->optlevel; if (optlevel >= 1) { if (lhs->always_true) { debug_opt(state, "-O1: short-circuit: %e <==> %e\n", expr, lhs); return extract_child_expr(expr, &expr->lhs); } else if (lhs == &expr_false) { debug_opt(state, "-O1: disjunctive syllogism: %e <==> %e\n", expr, rhs); return extract_child_expr(expr, &expr->rhs); } else if (rhs == &expr_false) { debug_opt(state, "-O1: disjunctive syllogism: %e <==> %e\n", expr, lhs); return extract_child_expr(expr, &expr->lhs); } else if (lhs->always_false && rhs == &expr_true) { debug_opt(state, "-O1: strength reduction: %e <==> ", expr); struct expr *ret = extract_child_expr(expr, &expr->lhs); ret = negate_expr(ret, &fake_not_arg); if (ret) { debug_opt(state, "%e\n", ret); } return ret; } else if (optlevel >= 2 && lhs->pure && rhs == &expr_true) { debug_opt(state, "-O2: purity: %e <==> %e\n", expr, rhs); return extract_child_expr(expr, &expr->rhs); } else if (lhs->eval == eval_not && rhs->eval == eval_not) { return de_morgan(state, expr, expr->lhs->argv); } } expr->pure = lhs->pure && rhs->pure; expr->always_true = lhs->always_true || rhs->always_true; expr->always_false = lhs->always_false && rhs->always_false; expr->cost = lhs->cost + (1 - lhs->probability)*rhs->cost; expr->probability = lhs->probability + rhs->probability - lhs->probability*rhs->probability; return expr; } /** Optimize a disjunction recursively. */ static struct expr *optimize_or_expr_recursive(struct opt_state *state, struct expr *expr) { struct opt_state lhs_state = *state; expr->lhs = optimize_expr_recursive(&lhs_state, expr->lhs); if (!expr->lhs) { goto fail; } struct opt_state rhs_state = *state; rhs_state.facts = lhs_state.facts_when_false; expr->rhs = optimize_expr_recursive(&rhs_state, expr->rhs); if (!expr->rhs) { goto fail; } facts_union(&state->facts_when_true, &lhs_state.facts_when_true, &rhs_state.facts_when_true); state->facts_when_false = rhs_state.facts_when_false; return optimize_or_expr(state, expr); fail: free_expr(expr); return NULL; } /** Optimize an expression in an ignored-result context. */ static struct expr *ignore_result(const struct opt_state *state, struct expr *expr) { int optlevel = state->cmdline->optlevel; if (optlevel >= 1) { while (true) { if (expr->eval == eval_not) { debug_opt(state, "-O1: ignored result: %e --> %e\n", expr, expr->rhs); expr = extract_child_expr(expr, &expr->rhs); } else if (optlevel >= 2 && (expr->eval == eval_and || expr->eval == eval_or || expr->eval == eval_comma) && expr->rhs->pure) { debug_opt(state, "-O2: ignored result: %e --> %e\n", expr, expr->lhs); expr = extract_child_expr(expr, &expr->lhs); } else { break; } } if (optlevel >= 2 && expr->pure && expr != &expr_false) { debug_opt(state, "-O2: ignored result: %e --> %e\n", expr, &expr_false); free_expr(expr); expr = &expr_false; } } return expr; } /** Optimize a comma expression. */ static struct expr *optimize_comma_expr(const struct opt_state *state, struct expr *expr) { assert(expr->eval == eval_comma); struct expr *lhs = expr->lhs; struct expr *rhs = expr->rhs; int optlevel = state->cmdline->optlevel; if (optlevel >= 1) { lhs = expr->lhs = ignore_result(state, lhs); if (expr_never_returns(lhs)) { debug_opt(state, "-O1: reachability: %e <==> %e\n", expr, lhs); return extract_child_expr(expr, &expr->lhs); } else if ((lhs->always_true && rhs == &expr_true) || (lhs->always_false && rhs == &expr_false)) { debug_opt(state, "-O1: redundancy elimination: %e <==> %e\n", expr, lhs); return extract_child_expr(expr, &expr->lhs); } else if (optlevel >= 2 && lhs->pure) { debug_opt(state, "-O2: purity: %e <==> %e\n", expr, rhs); return extract_child_expr(expr, &expr->rhs); } } expr->pure = lhs->pure && rhs->pure; expr->always_true = expr_never_returns(lhs) || rhs->always_true; expr->always_false = expr_never_returns(lhs) || rhs->always_false; expr->cost = lhs->cost + rhs->cost; expr->probability = rhs->probability; return expr; } /** Optimize a comma expression recursively. */ static struct expr *optimize_comma_expr_recursive(struct opt_state *state, struct expr *expr) { struct opt_state lhs_state = *state; expr->lhs = optimize_expr_recursive(&lhs_state, expr->lhs); if (!expr->lhs) { goto fail; } struct opt_state rhs_state = *state; facts_union(&rhs_state.facts, &lhs_state.facts_when_true, &lhs_state.facts_when_false); expr->rhs = optimize_expr_recursive(&rhs_state, expr->rhs); if (!expr->rhs) { goto fail; } return optimize_comma_expr(state, expr); fail: free_expr(expr); return NULL; } /** Infer data flow facts about an icmp-style ([+-]N) expression */ static void infer_icmp_facts(struct opt_state *state, const struct expr *expr, enum range_type type) { struct range *range_when_true = state->facts_when_true.ranges + type; struct range *range_when_false = state->facts_when_false.ranges + type; long long value = expr->idata; switch (expr->cmp_flag) { case CMP_EXACT: constrain_min(range_when_true, value); constrain_max(range_when_true, value); range_remove(range_when_false, value); break; case CMP_LESS: constrain_min(range_when_false, value); constrain_max(range_when_true, value); range_remove(range_when_true, value); break; case CMP_GREATER: constrain_max(range_when_false, value); constrain_min(range_when_true, value); range_remove(range_when_true, value); break; } } /** Infer data flow facts about a -samefile expression. */ static void infer_samefile_facts(struct opt_state *state, const struct expr *expr) { struct range *range_when_true = state->facts_when_true.ranges + INUM_RANGE; constrain_min(range_when_true, expr->ino); constrain_max(range_when_true, expr->ino); } /** Infer data flow facts about a -type expression. */ static void infer_type_facts(struct opt_state *state, const struct expr *expr) { state->facts_when_true.types &= expr->idata; state->facts_when_false.types &= ~expr->idata; } /** Infer data flow facts about an -xtype expression. */ static void infer_xtype_facts(struct opt_state *state, const struct expr *expr) { state->facts_when_true.xtypes &= expr->idata; state->facts_when_false.xtypes &= ~expr->idata; } static struct expr *optimize_expr_recursive(struct opt_state *state, struct expr *expr) { state->facts_when_true = state->facts; state->facts_when_false = state->facts; if (expr->eval == eval_depth) { infer_icmp_facts(state, expr, DEPTH_RANGE); } else if (expr->eval == eval_gid) { infer_icmp_facts(state, expr, GID_RANGE); } else if (expr->eval == eval_inum) { infer_icmp_facts(state, expr, INUM_RANGE); } else if (expr->eval == eval_links) { infer_icmp_facts(state, expr, LINKS_RANGE); } else if (expr->eval == eval_samefile) { infer_samefile_facts(state, expr); } else if (expr->eval == eval_size) { infer_icmp_facts(state, expr, SIZE_RANGE); } else if (expr->eval == eval_type) { infer_type_facts(state, expr); } else if (expr->eval == eval_uid) { infer_icmp_facts(state, expr, UID_RANGE); } else if (expr->eval == eval_xtype) { infer_xtype_facts(state, expr); } else if (expr->eval == eval_not) { expr = optimize_not_expr_recursive(state, expr); } else if (expr->eval == eval_and) { expr = optimize_and_expr_recursive(state, expr); } else if (expr->eval == eval_or) { expr = optimize_or_expr_recursive(state, expr); } else if (expr->eval == eval_comma) { expr = optimize_comma_expr_recursive(state, expr); } else if (!expr->pure) { facts_union(state->facts_when_impure, state->facts_when_impure, &state->facts); } if (!expr) { goto done; } struct expr *lhs = expr->lhs; struct expr *rhs = expr->rhs; if (rhs) { expr->persistent_fds = rhs->persistent_fds; expr->ephemeral_fds = rhs->ephemeral_fds; } if (lhs) { expr->persistent_fds += lhs->persistent_fds; if (lhs->ephemeral_fds > expr->ephemeral_fds) { expr->ephemeral_fds = lhs->ephemeral_fds; } } if (expr->always_true) { set_facts_impossible(&state->facts_when_false); } if (expr->always_false) { set_facts_impossible(&state->facts_when_true); } if (state->cmdline->optlevel < 2 || expr == &expr_true || expr == &expr_false) { goto done; } if (facts_impossible(&state->facts_when_true)) { if (expr->pure) { debug_opt(state, "-O2: data flow: %e --> %e\n", expr, &expr_false); free_expr(expr); expr = &expr_false; } else { expr->always_false = true; expr->probability = 0.0; } } else if (facts_impossible(&state->facts_when_false)) { if (expr->pure) { debug_opt(state, "-O2: data flow: %e --> %e\n", expr, &expr_true); free_expr(expr); expr = &expr_true; } else { expr->always_true = true; expr->probability = 1.0; } } done: return expr; } /** Swap the children of a binary expression if it would reduce the cost. */ static bool reorder_expr(const struct opt_state *state, struct expr *expr, double swapped_cost) { if (swapped_cost < expr->cost) { debug_opt(state, "-O3: cost: %e", expr); struct expr *lhs = expr->lhs; expr->lhs = expr->rhs; expr->rhs = lhs; debug_opt(state, " <==> %e (~%g --> ~%g)\n", expr, expr->cost, swapped_cost); expr->cost = swapped_cost; return true; } else { return false; } } /** * Recursively reorder sub-expressions to reduce the overall cost. * * @param expr * The expression to optimize. * @return * Whether any subexpression was reordered. */ static bool reorder_expr_recursive(const struct opt_state *state, struct expr *expr) { bool ret = false; struct expr *lhs = expr->lhs; struct expr *rhs = expr->rhs; if (lhs) { ret |= reorder_expr_recursive(state, lhs); } if (rhs) { ret |= reorder_expr_recursive(state, rhs); } if (expr->eval == eval_and || expr->eval == eval_or) { if (lhs->pure && rhs->pure) { double rhs_prob = expr->eval == eval_and ? rhs->probability : 1.0 - rhs->probability; double swapped_cost = rhs->cost + rhs_prob*lhs->cost; ret |= reorder_expr(state, expr, swapped_cost); } } return ret; } int optimize_cmdline(struct cmdline *cmdline) { struct opt_facts facts_when_impure; set_facts_impossible(&facts_when_impure); struct opt_state state = { .cmdline = cmdline, .facts_when_impure = &facts_when_impure, }; facts_init(&state.facts); struct range *depth = state.facts.ranges + DEPTH_RANGE; depth->min = cmdline->mindepth; depth->max = cmdline->maxdepth; int optlevel = cmdline->optlevel; cmdline->expr = optimize_expr_recursive(&state, cmdline->expr); if (!cmdline->expr) { return -1; } if (optlevel >= 3 && reorder_expr_recursive(&state, cmdline->expr)) { // Re-do optimizations to account for the new ordering set_facts_impossible(&facts_when_impure); cmdline->expr = optimize_expr_recursive(&state, cmdline->expr); if (!cmdline->expr) { return -1; } } cmdline->expr = ignore_result(&state, cmdline->expr); const struct range *depth_when_impure = facts_when_impure.ranges + DEPTH_RANGE; long long mindepth = depth_when_impure->min; long long maxdepth = depth_when_impure->max; if (optlevel >= 2 && mindepth > cmdline->mindepth) { if (mindepth > INT_MAX) { mindepth = INT_MAX; } cmdline->mindepth = mindepth; debug_opt(&state, "-O2: data flow: mindepth --> %d\n", cmdline->mindepth); } if (optlevel >= 4 && maxdepth < cmdline->maxdepth) { if (maxdepth < INT_MIN) { maxdepth = INT_MIN; } cmdline->maxdepth = maxdepth; debug_opt(&state, "-O4: data flow: maxdepth --> %d\n", cmdline->maxdepth); } return 0; } bfs-1.5.2/parse.c000066400000000000000000002433171360564154000135650ustar00rootroot00000000000000/**************************************************************************** * bfs * * Copyright (C) 2015-2019 Tavian Barnes * * * * 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. * ****************************************************************************/ /** * The command line parser. Expressions are parsed by recursive descent, with a * grammar described in the comments of the parse_*() functions. The parser * also accepts flags and paths at any point in the expression, by treating * flags like always-true options, and skipping over paths wherever they appear. */ #include "bfs.h" #include "cmdline.h" #include "darray.h" #include "diag.h" #include "dstring.h" #include "eval.h" #include "exec.h" #include "expr.h" #include "fsade.h" #include "mtab.h" #include "printf.h" #include "spawn.h" #include "stat.h" #include "typo.h" #include "util.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Strings printed by -D tree for "fake" expressions static char *fake_and_arg = "-a"; static char *fake_false_arg = "-false"; static char *fake_print_arg = "-print"; static char *fake_true_arg = "-true"; // Cost estimation constants #define FAST_COST 40.0 #define STAT_COST 1000.0 #define PRINT_COST 20000.0 struct expr expr_true = { .eval = eval_true, .lhs = NULL, .rhs = NULL, .pure = true, .always_true = true, .cost = FAST_COST, .probability = 1.0, .argc = 1, .argv = &fake_true_arg, }; struct expr expr_false = { .eval = eval_false, .lhs = NULL, .rhs = NULL, .pure = true, .always_false = true, .cost = FAST_COST, .probability = 0.0, .argc = 1, .argv = &fake_false_arg, }; /** * Free an expression. */ void free_expr(struct expr *expr) { if (!expr || expr == &expr_true || expr == &expr_false) { return; } if (expr->regex) { regfree(expr->regex); free(expr->regex); } free_bfs_printf(expr->printf); free_bfs_exec(expr->execbuf); free_expr(expr->lhs); free_expr(expr->rhs); free(expr); } struct expr *new_expr(eval_fn *eval, size_t argc, char **argv) { struct expr *expr = malloc(sizeof(*expr)); if (!expr) { perror("malloc()"); return NULL; } expr->eval = eval; expr->lhs = NULL; expr->rhs = NULL; expr->pure = false; expr->always_true = false; expr->always_false = false; expr->cost = FAST_COST; expr->probability = 0.5; expr->evaluations = 0; expr->successes = 0; expr->elapsed.tv_sec = 0; expr->elapsed.tv_nsec = 0; expr->argc = argc; expr->argv = argv; expr->cfile = NULL; expr->regex = NULL; expr->execbuf = NULL; expr->printf = NULL; expr->persistent_fds = 0; expr->ephemeral_fds = 0; return expr; } /** * Create a new unary expression. */ static struct expr *new_unary_expr(eval_fn *eval, struct expr *rhs, char **argv) { struct expr *expr = new_expr(eval, 1, argv); if (!expr) { free_expr(rhs); return NULL; } expr->rhs = rhs; expr->persistent_fds = rhs->persistent_fds; expr->ephemeral_fds = rhs->ephemeral_fds; return expr; } /** * Create a new binary expression. */ static struct expr *new_binary_expr(eval_fn *eval, struct expr *lhs, struct expr *rhs, char **argv) { struct expr *expr = new_expr(eval, 1, argv); if (!expr) { free_expr(rhs); free_expr(lhs); return NULL; } expr->lhs = lhs; expr->rhs = rhs; expr->persistent_fds = lhs->persistent_fds + rhs->persistent_fds; if (lhs->ephemeral_fds > rhs->ephemeral_fds) { expr->ephemeral_fds = lhs->ephemeral_fds; } else { expr->ephemeral_fds = rhs->ephemeral_fds; } return expr; } /** * Check if an expression never returns. */ bool expr_never_returns(const struct expr *expr) { // Expressions that never return are vacuously both always true and always false return expr->always_true && expr->always_false; } /** * Set an expression to always return true. */ static void expr_set_always_true(struct expr *expr) { expr->always_true = true; expr->probability = 1.0; } /** * Set an expression to never return. */ static void expr_set_never_returns(struct expr *expr) { expr->always_true = expr->always_false = true; } /** * Dump the parsed expression tree, for debugging. */ void dump_expr(CFILE *cfile, const struct expr *expr, bool verbose) { fputs("(", cfile->file); if (expr->lhs || expr->rhs) { cfprintf(cfile, "${red}%s${rs}", expr->argv[0]); } else { cfprintf(cfile, "${blu}%s${rs}", expr->argv[0]); } for (size_t i = 1; i < expr->argc; ++i) { cfprintf(cfile, " ${bld}%s${rs}", expr->argv[i]); } if (verbose) { double rate = 0.0, time = 0.0; if (expr->evaluations) { rate = 100.0*expr->successes/expr->evaluations; time = (1.0e9*expr->elapsed.tv_sec + expr->elapsed.tv_nsec)/expr->evaluations; } cfprintf(cfile, " [${ylw}%zu${rs}/${ylw}%zu${rs}=${ylw}%g%%${rs}; ${ylw}%gns${rs}]", expr->successes, expr->evaluations, rate, time); } if (expr->lhs) { fputs(" ", cfile->file); dump_expr(cfile, expr->lhs, verbose); } if (expr->rhs) { fputs(" ", cfile->file); dump_expr(cfile, expr->rhs, verbose); } fputs(")", cfile->file); } /** * An open file for the command line. */ struct open_file { /** The file itself. */ CFILE *cfile; /** The path to the file (for diagnostics). */ const char *path; }; /** * Free the parsed command line. */ int free_cmdline(struct cmdline *cmdline) { int ret = 0; if (cmdline) { CFILE *cout = cmdline->cout; CFILE *cerr = cmdline->cerr; free_expr(cmdline->expr); free_bfs_mtab(cmdline->mtab); struct trie_leaf *leaf; while ((leaf = trie_first_leaf(&cmdline->open_files))) { struct open_file *ofile = leaf->value; if (cfclose(ofile->cfile) != 0) { if (cerr) { bfs_error(cmdline, "'%s': %m.\n", ofile->path); } ret = -1; } free(ofile); trie_remove(&cmdline->open_files, leaf); } trie_destroy(&cmdline->open_files); if (cout && fflush(cout->file) != 0) { if (cerr) { bfs_error(cmdline, "standard output: %m.\n"); } ret = -1; } cfclose(cout); cfclose(cerr); free_colors(cmdline->colors); darray_free(cmdline->paths); free(cmdline->argv); free(cmdline); } return ret; } /** * Color use flags. */ enum use_color { COLOR_NEVER, COLOR_AUTO, COLOR_ALWAYS, }; /** * Ephemeral state for parsing the command line. */ struct parser_state { /** The command line being constructed. */ struct cmdline *cmdline; /** The command line arguments being parsed. */ char **argv; /** The name of this program. */ const char *command; /** The current regex flags to use. */ int regex_flags; /** Whether stdout is a terminal. */ bool stdout_tty; /** Whether this session is interactive (stdin and stderr are each a terminal). */ bool interactive; /** Whether -color or -nocolor has been passed. */ enum use_color use_color; /** Whether a -print action is implied. */ bool implicit_print; /** Whether warnings are enabled (see -warn, -nowarn). */ bool warn; /** Whether the expression has started. */ bool expr_started; /** Whether any non-option arguments have been encountered. */ bool non_option_seen; /** Whether an information option like -help or -version was passed. */ bool just_info; /** The last non-path argument. */ const char *last_arg; /** A "-depth"-type argument if any. */ const char *depth_arg; /** A "-prune"-type argument if any. */ const char *prune_arg; /** A "-mount"-type argument if any. */ const char *mount_arg; /** An "-xdev"-type argument if any. */ const char *xdev_arg; /** The current time. */ struct timespec now; }; /** * Possible token types. */ enum token_type { /** A flag. */ T_FLAG, /** A root path. */ T_PATH, /** An option. */ T_OPTION, /** A test. */ T_TEST, /** An action. */ T_ACTION, /** An operator. */ T_OPERATOR, }; /** * Print an error message during parsing. */ BFS_FORMATTER(2, 3) static void parse_error(const struct parser_state *state, const char *format, ...) { va_list args; va_start(args, format); bfs_verror(state->cmdline, format, args); va_end(args); } /** * Print a warning message during parsing. */ BFS_FORMATTER(2, 3) static void parse_warning(const struct parser_state *state, const char *format, ...) { va_list args; va_start(args, format); bfs_vwarning(state->cmdline, format, args); va_end(args); } /** * Fill in a "-print"-type expression. */ static void init_print_expr(struct parser_state *state, struct expr *expr) { expr_set_always_true(expr); expr->cost = PRINT_COST; expr->cfile = state->cmdline->cout; } /** * Open a file for an expression. */ static int expr_open(struct parser_state *state, struct expr *expr, const char *path) { int ret = -1; struct cmdline *cmdline = state->cmdline; CFILE *cfile = cfopen(path, state->use_color ? cmdline->colors : NULL); if (!cfile) { parse_error(state, "'%s': %m.\n", path); goto out; } struct bfs_stat sb; if (bfs_stat(fileno(cfile->file), NULL, 0, &sb) != 0) { parse_error(state, "'%s': %m.\n", path); goto out_close; } bfs_file_id id; bfs_stat_id(&sb, &id); struct trie_leaf *leaf = trie_insert_mem(&cmdline->open_files, id, sizeof(id)); if (leaf->value) { struct open_file *ofile = leaf->value; expr->cfile = ofile->cfile; ret = 0; goto out_close; } struct open_file *ofile = malloc(sizeof(*ofile)); if (!ofile) { perror("malloc()"); trie_remove(&cmdline->open_files, leaf); goto out_close; } ofile->cfile = cfile; ofile->path = path; leaf->value = ofile; ++cmdline->nopen_files; expr->cfile = cfile; ret = 0; goto out; out_close: cfclose(cfile); out: return ret; } /** * Invoke bfs_stat() on an argument. */ static int stat_arg(const struct parser_state *state, struct expr *expr, struct bfs_stat *sb) { const struct cmdline *cmdline = state->cmdline; bool follow = cmdline->flags & (BFTW_COMFOLLOW | BFTW_LOGICAL); enum bfs_stat_flag flags = follow ? BFS_STAT_TRYFOLLOW : BFS_STAT_NOFOLLOW; int ret = bfs_stat(AT_FDCWD, expr->sdata, flags, sb); if (ret != 0) { parse_error(state, "'%s': %m.\n", expr->sdata); } return ret; } /** * Parse the expression specified on the command line. */ static struct expr *parse_expr(struct parser_state *state); /** * Advance by a single token. */ static char **parser_advance(struct parser_state *state, enum token_type type, size_t argc) { if (type != T_FLAG && type != T_PATH) { state->expr_started = true; if (type != T_OPTION) { state->non_option_seen = true; } } if (type != T_PATH) { state->last_arg = *state->argv; } char **argv = state->argv; state->argv += argc; return argv; } /** * Parse a root path. */ static int parse_root(struct parser_state *state, const char *path) { struct cmdline *cmdline = state->cmdline; return DARRAY_PUSH(&cmdline->paths, &path); } /** * While parsing an expression, skip any paths and add them to the cmdline. */ static int skip_paths(struct parser_state *state) { while (true) { const char *arg = state->argv[0]; if (!arg) { return 0; } if (arg[0] == '-') { if (strcmp(arg, "--") == 0) { // find uses -- to separate flags from the rest // of the command line. We allow mixing flags // and paths/predicates, so we just ignore --. parser_advance(state, T_FLAG, 1); continue; } if (strcmp(arg, "-") != 0) { // - by itself is a file name. Anything else // starting with - is a flag/predicate. return 0; } } // By POSIX, these are always options if (strcmp(arg, "(") == 0 || strcmp(arg, "!") == 0) { return 0; } if (state->expr_started) { // By POSIX, these can be paths. We only treat them as // such at the beginning of the command line. if (strcmp(arg, ")") == 0 || strcmp(arg, ",") == 0) { return 0; } } if (parse_root(state, arg) != 0) { return -1; } parser_advance(state, T_PATH, 1); } } /** Integer parsing flags. */ enum int_flags { IF_BASE_MASK = 0x03F, IF_INT = 0x040, IF_LONG = 0x080, IF_LONG_LONG = 0x0C0, IF_SIZE_MASK = 0x0C0, IF_UNSIGNED = 0x100, IF_PARTIAL_OK = 0x200, IF_QUIET = 0x400, }; /** * Parse an integer. */ static const char *parse_int(const struct parser_state *state, const char *str, void *result, enum int_flags flags) { char *endptr; int base = flags & IF_BASE_MASK; if (base == 0) { base = 10; } errno = 0; long long value = strtoll(str, &endptr, base); if (errno != 0) { goto bad; } if (endptr == str) { goto bad; } if (!(flags & IF_PARTIAL_OK) && *endptr != '\0') { goto bad; } if ((flags & IF_UNSIGNED) && value < 0) { goto bad; } switch (flags & IF_SIZE_MASK) { case IF_INT: if (value < INT_MIN || value > INT_MAX) { goto bad; } *(int *)result = value; break; case IF_LONG: if (value < LONG_MIN || value > LONG_MAX) { goto bad; } *(long *)result = value; break; case IF_LONG_LONG: *(long long *)result = value; break; } return endptr; bad: if (!(flags & IF_QUIET)) { parse_error(state, "'%s' is not a valid integer.\n", str); } return NULL; } /** * Parse an integer and a comparison flag. */ static const char *parse_icmp(const struct parser_state *state, const char *str, struct expr *expr, enum int_flags flags) { switch (str[0]) { case '-': expr->cmp_flag = CMP_LESS; ++str; break; case '+': expr->cmp_flag = CMP_GREATER; ++str; break; default: expr->cmp_flag = CMP_EXACT; break; } return parse_int(state, str, &expr->idata, flags | IF_LONG_LONG | IF_UNSIGNED); } /** * Check if a string could be an integer comparison. */ static bool looks_like_icmp(const char *str) { int i; // One +/- for the comparison flag, one for the sign for (i = 0; i < 2; ++i) { if (str[i] != '-' && str[i] != '+') { break; } } return str[i] >= '0' && str[i] <= '9'; } /** * Parse a single flag. */ static struct expr *parse_flag(struct parser_state *state, size_t argc) { parser_advance(state, T_FLAG, argc); return &expr_true; } /** * Parse a flag that doesn't take a value. */ static struct expr *parse_nullary_flag(struct parser_state *state) { return parse_flag(state, 1); } /** * Parse a flag that takes a single value. */ static struct expr *parse_unary_flag(struct parser_state *state) { return parse_flag(state, 2); } /** * Parse a single option. */ static struct expr *parse_option(struct parser_state *state, size_t argc) { const char *arg = *parser_advance(state, T_OPTION, argc); if (state->warn && state->non_option_seen) { parse_warning(state, "The '%s' option applies to the entire command line. For clarity, place\n" "it before any non-option arguments.\n\n", arg); } return &expr_true; } /** * Parse an option that doesn't take a value. */ static struct expr *parse_nullary_option(struct parser_state *state) { return parse_option(state, 1); } /** * Parse an option that takes a value. */ static struct expr *parse_unary_option(struct parser_state *state) { return parse_option(state, 2); } /** * Parse a single positional option. */ static struct expr *parse_positional_option(struct parser_state *state, size_t argc) { parser_advance(state, T_OPTION, argc); return &expr_true; } /** * Parse a positional option that doesn't take a value. */ static struct expr *parse_nullary_positional_option(struct parser_state *state) { return parse_positional_option(state, 1); } /** * Parse a positional option that takes a single value. */ static struct expr *parse_unary_positional_option(struct parser_state *state, const char **value) { const char *arg = state->argv[0]; *value = state->argv[1]; if (!*value) { parse_error(state, "%s needs a value.\n", arg); return NULL; } return parse_positional_option(state, 2); } /** * Parse a single test. */ static struct expr *parse_test(struct parser_state *state, eval_fn *eval, size_t argc) { char **argv = parser_advance(state, T_TEST, argc); struct expr *expr = new_expr(eval, argc, argv); if (expr) { expr->pure = true; } return expr; } /** * Parse a test that doesn't take a value. */ static struct expr *parse_nullary_test(struct parser_state *state, eval_fn *eval) { return parse_test(state, eval, 1); } /** * Parse a test that takes a value. */ static struct expr *parse_unary_test(struct parser_state *state, eval_fn *eval) { const char *arg = state->argv[0]; const char *value = state->argv[1]; if (!value) { parse_error(state, "%s needs a value.\n", arg); return NULL; } struct expr *expr = parse_test(state, eval, 2); if (expr) { expr->sdata = value; } return expr; } /** * Parse a single action. */ static struct expr *parse_action(struct parser_state *state, eval_fn *eval, size_t argc) { if (eval != eval_nohidden && eval != eval_prune && eval != eval_quit) { state->implicit_print = false; } char **argv = parser_advance(state, T_ACTION, argc); return new_expr(eval, argc, argv); } /** * Parse an action that takes no arguments. */ static struct expr *parse_nullary_action(struct parser_state *state, eval_fn *eval) { return parse_action(state, eval, 1); } /** * Parse an action that takes one argument. */ static struct expr *parse_unary_action(struct parser_state *state, eval_fn *eval) { const char *arg = state->argv[0]; const char *value = state->argv[1]; if (!value) { parse_error(state, "%s needs a value.\n", arg); return NULL; } struct expr *expr = parse_action(state, eval, 2); if (expr) { expr->sdata = value; } return expr; } /** * Parse a test expression with integer data and a comparison flag. */ static struct expr *parse_test_icmp(struct parser_state *state, eval_fn *eval) { struct expr *expr = parse_unary_test(state, eval); if (!expr) { return NULL; } if (!parse_icmp(state, expr->sdata, expr, 0)) { free_expr(expr); return NULL; } return expr; } /** * Print usage information for -D. */ static void debug_help(CFILE *cfile) { cfprintf(cfile, "Supported debug flags:\n\n"); cfprintf(cfile, " ${bld}help${rs}: This message.\n"); cfprintf(cfile, " ${bld}cost${rs}: Show cost estimates.\n"); cfprintf(cfile, " ${bld}exec${rs}: Print executed command details.\n"); cfprintf(cfile, " ${bld}opt${rs}: Print optimization details.\n"); cfprintf(cfile, " ${bld}rates${rs}: Print predicate success rates.\n"); cfprintf(cfile, " ${bld}search${rs}: Trace the filesystem traversal.\n"); cfprintf(cfile, " ${bld}stat${rs}: Trace all stat() calls.\n"); cfprintf(cfile, " ${bld}tree${rs}: Print the parse tree.\n"); cfprintf(cfile, " ${bld}all${rs}: All debug flags at once.\n"); } /** A named debug flag. */ struct debug_flag { enum debug_flags flag; const char *name; }; /** The table of debug flags. */ struct debug_flag debug_flags[] = { {DEBUG_ALL, "all"}, {DEBUG_COST, "cost"}, {DEBUG_EXEC, "exec"}, {DEBUG_OPT, "opt"}, {DEBUG_RATES, "rates"}, {DEBUG_SEARCH, "search"}, {DEBUG_STAT, "stat"}, {DEBUG_TREE, "tree"}, {0}, }; /** Check if a substring matches a debug flag. */ static bool parse_debug_flag(const char *flag, size_t len, const char *expected) { if (len == strlen(expected)) { return strncmp(flag, expected, len) == 0; } else { return false; } } /** * Parse -D FLAG. */ static struct expr *parse_debug(struct parser_state *state, int arg1, int arg2) { struct cmdline *cmdline = state->cmdline; const char *arg = state->argv[0]; const char *flags = state->argv[1]; if (!flags) { parse_error(state, "%s needs a flag.\n\n", arg); debug_help(cmdline->cerr); return NULL; } bool unrecognized = false; for (const char *flag = flags, *next; flag; flag = next) { size_t len = strcspn(flag, ","); if (flag[len]) { next = flag + len + 1; } else { next = NULL; } if (parse_debug_flag(flag, len, "help")) { debug_help(cmdline->cout); state->just_info = true; return NULL; } for (int i = 0; ; ++i) { const char *expected = debug_flags[i].name; if (!expected) { parse_warning(state, "Unrecognized debug flag '"); fwrite(flag, 1, len, stderr); fputs("'\n\n", stderr); unrecognized = true; break; } if (parse_debug_flag(flag, len, expected)) { cmdline->debug |= debug_flags[i].flag; break; } } } if (unrecognized) { debug_help(cmdline->cerr); cfprintf(cmdline->cerr, "\n"); } return parse_unary_flag(state); } /** * Parse -On. */ static struct expr *parse_optlevel(struct parser_state *state, int arg1, int arg2) { int *optlevel = &state->cmdline->optlevel; if (strcmp(state->argv[0], "-Ofast") == 0) { *optlevel = 4; } else if (!parse_int(state, state->argv[0] + 2, optlevel, IF_INT | IF_UNSIGNED)) { return NULL; } if (state->warn && *optlevel > 4) { parse_warning(state, "%s is the same as -O4.\n\n", state->argv[0]); } return parse_nullary_flag(state); } /** * Parse -[PHL], -(no)?follow. */ static struct expr *parse_follow(struct parser_state *state, int flags, int option) { struct cmdline *cmdline = state->cmdline; cmdline->flags &= ~(BFTW_COMFOLLOW | BFTW_LOGICAL); cmdline->flags |= flags; if (option) { return parse_nullary_positional_option(state); } else { return parse_nullary_flag(state); } } /** * Parse -X. */ static struct expr *parse_xargs_safe(struct parser_state *state, int arg1, int arg2) { state->cmdline->xargs_safe = true; return parse_nullary_flag(state); } /** * Parse -executable, -readable, -writable */ static struct expr *parse_access(struct parser_state *state, int flag, int arg2) { struct expr *expr = parse_nullary_test(state, eval_access); if (!expr) { return NULL; } expr->idata = flag; expr->cost = STAT_COST; switch (flag) { case R_OK: expr->probability = 0.99; break; case W_OK: expr->probability = 0.8; break; case X_OK: expr->probability = 0.2; break; } return expr; } /** * Parse -acl. */ static struct expr *parse_acl(struct parser_state *state, int flag, int arg2) { #if BFS_CAN_CHECK_ACL struct expr *expr = parse_nullary_test(state, eval_acl); if (expr) { expr->cost = STAT_COST; expr->probability = 0.00002; } return expr; #else parse_error(state, "%s is missing platform support.\n", state->argv[0]); return NULL; #endif } /** * Parse -[aBcm]?newer. */ static struct expr *parse_newer(struct parser_state *state, int field, int arg2) { struct expr *expr = parse_unary_test(state, eval_newer); if (!expr) { return NULL; } struct bfs_stat sb; if (stat_arg(state, expr, &sb) != 0) { goto fail; } expr->cost = STAT_COST; expr->reftime = sb.mtime; expr->stat_field = field; return expr; fail: free_expr(expr); return NULL; } /** * Parse -[aBcm]{min,time}. */ static struct expr *parse_time(struct parser_state *state, int field, int unit) { struct expr *expr = parse_test_icmp(state, eval_time); if (!expr) { return NULL; } expr->cost = STAT_COST; expr->reftime = state->now; expr->stat_field = field; expr->time_unit = unit; return expr; } /** * Parse -capable. */ static struct expr *parse_capable(struct parser_state *state, int flag, int arg2) { #if BFS_CAN_CHECK_CAPABILITIES struct expr *expr = parse_nullary_test(state, eval_capable); if (expr) { expr->cost = STAT_COST; expr->probability = 0.000002; } return expr; #else parse_error(state, "%s is missing platform support.\n", state->argv[0]); return NULL; #endif } /** * Parse -(no)?color. */ static struct expr *parse_color(struct parser_state *state, int color, int arg2) { struct cmdline *cmdline = state->cmdline; struct colors *colors = cmdline->colors; if (color) { state->use_color = COLOR_ALWAYS; cmdline->cout->colors = colors; cmdline->cerr->colors = colors; } else { state->use_color = COLOR_NEVER; cmdline->cout->colors = NULL; cmdline->cerr->colors = NULL; } return parse_nullary_option(state); } /** * Parse -{false,true}. */ static struct expr *parse_const(struct parser_state *state, int value, int arg2) { parser_advance(state, T_TEST, 1); return value ? &expr_true : &expr_false; } /** * Parse -daystart. */ static struct expr *parse_daystart(struct parser_state *state, int arg1, int arg2) { struct tm tm; if (xlocaltime(&state->now.tv_sec, &tm) != 0) { perror("xlocaltime()"); return NULL; } if (tm.tm_hour || tm.tm_min || tm.tm_sec || state->now.tv_nsec) { ++tm.tm_mday; } tm.tm_hour = 0; tm.tm_min = 0; tm.tm_sec = 0; time_t time = mktime(&tm); if (time == -1) { perror("mktime()"); return NULL; } state->now.tv_sec = time; state->now.tv_nsec = 0; return parse_nullary_positional_option(state); } /** * Parse -delete. */ static struct expr *parse_delete(struct parser_state *state, int arg1, int arg2) { state->cmdline->flags |= BFTW_DEPTH; state->depth_arg = state->argv[0]; return parse_nullary_action(state, eval_delete); } /** * Parse -d. */ static struct expr *parse_depth(struct parser_state *state, int arg1, int arg2) { state->cmdline->flags |= BFTW_DEPTH; state->depth_arg = state->argv[0]; return parse_nullary_flag(state); } /** * Parse -depth [N]. */ static struct expr *parse_depth_n(struct parser_state *state, int arg1, int arg2) { const char *arg = state->argv[1]; if (arg && looks_like_icmp(arg)) { return parse_test_icmp(state, eval_depth); } else { return parse_depth(state, arg1, arg2); } } /** * Parse -{min,max}depth N. */ static struct expr *parse_depth_limit(struct parser_state *state, int is_min, int arg2) { struct cmdline *cmdline = state->cmdline; const char *arg = state->argv[0]; const char *value = state->argv[1]; if (!value) { parse_error(state, "%s needs a value.\n", arg); return NULL; } int *depth = is_min ? &cmdline->mindepth : &cmdline->maxdepth; if (!parse_int(state, value, depth, IF_INT | IF_UNSIGNED)) { return NULL; } return parse_unary_option(state); } /** * Parse -empty. */ static struct expr *parse_empty(struct parser_state *state, int arg1, int arg2) { struct expr *expr = parse_nullary_test(state, eval_empty); if (!expr) { return NULL; } expr->cost = 2000.0; expr->probability = 0.01; if (state->cmdline->optlevel < 4) { // Since -empty attempts to open and read directories, it may // have side effects such as reporting permission errors, and // thus shouldn't be re-ordered without aggressive optimizations expr->pure = false; } expr->ephemeral_fds = 1; return expr; } /** * Parse -exec(dir)?/-ok(dir)?. */ static struct expr *parse_exec(struct parser_state *state, int flags, int arg2) { struct bfs_exec *execbuf = parse_bfs_exec(state->argv, flags, state->cmdline); if (!execbuf) { return NULL; } struct expr *expr = parse_action(state, eval_exec, execbuf->tmpl_argc + 2); if (!expr) { free_bfs_exec(execbuf); return NULL; } expr->execbuf = execbuf; if (execbuf->flags & BFS_EXEC_MULTI) { expr_set_always_true(expr); } else { expr->cost = 1000000.0; } expr->ephemeral_fds = 2; if (execbuf->flags & BFS_EXEC_CHDIR) { if (execbuf->flags & BFS_EXEC_MULTI) { expr->persistent_fds = 1; } else { ++expr->ephemeral_fds; } } return expr; } /** * Parse -exit [STATUS]. */ static struct expr *parse_exit(struct parser_state *state, int arg1, int arg2) { size_t argc = 1; const char *value = state->argv[1]; int status = EXIT_SUCCESS; if (value && parse_int(state, value, &status, IF_INT | IF_UNSIGNED | IF_QUIET)) { argc = 2; } struct expr *expr = parse_action(state, eval_exit, argc); if (expr) { expr_set_never_returns(expr); expr->idata = status; } return expr; } /** * Parse -f PATH. */ static struct expr *parse_f(struct parser_state *state, int arg1, int arg2) { parser_advance(state, T_FLAG, 1); const char *path = state->argv[0]; if (!path) { parse_error(state, "-f requires a path.\n"); return NULL; } if (parse_root(state, path) != 0) { return NULL; } parser_advance(state, T_PATH, 1); return &expr_true; } /** * Parse -fls FILE. */ static struct expr *parse_fls(struct parser_state *state, int arg1, int arg2) { struct expr *expr = parse_unary_action(state, eval_fls); if (expr) { expr_set_always_true(expr); expr->cost = PRINT_COST; if (expr_open(state, expr, expr->sdata) != 0) { goto fail; } expr->reftime = state->now; } return expr; fail: free_expr(expr); return NULL; } /** * Parse -fprint FILE. */ static struct expr *parse_fprint(struct parser_state *state, int arg1, int arg2) { struct expr *expr = parse_unary_action(state, eval_fprint); if (expr) { expr_set_always_true(expr); expr->cost = PRINT_COST; if (expr_open(state, expr, expr->sdata) != 0) { goto fail; } } return expr; fail: free_expr(expr); return NULL; } /** * Parse -fprint0 FILE. */ static struct expr *parse_fprint0(struct parser_state *state, int arg1, int arg2) { struct expr *expr = parse_unary_action(state, eval_fprint0); if (expr) { expr_set_always_true(expr); expr->cost = PRINT_COST; if (expr_open(state, expr, expr->sdata) != 0) { goto fail; } } return expr; fail: free_expr(expr); return NULL; } /** * Parse -fprintf FILE FORMAT. */ static struct expr *parse_fprintf(struct parser_state *state, int arg1, int arg2) { const char *arg = state->argv[0]; const char *file = state->argv[1]; if (!file) { parse_error(state, "%s needs a file.\n", arg); return NULL; } const char *format = state->argv[2]; if (!format) { parse_error(state, "%s needs a format string.\n", arg); return NULL; } struct expr *expr = parse_action(state, eval_fprintf, 3); if (!expr) { return NULL; } expr_set_always_true(expr); expr->cost = PRINT_COST; if (expr_open(state, expr, file) != 0) { goto fail; } expr->printf = parse_bfs_printf(format, state->cmdline); if (!expr->printf) { goto fail; } return expr; fail: free_expr(expr); return NULL; } /** * Parse -fstype TYPE. */ static struct expr *parse_fstype(struct parser_state *state, int arg1, int arg2) { struct cmdline *cmdline = state->cmdline; if (!cmdline->mtab) { parse_error(state, "Couldn't parse the mount table: %s.\n", strerror(cmdline->mtab_error)); return NULL; } struct expr *expr = parse_unary_test(state, eval_fstype); if (expr) { expr->cost = STAT_COST; } return expr; } /** * Parse -gid/-group. */ static struct expr *parse_group(struct parser_state *state, int arg1, int arg2) { const char *arg = state->argv[0]; struct expr *expr = parse_unary_test(state, eval_gid); if (!expr) { return NULL; } struct group *grp = getgrnam(expr->sdata); if (grp) { expr->idata = grp->gr_gid; expr->cmp_flag = CMP_EXACT; } else if (looks_like_icmp(expr->sdata)) { if (!parse_icmp(state, expr->sdata, expr, 0)) { goto fail; } } else { parse_error(state, "%s %s: No such group.\n", arg, expr->sdata); goto fail; } expr->cost = STAT_COST; return expr; fail: free_expr(expr); return NULL; } /** * Parse -unique. */ static struct expr *parse_unique(struct parser_state *state, int arg1, int arg2) { state->cmdline->unique = true; return parse_nullary_option(state); } /** * Parse -used N. */ static struct expr *parse_used(struct parser_state *state, int arg1, int arg2) { struct expr *expr = parse_test_icmp(state, eval_used); if (expr) { expr->cost = STAT_COST; } return expr; } /** * Parse -uid/-user. */ static struct expr *parse_user(struct parser_state *state, int arg1, int arg2) { const char *arg = state->argv[0]; struct expr *expr = parse_unary_test(state, eval_uid); if (!expr) { return NULL; } struct passwd *pwd = getpwnam(expr->sdata); if (pwd) { expr->idata = pwd->pw_uid; expr->cmp_flag = CMP_EXACT; } else if (looks_like_icmp(expr->sdata)) { if (!parse_icmp(state, expr->sdata, expr, 0)) { goto fail; } } else { parse_error(state, "%s %s: No such user.\n", arg, expr->sdata); goto fail; } expr->cost = STAT_COST; return expr; fail: free_expr(expr); return NULL; } /** * Parse -hidden. */ static struct expr *parse_hidden(struct parser_state *state, int arg1, int arg2) { struct expr *expr = parse_nullary_test(state, eval_hidden); if (expr) { expr->probability = 0.01; } return expr; } /** * Parse -(no)?ignore_readdir_race. */ static struct expr *parse_ignore_races(struct parser_state *state, int ignore, int arg2) { state->cmdline->ignore_races = ignore; return parse_nullary_option(state); } /** * Parse -inum N. */ static struct expr *parse_inum(struct parser_state *state, int arg1, int arg2) { struct expr *expr = parse_test_icmp(state, eval_inum); if (expr) { expr->cost = STAT_COST; expr->probability = expr->cmp_flag == CMP_EXACT ? 0.01 : 0.50; } return expr; } /** * Parse -links N. */ static struct expr *parse_links(struct parser_state *state, int arg1, int arg2) { struct expr *expr = parse_test_icmp(state, eval_links); if (expr) { expr->cost = STAT_COST; expr->probability = expr_cmp(expr, 1) ? 0.99 : 0.01; } return expr; } /** * Parse -ls. */ static struct expr *parse_ls(struct parser_state *state, int arg1, int arg2) { struct expr *expr = parse_nullary_action(state, eval_fls); if (expr) { init_print_expr(state, expr); expr->reftime = state->now; } return expr; } /** * Parse -mount. */ static struct expr *parse_mount(struct parser_state *state, int arg1, int arg2) { if (state->warn) { parse_warning(state, "In the future, %s will skip mount points entirely, unlike\n" "-xdev, due to http://austingroupbugs.net/view.php?id=1133.\n\n", state->argv[0]); } state->cmdline->flags |= BFTW_XDEV; state->mount_arg = state->argv[0]; return parse_nullary_option(state); } /** * Common code for fnmatch() tests. */ static struct expr *parse_fnmatch(const struct parser_state *state, struct expr *expr, bool casefold) { if (!expr) { return NULL; } if (casefold) { #ifdef FNM_CASEFOLD expr->idata = FNM_CASEFOLD; #else parse_error(state, "%s is missing platform support.\n", expr->argv[0]); free_expr(expr); return NULL; #endif } else { expr->idata = 0; } expr->cost = 400.0; if (strchr(expr->sdata, '*')) { expr->probability = 0.5; } else { expr->probability = 0.1; } return expr; } /** * Parse -i?name. */ static struct expr *parse_name(struct parser_state *state, int casefold, int arg2) { struct expr *expr = parse_unary_test(state, eval_name); return parse_fnmatch(state, expr, casefold); } /** * Parse -i?path, -i?wholename. */ static struct expr *parse_path(struct parser_state *state, int casefold, int arg2) { struct expr *expr = parse_unary_test(state, eval_path); return parse_fnmatch(state, expr, casefold); } /** * Parse -i?lname. */ static struct expr *parse_lname(struct parser_state *state, int casefold, int arg2) { struct expr *expr = parse_unary_test(state, eval_lname); return parse_fnmatch(state, expr, casefold); } /** Get the bfs_stat_field for X/Y in -newerXY */ static enum bfs_stat_field parse_newerxy_field(char c) { switch (c) { case 'a': return BFS_STAT_ATIME; case 'B': return BFS_STAT_BTIME; case 'c': return BFS_STAT_CTIME; case 'm': return BFS_STAT_MTIME; default: return 0; } } /** * Parse -newerXY. */ static struct expr *parse_newerxy(struct parser_state *state, int arg1, int arg2) { const char *arg = state->argv[0]; if (strlen(arg) != 8) { parse_error(state, "Expected -newerXY; found %s.\n", arg); return NULL; } struct expr *expr = parse_unary_test(state, eval_newer); if (!expr) { goto fail; } expr->stat_field = parse_newerxy_field(arg[6]); if (!expr->stat_field) { parse_error(state, "%s: For -newerXY, X should be 'a', 'c', 'm', or 'B'.\n", arg); goto fail; } if (arg[7] == 't') { parse_error(state, "%s: Explicit reference times ('t') are not supported.\n", arg); goto fail; } else { enum bfs_stat_field field = parse_newerxy_field(arg[7]); if (!field) { parse_error(state, "%s: For -newerXY, Y should be 'a', 'c', 'm', 'B', or 't'.\n", arg); goto fail; } struct bfs_stat sb; if (stat_arg(state, expr, &sb) != 0) { goto fail; } const struct timespec *reftime = bfs_stat_time(&sb, field); if (!reftime) { parse_error(state, "'%s': Couldn't get file %s.\n", expr->sdata, bfs_stat_field_name(field)); goto fail; } expr->reftime = *reftime; } expr->cost = STAT_COST; return expr; fail: free_expr(expr); return NULL; } /** * Parse -nogroup. */ static struct expr *parse_nogroup(struct parser_state *state, int arg1, int arg2) { struct expr *expr = parse_nullary_test(state, eval_nogroup); if (expr) { expr->cost = 9000.0; expr->probability = 0.01; expr->ephemeral_fds = 1; } return expr; } /** * Parse -nohidden. */ static struct expr *parse_nohidden(struct parser_state *state, int arg1, int arg2) { state->prune_arg = state->argv[0]; return parse_nullary_action(state, eval_nohidden); } /** * Parse -noleaf. */ static struct expr *parse_noleaf(struct parser_state *state, int arg1, int arg2) { if (state->warn) { parse_warning(state, "bfs does not apply the optimization that %s inhibits.\n\n", state->argv[0]); } return parse_nullary_option(state); } /** * Parse -nouser. */ static struct expr *parse_nouser(struct parser_state *state, int arg1, int arg2) { struct expr *expr = parse_nullary_test(state, eval_nouser); if (expr) { expr->cost = 9000.0; expr->probability = 0.01; expr->ephemeral_fds = 1; } return expr; } /** * Parse a permission mode like chmod(1). */ static int parse_mode(const struct parser_state *state, const char *mode, struct expr *expr) { if (mode[0] >= '0' && mode[0] <= '9') { unsigned int parsed; if (!parse_int(state, mode, &parsed, 8 | IF_INT | IF_UNSIGNED | IF_QUIET)) { goto fail; } if (parsed > 07777) { goto fail; } expr->file_mode = parsed; expr->dir_mode = parsed; return 0; } expr->file_mode = 0; expr->dir_mode = 0; // Parse the same grammar as chmod(1), which looks like this: // // MODE : CLAUSE ["," CLAUSE]* // // CLAUSE : WHO* ACTION+ // // WHO : "u" | "g" | "o" | "a" // // ACTION : OP PERM* // | OP PERMCOPY // // OP : "+" | "-" | "=" // // PERM : "r" | "w" | "x" | "X" | "s" | "t" // // PERMCOPY : "u" | "g" | "o" // State machine state enum { MODE_CLAUSE, MODE_WHO, MODE_ACTION, MODE_ACTION_APPLY, MODE_OP, MODE_PERM, } mstate = MODE_CLAUSE; enum { MODE_PLUS, MODE_MINUS, MODE_EQUALS, } op; mode_t who; mode_t file_change; mode_t dir_change; const char *i = mode; while (true) { switch (mstate) { case MODE_CLAUSE: who = 0; mstate = MODE_WHO; // Fallthrough case MODE_WHO: switch (*i) { case 'u': who |= 0700; break; case 'g': who |= 0070; break; case 'o': who |= 0007; break; case 'a': who |= 0777; break; default: mstate = MODE_ACTION; continue; } break; case MODE_ACTION_APPLY: switch (op) { case MODE_EQUALS: expr->file_mode &= ~who; expr->dir_mode &= ~who; // Fallthrough case MODE_PLUS: expr->file_mode |= file_change; expr->dir_mode |= dir_change; break; case MODE_MINUS: expr->file_mode &= ~file_change; expr->dir_mode &= ~dir_change; break; } // Fallthrough case MODE_ACTION: if (who == 0) { who = 0777; } switch (*i) { case '+': op = MODE_PLUS; mstate = MODE_OP; break; case '-': op = MODE_MINUS; mstate = MODE_OP; break; case '=': op = MODE_EQUALS; mstate = MODE_OP; break; case ',': if (mstate == MODE_ACTION_APPLY) { mstate = MODE_CLAUSE; } else { goto fail; } break; case '\0': if (mstate == MODE_ACTION_APPLY) { goto done; } else { goto fail; } default: goto fail; } break; case MODE_OP: switch (*i) { case 'u': file_change = (expr->file_mode >> 6) & 07; dir_change = (expr->dir_mode >> 6) & 07; break; case 'g': file_change = (expr->file_mode >> 3) & 07; dir_change = (expr->dir_mode >> 3) & 07; break; case 'o': file_change = expr->file_mode & 07; dir_change = expr->dir_mode & 07; break; default: file_change = 0; dir_change = 0; mstate = MODE_PERM; continue; } file_change |= (file_change << 6) | (file_change << 3); file_change &= who; dir_change |= (dir_change << 6) | (dir_change << 3); dir_change &= who; mstate = MODE_ACTION_APPLY; break; case MODE_PERM: switch (*i) { case 'r': file_change |= who & 0444; dir_change |= who & 0444; break; case 'w': file_change |= who & 0222; dir_change |= who & 0222; break; case 'x': file_change |= who & 0111; // Fallthrough case 'X': dir_change |= who & 0111; break; case 's': if (who & 0700) { file_change |= S_ISUID; dir_change |= S_ISUID; } if (who & 0070) { file_change |= S_ISGID; dir_change |= S_ISGID; } break; case 't': file_change |= S_ISVTX; dir_change |= S_ISVTX; break; default: mstate = MODE_ACTION_APPLY; continue; } break; } ++i; } done: return 0; fail: parse_error(state, "'%s' is an invalid mode.\n", mode); return -1; } /** * Parse -perm MODE. */ static struct expr *parse_perm(struct parser_state *state, int field, int arg2) { struct expr *expr = parse_unary_test(state, eval_perm); if (!expr) { return NULL; } const char *mode = expr->sdata; switch (mode[0]) { case '-': expr->mode_cmp = MODE_ALL; ++mode; break; case '/': expr->mode_cmp = MODE_ANY; ++mode; break; case '+': if (mode[1] >= '0' && mode[1] <= '9') { expr->mode_cmp = MODE_ANY; ++mode; break; } // Fallthrough default: expr->mode_cmp = MODE_EXACT; break; } if (parse_mode(state, mode, expr) != 0) { goto fail; } expr->cost = STAT_COST; return expr; fail: free_expr(expr); return NULL; } /** * Parse -print. */ static struct expr *parse_print(struct parser_state *state, int arg1, int arg2) { struct expr *expr = parse_nullary_action(state, eval_fprint); if (expr) { init_print_expr(state, expr); } return expr; } /** * Parse -print0. */ static struct expr *parse_print0(struct parser_state *state, int arg1, int arg2) { struct expr *expr = parse_nullary_action(state, eval_fprint0); if (expr) { init_print_expr(state, expr); } return expr; } /** * Parse -printf FORMAT. */ static struct expr *parse_printf(struct parser_state *state, int arg1, int arg2) { struct expr *expr = parse_unary_action(state, eval_fprintf); if (!expr) { return NULL; } init_print_expr(state, expr); expr->printf = parse_bfs_printf(expr->sdata, state->cmdline); if (!expr->printf) { free_expr(expr); return NULL; } return expr; } /** * Parse -printx. */ static struct expr *parse_printx(struct parser_state *state, int arg1, int arg2) { struct expr *expr = parse_nullary_action(state, eval_fprintx); if (expr) { init_print_expr(state, expr); } return expr; } /** * Parse -prune. */ static struct expr *parse_prune(struct parser_state *state, int arg1, int arg2) { state->prune_arg = state->argv[0]; struct expr *expr = parse_nullary_action(state, eval_prune); if (expr) { expr_set_always_true(expr); } return expr; } /** * Parse -quit. */ static struct expr *parse_quit(struct parser_state *state, int arg1, int arg2) { struct expr *expr = parse_nullary_action(state, eval_quit); if (expr) { expr_set_never_returns(expr); } return expr; } /** * Parse -i?regex. */ static struct expr *parse_regex(struct parser_state *state, int flags, int arg2) { struct expr *expr = parse_unary_test(state, eval_regex); if (!expr) { goto fail; } expr->regex = malloc(sizeof(regex_t)); if (!expr->regex) { perror("malloc()"); goto fail; } int err = regcomp(expr->regex, expr->sdata, state->regex_flags | flags); if (err != 0) { char *str = xregerror(err, expr->regex); if (str) { parse_error(state, "%s %s: %s.\n", expr->argv[0], expr->argv[1], str); free(str); } else { perror("xregerror()"); } goto fail_regex; } return expr; fail_regex: free(expr->regex); expr->regex = NULL; fail: free_expr(expr); return NULL; } /** * Parse -E. */ static struct expr *parse_regex_extended(struct parser_state *state, int arg1, int arg2) { state->regex_flags = REG_EXTENDED; return parse_nullary_flag(state); } /** * Parse -regextype TYPE. */ static struct expr *parse_regextype(struct parser_state *state, int arg1, int arg2) { const char *type; struct expr *expr = parse_unary_positional_option(state, &type); if (!expr) { goto fail; } FILE *file = stderr; if (strcmp(type, "posix-basic") == 0) { state->regex_flags = 0; } else if (strcmp(type, "posix-extended") == 0) { state->regex_flags = REG_EXTENDED; } else if (strcmp(type, "help") == 0) { state->just_info = true; file = stdout; goto fail_list_types; } else { goto fail_bad_type; } return expr; fail_bad_type: parse_error(state, "Unsupported -regextype '%s'.\n\n", type); fail_list_types: fputs("Supported types are:\n\n", file); fputs(" posix-basic: POSIX basic regular expressions (BRE)\n", file); fputs(" posix-extended: POSIX extended regular expressions (ERE)\n", file); fail: free_expr(expr); return NULL; } /** * Parse -samefile FILE. */ static struct expr *parse_samefile(struct parser_state *state, int arg1, int arg2) { struct expr *expr = parse_unary_test(state, eval_samefile); if (!expr) { return NULL; } struct bfs_stat sb; if (stat_arg(state, expr, &sb) != 0) { free_expr(expr); return NULL; } expr->dev = sb.dev; expr->ino = sb.ino; expr->cost = STAT_COST; expr->probability = 0.01; return expr; } /** * Parse -S STRATEGY. */ static struct expr *parse_search_strategy(struct parser_state *state, int arg1, int arg2) { const char *flag = state->argv[0]; const char *arg = state->argv[1]; if (!arg) { parse_error(state, "%s needs an argument.\n", flag); return NULL; } struct cmdline *cmdline = state->cmdline; FILE *file = stderr; if (strcmp(arg, "bfs") == 0) { cmdline->strategy = BFTW_BFS; } else if (strcmp(arg, "dfs") == 0) { cmdline->strategy = BFTW_DFS; } else if (strcmp(arg, "ids") == 0) { cmdline->strategy = BFTW_IDS; } else if (strcmp(arg, "help") == 0) { state->just_info = true; file = stdout; goto fail_list_strategies; } else { goto fail_bad_strategy; } return parse_unary_flag(state); fail_bad_strategy: parse_error(state, "Unrecognized search strategy '%s'.\n\n", arg); fail_list_strategies: fputs("Supported search strategies:\n\n", file); fputs(" bfs: breadth-first search\n", file); fputs(" dfs: depth-first search\n", file); fputs(" ids: iterative deepening search\n", file); return NULL; } /** * Parse -size N[cwbkMGTP]?. */ static struct expr *parse_size(struct parser_state *state, int arg1, int arg2) { struct expr *expr = parse_unary_test(state, eval_size); if (!expr) { return NULL; } const char *unit = parse_icmp(state, expr->sdata, expr, IF_PARTIAL_OK); if (!unit) { goto fail; } if (strlen(unit) > 1) { goto bad_unit; } switch (*unit) { case '\0': case 'b': expr->size_unit = SIZE_BLOCKS; break; case 'c': expr->size_unit = SIZE_BYTES; break; case 'w': expr->size_unit = SIZE_WORDS; break; case 'k': expr->size_unit = SIZE_KB; break; case 'M': expr->size_unit = SIZE_MB; break; case 'G': expr->size_unit = SIZE_GB; break; case 'T': expr->size_unit = SIZE_TB; break; case 'P': expr->size_unit = SIZE_PB; break; default: goto bad_unit; } expr->cost = STAT_COST; expr->probability = expr->cmp_flag == CMP_EXACT ? 0.01 : 0.50; return expr; bad_unit: parse_error(state, "%s %s: Expected a size unit (one of cwbkMGTP); found '%s'.\n", expr->argv[0], expr->argv[1], unit); fail: free_expr(expr); return NULL; } /** * Parse -sparse. */ static struct expr *parse_sparse(struct parser_state *state, int arg1, int arg2) { struct expr *expr = parse_nullary_test(state, eval_sparse); if (expr) { expr->cost = STAT_COST; } return expr; } /** * Parse -x?type [bcdpflsD]. */ static struct expr *parse_type(struct parser_state *state, int x, int arg2) { eval_fn *eval = x ? eval_xtype : eval_type; struct expr *expr = parse_unary_test(state, eval); if (!expr) { return NULL; } enum bftw_typeflag types = 0; double probability = 0.0; const char *c = expr->sdata; while (true) { enum bftw_typeflag type; double type_prob; switch (*c) { case 'b': type = BFTW_BLK; type_prob = 0.00000721183; break; case 'c': type = BFTW_CHR; type_prob = 0.0000499855; break; case 'd': type = BFTW_DIR; type_prob = 0.114475; break; case 'D': type = BFTW_DOOR; type_prob = 0.000001; break; case 'p': type = BFTW_FIFO; type_prob = 0.00000248684; break; case 'f': type = BFTW_REG; type_prob = 0.859772; break; case 'l': type = BFTW_LNK; type_prob = 0.0256816; break; case 's': type = BFTW_SOCK; type_prob = 0.0000116881; break; case 'w': type = BFTW_WHT; type_prob = 0.000001; break; case '\0': parse_error(state, "%s %s: Expected a type flag.\n", expr->argv[0], expr->argv[1]); goto fail; default: parse_error(state, "%s %s: Unknown type flag '%c' (expected one of [bcdpflsD]).\n", expr->argv[0], expr->argv[1], *c); goto fail; } if (!(types & type)) { types |= type; probability += type_prob; } ++c; if (*c == '\0') { break; } else if (*c == ',') { ++c; continue; } else { parse_error(state, "%s %s: Types must be comma-separated.\n", expr->argv[0], expr->argv[1]); goto fail; } } expr->idata = types; expr->probability = probability; if (x && state->cmdline->optlevel < 4) { // Since -xtype dereferences symbolic links, it may have side // effects such as reporting permission errors, and thus // shouldn't be re-ordered without aggressive optimizations expr->pure = false; } return expr; fail: free_expr(expr); return NULL; } /** * Parse -(no)?warn. */ static struct expr *parse_warn(struct parser_state *state, int warn, int arg2) { state->warn = warn; return parse_nullary_positional_option(state); } /** * Parse -xattr. */ static struct expr *parse_xattr(struct parser_state *state, int arg1, int arg2) { #if BFS_CAN_CHECK_XATTRS struct expr *expr = parse_nullary_test(state, eval_xattr); if (expr) { expr->cost = STAT_COST; expr->probability = 0.01; } return expr; #else parse_error(state, "%s is missing platform support.\n", state->argv[0]); return NULL; #endif } /** * Parse -xdev. */ static struct expr *parse_xdev(struct parser_state *state, int arg1, int arg2) { state->cmdline->flags |= BFTW_XDEV; state->xdev_arg = state->argv[0]; return parse_nullary_option(state); } /** * Launch a pager for the help output. */ static CFILE *launch_pager(pid_t *pid, CFILE *cout) { char *pager = getenv("PAGER"); if (!pager) { pager = "more"; } int pipefd[2]; if (pipe(pipefd) != 0) { goto fail; } FILE *file = fdopen(pipefd[1], "w"); if (!file) { goto fail_pipe; } pipefd[1] = -1; CFILE *ret = cfdup(file, NULL); if (!ret) { goto fail_file; } file = NULL; ret->close = true; ret->colors = cout->colors; struct bfs_spawn ctx; if (bfs_spawn_init(&ctx) != 0) { goto fail_ret; } if (bfs_spawn_setflags(&ctx, BFS_SPAWN_USEPATH) != 0) { goto fail_ctx; } if (bfs_spawn_addclose(&ctx, fileno(ret->file)) != 0) { goto fail_ctx; } if (bfs_spawn_adddup2(&ctx, pipefd[0], STDIN_FILENO) != 0) { goto fail_ctx; } if (bfs_spawn_addclose(&ctx, pipefd[0]) != 0) { goto fail_ctx; } char *argv[] = { pager, NULL, }; extern char **environ; char **envp = environ; if (!getenv("LESS")) { size_t envc; for (envc = 0; environ[envc]; ++envc); ++envc; envp = malloc((envc + 1)*sizeof(*envp)); if (!envp) { goto fail_ctx; } memcpy(envp, environ, (envc - 1)*sizeof(*envp)); envp[envc - 1] = "LESS=FKRX"; envp[envc] = NULL; } *pid = bfs_spawn(pager, &ctx, argv, envp); if (*pid < 0) { goto fail_envp; } close(pipefd[0]); if (envp != environ) { free(envp); } bfs_spawn_destroy(&ctx); return ret; fail_envp: if (envp != environ) { free(envp); } fail_ctx: bfs_spawn_destroy(&ctx); fail_ret: cfclose(ret); fail_file: if (file) { fclose(file); } fail_pipe: if (pipefd[1] >= 0) { close(pipefd[1]); } if (pipefd[0] >= 0) { close(pipefd[0]); } fail: return cout; } /** * "Parse" -help. */ static struct expr *parse_help(struct parser_state *state, int arg1, int arg2) { CFILE *cout = state->cmdline->cout; pid_t pager = -1; if (state->stdout_tty) { cout = launch_pager(&pager, cout); } cfprintf(cout, "Usage: ${ex}%s${rs} [${cyn}flags${rs}...] [${mag}paths${rs}...] [${blu}expression${rs}...]\n\n", state->command); cfprintf(cout, "${ex}bfs${rs} is compatible with ${ex}find${rs}, with some extensions. " "${cyn}Flags${rs} (${cyn}-H${rs}/${cyn}-L${rs}/${cyn}-P${rs} etc.), ${mag}paths${rs},\n" "and ${blu}expressions${rs} may be freely mixed in any order.\n\n"); cfprintf(cout, "${bld}Flags:${rs}\n\n"); cfprintf(cout, " ${cyn}-H${rs}\n"); cfprintf(cout, " Follow symbolic links on the command line, but not while searching\n"); cfprintf(cout, " ${cyn}-L${rs}\n"); cfprintf(cout, " Follow all symbolic links\n"); cfprintf(cout, " ${cyn}-P${rs}\n"); cfprintf(cout, " Never follow symbolic links (the default)\n"); cfprintf(cout, " ${cyn}-E${rs}\n"); cfprintf(cout, " Use extended regular expressions (same as ${blu}-regextype${rs} ${bld}posix-extended${rs})\n"); cfprintf(cout, " ${cyn}-X${rs}\n"); cfprintf(cout, " Filter out files with non-${ex}xargs${rs}-safe names\n"); cfprintf(cout, " ${cyn}-d${rs}\n"); cfprintf(cout, " Search in post-order (same as ${blu}-depth${rs})\n"); cfprintf(cout, " ${cyn}-x${rs}\n"); cfprintf(cout, " Don't descend into other mount points (same as ${blu}-xdev${rs})\n"); cfprintf(cout, " ${cyn}-f${rs} ${mag}PATH${rs}\n"); cfprintf(cout, " Treat ${mag}PATH${rs} as a path to search (useful if begins with a dash)\n"); cfprintf(cout, " ${cyn}-D${rs} ${bld}FLAG${rs}\n"); cfprintf(cout, " Turn on a debugging flag (see ${cyn}-D${rs} ${bld}help${rs})\n"); cfprintf(cout, " ${cyn}-O${rs}${bld}N${rs}\n"); cfprintf(cout, " Enable optimization level ${bld}N${rs} (default: 3)\n"); cfprintf(cout, " ${cyn}-S${rs} ${bld}bfs${rs}|${bld}dfs${rs}|${bld}ids${rs}\n"); cfprintf(cout, " Use ${bld}b${rs}readth-${bld}f${rs}irst/${bld}d${rs}epth-${bld}f${rs}irst/${bld}i${rs}terative ${bld}d${rs}eepening ${bld}s${rs}earch (default: ${cyn}-S${rs} ${bld}bfs${rs})\n\n"); cfprintf(cout, "${bld}Operators:${rs}\n\n"); cfprintf(cout, " ${red}(${rs} ${blu}expression${rs} ${red})${rs}\n\n"); cfprintf(cout, " ${red}!${rs} ${blu}expression${rs}\n"); cfprintf(cout, " ${red}-not${rs} ${blu}expression${rs}\n\n"); cfprintf(cout, " ${blu}expression${rs} ${blu}expression${rs}\n"); cfprintf(cout, " ${blu}expression${rs} ${red}-a${rs} ${blu}expression${rs}\n"); cfprintf(cout, " ${blu}expression${rs} ${red}-and${rs} ${blu}expression${rs}\n\n"); cfprintf(cout, " ${blu}expression${rs} ${red}-o${rs} ${blu}expression${rs}\n"); cfprintf(cout, " ${blu}expression${rs} ${red}-or${rs} ${blu}expression${rs}\n\n"); cfprintf(cout, " ${blu}expression${rs} ${red},${rs} ${blu}expression${rs}\n\n"); cfprintf(cout, "${bld}Options:${rs}\n\n"); cfprintf(cout, " ${blu}-color${rs}\n"); cfprintf(cout, " ${blu}-nocolor${rs}\n"); cfprintf(cout, " Turn colors on or off (default: ${blu}-color${rs} if outputting to a terminal,\n"); cfprintf(cout, " ${blu}-nocolor${rs} otherwise)\n"); cfprintf(cout, " ${blu}-daystart${rs}\n"); cfprintf(cout, " Measure times relative to the start of today\n"); cfprintf(cout, " ${blu}-depth${rs}\n"); cfprintf(cout, " Search in post-order (descendents first)\n"); cfprintf(cout, " ${blu}-follow${rs}\n"); cfprintf(cout, " Follow all symbolic links (same as ${cyn}-L${rs})\n"); cfprintf(cout, " ${blu}-ignore_readdir_race${rs}\n"); cfprintf(cout, " ${blu}-noignore_readdir_race${rs}\n"); cfprintf(cout, " Whether to report an error if ${ex}bfs${rs} detects that the file tree is modified\n"); cfprintf(cout, " during the search (default: ${blu}-noignore_readdir_race${rs})\n"); cfprintf(cout, " ${blu}-maxdepth${rs} ${bld}N${rs}\n"); cfprintf(cout, " ${blu}-mindepth${rs} ${bld}N${rs}\n"); cfprintf(cout, " Ignore files deeper/shallower than ${bld}N${rs}\n"); cfprintf(cout, " ${blu}-mount${rs}\n"); cfprintf(cout, " Don't descend into other mount points (same as ${blu}-xdev${rs} for now, but will\n"); cfprintf(cout, " skip mount points entirely in the future)\n"); cfprintf(cout, " ${blu}-noleaf${rs}\n"); cfprintf(cout, " Ignored; for compatibility with GNU find\n"); cfprintf(cout, " ${blu}-regextype${rs} ${bld}TYPE${rs}\n"); cfprintf(cout, " Use ${bld}TYPE${rs}-flavored regexes (default: ${bld}posix-basic${rs}; see ${blu}-regextype${rs}" " ${bld}help${rs})\n"); cfprintf(cout, " ${blu}-unique${rs}\n"); cfprintf(cout, " Skip any files that have already been seen\n"); cfprintf(cout, " ${blu}-warn${rs}\n"); cfprintf(cout, " ${blu}-nowarn${rs}\n"); cfprintf(cout, " Turn on or off warnings about the command line\n"); cfprintf(cout, " ${blu}-xdev${rs}\n"); cfprintf(cout, " Don't descend into other mount points\n\n"); cfprintf(cout, "${bld}Tests:${rs}\n\n"); #if BFS_CAN_CHECK_ACL cfprintf(cout, " ${blu}-acl${rs}\n"); cfprintf(cout, " Find files with a non-trivial Access Control List\n"); #endif cfprintf(cout, " ${blu}-${rs}[${blu}aBcm${rs}]${blu}min${rs} ${bld}[-+]N${rs}\n"); cfprintf(cout, " Find files ${blu}a${rs}ccessed/${blu}B${rs}irthed/${blu}c${rs}hanged/${blu}m${rs}odified ${bld}N${rs} minutes ago\n"); cfprintf(cout, " ${blu}-${rs}[${blu}aBcm${rs}]${blu}newer${rs} ${bld}FILE${rs}\n"); cfprintf(cout, " Find files ${blu}a${rs}ccessed/${blu}B${rs}irthed/${blu}c${rs}hanged/${blu}m${rs}odified more recently than ${bld}FILE${rs} was\n" " modified\n"); cfprintf(cout, " ${blu}-${rs}[${blu}aBcm${rs}]${blu}time${rs} ${bld}[-+]N${rs}\n"); cfprintf(cout, " Find files ${blu}a${rs}ccessed/${blu}B${rs}irthed/${blu}c${rs}hanged/${blu}m${rs}odified ${bld}N${rs} days ago\n"); #if BFS_CAN_CHECK_CAPABILITIES cfprintf(cout, " ${blu}-capable${rs}\n"); cfprintf(cout, " Find files with POSIX.1e capabilities set\n"); #endif cfprintf(cout, " ${blu}-depth${rs} ${bld}[-+]N${rs}\n"); cfprintf(cout, " Find files with depth ${bld}N${rs}\n"); cfprintf(cout, " ${blu}-empty${rs}\n"); cfprintf(cout, " Find empty files/directories\n"); cfprintf(cout, " ${blu}-executable${rs}\n"); cfprintf(cout, " ${blu}-readable${rs}\n"); cfprintf(cout, " ${blu}-writable${rs}\n"); cfprintf(cout, " Find files the current user can execute/read/write\n"); cfprintf(cout, " ${blu}-false${rs}\n"); cfprintf(cout, " ${blu}-true${rs}\n"); cfprintf(cout, " Always false/true\n"); cfprintf(cout, " ${blu}-fstype${rs} ${bld}TYPE${rs}\n"); cfprintf(cout, " Find files on file systems with the given ${bld}TYPE${rs}\n"); cfprintf(cout, " ${blu}-gid${rs} ${bld}[-+]N${rs}\n"); cfprintf(cout, " ${blu}-uid${rs} ${bld}[-+]N${rs}\n"); cfprintf(cout, " Find files owned by group/user ID ${bld}N${rs}\n"); cfprintf(cout, " ${blu}-group${rs} ${bld}NAME${rs}\n"); cfprintf(cout, " ${blu}-user${rs} ${bld}NAME${rs}\n"); cfprintf(cout, " Find files owned by the group/user ${bld}NAME${rs}\n"); cfprintf(cout, " ${blu}-hidden${rs}\n"); cfprintf(cout, " ${blu}-nohidden${rs}\n"); cfprintf(cout, " Find hidden files, or filter them out\n"); #ifdef FNM_CASEFOLD cfprintf(cout, " ${blu}-ilname${rs} ${bld}GLOB${rs}\n"); cfprintf(cout, " ${blu}-iname${rs} ${bld}GLOB${rs}\n"); cfprintf(cout, " ${blu}-ipath${rs} ${bld}GLOB${rs}\n"); cfprintf(cout, " ${blu}-iregex${rs} ${bld}REGEX${rs}\n"); cfprintf(cout, " ${blu}-iwholename${rs} ${bld}GLOB${rs}\n"); cfprintf(cout, " Case-insensitive versions of ${blu}-lname${rs}/${blu}-name${rs}/${blu}-path${rs}" "/${blu}-regex${rs}/${blu}-wholename${rs}\n"); #endif cfprintf(cout, " ${blu}-inum${rs} ${bld}[-+]N${rs}\n"); cfprintf(cout, " Find files with inode number ${bld}N${rs}\n"); cfprintf(cout, " ${blu}-links${rs} ${bld}[-+]N${rs}\n"); cfprintf(cout, " Find files with ${bld}N${rs} hard links\n"); cfprintf(cout, " ${blu}-lname${rs} ${bld}GLOB${rs}\n"); cfprintf(cout, " Find symbolic links whose target matches the ${bld}GLOB${rs}\n"); cfprintf(cout, " ${blu}-name${rs} ${bld}GLOB${rs}\n"); cfprintf(cout, " Find files whose name matches the ${bld}GLOB${rs}\n"); cfprintf(cout, " ${blu}-newer${rs} ${bld}FILE${rs}\n"); cfprintf(cout, " Find files newer than ${bld}FILE${rs}\n"); cfprintf(cout, " ${blu}-newer${rs}${bld}XY${rs} ${bld}REFERENCE${rs}\n"); cfprintf(cout, " Find files whose ${bld}X${rs} time is newer than the ${bld}Y${rs} time of" " ${bld}REFERENCE${rs}. ${bld}X${rs} and ${bld}Y${rs}\n"); cfprintf(cout, " can be any of [${bld}aBcm${rs}].\n"); cfprintf(cout, " ${blu}-nogroup${rs}\n"); cfprintf(cout, " ${blu}-nouser${rs}\n"); cfprintf(cout, " Find files owned by nonexistent groups/users\n"); cfprintf(cout, " ${blu}-path${rs} ${bld}GLOB${rs}\n"); cfprintf(cout, " ${blu}-wholename${rs} ${bld}GLOB${rs}\n"); cfprintf(cout, " Find files whose entire path matches the ${bld}GLOB${rs}\n"); cfprintf(cout, " ${blu}-perm${rs} ${bld}[-]MODE${rs}\n"); cfprintf(cout, " Find files with a matching mode\n"); cfprintf(cout, " ${blu}-regex${rs} ${bld}REGEX${rs}\n"); cfprintf(cout, " Find files whose entire path matches the regular expression ${bld}REGEX${rs}\n"); cfprintf(cout, " ${blu}-samefile${rs} ${bld}FILE${rs}\n"); cfprintf(cout, " Find hard links to ${bld}FILE${rs}\n"); cfprintf(cout, " ${blu}-size${rs} ${bld}[-+]N[cwbkMGTP]${rs}\n"); cfprintf(cout, " Find files with the given size, in 1-byte ${bld}c${rs}haracters, 2-byte ${bld}w${rs}ords,\n"); cfprintf(cout, " 512-byte ${bld}b${rs}locks (default), or ${bld}k${rs}iB/${bld}M${rs}iB/${bld}G${rs}iB/${bld}T${rs}iB/${bld}P${rs}iB\n"); cfprintf(cout, " ${blu}-sparse${rs}\n"); cfprintf(cout, " Find files that occupy fewer disk blocks than expected\n"); cfprintf(cout, " ${blu}-type${rs} ${bld}[bcdlpfswD]${rs}\n"); cfprintf(cout, " Find files of the given type\n"); cfprintf(cout, " ${blu}-used${rs} ${bld}[-+]N${rs}\n"); cfprintf(cout, " Find files last accessed ${bld}N${rs} days after they were changed\n"); #if BFS_CAN_CHECK_XATTRS cfprintf(cout, " ${blu}-xattr${rs}\n"); cfprintf(cout, " Find files with extended attributes\n"); #endif cfprintf(cout, " ${blu}-xtype${rs} ${bld}[bcdlpfswD]${rs}\n"); cfprintf(cout, " Find files of the given type, following links when ${blu}-type${rs} would not, and\n"); cfprintf(cout, " vice versa\n\n"); cfprintf(cout, "${bld}Actions:${rs}\n\n"); cfprintf(cout, " ${blu}-delete${rs}\n"); cfprintf(cout, " ${blu}-rm${rs}\n"); cfprintf(cout, " Delete any found files (implies ${blu}-depth${rs})\n"); cfprintf(cout, " ${blu}-exec${rs} ${bld}command ... {} ;${rs}\n"); cfprintf(cout, " Execute a command\n"); cfprintf(cout, " ${blu}-exec${rs} ${bld}command ... {} +${rs}\n"); cfprintf(cout, " Execute a command with multiple files at once\n"); cfprintf(cout, " ${blu}-ok${rs} ${bld}command ... {} ;${rs}\n"); cfprintf(cout, " Prompt the user whether to execute a command\n"); cfprintf(cout, " ${blu}-execdir${rs} ${bld}command ... {} ;${rs}\n"); cfprintf(cout, " ${blu}-execdir${rs} ${bld}command ... {} +${rs}\n"); cfprintf(cout, " ${blu}-okdir${rs} ${bld}command ... {} ;${rs}\n"); cfprintf(cout, " Like ${blu}-exec${rs}/${blu}-ok${rs}, but run the command in the same directory as the found\n"); cfprintf(cout, " file(s)\n"); cfprintf(cout, " ${blu}-exit${rs} [${bld}STATUS${rs}]\n"); cfprintf(cout, " Exit immediately with the given status (%d if unspecified)\n", EXIT_SUCCESS); cfprintf(cout, " ${blu}-fls${rs} ${bld}FILE${rs}\n"); cfprintf(cout, " ${blu}-fprint${rs} ${bld}FILE${rs}\n"); cfprintf(cout, " ${blu}-fprint0${rs} ${bld}FILE${rs}\n"); cfprintf(cout, " ${blu}-fprintf${rs} ${bld}FORMAT${rs} ${bld}FILE${rs}\n"); cfprintf(cout, " Like ${blu}-ls${rs}/${blu}-print${rs}/${blu}-print0${rs}/${blu}-printf${rs}, but write to ${bld}FILE${rs} instead of standard\n" " output\n"); cfprintf(cout, " ${blu}-ls${rs}\n"); cfprintf(cout, " List files like ${ex}ls${rs} ${bld}-dils${rs}\n"); cfprintf(cout, " ${blu}-nohidden${rs}\n"); cfprintf(cout, " Filter out hidden files and directories\n"); cfprintf(cout, " ${blu}-print${rs}\n"); cfprintf(cout, " Print the path to the found file\n"); cfprintf(cout, " ${blu}-print0${rs}\n"); cfprintf(cout, " Like ${blu}-print${rs}, but use the null character ('\\0') as a separator rather than\n"); cfprintf(cout, " newlines\n"); cfprintf(cout, " ${blu}-printf${rs} ${bld}FORMAT${rs}\n"); cfprintf(cout, " Print according to a format string (see ${ex}man${rs} ${bld}find${rs}). The additional format\n"); cfprintf(cout, " directives %%w and %%W${bld}k${rs} for printing file birth times are supported.\n"); cfprintf(cout, " ${blu}-printx${rs}\n"); cfprintf(cout, " Like ${blu}-print${rs}, but escape whitespace and quotation characters, to make the\n"); cfprintf(cout, " output safe for ${ex}xargs${rs}. Consider using ${blu}-print0${rs} and ${ex}xargs${rs} ${bld}-0${rs} instead.\n"); cfprintf(cout, " ${blu}-prune${rs}\n"); cfprintf(cout, " Don't descend into this directory\n"); cfprintf(cout, " ${blu}-quit${rs}\n"); cfprintf(cout, " Quit immediately\n"); cfprintf(cout, " ${blu}-version${rs}\n"); cfprintf(cout, " Print version information\n"); cfprintf(cout, " ${blu}-help${rs}\n"); cfprintf(cout, " Print this help message\n\n"); cfprintf(cout, "%s\n", BFS_HOMEPAGE); if (pager > 0) { cfclose(cout); waitpid(pager, NULL, 0); } state->just_info = true; return NULL; } /** * "Parse" -version. */ static struct expr *parse_version(struct parser_state *state, int arg1, int arg2) { cfprintf(state->cmdline->cout, "${ex}bfs${rs} ${bld}%s${rs}\n\n", BFS_VERSION); printf("%s\n", BFS_HOMEPAGE); state->just_info = true; return NULL; } typedef struct expr *parse_fn(struct parser_state *state, int arg1, int arg2); /** * An entry in the parse table for literals. */ struct table_entry { char *arg; parse_fn *parse; int arg1; int arg2; bool prefix; }; /** * The parse table for literals. */ static const struct table_entry parse_table[] = { {"--"}, {"--help", parse_help}, {"--version", parse_version}, {"-Bmin", parse_time, BFS_STAT_BTIME, MINUTES}, {"-Bnewer", parse_newer, BFS_STAT_BTIME}, {"-Btime", parse_time, BFS_STAT_BTIME, DAYS}, {"-D", parse_debug}, {"-E", parse_regex_extended}, {"-H", parse_follow, BFTW_COMFOLLOW, false}, {"-L", parse_follow, BFTW_LOGICAL, false}, {"-O", parse_optlevel, 0, 0, true}, {"-P", parse_follow, 0, false}, {"-S", parse_search_strategy}, {"-X", parse_xargs_safe}, {"-a"}, {"-acl", parse_acl}, {"-amin", parse_time, BFS_STAT_ATIME, MINUTES}, {"-and"}, {"-anewer", parse_newer, BFS_STAT_ATIME}, {"-atime", parse_time, BFS_STAT_ATIME, DAYS}, {"-capable", parse_capable}, {"-cmin", parse_time, BFS_STAT_CTIME, MINUTES}, {"-cnewer", parse_newer, BFS_STAT_CTIME}, {"-color", parse_color, true}, {"-ctime", parse_time, BFS_STAT_CTIME, DAYS}, {"-d", parse_depth}, {"-daystart", parse_daystart}, {"-delete", parse_delete}, {"-depth", parse_depth_n}, {"-empty", parse_empty}, {"-exec", parse_exec, 0}, {"-execdir", parse_exec, BFS_EXEC_CHDIR}, {"-executable", parse_access, X_OK}, {"-exit", parse_exit}, {"-f", parse_f}, {"-false", parse_const, false}, {"-fls", parse_fls}, {"-follow", parse_follow, BFTW_LOGICAL, true}, {"-fprint", parse_fprint}, {"-fprint0", parse_fprint0}, {"-fprintf", parse_fprintf}, {"-fstype", parse_fstype}, {"-gid", parse_group}, {"-group", parse_group}, {"-help", parse_help}, {"-hidden", parse_hidden}, {"-ignore_readdir_race", parse_ignore_races, true}, {"-ilname", parse_lname, true}, {"-iname", parse_name, true}, {"-inum", parse_inum}, {"-ipath", parse_path, true}, {"-iregex", parse_regex, REG_ICASE}, {"-iwholename", parse_path, true}, {"-links", parse_links}, {"-lname", parse_lname, false}, {"-ls", parse_ls}, {"-maxdepth", parse_depth_limit, false}, {"-mindepth", parse_depth_limit, true}, {"-mmin", parse_time, BFS_STAT_MTIME, MINUTES}, {"-mnewer", parse_newer, BFS_STAT_MTIME}, {"-mount", parse_mount}, {"-mtime", parse_time, BFS_STAT_MTIME, DAYS}, {"-name", parse_name, false}, {"-newer", parse_newer, BFS_STAT_MTIME}, {"-newer", parse_newerxy, 0, 0, true}, {"-nocolor", parse_color, false}, {"-nogroup", parse_nogroup}, {"-nohidden", parse_nohidden}, {"-noignore_readdir_race", parse_ignore_races, false}, {"-noleaf", parse_noleaf}, {"-not"}, {"-nouser", parse_nouser}, {"-nowarn", parse_warn, false}, {"-o"}, {"-ok", parse_exec, BFS_EXEC_CONFIRM}, {"-okdir", parse_exec, BFS_EXEC_CONFIRM | BFS_EXEC_CHDIR}, {"-or"}, {"-path", parse_path, false}, {"-perm", parse_perm}, {"-print", parse_print}, {"-print0", parse_print0}, {"-printf", parse_printf}, {"-printx", parse_printx}, {"-prune", parse_prune}, {"-quit", parse_quit}, {"-readable", parse_access, R_OK}, {"-regex", parse_regex, 0}, {"-regextype", parse_regextype}, {"-rm", parse_delete}, {"-samefile", parse_samefile}, {"-size", parse_size}, {"-sparse", parse_sparse}, {"-true", parse_const, true}, {"-type", parse_type, false}, {"-uid", parse_user}, {"-unique", parse_unique}, {"-used", parse_used}, {"-user", parse_user}, {"-version", parse_version}, {"-warn", parse_warn, true}, {"-wholename", parse_path, false}, {"-writable", parse_access, W_OK}, {"-x", parse_xdev}, {"-xattr", parse_xattr}, {"-xdev", parse_xdev}, {"-xtype", parse_type, true}, {0}, }; /** Look up an argument in the parse table. */ static const struct table_entry *table_lookup(const char *arg) { for (const struct table_entry *entry = parse_table; entry->arg; ++entry) { bool match; if (entry->prefix) { match = strncmp(arg, entry->arg, strlen(entry->arg)) == 0; } else { match = strcmp(arg, entry->arg) == 0; } if (match) { return entry; } } return NULL; } /** Search for a fuzzy match in the parse table. */ static const struct table_entry *table_lookup_fuzzy(const char *arg) { const struct table_entry *best = NULL; int best_dist; for (const struct table_entry *entry = parse_table; entry->arg; ++entry) { int dist = typo_distance(arg, entry->arg); if (!best || dist < best_dist) { best = entry; best_dist = dist; } } return best; } /** * LITERAL : OPTION * | TEST * | ACTION */ static struct expr *parse_literal(struct parser_state *state) { // Paths are already skipped at this point const char *arg = state->argv[0]; if (arg[0] != '-') { goto unexpected; } const struct table_entry *match = table_lookup(arg); if (match) { if (match->parse) { goto matched; } else { goto unexpected; } } match = table_lookup_fuzzy(arg); parse_error(state, "Unknown argument '%s'; did you mean '%s'?", arg, match->arg); if (!state->interactive || !match->parse) { fprintf(stderr, "\n"); goto unmatched; } fprintf(stderr, " "); if (ynprompt() <= 0) { goto unmatched; } fprintf(stderr, "\n"); state->argv[0] = match->arg; matched: return match->parse(state, match->arg1, match->arg2); unmatched: return NULL; unexpected: parse_error(state, "Expected a predicate; found '%s'.\n", arg); return NULL; } /** * FACTOR : "(" EXPR ")" * | "!" FACTOR | "-not" FACTOR * | LITERAL */ static struct expr *parse_factor(struct parser_state *state) { if (skip_paths(state) != 0) { return NULL; } const char *arg = state->argv[0]; if (!arg) { parse_error(state, "Expression terminated prematurely after '%s'.\n", state->last_arg); return NULL; } if (strcmp(arg, "(") == 0) { parser_advance(state, T_OPERATOR, 1); struct expr *expr = parse_expr(state); if (!expr) { return NULL; } if (skip_paths(state) != 0) { free_expr(expr); return NULL; } arg = state->argv[0]; if (!arg || strcmp(arg, ")") != 0) { parse_error(state, "Expected a ')' after '%s'.\n", state->argv[-1]); free_expr(expr); return NULL; } parser_advance(state, T_OPERATOR, 1); return expr; } else if (strcmp(arg, "!") == 0 || strcmp(arg, "-not") == 0) { char **argv = parser_advance(state, T_OPERATOR, 1); struct expr *factor = parse_factor(state); if (!factor) { return NULL; } return new_unary_expr(eval_not, factor, argv); } else { return parse_literal(state); } } /** * TERM : FACTOR * | TERM FACTOR * | TERM "-a" FACTOR * | TERM "-and" FACTOR */ static struct expr *parse_term(struct parser_state *state) { struct expr *term = parse_factor(state); while (term) { if (skip_paths(state) != 0) { free_expr(term); return NULL; } const char *arg = state->argv[0]; if (!arg) { break; } if (strcmp(arg, "-o") == 0 || strcmp(arg, "-or") == 0 || strcmp(arg, ",") == 0 || strcmp(arg, ")") == 0) { break; } char **argv = &fake_and_arg; if (strcmp(arg, "-a") == 0 || strcmp(arg, "-and") == 0) { argv = parser_advance(state, T_OPERATOR, 1); } struct expr *lhs = term; struct expr *rhs = parse_factor(state); if (!rhs) { free_expr(lhs); return NULL; } term = new_binary_expr(eval_and, lhs, rhs, argv); } return term; } /** * CLAUSE : TERM * | CLAUSE "-o" TERM * | CLAUSE "-or" TERM */ static struct expr *parse_clause(struct parser_state *state) { struct expr *clause = parse_term(state); while (clause) { if (skip_paths(state) != 0) { free_expr(clause); return NULL; } const char *arg = state->argv[0]; if (!arg) { break; } if (strcmp(arg, "-o") != 0 && strcmp(arg, "-or") != 0) { break; } char **argv = parser_advance(state, T_OPERATOR, 1); struct expr *lhs = clause; struct expr *rhs = parse_term(state); if (!rhs) { free_expr(lhs); return NULL; } clause = new_binary_expr(eval_or, lhs, rhs, argv); } return clause; } /** * EXPR : CLAUSE * | EXPR "," CLAUSE */ static struct expr *parse_expr(struct parser_state *state) { struct expr *expr = parse_clause(state); while (expr) { if (skip_paths(state) != 0) { free_expr(expr); return NULL; } const char *arg = state->argv[0]; if (!arg) { break; } if (strcmp(arg, ",") != 0) { break; } char **argv = parser_advance(state, T_OPERATOR, 1); struct expr *lhs = expr; struct expr *rhs = parse_clause(state); if (!rhs) { free_expr(lhs); return NULL; } expr = new_binary_expr(eval_comma, lhs, rhs, argv); } return expr; } /** * Parse the top-level expression. */ static struct expr *parse_whole_expr(struct parser_state *state) { if (skip_paths(state) != 0) { return NULL; } struct expr *expr = &expr_true; if (state->argv[0]) { expr = parse_expr(state); if (!expr) { return NULL; } } if (state->argv[0]) { parse_error(state, "Unexpected argument '%s'.\n", state->argv[0]); goto fail; } if (state->implicit_print) { struct expr *print = new_expr(eval_fprint, 1, &fake_print_arg); if (!print) { goto fail; } init_print_expr(state, print); expr = new_binary_expr(eval_and, expr, print, &fake_and_arg); if (!expr) { goto fail; } } if (state->warn) { if (state->mount_arg && state->xdev_arg) { parse_warning(state, "%s is redundant in the presence of %s.\n\n", state->xdev_arg, state->mount_arg); } if (state->depth_arg && state->prune_arg) { parse_warning(state, "%s does not work in the presence of %s.\n", state->prune_arg, state->depth_arg); if (state->interactive) { fprintf(stderr, "Do you want to continue? "); if (ynprompt() == 0) { goto fail; } } fprintf(stderr, "\n"); } } return expr; fail: free_expr(expr); return NULL; } /** * Dump the parsed form of the command line, for debugging. */ void dump_cmdline(const struct cmdline *cmdline, bool verbose) { CFILE *cerr = cmdline->cerr; cfprintf(cerr, "${ex}%s${rs} ", cmdline->argv[0]); const char *strategy = NULL; switch (cmdline->strategy) { case BFTW_BFS: strategy = "bfs"; break; case BFTW_DFS: strategy = "dfs"; break; case BFTW_IDS: strategy = "ids"; break; } assert(strategy); cfprintf(cerr, "${cyn}-S${rs} ${bld}%s${rs} ", strategy); if (cmdline->flags & BFTW_LOGICAL) { cfprintf(cerr, "${cyn}-L${rs} "); } else if (cmdline->flags & BFTW_COMFOLLOW) { cfprintf(cerr, "${cyn}-H${rs} "); } else { cfprintf(cerr, "${cyn}-P${rs} "); } if (cmdline->optlevel != 3) { cfprintf(cerr, "${cyn}-O%d${rs} ", cmdline->optlevel); } enum debug_flags debug = cmdline->debug; if (debug) { cfprintf(cerr, "${cyn}-D${rs} "); for (int i = 0; debug; ++i) { enum debug_flags flag = debug_flags[i].flag; const char *name = debug_flags[i].name; if ((debug & flag) == flag) { cfprintf(cerr, "${bld}%s${rs}", name); debug ^= flag; if (debug) { cfprintf(cerr, ","); } } } cfprintf(cerr, " "); } for (size_t i = 0; i < darray_length(cmdline->paths); ++i) { const char *path = cmdline->paths[i]; char c = path[0]; if (c == '-' || c == '(' || c == ')' || c == '!' || c == ',') { cfprintf(cerr, "${cyn}-f${rs} "); } cfprintf(cerr, "${mag}%s${rs} ", path); } if (cmdline->cout->colors) { cfprintf(cerr, "${blu}-color${rs} "); } else { cfprintf(cerr, "${blu}-nocolor${rs} "); } if (cmdline->flags & BFTW_DEPTH) { cfprintf(cerr, "${blu}-depth${rs} "); } if (cmdline->ignore_races) { cfprintf(cerr, "${blu}-ignore_readdir_race${rs} "); } if (cmdline->flags & BFTW_MOUNT) { cfprintf(cerr, "${blu}-mount${rs} "); } else if (cmdline->flags & BFTW_XDEV) { cfprintf(cerr, "${blu}-xdev{rs} "); } if (cmdline->mindepth != 0) { cfprintf(cerr, "${blu}-mindepth${rs} ${bld}%d${rs} ", cmdline->mindepth); } if (cmdline->maxdepth != INT_MAX) { cfprintf(cerr, "${blu}-maxdepth${rs} ${bld}%d${rs} ", cmdline->maxdepth); } if (cmdline->unique) { cfprintf(cerr, "${blu}-unique${rs} "); } dump_expr(cerr, cmdline->expr, verbose); fputs("\n", stderr); } /** * Dump the estimated costs. */ static void dump_costs(const struct cmdline *cmdline) { CFILE *cerr = cmdline->cerr; const struct expr *expr = cmdline->expr; cfprintf(cerr, " Cost: ~${ylw}%g${rs}\n", expr->cost); cfprintf(cerr, "Probability: ~${ylw}%g%%${rs}\n", 100.0*expr->probability); } /** * Get the current time. */ static int parse_gettime(struct timespec *ts) { #if _POSIX_TIMERS > 0 int ret = clock_gettime(CLOCK_REALTIME, ts); if (ret != 0) { perror("clock_gettime()"); } return ret; #else struct timeval tv; int ret = gettimeofday(&tv, NULL); if (ret == 0) { ts->tv_sec = tv.tv_sec; ts->tv_nsec = tv.tv_usec * 1000L; } else { perror("gettimeofday()"); } return ret; #endif } /** * Parse the command line. */ struct cmdline *parse_cmdline(int argc, char *argv[]) { struct cmdline *cmdline = malloc(sizeof(struct cmdline)); if (!cmdline) { perror("malloc()"); goto fail; } cmdline->argv = NULL; cmdline->paths = NULL; cmdline->colors = NULL; cmdline->cout = NULL; cmdline->cerr = NULL; cmdline->mtab = NULL; cmdline->mtab_error = 0; cmdline->mindepth = 0; cmdline->maxdepth = INT_MAX; cmdline->flags = BFTW_RECOVER; cmdline->strategy = BFTW_BFS; cmdline->optlevel = 3; cmdline->debug = 0; cmdline->xargs_safe = false; cmdline->ignore_races = false; cmdline->unique = false; cmdline->expr = &expr_true; cmdline->nopen_files = 0; trie_init(&cmdline->open_files); static char* default_argv[] = {"bfs", NULL}; if (argc < 1) { argc = 1; argv = default_argv; } cmdline->argv = malloc((argc + 1)*sizeof(*cmdline->argv)); if (!cmdline->argv) { perror("malloc()"); goto fail; } for (int i = 0; i <= argc; ++i) { cmdline->argv[i] = argv[i]; } enum use_color use_color = COLOR_AUTO; if (getenv("NO_COLOR")) { // https://no-color.org/ use_color = COLOR_NEVER; } cmdline->colors = parse_colors(getenv("LS_COLORS")); cmdline->cout = cfdup(stdout, use_color ? cmdline->colors : NULL); cmdline->cerr = cfdup(stderr, use_color ? cmdline->colors : NULL); if (!cmdline->cout || !cmdline->cerr) { perror("cfdup()"); goto fail; } cmdline->mtab = parse_bfs_mtab(); if (!cmdline->mtab) { cmdline->mtab_error = errno; } bool stdin_tty = isatty(STDIN_FILENO); bool stdout_tty = isatty(STDOUT_FILENO); bool stderr_tty = isatty(STDERR_FILENO); struct parser_state state = { .cmdline = cmdline, .argv = cmdline->argv + 1, .command = cmdline->argv[0], .regex_flags = 0, .stdout_tty = stdout_tty, .interactive = stdin_tty && stderr_tty, .use_color = use_color, .implicit_print = true, .warn = stdin_tty, .non_option_seen = false, .just_info = false, .last_arg = NULL, .depth_arg = NULL, .prune_arg = NULL, .mount_arg = NULL, .xdev_arg = NULL, }; if (strcmp(xbasename(state.command), "find") == 0) { // Operate depth-first when invoked as "find" cmdline->strategy = BFTW_DFS; } if (parse_gettime(&state.now) != 0) { goto fail; } cmdline->expr = parse_whole_expr(&state); if (!cmdline->expr) { if (state.just_info) { goto done; } else { goto fail; } } if (optimize_cmdline(cmdline) != 0) { goto fail; } if (darray_length(cmdline->paths) == 0) { if (parse_root(&state, ".") != 0) { goto fail; } } if ((cmdline->flags & BFTW_LOGICAL) && !cmdline->unique) { // We need bftw() to detect cycles unless -unique does it for us cmdline->flags |= BFTW_DETECT_CYCLES; } if (cmdline->debug & DEBUG_TREE) { dump_cmdline(cmdline, false); } if (cmdline->debug & DEBUG_COST) { dump_costs(cmdline); } done: return cmdline; fail: free_cmdline(cmdline); return NULL; } bfs-1.5.2/printf.c000066400000000000000000000524351360564154000137540ustar00rootroot00000000000000/**************************************************************************** * bfs * * Copyright (C) 2017-2019 Tavian Barnes * * * * 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. * ****************************************************************************/ #include "printf.h" #include "cmdline.h" #include "color.h" #include "diag.h" #include "dstring.h" #include "expr.h" #include "mtab.h" #include "stat.h" #include "util.h" #include #include #include #include #include #include #include #include #include typedef int bfs_printf_fn(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf); struct bfs_printf { /** The printing function to invoke. */ bfs_printf_fn *fn; /** String data associated with this directive. */ char *str; /** The stat field to print. */ enum bfs_stat_field stat_field; /** Character data associated with this directive. */ char c; /** The current mount table. */ const struct bfs_mtab *mtab; /** The next printf directive in the chain. */ struct bfs_printf *next; }; /** Print some text as-is. */ static int bfs_printf_literal(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { size_t len = dstrlen(directive->str); if (fwrite(directive->str, 1, len, file) == len) { return 0; } else { return -1; } } /** \c: flush */ static int bfs_printf_flush(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { return fflush(file); } /** * Print a value to a temporary buffer before formatting it. */ #define BFS_PRINTF_BUF(buf, format, ...) \ char buf[256]; \ int ret = snprintf(buf, sizeof(buf), format, __VA_ARGS__); \ assert(ret >= 0 && ret < sizeof(buf)); \ (void)ret /** %a, %c, %t: ctime() */ static int bfs_printf_ctime(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { // Not using ctime() itself because GNU find adds nanoseconds static const char *days[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; static const char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); if (!statbuf) { return -1; } const struct timespec *ts = bfs_stat_time(statbuf, directive->stat_field); if (!ts) { return -1; } struct tm tm; if (xlocaltime(&ts->tv_sec, &tm) != 0) { return -1; } BFS_PRINTF_BUF(buf, "%s %s %2d %.2d:%.2d:%.2d.%09ld0 %4d", days[tm.tm_wday], months[tm.tm_mon], tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, (long)ts->tv_nsec, 1900 + tm.tm_year); return fprintf(file, directive->str, buf); } /** %A, %B/%W, %C, %T: strftime() */ static int bfs_printf_strftime(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); if (!statbuf) { return -1; } const struct timespec *ts = bfs_stat_time(statbuf, directive->stat_field); if (!ts) { return -1; } struct tm tm; if (xlocaltime(&ts->tv_sec, &tm) != 0) { return -1; } int ret; char buf[256]; char format[] = "% "; switch (directive->c) { // Non-POSIX strftime() features case '@': ret = snprintf(buf, sizeof(buf), "%lld.%09ld0", (long long)ts->tv_sec, (long)ts->tv_nsec); break; case '+': ret = snprintf(buf, sizeof(buf), "%4d-%.2d-%.2d+%.2d:%.2d:%.2d.%09ld0", 1900 + tm.tm_year, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, (long)ts->tv_nsec); break; case 'k': ret = snprintf(buf, sizeof(buf), "%2d", tm.tm_hour); break; case 'l': ret = snprintf(buf, sizeof(buf), "%2d", (tm.tm_hour + 11)%12 + 1); break; case 's': ret = snprintf(buf, sizeof(buf), "%lld", (long long)ts->tv_sec); break; case 'S': ret = snprintf(buf, sizeof(buf), "%.2d.%09ld0", tm.tm_sec, (long)ts->tv_nsec); break; case 'T': ret = snprintf(buf, sizeof(buf), "%.2d:%.2d:%.2d.%09ld0", tm.tm_hour, tm.tm_min, tm.tm_sec, (long)ts->tv_nsec); break; // POSIX strftime() features default: format[1] = directive->c; ret = strftime(buf, sizeof(buf), format, &tm); break; } assert(ret >= 0 && ret < sizeof(buf)); (void)ret; return fprintf(file, directive->str, buf); } /** %b: blocks */ static int bfs_printf_b(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); if (!statbuf) { return -1; } uintmax_t blocks = ((uintmax_t)statbuf->blocks*BFS_STAT_BLKSIZE + 511)/512; BFS_PRINTF_BUF(buf, "%ju", blocks); return fprintf(file, directive->str, buf); } /** %d: depth */ static int bfs_printf_d(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { return fprintf(file, directive->str, (intmax_t)ftwbuf->depth); } /** %D: device */ static int bfs_printf_D(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); if (!statbuf) { return -1; } BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)statbuf->dev); return fprintf(file, directive->str, buf); } /** %f: file name */ static int bfs_printf_f(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { return fprintf(file, directive->str, ftwbuf->path + ftwbuf->nameoff); } /** %F: file system type */ static int bfs_printf_F(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); if (!statbuf) { return -1; } const char *type = bfs_fstype(directive->mtab, statbuf); return fprintf(file, directive->str, type); } /** %G: gid */ static int bfs_printf_G(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); if (!statbuf) { return -1; } BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)statbuf->gid); return fprintf(file, directive->str, buf); } /** %g: group name */ static int bfs_printf_g(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); if (!statbuf) { return -1; } struct group *grp = getgrgid(statbuf->gid); if (!grp) { return bfs_printf_G(file, directive, ftwbuf); } return fprintf(file, directive->str, grp->gr_name); } /** %h: leading directories */ static int bfs_printf_h(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { char *copy = NULL; const char *buf; if (ftwbuf->nameoff > 0) { size_t len = ftwbuf->nameoff; if (len > 1) { --len; } buf = copy = strndup(ftwbuf->path, len); } else if (ftwbuf->path[0] == '/') { buf = "/"; } else { buf = "."; } if (!buf) { return -1; } int ret = fprintf(file, directive->str, buf); free(copy); return ret; } /** %H: current root */ static int bfs_printf_H(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { return fprintf(file, directive->str, ftwbuf->root); } /** %i: inode */ static int bfs_printf_i(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); if (!statbuf) { return -1; } BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)statbuf->ino); return fprintf(file, directive->str, buf); } /** %k: 1K blocks */ static int bfs_printf_k(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); if (!statbuf) { return -1; } uintmax_t blocks = ((uintmax_t)statbuf->blocks*BFS_STAT_BLKSIZE + 1023)/1024; BFS_PRINTF_BUF(buf, "%ju", blocks); return fprintf(file, directive->str, buf); } /** %l: link target */ static int bfs_printf_l(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { if (ftwbuf->typeflag != BFTW_LNK) { return 0; } char *target = xreadlinkat(ftwbuf->at_fd, ftwbuf->at_path, 0); if (!target) { return -1; } int ret = fprintf(file, directive->str, target); free(target); return ret; } /** %m: mode */ static int bfs_printf_m(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); if (!statbuf) { return -1; } return fprintf(file, directive->str, (unsigned int)(statbuf->mode & 07777)); } /** %M: symbolic mode */ static int bfs_printf_M(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); if (!statbuf) { return -1; } char buf[11]; format_mode(statbuf->mode, buf); return fprintf(file, directive->str, buf); } /** %n: link count */ static int bfs_printf_n(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); if (!statbuf) { return -1; } BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)statbuf->nlink); return fprintf(file, directive->str, buf); } /** %p: full path */ static int bfs_printf_p(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { return fprintf(file, directive->str, ftwbuf->path); } /** %P: path after root */ static int bfs_printf_P(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { const char *path = ftwbuf->path + strlen(ftwbuf->root); if (path[0] == '/') { ++path; } return fprintf(file, directive->str, path); } /** %s: size */ static int bfs_printf_s(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); if (!statbuf) { return -1; } BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)statbuf->size); return fprintf(file, directive->str, buf); } /** %S: sparseness */ static int bfs_printf_S(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); if (!statbuf) { return -1; } double sparsity; if (statbuf->size == 0 && statbuf->blocks == 0) { sparsity = 1.0; } else { sparsity = (double)BFS_STAT_BLKSIZE*statbuf->blocks/statbuf->size; } return fprintf(file, directive->str, sparsity); } /** %U: uid */ static int bfs_printf_U(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); if (!statbuf) { return -1; } BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)statbuf->uid); return fprintf(file, directive->str, buf); } /** %u: user name */ static int bfs_printf_u(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); if (!statbuf) { return -1; } struct passwd *pwd = getpwuid(statbuf->uid); if (!pwd) { return bfs_printf_U(file, directive, ftwbuf); } return fprintf(file, directive->str, pwd->pw_name); } static const char *bfs_printf_type(enum bftw_typeflag typeflag) { switch (typeflag) { case BFTW_BLK: return "b"; case BFTW_CHR: return "c"; case BFTW_DIR: return "d"; case BFTW_DOOR: return "D"; case BFTW_FIFO: return "p"; case BFTW_LNK: return "l"; case BFTW_REG: return "f"; case BFTW_SOCK: return "s"; default: return "U"; } } /** %y: type */ static int bfs_printf_y(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { const char *type = bfs_printf_type(ftwbuf->typeflag); return fprintf(file, directive->str, type); } /** %Y: target type */ static int bfs_printf_Y(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { int error = 0; if (ftwbuf->typeflag != BFTW_LNK) { return bfs_printf_y(file, directive, ftwbuf); } const char *type = "U"; const struct bfs_stat *statbuf = bftw_stat(ftwbuf, BFS_STAT_FOLLOW); if (statbuf) { type = bfs_printf_type(bftw_mode_typeflag(statbuf->mode)); } else { switch (errno) { case ELOOP: type = "L"; break; case ENOENT: case ENOTDIR: type = "N"; break; default: type = "?"; error = errno; break; } } int ret = fprintf(file, directive->str, type); if (error != 0) { ret = -1; errno = error; } return ret; } /** * Free a printf directive. */ static void free_directive(struct bfs_printf *directive) { if (directive) { dstrfree(directive->str); free(directive); } } /** * Create a new printf directive. */ static struct bfs_printf *new_directive(bfs_printf_fn *fn) { struct bfs_printf *directive = malloc(sizeof(*directive)); if (!directive) { perror("malloc()"); goto error; } directive->fn = fn; directive->str = dstralloc(2); if (!directive->str) { perror("dstralloc()"); goto error; } directive->stat_field = 0; directive->c = 0; directive->mtab = NULL; directive->next = NULL; return directive; error: free_directive(directive); return NULL; } /** * Append a printf directive to the chain. */ static struct bfs_printf **append_directive(struct bfs_printf **tail, struct bfs_printf *directive) { assert(directive); *tail = directive; return &directive->next; } /** * Append a literal string to the chain. */ static struct bfs_printf **append_literal(struct bfs_printf **tail, struct bfs_printf **literal) { struct bfs_printf *directive = *literal; if (directive && dstrlen(directive->str) > 0) { *literal = NULL; return append_directive(tail, directive); } else { return tail; } } struct bfs_printf *parse_bfs_printf(const char *format, struct cmdline *cmdline) { struct bfs_printf *head = NULL; struct bfs_printf **tail = &head; struct bfs_printf *literal = new_directive(bfs_printf_literal); if (!literal) { goto error; } for (const char *i = format; *i; ++i) { char c = *i; if (c == '\\') { c = *++i; if (c >= '0' && c < '8') { c = 0; for (int j = 0; j < 3 && *i >= '0' && *i < '8'; ++i, ++j) { c *= 8; c += *i - '0'; } --i; goto one_char; } switch (c) { case 'a': c = '\a'; break; case 'b': c = '\b'; break; case 'f': c = '\f'; break; case 'n': c = '\n'; break; case 'r': c = '\r'; break; case 't': c = '\t'; break; case 'v': c = '\v'; break; case '\\': c = '\\'; break; case 'c': tail = append_literal(tail, &literal); struct bfs_printf *directive = new_directive(bfs_printf_flush); if (!directive) { goto error; } tail = append_directive(tail, directive); goto done; case '\0': bfs_error(cmdline, "'%s': Incomplete escape sequence '\\'.\n", format); goto error; default: bfs_error(cmdline, "'%s': Unrecognized escape sequence '\\%c'.\n", format, c); goto error; } } else if (c == '%') { if (i[1] == '%') { c = *++i; goto one_char; } struct bfs_printf *directive = new_directive(NULL); if (!directive) { goto directive_error; } if (dstrapp(&directive->str, c) != 0) { perror("dstrapp()"); goto directive_error; } const char *specifier = "s"; // Parse any flags bool must_be_numeric = false; while (true) { c = *++i; switch (c) { case '#': case '0': case '+': must_be_numeric = true; // Fallthrough case ' ': case '-': if (strchr(directive->str, c)) { bfs_error(cmdline, "'%s': Duplicate flag '%c'.\n", format, c); goto directive_error; } if (dstrapp(&directive->str, c) != 0) { perror("dstrapp()"); goto directive_error; } continue; } break; } // Parse the field width while (c >= '0' && c <= '9') { if (dstrapp(&directive->str, c) != 0) { perror("dstrapp()"); goto directive_error; } c = *++i; } // Parse the precision if (c == '.') { do { if (dstrapp(&directive->str, c) != 0) { perror("dstrapp()"); goto directive_error; } c = *++i; } while (c >= '0' && c <= '9'); } switch (c) { case 'a': directive->fn = bfs_printf_ctime; directive->stat_field = BFS_STAT_ATIME; break; case 'b': directive->fn = bfs_printf_b; break; case 'c': directive->fn = bfs_printf_ctime; directive->stat_field = BFS_STAT_CTIME; break; case 'd': directive->fn = bfs_printf_d; specifier = "jd"; break; case 'D': directive->fn = bfs_printf_D; break; case 'f': directive->fn = bfs_printf_f; break; case 'F': if (!cmdline->mtab) { bfs_error(cmdline, "Couldn't parse the mount table: %s.\n", strerror(cmdline->mtab_error)); goto directive_error; } directive->fn = bfs_printf_F; directive->mtab = cmdline->mtab; break; case 'g': directive->fn = bfs_printf_g; break; case 'G': directive->fn = bfs_printf_G; break; case 'h': directive->fn = bfs_printf_h; break; case 'H': directive->fn = bfs_printf_H; break; case 'i': directive->fn = bfs_printf_i; break; case 'k': directive->fn = bfs_printf_k; break; case 'l': directive->fn = bfs_printf_l; break; case 'm': directive->fn = bfs_printf_m; specifier = "o"; break; case 'M': directive->fn = bfs_printf_M; break; case 'n': directive->fn = bfs_printf_n; break; case 'p': directive->fn = bfs_printf_p; break; case 'P': directive->fn = bfs_printf_P; break; case 's': directive->fn = bfs_printf_s; break; case 'S': directive->fn = bfs_printf_S; specifier = "g"; break; case 't': directive->fn = bfs_printf_ctime; directive->stat_field = BFS_STAT_MTIME; break; case 'u': directive->fn = bfs_printf_u; break; case 'U': directive->fn = bfs_printf_U; break; case 'w': directive->fn = bfs_printf_ctime; directive->stat_field = BFS_STAT_BTIME; break; case 'y': directive->fn = bfs_printf_y; break; case 'Y': directive->fn = bfs_printf_Y; break; case 'A': directive->stat_field = BFS_STAT_ATIME; goto directive_strftime; case 'B': case 'W': directive->stat_field = BFS_STAT_BTIME; goto directive_strftime; case 'C': directive->stat_field = BFS_STAT_CTIME; goto directive_strftime; case 'T': directive->stat_field = BFS_STAT_MTIME; goto directive_strftime; directive_strftime: directive->fn = bfs_printf_strftime; c = *++i; if (!c) { bfs_error(cmdline, "'%s': Incomplete time specifier '%s%c'.\n", format, directive->str, i[-1]); goto directive_error; } else if (strchr("%+@aAbBcCdDeFgGhHIjklmMnprRsStTuUVwWxXyYzZ", c)) { directive->c = c; } else { bfs_error(cmdline, "'%s': Unrecognized time specifier '%%%c%c'.\n", format, i[-1], c); goto directive_error; } break; case '\0': bfs_error(cmdline, "'%s': Incomplete format specifier '%s'.\n", format, directive->str); goto directive_error; default: bfs_error(cmdline, "'%s': Unrecognized format specifier '%%%c'.\n", format, c); goto directive_error; } if (must_be_numeric && strcmp(specifier, "s") == 0) { bfs_error(cmdline, "'%s': Invalid flags '%s' for string format '%%%c'.\n", format, directive->str + 1, c); goto directive_error; } if (dstrcat(&directive->str, specifier) != 0) { perror("dstrcat()"); goto directive_error; } tail = append_literal(tail, &literal); tail = append_directive(tail, directive); if (!literal) { literal = new_directive(bfs_printf_literal); if (!literal) { goto error; } } continue; directive_error: free_directive(directive); goto error; } one_char: if (dstrapp(&literal->str, c) != 0) { perror("dstrapp()"); goto error; } } done: tail = append_literal(tail, &literal); if (head) { free_directive(literal); return head; } else { return literal; } error: free_directive(literal); free_bfs_printf(head); return NULL; } int bfs_printf(FILE *file, const struct bfs_printf *command, const struct BFTW *ftwbuf) { int ret = 0, error = 0; for (const struct bfs_printf *directive = command; directive; directive = directive->next) { if (directive->fn(file, directive, ftwbuf) < 0) { ret = -1; error = errno; } } errno = error; return ret; } void free_bfs_printf(struct bfs_printf *command) { while (command) { struct bfs_printf *next = command->next; free_directive(command); command = next; } } bfs-1.5.2/printf.h000066400000000000000000000043101360564154000137460ustar00rootroot00000000000000/**************************************************************************** * bfs * * Copyright (C) 2017-2019 Tavian Barnes * * * * 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. * ****************************************************************************/ /** * Implementation of -printf/-fprintf. */ #ifndef BFS_PRINTF_H #define BFS_PRINTF_H #include "bftw.h" #include "cmdline.h" #include #include /** * A printf command, the result of parsing a single format string. */ struct bfs_printf; /** * Parse a -printf format string. * * @param format * The format string to parse. * @param cmdline * The command line. * @return The parsed printf command, or NULL on failure. */ struct bfs_printf *parse_bfs_printf(const char *format, struct cmdline *cmdline); /** * Evaluate a parsed format string. * * @param file * The FILE to print to. * @param command * The parsed printf format. * @param ftwbuf * The bftw() data for the current file. If needs_stat is true, statbuf * must be non-NULL. * @return 0 on success, -1 on failure. */ int bfs_printf(FILE *file, const struct bfs_printf *command, const struct BFTW *ftwbuf); /** * Free a parsed format string. */ void free_bfs_printf(struct bfs_printf *command); #endif // BFS_PRINTF_H bfs-1.5.2/spawn.c000066400000000000000000000125571360564154000136030ustar00rootroot00000000000000/**************************************************************************** * bfs * * Copyright (C) 2018-2019 Tavian Barnes * * * * 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. * ****************************************************************************/ #include "spawn.h" #include "util.h" #include #include #include #include #include #include #include /** * Types of spawn actions. */ enum bfs_spawn_op { BFS_SPAWN_CLOSE, BFS_SPAWN_DUP2, BFS_SPAWN_FCHDIR, }; /** * A spawn action. */ struct bfs_spawn_action { struct bfs_spawn_action *next; enum bfs_spawn_op op; int in_fd; int out_fd; }; int bfs_spawn_init(struct bfs_spawn *ctx) { ctx->flags = 0; ctx->actions = NULL; ctx->tail = &ctx->actions; return 0; } int bfs_spawn_destroy(struct bfs_spawn *ctx) { struct bfs_spawn_action *action = ctx->actions; while (action) { struct bfs_spawn_action *next = action->next; free(action); action = next; } return 0; } int bfs_spawn_setflags(struct bfs_spawn *ctx, enum bfs_spawn_flags flags) { ctx->flags = flags; return 0; } /** Add a spawn action to the chain. */ static struct bfs_spawn_action *bfs_spawn_add(struct bfs_spawn *ctx, enum bfs_spawn_op op) { struct bfs_spawn_action *action = malloc(sizeof(*action)); if (action) { action->next = NULL; action->op = op; action->in_fd = -1; action->out_fd = -1; *ctx->tail = action; ctx->tail = &action->next; } return action; } int bfs_spawn_addclose(struct bfs_spawn *ctx, int fd) { if (fd < 0) { errno = EBADF; return -1; } struct bfs_spawn_action *action = bfs_spawn_add(ctx, BFS_SPAWN_CLOSE); if (action) { action->out_fd = fd; return 0; } else { return -1; } } int bfs_spawn_adddup2(struct bfs_spawn *ctx, int oldfd, int newfd) { if (oldfd < 0 || newfd < 0) { errno = EBADF; return -1; } struct bfs_spawn_action *action = bfs_spawn_add(ctx, BFS_SPAWN_DUP2); if (action) { action->in_fd = oldfd; action->out_fd = newfd; return 0; } else { return -1; } } int bfs_spawn_addfchdir(struct bfs_spawn *ctx, int fd) { if (fd < 0) { errno = EBADF; return -1; } struct bfs_spawn_action *action = bfs_spawn_add(ctx, BFS_SPAWN_FCHDIR); if (action) { action->in_fd = fd; return 0; } else { return -1; } } /** Facade for execvpe() which is non-standard. */ static int bfs_execvpe(const char *exe, char **argv, char **envp) { #if __GLIBC__ || __linux__ || __NetBSD__ || __OpenBSD__ return execvpe(exe, argv, envp); #else extern char **environ; environ = envp; return execvp(exe, argv); #endif } /** Actually exec() the new process. */ static void bfs_spawn_exec(const char *exe, const struct bfs_spawn *ctx, char **argv, char **envp, int pipefd[2]) { int error; enum bfs_spawn_flags flags = ctx ? ctx->flags : 0; const struct bfs_spawn_action *actions = ctx ? ctx->actions : NULL; close(pipefd[0]); for (const struct bfs_spawn_action *action = actions; action; action = action->next) { // Move the error-reporting pipe out of the way if necessary... if (action->out_fd == pipefd[1]) { int fd = dup_cloexec(pipefd[1]); if (fd < 0) { goto fail; } close(pipefd[1]); pipefd[1] = fd; } // ... and pretend the pipe doesn't exist if (action->in_fd == pipefd[1]) { errno = EBADF; goto fail; } switch (action->op) { case BFS_SPAWN_CLOSE: if (close(action->out_fd) != 0) { goto fail; } break; case BFS_SPAWN_DUP2: if (dup2(action->in_fd, action->out_fd) < 0) { goto fail; } break; case BFS_SPAWN_FCHDIR: if (fchdir(action->in_fd) != 0) { goto fail; } break; } } if (flags & BFS_SPAWN_USEPATH) { bfs_execvpe(exe, argv, envp); } else { execve(exe, argv, envp); } fail: error = errno; while (write(pipefd[1], &error, sizeof(error)) < sizeof(error)); close(pipefd[1]); _Exit(127); } pid_t bfs_spawn(const char *exe, const struct bfs_spawn *ctx, char **argv, char **envp) { // Use a pipe to report errors from the child int pipefd[2]; if (pipe_cloexec(pipefd) != 0) { return -1; } int error; pid_t pid = fork(); if (pid < 0) { error = errno; close(pipefd[1]); close(pipefd[0]); errno = error; return -1; } else if (pid == 0) { // Child bfs_spawn_exec(exe, ctx, argv, envp, pipefd); } // Parent close(pipefd[1]); ssize_t nbytes = read(pipefd[0], &error, sizeof(error)); close(pipefd[0]); if (nbytes == sizeof(error)) { int wstatus; waitpid(pid, &wstatus, 0); errno = error; return -1; } return pid; } bfs-1.5.2/spawn.h000066400000000000000000000060041360564154000135760ustar00rootroot00000000000000/**************************************************************************** * bfs * * Copyright (C) 2018-2019 Tavian Barnes * * * * 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. * ****************************************************************************/ /** * A process-spawning library inspired by posix_spawn(). */ #ifndef BFS_SPAWN_H #define BFS_SPAWN_H #include #include #include /** * bfs_spawn() flags. */ enum bfs_spawn_flags { /** Use the PATH variable to resolve the executable (like execvp()). */ BFS_SPAWN_USEPATH = 1 << 0, }; /** * bfs_spawn() attributes, controlling the context of the new process. */ struct bfs_spawn { enum bfs_spawn_flags flags; struct bfs_spawn_action *actions; struct bfs_spawn_action **tail; }; /** * Create a new bfs_spawn() context. * * @return 0 on success, -1 on failure. */ int bfs_spawn_init(struct bfs_spawn *ctx); /** * Destroy a bfs_spawn() context. * * @return 0 on success, -1 on failure. */ int bfs_spawn_destroy(struct bfs_spawn *ctx); /** * Set the flags for a bfs_spawn() context. * * @return 0 on success, -1 on failure. */ int bfs_spawn_setflags(struct bfs_spawn *ctx, enum bfs_spawn_flags flags); /** * Add a close() action to a bfs_spawn() context. * * @return 0 on success, -1 on failure. */ int bfs_spawn_addclose(struct bfs_spawn *ctx, int fd); /** * Add a dup2() action to a bfs_spawn() context. * * @return 0 on success, -1 on failure. */ int bfs_spawn_adddup2(struct bfs_spawn *ctx, int oldfd, int newfd); /** * Add an fchdir() action to a bfs_spawn() context. * * @return 0 on success, -1 on failure. */ int bfs_spawn_addfchdir(struct bfs_spawn *ctx, int fd); /** * Spawn a new process. * * @param exe * The executable to run. * @param ctx * The context for the new process. * @param argv * The arguments for the new process. * @param envp * The environment variables for the new process. * @return * The PID of the new process, or -1 on error. */ pid_t bfs_spawn(const char *exe, const struct bfs_spawn *ctx, char **argv, char **envp); #endif // BFS_SPAWN_H bfs-1.5.2/stat.c000066400000000000000000000216051360564154000134200ustar00rootroot00000000000000/**************************************************************************** * bfs * * Copyright (C) 2018-2019 Tavian Barnes * * * * 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. * ****************************************************************************/ #include "stat.h" #include "util.h" #include #include #include #include #include #include #include #include #ifdef STATX_BASIC_STATS # define HAVE_STATX true #elif __linux__ # include # include #endif #if HAVE_STATX || defined(__NR_statx) # define HAVE_BFS_STATX true #endif #if __APPLE__ # define st_atim st_atimespec # define st_ctim st_ctimespec # define st_mtim st_mtimespec # define st_birthtim st_birthtimespec #endif const char *bfs_stat_field_name(enum bfs_stat_field field) { switch (field) { case BFS_STAT_DEV: return "device number"; case BFS_STAT_INO: return "inode nunmber"; case BFS_STAT_TYPE: return "type"; case BFS_STAT_MODE: return "mode"; case BFS_STAT_NLINK: return "link count"; case BFS_STAT_GID: return "group ID"; case BFS_STAT_UID: return "user ID"; case BFS_STAT_SIZE: return "size"; case BFS_STAT_BLOCKS: return "block count"; case BFS_STAT_RDEV: return "underlying device"; case BFS_STAT_ATIME: return "access time"; case BFS_STAT_BTIME: return "birth time"; case BFS_STAT_CTIME: return "change time"; case BFS_STAT_MTIME: return "modification time"; } assert(false); return "???"; } /** * Check if we should retry a failed stat() due to a potentially broken link. */ static bool bfs_stat_retry(int ret, enum bfs_stat_flag flags) { return ret != 0 && (flags & (BFS_STAT_NOFOLLOW | BFS_STAT_TRYFOLLOW)) == BFS_STAT_TRYFOLLOW && is_nonexistence_error(errno); } /** * Convert a struct stat to a struct bfs_stat. */ static void bfs_stat_convert(const struct stat *statbuf, struct bfs_stat *buf) { buf->mask = 0; buf->dev = statbuf->st_dev; buf->mask |= BFS_STAT_DEV; buf->ino = statbuf->st_ino; buf->mask |= BFS_STAT_INO; buf->mode = statbuf->st_mode; buf->mask |= BFS_STAT_TYPE | BFS_STAT_MODE; buf->nlink = statbuf->st_nlink; buf->mask |= BFS_STAT_NLINK; buf->gid = statbuf->st_gid; buf->mask |= BFS_STAT_GID; buf->uid = statbuf->st_uid; buf->mask |= BFS_STAT_UID; buf->size = statbuf->st_size; buf->mask |= BFS_STAT_SIZE; buf->blocks = statbuf->st_blocks; buf->mask |= BFS_STAT_BLOCKS; buf->rdev = statbuf->st_rdev; buf->mask |= BFS_STAT_RDEV; buf->atime = statbuf->st_atim; buf->mask |= BFS_STAT_ATIME; buf->ctime = statbuf->st_ctim; buf->mask |= BFS_STAT_CTIME; buf->mtime = statbuf->st_mtim; buf->mask |= BFS_STAT_MTIME; #if __APPLE__ || __FreeBSD__ || __NetBSD__ buf->btime = statbuf->st_birthtim; buf->mask |= BFS_STAT_BTIME; #endif } /** * bfs_stat() implementation backed by stat(). */ static int bfs_stat_impl(int at_fd, const char *at_path, int at_flags, enum bfs_stat_flag flags, struct bfs_stat *buf) { struct stat statbuf; int ret = fstatat(at_fd, at_path, &statbuf, at_flags); if (bfs_stat_retry(ret, flags)) { at_flags |= AT_SYMLINK_NOFOLLOW; ret = fstatat(at_fd, at_path, &statbuf, at_flags); } if (ret == 0) { bfs_stat_convert(&statbuf, buf); } return ret; } #if HAVE_BFS_STATX /** * Wrapper for the statx() system call, which had no glibc wrapper prior to 2.28. */ static int bfs_statx(int at_fd, const char *at_path, int at_flags, unsigned int mask, struct statx *buf) { // -fsanitize=memory doesn't know about statx(), so tell it the memory // got initialized #if BFS_HAS_FEATURE(memory_sanitizer, false) memset(buf, 0, sizeof(*buf)); #endif #if HAVE_STATX return statx(at_fd, at_path, at_flags, mask, buf); #else return syscall(__NR_statx, at_fd, at_path, at_flags, mask, buf); #endif } /** * bfs_stat() implementation backed by statx(). */ static int bfs_statx_impl(int at_fd, const char *at_path, int at_flags, enum bfs_stat_flag flags, struct bfs_stat *buf) { unsigned int mask = STATX_BASIC_STATS | STATX_BTIME; struct statx xbuf; int ret = bfs_statx(at_fd, at_path, at_flags, mask, &xbuf); if (bfs_stat_retry(ret, flags)) { at_flags |= AT_SYMLINK_NOFOLLOW; ret = bfs_statx(at_fd, at_path, at_flags, mask, &xbuf); } if (ret != 0) { return ret; } // Callers shouldn't have to check anything except the times const unsigned int guaranteed = STATX_BASIC_STATS ^ (STATX_ATIME | STATX_CTIME | STATX_MTIME); if ((xbuf.stx_mask & guaranteed) != guaranteed) { errno = ENOTSUP; return -1; } buf->mask = 0; buf->dev = bfs_makedev(xbuf.stx_dev_major, xbuf.stx_dev_minor); buf->mask |= BFS_STAT_DEV; if (xbuf.stx_mask & STATX_INO) { buf->ino = xbuf.stx_ino; buf->mask |= BFS_STAT_INO; } buf->mode = xbuf.stx_mode; if (xbuf.stx_mask & STATX_TYPE) { buf->mask |= BFS_STAT_TYPE; } if (xbuf.stx_mask & STATX_MODE) { buf->mask |= BFS_STAT_MODE; } if (xbuf.stx_mask & STATX_NLINK) { buf->nlink = xbuf.stx_nlink; buf->mask |= BFS_STAT_NLINK; } if (xbuf.stx_mask & STATX_GID) { buf->gid = xbuf.stx_gid; buf->mask |= BFS_STAT_GID; } if (xbuf.stx_mask & STATX_UID) { buf->uid = xbuf.stx_uid; buf->mask |= BFS_STAT_UID; } if (xbuf.stx_mask & STATX_SIZE) { buf->size = xbuf.stx_size; buf->mask |= BFS_STAT_SIZE; } if (xbuf.stx_mask & STATX_BLOCKS) { buf->blocks = xbuf.stx_blocks; buf->mask |= BFS_STAT_BLOCKS; } buf->rdev = bfs_makedev(xbuf.stx_rdev_major, xbuf.stx_rdev_minor); buf->mask |= BFS_STAT_RDEV; if (xbuf.stx_mask & STATX_ATIME) { buf->atime.tv_sec = xbuf.stx_atime.tv_sec; buf->atime.tv_nsec = xbuf.stx_atime.tv_nsec; buf->mask |= BFS_STAT_ATIME; } if (xbuf.stx_mask & STATX_BTIME) { buf->btime.tv_sec = xbuf.stx_btime.tv_sec; buf->btime.tv_nsec = xbuf.stx_btime.tv_nsec; buf->mask |= BFS_STAT_BTIME; } if (xbuf.stx_mask & STATX_CTIME) { buf->ctime.tv_sec = xbuf.stx_ctime.tv_sec; buf->ctime.tv_nsec = xbuf.stx_ctime.tv_nsec; buf->mask |= BFS_STAT_CTIME; } if (xbuf.stx_mask & STATX_MTIME) { buf->mtime.tv_sec = xbuf.stx_mtime.tv_sec; buf->mtime.tv_nsec = xbuf.stx_mtime.tv_nsec; buf->mask |= BFS_STAT_MTIME; } return ret; } #endif // HAVE_BFS_STATX /** * Allows calling stat with custom at_flags. */ static int bfs_stat_explicit(int at_fd, const char *at_path, int at_flags, enum bfs_stat_flag flags, struct bfs_stat *buf) { #if HAVE_BFS_STATX static bool has_statx = true; if (has_statx) { int ret = bfs_statx_impl(at_fd, at_path, at_flags, flags, buf); // EPERM is commonly returned in a seccomp() sandbox that does // not allow statx() if (ret != 0 && (errno == ENOSYS || errno == EPERM)) { has_statx = false; } else { return ret; } } #endif return bfs_stat_impl(at_fd, at_path, at_flags, flags, buf); } int bfs_stat(int at_fd, const char *at_path, enum bfs_stat_flag flags, struct bfs_stat *buf) { int at_flags = 0; if (flags & BFS_STAT_NOFOLLOW) { at_flags |= AT_SYMLINK_NOFOLLOW; } #ifdef AT_STATX_DONT_SYNC if (flags & BFS_STAT_NOSYNC) { at_flags |= AT_STATX_DONT_SYNC; } #endif if (at_path) { return bfs_stat_explicit(at_fd, at_path, at_flags, flags, buf); } #ifdef AT_EMPTY_PATH static bool has_at_ep = true; if (has_at_ep) { at_flags |= AT_EMPTY_PATH; int ret = bfs_stat_explicit(at_fd, "", at_flags, flags, buf); if (ret != 0 && errno == EINVAL) { has_at_ep = false; } else { return ret; } } #endif struct stat statbuf; if (fstat(at_fd, &statbuf) == 0) { bfs_stat_convert(&statbuf, buf); return 0; } else { return -1; } } const struct timespec *bfs_stat_time(const struct bfs_stat *buf, enum bfs_stat_field field) { if (!(buf->mask & field)) { errno = ENOTSUP; return NULL; } switch (field) { case BFS_STAT_ATIME: return &buf->atime; case BFS_STAT_BTIME: return &buf->btime; case BFS_STAT_CTIME: return &buf->ctime; case BFS_STAT_MTIME: return &buf->mtime; default: assert(false); errno = EINVAL; return NULL; } } void bfs_stat_id(const struct bfs_stat *buf, bfs_file_id *id) { memcpy(*id, &buf->dev, sizeof(buf->dev)); memcpy(*id + sizeof(buf->dev), &buf->ino, sizeof(buf->ino)); } bfs-1.5.2/stat.h000066400000000000000000000105651360564154000134300ustar00rootroot00000000000000/**************************************************************************** * bfs * * Copyright (C) 2018-2019 Tavian Barnes * * * * 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. * ****************************************************************************/ /** * A facade over the stat() API that unifies some details that diverge between * implementations, like the names of the timespec fields and the presence of * file "birth" times. On new enough Linux kernels, the facade is backed by * statx() instead, and so it exposes a similar interface with a mask for which * fields were successfully returned. */ #ifndef BFS_STAT_H #define BFS_STAT_H #include "util.h" #include #include #include #if BFS_HAS_SYS_PARAM # include #endif /** * bfs_stat field bitmask. */ enum bfs_stat_field { BFS_STAT_DEV = 1 << 0, BFS_STAT_INO = 1 << 1, BFS_STAT_TYPE = 1 << 2, BFS_STAT_MODE = 1 << 3, BFS_STAT_NLINK = 1 << 4, BFS_STAT_GID = 1 << 5, BFS_STAT_UID = 1 << 6, BFS_STAT_SIZE = 1 << 7, BFS_STAT_BLOCKS = 1 << 8, BFS_STAT_RDEV = 1 << 9, BFS_STAT_ATIME = 1 << 10, BFS_STAT_BTIME = 1 << 11, BFS_STAT_CTIME = 1 << 12, BFS_STAT_MTIME = 1 << 13, }; /** * Get the human-readable name of a bfs_stat field. */ const char *bfs_stat_field_name(enum bfs_stat_field field); /** * bfs_stat() flags. */ enum bfs_stat_flag { /** Follow symlinks (the default). */ BFS_STAT_FOLLOW = 0, /** Never follow symlinks. */ BFS_STAT_NOFOLLOW = 1 << 0, /** Try to follow symlinks, but fall back to the link itself if broken. */ BFS_STAT_TRYFOLLOW = 1 << 1, /** Try to use cached values without synchronizing remote filesystems. */ BFS_STAT_NOSYNC = 1 << 2, }; #ifdef DEV_BSIZE # define BFS_STAT_BLKSIZE DEV_BSIZE #elif defined(S_BLKSIZE) # define BFS_STAT_BLKSIZE S_BLKSIZE #else # define BFS_STAT_BLKSIZE 512 #endif /** * Facade over struct stat. */ struct bfs_stat { /** Bitmask indicating filled fields. */ enum bfs_stat_field mask; /** Device ID containing the file. */ dev_t dev; /** Inode number. */ ino_t ino; /** File type and access mode. */ mode_t mode; /** Number of hard links. */ nlink_t nlink; /** Owner group ID. */ gid_t gid; /** Owner user ID. */ uid_t uid; /** File size in bytes. */ off_t size; /** Number of disk blocks allocated (of size BFS_STAT_BLKSIZE). */ blkcnt_t blocks; /** The device ID represented by this file. */ dev_t rdev; /** Access time. */ struct timespec atime; /** Birth/creation time. */ struct timespec btime; /** Status change time. */ struct timespec ctime; /** Modification time. */ struct timespec mtime; }; /** * Facade over fstatat(). * * @param at_fd * The base file descriptor for the lookup. * @param at_path * The path to stat, relative to at_fd. Pass NULL to fstat() at_fd * itself. * @param flags * Flags that affect the lookup. * @param[out] buf * A place to store the stat buffer, if successful. * @return * 0 on success, -1 on error. */ int bfs_stat(int at_fd, const char *at_path, enum bfs_stat_flag flags, struct bfs_stat *buf); /** * Get a particular time field from a bfs_stat() buffer. */ const struct timespec *bfs_stat_time(const struct bfs_stat *buf, enum bfs_stat_field field); /** * A unique ID for a file. */ typedef unsigned char bfs_file_id[sizeof(dev_t) + sizeof(ino_t)]; /** * Compute a unique ID for a file. */ void bfs_stat_id(const struct bfs_stat *buf, bfs_file_id *id); #endif // BFS_STAT_H bfs-1.5.2/tests.sh000077500000000000000000001473461360564154000140150ustar00rootroot00000000000000#!/bin/bash ############################################################################ # bfs # # Copyright (C) 2015-2020 Tavian Barnes # # # # 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. # ############################################################################ set -e set -o physical umask 022 export LC_ALL=C export TZ=UTC if [ -t 1 ]; then BLD="$(printf '\033[01m')" RED="$(printf '\033[01;31m')" GRN="$(printf '\033[01;32m')" YLW="$(printf '\033[01;33m')" BLU="$(printf '\033[01;34m')" MAG="$(printf '\033[01;35m')" CYN="$(printf '\033[01;36m')" RST="$(printf '\033[0m')" fi if [ "$EUID" -eq 0 ]; then cat >&2 <&2 usage >&2 exit 1 ;; esac done posix_tests=( # General parsing test_basic test_parens test_bang test_implicit_and test_a test_o test_weird_names # Flags test_H test_H_slash test_H_broken test_H_notdir test_H_loops test_L test_L_broken test_L_notdir test_L_loops test_flag_weird_names test_flag_comma # Primaries test_depth test_depth_slash test_depth_error test_L_depth test_exec test_exec_plus test_exec_plus_status test_exec_plus_semicolon test_group_name test_group_id test_links test_links_plus test_links_minus test_name test_name_root test_name_root_depth test_name_trailing_slash test_newer test_newer_link test_ok_stdin test_ok_plus_semicolon test_path test_perm_000 test_perm_000_minus test_perm_222 test_perm_222_minus test_perm_644 test_perm_644_minus test_perm_symbolic test_perm_symbolic_minus test_perm_leading_plus_symbolic_minus test_permcopy test_prune test_prune_or_print test_not_prune test_size test_size_plus test_size_bytes test_type_d test_type_f test_user_name test_user_id # Closed file descriptors test_closed_stdin test_closed_stdout test_closed_stderr # PATH_MAX handling test_deep # Optimizer tests test_or_purity test_double_negation test_de_morgan_not test_de_morgan_and test_de_morgan_or test_data_flow_type test_data_flow_and_swap test_data_flow_or_swap ) bsd_tests=( # Flags test_E test_P test_P_slash test_X test_d_path test_f test_double_dash test_flag_double_dash # Primaries test_acl test_L_acl test_delete test_depth_maxdepth_1 test_depth_maxdepth_2 test_depth_mindepth_1 test_depth_mindepth_2 test_depth_n test_depth_n_plus test_depth_n_minus test_depth_depth_n test_depth_depth_n_plus test_depth_depth_n_minus test_depth_overflow test_data_flow_depth test_exec_substring test_execdir_pwd test_execdir_slash test_execdir_slash_pwd test_execdir_slashes test_execdir_ulimit test_exit test_follow test_gid_name test_ilname test_L_ilname test_iname test_inum test_ipath test_iregex test_lname test_L_lname test_maxdepth test_mindepth test_mnewer test_H_mnewer test_name_slash test_name_slashes test_H_newer test_newerma test_nogroup test_nogroup_ulimit test_nouser test_nouser_ulimit test_ok_stdin test_ok_closed_stdin test_okdir_stdin test_okdir_closed_stdin test_perm_000_plus test_perm_222_plus test_perm_644_plus test_printx test_quit test_quit_child test_quit_depth test_quit_depth_child test_quit_after_print test_quit_before_print test_quit_implicit_print test_rm test_regex test_regex_parens test_samefile test_samefile_symlink test_H_samefile_symlink test_L_samefile_symlink test_samefile_broken test_H_samefile_broken test_L_samefile_broken test_samefile_notdir test_H_samefile_notdir test_L_samefile_notdir test_size_T test_size_big test_uid_name ) gnu_tests=( # General parsing test_not test_and test_or test_comma test_precedence test_follow_comma # Flags test_P test_P_slash test_L_loops_continue test_double_dash test_flag_double_dash # Primaries test_anewer test_path_d test_daystart test_daystart_twice test_delete test_L_delete test_depth_mindepth_1 test_depth_mindepth_2 test_depth_maxdepth_1 test_depth_maxdepth_2 test_empty test_exec_nothing test_exec_substring test_execdir test_execdir_substring test_execdir_plus_semicolon test_execdir_pwd test_execdir_slash test_execdir_slash_pwd test_execdir_slashes test_execdir_ulimit test_executable test_false test_follow test_fprint test_fprint_duplicate test_fprint_error test_fstype test_gid test_gid_plus test_gid_plus_plus test_gid_minus test_gid_minus_plus test_ignore_readdir_race test_ignore_readdir_race_root test_ignore_readdir_race_notdir test_ilname test_L_ilname test_iname test_inum test_ipath test_iregex test_lname test_L_lname test_maxdepth test_mindepth test_name_slash test_name_slashes test_H_newer test_newerma test_nogroup test_nogroup_ulimit test_nouser test_nouser_ulimit test_ok_closed_stdin test_ok_nothing test_okdir_closed_stdin test_okdir_plus_semicolon test_perm_000_slash test_perm_222_slash test_perm_644_slash test_perm_symbolic_slash test_perm_leading_plus_symbolic_slash test_print_error test_printf test_printf_empty test_printf_slash test_printf_slashes test_printf_trailing_slash test_printf_trailing_slashes test_printf_flags test_printf_types test_printf_escapes test_printf_times test_printf_leak test_printf_nul test_printf_Y_error test_printf_H test_quit test_quit_child test_quit_depth test_quit_depth_child test_quit_after_print test_quit_before_print test_readable test_regex test_regex_parens test_regextype_posix_basic test_regextype_posix_extended test_samefile test_samefile_symlink test_H_samefile_symlink test_L_samefile_symlink test_samefile_broken test_H_samefile_broken test_L_samefile_broken test_samefile_notdir test_H_samefile_notdir test_L_samefile_notdir test_size_big test_true test_uid test_uid_plus test_uid_plus_plus test_uid_minus test_uid_minus_plus test_writable test_xtype_l test_xtype_f test_L_xtype_l test_L_xtype_f # Optimizer tests test_and_purity test_not_reachability test_comma_reachability test_and_false_or_true test_comma_redundant_true test_comma_redundant_false ) bfs_tests=( # General parsing test_path_flag_expr test_path_expr_flag test_flag_expr_path test_expr_flag_path test_expr_path_flag # Primaries test_color test_color_L test_color_rs_lc_rc_ec test_color_escapes test_color_nul test_color_ln_target test_color_L_ln_target test_color_mh test_color_mh0 test_color_or test_color_mi test_color_or_mi test_color_or_mi0 test_color_or0_mi test_color_or0_mi0 test_color_su_sg0 test_color_su0_sg test_color_su0_sg0 test_color_st_tw_ow0 test_color_st_tw0_ow test_color_st_tw0_ow0 test_color_st0_tw_ow test_color_st0_tw_ow0 test_color_st0_tw0_ow test_color_st0_tw0_ow0 test_color_ext test_color_ext0 test_color_ext_override test_color_ext_underride test_color_missing_colon test_color_no_stat test_color_L_no_stat test_color_star test_color_ls test_execdir_plus test_help test_hidden test_nohidden test_perm_symbolic_trailing_comma test_perm_symbolic_double_comma test_perm_symbolic_missing_action test_perm_leading_plus_symbolic test_printf_w test_type_multi test_unique test_unique_depth test_L_unique test_L_unique_loops test_L_unique_depth test_xtype_multi test_xtype_reorder # PATH_MAX handling test_deep_strict ) sudo_tests=( test_capable test_L_capable test_mount test_L_mount test_xdev test_L_xdev test_inum_mount test_inum_bind_mount test_type_bind_mount test_xtype_bind_mount ) if [ "$UNAME" = "Linux" ]; then sudo_tests+=( test_xattr test_L_xattr ) else bsd_tests+=( test_xattr test_L_xattr ) fi if [ "$DEFAULT" ]; then POSIX=yes BSD=yes GNU=yes ALL=yes fi if [ ! "$EXPLICIT" ]; then [ "$POSIX" ] && enabled_tests+=("${posix_tests[@]}") [ "$BSD" ] && enabled_tests+=("${bsd_tests[@]}") [ "$GNU" ] && enabled_tests+=("${gnu_tests[@]}") [ "$ALL" ] && enabled_tests+=("${bfs_tests[@]}") [ "$SUDO" ] && enabled_tests+=("${sudo_tests[@]}") fi eval enabled_tests=($(printf '%q\n' "${enabled_tests[@]}" | sort -u)) # The temporary directory that will hold our test data TMP="$(mktemp -d "${TMPDIR:-/tmp}"/bfs.XXXXXXXXXX)" chown "$(id -u):$(id -g)" "$TMP" # Clean up temporary directories on exit function cleanup() { # Don't force rm to deal with long paths for dir in "$TMP"/deep/*/*; do if [ -d "$dir" ]; then (cd "$dir" && rm -rf *) fi done # In case a test left anything weird in scratch/ if [ -e "$TMP"/scratch ]; then chmod -R +rX "$TMP"/scratch fi rm -rf "$TMP" } if [ "$CLEAN" ]; then trap cleanup EXIT else echo "Test files saved to $TMP" fi # Install a file, creating any parent directories function installp() { local target="${@: -1}" mkdir -p "${target%/*}" install "$@" } # Like a mythical touch -p function touchp() { for arg; do installp -m644 /dev/null "$arg" done } # Creates a simple file+directory structure for tests function make_basic() { touchp "$1/a" touchp "$1/b" touchp "$1/c/d" touchp "$1/e/f" mkdir -p "$1/g/h" mkdir -p "$1/i" touchp "$1/j/foo" touchp "$1/k/foo/bar" touchp "$1/l/foo/bar/baz" echo baz >"$1/l/foo/bar/baz" } make_basic "$TMP/basic" # Creates a file+directory structure with various permissions for tests function make_perms() { installp -m000 /dev/null "$1/0" installp -m444 /dev/null "$1/r" installp -m222 /dev/null "$1/w" installp -m644 /dev/null "$1/rw" installp -m555 /dev/null "$1/rx" installp -m311 /dev/null "$1/wx" installp -m755 /dev/null "$1/rwx" } make_perms "$TMP/perms" # Creates a file+directory structure with various symbolic and hard links function make_links() { touchp "$1/file" ln -s file "$1/symlink" ln "$1/file" "$1/hardlink" ln -s nowhere "$1/broken" ln -s symlink/file "$1/notdir" mkdir -p "$1/deeply/nested/dir" touchp "$1/deeply/nested/file" ln -s file "$1/deeply/nested/link" ln -s deeply/nested "$1/skip" } make_links "$TMP/links" # Creates a file+directory structure with symbolic link loops function make_loops() { touchp "$1/file" ln -s file "$1/symlink" ln -s nowhere "$1/broken" ln -s symlink/file "$1/notdir" ln -s loop "$1/loop" mkdir -p "$1/deeply/nested/dir" ln -s ../../deeply "$1/deeply/nested/loop" ln -s deeply/nested/loop/nested "$1/skip" } make_loops "$TMP/loops" # Creates a file+directory structure with varying timestamps function make_times() { mkdir -p "$1" touch -t 199112140000 "$1/a" touch -t 199112140001 "$1/b" touch -t 199112140002 "$1/c" ln -s a "$1/l" touch -h -t 199112140003 "$1/l" touch -t 199112140004 "$1" } make_times "$TMP/times" # Creates a file+directory structure with various weird file/directory names function make_weirdnames() { touchp "$1/-/a" touchp "$1/(/b" touchp "$1/(-/c" touchp "$1/!/d" touchp "$1/!-/e" touchp "$1/,/f" touchp "$1/)/g" touchp "$1/.../h" touchp "$1/\\/i" touchp "$1/ /j" } make_weirdnames "$TMP/weirdnames" # Creates a very deep directory structure for testing PATH_MAX handling function make_deep() { mkdir -p "$1" # $name will be 255 characters, aka _XOPEN_NAME_MAX local name="0123456789ABCDEF" name="${name}${name}${name}${name}" name="${name}${name}${name}${name}" name="${name:0:255}" for i in {0..9} A B C D E F; do ( mkdir "$1/$i" cd "$1/$i" # 16 * 256 == 4096 == PATH_MAX for j in {1..16}; do mkdir "$name" cd "$name" 2>/dev/null done touch "$name" ) done } make_deep "$TMP/deep" # Creates a directory structure with many different types, and therefore colors function make_rainbow() { touchp "$1/file.txt" touchp "$1/file.dat" touchp "$1/star".{gz,tar,tar.gz} ln -s file.txt "$1/link.txt" touchp "$1/mh1" ln "$1/mh1" "$1/mh2" mkfifo "$1/pipe" # TODO: block ln -s /dev/null "$1/chardev_link" ln -s nowhere "$1/broken" "$TESTS/mksock" "$1/socket" touchp "$1"/s{u,g,ug}id chmod u+s "$1"/su{,g}id chmod g+s "$1"/s{u,}gid mkdir "$1/ow" "$1"/sticky{,_ow} chmod o+w "$1"/*ow chmod +t "$1"/sticky* touchp "$1"/exec.sh chmod +x "$1"/exec.sh } make_rainbow "$TMP/rainbow" # Creates a scratch directory that tests can modify function make_scratch() { mkdir -p "$1" } make_scratch "$TMP/scratch" function bfs_sort() { awk -F/ '{ print NF - 1 " " $0 }' | sort -n | cut -d' ' -f2- } # Close stdin so bfs doesn't think we're interactive exec &1 fi function bfs_verbose() { if [ "$VERBOSE" ]; then if [ -t 3 ]; then printf "${GRN}%q${RST} " $BFS >&3 local expr_started= for arg; do if [[ $arg == -[A-Z]* ]]; then printf "${CYN}%q${RST} " "$arg" >&3 elif [[ $arg == [\(!] || $arg == -[ao] || $arg == -and || $arg == -or || $arg == -not ]]; then expr_started=yes printf "${RED}%q${RST} " "$arg" >&3 elif [[ $expr_started && $arg == [\),] ]]; then printf "${RED}%q${RST} " "$arg" >&3 elif [[ $arg == -?* ]]; then expr_started=yes printf "${BLU}%q${RST} " "$arg" >&3 elif [ "$expr_started" ]; then printf "${BLD}%q${RST} " "$arg" >&3 else printf "${MAG}%q${RST} " "$arg" >&3 fi done else printf '%q ' $BFS "$@" >&3 fi printf '\n' >&3 fi } function invoke_bfs() { bfs_verbose "$@" $BFS "$@" } # Return value when bfs fails EX_BFS=10 # Return value when a difference is detected EX_DIFF=20 function bfs_diff() ( bfs_verbose "$@" # Close the dup()'d stdout to make sure we have enough fd's for the process # substitution, even with low ulimit -n exec 3>&- local EXPECTED="$TESTS/${FUNCNAME[1]}.out" if [ "$UPDATE" ]; then local ACTUAL="$EXPECTED" else local ACTUAL="$TMP/${FUNCNAME[1]}.out" fi $BFS "$@" | bfs_sort >"$ACTUAL" local STATUS="${PIPESTATUS[0]}" if [ ! "$UPDATE" ]; then diff -u "$EXPECTED" "$ACTUAL" || return $EX_DIFF fi if [ "$STATUS" -eq 0 ]; then return 0 else return $EX_BFS fi ) function closefrom() { if [ -d /proc/self/fd ]; then local fds=/proc/self/fd else local fds=/dev/fd fi for fd in "$fds"/*; do if [ ! -e "$fd" ]; then continue fi local fd="${fd##*/}" if [ "$fd" -ge "$1" ]; then eval "exec ${fd}<&-" fi done } function inum() { ls -id "$@" | awk '{ print $1 }' } cd "$TMP" set +e # Test cases function test_basic() { bfs_diff basic } function test_type_d() { bfs_diff basic -type d } function test_type_f() { bfs_diff basic -type f } function test_type_multi() { bfs_diff links -type f,d,c } function test_mindepth() { bfs_diff basic -mindepth 1 } function test_maxdepth() { bfs_diff basic -maxdepth 1 } function test_depth() { bfs_diff basic -depth } function test_depth_slash() { bfs_diff basic/ -depth } function test_depth_mindepth_1() { bfs_diff basic -mindepth 1 -depth } function test_depth_mindepth_2() { bfs_diff basic -mindepth 2 -depth } function test_depth_maxdepth_1() { bfs_diff basic -maxdepth 1 -depth } function test_depth_maxdepth_2() { bfs_diff basic -maxdepth 2 -depth } function test_depth_error() { rm -rf scratch/* touchp scratch/foo/bar chmod -r scratch/foo bfs_diff scratch -depth 2>/dev/null local ret=$? chmod +r scratch/foo rm -rf scratch/* [ $ret -eq $EX_BFS ] } function test_name() { bfs_diff basic -name '*f*' } function test_name_root() { bfs_diff basic/a -name a } function test_name_root_depth() { bfs_diff basic/g -depth -name g } function test_name_trailing_slash() { bfs_diff basic/g/ -name g } function test_name_slash() { bfs_diff / -maxdepth 0 -name / } function test_name_slashes() { bfs_diff /// -maxdepth 0 -name / } function test_path() { bfs_diff basic -path 'basic/*f*' } function test_true() { bfs_diff basic -true } function test_false() { bfs_diff basic -false } function test_executable() { bfs_diff perms -executable } function test_readable() { bfs_diff perms -readable } function test_writable() { bfs_diff perms -writable } function test_empty() { bfs_diff basic -empty } function test_gid() { bfs_diff basic -gid "$(id -g)" } function test_gid_plus() { bfs_diff basic -gid +0 } function test_gid_plus_plus() { bfs_diff basic -gid +0 } function test_gid_minus() { bfs_diff basic -gid "-$(($(id -g) + 1))" } function test_gid_minus_plus() { bfs_diff basic -gid "-+$(($(id -g) + 1))" } function test_uid() { bfs_diff basic -uid "$(id -u)" } function test_uid_plus() { bfs_diff basic -uid +0 } function test_uid_plus_plus() { bfs_diff basic -uid ++0 } function test_uid_minus() { bfs_diff basic -uid "-$(($(id -u) + 1))" } function test_uid_minus_plus() { bfs_diff basic -uid "-+$(($(id -u) + 1))" } function test_newer() { bfs_diff times -newer times/a } function test_newer_link() { bfs_diff times -newer times/l } function test_anewer() { bfs_diff times -anewer times/a } function test_links() { bfs_diff links -type f -links 2 } function test_links_plus() { bfs_diff links -type f -links +1 } function test_links_minus() { bfs_diff links -type f -links -2 } function test_P() { bfs_diff -P links/deeply/nested/dir } function test_P_slash() { bfs_diff -P links/deeply/nested/dir/ } function test_H() { bfs_diff -H links/deeply/nested/dir } function test_H_slash() { bfs_diff -H links/deeply/nested/dir/ } function test_H_broken() { bfs_diff -H links/broken } function test_H_notdir() { bfs_diff -H links/notdir } function test_H_newer() { bfs_diff -H times -newer times/l } function test_H_loops() { bfs_diff -H loops/deeply/nested/loop } function test_L() { bfs_diff -L links } function test_L_broken() { bfs_diff -H links/broken } function test_L_notdir() { bfs_diff -H links/notdir } function test_L_loops() { # POSIX says it's okay to either stop or keep going on seeing a filesystem # loop, as long as a diagnostic is printed local errors="$(invoke_bfs -L loops 2>&1 >/dev/null)" [ -n "$errors" ] } function test_L_loops_continue() { bfs_diff -L loops 2>/dev/null [ $? -eq $EX_BFS ] } function test_X() { bfs_diff -X weirdnames 2>/dev/null [ $? -eq $EX_BFS ] } function test_follow() { bfs_diff links -follow } function test_L_depth() { bfs_diff -L links -depth } function test_samefile() { bfs_diff links -samefile links/file } function test_samefile_symlink() { bfs_diff links -samefile links/symlink } function test_H_samefile_symlink() { bfs_diff -H links -samefile links/symlink } function test_L_samefile_symlink() { bfs_diff -L links -samefile links/symlink } function test_samefile_broken() { bfs_diff links -samefile links/broken } function test_H_samefile_broken() { bfs_diff -H links -samefile links/broken } function test_L_samefile_broken() { bfs_diff -L links -samefile links/broken } function test_samefile_notdir() { bfs_diff links -samefile links/notdir } function test_H_samefile_notdir() { bfs_diff -H links -samefile links/notdir } function test_L_samefile_notdir() { bfs_diff -L links -samefile links/notdir } function test_xtype_l() { bfs_diff links -xtype l } function test_xtype_f() { bfs_diff links -xtype f } function test_L_xtype_l() { bfs_diff -L links -xtype l } function test_L_xtype_f() { bfs_diff -L links -xtype f } function test_xtype_multi() { bfs_diff links -xtype f,d,c } function test_xtype_reorder() { # Make sure -xtype is not reordered in front of anything -- if -xtype runs # before -links 100, it will report an ELOOP error bfs_diff loops -links 100 -xtype l invoke_bfs loops -links 100 -xtype l } function test_iname() { bfs_diff basic -iname '*F*' } function test_ipath() { bfs_diff basic -ipath 'basic/*F*' } function test_lname() { bfs_diff links -lname '[aq]' } function test_ilname() { bfs_diff links -ilname '[AQ]' } function test_L_lname() { bfs_diff -L links -lname '[aq]' } function test_L_ilname() { bfs_diff -L links -ilname '[AQ]' } function test_user_name() { bfs_diff basic -user "$(id -un)" } function test_user_id() { bfs_diff basic -user "$(id -u)" } function test_group_name() { bfs_diff basic -group "$(id -gn)" } function test_group_id() { bfs_diff basic -group "$(id -g)" } function test_daystart() { bfs_diff basic -daystart -mtime 0 } function test_daystart_twice() { bfs_diff basic -daystart -daystart -mtime 0 } function test_newerma() { bfs_diff times -newerma times/a } function test_size() { bfs_diff basic -type f -size 0 } function test_size_plus() { bfs_diff basic -type f -size +0 } function test_size_bytes() { bfs_diff basic -type f -size +0c } function test_size_big() { bfs_diff basic -size 9223372036854775807 } function test_exec() { bfs_diff basic -exec echo '{}' \; } function test_exec_nothing() { # Regression test: don't segfault on missing command ! invoke_bfs basic -exec \; 2>/dev/null } function test_exec_plus() { bfs_diff basic -exec "$TESTS/sort-args.sh" '{}' + } function test_exec_plus_status() { # -exec ... {} + should always return true, but if the command fails, bfs # should exit with a non-zero status bfs_diff basic -exec false '{}' + -print ! invoke_bfs basic -exec false '{}' + } function test_exec_plus_semicolon() { # POSIX says: # Only a that immediately follows an argument containing only the two characters "{}" # shall punctuate the end of the primary expression. Other uses of the shall not be # treated as special. bfs_diff basic -exec echo foo '{}' bar + baz \; } function test_exec_substring() { bfs_diff basic -exec echo '-{}-' ';' } function test_execdir() { bfs_diff basic -execdir echo '{}' ';' } function test_execdir_plus() { if [[ "$BFS" != *"-S dfs"* ]]; then bfs_diff basic -execdir "$TESTS/sort-args.sh" '{}' + fi } function test_execdir_substring() { bfs_diff basic -execdir echo '-{}-' ';' } function test_execdir_plus_semicolon() { bfs_diff basic -execdir echo foo '{}' bar + baz \; } function test_execdir_pwd() { local TMP_REAL="$(cd "$TMP" && pwd)" local OFFSET="$((${#TMP_REAL} + 2))" bfs_diff basic -execdir bash -c "pwd | cut -b$OFFSET-" ';' } function test_execdir_slash() { # Don't prepend ./ for absolute paths in -execdir bfs_diff / -maxdepth 0 -execdir echo '{}' ';' } function test_execdir_slash_pwd() { bfs_diff / -maxdepth 0 -execdir pwd ';' } function test_execdir_slashes() { bfs_diff /// -maxdepth 0 -execdir echo '{}' ';' } function test_execdir_ulimit() { rm -rf scratch/* mkdir -p scratch/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z mkdir -p scratch/a/b/c/d/e/f/g/h/i/j/k/l/m/0/1/2/3/4/5/6/7/8/9/A/B/C closefrom 4 ulimit -n 13 bfs_diff scratch -execdir echo '{}' ';' } function test_weird_names() { cd weirdnames bfs_diff '-' '(-' '!-' ',' ')' './(' './!' \( \! -print -o -print \) } function test_flag_weird_names() { cd weirdnames bfs_diff -L '-' '(-' '!-' ',' ')' './(' './!' \( \! -print -o -print \) } function test_flag_comma() { # , is a filename until a non-flag is seen cd weirdnames bfs_diff -L ',' -print } function test_follow_comma() { # , is an operator after a non-flag is seen cd weirdnames bfs_diff -follow ',' -print } function test_fprint() { invoke_bfs basic -fprint scratch/test_fprint.out sort -o scratch/test_fprint.out scratch/test_fprint.out if [ "$UPDATE" ]; then cp scratch/test_fprint.out "$TESTS/test_fprint.out" else diff -u scratch/test_fprint.out "$TESTS/test_fprint.out" fi } function test_fprint_duplicate() { touchp scratch/test_fprint_duplicate.out ln scratch/test_fprint_duplicate.out scratch/test_fprint_duplicate.hard ln -s test_fprint_duplicate.out scratch/test_fprint_duplicate.soft invoke_bfs basic -fprint scratch/test_fprint_duplicate.out -fprint scratch/test_fprint_duplicate.hard -fprint scratch/test_fprint_duplicate.soft sort -o scratch/test_fprint_duplicate.out scratch/test_fprint_duplicate.out if [ "$UPDATE" ]; then cp scratch/test_fprint_duplicate.out "$TESTS/test_fprint_duplicate.out" else diff -u scratch/test_fprint_duplicate.out "$TESTS/test_fprint_duplicate.out" fi } function test_double_dash() { cd basic bfs_diff -- . -type f } function test_flag_double_dash() { cd basic bfs_diff -L -- . -type f } function test_ignore_readdir_race() { rm -rf scratch/* touch scratch/{foo,bar} # -links 1 forces a stat() call, which will fail for the second file invoke_bfs scratch -mindepth 1 -ignore_readdir_race -links 1 -exec "$TESTS/remove-sibling.sh" '{}' ';' } function test_ignore_readdir_race_root() { # Make sure -ignore_readdir_race doesn't suppress ENOENT at the root ! invoke_bfs basic/nonexistent -ignore_readdir_race 2>/dev/null } function test_ignore_readdir_race_notdir() { # Check -ignore_readdir_race handling when a directory is replaced with a file rm -rf scratch/* touchp scratch/foo/bar invoke_bfs scratch -mindepth 1 -ignore_readdir_race -execdir rm -r '{}' \; -execdir touch '{}' \; } function test_perm_000() { bfs_diff perms -perm 000 } function test_perm_000_minus() { bfs_diff perms -perm -000 } function test_perm_000_slash() { bfs_diff perms -perm /000 } function test_perm_000_plus() { bfs_diff perms -perm +000 } function test_perm_222() { bfs_diff perms -perm 222 } function test_perm_222_minus() { bfs_diff perms -perm -222 } function test_perm_222_slash() { bfs_diff perms -perm /222 } function test_perm_222_plus() { bfs_diff perms -perm +222 } function test_perm_644() { bfs_diff perms -perm 644 } function test_perm_644_minus() { bfs_diff perms -perm -644 } function test_perm_644_slash() { bfs_diff perms -perm /644 } function test_perm_644_plus() { bfs_diff perms -perm +644 } function test_perm_symbolic() { bfs_diff perms -perm a+r,u=wX,g+wX-w } function test_perm_symbolic_minus() { bfs_diff perms -perm -a+r,u=wX,g+wX-w } function test_perm_symbolic_slash() { bfs_diff perms -perm /a+r,u=wX,g+wX-w } function test_perm_symbolic_trailing_comma() { ! invoke_bfs perms -perm a+r, 2>/dev/null } function test_perm_symbolic_double_comma() { ! invoke_bfs perms -perm a+r,,u+w 2>/dev/null } function test_perm_symbolic_missing_action() { ! invoke_bfs perms -perm a 2>/dev/null } function test_perm_leading_plus_symbolic() { bfs_diff perms -perm +rwx } function test_perm_leading_plus_symbolic_minus() { bfs_diff perms -perm -+rwx } function test_perm_leading_plus_symbolic_slash() { bfs_diff perms -perm /+rwx } function test_permcopy() { bfs_diff perms -perm u+rw,g+u-w,o=g } function test_prune() { bfs_diff basic -name foo -prune } function test_prune_or_print() { bfs_diff basic -name foo -prune -o -print } function test_not_prune() { bfs_diff basic \! \( -name foo -prune \) } function test_ok_nothing() { # Regression test: don't segfault on missing command ! invoke_bfs basic -ok \; 2>/dev/null } function test_ok_stdin() { # -ok should *not* close stdin # See https://savannah.gnu.org/bugs/?24561 yes | bfs_diff basic -ok bash -c 'printf "%s? " "$1" && head -n1' bash '{}' \; 2>/dev/null } function test_okdir_stdin() { # -okdir should *not* close stdin yes | bfs_diff basic -okdir bash -c 'printf "%s? " "$1" && head -n1' bash '{}' \; 2>/dev/null } function test_ok_plus_semicolon() { yes | bfs_diff basic -ok echo '{}' + \; 2>/dev/null } function test_okdir_plus_semicolon() { yes | bfs_diff basic -okdir echo '{}' + \; 2>/dev/null } function test_delete() { rm -rf scratch/* touchp scratch/foo/bar/baz # Don't try to delete '.' (cd scratch && invoke_bfs . -delete) bfs_diff scratch } function test_L_delete() { rm -rf scratch/* mkdir scratch/foo mkdir scratch/bar ln -s ../foo scratch/bar/baz # Don't try to rmdir() a symlink invoke_bfs -L scratch/bar -delete || return 1 bfs_diff scratch } function test_rm() { rm -rf scratch/* touchp scratch/foo/bar/baz (cd scratch && invoke_bfs . -rm) bfs_diff scratch } function test_regex() { bfs_diff basic -regex 'basic/./.' } function test_iregex() { bfs_diff basic -iregex 'basic/[A-Z]/[a-z]' } function test_regex_parens() { cd weirdnames bfs_diff . -regex '\./\((\)' } function test_E() { cd weirdnames bfs_diff -E . -regex '\./(\()' } function test_regextype_posix_basic() { cd weirdnames bfs_diff -regextype posix-basic -regex '\./\((\)' } function test_regextype_posix_extended() { cd weirdnames bfs_diff -regextype posix-extended -regex '\./(\()' } function test_d_path() { bfs_diff -d basic } function test_path_d() { bfs_diff basic -d } function test_f() { cd weirdnames bfs_diff -f '-' -f '(' } function test_hidden() { bfs_diff weirdnames -hidden } function test_nohidden() { bfs_diff weirdnames -nohidden } function test_depth_n() { bfs_diff basic -depth 2 } function test_depth_n_plus() { bfs_diff basic -depth +2 } function test_depth_n_minus() { bfs_diff basic -depth -2 } function test_depth_depth_n() { bfs_diff basic -depth -depth 2 } function test_depth_depth_n_plus() { bfs_diff basic -depth -depth +2 } function test_depth_depth_n_minus() { bfs_diff basic -depth -depth -2 } function test_depth_overflow() { bfs_diff basic -depth -4294967296 } function test_gid_name() { bfs_diff basic -gid "$(id -gn)" } function test_uid_name() { bfs_diff basic -uid "$(id -un)" } function test_mnewer() { bfs_diff times -mnewer times/a } function test_H_mnewer() { bfs_diff -H times -mnewer times/l } function test_size_T() { bfs_diff basic -type f -size 1T } function test_quit() { bfs_diff basic/g -print -name g -quit } function test_quit_child() { bfs_diff basic/g -print -name h -quit } function test_quit_depth() { bfs_diff basic/g -depth -print -name g -quit } function test_quit_depth_child() { bfs_diff basic/g -depth -print -name h -quit } function test_quit_after_print() { bfs_diff basic basic -print -quit } function test_quit_before_print() { bfs_diff basic basic -quit -print } function test_quit_implicit_print() { bfs_diff basic -name basic -o -quit } function test_inum() { bfs_diff basic -inum "$(inum basic/k/foo/bar)" } function test_nogroup() { bfs_diff basic -nogroup } function test_nogroup_ulimit() { closefrom 4 ulimit -n 16 bfs_diff deep -nogroup } function test_nouser() { bfs_diff basic -nouser } function test_nouser_ulimit() { closefrom 4 ulimit -n 16 bfs_diff deep -nouser } function test_printf() { bfs_diff basic -printf '%%p(%p) %%d(%d) %%f(%f) %%h(%h) %%H(%H) %%P(%P) %%m(%m) %%M(%M) %%y(%y)\n' } function test_printf_empty() { bfs_diff basic -printf '' } function test_printf_slash() { bfs_diff / -maxdepth 0 -printf '(%h)/(%f)\n' } function test_printf_slashes() { bfs_diff /// -maxdepth 0 -printf '(%h)/(%f)\n' } function test_printf_trailing_slash() { bfs_diff basic/ -printf '(%h)/(%f)\n' } function test_printf_trailing_slashes() { bfs_diff basic/// -printf '(%h)/(%f)\n' } function test_printf_flags() { bfs_diff basic -printf '|%- 10.10p| %+03d %#4m\n' } function test_printf_types() { bfs_diff loops -printf '(%p) (%l) %y %Y\n' } function test_printf_escapes() { bfs_diff basic -maxdepth 0 -printf '\18\118\1118\11118\n\cfoo' } function test_printf_times() { bfs_diff times -type f -printf '%p | %a %AY-%Am-%Ad %AH:%AI:%AS %T@ | %t %TY-%Tm-%Td %TH:%TI:%TS %T@\n' } function test_printf_leak() { # Memory leak regression test bfs_diff basic -maxdepth 0 -printf '%p' } function test_printf_nul() { # NUL byte regression test local EXPECTED="$TESTS/${FUNCNAME[0]}.out" if [ "$UPDATE" ]; then local ACTUAL="$EXPECTED" else local ACTUAL="$TMP/${FUNCNAME[0]}.out" fi invoke_bfs basic -maxdepth 0 -printf '%h\0%f\n' >"$ACTUAL" if [ ! "$UPDATE" ]; then diff -u "$EXPECTED" "$ACTUAL" fi } function test_printf_w() { # Birth times may not be supported, so just check that %w/%W/%B can be parsed bfs_diff times -false -printf '%w %WY %BY\n' } function test_printf_Y_error() { rm -rf scratch/* mkdir scratch/foo chmod -x scratch/foo ln -s foo/bar scratch/bar bfs_diff scratch -printf '(%p) (%l) %y %Y\n' 2>/dev/null local ret=$? chmod +x scratch/foo rm -rf scratch/* [ $ret -eq $EX_BFS ] } function test_printf_H() { bfs_diff basic links -printf '%%p(%p) %%d(%d) %%f(%f) %%h(%h) %%H(%H) %%P(%P) %%y(%y)\n' } function test_fstype() { fstype="$(invoke_bfs basic -maxdepth 0 -printf '%F\n')" bfs_diff basic -fstype "$fstype" } function test_path_flag_expr() { bfs_diff links/skip -H -type l } function test_path_expr_flag() { bfs_diff links/skip -type l -H } function test_flag_expr_path() { bfs_diff -H -type l links/skip } function test_expr_flag_path() { bfs_diff -type l -H links/skip } function test_expr_path_flag() { bfs_diff -type l links/skip -H } function test_parens() { bfs_diff basic \( -name '*f*' \) } function test_bang() { bfs_diff basic \! -name foo } function test_not() { bfs_diff basic -not -name foo } function test_implicit_and() { bfs_diff basic -name foo -type d } function test_a() { bfs_diff basic -name foo -a -type d } function test_and() { bfs_diff basic -name foo -and -type d } function test_o() { bfs_diff basic -name foo -o -type d } function test_or() { bfs_diff basic -name foo -or -type d } function test_comma() { bfs_diff basic -name '*f*' -print , -print } function test_precedence() { bfs_diff basic \( -name foo -type d -o -name bar -a -type f \) -print , \! -empty -type f -print } function test_color() { LS_COLORS= bfs_diff rainbow -color } function test_color_L() { LS_COLORS= bfs_diff -L rainbow -color } function test_color_rs_lc_rc_ec() { LS_COLORS="rs=RS:lc=LC:rc=RC:ec=EC:" bfs_diff rainbow -color } function test_color_escapes() { LS_COLORS="lc=\e[:rc=\155\::ec=^[\x5B\x6d:" bfs_diff rainbow -color } function test_color_nul() { local EXPECTED="$TESTS/${FUNCNAME[0]}.out" if [ "$UPDATE" ]; then local ACTUAL="$EXPECTED" else local ACTUAL="$TMP/${FUNCNAME[0]}.out" fi LS_COLORS="ec=\33[m\0:" invoke_bfs rainbow -color -maxdepth 0 >"$ACTUAL" if [ ! "$UPDATE" ]; then diff -u "$EXPECTED" "$ACTUAL" fi } function test_color_ln_target() { LS_COLORS="ln=target:or=01;31:mi=01;33:" bfs_diff rainbow -color } function test_color_L_ln_target() { LS_COLORS="ln=target:or=01;31:mi=01;33:" bfs_diff -L rainbow -color } function test_color_mh() { LS_COLORS="mh=01:" bfs_diff rainbow -color } function test_color_mh0() { LS_COLORS="mh=00:" bfs_diff rainbow -color } function test_color_or() { LS_COLORS="or=01:" bfs_diff rainbow -color } function test_color_mi() { LS_COLORS="mi=01:" bfs_diff rainbow -color } function test_color_or_mi() { LS_COLORS="or=01;31:mi=01;33:" bfs_diff rainbow -color } function test_color_or_mi0() { LS_COLORS="or=01;31:mi=00:" bfs_diff rainbow -color } function test_color_or0_mi() { LS_COLORS="or=00:mi=01;33:" bfs_diff rainbow -color } function test_color_or0_mi0() { LS_COLORS="or=00:mi=00:" bfs_diff rainbow -color } function test_color_su_sg0() { LS_COLORS="su=37;41:sg=00:" bfs_diff rainbow -color } function test_color_su0_sg() { LS_COLORS="su=00:sg=30;43:" bfs_diff rainbow -color } function test_color_su0_sg0() { LS_COLORS="su=00:sg=00:" bfs_diff rainbow -color } function test_color_st_tw_ow0() { LS_COLORS="st=37;44:tw=40;32:ow=00:" bfs_diff rainbow -color } function test_color_st_tw0_ow() { LS_COLORS="st=37;44:tw=00:ow=34;42:" bfs_diff rainbow -color } function test_color_st_tw0_ow0() { LS_COLORS="st=37;44:tw=00:ow=00:" bfs_diff rainbow -color } function test_color_st0_tw_ow() { LS_COLORS="st=00:tw=40;32:ow=34;42:" bfs_diff rainbow -color } function test_color_st0_tw_ow0() { LS_COLORS="st=00:tw=40;32:ow=00:" bfs_diff rainbow -color } function test_color_st0_tw0_ow() { LS_COLORS="st=00:tw=00:ow=34;42:" bfs_diff rainbow -color } function test_color_st0_tw0_ow0() { LS_COLORS="st=00:tw=00:ow=00:" bfs_diff rainbow -color } function test_color_ext() { LS_COLORS="*.txt=01:" bfs_diff rainbow -color } function test_color_ext0() { LS_COLORS="*.txt=00:" bfs_diff rainbow -color } function test_color_ext_override() { LS_COLORS="*.tar.gz=01;31:*.tar=01;32:*.gz=01;33:" bfs_diff rainbow -color } function test_color_ext_underride() { LS_COLORS="*.gz=01;33:*.tar=01;32:*.tar.gz=01;31:" bfs_diff rainbow -color } function test_color_missing_colon() { LS_COLORS="*.txt=01" bfs_diff rainbow -color } function test_color_no_stat() { LS_COLORS="mh=0:ex=0:sg=0:su=0:st=0:ow=0:tw=0:*.txt=01:" bfs_diff rainbow -color } function test_color_L_no_stat() { LS_COLORS="mh=0:ex=0:sg=0:su=0:st=0:ow=0:tw=0:*.txt=01:" bfs_diff -L rainbow -color } function test_color_star() { # Regression test: don't segfault on LS_COLORS="*" LS_COLORS="*" bfs_diff rainbow -color } function test_color_ls() { rm -rf scratch/* touchp scratch/foo/bar/baz ln -s foo/bar/baz scratch/link ln -s foo/bar/nowhere scratch/broken ln -s foo/bar/nowhere/nothing scratch/nested ln -s foo/bar/baz/qux scratch/notdir ln -s scratch/foo/bar scratch/relative mkdir scratch/__bfs__ ln -s /__bfs__/nowhere scratch/absolute local EXPECTED="$TESTS/${FUNCNAME[0]}.out" if [ "$UPDATE" ]; then local ACTUAL="$EXPECTED" else local ACTUAL="$TMP/${FUNCNAME[0]}.out" fi LS_COLORS="or=01;31:" invoke_bfs scratch/{,link,broken,nested,notdir,relative,absolute} -color -type l -ls \ | sed 's/.* -> //' \ | sort -o "$ACTUAL" if [ ! "$UPDATE" ]; then diff -u "$EXPECTED" "$ACTUAL" fi } function test_deep() { closefrom 4 ulimit -n 16 bfs_diff deep -type f -exec bash -c 'echo "${1:0:6}/.../${1##*/} (${#1})"' bash '{}' \; } function test_deep_strict() { closefrom 4 # Not even enough fds to keep the root open ulimit -n 7 bfs_diff deep -type f -exec bash -c 'echo "${1:0:6}/.../${1##*/} (${#1})"' bash '{}' \; } function test_exit() { invoke_bfs basic -name foo -exit 42 if [ $? -ne 42 ]; then return 1 fi invoke_bfs basic -name qux -exit 42 if [ $? -ne 0 ]; then return 1 fi bfs_diff basic/g -print -name g -exit } function test_printx() { bfs_diff weirdnames -printx } function test_and_purity() { # Regression test: (-a lhs(pure) rhs(always_false)) <==> rhs is only valid if rhs is pure bfs_diff basic -name nonexistent \( -print , -false \) } function test_or_purity() { # Regression test: (-o lhs(pure) rhs(always_true)) <==> rhs is only valid if rhs is pure bfs_diff basic -name '*' -o -print } function test_double_negation() { bfs_diff basic \! \! -name 'foo' } function test_not_reachability() { bfs_diff basic -print \! -quit -print } function test_comma_reachability() { bfs_diff basic -print -quit , -print } function test_de_morgan_not() { bfs_diff basic \! \( -name 'foo' -o \! -type f \) } function test_de_morgan_and() { bfs_diff basic \( \! -name 'foo' -a \! -type f \) } function test_de_morgan_or() { bfs_diff basic \( \! -name 'foo' -o \! -type f \) } function test_and_false_or_true() { # Test (-a lhs(always_true) -false) <==> (! lhs), # (-o lhs(always_false) -true) <==> (! lhs) bfs_diff basic -prune -false -o -true } function test_comma_redundant_true() { # Test (, lhs(always_true) -true) <==> lhs bfs_diff basic -prune , -true } function test_comma_redundant_false() { # Test (, lhs(always_false) -false) <==> lhs bfs_diff basic -print -not -prune , -false } function test_data_flow_depth() { bfs_diff basic -depth +1 -depth -4 } function test_data_flow_type() { bfs_diff basic \! \( -type f -o \! -type f \) } function test_data_flow_and_swap() { bfs_diff basic \! -type f -a -type d } function test_data_flow_or_swap() { bfs_diff basic \! \( -type f -o \! -type d \) } function test_print_error() { if [ -e /dev/full ]; then ! invoke_bfs basic -maxdepth 0 >/dev/full 2>/dev/null fi } function test_fprint_error() { if [ -e /dev/full ]; then ! invoke_bfs basic -maxdepth 0 -fprint /dev/full 2>/dev/null fi } function test_closed_stdin() { bfs_diff basic <&- } function test_ok_closed_stdin() { bfs_diff basic -ok echo \; <&- 2>/dev/null } function test_okdir_closed_stdin() { bfs_diff basic -okdir echo {} \; <&- 2>/dev/null } function test_closed_stdout() { ! invoke_bfs basic >&- 2>/dev/null } function test_closed_stderr() { ! invoke_bfs basic >&- 2>&- } function test_unique() { bfs_diff links/{file,symlink,hardlink} -unique } function test_unique_depth() { bfs_diff basic -unique -depth } function test_L_unique() { bfs_diff -L links/{file,symlink,hardlink} -unique } function test_L_unique_loops() { bfs_diff -L loops/deeply/nested -unique } function test_L_unique_depth() { bfs_diff -L loops/deeply/nested -unique -depth } function test_mount() { rm -rf scratch/* mkdir scratch/{foo,mnt} sudo mount -t tmpfs tmpfs scratch/mnt touch scratch/foo/bar scratch/mnt/baz bfs_diff scratch -mount local ret=$? sudo umount scratch/mnt return $ret } function test_L_mount() { rm -rf scratch/* mkdir scratch/{foo,mnt} sudo mount -t tmpfs tmpfs scratch/mnt ln -s ../mnt scratch/foo/bar touch scratch/mnt/baz ln -s ../mnt/baz scratch/foo/qux bfs_diff -L scratch -mount local ret=$? sudo umount scratch/mnt return $ret } function test_xdev() { rm -rf scratch/* mkdir scratch/{foo,mnt} sudo mount -t tmpfs tmpfs scratch/mnt touch scratch/foo/bar scratch/mnt/baz bfs_diff scratch -xdev local ret=$? sudo umount scratch/mnt return $ret } function test_L_xdev() { rm -rf scratch/* mkdir scratch/{foo,mnt} sudo mount -t tmpfs tmpfs scratch/mnt ln -s ../mnt scratch/foo/bar touch scratch/mnt/baz ln -s ../mnt/baz scratch/foo/qux bfs_diff -L scratch -xdev local ret=$? sudo umount scratch/mnt return $ret } function test_inum_mount() { rm -rf scratch/* mkdir scratch/{foo,mnt} sudo mount -t tmpfs tmpfs scratch/mnt bfs_diff scratch -inum "$(inum scratch/mnt)" local ret=$? sudo umount scratch/mnt return $ret } function test_inum_bind_mount() { rm -rf scratch/* touch scratch/{foo,bar} sudo mount --bind scratch/{foo,bar} bfs_diff scratch -inum "$(inum scratch/bar)" local ret=$? sudo umount scratch/bar return $ret } function test_type_bind_mount() { rm -rf scratch/* touch scratch/{file,null} sudo mount --bind /dev/null scratch/null bfs_diff scratch -type c local ret=$? sudo umount scratch/null return $ret } function test_xtype_bind_mount() { rm -rf scratch/* touch scratch/{file,null} sudo mount --bind /dev/null scratch/null ln -s /dev/null scratch/link bfs_diff -L scratch -type c local ret=$? sudo umount scratch/null return $ret } function set_acl() { case "$UNAME" in Darwin) chmod +a "$(id -un) allow read,write" "$1" ;; FreeBSD) if [ "$(getconf ACL_NFS4 "$1")" -gt 0 ]; then setfacl -m "u:$(id -un):rw::allow" "$1" else setfacl -m "u:$(id -un):rw" "$1" fi ;; *) setfacl -m "u:$(id -un):rw" "$1" ;; esac } function test_acl() { rm -rf scratch/* invoke_bfs scratch -quit -acl 2>/dev/null || return 0 touch scratch/{normal,acl} set_acl scratch/acl || return 0 ln -s acl scratch/link bfs_diff scratch -acl } function test_L_acl() { rm -rf scratch/* invoke_bfs scratch -quit -acl 2>/dev/null || return 0 touch scratch/{normal,acl} set_acl scratch/acl || return 0 ln -s acl scratch/link bfs_diff -L scratch -acl } function test_capable() { rm -rf scratch/* if ! invoke_bfs scratch -quit -capable 2>/dev/null; then return 0 fi touch scratch/{normal,capable} sudo setcap all+ep scratch/capable ln -s capable scratch/link bfs_diff scratch -capable } function test_L_capable() { rm -rf scratch/* if ! invoke_bfs scratch -quit -capable 2>/dev/null; then return 0 fi touch scratch/{normal,capable} sudo setcap all+ep scratch/capable ln -s capable scratch/link bfs_diff -L scratch -capable } function set_xattr() { case "$UNAME" in Darwin) xattr -w bfs_test true "$1" xattr -s -w bfs_test true "$2" ;; FreeBSD) setextattr user bfs_test true "$1" setextattr -h user bfs_test true "$2" ;; *) # Linux tmpfs doesn't support the user.* namespace, so we use the security.* # namespace, which is writable by root and readable by others sudo setfattr -n security.bfs_test "$1" sudo setfattr -h -n security.bfs_test "$2" ;; esac } function test_xattr() { rm -rf scratch/* if ! invoke_bfs scratch -quit -xattr 2>/dev/null; then return 0 fi touch scratch/{normal,xattr} ln -s xattr scratch/link ln -s normal scratch/xattr_link set_xattr scratch/xattr scratch/xattr_link bfs_diff scratch -xattr } function test_L_xattr() { rm -rf scratch/* if ! invoke_bfs scratch -quit -xattr 2>/dev/null; then return 0 fi touch scratch/{normal,xattr} ln -s xattr scratch/link ln -s normal scratch/xattr_link set_xattr scratch/xattr scratch/xattr_link bfs_diff -L scratch -xattr } function test_help() { ! invoke_bfs -help | grep -E '\{...?\}' } BOL= EOL='\n' function update_eol() { # Put the cursor at the last column, then write a space so the next # character will wrap EOL="\\033[${COLUMNS}G " } if [ -t 1 -a ! "$VERBOSE" ]; then BOL='\r\033[K' # Workaround for bash 4: checkwinsize is off by default. We can turn it on, # but we also have to explicitly trigger a foreground job to finish so that # it will update the window size before we use $COLUMNS shopt -s checkwinsize (:) update_eol trap update_eol WINCH fi passed=0 failed=0 for test in "${enabled_tests[@]}"; do printf "${BOL}${YLW}%s${RST}${EOL}" "$test" ("$test") status=$? if [ $status -eq 0 ]; then ((++passed)) else ((++failed)) printf "${BOL}${RED}%s failed!${RST}\n" "$test" fi done if [ $passed -gt 0 ]; then printf "${BOL}${GRN}tests passed: %d${RST}\n" "$passed" fi if [ $failed -gt 0 ]; then printf "${BOL}${RED}tests failed: %s${RST}\n" "$failed" exit 1 fi bfs-1.5.2/tests/000077500000000000000000000000001360564154000134375ustar00rootroot00000000000000bfs-1.5.2/tests/find-color.sh000077500000000000000000000030001360564154000160230ustar00rootroot00000000000000#!/bin/bash ############################################################################ # bfs # # Copyright (C) 2019 Tavian Barnes # # # # 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. # ############################################################################ set -e L= COLOR= ARGS=() for ARG; do case "$ARG" in -L) L="$ARG" ;; -color) COLOR=y ;; *) ARGS+=("$ARG") ;; esac done LS_COLOR="${BASH_SOURCE%/*}/ls-color.sh" if [ "$COLOR" ]; then find "${ARGS[@]}" -exec "$LS_COLOR" $L {} \; else find "${ARGS[@]}" fi bfs-1.5.2/tests/ls-color.sh000077500000000000000000000031551360564154000155340ustar00rootroot00000000000000#!/bin/bash ############################################################################ # bfs # # Copyright (C) 2019 Tavian Barnes # # # # 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. # ############################################################################ # Prints the "ground truth" coloring of a path using ls set -e L= if [ "$1" = "-L" ]; then L="$1" shift fi function ls_color() { # Strip the leading reset sequence from the ls output ls -1d --color "$@" | sed $'s/^\033\\[0m//' } DIR="${1%/*}" if [ "$DIR" = "$1" ]; then ls_color "$1" exit fi BASE="${1##*/}" ls_color $L "$DIR/" | tr -d '\n' if [ -e "$1" ]; then (cd "$DIR" && ls_color $L "$BASE") else (cd "$DIR" && ls_color "$BASE") fi bfs-1.5.2/tests/mksock.c000066400000000000000000000064211360564154000150750ustar00rootroot00000000000000/**************************************************************************** * bfs * * Copyright (C) 2019 Tavian Barnes * * * * 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. * ****************************************************************************/ /** * There's no standard Unix utility that creates a socket file, so this small * program does the job. */ #include #include #include #include #include #include #include #include #include #include #include /** * Print an error message. */ static void errmsg(const char *cmd, const char *path) { fprintf(stderr, "%s: '%s': %s.\n", cmd, path, strerror(errno)); } /** * struct sockaddr_un::sun_path is very short, so we chdir() into the target * directory before creating sockets in case the full path is too long but the * file name is not. */ static int chdir_parent(const char *path) { char *copy = strdup(path); if (!copy) { return -1; } const char *dir = dirname(copy); int ret = chdir(dir); int error = errno; free(copy); errno = error; return ret; } /** * Initialize a struct sockaddr_un with the right filename. */ static int init_sun(struct sockaddr_un *sock, const char *path) { size_t len = strlen(path); if (len == 0 || path[len - 1] == '/') { errno = ENOENT; return -1; } char *copy = strdup(path); if (!copy) { return -1; } const char *base = basename(copy); len = strlen(base); if (len >= sizeof(sock->sun_path)) { free(copy); errno = ENAMETOOLONG; return -1; } sock->sun_family = AF_UNIX; memcpy(sock->sun_path, base, len + 1); free(copy); return 0; } int main(int argc, char *argv[]) { const char *cmd = argc > 0 ? argv[0] : "mksock"; if (argc != 2) { fprintf(stderr, "Usage: %s NAME\n", cmd); return EXIT_FAILURE; } const char *path = argv[1]; if (chdir_parent(path) != 0) { errmsg(cmd, path); return EXIT_FAILURE; } struct sockaddr_un sock; if (init_sun(&sock, path) != 0) { errmsg(cmd, path); return EXIT_FAILURE; } int fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd < 0) { errmsg(cmd, path); return EXIT_FAILURE; } int ret = EXIT_SUCCESS; if (bind(fd, (struct sockaddr *)&sock, sizeof(sock)) != 0) { errmsg(cmd, path); ret = EXIT_FAILURE; } if (close(fd) != 0) { errmsg(cmd, path); ret = EXIT_FAILURE; } return ret; } bfs-1.5.2/tests/remove-sibling.sh000077500000000000000000000002011360564154000167110ustar00rootroot00000000000000#!/bin/bash for file in "${1%/*}"/*; do if [ "$file" != "$1" ]; then rm "$file" exit $? fi done exit 1 bfs-1.5.2/tests/sort-args.sh000077500000000000000000000001221360564154000157120ustar00rootroot00000000000000#!/bin/bash args=($({ for arg; do echo "$arg"; done } | sort)) echo "${args[@]}" bfs-1.5.2/tests/test_E.out000066400000000000000000000000041360564154000154050ustar00rootroot00000000000000./( bfs-1.5.2/tests/test_H.out000066400000000000000000000000301360564154000154070ustar00rootroot00000000000000links/deeply/nested/dir bfs-1.5.2/tests/test_H_broken.out000066400000000000000000000000151360564154000167520ustar00rootroot00000000000000links/broken bfs-1.5.2/tests/test_H_loops.out000066400000000000000000000002021360564154000166240ustar00rootroot00000000000000loops/deeply/nested/loop loops/deeply/nested/loop/nested loops/deeply/nested/loop/nested/dir loops/deeply/nested/loop/nested/loop bfs-1.5.2/tests/test_H_mnewer.out000066400000000000000000000000361360564154000167720ustar00rootroot00000000000000times times/b times/c times/l bfs-1.5.2/tests/test_H_newer.out000066400000000000000000000000361360564154000166150ustar00rootroot00000000000000times times/b times/c times/l bfs-1.5.2/tests/test_H_notdir.out000066400000000000000000000000151360564154000167710ustar00rootroot00000000000000links/notdir bfs-1.5.2/tests/test_H_samefile_broken.out000066400000000000000000000000151360564154000206170ustar00rootroot00000000000000links/broken bfs-1.5.2/tests/test_H_samefile_notdir.out000066400000000000000000000000151360564154000206360ustar00rootroot00000000000000links/notdir bfs-1.5.2/tests/test_H_samefile_symlink.out000066400000000000000000000000321360564154000210240ustar00rootroot00000000000000links/file links/hardlink bfs-1.5.2/tests/test_H_slash.out000066400000000000000000000000311360564154000166020ustar00rootroot00000000000000links/deeply/nested/dir/ bfs-1.5.2/tests/test_L.out000066400000000000000000000003551360564154000154250ustar00rootroot00000000000000links links/broken links/deeply links/file links/hardlink links/notdir links/skip links/symlink links/deeply/nested links/skip/dir links/skip/file links/skip/link links/deeply/nested/dir links/deeply/nested/file links/deeply/nested/link bfs-1.5.2/tests/test_L_acl.out000066400000000000000000000000311360564154000162330ustar00rootroot00000000000000scratch/acl scratch/link bfs-1.5.2/tests/test_L_broken.out000066400000000000000000000000151360564154000167560ustar00rootroot00000000000000links/broken bfs-1.5.2/tests/test_L_capable.out000066400000000000000000000000351360564154000170670ustar00rootroot00000000000000scratch/capable scratch/link bfs-1.5.2/tests/test_L_delete.out000066400000000000000000000000241360564154000167400ustar00rootroot00000000000000scratch scratch/foo bfs-1.5.2/tests/test_L_depth.out000066400000000000000000000003551360564154000166110ustar00rootroot00000000000000links links/broken links/deeply links/file links/hardlink links/notdir links/skip links/symlink links/deeply/nested links/skip/dir links/skip/file links/skip/link links/deeply/nested/dir links/deeply/nested/file links/deeply/nested/link bfs-1.5.2/tests/test_L_ilname.out000066400000000000000000000000001360564154000167350ustar00rootroot00000000000000bfs-1.5.2/tests/test_L_lname.out000066400000000000000000000000001360564154000165640ustar00rootroot00000000000000bfs-1.5.2/tests/test_L_loops_continue.out000066400000000000000000000002341360564154000205410ustar00rootroot00000000000000loops loops/broken loops/deeply loops/file loops/notdir loops/skip loops/symlink loops/deeply/nested loops/skip/dir loops/skip/loop loops/deeply/nested/dir bfs-1.5.2/tests/test_L_mount.out000066400000000000000000000001001360564154000166330ustar00rootroot00000000000000scratch scratch/foo scratch/mnt scratch/foo/bar scratch/foo/qux bfs-1.5.2/tests/test_L_notdir.out000066400000000000000000000000151360564154000167750ustar00rootroot00000000000000links/notdir bfs-1.5.2/tests/test_L_samefile_broken.out000066400000000000000000000000151360564154000206230ustar00rootroot00000000000000links/broken bfs-1.5.2/tests/test_L_samefile_notdir.out000066400000000000000000000000151360564154000206420ustar00rootroot00000000000000links/notdir bfs-1.5.2/tests/test_L_samefile_symlink.out000066400000000000000000000000501360564154000210300ustar00rootroot00000000000000links/file links/hardlink links/symlink bfs-1.5.2/tests/test_L_unique.out000066400000000000000000000000131360564154000170020ustar00rootroot00000000000000links/file bfs-1.5.2/tests/test_L_unique_depth.out000066400000000000000000000001051360564154000201700ustar00rootroot00000000000000loops/deeply/nested loops/deeply/nested/dir loops/deeply/nested/loop bfs-1.5.2/tests/test_L_unique_loops.out000066400000000000000000000001051360564154000202200ustar00rootroot00000000000000loops/deeply/nested loops/deeply/nested/dir loops/deeply/nested/loop bfs-1.5.2/tests/test_L_xattr.out000066400000000000000000000000331360564154000166400ustar00rootroot00000000000000scratch/link scratch/xattr bfs-1.5.2/tests/test_L_xdev.out000066400000000000000000000001001360564154000164370ustar00rootroot00000000000000scratch scratch/foo scratch/mnt scratch/foo/bar scratch/foo/qux bfs-1.5.2/tests/test_L_xtype_f.out000066400000000000000000000001031360564154000171520ustar00rootroot00000000000000links/file links/hardlink links/skip/file links/deeply/nested/file bfs-1.5.2/tests/test_L_xtype_l.out000066400000000000000000000001341360564154000171640ustar00rootroot00000000000000links/broken links/notdir links/skip links/symlink links/skip/link links/deeply/nested/link bfs-1.5.2/tests/test_P.out000066400000000000000000000000301360564154000154170ustar00rootroot00000000000000links/deeply/nested/dir bfs-1.5.2/tests/test_P_slash.out000066400000000000000000000000311360564154000166120ustar00rootroot00000000000000links/deeply/nested/dir/ bfs-1.5.2/tests/test_X.out000066400000000000000000000003631360564154000154400ustar00rootroot00000000000000weirdnames weirdnames/! weirdnames/!- weirdnames/( weirdnames/(- weirdnames/) weirdnames/, weirdnames/- weirdnames/... weirdnames/!-/e weirdnames/!/d weirdnames/(-/c weirdnames/(/b weirdnames/)/g weirdnames/,/f weirdnames/-/a weirdnames/.../h bfs-1.5.2/tests/test_a.out000066400000000000000000000000301360564154000154400ustar00rootroot00000000000000basic/k/foo basic/l/foo bfs-1.5.2/tests/test_acl.out000066400000000000000000000000141360564154000157610ustar00rootroot00000000000000scratch/acl bfs-1.5.2/tests/test_and.out000066400000000000000000000000301360564154000157620ustar00rootroot00000000000000basic/k/foo basic/l/foo bfs-1.5.2/tests/test_and_false_or_true.out000066400000000000000000000000061360564154000206760ustar00rootroot00000000000000basic bfs-1.5.2/tests/test_and_purity.out000066400000000000000000000000001360564154000173730ustar00rootroot00000000000000bfs-1.5.2/tests/test_anewer.out000066400000000000000000000000361360564154000165070ustar00rootroot00000000000000times times/b times/c times/l bfs-1.5.2/tests/test_bang.out000066400000000000000000000002401360564154000161320ustar00rootroot00000000000000basic basic/a basic/b basic/c basic/e basic/g basic/i basic/j basic/k basic/l basic/c/d basic/e/f basic/g/h basic/k/foo/bar basic/l/foo/bar basic/l/foo/bar/baz bfs-1.5.2/tests/test_basic.out000066400000000000000000000003041360564154000163050ustar00rootroot00000000000000basic basic/a basic/b basic/c basic/e basic/g basic/i basic/j basic/k basic/l basic/c/d basic/e/f basic/g/h basic/j/foo basic/k/foo basic/l/foo basic/k/foo/bar basic/l/foo/bar basic/l/foo/bar/baz bfs-1.5.2/tests/test_capable.out000066400000000000000000000000201360564154000166060ustar00rootroot00000000000000scratch/capable bfs-1.5.2/tests/test_closed_stdin.out000066400000000000000000000003041360564154000176760ustar00rootroot00000000000000basic basic/a basic/b basic/c basic/e basic/g basic/i basic/j basic/k basic/l basic/c/d basic/e/f basic/g/h basic/j/foo basic/k/foo basic/l/foo basic/k/foo/bar basic/l/foo/bar basic/l/foo/bar/baz bfs-1.5.2/tests/test_color.out000066400000000000000000000012511360564154000163440ustar00rootroot00000000000000rainbow rainbow/exec.sh rainbow/socket rainbow/broken rainbow/chardev_link rainbow/link.txt rainbow/sticky_ow rainbow/sgid rainbow/pipe rainbow/ow rainbow/sugid rainbow/suid rainbow/sticky rainbow/file.dat rainbow/file.txt rainbow/mh1 rainbow/mh2 rainbow/star.gz rainbow/star.tar rainbow/star.tar.gz bfs-1.5.2/tests/test_color_L.out000066400000000000000000000012351360564154000166210ustar00rootroot00000000000000rainbow rainbow/exec.sh rainbow/chardev_link rainbow/socket rainbow/broken rainbow/sticky_ow rainbow/sgid rainbow/pipe rainbow/ow rainbow/sugid rainbow/suid rainbow/sticky rainbow/file.dat rainbow/file.txt rainbow/link.txt rainbow/mh1 rainbow/mh2 rainbow/star.gz rainbow/star.tar rainbow/star.tar.gz bfs-1.5.2/tests/test_color_L_ln_target.out000066400000000000000000000012351360564154000206600ustar00rootroot00000000000000rainbow rainbow/broken rainbow/exec.sh rainbow/chardev_link rainbow/socket rainbow/sticky_ow rainbow/sgid rainbow/pipe rainbow/ow rainbow/sugid rainbow/suid rainbow/sticky rainbow/file.dat rainbow/file.txt rainbow/link.txt rainbow/mh1 rainbow/mh2 rainbow/star.gz rainbow/star.tar rainbow/star.tar.gz bfs-1.5.2/tests/test_color_L_no_stat.out000066400000000000000000000011771360564154000203550ustar00rootroot00000000000000rainbow rainbow/chardev_link rainbow/ow rainbow/sticky rainbow/sticky_ow rainbow/socket rainbow/broken rainbow/file.txt rainbow/link.txt rainbow/pipe rainbow/exec.sh rainbow/file.dat rainbow/mh1 rainbow/mh2 rainbow/sgid rainbow/star.gz rainbow/star.tar rainbow/star.tar.gz rainbow/sugid rainbow/suid bfs-1.5.2/tests/test_color_escapes.out000066400000000000000000000012511360564154000200470ustar00rootroot00000000000000:rainbow :rainbow/:exec.sh :rainbow/:socket :rainbow/:broken :rainbow/:chardev_link :rainbow/:link.txt :rainbow/:sticky_ow :rainbow/:sgid :rainbow/:pipe :rainbow/:ow :rainbow/:sugid :rainbow/:suid :rainbow/:sticky :rainbow/file.dat :rainbow/file.txt :rainbow/mh1 :rainbow/mh2 :rainbow/star.gz :rainbow/star.tar :rainbow/star.tar.gz bfs-1.5.2/tests/test_color_ext.out000066400000000000000000000012621360564154000172260ustar00rootroot00000000000000rainbow rainbow/exec.sh rainbow/socket rainbow/broken rainbow/chardev_link rainbow/link.txt rainbow/file.txt rainbow/sticky_ow rainbow/sgid rainbow/pipe rainbow/ow rainbow/sugid rainbow/suid rainbow/sticky rainbow/file.dat rainbow/mh1 rainbow/mh2 rainbow/star.gz rainbow/star.tar rainbow/star.tar.gz bfs-1.5.2/tests/test_color_ext0.out000066400000000000000000000012621360564154000173060ustar00rootroot00000000000000rainbow rainbow/file.txt rainbow/exec.sh rainbow/socket rainbow/broken rainbow/chardev_link rainbow/link.txt rainbow/sticky_ow rainbow/sgid rainbow/pipe rainbow/ow rainbow/sugid rainbow/suid rainbow/sticky rainbow/file.dat rainbow/mh1 rainbow/mh2 rainbow/star.gz rainbow/star.tar rainbow/star.tar.gz bfs-1.5.2/tests/test_color_ext_override.out000066400000000000000000000013151360564154000211240ustar00rootroot00000000000000rainbow rainbow/exec.sh rainbow/star.tar rainbow/star.gz rainbow/star.tar.gz rainbow/socket rainbow/broken rainbow/chardev_link rainbow/link.txt rainbow/sticky_ow rainbow/sgid rainbow/pipe rainbow/ow rainbow/sugid rainbow/suid rainbow/sticky rainbow/file.dat rainbow/file.txt rainbow/mh1 rainbow/mh2 bfs-1.5.2/tests/test_color_ext_underride.out000066400000000000000000000013151360564154000212660ustar00rootroot00000000000000rainbow rainbow/star.tar.gz rainbow/exec.sh rainbow/star.tar rainbow/star.gz rainbow/socket rainbow/broken rainbow/chardev_link rainbow/link.txt rainbow/sticky_ow rainbow/sgid rainbow/pipe rainbow/ow rainbow/sugid rainbow/suid rainbow/sticky rainbow/file.dat rainbow/file.txt rainbow/mh1 rainbow/mh2 bfs-1.5.2/tests/test_color_ln_target.out000066400000000000000000000012351360564154000204050ustar00rootroot00000000000000rainbow rainbow/broken rainbow/exec.sh rainbow/chardev_link rainbow/socket rainbow/sticky_ow rainbow/sgid rainbow/pipe rainbow/ow rainbow/sugid rainbow/suid rainbow/sticky rainbow/file.dat rainbow/file.txt rainbow/link.txt rainbow/mh1 rainbow/mh2 rainbow/star.gz rainbow/star.tar rainbow/star.tar.gz bfs-1.5.2/tests/test_color_ls.out000066400000000000000000000010321360564154000170370ustar00rootroot00000000000000scratch/foo/bar scratch/foo/bar /__bfs__/nowhere /__bfs__/nowhere foo/bar/baz/qux foo/bar/baz/qux foo/bar/nowhere foo/bar/nowhere foo/bar/nowhere/nothing foo/bar/nowhere/nothing foo/bar/baz foo/bar/baz bfs-1.5.2/tests/test_color_mh.out000066400000000000000000000012731360564154000170340ustar00rootroot00000000000000rainbow rainbow/exec.sh rainbow/socket rainbow/broken rainbow/chardev_link rainbow/link.txt rainbow/mh1 rainbow/mh2 rainbow/sticky_ow rainbow/sgid rainbow/pipe rainbow/ow rainbow/sugid rainbow/suid rainbow/sticky rainbow/file.dat rainbow/file.txt rainbow/star.gz rainbow/star.tar rainbow/star.tar.gz bfs-1.5.2/tests/test_color_mh0.out000066400000000000000000000012511360564154000171100ustar00rootroot00000000000000rainbow rainbow/exec.sh rainbow/socket rainbow/broken rainbow/chardev_link rainbow/link.txt rainbow/sticky_ow rainbow/sgid rainbow/pipe rainbow/ow rainbow/sugid rainbow/suid rainbow/sticky rainbow/file.dat rainbow/file.txt rainbow/mh1 rainbow/mh2 rainbow/star.gz rainbow/star.tar rainbow/star.tar.gz bfs-1.5.2/tests/test_color_mi.out000066400000000000000000000012511360564154000170310ustar00rootroot00000000000000rainbow rainbow/exec.sh rainbow/socket rainbow/broken rainbow/chardev_link rainbow/link.txt rainbow/sticky_ow rainbow/sgid rainbow/pipe rainbow/ow rainbow/sugid rainbow/suid rainbow/sticky rainbow/file.dat rainbow/file.txt rainbow/mh1 rainbow/mh2 rainbow/star.gz rainbow/star.tar rainbow/star.tar.gz bfs-1.5.2/tests/test_color_missing_colon.out000066400000000000000000000012621360564154000212710ustar00rootroot00000000000000rainbow rainbow/exec.sh rainbow/socket rainbow/broken rainbow/chardev_link rainbow/link.txt rainbow/file.txt rainbow/sticky_ow rainbow/sgid rainbow/pipe rainbow/ow rainbow/sugid rainbow/suid rainbow/sticky rainbow/file.dat rainbow/mh1 rainbow/mh2 rainbow/star.gz rainbow/star.tar rainbow/star.tar.gz bfs-1.5.2/tests/test_color_no_stat.out000066400000000000000000000012021360564154000200670ustar00rootroot00000000000000rainbow rainbow/ow rainbow/sticky rainbow/sticky_ow rainbow/socket rainbow/broken rainbow/chardev_link rainbow/link.txt rainbow/file.txt rainbow/pipe rainbow/exec.sh rainbow/file.dat rainbow/mh1 rainbow/mh2 rainbow/sgid rainbow/star.gz rainbow/star.tar rainbow/star.tar.gz rainbow/sugid rainbow/suid bfs-1.5.2/tests/test_color_nul.out000066400000000000000000000000241360564154000172170ustar00rootroot00000000000000rainbow bfs-1.5.2/tests/test_color_or.out000066400000000000000000000012461360564154000170500ustar00rootroot00000000000000rainbow rainbow/exec.sh rainbow/socket rainbow/chardev_link rainbow/link.txt rainbow/broken rainbow/sticky_ow rainbow/sgid rainbow/pipe rainbow/ow rainbow/sugid rainbow/suid rainbow/sticky rainbow/file.dat rainbow/file.txt rainbow/mh1 rainbow/mh2 rainbow/star.gz rainbow/star.tar rainbow/star.tar.gz bfs-1.5.2/tests/test_color_or0_mi.out000066400000000000000000000012511360564154000176110ustar00rootroot00000000000000rainbow rainbow/exec.sh rainbow/socket rainbow/broken rainbow/chardev_link rainbow/link.txt rainbow/sticky_ow rainbow/sgid rainbow/pipe rainbow/ow rainbow/sugid rainbow/suid rainbow/sticky rainbow/file.dat rainbow/file.txt rainbow/mh1 rainbow/mh2 rainbow/star.gz rainbow/star.tar rainbow/star.tar.gz bfs-1.5.2/tests/test_color_or0_mi0.out000066400000000000000000000012511360564154000176710ustar00rootroot00000000000000rainbow rainbow/exec.sh rainbow/socket rainbow/broken rainbow/chardev_link rainbow/link.txt rainbow/sticky_ow rainbow/sgid rainbow/pipe rainbow/ow rainbow/sugid rainbow/suid rainbow/sticky rainbow/file.dat rainbow/file.txt rainbow/mh1 rainbow/mh2 rainbow/star.gz rainbow/star.tar rainbow/star.tar.gz bfs-1.5.2/tests/test_color_or_mi.out000066400000000000000000000012511360564154000175310ustar00rootroot00000000000000rainbow rainbow/broken rainbow/exec.sh rainbow/socket rainbow/chardev_link rainbow/link.txt rainbow/sticky_ow rainbow/sgid rainbow/pipe rainbow/ow rainbow/sugid rainbow/suid rainbow/sticky rainbow/file.dat rainbow/file.txt rainbow/mh1 rainbow/mh2 rainbow/star.gz rainbow/star.tar rainbow/star.tar.gz bfs-1.5.2/tests/test_color_or_mi0.out000066400000000000000000000012511360564154000176110ustar00rootroot00000000000000rainbow rainbow/broken rainbow/exec.sh rainbow/socket rainbow/chardev_link rainbow/link.txt rainbow/sticky_ow rainbow/sgid rainbow/pipe rainbow/ow rainbow/sugid rainbow/suid rainbow/sticky rainbow/file.dat rainbow/file.txt rainbow/mh1 rainbow/mh2 rainbow/star.gz rainbow/star.tar rainbow/star.tar.gz bfs-1.5.2/tests/test_color_rs_lc_rc_ec.out000066400000000000000000000012111360564154000206550ustar00rootroot00000000000000LC01;34RCrainbowEC LC01;34RCrainbow/ECLC01;32RCexec.shEC LC01;34RCrainbow/ECLC01;35RCsocketEC LC01;34RCrainbow/ECLC01;36RCbrokenEC LC01;34RCrainbow/ECLC01;36RCchardev_linkEC LC01;34RCrainbow/ECLC01;36RClink.txtEC LC01;34RCrainbow/ECLC30;42RCsticky_owEC LC01;34RCrainbow/ECLC30;43RCsgidEC LC01;34RCrainbow/ECLC33RCpipeEC LC01;34RCrainbow/ECLC34;42RCowEC LC01;34RCrainbow/ECLC37;41RCsugidEC LC01;34RCrainbow/ECLC37;41RCsuidEC LC01;34RCrainbow/ECLC37;44RCstickyEC LC01;34RCrainbow/ECfile.dat LC01;34RCrainbow/ECfile.txt LC01;34RCrainbow/ECmh1 LC01;34RCrainbow/ECmh2 LC01;34RCrainbow/ECstar.gz LC01;34RCrainbow/ECstar.tar LC01;34RCrainbow/ECstar.tar.gz bfs-1.5.2/tests/test_color_st0_tw0_ow.out000066400000000000000000000012511360564154000204310ustar00rootroot00000000000000rainbow rainbow/exec.sh rainbow/sticky rainbow/socket rainbow/broken rainbow/chardev_link rainbow/link.txt rainbow/sgid rainbow/pipe rainbow/ow rainbow/sticky_ow rainbow/sugid rainbow/suid rainbow/file.dat rainbow/file.txt rainbow/mh1 rainbow/mh2 rainbow/star.gz rainbow/star.tar rainbow/star.tar.gz bfs-1.5.2/tests/test_color_st0_tw0_ow0.out000066400000000000000000000012511360564154000205110ustar00rootroot00000000000000rainbow rainbow/exec.sh rainbow/ow rainbow/sticky rainbow/sticky_ow rainbow/socket rainbow/broken rainbow/chardev_link rainbow/link.txt rainbow/sgid rainbow/pipe rainbow/sugid rainbow/suid rainbow/file.dat rainbow/file.txt rainbow/mh1 rainbow/mh2 rainbow/star.gz rainbow/star.tar rainbow/star.tar.gz bfs-1.5.2/tests/test_color_st0_tw_ow.out000066400000000000000000000012511360564154000203510ustar00rootroot00000000000000rainbow rainbow/exec.sh rainbow/sticky rainbow/socket rainbow/broken rainbow/chardev_link rainbow/link.txt rainbow/sgid rainbow/pipe rainbow/ow rainbow/sugid rainbow/suid rainbow/sticky_ow rainbow/file.dat rainbow/file.txt rainbow/mh1 rainbow/mh2 rainbow/star.gz rainbow/star.tar rainbow/star.tar.gz bfs-1.5.2/tests/test_color_st0_tw_ow0.out000066400000000000000000000012511360564154000204310ustar00rootroot00000000000000rainbow rainbow/exec.sh rainbow/ow rainbow/sticky rainbow/socket rainbow/broken rainbow/chardev_link rainbow/link.txt rainbow/sgid rainbow/pipe rainbow/sugid rainbow/suid rainbow/sticky_ow rainbow/file.dat rainbow/file.txt rainbow/mh1 rainbow/mh2 rainbow/star.gz rainbow/star.tar rainbow/star.tar.gz bfs-1.5.2/tests/test_color_st_tw0_ow.out000066400000000000000000000012511360564154000203510ustar00rootroot00000000000000rainbow rainbow/exec.sh rainbow/socket rainbow/broken rainbow/chardev_link rainbow/link.txt rainbow/sgid rainbow/pipe rainbow/ow rainbow/sticky_ow rainbow/sugid rainbow/suid rainbow/sticky rainbow/file.dat rainbow/file.txt rainbow/mh1 rainbow/mh2 rainbow/star.gz rainbow/star.tar rainbow/star.tar.gz bfs-1.5.2/tests/test_color_st_tw0_ow0.out000066400000000000000000000012511360564154000204310ustar00rootroot00000000000000rainbow rainbow/exec.sh rainbow/ow rainbow/socket rainbow/broken rainbow/chardev_link rainbow/link.txt rainbow/sgid rainbow/pipe rainbow/sugid rainbow/suid rainbow/sticky rainbow/sticky_ow rainbow/file.dat rainbow/file.txt rainbow/mh1 rainbow/mh2 rainbow/star.gz rainbow/star.tar rainbow/star.tar.gz bfs-1.5.2/tests/test_color_st_tw_ow0.out000066400000000000000000000012511360564154000203510ustar00rootroot00000000000000rainbow rainbow/exec.sh rainbow/ow rainbow/socket rainbow/broken rainbow/chardev_link rainbow/link.txt rainbow/sgid rainbow/pipe rainbow/sugid rainbow/suid rainbow/sticky rainbow/sticky_ow rainbow/file.dat rainbow/file.txt rainbow/mh1 rainbow/mh2 rainbow/star.gz rainbow/star.tar rainbow/star.tar.gz bfs-1.5.2/tests/test_color_star.out000066400000000000000000000012511360564154000173750ustar00rootroot00000000000000rainbow rainbow/exec.sh rainbow/socket rainbow/broken rainbow/chardev_link rainbow/link.txt rainbow/sticky_ow rainbow/sgid rainbow/pipe rainbow/ow rainbow/sugid rainbow/suid rainbow/sticky rainbow/file.dat rainbow/file.txt rainbow/mh1 rainbow/mh2 rainbow/star.gz rainbow/star.tar rainbow/star.tar.gz bfs-1.5.2/tests/test_color_su0_sg.out000066400000000000000000000012351360564154000176260ustar00rootroot00000000000000rainbow rainbow/exec.sh rainbow/socket rainbow/broken rainbow/chardev_link rainbow/link.txt rainbow/sticky_ow rainbow/sgid rainbow/sugid rainbow/pipe rainbow/ow rainbow/sticky rainbow/file.dat rainbow/file.txt rainbow/mh1 rainbow/mh2 rainbow/star.gz rainbow/star.tar rainbow/star.tar.gz rainbow/suid bfs-1.5.2/tests/test_color_su0_sg0.out000066400000000000000000000012051360564154000177030ustar00rootroot00000000000000rainbow rainbow/exec.sh rainbow/socket rainbow/broken rainbow/chardev_link rainbow/link.txt rainbow/sticky_ow rainbow/pipe rainbow/ow rainbow/sticky rainbow/file.dat rainbow/file.txt rainbow/mh1 rainbow/mh2 rainbow/sgid rainbow/star.gz rainbow/star.tar rainbow/star.tar.gz rainbow/sugid rainbow/suid bfs-1.5.2/tests/test_color_su_sg0.out000066400000000000000000000012351360564154000176260ustar00rootroot00000000000000rainbow rainbow/exec.sh rainbow/socket rainbow/broken rainbow/chardev_link rainbow/link.txt rainbow/sticky_ow rainbow/pipe rainbow/ow rainbow/sugid rainbow/suid rainbow/sticky rainbow/file.dat rainbow/file.txt rainbow/mh1 rainbow/mh2 rainbow/sgid rainbow/star.gz rainbow/star.tar rainbow/star.tar.gz bfs-1.5.2/tests/test_comma.out000066400000000000000000000003621360564154000163240ustar00rootroot00000000000000basic basic/a basic/b basic/c basic/e basic/g basic/i basic/j basic/k basic/l basic/c/d basic/e/f basic/e/f basic/g/h basic/j/foo basic/j/foo basic/k/foo basic/k/foo basic/l/foo basic/l/foo basic/k/foo/bar basic/l/foo/bar basic/l/foo/bar/baz bfs-1.5.2/tests/test_comma_reachability.out000066400000000000000000000000061360564154000210370ustar00rootroot00000000000000basic bfs-1.5.2/tests/test_comma_redundant_false.out000066400000000000000000000000061360564154000215350ustar00rootroot00000000000000basic bfs-1.5.2/tests/test_comma_redundant_true.out000066400000000000000000000000061360564154000214220ustar00rootroot00000000000000basic bfs-1.5.2/tests/test_d_path.out000066400000000000000000000003041360564154000164630ustar00rootroot00000000000000basic basic/a basic/b basic/c basic/e basic/g basic/i basic/j basic/k basic/l basic/c/d basic/e/f basic/g/h basic/j/foo basic/k/foo basic/l/foo basic/k/foo/bar basic/l/foo/bar basic/l/foo/bar/baz bfs-1.5.2/tests/test_data_flow_and_swap.out000066400000000000000000000001601360564154000210400ustar00rootroot00000000000000basic basic/c basic/e basic/g basic/i basic/j basic/k basic/l basic/g/h basic/k/foo basic/l/foo basic/l/foo/bar bfs-1.5.2/tests/test_data_flow_depth.out000066400000000000000000000001421360564154000203500ustar00rootroot00000000000000basic/c/d basic/e/f basic/g/h basic/j/foo basic/k/foo basic/l/foo basic/k/foo/bar basic/l/foo/bar bfs-1.5.2/tests/test_data_flow_or_swap.out000066400000000000000000000001601360564154000207160ustar00rootroot00000000000000basic basic/c basic/e basic/g basic/i basic/j basic/k basic/l basic/g/h basic/k/foo basic/l/foo basic/l/foo/bar bfs-1.5.2/tests/test_data_flow_type.out000066400000000000000000000000001360564154000202160ustar00rootroot00000000000000bfs-1.5.2/tests/test_daystart.out000066400000000000000000000003041360564154000170570ustar00rootroot00000000000000basic basic/a basic/b basic/c basic/e basic/g basic/i basic/j basic/k basic/l basic/c/d basic/e/f basic/g/h basic/j/foo basic/k/foo basic/l/foo basic/k/foo/bar basic/l/foo/bar basic/l/foo/bar/baz bfs-1.5.2/tests/test_daystart_twice.out000066400000000000000000000003041360564154000202520ustar00rootroot00000000000000basic basic/a basic/b basic/c basic/e basic/g basic/i basic/j basic/k basic/l basic/c/d basic/e/f basic/g/h basic/j/foo basic/k/foo basic/l/foo basic/k/foo/bar basic/l/foo/bar basic/l/foo/bar/baz bfs-1.5.2/tests/test_de_morgan_and.out000066400000000000000000000001301360564154000177760ustar00rootroot00000000000000basic basic/c basic/e basic/g basic/i basic/j basic/k basic/l basic/g/h basic/l/foo/bar bfs-1.5.2/tests/test_de_morgan_not.out000066400000000000000000000001101360564154000200320ustar00rootroot00000000000000basic/a basic/b basic/c/d basic/e/f basic/k/foo/bar basic/l/foo/bar/baz bfs-1.5.2/tests/test_de_morgan_or.out000066400000000000000000000002701360564154000176610ustar00rootroot00000000000000basic basic/a basic/b basic/c basic/e basic/g basic/i basic/j basic/k basic/l basic/c/d basic/e/f basic/g/h basic/k/foo basic/l/foo basic/k/foo/bar basic/l/foo/bar basic/l/foo/bar/baz bfs-1.5.2/tests/test_deep.out000066400000000000000000000104401360564154000161430ustar00rootroot00000000000000deep/0/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE (4358) deep/1/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE (4358) deep/2/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE (4358) deep/3/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE (4358) deep/4/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE (4358) deep/5/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE (4358) deep/6/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE (4358) deep/7/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE (4358) deep/8/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE (4358) deep/9/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE (4358) deep/A/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE (4358) deep/B/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE (4358) deep/C/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE (4358) deep/D/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE (4358) deep/E/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE (4358) deep/F/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE (4358) bfs-1.5.2/tests/test_deep_strict.out000066400000000000000000000104401360564154000175330ustar00rootroot00000000000000deep/0/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE (4358) deep/1/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE (4358) deep/2/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE (4358) deep/3/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE (4358) deep/4/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE (4358) deep/5/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE (4358) deep/6/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE (4358) deep/7/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE (4358) deep/8/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE (4358) deep/9/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE (4358) deep/A/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE (4358) deep/B/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE (4358) deep/C/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE (4358) deep/D/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE (4358) deep/E/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE (4358) deep/F/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE (4358) bfs-1.5.2/tests/test_delete.out000066400000000000000000000000101360564154000164600ustar00rootroot00000000000000scratch bfs-1.5.2/tests/test_depth.out000066400000000000000000000003041360564154000163300ustar00rootroot00000000000000basic basic/a basic/b basic/c basic/e basic/g basic/i basic/j basic/k basic/l basic/c/d basic/e/f basic/g/h basic/j/foo basic/k/foo basic/l/foo basic/k/foo/bar basic/l/foo/bar basic/l/foo/bar/baz bfs-1.5.2/tests/test_depth_depth_n.out000066400000000000000000000001021360564154000200250ustar00rootroot00000000000000basic/c/d basic/e/f basic/g/h basic/j/foo basic/k/foo basic/l/foo bfs-1.5.2/tests/test_depth_depth_n_minus.out000066400000000000000000000001161360564154000212450ustar00rootroot00000000000000basic basic/a basic/b basic/c basic/e basic/g basic/i basic/j basic/k basic/l bfs-1.5.2/tests/test_depth_depth_n_plus.out000066400000000000000000000000641360564154000210770ustar00rootroot00000000000000basic/k/foo/bar basic/l/foo/bar basic/l/foo/bar/baz bfs-1.5.2/tests/test_depth_error.out000066400000000000000000000000241360564154000175400ustar00rootroot00000000000000scratch scratch/foo bfs-1.5.2/tests/test_depth_maxdepth_1.out000066400000000000000000000001161360564154000204430ustar00rootroot00000000000000basic basic/a basic/b basic/c basic/e basic/g basic/i basic/j basic/k basic/l bfs-1.5.2/tests/test_depth_maxdepth_2.out000066400000000000000000000002201360564154000204400ustar00rootroot00000000000000basic basic/a basic/b basic/c basic/e basic/g basic/i basic/j basic/k basic/l basic/c/d basic/e/f basic/g/h basic/j/foo basic/k/foo basic/l/foo bfs-1.5.2/tests/test_depth_mindepth_1.out000066400000000000000000000002761360564154000204500ustar00rootroot00000000000000basic/a basic/b basic/c basic/e basic/g basic/i basic/j basic/k basic/l basic/c/d basic/e/f basic/g/h basic/j/foo basic/k/foo basic/l/foo basic/k/foo/bar basic/l/foo/bar basic/l/foo/bar/baz bfs-1.5.2/tests/test_depth_mindepth_2.out000066400000000000000000000001661360564154000204470ustar00rootroot00000000000000basic/c/d basic/e/f basic/g/h basic/j/foo basic/k/foo basic/l/foo basic/k/foo/bar basic/l/foo/bar basic/l/foo/bar/baz bfs-1.5.2/tests/test_depth_n.out000066400000000000000000000001021360564154000166410ustar00rootroot00000000000000basic/c/d basic/e/f basic/g/h basic/j/foo basic/k/foo basic/l/foo bfs-1.5.2/tests/test_depth_n_minus.out000066400000000000000000000001161360564154000200610ustar00rootroot00000000000000basic basic/a basic/b basic/c basic/e basic/g basic/i basic/j basic/k basic/l bfs-1.5.2/tests/test_depth_n_plus.out000066400000000000000000000000641360564154000177130ustar00rootroot00000000000000basic/k/foo/bar basic/l/foo/bar basic/l/foo/bar/baz bfs-1.5.2/tests/test_depth_overflow.out000066400000000000000000000003041360564154000202530ustar00rootroot00000000000000basic basic/a basic/b basic/c basic/e basic/g basic/i basic/j basic/k basic/l basic/c/d basic/e/f basic/g/h basic/j/foo basic/k/foo basic/l/foo basic/k/foo/bar basic/l/foo/bar basic/l/foo/bar/baz bfs-1.5.2/tests/test_depth_slash.out000066400000000000000000000003051360564154000175230ustar00rootroot00000000000000basic/ basic/a basic/b basic/c basic/e basic/g basic/i basic/j basic/k basic/l basic/c/d basic/e/f basic/g/h basic/j/foo basic/k/foo basic/l/foo basic/k/foo/bar basic/l/foo/bar basic/l/foo/bar/baz bfs-1.5.2/tests/test_double_dash.out000066400000000000000000000000701360564154000174750ustar00rootroot00000000000000./a ./b ./c/d ./e/f ./j/foo ./k/foo/bar ./l/foo/bar/baz bfs-1.5.2/tests/test_double_negation.out000066400000000000000000000000441360564154000203630ustar00rootroot00000000000000basic/j/foo basic/k/foo basic/l/foo bfs-1.5.2/tests/test_empty.out000066400000000000000000000001221360564154000163600ustar00rootroot00000000000000basic/a basic/b basic/i basic/c/d basic/e/f basic/g/h basic/j/foo basic/k/foo/bar bfs-1.5.2/tests/test_exec.out000066400000000000000000000003041360564154000161500ustar00rootroot00000000000000basic basic/a basic/b basic/c basic/e basic/g basic/i basic/j basic/k basic/l basic/c/d basic/e/f basic/g/h basic/j/foo basic/k/foo basic/l/foo basic/k/foo/bar basic/l/foo/bar basic/l/foo/bar/baz bfs-1.5.2/tests/test_exec_plus.out000066400000000000000000000003041360564154000172130ustar00rootroot00000000000000basic basic/a basic/b basic/c basic/c/d basic/e basic/e/f basic/g basic/g/h basic/i basic/j basic/j/foo basic/k basic/k/foo basic/k/foo/bar basic/l basic/l/foo basic/l/foo/bar basic/l/foo/bar/baz bfs-1.5.2/tests/test_exec_plus_semicolon.out000066400000000000000000000007161360564154000212720ustar00rootroot00000000000000foo basic bar + baz foo basic/a bar + baz foo basic/b bar + baz foo basic/c bar + baz foo basic/e bar + baz foo basic/g bar + baz foo basic/i bar + baz foo basic/j bar + baz foo basic/k bar + baz foo basic/l bar + baz foo basic/c/d bar + baz foo basic/e/f bar + baz foo basic/g/h bar + baz foo basic/j/foo bar + baz foo basic/k/foo bar + baz foo basic/l/foo bar + baz foo basic/k/foo/bar bar + baz foo basic/l/foo/bar bar + baz foo basic/l/foo/bar/baz bar + baz bfs-1.5.2/tests/test_exec_plus_status.out000066400000000000000000000003041360564154000206160ustar00rootroot00000000000000basic basic/a basic/b basic/c basic/e basic/g basic/i basic/j basic/k basic/l basic/c/d basic/e/f basic/g/h basic/j/foo basic/k/foo basic/l/foo basic/k/foo/bar basic/l/foo/bar basic/l/foo/bar/baz bfs-1.5.2/tests/test_exec_substring.out000066400000000000000000000003521360564154000202530ustar00rootroot00000000000000-basic- -basic/a- -basic/b- -basic/c- -basic/e- -basic/g- -basic/i- -basic/j- -basic/k- -basic/l- -basic/c/d- -basic/e/f- -basic/g/h- -basic/j/foo- -basic/k/foo- -basic/l/foo- -basic/k/foo/bar- -basic/l/foo/bar- -basic/l/foo/bar/baz- bfs-1.5.2/tests/test_execdir.out000066400000000000000000000001341360564154000166500ustar00rootroot00000000000000./a ./b ./bar ./bar ./basic ./baz ./c ./d ./e ./f ./foo ./foo ./foo ./g ./h ./i ./j ./k ./l bfs-1.5.2/tests/test_execdir_plus.out000066400000000000000000000001341360564154000177130ustar00rootroot00000000000000./bar ./bar ./basic ./baz ./d ./f ./foo ./foo ./foo ./h ./a ./b ./c ./e ./g ./i ./j ./k ./l bfs-1.5.2/tests/test_execdir_plus_semicolon.out000066400000000000000000000005461360564154000217720ustar00rootroot00000000000000foo ./a bar + baz foo ./b bar + baz foo ./bar bar + baz foo ./bar bar + baz foo ./basic bar + baz foo ./baz bar + baz foo ./c bar + baz foo ./d bar + baz foo ./e bar + baz foo ./f bar + baz foo ./foo bar + baz foo ./foo bar + baz foo ./foo bar + baz foo ./g bar + baz foo ./h bar + baz foo ./i bar + baz foo ./j bar + baz foo ./k bar + baz foo ./l bar + baz bfs-1.5.2/tests/test_execdir_pwd.out000066400000000000000000000002171360564154000175240ustar00rootroot00000000000000 basic basic basic basic basic basic basic basic basic basic/c basic/e basic/g basic/j basic/k basic/l basic/k/foo basic/l/foo basic/l/foo/bar bfs-1.5.2/tests/test_execdir_slash.out000066400000000000000000000000021360564154000200340ustar00rootroot00000000000000/ bfs-1.5.2/tests/test_execdir_slash_pwd.out000066400000000000000000000000021360564154000207060ustar00rootroot00000000000000/ bfs-1.5.2/tests/test_execdir_slashes.out000066400000000000000000000000021360564154000203640ustar00rootroot00000000000000/ bfs-1.5.2/tests/test_execdir_substring.out000066400000000000000000000002021360564154000207440ustar00rootroot00000000000000-./a- -./b- -./bar- -./bar- -./basic- -./baz- -./c- -./d- -./e- -./f- -./foo- -./foo- -./foo- -./g- -./h- -./i- -./j- -./k- -./l- bfs-1.5.2/tests/test_execdir_ulimit.out000066400000000000000000000002461360564154000202370ustar00rootroot00000000000000./0 ./1 ./2 ./3 ./4 ./5 ./6 ./7 ./8 ./9 ./A ./B ./C ./a ./b ./c ./d ./e ./f ./g ./h ./i ./j ./k ./l ./m ./n ./o ./p ./q ./r ./s ./scratch ./t ./u ./v ./w ./x ./y ./z bfs-1.5.2/tests/test_executable.out000066400000000000000000000000421360564154000173440ustar00rootroot00000000000000perms perms/rwx perms/rx perms/wx bfs-1.5.2/tests/test_exit.out000066400000000000000000000000101360564154000161670ustar00rootroot00000000000000basic/g bfs-1.5.2/tests/test_expr_flag_path.out000066400000000000000000000000201360564154000202020ustar00rootroot00000000000000links/skip/link bfs-1.5.2/tests/test_expr_path_flag.out000066400000000000000000000000201360564154000202020ustar00rootroot00000000000000links/skip/link bfs-1.5.2/tests/test_f.out000066400000000000000000000000141360564154000154470ustar00rootroot00000000000000( - (/b -/a bfs-1.5.2/tests/test_false.out000066400000000000000000000000001360564154000163070ustar00rootroot00000000000000bfs-1.5.2/tests/test_flag_comma.out000066400000000000000000000000061360564154000173100ustar00rootroot00000000000000, ,/f bfs-1.5.2/tests/test_flag_double_dash.out000066400000000000000000000000701360564154000204660ustar00rootroot00000000000000./a ./b ./c/d ./e/f ./j/foo ./k/foo/bar ./l/foo/bar/baz bfs-1.5.2/tests/test_flag_expr_path.out000066400000000000000000000000201360564154000202020ustar00rootroot00000000000000links/skip/link bfs-1.5.2/tests/test_flag_weird_names.out000066400000000000000000000001541360564154000205150ustar00rootroot00000000000000!- !- (- (- ) ) , , - - !-/e !-/e (-/c (-/c )/g )/g ,/f ,/f -/a -/a ./! ./! ./( ./( ./!/d ./!/d ./(/b ./(/b bfs-1.5.2/tests/test_follow.out000066400000000000000000000003551360564154000165340ustar00rootroot00000000000000links links/broken links/deeply links/file links/hardlink links/notdir links/skip links/symlink links/deeply/nested links/skip/dir links/skip/file links/skip/link links/deeply/nested/dir links/deeply/nested/file links/deeply/nested/link bfs-1.5.2/tests/test_follow_comma.out000066400000000000000000000001561360564154000177070ustar00rootroot00000000000000. ./ ./! ./!- ./( ./(- ./) ./, ./- ./... ./\ ./ /j ./!-/e ./!/d ./(-/c ./(/b ./)/g ./,/f ./-/a ./.../h ./\/i bfs-1.5.2/tests/test_fprint.out000066400000000000000000000003041360564154000165260ustar00rootroot00000000000000basic basic/a basic/b basic/c basic/c/d basic/e basic/e/f basic/g basic/g/h basic/i basic/j basic/j/foo basic/k basic/k/foo basic/k/foo/bar basic/l basic/l/foo basic/l/foo/bar basic/l/foo/bar/baz bfs-1.5.2/tests/test_fprint_duplicate.out000066400000000000000000000011141360564154000205600ustar00rootroot00000000000000basic basic basic basic/a basic/a basic/a basic/b basic/b basic/b basic/c basic/c basic/c basic/c/d basic/c/d basic/c/d basic/e basic/e basic/e basic/e/f basic/e/f basic/e/f basic/g basic/g basic/g basic/g/h basic/g/h basic/g/h basic/i basic/i basic/i basic/j basic/j basic/j basic/j/foo basic/j/foo basic/j/foo basic/k basic/k basic/k basic/k/foo basic/k/foo basic/k/foo basic/k/foo/bar basic/k/foo/bar basic/k/foo/bar basic/l basic/l basic/l basic/l/foo basic/l/foo basic/l/foo basic/l/foo/bar basic/l/foo/bar basic/l/foo/bar basic/l/foo/bar/baz basic/l/foo/bar/baz basic/l/foo/bar/baz bfs-1.5.2/tests/test_fstype.out000066400000000000000000000003041360564154000165360ustar00rootroot00000000000000basic basic/a basic/b basic/c basic/e basic/g basic/i basic/j basic/k basic/l basic/c/d basic/e/f basic/g/h basic/j/foo basic/k/foo basic/l/foo basic/k/foo/bar basic/l/foo/bar basic/l/foo/bar/baz bfs-1.5.2/tests/test_gid.out000066400000000000000000000003041360564154000157670ustar00rootroot00000000000000basic basic/a basic/b basic/c basic/e basic/g basic/i basic/j basic/k basic/l basic/c/d basic/e/f basic/g/h basic/j/foo basic/k/foo basic/l/foo basic/k/foo/bar basic/l/foo/bar basic/l/foo/bar/baz bfs-1.5.2/tests/test_gid_minus.out000066400000000000000000000003041360564154000172020ustar00rootroot00000000000000basic basic/a basic/b basic/c basic/e basic/g basic/i basic/j basic/k basic/l basic/c/d basic/e/f basic/g/h basic/j/foo basic/k/foo basic/l/foo basic/k/foo/bar basic/l/foo/bar basic/l/foo/bar/baz bfs-1.5.2/tests/test_gid_minus_plus.out000066400000000000000000000003041360564154000202450ustar00rootroot00000000000000basic basic/a basic/b basic/c basic/e basic/g basic/i basic/j basic/k basic/l basic/c/d basic/e/f basic/g/h basic/j/foo basic/k/foo basic/l/foo basic/k/foo/bar basic/l/foo/bar basic/l/foo/bar/baz bfs-1.5.2/tests/test_gid_name.out000066400000000000000000000003041360564154000167670ustar00rootroot00000000000000basic basic/a basic/b basic/c basic/e basic/g basic/i basic/j basic/k basic/l basic/c/d basic/e/f basic/g/h basic/j/foo basic/k/foo basic/l/foo basic/k/foo/bar basic/l/foo/bar basic/l/foo/bar/baz bfs-1.5.2/tests/test_gid_plus.out000066400000000000000000000003041360564154000170320ustar00rootroot00000000000000basic basic/a basic/b basic/c basic/e basic/g basic/i basic/j basic/k basic/l basic/c/d basic/e/f basic/g/h basic/j/foo basic/k/foo basic/l/foo basic/k/foo/bar basic/l/foo/bar basic/l/foo/bar/baz bfs-1.5.2/tests/test_gid_plus_plus.out000066400000000000000000000003041360564154000200750ustar00rootroot00000000000000basic basic/a basic/b basic/c basic/e basic/g basic/i basic/j basic/k basic/l basic/c/d basic/e/f basic/g/h basic/j/foo basic/k/foo basic/l/foo basic/k/foo/bar basic/l/foo/bar basic/l/foo/bar/baz bfs-1.5.2/tests/test_group_id.out000066400000000000000000000003041360564154000170340ustar00rootroot00000000000000basic basic/a basic/b basic/c basic/e basic/g basic/i basic/j basic/k basic/l basic/c/d basic/e/f basic/g/h basic/j/foo basic/k/foo basic/l/foo basic/k/foo/bar basic/l/foo/bar basic/l/foo/bar/baz bfs-1.5.2/tests/test_group_name.out000066400000000000000000000003041360564154000173600ustar00rootroot00000000000000basic basic/a basic/b basic/c basic/e basic/g basic/i basic/j basic/k basic/l basic/c/d basic/e/f basic/g/h basic/j/foo basic/k/foo basic/l/foo basic/k/foo/bar basic/l/foo/bar basic/l/foo/bar/baz bfs-1.5.2/tests/test_hidden.out000066400000000000000000000000171360564154000164600ustar00rootroot00000000000000weirdnames/... bfs-1.5.2/tests/test_ilname.out000066400000000000000000000000001360564154000164620ustar00rootroot00000000000000bfs-1.5.2/tests/test_implicit_and.out000066400000000000000000000000301360564154000176540ustar00rootroot00000000000000basic/k/foo basic/l/foo bfs-1.5.2/tests/test_iname.out000066400000000000000000000000561360564154000163210ustar00rootroot00000000000000basic/e/f basic/j/foo basic/k/foo basic/l/foo bfs-1.5.2/tests/test_inum.out000066400000000000000000000000201360564154000161670ustar00rootroot00000000000000basic/k/foo/bar bfs-1.5.2/tests/test_inum_bind_mount.out000066400000000000000000000000301360564154000204060ustar00rootroot00000000000000scratch/bar scratch/foo bfs-1.5.2/tests/test_inum_mount.out000066400000000000000000000000141360564154000174140ustar00rootroot00000000000000scratch/mnt bfs-1.5.2/tests/test_ipath.out000066400000000000000000000001421360564154000163310ustar00rootroot00000000000000basic/e/f basic/j/foo basic/k/foo basic/l/foo basic/k/foo/bar basic/l/foo/bar basic/l/foo/bar/baz bfs-1.5.2/tests/test_iregex.out000066400000000000000000000000361360564154000165110ustar00rootroot00000000000000basic/c/d basic/e/f basic/g/h bfs-1.5.2/tests/test_links.out000066400000000000000000000000321360564154000163420ustar00rootroot00000000000000links/file links/hardlink bfs-1.5.2/tests/test_links_minus.out000066400000000000000000000000311360564154000175540ustar00rootroot00000000000000links/deeply/nested/file bfs-1.5.2/tests/test_links_plus.out000066400000000000000000000000321360564154000174050ustar00rootroot00000000000000links/file links/hardlink bfs-1.5.2/tests/test_lname.out000066400000000000000000000000001360564154000163110ustar00rootroot00000000000000bfs-1.5.2/tests/test_maxdepth.out000066400000000000000000000001161360564154000170370ustar00rootroot00000000000000basic basic/a basic/b basic/c basic/e basic/g basic/i basic/j basic/k basic/l bfs-1.5.2/tests/test_mindepth.out000066400000000000000000000002761360564154000170440ustar00rootroot00000000000000basic/a basic/b basic/c basic/e basic/g basic/i basic/j basic/k basic/l basic/c/d basic/e/f basic/g/h basic/j/foo basic/k/foo basic/l/foo basic/k/foo/bar basic/l/foo/bar basic/l/foo/bar/baz bfs-1.5.2/tests/test_mnewer.out000066400000000000000000000000361360564154000165230ustar00rootroot00000000000000times times/b times/c times/l bfs-1.5.2/tests/test_mount.out000066400000000000000000000000601360564154000163650ustar00rootroot00000000000000scratch scratch/foo scratch/mnt scratch/foo/bar bfs-1.5.2/tests/test_name.out000066400000000000000000000000561360564154000161500ustar00rootroot00000000000000basic/e/f basic/j/foo basic/k/foo basic/l/foo bfs-1.5.2/tests/test_name_root.out000066400000000000000000000000101360564154000172010ustar00rootroot00000000000000basic/a bfs-1.5.2/tests/test_name_root_depth.out000066400000000000000000000000101360564154000203650ustar00rootroot00000000000000basic/g bfs-1.5.2/tests/test_name_slash.out000066400000000000000000000000021360564154000173310ustar00rootroot00000000000000/ bfs-1.5.2/tests/test_name_slashes.out000066400000000000000000000000041360564154000176630ustar00rootroot00000000000000/// bfs-1.5.2/tests/test_name_trailing_slash.out000066400000000000000000000000111360564154000212220ustar00rootroot00000000000000basic/g/ bfs-1.5.2/tests/test_newer.out000066400000000000000000000000361360564154000163460ustar00rootroot00000000000000times times/b times/c times/l bfs-1.5.2/tests/test_newer_link.out000066400000000000000000000000061360564154000173600ustar00rootroot00000000000000times bfs-1.5.2/tests/test_newerma.out000066400000000000000000000000361360564154000166640ustar00rootroot00000000000000times times/b times/c times/l bfs-1.5.2/tests/test_nogroup.out000066400000000000000000000000001360564154000167060ustar00rootroot00000000000000bfs-1.5.2/tests/test_nogroup_ulimit.out000066400000000000000000000000001360564154000202710ustar00rootroot00000000000000bfs-1.5.2/tests/test_nohidden.out000066400000000000000000000004131360564154000170150ustar00rootroot00000000000000weirdnames weirdnames/ weirdnames/! weirdnames/!- weirdnames/( weirdnames/(- weirdnames/) weirdnames/, weirdnames/- weirdnames/\ weirdnames/ /j weirdnames/!-/e weirdnames/!/d weirdnames/(-/c weirdnames/(/b weirdnames/)/g weirdnames/,/f weirdnames/-/a weirdnames/\/i bfs-1.5.2/tests/test_not.out000066400000000000000000000002401360564154000160230ustar00rootroot00000000000000basic basic/a basic/b basic/c basic/e basic/g basic/i basic/j basic/k basic/l basic/c/d basic/e/f basic/g/h basic/k/foo/bar basic/l/foo/bar basic/l/foo/bar/baz bfs-1.5.2/tests/test_not_prune.out000066400000000000000000000001541360564154000172400ustar00rootroot00000000000000basic basic/a basic/b basic/c basic/e basic/g basic/i basic/j basic/k basic/l basic/c/d basic/e/f basic/g/h bfs-1.5.2/tests/test_not_reachability.out000066400000000000000000000000061360564154000205430ustar00rootroot00000000000000basic bfs-1.5.2/tests/test_nouser.out000066400000000000000000000000001360564154000165300ustar00rootroot00000000000000bfs-1.5.2/tests/test_nouser_ulimit.out000066400000000000000000000000001360564154000201130ustar00rootroot00000000000000bfs-1.5.2/tests/test_o.out000066400000000000000000000001741360564154000154670ustar00rootroot00000000000000basic basic/c basic/e basic/g basic/i basic/j basic/k basic/l basic/g/h basic/j/foo basic/k/foo basic/l/foo basic/l/foo/bar bfs-1.5.2/tests/test_ok_closed_stdin.out000066400000000000000000000000001360564154000203600ustar00rootroot00000000000000bfs-1.5.2/tests/test_ok_plus_semicolon.out000066400000000000000000000003521360564154000207530ustar00rootroot00000000000000basic + basic/a + basic/b + basic/c + basic/e + basic/g + basic/i + basic/j + basic/k + basic/l + basic/c/d + basic/e/f + basic/g/h + basic/j/foo + basic/k/foo + basic/l/foo + basic/k/foo/bar + basic/l/foo/bar + basic/l/foo/bar/baz + bfs-1.5.2/tests/test_ok_stdin.out000066400000000000000000000003751360564154000170460ustar00rootroot00000000000000basic? y basic/a? y basic/b? y basic/c? y basic/e? y basic/g? y basic/i? y basic/j? y basic/k? y basic/l? y basic/c/d? y basic/e/f? y basic/g/h? y basic/j/foo? y basic/k/foo? y basic/l/foo? y basic/k/foo/bar? y basic/l/foo/bar? y basic/l/foo/bar/baz? y bfs-1.5.2/tests/test_okdir_closed_stdin.out000066400000000000000000000000001360564154000210570ustar00rootroot00000000000000bfs-1.5.2/tests/test_okdir_plus_semicolon.out000066400000000000000000000002021360564154000214440ustar00rootroot00000000000000./a + ./b + ./bar + ./bar + ./basic + ./baz + ./c + ./d + ./e + ./f + ./foo + ./foo + ./foo + ./g + ./h + ./i + ./j + ./k + ./l + bfs-1.5.2/tests/test_okdir_stdin.out000066400000000000000000000002251360564154000175370ustar00rootroot00000000000000./a? y ./b? y ./bar? y ./bar? y ./basic? y ./baz? y ./c? y ./d? y ./e? y ./f? y ./foo? y ./foo? y ./foo? y ./g? y ./h? y ./i? y ./j? y ./k? y ./l? y bfs-1.5.2/tests/test_or.out000066400000000000000000000001741360564154000156510ustar00rootroot00000000000000basic basic/c basic/e basic/g basic/i basic/j basic/k basic/l basic/g/h basic/j/foo basic/k/foo basic/l/foo basic/l/foo/bar bfs-1.5.2/tests/test_or_purity.out000066400000000000000000000000001360564154000172510ustar00rootroot00000000000000bfs-1.5.2/tests/test_parens.out000066400000000000000000000000561360564154000165200ustar00rootroot00000000000000basic/e/f basic/j/foo basic/k/foo basic/l/foo bfs-1.5.2/tests/test_path.out000066400000000000000000000001421360564154000161600ustar00rootroot00000000000000basic/e/f basic/j/foo basic/k/foo basic/l/foo basic/k/foo/bar basic/l/foo/bar basic/l/foo/bar/baz bfs-1.5.2/tests/test_path_d.out000066400000000000000000000003041360564154000164630ustar00rootroot00000000000000basic basic/a basic/b basic/c basic/e basic/g basic/i basic/j basic/k basic/l basic/c/d basic/e/f basic/g/h basic/j/foo basic/k/foo basic/l/foo basic/k/foo/bar basic/l/foo/bar basic/l/foo/bar/baz bfs-1.5.2/tests/test_path_expr_flag.out000066400000000000000000000000201360564154000202020ustar00rootroot00000000000000links/skip/link bfs-1.5.2/tests/test_path_flag_expr.out000066400000000000000000000000201360564154000202020ustar00rootroot00000000000000links/skip/link bfs-1.5.2/tests/test_perm_000.out000066400000000000000000000000101360564154000165400ustar00rootroot00000000000000perms/0 bfs-1.5.2/tests/test_perm_000_minus.out000066400000000000000000000001031360564154000177560ustar00rootroot00000000000000perms perms/0 perms/r perms/rw perms/rwx perms/rx perms/w perms/wx bfs-1.5.2/tests/test_perm_000_plus.out000066400000000000000000000001031360564154000176060ustar00rootroot00000000000000perms perms/0 perms/r perms/rw perms/rwx perms/rx perms/w perms/wx bfs-1.5.2/tests/test_perm_000_slash.out000066400000000000000000000001031360564154000177350ustar00rootroot00000000000000perms perms/0 perms/r perms/rw perms/rwx perms/rx perms/w perms/wx bfs-1.5.2/tests/test_perm_222.out000066400000000000000000000000101360564154000165460ustar00rootroot00000000000000perms/w bfs-1.5.2/tests/test_perm_222_minus.out000066400000000000000000000000101360564154000177610ustar00rootroot00000000000000perms/w bfs-1.5.2/tests/test_perm_222_plus.out000066400000000000000000000000521360564154000176170ustar00rootroot00000000000000perms perms/rw perms/rwx perms/w perms/wx bfs-1.5.2/tests/test_perm_222_slash.out000066400000000000000000000000521360564154000177460ustar00rootroot00000000000000perms perms/rw perms/rwx perms/w perms/wx bfs-1.5.2/tests/test_perm_644.out000066400000000000000000000000111360564154000165570ustar00rootroot00000000000000perms/rw bfs-1.5.2/tests/test_perm_644_minus.out000066400000000000000000000000311360564154000177740ustar00rootroot00000000000000perms perms/rw perms/rwx bfs-1.5.2/tests/test_perm_644_plus.out000066400000000000000000000000731360564154000176320ustar00rootroot00000000000000perms perms/r perms/rw perms/rwx perms/rx perms/w perms/wx bfs-1.5.2/tests/test_perm_644_slash.out000066400000000000000000000000731360564154000177610ustar00rootroot00000000000000perms perms/r perms/rw perms/rwx perms/rx perms/w perms/wx bfs-1.5.2/tests/test_perm_leading_plus_symbolic.out000066400000000000000000000000001360564154000226070ustar00rootroot00000000000000bfs-1.5.2/tests/test_perm_leading_plus_symbolic_minus.out000066400000000000000000000000001360564154000240220ustar00rootroot00000000000000bfs-1.5.2/tests/test_perm_leading_plus_symbolic_slash.out000066400000000000000000000000731360564154000240130ustar00rootroot00000000000000perms perms/r perms/rw perms/rwx perms/rx perms/w perms/wx bfs-1.5.2/tests/test_perm_symbolic.out000066400000000000000000000000001360564154000200610ustar00rootroot00000000000000bfs-1.5.2/tests/test_perm_symbolic_minus.out000066400000000000000000000000311360564154000213000ustar00rootroot00000000000000perms perms/rw perms/rwx bfs-1.5.2/tests/test_perm_symbolic_slash.out000066400000000000000000000000731360564154000212650ustar00rootroot00000000000000perms perms/r perms/rw perms/rwx perms/rx perms/w perms/wx bfs-1.5.2/tests/test_permcopy.out000066400000000000000000000000111360564154000170550ustar00rootroot00000000000000perms/rw bfs-1.5.2/tests/test_precedence.out000066400000000000000000000000741360564154000173250ustar00rootroot00000000000000basic/k/foo basic/l/foo basic/k/foo/bar basic/l/foo/bar/baz bfs-1.5.2/tests/test_printf.out000066400000000000000000000031441360564154000165330ustar00rootroot00000000000000%p(basic) %d(0) %f(basic) %h(.) %H(basic) %P() %m(755) %M(drwxr-xr-x) %y(d) %p(basic/a) %d(1) %f(a) %h(basic) %H(basic) %P(a) %m(644) %M(-rw-r--r--) %y(f) %p(basic/b) %d(1) %f(b) %h(basic) %H(basic) %P(b) %m(644) %M(-rw-r--r--) %y(f) %p(basic/c) %d(1) %f(c) %h(basic) %H(basic) %P(c) %m(755) %M(drwxr-xr-x) %y(d) %p(basic/e) %d(1) %f(e) %h(basic) %H(basic) %P(e) %m(755) %M(drwxr-xr-x) %y(d) %p(basic/g) %d(1) %f(g) %h(basic) %H(basic) %P(g) %m(755) %M(drwxr-xr-x) %y(d) %p(basic/i) %d(1) %f(i) %h(basic) %H(basic) %P(i) %m(755) %M(drwxr-xr-x) %y(d) %p(basic/j) %d(1) %f(j) %h(basic) %H(basic) %P(j) %m(755) %M(drwxr-xr-x) %y(d) %p(basic/k) %d(1) %f(k) %h(basic) %H(basic) %P(k) %m(755) %M(drwxr-xr-x) %y(d) %p(basic/l) %d(1) %f(l) %h(basic) %H(basic) %P(l) %m(755) %M(drwxr-xr-x) %y(d) %p(basic/c/d) %d(2) %f(d) %h(basic/c) %H(basic) %P(c/d) %m(644) %M(-rw-r--r--) %y(f) %p(basic/e/f) %d(2) %f(f) %h(basic/e) %H(basic) %P(e/f) %m(644) %M(-rw-r--r--) %y(f) %p(basic/g/h) %d(2) %f(h) %h(basic/g) %H(basic) %P(g/h) %m(755) %M(drwxr-xr-x) %y(d) %p(basic/j/foo) %d(2) %f(foo) %h(basic/j) %H(basic) %P(j/foo) %m(644) %M(-rw-r--r--) %y(f) %p(basic/k/foo) %d(2) %f(foo) %h(basic/k) %H(basic) %P(k/foo) %m(755) %M(drwxr-xr-x) %y(d) %p(basic/l/foo) %d(2) %f(foo) %h(basic/l) %H(basic) %P(l/foo) %m(755) %M(drwxr-xr-x) %y(d) %p(basic/k/foo/bar) %d(3) %f(bar) %h(basic/k/foo) %H(basic) %P(k/foo/bar) %m(644) %M(-rw-r--r--) %y(f) %p(basic/l/foo/bar) %d(3) %f(bar) %h(basic/l/foo) %H(basic) %P(l/foo/bar) %m(755) %M(drwxr-xr-x) %y(d) %p(basic/l/foo/bar/baz) %d(4) %f(baz) %h(basic/l/foo/bar) %H(basic) %P(l/foo/bar/baz) %m(644) %M(-rw-r--r--) %y(f) bfs-1.5.2/tests/test_printf_H.out000066400000000000000000000041541360564154000170040ustar00rootroot00000000000000%p(basic) %d(0) %f(basic) %h(.) %H(basic) %P() %y(d) %p(links) %d(0) %f(links) %h(.) %H(links) %P() %y(d) %p(basic/a) %d(1) %f(a) %h(basic) %H(basic) %P(a) %y(f) %p(basic/b) %d(1) %f(b) %h(basic) %H(basic) %P(b) %y(f) %p(basic/c) %d(1) %f(c) %h(basic) %H(basic) %P(c) %y(d) %p(basic/e) %d(1) %f(e) %h(basic) %H(basic) %P(e) %y(d) %p(basic/g) %d(1) %f(g) %h(basic) %H(basic) %P(g) %y(d) %p(basic/i) %d(1) %f(i) %h(basic) %H(basic) %P(i) %y(d) %p(basic/j) %d(1) %f(j) %h(basic) %H(basic) %P(j) %y(d) %p(basic/k) %d(1) %f(k) %h(basic) %H(basic) %P(k) %y(d) %p(basic/l) %d(1) %f(l) %h(basic) %H(basic) %P(l) %y(d) %p(links/broken) %d(1) %f(broken) %h(links) %H(links) %P(broken) %y(l) %p(links/deeply) %d(1) %f(deeply) %h(links) %H(links) %P(deeply) %y(d) %p(links/file) %d(1) %f(file) %h(links) %H(links) %P(file) %y(f) %p(links/hardlink) %d(1) %f(hardlink) %h(links) %H(links) %P(hardlink) %y(f) %p(links/notdir) %d(1) %f(notdir) %h(links) %H(links) %P(notdir) %y(l) %p(links/skip) %d(1) %f(skip) %h(links) %H(links) %P(skip) %y(l) %p(links/symlink) %d(1) %f(symlink) %h(links) %H(links) %P(symlink) %y(l) %p(basic/c/d) %d(2) %f(d) %h(basic/c) %H(basic) %P(c/d) %y(f) %p(basic/e/f) %d(2) %f(f) %h(basic/e) %H(basic) %P(e/f) %y(f) %p(basic/g/h) %d(2) %f(h) %h(basic/g) %H(basic) %P(g/h) %y(d) %p(basic/j/foo) %d(2) %f(foo) %h(basic/j) %H(basic) %P(j/foo) %y(f) %p(basic/k/foo) %d(2) %f(foo) %h(basic/k) %H(basic) %P(k/foo) %y(d) %p(basic/l/foo) %d(2) %f(foo) %h(basic/l) %H(basic) %P(l/foo) %y(d) %p(links/deeply/nested) %d(2) %f(nested) %h(links/deeply) %H(links) %P(deeply/nested) %y(d) %p(basic/k/foo/bar) %d(3) %f(bar) %h(basic/k/foo) %H(basic) %P(k/foo/bar) %y(f) %p(basic/l/foo/bar) %d(3) %f(bar) %h(basic/l/foo) %H(basic) %P(l/foo/bar) %y(d) %p(links/deeply/nested/dir) %d(3) %f(dir) %h(links/deeply/nested) %H(links) %P(deeply/nested/dir) %y(d) %p(links/deeply/nested/file) %d(3) %f(file) %h(links/deeply/nested) %H(links) %P(deeply/nested/file) %y(f) %p(links/deeply/nested/link) %d(3) %f(link) %h(links/deeply/nested) %H(links) %P(deeply/nested/link) %y(l) %p(basic/l/foo/bar/baz) %d(4) %f(baz) %h(basic/l/foo/bar) %H(basic) %P(l/foo/bar/baz) %y(f) bfs-1.5.2/tests/test_printf_Y_error.out000066400000000000000000000001021360564154000202230ustar00rootroot00000000000000(scratch) () d d (scratch/foo) () d d (scratch/bar) (foo/bar) l ? bfs-1.5.2/tests/test_printf_empty.out000066400000000000000000000000001360564154000177350ustar00rootroot00000000000000bfs-1.5.2/tests/test_printf_escapes.out000066400000000000000000000000121360564154000202250ustar00rootroot000000000000008 8I8I18 bfs-1.5.2/tests/test_printf_flags.out000066400000000000000000000006421360564154000177070ustar00rootroot00000000000000|basic | +00 0755 |basic/a | +01 0644 |basic/b | +01 0644 |basic/c | +01 0755 |basic/e | +01 0755 |basic/g | +01 0755 |basic/i | +01 0755 |basic/j | +01 0755 |basic/k | +01 0755 |basic/l | +01 0755 |basic/c/d | +02 0644 |basic/e/f | +02 0644 |basic/g/h | +02 0755 |basic/j/fo| +02 0644 |basic/k/fo| +02 0755 |basic/k/fo| +03 0644 |basic/l/fo| +02 0755 |basic/l/fo| +03 0755 |basic/l/fo| +04 0644 bfs-1.5.2/tests/test_printf_leak.out000066400000000000000000000000061360564154000175210ustar00rootroot00000000000000basic bfs-1.5.2/tests/test_printf_nul.out000066400000000000000000000000101360564154000173760ustar00rootroot00000000000000.basic bfs-1.5.2/tests/test_printf_slash.out000066400000000000000000000000101360564154000177120ustar00rootroot00000000000000(/)/(/) bfs-1.5.2/tests/test_printf_slashes.out000066400000000000000000000000101360564154000202420ustar00rootroot00000000000000(/)/(/) bfs-1.5.2/tests/test_printf_times.out000066400000000000000000000010641360564154000177330ustar00rootroot00000000000000times/a | Sat Dec 14 00:00:00.0000000000 1991 1991-12-14 00:12:00.0000000000 692668800.0000000000 | Sat Dec 14 00:00:00.0000000000 1991 1991-12-14 00:12:00.0000000000 692668800.0000000000 times/b | Sat Dec 14 00:01:00.0000000000 1991 1991-12-14 00:12:00.0000000000 692668860.0000000000 | Sat Dec 14 00:01:00.0000000000 1991 1991-12-14 00:12:00.0000000000 692668860.0000000000 times/c | Sat Dec 14 00:02:00.0000000000 1991 1991-12-14 00:12:00.0000000000 692668920.0000000000 | Sat Dec 14 00:02:00.0000000000 1991 1991-12-14 00:12:00.0000000000 692668920.0000000000 bfs-1.5.2/tests/test_printf_trailing_slash.out000066400000000000000000000004231360564154000216130ustar00rootroot00000000000000(basic)/(a) (basic)/(b) (basic)/(c) (basic)/(e) (basic)/(g) (basic)/(i) (basic)/(j) (basic)/(k) (basic)/(l) (.)/(basic/) (basic/c)/(d) (basic/e)/(f) (basic/g)/(h) (basic/j)/(foo) (basic/k)/(foo) (basic/l)/(foo) (basic/k/foo)/(bar) (basic/l/foo)/(bar) (basic/l/foo/bar)/(baz) bfs-1.5.2/tests/test_printf_trailing_slashes.out000066400000000000000000000004711360564154000221460ustar00rootroot00000000000000(basic//)/(a) (basic//)/(b) (basic//)/(c) (basic//)/(e) (basic//)/(g) (basic//)/(i) (basic//)/(j) (basic//)/(k) (basic//)/(l) (.)/(basic///) (basic///c)/(d) (basic///e)/(f) (basic///g)/(h) (basic///j)/(foo) (basic///k)/(foo) (basic///l)/(foo) (basic///k/foo)/(bar) (basic///l/foo)/(bar) (basic///l/foo/bar)/(baz) bfs-1.5.2/tests/test_printf_types.out000066400000000000000000000005041360564154000177540ustar00rootroot00000000000000(loops) () d d (loops/broken) (nowhere) l N (loops/deeply) () d d (loops/file) () f f (loops/loop) (loop) l L (loops/symlink) (file) l f (loops/deeply/nested) () d d (loops/notdir) (symlink/file) l N (loops/deeply/nested/dir) () d d (loops/skip) (deeply/nested/loop/nested) l d (loops/deeply/nested/loop) (../../deeply) l d bfs-1.5.2/tests/test_printf_w.out000066400000000000000000000000001360564154000170450ustar00rootroot00000000000000bfs-1.5.2/tests/test_printx.out000066400000000000000000000004571360564154000165610ustar00rootroot00000000000000weirdnames weirdnames/! weirdnames/!- weirdnames/( weirdnames/(- weirdnames/) weirdnames/, weirdnames/- weirdnames/... weirdnames/\ weirdnames/\\ weirdnames/!-/e weirdnames/!/d weirdnames/(-/c weirdnames/(/b weirdnames/)/g weirdnames/,/f weirdnames/-/a weirdnames/.../h weirdnames/\ /j weirdnames/\\/i bfs-1.5.2/tests/test_prune.out000066400000000000000000000000441360564154000163560ustar00rootroot00000000000000basic/j/foo basic/k/foo basic/l/foo bfs-1.5.2/tests/test_prune_or_print.out000066400000000000000000000001541360564154000202740ustar00rootroot00000000000000basic basic/a basic/b basic/c basic/e basic/g basic/i basic/j basic/k basic/l basic/c/d basic/e/f basic/g/h bfs-1.5.2/tests/test_quit.out000066400000000000000000000000101360564154000162000ustar00rootroot00000000000000basic/g bfs-1.5.2/tests/test_quit_after_print.out000066400000000000000000000000061360564154000206020ustar00rootroot00000000000000basic bfs-1.5.2/tests/test_quit_before_print.out000066400000000000000000000000001360564154000207350ustar00rootroot00000000000000bfs-1.5.2/tests/test_quit_child.out000066400000000000000000000000221360564154000173460ustar00rootroot00000000000000basic/g basic/g/h bfs-1.5.2/tests/test_quit_depth.out000066400000000000000000000000221360564154000173670ustar00rootroot00000000000000basic/g basic/g/h bfs-1.5.2/tests/test_quit_depth_child.out000066400000000000000000000000121360564154000205310ustar00rootroot00000000000000basic/g/h bfs-1.5.2/tests/test_quit_implicit_print.out000066400000000000000000000000061360564154000213130ustar00rootroot00000000000000basic bfs-1.5.2/tests/test_readable.out000066400000000000000000000000521360564154000167630ustar00rootroot00000000000000perms perms/r perms/rw perms/rwx perms/rx bfs-1.5.2/tests/test_regex.out000066400000000000000000000000361360564154000163400ustar00rootroot00000000000000basic/c/d basic/e/f basic/g/h bfs-1.5.2/tests/test_regex_parens.out000066400000000000000000000000041360564154000177030ustar00rootroot00000000000000./( bfs-1.5.2/tests/test_regextype_posix_basic.out000066400000000000000000000000041360564154000216200ustar00rootroot00000000000000./( bfs-1.5.2/tests/test_regextype_posix_extended.out000066400000000000000000000000041360564154000223370ustar00rootroot00000000000000./( bfs-1.5.2/tests/test_rm.out000066400000000000000000000000101360564154000156340ustar00rootroot00000000000000scratch bfs-1.5.2/tests/test_samefile.out000066400000000000000000000000321360564154000170070ustar00rootroot00000000000000links/file links/hardlink bfs-1.5.2/tests/test_samefile_broken.out000066400000000000000000000000151360564154000203500ustar00rootroot00000000000000links/broken bfs-1.5.2/tests/test_samefile_notdir.out000066400000000000000000000000151360564154000203670ustar00rootroot00000000000000links/notdir bfs-1.5.2/tests/test_samefile_symlink.out000066400000000000000000000000161360564154000205570ustar00rootroot00000000000000links/symlink bfs-1.5.2/tests/test_size.out000066400000000000000000000001001360564154000161700ustar00rootroot00000000000000basic/a basic/b basic/c/d basic/e/f basic/j/foo basic/k/foo/bar bfs-1.5.2/tests/test_size_T.out000066400000000000000000000000241360564154000164600ustar00rootroot00000000000000basic/l/foo/bar/baz bfs-1.5.2/tests/test_size_big.out000066400000000000000000000000001360564154000170100ustar00rootroot00000000000000bfs-1.5.2/tests/test_size_bytes.out000066400000000000000000000000241360564154000174030ustar00rootroot00000000000000basic/l/foo/bar/baz bfs-1.5.2/tests/test_size_plus.out000066400000000000000000000000241360564154000172400ustar00rootroot00000000000000basic/l/foo/bar/baz bfs-1.5.2/tests/test_true.out000066400000000000000000000003041360564154000162030ustar00rootroot00000000000000basic basic/a basic/b basic/c basic/e basic/g basic/i basic/j basic/k basic/l basic/c/d basic/e/f basic/g/h basic/j/foo basic/k/foo basic/l/foo basic/k/foo/bar basic/l/foo/bar basic/l/foo/bar/baz bfs-1.5.2/tests/test_type_bind_mount.out000066400000000000000000000000151360564154000204220ustar00rootroot00000000000000scratch/null bfs-1.5.2/tests/test_type_d.out000066400000000000000000000001601360564154000165100ustar00rootroot00000000000000basic basic/c basic/e basic/g basic/i basic/j basic/k basic/l basic/g/h basic/k/foo basic/l/foo basic/l/foo/bar bfs-1.5.2/tests/test_type_f.out000066400000000000000000000001241360564154000165120ustar00rootroot00000000000000basic/a basic/b basic/c/d basic/e/f basic/j/foo basic/k/foo/bar basic/l/foo/bar/baz bfs-1.5.2/tests/test_type_multi.out000066400000000000000000000001621360564154000174210ustar00rootroot00000000000000links links/deeply links/file links/hardlink links/deeply/nested links/deeply/nested/dir links/deeply/nested/file bfs-1.5.2/tests/test_uid.out000066400000000000000000000003041360564154000160050ustar00rootroot00000000000000basic basic/a basic/b basic/c basic/e basic/g basic/i basic/j basic/k basic/l basic/c/d basic/e/f basic/g/h basic/j/foo basic/k/foo basic/l/foo basic/k/foo/bar basic/l/foo/bar basic/l/foo/bar/baz bfs-1.5.2/tests/test_uid_minus.out000066400000000000000000000003041360564154000172200ustar00rootroot00000000000000basic basic/a basic/b basic/c basic/e basic/g basic/i basic/j basic/k basic/l basic/c/d basic/e/f basic/g/h basic/j/foo basic/k/foo basic/l/foo basic/k/foo/bar basic/l/foo/bar basic/l/foo/bar/baz bfs-1.5.2/tests/test_uid_minus_plus.out000066400000000000000000000003041360564154000202630ustar00rootroot00000000000000basic basic/a basic/b basic/c basic/e basic/g basic/i basic/j basic/k basic/l basic/c/d basic/e/f basic/g/h basic/j/foo basic/k/foo basic/l/foo basic/k/foo/bar basic/l/foo/bar basic/l/foo/bar/baz bfs-1.5.2/tests/test_uid_name.out000066400000000000000000000003041360564154000170050ustar00rootroot00000000000000basic basic/a basic/b basic/c basic/e basic/g basic/i basic/j basic/k basic/l basic/c/d basic/e/f basic/g/h basic/j/foo basic/k/foo basic/l/foo basic/k/foo/bar basic/l/foo/bar basic/l/foo/bar/baz bfs-1.5.2/tests/test_uid_plus.out000066400000000000000000000003041360564154000170500ustar00rootroot00000000000000basic basic/a basic/b basic/c basic/e basic/g basic/i basic/j basic/k basic/l basic/c/d basic/e/f basic/g/h basic/j/foo basic/k/foo basic/l/foo basic/k/foo/bar basic/l/foo/bar basic/l/foo/bar/baz bfs-1.5.2/tests/test_uid_plus_plus.out000066400000000000000000000003041360564154000201130ustar00rootroot00000000000000basic basic/a basic/b basic/c basic/e basic/g basic/i basic/j basic/k basic/l basic/c/d basic/e/f basic/g/h basic/j/foo basic/k/foo basic/l/foo basic/k/foo/bar basic/l/foo/bar basic/l/foo/bar/baz bfs-1.5.2/tests/test_unique.out000066400000000000000000000000311360564154000165270ustar00rootroot00000000000000links/file links/symlink bfs-1.5.2/tests/test_unique_depth.out000066400000000000000000000003041360564154000177160ustar00rootroot00000000000000basic basic/a basic/b basic/c basic/e basic/g basic/i basic/j basic/k basic/l basic/c/d basic/e/f basic/g/h basic/j/foo basic/k/foo basic/l/foo basic/k/foo/bar basic/l/foo/bar basic/l/foo/bar/baz bfs-1.5.2/tests/test_user_id.out000066400000000000000000000003041360564154000166560ustar00rootroot00000000000000basic basic/a basic/b basic/c basic/e basic/g basic/i basic/j basic/k basic/l basic/c/d basic/e/f basic/g/h basic/j/foo basic/k/foo basic/l/foo basic/k/foo/bar basic/l/foo/bar basic/l/foo/bar/baz bfs-1.5.2/tests/test_user_name.out000066400000000000000000000003041360564154000172020ustar00rootroot00000000000000basic basic/a basic/b basic/c basic/e basic/g basic/i basic/j basic/k basic/l basic/c/d basic/e/f basic/g/h basic/j/foo basic/k/foo basic/l/foo basic/k/foo/bar basic/l/foo/bar basic/l/foo/bar/baz bfs-1.5.2/tests/test_weird_names.out000066400000000000000000000001541360564154000175240ustar00rootroot00000000000000!- !- (- (- ) ) , , - - !-/e !-/e (-/c (-/c )/g )/g ,/f ,/f -/a -/a ./! ./! ./( ./( ./!/d ./!/d ./(/b ./(/b bfs-1.5.2/tests/test_writable.out000066400000000000000000000000521360564154000170350ustar00rootroot00000000000000perms perms/rw perms/rwx perms/w perms/wx bfs-1.5.2/tests/test_xattr.out000066400000000000000000000000411360564154000163640ustar00rootroot00000000000000scratch/xattr scratch/xattr_link bfs-1.5.2/tests/test_xdev.out000066400000000000000000000000601360564154000161710ustar00rootroot00000000000000scratch scratch/foo scratch/mnt scratch/foo/bar bfs-1.5.2/tests/test_xtype_bind_mount.out000066400000000000000000000000321360564154000206110ustar00rootroot00000000000000scratch/link scratch/null bfs-1.5.2/tests/test_xtype_f.out000066400000000000000000000001321360564154000167010ustar00rootroot00000000000000links/file links/hardlink links/symlink links/deeply/nested/file links/deeply/nested/link bfs-1.5.2/tests/test_xtype_l.out000066400000000000000000000000321360564154000167060ustar00rootroot00000000000000links/broken links/notdir bfs-1.5.2/tests/test_xtype_multi.out000066400000000000000000000002441360564154000176120ustar00rootroot00000000000000links links/deeply links/file links/hardlink links/skip links/symlink links/deeply/nested links/deeply/nested/dir links/deeply/nested/file links/deeply/nested/link bfs-1.5.2/tests/test_xtype_reorder.out000066400000000000000000000000001360564154000201100ustar00rootroot00000000000000bfs-1.5.2/trie.c000066400000000000000000000446561360564154000134230ustar00rootroot00000000000000/**************************************************************************** * bfs * * Copyright (C) 2019 Tavian Barnes * * * * 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. * ****************************************************************************/ /** * This is an implementation of a "qp trie," as documented at * https://dotat.at/prog/qp/README.html * * An uncompressed trie over the dataset {AAAA, AADD, ABCD, DDAA, DDDD} would * look like * * A A A A * *--->*--->*--->*--->$ * | | | D D * | | +--->*--->$ * | | B C D * | +--->*--->*--->$ * | D D A A * +--->*--->*--->*--->$ * | D D * +--->*--->$ * * A compressed (PATRICIA) trie collapses internal nodes that have only a single * child, like this: * * A A AA * *--->*--->*---->$ * | | | DD * | | +---->$ * | | BCD * | +----->$ * | DD AA * +---->*---->$ * | DD * +---->$ * * The nodes can be compressed further by dropping the actual compressed * sequences from the nodes, storing it only in the leaves. This is the * technique applied in QP tries, and the crit-bit trees that inspired them * (https://cr.yp.to/critbit.html). Only the index to test, and the values to * branch on, need to be stored in each node. * * A A A * 0--->1--->2--->AAAA * | | | D * | | +--->AADD * | | B * | +--->ABCD * | D A * +--->2--->DDAA * | D * +--->DDDD * * Nodes are represented very compactly. Rather than a dense array of children, * a sparse array of only the non-NULL children directly follows the node in * memory. A bitmap is used to track which children exist; the index of a child * i is found by counting the number of bits below bit i that are set. A tag * bit is used to tell pointers to internal nodes apart from pointers to leaves. * * This implementation tests a whole nibble (half byte/hex digit) at every * branch, so the bitmap takes up 16 bits. The remainder of a machine word is * used to hold the offset, which severely constrains its range on 32-bit * platforms. As a workaround, we store relative instead of absolute offsets, * and insert intermediate singleton "jump" nodes when necessary. */ #include "trie.h" #include #include #include #include #include #include #if CHAR_BIT != 8 # error "This trie implementation assumes 8-bit bytes." #endif /** Number of bits for the sparse array bitmap, aka the range of a nibble. */ #define BITMAP_BITS 16 /** The number of remaining bits in a word, to hold the offset. */ #define OFFSET_BITS (sizeof(size_t)*CHAR_BIT - BITMAP_BITS) /** The highest representable offset (only 64k on a 32-bit architecture). */ #define OFFSET_MAX (((size_t)1 << OFFSET_BITS) - 1) /** * An internal node of the trie. */ struct trie_node { /** * A bitmap that hold which indices exist in the sparse children array. * Bit i will be set if a child exists at logical index i, and its index * into the array will be popcount(bitmap & ((1 << i) - 1)). */ size_t bitmap : BITMAP_BITS; /** * The offset into the key in nibbles. This is relative to the parent * node, to support offsets larger than OFFSET_MAX. */ size_t offset : OFFSET_BITS; /** * Flexible array of children. Each pointer uses the lowest bit as a * tag to distinguish internal nodes from leaves. This is safe as long * as all dynamic allocations are aligned to more than a single byte. */ uintptr_t children[]; }; /** Check if an encoded pointer is to a leaf. */ static bool trie_is_leaf(uintptr_t ptr) { return ptr & 1; } /** Decode a pointer to a leaf. */ static struct trie_leaf *trie_decode_leaf(uintptr_t ptr) { assert(trie_is_leaf(ptr)); return (struct trie_leaf *)(ptr ^ 1); } /** Encode a pointer to a leaf. */ static uintptr_t trie_encode_leaf(const struct trie_leaf *leaf) { uintptr_t ptr = (uintptr_t)leaf ^ 1; assert(trie_is_leaf(ptr)); return ptr; } /** Decode a pointer to an internal node. */ static struct trie_node *trie_decode_node(uintptr_t ptr) { assert(!trie_is_leaf(ptr)); return (struct trie_node *)ptr; } /** Encode a pointer to an internal node. */ static uintptr_t trie_encode_node(const struct trie_node *node) { uintptr_t ptr = (uintptr_t)node; assert(!trie_is_leaf(ptr)); return ptr; } void trie_init(struct trie *trie) { trie->root = 0; } /** Compute the popcount (Hamming weight) of a bitmap. */ static unsigned int trie_popcount(unsigned int n) { #if __POPCNT__ // Use the x86 instruction if we have it. Otherwise, GCC generates a // library call, so use the below implementation instead. return __builtin_popcount(n); #else // See https://en.wikipedia.org/wiki/Hamming_weight#Efficient_implementation n -= (n >> 1) & 0x5555; n = (n & 0x3333) + ((n >> 2) & 0x3333); n = (n + (n >> 4)) & 0x0F0F; n = (n + (n >> 8)) & 0xFF; return n; #endif } /** Extract the nibble at a certain offset from a byte sequence. */ static unsigned char trie_key_nibble(const void *key, size_t offset) { const unsigned char *bytes = key; size_t byte = offset >> 1; // A branchless version of // if (offset & 1) { // return bytes[byte] >> 4; // } else { // return bytes[byte] & 0xF; // } unsigned int shift = (offset & 1) << 2; return (bytes[byte] >> shift) & 0xF; } /** * Finds a leaf in the trie that matches the key at every branch. If the key * exists in the trie, the representative will match the searched key. But * since only branch points are tested, it can be different from the key. In * that case, the first mismatch between the key and the representative will be * the depth at which to make a new branch to insert the key. */ static struct trie_leaf *trie_representative(const struct trie *trie, const void *key, size_t length) { uintptr_t ptr = trie->root; if (!ptr) { return NULL; } size_t offset = 0; while (!trie_is_leaf(ptr)) { struct trie_node *node = trie_decode_node(ptr); offset += node->offset; unsigned int index = 0; if ((offset >> 1) < length) { unsigned char nibble = trie_key_nibble(key, offset); unsigned int bit = 1U << nibble; if (node->bitmap & bit) { index = trie_popcount(node->bitmap & (bit - 1)); } } ptr = node->children[index]; } return trie_decode_leaf(ptr); } struct trie_leaf *trie_first_leaf(const struct trie *trie) { return trie_representative(trie, NULL, 0); } struct trie_leaf *trie_find_str(const struct trie *trie, const char *key) { return trie_find_mem(trie, key, strlen(key) + 1); } struct trie_leaf *trie_find_mem(const struct trie *trie, const void *key, size_t length) { struct trie_leaf *rep = trie_representative(trie, key, length); if (rep && rep->length == length && memcmp(rep->key, key, length) == 0) { return rep; } else { return NULL; } } struct trie_leaf *trie_find_postfix(const struct trie *trie, const char *key) { size_t length = strlen(key); struct trie_leaf *rep = trie_representative(trie, key, length + 1); if (rep && rep->length >= length && memcmp(rep->key, key, length) == 0) { return rep; } else { return NULL; } } /** * Find a leaf that may end at the current node. */ static struct trie_leaf *trie_terminal_leaf(const struct trie_node *node) { // Finding a terminating NUL byte may take two nibbles for (int i = 0; i < 2; ++i) { if (!(node->bitmap & 1)) { break; } uintptr_t ptr = node->children[0]; if (trie_is_leaf(ptr)) { return trie_decode_leaf(ptr); } else { node = trie_decode_node(ptr); } } return NULL; } /** Check if a leaf is a prefix of a search key. */ static bool trie_check_prefix(struct trie_leaf *leaf, size_t skip, const char *key, size_t length) { if (leaf && leaf->length <= length) { return memcmp(key + skip, leaf->key + skip, leaf->length - skip - 1) == 0; } else { return false; } } struct trie_leaf *trie_find_prefix(const struct trie *trie, const char *key) { uintptr_t ptr = trie->root; if (!ptr) { return NULL; } struct trie_leaf *best = NULL; size_t skip = 0; size_t length = strlen(key) + 1; size_t offset = 0; while (!trie_is_leaf(ptr)) { struct trie_node *node = trie_decode_node(ptr); offset += node->offset; if ((offset >> 1) >= length) { return best; } struct trie_leaf *leaf = trie_terminal_leaf(node); if (trie_check_prefix(leaf, skip, key, length)) { best = leaf; skip = offset >> 1; } unsigned char nibble = trie_key_nibble(key, offset); unsigned int bit = 1U << nibble; if (node->bitmap & bit) { unsigned int index = trie_popcount(node->bitmap & (bit - 1)); ptr = node->children[index]; } else { return best; } } struct trie_leaf *leaf = trie_decode_leaf(ptr); if (trie_check_prefix(leaf, skip, key, length)) { best = leaf; } return best; } /** Create a new leaf, holding a copy of the given key. */ static struct trie_leaf *new_trie_leaf(const void *key, size_t length) { struct trie_leaf *leaf = malloc(sizeof(*leaf) + length); if (leaf) { leaf->value = NULL; leaf->length = length; memcpy(leaf->key, key, length); } return leaf; } /** Compute the size of a trie node with a certain number of children. */ static size_t trie_node_size(unsigned int size) { // Empty nodes aren't supported assert(size > 0); // Node size must be a power of two assert((size & (size - 1)) == 0); return sizeof(struct trie_node) + size*sizeof(uintptr_t); } /** Find the offset of the first nibble that differs between two keys. */ static size_t trie_key_mismatch(const void *key1, const void *key2, size_t length) { const unsigned char *bytes1 = key1; const unsigned char *bytes2 = key2; size_t i = 0; size_t offset = 0; const size_t chunk = sizeof(size_t); for (; i + chunk <= length; i += chunk) { if (memcmp(bytes1 + i, bytes2 + i, chunk) != 0) { break; } } for (; i < length; ++i) { unsigned char b1 = bytes1[i], b2 = bytes2[i]; if (b1 != b2) { offset = (b1 & 0xF) == (b2 & 0xF); break; } } offset |= i << 1; return offset; } /** * Insert a key into a node. The node must not have a child in that position * already. Effectively takes a subtrie like this: * * ptr * | * v X * *--->... * | Z * +--->... * * and transforms it to: * * ptr * | * v X * *--->... * | Y * +--->key * | Z * +--->... */ static struct trie_leaf *trie_node_insert(uintptr_t *ptr, const void *key, size_t length, size_t offset) { struct trie_node *node = trie_decode_node(*ptr); unsigned int size = trie_popcount(node->bitmap); // Double the capacity every power of two if ((size & (size - 1)) == 0) { node = realloc(node, trie_node_size(2*size)); if (!node) { return NULL; } *ptr = trie_encode_node(node); } struct trie_leaf *leaf = new_trie_leaf(key, length); if (!leaf) { return NULL; } unsigned char nibble = trie_key_nibble(key, offset); unsigned int bit = 1U << nibble; // The child must not already be present assert(!(node->bitmap & bit)); node->bitmap |= bit; unsigned int index = trie_popcount(node->bitmap & (bit - 1)); uintptr_t *child = node->children + index; if (index < size) { memmove(child + 1, child, (size - index)*sizeof(*child)); } *child = trie_encode_leaf(leaf); return leaf; } /** * When the current offset exceeds OFFSET_MAX, insert "jump" nodes that bridge * the gap. This function takes a subtrie like this: * * ptr * | * v * *--->rep * * and changes it to: * * ptr ret * | | * v v * *--->*--->rep * * so that a new key can be inserted like: * * ptr ret * | | * v v X * *--->*--->rep * | Y * +--->key */ static uintptr_t *trie_jump(uintptr_t *ptr, const char *key, size_t *offset) { // We only ever need to jump to leaf nodes, since internal nodes are // guaranteed to be within OFFSET_MAX anyway assert(trie_is_leaf(*ptr)); struct trie_node *node = malloc(trie_node_size(1)); if (!node) { return NULL; } *offset += OFFSET_MAX; node->offset = OFFSET_MAX; unsigned char nibble = trie_key_nibble(key, *offset); node->bitmap = 1 << nibble; node->children[0] = *ptr; *ptr = trie_encode_node(node); return node->children; } /** * Split a node in the trie. Changes a subtrie like this: * * ptr * | * v * *...>--->rep * * into this: * * ptr * | * v X * *--->*...>--->rep * | Y * +--->key */ static struct trie_leaf *trie_split(uintptr_t *ptr, const void *key, size_t length, struct trie_leaf *rep, size_t offset, size_t mismatch) { unsigned char key_nibble = trie_key_nibble(key, mismatch); unsigned char rep_nibble = trie_key_nibble(rep->key, mismatch); assert(key_nibble != rep_nibble); struct trie_node *node = malloc(trie_node_size(2)); if (!node) { return NULL; } struct trie_leaf *leaf = new_trie_leaf(key, length); if (!leaf) { free(node); return NULL; } node->bitmap = (1 << key_nibble) | (1 << rep_nibble); size_t delta = mismatch - offset; if (!trie_is_leaf(*ptr)) { struct trie_node *child = trie_decode_node(*ptr); child->offset -= delta; } node->offset = delta; unsigned int key_index = key_nibble > rep_nibble; node->children[key_index] = trie_encode_leaf(leaf); node->children[key_index ^ 1] = *ptr; *ptr = trie_encode_node(node); return leaf; } struct trie_leaf *trie_insert_str(struct trie *trie, const char *key) { return trie_insert_mem(trie, key, strlen(key) + 1); } struct trie_leaf *trie_insert_mem(struct trie *trie, const void *key, size_t length) { struct trie_leaf *rep = trie_representative(trie, key, length); if (!rep) { struct trie_leaf *leaf = new_trie_leaf(key, length); if (leaf) { trie->root = trie_encode_leaf(leaf); } return leaf; } size_t limit = length < rep->length ? length : rep->length; size_t mismatch = trie_key_mismatch(key, rep->key, limit); if ((mismatch >> 1) >= length) { return rep; } size_t offset = 0; uintptr_t *ptr = &trie->root; while (!trie_is_leaf(*ptr)) { struct trie_node *node = trie_decode_node(*ptr); if (offset + node->offset > mismatch) { break; } offset += node->offset; unsigned char nibble = trie_key_nibble(key, offset); unsigned int bit = 1U << nibble; if (node->bitmap & bit) { assert(offset < mismatch); unsigned int index = trie_popcount(node->bitmap & (bit - 1)); ptr = node->children + index; } else { assert(offset == mismatch); return trie_node_insert(ptr, key, length, offset); } } while (mismatch - offset > OFFSET_MAX) { ptr = trie_jump(ptr, key, &offset); if (!ptr) { return NULL; } } return trie_split(ptr, key, length, rep, offset, mismatch); } /** Free a chain of singleton nodes. */ static void trie_free_singletons(uintptr_t ptr) { while (!trie_is_leaf(ptr)) { struct trie_node *node = trie_decode_node(ptr); // Make sure the bitmap is a power of two, i.e. it has just one child assert((node->bitmap & (node->bitmap - 1)) == 0); ptr = node->children[0]; free(node); } free(trie_decode_leaf(ptr)); } /** * Try to collapse a two-child node like: * * parent child * | | * v v * *----->*----->*----->leaf * | * +----->other * * into * * parent * | * v * other */ static int trie_collapse_node(uintptr_t *parent, struct trie_node *parent_node, unsigned int child_index) { uintptr_t other = parent_node->children[child_index ^ 1]; if (!trie_is_leaf(other)) { struct trie_node *other_node = trie_decode_node(other); if (other_node->offset + parent_node->offset <= OFFSET_MAX) { other_node->offset += parent_node->offset; } else { return -1; } } *parent = other; free(parent_node); return 0; } void trie_remove(struct trie *trie, struct trie_leaf *leaf) { uintptr_t *child = &trie->root; uintptr_t *parent = NULL; unsigned int child_bit = 0, child_index = 0; size_t offset = 0; while (!trie_is_leaf(*child)) { struct trie_node *node = trie_decode_node(*child); offset += node->offset; assert((offset >> 1) < leaf->length); unsigned char nibble = trie_key_nibble(leaf->key, offset); unsigned int bit = 1U << nibble; unsigned int bitmap = node->bitmap; assert(bitmap & bit); unsigned int index = trie_popcount(bitmap & (bit - 1)); // Advance the parent pointer, unless this node had only one child if (bitmap & (bitmap - 1)) { parent = child; child_bit = bit; child_index = index; } child = node->children + index; } assert(trie_decode_leaf(*child) == leaf); if (!parent) { trie_free_singletons(trie->root); trie->root = 0; return; } struct trie_node *node = trie_decode_node(*parent); child = node->children + child_index; trie_free_singletons(*child); node->bitmap ^= child_bit; unsigned int parent_size = trie_popcount(node->bitmap); assert(parent_size > 0); if (parent_size == 1 && trie_collapse_node(parent, node, child_index) == 0) { return; } if (child_index < parent_size) { memmove(child, child + 1, (parent_size - child_index)*sizeof(*child)); } if ((parent_size & (parent_size - 1)) == 0) { node = realloc(node, trie_node_size(parent_size)); if (node) { *parent = trie_encode_node(node); } } } /** Free an encoded pointer to a node. */ static void free_trie_ptr(uintptr_t ptr) { if (trie_is_leaf(ptr)) { free(trie_decode_leaf(ptr)); } else { struct trie_node *node = trie_decode_node(ptr); size_t size = trie_popcount(node->bitmap); for (size_t i = 0; i < size; ++i) { free_trie_ptr(node->children[i]); } free(node); } } void trie_destroy(struct trie *trie) { if (trie->root) { free_trie_ptr(trie->root); } } bfs-1.5.2/trie.h000066400000000000000000000100671360564154000134150ustar00rootroot00000000000000/**************************************************************************** * bfs * * Copyright (C) 2019 Tavian Barnes * * * * 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. * ****************************************************************************/ #ifndef BFS_TRIE_H #define BFS_TRIE_H #include #include #include /** * A trie that holds a set of fixed- or variable-length strings. */ struct trie { uintptr_t root; }; /** * A leaf of a trie. */ struct trie_leaf { /** * An arbitrary value associated with this leaf. */ void *value; /** * The length of the key in bytes. */ size_t length; /** * The key itself, stored inline. */ char key[]; }; /** * Initialize an empty trie. */ void trie_init(struct trie *trie); /** * Get the first (lexicographically earliest) leaf in the trie. * * @param trie * The trie to search. * @return * The first leaf, or NULL if the trie is empty. */ struct trie_leaf *trie_first_leaf(const struct trie *trie); /** * Find the leaf for a string key. * * @param trie * The trie to search. * @param key * The key to look up. * @return * The found leaf, or NULL if the key is not present. */ struct trie_leaf *trie_find_str(const struct trie *trie, const char *key); /** * Find the leaf for a fixed-size key. * * @param trie * The trie to search. * @param key * The key to look up. * @param length * The length of the key in bytes. * @return * The found leaf, or NULL if the key is not present. */ struct trie_leaf *trie_find_mem(const struct trie *trie, const void *key, size_t length); /** * Find the shortest leaf that starts with a given key. * * @param trie * The trie to search. * @param key * The key to look up. * @return * A leaf that starts with the given key, or NULL. */ struct trie_leaf *trie_find_postfix(const struct trie *trie, const char *key); /** * Find the leaf that is the longest prefix of the given key. * * @param trie * The trie to search. * @param key * The key to look up. * @return * The longest prefix match for the given key, or NULL. */ struct trie_leaf *trie_find_prefix(const struct trie *trie, const char *key); /** * Insert a string key into the trie. * * @param trie * The trie to modify. * @param key * The key to insert. * @return * The inserted leaf, or NULL on failure. */ struct trie_leaf *trie_insert_str(struct trie *trie, const char *key); /** * Insert a fixed-size key into the trie. * * @param trie * The trie to modify. * @param key * The key to insert. * @param length * The length of the key in bytes. * @return * The inserted leaf, or NULL on failure. */ struct trie_leaf *trie_insert_mem(struct trie *trie, const void *key, size_t length); /** * Remove a leaf from a trie. * * @param trie * The trie to modify. * @param leaf * The leaf to remove. */ void trie_remove(struct trie *trie, struct trie_leaf *leaf); /** * Destroy a trie and its contents. */ void trie_destroy(struct trie *trie); #endif // BFS_TRIE_H bfs-1.5.2/typo.c000066400000000000000000000111741360564154000134400ustar00rootroot00000000000000/**************************************************************************** * bfs * * Copyright (C) 2016 Tavian Barnes * * * * 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. * ****************************************************************************/ #include "typo.h" #include #include #include // Assume QWERTY layout for now static const int key_coords[UCHAR_MAX][3] = { ['`'] = { 0, 0, 0}, ['~'] = { 0, 0, 1}, ['1'] = { 3, 0, 0}, ['!'] = { 3, 0, 1}, ['2'] = { 6, 0, 0}, ['@'] = { 6, 0, 1}, ['3'] = { 9, 0, 0}, ['#'] = { 9, 0, 1}, ['4'] = {12, 0, 0}, ['$'] = {12, 0, 1}, ['5'] = {15, 0, 0}, ['%'] = {15, 0, 1}, ['6'] = {18, 0, 0}, ['^'] = {18, 0, 1}, ['7'] = {21, 0, 0}, ['&'] = {21, 0, 1}, ['8'] = {24, 0, 0}, ['*'] = {24, 0, 1}, ['9'] = {27, 0, 0}, ['('] = {27, 0, 1}, ['0'] = {30, 0, 0}, [')'] = {30, 0, 1}, ['-'] = {33, 0, 0}, ['_'] = {33, 0, 1}, ['='] = {36, 0, 0}, ['+'] = {36, 0, 1}, ['\t'] = { 1, 3, 0}, ['q'] = { 4, 3, 0}, ['Q'] = { 4, 3, 1}, ['w'] = { 7, 3, 0}, ['W'] = { 7, 3, 1}, ['e'] = {10, 3, 0}, ['E'] = {10, 3, 1}, ['r'] = {13, 3, 0}, ['R'] = {13, 3, 1}, ['t'] = {16, 3, 0}, ['T'] = {16, 3, 1}, ['y'] = {19, 3, 0}, ['Y'] = {19, 3, 1}, ['u'] = {22, 3, 0}, ['U'] = {22, 3, 1}, ['i'] = {25, 3, 0}, ['I'] = {25, 3, 1}, ['o'] = {28, 3, 0}, ['O'] = {28, 3, 1}, ['p'] = {31, 3, 0}, ['P'] = {31, 3, 1}, ['['] = {34, 3, 0}, ['{'] = {34, 3, 1}, [']'] = {37, 3, 0}, ['}'] = {37, 3, 1}, ['\\'] = {40, 3, 0}, ['|'] = {40, 3, 1}, ['a'] = { 5, 6, 0}, ['A'] = { 5, 6, 1}, ['s'] = { 8, 6, 0}, ['S'] = { 8, 6, 1}, ['d'] = {11, 6, 0}, ['D'] = {11, 6, 1}, ['f'] = {14, 6, 0}, ['F'] = {14, 6, 1}, ['g'] = {17, 6, 0}, ['G'] = {17, 6, 1}, ['h'] = {20, 6, 0}, ['H'] = {20, 6, 1}, ['j'] = {23, 6, 0}, ['J'] = {23, 6, 1}, ['k'] = {26, 6, 0}, ['K'] = {26, 6, 1}, ['l'] = {29, 6, 0}, ['L'] = {29, 6, 1}, [';'] = {32, 6, 0}, [':'] = {32, 6, 1}, ['\''] = {35, 6, 0}, ['"'] = {35, 6, 1}, ['\n'] = {38, 6, 0}, ['z'] = { 6, 9, 0}, ['Z'] = { 6, 9, 1}, ['x'] = { 9, 9, 0}, ['X'] = { 9, 9, 1}, ['c'] = {12, 9, 0}, ['C'] = {12, 9, 1}, ['v'] = {15, 9, 0}, ['V'] = {15, 9, 1}, ['b'] = {18, 9, 0}, ['B'] = {18, 9, 1}, ['n'] = {21, 9, 0}, ['N'] = {21, 9, 1}, ['m'] = {24, 9, 0}, ['M'] = {24, 9, 1}, [','] = {27, 9, 0}, ['<'] = {27, 9, 1}, ['.'] = {30, 9, 0}, ['>'] = {30, 9, 1}, ['/'] = {33, 9, 0}, ['?'] = {33, 9, 1}, [' '] = {18, 12, 0}, }; static int char_distance(char a, char b) { const int *ac = key_coords[(unsigned char)a], *bc = key_coords[(unsigned char)b]; int ret = 0; for (int i = 0; i < 3; ++i) { ret += abs(ac[i] - bc[i]); } return ret; } int typo_distance(const char *actual, const char *expected) { // This is the Wagner-Fischer algorithm for Levenshtein distance, using // Manhattan distance on the keyboard for individual characters. const int insert_cost = 12; size_t rows = strlen(actual) + 1; size_t cols = strlen(expected) + 1; int arr0[cols], arr1[cols]; int *row0 = arr0, *row1 = arr1; for (size_t j = 0; j < cols; ++j) { row0[j] = insert_cost * j; } for (size_t i = 1; i < rows; ++i) { row1[0] = row0[0] + insert_cost; char a = actual[i - 1]; for (size_t j = 1; j < cols; ++j) { char b = expected[j - 1]; int cost = row0[j - 1] + char_distance(a, b); int del_cost = row0[j] + insert_cost; if (del_cost < cost) { cost = del_cost; } int ins_cost = row1[j - 1] + insert_cost; if (ins_cost < cost) { cost = ins_cost; } row1[j] = cost; } int *tmp = row0; row0 = row1; row1 = tmp; } return row0[cols - 1]; } bfs-1.5.2/typo.h000066400000000000000000000027651360564154000134530ustar00rootroot00000000000000/**************************************************************************** * bfs * * Copyright (C) 2016 Tavian Barnes * * * * 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. * ****************************************************************************/ #ifndef BFS_TYPO_H #define BFS_TYPO_H /** * Find the "typo" distance between two strings. * * @param actual * The actual string typed by the user. * @param expected * The expected valid string. * @return The distance between the two strings. */ int typo_distance(const char *actual, const char *expected); #endif // BFS_TYPO_H bfs-1.5.2/util.c000066400000000000000000000166401360564154000134250ustar00rootroot00000000000000/**************************************************************************** * bfs * * Copyright (C) 2016-2020 Tavian Barnes * * * * 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. * ****************************************************************************/ #include "util.h" #include "bftw.h" #include "dstring.h" #include #include #include #include #include #include #include #include #include #include #include #include #if BFS_HAS_SYS_PARAM # include #endif #if BFS_HAS_SYS_SYSMACROS # include #elif BFS_HAS_SYS_MKDEV # include #endif int xreaddir(DIR *dir, struct dirent **de) { while (true) { errno = 0; *de = readdir(dir); if (*de) { const char *name = (*de)->d_name; if (name[0] != '.' || (name[1] != '\0' && (name[1] != '.' || name[2] != '\0'))) { return 0; } } else if (errno != 0) { return -1; } else { return 0; } } } char *xreadlinkat(int fd, const char *path, size_t size) { ++size; // NUL-terminator ssize_t len; char *name = NULL; while (true) { char *new_name = realloc(name, size); if (!new_name) { goto error; } name = new_name; len = readlinkat(fd, path, name, size); if (len < 0) { goto error; } else if (len >= size) { size *= 2; } else { break; } } name[len] = '\0'; return name; error: free(name); return NULL; } bool isopen(int fd) { return fcntl(fd, F_GETFD) >= 0 || errno != EBADF; } int redirect(int fd, const char *path, int flags, ...) { mode_t mode = 0; if (flags & O_CREAT) { va_list args; va_start(args, flags); // Use int rather than mode_t, because va_arg must receive a // fully-promoted type mode = va_arg(args, int); va_end(args); } int ret = open(path, flags, mode); if (ret >= 0 && ret != fd) { int orig = ret; ret = dup2(orig, fd); close(orig); } return ret; } int dup_cloexec(int fd) { #ifdef F_DUPFD_CLOEXEC return fcntl(fd, F_DUPFD_CLOEXEC, 0); #else int ret = dup(fd); if (ret < 0) { return -1; } if (fcntl(ret, F_SETFD, FD_CLOEXEC) == -1) { close(ret); return -1; } return ret; #endif } int pipe_cloexec(int pipefd[2]) { #if __linux__ || (BSD && !__APPLE__) return pipe2(pipefd, O_CLOEXEC); #else if (pipe(pipefd) != 0) { return -1; } if (fcntl(pipefd[0], F_SETFD, FD_CLOEXEC) == -1 || fcntl(pipefd[1], F_SETFD, FD_CLOEXEC) == -1) { int error = errno; close(pipefd[1]); close(pipefd[0]); errno = error; return -1; } return 0; #endif } char *xregerror(int err, const regex_t *regex) { size_t len = regerror(err, regex, NULL, 0); char *str = malloc(len); if (str) { regerror(err, regex, str, len); } return str; } int xlocaltime(const time_t *timep, struct tm *result) { // Should be called before localtime_r() according to POSIX.1-2004 tzset(); if (localtime_r(timep, result)) { return 0; } else { return -1; } } void format_mode(mode_t mode, char str[11]) { strcpy(str, "----------"); switch (bftw_mode_typeflag(mode)) { case BFTW_REG: break; case BFTW_BLK: str[0] = 'b'; break; case BFTW_CHR: str[0] = 'c'; break; case BFTW_DIR: str[0] = 'd'; break; case BFTW_DOOR: str[0] = 'D'; break; case BFTW_LNK: str[0] = 'l'; break; case BFTW_FIFO: str[0] = 'p'; break; case BFTW_PORT: str[0] = 'P'; break; case BFTW_SOCK: str[0] = 's'; break; case BFTW_WHT: str[0] = 'w'; break; case BFTW_UNKNOWN: case BFTW_ERROR: str[0] = '?'; break; } if (mode & 00400) { str[1] = 'r'; } if (mode & 00200) { str[2] = 'w'; } if ((mode & 04100) == 04000) { str[3] = 'S'; } else if (mode & 04000) { str[3] = 's'; } else if (mode & 00100) { str[3] = 'x'; } if (mode & 00040) { str[4] = 'r'; } if (mode & 00020) { str[5] = 'w'; } if ((mode & 02010) == 02000) { str[6] = 'S'; } else if (mode & 02000) { str[6] = 's'; } else if (mode & 00010) { str[6] = 'x'; } if (mode & 00004) { str[7] = 'r'; } if (mode & 00002) { str[8] = 'w'; } if ((mode & 01001) == 01000) { str[9] = 'T'; } else if (mode & 01000) { str[9] = 't'; } else if (mode & 00001) { str[9] = 'x'; } } const char *xbasename(const char *path) { const char *i; // Skip trailing slashes for (i = path + strlen(path); i > path && i[-1] == '/'; --i); // Find the beginning of the name for (; i > path && i[-1] != '/'; --i); // Skip leading slashes for (; i[0] == '/' && i[1]; ++i); return i; } int xfaccessat(int fd, const char *path, int amode) { int ret = faccessat(fd, path, amode, 0); #ifdef AT_EACCESS // Some platforms, like Hurd, only support AT_EACCESS. Other platforms, // like Android, don't support AT_EACCESS at all. if (ret != 0 && (errno == EINVAL || errno == ENOTSUP)) { ret = faccessat(fd, path, amode, AT_EACCESS); } #endif return ret; } bool is_nonexistence_error(int error) { return error == ENOENT || errno == ENOTDIR; } /** Read a line from standard input. */ static char *xgetline(void) { char *line = dstralloc(0); if (!line) { return NULL; } while (true) { int c = getchar(); if (c == '\n' || c == EOF) { break; } if (dstrapp(&line, c) != 0) { goto error; } } return line; error: dstrfree(line); return NULL; } /** Compile and execute a regular expression for xrpmatch(). */ static int xrpregex(nl_item item, const char *response) { const char *pattern = nl_langinfo(item); if (!pattern) { return REG_BADPAT; } regex_t regex; int ret = regcomp(®ex, pattern, REG_EXTENDED); if (ret != 0) { return ret; } ret = regexec(®ex, response, 0, NULL, 0); regfree(®ex); return ret; } /** Check if a response is affirmative or negative. */ static int xrpmatch(const char *response) { int ret = xrpregex(NOEXPR, response); if (ret == 0) { return 0; } else if (ret != REG_NOMATCH) { return -1; } ret = xrpregex(YESEXPR, response); if (ret == 0) { return 1; } else if (ret != REG_NOMATCH) { return -1; } // Failsafe: always handle y/n char c = response[0]; if (c == 'n' || c == 'N') { return 0; } else if (c == 'y' || c == 'Y') { return 1; } else { return -1; } } int ynprompt() { fflush(stderr); char *line = xgetline(); int ret = line ? xrpmatch(line) : -1; dstrfree(line); return ret; } dev_t bfs_makedev(int ma, int mi) { #ifdef makedev return makedev(ma, mi); #else return (ma << 8) | mi; #endif } int bfs_major(dev_t dev) { #ifdef major return major(dev); #else return dev >> 8; #endif } int bfs_minor(dev_t dev) { #ifdef minor return minor(dev); #else return dev & 0xFF; #endif } bfs-1.5.2/util.h000066400000000000000000000134341360564154000134300ustar00rootroot00000000000000/**************************************************************************** * bfs * * Copyright (C) 2016-2020 Tavian Barnes * * * * 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. * ****************************************************************************/ /** * Assorted utilities that don't belong anywhere else. */ #ifndef BFS_UTIL_H #define BFS_UTIL_H #include #include #include #include #include #include #include // Some portability concerns #ifdef __has_feature # define BFS_HAS_FEATURE(feature, fallback) __has_feature(feature) #else # define BFS_HAS_FEATURE(feature, fallback) fallback #endif #ifdef __has_include # define BFS_HAS_INCLUDE(header, fallback) __has_include(header) #else # define BFS_HAS_INCLUDE(header, fallback) fallback #endif #ifndef BFS_HAS_MNTENT # define BFS_HAS_MNTENT BFS_HAS_INCLUDE(, __GLIBC__) #endif #ifndef BFS_HAS_SYS_ACL # define BFS_HAS_SYS_ACL BFS_HAS_INCLUDE(, true) #endif #ifndef BFS_HAS_SYS_CAPABILITY # define BFS_HAS_SYS_CAPABILITY BFS_HAS_INCLUDE(, __linux__) #endif #ifndef BFS_HAS_SYS_EXTATTR # define BFS_HAS_SYS_EXTATTR BFS_HAS_INCLUDE(, __FreeBSD__) #endif #ifndef BFS_HAS_SYS_MKDEV # define BFS_HAS_SYS_MKDEV BFS_HAS_INCLUDE(, false) #endif #ifndef BFS_HAS_SYS_PARAM # define BFS_HAS_SYS_PARAM BFS_HAS_INCLUDE(, true) #endif #ifndef BFS_HAS_SYS_SYSMACROS # define BFS_HAS_SYS_SYSMACROS BFS_HAS_INCLUDE(, __GLIBC__) #endif #ifndef BFS_HAS_SYS_XATTR # define BFS_HAS_SYS_XATTR BFS_HAS_INCLUDE(, __linux__) #endif #if !defined(FNM_CASEFOLD) && defined(FNM_IGNORECASE) # define FNM_CASEFOLD FNM_IGNORECASE #endif #ifndef O_DIRECTORY # define O_DIRECTORY 0 #endif /** * Adds compiler warnings for bad printf()-style function calls, if supported. */ #if __GNUC__ # define BFS_FORMATTER(fmt, args) __attribute__((format(printf, fmt, args))) #else # define BFS_FORMATTER(fmt, args) #endif /** * readdir() wrapper that makes error handling cleaner. */ int xreaddir(DIR *dir, struct dirent **de); /** * readlinkat() wrapper that dynamically allocates the result. * * @param fd * The base directory descriptor. * @param path * The path to the link, relative to fd. * @param size * An estimate for the size of the link name (pass 0 if unknown). * @return The target of the link, allocated with malloc(), or NULL on failure. */ char *xreadlinkat(int fd, const char *path, size_t size); /** * Check if a file descriptor is open. */ bool isopen(int fd); /** * Open a file and redirect it to a particular descriptor. * * @param fd * The file descriptor to redirect. * @param path * The path to open. * @param flags * The flags passed to open(). * @param mode * The mode passed to open() (optional). * @return fd on success, -1 on failure. */ int redirect(int fd, const char *path, int flags, ...); /** * Like dup(), but set the FD_CLOEXEC flag. * * @param fd * The file descriptor to duplicate. * @return A duplicated file descriptor, or -1 on failure. */ int dup_cloexec(int fd); /** * Like pipe(), but set the FD_CLOEXEC flag. * * @param pipefd * The array to hold the two file descriptors. * @return 0 on success, -1 on failure. */ int pipe_cloexec(int pipefd[2]); /** * Dynamically allocate a regex error message. * * @param err * The error code to stringify. * @param regex * The (partially) compiled regex. * @return A human-readable description of the error, allocated with malloc(). */ char *xregerror(int err, const regex_t *regex); /** * localtime_r() wrapper that calls tzset() first. * * @param timep * The time_t to convert. * @param result * Buffer to hold the result. * @return 0 on success, -1 on failure. */ int xlocaltime(const time_t *timep, struct tm *result); /** * Format a mode like ls -l (e.g. -rw-r--r--). * * @param mode * The mode to format. * @param str * The string to hold the formatted mode. */ void format_mode(mode_t mode, char str[11]); /** * basename() variant that doesn't modify the input. * * @param path * The path in question. * @return A pointer into path at the base name offset. */ const char *xbasename(const char *path); /** * Wrapper for faccessat() that handles some portability issues. */ int xfaccessat(int fd, const char *path, int amode); /** * Return whether an error code is due to a path not existing. */ bool is_nonexistence_error(int error); /** * Process a yes/no prompt. * * @return 1 for yes, 0 for no, and -1 for unknown. */ int ynprompt(void); /** * Portable version of makedev(). */ dev_t bfs_makedev(int ma, int mi); /** * Portable version of major(). */ int bfs_major(dev_t dev); /** * Portable version of minor(). */ int bfs_minor(dev_t dev); #endif // BFS_UTIL_H