pax_global_header00006660000000000000000000000064132371574700014523gustar00rootroot0000000000000052 comment=f21cbfd4eaef06b5cd45455130af37b66413954c bfs-1.2.1/000077500000000000000000000000001323715747000122765ustar00rootroot00000000000000bfs-1.2.1/COPYING000066400000000000000000000012421323715747000133300ustar00rootroot00000000000000Copyright (C) 2015-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. bfs-1.2.1/Makefile000066400000000000000000000046521323715747000137450ustar00rootroot00000000000000############################################################################ # bfs # # Copyright (C) 2015-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. # ############################################################################ ifeq ($(wildcard .git),) VERSION := 1.2.1 else VERSION := $(shell git describe --always) endif CC ?= gcc INSTALL ?= install MKDIR ?= mkdir -p RM ?= rm -f WFLAGS ?= -Wall -Wmissing-declarations CFLAGS ?= -g $(WFLAGS) LDFLAGS ?= DEPFLAGS ?= -MD -MP -MF $(@:.o=.d) DESTDIR ?= PREFIX ?= /usr 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 ALL_CPPFLAGS = $(LOCAL_CPPFLAGS) $(CPPFLAGS) ALL_CFLAGS = $(ALL_CPPFLAGS) $(LOCAL_CFLAGS) $(CFLAGS) $(DEPFLAGS) ALL_LDFLAGS = $(ALL_CFLAGS) $(LDFLAGS) all: bfs bfs: bftw.o color.o dstring.o eval.o exec.o main.o mtab.o opt.o parse.o printf.o stat.o typo.o util.o $(CC) $(ALL_LDFLAGS) $^ -o $@ release: CFLAGS := -O3 -flto $(WFLAGS) -DNDEBUG -g release: bfs %.o: %.c $(CC) $(ALL_CFLAGS) -c $< -o $@ check: all ./tests.sh clean: $(RM) bfs *.o *.d install: $(MKDIR) $(DESTDIR)$(PREFIX)/bin $(INSTALL) -m755 bfs $(DESTDIR)$(PREFIX)/bin/bfs $(MKDIR) $(DESTDIR)$(PREFIX)/share/man/man1 $(INSTALL) -m644 bfs.1 $(DESTDIR)$(PREFIX)/share/man/man1/bfs.1 uninstall: $(RM) $(DESTDIR)$(PREFIX)/bin/bfs .PHONY: all release check clean install uninstall -include $(wildcard *.d) bfs-1.2.1/README.md000066400000000000000000000103131323715747000135530ustar00rootroot00000000000000`bfs` ===== [![License](http://img.shields.io/badge/license-0BSD-blue.svg)](https://github.com/tavianator/bfs/blob/master/COPYING) [![LOC](https://tokei.rs/b1/github/tavianator/bfs?category=code)](https://github.com/Aaronepower/tokei) [![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://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man1/find.1.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 Pretty ------ When `bfs` detects that its output is a terminal, it automatically colors its output with the same colors `ls` uses. This makes it easier to identify relevant files at a glance. Screenshot Try it! ------- 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 If you're on Arch Linux, `bfs` is available in the [AUR](https://aur.archlinux.org/packages/bfs/). If you're on Debian GNU/Linux, `bfs` is available via `apt-get install bfs`. If you're on macOS, `bfs` can be installed with Homebrew via `brew install tavianator/tap/bfs`. bfs-1.2.1/RELEASES.md000066400000000000000000000144401323715747000140260ustar00rootroot000000000000001.* === 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.2.1/bfs.1000066400000000000000000000272301323715747000131360ustar00rootroot00000000000000.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 (see .B bfs-SPECIFIC FEATURES below). .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 POSIX find FEATURES 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. .TP \fB! \fIexpression\fR The "not" operator: returns the negation of the truth value of the .IR expression . You may have to write .B \\\\! .I expression to avoid .B ! being interpreted by the shell. .TP \fIexpression\fR [\fB\-a\fR] \fIexpression\fR Short-circuiting "and" operator: if the left-hand .I expression is .BR true , returns the right-hand .IR expression ; otherwise, returns .BR false . .TP \fIexpression \fB\-o \fIexpression\fR Short-circuiting "or" operator: if the left-hand .I expression is .BR false , returns the right-hand .IR expression ; otherwise, returns .BR true . .LP Flags: .TP .B \-H Follow symbolic links on the command line, but not while searching. .TP .B \-L Follow all symbolic links. .LP Options: .TP .B \-depth Search in post-order (descendents first). .TP .B \-xdev Don't descend into other mount points. .LP Tests: .PP \fB\-atime\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/ c hanged/ m odified .I N days ago. .RE .PP .B \-group NAME .br .B \-user NAME .RS Find files owned by the group/user .BR NAME . .RE .TP \fB\-links\fR [\fI\-+\fR]\fIN\fR Find files with .I N hard links. .TP \fB\-name \fIGLOB\fR Find files whose name matches the .IR GLOB . .TP \fB\-path \fIGLOB\fR Find files whose entire path matches the .IR GLOB . .TP \fB\-newer \fIFILE\fR Find files newer than .IR FILE . .TP \fB\-perm\fR [\fI\-\fR]\fIMODE\fR Find files with a matching mode. .TP \fB\-type\fR [\fIbcdlpfs\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, and .IR s ocket. .TP \fB\-size\fR [\fI\-+\fR]\fIN\fR[\fIc\fR] Find files with the given size. The default unit is 512-byte blocks; .I c counts .IR c haracters/bytes instead. .LP Actions: .TP .B \-prune Don't descend into this directory. .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. .TP .B \-print Print the path to the found file. .SH GNU find FEATURES Operators: .TP \fB\-not \fIexpression\fR Same as .B ! .IR expression . .TP \fIexpression \fB\-and \fIexpression\fR Same as .I expression .B \-a .IR expression . .TP \fIexpression \fB\-or \fIexpression\fR Same as .I expression .B \-o .IR expression . .TP \fIexpression \fB, \fIexpression\fR The "comma" operator: evaluates the left-hand .I expression but discards the result, returning the right-hand .IR expression . .LP Flags: .TP .B \-P Never follow symbolic links (the default). .TP \fB\-D \fIFLAG\fR Turn on a debugging flag (see .B \-D .IR help ). .TP \fB\-O\fIN\fR Enable optimization level .I N (default: 3; interpreted differently than GNU find -- see below). .LP Options: .TP .B \-d Search in post-order (same as .BR \-depth ). .TP .B \-daystart Measure time relative to the start of today. .TP .B \-follow Follow all symbolic links (same as .BR \-L ). .LP .B \-ignore_readdir_race .br .B \-noignore_readdir_race .RS Whether to report an error if .B bfs detects that the file tree is modified during the search (default: .BR \-noignore_readdir_race ). .RE .PP \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 .BR \-xdev ). .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 ). .LP .B \-warn .br .B \-nowarn .RS Turn on or off warnings about the command line. .RE .PP Tests: .PP \fB\-amin\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/ c hanged/ m odified .I N minutes ago. .RE .PP \fB\-anewer \fIFILE\fR .br \fB\-cnewer \fIFILE\fR .br \fB\-mnewer \fIFILE\fR .RS Find files .BR a ccessed/ c hanged/ m odified more recently than .I FILE was modified. .RE .TP .B \-empty Find empty files/directories. .LP .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 . .LP \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 .TP \fB\-inum\fR [\fI\-+\fR]\fIN\fR Find files with inode number .IR N . .TP \fB\-lname \fIGLOB\fR Find symbolic links whose target matches the .IR GLOB . .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). .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[\fIcwbkMG\fR] 1-byte .IR c haracters, 2-byte .IR w ords, 512-byte .IR b locks, and .IR k iB/ M iB/ G iB. .TP \fB\-type\fR [\fIbcdlpfsD\fR] The .IR D oor file type is also supported on platforms that have it (Solaris). .TP \fB\-used\fR [\fI\-+\fR]\fIN\fR Find files last accessed .I N days after they were changed. .TP \fB\-wholename \fIGLOB\fR Find files whose entire path matches the .I GLOB (same as .BR \-path ). .LP \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\-xtype\fR [\fIbcdlpfsD\fR] Find files of the given type, following links when .B \-type would not, and vice versa. .LP Actions: .TP .B \-delete Delete any found files (implies \fB-depth\fR). .LP \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 .B \-ls List files like .B ls .IR \-dils . .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)). .LP \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 \-quit Quit immediately. .TP .B \-version Print version information. .TP .B \-help Print usage information. .SH BSD find FEATURES Flags: .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 \-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). .LP Tests: .PP \fB\-Bmin\fR [\fI\-+\fR]\fIN\fR .br \fB\-Btime\fR [\fI\-+\fR]\fIN\fR .RS Find files .BR B irthed .I N minutes/days ago. .RE .TP \fB\-Bnewer \fIFILE\fR Find files .BR B irthed more recently than .I FILE was modified. .TP \fB\-depth\fR [\fI\-+\fR]\fIN\fR Find files with depth .IR N . .LP .B \-gid NAME .br .B \-uid NAME .RS Group/user names are supported in addition to numeric IDs. .RE .TP \fB\-size\fR [\fI\-+\fR]\fIN\fR[\fIcwbkMGTP\fR] Units of .IR T iB/ P iB are additionally supported. .TP .B \-sparse Find files that occupy fewer disk blocks than expected. .LP Actions: .TP \fB\-exit\fR [\fISTATUS\fR] Exit immediately with the given status (0 if unspecified). .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 \-rm Delete any found files (same as .BR \-delete ; implies .BR \-depth ). .SH bfs-SPECIFIC FEATURES Flags: .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. .LP 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 .PP Tests: .TP .B \-hidden Match hidden files (those beginning with .IR . ). .LP Actions: .TP .B \-nohidden Filter out hidden files and directories. .LP \fB\-printf \fIFORMAT\fR .br \fB\-fprintf \fIFORMAT FILE\fR .RS These additional format directives are supported: .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 .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.2.1/bfs.h000066400000000000000000000025211323715747000132210ustar00rootroot00000000000000/**************************************************************************** * bfs * * Copyright (C) 2015-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. * ****************************************************************************/ #ifndef BFS_H #define BFS_H #ifndef BFS_VERSION # define BFS_VERSION "1.2.1" #endif #ifndef BFS_HOMEPAGE # define BFS_HOMEPAGE "https://github.com/tavianator/bfs" #endif #endif // BFS_H bfs-1.2.1/bftw.c000066400000000000000000000630121323715747000134060ustar00rootroot00000000000000/**************************************************************************** * bfs * * Copyright (C) 2015-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. * ****************************************************************************/ /** * The bftw() implementation consists of the following components: * * - struct bftw_dir: A directory that has been encountered during the * traversal. They have reference-counted links to their parents in the * directory tree. * * - struct bftw_cache: Holds bftw_dir'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_dir's left to explore. Implemented as * a simple circular buffer. * * - struct bftw_state: Represents the current state of the traversal, allowing * bftw() to be factored into various helper functions. */ #include "bftw.h" #include "dstring.h" #include "stat.h" #include "util.h" #include #include #include #include #include #include #include #include #include #include /** * A directory. */ struct bftw_dir { /** The parent directory, if any. */ struct bftw_dir *parent; /** This directory'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 file descriptor to this directory, or -1. */ int fd; /** The device number, for cycle detection. */ dev_t dev; /** The inode number, for cycle detection. */ ino_t ino; /** The offset of this directory in the full path. */ size_t nameoff; /** The length of the directory's name. */ size_t namelen; /** The directory's name. */ char name[]; }; /** * A cache of open directories. */ struct bftw_cache { /** A min-heap of open directories. */ struct bftw_dir **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_dir *parent, const struct bftw_dir *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_dir to a particular place in the heap. */ static void bftw_heap_move(struct bftw_cache *cache, struct bftw_dir *dir, size_t i) { cache->heap[i] = dir; dir->heap_index = i; } /** Bubble an entry up the heap. */ static void bftw_heap_bubble_up(struct bftw_cache *cache, struct bftw_dir *dir) { size_t i = dir->heap_index; while (i > 0) { size_t pi = (i - 1)/2; struct bftw_dir *parent = cache->heap[pi]; if (bftw_heap_check(parent, dir)) { break; } bftw_heap_move(cache, parent, i); i = pi; } bftw_heap_move(cache, dir, i); } /** Bubble an entry down the heap. */ static void bftw_heap_bubble_down(struct bftw_cache *cache, struct bftw_dir *dir) { size_t i = dir->heap_index; while (true) { size_t ci = 2*i + 1; if (ci >= cache->size) { break; } struct bftw_dir *child = cache->heap[ci]; size_t ri = ci + 1; if (ri < cache->size) { struct bftw_dir *right = cache->heap[ri]; if (!bftw_heap_check(child, right)) { ci = ri; child = right; } } if (bftw_heap_check(dir, child)) { break; } bftw_heap_move(cache, child, i); i = ci; } bftw_heap_move(cache, dir, i); } /** Bubble an entry up or down the heap. */ static void bftw_heap_bubble(struct bftw_cache *cache, struct bftw_dir *dir) { size_t i = dir->heap_index; if (i > 0) { size_t pi = (i - 1)/2; struct bftw_dir *parent = cache->heap[pi]; if (!bftw_heap_check(parent, dir)) { bftw_heap_bubble_up(cache, dir); return; } } bftw_heap_bubble_down(cache, dir); } /** Increment a bftw_dir's reference count. */ static void bftw_dir_incref(struct bftw_cache *cache, struct bftw_dir *dir) { ++dir->refcount; if (dir->fd >= 0) { bftw_heap_bubble_down(cache, dir); } } /** Decrement a bftw_dir's reference count. */ static void bftw_dir_decref(struct bftw_cache *cache, struct bftw_dir *dir) { --dir->refcount; if (dir->fd >= 0) { bftw_heap_bubble_up(cache, dir); } } /** Add a bftw_dir to the cache. */ static void bftw_cache_add(struct bftw_cache *cache, struct bftw_dir *dir) { assert(cache->size < cache->capacity); assert(dir->fd >= 0); size_t size = cache->size++; dir->heap_index = size; bftw_heap_bubble_up(cache, dir); } /** Remove a bftw_dir from the cache. */ static void bftw_cache_remove(struct bftw_cache *cache, struct bftw_dir *dir) { assert(cache->size > 0); assert(dir->fd >= 0); size_t size = --cache->size; size_t i = dir->heap_index; if (i != size) { struct bftw_dir *end = cache->heap[size]; end->heap_index = i; bftw_heap_bubble(cache, end); } } /** Close a bftw_dir. */ static void bftw_dir_close(struct bftw_cache *cache, struct bftw_dir *dir) { assert(dir->fd >= 0); bftw_cache_remove(cache, dir); close(dir->fd); dir->fd = -1; } /** Pop a directory from the cache. */ static void bftw_cache_pop(struct bftw_cache *cache) { assert(cache->size > 0); bftw_dir_close(cache, cache->heap[0]); } /** Pop a directory other than 'saved' from the cache. */ static void bftw_cache_pop_other(struct bftw_cache *cache, const struct bftw_dir *saved) { assert(cache->size > 1); struct bftw_dir *dir = cache->heap[0]; if (dir == saved) { dir = cache->heap[1]; } bftw_dir_close(cache, dir); } /** Create a new bftw_dir. */ static struct bftw_dir *bftw_dir_new(struct bftw_cache *cache, struct bftw_dir *parent, const char *name) { size_t namelen = strlen(name); size_t size = sizeof(struct bftw_dir) + namelen + 1; bool needs_slash = false; if (namelen == 0 || name[namelen - 1] != '/') { needs_slash = true; ++size; } struct bftw_dir *dir = malloc(size); if (!dir) { return NULL; } dir->parent = parent; if (parent) { dir->depth = parent->depth + 1; dir->nameoff = parent->nameoff + parent->namelen; bftw_dir_incref(cache, parent); } else { dir->depth = 0; dir->nameoff = 0; } dir->refcount = 1; dir->fd = -1; dir->dev = -1; dir->ino = -1; memcpy(dir->name, name, namelen); if (needs_slash) { dir->name[namelen++] = '/'; } dir->name[namelen] = '\0'; dir->namelen = namelen; return dir; } /** * Get the appropriate (fd, path) pair for the *at() family of functions. * * @param dir * The directory 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_dir *bftw_dir_base(struct bftw_dir *dir, int *at_fd, const char **at_path) { struct bftw_dir *base = dir; do { base = base->parent; } while (base && base->fd < 0); if (base) { *at_fd = base->fd; *at_path += base->nameoff + base->namelen; } return base; } /** * Check if we should retry an operation due to EMFILE. * * @param cache * The cache in question. * @param saved * A bftw_dir that must be preserved. */ static bool bftw_should_retry(struct bftw_cache *cache, const struct bftw_dir *saved) { if (errno == EMFILE && cache->size > 1) { // Too many open files, shrink the cache bftw_cache_pop_other(cache, saved); cache->capacity = cache->size; return true; } else { return false; } } /** * Open a bftw_dir relative to another one. * * @param cache * The cache containing the dir. * @param dir * The directory to open. * @param base * The base directory for the relative path (may be NULL). * @param at_fd * The base file descriptor, AT_CWDFD if base == NULL. * @param at_path * The relative path to the dir. * @return * The opened file descriptor, or negative on error. */ static int bftw_dir_openat(struct bftw_cache *cache, struct bftw_dir *dir, const struct bftw_dir *base, int at_fd, const char *at_path) { assert(dir->fd < 0); int flags = O_RDONLY | O_CLOEXEC | O_DIRECTORY; int fd = openat(at_fd, at_path, flags); if (fd < 0 && bftw_should_retry(cache, base)) { fd = openat(at_fd, at_path, flags); } if (fd >= 0) { if (cache->size == cache->capacity) { bftw_cache_pop(cache); } dir->fd = fd; bftw_cache_add(cache, dir); } return fd; } /** * Open a bftw_dir. * * @param cache * The cache containing the directory. * @param dir * The directory to open. * @param path * The full path to the dir. * @return * The opened file descriptor, or negative on error. */ static int bftw_dir_open(struct bftw_cache *cache, struct bftw_dir *dir, const char *path) { int at_fd = AT_FDCWD; const char *at_path = path; struct bftw_dir *base = bftw_dir_base(dir, &at_fd, &at_path); int fd = bftw_dir_openat(cache, dir, 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 = dir->depth - offset; if (levels < 2) { return fd; } struct bftw_dir **parents = malloc(levels * sizeof(*parents)); if (!parents) { return fd; } struct bftw_dir *parent = dir; for (size_t i = levels; i-- > 0;) { parents[i] = parent; parent = parent->parent; } for (size_t i = 0; i < levels; ++i) { fd = bftw_dir_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_dir. * * @param cache * The cache containing the directory. * @param dir * The directory to open. * @param path * The full path to the directory. * @return * The opened DIR *, or NULL on error. */ static DIR *bftw_dir_opendir(struct bftw_cache *cache, struct bftw_dir *dir, const char *path) { int fd = bftw_dir_open(cache, dir, 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 && bftw_should_retry(cache, dir)) { dfd = dup_cloexec(fd); } if (dfd < 0) { return NULL; } DIR *ret = fdopendir(dfd); if (!ret) { close(dfd); } return ret; } /** Free a bftw_dir. */ static void bftw_dir_free(struct bftw_cache *cache, struct bftw_dir *dir) { assert(dir->refcount == 0); if (dir->fd >= 0) { bftw_dir_close(cache, dir); } free(dir); } /** * A queue of bftw_dir's to examine. */ struct bftw_queue { /** The circular buffer of directories. */ struct bftw_dir **buffer; /** The head of the queue. */ size_t head; /** The size of the queue. */ size_t size; /** The capacity of the queue (always a power of two). */ size_t capacity; }; /** Initialize a bftw_queue. */ static int bftw_queue_init(struct bftw_queue *queue) { queue->head = 0; queue->size = 0; queue->capacity = 256; queue->buffer = malloc(queue->capacity*sizeof(*queue->buffer)); if (queue->buffer) { return 0; } else { return -1; } } /** Add a directory to the bftw_queue. */ static int bftw_queue_push(struct bftw_queue *queue, struct bftw_dir *dir) { struct bftw_dir **buffer = queue->buffer; size_t head = queue->head; size_t size = queue->size; size_t tail = head + size; size_t capacity = queue->capacity; if (size == capacity) { capacity *= 2; buffer = realloc(buffer, capacity*sizeof(struct bftw_dir *)); if (!buffer) { return -1; } for (size_t i = size; i < tail; ++i) { buffer[i] = buffer[i - size]; } queue->buffer = buffer; queue->capacity = capacity; } tail &= capacity - 1; buffer[tail] = dir; ++queue->size; return 0; } /** Remove a directory from the bftw_queue. */ static struct bftw_dir *bftw_queue_pop(struct bftw_queue *queue) { if (queue->size == 0) { return NULL; } struct bftw_dir *dir = queue->buffer[queue->head]; --queue->size; ++queue->head; queue->head &= queue->capacity - 1; return dir; } /** Destroy a bftw_queue. */ static void bftw_queue_destroy(struct bftw_queue *queue) { assert(queue->size == 0); free(queue->buffer); } /** Call stat() and use the results. */ static int ftwbuf_stat(struct BFTW *ftwbuf, struct bfs_stat *sb) { int ret = bfs_stat(ftwbuf->at_fd, ftwbuf->at_path, ftwbuf->at_flags, BFS_STAT_BROKEN_OK, sb); if (ret == 0) { ftwbuf->statbuf = sb; ftwbuf->typeflag = mode_to_typeflag(sb->mode); } return ret; } /** * Possible bftw() traversal statuses. */ enum bftw_status { /** The current path is state.current. */ BFTW_CURRENT, /** The current path is a child of state.current. */ BFTW_CHILD, /** bftw_dir's are being garbage collected. */ BFTW_GC, }; /** * Holds the current state of the bftw() traversal. */ struct bftw_state { /** bftw() callback. */ bftw_fn *fn; /** bftw() flags. */ int flags; /** bftw() callback data. */ void *ptr; /** 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; /** The current directory. */ struct bftw_dir *current; /** The previous directory. */ struct bftw_dir *previous; /** The currently open directory. */ DIR *dir; /** The current traversal status. */ enum bftw_status status; /** The root path of the walk. */ const char *root; /** The current path being explored. */ char *path; /** Extra data about the current file. */ struct BFTW ftwbuf; /** bfs_stat() buffer for the current file. */ struct bfs_stat statbuf; }; /** * Initialize the bftw() state. */ static int bftw_state_init(struct bftw_state *state, const char *root, bftw_fn *fn, int nopenfd, int flags, void *ptr) { state->root = root; state->fn = fn; state->flags = flags; state->ptr = ptr; state->error = 0; if (nopenfd < 2) { errno = EMFILE; return -1; } // -1 to account for dup() if (bftw_cache_init(&state->cache, nopenfd - 1) != 0) { goto err; } if (bftw_queue_init(&state->queue) != 0) { goto err_cache; } state->current = NULL; state->previous = NULL; state->dir = NULL; state->status = BFTW_CURRENT; state->path = dstralloc(0); if (!state->path) { goto err_queue; } return 0; err_queue: bftw_queue_destroy(&state->queue); err_cache: bftw_cache_destroy(&state->cache); err: return -1; } /** * Compute the path to the current bftw_dir. */ static int bftw_build_path(struct bftw_state *state) { const struct bftw_dir *dir = state->current; size_t namelen = dir->namelen; size_t pathlen = dir->nameoff + namelen; if (dstresize(&state->path, pathlen) != 0) { return -1; } // Only rebuild the part of the path that changes const struct bftw_dir *base = state->previous; while (base && base->depth > dir->depth) { base = base->parent; } // Build the path backwards char *path = state->path; while (dir != base) { char *segment = path + dir->nameoff; namelen = dir->namelen; memcpy(segment, dir->name, namelen); if (base && base->depth == dir->depth) { base = base->parent; } dir = dir->parent; } state->previous = state->current; return 0; } /** * Concatenate a subpath to the current path. */ static int bftw_path_concat(struct bftw_state *state, const char *subpath) { size_t nameoff = 0; struct bftw_dir *current = state->current; if (current) { nameoff = current->nameoff + current->namelen; } state->status = BFTW_CHILD; if (dstresize(&state->path, nameoff) != 0) { return -1; } return dstrcat(&state->path, subpath); } /** * Trim the path to just the current directory. */ static void bftw_path_trim(struct bftw_state *state) { struct bftw_dir *current = state->current; size_t length; if (current->depth == 0) { // Use exactly the string passed to bftw(), including any // trailing slashes length = strlen(state->root); } else { length = current->nameoff + current->namelen; if (current->namelen > 1) { // Trim the trailing slash --length; state->previous = current->parent; } } dstresize(&state->path, length); if (state->status == BFTW_CHILD) { state->status = BFTW_CURRENT; } } /** * Open the current directory. */ static int bftw_opendir(struct bftw_state *state) { assert(!state->dir); state->dir = bftw_dir_opendir(&state->cache, state->current, state->path); if (state->dir) { return 0; } else { return 1; } } /** * Close the current directory. */ static int bftw_closedir(struct bftw_state *state) { DIR *dir = state->dir; state->dir = NULL; if (dir) { return closedir(dir); } else { return 0; } } /** * Record an error. */ static void bftw_set_error(struct bftw_state *state, int error) { state->ftwbuf.error = error; state->ftwbuf.typeflag = BFTW_ERROR; if (!(state->flags & BFTW_RECOVER)) { state->error = error; } } /** * Initialize the buffers with data about the current path. */ static void bftw_init_buffers(struct bftw_state *state, const struct dirent *de) { struct BFTW *ftwbuf = &state->ftwbuf; ftwbuf->path = state->path; ftwbuf->root = state->root; ftwbuf->error = 0; ftwbuf->visit = (state->status == BFTW_GC ? BFTW_POST : BFTW_PRE); ftwbuf->statbuf = NULL; ftwbuf->at_fd = AT_FDCWD; ftwbuf->at_path = ftwbuf->path; struct bftw_dir *current = state->current; if (current) { ftwbuf->nameoff = current->nameoff; ftwbuf->depth = current->depth; if (state->status == BFTW_CHILD) { ftwbuf->nameoff += current->namelen; ++ftwbuf->depth; ftwbuf->at_fd = current->fd; ftwbuf->at_path += ftwbuf->nameoff; } else { bftw_dir_base(current, &ftwbuf->at_fd, &ftwbuf->at_path); } } else { ftwbuf->depth = 0; } if (ftwbuf->depth == 0) { // Compute the name offset for root paths like "foo/bar" ftwbuf->nameoff = xbasename(ftwbuf->path) - ftwbuf->path; } ftwbuf->typeflag = BFTW_UNKNOWN; if (de) { ftwbuf->typeflag = dirent_to_typeflag(de); } else if (state->status != BFTW_CHILD) { ftwbuf->typeflag = BFTW_DIR; } int follow_flags = BFTW_LOGICAL; if (ftwbuf->depth == 0) { follow_flags |= BFTW_COMFOLLOW; } bool follow = state->flags & follow_flags; ftwbuf->at_flags = follow ? 0 : AT_SYMLINK_NOFOLLOW; bool detect_cycles = (state->flags & BFTW_DETECT_CYCLES) && state->status == BFTW_CHILD; bool xdev = state->flags & BFTW_XDEV; if ((state->flags & BFTW_STAT) || ftwbuf->typeflag == BFTW_UNKNOWN || (ftwbuf->typeflag == BFTW_LNK && follow) || (ftwbuf->typeflag == BFTW_DIR && (detect_cycles || xdev))) { if (ftwbuf_stat(ftwbuf, &state->statbuf) != 0) { bftw_set_error(state, errno); return; } if (ftwbuf->typeflag == BFTW_DIR && detect_cycles) { dev_t dev = ftwbuf->statbuf->dev; ino_t ino = ftwbuf->statbuf->ino; for (const struct bftw_dir *dir = current; dir; dir = dir->parent) { if (dev == dir->dev && ino == dir->ino) { bftw_set_error(state, ELOOP); return; } } } } } /** internal action: Abort the traversal. */ #define BFTW_FAIL (-1) /** * Invoke the callback on the current file. */ static int bftw_handle_path(struct bftw_state *state) { // Never give the callback BFTW_ERROR unless BFTW_RECOVER is specified if (state->ftwbuf.typeflag == BFTW_ERROR && !(state->flags & BFTW_RECOVER)) { return BFTW_FAIL; } // Defensive copy struct BFTW ftwbuf = state->ftwbuf; enum bftw_action action = state->fn(&ftwbuf, state->ptr); switch (action) { case BFTW_CONTINUE: case BFTW_SKIP_SIBLINGS: case BFTW_SKIP_SUBTREE: case BFTW_STOP: return action; default: state->error = EINVAL; return BFTW_FAIL; } } /** * Create a new bftw_dir for the current file. */ static struct bftw_dir *bftw_add(struct bftw_state *state, const char *name) { struct bftw_dir *dir = bftw_dir_new(&state->cache, state->current, name); if (!dir) { return NULL; } const struct bfs_stat *statbuf = state->ftwbuf.statbuf; if (statbuf) { dir->dev = statbuf->dev; dir->ino = statbuf->ino; } return dir; } /** * Garbage-collect a bftw_dir. */ static int bftw_gc(struct bftw_state *state, struct bftw_dir *dir, bool invoke_callback) { int ret = BFTW_CONTINUE; if (!(state->flags & BFTW_DEPTH)) { invoke_callback = false; } if (invoke_callback) { if (bftw_build_path(state) != 0) { ret = BFTW_FAIL; invoke_callback = false; } } state->status = BFTW_GC; while (dir) { bftw_dir_decref(&state->cache, dir); if (dir->refcount > 0) { break; } if (invoke_callback) { state->current = dir; bftw_path_trim(state); bftw_init_buffers(state, NULL); int action = bftw_handle_path(state); switch (action) { case BFTW_CONTINUE: case BFTW_SKIP_SIBLINGS: case BFTW_SKIP_SUBTREE: break; case BFTW_STOP: case BFTW_FAIL: ret = action; invoke_callback = false; break; } } struct bftw_dir *parent = dir->parent; bftw_dir_free(&state->cache, dir); dir = parent; } state->previous = dir; return ret; } /** * Push a new directory onto the queue. */ static int bftw_push(struct bftw_state *state, const char *name) { struct bftw_dir *dir = bftw_add(state, name); if (!dir) { return -1; } if (bftw_queue_push(&state->queue, dir) != 0) { state->error = errno; bftw_gc(state, dir, false); return -1; } return 0; } /** * Pop a directory off the queue. */ static int bftw_pop(struct bftw_state *state, bool invoke_callback) { int ret = bftw_gc(state, state->current, invoke_callback); state->current = bftw_queue_pop(&state->queue); state->status = BFTW_CURRENT; return ret; } /** * Dispose of the bftw() state. */ static void bftw_state_destroy(struct bftw_state *state) { bftw_closedir(state); while (state->current) { bftw_pop(state, false); } bftw_queue_destroy(&state->queue); bftw_cache_destroy(&state->cache); dstrfree(state->path); } int bftw(const char *path, bftw_fn *fn, int nopenfd, enum bftw_flags flags, void *ptr) { int ret = -1, error; struct bftw_state state; if (bftw_state_init(&state, path, fn, nopenfd, flags, ptr) != 0) { return -1; } // Handle 'path' itself first if (bftw_path_concat(&state, path) != 0) { goto fail; } bftw_init_buffers(&state, NULL); switch (bftw_handle_path(&state)) { case BFTW_CONTINUE: case BFTW_SKIP_SIBLINGS: break; case BFTW_SKIP_SUBTREE: case BFTW_STOP: goto done; case BFTW_FAIL: goto fail; } if (state.ftwbuf.typeflag != BFTW_DIR) { goto done; } // Now start the breadth-first search state.current = bftw_add(&state, path); if (!state.current) { goto fail; } do { if (bftw_build_path(&state) != 0) { goto fail; } if (bftw_opendir(&state) != 0) { goto dir_error; } while (true) { struct dirent *de; if (xreaddir(state.dir, &de) != 0) { goto dir_error; } if (!de) { break; } const char *name = de->d_name; if (name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'))) { continue; } if (bftw_path_concat(&state, name) != 0) { goto fail; } bftw_init_buffers(&state, de); switch (bftw_handle_path(&state)) { case BFTW_CONTINUE: break; case BFTW_SKIP_SIBLINGS: goto next; case BFTW_SKIP_SUBTREE: continue; case BFTW_STOP: goto done; case BFTW_FAIL: goto fail; } if (state.ftwbuf.typeflag == BFTW_DIR) { const struct bfs_stat *statbuf = state.ftwbuf.statbuf; if ((flags & BFTW_XDEV) && statbuf && statbuf->dev != state.current->dev) { continue; } if (bftw_push(&state, name) != 0) { goto fail; } } } next: if (bftw_closedir(&state) != 0) { goto dir_error; } switch (bftw_pop(&state, true)) { case BFTW_CONTINUE: case BFTW_SKIP_SIBLINGS: case BFTW_SKIP_SUBTREE: break; case BFTW_STOP: goto done; case BFTW_FAIL: goto fail; } continue; dir_error: error = errno; bftw_closedir(&state); bftw_path_trim(&state); bftw_init_buffers(&state, NULL); bftw_set_error(&state, error); switch (bftw_handle_path(&state)) { case BFTW_CONTINUE: case BFTW_SKIP_SIBLINGS: case BFTW_SKIP_SUBTREE: goto next; case BFTW_STOP: goto done; case BFTW_FAIL: goto fail; } } while (state.current); done: if (state.error == 0) { ret = 0; } fail: if (state.error == 0) { state.error = errno; } bftw_state_destroy(&state); errno = state.error; return ret; } bfs-1.2.1/bftw.h000066400000000000000000000111451323715747000134130ustar00rootroot00000000000000/**************************************************************************** * bfs * * Copyright (C) 2015-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. * ****************************************************************************/ #ifndef BFS_BFTW_H #define BFS_BFTW_H #include "stat.h" #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, }; /** * Possible visit occurrences. */ enum bftw_visit { /** Pre-order visit. */ BFTW_PRE, /** Post-order visit. */ BFTW_POST, }; /** * 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 bfs_stat() buffer; may be NULL if no stat() call was needed. */ const struct bfs_stat *statbuf; /** A parent file descriptor for the *at() family of calls. */ int at_fd; /** The path relative to atfd for the *at() family of calls. */ const char *at_path; /** Appropriate flags (such as AT_SYMLINK_NOFOLLOW) for the *at() family of calls. */ int at_flags; }; /** * Walk actions returned by the bftw() callback. */ enum bftw_action { /** Keep walking. */ BFTW_CONTINUE, /** Skip this path's siblings. */ BFTW_SKIP_SIBLINGS, /** Skip this path's children. */ BFTW_SKIP_SUBTREE, /** 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_fn(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, /** Stay on the same filesystem. */ BFTW_XDEV = 1 << 6, }; /** * 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. However, bftw() operates * breadth-first. * * @param path * The starting path. * @param fn * The callback to invoke. * @param nopenfd * The maximum number of file descriptors to keep open. * @param flags * Flags that control bftw() behavior. * @param ptr * A generic pointer which is passed to fn(). * @return * 0 on success, or -1 on failure. */ int bftw(const char *path, bftw_fn *fn, int nopenfd, enum bftw_flags flags, void *ptr); #endif // BFS_BFTW_H bfs-1.2.1/cmdline.h000066400000000000000000000066051323715747000140710ustar00rootroot00000000000000/**************************************************************************** * bfs * * Copyright (C) 2015-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. * ****************************************************************************/ #ifndef BFS_CMDLINE_H #define BFS_CMDLINE_H #include "color.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, }; /** * A root path to explore. */ struct root { /** The root path itself. */ const char *path; /** The next path in the list. */ struct root *next; }; /** * An open file for the command line. */ struct open_file; /** * The parsed command line. */ struct cmdline { /** The unparsed command line arguments. */ char **argv; /** The list of root paths. */ struct root *roots; /** Color data. */ struct colors *colors; /** Colored stdout. */ CFILE *cout; /** Colored stderr. */ CFILE *cerr; /** Table of mounted file systems. */ struct bfs_mtab *mtab; /** -mindepth option. */ int mindepth; /** -maxdepth option. */ int maxdepth; /** bftw() flags. */ enum bftw_flags flags; /** 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; /** The command line expression. */ struct expr *expr; /** All the open files owned by the command line. */ struct open_file *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.2.1/color.c000066400000000000000000000273211323715747000135650ustar00rootroot00000000000000/**************************************************************************** * bfs * * Copyright (C) 2015-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. * ****************************************************************************/ #include "color.h" #include "bftw.h" #include "stat.h" #include "util.h" #include #include #include #include #include #include #include #include #include struct ext_color { const char *ext; size_t len; const char *color; struct ext_color *next; }; struct colors { const char *reset; const char *bold; const char *gray; const char *red; const char *green; const char *yellow; const char *blue; const char *magenta; const char *cyan; const char *normal; const char *file; const char *dir; const char *link; const char *multi_hard; const char *pipe; const char *door; const char *block; const char *chardev; const char *orphan; const char *missing; const char *socket; const char *setuid; const char *setgid; const char *capable; const char *sticky_ow; const char *ow; const char *sticky; const char *exec; const char *warning; const char *error; struct ext_color *ext_list; char *data; }; struct color_name { const char *name; size_t offset; }; #define COLOR_NAME(name, field) {name, offsetof(struct colors, field)} static const struct color_name color_names[] = { COLOR_NAME("bd", block), COLOR_NAME("bld", bold), COLOR_NAME("blu", blue), COLOR_NAME("ca", capable), COLOR_NAME("cd", chardev), COLOR_NAME("cyn", cyan), COLOR_NAME("di", dir), COLOR_NAME("do", door), COLOR_NAME("er", error), COLOR_NAME("ex", exec), COLOR_NAME("fi", file), COLOR_NAME("grn", green), COLOR_NAME("gry", gray), COLOR_NAME("ln", link), COLOR_NAME("mag", magenta), COLOR_NAME("mh", multi_hard), COLOR_NAME("mi", missing), COLOR_NAME("no", normal), COLOR_NAME("or", orphan), COLOR_NAME("ow", ow), COLOR_NAME("pi", pipe), COLOR_NAME("red", red), COLOR_NAME("rs", reset), COLOR_NAME("sg", setgid), COLOR_NAME("so", socket), COLOR_NAME("st", sticky), COLOR_NAME("su", setuid), COLOR_NAME("tw", sticky_ow), COLOR_NAME("wr", warning), COLOR_NAME("ylw", yellow), {0}, }; static const char **look_up_color(const struct colors *colors, const char *name) { for (const struct color_name *entry = color_names; entry->name; ++entry) { if (strcmp(name, entry->name) == 0) { return (const char **)((char *)colors + entry->offset); } } return NULL; } static const char *get_color(const struct colors *colors, const char *name) { const char **color = look_up_color(colors, name); if (color) { return *color; } else { return NULL; } } static void set_color(struct colors *colors, const char *name, const char *value) { const char **color = look_up_color(colors, name); if (color) { *color = value; } } struct colors *parse_colors(const char *ls_colors) { struct colors *colors = malloc(sizeof(struct colors)); if (!colors) { goto done; } // From man console_codes colors->reset = "0"; colors->bold = "01"; colors->gray = "01;30"; colors->red = "01;31"; colors->green = "01;32"; colors->yellow = "01;33"; colors->blue = "01;34"; colors->magenta = "01;35"; colors->cyan = "01;36"; // Defaults generated by dircolors --print-database colors->normal = NULL; colors->file = NULL; colors->dir = "01;34"; colors->link = "01;36"; colors->multi_hard = NULL; colors->pipe = "40;33"; colors->socket = "01;35"; colors->door = "01;35"; colors->block = "40;33;01"; colors->chardev = "40;33;01"; colors->orphan = "40;31;01"; colors->setuid = "37;41"; colors->setgid = "30;43"; colors->capable = "30;41"; colors->sticky_ow = "30;42"; colors->ow = "34;42"; colors->sticky = "37;44"; colors->exec = "01;32"; colors->warning = "40;33;01"; colors->error = "40;31;01"; colors->ext_list = NULL; colors->data = NULL; if (ls_colors) { colors->data = strdup(ls_colors); } if (!colors->data) { goto done; } char *start = colors->data; char *end; struct ext_color *ext; for (end = strchr(start, ':'); *start && end; start = end + 1, end = strchr(start, ':')) { char *equals = strchr(start, '='); if (!equals) { continue; } *equals = '\0'; *end = '\0'; const char *key = start; const char *value = equals + 1; // Ignore all-zero values if (strspn(value, "0") == strlen(value)) { continue; } if (key[0] == '*') { ext = malloc(sizeof(struct ext_color)); if (ext) { ext->ext = key + 1; ext->len = strlen(ext->ext); ext->color = value; ext->next = colors->ext_list; colors->ext_list = ext; } } else { set_color(colors, key, value); } } done: return colors; } void free_colors(struct colors *colors) { if (colors) { struct ext_color *ext = colors->ext_list; while (ext) { struct ext_color *saved = ext; ext = ext->next; free(saved); } free(colors->data); 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; } static const char *file_color(const struct colors *colors, const char *filename, const struct BFTW *ftwbuf) { const struct bfs_stat *sb = ftwbuf->statbuf; if (!sb) { return colors->orphan; } const char *color = NULL; switch (sb->mode & S_IFMT) { case S_IFREG: if (sb->mode & S_ISUID) { color = colors->setuid; } else if (sb->mode & S_ISGID) { color = colors->setgid; } else if (sb->mode & 0111) { color = colors->exec; } if (!color && sb->nlink > 1) { color = colors->multi_hard; } if (!color) { size_t namelen = strlen(filename); for (struct ext_color *ext = colors->ext_list; ext; ext = ext->next) { if (namelen >= ext->len && memcmp(filename + namelen - ext->len, ext->ext, ext->len) == 0) { color = ext->color; break; } } } if (!color) { color = colors->file; } break; case S_IFDIR: if (sb->mode & S_ISVTX) { if (sb->mode & S_IWOTH) { color = colors->sticky_ow; } else { color = colors->sticky; } } else if (sb->mode & S_IWOTH) { color = colors->ow; } if (!color) { color = colors->dir; } break; case S_IFLNK: if (xfaccessat(ftwbuf->at_fd, ftwbuf->at_path, F_OK) == 0) { color = colors->link; } else { color = colors->orphan; } break; case S_IFBLK: color = colors->block; break; case S_IFCHR: color = colors->chardev; break; case S_IFIFO: color = colors->pipe; break; case S_IFSOCK: color = colors->socket; break; #ifdef S_IFDOOR case S_IFDOOR: color = colors->door; break; #endif } if (!color) { color = colors->normal; } return color; } static int print_esc(const char *esc, FILE *file) { if (fputs("\033[", file) == EOF) { return -1; } if (fputs(esc, file) == EOF) { return -1; } if (fputs("m", file) == EOF) { return -1; } return 0; } static int print_path(CFILE *cfile, const struct BFTW *ftwbuf) { const struct colors *colors = cfile->colors; FILE *file = cfile->file; const char *path = ftwbuf->path; if (!colors) { return fputs(path, file) == EOF ? -1 : 0; } const char *filename = path + ftwbuf->nameoff; if (colors->dir) { if (print_esc(colors->dir, file) != 0) { return -1; } } if (fwrite(path, 1, ftwbuf->nameoff, file) != ftwbuf->nameoff) { return -1; } if (colors->dir) { if (print_esc(colors->reset, file) != 0) { return -1; } } const char *color = file_color(colors, filename, ftwbuf); if (color) { if (print_esc(color, file) != 0) { return -1; } } if (fputs(filename, file) == EOF) { return -1; } if (color) { if (print_esc(colors->reset, file) != 0) { return -1; } } return 0; } static int print_link(CFILE *cfile, const struct BFTW *ftwbuf) { int ret = -1; char *target = xreadlinkat(ftwbuf->at_fd, ftwbuf->at_path, 0); if (!target) { goto done; } struct BFTW altbuf = *ftwbuf; altbuf.path = target; altbuf.nameoff = xbasename(target) - target; struct bfs_stat statbuf; if (bfs_stat(ftwbuf->at_fd, ftwbuf->at_path, 0, 0, &statbuf) == 0) { altbuf.statbuf = &statbuf; } else { altbuf.statbuf = NULL; } ret = print_path(cfile, &altbuf); done: free(target); return ret; } int cfprintf(CFILE *cfile, const char *format, ...) { const struct colors *colors = cfile->colors; FILE *file = cfile->file; int ret = -1; int error = errno; va_list args; va_start(args, format); for (const char *i = format; *i; ++i) { const char *percent = strchr(i, '%'); if (!percent) { if (fputs(i, file) == EOF) { goto done; } break; } size_t len = percent - i; if (fwrite(i, 1, len, file) != len) { goto done; } i = percent + 1; switch (*i) { case '%': if (fputc('%', file) == EOF) { goto done; } break; case 'c': if (fputc(va_arg(args, int), file) == EOF) { goto done; } break; case 'd': if (fprintf(file, "%d", va_arg(args, int)) < 0) { goto done; } break; case 'g': if (fprintf(file, "%g", va_arg(args, double)) < 0) { goto done; } break; case 's': if (fputs(va_arg(args, const char *), file) == EOF) { goto done; } break; case 'z': ++i; if (*i != 'u') { goto invalid; } if (fprintf(file, "%zu", va_arg(args, size_t)) < 0) { goto done; } break; case 'm': if (fputs(strerror(error), file) == EOF) { goto done; } break; case 'P': if (print_path(cfile, va_arg(args, const struct BFTW *)) != 0) { goto done; } break; case 'L': if (print_link(cfile, va_arg(args, const struct BFTW *)) != 0) { goto done; } 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'; const char *esc = get_color(colors, name); if (!esc) { goto invalid; } if (print_esc(esc, file) != 0) { goto done; } i = end; break; } default: invalid: assert(false); errno = EINVAL; goto done; } } ret = 0; done: va_end(args); return ret; } bfs-1.2.1/color.h000066400000000000000000000063661323715747000136000ustar00rootroot00000000000000/**************************************************************************** * bfs * * Copyright (C) 2015-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. * ****************************************************************************/ #ifndef BFS_COLOR_H #define BFS_COLOR_H #include "bftw.h" #include #include /** * A lookup table for colors. */ struct colors; /** * Parse a color table. * * @param ls_color * 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: * * %%: A literal '%' * %c: A single character * %d: An integer * %g: A double * %s: A string * %zu: A size_t * %m: strerror(errno) * %P: A colored file path, from a const struct BFTW * argument * %L: A colored link target, from a const struct BFTW * argument * %{cc}: Change the color to 'cc' * @return 0 on success, -1 on failure. */ int cfprintf(CFILE *cfile, const char *format, ...); #endif // BFS_COLOR_H bfs-1.2.1/dstring.c000066400000000000000000000056531323715747000141250ustar00rootroot00000000000000/**************************************************************************** * bfs * * Copyright (C) 2016-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. * ****************************************************************************/ #include "dstring.h" #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[]; }; static struct dstring *dstrheader(const char *dstr) { return (struct dstring *)(dstr - offsetof(struct dstring, data)); } static size_t dstrsize(size_t capacity) { return sizeof(struct dstring) + capacity + 1; } char *dstralloc(size_t capacity) { struct dstring *header = malloc(dstrsize(capacity)); if (!header) { return NULL; } header->capacity = capacity; header->length = 0; return header->data; } 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; } 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); } void dstrfree(char *dstr) { if (dstr) { free(dstrheader(dstr)); } } bfs-1.2.1/dstring.h000066400000000000000000000056311323715747000141260ustar00rootroot00000000000000/**************************************************************************** * bfs * * Copyright (C) 2016-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. * ****************************************************************************/ #ifndef BFS_DSTRING_H #define BFS_DSTRING_H #include /** * Allocate a dynamic string. * * @param capacity * The initial capacity of the string. */ char *dstralloc(size_t capacity); /** * 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); /** * Free a dynamic string. * * @param dstr * The string to free. */ void dstrfree(char *dstr); #endif // BFS_DSTRING_H bfs-1.2.1/eval.c000066400000000000000000000653501323715747000134020ustar00rootroot00000000000000/**************************************************************************** * bfs * * Copyright (C) 2015-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. * ****************************************************************************/ #include "eval.h" #include "bftw.h" #include "cmdline.h" #include "color.h" #include "dstring.h" #include "exec.h" #include "mtab.h" #include "printf.h" #include "stat.h" #include "util.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct eval_state { /** Data about the current file. */ 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; /** A stat() buffer, if necessary. */ struct bfs_stat statbuf; /** A bfs_stat() buffer, if necessary. */ struct bfs_stat bfs_statbuf; }; /** * 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_error(struct eval_state *state) { if (!eval_should_ignore(state, errno)) { cfprintf(state->cmdline->cerr, "%{er}error: '%s': %m%{rs}\n", state->ftwbuf->path); *state->ret = EXIT_FAILURE; } } /** * Perform a bfs_stat() call if necessary. */ static const struct bfs_stat *eval_bfs_stat(struct eval_state *state) { struct BFTW *ftwbuf = state->ftwbuf; if (!ftwbuf->statbuf) { if (bfs_stat(ftwbuf->at_fd, ftwbuf->at_path, ftwbuf->at_flags, BFS_STAT_BROKEN_OK, &state->bfs_statbuf) == 0) { ftwbuf->statbuf = &state->bfs_statbuf; } else { eval_error(state); } } return ftwbuf->statbuf; } /** * 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) { struct BFTW *ftwbuf = state->ftwbuf; return xfaccessat(ftwbuf->at_fd, ftwbuf->at_path, expr->idata) == 0; } /** * Get the given timespec field out of a stat buffer. */ static const struct timespec *eval_stat_time(const struct expr *expr, struct eval_state *state) { const struct bfs_stat *statbuf = eval_bfs_stat(state); if (!statbuf) { return NULL; } if (!(statbuf->mask & expr->stat_field)) { assert(expr->stat_field == BFS_STAT_BTIME); cfprintf(state->cmdline->cerr, "%{er}error: '%s': Couldn't get file birth time.%{rs}\n", state->ftwbuf->path); *state->ret = EXIT_FAILURE; return NULL; } switch (expr->stat_field) { case BFS_STAT_ATIME: return &statbuf->atime; case BFS_STAT_BTIME: return &statbuf->btime; case BFS_STAT_CTIME: return &statbuf->ctime; case BFS_STAT_MTIME: return &statbuf->mtime; default: assert(false); return NULL; } } /** * -[aBcm]?newer tests. */ bool eval_newer(const struct expr *expr, struct eval_state *state) { const struct timespec *time = eval_stat_time(expr, 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 timespec *time = eval_stat_time(expr, 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_bfs_stat(state); if (!statbuf) { return false; } time_t diff = timespec_diff(&statbuf->atime, &statbuf->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_bfs_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_bfs_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_bfs_stat(state); if (!statbuf) { return false; } return getgrgid(statbuf->gid) == NULL; } /** * -nouser test. */ bool eval_nouser(const struct expr *expr, struct eval_state *state) { const struct bfs_stat *statbuf = eval_bfs_stat(state); if (!statbuf) { return false; } return getpwuid(statbuf->uid) == NULL; } /** * -delete action. */ bool eval_delete(const struct expr *expr, struct eval_state *state) { struct BFTW *ftwbuf = state->ftwbuf; // Don't try to delete the current directory if (strcmp(ftwbuf->path, ".") == 0) { return true; } int flag = 0; if (ftwbuf->typeflag == BFTW_DIR) { flag |= AT_REMOVEDIR; } if (unlinkat(ftwbuf->at_fd, ftwbuf->at_path, flag) != 0) { eval_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) { cfprintf(cmdline->cerr, "%{er}error: %s %s: %m%{rs}\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) { cfprintf(state->cmdline->cerr, "%{er}error: %s %s: %m%{rs}\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; 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_error(state); goto done; } DIR *dir = fdopendir(dfd); if (!dir) { eval_error(state); close(dfd); goto done; } ret = true; while (true) { struct dirent *de; if (xreaddir(dir, &de) != 0) { eval_error(state); goto done_dir; } if (!de) { break; } if (strcmp(de->d_name, ".") != 0 && strcmp(de->d_name, "..") != 0) { ret = false; break; } } done_dir: closedir(dir); } else { const struct bfs_stat *statbuf = eval_bfs_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_bfs_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) { 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_bfs_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_bfs_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; struct BFTW *ftwbuf = state->ftwbuf; if (ftwbuf->typeflag != BFTW_LNK) { goto done; } const struct bfs_stat *statbuf = eval_bfs_stat(state); if (!statbuf) { goto done; } name = xreadlinkat(ftwbuf->at_fd, ftwbuf->at_path, statbuf->size); if (!name) { eval_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) { 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_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) { 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_bfs_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_bfs_stat(state); if (!statbuf) { goto done; } uintmax_t ino = statbuf->ino; uintmax_t blocks = (statbuf->blocks + 1)/2; char mode[11]; format_mode(statbuf->mode, mode); uintmax_t nlink = statbuf->nlink; if (fprintf(file, "%9ju %6ju %s %3ju ", ino, blocks, mode, 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; } } 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, " %P", ftwbuf) < 0) { goto error; } if (ftwbuf->typeflag == BFTW_LNK) { if (cfprintf(cfile, " -> %L", ftwbuf) < 0) { goto error; } } if (fputc('\n', file) == EOF) { goto error; } done: return true; error: eval_error(state); return true; } /** * -f?print action. */ bool eval_fprint(const struct expr *expr, struct eval_state *state) { CFILE *cfile = expr->cfile; if (cfile->colors) { eval_bfs_stat(state); } if (cfprintf(cfile, "%P\n", state->ftwbuf) < 0) { eval_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_error(state); } return true; } /** * -f?printf action. */ bool eval_fprintf(const struct expr *expr, struct eval_state *state) { if (expr->printf->needs_stat) { if (!eval_bfs_stat(state)) { goto done; } } if (bfs_printf(expr->cfile->file, expr->printf, state->ftwbuf) != 0) { eval_error(state); } done: 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_error(state); return true; } /** * -prune action. */ bool eval_prune(const struct expr *expr, struct eval_state *state) { state->action = BFTW_SKIP_SUBTREE; 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) { cfprintf(state->cmdline->cerr, "%{er}error: '%s': %s%{rs}\n", path, 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_bfs_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_bfs_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_bfs_stat(state); if (!statbuf) { return false; } blkcnt_t expected = (statbuf->size + 511)/512; return statbuf->blocks < expected; } /** * -type test. */ bool eval_type(const struct expr *expr, struct eval_state *state) { return state->ftwbuf->typeflag & expr->idata; } /** * -xtype test. */ bool eval_xtype(const struct expr *expr, struct eval_state *state) { struct BFTW *ftwbuf = state->ftwbuf; bool follow = !(ftwbuf->at_flags & AT_SYMLINK_NOFOLLOW); bool is_link = ftwbuf->typeflag == BFTW_LNK; if (follow == is_link) { return eval_type(expr, state); } // -xtype does the opposite of everything else int at_flags = ftwbuf->at_flags ^ AT_SYMLINK_NOFOLLOW; struct bfs_stat sb; if (bfs_stat(ftwbuf->at_fd, ftwbuf->at_path, at_flags, 0, &sb) != 0) { if (!follow && is_nonexistence_error(errno)) { // Broken symlink return eval_type(expr, state); } else { eval_error(state); return false; } } return mode_to_typeflag(sb.mode) & 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); } /** * Debug stat() calls. */ static void debug_stat(const struct eval_state *state) { struct BFTW *ftwbuf = state->ftwbuf; fprintf(stderr, "fstatat("); 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); if (ftwbuf->at_flags == AT_SYMLINK_NOFOLLOW) { fprintf(stderr, "AT_SYMLINK_NOFOLLOW"); } else { fprintf(stderr, "%d", ftwbuf->at_flags); } fprintf(stderr, ")\n"); } /** * 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_SKIP_SIBLINGS), DUMP_BFTW_MAP(BFTW_SKIP_SUBTREE), 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; /** Eventual return value from eval_cmdline(). */ int ret; /** Whether to quit immediately. */ bool quit; }; /** * bftw() callback. */ static enum bftw_action cmdline_callback(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 = &args->quit; if (ftwbuf->typeflag == BFTW_ERROR) { if (!eval_should_ignore(&state, ftwbuf->error)) { args->ret = EXIT_FAILURE; cfprintf(cmdline->cerr, "%{er}error: '%s': %m%{rs}\n", ftwbuf->path); } state.action = BFTW_SKIP_SUBTREE; goto done; } if (cmdline->xargs_safe && strpbrk(ftwbuf->path, " \t\n\'\"\\")) { args->ret = EXIT_FAILURE; cfprintf(cmdline->cerr, "%{er}error: '%s': Path is not safe for xargs.%{rs}\n", ftwbuf->path); state.action = BFTW_SKIP_SUBTREE; goto done; } if (cmdline->maxdepth < 0 || ftwbuf->depth >= cmdline->maxdepth) { state.action = BFTW_SKIP_SUBTREE; } // In -depth mode, only handle directories on the BFTW_POST visit enum bftw_visit expected_visit = BFTW_PRE; if ((cmdline->flags & BFTW_DEPTH) && 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) && ftwbuf->statbuf) { debug_stat(&state); } if (cmdline->debug & DEBUG_SEARCH) { fprintf(stderr, "cmdline_callback({ " ".path = \"%s\", " ".depth = %zu, " ".visit = %s, " ".typeflag = %s, " ".error = %d " "}) == %s\n", ftwbuf->path, ftwbuf->depth, dump_bftw_visit(ftwbuf->visit), dump_bftw_typeflag(ftwbuf->typeflag), ftwbuf->error, 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; while (true) { struct dirent *de; if (xreaddir(dir, &de) != 0 || !de) { break; } if (strcmp(de->d_name, ".") != 0 && strcmp(de->d_name, "..") != 0) { ++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) { #define DUMP_BFTW_FLAG(flag) \ if (flags & flag) { \ fputs(#flag, stderr); \ flags ^= flag; \ if (flags) { \ fputs(" | ", stderr); \ } \ } DUMP_BFTW_FLAG(BFTW_STAT); DUMP_BFTW_FLAG(BFTW_RECOVER); DUMP_BFTW_FLAG(BFTW_DEPTH); DUMP_BFTW_FLAG(BFTW_COMFOLLOW); DUMP_BFTW_FLAG(BFTW_LOGICAL); DUMP_BFTW_FLAG(BFTW_DETECT_CYCLES); DUMP_BFTW_FLAG(BFTW_XDEV); assert(!flags); } /** * Evaluate the command line. */ int eval_cmdline(const struct cmdline *cmdline) { if (!cmdline->expr) { return EXIT_SUCCESS; } int nopenfd = infer_fdlimit(cmdline); struct callback_args args = { .cmdline = cmdline, .ret = EXIT_SUCCESS, .quit = false, }; for (struct root *root = cmdline->roots; root && !args.quit; root = root->next) { if (cmdline->debug & DEBUG_SEARCH) { fprintf(stderr, "bftw(\"%s\", cmdline_callback, %d, ", root->path, nopenfd); dump_bftw_flags(cmdline->flags); fprintf(stderr, ", &args)\n"); } if (bftw(root->path, cmdline_callback, nopenfd, cmdline->flags, &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); } return args.ret; } bfs-1.2.1/eval.h000066400000000000000000000100161323715747000133740ustar00rootroot00000000000000/**************************************************************************** * bfs * * Copyright (C) 2015-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. * ****************************************************************************/ #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_perm(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.2.1/exec.c000066400000000000000000000355411323715747000133760ustar00rootroot00000000000000/**************************************************************************** * 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. * ****************************************************************************/ #include "exec.h" #include "bftw.h" #include "cmdline.h" #include "color.h" #include "dstring.h" #include "util.h" #include #include #include #include #include #include #include #include #include #include #include /** Print some debugging info. */ 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, // and allow two pages of headroom to account for rounding as well as // any other data we may not be counting long page_size = sysconf(_SC_PAGESIZE); if (page_size < 4096) { page_size = 4096; } arg_max -= 2*page_size; 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) { CFILE *cerr = cmdline->cerr; 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) { cfprintf(cerr, "%{er}error: %s: Expected '... ;'.%{rs}\n", argv[0]); } else { cfprintf(cerr, "%{er}error: %s: Expected '... ;' or '... {} +'.%{rs}\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; 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, "{}")) { cfprintf(cerr, "%{er}error: %s ... +: Only one '{}' is supported.%{rs}\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) { 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); } // Use a pipe to report errors from the child int pipefd[2] = {-1, -1}; if (pipe_cloexec(pipefd) != 0) { bfs_exec_debug(execbuf, "pipe() failed: %s\n", strerror(errno)); } pid_t pid = fork(); if (pid < 0) { close(pipefd[1]); close(pipefd[0]); return -1; } else if (pid > 0) { // Parent close(pipefd[1]); int error; ssize_t nbytes = read(pipefd[0], &error, sizeof(error)); close(pipefd[0]); if (nbytes == sizeof(error)) { errno = error; return -1; } int status; if (waitpid(pid, &status, 0) < 0) { return -1; } errno = 0; if (WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS) { return 0; } else { return -1; } } else { // Child close(pipefd[0]); if (execbuf->wd_fd >= 0) { if (fchdir(execbuf->wd_fd) != 0) { goto child_err; } } execvp(execbuf->argv[0], execbuf->argv); int error; child_err: error = errno; if (write(pipefd[1], &error, sizeof(error)) != sizeof(error)) { // Parent will still see that we exited unsuccessfully, but won't know why } close(pipefd[1]); _Exit(EXIT_FAILURE); } } /** 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; size_t orig_argc = execbuf->argc; while (bfs_exec_args_remain(execbuf)) { execbuf->argv[execbuf->argc] = NULL; ret = bfs_exec_spawn(execbuf); if (ret == 0 || errno != 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; int error = errno; for (size_t i = execbuf->tmpl_argc - 1; i < new_argc; ++i) { free(execbuf->argv[i]); } execbuf->argc = execbuf->tmpl_argc - 1; if (new_argc < orig_argc) { // 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->argc; } else { free(execbuf->argv[i]); } } execbuf->arg_max = execbuf->arg_size; bfs_exec_debug(execbuf, "ARG_MAX: %zu\n", execbuf->arg_max); } execbuf->arg_size = 0; 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); } } 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.2.1/exec.h000066400000000000000000000070661323715747000134040ustar00rootroot00000000000000/**************************************************************************** * 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. * ****************************************************************************/ #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.2.1/expr.h000066400000000000000000000124011323715747000134230ustar00rootroot00000000000000/**************************************************************************** * bfs * * Copyright (C) 2015-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. * ****************************************************************************/ #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.2.1/main.c000066400000000000000000000041131323715747000133650ustar00rootroot00000000000000/**************************************************************************** * bfs * * Copyright (C) 2015-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. * ****************************************************************************/ #include "cmdline.h" #include "util.h" #include #include #include #include #include #include /** * Ensure that a file descriptor is open. */ static int ensure_fd_open(int fd, int flags) { if (isopen(fd)) { return 0; } else if (redirect(fd, "/dev/null", flags) >= 0) { return 0; } else { return -1; } } /** * bfs entry point. */ int main(int argc, char *argv[]) { int ret = EXIT_FAILURE; if (ensure_fd_open(STDIN_FILENO, O_RDONLY) != 0) { goto done; } if (ensure_fd_open(STDOUT_FILENO, O_WRONLY) != 0) { goto done; } if (ensure_fd_open(STDERR_FILENO, O_WRONLY) != 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.2.1/mtab.c000066400000000000000000000117461323715747000133760ustar00rootroot00000000000000/**************************************************************************** * 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. * ****************************************************************************/ #include "mtab.h" #include "util.h" #include #include #include #include #include #include #include #ifndef __has_include # define __has_include(header) 0 #endif #if __GLIBC__ || __has_include() # 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 mount table. */ struct bfs_mtab_entry { /** The device number for this mount point. */ dev_t dev; /** The file system type of this mount point. */ char *type; }; struct bfs_mtab { /** The array of mtab entries. */ struct bfs_mtab_entry *table; /** The size of the array. */ size_t size; /** Capacity of the array. */ size_t capacity; }; /** * Add an entry to the mount table. */ static int bfs_mtab_push(struct bfs_mtab *mtab, dev_t dev, const char *type) { size_t size = mtab->size + 1; if (size >= mtab->capacity) { size_t capacity = 2*size; struct bfs_mtab_entry *table = realloc(mtab->table, capacity*sizeof(*table)); if (!table) { return -1; } mtab->table = table; mtab->capacity = capacity; } struct bfs_mtab_entry *entry = mtab->table + (size - 1); entry->dev = dev; entry->type = strdup(type); if (!entry->type) { return -1; } mtab->size = size; return 0; } struct bfs_mtab *parse_bfs_mtab() { #if BFS_MNTENT FILE *file = setmntent(_PATH_MOUNTED, "r"); if (!file) { goto fail; } struct bfs_mtab *mtab = malloc(sizeof(*mtab)); if (!mtab) { goto fail_file; } mtab->table = NULL; mtab->size = 0; mtab->capacity = 0; struct mntent *mnt; while ((mnt = getmntent(file))) { struct bfs_stat sb; if (bfs_stat(AT_FDCWD, mnt->mnt_dir, 0, 0, &sb) != 0) { continue; } if (bfs_mtab_push(mtab, sb.dev, mnt->mnt_type) != 0) { goto fail_mtab; } } endmntent(file); return mtab; fail_mtab: free_bfs_mtab(mtab); fail_file: endmntent(file); fail: return NULL; #elif BFS_MNTINFO struct statfs *mntbuf; int size = getmntinfo(&mntbuf, MNT_WAIT); if (size < 0) { return NULL; } struct bfs_mtab *mtab = malloc(sizeof(*mtab)); if (!mtab) { goto fail; } mtab->size = 0; mtab->table = malloc(size*sizeof(*mtab->table)); if (!mtab->table) { goto fail_mtab; } mtab->capacity = size; for (struct statfs *mnt = mntbuf; mnt < mntbuf + size; ++mnt) { struct bfs_stat sb; if (bfs_stat(AT_FDCWD, mnt->f_mntonname, 0, 0, &sb) != 0) { continue; } if (bfs_mtab_push(mtab, sb.dev, mnt->f_fstypename) != 0) { goto fail_mtab; } } return mtab; fail_mtab: free_bfs_mtab(mtab); fail: return NULL; #elif BFS_MNTTAB FILE *file = fopen(MNTTAB, "r"); if (!file) { goto fail; } struct bfs_mtab *mtab = malloc(sizeof(*mtab)); if (!mtab) { goto fail_file; } mtab->table = NULL; mtab->size = 0; mtab->capacity = 0; struct mnttab mnt; while (getmntent(file, &mnt) == 0) { struct bfs_stat sb; if (bfs_stat(AT_FDCWD, mnt.mnt_mountp, 0, 0, &sb) != 0) { continue; } if (bfs_mtab_push(mtab, sb.dev, mnt.mnt_fstype) != 0) { goto fail_mtab; } } fclose(file); return mtab; fail_mtab: free_bfs_mtab(mtab); fail_file: fclose(file); fail: return NULL; #else errno = ENOTSUP; return NULL; #endif } const char *bfs_fstype(const struct bfs_mtab *mtab, const struct bfs_stat *statbuf) { for (struct bfs_mtab_entry *mnt = mtab->table; mnt < mtab->table + mtab->size; ++mnt) { if (statbuf->dev == mnt->dev) { return mnt->type; } } return "unknown"; } void free_bfs_mtab(struct bfs_mtab *mtab) { if (mtab) { for (struct bfs_mtab_entry *mnt = mtab->table; mnt < mtab->table + mtab->size; ++mnt) { free(mnt->type); } free(mtab->table); free(mtab); } } bfs-1.2.1/mtab.h000066400000000000000000000035541323715747000134010ustar00rootroot00000000000000/**************************************************************************** * 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. * ****************************************************************************/ #ifndef BFS_MTAB_H #define BFS_MTAB_H #include "stat.h" /** * 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); /** * Free a mount table. */ void free_bfs_mtab(struct bfs_mtab *mtab); #endif // BFS_MTAB_H bfs-1.2.1/opt.c000066400000000000000000000452111323715747000132470ustar00rootroot00000000000000/**************************************************************************** * 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. * ****************************************************************************/ #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"; /** * Data flow facts about an evaluation point. */ struct opt_facts { /** Minimum possible depth at this point. */ int mindepth; /** Maximum possible depth at this point. */ int maxdepth; /** Bitmask of possible file types at this point. */ enum bftw_typeflag types; }; /** 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) { if (lhs->mindepth < rhs->mindepth) { result->mindepth = lhs->mindepth; } else { result->mindepth = rhs->mindepth; } if (lhs->maxdepth > rhs->maxdepth) { result->maxdepth = lhs->maxdepth; } else { result->maxdepth = rhs->maxdepth; } result->types = lhs->types | rhs->types; } /** Determine whether a fact set is impossible. */ static bool facts_impossible(const struct opt_facts *facts) { return facts->mindepth > facts->maxdepth || !facts->types; } /** Set some facts to be impossible. */ static void set_facts_impossible(struct opt_facts *facts) { facts->mindepth = INT_MAX; facts->maxdepth = -1; facts->types = 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; } } else { fputc(*i, stderr); } } va_end(args); } /** Update the inferred mindepth. */ static void update_mindepth(struct opt_facts *facts, long long mindepth) { if (mindepth > facts->mindepth) { if (mindepth > INT_MAX) { facts->maxdepth = -1; } else { facts->mindepth = mindepth; } } } /** Update the inferred maxdepth. */ static void update_maxdepth(struct opt_facts *facts, long long maxdepth) { if (maxdepth < facts->maxdepth) { facts->maxdepth = maxdepth; } } /** Infer data flow facts about a -depth N expression. */ static void infer_depth_facts(struct opt_state *state, const struct expr *expr) { switch (expr->cmp_flag) { case CMP_EXACT: update_mindepth(&state->facts_when_true, expr->idata); update_maxdepth(&state->facts_when_true, expr->idata); break; case CMP_LESS: update_maxdepth(&state->facts_when_true, expr->idata - 1); update_mindepth(&state->facts_when_false, expr->idata); break; case CMP_GREATER: if (expr->idata == LLONG_MAX) { // Avoid overflow state->facts_when_true.maxdepth = -1; } else { update_mindepth(&state->facts_when_true, expr->idata + 1); } update_maxdepth(&state->facts_when_false, expr->idata); break; } } /** 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; } /** 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 (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; if (optlevel >= 3 && lhs->pure && rhs->pure) { double swapped_cost = rhs->cost + rhs->probability*lhs->cost; if (swapped_cost < expr->cost) { debug_opt(state, "-O3: cost: %e", expr); expr->lhs = rhs; expr->rhs = lhs; debug_opt(state, " <==> %e (~%g --> ~%g)\n", expr, expr->cost, swapped_cost); expr->cost = swapped_cost; } } 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 (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; if (optlevel >= 3 && lhs->pure && rhs->pure) { double swapped_cost = rhs->cost + (1 - rhs->probability)*lhs->cost; if (swapped_cost < expr->cost) { debug_opt(state, "-O3: cost: %e", expr); expr->lhs = rhs; expr->rhs = lhs; debug_opt(state, " <==> %e (~%g --> ~%g)\n", expr, expr->cost, swapped_cost); expr->cost = swapped_cost; } } 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); } 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; } 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_depth_facts(state, expr); } else if (expr->eval == eval_type) { infer_type_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; } 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 = { .mindepth = cmdline->mindepth, .maxdepth = cmdline->maxdepth, .types = ~0, }, .facts_when_impure = &facts_when_impure, }; cmdline->expr = optimize_expr_recursive(&state, cmdline->expr); if (!cmdline->expr) { return -1; } cmdline->expr = ignore_result(&state, cmdline->expr); int optlevel = cmdline->optlevel; if (optlevel >= 2 && facts_when_impure.mindepth > cmdline->mindepth) { debug_opt(&state, "-O2: data flow: mindepth --> %d\n", facts_when_impure.mindepth); cmdline->mindepth = facts_when_impure.mindepth; } if (optlevel >= 4 && facts_when_impure.maxdepth < cmdline->maxdepth) { debug_opt(&state, "-O4: data flow: maxdepth --> %d\n", facts_when_impure.maxdepth); cmdline->maxdepth = facts_when_impure.maxdepth; } return 0; } bfs-1.2.1/parse.c000066400000000000000000002300321323715747000135540ustar00rootroot00000000000000/**************************************************************************** * bfs * * Copyright (C) 2015-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. * ****************************************************************************/ #include "bfs.h" #include "cmdline.h" #include "dstring.h" #include "eval.h" #include "exec.h" #include "expr.h" #include "mtab.h" #include "printf.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 // 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; /** Device number (for deduplication). */ dev_t dev; /** Inode number (for deduplication). */ ino_t ino; /** The next open file in the chain. */ struct open_file *next; }; /** * 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 open_file *ofile = cmdline->open_files; while (ofile) { struct open_file *next = ofile->next; if (cfclose(ofile->cfile) != 0) { if (cerr) { cfprintf(cerr, "%{er}error: '%s': %m%{rs}\n", ofile->path); } ret = -1; } free(ofile); ofile = next; } if (cout && fflush(cout->file) != 0) { if (cerr) { cfprintf(cerr, "%{er}error: standard output: %m%{rs}\n"); } ret = -1; } cfclose(cout); cfclose(cerr); free_colors(cmdline->colors); struct root *root = cmdline->roots; while (root) { struct root *next = root->next; free(root); root = next; } 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 tail of the root path list. */ struct root **roots_tail; /** The current regex flags to use. */ int regex_flags; /** Whether this session is interactive. */ 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; /** A "-depth"-type argument if any. */ const char *depth_arg; /** A "-prune"-type argument if any. */ const char *prune_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, }; /** * 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) { cfprintf(cmdline->cerr, "%{er}error: '%s': %m%{rs}\n", path); goto out; } struct bfs_stat sb; if (bfs_fstat(fileno(cfile->file), &sb) != 0) { cfprintf(cmdline->cerr, "%{er}error: '%s': %m%{rs}\n", path); goto out_close; } for (struct open_file *ofile = cmdline->open_files; ofile; ofile = ofile->next) { if (ofile->dev == sb.dev && ofile->ino == sb.ino) { expr->cfile = ofile->cfile; ret = 0; goto out_close; } } struct open_file *ofile = malloc(sizeof(*ofile)); if (!ofile) { goto out_close; } ofile->cfile = cfile; ofile->path = path; ofile->dev = sb.dev; ofile->ino = sb.ino; ofile->next = cmdline->open_files; cmdline->open_files = 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); int at_flags = follow ? 0 : AT_SYMLINK_NOFOLLOW; int ret = bfs_stat(AT_FDCWD, expr->sdata, at_flags, BFS_STAT_BROKEN_OK, sb); if (ret != 0) { cfprintf(cmdline->cerr, "%{er}error: '%s': %m%{rs}\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; } } 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 root *root = malloc(sizeof(struct root)); if (!root) { perror("malloc()"); return -1; } root->path = path; root->next = NULL; *state->roots_tail = root; state->roots_tail = &root->next; return 0; } /** * 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)) { cfprintf(state->cmdline->cerr, "%{er}error: '%s' is not a valid integer.%{rs}\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) { if (str[0] == '-' || str[0] == '+') { ++str; } return str[0] >= '0' && str[0] <= '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) { cfprintf(state->cmdline->cerr, "%{wr}warning: The '%s' option applies to the entire command line. For clarity, place\n" "it before any non-option arguments.%{rs}\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) { cfprintf(state->cmdline->cerr, "%{er}error: %s needs a value.%{rs}\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) { cfprintf(state->cmdline->cerr, "%{er}error: %s needs a value.%{rs}\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) { cfprintf(state->cmdline->cerr, "%{er}error: %s needs a value.%{rs}\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; } /** * 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 *flag = state->argv[1]; if (!flag) { cfprintf(cmdline->cerr, "%{er}error: %s needs a flag.%{rs}\n", arg); return NULL; } if (strcmp(flag, "help") == 0) { printf("Supported debug flags:\n\n"); printf(" help: This message.\n"); printf(" cost: Show cost estimates.\n"); printf(" exec: Print executed command details.\n"); printf(" opt: Print optimization details.\n"); printf(" rates: Print predicate success rates.\n"); printf(" search: Trace the filesystem traversal.\n"); printf(" stat: Trace all stat() calls.\n"); printf(" tree: Print the parse tree.\n"); state->just_info = true; return NULL; } else if (strcmp(flag, "cost") == 0) { cmdline->debug |= DEBUG_COST; } else if (strcmp(flag, "exec") == 0) { cmdline->debug |= DEBUG_EXEC; } else if (strcmp(flag, "opt") == 0) { cmdline->debug |= DEBUG_OPT; } else if (strcmp(flag, "rates") == 0) { cmdline->debug |= DEBUG_RATES; } else if (strcmp(flag, "search") == 0) { cmdline->debug |= DEBUG_SEARCH; } else if (strcmp(flag, "stat") == 0) { cmdline->debug |= DEBUG_STAT; } else if (strcmp(flag, "tree") == 0) { cmdline->debug |= DEBUG_TREE; } else { cfprintf(cmdline->cerr, "%{wr}warning: Unrecognized debug flag '%s'.%{rs}\n\n", flag); } 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) { cfprintf(state->cmdline->cerr, "%{wr}warning: %s is the same as -O4.%{rs}\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 | BFTW_DETECT_CYCLES); 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) { expr->idata = flag; } return expr; } /** * 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 -(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, -depth. */ 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_option(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) { cfprintf(cmdline->cerr, "%{er}error: %s needs a value.%{rs}\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) { cfprintf(state->cmdline->cerr, "%{er}error: -f requires a path.%{rs}\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) { cfprintf(state->cmdline->cerr, "%{er}error: %s needs a file.%{rs}\n", arg); return NULL; } const char *format = state->argv[2]; if (!format) { cfprintf(state->cmdline->cerr, "%{er}error: %s needs a format string.%{rs}\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) { cmdline->mtab = parse_bfs_mtab(); if (!cmdline->mtab) { cfprintf(cmdline->cerr, "%{er}error: Couldn't parse the mount table: %m%{rs}\n"); 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 { cfprintf(state->cmdline->cerr, "%{er}error: %s %s: No such group.%{rs}\n", arg, expr->sdata); goto fail; } expr->cost = STAT_COST; return expr; fail: free_expr(expr); return NULL; } /** * 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 { cfprintf(state->cmdline->cerr, "%{er}error: %s %s: No such user.%{rs}\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, -xdev. */ static struct expr *parse_mount(struct parser_state *state, int arg1, int arg2) { state->cmdline->flags |= BFTW_XDEV; 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 cfprintf(state->cmdline->cerr, "%{er}error: %s is missing platform support.%{rs}\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); } /** * Parse -newerXY. */ static struct expr *parse_newerxy(struct parser_state *state, int arg1, int arg2) { CFILE *cerr = state->cmdline->cerr; const char *arg = state->argv[0]; if (strlen(arg) != 8) { cfprintf(cerr, "%{er}error: Expected -newerXY; found %s.%{rs}\n", arg); return NULL; } struct expr *expr = parse_unary_test(state, eval_newer); if (!expr) { goto fail; } switch (arg[6]) { case 'a': expr->stat_field = BFS_STAT_ATIME; break; case 'c': expr->stat_field = BFS_STAT_CTIME; break; case 'm': expr->stat_field = BFS_STAT_MTIME; break; case 'B': expr->stat_field = BFS_STAT_BTIME; break; default: cfprintf(cerr, "%{er}error: %s: For -newerXY, X should be 'a', 'c', 'm', or 'B'.%{rs}\n", arg); goto fail; } if (arg[7] == 't') { cfprintf(cerr, "%{er}error: %s: Explicit reference times ('t') are not supported.%{rs}\n", arg); goto fail; } else { struct bfs_stat sb; if (stat_arg(state, expr, &sb) != 0) { goto fail; } switch (arg[7]) { case 'a': expr->reftime = sb.atime; break; case 'c': expr->reftime = sb.ctime; break; case 'm': expr->reftime = sb.mtime; break; case 'B': if (sb.mask & BFS_STAT_BTIME) { expr->reftime = sb.btime; } else { cfprintf(cerr, "%{er}error: '%s': Couldn't get file birth time.%{rs}\n", expr->sdata); goto fail; } break; default: cfprintf(cerr, "%{er}error: %s: For -newerXY, Y should be 'a', 'c', 'm', 'B', or 't'.%{rs}\n", arg); goto fail; } } 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; } 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) { cfprintf(state->cmdline->cerr, "%{wr}warning: bfs does not apply the optimization that %s inhibits.%{rs}\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; } 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: cfprintf(state->cmdline->cerr, "%{er}error: '%s' is an invalid mode.%{rs}\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; 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) { cfprintf(state->cmdline->cerr, "%{er}error: %s %s: %s.%{rs}\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: cfprintf(state->cmdline->cerr, "%{er}error: Unsupported -regextype '%s'.%{rs}\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 -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: cfprintf(state->cmdline->cerr, "%{er}error: %s %s: Expected a size unit (one of cwbkMGTP); found %s.%{rs}\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) { switch (*c) { case 'b': types |= BFTW_BLK; probability += 0.00000721183; break; case 'c': types |= BFTW_CHR; probability += 0.0000499855; break; case 'd': types |= BFTW_DIR; probability += 0.114475; break; case 'D': types |= BFTW_DOOR; probability += 0.000001; break; case 'p': types |= BFTW_FIFO; probability += 0.00000248684; break; case 'f': types |= BFTW_REG; probability += 0.859772; break; case 'l': types |= BFTW_LNK; probability += 0.0256816; break; case 's': types |= BFTW_SOCK; probability += 0.0000116881; break; case '\0': cfprintf(state->cmdline->cerr, "%{er}error: %s %s: Expected a type flag.%{rs}\n", expr->argv[0], expr->argv[1]); goto fail; default: cfprintf(state->cmdline->cerr, "%{er}error: %s %s: Unknown type flag '%c' (expected one of [bcdpflsD]).%{rs}\n", expr->argv[0], expr->argv[1], *c); goto fail; } ++c; if (*c == '\0') { break; } else if (*c == ',') { ++c; continue; } else { cfprintf(state->cmdline->cerr, "%{er}error: %s %s: Types must be comma-separated.%{rs}\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" -help. */ static struct expr *parse_help(struct parser_state *state, int arg1, int arg2) { CFILE *cout = state->cmdline->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}; see %{ex}find%{rs} %{blu}-help%{rs} or" " %{ex}man%{rs} %{bld}find%{rs} for help with %{ex}find%{rs}-\n" "compatible options :)\n\n"); cfprintf(cout, "%{cyn}flags%{rs} (%{cyn}-H%{rs}/%{cyn}-L%{rs}/%{cyn}-P%{rs} etc.), %{mag}paths%{rs}, and" " %{blu}expressions%{rs} may be freely mixed in any order.\n\n"); cfprintf(cout, "%{bld}POSIX find features:%{rs}\n\n"); cfprintf(cout, " %{red}(%{rs} %{blu}expression%{rs} %{red})%{rs}\n"); cfprintf(cout, " %{red}!%{rs} %{blu}expression%{rs}\n"); cfprintf(cout, " %{blu}expression%{rs} [%{red}-a%{rs}] %{blu}expression%{rs}\n"); cfprintf(cout, " %{blu}expression%{rs} %{red}-o%{rs} %{blu}expression%{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\n"); cfprintf(cout, " %{blu}-depth%{rs}\n"); cfprintf(cout, " Search in post-order (descendents first)\n"); cfprintf(cout, " %{blu}-xdev%{rs}\n"); cfprintf(cout, " Don't descend into other mount points\n\n"); cfprintf(cout, " %{blu}-atime%{rs} %{bld}[-+]N%{rs}\n"); cfprintf(cout, " %{blu}-ctime%{rs} %{bld}[-+]N%{rs}\n"); cfprintf(cout, " %{blu}-mtime%{rs} %{bld}[-+]N%{rs}\n"); cfprintf(cout, " Find files accessed/changed/modified %{bld}N%{rs} days ago\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}-links%{rs} %{bld}[-+]N%{rs}\n"); cfprintf(cout, " Find files with %{bld}N%{rs} hard links\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}-path%{rs} %{bld}GLOB%{rs}\n"); cfprintf(cout, " Find files whose entire path 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}-perm%{rs} %{bld}[-]MODE%{rs}\n"); cfprintf(cout, " Find files with a matching mode\n"); cfprintf(cout, " %{blu}-type%{rs} %{bld}[bcdlpfs]%{rs}\n"); cfprintf(cout, " Find files of the given type\n"); cfprintf(cout, " %{blu}-size%{rs} %{bld}[-+]N[c]%{rs}\n"); cfprintf(cout, " Find files with the given size\n\n"); cfprintf(cout, " %{blu}-prune%{rs}\n"); cfprintf(cout, " Don't descend into this directory\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}-print%{rs}\n"); cfprintf(cout, " Print the path to the found file\n\n"); cfprintf(cout, "%{bld}GNU find features:%{rs}\n\n"); cfprintf(cout, " %{red}-not%{rs} %{blu}expression%{rs}\n"); cfprintf(cout, " %{blu}expression%{rs} %{red}-and%{rs} %{blu}expression%{rs}\n"); cfprintf(cout, " %{blu}expression%{rs} %{red}-or%{rs} %{blu}expression%{rs}\n"); cfprintf(cout, " %{blu}expression%{rs} %{red},%{rs} %{blu}expression%{rs}\n\n"); cfprintf(cout, " %{cyn}-P%{rs}\n"); cfprintf(cout, " Never follow symbolic links (the default)\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; interpreted differently than GNU\n"); cfprintf(cout, " find -- see below)\n\n"); cfprintf(cout, " %{blu}-d%{rs}\n"); cfprintf(cout, " Search in post-order (same as %{blu}-depth%{rs})\n"); cfprintf(cout, " %{blu}-daystart%{rs}\n"); cfprintf(cout, " Measure times relative to the start of today\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})\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}-warn%{rs}\n"); cfprintf(cout, " %{blu}-nowarn%{rs}\n"); cfprintf(cout, " Turn on or off warnings about the command line\n\n"); cfprintf(cout, " %{blu}-amin%{rs} %{bld}[-+]N%{rs}\n"); cfprintf(cout, " %{blu}-cmin%{rs} %{bld}[-+]N%{rs}\n"); cfprintf(cout, " %{blu}-mmin%{rs} %{bld}[-+]N%{rs}\n"); cfprintf(cout, " Find files accessed/changed/modified %{bld}N%{rs} minutes ago\n"); cfprintf(cout, " %{blu}-anewer%{rs} %{bld}FILE%{rs}\n"); cfprintf(cout, " %{blu}-cnewer%{rs} %{bld}FILE%{rs}\n"); cfprintf(cout, " %{blu}-mnewer%{rs} %{bld}FILE%{rs}\n"); cfprintf(cout, " Find files accessed/changed/modified more recently than %{bld}FILE%{rs} was modified\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}-inum%{rs} %{bld}[-+]N%{rs}\n"); cfprintf(cout, " Find files with inode number %{bld}N%{rs}\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}-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 [aBcm].\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[cwbkMG]%{rs}\n"); cfprintf(cout, " 1-byte %{bld}c%{rs}haracters, 2-byte %{bld}w%{rs}ords, 512-byte %{bld}b%{rs}locks, and" " %{bld}k%{rs}iB/%{bld}M%{rs}iB/%{bld}G%{rs}iB\n"); cfprintf(cout, " %{blu}-type%{rs} %{bld}[bcdlpfsD]%{rs}\n"); cfprintf(cout, " The %{bld}D%{rs}oor file type is also supported on platforms that have it (Solaris)\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"); cfprintf(cout, " %{blu}-wholename%{rs} %{bld}GLOB%{rs}\n"); cfprintf(cout, " Find files whose entire path matches the %{bld}GLOB%{rs} (same as %{blu}-path%{rs})\n"); 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"); cfprintf(cout, " %{blu}-xtype%{rs} %{bld}[bcdlpfsD]%{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, " %{blu}-delete%{rs}\n"); cfprintf(cout, " Delete any found files (implies %{blu}-depth%{rs})\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}-ls%{rs}\n"); cfprintf(cout, " List files like %{ex}ls%{rs} %{bld}-dils%{rs}\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})\n"); 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 output\n"); cfprintf(cout, " %{blu}-quit%{rs}\n"); cfprintf(cout, " Quit immediately\n\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, "%{bld}BSD find features:%{rs}\n\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}-x%{rs}\n"); cfprintf(cout, " Don't descend into other mount points (same as %{blu}-xdev%{rs})\n\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\n"); cfprintf(cout, " %{blu}-Bmin%{rs} %{bld}[-+]N%{rs}\n"); cfprintf(cout, " %{blu}-Btime%{rs} %{bld}[-+]N%{rs}\n"); cfprintf(cout, " Find files Birthed %{bld}N%{rs} minutes/days ago\n"); cfprintf(cout, " %{blu}-Bnewer%{rs} %{bld}FILE%{rs}\n"); cfprintf(cout, " Find files Birthed more recently than %{bld}FILE%{rs} was modified\n"); cfprintf(cout, " %{blu}-depth%{rs} %{bld}[-+]N%{rs}\n"); cfprintf(cout, " Find files with depth %{bld}N%{rs}\n"); cfprintf(cout, " %{blu}-gid%{rs} %{bld}NAME%{rs}\n"); cfprintf(cout, " %{blu}-uid%{rs} %{bld}NAME%{rs}\n"); cfprintf(cout, " Group/user names are supported in addition to numeric IDs\n"); cfprintf(cout, " %{blu}-size%{rs} %{bld}[-+]N[cwbkMGTP]%{rs}\n"); cfprintf(cout, " Units of %{bld}T%{rs}iB/%{bld}P%{rs}iB are additionally supported\n"); cfprintf(cout, " %{blu}-sparse%{rs}\n"); cfprintf(cout, " Find files that occupy fewer disk blocks than expected\n\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}-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}-rm%{rs}\n"); cfprintf(cout, " Delete any found files (same as %{blu}-delete%{rs}; implies %{blu}-depth%{rs})\n\n"); cfprintf(cout, "%{ex}bfs%{rs}%{bld}-specific features:%{rs}\n\n"); cfprintf(cout, " %{cyn}-O%{rs}%{bld}0%{rs}\n"); cfprintf(cout, " Disable all optimizations\n"); cfprintf(cout, " %{cyn}-O%{rs}%{bld}1%{rs}\n"); cfprintf(cout, " Basic logical simplification\n"); cfprintf(cout, " %{cyn}-O%{rs}%{bld}2%{rs}\n"); cfprintf(cout, " All %{cyn}-O%{rs}%{bld}1%{rs} optimizations, plus dead code elimination and data flow analysis\n"); cfprintf(cout, " %{cyn}-O%{rs}%{bld}3%{rs} (default)\n"); cfprintf(cout, " All %{cyn}-O%{rs}%{bld}2%{rs} optimizations, plus re-order expressions to reduce expected cost\n"); cfprintf(cout, " %{cyn}-O%{rs}%{bld}4%{rs}/%{cyn}-O%{rs}%{bld}fast%{rs}\n"); cfprintf(cout, " All optimizations, including aggressive optimizations that may alter the\n"); cfprintf(cout, " observed behavior in corner cases\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\n"); cfprintf(cout, " %{blu}-hidden%{rs}\n"); cfprintf(cout, " %{blu}-nohidden%{rs}\n"); cfprintf(cout, " Match hidden files, or filter them out\n\n"); cfprintf(cout, " %{blu}-printf%{rs} %{bld}FORMAT%{rs}\n"); cfprintf(cout, " %{blu}-fprintf%{rs} %{bld}FORMAT%{rs} %{bld}FILE%{rs}\n"); cfprintf(cout, " The additional format directives %%w and %%W%{bld}k%{rs} for printing file birth times\n"); cfprintf(cout, " are supported.\n\n"); cfprintf(cout, "%s\n", BFS_HOMEPAGE); 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; bool prefix; parse_fn *parse; int arg1; int arg2; }; /** * The parse table for literals. */ static const struct table_entry parse_table[] = { {"-Bmin", false, parse_time, BFS_STAT_BTIME, MINUTES}, {"-Bnewer", false, parse_newer, BFS_STAT_BTIME}, {"-Btime", false, parse_time, BFS_STAT_BTIME, DAYS}, {"-D", false, parse_debug}, {"-E", false, parse_regex_extended}, {"-O", true, parse_optlevel}, {"-P", false, parse_follow, 0, false}, {"-H", false, parse_follow, BFTW_COMFOLLOW, false}, {"-L", false, parse_follow, BFTW_LOGICAL | BFTW_DETECT_CYCLES, false}, {"-X", false, parse_xargs_safe}, {"-a"}, {"-amin", false, parse_time, BFS_STAT_ATIME, MINUTES}, {"-and"}, {"-anewer", false, parse_newer, BFS_STAT_ATIME}, {"-atime", false, parse_time, BFS_STAT_ATIME, DAYS}, {"-cmin", false, parse_time, BFS_STAT_CTIME, MINUTES}, {"-cnewer", false, parse_newer, BFS_STAT_CTIME}, {"-ctime", false, parse_time, BFS_STAT_CTIME, DAYS}, {"-color", false, parse_color, true}, {"-d", false, parse_depth}, {"-daystart", false, parse_daystart}, {"-delete", false, parse_delete}, {"-depth", false, parse_depth_n}, {"-empty", false, parse_empty}, {"-exec", false, parse_exec, 0}, {"-execdir", false, parse_exec, BFS_EXEC_CHDIR}, {"-executable", false, parse_access, X_OK}, {"-exit", false, parse_exit}, {"-f", false, parse_f}, {"-false", false, parse_const, false}, {"-fls", false, parse_fls}, {"-follow", false, parse_follow, BFTW_LOGICAL | BFTW_DETECT_CYCLES, true}, {"-fprint", false, parse_fprint}, {"-fprint0", false, parse_fprint0}, {"-fprintf", false, parse_fprintf}, {"-fstype", false, parse_fstype}, {"-gid", false, parse_group}, {"-group", false, parse_group}, {"-help", false, parse_help}, {"-hidden", false, parse_hidden}, {"-ignore_readdir_race", false, parse_ignore_races, true}, {"-ilname", false, parse_lname, true}, {"-iname", false, parse_name, true}, {"-inum", false, parse_inum}, {"-ipath", false, parse_path, true}, {"-iregex", false, parse_regex, REG_ICASE}, {"-iwholename", false, parse_path, true}, {"-links", false, parse_links}, {"-lname", false, parse_lname, false}, {"-ls", false, parse_ls}, {"-maxdepth", false, parse_depth_limit, false}, {"-mindepth", false, parse_depth_limit, true}, {"-mmin", false, parse_time, BFS_STAT_MTIME, MINUTES}, {"-mnewer", false, parse_newer, BFS_STAT_MTIME}, {"-mount", false, parse_mount}, {"-mtime", false, parse_time, BFS_STAT_MTIME, DAYS}, {"-name", false, parse_name, false}, {"-newer", false, parse_newer, BFS_STAT_MTIME}, {"-newer", true, parse_newerxy}, {"-nocolor", false, parse_color, false}, {"-nogroup", false, parse_nogroup}, {"-nohidden", false, parse_nohidden}, {"-noignore_readdir_race", false, parse_ignore_races, false}, {"-noleaf", false, parse_noleaf}, {"-not"}, {"-nouser", false, parse_nouser}, {"-nowarn", false, parse_warn, false}, {"-o"}, {"-ok", false, parse_exec, BFS_EXEC_CONFIRM}, {"-okdir", false, parse_exec, BFS_EXEC_CONFIRM | BFS_EXEC_CHDIR}, {"-or"}, {"-path", false, parse_path, false}, {"-perm", false, parse_perm}, {"-print", false, parse_print}, {"-print0", false, parse_print0}, {"-printf", false, parse_printf}, {"-printx", false, parse_printx}, {"-prune", false, parse_prune}, {"-quit", false, parse_quit}, {"-readable", false, parse_access, R_OK}, {"-regex", false, parse_regex, 0}, {"-regextype", false, parse_regextype}, {"-rm", false, parse_delete}, {"-samefile", false, parse_samefile}, {"-size", false, parse_size}, {"-sparse", false, parse_sparse}, {"-true", false, parse_const, true}, {"-type", false, parse_type, false}, {"-uid", false, parse_user}, {"-used", false, parse_used}, {"-user", false, parse_user}, {"-version", false, parse_version}, {"-warn", false, parse_warn, true}, {"-wholename", false, parse_path, false}, {"-writable", false, parse_access, W_OK}, {"-x", false, parse_mount}, {"-xdev", false, parse_mount}, {"-xtype", false, parse_type, true}, {"--"}, {"--help", false, parse_help}, {"--version", false, parse_version}, {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) { struct cmdline *cmdline = state->cmdline; // 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); cfprintf(cmdline->cerr, "%{er}error: Unknown argument '%s'; did you mean '%s'?%{rs}", 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: cfprintf(cmdline->cerr, "%{er}error: Expected a predicate; found '%s'.%{rs}\n", arg); return NULL; } /** * FACTOR : "(" EXPR ")" * | "!" FACTOR | "-not" FACTOR * | LITERAL */ static struct expr *parse_factor(struct parser_state *state) { CFILE *cerr = state->cmdline->cerr; if (skip_paths(state) != 0) { return NULL; } const char *arg = state->argv[0]; if (!arg) { cfprintf(cerr, "%{er}error: Expression terminated prematurely after '%s'.%{rs}\n", state->argv[-1]); 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) { cfprintf(cerr, "%{er}error: Expected a ')' after '%s'.%{rs}\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; } CFILE *cerr = state->cmdline->cerr; struct expr *expr = &expr_true; if (state->argv[0]) { expr = parse_expr(state); if (!expr) { return NULL; } } if (state->argv[0]) { cfprintf(cerr, "%{er}error: Unexpected argument '%s'.%{rs}\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 && state->depth_arg && state->prune_arg) { cfprintf(cerr, "%{wr}warning: %s does not work in the presence of %s.%{rs}\n", state->prune_arg, state->depth_arg); if (state->interactive) { cfprintf(cerr, "%{wr}Do you want to continue?%{rs} "); 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; 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); } if (cmdline->debug & DEBUG_COST) { cfprintf(cerr, "%{cyn}-D%{rs} %{bld}cost%{rs} "); } if (cmdline->debug & DEBUG_EXEC) { cfprintf(cerr, "%{cyn}-D%{rs} %{bld}exec%{rs} "); } if (cmdline->debug & DEBUG_OPT) { cfprintf(cerr, "%{cyn}-D%{rs} %{bld}opt%{rs} "); } if (cmdline->debug & DEBUG_RATES) { cfprintf(cerr, "%{cyn}-D%{rs} %{bld}rates%{rs} "); } if (cmdline->debug & DEBUG_STAT) { cfprintf(cerr, "%{cyn}-D%{rs} %{bld}stat%{rs} "); } if (cmdline->debug & DEBUG_TREE) { cfprintf(cerr, "%{cyn}-D%{rs} %{bld}tree%{rs} "); } for (struct root *root = cmdline->roots; root; root = root->next) { char c = root->path[0]; if (c == '-' || c == '(' || c == ')' || c == '!' || c == ',') { cfprintf(cerr, "%{cyn}-f%{rs} "); } cfprintf(cerr, "%{mag}%s%{rs} ", root->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_XDEV) { cfprintf(cerr, "%{blu}-mount%{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); } 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->roots = NULL; cmdline->colors = NULL; cmdline->cout = NULL; cmdline->cerr = NULL; cmdline->mtab = NULL; cmdline->mindepth = 0; cmdline->maxdepth = INT_MAX; cmdline->flags = BFTW_RECOVER; cmdline->optlevel = 3; cmdline->debug = 0; cmdline->xargs_safe = false; cmdline->ignore_races = false; cmdline->expr = &expr_true; cmdline->open_files = NULL; cmdline->nopen_files = 0; cmdline->argv = malloc((argc + 1)*sizeof(*cmdline->argv)); if (!cmdline->argv) { goto fail; } for (int i = 0; i <= argc; ++i) { cmdline->argv[i] = argv[i]; } cmdline->colors = parse_colors(getenv("LS_COLORS")); cmdline->cout = cfdup(stdout, cmdline->colors); cmdline->cerr = cfdup(stderr, cmdline->colors); if (!cmdline->cout || !cmdline->cerr) { perror("cfdup()"); goto fail; } bool stderr_tty = cmdline->cerr->colors; bool stdin_tty = isatty(STDIN_FILENO); struct parser_state state = { .cmdline = cmdline, .argv = cmdline->argv + 1, .command = cmdline->argv[0], .roots_tail = &cmdline->roots, .regex_flags = 0, .interactive = stderr_tty && stdin_tty, .use_color = COLOR_AUTO, .implicit_print = true, .warn = stdin_tty, .non_option_seen = false, .just_info = false, .depth_arg = NULL, .prune_arg = NULL, }; 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 (!cmdline->roots) { if (parse_root(&state, ".") != 0) { goto fail; } } 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.2.1/printf.c000066400000000000000000000522351323715747000137530ustar00rootroot00000000000000/**************************************************************************** * 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. * ****************************************************************************/ #include "printf.h" #include "cmdline.h" #include "color.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 *directive, const struct BFTW *ftwbuf); /** * A single directive in a printf command. */ struct bfs_printf_directive { /** 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_directive *next; }; /** Print some text as-is. */ static int bfs_printf_literal(FILE *file, const struct bfs_printf_directive *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 *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 /** * Get a particular time field from a struct bfs_stat. */ static const struct timespec *get_time_field(const struct bfs_stat *statbuf, enum bfs_stat_field stat_field) { if (!(statbuf->mask & stat_field)) { errno = ENOTSUP; return NULL; } switch (stat_field) { case BFS_STAT_ATIME: return &statbuf->atime; case BFS_STAT_BTIME: return &statbuf->btime; case BFS_STAT_CTIME: return &statbuf->ctime; case BFS_STAT_MTIME: return &statbuf->mtime; default: assert(false); return NULL; } } /** %c, %c, and %t: ctime() */ static int bfs_printf_ctime(FILE *file, const struct bfs_printf_directive *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 timespec *ts = get_time_field(ftwbuf->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, %C, %T: strftime() */ static int bfs_printf_strftime(FILE *file, const struct bfs_printf_directive *directive, const struct BFTW *ftwbuf) { const struct timespec *ts = get_time_field(ftwbuf->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 '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), "%.2d.%09ld0", tm.tm_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; // 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 *directive, const struct BFTW *ftwbuf) { BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)ftwbuf->statbuf->blocks); return fprintf(file, directive->str, buf); } /** %d: depth */ static int bfs_printf_d(FILE *file, const struct bfs_printf_directive *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 *directive, const struct BFTW *ftwbuf) { BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)ftwbuf->statbuf->dev); return fprintf(file, directive->str, buf); } /** %f: file name */ static int bfs_printf_f(FILE *file, const struct bfs_printf_directive *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 *directive, const struct BFTW *ftwbuf) { const char *type = bfs_fstype(directive->mtab, ftwbuf->statbuf); return fprintf(file, directive->str, type); } /** %G: gid */ static int bfs_printf_G(FILE *file, const struct bfs_printf_directive *directive, const struct BFTW *ftwbuf) { BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)ftwbuf->statbuf->gid); return fprintf(file, directive->str, buf); } /** %g: group name */ static int bfs_printf_g(FILE *file, const struct bfs_printf_directive *directive, const struct BFTW *ftwbuf) { struct group *grp = getgrgid(ftwbuf->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 *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 *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 *directive, const struct BFTW *ftwbuf) { BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)ftwbuf->statbuf->ino); return fprintf(file, directive->str, buf); } /** %k: 1K blocks */ static int bfs_printf_k(FILE *file, const struct bfs_printf_directive *directive, const struct BFTW *ftwbuf) { BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)(ftwbuf->statbuf->blocks + 1)/2); return fprintf(file, directive->str, buf); } /** %l: link target */ static int bfs_printf_l(FILE *file, const struct bfs_printf_directive *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 *directive, const struct BFTW *ftwbuf) { return fprintf(file, directive->str, (unsigned int)(ftwbuf->statbuf->mode & 07777)); } /** %M: symbolic mode */ static int bfs_printf_M(FILE *file, const struct bfs_printf_directive *directive, const struct BFTW *ftwbuf) { char buf[11]; format_mode(ftwbuf->statbuf->mode, buf); return fprintf(file, directive->str, buf); } /** %n: link count */ static int bfs_printf_n(FILE *file, const struct bfs_printf_directive *directive, const struct BFTW *ftwbuf) { BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)ftwbuf->statbuf->nlink); return fprintf(file, directive->str, buf); } /** %p: full path */ static int bfs_printf_p(FILE *file, const struct bfs_printf_directive *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 *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 *directive, const struct BFTW *ftwbuf) { BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)ftwbuf->statbuf->size); return fprintf(file, directive->str, buf); } /** %S: sparseness */ static int bfs_printf_S(FILE *file, const struct bfs_printf_directive *directive, const struct BFTW *ftwbuf) { double sparsity = 512.0 * ftwbuf->statbuf->blocks / ftwbuf->statbuf->size; return fprintf(file, directive->str, sparsity); } /** %U: uid */ static int bfs_printf_U(FILE *file, const struct bfs_printf_directive *directive, const struct BFTW *ftwbuf) { BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)ftwbuf->statbuf->uid); return fprintf(file, directive->str, buf); } /** %u: user name */ static int bfs_printf_u(FILE *file, const struct bfs_printf_directive *directive, const struct BFTW *ftwbuf) { struct passwd *pwd = getpwuid(ftwbuf->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 *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 *directive, const struct BFTW *ftwbuf) { if (ftwbuf->typeflag != BFTW_LNK) { return bfs_printf_y(file, directive, ftwbuf); } const char *type = "U"; struct bfs_stat sb; if (bfs_stat(ftwbuf->at_fd, ftwbuf->at_path, 0, 0, &sb) == 0) { type = bfs_printf_type(mode_to_typeflag(sb.mode)); } else { switch (errno) { case ELOOP: type = "L"; break; case ENOENT: case ENOTDIR: type = "N"; break; } } return fprintf(file, directive->str, type); } /** * Free a printf directive. */ static void free_directive(struct bfs_printf_directive *directive) { if (directive) { dstrfree(directive->str); free(directive); } } /** * Create a new printf directive. */ static struct bfs_printf_directive *new_directive() { struct bfs_printf_directive *directive = malloc(sizeof(*directive)); if (!directive) { perror("malloc()"); goto error; } directive->fn = NULL; 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 void append_directive(struct bfs_printf_directive ***tail, struct bfs_printf_directive *directive) { **tail = directive; *tail = &directive->next; } /** * Append a literal string to the chain. */ static int append_literal(struct bfs_printf_directive ***tail, struct bfs_printf_directive **literal, bool last) { struct bfs_printf_directive *directive = *literal; if (!directive || dstrlen(directive->str) == 0) { return 0; } directive->fn = bfs_printf_literal; append_directive(tail, directive); if (last) { *literal = NULL; } else { *literal = new_directive(); if (!*literal) { return -1; } } return 0; } struct bfs_printf *parse_bfs_printf(const char *format, struct cmdline *cmdline) { CFILE *cerr = cmdline->cerr; struct bfs_printf *command = malloc(sizeof(*command)); if (!command) { perror("malloc()"); return NULL; } command->directives = NULL; command->needs_stat = false; struct bfs_printf_directive **tail = &command->directives; struct bfs_printf_directive *literal = new_directive(); 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': if (append_literal(&tail, &literal, true) != 0) { goto error; } struct bfs_printf_directive *directive = new_directive(); if (!directive) { goto error; } directive->fn = bfs_printf_flush; append_directive(&tail, directive); goto done; case '\0': cfprintf(cerr, "%{er}error: '%s': Incomplete escape sequence '\\'.%{rs}\n", format); goto error; default: cfprintf(cerr, "%{er}error: '%s': Unrecognized escape sequence '\\%c'.%{rs}\n", format, c); goto error; } } else if (c == '%') { if (i[1] == '%') { c = *++i; goto one_char; } struct bfs_printf_directive *directive = new_directive(); 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; case ' ': case '-': if (strchr(directive->str, c)) { cfprintf(cerr, "%{er}error: '%s': Duplicate flag '%c'.%{rs}\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; command->needs_stat = true; break; case 'b': directive->fn = bfs_printf_b; command->needs_stat = true; break; case 'c': directive->fn = bfs_printf_ctime; directive->stat_field = BFS_STAT_CTIME; command->needs_stat = true; break; case 'd': directive->fn = bfs_printf_d; specifier = "jd"; break; case 'D': directive->fn = bfs_printf_D; command->needs_stat = true; break; case 'f': directive->fn = bfs_printf_f; break; case 'F': if (!cmdline->mtab) { cmdline->mtab = parse_bfs_mtab(); if (!cmdline->mtab) { cfprintf(cmdline->cerr, "%{er}error: Couldn't parse the mount table: %m%{rs}\n"); goto directive_error; } } directive->fn = bfs_printf_F; directive->mtab = cmdline->mtab; command->needs_stat = true; break; case 'g': directive->fn = bfs_printf_g; command->needs_stat = true; break; case 'G': directive->fn = bfs_printf_G; command->needs_stat = true; break; case 'h': directive->fn = bfs_printf_h; break; case 'H': directive->fn = bfs_printf_H; break; case 'i': directive->fn = bfs_printf_i; command->needs_stat = true; break; case 'k': directive->fn = bfs_printf_k; command->needs_stat = true; break; case 'l': directive->fn = bfs_printf_l; break; case 'm': directive->fn = bfs_printf_m; specifier = "o"; command->needs_stat = true; break; case 'M': directive->fn = bfs_printf_M; command->needs_stat = true; break; case 'n': directive->fn = bfs_printf_n; command->needs_stat = true; break; case 'p': directive->fn = bfs_printf_p; break; case 'P': directive->fn = bfs_printf_P; break; case 's': directive->fn = bfs_printf_s; command->needs_stat = true; break; case 'S': directive->fn = bfs_printf_S; specifier = "g"; command->needs_stat = true; break; case 't': directive->fn = bfs_printf_ctime; directive->stat_field = BFS_STAT_MTIME; command->needs_stat = true; break; case 'u': directive->fn = bfs_printf_u; command->needs_stat = true; break; case 'U': directive->fn = bfs_printf_U; command->needs_stat = true; break; case 'w': directive->fn = bfs_printf_ctime; directive->stat_field = BFS_STAT_BTIME; command->needs_stat = true; 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 'C': directive->stat_field = BFS_STAT_CTIME; goto directive_strftime; case 'T': directive->stat_field = BFS_STAT_MTIME; goto directive_strftime; case 'W': directive->stat_field = BFS_STAT_BTIME; goto directive_strftime; directive_strftime: directive->fn = bfs_printf_strftime; command->needs_stat = true; c = *++i; if (!c) { cfprintf(cerr, "%{er}error: '%s': Incomplete time specifier '%s%c'.%{rs}\n", format, directive->str, i[-1]); goto directive_error; } else if (strchr("@HIklMprST+XZaAbBcdDhjmUwWxyY", c)) { directive->c = c; } else { cfprintf(cerr, "%{er}error: '%s': Unrecognized time specifier '%%%c%c'.%{rs}\n", format, i[-1], c); goto directive_error; } break; case '\0': cfprintf(cerr, "%{er}error: '%s': Incomplete format specifier '%s'.%{rs}\n", format, directive->str); goto directive_error; default: cfprintf(cerr, "%{er}error: '%s': Unrecognized format specifier '%%%c'.%{rs}\n", format, c); goto directive_error; } if (must_be_numeric && strcmp(specifier, "s") == 0) { cfprintf(cerr, "%{er}error: '%s': Invalid flags '%s' for string format '%%%c'.%{rs}\n", format, directive->str + 1, c); goto directive_error; } if (dstrcat(&directive->str, specifier) != 0) { perror("dstrcat()"); goto directive_error; } if (append_literal(&tail, &literal, false) != 0) { goto directive_error; } append_directive(&tail, directive); continue; directive_error: free_directive(directive); goto error; } one_char: if (dstrapp(&literal->str, c) != 0) { perror("dstrapp()"); goto error; } } done: if (append_literal(&tail, &literal, true) != 0) { goto error; } free_directive(literal); return command; error: free_directive(literal); free_bfs_printf(command); return NULL; } int bfs_printf(FILE *file, const struct bfs_printf *command, const struct BFTW *ftwbuf) { int ret = -1; for (struct bfs_printf_directive *directive = command->directives; directive; directive = directive->next) { if (directive->fn(file, directive, ftwbuf) < 0) { goto done; } } ret = 0; done: return ret; } void free_bfs_printf(struct bfs_printf *command) { if (command) { struct bfs_printf_directive *directive = command->directives; while (directive) { struct bfs_printf_directive *next = directive->next; free_directive(directive); directive = next; } free(command); } } bfs-1.2.1/printf.h000066400000000000000000000045431323715747000137570ustar00rootroot00000000000000/**************************************************************************** * 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. * ****************************************************************************/ #ifndef BFS_PRINTF_H #define BFS_PRINTF_H #include "bftw.h" #include "color.h" #include #include struct cmdline; struct bfs_printf_directive; /** * A printf command, the result of parsing a single format string. */ struct bfs_printf { /** The chain of printf directives. */ struct bfs_printf_directive *directives; /** Whether the struct bfs_stat must be filled in. */ bool needs_stat; }; /** * 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.2.1/stat.c000066400000000000000000000145001323715747000134150ustar00rootroot00000000000000/**************************************************************************** * bfs * * Copyright (C) 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 "stat.h" #include "util.h" #include #include #include #include #include #include #if __linux__ # include # include # include #elif __APPLE__ # define st_atim st_atimespec # define st_ctim st_ctimespec # define st_mtim st_mtimespec # define st_birthtim st_birthtimespec #endif /** * Check if we should retry a failed stat() due to a potentially broken link. */ static bool bfs_stat_retry(int ret, int at_flags, enum bfs_stat_flag flags) { return ret != 0 && !(at_flags & AT_SYMLINK_NOFOLLOW) && (flags & BFS_STAT_BROKEN_OK) && 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->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, at_flags, 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; } #ifdef STATX_BASIC_STATS /** * Wrapper for the statx() system call, which has no native glibc wrapper. */ static int bfs_statx(int at_fd, const char *at_path, int at_flags, unsigned int mask, struct statx *buf) { return syscall(__NR_statx, at_fd, at_path, at_flags, mask, buf); } /** * 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, at_flags, flags)) { at_flags |= AT_SYMLINK_NOFOLLOW; ret = bfs_statx(at_fd, at_path, at_flags, mask, &xbuf); } if (ret != 0) { return ret; } if ((xbuf.stx_mask & STATX_BASIC_STATS) != STATX_BASIC_STATS) { // Callers shouldn't have to check anything except BFS_STAT_BTIME errno = EINVAL; return -1; } buf->mask = 0; buf->dev = 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; } 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 // STATX_BASIC_STATS int bfs_stat(int at_fd, const char *at_path, int at_flags, enum bfs_stat_flag flags, struct bfs_stat *buf) { #ifdef STATX_BASIC_STATS static bool has_statx = true; if (has_statx) { int ret = bfs_statx_impl(at_fd, at_path, at_flags, flags, buf); if (ret != 0 && errno == ENOSYS) { has_statx = false; } else { return ret; } } #endif return bfs_stat_impl(at_fd, at_path, at_flags, flags, buf); } int bfs_fstat(int fd, struct bfs_stat *buf) { #ifdef AT_EMPTY_PATH return bfs_stat(fd, "", AT_EMPTY_PATH, 0, buf); #else struct stat statbuf; int ret = fstat(fd, &statbuf); if (ret == 0) { bfs_stat_convert(&statbuf, buf); } return ret; #endif } bfs-1.2.1/stat.h000066400000000000000000000052541323715747000134300ustar00rootroot00000000000000/**************************************************************************** * bfs * * Copyright (C) 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. * ****************************************************************************/ #ifndef BFS_STAT_H #define BFS_STAT_H #include #include /** * 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_ATIME = 1 << 9, BFS_STAT_BTIME = 1 << 10, BFS_STAT_CTIME = 1 << 11, BFS_STAT_MTIME = 1 << 12, }; /** * bfs_stat() flags. */ enum bfs_stat_flag { /** Fall back to the link itself on broken symlinks. */ BFS_STAT_BROKEN_OK = 1 << 0, }; /** * 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 512B blocks allocated. */ blkcnt_t blocks; /** 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(). */ int bfs_stat(int at_fd, const char *at_path, int at_flags, enum bfs_stat_flag flags, struct bfs_stat *buf); /** * Facade over fstat(). */ int bfs_fstat(int fd, struct bfs_stat *buf); #endif // BFS_STAT_H bfs-1.2.1/tests.sh000077500000000000000000000763651323715747000140200ustar00rootroot00000000000000#!/bin/bash ############################################################################ # bfs # # Copyright (C) 2015-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. # ############################################################################ set -o physical umask 022 export LC_ALL=C export TZ=UTC # 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() { rm -rf "$TMP" } trap cleanup EXIT # Install a file, creating any parent directories function installp() { local target="${@: -1}" mkdir -p "${target%/*}" install "$@" } # Like a mythical touch -p function touchp() { installp -m644 /dev/null "$1" } # 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 -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" 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_links "$TMP/links" # 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" # 17 * 256 > 16 * 256 == 4096 == PATH_MAX for j in {1..17}; do mkdir "$name" cd "$name" 2>/dev/null done ) done } make_deep "$TMP/deep" # Creates a scratch directory that tests can modify function make_scratch() { mkdir -p "$1" } make_scratch "$TMP/scratch" function _realpath() { ( cd "${1%/*}" echo "$PWD/${1##*/}" ) } BFS="$(_realpath ./bfs)" TESTS="$(_realpath ./tests)" posix_tests=( test_basic test_type_d test_type_f test_mindepth test_maxdepth test_depth test_depth_slash test_depth_mindepth_1 test_depth_mindepth_2 test_depth_maxdepth_1 test_depth_maxdepth_2 test_name test_name_root test_name_root_depth test_name_trailing_slash test_path test_newer test_newer_link test_links test_links_plus test_links_minus test_H test_H_slash test_H_broken test_H_newer test_L test_L_depth test_user_name test_user_id test_group_name test_group_id test_size test_size_plus test_size_bytes test_exec test_exec_plus test_exec_plus_status test_exec_plus_semicolon test_flag_comma 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_ok_stdin test_ok_plus_semicolon test_parens test_bang test_implicit_and test_a test_o test_deep test_double_negation test_de_morgan_not test_de_morgan_and test_de_morgan_or test_data_flow_type ) bsd_tests=( test_P test_P_slash test_X test_follow test_samefile test_samefile_symlink test_H_samefile_symlink test_samefile_broken test_H_samefile_broken test_name_slash test_name_slashes test_iname test_ipath test_lname test_ilname test_L_lname test_L_ilname test_newerma test_size_big test_exec_substring test_execdir_pwd test_execdir_slash test_execdir_slash_pwd test_execdir_slashes test_double_dash test_flag_double_dash test_ok_stdin test_okdir_stdin test_delete test_rm test_regex test_iregex test_regex_parens test_E test_d_path test_f 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_gid_name test_uid_name test_mnewer test_H_mnewer test_perm_222_plus test_perm_644_plus test_size_T 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_inum test_nogroup test_nouser test_exit test_printx test_data_flow_depth ) gnu_tests=( test_true test_false test_executable test_readable test_writable test_empty test_gid test_gid_plus test_gid_minus test_uid test_uid_plus test_uid_minus test_anewer test_P test_P_slash test_follow test_samefile test_samefile_symlink test_H_samefile_symlink test_samefile_broken test_H_samefile_broken test_xtype_l test_xtype_f test_L_xtype_l test_L_xtype_f test_name_slash test_name_slashes test_iname test_ipath test_lname test_ilname test_L_lname test_L_ilname test_daystart test_daystart_twice test_newerma test_size_big 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_okdir_plus_semicolon test_weird_names test_flag_weird_names test_follow_comma test_fprint test_fprint_duplicate test_double_dash test_flag_double_dash test_ignore_readdir_race test_ignore_readdir_race_root test_ignore_readdir_race_notdir test_perm_222_slash test_perm_644_slash test_perm_symbolic_slash test_perm_leading_plus_symbolic_slash test_delete test_regex test_iregex test_regex_parens test_regextype_posix_basic test_regextype_posix_extended test_path_d test_quit test_quit_child test_quit_depth test_quit_depth_child test_inum test_nogroup test_nouser test_printf 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_quit_after_print test_quit_before_print test_fstype test_not test_and test_or test_comma test_precedence test_and_purity test_or_purity test_not_reachability test_comma_reachability test_print_error test_fprint_error ) bfs_tests=( test_type_multi test_xtype_multi test_xtype_reorder test_perm_symbolic_trailing_comma test_perm_symbolic_double_comma test_perm_symbolic_missing_action test_perm_leading_plus_symbolic test_execdir_plus test_hidden test_nohidden test_printf_w test_path_flag_expr test_path_expr_flag test_flag_expr_path test_expr_flag_path test_expr_path_flag test_colors test_deep_strict test_deep_stricter ) BSD=yes GNU=yes ALL=yes function enable_tests() { for test; do eval run_$test=yes done } for arg; do case "$arg" in --bfs=*) BFS="${arg#*=}" ;; --posix) BSD= GNU= ALL= ;; --bsd) BSD=yes GNU= ALL= ;; --gnu) BSD= GNU=yes ALL= ;; --all) BSD=yes GNU=yes ALL=yes ;; --update) UPDATE=yes ;; --verbose) VERBOSE=yes ;; test_*) EXPLICIT=yes enable_tests "$arg" ;; *) echo "Unrecognized option '$arg'." >&2 exit 1 ;; esac done if [ -z "$EXPLICIT" ]; then enable_tests "${posix_tests[@]}" [ "$BSD" ] && enable_tests "${bsd_tests[@]}" [ "$GNU" ] && enable_tests "${gnu_tests[@]}" [ "$ALL" ] && enable_tests "${bfs_tests[@]}" fi function bfs_sort() { awk -F/ '{ print NF - 1 " " $0 }' | sort -n | cut -d' ' -f2- } if [ "$VERBOSE" ]; then # dup stdout for verbose logging even when redirected exec 3>&1 fi function bfs_verbose() { if [ "$VERBOSE" ]; then if [ -t 3 ]; then printf '\033[1;32m%q\033[0m ' $BFS >&3 local expr_started= for arg; do if [[ $arg == -[A-Z]* ]]; then printf '\033[1;36m%q\033[0m ' "$arg" >&3 elif [[ $arg == [\(!] || $arg == -[ao] || $arg == -and || $arg == -or || $arg == -not ]]; then expr_started=yes printf '\033[1;31m%q\033[0m ' "$arg" >&3 elif [[ $expr_started && $arg == [\),] ]]; then printf '\033[1;31m%q\033[0m ' "$arg" >&3 elif [[ $arg == -?* ]]; then expr_started=yes printf '\033[1;34m%q\033[0m ' "$arg" >&3 elif [ "$expr_started" ]; then printf '\033[1m%q\033[0m ' "$arg" >&3 else printf '\033[1;35m%q\033[0m ' "$arg" >&3 fi done else printf '%q ' $BFS "$@" >&3 fi printf '\n' >&3 fi } function invoke_bfs() { bfs_verbose "$@" $BFS "$@" } 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 OUT="$TESTS/${FUNCNAME[1]}.out" if [ "$UPDATE" ]; then $BFS "$@" | bfs_sort >"$OUT" else diff -u "$OUT" <($BFS "$@" | bfs_sort) 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 } cd "$TMP" # 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_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 / 2>/dev/null } function test_name_slashes() { bfs_diff /// -maxdepth 0 -name / 2>/dev/null } 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_minus() { 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_minus() { 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_newer() { bfs_diff -H times -newer times/l } function test_L() { bfs_diff -L links 2>/dev/null } function test_X() { bfs_diff -X weirdnames 2>/dev/null } function test_follow() { bfs_diff links -follow 2>/dev/null } function test_L_depth() { bfs_diff -L links -depth 2>/dev/null } 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_samefile_broken() { bfs_diff links -samefile links/broken } function test_H_samefile_broken() { bfs_diff -H links -samefile links/broken } function test_xtype_l() { bfs_diff links -xtype l 2>/dev/null } function test_xtype_f() { bfs_diff links -xtype f 2>/dev/null } function test_L_xtype_l() { bfs_diff -L links -xtype l 2>/dev/null } function test_L_xtype_f() { bfs_diff -L links -xtype f 2>/dev/null } function test_xtype_multi() { bfs_diff links -xtype f,d,c 2>/dev/null } 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 links -links 100 -xtype l invoke_bfs links -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]' 2>/dev/null } function test_L_ilname() { bfs_diff -L links -ilname '[AQ]' 2>/dev/null } 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_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() { bfs_diff basic -execdir "$TESTS/sort-args.sh" '{}' + } 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_weird_names() { cd weirdnames bfs_diff '-' '(-' '!-' ',' ')' './(' './!' \( \! -print , -print \) } function test_flag_weird_names() { cd weirdnames bfs_diff -L '-' '(-' '!-' ',' ')' './(' './!' \( \! -print , -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_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_ok_stdin() { # -ok should *not* close stdin # See https://savannah.gnu.org/bugs/?24561 yes | bfs_diff basic -ok bash -c "printf '%s? ' {} && head -n1" \; 2>/dev/null } function test_okdir_stdin() { # -okdir should *not* close stdin yes | bfs_diff basic -okdir bash -c "printf '%s? ' {} && head -n1" \; 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_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() { local inode="$(ls -id basic/k/foo/bar | cut -f1 -d' ')" bfs_diff basic -inum "$inode" } function test_nogroup() { bfs_diff basic -nogroup } function test_nouser() { bfs_diff basic -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_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 links -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 OUT="$TESTS/${FUNCNAME[0]}.out" local ARGS=(basic -maxdepth 0 -printf '%h\0%f\n') if [ "$UPDATE" ]; then invoke_bfs "${ARGS[@]}" >"$OUT" else diff -u "$OUT" <(invoke_bfs "${ARGS[@]}") fi } function test_printf_w() { # Birth times may not be supported, so just check that %w/%W can be parsed bfs_diff times -false -printf '%w %WY\n' } function test_fstype() { fstype="$(invoke_bfs -printf '%F\n' | head -n1)" 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_colors() { LS_COLORS= bfs_diff links -color } function test_deep() { closefrom 4 ulimit -n 16 bfs_diff deep -mindepth 18 -exec /bin/bash -c 'echo "${1:0:6}/.../${1##*/} (${#1})"' /bin/bash '{}' \; } function test_deep_strict() { closefrom 4 # Not even enough fds to keep the root open ulimit -n 7 bfs_diff deep -mindepth 18 -exec /bin/bash -c 'echo "${1:0:6}/.../${1##*/} (${#1})"' /bin/bash '{}' \; } function test_deep_stricter() { closefrom 4 # So few fds that pipe() fails in the -exec implementation ulimit -n 6 bfs_diff deep -mindepth 18 -exec /bin/bash -c 'echo "${1:0:6}/.../${1##*/} (${#1})"' /bin/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 -name bar -exit -o -print } 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_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_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 } if [ -t 1 -a ! "$VERBOSE" ]; then in_place=yes fi passed=0 failed=0 for test in ${!run_*}; do test=${test#run_} if [ "$in_place" ]; then printf '\r\033[J%s' "$test" else echo "$test" fi ("$test" "$dir") status=$? if [ $status -eq 0 ]; then ((++passed)) else ((++failed)) if [ "$in_place" ]; then printf '\r\033[J%s\n' "$test failed!" else echo "$test failed!" fi fi done if [ "$in_place" ]; then printf '\r\033[J' fi if [ $passed -gt 0 ]; then echo "tests passed: $passed" fi if [ $failed -gt 0 ]; then echo "tests failed: $failed" exit 1 fi bfs-1.2.1/tests/000077500000000000000000000000001323715747000134405ustar00rootroot00000000000000bfs-1.2.1/tests/remove-sibling.sh000077500000000000000000000002011323715747000167120ustar00rootroot00000000000000#!/bin/bash for file in "${1%/*}"/*; do if [ "$file" != "$1" ]; then rm "$file" exit $? fi done exit 1 bfs-1.2.1/tests/sort-args.sh000077500000000000000000000001221323715747000157130ustar00rootroot00000000000000#!/bin/bash args=($({ for arg; do echo "$arg"; done } | sort)) echo "${args[@]}" bfs-1.2.1/tests/test_E.out000066400000000000000000000000041323715747000154060ustar00rootroot00000000000000./( bfs-1.2.1/tests/test_H.out000066400000000000000000000000301323715747000154100ustar00rootroot00000000000000links/deeply/nested/dir bfs-1.2.1/tests/test_H_broken.out000066400000000000000000000000151323715747000167530ustar00rootroot00000000000000links/broken bfs-1.2.1/tests/test_H_mnewer.out000066400000000000000000000000361323715747000167730ustar00rootroot00000000000000times times/b times/c times/l bfs-1.2.1/tests/test_H_newer.out000066400000000000000000000000361323715747000166160ustar00rootroot00000000000000times times/b times/c times/l bfs-1.2.1/tests/test_H_samefile_broken.out000066400000000000000000000000151323715747000206200ustar00rootroot00000000000000links/broken bfs-1.2.1/tests/test_H_samefile_symlink.out000066400000000000000000000000321323715747000210250ustar00rootroot00000000000000links/file links/hardlink bfs-1.2.1/tests/test_H_slash.out000066400000000000000000000000311323715747000166030ustar00rootroot00000000000000links/deeply/nested/dir/ bfs-1.2.1/tests/test_L.out000066400000000000000000000002531323715747000154230ustar00rootroot00000000000000links links/broken links/deeply links/file links/hardlink links/notdir links/skip links/symlink links/deeply/nested links/skip/dir links/skip/loop links/deeply/nested/dir bfs-1.2.1/tests/test_L_depth.out000066400000000000000000000002531323715747000166070ustar00rootroot00000000000000links links/broken links/deeply links/file links/hardlink links/notdir links/skip links/symlink links/deeply/nested links/skip/dir links/skip/loop links/deeply/nested/dir bfs-1.2.1/tests/test_L_ilname.out000066400000000000000000000000001323715747000167360ustar00rootroot00000000000000bfs-1.2.1/tests/test_L_lname.out000066400000000000000000000000001323715747000165650ustar00rootroot00000000000000bfs-1.2.1/tests/test_L_xtype_f.out000066400000000000000000000000321323715747000171540ustar00rootroot00000000000000links/file links/hardlink bfs-1.2.1/tests/test_L_xtype_l.out000066400000000000000000000001031323715747000171610ustar00rootroot00000000000000links/broken links/notdir links/skip links/symlink links/skip/loop bfs-1.2.1/tests/test_P.out000066400000000000000000000000301323715747000154200ustar00rootroot00000000000000links/deeply/nested/dir bfs-1.2.1/tests/test_P_slash.out000066400000000000000000000000311323715747000166130ustar00rootroot00000000000000links/deeply/nested/dir/ bfs-1.2.1/tests/test_X.out000066400000000000000000000003631323715747000154410ustar00rootroot00000000000000weirdnames 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.2.1/tests/test_a.out000066400000000000000000000000301323715747000154410ustar00rootroot00000000000000basic/k/foo basic/l/foo bfs-1.2.1/tests/test_and.out000066400000000000000000000000301323715747000157630ustar00rootroot00000000000000basic/k/foo basic/l/foo bfs-1.2.1/tests/test_and_purity.out000066400000000000000000000000001323715747000173740ustar00rootroot00000000000000bfs-1.2.1/tests/test_anewer.out000066400000000000000000000000361323715747000165100ustar00rootroot00000000000000times times/b times/c times/l bfs-1.2.1/tests/test_bang.out000066400000000000000000000002401323715747000161330ustar00rootroot00000000000000basic 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.2.1/tests/test_basic.out000066400000000000000000000003041323715747000163060ustar00rootroot00000000000000basic 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.2.1/tests/test_colors.out000066400000000000000000000007011323715747000165270ustar00rootroot00000000000000links links/deeply links/skip links/symlink links/broken links/loop links/notdir links/file links/hardlink links/deeply/nested links/deeply/nested/dir links/deeply/nested/loop bfs-1.2.1/tests/test_comma.out000066400000000000000000000003621323715747000163250ustar00rootroot00000000000000basic 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.2.1/tests/test_comma_reachability.out000066400000000000000000000000061323715747000210400ustar00rootroot00000000000000basic bfs-1.2.1/tests/test_d_path.out000066400000000000000000000003041323715747000164640ustar00rootroot00000000000000basic 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.2.1/tests/test_data_flow_depth.out000066400000000000000000000001421323715747000203510ustar00rootroot00000000000000basic/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.2.1/tests/test_data_flow_type.out000066400000000000000000000000001323715747000202170ustar00rootroot00000000000000bfs-1.2.1/tests/test_daystart.out000066400000000000000000000003041323715747000170600ustar00rootroot00000000000000basic 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.2.1/tests/test_daystart_twice.out000066400000000000000000000003041323715747000202530ustar00rootroot00000000000000basic 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.2.1/tests/test_de_morgan_and.out000066400000000000000000000001301323715747000177770ustar00rootroot00000000000000basic basic/c basic/e basic/g basic/i basic/j basic/k basic/l basic/g/h basic/l/foo/bar bfs-1.2.1/tests/test_de_morgan_not.out000066400000000000000000000001101323715747000200330ustar00rootroot00000000000000basic/a basic/b basic/c/d basic/e/f basic/k/foo/bar basic/l/foo/bar/baz bfs-1.2.1/tests/test_de_morgan_or.out000066400000000000000000000002701323715747000176620ustar00rootroot00000000000000basic 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.2.1/tests/test_deep.out000066400000000000000000000104401323715747000161440ustar00rootroot00000000000000deep/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.2.1/tests/test_deep_strict.out000066400000000000000000000104401323715747000175340ustar00rootroot00000000000000deep/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.2.1/tests/test_deep_stricter.out000066400000000000000000000104401323715747000200630ustar00rootroot00000000000000deep/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.2.1/tests/test_delete.out000066400000000000000000000000101323715747000164610ustar00rootroot00000000000000scratch bfs-1.2.1/tests/test_depth.out000066400000000000000000000003041323715747000163310ustar00rootroot00000000000000basic 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.2.1/tests/test_depth_depth_n.out000066400000000000000000000001021323715747000200260ustar00rootroot00000000000000basic/c/d basic/e/f basic/g/h basic/j/foo basic/k/foo basic/l/foo bfs-1.2.1/tests/test_depth_depth_n_minus.out000066400000000000000000000001161323715747000212460ustar00rootroot00000000000000basic basic/a basic/b basic/c basic/e basic/g basic/i basic/j basic/k basic/l bfs-1.2.1/tests/test_depth_depth_n_plus.out000066400000000000000000000000641323715747000211000ustar00rootroot00000000000000basic/k/foo/bar basic/l/foo/bar basic/l/foo/bar/baz bfs-1.2.1/tests/test_depth_maxdepth_1.out000066400000000000000000000001161323715747000204440ustar00rootroot00000000000000basic basic/a basic/b basic/c basic/e basic/g basic/i basic/j basic/k basic/l bfs-1.2.1/tests/test_depth_maxdepth_2.out000066400000000000000000000002201323715747000204410ustar00rootroot00000000000000basic 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.2.1/tests/test_depth_mindepth_1.out000066400000000000000000000002761323715747000204510ustar00rootroot00000000000000basic/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.2.1/tests/test_depth_mindepth_2.out000066400000000000000000000001661323715747000204500ustar00rootroot00000000000000basic/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.2.1/tests/test_depth_n.out000066400000000000000000000001021323715747000166420ustar00rootroot00000000000000basic/c/d basic/e/f basic/g/h basic/j/foo basic/k/foo basic/l/foo bfs-1.2.1/tests/test_depth_n_minus.out000066400000000000000000000001161323715747000200620ustar00rootroot00000000000000basic basic/a basic/b basic/c basic/e basic/g basic/i basic/j basic/k basic/l bfs-1.2.1/tests/test_depth_n_plus.out000066400000000000000000000000641323715747000177140ustar00rootroot00000000000000basic/k/foo/bar basic/l/foo/bar basic/l/foo/bar/baz bfs-1.2.1/tests/test_depth_overflow.out000066400000000000000000000003041323715747000202540ustar00rootroot00000000000000basic 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.2.1/tests/test_depth_slash.out000066400000000000000000000003051323715747000175240ustar00rootroot00000000000000basic/ 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.2.1/tests/test_double_dash.out000066400000000000000000000000701323715747000174760ustar00rootroot00000000000000./a ./b ./c/d ./e/f ./j/foo ./k/foo/bar ./l/foo/bar/baz bfs-1.2.1/tests/test_double_negation.out000066400000000000000000000000441323715747000203640ustar00rootroot00000000000000basic/j/foo basic/k/foo basic/l/foo bfs-1.2.1/tests/test_empty.out000066400000000000000000000001221323715747000163610ustar00rootroot00000000000000basic/a basic/b basic/i basic/c/d basic/e/f basic/g/h basic/j/foo basic/k/foo/bar bfs-1.2.1/tests/test_exec.out000066400000000000000000000003041323715747000161510ustar00rootroot00000000000000basic 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.2.1/tests/test_exec_plus.out000066400000000000000000000003041323715747000172140ustar00rootroot00000000000000basic 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.2.1/tests/test_exec_plus_semicolon.out000066400000000000000000000007161323715747000212730ustar00rootroot00000000000000foo 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.2.1/tests/test_exec_plus_status.out000066400000000000000000000003041323715747000206170ustar00rootroot00000000000000basic 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.2.1/tests/test_exec_substring.out000066400000000000000000000003521323715747000202540ustar00rootroot00000000000000-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.2.1/tests/test_execdir.out000066400000000000000000000001341323715747000166510ustar00rootroot00000000000000./a ./b ./bar ./bar ./basic ./baz ./c ./d ./e ./f ./foo ./foo ./foo ./g ./h ./i ./j ./k ./l bfs-1.2.1/tests/test_execdir_plus.out000066400000000000000000000001341323715747000177140ustar00rootroot00000000000000./bar ./bar ./basic ./baz ./d ./f ./foo ./foo ./foo ./h ./a ./b ./c ./e ./g ./i ./j ./k ./l bfs-1.2.1/tests/test_execdir_plus_semicolon.out000066400000000000000000000005461323715747000217730ustar00rootroot00000000000000foo ./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.2.1/tests/test_execdir_pwd.out000066400000000000000000000002171323715747000175250ustar00rootroot00000000000000 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.2.1/tests/test_execdir_slash.out000066400000000000000000000000021323715747000200350ustar00rootroot00000000000000/ bfs-1.2.1/tests/test_execdir_slash_pwd.out000066400000000000000000000000021323715747000207070ustar00rootroot00000000000000/ bfs-1.2.1/tests/test_execdir_slashes.out000066400000000000000000000000021323715747000203650ustar00rootroot00000000000000/ bfs-1.2.1/tests/test_execdir_substring.out000066400000000000000000000002021323715747000207450ustar00rootroot00000000000000-./a- -./b- -./bar- -./bar- -./basic- -./baz- -./c- -./d- -./e- -./f- -./foo- -./foo- -./foo- -./g- -./h- -./i- -./j- -./k- -./l- bfs-1.2.1/tests/test_executable.out000066400000000000000000000000421323715747000173450ustar00rootroot00000000000000perms perms/rwx perms/rx perms/wx bfs-1.2.1/tests/test_exit.out000066400000000000000000000002201323715747000161730ustar00rootroot00000000000000basic 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.2.1/tests/test_expr_flag_path.out000066400000000000000000000000201323715747000202030ustar00rootroot00000000000000links/skip/loop bfs-1.2.1/tests/test_expr_path_flag.out000066400000000000000000000000201323715747000202030ustar00rootroot00000000000000links/skip/loop bfs-1.2.1/tests/test_f.out000066400000000000000000000000141323715747000154500ustar00rootroot00000000000000( - (/b -/a bfs-1.2.1/tests/test_false.out000066400000000000000000000000001323715747000163100ustar00rootroot00000000000000bfs-1.2.1/tests/test_flag_comma.out000066400000000000000000000000061323715747000173110ustar00rootroot00000000000000, ,/f bfs-1.2.1/tests/test_flag_double_dash.out000066400000000000000000000000701323715747000204670ustar00rootroot00000000000000./a ./b ./c/d ./e/f ./j/foo ./k/foo/bar ./l/foo/bar/baz bfs-1.2.1/tests/test_flag_expr_path.out000066400000000000000000000000201323715747000202030ustar00rootroot00000000000000links/skip/loop bfs-1.2.1/tests/test_flag_weird_names.out000066400000000000000000000001541323715747000205160ustar00rootroot00000000000000!- !- (- (- ) ) , , - - !-/e !-/e (-/c (-/c )/g )/g ,/f ,/f -/a -/a ./! ./! ./( ./( ./!/d ./!/d ./(/b ./(/b bfs-1.2.1/tests/test_follow.out000066400000000000000000000002531323715747000165320ustar00rootroot00000000000000links links/broken links/deeply links/file links/hardlink links/notdir links/skip links/symlink links/deeply/nested links/skip/dir links/skip/loop links/deeply/nested/dir bfs-1.2.1/tests/test_follow_comma.out000066400000000000000000000001561323715747000177100ustar00rootroot00000000000000. ./ ./! ./!- ./( ./(- ./) ./, ./- ./... ./\ ./ /j ./!-/e ./!/d ./(-/c ./(/b ./)/g ./,/f ./-/a ./.../h ./\/i bfs-1.2.1/tests/test_fprint.out000066400000000000000000000003041323715747000165270ustar00rootroot00000000000000basic 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.2.1/tests/test_fprint_duplicate.out000066400000000000000000000011141323715747000205610ustar00rootroot00000000000000basic 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.2.1/tests/test_fstype.out000066400000000000000000000003041323715747000165370ustar00rootroot00000000000000basic 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.2.1/tests/test_gid.out000066400000000000000000000003041323715747000157700ustar00rootroot00000000000000basic 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.2.1/tests/test_gid_minus.out000066400000000000000000000003041323715747000172030ustar00rootroot00000000000000basic 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.2.1/tests/test_gid_name.out000066400000000000000000000003041323715747000167700ustar00rootroot00000000000000basic 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.2.1/tests/test_gid_plus.out000066400000000000000000000003041323715747000170330ustar00rootroot00000000000000basic 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.2.1/tests/test_group_id.out000066400000000000000000000003041323715747000170350ustar00rootroot00000000000000basic 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.2.1/tests/test_group_name.out000066400000000000000000000003041323715747000173610ustar00rootroot00000000000000basic 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.2.1/tests/test_hidden.out000066400000000000000000000000171323715747000164610ustar00rootroot00000000000000weirdnames/... bfs-1.2.1/tests/test_ilname.out000066400000000000000000000000001323715747000164630ustar00rootroot00000000000000bfs-1.2.1/tests/test_implicit_and.out000066400000000000000000000000301323715747000176550ustar00rootroot00000000000000basic/k/foo basic/l/foo bfs-1.2.1/tests/test_iname.out000066400000000000000000000000561323715747000163220ustar00rootroot00000000000000basic/e/f basic/j/foo basic/k/foo basic/l/foo bfs-1.2.1/tests/test_inum.out000066400000000000000000000000201323715747000161700ustar00rootroot00000000000000basic/k/foo/bar bfs-1.2.1/tests/test_ipath.out000066400000000000000000000001421323715747000163320ustar00rootroot00000000000000basic/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.2.1/tests/test_iregex.out000066400000000000000000000000361323715747000165120ustar00rootroot00000000000000basic/c/d basic/e/f basic/g/h bfs-1.2.1/tests/test_links.out000066400000000000000000000000321323715747000163430ustar00rootroot00000000000000links/file links/hardlink bfs-1.2.1/tests/test_links_minus.out000066400000000000000000000000001323715747000175510ustar00rootroot00000000000000bfs-1.2.1/tests/test_links_plus.out000066400000000000000000000000321323715747000174060ustar00rootroot00000000000000links/file links/hardlink bfs-1.2.1/tests/test_lname.out000066400000000000000000000000001323715747000163120ustar00rootroot00000000000000bfs-1.2.1/tests/test_maxdepth.out000066400000000000000000000001161323715747000170400ustar00rootroot00000000000000basic basic/a basic/b basic/c basic/e basic/g basic/i basic/j basic/k basic/l bfs-1.2.1/tests/test_mindepth.out000066400000000000000000000002761323715747000170450ustar00rootroot00000000000000basic/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.2.1/tests/test_mnewer.out000066400000000000000000000000361323715747000165240ustar00rootroot00000000000000times times/b times/c times/l bfs-1.2.1/tests/test_name.out000066400000000000000000000000561323715747000161510ustar00rootroot00000000000000basic/e/f basic/j/foo basic/k/foo basic/l/foo bfs-1.2.1/tests/test_name_root.out000066400000000000000000000000101323715747000172020ustar00rootroot00000000000000basic/a bfs-1.2.1/tests/test_name_root_depth.out000066400000000000000000000000101323715747000203660ustar00rootroot00000000000000basic/g bfs-1.2.1/tests/test_name_slash.out000066400000000000000000000000021323715747000173320ustar00rootroot00000000000000/ bfs-1.2.1/tests/test_name_slashes.out000066400000000000000000000000041323715747000176640ustar00rootroot00000000000000/// bfs-1.2.1/tests/test_name_trailing_slash.out000066400000000000000000000000111323715747000212230ustar00rootroot00000000000000basic/g/ bfs-1.2.1/tests/test_newer.out000066400000000000000000000000361323715747000163470ustar00rootroot00000000000000times times/b times/c times/l bfs-1.2.1/tests/test_newer_link.out000066400000000000000000000000061323715747000173610ustar00rootroot00000000000000times bfs-1.2.1/tests/test_newerma.out000066400000000000000000000000361323715747000166650ustar00rootroot00000000000000times times/b times/c times/l bfs-1.2.1/tests/test_nogroup.out000066400000000000000000000000001323715747000167070ustar00rootroot00000000000000bfs-1.2.1/tests/test_nohidden.out000066400000000000000000000004131323715747000170160ustar00rootroot00000000000000weirdnames 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.2.1/tests/test_not.out000066400000000000000000000002401323715747000160240ustar00rootroot00000000000000basic 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.2.1/tests/test_not_reachability.out000066400000000000000000000000061323715747000205440ustar00rootroot00000000000000basic bfs-1.2.1/tests/test_nouser.out000066400000000000000000000000001323715747000165310ustar00rootroot00000000000000bfs-1.2.1/tests/test_o.out000066400000000000000000000001741323715747000154700ustar00rootroot00000000000000basic 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.2.1/tests/test_ok_plus_semicolon.out000066400000000000000000000003521323715747000207540ustar00rootroot00000000000000basic + 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.2.1/tests/test_ok_stdin.out000066400000000000000000000003751323715747000170470ustar00rootroot00000000000000basic? 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.2.1/tests/test_okdir_plus_semicolon.out000066400000000000000000000002021323715747000214450ustar00rootroot00000000000000./a + ./b + ./bar + ./bar + ./basic + ./baz + ./c + ./d + ./e + ./f + ./foo + ./foo + ./foo + ./g + ./h + ./i + ./j + ./k + ./l + bfs-1.2.1/tests/test_okdir_stdin.out000066400000000000000000000002251323715747000175400ustar00rootroot00000000000000./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.2.1/tests/test_or.out000066400000000000000000000001741323715747000156520ustar00rootroot00000000000000basic 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.2.1/tests/test_or_purity.out000066400000000000000000000000001323715747000172520ustar00rootroot00000000000000bfs-1.2.1/tests/test_parens.out000066400000000000000000000000561323715747000165210ustar00rootroot00000000000000basic/e/f basic/j/foo basic/k/foo basic/l/foo bfs-1.2.1/tests/test_path.out000066400000000000000000000001421323715747000161610ustar00rootroot00000000000000basic/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.2.1/tests/test_path_d.out000066400000000000000000000003041323715747000164640ustar00rootroot00000000000000basic 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.2.1/tests/test_path_expr_flag.out000066400000000000000000000000201323715747000202030ustar00rootroot00000000000000links/skip/loop bfs-1.2.1/tests/test_path_flag_expr.out000066400000000000000000000000201323715747000202030ustar00rootroot00000000000000links/skip/loop bfs-1.2.1/tests/test_perm_222.out000066400000000000000000000000101323715747000165470ustar00rootroot00000000000000perms/w bfs-1.2.1/tests/test_perm_222_minus.out000066400000000000000000000000101323715747000177620ustar00rootroot00000000000000perms/w bfs-1.2.1/tests/test_perm_222_plus.out000066400000000000000000000000521323715747000176200ustar00rootroot00000000000000perms perms/rw perms/rwx perms/w perms/wx bfs-1.2.1/tests/test_perm_222_slash.out000066400000000000000000000000521323715747000177470ustar00rootroot00000000000000perms perms/rw perms/rwx perms/w perms/wx bfs-1.2.1/tests/test_perm_644.out000066400000000000000000000000111323715747000165600ustar00rootroot00000000000000perms/rw bfs-1.2.1/tests/test_perm_644_minus.out000066400000000000000000000000311323715747000177750ustar00rootroot00000000000000perms perms/rw perms/rwx bfs-1.2.1/tests/test_perm_644_plus.out000066400000000000000000000000731323715747000176330ustar00rootroot00000000000000perms perms/r perms/rw perms/rwx perms/rx perms/w perms/wx bfs-1.2.1/tests/test_perm_644_slash.out000066400000000000000000000000731323715747000177620ustar00rootroot00000000000000perms perms/r perms/rw perms/rwx perms/rx perms/w perms/wx bfs-1.2.1/tests/test_perm_leading_plus_symbolic.out000066400000000000000000000000001323715747000226100ustar00rootroot00000000000000bfs-1.2.1/tests/test_perm_leading_plus_symbolic_minus.out000066400000000000000000000000001323715747000240230ustar00rootroot00000000000000bfs-1.2.1/tests/test_perm_leading_plus_symbolic_slash.out000066400000000000000000000000731323715747000240140ustar00rootroot00000000000000perms perms/r perms/rw perms/rwx perms/rx perms/w perms/wx bfs-1.2.1/tests/test_perm_symbolic.out000066400000000000000000000000001323715747000200620ustar00rootroot00000000000000bfs-1.2.1/tests/test_perm_symbolic_minus.out000066400000000000000000000000311323715747000213010ustar00rootroot00000000000000perms perms/rw perms/rwx bfs-1.2.1/tests/test_perm_symbolic_slash.out000066400000000000000000000000731323715747000212660ustar00rootroot00000000000000perms perms/r perms/rw perms/rwx perms/rx perms/w perms/wx bfs-1.2.1/tests/test_permcopy.out000066400000000000000000000000111323715747000170560ustar00rootroot00000000000000perms/rw bfs-1.2.1/tests/test_precedence.out000066400000000000000000000000741323715747000173260ustar00rootroot00000000000000basic/k/foo basic/l/foo basic/k/foo/bar basic/l/foo/bar/baz bfs-1.2.1/tests/test_printf.out000066400000000000000000000031441323715747000165340ustar00rootroot00000000000000%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.2.1/tests/test_printf_escapes.out000066400000000000000000000000121323715747000202260ustar00rootroot000000000000008 8I8I18 bfs-1.2.1/tests/test_printf_flags.out000066400000000000000000000006421323715747000177100ustar00rootroot00000000000000|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.2.1/tests/test_printf_leak.out000066400000000000000000000000061323715747000175220ustar00rootroot00000000000000basic bfs-1.2.1/tests/test_printf_nul.out000066400000000000000000000000101323715747000173770ustar00rootroot00000000000000.basic bfs-1.2.1/tests/test_printf_slash.out000066400000000000000000000000101323715747000177130ustar00rootroot00000000000000(/)/(/) bfs-1.2.1/tests/test_printf_slashes.out000066400000000000000000000000101323715747000202430ustar00rootroot00000000000000(/)/(/) bfs-1.2.1/tests/test_printf_times.out000066400000000000000000000010641323715747000177340ustar00rootroot00000000000000times/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.2.1/tests/test_printf_trailing_slash.out000066400000000000000000000004231323715747000216140ustar00rootroot00000000000000(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.2.1/tests/test_printf_trailing_slashes.out000066400000000000000000000004711323715747000221470ustar00rootroot00000000000000(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.2.1/tests/test_printf_types.out000066400000000000000000000005341323715747000177600ustar00rootroot00000000000000(links) () d d (links/broken) (nowhere) l N (links/deeply) () d d (links/file) () f f (links/hardlink) () f f (links/loop) (loop) l L (links/symlink) (file) l f (links/deeply/nested) () d d (links/notdir) (symlink/file) l N (links/deeply/nested/dir) () d d (links/skip) (deeply/nested/loop/nested) l d (links/deeply/nested/loop) (../../deeply) l d bfs-1.2.1/tests/test_printf_w.out000066400000000000000000000000001323715747000170460ustar00rootroot00000000000000bfs-1.2.1/tests/test_printx.out000066400000000000000000000004571323715747000165620ustar00rootroot00000000000000weirdnames 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.2.1/tests/test_quit.out000066400000000000000000000000101323715747000162010ustar00rootroot00000000000000basic/g bfs-1.2.1/tests/test_quit_after_print.out000066400000000000000000000000061323715747000206030ustar00rootroot00000000000000basic bfs-1.2.1/tests/test_quit_before_print.out000066400000000000000000000000001323715747000207360ustar00rootroot00000000000000bfs-1.2.1/tests/test_quit_child.out000066400000000000000000000000221323715747000173470ustar00rootroot00000000000000basic/g basic/g/h bfs-1.2.1/tests/test_quit_depth.out000066400000000000000000000000221323715747000173700ustar00rootroot00000000000000basic/g basic/g/h bfs-1.2.1/tests/test_quit_depth_child.out000066400000000000000000000000121323715747000205320ustar00rootroot00000000000000basic/g/h bfs-1.2.1/tests/test_quit_implicit_print.out000066400000000000000000000000061323715747000213140ustar00rootroot00000000000000basic bfs-1.2.1/tests/test_readable.out000066400000000000000000000000521323715747000167640ustar00rootroot00000000000000perms perms/r perms/rw perms/rwx perms/rx bfs-1.2.1/tests/test_regex.out000066400000000000000000000000361323715747000163410ustar00rootroot00000000000000basic/c/d basic/e/f basic/g/h bfs-1.2.1/tests/test_regex_parens.out000066400000000000000000000000041323715747000177040ustar00rootroot00000000000000./( bfs-1.2.1/tests/test_regextype_posix_basic.out000066400000000000000000000000041323715747000216210ustar00rootroot00000000000000./( bfs-1.2.1/tests/test_regextype_posix_extended.out000066400000000000000000000000041323715747000223400ustar00rootroot00000000000000./( bfs-1.2.1/tests/test_rm.out000066400000000000000000000000101323715747000156350ustar00rootroot00000000000000scratch bfs-1.2.1/tests/test_samefile.out000066400000000000000000000000321323715747000170100ustar00rootroot00000000000000links/file links/hardlink bfs-1.2.1/tests/test_samefile_broken.out000066400000000000000000000000151323715747000203510ustar00rootroot00000000000000links/broken bfs-1.2.1/tests/test_samefile_symlink.out000066400000000000000000000000161323715747000205600ustar00rootroot00000000000000links/symlink bfs-1.2.1/tests/test_size.out000066400000000000000000000001001323715747000161710ustar00rootroot00000000000000basic/a basic/b basic/c/d basic/e/f basic/j/foo basic/k/foo/bar bfs-1.2.1/tests/test_size_T.out000066400000000000000000000000241323715747000164610ustar00rootroot00000000000000basic/l/foo/bar/baz bfs-1.2.1/tests/test_size_big.out000066400000000000000000000000001323715747000170110ustar00rootroot00000000000000bfs-1.2.1/tests/test_size_bytes.out000066400000000000000000000000241323715747000174040ustar00rootroot00000000000000basic/l/foo/bar/baz bfs-1.2.1/tests/test_size_plus.out000066400000000000000000000000241323715747000172410ustar00rootroot00000000000000basic/l/foo/bar/baz bfs-1.2.1/tests/test_true.out000066400000000000000000000003041323715747000162040ustar00rootroot00000000000000basic 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.2.1/tests/test_type_d.out000066400000000000000000000001601323715747000165110ustar00rootroot00000000000000basic 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.2.1/tests/test_type_f.out000066400000000000000000000001241323715747000165130ustar00rootroot00000000000000basic/a basic/b basic/c/d basic/e/f basic/j/foo basic/k/foo/bar basic/l/foo/bar/baz bfs-1.2.1/tests/test_type_multi.out000066400000000000000000000001311323715747000174160ustar00rootroot00000000000000links links/deeply links/file links/hardlink links/deeply/nested links/deeply/nested/dir bfs-1.2.1/tests/test_uid.out000066400000000000000000000003041323715747000160060ustar00rootroot00000000000000basic 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.2.1/tests/test_uid_minus.out000066400000000000000000000003041323715747000172210ustar00rootroot00000000000000basic 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.2.1/tests/test_uid_name.out000066400000000000000000000003041323715747000170060ustar00rootroot00000000000000basic 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.2.1/tests/test_uid_plus.out000066400000000000000000000003041323715747000170510ustar00rootroot00000000000000basic 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.2.1/tests/test_user_id.out000066400000000000000000000003041323715747000166570ustar00rootroot00000000000000basic 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.2.1/tests/test_user_name.out000066400000000000000000000003041323715747000172030ustar00rootroot00000000000000basic 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.2.1/tests/test_weird_names.out000066400000000000000000000001541323715747000175250ustar00rootroot00000000000000!- !- (- (- ) ) , , - - !-/e !-/e (-/c (-/c )/g )/g ,/f ,/f -/a -/a ./! ./! ./( ./( ./!/d ./!/d ./(/b ./(/b bfs-1.2.1/tests/test_writable.out000066400000000000000000000000521323715747000170360ustar00rootroot00000000000000perms perms/rw perms/rwx perms/w perms/wx bfs-1.2.1/tests/test_xtype_f.out000066400000000000000000000000501323715747000167010ustar00rootroot00000000000000links/file links/hardlink links/symlink bfs-1.2.1/tests/test_xtype_l.out000066400000000000000000000000321323715747000167070ustar00rootroot00000000000000links/broken links/notdir bfs-1.2.1/tests/test_xtype_multi.out000066400000000000000000000002131323715747000176070ustar00rootroot00000000000000links links/deeply links/file links/hardlink links/skip links/symlink links/deeply/nested links/deeply/nested/dir links/deeply/nested/loop bfs-1.2.1/tests/test_xtype_reorder.out000066400000000000000000000000001323715747000201110ustar00rootroot00000000000000bfs-1.2.1/typo.c000066400000000000000000000111741323715747000134410ustar00rootroot00000000000000/**************************************************************************** * 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.2.1/typo.h000066400000000000000000000027651323715747000134540ustar00rootroot00000000000000/**************************************************************************** * 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.2.1/util.c000066400000000000000000000177441323715747000134340ustar00rootroot00000000000000/**************************************************************************** * bfs * * Copyright (C) 2016-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. * ****************************************************************************/ #include "util.h" #include "bftw.h" #include "dstring.h" #include #include #include #include #include #include #include #include #include #include #include int xreaddir(DIR *dir, struct dirent **de) { errno = 0; *de = readdir(dir); if (!*de && 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, ...) { close(fd); 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 other = ret; ret = dup2(other, fd); if (close(other) != 0) { ret = -1; } } 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 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) { close(pipefd[1]); close(pipefd[0]); 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 (mode_to_typeflag(mode)) { 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_FIFO: str[0] = 'p'; break; case BFTW_LNK: str[0] = 'l'; break; case BFTW_SOCK: str[0] = 's'; break; default: 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; } enum bftw_typeflag mode_to_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; } } enum bftw_typeflag dirent_to_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; } /** Read a line from standard input. */ static char *xgetline() { 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; } bfs-1.2.1/util.h000066400000000000000000000111251323715747000134240ustar00rootroot00000000000000/**************************************************************************** * bfs * * Copyright (C) 2016-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. * ****************************************************************************/ #ifndef BFS_UTIL_H #define BFS_UTIL_H #include "bftw.h" #include #include #include #include #include #include #include // Some portability concerns #if !defined(FNM_CASEFOLD) && defined(FNM_IGNORECASE) # define FNM_CASEFOLD FNM_IGNORECASE #endif #ifndef S_ISDOOR # define S_ISDOOR(mode) false #endif #ifndef S_ISPORT # define S_ISPORT(mode) false #endif #ifndef S_ISWHT # define S_ISWHT(mode) false #endif #ifndef O_DIRECTORY # define O_DIRECTORY 0 #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); /** * Convert a bfs_stat() mode to a bftw() typeflag. */ enum bftw_typeflag mode_to_typeflag(mode_t mode); /** * Convert a directory entry to a bftw() typeflag. */ enum bftw_typeflag dirent_to_typeflag(const struct dirent *de); /** * Process a yes/no prompt. * * @return 1 for yes, 0 for no, and -1 for unknown. */ int ynprompt(void); #endif // BFS_UTIL_H