/**************************************************************************** * bfs * * Copyright (C) 2016-2019 Tavian Barnes * * * * Permission to use, copy, modify, and/or distribute this software for any * * purpose with or without fee is hereby granted. * * * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * ****************************************************************************/ /** * Assorted utilities that don't belong anywhere else. */ #ifndef BFS_UTIL_H #define BFS_UTIL_H #include #include #include #include #include #include #include // Some portability concerns #ifdef __has_feature # define BFS_HAS_FEATURE(feature, fallback) __has_feature(feature) #else # define BFS_HAS_FEATURE(feature, fallback) fallback #endif #ifdef __has_include # define BFS_HAS_INCLUDE(header, fallback) __has_include(header) #else # define BFS_HAS_INCLUDE(header, fallback) fallback #endif #ifndef BFS_HAS_MNTENT # define BFS_HAS_MNTENT BFS_HAS_INCLUDE(, __GLIBC__) #endif #ifndef BFS_HAS_SYS_ACL # define BFS_HAS_SYS_ACL BFS_HAS_INCLUDE(, true) #endif #ifndef BFS_HAS_SYS_CAPABILITY # define BFS_HAS_SYS_CAPABILITY BFS_HAS_INCLUDE(, __linux__) #endif #ifndef BFS_HAS_SYS_MKDEV # define BFS_HAS_SYS_MKDEV BFS_HAS_INCLUDE(, false) #endif #ifndef BFS_HAS_SYS_PARAM # define BFS_HAS_SYS_PARAM BFS_HAS_INCLUDE(, true) #endif #ifndef BFS_HAS_SYS_SYSMACROS # define BFS_HAS_SYS_SYSMACROS BFS_HAS_INCLUDE(, __GLIBC__) #endif #ifndef BFS_HAS_SYS_XATTR # define BFS_HAS_SYS_XATTR BFS_HAS_INCLUDE(, __linux__) #endif #if !defined(FNM_CASEFOLD) && defined(FNM_IGNORECASE) # define FNM_CASEFOLD FNM_IGNORECASE #endif #ifndef O_DIRECTORY # define O_DIRECTORY 0 #endif /** * Adds compiler warnings for bad printf()-style function calls, if supported. */ #if __GNUC__ # define BFS_FORMATTER(fmt, args) __attribute__((format(printf, fmt, args))) #else # define BFS_FORMATTER(fmt, args) #endif /** * readdir() wrapper that makes error handling cleaner. */ int xreaddir(DIR *dir, struct dirent **de); /** * readlinkat() wrapper that dynamically allocates the result. * * @param fd * The base directory descriptor. * @param path * The path to the link, relative to fd. * @param size * An estimate for the size of the link name (pass 0 if unknown). * @return The target of the link, allocated with malloc(), or NULL on failure. */ char *xreadlinkat(int fd, const char *path, size_t size); /** * Check if a file descriptor is open. */ bool isopen(int fd); /** * Open a file and redirect it to a particular descriptor. * * @param fd * The file descriptor to redirect. * @param path * The path to open. * @param flags * The flags passed to open(). * @param mode * The mode passed to open() (optional). * @return fd on success, -1 on failure. */ int redirect(int fd, const char *path, int flags, ...); /** * Like dup(), but set the FD_CLOEXEC flag. * * @param fd * The file descriptor to duplicate. * @return A duplicated file descriptor, or -1 on failure. */ int dup_cloexec(int fd); /** * Like pipe(), but set the FD_CLOEXEC flag. * * @param pipefd * The array to hold the two file descriptors. * @return 0 on success, -1 on failure. */ int pipe_cloexec(int pipefd[2]); /** * Dynamically allocate a regex error message. * * @param err * The error code to stringify. * @param regex * The (partially) compiled regex. * @return A human-readable description of the error, allocated with malloc(). */ char *xregerror(int err, const regex_t *regex); /** * localtime_r() wrapper that calls tzset() first. * * @param timep * The time_t to convert. * @param result * Buffer to hold the result. * @return 0 on success, -1 on failure. */ int xlocaltime(const time_t *timep, struct tm *result); /** * Format a mode like ls -l (e.g. -rw-r--r--). * * @param mode * The mode to format. * @param str * The string to hold the formatted mode. */ void format_mode(mode_t mode, char str[11]); /** * basename() variant that doesn't modify the input. * * @param path * The path in question. * @return A pointer into path at the base name offset. */ const char *xbasename(const char *path); /** * Wrapper for faccessat() that handles some portability issues. */ int xfaccessat(int fd, const char *path, int amode); /** * Return whether an error code is due to a path not existing. */ bool is_nonexistence_error(int error); /** * Process a yes/no prompt. * * @return 1 for yes, 0 for no, and -1 for unknown. */ int ynprompt(void); /** * Portable version of makedev(). */ dev_t bfs_makedev(int ma, int mi); /** * Portable version of major(). */ int bfs_major(dev_t dev); /** * Portable version of minor(). */ int bfs_minor(dev_t dev); #endif // BFS_UTIL_H /**************************************************************************** * bfs * * Copyright (C) 2018-2019 Tavian Barnes * * * * Permission to use, copy, modify, and/or distribute this software for any * * purpose with or without fee is hereby granted. * * * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * ****************************************************************************/ /** * A facade over the stat() API that unifies some details that diverge between * implementations, like the names of the timespec fields and the presence of * file "birth" times. On new enough Linux kernels, the facade is backed by * statx() instead, and so it exposes a similar interface with a mask for which * fields were successfully returned. */ #ifndef BFS_STAT_H #define BFS_STAT_H #include #include #include #if BFS_HAS_SYS_PARAM # include #endif /** * bfs_stat field bitmask. */ enum bfs_stat_field { BFS_STAT_DEV = 1 << 0, BFS_STAT_INO = 1 << 1, BFS_STAT_TYPE = 1 << 2, BFS_STAT_MODE = 1 << 3, BFS_STAT_NLINK = 1 << 4, BFS_STAT_GID = 1 << 5, BFS_STAT_UID = 1 << 6, BFS_STAT_SIZE = 1 << 7, BFS_STAT_BLOCKS = 1 << 8, BFS_STAT_RDEV = 1 << 9, BFS_STAT_ATIME = 1 << 10, BFS_STAT_BTIME = 1 << 11, BFS_STAT_CTIME = 1 << 12, BFS_STAT_MTIME = 1 << 13, }; /** * Get the human-readable name of a bfs_stat field. */ const char *bfs_stat_field_name(enum bfs_stat_field field); /** * bfs_stat() flags. */ enum bfs_stat_flag { /** Follow symlinks (the default). */ BFS_STAT_FOLLOW = 0, /** Never follow symlinks. */ BFS_STAT_NOFOLLOW = 1 << 0, /** Try to follow symlinks, but fall back to the link itself if broken. */ BFS_STAT_TRYFOLLOW = 1 << 1, }; #ifdef DEV_BSIZE # define BFS_STAT_BLKSIZE DEV_BSIZE #elif defined(S_BLKSIZE) # define BFS_STAT_BLKSIZE S_BLKSIZE #else # define BFS_STAT_BLKSIZE 512 #endif /** * Facade over struct stat. */ struct bfs_stat { /** Bitmask indicating filled fields. */ enum bfs_stat_field mask; /** Device ID containing the file. */ dev_t dev; /** Inode number. */ ino_t ino; /** File type and access mode. */ mode_t mode; /** Number of hard links. */ nlink_t nlink; /** Owner group ID. */ gid_t gid; /** Owner user ID. */ uid_t uid; /** File size in bytes. */ off_t size; /** Number of disk blocks allocated (of size BFS_STAT_BLKSIZE). */ blkcnt_t blocks; /** The device ID represented by this file. */ dev_t rdev; /** Access time. */ struct timespec atime; /** Birth/creation time. */ struct timespec btime; /** Status change time. */ struct timespec ctime; /** Modification time. */ struct timespec mtime; }; /** * Facade over fstatat(). * * @param at_fd * The base file descriptor for the lookup. * @param at_path * The path to stat, relative to at_fd. Pass NULL to fstat() at_fd * itself. * @param flags * Flags that affect the lookup. * @param[out] buf * A place to store the stat buffer, if successful. * @return * 0 on success, -1 on error. */ int bfs_stat(int at_fd, const char *at_path, enum bfs_stat_flag flags, struct bfs_stat *buf); /** * Get a particular time field from a bfs_stat() buffer. */ const struct timespec *bfs_stat_time(const struct bfs_stat *buf, enum bfs_stat_field field); /** * A unique ID for a file. */ typedef unsigned char bfs_file_id[sizeof(dev_t) + sizeof(ino_t)]; /** * Compute a unique ID for a file. */ void bfs_stat_id(const struct bfs_stat *buf, bfs_file_id *id); #endif // BFS_STAT_H /**************************************************************************** * bfs * * Copyright (C) 2015-2018 Tavian Barnes * * * * Permission to use, copy, modify, and/or distribute this software for any * * purpose with or without fee is hereby granted. * * * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * ****************************************************************************/ /** * Utilities for colored output on ANSI terminals. */ #ifndef BFS_COLOR_H #define BFS_COLOR_H #include #include #include /** * A lookup table for colors. */ struct colors; /** * Parse a color table. * * @param ls_colors * A color table in the LS_COLORS environment variable format. * @return The parsed color table. */ struct colors *parse_colors(const char *ls_colors); /** * Free a color table. * * @param colors * The color table to free. */ void free_colors(struct colors *colors); /** * A file/stream with associated colors. */ typedef struct CFILE { /** The underlying file/stream. */ FILE *file; /** The color table to use, if any. */ const struct colors *colors; /** Whether to close the underlying stream. */ bool close; } CFILE; /** * Open a file for colored output. * * @param path * The path to the file to open. * @param colors * The color table to use if file is a TTY. * @return A colored file stream. */ CFILE *cfopen(const char *path, const struct colors *colors); /** * Make a colored copy of an open file. * * @param file * The underlying file. * @param colors * The color table to use if file is a TTY. * @return A colored wrapper around file. */ CFILE *cfdup(FILE *file, const struct colors *colors); /** * Close a colored file. * * @param cfile * The colored file to close. * @return 0 on success, -1 on failure. */ int cfclose(CFILE *cfile); /** * Colored, formatted output. * * @param cfile * The colored stream to print to. * @param format * A printf()-style format string, supporting these format specifiers: * * %c: A single character * %d: An integer * %g: A double * %s: A string * %zu: A size_t * %m: strerror(errno) * %pP: A colored file path, from a const struct BFTW * argument * %pL: A colored link target, from a const struct BFTW * argument * %%: A literal '%' * ${cc}: Change the color to 'cc' * $$: A literal '$' * @return 0 on success, -1 on failure. */ BFS_FORMATTER(2, 3) int cfprintf(CFILE *cfile, const char *format, ...); /** * cfprintf() variant that takes a va_list. */ int cvfprintf(CFILE *cfile, const char *format, va_list args); #endif // BFS_COLOR_H /**************************************************************************** * bfs * * Copyright (C) 2019 Tavian Barnes * * * * Permission to use, copy, modify, and/or distribute this software for any * * purpose with or without fee is hereby granted. * * * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * ****************************************************************************/ #ifndef BFS_TRIE_H #define BFS_TRIE_H #include #include #include /** * A trie that holds a set of fixed- or variable-length strings. */ struct trie { uintptr_t root; }; /** * A leaf of a trie. */ struct trie_leaf { /** * An arbitrary value associated with this leaf. */ void *value; /** * The length of the key in bytes. */ size_t length; /** * The key itself, stored inline. */ char key[]; }; /** * Initialize an empty trie. */ void trie_init(struct trie *trie); /** * Get the first (lexicographically earliest) leaf in the trie. * * @param trie * The trie to search. * @return * The first leaf, or NULL if the trie is empty. */ struct trie_leaf *trie_first_leaf(const struct trie *trie); /** * Find the leaf for a string key. * * @param trie * The trie to search. * @param key * The key to look up. * @return * The found leaf, or NULL if the key is not present. */ struct trie_leaf *trie_find_str(const struct trie *trie, const char *key); /** * Find the leaf for a fixed-size key. * * @param trie * The trie to search. * @param key * The key to look up. * @param length * The length of the key in bytes. * @return * The found leaf, or NULL if the key is not present. */ struct trie_leaf *trie_find_mem(const struct trie *trie, const void *key, size_t length); /** * Find the shortest leaf that starts with a given key. * * @param trie * The trie to search. * @param key * The key to look up. * @return * A leaf that starts with the given key, or NULL. */ struct trie_leaf *trie_find_postfix(const struct trie *trie, const char *key); /** * Find the leaf that is the longest prefix of the given key. * * @param trie * The trie to search. * @param key * The key to look up. * @return * The longest prefix match for the given key, or NULL. */ struct trie_leaf *trie_find_prefix(const struct trie *trie, const char *key); /** * Insert a string key into the trie. * * @param trie * The trie to modify. * @param key * The key to insert. * @return * The inserted leaf, or NULL on failure. */ struct trie_leaf *trie_insert_str(struct trie *trie, const char *key); /** * Insert a fixed-size key into the trie. * * @param trie * The trie to modify. * @param key * The key to insert. * @param length * The length of the key in bytes. * @return * The inserted leaf, or NULL on failure. */ struct trie_leaf *trie_insert_mem(struct trie *trie, const void *key, size_t length); /** * Remove a leaf from a trie. * * @param trie * The trie to modify. * @param leaf * The leaf to remove. */ void trie_remove(struct trie *trie, struct trie_leaf *leaf); /** * Destroy a trie and its contents. */ void trie_destroy(struct trie *trie); #endif // BFS_TRIE_H /**************************************************************************** * bfs * * Copyright (C) 2015-2018 Tavian Barnes * * * * Permission to use, copy, modify, and/or distribute this software for any * * purpose with or without fee is hereby granted. * * * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * ****************************************************************************/ /** * The expression tree representation. */ #ifndef BFS_EXPR_H #define BFS_EXPR_H #include #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 * * Copyright (C) 2015-2019 Tavian Barnes * * * * Permission to use, copy, modify, and/or distribute this software for any * * purpose with or without fee is hereby granted. * * * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * ****************************************************************************/ /** * Constants about the bfs program itself. */ #ifndef BFS_H #define BFS_H #ifndef BFS_VERSION # define BFS_VERSION "1.5" #endif #ifndef BFS_HOMEPAGE # define BFS_HOMEPAGE "https://github.com/tavianator/bfs" #endif #endif // BFS_H /**************************************************************************** * bfs * * Copyright (C) 2015-2019 Tavian Barnes * * * * Permission to use, copy, modify, and/or distribute this software for any * * purpose with or without fee is hereby granted. * * * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * ****************************************************************************/ /** * A file-walking API based on nftw(). */ #ifndef BFS_BFTW_H #define BFS_BFTW_H #include #include /** * Possible file types. */ enum bftw_typeflag { /** Unknown type. */ BFTW_UNKNOWN = 0, /** Block device. */ BFTW_BLK = 1 << 0, /** Character device. */ BFTW_CHR = 1 << 1, /** Directory. */ BFTW_DIR = 1 << 2, /** Solaris door. */ BFTW_DOOR = 1 << 3, /** Pipe. */ BFTW_FIFO = 1 << 4, /** Symbolic link. */ BFTW_LNK = 1 << 5, /** Solaris event port. */ BFTW_PORT = 1 << 6, /** Regular file. */ BFTW_REG = 1 << 7, /** Socket. */ BFTW_SOCK = 1 << 8, /** BSD whiteout. */ BFTW_WHT = 1 << 9, /** An error occurred for this file. */ BFTW_ERROR = 1 << 10, }; /** * Convert a bfs_stat() mode to a bftw() typeflag. */ enum bftw_typeflag bftw_mode_typeflag(mode_t mode); /** * Possible visit occurrences. */ enum bftw_visit { /** Pre-order visit. */ BFTW_PRE, /** Post-order visit. */ BFTW_POST, }; /** * Cached bfs_stat() info for a file. */ struct bftw_stat { /** A pointer to the bfs_stat() buffer, if available. */ const struct bfs_stat *buf; /** Storage for the bfs_stat() buffer, if needed. */ struct bfs_stat storage; /** The cached error code, if any. */ int error; }; /** * Data about the current file for the bftw() callback. */ struct BFTW { /** The path to the file. */ const char *path; /** The string offset of the filename. */ size_t nameoff; /** The root path passed to bftw(). */ const char *root; /** The depth of this file in the traversal. */ size_t depth; /** Which visit this is. */ enum bftw_visit visit; /** The file type. */ enum bftw_typeflag typeflag; /** The errno that occurred, if typeflag == BFTW_ERROR. */ int error; /** A parent file descriptor for the *at() family of calls. */ int at_fd; /** The path relative to at_fd for the *at() family of calls. */ const char *at_path; /** Flags for bfs_stat(). */ enum bfs_stat_flag stat_flags; /** Cached bfs_stat() info for BFS_STAT_NOFOLLOW. */ struct bftw_stat lstat_cache; /** Cached bfs_stat() info for BFS_STAT_FOLLOW. */ struct bftw_stat stat_cache; }; /** * Get bfs_stat() info for a file encountered during bftw(), caching the result * whenever possible. * * @param ftwbuf * bftw() data for the file to stat. * @param flags * flags for bfs_stat(). Pass ftwbuf->stat_flags for the default flags. * @return * A pointer to a bfs_stat() buffer, or NULL if the call failed. */ const struct bfs_stat *bftw_stat(const struct BFTW *ftwbuf, enum bfs_stat_flag flags); /** * Get the type of a file encountered during bftw(), with flags controlling * whether to follow links. This function will avoid calling bfs_stat() if * possible. * * @param ftwbuf * bftw() data for the file to check. * @param flags * flags for bfs_stat(). Pass ftwbuf->stat_flags for the default flags. * @return * The type of the file, or BFTW_ERROR if an error occurred. */ enum bftw_typeflag bftw_typeflag(const struct BFTW *ftwbuf, enum bfs_stat_flag flags); /** * Walk actions returned by the bftw() callback. */ enum bftw_action { /** Keep walking. */ BFTW_CONTINUE, /** Skip this path's children. */ BFTW_PRUNE, /** Stop walking. */ BFTW_STOP, }; /** * Callback function type for bftw(). * * @param ftwbuf * Data about the current file. * @param ptr * The pointer passed to bftw(). * @return * An action value. */ typedef enum bftw_action bftw_callback(const struct BFTW *ftwbuf, void *ptr); /** * Flags that control bftw() behavior. */ enum bftw_flags { /** stat() each encountered file. */ BFTW_STAT = 1 << 0, /** Attempt to recover from encountered errors. */ BFTW_RECOVER = 1 << 1, /** Visit directories in post-order as well as pre-order. */ BFTW_DEPTH = 1 << 2, /** If the initial path is a symbolic link, follow it. */ BFTW_COMFOLLOW = 1 << 3, /** Follow all symbolic links. */ BFTW_LOGICAL = 1 << 4, /** Detect directory cycles. */ BFTW_DETECT_CYCLES = 1 << 5, /** Stay on the same filesystem. */ BFTW_XDEV = 1 << 6, }; /** * Tree search strategies for bftw(). */ enum bftw_strategy { /** Breadth-first search. */ BFTW_BFS, /** Depth-first search. */ BFTW_DFS, /** Iterative deepening search. */ BFTW_IDS, }; /** * Structure for holding the arguments passed to bftw(). */ struct bftw_args { /** The path(s) to start from. */ const char **paths; /** The number of starting paths. */ size_t npaths; /** The callback to invoke. */ bftw_callback *callback; /** A pointer which is passed to the callback. */ void *ptr; /** The maximum number of file descriptors to keep open. */ int nopenfd; /** Flags that control bftw() behaviour. */ enum bftw_flags flags; /** The search strategy to use. */ enum bftw_strategy strategy; /** The parsed mount table, if available. */ const struct bfs_mtab *mtab; }; /** * Breadth First Tree Walk (or Better File Tree Walk). * * Like ftw(3) and nftw(3), this function walks a directory tree recursively, * and invokes a callback for each path it encounters. * * @param args * The arguments that control the walk. * @return * 0 on success, or -1 on failure. */ int bftw(const struct bftw_args *args); #endif // BFS_BFTW_H /**************************************************************************** * bfs * * Copyright (C) 2015-2018 Tavian Barnes * * * * Permission to use, copy, modify, and/or distribute this software for any * * purpose with or without fee is hereby granted. * * * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * ****************************************************************************/ /** * Representation of the parsed command line. */ #ifndef BFS_CMDLINE_H #define BFS_CMDLINE_H /** * Various debugging flags. */ enum debug_flags { /** Print cost estimates. */ DEBUG_COST = 1 << 0, /** Print executed command details. */ DEBUG_EXEC = 1 << 1, /** Print optimization details. */ DEBUG_OPT = 1 << 2, /** Print rate information. */ DEBUG_RATES = 1 << 3, /** Trace the filesystem traversal. */ DEBUG_SEARCH = 1 << 4, /** Trace all stat() calls. */ DEBUG_STAT = 1 << 5, /** Print the parse tree. */ DEBUG_TREE = 1 << 6, /** All debug flags. */ DEBUG_ALL = (1 << 7) - 1, }; /** * The parsed command line. */ struct cmdline { /** The unparsed command line arguments. */ char **argv; /** The root paths. */ const char **paths; /** The number of root paths. */ size_t npaths; /** Color data. */ struct colors *colors; /** Colored stdout. */ CFILE *cout; /** Colored stderr. */ CFILE *cerr; /** Table of mounted file systems. */ struct bfs_mtab *mtab; /** The error that occurred parsing the mount table, if any. */ int mtab_error; /** -mindepth option. */ int mindepth; /** -maxdepth option. */ int maxdepth; /** bftw() flags. */ enum bftw_flags flags; /** bftw() search strategy. */ enum bftw_strategy strategy; /** Optimization level. */ int optlevel; /** Debugging flags. */ enum debug_flags debug; /** Whether to only handle paths with xargs-safe characters. */ bool xargs_safe; /** Whether to ignore deletions that race with bfs. */ bool ignore_races; /** Whether to only return unique files. */ bool unique; /** The command line expression. */ struct expr *expr; /** All the open files owned by the command line. */ struct trie open_files; /** The number of open files owned by the command line. */ int nopen_files; }; /** * Parse the command line. */ struct cmdline *parse_cmdline(int argc, char *argv[]); /** * Dump the parsed command line. */ void dump_cmdline(const struct cmdline *cmdline, bool verbose); /** * Optimize the parsed command line. * * @return 0 if successful, -1 on error. */ int optimize_cmdline(struct cmdline *cmdline); /** * Evaluate the command line. */ int eval_cmdline(const struct cmdline *cmdline); /** * Free the parsed command line. * * @return 0 if successful, -1 on error. */ int free_cmdline(struct cmdline *cmdline); #endif // BFS_CMDLINE_H /**************************************************************************** * bfs * * Copyright (C) 2019 Tavian Barnes * * * * Permission to use, copy, modify, and/or distribute this software for any * * purpose with or without fee is hereby granted. * * * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * ****************************************************************************/ /** * Formatters for diagnostic messages. */ #ifndef BFS_DIAG_H #define BFS_DIAG_H #include /** * Shorthand for printing error messages. */ BFS_FORMATTER(2, 3) void bfs_error(const struct cmdline *cmdline, const char *format, ...); /** * Shorthand for printing warning messages. */ BFS_FORMATTER(2, 3) void bfs_warning(const struct cmdline *cmdline, const char *format, ...); /** * bfs_error() variant that takes a va_list. */ void bfs_verror(const struct cmdline *cmdline, const char *format, va_list args); /** * bfs_warning() variant that takes a va_list. */ void bfs_vwarning(const struct cmdline *cmdline, const char *format, va_list args); /** * Print the error message prefix. */ void bfs_error_prefix(const struct cmdline *cmdline); /** * Print the warning message prefix. */ void bfs_warning_prefix(const struct cmdline *cmdline); #endif // BFS_DIAG_H /**************************************************************************** * bfs * * Copyright (C) 2016-2019 Tavian Barnes * * * * Permission to use, copy, modify, and/or distribute this software for any * * purpose with or without fee is hereby granted. * * * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * ****************************************************************************/ /** * A dynamic string library. */ #ifndef BFS_DSTRING_H #define BFS_DSTRING_H #include /** * Allocate a dynamic string. * * @param capacity * The initial capacity of the string. */ char *dstralloc(size_t capacity); /** * Create a dynamic copy of a string. * * @param str * The NUL-terminated string to copy. */ char *dstrdup(const char *str); /** * Get a dynamic string's length. * * @param dstr * The string to measure. * @return The length of dstr. */ size_t dstrlen(const char *dstr); /** * Reserve some capacity in a dynamic string. * * @param dstr * The dynamic string to preallocate. * @param capacity * The new capacity for the string. * @return 0 on success, -1 on failure. */ int dstreserve(char **dstr, size_t capacity); /** * Resize a dynamic string. * * @param dstr * The dynamic string to resize. * @param length * The new length for the dynamic string. * @return 0 on success, -1 on failure. */ int dstresize(char **dstr, size_t length); /** * Append to a dynamic string. * * @param dest * The destination dynamic string. * @param src * The string to append. * @return 0 on success, -1 on failure. */ int dstrcat(char **dest, const char *src); /** * Append to a dynamic string. * * @param dest * The destination dynamic string. * @param src * The string to append. * @param n * The maximum number of characters to take from src. * @return 0 on success, -1 on failure. */ int dstrncat(char **dest, const char *src, size_t n); /** * Append a single character to a dynamic string. * * @param str * The string to append to. * @param c * The character to append. * @return 0 on success, -1 on failure. */ int dstrapp(char **str, char c); /** * Create a dynamic string from a format string. * * @param format * The format string to fill in. * @param ... * Any arguments for the format string. * @return * The created string, or NULL on failure. */ BFS_FORMATTER(1, 2) char *dstrprintf(const char *format, ...); /** * Free a dynamic string. * * @param dstr * The string to free. */ void dstrfree(char *dstr); #endif // BFS_DSTRING_H /**************************************************************************** * bfs * * Copyright (C) 2015-2018 Tavian Barnes * * * * Permission to use, copy, modify, and/or distribute this software for any * * purpose with or without fee is hereby granted. * * * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * ****************************************************************************/ /** * The evaluation functions that implement literal expressions like -name, * -print, etc. */ #ifndef BFS_EVAL_H #define BFS_EVAL_H // Predicate evaluation functions bool eval_true(const struct expr *expr, struct eval_state *state); bool eval_false(const struct expr *expr, struct eval_state *state); bool eval_access(const struct expr *expr, struct eval_state *state); bool eval_acl(const struct expr *expr, struct eval_state *state); bool eval_capable(const struct expr *expr, struct eval_state *state); bool eval_perm(const struct expr *expr, struct eval_state *state); bool eval_xattr(const struct expr *expr, struct eval_state *state); bool eval_newer(const struct expr *expr, struct eval_state *state); bool eval_time(const struct expr *expr, struct eval_state *state); bool eval_used(const struct expr *expr, struct eval_state *state); bool eval_gid(const struct expr *expr, struct eval_state *state); bool eval_uid(const struct expr *expr, struct eval_state *state); bool eval_nogroup(const struct expr *expr, struct eval_state *state); bool eval_nouser(const struct expr *expr, struct eval_state *state); bool eval_depth(const struct expr *expr, struct eval_state *state); bool eval_empty(const struct expr *expr, struct eval_state *state); bool eval_fstype(const struct expr *expr, struct eval_state *state); bool eval_hidden(const struct expr *expr, struct eval_state *state); bool eval_inum(const struct expr *expr, struct eval_state *state); bool eval_links(const struct expr *expr, struct eval_state *state); bool eval_samefile(const struct expr *expr, struct eval_state *state); bool eval_size(const struct expr *expr, struct eval_state *state); bool eval_sparse(const struct expr *expr, struct eval_state *state); bool eval_type(const struct expr *expr, struct eval_state *state); bool eval_xtype(const struct expr *expr, struct eval_state *state); bool eval_lname(const struct expr *expr, struct eval_state *state); bool eval_name(const struct expr *expr, struct eval_state *state); bool eval_path(const struct expr *expr, struct eval_state *state); bool eval_regex(const struct expr *expr, struct eval_state *state); bool eval_delete(const struct expr *expr, struct eval_state *state); bool eval_exec(const struct expr *expr, struct eval_state *state); bool eval_exit(const struct expr *expr, struct eval_state *state); bool eval_nohidden(const struct expr *expr, struct eval_state *state); bool eval_fls(const struct expr *expr, struct eval_state *state); bool eval_fprint(const struct expr *expr, struct eval_state *state); bool eval_fprint0(const struct expr *expr, struct eval_state *state); bool eval_fprintf(const struct expr *expr, struct eval_state *state); bool eval_fprintx(const struct expr *expr, struct eval_state *state); bool eval_prune(const struct expr *expr, struct eval_state *state); bool eval_quit(const struct expr *expr, struct eval_state *state); // Operator evaluation functions bool eval_not(const struct expr *expr, struct eval_state *state); bool eval_and(const struct expr *expr, struct eval_state *state); bool eval_or(const struct expr *expr, struct eval_state *state); bool eval_comma(const struct expr *expr, struct eval_state *state); #endif // BFS_EVAL_H /**************************************************************************** * bfs * * Copyright (C) 2017 Tavian Barnes * * * * Permission to use, copy, modify, and/or distribute this software for any * * purpose with or without fee is hereby granted. * * * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * ****************************************************************************/ /** * Implementation of -exec/-execdir/-ok/-okdir. */ #ifndef BFS_EXEC_H #define BFS_EXEC_H 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 * * Copyright (C) 2019 Tavian Barnes * * * * Permission to use, copy, modify, and/or distribute this software for any * * purpose with or without fee is hereby granted. * * * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * ****************************************************************************/ /** * A facade over (file)system features that are (un)implemented differently * between platforms. */ #ifndef BFS_FSADE_H #define BFS_FSADE_H #include #define BFS_CAN_CHECK_ACL BFS_HAS_SYS_ACL #if !defined(BFS_CAN_CHECK_CAPABILITIES) && BFS_HAS_SYS_CAPABILITY && !__FreeBSD__ # include # ifdef CAP_CHOWN # define BFS_CAN_CHECK_CAPABILITIES true # endif #endif #define BFS_CAN_CHECK_XATTRS BFS_HAS_SYS_XATTR /** * Check if a file has a non-trvial Access Control List. * * @param ftwbuf * The file to check. * @return * 1 if it does, 0 if it doesn't, or -1 if an error occurred. */ int bfs_check_acl(const struct BFTW *ftwbuf); /** * Check if a file has a non-trvial capability set. * * @param ftwbuf * The file to check. * @return * 1 if it does, 0 if it doesn't, or -1 if an error occurred. */ int bfs_check_capabilities(const struct BFTW *ftwbuf); /** * Check if a file has any extended attributes set. * * @param ftwbuf * The file to check. * @return * 1 if it does, 0 if it doesn't, or -1 if an error occurred. */ int bfs_check_xattrs(const struct BFTW *ftwbuf); #endif // BFS_FSADE_H /**************************************************************************** * bfs * * Copyright (C) 2017-2019 Tavian Barnes * * * * Permission to use, copy, modify, and/or distribute this software for any * * purpose with or without fee is hereby granted. * * * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * ****************************************************************************/ /** * A facade over platform-specific APIs for enumerating mounted filesystems. */ #ifndef BFS_MTAB_H #define BFS_MTAB_H #include /** * A file system mount table. */ struct bfs_mtab; /** * Parse the mount table. * * @return * The parsed mount table, or NULL on error. */ struct bfs_mtab *parse_bfs_mtab(void); /** * Determine the file system type that a file is on. * * @param mtab * The current mount table. * @param statbuf * The bfs_stat() buffer for the file in question. * @return * The type of file system containing this file, "unknown" if not known, * or NULL on error. */ const char *bfs_fstype(const struct bfs_mtab *mtab, const struct bfs_stat *statbuf); /** * Check if a file could be a mount point. * * @param mtab * The current mount table. * @param path * The path to check. * @return * Whether the named file could be a mount point. */ bool bfs_maybe_mount(const struct bfs_mtab *mtab, const char *path); /** * Free a mount table. */ void free_bfs_mtab(struct bfs_mtab *mtab); #endif // BFS_MTAB_H /**************************************************************************** * bfs * * Copyright (C) 2017-2019 Tavian Barnes * * * * Permission to use, copy, modify, and/or distribute this software for any * * purpose with or without fee is hereby granted. * * * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * ****************************************************************************/ /** * Implementation of -printf/-fprintf. */ #ifndef BFS_PRINTF_H #define BFS_PRINTF_H #include #include /** * A printf command, the result of parsing a single format string. */ struct bfs_printf; /** * Parse a -printf format string. * * @param format * The format string to parse. * @param cmdline * The command line. * @return The parsed printf command, or NULL on failure. */ struct bfs_printf *parse_bfs_printf(const char *format, struct cmdline *cmdline); /** * Evaluate a parsed format string. * * @param file * The FILE to print to. * @param command * The parsed printf format. * @param ftwbuf * The bftw() data for the current file. If needs_stat is true, statbuf * must be non-NULL. * @return 0 on success, -1 on failure. */ int bfs_printf(FILE *file, const struct bfs_printf *command, const struct BFTW *ftwbuf); /** * Free a parsed format string. */ void free_bfs_printf(struct bfs_printf *command); #endif // BFS_PRINTF_H /**************************************************************************** * bfs * * Copyright (C) 2018-2019 Tavian Barnes * * * * Permission to use, copy, modify, and/or distribute this software for any * * purpose with or without fee is hereby granted. * * * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * ****************************************************************************/ /** * A process-spawning library inspired by posix_spawn(). */ #ifndef BFS_SPAWN_H #define BFS_SPAWN_H #include #include #include /** * bfs_spawn() flags. */ enum bfs_spawn_flags { /** Use the PATH variable to resolve the executable (like execvp()). */ BFS_SPAWN_USEPATH = 1 << 0, }; /** * bfs_spawn() attributes, controlling the context of the new process. */ struct bfs_spawn { enum bfs_spawn_flags flags; struct bfs_spawn_action *actions; struct bfs_spawn_action **tail; }; /** * Create a new bfs_spawn() context. * * @return 0 on success, -1 on failure. */ int bfs_spawn_init(struct bfs_spawn *ctx); /** * Destroy a bfs_spawn() context. * * @return 0 on success, -1 on failure. */ int bfs_spawn_destroy(struct bfs_spawn *ctx); /** * Set the flags for a bfs_spawn() context. * * @return 0 on success, -1 on failure. */ int bfs_spawn_setflags(struct bfs_spawn *ctx, enum bfs_spawn_flags flags); /** * Add a close() action to a bfs_spawn() context. * * @return 0 on success, -1 on failure. */ int bfs_spawn_addclose(struct bfs_spawn *ctx, int fd); /** * Add a dup2() action to a bfs_spawn() context. * * @return 0 on success, -1 on failure. */ int bfs_spawn_adddup2(struct bfs_spawn *ctx, int oldfd, int newfd); /** * Add an fchdir() action to a bfs_spawn() context. * * @return 0 on success, -1 on failure. */ int bfs_spawn_addfchdir(struct bfs_spawn *ctx, int fd); /** * Spawn a new process. * * @param exe * The executable to run. * @param ctx * The context for the new process. * @param argv * The arguments for the new process. * @param envp * The environment variables for the new process. * @return * The PID of the new process, or -1 on error. */ pid_t bfs_spawn(const char *exe, const struct bfs_spawn *ctx, char **argv, char **envp); #endif // BFS_SPAWN_H /**************************************************************************** * bfs * * 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 * * Copyright (C) 2015-2019 Tavian Barnes * * * * Permission to use, copy, modify, and/or distribute this software for any * * purpose with or without fee is hereby granted. * * * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * ****************************************************************************/ /** * The bftw() implementation consists of the following components: * * - struct bftw_file: A file that has been encountered during the traversal. * They have reference-counted links to their parents in the directory tree. * * - struct bftw_cache: Holds bftw_file's with open file descriptors, used for * openat() to minimize the amount of path re-traversals that need to happen. * Currently implemented as a priority queue based on depth and reference * count. * * - struct bftw_queue: The queue of bftw_file's left to explore. Implemented * as a simple circular buffer. * * - struct bftw_reader: A reader object that simplifies reading directories and * reporting errors. * * - struct bftw_state: Represents the current state of the traversal, allowing * various helper functions to take fewer parameters. */ #include #include #include #include #include #include #include #include #include #include /** * A file. */ struct bftw_file { /** The parent directory, if any. */ struct bftw_file *parent; /** This file's depth in the walk. */ size_t depth; /** The root path this file was found from. */ const char *root; /** The next file in the queue, if any. */ struct bftw_file *next; /** Reference count. */ size_t refcount; /** Index in the bftw_cache priority queue. */ size_t heap_index; /** An open descriptor to this file, or -1. */ int fd; /** This file's type, if known. */ enum bftw_typeflag typeflag; /** The device number, for cycle detection. */ dev_t dev; /** The inode number, for cycle detection. */ ino_t ino; /** The offset of this file in the full path. */ size_t nameoff; /** The length of the file's name. */ size_t namelen; /** The file's name. */ char name[]; }; /** * A cache of open directories. */ struct bftw_cache { /** A min-heap of open directories. */ struct bftw_file **heap; /** Current heap size. */ size_t size; /** Maximum heap size. */ size_t capacity; }; /** Initialize a cache. */ static int bftw_cache_init(struct bftw_cache *cache, size_t capacity) { cache->heap = malloc(capacity*sizeof(*cache->heap)); if (!cache->heap) { return -1; } cache->size = 0; cache->capacity = capacity; return 0; } /** Destroy a cache. */ static void bftw_cache_destroy(struct bftw_cache *cache) { assert(cache->size == 0); free(cache->heap); } /** Check if two heap entries are in heap order. */ static bool bftw_heap_check(const struct bftw_file *parent, const struct bftw_file *child) { if (parent->depth > child->depth) { return true; } else if (parent->depth < child->depth) { return false; } else { return parent->refcount <= child->refcount; } } /** Move a bftw_file to a particular place in the heap. */ static void bftw_heap_move(struct bftw_cache *cache, struct bftw_file *file, size_t i) { cache->heap[i] = file; file->heap_index = i; } /** Bubble an entry up the heap. */ static void bftw_heap_bubble_up(struct bftw_cache *cache, struct bftw_file *file) { size_t i = file->heap_index; while (i > 0) { size_t pi = (i - 1)/2; struct bftw_file *parent = cache->heap[pi]; if (bftw_heap_check(parent, file)) { break; } bftw_heap_move(cache, parent, i); i = pi; } bftw_heap_move(cache, file, i); } /** Bubble an entry down the heap. */ static void bftw_heap_bubble_down(struct bftw_cache *cache, struct bftw_file *file) { size_t i = file->heap_index; while (true) { size_t ci = 2*i + 1; if (ci >= cache->size) { break; } struct bftw_file *child = cache->heap[ci]; size_t ri = ci + 1; if (ri < cache->size) { struct bftw_file *right = cache->heap[ri]; if (!bftw_heap_check(child, right)) { ci = ri; child = right; } } if (bftw_heap_check(file, child)) { break; } bftw_heap_move(cache, child, i); i = ci; } bftw_heap_move(cache, file, i); } /** Bubble an entry up or down the heap. */ static void bftw_heap_bubble(struct bftw_cache *cache, struct bftw_file *file) { size_t i = file->heap_index; if (i > 0) { size_t pi = (i - 1)/2; struct bftw_file *parent = cache->heap[pi]; if (!bftw_heap_check(parent, file)) { bftw_heap_bubble_up(cache, file); return; } } bftw_heap_bubble_down(cache, file); } /** Increment a bftw_file's reference count. */ static size_t bftw_file_incref(struct bftw_cache *cache, struct bftw_file *file) { size_t ret = ++file->refcount; if (file->fd >= 0) { bftw_heap_bubble_down(cache, file); } return ret; } /** Decrement a bftw_file's reference count. */ static size_t bftw_file_decref(struct bftw_cache *cache, struct bftw_file *file) { size_t ret = --file->refcount; if (file->fd >= 0) { bftw_heap_bubble_up(cache, file); } return ret; } /** Add a bftw_file to the cache. */ static void bftw_cache_add(struct bftw_cache *cache, struct bftw_file *file) { assert(cache->size < cache->capacity); assert(file->fd >= 0); size_t size = cache->size++; file->heap_index = size; bftw_heap_bubble_up(cache, file); } /** Remove a bftw_file from the cache. */ static void bftw_cache_remove(struct bftw_cache *cache, struct bftw_file *file) { assert(cache->size > 0); assert(file->fd >= 0); size_t size = --cache->size; size_t i = file->heap_index; if (i != size) { struct bftw_file *end = cache->heap[size]; end->heap_index = i; bftw_heap_bubble(cache, end); } } /** Close a bftw_file. */ static void bftw_file_close(struct bftw_cache *cache, struct bftw_file *file) { assert(file->fd >= 0); bftw_cache_remove(cache, file); close(file->fd); file->fd = -1; } /** Pop a directory from the cache. */ static void bftw_cache_pop(struct bftw_cache *cache) { assert(cache->size > 0); bftw_file_close(cache, cache->heap[0]); } /** * Shrink the cache, to recover from EMFILE. * * @param cache * The cache in question. * @param saved * A bftw_file that must be preserved. * @return * 0 if successfully shrunk, otherwise -1. */ static int bftw_cache_shrink(struct bftw_cache *cache, const struct bftw_file *saved) { int ret = -1; struct bftw_file *file = NULL; if (cache->size >= 1) { file = cache->heap[0]; if (file == saved && cache->size >= 2) { file = cache->heap[1]; } } if (file && file != saved) { bftw_file_close(cache, file); ret = 0; } cache->capacity = cache->size; return ret; } /** Compute the name offset of a child path. */ static size_t bftw_child_nameoff(const struct bftw_file *parent) { size_t ret = parent->nameoff + parent->namelen; if (parent->name[parent->namelen - 1] != '/') { ++ret; } return ret; } /** Create a new bftw_file. */ static struct bftw_file *bftw_file_new(struct bftw_cache *cache, struct bftw_file *parent, const char *name) { size_t namelen = strlen(name); size_t size = sizeof(struct bftw_file) + namelen + 1; struct bftw_file *file = malloc(size); if (!file) { return NULL; } file->parent = parent; if (parent) { file->depth = parent->depth + 1; file->root = parent->root; file->nameoff = bftw_child_nameoff(parent); bftw_file_incref(cache, parent); } else { file->depth = 0; file->root = name; file->nameoff = 0; } file->next = NULL; file->refcount = 1; file->fd = -1; file->typeflag = BFTW_UNKNOWN; file->dev = -1; file->ino = -1; file->namelen = namelen; memcpy(file->name, name, namelen + 1); return file; } /** * Get the appropriate (fd, path) pair for the *at() family of functions. * * @param file * The file being accessed. * @param[out] at_fd * Will hold the appropriate file descriptor to use. * @param[in,out] at_path * Will hold the appropriate path to use. * @return The closest open ancestor file. */ static struct bftw_file *bftw_file_base(struct bftw_file *file, int *at_fd, const char **at_path) { struct bftw_file *base = file; do { base = base->parent; } while (base && base->fd < 0); if (base) { *at_fd = base->fd; *at_path += bftw_child_nameoff(base); } return base; } /** * Open a bftw_file relative to another one. * * @param cache * The cache to hold the file. * @param file * The file to open. * @param base * The base directory for the relative path (may be NULL). * @param at_fd * The base file descriptor, AT_FDCWD if base == NULL. * @param at_path * The relative path to the file. * @return * The opened file descriptor, or negative on error. */ static int bftw_file_openat(struct bftw_cache *cache, struct bftw_file *file, const struct bftw_file *base, int at_fd, const char *at_path) { assert(file->fd < 0); int flags = O_RDONLY | O_CLOEXEC | O_DIRECTORY; int fd = openat(at_fd, at_path, flags); if (fd < 0 && errno == EMFILE) { if (bftw_cache_shrink(cache, base) == 0) { fd = openat(base->fd, at_path, flags); } } if (fd >= 0) { if (cache->size == cache->capacity) { bftw_cache_pop(cache); } file->fd = fd; bftw_cache_add(cache, file); } return fd; } /** * Open a bftw_file. * * @param cache * The cache to hold the file. * @param file * The file to open. * @param path * The full path to the file. * @return * The opened file descriptor, or negative on error. */ static int bftw_file_open(struct bftw_cache *cache, struct bftw_file *file, const char *path) { int at_fd = AT_FDCWD; const char *at_path = path; struct bftw_file *base = bftw_file_base(file, &at_fd, &at_path); int fd = bftw_file_openat(cache, file, base, at_fd, at_path); if (fd >= 0 || errno != ENAMETOOLONG) { return fd; } // Handle ENAMETOOLONG by manually traversing the path component-by-component // -1 to include the root, which has depth == 0 size_t offset = base ? base->depth : -1; size_t levels = file->depth - offset; if (levels < 2) { return fd; } struct bftw_file **parents = malloc(levels * sizeof(*parents)); if (!parents) { return fd; } struct bftw_file *parent = file; for (size_t i = levels; i-- > 0;) { parents[i] = parent; parent = parent->parent; } for (size_t i = 0; i < levels; ++i) { fd = bftw_file_openat(cache, parents[i], base, at_fd, parents[i]->name); if (fd < 0) { break; } base = parents[i]; at_fd = fd; } free(parents); return fd; } /** * Open a DIR* for a bftw_file. * * @param cache * The cache to hold the file. * @param file * The directory to open. * @param path * The full path to the directory. * @return * The opened DIR *, or NULL on error. */ static DIR *bftw_file_opendir(struct bftw_cache *cache, struct bftw_file *file, const char *path) { int fd = bftw_file_open(cache, file, path); if (fd < 0) { return NULL; } // Now we dup() the fd and pass it to fdopendir(). This way we can // close the DIR* as soon as we're done with it, reducing the memory // footprint significantly, while keeping the fd around for future // openat() calls. int dfd = dup_cloexec(fd); if (dfd < 0 && errno == EMFILE) { if (bftw_cache_shrink(cache, file) == 0) { dfd = dup_cloexec(fd); } } if (dfd < 0) { return NULL; } DIR *ret = fdopendir(dfd); if (!ret) { int error = errno; close(dfd); errno = error; } return ret; } /** Free a bftw_file. */ static void bftw_file_free(struct bftw_cache *cache, struct bftw_file *file) { assert(file->refcount == 0); if (file->fd >= 0) { bftw_file_close(cache, file); } free(file); } /** * A queue of bftw_file's to examine. */ struct bftw_queue { /** The head of the queue. */ struct bftw_file *head; /** The tail of the queue. */ struct bftw_file *tail; }; /** Initialize a bftw_queue. */ static void bftw_queue_init(struct bftw_queue *queue) { queue->head = NULL; queue->tail = NULL; } /** Add a file to the tail of the bftw_queue. */ static void bftw_queue_push(struct bftw_queue *queue, struct bftw_file *file) { assert(file->next == NULL); if (!queue->head) { queue->head = file; } if (queue->tail) { queue->tail->next = file; } queue->tail = file; } /** Prepend a queue to the head of another one. */ static void bftw_queue_prepend(struct bftw_queue *head, struct bftw_queue *tail) { if (head->tail) { head->tail->next = tail->head; } if (head->head) { tail->head = head->head; } if (!tail->tail) { tail->tail = head->tail; } head->head = NULL; head->tail = NULL; } /** Pop the next file from the head of the queue. */ static struct bftw_file *bftw_queue_pop(struct bftw_queue *queue) { struct bftw_file *file = queue->head; queue->head = file->next; if (queue->tail == file) { queue->tail = NULL; } file->next = NULL; return file; } /** * A directory reader. */ struct bftw_reader { /** The open handle to the directory. */ DIR *dir; /** The current directory entry. */ struct dirent *de; /** Any error code that has occurred. */ int error; }; /** Initialize a reader. */ static void bftw_reader_init(struct bftw_reader *reader) { reader->dir = NULL; reader->de = NULL; reader->error = 0; } /** Open a directory for reading. */ static int bftw_reader_open(struct bftw_reader *reader, struct bftw_cache *cache, struct bftw_file *file, const char *path) { assert(!reader->dir); assert(!reader->de); reader->error = 0; reader->dir = bftw_file_opendir(cache, file, path); if (!reader->dir) { reader->error = errno; return -1; } return 0; } /** Read a directory entry. */ static int bftw_reader_read(struct bftw_reader *reader) { if (!reader->dir) { return -1; } if (xreaddir(reader->dir, &reader->de) != 0) { reader->error = errno; return -1; } else if (reader->de) { return 1; } else { return 0; } } /** Close a directory. */ static int bftw_reader_close(struct bftw_reader *reader) { int ret = 0; if (reader->dir && closedir(reader->dir) != 0) { reader->error = errno; ret = -1; } reader->de = NULL; reader->dir = NULL; return ret; } /** * Holds the current state of the bftw() traversal. */ struct bftw_state { /** bftw() callback. */ bftw_callback *callback; /** bftw() callback data. */ void *ptr; /** bftw() flags. */ enum bftw_flags flags; /** Search strategy. */ enum bftw_strategy strategy; /** The mount table. */ const struct bfs_mtab *mtab; /** The appropriate errno value, if any. */ int error; /** The cache of open directories. */ struct bftw_cache cache; /** The queue of directories left to explore. */ struct bftw_queue queue; /** An intermediate queue used for depth-first searches. */ struct bftw_queue prequeue; /** The current path. */ char *path; /** The current file. */ struct bftw_file *file; /** The previous file. */ struct bftw_file *previous; /** The reader for the current directory. */ struct bftw_reader reader; /** Extra data about the current file. */ struct BFTW ftwbuf; }; /** * Initialize the bftw() state. */ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *args) { state->callback = args->callback; state->ptr = args->ptr; state->flags = args->flags; state->strategy = args->strategy; state->mtab = args->mtab; state->error = 0; if (args->nopenfd < 2) { errno = EMFILE; goto err; } // Reserve 1 fd for the open DIR * if (bftw_cache_init(&state->cache, args->nopenfd - 1) != 0) { goto err; } bftw_queue_init(&state->queue); bftw_queue_init(&state->prequeue); state->path = dstralloc(0); if (!state->path) { goto err_cache; } state->file = NULL; state->previous = NULL; bftw_reader_init(&state->reader); return 0; err_cache: bftw_cache_destroy(&state->cache); err: return -1; } enum bftw_typeflag bftw_mode_typeflag(mode_t mode) { switch (mode & S_IFMT) { #ifdef S_IFBLK case S_IFBLK: return BFTW_BLK; #endif #ifdef S_IFCHR case S_IFCHR: return BFTW_CHR; #endif #ifdef S_IFDIR case S_IFDIR: return BFTW_DIR; #endif #ifdef S_IFDOOR case S_IFDOOR: return BFTW_DOOR; #endif #ifdef S_IFIFO case S_IFIFO: return BFTW_FIFO; #endif #ifdef S_IFLNK case S_IFLNK: return BFTW_LNK; #endif #ifdef S_IFPORT case S_IFPORT: return BFTW_PORT; #endif #ifdef S_IFREG case S_IFREG: return BFTW_REG; #endif #ifdef S_IFSOCK case S_IFSOCK: return BFTW_SOCK; #endif #ifdef S_IFWHT case S_IFWHT: return BFTW_WHT; #endif default: return BFTW_UNKNOWN; } } static enum bftw_typeflag bftw_dirent_typeflag(const struct dirent *de) { #if defined(_DIRENT_HAVE_D_TYPE) || defined(DT_UNKNOWN) switch (de->d_type) { #ifdef DT_BLK case DT_BLK: return BFTW_BLK; #endif #ifdef DT_CHR case DT_CHR: return BFTW_CHR; #endif #ifdef DT_DIR case DT_DIR: return BFTW_DIR; #endif #ifdef DT_DOOR case DT_DOOR: return BFTW_DOOR; #endif #ifdef DT_FIFO case DT_FIFO: return BFTW_FIFO; #endif #ifdef DT_LNK case DT_LNK: return BFTW_LNK; #endif #ifdef DT_PORT case DT_PORT: return BFTW_PORT; #endif #ifdef DT_REG case DT_REG: return BFTW_REG; #endif #ifdef DT_SOCK case DT_SOCK: return BFTW_SOCK; #endif #ifdef DT_WHT case DT_WHT: return BFTW_WHT; #endif } #endif return BFTW_UNKNOWN; } /** Cached bfs_stat(). */ static const struct bfs_stat *bftw_stat_impl(struct BFTW *ftwbuf, struct bftw_stat *cache, enum bfs_stat_flag flags) { if (!cache->buf) { if (cache->error) { errno = cache->error; } else if (bfs_stat(ftwbuf->at_fd, ftwbuf->at_path, flags, &cache->storage) == 0) { cache->buf = &cache->storage; } else { cache->error = errno; } } return cache->buf; } const struct bfs_stat *bftw_stat(const struct BFTW *ftwbuf, enum bfs_stat_flag flags) { struct BFTW *mutbuf = (struct BFTW *)ftwbuf; const struct bfs_stat *ret; if (flags & BFS_STAT_NOFOLLOW) { ret = bftw_stat_impl(mutbuf, &mutbuf->lstat_cache, BFS_STAT_NOFOLLOW); if (ret && !S_ISLNK(ret->mode) && !mutbuf->stat_cache.buf) { // Non-link, so share stat info mutbuf->stat_cache.buf = ret; } } else { ret = bftw_stat_impl(mutbuf, &mutbuf->stat_cache, BFS_STAT_FOLLOW); if (!ret && (flags & BFS_STAT_TRYFOLLOW) && is_nonexistence_error(errno)) { ret = bftw_stat_impl(mutbuf, &mutbuf->lstat_cache, BFS_STAT_NOFOLLOW); } } return ret; } enum bftw_typeflag bftw_typeflag(const struct BFTW *ftwbuf, enum bfs_stat_flag flags) { if (ftwbuf->stat_flags & BFS_STAT_NOFOLLOW) { if ((flags & BFS_STAT_NOFOLLOW) || ftwbuf->typeflag != BFTW_LNK) { return ftwbuf->typeflag; } } else if ((flags & (BFS_STAT_NOFOLLOW | BFS_STAT_TRYFOLLOW)) == BFS_STAT_TRYFOLLOW || ftwbuf->typeflag == BFTW_LNK) { return ftwbuf->typeflag; } const struct bfs_stat *statbuf = bftw_stat(ftwbuf, flags); if (statbuf) { return bftw_mode_typeflag(statbuf->mode); } else { return BFTW_ERROR; } } /** * Update the path for the current file. */ static int bftw_update_path(struct bftw_state *state, const char *name) { const struct bftw_file *file = state->file; size_t length = file ? file->nameoff + file->namelen : 0; assert(dstrlen(state->path) >= length); dstresize(&state->path, length); if (name) { if (length > 0 && state->path[length - 1] != '/') { if (dstrapp(&state->path, '/') != 0) { return -1; } } if (dstrcat(&state->path, name) != 0) { return -1; } } return 0; } /** Check if a stat() call is needed for this visit. */ static bool bftw_need_stat(const struct bftw_state *state) { if (state->flags & BFTW_STAT) { return true; } const struct BFTW *ftwbuf = &state->ftwbuf; if (ftwbuf->typeflag == BFTW_UNKNOWN) { return true; } if (ftwbuf->typeflag == BFTW_LNK && !(ftwbuf->stat_flags & BFS_STAT_NOFOLLOW)) { return true; } if (ftwbuf->typeflag == BFTW_DIR) { if (state->flags & (BFTW_DETECT_CYCLES | BFTW_XDEV)) { return true; } #if __linux__ } else if (state->mtab) { // Linux fills in d_type from the underlying inode, even when // the directory entry is a bind mount point. In that case, we // need to stat() to get the correct type. We don't need to // check for directories because they can only be mounted over // by other directories. if (bfs_maybe_mount(state->mtab, ftwbuf->path)) { return true; } #endif } return false; } /** Initialize bftw_stat cache. */ static void bftw_stat_init(struct bftw_stat *cache) { cache->buf = NULL; cache->error = 0; } /** * Open a file if necessary. * * @param file * The file to open. * @param path * The path to that file or one of its descendants. * @return * The opened file descriptor, or -1 on error. */ static int bftw_ensure_open(struct bftw_cache *cache, struct bftw_file *file, const char *path) { int ret = file->fd; if (ret < 0) { char *copy = strndup(path, file->nameoff + file->namelen); if (!copy) { return -1; } ret = bftw_file_open(cache, file, copy); free(copy); } return ret; } /** * Initialize the buffers with data about the current path. */ static void bftw_init_ftwbuf(struct bftw_state *state, enum bftw_visit visit) { struct bftw_file *file = state->file; const struct bftw_reader *reader = &state->reader; const struct dirent *de = reader->de; struct BFTW *ftwbuf = &state->ftwbuf; ftwbuf->path = state->path; ftwbuf->root = file ? file->root : ftwbuf->path; ftwbuf->depth = 0; ftwbuf->visit = visit; ftwbuf->typeflag = BFTW_UNKNOWN; ftwbuf->error = reader->error; ftwbuf->at_fd = AT_FDCWD; ftwbuf->at_path = ftwbuf->path; ftwbuf->stat_flags = BFS_STAT_NOFOLLOW; bftw_stat_init(&ftwbuf->lstat_cache); bftw_stat_init(&ftwbuf->stat_cache); struct bftw_file *parent = NULL; if (de) { parent = file; ftwbuf->depth = file->depth + 1; ftwbuf->typeflag = bftw_dirent_typeflag(de); ftwbuf->nameoff = bftw_child_nameoff(file); } else if (file) { parent = file->parent; ftwbuf->depth = file->depth; ftwbuf->typeflag = file->typeflag; ftwbuf->nameoff = file->nameoff; } if (parent) { // Try to ensure the immediate parent is open, to avoid ENAMETOOLONG if (bftw_ensure_open(&state->cache, parent, state->path) >= 0) { ftwbuf->at_fd = parent->fd; ftwbuf->at_path += ftwbuf->nameoff; } else { ftwbuf->error = errno; } } if (ftwbuf->depth == 0) { // Compute the name offset for root paths like "foo/bar" ftwbuf->nameoff = xbasename(ftwbuf->path) - ftwbuf->path; } if (ftwbuf->error != 0) { ftwbuf->typeflag = BFTW_ERROR; return; } int follow_flags = BFTW_LOGICAL; if (ftwbuf->depth == 0) { follow_flags |= BFTW_COMFOLLOW; } bool follow = state->flags & follow_flags; if (follow) { ftwbuf->stat_flags = BFS_STAT_TRYFOLLOW; } const struct bfs_stat *statbuf = NULL; if (bftw_need_stat(state)) { statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); if (statbuf) { ftwbuf->typeflag = bftw_mode_typeflag(statbuf->mode); } else { ftwbuf->typeflag = BFTW_ERROR; ftwbuf->error = errno; return; } } if (ftwbuf->typeflag == BFTW_DIR && (state->flags & BFTW_DETECT_CYCLES)) { for (const struct bftw_file *parent = file; parent; parent = parent->parent) { if (parent->depth == ftwbuf->depth) { continue; } if (parent->dev == statbuf->dev && parent->ino == statbuf->ino) { ftwbuf->typeflag = BFTW_ERROR; ftwbuf->error = ELOOP; return; } } } } /** Fill file identity information from an ftwbuf. */ static void bftw_fill_id(struct bftw_file *file, const struct BFTW *ftwbuf) { const struct bfs_stat *statbuf = ftwbuf->stat_cache.buf; if (!statbuf || (ftwbuf->stat_flags & BFS_STAT_NOFOLLOW)) { statbuf = ftwbuf->lstat_cache.buf; } if (statbuf) { file->dev = statbuf->dev; file->ino = statbuf->ino; } } /** * Visit a path, invoking the callback. */ static enum bftw_action bftw_visit(struct bftw_state *state, const char *name, enum bftw_visit visit) { if (bftw_update_path(state, name) != 0) { state->error = errno; return BFTW_STOP; } const struct BFTW *ftwbuf = &state->ftwbuf; bftw_init_ftwbuf(state, visit); // Never give the callback BFTW_ERROR unless BFTW_RECOVER is specified if (ftwbuf->typeflag == BFTW_ERROR && !(state->flags & BFTW_RECOVER)) { state->error = ftwbuf->error; return BFTW_STOP; } enum bftw_action ret = state->callback(ftwbuf, state->ptr); switch (ret) { case BFTW_CONTINUE: break; case BFTW_PRUNE: case BFTW_STOP: goto done; default: state->error = EINVAL; return BFTW_STOP; } if (visit != BFTW_PRE || ftwbuf->typeflag != BFTW_DIR) { ret = BFTW_PRUNE; goto done; } if (state->flags & BFTW_XDEV) { const struct bftw_file *parent = state->file; if (parent && !name) { parent = parent->parent; } if (parent) { const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); if (statbuf && statbuf->dev != parent->dev) { ret = BFTW_PRUNE; goto done; } } } done: if (state->file && !name) { bftw_fill_id(state->file, ftwbuf); } return ret; } /** * Push a new file onto the queue. */ static int bftw_push(struct bftw_state *state, const char *name, bool fill_id) { struct bftw_file *parent = state->file; struct bftw_file *file = bftw_file_new(&state->cache, parent, name); if (!file) { state->error = errno; return -1; } struct dirent *de = state->reader.de; if (de) { file->typeflag = bftw_dirent_typeflag(de); } if (fill_id) { bftw_fill_id(file, &state->ftwbuf); } if (state->strategy == BFTW_DFS) { bftw_queue_push(&state->prequeue, file); } else { bftw_queue_push(&state->queue, file); } return 0; } /** * Build the path to the current file. */ static int bftw_build_path(struct bftw_state *state) { const struct bftw_file *file = state->file; size_t pathlen = file->nameoff + file->namelen; if (dstresize(&state->path, pathlen) != 0) { state->error = errno; return -1; } // Try to find a common ancestor with the existing path const struct bftw_file *ancestor = state->previous; while (ancestor && ancestor->depth > file->depth) { ancestor = ancestor->parent; } // Build the path backwards while (file && file != ancestor) { if (file->nameoff > 0) { state->path[file->nameoff - 1] = '/'; } memcpy(state->path + file->nameoff, file->name, file->namelen); if (ancestor && ancestor->depth == file->depth) { ancestor = ancestor->parent; } file = file->parent; } state->previous = state->file; return 0; } /** * Pop the next file from the queue. */ static int bftw_pop(struct bftw_state *state) { if (state->strategy == BFTW_DFS) { bftw_queue_prepend(&state->prequeue, &state->queue); } if (!state->queue.head) { return 0; } state->file = bftw_queue_pop(&state->queue); if (bftw_build_path(state) != 0) { return -1; } return 1; } /** * Open a reader for the current directory. */ static struct bftw_reader *bftw_open(struct bftw_state *state) { struct bftw_reader *reader = &state->reader; bftw_reader_open(reader, &state->cache, state->file, state->path); return reader; } /** * Close and release the reader. */ static enum bftw_action bftw_release_reader(struct bftw_state *state, bool do_visit) { enum bftw_action ret = BFTW_CONTINUE; struct bftw_reader *reader = &state->reader; bftw_reader_close(reader); if (reader->error != 0) { if (do_visit) { if (bftw_visit(state, NULL, BFTW_PRE) == BFTW_STOP) { ret = BFTW_STOP; } } else { state->error = reader->error; } reader->error = 0; } return ret; } /** * Finalize and free a file we're done with. */ static enum bftw_action bftw_release_file(struct bftw_state *state, bool visit_file, bool visit_parents) { enum bftw_action ret = BFTW_CONTINUE; if (!(state->flags & BFTW_DEPTH)) { visit_file = false; visit_parents = false; } bool do_visit = visit_file; while (state->file) { if (bftw_file_decref(&state->cache, state->file) > 0) { state->file = NULL; break; } if (do_visit) { if (bftw_visit(state, NULL, BFTW_POST) == BFTW_STOP) { ret = BFTW_STOP; visit_parents = false; } } do_visit = visit_parents; struct bftw_file *parent = state->file->parent; if (state->previous == state->file) { state->previous = parent; } bftw_file_free(&state->cache, state->file); state->file = parent; } return ret; } /** * Drain all the entries from a bftw_queue. */ static void bftw_drain_queue(struct bftw_state *state, struct bftw_queue *queue) { while (queue->head) { state->file = bftw_queue_pop(queue); bftw_release_file(state, false, false); } } /** * Dispose of the bftw() state. * * @return * The bftw() return value. */ static int bftw_state_destroy(struct bftw_state *state) { dstrfree(state->path); bftw_release_reader(state, false); bftw_release_file(state, false, false); bftw_drain_queue(state, &state->prequeue); bftw_drain_queue(state, &state->queue); bftw_cache_destroy(&state->cache); errno = state->error; return state->error ? -1 : 0; } /** * Breadth-first bftw() implementation. */ static int bftw_bfs(const struct bftw_args *args) { struct bftw_state state; if (bftw_state_init(&state, args) != 0) { return -1; } for (size_t i = 0; i < args->npaths; ++i) { const char *path = args->paths[i]; switch (bftw_visit(&state, path, BFTW_PRE)) { case BFTW_CONTINUE: break; case BFTW_PRUNE: continue; case BFTW_STOP: goto done; } if (bftw_push(&state, path, true) != 0) { goto done; } } while (bftw_pop(&state) > 0) { struct bftw_reader *reader = bftw_open(&state); while (bftw_reader_read(reader) > 0) { const char *name = reader->de->d_name; switch (bftw_visit(&state, name, BFTW_PRE)) { case BFTW_CONTINUE: break; case BFTW_PRUNE: continue; case BFTW_STOP: goto done; } if (bftw_push(&state, name, true) != 0) { goto done; } } if (bftw_release_reader(&state, true) == BFTW_STOP) { goto done; } if (bftw_release_file(&state, true, true) == BFTW_STOP) { goto done; } } done: return bftw_state_destroy(&state); } /** * Depth-first bftw() implementation. */ static int bftw_dfs(const struct bftw_args *args) { struct bftw_state state; if (bftw_state_init(&state, args) != 0) { return -1; } for (size_t i = 0; i < args->npaths; ++i) { if (bftw_push(&state, args->paths[i], false) != 0) { goto done; } } while (bftw_pop(&state) > 0) { bool visit_post = true; switch (bftw_visit(&state, NULL, BFTW_PRE)) { case BFTW_CONTINUE: break; case BFTW_PRUNE: visit_post = false; goto next; case BFTW_STOP: goto done; } struct bftw_reader *reader = bftw_open(&state); while (bftw_reader_read(reader) > 0) { if (bftw_push(&state, reader->de->d_name, false) != 0) { goto done; } } if (bftw_release_reader(&state, true) == BFTW_STOP) { goto done; } next: if (bftw_release_file(&state, visit_post, true) == BFTW_STOP) { goto done; } } done: return bftw_state_destroy(&state); } /** * Iterative deepening search state. */ struct bftw_ids_state { /** The wrapped callback. */ bftw_callback *delegate; /** The wrapped callback arguments. */ void *ptr; /** Which visit this search corresponds to. */ enum bftw_visit visit; /** The current target depth. */ size_t depth; /** The set of pruned paths. */ struct trie *pruned; /** An error code to report. */ int error; /** Whether the bottom has been found. */ bool bottom; /** Whether to quit the search. */ bool quit; }; /** Iterative deepening callback function. */ static enum bftw_action bftw_ids_callback(const struct BFTW *ftwbuf, void *ptr) { struct bftw_ids_state *state = ptr; struct BFTW *mutbuf = (struct BFTW *)ftwbuf; mutbuf->visit = state->visit; if (ftwbuf->typeflag == BFTW_ERROR) { if (state->depth - ftwbuf->depth <= 1) { return state->delegate(ftwbuf, state->ptr); } else { return BFTW_PRUNE; } } if (ftwbuf->depth < state->depth) { if (trie_find_str(state->pruned, ftwbuf->path)) { return BFTW_PRUNE; } else { return BFTW_CONTINUE; } } else if (state->visit == BFTW_POST) { if (trie_find_str(state->pruned, ftwbuf->path)) { return BFTW_PRUNE; } } state->bottom = false; enum bftw_action ret = state->delegate(ftwbuf, state->ptr); switch (ret) { case BFTW_CONTINUE: ret = BFTW_PRUNE; break; case BFTW_PRUNE: if (ftwbuf->typeflag == BFTW_DIR) { if (!trie_insert_str(state->pruned, ftwbuf->path)) { state->error = errno; state->quit = true; ret = BFTW_STOP; } } break; case BFTW_STOP: state->quit = true; break; } return ret; } /** * Iterative deepening bftw() wrapper. */ static int bftw_ids(const struct bftw_args *args) { struct trie pruned; trie_init(&pruned); struct bftw_ids_state state = { .delegate = args->callback, .ptr = args->ptr, .visit = BFTW_PRE, .depth = 0, .pruned = &pruned, .bottom = false, }; struct bftw_args ids_args = *args; ids_args.callback = bftw_ids_callback; ids_args.ptr = &state; ids_args.flags &= ~BFTW_DEPTH; ids_args.strategy = BFTW_DFS; int ret = 0; while (ret == 0 && !state.quit && !state.bottom) { state.bottom = true; // bftw_bfs() is more efficient than bftw_dfs() since it visits // directory entries as it reads them. With args->strategy == // BFTW_DFS, it gives a hybrid ordering that visits immediate // children first, then deeper descendants depth-first. This // doesn't matter for iterative deepening since we only visit // one level at a time. ret = bftw_bfs(&ids_args); ++state.depth; } if (args->flags & BFTW_DEPTH) { state.visit = BFTW_POST; while (ret == 0 && !state.quit && state.depth > 0) { --state.depth; ret = bftw_bfs(&ids_args); } } if (state.error) { ret = -1; } else { state.error = errno; } trie_destroy(&pruned); errno = state.error; return ret; } int bftw(const struct bftw_args *args) { switch (args->strategy) { case BFTW_BFS: return bftw_bfs(args); case BFTW_DFS: return bftw_dfs(args); case BFTW_IDS: return bftw_ids(args); } errno = EINVAL; return -1; } /**************************************************************************** * bfs * * Copyright (C) 2015-2019 Tavian Barnes * * * * Permission to use, copy, modify, and/or distribute this software for any * * purpose with or without fee is hereby granted. * * * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * ****************************************************************************/ #include #include #include #include #include #include #include #include #include /** * The parsed form of LS_COLORS. */ struct colors { char *reset; char *leftcode; char *rightcode; char *endcode; char *clear_to_eol; char *bold; char *gray; char *red; char *green; char *yellow; char *blue; char *magenta; char *cyan; char *white; char *warning; char *error; char *normal; char *file; char *multi_hard; char *executable; char *capable; char *setgid; char *setuid; char *directory; char *sticky; char *other_writable; char *sticky_other_writable; char *link; char *orphan; char *missing; bool link_as_target; char *blockdev; char *chardev; char *door; char *pipe; char *socket; /** A mapping from color names (fi, di, ln, etc.) to struct fields. */ struct trie names; /** A mapping from file extensions to colors. */ struct trie ext_colors; }; /** Initialize a color in the table. */ static int init_color(struct colors *colors, const char *name, const char *value, char **field) { if (value) { *field = dstrdup(value); if (!*field) { return -1; } } else { *field = NULL; } struct trie_leaf *leaf = trie_insert_str(&colors->names, name); if (leaf) { leaf->value = field; return 0; } else { return -1; } } /** Get a color from the table. */ static char **get_color(const struct colors *colors, const char *name) { const struct trie_leaf *leaf = trie_find_str(&colors->names, name); if (leaf) { return (char **)leaf->value; } else { return NULL; } } /** Set the value of a color. */ static void set_color(struct colors *colors, const char *name, char *value) { char **color = get_color(colors, name); if (color) { dstrfree(*color); *color = value; } } /** * Transform a file extension for fast lookups, by reversing and lowercasing it. */ static void extxfrm(char *ext) { size_t len = strlen(ext); for (size_t i = 0; i < len - i; ++i) { char a = ext[i]; char b = ext[len - i - 1]; // What's internationalization? Doesn't matter, this is what // GNU ls does. Luckily, since there's no standard C way to // casefold. Not using tolower() here since it respects the // current locale, which GNU ls doesn't do. if (a >= 'A' && a <= 'Z') { a += 'a' - 'A'; } if (b >= 'A' && b <= 'Z') { b += 'a' - 'A'; } ext[i] = b; ext[len - i - 1] = a; } } /** * Set the color for an extension. */ static int set_ext_color(struct colors *colors, char *key, const char *value) { extxfrm(key); // A later *.x should override any earlier *.x, *.y.x, etc. struct trie_leaf *match; while ((match = trie_find_postfix(&colors->ext_colors, key))) { dstrfree(match->value); trie_remove(&colors->ext_colors, match); } struct trie_leaf *leaf = trie_insert_str(&colors->ext_colors, key); if (leaf) { leaf->value = (char *)value; return 0; } else { return -1; } } /** * Find a color by an extension. */ static const char *get_ext_color(const struct colors *colors, const char *filename) { char *xfrm = strdup(filename); if (!xfrm) { return NULL; } extxfrm(xfrm); const struct trie_leaf *leaf = trie_find_prefix(&colors->ext_colors, xfrm); free(xfrm); if (leaf) { return leaf->value; } else { return NULL; } } /** * Parse a chunk of LS_COLORS that may have escape sequences. The supported * escapes are: * * \a, \b, \f, \n, \r, \t, \v: * As in C * \e: * ESC (\033) * \?: * DEL (\177) * \_: * ' ' (space) * \NNN: * Octal * \xNN: * Hex * ^C: * Control character. * * See man dir_colors. * * @param value * The value to parse. * @param end * The character that marks the end of the chunk. * @param[out] next * Will be set to the next chunk. * @return * The parsed chunk as a dstring. */ static char *unescape(const char *value, char end, const char **next) { if (!value) { goto fail; } char *str = dstralloc(0); if (!str) { goto fail_str; } const char *i; for (i = value; *i && *i != end; ++i) { unsigned char c = 0; switch (*i) { case '\\': switch (*++i) { case 'a': c = '\a'; break; case 'b': c = '\b'; break; case 'e': c = '\033'; break; case 'f': c = '\f'; break; case 'n': c = '\n'; break; case 'r': c = '\r'; break; case 't': c = '\t'; break; case 'v': c = '\v'; break; case '?': c = '\177'; break; case '_': c = ' '; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': while (i[1] >= '0' && i[1] <= '7') { c <<= 3; c |= *i++ - '0'; } c <<= 3; c |= *i - '0'; break; case 'X': case 'x': while (true) { if (i[1] >= '0' && i[1] <= '9') { c <<= 4; c |= i[1] - '0'; } else if (i[1] >= 'A' && i[1] <= 'F') { c <<= 4; c |= i[1] - 'A' + 0xA; } else if (i[1] >= 'a' && i[1] <= 'f') { c <<= 4; c |= i[1] - 'a' + 0xA; } else { break; } ++i; } break; case '\0': goto fail_str; default: c = *i; break; } break; case '^': switch (*++i) { case '?': c = '\177'; break; case '\0': goto fail_str; default: // CTRL masks bits 6 and 7 c = *i & 0x1F; break; } break; default: c = *i; break; } if (dstrapp(&str, c) != 0) { goto fail_str; } } if (*i) { *next = i + 1; } else { *next = NULL; } return str; fail_str: dstrfree(str); fail: *next = NULL; return NULL; } struct colors *parse_colors(const char *ls_colors) { struct colors *colors = malloc(sizeof(struct colors)); if (!colors) { return NULL; } trie_init(&colors->names); trie_init(&colors->ext_colors); int ret = 0; // From man console_codes ret |= init_color(colors, "rs", "0", &colors->reset); ret |= init_color(colors, "lc", "\033[", &colors->leftcode); ret |= init_color(colors, "rc", "m", &colors->rightcode); ret |= init_color(colors, "ec", NULL, &colors->endcode); ret |= init_color(colors, "cl", "\033[K", &colors->clear_to_eol); ret |= init_color(colors, "bld", "01", &colors->bold); ret |= init_color(colors, "gry", "01;30", &colors->gray); ret |= init_color(colors, "red", "01;31", &colors->red); ret |= init_color(colors, "grn", "01;32", &colors->green); ret |= init_color(colors, "ylw", "01;33", &colors->yellow); ret |= init_color(colors, "blu", "01;34", &colors->blue); ret |= init_color(colors, "mag", "01;35", &colors->magenta); ret |= init_color(colors, "cyn", "01;36", &colors->cyan); ret |= init_color(colors, "wht", "01;37", &colors->white); ret |= init_color(colors, "wr", "01;33", &colors->warning); ret |= init_color(colors, "er", "01;31", &colors->error); // Defaults from man dir_colors ret |= init_color(colors, "no", NULL, &colors->normal); ret |= init_color(colors, "fi", NULL, &colors->file); ret |= init_color(colors, "mh", NULL, &colors->multi_hard); ret |= init_color(colors, "ex", "01;32", &colors->executable); ret |= init_color(colors, "ca", "30;41", &colors->capable); ret |= init_color(colors, "sg", "30;43", &colors->setgid); ret |= init_color(colors, "su", "37;41", &colors->setuid); ret |= init_color(colors, "di", "01;34", &colors->directory); ret |= init_color(colors, "st", "37;44", &colors->sticky); ret |= init_color(colors, "ow", "34;42", &colors->other_writable); ret |= init_color(colors, "tw", "30;42", &colors->sticky_other_writable); ret |= init_color(colors, "ln", "01;36", &colors->link); ret |= init_color(colors, "or", NULL, &colors->orphan); ret |= init_color(colors, "mi", NULL, &colors->missing); colors->link_as_target = false; ret |= init_color(colors, "bd", "01;33", &colors->blockdev); ret |= init_color(colors, "cd", "01;33", &colors->chardev); ret |= init_color(colors, "do", "01;35", &colors->door); ret |= init_color(colors, "pi", "33", &colors->pipe); ret |= init_color(colors, "so", "01;35", &colors->socket); if (ret) { free_colors(colors); return NULL; } for (const char *chunk = ls_colors, *next; chunk; chunk = next) { if (chunk[0] == '*') { char *key = unescape(chunk + 1, '=', &next); if (!key) { continue; } char *value = unescape(next, ':', &next); if (value) { if (set_ext_color(colors, key, value) != 0) { dstrfree(value); } } dstrfree(key); } else { const char *equals = strchr(chunk, '='); if (!equals) { break; } char *value = unescape(equals + 1, ':', &next); if (!value) { continue; } char *key = strndup(chunk, equals - chunk); if (!key) { dstrfree(value); continue; } // All-zero values should be treated like NULL, to fall // back on any other relevant coloring for that file if (strspn(value, "0") == strlen(value) && strcmp(key, "rs") != 0 && strcmp(key, "lc") != 0 && strcmp(key, "rc") != 0 && strcmp(key, "ec") != 0) { dstrfree(value); value = NULL; } set_color(colors, key, value); free(key); } } if (colors->link && strcmp(colors->link, "target") == 0) { colors->link_as_target = true; dstrfree(colors->link); colors->link = NULL; } return colors; } void free_colors(struct colors *colors) { if (colors) { struct trie_leaf *leaf; while ((leaf = trie_first_leaf(&colors->ext_colors))) { dstrfree(leaf->value); trie_remove(&colors->ext_colors, leaf); } trie_destroy(&colors->ext_colors); while ((leaf = trie_first_leaf(&colors->names))) { char **field = leaf->value; dstrfree(*field); trie_remove(&colors->names, leaf); } trie_destroy(&colors->names); free(colors); } } CFILE *cfopen(const char *path, const struct colors *colors) { CFILE *cfile = malloc(sizeof(*cfile)); if (!cfile) { return NULL; } cfile->close = false; cfile->file = fopen(path, "wb"); if (!cfile->file) { cfclose(cfile); return NULL; } cfile->close = true; if (isatty(fileno(cfile->file))) { cfile->colors = colors; } else { cfile->colors = NULL; } return cfile; } CFILE *cfdup(FILE *file, const struct colors *colors) { CFILE *cfile = malloc(sizeof(*cfile)); if (!cfile) { return NULL; } cfile->close = false; cfile->file = file; if (isatty(fileno(file))) { cfile->colors = colors; } else { cfile->colors = NULL; } return cfile; } int cfclose(CFILE *cfile) { int ret = 0; if (cfile) { if (cfile->close) { ret = fclose(cfile->file); } free(cfile); } return ret; } /** Check if a symlink is broken. */ static bool is_link_broken(const struct BFTW *ftwbuf) { if (ftwbuf->stat_flags & BFS_STAT_NOFOLLOW) { return xfaccessat(ftwbuf->at_fd, ftwbuf->at_path, F_OK) != 0; } else { return true; } } /** Get the color for a file. */ static const char *file_color(const struct colors *colors, const char *filename, const struct BFTW *ftwbuf, enum bfs_stat_flag flags) { enum bftw_typeflag typeflag = bftw_typeflag(ftwbuf, flags); if (typeflag == BFTW_ERROR) { goto error; } const struct bfs_stat *statbuf = NULL; const char *color = NULL; switch (typeflag) { case BFTW_REG: if (colors->setuid || colors->setgid || colors->executable || colors->multi_hard) { statbuf = bftw_stat(ftwbuf, flags); if (!statbuf) { goto error; } } if (colors->setuid && (statbuf->mode & 04000)) { color = colors->setuid; } else if (colors->setgid && (statbuf->mode & 02000)) { color = colors->setgid; } else if (colors->capable && bfs_check_capabilities(ftwbuf) > 0) { color = colors->capable; } else if (colors->executable && (statbuf->mode & 00111)) { color = colors->executable; } else if (colors->multi_hard && statbuf->nlink > 1) { color = colors->multi_hard; } if (!color) { color = get_ext_color(colors, filename); } if (!color) { color = colors->file; } break; case BFTW_DIR: if (colors->sticky_other_writable || colors->other_writable || colors->sticky) { statbuf = bftw_stat(ftwbuf, flags); if (!statbuf) { goto error; } } if (colors->sticky_other_writable && (statbuf->mode & 01002) == 01002) { color = colors->sticky_other_writable; } else if (colors->other_writable && (statbuf->mode & 00002)) { color = colors->other_writable; } else if (colors->sticky && (statbuf->mode & 01000)) { color = colors->sticky; } else { color = colors->directory; } break; case BFTW_LNK: if (colors->orphan && is_link_broken(ftwbuf)) { color = colors->orphan; } else { color = colors->link; } break; case BFTW_BLK: color = colors->blockdev; break; case BFTW_CHR: color = colors->chardev; break; case BFTW_FIFO: color = colors->pipe; break; case BFTW_SOCK: color = colors->socket; break; case BFTW_DOOR: color = colors->door; break; default: break; } if (!color) { color = colors->normal; } return color; error: if (colors->missing) { return colors->missing; } else { return colors->orphan; } } /** Print a fixed-length string. */ static int print_strn(const char *str, size_t len, FILE *file) { if (fwrite(str, 1, len, file) == len) { return 0; } else { return -1; } } /** Print a dstring. */ static int print_dstr(const char *str, FILE *file) { return print_strn(str, dstrlen(str), file); } /** Print an ANSI escape sequence. */ static int print_esc(const struct colors *colors, const char *esc, FILE *file) { if (print_dstr(colors->leftcode, file) != 0) { return -1; } if (print_dstr(esc, file) != 0) { return -1; } if (print_dstr(colors->rightcode, file) != 0) { return -1; } return 0; } /** Reset after an ANSI escape sequence. */ static int print_reset(const struct colors *colors, FILE *file) { if (colors->endcode) { return print_dstr(colors->endcode, file); } else { return print_esc(colors, colors->reset, file); } } /** Print a string with an optional color. */ static int print_colored(const struct colors *colors, const char *esc, const char *str, size_t len, FILE *file) { if (esc) { if (print_esc(colors, esc, file) != 0) { return -1; } } if (print_strn(str, len, file) != 0) { return -1; } if (esc) { if (print_reset(colors, file) != 0) { return -1; } } return 0; } /** Print a path with colors. */ static int print_path_colored(CFILE *cfile, const char *path, const struct BFTW *ftwbuf, enum bfs_stat_flag flags) { const struct colors *colors = cfile->colors; FILE *file = cfile->file; size_t nameoff; if (path == ftwbuf->path) { nameoff = ftwbuf->nameoff; } else { nameoff = xbasename(path) - path; } if (nameoff > 0) { if (print_colored(colors, colors->directory, path, nameoff, file) != 0) { return -1; } } const char *filename = path + nameoff; const char *color = file_color(colors, filename, ftwbuf, flags); return print_colored(colors, color, filename, strlen(filename), file); } /** Print the path to a file with the appropriate colors. */ static int print_path(CFILE *cfile, const struct BFTW *ftwbuf) { const struct colors *colors = cfile->colors; if (!colors) { return fputs(ftwbuf->path, cfile->file) == EOF ? -1 : 0; } enum bfs_stat_flag flags = ftwbuf->stat_flags; if (colors && colors->link_as_target && ftwbuf->typeflag == BFTW_LNK) { flags = BFS_STAT_TRYFOLLOW; } return print_path_colored(cfile, ftwbuf->path, ftwbuf, flags); } /** Print a link target with the appropriate colors. */ static int print_link_target(CFILE *cfile, const struct BFTW *ftwbuf) { int ret = -1; size_t len = 0; const struct bfs_stat *statbuf = bftw_stat(ftwbuf, BFS_STAT_NOFOLLOW); if (statbuf) { len = statbuf->size; } char *target = xreadlinkat(ftwbuf->at_fd, ftwbuf->at_path, len); if (!target) { goto done; } if (!cfile->colors) { ret = fputs(target, cfile->file) == EOF ? -1 : 0; goto done; } ret = print_path_colored(cfile, target, ftwbuf, BFS_STAT_FOLLOW); done: free(target); return ret; } int cfprintf(CFILE *cfile, const char *format, ...) { va_list args; va_start(args, format); int ret = cvfprintf(cfile, format, args); va_end(args); return ret; } int cvfprintf(CFILE *cfile, const char *format, va_list args) { const struct colors *colors = cfile->colors; FILE *file = cfile->file; int error = errno; for (const char *i = format; *i; ++i) { size_t verbatim = strcspn(i, "%$"); if (fwrite(i, 1, verbatim, file) != verbatim) { return -1; } i += verbatim; switch (*i) { case '%': switch (*++i) { case '%': if (fputc('%', file) == EOF) { return -1; } break; case 'c': if (fputc(va_arg(args, int), file) == EOF) { return -1; } break; case 'd': if (fprintf(file, "%d", va_arg(args, int)) < 0) { return -1; } break; case 'g': if (fprintf(file, "%g", va_arg(args, double)) < 0) { return -1; } break; case 's': if (fputs(va_arg(args, const char *), file) == EOF) { return -1; } break; case 'z': ++i; if (*i != 'u') { goto invalid; } if (fprintf(file, "%zu", va_arg(args, size_t)) < 0) { return -1; } break; case 'm': if (fputs(strerror(error), file) == EOF) { return -1; } break; case 'p': switch (*++i) { case 'P': if (print_path(cfile, va_arg(args, const struct BFTW *)) != 0) { return -1; } break; case 'L': if (print_link_target(cfile, va_arg(args, const struct BFTW *)) != 0) { return -1; } break; default: goto invalid; } break; default: goto invalid; } break; case '$': switch (*++i) { case '$': if (fputc('$', file) == EOF) { return -1; } break; case '{': { ++i; const char *end = strchr(i, '}'); if (!end) { goto invalid; } if (!colors) { i = end; break; } size_t len = end - i; char name[len + 1]; memcpy(name, i, len); name[len] = '\0'; char **esc = get_color(colors, name); if (!esc) { goto invalid; } if (*esc) { if (print_esc(colors, *esc, file) != 0) { return -1; } } i = end; break; } default: goto invalid; } break; default: return 0; } } return 0; invalid: assert(false); errno = EINVAL; return -1; } /**************************************************************************** * bfs * * Copyright (C) 2019 Tavian Barnes * * * * Permission to use, copy, modify, and/or distribute this software for any * * purpose with or without fee is hereby granted. * * * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * ****************************************************************************/ #include #include #include #include void bfs_error(const struct cmdline *cmdline, const char *format, ...) { va_list args; va_start(args, format); bfs_verror(cmdline, format, args); va_end(args); } void bfs_warning(const struct cmdline *cmdline, const char *format, ...) { va_list args; va_start(args, format); bfs_vwarning(cmdline, format, args); va_end(args); } void bfs_verror(const struct cmdline *cmdline, const char *format, va_list args) { int error = errno; bfs_error_prefix(cmdline); errno = error; cvfprintf(cmdline->cerr, format, args); } void bfs_vwarning(const struct cmdline *cmdline, const char *format, va_list args) { int error = errno; bfs_warning_prefix(cmdline); errno = error; cvfprintf(cmdline->cerr, format, args); } void bfs_error_prefix(const struct cmdline *cmdline) { cfprintf(cmdline->cerr, "${bld}%s:${rs} ${er}error:${rs} ", xbasename(cmdline->argv[0])); } void bfs_warning_prefix(const struct cmdline *cmdline) { cfprintf(cmdline->cerr, "${bld}%s:${rs} ${wr}warning:${rs} ", xbasename(cmdline->argv[0])); } /**************************************************************************** * bfs * * Copyright (C) 2016-2019 Tavian Barnes * * * * Permission to use, copy, modify, and/or distribute this software for any * * purpose with or without fee is hereby granted. * * * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * ****************************************************************************/ #include #include #include #include #include /** * The memory representation of a dynamic string. Users get a pointer to data. */ struct dstring { size_t capacity; size_t length; char data[]; }; /** Get the string header from the string data pointer. */ static struct dstring *dstrheader(const char *dstr) { return (struct dstring *)(dstr - offsetof(struct dstring, data)); } /** Get the correct size for a dstring with the given capacity. */ static size_t dstrsize(size_t capacity) { return sizeof(struct dstring) + capacity + 1; } /** Allocate a dstring with the given contents. */ static char *dstralloc_impl(size_t capacity, size_t length, const char *data) { struct dstring *header = malloc(dstrsize(capacity)); if (!header) { return NULL; } header->capacity = capacity; header->length = length; return memcpy(header->data, data, length + 1); } char *dstralloc(size_t capacity) { return dstralloc_impl(capacity, 0, ""); } char *dstrdup(const char *str) { size_t len = strlen(str); return dstralloc_impl(len, len, str); } size_t dstrlen(const char *dstr) { return dstrheader(dstr)->length; } int dstreserve(char **dstr, size_t capacity) { struct dstring *header = dstrheader(*dstr); if (capacity > header->capacity) { capacity *= 2; header = realloc(header, dstrsize(capacity)); if (!header) { return -1; } header->capacity = capacity; *dstr = header->data; } return 0; } int dstresize(char **dstr, size_t length) { if (dstreserve(dstr, length) != 0) { return -1; } struct dstring *header = dstrheader(*dstr); header->length = length; header->data[length] = '\0'; return 0; } /** Common implementation of dstr{cat,ncat,app}. */ static int dstrcat_impl(char **dest, const char *src, size_t srclen) { size_t oldlen = dstrlen(*dest); size_t newlen = oldlen + srclen; if (dstresize(dest, newlen) != 0) { return -1; } memcpy(*dest + oldlen, src, srclen); return 0; } int dstrcat(char **dest, const char *src) { return dstrcat_impl(dest, src, strlen(src)); } int dstrncat(char **dest, const char *src, size_t n) { return dstrcat_impl(dest, src, strnlen(src, n)); } int dstrapp(char **str, char c) { return dstrcat_impl(str, &c, 1); } char *dstrprintf(const char *format, ...) { va_list args; va_start(args, format); int len = vsnprintf(NULL, 0, format, args); va_end(args); assert(len > 0); char *str = dstralloc(len); if (!str) { return NULL; } va_start(args, format); len = vsnprintf(str, len + 1, format, args); va_end(args); struct dstring *header = dstrheader(str); assert(len == header->capacity); header->length = len; return str; } void dstrfree(char *dstr) { if (dstr) { free(dstrheader(dstr)); } } /**************************************************************************** * bfs * * Copyright (C) 2015-2019 Tavian Barnes * * * * Permission to use, copy, modify, and/or distribute this software for any * * purpose with or without fee is hereby granted. * * * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * ****************************************************************************/ /** * Implementation of all the literal expressions. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct eval_state { /** Data about the current file. */ const struct BFTW *ftwbuf; /** The parsed command line. */ const struct cmdline *cmdline; /** The bftw() callback return value. */ enum bftw_action action; /** The eval_cmdline() return value. */ int *ret; /** Whether to quit immediately. */ bool quit; }; /** * Print an error message. */ BFS_FORMATTER(2, 3) static void eval_error(struct eval_state *state, const char *format, ...) { int error = errno; const struct cmdline *cmdline = state->cmdline; CFILE *cerr = cmdline->cerr; bfs_error(cmdline, "%pP: ", state->ftwbuf); va_list args; va_start(args, format); errno = error; cvfprintf(cerr, format, args); va_end(args); } /** * Check if an error should be ignored. */ static bool eval_should_ignore(const struct eval_state *state, int error) { return state->cmdline->ignore_races && is_nonexistence_error(error) && state->ftwbuf->depth > 0; } /** * Report an error that occurs during evaluation. */ static void eval_report_error(struct eval_state *state) { if (!eval_should_ignore(state, errno)) { eval_error(state, "%m.\n"); *state->ret = EXIT_FAILURE; } } /** * Perform a bfs_stat() call if necessary. */ static const struct bfs_stat *eval_stat(struct eval_state *state) { const struct BFTW *ftwbuf = state->ftwbuf; const struct bfs_stat *ret = bftw_stat(ftwbuf, ftwbuf->stat_flags); if (!ret) { eval_report_error(state); } return ret; } /** * Get the difference (in seconds) between two struct timespecs. */ static time_t timespec_diff(const struct timespec *lhs, const struct timespec *rhs) { time_t ret = lhs->tv_sec - rhs->tv_sec; if (lhs->tv_nsec < rhs->tv_nsec) { --ret; } return ret; } bool expr_cmp(const struct expr *expr, long long n) { switch (expr->cmp_flag) { case CMP_EXACT: return n == expr->idata; case CMP_LESS: return n < expr->idata; case CMP_GREATER: return n > expr->idata; } return false; } /** * -true test. */ bool eval_true(const struct expr *expr, struct eval_state *state) { return true; } /** * -false test. */ bool eval_false(const struct expr *expr, struct eval_state *state) { return false; } /** * -executable, -readable, -writable tests. */ bool eval_access(const struct expr *expr, struct eval_state *state) { const struct BFTW *ftwbuf = state->ftwbuf; return xfaccessat(ftwbuf->at_fd, ftwbuf->at_path, expr->idata) == 0; } /** * -acl test. */ bool eval_acl(const struct expr *expr, struct eval_state *state) { int ret = bfs_check_acl(state->ftwbuf); if (ret >= 0) { return ret; } else { eval_report_error(state); return false; } } /** * -capable test. */ bool eval_capable(const struct expr *expr, struct eval_state *state) { int ret = bfs_check_capabilities(state->ftwbuf); if (ret >= 0) { return ret; } else { eval_report_error(state); return false; } } /** * Get the given timespec field out of a stat buffer. */ static const struct timespec *eval_stat_time(const struct bfs_stat *statbuf, enum bfs_stat_field field, struct eval_state *state) { const struct timespec *ret = bfs_stat_time(statbuf, field); if (!ret) { eval_error(state, "Couldn't get file %s: %m.\n", bfs_stat_field_name(field)); *state->ret = EXIT_FAILURE; } return ret; } /** * -[aBcm]?newer tests. */ bool eval_newer(const struct expr *expr, struct eval_state *state) { const struct bfs_stat *statbuf = eval_stat(state); if (!statbuf) { return false; } const struct timespec *time = eval_stat_time(statbuf, expr->stat_field, state); if (!time) { return false; } return time->tv_sec > expr->reftime.tv_sec || (time->tv_sec == expr->reftime.tv_sec && time->tv_nsec > expr->reftime.tv_nsec); } /** * -[aBcm]{min,time} tests. */ bool eval_time(const struct expr *expr, struct eval_state *state) { const struct bfs_stat *statbuf = eval_stat(state); if (!statbuf) { return false; } const struct timespec *time = eval_stat_time(statbuf, expr->stat_field, state); if (!time) { return false; } time_t diff = timespec_diff(&expr->reftime, time); switch (expr->time_unit) { case MINUTES: diff /= 60; break; case DAYS: diff /= 60*60*24; break; } return expr_cmp(expr, diff); } /** * -used test. */ bool eval_used(const struct expr *expr, struct eval_state *state) { const struct bfs_stat *statbuf = eval_stat(state); if (!statbuf) { return false; } const struct timespec *atime = eval_stat_time(statbuf, BFS_STAT_ATIME, state); const struct timespec *ctime = eval_stat_time(statbuf, BFS_STAT_CTIME, state); if (!atime || !ctime) { return false; } time_t diff = timespec_diff(atime, ctime); diff /= 60*60*24; return expr_cmp(expr, diff); } /** * -gid test. */ bool eval_gid(const struct expr *expr, struct eval_state *state) { const struct bfs_stat *statbuf = eval_stat(state); if (!statbuf) { return false; } return expr_cmp(expr, statbuf->gid); } /** * -uid test. */ bool eval_uid(const struct expr *expr, struct eval_state *state) { const struct bfs_stat *statbuf = eval_stat(state); if (!statbuf) { return false; } return expr_cmp(expr, statbuf->uid); } /** * -nogroup test. */ bool eval_nogroup(const struct expr *expr, struct eval_state *state) { const struct bfs_stat *statbuf = eval_stat(state); if (!statbuf) { return false; } errno = 0; if (getgrgid(statbuf->gid) == NULL) { if (errno == 0) { return true; } else { eval_report_error(state); } } return false; } /** * -nouser test. */ bool eval_nouser(const struct expr *expr, struct eval_state *state) { const struct bfs_stat *statbuf = eval_stat(state); if (!statbuf) { return false; } errno = 0; if (getpwuid(statbuf->uid) == NULL) { if (errno == 0) { return true; } else { eval_report_error(state); } } return false; } /** * -delete action. */ bool eval_delete(const struct expr *expr, struct eval_state *state) { const struct BFTW *ftwbuf = state->ftwbuf; // Don't try to delete the current directory if (strcmp(ftwbuf->path, ".") == 0) { return true; } int flag = 0; // We need to know the actual type of the path, not what it points to enum bftw_typeflag type = bftw_typeflag(ftwbuf, BFS_STAT_NOFOLLOW); if (type == BFTW_DIR) { flag |= AT_REMOVEDIR; } else if (type == BFTW_ERROR) { eval_report_error(state); return false; } if (unlinkat(ftwbuf->at_fd, ftwbuf->at_path, flag) != 0) { eval_report_error(state); return false; } return true; } /** Finish any pending -exec ... + operations. */ static int eval_exec_finish(const struct expr *expr, const struct cmdline *cmdline) { int ret = 0; if (expr->execbuf && bfs_exec_finish(expr->execbuf) != 0) { if (errno != 0) { bfs_error(cmdline, "%s %s: %m.\n", expr->argv[0], expr->argv[1]); } ret = -1; } if (expr->lhs && eval_exec_finish(expr->lhs, cmdline) != 0) { ret = -1; } if (expr->rhs && eval_exec_finish(expr->rhs, cmdline) != 0) { ret = -1; } return ret; } /** * -exec[dir]/-ok[dir] actions. */ bool eval_exec(const struct expr *expr, struct eval_state *state) { bool ret = bfs_exec(expr->execbuf, state->ftwbuf) == 0; if (errno != 0) { eval_error(state, "%s %s: %m.\n", expr->argv[0], expr->argv[1]); *state->ret = EXIT_FAILURE; } return ret; } /** * -exit action. */ bool eval_exit(const struct expr *expr, struct eval_state *state) { state->action = BFTW_STOP; *state->ret = expr->idata; state->quit = true; return true; } /** * -depth N test. */ bool eval_depth(const struct expr *expr, struct eval_state *state) { return expr_cmp(expr, state->ftwbuf->depth); } /** * -empty test. */ bool eval_empty(const struct expr *expr, struct eval_state *state) { bool ret = false; const struct BFTW *ftwbuf = state->ftwbuf; if (ftwbuf->typeflag == BFTW_DIR) { int dfd = openat(ftwbuf->at_fd, ftwbuf->at_path, O_RDONLY | O_CLOEXEC | O_DIRECTORY); if (dfd < 0) { eval_report_error(state); goto done; } DIR *dir = fdopendir(dfd); if (!dir) { eval_report_error(state); close(dfd); goto done; } struct dirent *de; if (xreaddir(dir, &de) == 0) { ret = !de; } else { eval_report_error(state); } closedir(dir); } else { const struct bfs_stat *statbuf = eval_stat(state); if (statbuf) { ret = statbuf->size == 0; } } done: return ret; } /** * -fstype test. */ bool eval_fstype(const struct expr *expr, struct eval_state *state) { const struct bfs_stat *statbuf = eval_stat(state); if (!statbuf) { return false; } const char *type = bfs_fstype(state->cmdline->mtab, statbuf); return strcmp(type, expr->sdata) == 0; } /** * -hidden test. */ bool eval_hidden(const struct expr *expr, struct eval_state *state) { const struct BFTW *ftwbuf = state->ftwbuf; return ftwbuf->nameoff > 0 && ftwbuf->path[ftwbuf->nameoff] == '.'; } /** * -nohidden action. */ bool eval_nohidden(const struct expr *expr, struct eval_state *state) { if (eval_hidden(expr, state)) { eval_prune(expr, state); return false; } else { return true; } } /** * -inum test. */ bool eval_inum(const struct expr *expr, struct eval_state *state) { const struct bfs_stat *statbuf = eval_stat(state); if (!statbuf) { return false; } return expr_cmp(expr, statbuf->ino); } /** * -links test. */ bool eval_links(const struct expr *expr, struct eval_state *state) { const struct bfs_stat *statbuf = eval_stat(state); if (!statbuf) { return false; } return expr_cmp(expr, statbuf->nlink); } /** * -i?lname test. */ bool eval_lname(const struct expr *expr, struct eval_state *state) { bool ret = false; char *name = NULL; const struct BFTW *ftwbuf = state->ftwbuf; if (ftwbuf->typeflag != BFTW_LNK) { goto done; } const struct bfs_stat *statbuf = eval_stat(state); if (!statbuf) { goto done; } name = xreadlinkat(ftwbuf->at_fd, ftwbuf->at_path, statbuf->size); if (!name) { eval_report_error(state); goto done; } ret = fnmatch(expr->sdata, name, expr->idata) == 0; done: free(name); return ret; } /** * -i?name test. */ bool eval_name(const struct expr *expr, struct eval_state *state) { const struct BFTW *ftwbuf = state->ftwbuf; const char *name = ftwbuf->path + ftwbuf->nameoff; char *copy = NULL; if (ftwbuf->depth == 0) { // Any trailing slashes are not part of the name. This can only // happen for the root path. const char *slash = strchr(name, '/'); if (slash && slash > name) { copy = strndup(name, slash - name); if (!copy) { eval_report_error(state); return false; } name = copy; } } bool ret = fnmatch(expr->sdata, name, expr->idata) == 0; free(copy); return ret; } /** * -i?path test. */ bool eval_path(const struct expr *expr, struct eval_state *state) { const struct BFTW *ftwbuf = state->ftwbuf; return fnmatch(expr->sdata, ftwbuf->path, expr->idata) == 0; } /** * -perm test. */ bool eval_perm(const struct expr *expr, struct eval_state *state) { const struct bfs_stat *statbuf = eval_stat(state); if (!statbuf) { return false; } mode_t mode = statbuf->mode; mode_t target; if (state->ftwbuf->typeflag == BFTW_DIR) { target = expr->dir_mode; } else { target = expr->file_mode; } switch (expr->mode_cmp) { case MODE_EXACT: return (mode & 07777) == target; case MODE_ALL: return (mode & target) == target; case MODE_ANY: return !(mode & target) == !target; } return false; } /** * -f?ls action. */ bool eval_fls(const struct expr *expr, struct eval_state *state) { CFILE *cfile = expr->cfile; FILE *file = cfile->file; const struct BFTW *ftwbuf = state->ftwbuf; const struct bfs_stat *statbuf = eval_stat(state); if (!statbuf) { goto done; } uintmax_t ino = statbuf->ino; uintmax_t blocks = ((uintmax_t)statbuf->blocks*BFS_STAT_BLKSIZE + 1023)/1024; char mode[11]; format_mode(statbuf->mode, mode); 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; } } if (ftwbuf->typeflag & (BFTW_BLK | BFTW_CHR)) { int ma = bfs_major(statbuf->rdev); int mi = bfs_minor(statbuf->rdev); if (fprintf(file, " %3d, %3d", ma, mi) < 0) { goto error; } } else { uintmax_t size = statbuf->size; if (fprintf(file, " %8ju", size) < 0) { goto error; } } time_t time = statbuf->mtime.tv_sec; time_t now = expr->reftime.tv_sec; time_t six_months_ago = now - 6*30*24*60*60; time_t tomorrow = now + 24*60*60; struct tm tm; if (xlocaltime(&time, &tm) != 0) { goto error; } char time_str[256]; const char *time_format = "%b %e %H:%M"; if (time <= six_months_ago || time >= tomorrow) { time_format = "%b %e %Y"; } if (!strftime(time_str, sizeof(time_str), time_format, &tm)) { errno = EOVERFLOW; goto error; } if (fprintf(file, " %s", time_str) < 0) { goto error; } if (cfprintf(cfile, " %pP", ftwbuf) < 0) { goto error; } if (ftwbuf->typeflag == BFTW_LNK) { if (cfprintf(cfile, " -> %pL", ftwbuf) < 0) { goto error; } } if (fputc('\n', file) == EOF) { goto error; } done: return true; error: eval_report_error(state); return true; } /** * -f?print action. */ bool eval_fprint(const struct expr *expr, struct eval_state *state) { if (cfprintf(expr->cfile, "%pP\n", state->ftwbuf) < 0) { eval_report_error(state); } return true; } /** * -f?print0 action. */ bool eval_fprint0(const struct expr *expr, struct eval_state *state) { const char *path = state->ftwbuf->path; size_t length = strlen(path) + 1; if (fwrite(path, 1, length, expr->cfile->file) != length) { eval_report_error(state); } return true; } /** * -f?printf action. */ bool eval_fprintf(const struct expr *expr, struct eval_state *state) { if (bfs_printf(expr->cfile->file, expr->printf, state->ftwbuf) != 0) { eval_report_error(state); } return true; } /** * -printx action. */ bool eval_fprintx(const struct expr *expr, struct eval_state *state) { FILE *file = expr->cfile->file; const char *path = state->ftwbuf->path; while (true) { size_t span = strcspn(path, " \t\n\\$'\"`"); if (fwrite(path, 1, span, file) != span) { goto error; } path += span; char c = path[0]; if (!c) { break; } char escaped[] = {'\\', c}; if (fwrite(escaped, 1, sizeof(escaped), file) != sizeof(escaped)) { goto error; } ++path; } if (fputc('\n', file) == EOF) { goto error; } return true; error: eval_report_error(state); return true; } /** * -prune action. */ bool eval_prune(const struct expr *expr, struct eval_state *state) { state->action = BFTW_PRUNE; return true; } /** * -quit action. */ bool eval_quit(const struct expr *expr, struct eval_state *state) { state->action = BFTW_STOP; state->quit = true; return true; } /** * -i?regex test. */ bool eval_regex(const struct expr *expr, struct eval_state *state) { const char *path = state->ftwbuf->path; size_t len = strlen(path); regmatch_t match = { .rm_so = 0, .rm_eo = len, }; int flags = 0; #ifdef REG_STARTEND flags |= REG_STARTEND; #endif int err = regexec(expr->regex, path, 1, &match, flags); if (err == 0) { return match.rm_so == 0 && match.rm_eo == len; } else if (err != REG_NOMATCH) { char *str = xregerror(err, expr->regex); if (str) { eval_error(state, "%s.\n", str); free(str); } else { perror("xregerror()"); } *state->ret = EXIT_FAILURE; } return false; } /** * -samefile test. */ bool eval_samefile(const struct expr *expr, struct eval_state *state) { const struct bfs_stat *statbuf = eval_stat(state); if (!statbuf) { return false; } return statbuf->dev == expr->dev && statbuf->ino == expr->ino; } /** * -size test. */ bool eval_size(const struct expr *expr, struct eval_state *state) { const struct bfs_stat *statbuf = eval_stat(state); if (!statbuf) { return false; } static const off_t scales[] = { [SIZE_BLOCKS] = 512, [SIZE_BYTES] = 1, [SIZE_WORDS] = 2, [SIZE_KB] = 1024, [SIZE_MB] = 1024LL*1024, [SIZE_GB] = 1024LL*1024*1024, [SIZE_TB] = 1024LL*1024*1024*1024, [SIZE_PB] = 1024LL*1024*1024*1024*1024, }; off_t scale = scales[expr->size_unit]; off_t size = (statbuf->size + scale - 1)/scale; // Round up return expr_cmp(expr, size); } /** * -sparse test. */ bool eval_sparse(const struct expr *expr, struct eval_state *state) { const struct bfs_stat *statbuf = eval_stat(state); if (!statbuf) { return false; } blkcnt_t expected = (statbuf->size + BFS_STAT_BLKSIZE - 1)/BFS_STAT_BLKSIZE; return statbuf->blocks < expected; } /** * -type test. */ bool eval_type(const struct expr *expr, struct eval_state *state) { return state->ftwbuf->typeflag & expr->idata; } /** * -xattr test. */ bool eval_xattr(const struct expr *expr, struct eval_state *state) { int ret = bfs_check_xattrs(state->ftwbuf); if (ret >= 0) { return ret; } else { eval_report_error(state); return false; } } /** * -xtype test. */ bool eval_xtype(const struct expr *expr, struct eval_state *state) { const struct BFTW *ftwbuf = state->ftwbuf; enum bfs_stat_flag flags = ftwbuf->stat_flags ^ (BFS_STAT_NOFOLLOW | BFS_STAT_TRYFOLLOW); enum bftw_typeflag type = bftw_typeflag(ftwbuf, flags); if (type == BFTW_ERROR) { eval_report_error(state); return false; } else { return type & expr->idata; } } #if _POSIX_MONOTONIC_CLOCK > 0 # define BFS_CLOCK CLOCK_MONOTONIC #elif _POSIX_TIMERS > 0 # define BFS_CLOCK CLOCK_REALTIME #endif /** * Call clock_gettime(), if available. */ static int eval_gettime(struct timespec *ts) { #ifdef BFS_CLOCK int ret = clock_gettime(BFS_CLOCK, ts); if (ret != 0) { perror("clock_gettime()"); } return ret; #else return -1; #endif } /** * Record the time that elapsed evaluating an expression. */ static void add_elapsed(struct expr *expr, const struct timespec *start, const struct timespec *end) { expr->elapsed.tv_sec += end->tv_sec - start->tv_sec; expr->elapsed.tv_nsec += end->tv_nsec - start->tv_nsec; if (expr->elapsed.tv_nsec < 0) { expr->elapsed.tv_nsec += 1000000000L; --expr->elapsed.tv_sec; } else if (expr->elapsed.tv_nsec >= 1000000000L) { expr->elapsed.tv_nsec -= 1000000000L; ++expr->elapsed.tv_sec; } } /** * Evaluate an expression. */ static bool eval_expr(struct expr *expr, struct eval_state *state) { struct timespec start, end; bool time = state->cmdline->debug & DEBUG_RATES; if (time) { if (eval_gettime(&start) != 0) { time = false; } } assert(!state->quit); bool ret = expr->eval(expr, state); if (time) { if (eval_gettime(&end) == 0) { add_elapsed(expr, &start, &end); } } ++expr->evaluations; if (ret) { ++expr->successes; } if (expr_never_returns(expr)) { assert(state->quit); } else if (!state->quit) { assert(!expr->always_true || ret); assert(!expr->always_false || !ret); } return ret; } /** * Evaluate a negation. */ bool eval_not(const struct expr *expr, struct eval_state *state) { return !eval_expr(expr->rhs, state); } /** * Evaluate a conjunction. */ bool eval_and(const struct expr *expr, struct eval_state *state) { if (!eval_expr(expr->lhs, state)) { return false; } if (state->quit) { return false; } return eval_expr(expr->rhs, state); } /** * Evaluate a disjunction. */ bool eval_or(const struct expr *expr, struct eval_state *state) { if (eval_expr(expr->lhs, state)) { return true; } if (state->quit) { return false; } return eval_expr(expr->rhs, state); } /** * Evaluate the comma operator. */ bool eval_comma(const struct expr *expr, struct eval_state *state) { eval_expr(expr->lhs, state); if (state->quit) { return false; } return eval_expr(expr->rhs, state); } /** Check if we've seen a file before. */ static bool eval_file_unique(struct eval_state *state, struct trie *seen) { const struct bfs_stat *statbuf = eval_stat(state); if (!statbuf) { return false; } bfs_file_id id; bfs_stat_id(statbuf, &id); struct trie_leaf *leaf = trie_insert_mem(seen, id, sizeof(id)); if (!leaf) { eval_report_error(state); return false; } if (leaf->value) { state->action = BFTW_PRUNE; return false; } else { leaf->value = leaf; return true; } } #define DEBUG_FLAG(flags, flag) \ do { \ if ((flags & flag) || flags == flag) { \ fputs(#flag, stderr); \ flags ^= flag; \ if (flags) { \ fputs(" | ", stderr); \ } \ } \ } while (0) /** * Log a stat() call. */ static void debug_stat(const struct BFTW *ftwbuf, const struct bftw_stat *cache, enum bfs_stat_flag flags) { fprintf(stderr, "bfs_stat("); if (ftwbuf->at_fd == AT_FDCWD) { fprintf(stderr, "AT_FDCWD"); } else { size_t baselen = strlen(ftwbuf->path) - strlen(ftwbuf->at_path); fprintf(stderr, "\""); fwrite(ftwbuf->path, 1, baselen, stderr); fprintf(stderr, "\""); } fprintf(stderr, ", \"%s\", ", ftwbuf->at_path); DEBUG_FLAG(flags, BFS_STAT_FOLLOW); DEBUG_FLAG(flags, BFS_STAT_NOFOLLOW); DEBUG_FLAG(flags, BFS_STAT_TRYFOLLOW); fprintf(stderr, ") == %d", cache->buf ? 0 : -1); if (cache->error) { fprintf(stderr, " [%d]", cache->error); } fprintf(stderr, "\n"); } /** * Log any stat() calls that happened. */ static void debug_stats(const struct BFTW *ftwbuf) { const struct bfs_stat *statbuf = ftwbuf->stat_cache.buf; if (statbuf || ftwbuf->stat_cache.error) { debug_stat(ftwbuf, &ftwbuf->stat_cache, BFS_STAT_FOLLOW); } const struct bfs_stat *lstatbuf = ftwbuf->lstat_cache.buf; if ((lstatbuf && lstatbuf != statbuf) || ftwbuf->lstat_cache.error) { debug_stat(ftwbuf, &ftwbuf->lstat_cache, BFS_STAT_NOFOLLOW); } } /** * Dump the bftw_typeflag for -D search. */ static const char *dump_bftw_typeflag(enum bftw_typeflag type) { #define DUMP_BFTW_TYPEFLAG_CASE(flag) \ case flag: \ return #flag switch (type) { DUMP_BFTW_TYPEFLAG_CASE(BFTW_BLK); DUMP_BFTW_TYPEFLAG_CASE(BFTW_CHR); DUMP_BFTW_TYPEFLAG_CASE(BFTW_DIR); DUMP_BFTW_TYPEFLAG_CASE(BFTW_DOOR); DUMP_BFTW_TYPEFLAG_CASE(BFTW_FIFO); DUMP_BFTW_TYPEFLAG_CASE(BFTW_LNK); DUMP_BFTW_TYPEFLAG_CASE(BFTW_PORT); DUMP_BFTW_TYPEFLAG_CASE(BFTW_REG); DUMP_BFTW_TYPEFLAG_CASE(BFTW_SOCK); DUMP_BFTW_TYPEFLAG_CASE(BFTW_WHT); DUMP_BFTW_TYPEFLAG_CASE(BFTW_ERROR); default: DUMP_BFTW_TYPEFLAG_CASE(BFTW_UNKNOWN); } } #define DUMP_BFTW_MAP(value) [value] = #value /** * Dump the bftw_visit for -D search. */ static const char *dump_bftw_visit(enum bftw_visit visit) { static const char *visits[] = { DUMP_BFTW_MAP(BFTW_PRE), DUMP_BFTW_MAP(BFTW_POST), }; return visits[visit]; } /** * Dump the bftw_action for -D search. */ static const char *dump_bftw_action(enum bftw_action action) { static const char *actions[] = { DUMP_BFTW_MAP(BFTW_CONTINUE), DUMP_BFTW_MAP(BFTW_PRUNE), DUMP_BFTW_MAP(BFTW_STOP), }; return actions[action]; } /** * Type passed as the argument to the bftw() callback. */ struct callback_args { /** The parsed command line. */ const struct cmdline *cmdline; /** The set of seen files. */ struct trie *seen; /** Eventual return value from eval_cmdline(). */ int ret; }; /** * bftw() callback. */ static enum bftw_action cmdline_callback(const struct BFTW *ftwbuf, void *ptr) { struct callback_args *args = ptr; const struct cmdline *cmdline = args->cmdline; struct eval_state state; state.ftwbuf = ftwbuf; state.cmdline = cmdline; state.action = BFTW_CONTINUE; state.ret = &args->ret; state.quit = false; if (ftwbuf->typeflag == BFTW_ERROR) { if (!eval_should_ignore(&state, ftwbuf->error)) { args->ret = EXIT_FAILURE; eval_error(&state, "%s.\n", strerror(ftwbuf->error)); } state.action = BFTW_PRUNE; goto done; } if (cmdline->unique && ftwbuf->visit == BFTW_PRE) { if (!eval_file_unique(&state, args->seen)) { goto done; } } if (cmdline->xargs_safe && strpbrk(ftwbuf->path, " \t\n\'\"\\")) { args->ret = EXIT_FAILURE; eval_error(&state, "Path is not safe for xargs.\n"); state.action = BFTW_PRUNE; goto done; } if (cmdline->maxdepth < 0 || ftwbuf->depth >= cmdline->maxdepth) { state.action = BFTW_PRUNE; } // In -depth mode, only handle directories on the BFTW_POST visit enum bftw_visit expected_visit = BFTW_PRE; if ((cmdline->flags & BFTW_DEPTH) && (cmdline->strategy == BFTW_IDS || ftwbuf->typeflag == BFTW_DIR) && ftwbuf->depth < cmdline->maxdepth) { expected_visit = BFTW_POST; } if (ftwbuf->visit == expected_visit && ftwbuf->depth >= cmdline->mindepth && ftwbuf->depth <= cmdline->maxdepth) { eval_expr(cmdline->expr, &state); } done: if (cmdline->debug & DEBUG_STAT) { debug_stats(ftwbuf); } if (cmdline->debug & DEBUG_SEARCH) { fprintf(stderr, "cmdline_callback({\n"); fprintf(stderr, "\t.path = \"%s\",\n", ftwbuf->path); fprintf(stderr, "\t.root = \"%s\",\n", ftwbuf->root); fprintf(stderr, "\t.depth = %zu,\n", ftwbuf->depth); fprintf(stderr, "\t.visit = %s,\n", dump_bftw_visit(ftwbuf->visit)); fprintf(stderr, "\t.typeflag = %s,\n", dump_bftw_typeflag(ftwbuf->typeflag)); fprintf(stderr, "\t.error = %d,\n", ftwbuf->error); fprintf(stderr, "}) == %s\n", dump_bftw_action(state.action)); } return state.action; } /** * Infer the number of open file descriptors we're allowed to have. */ static int infer_fdlimit(const struct cmdline *cmdline) { int ret = 4096; struct rlimit rl; if (getrlimit(RLIMIT_NOFILE, &rl) == 0) { if (rl.rlim_cur != RLIM_INFINITY) { ret = rl.rlim_cur; } } // 3 for std{in,out,err} int nopen = 3 + cmdline->nopen_files; // Check /proc/self/fd for the current number of open fds, if possible // (we may have inherited more than just the standard ones) DIR *dir = opendir("/proc/self/fd"); if (!dir) { dir = opendir("/dev/fd"); } if (dir) { // Account for 'dir' itself nopen = -1; struct dirent *de; while (xreaddir(dir, &de) == 0 && de) { ++nopen; } closedir(dir); } ret -= nopen; ret -= cmdline->expr->persistent_fds; ret -= cmdline->expr->ephemeral_fds; // bftw() needs at least 2 available fds if (ret < 2) { ret = 2; } return ret; } /** * Dump the bftw() flags for -D search. */ static void dump_bftw_flags(enum bftw_flags flags) { DEBUG_FLAG(flags, 0); DEBUG_FLAG(flags, BFTW_STAT); DEBUG_FLAG(flags, BFTW_RECOVER); DEBUG_FLAG(flags, BFTW_DEPTH); DEBUG_FLAG(flags, BFTW_COMFOLLOW); DEBUG_FLAG(flags, BFTW_LOGICAL); DEBUG_FLAG(flags, BFTW_DETECT_CYCLES); DEBUG_FLAG(flags, BFTW_XDEV); assert(!flags); } /** * Dump the bftw_strategy for -D search. */ static const char *dump_bftw_strategy(enum bftw_strategy strategy) { static const char *strategies[] = { DUMP_BFTW_MAP(BFTW_BFS), DUMP_BFTW_MAP(BFTW_DFS), DUMP_BFTW_MAP(BFTW_IDS), }; return strategies[strategy]; } /** * Evaluate the command line. */ int eval_cmdline(const struct cmdline *cmdline) { if (!cmdline->expr) { return EXIT_SUCCESS; } struct callback_args args = { .cmdline = cmdline, .ret = EXIT_SUCCESS, }; struct trie seen; if (cmdline->unique) { trie_init(&seen); args.seen = &seen; } struct bftw_args bftw_args = { .paths = cmdline->paths, .npaths = cmdline->npaths, .callback = cmdline_callback, .ptr = &args, .nopenfd = infer_fdlimit(cmdline), .flags = cmdline->flags, .strategy = cmdline->strategy, .mtab = cmdline->mtab, }; if (cmdline->debug & DEBUG_SEARCH) { fprintf(stderr, "bftw({\n"); fprintf(stderr, "\t.paths = {\n"); for (size_t i = 0; i < bftw_args.npaths; ++i) { fprintf(stderr, "\t\t\"%s\",\n", bftw_args.paths[i]); } fprintf(stderr, "\t},\n"); fprintf(stderr, "\t.npaths = %zu,\n", bftw_args.npaths); fprintf(stderr, "\t.callback = cmdline_callback,\n"); fprintf(stderr, "\t.ptr = &args,\n"); fprintf(stderr, "\t.nopenfd = %d,\n", bftw_args.nopenfd); fprintf(stderr, "\t.flags = "); dump_bftw_flags(bftw_args.flags); fprintf(stderr, ",\n\t.strategy = %s,\n", dump_bftw_strategy(bftw_args.strategy)); fprintf(stderr, "\t.mtab = "); if (bftw_args.mtab) { fprintf(stderr, "cmdline->mtab"); } else { fprintf(stderr, "NULL"); } fprintf(stderr, ",\n})\n"); } if (bftw(&bftw_args) != 0) { args.ret = EXIT_FAILURE; perror("bftw()"); } if (eval_exec_finish(cmdline->expr, cmdline) != 0) { args.ret = EXIT_FAILURE; } if (cmdline->debug & DEBUG_RATES) { dump_cmdline(cmdline, true); } if (cmdline->unique) { trie_destroy(&seen); } return args.ret; } /**************************************************************************** * bfs * * Copyright (C) 2017-2018 Tavian Barnes * * * * Permission to use, copy, modify, and/or distribute this software for any * * purpose with or without fee is hereby granted. * * * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * ****************************************************************************/ #include #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, // so allow a one page cushion to account for rounding up long page_size = sysconf(_SC_PAGESIZE); if (page_size < 4096) { page_size = 4096; } arg_max -= page_size; bfs_exec_debug(execbuf, "ARG_MAX: %ld remaining after page cushion\n", arg_max); // POSIX recommends an additional 2048 bytes of headroom arg_max -= 2048; bfs_exec_debug(execbuf, "ARG_MAX: %ld remaining after headroom\n", arg_max); if (arg_max < 0) { arg_max = 0; } else if (arg_max > BFS_EXEC_ARG_MAX) { arg_max = BFS_EXEC_ARG_MAX; } bfs_exec_debug(execbuf, "ARG_MAX: %ld final value\n", arg_max); return arg_max; } struct bfs_exec *parse_bfs_exec(char **argv, enum bfs_exec_flags flags, const struct cmdline *cmdline) { struct bfs_exec *execbuf = malloc(sizeof(*execbuf)); if (!execbuf) { perror("malloc()"); goto fail; } execbuf->flags = flags; execbuf->argv = NULL; execbuf->argc = 0; execbuf->argv_cap = 0; execbuf->arg_size = 0; execbuf->arg_max = 0; execbuf->wd_fd = -1; execbuf->wd_path = NULL; execbuf->wd_len = 0; execbuf->ret = 0; if (cmdline->debug & DEBUG_EXEC) { execbuf->flags |= BFS_EXEC_DEBUG; } size_t i; for (i = 1; ; ++i) { const char *arg = argv[i]; if (!arg) { if (execbuf->flags & BFS_EXEC_CONFIRM) { bfs_error(cmdline, "%s: Expected '... ;'.\n", argv[0]); } else { bfs_error(cmdline, "%s: Expected '... ;' or '... {} +'.\n", argv[0]); } goto fail; } else if (strcmp(arg, ";") == 0) { break; } else if (strcmp(arg, "+") == 0) { if (!(execbuf->flags & BFS_EXEC_CONFIRM) && strcmp(argv[i - 1], "{}") == 0) { execbuf->flags |= BFS_EXEC_MULTI; break; } } } execbuf->tmpl_argv = argv + 1; execbuf->tmpl_argc = i - 1; if (execbuf->tmpl_argc == 0) { bfs_error(cmdline, "%s: Missing command.\n", argv[0]); goto fail; } execbuf->argv_cap = execbuf->tmpl_argc + 1; execbuf->argv = malloc(execbuf->argv_cap*sizeof(*execbuf->argv)); if (!execbuf->argv) { perror("malloc()"); goto fail; } if (execbuf->flags & BFS_EXEC_MULTI) { for (i = 0; i < execbuf->tmpl_argc - 1; ++i) { char *arg = execbuf->tmpl_argv[i]; if (strstr(arg, "{}")) { bfs_error(cmdline, "%s ... +: Only one '{}' is supported.\n", argv[0]); goto fail; } execbuf->argv[i] = arg; } execbuf->argc = execbuf->tmpl_argc - 1; execbuf->arg_max = bfs_exec_arg_max(execbuf); } return execbuf; fail: free_bfs_exec(execbuf); return NULL; } /** Format the current path for use as a command line argument. */ static char *bfs_exec_format_path(const struct bfs_exec *execbuf, const struct BFTW *ftwbuf) { if (!(execbuf->flags & BFS_EXEC_CHDIR)) { return strdup(ftwbuf->path); } const char *name = ftwbuf->path + ftwbuf->nameoff; if (name[0] == '/') { // Must be a root path ("/", "//", etc.) return strdup(name); } // For compatibility with GNU find, use './name' instead of just 'name' char *path = malloc(2 + strlen(name) + 1); if (!path) { return NULL; } strcpy(path, "./"); strcpy(path + 2, name); return path; } /** Format an argument, expanding "{}" to the current path. */ static char *bfs_exec_format_arg(char *arg, const char *path) { char *match = strstr(arg, "{}"); if (!match) { return arg; } char *ret = dstralloc(0); if (!ret) { return NULL; } char *last = arg; do { if (dstrncat(&ret, last, match - last) != 0) { goto err; } if (dstrcat(&ret, path) != 0) { goto err; } last = match + 2; match = strstr(last, "{}"); } while (match); if (dstrcat(&ret, last) != 0) { goto err; } return ret; err: dstrfree(ret); return NULL; } /** Free a formatted argument. */ static void bfs_exec_free_arg(char *arg, const char *tmpl) { if (arg != tmpl) { dstrfree(arg); } } /** Open a file to use as the working directory. */ static int bfs_exec_openwd(struct bfs_exec *execbuf, const struct BFTW *ftwbuf) { assert(execbuf->wd_fd < 0); assert(!execbuf->wd_path); if (ftwbuf->at_fd != AT_FDCWD) { // Rely on at_fd being the immediate parent assert(ftwbuf->at_path == xbasename(ftwbuf->at_path)); execbuf->wd_fd = ftwbuf->at_fd; if (!(execbuf->flags & BFS_EXEC_MULTI)) { return 0; } execbuf->wd_fd = dup_cloexec(execbuf->wd_fd); if (execbuf->wd_fd < 0) { return -1; } } execbuf->wd_len = ftwbuf->nameoff; if (execbuf->wd_len == 0) { if (ftwbuf->path[0] == '/') { ++execbuf->wd_len; } else { // The path is something like "foo", so we're already in the right directory return 0; } } execbuf->wd_path = strndup(ftwbuf->path, execbuf->wd_len); if (!execbuf->wd_path) { return -1; } if (execbuf->wd_fd < 0) { execbuf->wd_fd = open(execbuf->wd_path, O_RDONLY | O_CLOEXEC | O_DIRECTORY); } if (execbuf->wd_fd < 0) { return -1; } return 0; } /** Close the working directory. */ static int bfs_exec_closewd(struct bfs_exec *execbuf, const struct BFTW *ftwbuf) { int ret = 0; if (execbuf->wd_fd >= 0) { if (!ftwbuf || execbuf->wd_fd != ftwbuf->at_fd) { ret = close(execbuf->wd_fd); } execbuf->wd_fd = -1; } if (execbuf->wd_path) { free(execbuf->wd_path); execbuf->wd_path = NULL; execbuf->wd_len = 0; } return ret; } /** Actually spawn the process. */ static int bfs_exec_spawn(const struct bfs_exec *execbuf) { if (execbuf->flags & BFS_EXEC_CONFIRM) { for (size_t i = 0; i < execbuf->argc; ++i) { fprintf(stderr, "%s ", execbuf->argv[i]); } fprintf(stderr, "? "); if (ynprompt() <= 0) { errno = 0; return -1; } } if (execbuf->flags & BFS_EXEC_MULTI) { bfs_exec_debug(execbuf, "Executing '%s' ... [%zu arguments] (size %zu)\n", execbuf->argv[0], execbuf->argc - 1, execbuf->arg_size); } else { bfs_exec_debug(execbuf, "Executing '%s' ... [%zu arguments]\n", execbuf->argv[0], execbuf->argc - 1); } pid_t pid = -1; int error; struct bfs_spawn ctx; if (bfs_spawn_init(&ctx) != 0) { return -1; } if (bfs_spawn_setflags(&ctx, BFS_SPAWN_USEPATH) != 0) { goto fail; } if (execbuf->wd_fd >= 0) { if (bfs_spawn_addfchdir(&ctx, execbuf->wd_fd) != 0) { goto fail; } } pid = bfs_spawn(execbuf->argv[0], &ctx, execbuf->argv, environ); fail: error = errno; bfs_spawn_destroy(&ctx); if (pid < 0) { errno = error; return -1; } int wstatus; if (waitpid(pid, &wstatus, 0) < 0) { return -1; } int ret = -1; if (WIFEXITED(wstatus)) { int status = WEXITSTATUS(wstatus); if (status == EXIT_SUCCESS) { ret = 0; } else { bfs_exec_debug(execbuf, "Command '%s' failed with status %d\n", execbuf->argv[0], status); } } else if (WIFSIGNALED(wstatus)) { int sig = WTERMSIG(wstatus); bfs_exec_debug(execbuf, "Command '%s' terminated by signal %d\n", execbuf->argv[0], sig); } else { bfs_exec_debug(execbuf, "Command '%s' terminated abnormally\n", execbuf->argv[0]); } errno = 0; return ret; } /** exec() a command for a single file. */ static int bfs_exec_single(struct bfs_exec *execbuf, const struct BFTW *ftwbuf) { int ret = -1, error = 0; char *path = bfs_exec_format_path(execbuf, ftwbuf); if (!path) { goto out; } size_t i; for (i = 0; i < execbuf->tmpl_argc; ++i) { execbuf->argv[i] = bfs_exec_format_arg(execbuf->tmpl_argv[i], path); if (!execbuf->argv[i]) { goto out_free; } } execbuf->argv[i] = NULL; execbuf->argc = i; if (execbuf->flags & BFS_EXEC_CHDIR) { if (bfs_exec_openwd(execbuf, ftwbuf) != 0) { goto out_free; } } ret = bfs_exec_spawn(execbuf); out_free: error = errno; bfs_exec_closewd(execbuf, ftwbuf); for (size_t j = 0; j < i; ++j) { bfs_exec_free_arg(execbuf->argv[j], execbuf->tmpl_argv[j]); } free(path); errno = error; out: return ret; } /** Check if any arguments remain in the buffer. */ static bool bfs_exec_args_remain(const struct bfs_exec *execbuf) { return execbuf->argc >= execbuf->tmpl_argc; } /** Execute the pending command from a BFS_EXEC_MULTI execbuf. */ static int bfs_exec_flush(struct bfs_exec *execbuf) { int ret = 0, error = 0; size_t orig_argc = execbuf->argc; while (bfs_exec_args_remain(execbuf)) { execbuf->argv[execbuf->argc] = NULL; ret = bfs_exec_spawn(execbuf); error = errno; if (ret == 0 || error != E2BIG) { break; } // Try to recover from E2BIG by trying fewer and fewer arguments // until they fit bfs_exec_debug(execbuf, "Got E2BIG, shrinking argument list...\n"); execbuf->argv[execbuf->argc] = execbuf->argv[execbuf->argc - 1]; execbuf->arg_size -= bfs_exec_arg_size(execbuf->argv[execbuf->argc]); --execbuf->argc; } size_t new_argc = execbuf->argc; size_t new_size = execbuf->arg_size; for (size_t i = execbuf->tmpl_argc - 1; i < new_argc; ++i) { free(execbuf->argv[i]); } execbuf->argc = execbuf->tmpl_argc - 1; execbuf->arg_size = 0; if (new_argc < orig_argc) { execbuf->arg_max = new_size; bfs_exec_debug(execbuf, "ARG_MAX: %zu\n", execbuf->arg_max); // If we recovered from E2BIG, there are unused arguments at the // end of the list for (size_t i = new_argc + 1; i <= orig_argc; ++i) { if (error == 0) { execbuf->argv[execbuf->argc] = execbuf->argv[i]; execbuf->arg_size += bfs_exec_arg_size(execbuf->argv[execbuf->argc]); ++execbuf->argc; } else { free(execbuf->argv[i]); } } } errno = error; return ret; } /** Check if we need to flush the execbuf because we're changing directories. */ static bool bfs_exec_changed_dirs(const struct bfs_exec *execbuf, const struct BFTW *ftwbuf) { if (execbuf->flags & BFS_EXEC_CHDIR) { if (ftwbuf->nameoff > execbuf->wd_len || (execbuf->wd_path && strncmp(ftwbuf->path, execbuf->wd_path, execbuf->wd_len) != 0)) { bfs_exec_debug(execbuf, "Changed directories, executing buffered command\n"); return true; } } return false; } /** Check if we need to flush the execbuf because we're too big. */ static bool bfs_exec_would_overflow(const struct bfs_exec *execbuf, const char *arg) { size_t next_size = execbuf->arg_size + bfs_exec_arg_size(arg); if (next_size > execbuf->arg_max) { bfs_exec_debug(execbuf, "Command size (%zu) would exceed maximum (%zu), executing buffered command\n", next_size, execbuf->arg_max); return true; } return false; } /** Push a new argument to a BFS_EXEC_MULTI execbuf. */ static int bfs_exec_push(struct bfs_exec *execbuf, char *arg) { execbuf->argv[execbuf->argc] = arg; if (execbuf->argc + 1 >= execbuf->argv_cap) { size_t cap = 2*execbuf->argv_cap; char **argv = realloc(execbuf->argv, cap*sizeof(*argv)); if (!argv) { return -1; } execbuf->argv = argv; execbuf->argv_cap = cap; } ++execbuf->argc; execbuf->arg_size += bfs_exec_arg_size(arg); return 0; } /** Handle a new path for a BFS_EXEC_MULTI execbuf. */ static int bfs_exec_multi(struct bfs_exec *execbuf, const struct BFTW *ftwbuf) { int ret = 0; char *arg = bfs_exec_format_path(execbuf, ftwbuf); if (!arg) { ret = -1; goto out; } if (bfs_exec_changed_dirs(execbuf, ftwbuf)) { while (bfs_exec_args_remain(execbuf)) { ret |= bfs_exec_flush(execbuf); } bfs_exec_closewd(execbuf, ftwbuf); } else if (bfs_exec_would_overflow(execbuf, arg)) { ret |= bfs_exec_flush(execbuf); } if ((execbuf->flags & BFS_EXEC_CHDIR) && execbuf->wd_fd < 0) { if (bfs_exec_openwd(execbuf, ftwbuf) != 0) { ret = -1; goto out_arg; } } if (bfs_exec_push(execbuf, arg) != 0) { ret = -1; goto out_arg; } // arg will get cleaned up later by bfs_exec_flush() goto out; out_arg: free(arg); out: return ret; } int bfs_exec(struct bfs_exec *execbuf, const struct BFTW *ftwbuf) { if (execbuf->flags & BFS_EXEC_MULTI) { if (bfs_exec_multi(execbuf, ftwbuf) == 0) { errno = 0; } else { execbuf->ret = -1; } // -exec ... + never returns false return 0; } else { return bfs_exec_single(execbuf, ftwbuf); } } int bfs_exec_finish(struct bfs_exec *execbuf) { if (execbuf->flags & BFS_EXEC_MULTI) { bfs_exec_debug(execbuf, "Finishing execution, executing buffered command\n"); while (bfs_exec_args_remain(execbuf)) { execbuf->ret |= bfs_exec_flush(execbuf); } if (execbuf->ret != 0) { bfs_exec_debug(execbuf, "One or more executions of '%s' failed\n", execbuf->argv[0]); } } return execbuf->ret; } void free_bfs_exec(struct bfs_exec *execbuf) { if (execbuf) { bfs_exec_closewd(execbuf, NULL); free(execbuf->argv); free(execbuf); } } /**************************************************************************** * bfs * * Copyright (C) 2019 Tavian Barnes * * * * Permission to use, copy, modify, and/or distribute this software for any * * purpose with or without fee is hereby granted. * * * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * ****************************************************************************/ #include #include #include #if BFS_CAN_CHECK_ACL # include #endif #if BFS_CAN_CHECK_CAPABILITIES # include #endif #if BFS_CAN_CHECK_XATTRS # include #endif #if BFS_CAN_CHECK_ACL || BFS_CAN_CHECK_CAPABILITIES || BFS_CAN_CHECK_XATTRS /** * Many of the APIs used here don't have *at() variants, but we can try to * emulate something similar if /proc/self/fd is available. */ static const char *fake_at(const struct BFTW *ftwbuf) { static bool proc_works = true; static bool proc_checked = false; char *path = NULL; if (!proc_works || ftwbuf->at_fd == AT_FDCWD) { goto fail; } path = dstrprintf("/proc/self/fd/%d/", ftwbuf->at_fd); if (!path) { goto fail; } if (!proc_checked) { proc_checked = true; if (xfaccessat(AT_FDCWD, path, F_OK) != 0) { proc_works = false; goto fail; } } if (dstrcat(&path, ftwbuf->at_path) != 0) { goto fail; } return path; fail: dstrfree(path); return ftwbuf->path; } static void free_fake_at(const struct BFTW *ftwbuf, const char *path) { if (path != ftwbuf->path) { dstrfree((char *)path); } } /** * Check if an error was caused by the absence of support or data for a feature. */ static bool is_absence_error(int error) { // If the OS doesn't support the feature, it's obviously not enabled for // any files if (error == ENOTSUP) { return true; } // On Linux, ACLs and capabilities are implemented in terms of extended // attributes, which report ENODATA/ENOATTR when missing #ifdef ENODATA if (error == ENODATA) { return true; } #endif #if defined(ENOATTR) && ENOATTR != ENODATA if (error == ENOATTR) { return true; } #endif #if __APPLE__ // On macOS, ENOENT can also signal that a file has no ACLs if (error == ENOENT) { return true; } #endif return false; } #endif // BFS_CAN_CHECK_ACL || BFS_CAN_CHECK_CAPABILITIES || BFS_CAN_CHECK_XATTRS #if BFS_CAN_CHECK_ACL /** Check if any ACLs of the given type are non-trivial. */ static int bfs_check_acl_type(const char *path, acl_type_t type) { acl_t acl = acl_get_file(path, type); if (!acl) { if (is_absence_error(errno)) { return 0; } else { return -1; } } int ret = 0; acl_entry_t entry; for (int status = acl_get_entry(acl, ACL_FIRST_ENTRY, &entry); #if __APPLE__ // POSIX.1e specifies a return value of 1 for success, but macOS // returns 0 instead status == 0; #else status > 0; #endif status = acl_get_entry(acl, ACL_NEXT_ENTRY, &entry)) { #if defined(ACL_USER_OBJ) && defined(ACL_GROUP_OBJ) && defined(ACL_OTHER) acl_tag_t tag; if (acl_get_tag_type(entry, &tag) != 0) { continue; } if (tag != ACL_USER_OBJ && tag != ACL_GROUP_OBJ && tag != ACL_OTHER) { ret = 1; break; } #else ret = 1; break; #endif } acl_free(acl); return ret; } int bfs_check_acl(const struct BFTW *ftwbuf) { if (ftwbuf->typeflag == BFTW_LNK) { return 0; } const char *path = fake_at(ftwbuf); int error = ENOTSUP; int ret = -1; #if __APPLE__ // macOS gives EINVAL for either of the two standard ACL types, // supporting only ACL_TYPE_EXTENDED if (ret <= 0) { ret = bfs_check_acl_type(path, ACL_TYPE_EXTENDED); if (ret < 0) { error = errno; } } #else if (ret <= 0) { ret = bfs_check_acl_type(path, ACL_TYPE_ACCESS); if (ret < 0) { error = errno; } } if (ret <= 0 && ftwbuf->typeflag == BFTW_DIR) { ret = bfs_check_acl_type(path, ACL_TYPE_DEFAULT); if (ret < 0) { error = errno; } } #endif free_fake_at(ftwbuf, path); errno = error; return ret; } #else // !BFS_CAN_CHECK_ACL int bfs_check_acl(const struct BFTW *ftwbuf) { errno = ENOTSUP; return -1; } #endif #if BFS_CAN_CHECK_CAPABILITIES int bfs_check_capabilities(const struct BFTW *ftwbuf) { if (ftwbuf->typeflag == BFTW_LNK) { return 0; } int ret = -1, error; const char *path = fake_at(ftwbuf); cap_t caps = cap_get_file(path); if (!caps) { error = errno; if (is_absence_error(error)) { ret = 0; } goto out_path; } // TODO: Any better way to check for a non-empty capability set? char *text = cap_to_text(caps, NULL); if (!text) { error = errno; goto out_caps; } ret = text[0] ? 1 : 0; error = errno; cap_free(text); out_caps: cap_free(caps); out_path: free_fake_at(ftwbuf, path); errno = error; return ret; } #else // !BFS_CAN_CHECK_CAPABILITIES int bfs_check_capabilities(const struct BFTW *ftwbuf) { errno = ENOTSUP; return -1; } #endif #if BFS_CAN_CHECK_XATTRS int bfs_check_xattrs(const struct BFTW *ftwbuf) { const char *path = fake_at(ftwbuf); ssize_t len; #if __APPLE__ int options = ftwbuf->typeflag == BFTW_LNK ? XATTR_NOFOLLOW : 0; len = listxattr(path, NULL, 0, options); #else if (ftwbuf->typeflag == BFTW_LNK) { len = llistxattr(path, NULL, 0); } else { len = listxattr(path, NULL, 0); } #endif int error = errno; free_fake_at(ftwbuf, path); if (len > 0) { return 1; } else if (len == 0 || is_absence_error(error)) { return 0; } else if (error == E2BIG) { return 1; } else { errno = error; return -1; } } #else // !BFS_CAN_CHECK_XATTRS int bfs_check_xattrs(const struct BFTW *ftwbuf) { errno = ENOTSUP; return -1; } #endif /**************************************************************************** * bfs * * Copyright (C) 2015-2019 Tavian Barnes * * * * Permission to use, copy, modify, and/or distribute this software for any * * purpose with or without fee is hereby granted. * * * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * ****************************************************************************/ /** * - main(): the entry point for bfs(1), a breadth-first version of find(1) * - main.c (this file) * * - parse_cmdline(): parses the command line into an expression tree * - cmdline.h (declares the parsed command line structure) * - expr.h (declares the expression tree nodes) * - parse.c (the parser itself) * - opt.c (the expression optimizer) * * - eval_cmdline(): runs the expression on every file it sees * - eval.[ch] (the main evaluation functions) * - exec.[ch] (implements -exec[dir]/-ok[dir]) * - printf.[ch] (implements -[f]printf) * * - bftw(): used by eval_cmdline() to walk the directory tree(s) * - bftw.[ch] (an extended version of nftw(3)) * * - Utilities: * - bfs.h (constants about bfs itself) * - color.[ch] (for pretty terminal colors) * - diag.[ch] (formats diagnostic messages) * - dstring.[ch] (a dynamic string library) * - fsade.[ch] (a facade over non-standard filesystem features) * - mtab.[ch] (parses the system's mount table) * - spawn.[ch] (spawns processes) * - stat.[ch] (wraps stat(), or statx() on Linux) * - trie.[ch] (a trie set/map implementation) * - typo.[ch] (fuzzy matching for typos) * - util.[ch] (everything else) */ #include #include #include #include #include #include /** * Make sure the standard streams std{in,out,err} are open. If they are not, * future open() calls may use those file descriptors, and std{in,out,err} will * use them unintentionally. */ static int open_std_streams(void) { #ifdef O_PATH const int inflags = O_PATH, outflags = O_PATH; #else // These are intentionally backwards so that bfs >&- still fails with EBADF const int inflags = O_WRONLY, outflags = O_RDONLY; #endif if (!isopen(STDERR_FILENO) && redirect(STDERR_FILENO, "/dev/null", outflags) < 0) { return -1; } if (!isopen(STDOUT_FILENO) && redirect(STDOUT_FILENO, "/dev/null", outflags) < 0) { perror("redirect()"); return -1; } if (!isopen(STDIN_FILENO) && redirect(STDIN_FILENO, "/dev/null", inflags) < 0) { perror("redirect()"); return -1; } return 0; } /** * bfs entry point. */ int main(int argc, char *argv[]) { int ret = EXIT_FAILURE; // Make sure the standard streams are open if (open_std_streams() != 0) { goto done; } // Use the system locale instead of "C" setlocale(LC_ALL, ""); struct cmdline *cmdline = parse_cmdline(argc, argv); if (cmdline) { ret = eval_cmdline(cmdline); } if (free_cmdline(cmdline) != 0 && ret == EXIT_SUCCESS) { ret = EXIT_FAILURE; } done: return ret; } /**************************************************************************** * bfs * * Copyright (C) 2017-2019 Tavian Barnes * * * * Permission to use, copy, modify, and/or distribute this software for any * * purpose with or without fee is hereby granted. * * * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * ****************************************************************************/ #include #include #include #include #include #include #include #if BFS_HAS_SYS_PARAM # include #endif #if BFS_HAS_MNTENT # define BFS_MNTENT 1 #elif BSD # define BFS_MNTINFO 1 #elif __SVR4 # define BFS_MNTTAB 1 #endif #if BFS_MNTENT # include # include # include #elif BFS_MNTINFO # include # include #elif BFS_MNTTAB # include # include #endif struct bfs_mtab { /** A map from device ID to file system type. */ struct trie types; /** The names of all the mount points. */ struct trie names; }; /** * Add an entry to the mount table. */ static int bfs_mtab_add(struct bfs_mtab *mtab, const char *path, dev_t dev, const char *type) { if (!trie_insert_str(&mtab->names, xbasename(path))) { return -1; } struct trie_leaf *leaf = trie_insert_mem(&mtab->types, &dev, sizeof(dev)); if (!leaf) { return -1; } if (leaf->value) { return 0; } leaf->value = strdup(type); if (leaf->value) { return 0; } else { trie_remove(&mtab->types, leaf); return -1; } } struct bfs_mtab *parse_bfs_mtab() { #if BFS_MNTENT FILE *file = setmntent(_PATH_MOUNTED, "r"); if (!file) { // In case we're in a chroot or something with /proc but no /etc/mtab file = setmntent("/proc/mounts", "r"); } if (!file) { goto fail; } struct bfs_mtab *mtab = malloc(sizeof(*mtab)); if (!mtab) { goto fail_file; } trie_init(&mtab->types); trie_init(&mtab->names); struct mntent *mnt; while ((mnt = getmntent(file))) { struct bfs_stat sb; if (bfs_stat(AT_FDCWD, mnt->mnt_dir, BFS_STAT_NOFOLLOW, &sb) != 0) { continue; } if (bfs_mtab_add(mtab, mnt->mnt_dir, 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; } trie_init(&mtab->types); trie_init(&mtab->names); for (struct statfs *mnt = mntbuf; mnt < mntbuf + size; ++mnt) { struct bfs_stat sb; if (bfs_stat(AT_FDCWD, mnt->f_mntonname, BFS_STAT_NOFOLLOW, &sb) != 0) { continue; } if (bfs_mtab_add(mtab, mnt->f_mntonname, 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; } trie_init(&mtab->types); trie_init(&mtab->names); struct mnttab mnt; while (getmntent(file, &mnt) == 0) { struct bfs_stat sb; if (bfs_stat(AT_FDCWD, mnt.mnt_mountp, BFS_STAT_NOFOLLOW, &sb) != 0) { continue; } if (bfs_mtab_add(mtab, mnt.mnt_mountp, 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) { const struct trie_leaf *leaf = trie_find_mem(&mtab->types, &statbuf->dev, sizeof(statbuf->dev)); if (leaf) { return leaf->value; } else { return "unknown"; } } bool bfs_maybe_mount(const struct bfs_mtab *mtab, const char *path) { const char *name = xbasename(path); return trie_find_str(&mtab->names, name); } void free_bfs_mtab(struct bfs_mtab *mtab) { if (mtab) { trie_destroy(&mtab->names); struct trie_leaf *leaf; while ((leaf = trie_first_leaf(&mtab->types))) { free(leaf->value); trie_remove(&mtab->types, leaf); } trie_destroy(&mtab->types); free(mtab); } } /**************************************************************************** * bfs * * Copyright (C) 2017-2019 Tavian Barnes * * * * Permission to use, copy, modify, and/or distribute this software for any * * purpose with or without fee is hereby granted. * * * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * ****************************************************************************/ /** * The expression optimizer. Different optimization levels are supported: * * -O1: basic logical simplifications, like folding (-true -and -foo) to -foo. * * -O2: dead code elimination and data flow analysis. struct opt_facts is used * to record data flow facts that are true at various points of evaluation. * Specifically, struct opt_facts records the facts that must be true before an * expression is evaluated (state->facts), and those that must be true after the * expression is evaluated, given that it returns true (state->facts_when_true) * or false (state->facts_when_true). Additionally, state->facts_when_impure * records the possible data flow facts before any expressions with side effects * are evaluated. * * -O3: expression re-ordering to reduce expected cost. In an expression like * (-foo -and -bar), if both -foo and -bar are pure (no side effects), they can * be re-ordered to (-bar -and -foo). This is profitable if the expected cost * is lower for the re-ordered expression, for example if -foo is very slow or * -bar is likely to return false. * * -O4/-Ofast: aggressive optimizations that may affect correctness in corner * cases. The main effect is to use facts_when_impure to determine if any side- * effects are reachable at all, and skipping the traversal if not. */ #include #include #include #include static char *fake_and_arg = "-a"; static char *fake_or_arg = "-o"; static char *fake_not_arg = "!"; /** * A contrained integer range. */ struct range { /** The (inclusive) minimum value. */ long long min; /** The (inclusive) maximum value. */ long long max; }; /** Compute the minimum of two values. */ static long long min_value(long long a, long long b) { if (a < b) { return a; } else { return b; } } /** Compute the maximum of two values. */ static long long max_value(long long a, long long b) { if (a > b) { return a; } else { return b; } } /** Constrain the minimum of a range. */ static void constrain_min(struct range *range, long long value) { range->min = max_value(range->min, value); } /** Contrain the maximum of a range. */ static void constrain_max(struct range *range, long long value) { range->max = min_value(range->max, value); } /** Remove a single value from a range. */ static void range_remove(struct range *range, long long value) { if (range->min == value) { if (range->min == LLONG_MAX) { range->max = LLONG_MIN; } else { ++range->min; } } if (range->max == value) { if (range->max == LLONG_MIN) { range->min = LLONG_MAX; } else { --range->max; } } } /** Compute the union of two ranges. */ static void range_union(struct range *result, const struct range *lhs, const struct range *rhs) { result->min = min_value(lhs->min, rhs->min); result->max = max_value(lhs->max, rhs->max); } /** Check if a range contains no values. */ static bool range_impossible(const struct range *range) { return range->min > range->max; } /** Set a range to contain no values. */ static void set_range_impossible(struct range *range) { range->min = LLONG_MAX; range->max = LLONG_MIN; } /** * Types of ranges we track. */ enum range_type { /** Search tree depth. */ DEPTH_RANGE, /** Group ID. */ GID_RANGE, /** Inode number. */ INUM_RANGE, /** Hard link count. */ LINKS_RANGE, /** File size. */ SIZE_RANGE, /** User ID. */ UID_RANGE, /** The number of range_types. */ MAX_RANGE, }; /** * Data flow facts about an evaluation point. */ struct opt_facts { /** The value ranges we track. */ struct range ranges[MAX_RANGE]; /** Bitmask of possible file types. */ enum bftw_typeflag types; /** Bitmask of possible link target types. */ enum bftw_typeflag xtypes; }; /** Initialize some data flow facts. */ static void facts_init(struct opt_facts *facts) { for (int i = 0; i < MAX_RANGE; ++i) { struct range *range = facts->ranges + i; range->min = 0; // All ranges we currently track are non-negative range->max = LLONG_MAX; } facts->types = ~0; facts->xtypes = ~0; } /** Compute the union of two fact sets. */ static void facts_union(struct opt_facts *result, const struct opt_facts *lhs, const struct opt_facts *rhs) { for (int i = 0; i < MAX_RANGE; ++i) { range_union(result->ranges + i, lhs->ranges + i, rhs->ranges + i); } result->types = lhs->types | rhs->types; result->xtypes = lhs->xtypes | rhs->xtypes; } /** Determine whether a fact set is impossible. */ static bool facts_impossible(const struct opt_facts *facts) { for (int i = 0; i < MAX_RANGE; ++i) { if (range_impossible(facts->ranges + i)) { return true; } } if (!facts->types || !facts->xtypes) { return true; } return false; } /** Set some facts to be impossible. */ static void set_facts_impossible(struct opt_facts *facts) { for (int i = 0; i < MAX_RANGE; ++i) { set_range_impossible(facts->ranges + i); } facts->types = 0; facts->xtypes = 0; } /** * Optimizer state. */ struct opt_state { /** The command line we're optimizing. */ const struct cmdline *cmdline; /** Data flow facts before this expression is evaluated. */ struct opt_facts facts; /** Data flow facts after this expression returns true. */ struct opt_facts facts_when_true; /** Data flow facts after this expression returns false. */ struct opt_facts facts_when_false; /** Data flow facts before any side-effecting expressions are evaluated. */ struct opt_facts *facts_when_impure; }; /** Log an optimization. */ static void debug_opt(const struct opt_state *state, const char *format, ...) { if (!(state->cmdline->debug & DEBUG_OPT)) { return; } CFILE *cerr = state->cmdline->cerr; va_list args; va_start(args, format); for (const char *i = format; *i != '\0'; ++i) { if (*i == '%') { switch (*++i) { case 'd': fprintf(cerr->file, "%d", va_arg(args, int)); break; case 'e': dump_expr(cerr, va_arg(args, const struct expr *), false); break; case 'g': cfprintf(cerr, "${ylw}%g${rs}", va_arg(args, double)); break; default: assert(false); break; } } else { fputc(*i, stderr); } } va_end(args); } /** Extract a child expression, freeing the outer expression. */ static struct expr *extract_child_expr(struct expr *expr, struct expr **child) { struct expr *ret = *child; *child = NULL; free_expr(expr); return ret; } /** * Negate an expression. */ static struct expr *negate_expr(struct expr *rhs, char **argv) { if (rhs->eval == eval_not) { return extract_child_expr(rhs, &rhs->rhs); } struct expr *expr = new_expr(eval_not, 1, argv); if (!expr) { free_expr(rhs); return NULL; } expr->rhs = rhs; return expr; } static struct expr *optimize_not_expr(const struct opt_state *state, struct expr *expr); static struct expr *optimize_and_expr(const struct opt_state *state, struct expr *expr); static struct expr *optimize_or_expr(const struct opt_state *state, struct expr *expr); /** * Apply De Morgan's laws. */ static struct expr *de_morgan(const struct opt_state *state, struct expr *expr, char **argv) { debug_opt(state, "-O1: De Morgan's laws: %e ", expr); struct expr *parent = negate_expr(expr, argv); if (!parent) { return NULL; } bool has_parent = true; if (parent->eval != eval_not) { expr = parent; has_parent = false; } if (expr->eval == eval_and) { expr->eval = eval_or; expr->argv = &fake_or_arg; } else { assert(expr->eval == eval_or); expr->eval = eval_and; expr->argv = &fake_and_arg; } expr->lhs = negate_expr(expr->lhs, argv); expr->rhs = negate_expr(expr->rhs, argv); if (!expr->lhs || !expr->rhs) { free_expr(parent); return NULL; } debug_opt(state, "<==> %e\n", parent); if (expr->lhs->eval == eval_not) { expr->lhs = optimize_not_expr(state, expr->lhs); } if (expr->rhs->eval == eval_not) { expr->rhs = optimize_not_expr(state, expr->rhs); } if (!expr->lhs || !expr->rhs) { free_expr(parent); return NULL; } if (expr->eval == eval_and) { expr = optimize_and_expr(state, expr); } else { expr = optimize_or_expr(state, expr); } if (!expr) { if (has_parent) { parent->rhs = NULL; free_expr(parent); } return NULL; } if (has_parent) { parent = optimize_not_expr(state, parent); } return parent; } /** Optimize an expression recursively. */ static struct expr *optimize_expr_recursive(struct opt_state *state, struct expr *expr); /** * Optimize a negation. */ static struct expr *optimize_not_expr(const struct opt_state *state, struct expr *expr) { assert(expr->eval == eval_not); struct expr *rhs = expr->rhs; int optlevel = state->cmdline->optlevel; if (optlevel >= 1) { if (rhs == &expr_true) { debug_opt(state, "-O1: constant propagation: %e <==> %e\n", expr, &expr_false); free_expr(expr); return &expr_false; } else if (rhs == &expr_false) { debug_opt(state, "-O1: constant propagation: %e <==> %e\n", expr, &expr_true); free_expr(expr); return &expr_true; } else if (rhs->eval == eval_not) { debug_opt(state, "-O1: double negation: %e <==> %e\n", expr, rhs->rhs); return extract_child_expr(expr, &rhs->rhs); } else if (expr_never_returns(rhs)) { debug_opt(state, "-O1: reachability: %e <==> %e\n", expr, rhs); return extract_child_expr(expr, &expr->rhs); } else if ((rhs->eval == eval_and || rhs->eval == eval_or) && (rhs->lhs->eval == eval_not || rhs->rhs->eval == eval_not)) { return de_morgan(state, expr, expr->argv); } } expr->pure = rhs->pure; expr->always_true = rhs->always_false; expr->always_false = rhs->always_true; expr->cost = rhs->cost; expr->probability = 1.0 - rhs->probability; return expr; } /** Optimize a negation recursively. */ static struct expr *optimize_not_expr_recursive(struct opt_state *state, struct expr *expr) { struct opt_state rhs_state = *state; expr->rhs = optimize_expr_recursive(&rhs_state, expr->rhs); if (!expr->rhs) { goto fail; } state->facts_when_true = rhs_state.facts_when_false; state->facts_when_false = rhs_state.facts_when_true; return optimize_not_expr(state, expr); fail: free_expr(expr); return NULL; } /** Optimize a conjunction. */ static struct expr *optimize_and_expr(const struct opt_state *state, struct expr *expr) { assert(expr->eval == eval_and); struct expr *lhs = expr->lhs; struct expr *rhs = expr->rhs; int optlevel = state->cmdline->optlevel; if (optlevel >= 1) { if (lhs == &expr_true) { debug_opt(state, "-O1: conjunction elimination: %e <==> %e\n", expr, rhs); return extract_child_expr(expr, &expr->rhs); } else if (rhs == &expr_true) { debug_opt(state, "-O1: conjunction elimination: %e <==> %e\n", expr, lhs); return extract_child_expr(expr, &expr->lhs); } else if (lhs->always_false) { debug_opt(state, "-O1: short-circuit: %e <==> %e\n", expr, lhs); return extract_child_expr(expr, &expr->lhs); } else if (lhs->always_true && rhs == &expr_false) { debug_opt(state, "-O1: strength reduction: %e <==> ", expr); struct expr *ret = extract_child_expr(expr, &expr->lhs); ret = negate_expr(ret, &fake_not_arg); if (ret) { debug_opt(state, "%e\n", ret); } return ret; } else if (optlevel >= 2 && lhs->pure && rhs == &expr_false) { debug_opt(state, "-O2: purity: %e <==> %e\n", expr, rhs); return extract_child_expr(expr, &expr->rhs); } else if (lhs->eval == eval_not && rhs->eval == eval_not) { return de_morgan(state, expr, expr->lhs->argv); } } expr->pure = lhs->pure && rhs->pure; expr->always_true = lhs->always_true && rhs->always_true; expr->always_false = lhs->always_false || rhs->always_false; expr->cost = lhs->cost + lhs->probability*rhs->cost; expr->probability = lhs->probability*rhs->probability; return expr; } /** Optimize a conjunction recursively. */ static struct expr *optimize_and_expr_recursive(struct opt_state *state, struct expr *expr) { struct opt_state lhs_state = *state; expr->lhs = optimize_expr_recursive(&lhs_state, expr->lhs); if (!expr->lhs) { goto fail; } struct opt_state rhs_state = *state; rhs_state.facts = lhs_state.facts_when_true; expr->rhs = optimize_expr_recursive(&rhs_state, expr->rhs); if (!expr->rhs) { goto fail; } state->facts_when_true = rhs_state.facts_when_true; facts_union(&state->facts_when_false, &lhs_state.facts_when_false, &rhs_state.facts_when_false); return optimize_and_expr(state, expr); fail: free_expr(expr); return NULL; } /** Optimize a disjunction. */ static struct expr *optimize_or_expr(const struct opt_state *state, struct expr *expr) { assert(expr->eval == eval_or); struct expr *lhs = expr->lhs; struct expr *rhs = expr->rhs; int optlevel = state->cmdline->optlevel; if (optlevel >= 1) { if (lhs->always_true) { debug_opt(state, "-O1: short-circuit: %e <==> %e\n", expr, lhs); return extract_child_expr(expr, &expr->lhs); } else if (lhs == &expr_false) { debug_opt(state, "-O1: disjunctive syllogism: %e <==> %e\n", expr, rhs); return extract_child_expr(expr, &expr->rhs); } else if (rhs == &expr_false) { debug_opt(state, "-O1: disjunctive syllogism: %e <==> %e\n", expr, lhs); return extract_child_expr(expr, &expr->lhs); } else if (lhs->always_false && rhs == &expr_true) { debug_opt(state, "-O1: strength reduction: %e <==> ", expr); struct expr *ret = extract_child_expr(expr, &expr->lhs); ret = negate_expr(ret, &fake_not_arg); if (ret) { debug_opt(state, "%e\n", ret); } return ret; } else if (optlevel >= 2 && lhs->pure && rhs == &expr_true) { debug_opt(state, "-O2: purity: %e <==> %e\n", expr, rhs); return extract_child_expr(expr, &expr->rhs); } else if (lhs->eval == eval_not && rhs->eval == eval_not) { return de_morgan(state, expr, expr->lhs->argv); } } expr->pure = lhs->pure && rhs->pure; expr->always_true = lhs->always_true || rhs->always_true; expr->always_false = lhs->always_false && rhs->always_false; expr->cost = lhs->cost + (1 - lhs->probability)*rhs->cost; expr->probability = lhs->probability + rhs->probability - lhs->probability*rhs->probability; return expr; } /** Optimize a disjunction recursively. */ static struct expr *optimize_or_expr_recursive(struct opt_state *state, struct expr *expr) { struct opt_state lhs_state = *state; expr->lhs = optimize_expr_recursive(&lhs_state, expr->lhs); if (!expr->lhs) { goto fail; } struct opt_state rhs_state = *state; rhs_state.facts = lhs_state.facts_when_false; expr->rhs = optimize_expr_recursive(&rhs_state, expr->rhs); if (!expr->rhs) { goto fail; } facts_union(&state->facts_when_true, &lhs_state.facts_when_true, &rhs_state.facts_when_true); state->facts_when_false = rhs_state.facts_when_false; return optimize_or_expr(state, expr); fail: free_expr(expr); return NULL; } /** Optimize an expression in an ignored-result context. */ static struct expr *ignore_result(const struct opt_state *state, struct expr *expr) { int optlevel = state->cmdline->optlevel; if (optlevel >= 1) { while (true) { if (expr->eval == eval_not) { debug_opt(state, "-O1: ignored result: %e --> %e\n", expr, expr->rhs); expr = extract_child_expr(expr, &expr->rhs); } else if (optlevel >= 2 && (expr->eval == eval_and || expr->eval == eval_or || expr->eval == eval_comma) && expr->rhs->pure) { debug_opt(state, "-O2: ignored result: %e --> %e\n", expr, expr->lhs); expr = extract_child_expr(expr, &expr->lhs); } else { break; } } if (optlevel >= 2 && expr->pure && expr != &expr_false) { debug_opt(state, "-O2: ignored result: %e --> %e\n", expr, &expr_false); free_expr(expr); expr = &expr_false; } } return expr; } /** Optimize a comma expression. */ static struct expr *optimize_comma_expr(const struct opt_state *state, struct expr *expr) { assert(expr->eval == eval_comma); struct expr *lhs = expr->lhs; struct expr *rhs = expr->rhs; int optlevel = state->cmdline->optlevel; if (optlevel >= 1) { lhs = expr->lhs = ignore_result(state, lhs); if (expr_never_returns(lhs)) { debug_opt(state, "-O1: reachability: %e <==> %e\n", expr, lhs); return extract_child_expr(expr, &expr->lhs); } else if ((lhs->always_true && rhs == &expr_true) || (lhs->always_false && rhs == &expr_false)) { debug_opt(state, "-O1: redundancy elimination: %e <==> %e\n", expr, lhs); return extract_child_expr(expr, &expr->lhs); } else if (optlevel >= 2 && lhs->pure) { debug_opt(state, "-O2: purity: %e <==> %e\n", expr, rhs); return extract_child_expr(expr, &expr->rhs); } } expr->pure = lhs->pure && rhs->pure; expr->always_true = expr_never_returns(lhs) || rhs->always_true; expr->always_false = expr_never_returns(lhs) || rhs->always_false; expr->cost = lhs->cost + rhs->cost; expr->probability = rhs->probability; return expr; } /** Optimize a comma expression recursively. */ static struct expr *optimize_comma_expr_recursive(struct opt_state *state, struct expr *expr) { struct opt_state lhs_state = *state; expr->lhs = optimize_expr_recursive(&lhs_state, expr->lhs); if (!expr->lhs) { goto fail; } struct opt_state rhs_state = *state; facts_union(&rhs_state.facts, &lhs_state.facts_when_true, &lhs_state.facts_when_false); expr->rhs = optimize_expr_recursive(&rhs_state, expr->rhs); if (!expr->rhs) { goto fail; } return optimize_comma_expr(state, expr); fail: free_expr(expr); return NULL; } /** Infer data flow facts about an icmp-style ([+-]N) expression */ static void infer_icmp_facts(struct opt_state *state, const struct expr *expr, enum range_type type) { struct range *range_when_true = state->facts_when_true.ranges + type; struct range *range_when_false = state->facts_when_false.ranges + type; long long value = expr->idata; switch (expr->cmp_flag) { case CMP_EXACT: constrain_min(range_when_true, value); constrain_max(range_when_true, value); range_remove(range_when_false, value); break; case CMP_LESS: constrain_min(range_when_false, value); constrain_max(range_when_true, value); range_remove(range_when_true, value); break; case CMP_GREATER: constrain_max(range_when_false, value); constrain_min(range_when_true, value); range_remove(range_when_true, value); break; } } /** Infer data flow facts about a -samefile expression. */ static void infer_samefile_facts(struct opt_state *state, const struct expr *expr) { struct range *range_when_true = state->facts_when_true.ranges + INUM_RANGE; constrain_min(range_when_true, expr->ino); constrain_max(range_when_true, expr->ino); } /** Infer data flow facts about a -type expression. */ static void infer_type_facts(struct opt_state *state, const struct expr *expr) { state->facts_when_true.types &= expr->idata; state->facts_when_false.types &= ~expr->idata; } /** Infer data flow facts about an -xtype expression. */ static void infer_xtype_facts(struct opt_state *state, const struct expr *expr) { state->facts_when_true.xtypes &= expr->idata; state->facts_when_false.xtypes &= ~expr->idata; } static struct expr *optimize_expr_recursive(struct opt_state *state, struct expr *expr) { state->facts_when_true = state->facts; state->facts_when_false = state->facts; if (expr->eval == eval_depth) { infer_icmp_facts(state, expr, DEPTH_RANGE); } else if (expr->eval == eval_gid) { infer_icmp_facts(state, expr, GID_RANGE); } else if (expr->eval == eval_inum) { infer_icmp_facts(state, expr, INUM_RANGE); } else if (expr->eval == eval_links) { infer_icmp_facts(state, expr, LINKS_RANGE); } else if (expr->eval == eval_samefile) { infer_samefile_facts(state, expr); } else if (expr->eval == eval_size) { infer_icmp_facts(state, expr, SIZE_RANGE); } else if (expr->eval == eval_type) { infer_type_facts(state, expr); } else if (expr->eval == eval_uid) { infer_icmp_facts(state, expr, UID_RANGE); } else if (expr->eval == eval_xtype) { infer_xtype_facts(state, expr); } else if (expr->eval == eval_not) { expr = optimize_not_expr_recursive(state, expr); } else if (expr->eval == eval_and) { expr = optimize_and_expr_recursive(state, expr); } else if (expr->eval == eval_or) { expr = optimize_or_expr_recursive(state, expr); } else if (expr->eval == eval_comma) { expr = optimize_comma_expr_recursive(state, expr); } else if (!expr->pure) { facts_union(state->facts_when_impure, state->facts_when_impure, &state->facts); } if (!expr) { goto done; } struct expr *lhs = expr->lhs; struct expr *rhs = expr->rhs; if (rhs) { expr->persistent_fds = rhs->persistent_fds; expr->ephemeral_fds = rhs->ephemeral_fds; } if (lhs) { expr->persistent_fds += lhs->persistent_fds; if (lhs->ephemeral_fds > expr->ephemeral_fds) { expr->ephemeral_fds = lhs->ephemeral_fds; } } if (expr->always_true) { set_facts_impossible(&state->facts_when_false); } if (expr->always_false) { set_facts_impossible(&state->facts_when_true); } if (state->cmdline->optlevel < 2 || expr == &expr_true || expr == &expr_false) { goto done; } if (facts_impossible(&state->facts_when_true)) { if (expr->pure) { debug_opt(state, "-O2: data flow: %e --> %e\n", expr, &expr_false); free_expr(expr); expr = &expr_false; } else { expr->always_false = true; expr->probability = 0.0; } } else if (facts_impossible(&state->facts_when_false)) { if (expr->pure) { debug_opt(state, "-O2: data flow: %e --> %e\n", expr, &expr_true); free_expr(expr); expr = &expr_true; } else { expr->always_true = true; expr->probability = 1.0; } } done: return expr; } /** Swap the children of a binary expression if it would reduce the cost. */ static bool reorder_expr(const struct opt_state *state, struct expr *expr, double swapped_cost) { if (swapped_cost < expr->cost) { debug_opt(state, "-O3: cost: %e", expr); struct expr *lhs = expr->lhs; expr->lhs = expr->rhs; expr->rhs = lhs; debug_opt(state, " <==> %e (~%g --> ~%g)\n", expr, expr->cost, swapped_cost); expr->cost = swapped_cost; return true; } else { return false; } } /** * Recursively reorder sub-expressions to reduce the overall cost. * * @param expr * The expression to optimize. * @return * Whether any subexpression was reordered. */ static bool reorder_expr_recursive(const struct opt_state *state, struct expr *expr) { bool ret = false; struct expr *lhs = expr->lhs; struct expr *rhs = expr->rhs; if (lhs) { ret |= reorder_expr_recursive(state, lhs); } if (rhs) { ret |= reorder_expr_recursive(state, rhs); } if (expr->eval == eval_and || expr->eval == eval_or) { if (lhs->pure && rhs->pure) { double rhs_prob = expr->eval == eval_and ? rhs->probability : 1.0 - rhs->probability; double swapped_cost = rhs->cost + rhs_prob*lhs->cost; ret |= reorder_expr(state, expr, swapped_cost); } } return ret; } int optimize_cmdline(struct cmdline *cmdline) { struct opt_facts facts_when_impure; set_facts_impossible(&facts_when_impure); struct opt_state state = { .cmdline = cmdline, .facts_when_impure = &facts_when_impure, }; facts_init(&state.facts); struct range *depth = state.facts.ranges + DEPTH_RANGE; depth->min = cmdline->mindepth; depth->max = cmdline->maxdepth; int optlevel = cmdline->optlevel; cmdline->expr = optimize_expr_recursive(&state, cmdline->expr); if (!cmdline->expr) { return -1; } if (optlevel >= 3 && reorder_expr_recursive(&state, cmdline->expr)) { // Re-do optimizations to account for the new ordering set_facts_impossible(&facts_when_impure); cmdline->expr = optimize_expr_recursive(&state, cmdline->expr); if (!cmdline->expr) { return -1; } } cmdline->expr = ignore_result(&state, cmdline->expr); const struct range *depth_when_impure = facts_when_impure.ranges + DEPTH_RANGE; long long mindepth = depth_when_impure->min; long long maxdepth = depth_when_impure->max; if (optlevel >= 2 && mindepth > cmdline->mindepth) { if (mindepth > INT_MAX) { mindepth = INT_MAX; } cmdline->mindepth = mindepth; debug_opt(&state, "-O2: data flow: mindepth --> %d\n", cmdline->mindepth); } if (optlevel >= 4 && maxdepth < cmdline->maxdepth) { if (maxdepth < INT_MIN) { maxdepth = INT_MIN; } cmdline->maxdepth = maxdepth; debug_opt(&state, "-O4: data flow: maxdepth --> %d\n", cmdline->maxdepth); } return 0; } /**************************************************************************** * bfs * * Copyright (C) 2015-2019 Tavian Barnes * * * * Permission to use, copy, modify, and/or distribute this software for any * * purpose with or without fee is hereby granted. * * * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * ****************************************************************************/ /** * The command line parser. Expressions are parsed by recursive descent, with a * grammar described in the comments of the parse_*() functions. The parser * also accepts flags and paths at any point in the expression, by treating * flags like always-true options, and skipping over paths wherever they appear. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Strings printed by -D tree for "fake" expressions static char *fake_false_arg = "-false"; static char *fake_print_arg = "-print"; static char *fake_true_arg = "-true"; // Cost estimation constants #define FAST_COST 40.0 #define STAT_COST 1000.0 #define PRINT_COST 20000.0 struct expr expr_true = { .eval = eval_true, .lhs = NULL, .rhs = NULL, .pure = true, .always_true = true, .cost = FAST_COST, .probability = 1.0, .argc = 1, .argv = &fake_true_arg, }; struct expr expr_false = { .eval = eval_false, .lhs = NULL, .rhs = NULL, .pure = true, .always_false = true, .cost = FAST_COST, .probability = 0.0, .argc = 1, .argv = &fake_false_arg, }; /** * Free an expression. */ void free_expr(struct expr *expr) { if (!expr || expr == &expr_true || expr == &expr_false) { return; } if (expr->regex) { regfree(expr->regex); free(expr->regex); } free_bfs_printf(expr->printf); free_bfs_exec(expr->execbuf); free_expr(expr->lhs); free_expr(expr->rhs); free(expr); } struct expr *new_expr(eval_fn *eval, size_t argc, char **argv) { struct expr *expr = malloc(sizeof(*expr)); if (!expr) { perror("malloc()"); return NULL; } expr->eval = eval; expr->lhs = NULL; expr->rhs = NULL; expr->pure = false; expr->always_true = false; expr->always_false = false; expr->cost = FAST_COST; expr->probability = 0.5; expr->evaluations = 0; expr->successes = 0; expr->elapsed.tv_sec = 0; expr->elapsed.tv_nsec = 0; expr->argc = argc; expr->argv = argv; expr->cfile = NULL; expr->regex = NULL; expr->execbuf = NULL; expr->printf = NULL; expr->persistent_fds = 0; expr->ephemeral_fds = 0; return expr; } /** * Create a new unary expression. */ static struct expr *new_unary_expr(eval_fn *eval, struct expr *rhs, char **argv) { struct expr *expr = new_expr(eval, 1, argv); if (!expr) { free_expr(rhs); return NULL; } expr->rhs = rhs; expr->persistent_fds = rhs->persistent_fds; expr->ephemeral_fds = rhs->ephemeral_fds; return expr; } /** * Create a new binary expression. */ static struct expr *new_binary_expr(eval_fn *eval, struct expr *lhs, struct expr *rhs, char **argv) { struct expr *expr = new_expr(eval, 1, argv); if (!expr) { free_expr(rhs); free_expr(lhs); return NULL; } expr->lhs = lhs; expr->rhs = rhs; expr->persistent_fds = lhs->persistent_fds + rhs->persistent_fds; if (lhs->ephemeral_fds > rhs->ephemeral_fds) { expr->ephemeral_fds = lhs->ephemeral_fds; } else { expr->ephemeral_fds = rhs->ephemeral_fds; } return expr; } /** * Check if an expression never returns. */ bool expr_never_returns(const struct expr *expr) { // Expressions that never return are vacuously both always true and always false return expr->always_true && expr->always_false; } /** * Set an expression to always return true. */ static void expr_set_always_true(struct expr *expr) { expr->always_true = true; expr->probability = 1.0; } /** * Set an expression to never return. */ static void expr_set_never_returns(struct expr *expr) { expr->always_true = expr->always_false = true; } /** * Dump the parsed expression tree, for debugging. */ void dump_expr(CFILE *cfile, const struct expr *expr, bool verbose) { fputs("(", cfile->file); if (expr->lhs || expr->rhs) { cfprintf(cfile, "${red}%s${rs}", expr->argv[0]); } else { cfprintf(cfile, "${blu}%s${rs}", expr->argv[0]); } for (size_t i = 1; i < expr->argc; ++i) { cfprintf(cfile, " ${bld}%s${rs}", expr->argv[i]); } if (verbose) { double rate = 0.0, time = 0.0; if (expr->evaluations) { rate = 100.0*expr->successes/expr->evaluations; time = (1.0e9*expr->elapsed.tv_sec + expr->elapsed.tv_nsec)/expr->evaluations; } cfprintf(cfile, " [${ylw}%zu${rs}/${ylw}%zu${rs}=${ylw}%g%%${rs}; ${ylw}%gns${rs}]", expr->successes, expr->evaluations, rate, time); } if (expr->lhs) { fputs(" ", cfile->file); dump_expr(cfile, expr->lhs, verbose); } if (expr->rhs) { fputs(" ", cfile->file); dump_expr(cfile, expr->rhs, verbose); } fputs(")", cfile->file); } /** * An open file for the command line. */ struct open_file { /** The file itself. */ CFILE *cfile; /** The path to the file (for diagnostics). */ const char *path; }; /** * Free the parsed command line. */ int free_cmdline(struct cmdline *cmdline) { int ret = 0; if (cmdline) { CFILE *cout = cmdline->cout; CFILE *cerr = cmdline->cerr; free_expr(cmdline->expr); free_bfs_mtab(cmdline->mtab); struct trie_leaf *leaf; while ((leaf = trie_first_leaf(&cmdline->open_files))) { struct open_file *ofile = leaf->value; if (cfclose(ofile->cfile) != 0) { if (cerr) { bfs_error(cmdline, "'%s': %m.\n", ofile->path); } ret = -1; } free(ofile); trie_remove(&cmdline->open_files, leaf); } trie_destroy(&cmdline->open_files); if (cout && fflush(cout->file) != 0) { if (cerr) { bfs_error(cmdline, "standard output: %m.\n"); } ret = -1; } cfclose(cout); cfclose(cerr); free_colors(cmdline->colors); free(cmdline->paths); free(cmdline->argv); free(cmdline); } return ret; } /** * Color use flags. */ enum use_color { COLOR_NEVER, COLOR_AUTO, COLOR_ALWAYS, }; /** * Ephemeral state for parsing the command line. */ struct parser_state { /** The command line being constructed. */ struct cmdline *cmdline; /** The command line arguments being parsed. */ char **argv; /** The name of this program. */ const char *command; /** The current regex flags to use. */ int regex_flags; /** Whether stdout is a terminal. */ bool stdout_tty; /** Whether this session is interactive (stdin and stderr are each a terminal). */ bool interactive; /** Whether -color or -nocolor has been passed. */ enum use_color use_color; /** Whether a -print action is implied. */ bool implicit_print; /** Whether warnings are enabled (see -warn, -nowarn). */ bool warn; /** Whether the expression has started. */ bool expr_started; /** Whether any non-option arguments have been encountered. */ bool non_option_seen; /** Whether an information option like -help or -version was passed. */ bool just_info; /** The last non-path argument. */ const char *last_arg; /** A "-depth"-type argument if any. */ const char *depth_arg; /** A "-prune"-type argument if any. */ const char *prune_arg; /** The current time. */ struct timespec now; }; /** * Possible token types. */ enum token_type { /** A flag. */ T_FLAG, /** A root path. */ T_PATH, /** An option. */ T_OPTION, /** A test. */ T_TEST, /** An action. */ T_ACTION, /** An operator. */ T_OPERATOR, }; /** * Print an error message during parsing. */ BFS_FORMATTER(2, 3) static void parse_error(const struct parser_state *state, const char *format, ...) { va_list args; va_start(args, format); bfs_verror(state->cmdline, format, args); va_end(args); } /** * Print a warning message during parsing. */ BFS_FORMATTER(2, 3) static void parse_warning(const struct parser_state *state, const char *format, ...) { va_list args; va_start(args, format); bfs_vwarning(state->cmdline, format, args); va_end(args); } /** * Fill in a "-print"-type expression. */ static void init_print_expr(struct parser_state *state, struct expr *expr) { expr_set_always_true(expr); expr->cost = PRINT_COST; expr->cfile = state->cmdline->cout; } /** * Open a file for an expression. */ static int expr_open(struct parser_state *state, struct expr *expr, const char *path) { int ret = -1; struct cmdline *cmdline = state->cmdline; CFILE *cfile = cfopen(path, state->use_color ? cmdline->colors : NULL); if (!cfile) { parse_error(state, "'%s': %m.\n", path); goto out; } struct bfs_stat sb; if (bfs_stat(fileno(cfile->file), NULL, 0, &sb) != 0) { parse_error(state, "'%s': %m.\n", path); goto out_close; } bfs_file_id id; bfs_stat_id(&sb, &id); struct trie_leaf *leaf = trie_insert_mem(&cmdline->open_files, id, sizeof(id)); if (leaf->value) { struct open_file *ofile = leaf->value; expr->cfile = ofile->cfile; ret = 0; goto out_close; } struct open_file *ofile = malloc(sizeof(*ofile)); if (!ofile) { perror("malloc()"); trie_remove(&cmdline->open_files, leaf); goto out_close; } ofile->cfile = cfile; ofile->path = path; leaf->value = ofile; ++cmdline->nopen_files; expr->cfile = cfile; ret = 0; goto out; out_close: cfclose(cfile); out: return ret; } /** * Invoke bfs_stat() on an argument. */ static int stat_arg(const struct parser_state *state, struct expr *expr, struct bfs_stat *sb) { const struct cmdline *cmdline = state->cmdline; bool follow = cmdline->flags & (BFTW_COMFOLLOW | BFTW_LOGICAL); enum bfs_stat_flag flags = follow ? BFS_STAT_TRYFOLLOW : BFS_STAT_NOFOLLOW; int ret = bfs_stat(AT_FDCWD, expr->sdata, flags, sb); if (ret != 0) { parse_error(state, "'%s': %m.\n", expr->sdata); } return ret; } /** * Parse the expression specified on the command line. */ static struct expr *parse_expr(struct parser_state *state); /** * Advance by a single token. */ static char **parser_advance(struct parser_state *state, enum token_type type, size_t argc) { if (type != T_FLAG && type != T_PATH) { state->expr_started = true; if (type != T_OPTION) { state->non_option_seen = true; } } if (type != T_PATH) { state->last_arg = *state->argv; } char **argv = state->argv; state->argv += argc; return argv; } /** * Parse a root path. */ static int parse_root(struct parser_state *state, const char *path) { struct cmdline *cmdline = state->cmdline; size_t i = cmdline->npaths; if ((i & (i + 1)) == 0) { const char **paths = realloc(cmdline->paths, 2*(i + 1)*sizeof(*paths)); if (!paths) { return -1; } cmdline->paths = paths; } cmdline->paths[i] = path; cmdline->npaths = i + 1; 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)) { parse_error(state, "'%s' is not a valid integer.\n", str); } return NULL; } /** * Parse an integer and a comparison flag. */ static const char *parse_icmp(const struct parser_state *state, const char *str, struct expr *expr, enum int_flags flags) { switch (str[0]) { case '-': expr->cmp_flag = CMP_LESS; ++str; break; case '+': expr->cmp_flag = CMP_GREATER; ++str; break; default: expr->cmp_flag = CMP_EXACT; break; } return parse_int(state, str, &expr->idata, flags | IF_LONG_LONG | IF_UNSIGNED); } /** * Check if a string could be an integer comparison. */ static bool looks_like_icmp(const char *str) { int i; // One +/- for the comparison flag, one for the sign for (i = 0; i < 2; ++i) { if (str[i] != '-' && str[i] != '+') { break; } } return str[i] >= '0' && str[i] <= '9'; } /** * Parse a single flag. */ static struct expr *parse_flag(struct parser_state *state, size_t argc) { parser_advance(state, T_FLAG, argc); return &expr_true; } /** * Parse a flag that doesn't take a value. */ static struct expr *parse_nullary_flag(struct parser_state *state) { return parse_flag(state, 1); } /** * Parse a flag that takes a single value. */ static struct expr *parse_unary_flag(struct parser_state *state) { return parse_flag(state, 2); } /** * Parse a single option. */ static struct expr *parse_option(struct parser_state *state, size_t argc) { const char *arg = *parser_advance(state, T_OPTION, argc); if (state->warn && state->non_option_seen) { parse_warning(state, "The '%s' option applies to the entire command line. For clarity, place\n" "it before any non-option arguments.\n\n", arg); } return &expr_true; } /** * Parse an option that doesn't take a value. */ static struct expr *parse_nullary_option(struct parser_state *state) { return parse_option(state, 1); } /** * Parse an option that takes a value. */ static struct expr *parse_unary_option(struct parser_state *state) { return parse_option(state, 2); } /** * Parse a single positional option. */ static struct expr *parse_positional_option(struct parser_state *state, size_t argc) { parser_advance(state, T_OPTION, argc); return &expr_true; } /** * Parse a positional option that doesn't take a value. */ static struct expr *parse_nullary_positional_option(struct parser_state *state) { return parse_positional_option(state, 1); } /** * Parse a positional option that takes a single value. */ static struct expr *parse_unary_positional_option(struct parser_state *state, const char **value) { const char *arg = state->argv[0]; *value = state->argv[1]; if (!*value) { parse_error(state, "%s needs a value.\n", arg); return NULL; } return parse_positional_option(state, 2); } /** * Parse a single test. */ static struct expr *parse_test(struct parser_state *state, eval_fn *eval, size_t argc) { char **argv = parser_advance(state, T_TEST, argc); struct expr *expr = new_expr(eval, argc, argv); if (expr) { expr->pure = true; } return expr; } /** * Parse a test that doesn't take a value. */ static struct expr *parse_nullary_test(struct parser_state *state, eval_fn *eval) { return parse_test(state, eval, 1); } /** * Parse a test that takes a value. */ static struct expr *parse_unary_test(struct parser_state *state, eval_fn *eval) { const char *arg = state->argv[0]; const char *value = state->argv[1]; if (!value) { parse_error(state, "%s needs a value.\n", arg); return NULL; } struct expr *expr = parse_test(state, eval, 2); if (expr) { expr->sdata = value; } return expr; } /** * Parse a single action. */ static struct expr *parse_action(struct parser_state *state, eval_fn *eval, size_t argc) { if (eval != eval_nohidden && eval != eval_prune && eval != eval_quit) { state->implicit_print = false; } char **argv = parser_advance(state, T_ACTION, argc); return new_expr(eval, argc, argv); } /** * Parse an action that takes no arguments. */ static struct expr *parse_nullary_action(struct parser_state *state, eval_fn *eval) { return parse_action(state, eval, 1); } /** * Parse an action that takes one argument. */ static struct expr *parse_unary_action(struct parser_state *state, eval_fn *eval) { const char *arg = state->argv[0]; const char *value = state->argv[1]; if (!value) { parse_error(state, "%s needs a value.\n", arg); return NULL; } struct expr *expr = parse_action(state, eval, 2); if (expr) { expr->sdata = value; } return expr; } /** * Parse a test expression with integer data and a comparison flag. */ static struct expr *parse_test_icmp(struct parser_state *state, eval_fn *eval) { struct expr *expr = parse_unary_test(state, eval); if (!expr) { return NULL; } if (!parse_icmp(state, expr->sdata, expr, 0)) { free_expr(expr); return NULL; } return expr; } /** * Print usage information for -D. */ static void debug_help(CFILE *cfile) { cfprintf(cfile, "Supported debug flags:\n\n"); cfprintf(cfile, " ${bld}help${rs}: This message.\n"); cfprintf(cfile, " ${bld}cost${rs}: Show cost estimates.\n"); cfprintf(cfile, " ${bld}exec${rs}: Print executed command details.\n"); cfprintf(cfile, " ${bld}opt${rs}: Print optimization details.\n"); cfprintf(cfile, " ${bld}rates${rs}: Print predicate success rates.\n"); cfprintf(cfile, " ${bld}search${rs}: Trace the filesystem traversal.\n"); cfprintf(cfile, " ${bld}stat${rs}: Trace all stat() calls.\n"); cfprintf(cfile, " ${bld}tree${rs}: Print the parse tree.\n"); cfprintf(cfile, " ${bld}all${rs}: All debug flags at once.\n"); } /** A named debug flag. */ struct debug_flag { enum debug_flags flag; const char *name; }; /** The table of debug flags. */ struct debug_flag debug_flags[] = { {DEBUG_ALL, "all"}, {DEBUG_COST, "cost"}, {DEBUG_EXEC, "exec"}, {DEBUG_OPT, "opt"}, {DEBUG_RATES, "rates"}, {DEBUG_SEARCH, "search"}, {DEBUG_STAT, "stat"}, {DEBUG_TREE, "tree"}, {0}, }; /** Check if a substring matches a debug flag. */ static bool parse_debug_flag(const char *flag, size_t len, const char *expected) { if (len == strlen(expected)) { return strncmp(flag, expected, len) == 0; } else { return false; } } /** * Parse -D FLAG. */ static struct expr *parse_debug(struct parser_state *state, int arg1, int arg2) { struct cmdline *cmdline = state->cmdline; const char *arg = state->argv[0]; const char *flags = state->argv[1]; if (!flags) { parse_error(state, "%s needs a flag.\n\n", arg); debug_help(cmdline->cerr); return NULL; } bool unrecognized = false; for (const char *flag = flags, *next; flag; flag = next) { size_t len = strcspn(flag, ","); if (flag[len]) { next = flag + len + 1; } else { next = NULL; } if (parse_debug_flag(flag, len, "help")) { debug_help(cmdline->cout); state->just_info = true; return NULL; } for (int i = 0; ; ++i) { const char *expected = debug_flags[i].name; if (!expected) { parse_warning(state, "Unrecognized debug flag '"); fwrite(flag, 1, len, stderr); fputs("'\n\n", stderr); unrecognized = true; break; } if (parse_debug_flag(flag, len, expected)) { cmdline->debug |= debug_flags[i].flag; break; } } } if (unrecognized) { debug_help(cmdline->cerr); cfprintf(cmdline->cerr, "\n"); } return parse_unary_flag(state); } /** * Parse -On. */ static struct expr *parse_optlevel(struct parser_state *state, int arg1, int arg2) { int *optlevel = &state->cmdline->optlevel; if (strcmp(state->argv[0], "-Ofast") == 0) { *optlevel = 4; } else if (!parse_int(state, state->argv[0] + 2, optlevel, IF_INT | IF_UNSIGNED)) { return NULL; } if (state->warn && *optlevel > 4) { parse_warning(state, "%s is the same as -O4.\n\n", state->argv[0]); } return parse_nullary_flag(state); } /** * Parse -[PHL], -(no)?follow. */ static struct expr *parse_follow(struct parser_state *state, int flags, int option) { struct cmdline *cmdline = state->cmdline; cmdline->flags &= ~(BFTW_COMFOLLOW | BFTW_LOGICAL); cmdline->flags |= flags; if (option) { return parse_nullary_positional_option(state); } else { return parse_nullary_flag(state); } } /** * Parse -X. */ static struct expr *parse_xargs_safe(struct parser_state *state, int arg1, int arg2) { state->cmdline->xargs_safe = true; return parse_nullary_flag(state); } /** * Parse -executable, -readable, -writable */ static struct expr *parse_access(struct parser_state *state, int flag, int arg2) { struct expr *expr = parse_nullary_test(state, eval_access); if (!expr) { return NULL; } expr->idata = flag; expr->cost = STAT_COST; switch (flag) { case R_OK: expr->probability = 0.99; break; case W_OK: expr->probability = 0.8; break; case X_OK: expr->probability = 0.2; break; } return expr; } /** * Parse -acl. */ static struct expr *parse_acl(struct parser_state *state, int flag, int arg2) { #if BFS_CAN_CHECK_ACL struct expr *expr = parse_nullary_test(state, eval_acl); if (expr) { expr->cost = STAT_COST; expr->probability = 0.00002; } return expr; #else parse_error(state, "%s is missing platform support.\n", state->argv[0]); return NULL; #endif } /** * Parse -[aBcm]?newer. */ static struct expr *parse_newer(struct parser_state *state, int field, int arg2) { struct expr *expr = parse_unary_test(state, eval_newer); if (!expr) { return NULL; } struct bfs_stat sb; if (stat_arg(state, expr, &sb) != 0) { goto fail; } expr->cost = STAT_COST; expr->reftime = sb.mtime; expr->stat_field = field; return expr; fail: free_expr(expr); return NULL; } /** * Parse -[aBcm]{min,time}. */ static struct expr *parse_time(struct parser_state *state, int field, int unit) { struct expr *expr = parse_test_icmp(state, eval_time); if (!expr) { return NULL; } expr->cost = STAT_COST; expr->reftime = state->now; expr->stat_field = field; expr->time_unit = unit; return expr; } /** * Parse -capable. */ static struct expr *parse_capable(struct parser_state *state, int flag, int arg2) { #if BFS_CAN_CHECK_CAPABILITIES struct expr *expr = parse_nullary_test(state, eval_capable); if (expr) { expr->cost = STAT_COST; expr->probability = 0.000002; } return expr; #else parse_error(state, "%s is missing platform support.\n", state->argv[0]); return NULL; #endif } /** * Parse -(no)?color. */ static struct expr *parse_color(struct parser_state *state, int color, int arg2) { struct cmdline *cmdline = state->cmdline; struct colors *colors = cmdline->colors; if (color) { state->use_color = COLOR_ALWAYS; cmdline->cout->colors = colors; cmdline->cerr->colors = colors; } else { state->use_color = COLOR_NEVER; cmdline->cout->colors = NULL; cmdline->cerr->colors = NULL; } return parse_nullary_option(state); } /** * Parse -{false,true}. */ static struct expr *parse_const(struct parser_state *state, int value, int arg2) { parser_advance(state, T_TEST, 1); return value ? &expr_true : &expr_false; } /** * Parse -daystart. */ static struct expr *parse_daystart(struct parser_state *state, int arg1, int arg2) { struct tm tm; if (xlocaltime(&state->now.tv_sec, &tm) != 0) { perror("xlocaltime()"); return NULL; } if (tm.tm_hour || tm.tm_min || tm.tm_sec || state->now.tv_nsec) { ++tm.tm_mday; } tm.tm_hour = 0; tm.tm_min = 0; tm.tm_sec = 0; time_t time = mktime(&tm); if (time == -1) { perror("mktime()"); return NULL; } state->now.tv_sec = time; state->now.tv_nsec = 0; return parse_nullary_positional_option(state); } /** * Parse -delete. */ static struct expr *parse_delete(struct parser_state *state, int arg1, int arg2) { state->cmdline->flags |= BFTW_DEPTH; state->depth_arg = state->argv[0]; return parse_nullary_action(state, eval_delete); } /** * Parse -d. */ static struct expr *parse_depth(struct parser_state *state, int arg1, int arg2) { state->cmdline->flags |= BFTW_DEPTH; state->depth_arg = state->argv[0]; return parse_nullary_flag(state); } /** * Parse -depth [N]. */ static struct expr *parse_depth_n(struct parser_state *state, int arg1, int arg2) { const char *arg = state->argv[1]; if (arg && looks_like_icmp(arg)) { return parse_test_icmp(state, eval_depth); } else { return parse_depth(state, arg1, arg2); } } /** * Parse -{min,max}depth N. */ static struct expr *parse_depth_limit(struct parser_state *state, int is_min, int arg2) { struct cmdline *cmdline = state->cmdline; const char *arg = state->argv[0]; const char *value = state->argv[1]; if (!value) { parse_error(state, "%s needs a value.\n", arg); return NULL; } int *depth = is_min ? &cmdline->mindepth : &cmdline->maxdepth; if (!parse_int(state, value, depth, IF_INT | IF_UNSIGNED)) { return NULL; } return parse_unary_option(state); } /** * Parse -empty. */ static struct expr *parse_empty(struct parser_state *state, int arg1, int arg2) { struct expr *expr = parse_nullary_test(state, eval_empty); if (!expr) { return NULL; } expr->cost = 2000.0; expr->probability = 0.01; if (state->cmdline->optlevel < 4) { // Since -empty attempts to open and read directories, it may // have side effects such as reporting permission errors, and // thus shouldn't be re-ordered without aggressive optimizations expr->pure = false; } expr->ephemeral_fds = 1; return expr; } /** * Parse -exec(dir)?/-ok(dir)?. */ static struct expr *parse_exec(struct parser_state *state, int flags, int arg2) { struct bfs_exec *execbuf = parse_bfs_exec(state->argv, flags, state->cmdline); if (!execbuf) { return NULL; } struct expr *expr = parse_action(state, eval_exec, execbuf->tmpl_argc + 2); if (!expr) { free_bfs_exec(execbuf); return NULL; } expr->execbuf = execbuf; if (execbuf->flags & BFS_EXEC_MULTI) { expr_set_always_true(expr); } else { expr->cost = 1000000.0; } expr->ephemeral_fds = 2; if (execbuf->flags & BFS_EXEC_CHDIR) { if (execbuf->flags & BFS_EXEC_MULTI) { expr->persistent_fds = 1; } else { ++expr->ephemeral_fds; } } return expr; } /** * Parse -exit [STATUS]. */ static struct expr *parse_exit(struct parser_state *state, int arg1, int arg2) { size_t argc = 1; const char *value = state->argv[1]; int status = EXIT_SUCCESS; if (value && parse_int(state, value, &status, IF_INT | IF_UNSIGNED | IF_QUIET)) { argc = 2; } struct expr *expr = parse_action(state, eval_exit, argc); if (expr) { expr_set_never_returns(expr); expr->idata = status; } return expr; } /** * Parse -f PATH. */ static struct expr *parse_f(struct parser_state *state, int arg1, int arg2) { parser_advance(state, T_FLAG, 1); const char *path = state->argv[0]; if (!path) { parse_error(state, "-f requires a path.\n"); return NULL; } if (parse_root(state, path) != 0) { return NULL; } parser_advance(state, T_PATH, 1); return &expr_true; } /** * Parse -fls FILE. */ static struct expr *parse_fls(struct parser_state *state, int arg1, int arg2) { struct expr *expr = parse_unary_action(state, eval_fls); if (expr) { expr_set_always_true(expr); expr->cost = PRINT_COST; if (expr_open(state, expr, expr->sdata) != 0) { goto fail; } expr->reftime = state->now; } return expr; fail: free_expr(expr); return NULL; } /** * Parse -fprint FILE. */ static struct expr *parse_fprint(struct parser_state *state, int arg1, int arg2) { struct expr *expr = parse_unary_action(state, eval_fprint); if (expr) { expr_set_always_true(expr); expr->cost = PRINT_COST; if (expr_open(state, expr, expr->sdata) != 0) { goto fail; } } return expr; fail: free_expr(expr); return NULL; } /** * Parse -fprint0 FILE. */ static struct expr *parse_fprint0(struct parser_state *state, int arg1, int arg2) { struct expr *expr = parse_unary_action(state, eval_fprint0); if (expr) { expr_set_always_true(expr); expr->cost = PRINT_COST; if (expr_open(state, expr, expr->sdata) != 0) { goto fail; } } return expr; fail: free_expr(expr); return NULL; } /** * Parse -fprintf FILE FORMAT. */ static struct expr *parse_fprintf(struct parser_state *state, int arg1, int arg2) { const char *arg = state->argv[0]; const char *file = state->argv[1]; if (!file) { parse_error(state, "%s needs a file.\n", arg); return NULL; } const char *format = state->argv[2]; if (!format) { parse_error(state, "%s needs a format string.\n", arg); return NULL; } struct expr *expr = parse_action(state, eval_fprintf, 3); if (!expr) { return NULL; } expr_set_always_true(expr); expr->cost = PRINT_COST; if (expr_open(state, expr, file) != 0) { goto fail; } expr->printf = parse_bfs_printf(format, state->cmdline); if (!expr->printf) { goto fail; } return expr; fail: free_expr(expr); return NULL; } /** * Parse -fstype TYPE. */ static struct expr *parse_fstype(struct parser_state *state, int arg1, int arg2) { struct cmdline *cmdline = state->cmdline; if (!cmdline->mtab) { parse_error(state, "Couldn't parse the mount table: %s.\n", strerror(cmdline->mtab_error)); return NULL; } struct expr *expr = parse_unary_test(state, eval_fstype); if (expr) { expr->cost = STAT_COST; } return expr; } /** * Parse -gid/-group. */ static struct expr *parse_group(struct parser_state *state, int arg1, int arg2) { const char *arg = state->argv[0]; struct expr *expr = parse_unary_test(state, eval_gid); if (!expr) { return NULL; } struct group *grp = getgrnam(expr->sdata); if (grp) { expr->idata = grp->gr_gid; expr->cmp_flag = CMP_EXACT; } else if (looks_like_icmp(expr->sdata)) { if (!parse_icmp(state, expr->sdata, expr, 0)) { goto fail; } } else { parse_error(state, "%s %s: No such group.\n", arg, expr->sdata); goto fail; } expr->cost = STAT_COST; return expr; fail: free_expr(expr); return NULL; } /** * Parse -unique. */ static struct expr *parse_unique(struct parser_state *state, int arg1, int arg2) { state->cmdline->unique = true; return parse_nullary_option(state); } /** * Parse -used N. */ static struct expr *parse_used(struct parser_state *state, int arg1, int arg2) { struct expr *expr = parse_test_icmp(state, eval_used); if (expr) { expr->cost = STAT_COST; } return expr; } /** * Parse -uid/-user. */ static struct expr *parse_user(struct parser_state *state, int arg1, int arg2) { const char *arg = state->argv[0]; struct expr *expr = parse_unary_test(state, eval_uid); if (!expr) { return NULL; } struct passwd *pwd = getpwnam(expr->sdata); if (pwd) { expr->idata = pwd->pw_uid; expr->cmp_flag = CMP_EXACT; } else if (looks_like_icmp(expr->sdata)) { if (!parse_icmp(state, expr->sdata, expr, 0)) { goto fail; } } else { parse_error(state, "%s %s: No such user.\n", arg, expr->sdata); goto fail; } expr->cost = STAT_COST; return expr; fail: free_expr(expr); return NULL; } /** * Parse -hidden. */ static struct expr *parse_hidden(struct parser_state *state, int arg1, int arg2) { struct expr *expr = parse_nullary_test(state, eval_hidden); if (expr) { expr->probability = 0.01; } return expr; } /** * Parse -(no)?ignore_readdir_race. */ static struct expr *parse_ignore_races(struct parser_state *state, int ignore, int arg2) { state->cmdline->ignore_races = ignore; return parse_nullary_option(state); } /** * Parse -inum N. */ static struct expr *parse_inum(struct parser_state *state, int arg1, int arg2) { struct expr *expr = parse_test_icmp(state, eval_inum); if (expr) { expr->cost = STAT_COST; expr->probability = expr->cmp_flag == CMP_EXACT ? 0.01 : 0.50; } return expr; } /** * Parse -links N. */ static struct expr *parse_links(struct parser_state *state, int arg1, int arg2) { struct expr *expr = parse_test_icmp(state, eval_links); if (expr) { expr->cost = STAT_COST; expr->probability = expr_cmp(expr, 1) ? 0.99 : 0.01; } return expr; } /** * Parse -ls. */ static struct expr *parse_ls(struct parser_state *state, int arg1, int arg2) { struct expr *expr = parse_nullary_action(state, eval_fls); if (expr) { init_print_expr(state, expr); expr->reftime = state->now; } return expr; } /** * Parse -mount, -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 parse_error(state, "%s is missing platform support.\n", expr->argv[0]); free_expr(expr); return NULL; #endif } else { expr->idata = 0; } expr->cost = 400.0; if (strchr(expr->sdata, '*')) { expr->probability = 0.5; } else { expr->probability = 0.1; } return expr; } /** * Parse -i?name. */ static struct expr *parse_name(struct parser_state *state, int casefold, int arg2) { struct expr *expr = parse_unary_test(state, eval_name); return parse_fnmatch(state, expr, casefold); } /** * Parse -i?path, -i?wholename. */ static struct expr *parse_path(struct parser_state *state, int casefold, int arg2) { struct expr *expr = parse_unary_test(state, eval_path); return parse_fnmatch(state, expr, casefold); } /** * Parse -i?lname. */ static struct expr *parse_lname(struct parser_state *state, int casefold, int arg2) { struct expr *expr = parse_unary_test(state, eval_lname); return parse_fnmatch(state, expr, casefold); } /** Get the bfs_stat_field for X/Y in -newerXY */ static enum bfs_stat_field parse_newerxy_field(char c) { switch (c) { case 'a': return BFS_STAT_ATIME; case 'B': return BFS_STAT_BTIME; case 'c': return BFS_STAT_CTIME; case 'm': return BFS_STAT_MTIME; default: return 0; } } /** * Parse -newerXY. */ static struct expr *parse_newerxy(struct parser_state *state, int arg1, int arg2) { const char *arg = state->argv[0]; if (strlen(arg) != 8) { parse_error(state, "Expected -newerXY; found %s.\n", arg); return NULL; } struct expr *expr = parse_unary_test(state, eval_newer); if (!expr) { goto fail; } expr->stat_field = parse_newerxy_field(arg[6]); if (!expr->stat_field) { parse_error(state, "%s: For -newerXY, X should be 'a', 'c', 'm', or 'B'.\n", arg); goto fail; } if (arg[7] == 't') { parse_error(state, "%s: Explicit reference times ('t') are not supported.\n", arg); goto fail; } else { enum bfs_stat_field field = parse_newerxy_field(arg[7]); if (!field) { parse_error(state, "%s: For -newerXY, Y should be 'a', 'c', 'm', 'B', or 't'.\n", arg); goto fail; } struct bfs_stat sb; if (stat_arg(state, expr, &sb) != 0) { goto fail; } const struct timespec *reftime = bfs_stat_time(&sb, field); if (!reftime) { parse_error(state, "'%s': Couldn't get file %s.\n", expr->sdata, bfs_stat_field_name(field)); goto fail; } expr->reftime = *reftime; } expr->cost = STAT_COST; return expr; fail: free_expr(expr); return NULL; } /** * Parse -nogroup. */ static struct expr *parse_nogroup(struct parser_state *state, int arg1, int arg2) { struct expr *expr = parse_nullary_test(state, eval_nogroup); if (expr) { expr->cost = 9000.0; expr->probability = 0.01; expr->ephemeral_fds = 1; } return expr; } /** * Parse -nohidden. */ static struct expr *parse_nohidden(struct parser_state *state, int arg1, int arg2) { state->prune_arg = state->argv[0]; return parse_nullary_action(state, eval_nohidden); } /** * Parse -noleaf. */ static struct expr *parse_noleaf(struct parser_state *state, int arg1, int arg2) { if (state->warn) { parse_warning(state, "bfs does not apply the optimization that %s inhibits.\n\n", state->argv[0]); } return parse_nullary_option(state); } /** * Parse -nouser. */ static struct expr *parse_nouser(struct parser_state *state, int arg1, int arg2) { struct expr *expr = parse_nullary_test(state, eval_nouser); if (expr) { expr->cost = 9000.0; expr->probability = 0.01; expr->ephemeral_fds = 1; } return expr; } /** * Parse a permission mode like chmod(1). */ static int parse_mode(const struct parser_state *state, const char *mode, struct expr *expr) { if (mode[0] >= '0' && mode[0] <= '9') { unsigned int parsed; if (!parse_int(state, mode, &parsed, 8 | IF_INT | IF_UNSIGNED | IF_QUIET)) { goto fail; } if (parsed > 07777) { goto fail; } expr->file_mode = parsed; expr->dir_mode = parsed; return 0; } expr->file_mode = 0; expr->dir_mode = 0; // Parse the same grammar as chmod(1), which looks like this: // // MODE : CLAUSE ["," CLAUSE]* // // CLAUSE : WHO* ACTION+ // // WHO : "u" | "g" | "o" | "a" // // ACTION : OP PERM* // | OP PERMCOPY // // OP : "+" | "-" | "=" // // PERM : "r" | "w" | "x" | "X" | "s" | "t" // // PERMCOPY : "u" | "g" | "o" // State machine state enum { MODE_CLAUSE, MODE_WHO, MODE_ACTION, MODE_ACTION_APPLY, MODE_OP, MODE_PERM, } mstate = MODE_CLAUSE; enum { MODE_PLUS, MODE_MINUS, MODE_EQUALS, } op; mode_t who; mode_t file_change; mode_t dir_change; const char *i = mode; while (true) { switch (mstate) { case MODE_CLAUSE: who = 0; mstate = MODE_WHO; // Fallthrough case MODE_WHO: switch (*i) { case 'u': who |= 0700; break; case 'g': who |= 0070; break; case 'o': who |= 0007; break; case 'a': who |= 0777; break; default: mstate = MODE_ACTION; continue; } break; case MODE_ACTION_APPLY: switch (op) { case MODE_EQUALS: expr->file_mode &= ~who; expr->dir_mode &= ~who; // Fallthrough case MODE_PLUS: expr->file_mode |= file_change; expr->dir_mode |= dir_change; break; case MODE_MINUS: expr->file_mode &= ~file_change; expr->dir_mode &= ~dir_change; break; } // Fallthrough case MODE_ACTION: if (who == 0) { who = 0777; } switch (*i) { case '+': op = MODE_PLUS; mstate = MODE_OP; break; case '-': op = MODE_MINUS; mstate = MODE_OP; break; case '=': op = MODE_EQUALS; mstate = MODE_OP; break; case ',': if (mstate == MODE_ACTION_APPLY) { mstate = MODE_CLAUSE; } else { goto fail; } break; case '\0': if (mstate == MODE_ACTION_APPLY) { goto done; } else { goto fail; } default: goto fail; } break; case MODE_OP: switch (*i) { case 'u': file_change = (expr->file_mode >> 6) & 07; dir_change = (expr->dir_mode >> 6) & 07; break; case 'g': file_change = (expr->file_mode >> 3) & 07; dir_change = (expr->dir_mode >> 3) & 07; break; case 'o': file_change = expr->file_mode & 07; dir_change = expr->dir_mode & 07; break; default: file_change = 0; dir_change = 0; mstate = MODE_PERM; continue; } file_change |= (file_change << 6) | (file_change << 3); file_change &= who; dir_change |= (dir_change << 6) | (dir_change << 3); dir_change &= who; mstate = MODE_ACTION_APPLY; break; case MODE_PERM: switch (*i) { case 'r': file_change |= who & 0444; dir_change |= who & 0444; break; case 'w': file_change |= who & 0222; dir_change |= who & 0222; break; case 'x': file_change |= who & 0111; // Fallthrough case 'X': dir_change |= who & 0111; break; case 's': if (who & 0700) { file_change |= S_ISUID; dir_change |= S_ISUID; } if (who & 0070) { file_change |= S_ISGID; dir_change |= S_ISGID; } break; case 't': file_change |= S_ISVTX; dir_change |= S_ISVTX; break; default: mstate = MODE_ACTION_APPLY; continue; } break; } ++i; } done: return 0; fail: parse_error(state, "'%s' is an invalid mode.\n", mode); return -1; } /** * Parse -perm MODE. */ static struct expr *parse_perm(struct parser_state *state, int field, int arg2) { struct expr *expr = parse_unary_test(state, eval_perm); if (!expr) { return NULL; } const char *mode = expr->sdata; switch (mode[0]) { case '-': expr->mode_cmp = MODE_ALL; ++mode; break; case '/': expr->mode_cmp = MODE_ANY; ++mode; break; case '+': if (mode[1] >= '0' && mode[1] <= '9') { expr->mode_cmp = MODE_ANY; ++mode; break; } // Fallthrough default: expr->mode_cmp = MODE_EXACT; break; } if (parse_mode(state, mode, expr) != 0) { goto fail; } expr->cost = STAT_COST; return expr; fail: free_expr(expr); return NULL; } /** * Parse -print. */ static struct expr *parse_print(struct parser_state *state, int arg1, int arg2) { struct expr *expr = parse_nullary_action(state, eval_fprint); if (expr) { init_print_expr(state, expr); } return expr; } /** * Parse -print0. */ static struct expr *parse_print0(struct parser_state *state, int arg1, int arg2) { struct expr *expr = parse_nullary_action(state, eval_fprint0); if (expr) { init_print_expr(state, expr); } return expr; } /** * Parse -printf FORMAT. */ static struct expr *parse_printf(struct parser_state *state, int arg1, int arg2) { struct expr *expr = parse_unary_action(state, eval_fprintf); if (!expr) { return NULL; } init_print_expr(state, expr); expr->printf = parse_bfs_printf(expr->sdata, state->cmdline); if (!expr->printf) { free_expr(expr); return NULL; } return expr; } /** * Parse -printx. */ static struct expr *parse_printx(struct parser_state *state, int arg1, int arg2) { struct expr *expr = parse_nullary_action(state, eval_fprintx); if (expr) { init_print_expr(state, expr); } return expr; } /** * Parse -prune. */ static struct expr *parse_prune(struct parser_state *state, int arg1, int arg2) { state->prune_arg = state->argv[0]; struct expr *expr = parse_nullary_action(state, eval_prune); if (expr) { expr_set_always_true(expr); } return expr; } /** * Parse -quit. */ static struct expr *parse_quit(struct parser_state *state, int arg1, int arg2) { struct expr *expr = parse_nullary_action(state, eval_quit); if (expr) { expr_set_never_returns(expr); } return expr; } /** * Parse -i?regex. */ static struct expr *parse_regex(struct parser_state *state, int flags, int arg2) { struct expr *expr = parse_unary_test(state, eval_regex); if (!expr) { goto fail; } expr->regex = malloc(sizeof(regex_t)); if (!expr->regex) { perror("malloc()"); goto fail; } int err = regcomp(expr->regex, expr->sdata, state->regex_flags | flags); if (err != 0) { char *str = xregerror(err, expr->regex); if (str) { parse_error(state, "%s %s: %s.\n", expr->argv[0], expr->argv[1], str); free(str); } else { perror("xregerror()"); } goto fail_regex; } return expr; fail_regex: free(expr->regex); expr->regex = NULL; fail: free_expr(expr); return NULL; } /** * Parse -E. */ static struct expr *parse_regex_extended(struct parser_state *state, int arg1, int arg2) { state->regex_flags = REG_EXTENDED; return parse_nullary_flag(state); } /** * Parse -regextype TYPE. */ static struct expr *parse_regextype(struct parser_state *state, int arg1, int arg2) { const char *type; struct expr *expr = parse_unary_positional_option(state, &type); if (!expr) { goto fail; } FILE *file = stderr; if (strcmp(type, "posix-basic") == 0) { state->regex_flags = 0; } else if (strcmp(type, "posix-extended") == 0) { state->regex_flags = REG_EXTENDED; } else if (strcmp(type, "help") == 0) { state->just_info = true; file = stdout; goto fail_list_types; } else { goto fail_bad_type; } return expr; fail_bad_type: parse_error(state, "Unsupported -regextype '%s'.\n\n", type); fail_list_types: fputs("Supported types are:\n\n", file); fputs(" posix-basic: POSIX basic regular expressions (BRE)\n", file); fputs(" posix-extended: POSIX extended regular expressions (ERE)\n", file); fail: free_expr(expr); return NULL; } /** * Parse -samefile FILE. */ static struct expr *parse_samefile(struct parser_state *state, int arg1, int arg2) { struct expr *expr = parse_unary_test(state, eval_samefile); if (!expr) { return NULL; } struct bfs_stat sb; if (stat_arg(state, expr, &sb) != 0) { free_expr(expr); return NULL; } expr->dev = sb.dev; expr->ino = sb.ino; expr->cost = STAT_COST; expr->probability = 0.01; return expr; } /** * Parse -S STRATEGY. */ static struct expr *parse_search_strategy(struct parser_state *state, int arg1, int arg2) { const char *flag = state->argv[0]; const char *arg = state->argv[1]; if (!arg) { parse_error(state, "%s needs an argument.\n", flag); return NULL; } struct cmdline *cmdline = state->cmdline; FILE *file = stderr; if (strcmp(arg, "bfs") == 0) { cmdline->strategy = BFTW_BFS; } else if (strcmp(arg, "dfs") == 0) { cmdline->strategy = BFTW_DFS; } else if (strcmp(arg, "ids") == 0) { cmdline->strategy = BFTW_IDS; } else if (strcmp(arg, "help") == 0) { state->just_info = true; file = stdout; goto fail_list_strategies; } else { goto fail_bad_strategy; } return parse_unary_flag(state); fail_bad_strategy: parse_error(state, "Unrecognized search strategy '%s'.\n\n", arg); fail_list_strategies: fputs("Supported search strategies:\n\n", file); fputs(" bfs: breadth-first search\n", file); fputs(" dfs: depth-first search\n", file); fputs(" ids: iterative deepening search\n", file); return NULL; } /** * Parse -size N[cwbkMGTP]?. */ static struct expr *parse_size(struct parser_state *state, int arg1, int arg2) { struct expr *expr = parse_unary_test(state, eval_size); if (!expr) { return NULL; } const char *unit = parse_icmp(state, expr->sdata, expr, IF_PARTIAL_OK); if (!unit) { goto fail; } if (strlen(unit) > 1) { goto bad_unit; } switch (*unit) { case '\0': case 'b': expr->size_unit = SIZE_BLOCKS; break; case 'c': expr->size_unit = SIZE_BYTES; break; case 'w': expr->size_unit = SIZE_WORDS; break; case 'k': expr->size_unit = SIZE_KB; break; case 'M': expr->size_unit = SIZE_MB; break; case 'G': expr->size_unit = SIZE_GB; break; case 'T': expr->size_unit = SIZE_TB; break; case 'P': expr->size_unit = SIZE_PB; break; default: goto bad_unit; } expr->cost = STAT_COST; expr->probability = expr->cmp_flag == CMP_EXACT ? 0.01 : 0.50; return expr; bad_unit: parse_error(state, "%s %s: Expected a size unit (one of cwbkMGTP); found '%s'.\n", expr->argv[0], expr->argv[1], unit); fail: free_expr(expr); return NULL; } /** * Parse -sparse. */ static struct expr *parse_sparse(struct parser_state *state, int arg1, int arg2) { struct expr *expr = parse_nullary_test(state, eval_sparse); if (expr) { expr->cost = STAT_COST; } return expr; } /** * Parse -x?type [bcdpflsD]. */ static struct expr *parse_type(struct parser_state *state, int x, int arg2) { eval_fn *eval = x ? eval_xtype : eval_type; struct expr *expr = parse_unary_test(state, eval); if (!expr) { return NULL; } enum bftw_typeflag types = 0; double probability = 0.0; const char *c = expr->sdata; while (true) { enum bftw_typeflag type; double type_prob; switch (*c) { case 'b': type = BFTW_BLK; type_prob = 0.00000721183; break; case 'c': type = BFTW_CHR; type_prob = 0.0000499855; break; case 'd': type = BFTW_DIR; type_prob = 0.114475; break; case 'D': type = BFTW_DOOR; type_prob = 0.000001; break; case 'p': type = BFTW_FIFO; type_prob = 0.00000248684; break; case 'f': type = BFTW_REG; type_prob = 0.859772; break; case 'l': type = BFTW_LNK; type_prob = 0.0256816; break; case 's': type = BFTW_SOCK; type_prob = 0.0000116881; break; case 'w': type = BFTW_WHT; type_prob = 0.000001; break; case '\0': parse_error(state, "%s %s: Expected a type flag.\n", expr->argv[0], expr->argv[1]); goto fail; default: parse_error(state, "%s %s: Unknown type flag '%c' (expected one of [bcdpflsD]).\n", expr->argv[0], expr->argv[1], *c); goto fail; } if (!(types & type)) { types |= type; probability += type_prob; } ++c; if (*c == '\0') { break; } else if (*c == ',') { ++c; continue; } else { parse_error(state, "%s %s: Types must be comma-separated.\n", expr->argv[0], expr->argv[1]); goto fail; } } expr->idata = types; expr->probability = probability; if (x && state->cmdline->optlevel < 4) { // Since -xtype dereferences symbolic links, it may have side // effects such as reporting permission errors, and thus // shouldn't be re-ordered without aggressive optimizations expr->pure = false; } return expr; fail: free_expr(expr); return NULL; } /** * Parse -(no)?warn. */ static struct expr *parse_warn(struct parser_state *state, int warn, int arg2) { state->warn = warn; return parse_nullary_positional_option(state); } /** * Parse -xattr. */ static struct expr *parse_xattr(struct parser_state *state, int arg1, int arg2) { #if BFS_CAN_CHECK_XATTRS struct expr *expr = parse_nullary_test(state, eval_xattr); if (expr) { expr->cost = STAT_COST; expr->probability = 0.01; } return expr; #else parse_error(state, "%s is missing platform support.\n", state->argv[0]); return NULL; #endif } /** * Launch a pager for the help output. */ static CFILE *launch_pager(pid_t *pid, CFILE *cout) { char *pager = getenv("PAGER"); if (!pager) { pager = "more"; } int pipefd[2]; if (pipe(pipefd) != 0) { goto fail; } FILE *file = fdopen(pipefd[1], "w"); if (!file) { goto fail_pipe; } pipefd[1] = -1; CFILE *ret = cfdup(file, NULL); if (!ret) { goto fail_file; } file = NULL; ret->close = true; ret->colors = cout->colors; struct bfs_spawn ctx; if (bfs_spawn_init(&ctx) != 0) { goto fail_ret; } if (bfs_spawn_setflags(&ctx, BFS_SPAWN_USEPATH) != 0) { goto fail_ctx; } if (bfs_spawn_addclose(&ctx, fileno(ret->file)) != 0) { goto fail_ctx; } if (bfs_spawn_adddup2(&ctx, pipefd[0], STDIN_FILENO) != 0) { goto fail_ctx; } if (bfs_spawn_addclose(&ctx, pipefd[0]) != 0) { goto fail_ctx; } char *argv[] = { pager, NULL, }; extern char **environ; char **envp = environ; if (!getenv("LESS")) { size_t envc; for (envc = 0; environ[envc]; ++envc); ++envc; envp = malloc((envc + 1)*sizeof(*envp)); if (!envp) { goto fail_ctx; } memcpy(envp, environ, (envc - 1)*sizeof(*envp)); envp[envc - 1] = "LESS=FKRX"; envp[envc] = NULL; } *pid = bfs_spawn(pager, &ctx, argv, envp); if (*pid < 0) { goto fail_envp; } close(pipefd[0]); if (envp != environ) { free(envp); } bfs_spawn_destroy(&ctx); return ret; fail_envp: if (envp != environ) { free(envp); } fail_ctx: bfs_spawn_destroy(&ctx); fail_ret: cfclose(ret); fail_file: if (file) { fclose(file); } fail_pipe: if (pipefd[1] >= 0) { close(pipefd[1]); } if (pipefd[0] >= 0) { close(pipefd[0]); } fail: return cout; } /** * "Parse" -help. */ static struct expr *parse_help(struct parser_state *state, int arg1, int arg2) { CFILE *cout = state->cmdline->cout; pid_t pager = -1; if (state->stdout_tty) { cout = launch_pager(&pager, cout); } cfprintf(cout, "Usage: ${ex}%s${rs} [${cyn}flags${rs}...] [${mag}paths${rs}...] [${blu}expression${rs}...]\n\n", state->command); cfprintf(cout, "${ex}bfs${rs} is compatible with ${ex}find${rs}, with some extensions. " "${cyn}Flags${rs} (${cyn}-H${rs}/${cyn}-L${rs}/${cyn}-P${rs} etc.), ${mag}paths${rs},\n" "and ${blu}expressions${rs} may be freely mixed in any order.\n\n"); cfprintf(cout, "${bld}Flags:${rs}\n\n"); cfprintf(cout, " ${cyn}-H${rs}\n"); cfprintf(cout, " Follow symbolic links on the command line, but not while searching\n"); cfprintf(cout, " ${cyn}-L${rs}\n"); cfprintf(cout, " Follow all symbolic links\n"); cfprintf(cout, " ${cyn}-P${rs}\n"); cfprintf(cout, " Never follow symbolic links (the default)\n"); cfprintf(cout, " ${cyn}-E${rs}\n"); cfprintf(cout, " Use extended regular expressions (same as ${blu}-regextype${rs} ${bld}posix-extended${rs})\n"); cfprintf(cout, " ${cyn}-X${rs}\n"); cfprintf(cout, " Filter out files with non-${ex}xargs${rs}-safe names\n"); cfprintf(cout, " ${cyn}-d${rs}\n"); cfprintf(cout, " Search in post-order (same as ${blu}-depth${rs})\n"); cfprintf(cout, " ${cyn}-x${rs}\n"); cfprintf(cout, " Don't descend into other mount points (same as ${blu}-xdev${rs})\n"); cfprintf(cout, " ${cyn}-f${rs} ${mag}PATH${rs}\n"); cfprintf(cout, " Treat ${mag}PATH${rs} as a path to search (useful if begins with a dash)\n"); cfprintf(cout, " ${cyn}-D${rs} ${bld}FLAG${rs}\n"); cfprintf(cout, " Turn on a debugging flag (see ${cyn}-D${rs} ${bld}help${rs})\n"); cfprintf(cout, " ${cyn}-O${rs}${bld}N${rs}\n"); cfprintf(cout, " Enable optimization level ${bld}N${rs} (default: 3)\n"); cfprintf(cout, " ${cyn}-S${rs} ${bld}bfs${rs}|${bld}dfs${rs}|${bld}ids${rs}\n"); cfprintf(cout, " Use ${bld}b${rs}readth-${bld}f${rs}irst/${bld}d${rs}epth-${bld}f${rs}irst/${bld}i${rs}terative ${bld}d${rs}eepening ${bld}s${rs}earch (default: ${cyn}-S${rs} ${bld}bfs${rs})\n\n"); cfprintf(cout, "${bld}Operators:${rs}\n\n"); cfprintf(cout, " ${red}(${rs} ${blu}expression${rs} ${red})${rs}\n\n"); cfprintf(cout, " ${red}!${rs} ${blu}expression${rs}\n"); cfprintf(cout, " ${red}-not${rs} ${blu}expression${rs}\n\n"); cfprintf(cout, " ${blu}expression${rs} ${blu}expression${rs}\n"); cfprintf(cout, " ${blu}expression${rs} ${red}-a${rs} ${blu}expression${rs}\n"); cfprintf(cout, " ${blu}expression${rs} ${red}-and${rs} ${blu}expression${rs}\n\n"); cfprintf(cout, " ${blu}expression${rs} ${red}-o${rs} ${blu}expression${rs}\n"); cfprintf(cout, " ${blu}expression${rs} ${red}-or${rs} ${blu}expression${rs}\n\n"); cfprintf(cout, " ${blu}expression${rs} ${red},${rs} ${blu}expression${rs}\n\n"); cfprintf(cout, "${bld}Options:${rs}\n\n"); cfprintf(cout, " ${blu}-color${rs}\n"); cfprintf(cout, " ${blu}-nocolor${rs}\n"); cfprintf(cout, " Turn colors on or off (default: ${blu}-color${rs} if outputting to a terminal,\n"); cfprintf(cout, " ${blu}-nocolor${rs} otherwise)\n"); cfprintf(cout, " ${blu}-daystart${rs}\n"); cfprintf(cout, " Measure times relative to the start of today\n"); cfprintf(cout, " ${blu}-depth${rs}\n"); cfprintf(cout, " Search in post-order (descendents first)\n"); cfprintf(cout, " ${blu}-follow${rs}\n"); cfprintf(cout, " Follow all symbolic links (same as ${cyn}-L${rs})\n"); cfprintf(cout, " ${blu}-ignore_readdir_race${rs}\n"); cfprintf(cout, " ${blu}-noignore_readdir_race${rs}\n"); cfprintf(cout, " Whether to report an error if ${ex}bfs${rs} detects that the file tree is modified\n"); cfprintf(cout, " during the search (default: ${blu}-noignore_readdir_race${rs})\n"); cfprintf(cout, " ${blu}-maxdepth${rs} ${bld}N${rs}\n"); cfprintf(cout, " ${blu}-mindepth${rs} ${bld}N${rs}\n"); cfprintf(cout, " Ignore files deeper/shallower than ${bld}N${rs}\n"); cfprintf(cout, " ${blu}-mount${rs}\n"); cfprintf(cout, " ${blu}-xdev${rs}\n"); cfprintf(cout, " Don't descend into other mount points\n"); cfprintf(cout, " ${blu}-noleaf${rs}\n"); cfprintf(cout, " Ignored; for compatibility with GNU find\n"); cfprintf(cout, " ${blu}-regextype${rs} ${bld}TYPE${rs}\n"); cfprintf(cout, " Use ${bld}TYPE${rs}-flavored regexes (default: ${bld}posix-basic${rs}; see ${blu}-regextype${rs}" " ${bld}help${rs})\n"); cfprintf(cout, " ${blu}-unique${rs}\n"); cfprintf(cout, " Skip any files that have already been seen\n"); cfprintf(cout, " ${blu}-warn${rs}\n"); cfprintf(cout, " ${blu}-nowarn${rs}\n"); cfprintf(cout, " Turn on or off warnings about the command line\n\n"); cfprintf(cout, "${bld}Tests:${rs}\n\n"); #if BFS_CAN_CHECK_ACL cfprintf(cout, " ${blu}-acl${rs}\n"); cfprintf(cout, " Find files with a non-trivial Access Control List\n"); #endif cfprintf(cout, " ${blu}-${rs}[${blu}aBcm${rs}]${blu}min${rs} ${bld}[-+]N${rs}\n"); cfprintf(cout, " Find files ${blu}a${rs}ccessed/${blu}B${rs}irthed/${blu}c${rs}hanged/${blu}m${rs}odified ${bld}N${rs} minutes ago\n"); cfprintf(cout, " ${blu}-${rs}[${blu}aBcm${rs}]${blu}newer${rs} ${bld}FILE${rs}\n"); cfprintf(cout, " Find files ${blu}a${rs}ccessed/${blu}B${rs}irthed/${blu}c${rs}hanged/${blu}m${rs}odified more recently than ${bld}FILE${rs} was\n" " modified\n"); cfprintf(cout, " ${blu}-${rs}[${blu}aBcm${rs}]${blu}time${rs} ${bld}[-+]N${rs}\n"); cfprintf(cout, " Find files ${blu}a${rs}ccessed/${blu}B${rs}irthed/${blu}c${rs}hanged/${blu}m${rs}odified ${bld}N${rs} days ago\n"); #if BFS_CAN_CHECK_CAPABILITIES cfprintf(cout, " ${blu}-capable${rs}\n"); cfprintf(cout, " Find files with POSIX.1e capabilities set\n"); #endif cfprintf(cout, " ${blu}-depth${rs} ${bld}[-+]N${rs}\n"); cfprintf(cout, " Find files with depth ${bld}N${rs}\n"); cfprintf(cout, " ${blu}-empty${rs}\n"); cfprintf(cout, " Find empty files/directories\n"); cfprintf(cout, " ${blu}-executable${rs}\n"); cfprintf(cout, " ${blu}-readable${rs}\n"); cfprintf(cout, " ${blu}-writable${rs}\n"); cfprintf(cout, " Find files the current user can execute/read/write\n"); cfprintf(cout, " ${blu}-false${rs}\n"); cfprintf(cout, " ${blu}-true${rs}\n"); cfprintf(cout, " Always false/true\n"); cfprintf(cout, " ${blu}-fstype${rs} ${bld}TYPE${rs}\n"); cfprintf(cout, " Find files on file systems with the given ${bld}TYPE${rs}\n"); cfprintf(cout, " ${blu}-gid${rs} ${bld}[-+]N${rs}\n"); cfprintf(cout, " ${blu}-uid${rs} ${bld}[-+]N${rs}\n"); cfprintf(cout, " Find files owned by group/user ID ${bld}N${rs}\n"); cfprintf(cout, " ${blu}-group${rs} ${bld}NAME${rs}\n"); cfprintf(cout, " ${blu}-user${rs} ${bld}NAME${rs}\n"); cfprintf(cout, " Find files owned by the group/user ${bld}NAME${rs}\n"); cfprintf(cout, " ${blu}-hidden${rs}\n"); cfprintf(cout, " ${blu}-nohidden${rs}\n"); cfprintf(cout, " Find hidden files, or filter them out\n"); #ifdef FNM_CASEFOLD cfprintf(cout, " ${blu}-ilname${rs} ${bld}GLOB${rs}\n"); cfprintf(cout, " ${blu}-iname${rs} ${bld}GLOB${rs}\n"); cfprintf(cout, " ${blu}-ipath${rs} ${bld}GLOB${rs}\n"); cfprintf(cout, " ${blu}-iregex${rs} ${bld}REGEX${rs}\n"); cfprintf(cout, " ${blu}-iwholename${rs} ${bld}GLOB${rs}\n"); cfprintf(cout, " Case-insensitive versions of ${blu}-lname${rs}/${blu}-name${rs}/${blu}-path${rs}" "/${blu}-regex${rs}/${blu}-wholename${rs}\n"); #endif cfprintf(cout, " ${blu}-inum${rs} ${bld}[-+]N${rs}\n"); cfprintf(cout, " Find files with inode number ${bld}N${rs}\n"); cfprintf(cout, " ${blu}-links${rs} ${bld}[-+]N${rs}\n"); cfprintf(cout, " Find files with ${bld}N${rs} hard links\n"); cfprintf(cout, " ${blu}-lname${rs} ${bld}GLOB${rs}\n"); cfprintf(cout, " Find symbolic links whose target matches the ${bld}GLOB${rs}\n"); cfprintf(cout, " ${blu}-name${rs} ${bld}GLOB${rs}\n"); cfprintf(cout, " Find files whose name matches the ${bld}GLOB${rs}\n"); cfprintf(cout, " ${blu}-newer${rs} ${bld}FILE${rs}\n"); cfprintf(cout, " Find files newer than ${bld}FILE${rs}\n"); cfprintf(cout, " ${blu}-newer${rs}${bld}XY${rs} ${bld}REFERENCE${rs}\n"); cfprintf(cout, " Find files whose ${bld}X${rs} time is newer than the ${bld}Y${rs} time of" " ${bld}REFERENCE${rs}. ${bld}X${rs} and ${bld}Y${rs}\n"); cfprintf(cout, " can be any of [${bld}aBcm${rs}].\n"); cfprintf(cout, " ${blu}-nogroup${rs}\n"); cfprintf(cout, " ${blu}-nouser${rs}\n"); cfprintf(cout, " Find files owned by nonexistent groups/users\n"); cfprintf(cout, " ${blu}-path${rs} ${bld}GLOB${rs}\n"); cfprintf(cout, " ${blu}-wholename${rs} ${bld}GLOB${rs}\n"); cfprintf(cout, " Find files whose entire path matches the ${bld}GLOB${rs}\n"); cfprintf(cout, " ${blu}-perm${rs} ${bld}[-]MODE${rs}\n"); cfprintf(cout, " Find files with a matching mode\n"); cfprintf(cout, " ${blu}-regex${rs} ${bld}REGEX${rs}\n"); cfprintf(cout, " Find files whose entire path matches the regular expression ${bld}REGEX${rs}\n"); cfprintf(cout, " ${blu}-samefile${rs} ${bld}FILE${rs}\n"); cfprintf(cout, " Find hard links to ${bld}FILE${rs}\n"); cfprintf(cout, " ${blu}-size${rs} ${bld}[-+]N[cwbkMGTP]${rs}\n"); cfprintf(cout, " Find files with the given size, in 1-byte ${bld}c${rs}haracters, 2-byte ${bld}w${rs}ords,\n"); cfprintf(cout, " 512-byte ${bld}b${rs}locks (default), or ${bld}k${rs}iB/${bld}M${rs}iB/${bld}G${rs}iB/${bld}T${rs}iB/${bld}P${rs}iB\n"); cfprintf(cout, " ${blu}-sparse${rs}\n"); cfprintf(cout, " Find files that occupy fewer disk blocks than expected\n"); cfprintf(cout, " ${blu}-type${rs} ${bld}[bcdlpfswD]${rs}\n"); cfprintf(cout, " Find files of the given type\n"); cfprintf(cout, " ${blu}-used${rs} ${bld}[-+]N${rs}\n"); cfprintf(cout, " Find files last accessed ${bld}N${rs} days after they were changed\n"); #if BFS_CAN_CHECK_XATTRS cfprintf(cout, " ${blu}-xattr${rs}\n"); cfprintf(cout, " Find files with extended attributes\n"); #endif cfprintf(cout, " ${blu}-xtype${rs} ${bld}[bcdlpfswD]${rs}\n"); cfprintf(cout, " Find files of the given type, following links when ${blu}-type${rs} would not, and\n"); cfprintf(cout, " vice versa\n\n"); cfprintf(cout, "${bld}Actions:${rs}\n\n"); cfprintf(cout, " ${blu}-delete${rs}\n"); cfprintf(cout, " ${blu}-rm${rs}\n"); cfprintf(cout, " Delete any found files (implies ${blu}-depth${rs})\n"); cfprintf(cout, " ${blu}-exec${rs} ${bld}command ... {} ;${rs}\n"); cfprintf(cout, " Execute a command\n"); cfprintf(cout, " ${blu}-exec${rs} ${bld}command ... {} +${rs}\n"); cfprintf(cout, " Execute a command with multiple files at once\n"); cfprintf(cout, " ${blu}-ok${rs} ${bld}command ... {} ;${rs}\n"); cfprintf(cout, " Prompt the user whether to execute a command\n"); cfprintf(cout, " ${blu}-execdir${rs} ${bld}command ... {} ;${rs}\n"); cfprintf(cout, " ${blu}-execdir${rs} ${bld}command ... {} +${rs}\n"); cfprintf(cout, " ${blu}-okdir${rs} ${bld}command ... {} ;${rs}\n"); cfprintf(cout, " Like ${blu}-exec${rs}/${blu}-ok${rs}, but run the command in the same directory as the found\n"); cfprintf(cout, " file(s)\n"); cfprintf(cout, " ${blu}-exit${rs} [${bld}STATUS${rs}]\n"); cfprintf(cout, " Exit immediately with the given status (%d if unspecified)\n", EXIT_SUCCESS); cfprintf(cout, " ${blu}-fls${rs} ${bld}FILE${rs}\n"); cfprintf(cout, " ${blu}-fprint${rs} ${bld}FILE${rs}\n"); cfprintf(cout, " ${blu}-fprint0${rs} ${bld}FILE${rs}\n"); cfprintf(cout, " ${blu}-fprintf${rs} ${bld}FORMAT${rs} ${bld}FILE${rs}\n"); cfprintf(cout, " Like ${blu}-ls${rs}/${blu}-print${rs}/${blu}-print0${rs}/${blu}-printf${rs}, but write to ${bld}FILE${rs} instead of standard\n" " output\n"); cfprintf(cout, " ${blu}-ls${rs}\n"); cfprintf(cout, " List files like ${ex}ls${rs} ${bld}-dils${rs}\n"); cfprintf(cout, " ${blu}-nohidden${rs}\n"); cfprintf(cout, " Filter out hidden files and directories\n"); cfprintf(cout, " ${blu}-print${rs}\n"); cfprintf(cout, " Print the path to the found file\n"); cfprintf(cout, " ${blu}-print0${rs}\n"); cfprintf(cout, " Like ${blu}-print${rs}, but use the null character ('\\0') as a separator rather than\n"); cfprintf(cout, " newlines\n"); cfprintf(cout, " ${blu}-printf${rs} ${bld}FORMAT${rs}\n"); cfprintf(cout, " Print according to a format string (see ${ex}man${rs} ${bld}find${rs}). The additional format\n"); cfprintf(cout, " directives %%w and %%W${bld}k${rs} for printing file birth times are supported.\n"); cfprintf(cout, " ${blu}-printx${rs}\n"); cfprintf(cout, " Like ${blu}-print${rs}, but escape whitespace and quotation characters, to make the\n"); cfprintf(cout, " output safe for ${ex}xargs${rs}. Consider using ${blu}-print0${rs} and ${ex}xargs${rs} ${bld}-0${rs} instead.\n"); cfprintf(cout, " ${blu}-prune${rs}\n"); cfprintf(cout, " Don't descend into this directory\n"); cfprintf(cout, " ${blu}-quit${rs}\n"); cfprintf(cout, " Quit immediately\n"); cfprintf(cout, " ${blu}-version${rs}\n"); cfprintf(cout, " Print version information\n"); cfprintf(cout, " ${blu}-help${rs}\n"); cfprintf(cout, " Print this help message\n\n"); cfprintf(cout, "%s\n", BFS_HOMEPAGE); if (pager > 0) { cfclose(cout); waitpid(pager, NULL, 0); } state->just_info = true; return NULL; } /** * "Parse" -version. */ static struct expr *parse_version(struct parser_state *state, int arg1, int arg2) { cfprintf(state->cmdline->cout, "${ex}bfs${rs} ${bld}%s${rs}\n\n", BFS_VERSION); printf("%s\n", BFS_HOMEPAGE); state->just_info = true; return NULL; } typedef struct expr *parse_fn(struct parser_state *state, int arg1, int arg2); /** * An entry in the parse table for literals. */ struct table_entry { char *arg; parse_fn *parse; int arg1; int arg2; bool prefix; }; /** * The parse table for literals. */ static const struct table_entry parse_table[] = { {"--"}, {"--help", parse_help}, {"--version", parse_version}, {"-Bmin", parse_time, BFS_STAT_BTIME, MINUTES}, {"-Bnewer", parse_newer, BFS_STAT_BTIME}, {"-Btime", parse_time, BFS_STAT_BTIME, DAYS}, {"-D", parse_debug}, {"-E", parse_regex_extended}, {"-H", parse_follow, BFTW_COMFOLLOW, false}, {"-L", parse_follow, BFTW_LOGICAL, false}, {"-O", parse_optlevel, 0, 0, true}, {"-P", parse_follow, 0, false}, {"-S", parse_search_strategy}, {"-X", parse_xargs_safe}, {"-a"}, {"-acl", parse_acl}, {"-amin", parse_time, BFS_STAT_ATIME, MINUTES}, {"-and"}, {"-anewer", parse_newer, BFS_STAT_ATIME}, {"-atime", parse_time, BFS_STAT_ATIME, DAYS}, {"-capable", parse_capable}, {"-cmin", parse_time, BFS_STAT_CTIME, MINUTES}, {"-cnewer", parse_newer, BFS_STAT_CTIME}, {"-color", parse_color, true}, {"-ctime", parse_time, BFS_STAT_CTIME, DAYS}, {"-d", parse_depth}, {"-daystart", parse_daystart}, {"-delete", parse_delete}, {"-depth", parse_depth_n}, {"-empty", parse_empty}, {"-exec", parse_exec, 0}, {"-execdir", parse_exec, BFS_EXEC_CHDIR}, {"-executable", parse_access, X_OK}, {"-exit", parse_exit}, {"-f", parse_f}, {"-false", parse_const, false}, {"-fls", parse_fls}, {"-follow", parse_follow, BFTW_LOGICAL, true}, {"-fprint", parse_fprint}, {"-fprint0", parse_fprint0}, {"-fprintf", parse_fprintf}, {"-fstype", parse_fstype}, {"-gid", parse_group}, {"-group", parse_group}, {"-help", parse_help}, {"-hidden", parse_hidden}, {"-ignore_readdir_race", parse_ignore_races, true}, {"-ilname", parse_lname, true}, {"-iname", parse_name, true}, {"-inum", parse_inum}, {"-ipath", parse_path, true}, {"-iregex", parse_regex, REG_ICASE}, {"-iwholename", parse_path, true}, {"-links", parse_links}, {"-lname", parse_lname, false}, {"-ls", parse_ls}, {"-maxdepth", parse_depth_limit, false}, {"-mindepth", parse_depth_limit, true}, {"-mmin", parse_time, BFS_STAT_MTIME, MINUTES}, {"-mnewer", parse_newer, BFS_STAT_MTIME}, {"-mount", parse_mount}, {"-mtime", parse_time, BFS_STAT_MTIME, DAYS}, {"-name", parse_name, false}, {"-newer", parse_newer, BFS_STAT_MTIME}, {"-newer", parse_newerxy, 0, 0, true}, {"-nocolor", parse_color, false}, {"-nogroup", parse_nogroup}, {"-nohidden", parse_nohidden}, {"-noignore_readdir_race", parse_ignore_races, false}, {"-noleaf", parse_noleaf}, {"-not"}, {"-nouser", parse_nouser}, {"-nowarn", parse_warn, false}, {"-o"}, {"-ok", parse_exec, BFS_EXEC_CONFIRM}, {"-okdir", parse_exec, BFS_EXEC_CONFIRM | BFS_EXEC_CHDIR}, {"-or"}, {"-path", parse_path, false}, {"-perm", parse_perm}, {"-print", parse_print}, {"-print0", parse_print0}, {"-printf", parse_printf}, {"-printx", parse_printx}, {"-prune", parse_prune}, {"-quit", parse_quit}, {"-readable", parse_access, R_OK}, {"-regex", parse_regex, 0}, {"-regextype", parse_regextype}, {"-rm", parse_delete}, {"-samefile", parse_samefile}, {"-size", parse_size}, {"-sparse", parse_sparse}, {"-true", parse_const, true}, {"-type", parse_type, false}, {"-uid", parse_user}, {"-unique", parse_unique}, {"-used", parse_used}, {"-user", parse_user}, {"-version", parse_version}, {"-warn", parse_warn, true}, {"-wholename", parse_path, false}, {"-writable", parse_access, W_OK}, {"-x", parse_mount}, {"-xattr", parse_xattr}, {"-xdev", parse_mount}, {"-xtype", parse_type, true}, {0}, }; /** Look up an argument in the parse table. */ static const struct table_entry *table_lookup(const char *arg) { for (const struct table_entry *entry = parse_table; entry->arg; ++entry) { bool match; if (entry->prefix) { match = strncmp(arg, entry->arg, strlen(entry->arg)) == 0; } else { match = strcmp(arg, entry->arg) == 0; } if (match) { return entry; } } return NULL; } /** Search for a fuzzy match in the parse table. */ static const struct table_entry *table_lookup_fuzzy(const char *arg) { const struct table_entry *best = NULL; int best_dist; for (const struct table_entry *entry = parse_table; entry->arg; ++entry) { int dist = typo_distance(arg, entry->arg); if (!best || dist < best_dist) { best = entry; best_dist = dist; } } return best; } /** * LITERAL : OPTION * | TEST * | ACTION */ static struct expr *parse_literal(struct parser_state *state) { // Paths are already skipped at this point const char *arg = state->argv[0]; if (arg[0] != '-') { goto unexpected; } const struct table_entry *match = table_lookup(arg); if (match) { if (match->parse) { goto matched; } else { goto unexpected; } } match = table_lookup_fuzzy(arg); parse_error(state, "Unknown argument '%s'; did you mean '%s'?", arg, match->arg); if (!state->interactive || !match->parse) { fprintf(stderr, "\n"); goto unmatched; } fprintf(stderr, " "); if (ynprompt() <= 0) { goto unmatched; } fprintf(stderr, "\n"); state->argv[0] = match->arg; matched: return match->parse(state, match->arg1, match->arg2); unmatched: return NULL; unexpected: parse_error(state, "Expected a predicate; found '%s'.\n", arg); return NULL; } /** * FACTOR : "(" EXPR ")" * | "!" FACTOR | "-not" FACTOR * | LITERAL */ static struct expr *parse_factor(struct parser_state *state) { if (skip_paths(state) != 0) { return NULL; } const char *arg = state->argv[0]; if (!arg) { parse_error(state, "Expression terminated prematurely after '%s'.\n", state->last_arg); return NULL; } if (strcmp(arg, "(") == 0) { parser_advance(state, T_OPERATOR, 1); struct expr *expr = parse_expr(state); if (!expr) { return NULL; } if (skip_paths(state) != 0) { free_expr(expr); return NULL; } arg = state->argv[0]; if (!arg || strcmp(arg, ")") != 0) { parse_error(state, "Expected a ')' after '%s'.\n", state->argv[-1]); free_expr(expr); return NULL; } parser_advance(state, T_OPERATOR, 1); return expr; } else if (strcmp(arg, "!") == 0 || strcmp(arg, "-not") == 0) { char **argv = parser_advance(state, T_OPERATOR, 1); struct expr *factor = parse_factor(state); if (!factor) { return NULL; } return new_unary_expr(eval_not, factor, argv); } else { return parse_literal(state); } } /** * TERM : FACTOR * | TERM FACTOR * | TERM "-a" FACTOR * | TERM "-and" FACTOR */ static struct expr *parse_term(struct parser_state *state) { struct expr *term = parse_factor(state); while (term) { if (skip_paths(state) != 0) { free_expr(term); return NULL; } const char *arg = state->argv[0]; if (!arg) { break; } if (strcmp(arg, "-o") == 0 || strcmp(arg, "-or") == 0 || strcmp(arg, ",") == 0 || strcmp(arg, ")") == 0) { break; } char **argv = &fake_and_arg; if (strcmp(arg, "-a") == 0 || strcmp(arg, "-and") == 0) { argv = parser_advance(state, T_OPERATOR, 1); } struct expr *lhs = term; struct expr *rhs = parse_factor(state); if (!rhs) { free_expr(lhs); return NULL; } term = new_binary_expr(eval_and, lhs, rhs, argv); } return term; } /** * CLAUSE : TERM * | CLAUSE "-o" TERM * | CLAUSE "-or" TERM */ static struct expr *parse_clause(struct parser_state *state) { struct expr *clause = parse_term(state); while (clause) { if (skip_paths(state) != 0) { free_expr(clause); return NULL; } const char *arg = state->argv[0]; if (!arg) { break; } if (strcmp(arg, "-o") != 0 && strcmp(arg, "-or") != 0) { break; } char **argv = parser_advance(state, T_OPERATOR, 1); struct expr *lhs = clause; struct expr *rhs = parse_term(state); if (!rhs) { free_expr(lhs); return NULL; } clause = new_binary_expr(eval_or, lhs, rhs, argv); } return clause; } /** * EXPR : CLAUSE * | EXPR "," CLAUSE */ static struct expr *parse_expr(struct parser_state *state) { struct expr *expr = parse_clause(state); while (expr) { if (skip_paths(state) != 0) { free_expr(expr); return NULL; } const char *arg = state->argv[0]; if (!arg) { break; } if (strcmp(arg, ",") != 0) { break; } char **argv = parser_advance(state, T_OPERATOR, 1); struct expr *lhs = expr; struct expr *rhs = parse_clause(state); if (!rhs) { free_expr(lhs); return NULL; } expr = new_binary_expr(eval_comma, lhs, rhs, argv); } return expr; } /** * Parse the top-level expression. */ static struct expr *parse_whole_expr(struct parser_state *state) { if (skip_paths(state) != 0) { return NULL; } struct expr *expr = &expr_true; if (state->argv[0]) { expr = parse_expr(state); if (!expr) { return NULL; } } if (state->argv[0]) { parse_error(state, "Unexpected argument '%s'.\n", state->argv[0]); goto fail; } if (state->implicit_print) { struct expr *print = new_expr(eval_fprint, 1, &fake_print_arg); if (!print) { goto fail; } init_print_expr(state, print); expr = new_binary_expr(eval_and, expr, print, &fake_and_arg); if (!expr) { goto fail; } } if (state->warn && state->depth_arg && state->prune_arg) { parse_warning(state, "%s does not work in the presence of %s.\n", state->prune_arg, state->depth_arg); if (state->interactive) { fprintf(stderr, "Do you want to continue? "); if (ynprompt() == 0) { goto fail; } } fprintf(stderr, "\n"); } return expr; fail: free_expr(expr); return NULL; } /** * Dump the parsed form of the command line, for debugging. */ void dump_cmdline(const struct cmdline *cmdline, bool verbose) { CFILE *cerr = cmdline->cerr; cfprintf(cerr, "${ex}%s${rs} ", cmdline->argv[0]); const char *strategy = NULL; switch (cmdline->strategy) { case BFTW_BFS: strategy = "bfs"; break; case BFTW_DFS: strategy = "dfs"; break; case BFTW_IDS: strategy = "ids"; break; } assert(strategy); cfprintf(cerr, "${cyn}-S${rs} ${bld}%s${rs} ", strategy); if (cmdline->flags & BFTW_LOGICAL) { cfprintf(cerr, "${cyn}-L${rs} "); } else if (cmdline->flags & BFTW_COMFOLLOW) { cfprintf(cerr, "${cyn}-H${rs} "); } else { cfprintf(cerr, "${cyn}-P${rs} "); } if (cmdline->optlevel != 3) { cfprintf(cerr, "${cyn}-O%d${rs} ", cmdline->optlevel); } enum debug_flags debug = cmdline->debug; if (debug) { cfprintf(cerr, "${cyn}-D${rs} "); for (int i = 0; debug; ++i) { enum debug_flags flag = debug_flags[i].flag; const char *name = debug_flags[i].name; if ((debug & flag) == flag) { cfprintf(cerr, "${bld}%s${rs}", name); debug ^= flag; if (debug) { cfprintf(cerr, ","); } } } cfprintf(cerr, " "); } for (size_t i = 0; i < cmdline->npaths; ++i) { const char *path = cmdline->paths[i]; char c = path[0]; if (c == '-' || c == '(' || c == ')' || c == '!' || c == ',') { cfprintf(cerr, "${cyn}-f${rs} "); } cfprintf(cerr, "${mag}%s${rs} ", path); } if (cmdline->cout->colors) { cfprintf(cerr, "${blu}-color${rs} "); } else { cfprintf(cerr, "${blu}-nocolor${rs} "); } if (cmdline->flags & BFTW_DEPTH) { cfprintf(cerr, "${blu}-depth${rs} "); } if (cmdline->ignore_races) { cfprintf(cerr, "${blu}-ignore_readdir_race${rs} "); } if (cmdline->flags & BFTW_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); } if (cmdline->unique) { cfprintf(cerr, "${blu}-unique${rs} "); } dump_expr(cerr, cmdline->expr, verbose); fputs("\n", stderr); } /** * Dump the estimated costs. */ static void dump_costs(const struct cmdline *cmdline) { CFILE *cerr = cmdline->cerr; const struct expr *expr = cmdline->expr; cfprintf(cerr, " Cost: ~${ylw}%g${rs}\n", expr->cost); cfprintf(cerr, "Probability: ~${ylw}%g%%${rs}\n", 100.0*expr->probability); } /** * Get the current time. */ static int parse_gettime(struct timespec *ts) { #if _POSIX_TIMERS > 0 int ret = clock_gettime(CLOCK_REALTIME, ts); if (ret != 0) { perror("clock_gettime()"); } return ret; #else struct timeval tv; int ret = gettimeofday(&tv, NULL); if (ret == 0) { ts->tv_sec = tv.tv_sec; ts->tv_nsec = tv.tv_usec * 1000L; } else { perror("gettimeofday()"); } return ret; #endif } /** * Parse the command line. */ struct cmdline *parse_cmdline(int argc, char *argv[]) { struct cmdline *cmdline = malloc(sizeof(struct cmdline)); if (!cmdline) { perror("malloc()"); goto fail; } cmdline->argv = NULL; cmdline->paths = NULL; cmdline->npaths = 0; cmdline->colors = NULL; cmdline->cout = NULL; cmdline->cerr = NULL; cmdline->mtab = NULL; cmdline->mtab_error = 0; cmdline->mindepth = 0; cmdline->maxdepth = INT_MAX; cmdline->flags = BFTW_RECOVER; cmdline->strategy = BFTW_BFS; cmdline->optlevel = 3; cmdline->debug = 0; cmdline->xargs_safe = false; cmdline->ignore_races = false; cmdline->unique = false; cmdline->expr = &expr_true; cmdline->nopen_files = 0; trie_init(&cmdline->open_files); static char* default_argv[] = {"bfs", NULL}; if (argc < 1) { argc = 1; argv = default_argv; } cmdline->argv = malloc((argc + 1)*sizeof(*cmdline->argv)); if (!cmdline->argv) { perror("malloc()"); goto fail; } for (int i = 0; i <= argc; ++i) { cmdline->argv[i] = argv[i]; } enum use_color use_color = COLOR_AUTO; if (getenv("NO_COLOR")) { // https://no-color.org/ use_color = COLOR_NEVER; } cmdline->colors = parse_colors(getenv("LS_COLORS")); cmdline->cout = cfdup(stdout, use_color ? cmdline->colors : NULL); cmdline->cerr = cfdup(stderr, use_color ? cmdline->colors : NULL); if (!cmdline->cout || !cmdline->cerr) { perror("cfdup()"); goto fail; } cmdline->mtab = parse_bfs_mtab(); if (!cmdline->mtab) { cmdline->mtab_error = errno; } bool stdin_tty = isatty(STDIN_FILENO); bool stdout_tty = isatty(STDOUT_FILENO); bool stderr_tty = isatty(STDERR_FILENO); struct parser_state state = { .cmdline = cmdline, .argv = cmdline->argv + 1, .command = cmdline->argv[0], .regex_flags = 0, .stdout_tty = stdout_tty, .interactive = stdin_tty && stderr_tty, .use_color = use_color, .implicit_print = true, .warn = stdin_tty, .non_option_seen = false, .just_info = false, .last_arg = NULL, .depth_arg = NULL, .prune_arg = NULL, }; if (strcmp(xbasename(state.command), "find") == 0) { // Operate depth-first when invoked as "find" cmdline->strategy = BFTW_DFS; } if (parse_gettime(&state.now) != 0) { goto fail; } cmdline->expr = parse_whole_expr(&state); if (!cmdline->expr) { if (state.just_info) { goto done; } else { goto fail; } } if (optimize_cmdline(cmdline) != 0) { goto fail; } if (cmdline->npaths == 0) { if (parse_root(&state, ".") != 0) { goto fail; } } if ((cmdline->flags & BFTW_LOGICAL) && !cmdline->unique) { // We need bftw() to detect cycles unless -unique does it for us cmdline->flags |= BFTW_DETECT_CYCLES; } if (cmdline->debug & DEBUG_TREE) { dump_cmdline(cmdline, false); } if (cmdline->debug & DEBUG_COST) { dump_costs(cmdline); } done: return cmdline; fail: free_cmdline(cmdline); return NULL; } /**************************************************************************** * bfs * * Copyright (C) 2017-2019 Tavian Barnes * * * * Permission to use, copy, modify, and/or distribute this software for any * * purpose with or without fee is hereby granted. * * * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * ****************************************************************************/ #include #include #include #include #include #include #include #include #include typedef int bfs_printf_fn(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf); struct bfs_printf { /** The printing function to invoke. */ bfs_printf_fn *fn; /** String data associated with this directive. */ char *str; /** The stat field to print. */ enum bfs_stat_field stat_field; /** Character data associated with this directive. */ char c; /** The current mount table. */ const struct bfs_mtab *mtab; /** The next printf directive in the chain. */ struct bfs_printf *next; }; /** Print some text as-is. */ static int bfs_printf_literal(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { size_t len = dstrlen(directive->str); if (fwrite(directive->str, 1, len, file) == len) { return 0; } else { return -1; } } /** \c: flush */ static int bfs_printf_flush(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { return fflush(file); } /** * Print a value to a temporary buffer before formatting it. */ #define BFS_PRINTF_BUF(buf, format, ...) \ char buf[256]; \ int ret = snprintf(buf, sizeof(buf), format, __VA_ARGS__); \ assert(ret >= 0 && ret < sizeof(buf)); \ (void)ret /** %a, %c, %t: ctime() */ static int bfs_printf_ctime(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { // Not using ctime() itself because GNU find adds nanoseconds static const char *days[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; static const char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); if (!statbuf) { return -1; } const struct timespec *ts = bfs_stat_time(statbuf, directive->stat_field); if (!ts) { return -1; } struct tm tm; if (xlocaltime(&ts->tv_sec, &tm) != 0) { return -1; } BFS_PRINTF_BUF(buf, "%s %s %2d %.2d:%.2d:%.2d.%09ld0 %4d", days[tm.tm_wday], months[tm.tm_mon], tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, (long)ts->tv_nsec, 1900 + tm.tm_year); return fprintf(file, directive->str, buf); } /** %A, %B/%W, %C, %T: strftime() */ static int bfs_printf_strftime(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); if (!statbuf) { return -1; } const struct timespec *ts = bfs_stat_time(statbuf, directive->stat_field); if (!ts) { return -1; } struct tm tm; if (xlocaltime(&ts->tv_sec, &tm) != 0) { return -1; } int ret; char buf[256]; char format[] = "% "; switch (directive->c) { // Non-POSIX strftime() features case '@': ret = snprintf(buf, sizeof(buf), "%lld.%09ld0", (long long)ts->tv_sec, (long)ts->tv_nsec); break; case '+': ret = snprintf(buf, sizeof(buf), "%4d-%.2d-%.2d+%.2d:%.2d:%.2d.%09ld0", 1900 + tm.tm_year, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, (long)ts->tv_nsec); break; case 'k': ret = snprintf(buf, sizeof(buf), "%2d", tm.tm_hour); break; case 'l': ret = snprintf(buf, sizeof(buf), "%2d", (tm.tm_hour + 11)%12 + 1); break; case 's': ret = snprintf(buf, sizeof(buf), "%lld", (long long)ts->tv_sec); break; case 'S': ret = snprintf(buf, sizeof(buf), "%.2d.%09ld0", tm.tm_sec, (long)ts->tv_nsec); break; case 'T': ret = snprintf(buf, sizeof(buf), "%.2d:%.2d:%.2d.%09ld0", tm.tm_hour, tm.tm_min, tm.tm_sec, (long)ts->tv_nsec); break; // POSIX strftime() features default: format[1] = directive->c; ret = strftime(buf, sizeof(buf), format, &tm); break; } assert(ret >= 0 && ret < sizeof(buf)); (void)ret; return fprintf(file, directive->str, buf); } /** %b: blocks */ static int bfs_printf_b(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); if (!statbuf) { return -1; } uintmax_t blocks = ((uintmax_t)statbuf->blocks*BFS_STAT_BLKSIZE + 511)/512; BFS_PRINTF_BUF(buf, "%ju", blocks); return fprintf(file, directive->str, buf); } /** %d: depth */ static int bfs_printf_d(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { return fprintf(file, directive->str, (intmax_t)ftwbuf->depth); } /** %D: device */ static int bfs_printf_D(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); if (!statbuf) { return -1; } BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)statbuf->dev); return fprintf(file, directive->str, buf); } /** %f: file name */ static int bfs_printf_f(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { return fprintf(file, directive->str, ftwbuf->path + ftwbuf->nameoff); } /** %F: file system type */ static int bfs_printf_F(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); if (!statbuf) { return -1; } const char *type = bfs_fstype(directive->mtab, statbuf); return fprintf(file, directive->str, type); } /** %G: gid */ static int bfs_printf_G(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); if (!statbuf) { return -1; } BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)statbuf->gid); return fprintf(file, directive->str, buf); } /** %g: group name */ static int bfs_printf_g(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); if (!statbuf) { return -1; } struct group *grp = getgrgid(statbuf->gid); if (!grp) { return bfs_printf_G(file, directive, ftwbuf); } return fprintf(file, directive->str, grp->gr_name); } /** %h: leading directories */ static int bfs_printf_h(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { char *copy = NULL; const char *buf; if (ftwbuf->nameoff > 0) { size_t len = ftwbuf->nameoff; if (len > 1) { --len; } buf = copy = strndup(ftwbuf->path, len); } else if (ftwbuf->path[0] == '/') { buf = "/"; } else { buf = "."; } if (!buf) { return -1; } int ret = fprintf(file, directive->str, buf); free(copy); return ret; } /** %H: current root */ static int bfs_printf_H(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { return fprintf(file, directive->str, ftwbuf->root); } /** %i: inode */ static int bfs_printf_i(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); if (!statbuf) { return -1; } BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)statbuf->ino); return fprintf(file, directive->str, buf); } /** %k: 1K blocks */ static int bfs_printf_k(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); if (!statbuf) { return -1; } uintmax_t blocks = ((uintmax_t)statbuf->blocks*BFS_STAT_BLKSIZE + 1023)/1024; BFS_PRINTF_BUF(buf, "%ju", blocks); return fprintf(file, directive->str, buf); } /** %l: link target */ static int bfs_printf_l(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { if (ftwbuf->typeflag != BFTW_LNK) { return 0; } char *target = xreadlinkat(ftwbuf->at_fd, ftwbuf->at_path, 0); if (!target) { return -1; } int ret = fprintf(file, directive->str, target); free(target); return ret; } /** %m: mode */ static int bfs_printf_m(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); if (!statbuf) { return -1; } return fprintf(file, directive->str, (unsigned int)(statbuf->mode & 07777)); } /** %M: symbolic mode */ static int bfs_printf_M(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); if (!statbuf) { return -1; } char buf[11]; format_mode(statbuf->mode, buf); return fprintf(file, directive->str, buf); } /** %n: link count */ static int bfs_printf_n(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); if (!statbuf) { return -1; } BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)statbuf->nlink); return fprintf(file, directive->str, buf); } /** %p: full path */ static int bfs_printf_p(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { return fprintf(file, directive->str, ftwbuf->path); } /** %P: path after root */ static int bfs_printf_P(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { const char *path = ftwbuf->path + strlen(ftwbuf->root); if (path[0] == '/') { ++path; } return fprintf(file, directive->str, path); } /** %s: size */ static int bfs_printf_s(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); if (!statbuf) { return -1; } BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)statbuf->size); return fprintf(file, directive->str, buf); } /** %S: sparseness */ static int bfs_printf_S(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); if (!statbuf) { return -1; } double sparsity; if (statbuf->size == 0 && statbuf->blocks == 0) { sparsity = 1.0; } else { sparsity = (double)BFS_STAT_BLKSIZE*statbuf->blocks/statbuf->size; } return fprintf(file, directive->str, sparsity); } /** %U: uid */ static int bfs_printf_U(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); if (!statbuf) { return -1; } BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)statbuf->uid); return fprintf(file, directive->str, buf); } /** %u: user name */ static int bfs_printf_u(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); if (!statbuf) { return -1; } struct passwd *pwd = getpwuid(statbuf->uid); if (!pwd) { return bfs_printf_U(file, directive, ftwbuf); } return fprintf(file, directive->str, pwd->pw_name); } static const char *bfs_printf_type(enum bftw_typeflag typeflag) { switch (typeflag) { case BFTW_BLK: return "b"; case BFTW_CHR: return "c"; case BFTW_DIR: return "d"; case BFTW_DOOR: return "D"; case BFTW_FIFO: return "p"; case BFTW_LNK: return "l"; case BFTW_REG: return "f"; case BFTW_SOCK: return "s"; default: return "U"; } } /** %y: type */ static int bfs_printf_y(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { const char *type = bfs_printf_type(ftwbuf->typeflag); return fprintf(file, directive->str, type); } /** %Y: target type */ static int bfs_printf_Y(FILE *file, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { int error = 0; if (ftwbuf->typeflag != BFTW_LNK) { return bfs_printf_y(file, directive, ftwbuf); } const char *type = "U"; const struct bfs_stat *statbuf = bftw_stat(ftwbuf, BFS_STAT_FOLLOW); if (statbuf) { type = bfs_printf_type(bftw_mode_typeflag(statbuf->mode)); } else { switch (errno) { case ELOOP: type = "L"; break; case ENOENT: case ENOTDIR: type = "N"; break; default: type = "?"; error = errno; break; } } int ret = fprintf(file, directive->str, type); if (error != 0) { ret = -1; errno = error; } return ret; } /** * Free a printf directive. */ static void free_directive(struct bfs_printf *directive) { if (directive) { dstrfree(directive->str); free(directive); } } /** * Create a new printf directive. */ static struct bfs_printf *new_directive(bfs_printf_fn *fn) { struct bfs_printf *directive = malloc(sizeof(*directive)); if (!directive) { perror("malloc()"); goto error; } directive->fn = fn; directive->str = dstralloc(2); if (!directive->str) { perror("dstralloc()"); goto error; } directive->stat_field = 0; directive->c = 0; directive->mtab = NULL; directive->next = NULL; return directive; error: free_directive(directive); return NULL; } /** * Append a printf directive to the chain. */ static struct bfs_printf **append_directive(struct bfs_printf **tail, struct bfs_printf *directive) { assert(directive); *tail = directive; return &directive->next; } /** * Append a literal string to the chain. */ static struct bfs_printf **append_literal(struct bfs_printf **tail, struct bfs_printf **literal) { struct bfs_printf *directive = *literal; if (directive && dstrlen(directive->str) > 0) { *literal = NULL; return append_directive(tail, directive); } else { return tail; } } struct bfs_printf *parse_bfs_printf(const char *format, struct cmdline *cmdline) { struct bfs_printf *head = NULL; struct bfs_printf **tail = &head; struct bfs_printf *literal = new_directive(bfs_printf_literal); if (!literal) { goto error; } for (const char *i = format; *i; ++i) { char c = *i; if (c == '\\') { c = *++i; if (c >= '0' && c < '8') { c = 0; for (int j = 0; j < 3 && *i >= '0' && *i < '8'; ++i, ++j) { c *= 8; c += *i - '0'; } --i; goto one_char; } switch (c) { case 'a': c = '\a'; break; case 'b': c = '\b'; break; case 'f': c = '\f'; break; case 'n': c = '\n'; break; case 'r': c = '\r'; break; case 't': c = '\t'; break; case 'v': c = '\v'; break; case '\\': c = '\\'; break; case 'c': tail = append_literal(tail, &literal); struct bfs_printf *directive = new_directive(bfs_printf_flush); if (!directive) { goto error; } tail = append_directive(tail, directive); goto done; case '\0': bfs_error(cmdline, "'%s': Incomplete escape sequence '\\'.\n", format); goto error; default: bfs_error(cmdline, "'%s': Unrecognized escape sequence '\\%c'.\n", format, c); goto error; } } else if (c == '%') { if (i[1] == '%') { c = *++i; goto one_char; } struct bfs_printf *directive = new_directive(NULL); if (!directive) { goto directive_error; } if (dstrapp(&directive->str, c) != 0) { perror("dstrapp()"); goto directive_error; } const char *specifier = "s"; // Parse any flags bool must_be_numeric = false; while (true) { c = *++i; switch (c) { case '#': case '0': case '+': must_be_numeric = true; // Fallthrough case ' ': case '-': if (strchr(directive->str, c)) { bfs_error(cmdline, "'%s': Duplicate flag '%c'.\n", format, c); goto directive_error; } if (dstrapp(&directive->str, c) != 0) { perror("dstrapp()"); goto directive_error; } continue; } break; } // Parse the field width while (c >= '0' && c <= '9') { if (dstrapp(&directive->str, c) != 0) { perror("dstrapp()"); goto directive_error; } c = *++i; } // Parse the precision if (c == '.') { do { if (dstrapp(&directive->str, c) != 0) { perror("dstrapp()"); goto directive_error; } c = *++i; } while (c >= '0' && c <= '9'); } switch (c) { case 'a': directive->fn = bfs_printf_ctime; directive->stat_field = BFS_STAT_ATIME; break; case 'b': directive->fn = bfs_printf_b; break; case 'c': directive->fn = bfs_printf_ctime; directive->stat_field = BFS_STAT_CTIME; break; case 'd': directive->fn = bfs_printf_d; specifier = "jd"; break; case 'D': directive->fn = bfs_printf_D; break; case 'f': directive->fn = bfs_printf_f; break; case 'F': if (!cmdline->mtab) { bfs_error(cmdline, "Couldn't parse the mount table: %s.\n", strerror(cmdline->mtab_error)); goto directive_error; } directive->fn = bfs_printf_F; directive->mtab = cmdline->mtab; break; case 'g': directive->fn = bfs_printf_g; break; case 'G': directive->fn = bfs_printf_G; break; case 'h': directive->fn = bfs_printf_h; break; case 'H': directive->fn = bfs_printf_H; break; case 'i': directive->fn = bfs_printf_i; break; case 'k': directive->fn = bfs_printf_k; break; case 'l': directive->fn = bfs_printf_l; break; case 'm': directive->fn = bfs_printf_m; specifier = "o"; break; case 'M': directive->fn = bfs_printf_M; break; case 'n': directive->fn = bfs_printf_n; break; case 'p': directive->fn = bfs_printf_p; break; case 'P': directive->fn = bfs_printf_P; break; case 's': directive->fn = bfs_printf_s; break; case 'S': directive->fn = bfs_printf_S; specifier = "g"; break; case 't': directive->fn = bfs_printf_ctime; directive->stat_field = BFS_STAT_MTIME; break; case 'u': directive->fn = bfs_printf_u; break; case 'U': directive->fn = bfs_printf_U; break; case 'w': directive->fn = bfs_printf_ctime; directive->stat_field = BFS_STAT_BTIME; break; case 'y': directive->fn = bfs_printf_y; break; case 'Y': directive->fn = bfs_printf_Y; break; case 'A': directive->stat_field = BFS_STAT_ATIME; goto directive_strftime; case 'B': case 'W': directive->stat_field = BFS_STAT_BTIME; goto directive_strftime; case 'C': directive->stat_field = BFS_STAT_CTIME; goto directive_strftime; case 'T': directive->stat_field = BFS_STAT_MTIME; goto directive_strftime; directive_strftime: directive->fn = bfs_printf_strftime; c = *++i; if (!c) { bfs_error(cmdline, "'%s': Incomplete time specifier '%s%c'.\n", format, directive->str, i[-1]); goto directive_error; } else if (strchr("%+@aAbBcCdDeFgGhHIjklmMnprRsStTuUVwWxXyYzZ", c)) { directive->c = c; } else { bfs_error(cmdline, "'%s': Unrecognized time specifier '%%%c%c'.\n", format, i[-1], c); goto directive_error; } break; case '\0': bfs_error(cmdline, "'%s': Incomplete format specifier '%s'.\n", format, directive->str); goto directive_error; default: bfs_error(cmdline, "'%s': Unrecognized format specifier '%%%c'.\n", format, c); goto directive_error; } if (must_be_numeric && strcmp(specifier, "s") == 0) { bfs_error(cmdline, "'%s': Invalid flags '%s' for string format '%%%c'.\n", format, directive->str + 1, c); goto directive_error; } if (dstrcat(&directive->str, specifier) != 0) { perror("dstrcat()"); goto directive_error; } tail = append_literal(tail, &literal); tail = append_directive(tail, directive); if (!literal) { literal = new_directive(bfs_printf_literal); if (!literal) { goto error; } } continue; directive_error: free_directive(directive); goto error; } one_char: if (dstrapp(&literal->str, c) != 0) { perror("dstrapp()"); goto error; } } done: tail = append_literal(tail, &literal); if (head) { free_directive(literal); return head; } else { return literal; } error: free_directive(literal); free_bfs_printf(head); return NULL; } int bfs_printf(FILE *file, const struct bfs_printf *command, const struct BFTW *ftwbuf) { int ret = 0, error = 0; for (const struct bfs_printf *directive = command; directive; directive = directive->next) { if (directive->fn(file, directive, ftwbuf) < 0) { ret = -1; error = errno; } } errno = error; return ret; } void free_bfs_printf(struct bfs_printf *command) { while (command) { struct bfs_printf *next = command->next; free_directive(command); command = next; } } /**************************************************************************** * bfs * * Copyright (C) 2018-2019 Tavian Barnes * * * * Permission to use, copy, modify, and/or distribute this software for any * * purpose with or without fee is hereby granted. * * * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * ****************************************************************************/ #include #include #include #include #include #include #include /** * Types of spawn actions. */ enum bfs_spawn_op { BFS_SPAWN_CLOSE, BFS_SPAWN_DUP2, BFS_SPAWN_FCHDIR, }; /** * A spawn action. */ struct bfs_spawn_action { struct bfs_spawn_action *next; enum bfs_spawn_op op; int in_fd; int out_fd; }; int bfs_spawn_init(struct bfs_spawn *ctx) { ctx->flags = 0; ctx->actions = NULL; ctx->tail = &ctx->actions; return 0; } int bfs_spawn_destroy(struct bfs_spawn *ctx) { struct bfs_spawn_action *action = ctx->actions; while (action) { struct bfs_spawn_action *next = action->next; free(action); action = next; } return 0; } int bfs_spawn_setflags(struct bfs_spawn *ctx, enum bfs_spawn_flags flags) { ctx->flags = flags; return 0; } /** Add a spawn action to the chain. */ static struct bfs_spawn_action *bfs_spawn_add(struct bfs_spawn *ctx, enum bfs_spawn_op op) { struct bfs_spawn_action *action = malloc(sizeof(*action)); if (action) { action->next = NULL; action->op = op; action->in_fd = -1; action->out_fd = -1; *ctx->tail = action; ctx->tail = &action->next; } return action; } int bfs_spawn_addclose(struct bfs_spawn *ctx, int fd) { if (fd < 0) { errno = EBADF; return -1; } struct bfs_spawn_action *action = bfs_spawn_add(ctx, BFS_SPAWN_CLOSE); if (action) { action->out_fd = fd; return 0; } else { return -1; } } int bfs_spawn_adddup2(struct bfs_spawn *ctx, int oldfd, int newfd) { if (oldfd < 0 || newfd < 0) { errno = EBADF; return -1; } struct bfs_spawn_action *action = bfs_spawn_add(ctx, BFS_SPAWN_DUP2); if (action) { action->in_fd = oldfd; action->out_fd = newfd; return 0; } else { return -1; } } int bfs_spawn_addfchdir(struct bfs_spawn *ctx, int fd) { if (fd < 0) { errno = EBADF; return -1; } struct bfs_spawn_action *action = bfs_spawn_add(ctx, BFS_SPAWN_FCHDIR); if (action) { action->in_fd = fd; return 0; } else { return -1; } } /** Facade for execvpe() which is non-standard. */ static int bfs_execvpe(const char *exe, char **argv, char **envp) { #if __GLIBC__ || __linux__ || __NetBSD__ || __OpenBSD__ return execvpe(exe, argv, envp); #else extern char **environ; environ = envp; return execvp(exe, argv); #endif } /** Actually exec() the new process. */ static void bfs_spawn_exec(const char *exe, const struct bfs_spawn *ctx, char **argv, char **envp, int pipefd[2]) { int error; enum bfs_spawn_flags flags = ctx ? ctx->flags : 0; const struct bfs_spawn_action *actions = ctx ? ctx->actions : NULL; close(pipefd[0]); for (const struct bfs_spawn_action *action = actions; action; action = action->next) { // Move the error-reporting pipe out of the way if necessary if (action->in_fd == pipefd[1] || action->out_fd == pipefd[1]) { int fd = dup(pipefd[1]); if (fd < 0) { goto fail; } close(pipefd[1]); pipefd[1] = fd; } switch (action->op) { case BFS_SPAWN_CLOSE: if (close(action->out_fd) != 0) { goto fail; } break; case BFS_SPAWN_DUP2: if (dup2(action->in_fd, action->out_fd) < 0) { goto fail; } break; case BFS_SPAWN_FCHDIR: if (fchdir(action->in_fd) != 0) { goto fail; } break; } } if (flags & BFS_SPAWN_USEPATH) { bfs_execvpe(exe, argv, envp); } else { execve(exe, argv, envp); } fail: error = errno; while (write(pipefd[1], &error, sizeof(error)) < sizeof(error)); close(pipefd[1]); _Exit(127); } pid_t bfs_spawn(const char *exe, const struct bfs_spawn *ctx, char **argv, char **envp) { // Use a pipe to report errors from the child int pipefd[2]; if (pipe_cloexec(pipefd) != 0) { return -1; } int error; pid_t pid = fork(); if (pid < 0) { error = errno; close(pipefd[1]); close(pipefd[0]); errno = error; return -1; } else if (pid == 0) { // Child bfs_spawn_exec(exe, ctx, argv, envp, pipefd); } // Parent close(pipefd[1]); ssize_t nbytes = read(pipefd[0], &error, sizeof(error)); close(pipefd[0]); if (nbytes == sizeof(error)) { int wstatus; waitpid(pid, &wstatus, 0); errno = error; return -1; } return pid; } /**************************************************************************** * bfs * * Copyright (C) 2018-2019 Tavian Barnes * * * * Permission to use, copy, modify, and/or distribute this software for any * * purpose with or without fee is hereby granted. * * * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * ****************************************************************************/ #include #include #include #include #include #include #include #include #ifdef STATX_BASIC_STATS # define HAVE_STATX true #elif __linux__ # include # include #endif #if HAVE_STATX || defined(__NR_statx) # define HAVE_BFS_STATX true #endif #if __APPLE__ # define st_atim st_atimespec # define st_ctim st_ctimespec # define st_mtim st_mtimespec # define st_birthtim st_birthtimespec #endif const char *bfs_stat_field_name(enum bfs_stat_field field) { switch (field) { case BFS_STAT_DEV: return "device number"; case BFS_STAT_INO: return "inode nunmber"; case BFS_STAT_TYPE: return "type"; case BFS_STAT_MODE: return "mode"; case BFS_STAT_NLINK: return "link count"; case BFS_STAT_GID: return "group ID"; case BFS_STAT_UID: return "user ID"; case BFS_STAT_SIZE: return "size"; case BFS_STAT_BLOCKS: return "block count"; case BFS_STAT_RDEV: return "underlying device"; case BFS_STAT_ATIME: return "access time"; case BFS_STAT_BTIME: return "birth time"; case BFS_STAT_CTIME: return "change time"; case BFS_STAT_MTIME: return "modification time"; } assert(false); return "???"; } /** * Check if we should retry a failed stat() due to a potentially broken link. */ static bool bfs_stat_retry(int ret, enum bfs_stat_flag flags) { return ret != 0 && (flags & (BFS_STAT_NOFOLLOW | BFS_STAT_TRYFOLLOW)) == BFS_STAT_TRYFOLLOW && is_nonexistence_error(errno); } /** * Convert a struct stat to a struct bfs_stat. */ static void bfs_stat_convert(const struct stat *statbuf, struct bfs_stat *buf) { buf->mask = 0; buf->dev = statbuf->st_dev; buf->mask |= BFS_STAT_DEV; buf->ino = statbuf->st_ino; buf->mask |= BFS_STAT_INO; buf->mode = statbuf->st_mode; buf->mask |= BFS_STAT_TYPE | BFS_STAT_MODE; buf->nlink = statbuf->st_nlink; buf->mask |= BFS_STAT_NLINK; buf->gid = statbuf->st_gid; buf->mask |= BFS_STAT_GID; buf->uid = statbuf->st_uid; buf->mask |= BFS_STAT_UID; buf->size = statbuf->st_size; buf->mask |= BFS_STAT_SIZE; buf->blocks = statbuf->st_blocks; buf->mask |= BFS_STAT_BLOCKS; buf->rdev = statbuf->st_rdev; buf->mask |= BFS_STAT_RDEV; buf->atime = statbuf->st_atim; buf->mask |= BFS_STAT_ATIME; buf->ctime = statbuf->st_ctim; buf->mask |= BFS_STAT_CTIME; buf->mtime = statbuf->st_mtim; buf->mask |= BFS_STAT_MTIME; #if __APPLE__ || __FreeBSD__ || __NetBSD__ buf->btime = statbuf->st_birthtim; buf->mask |= BFS_STAT_BTIME; #endif } /** * bfs_stat() implementation backed by stat(). */ static int bfs_stat_impl(int at_fd, const char *at_path, int at_flags, enum bfs_stat_flag flags, struct bfs_stat *buf) { struct stat statbuf; int ret = fstatat(at_fd, at_path, &statbuf, at_flags); if (bfs_stat_retry(ret, flags)) { at_flags |= AT_SYMLINK_NOFOLLOW; ret = fstatat(at_fd, at_path, &statbuf, at_flags); } if (ret == 0) { bfs_stat_convert(&statbuf, buf); } return ret; } #if HAVE_BFS_STATX /** * Wrapper for the statx() system call, which had no glibc wrapper prior to 2.28. */ static int bfs_statx(int at_fd, const char *at_path, int at_flags, unsigned int mask, struct statx *buf) { // -fsanitize=memory doesn't know about statx(), so tell it the memory // got initialized #if BFS_HAS_FEATURE(memory_sanitizer, false) memset(buf, 0, sizeof(*buf)); #endif #if HAVE_STATX return statx(at_fd, at_path, at_flags, mask, buf); #else return syscall(__NR_statx, at_fd, at_path, at_flags, mask, buf); #endif } /** * bfs_stat() implementation backed by statx(). */ static int bfs_statx_impl(int at_fd, const char *at_path, int at_flags, enum bfs_stat_flag flags, struct bfs_stat *buf) { unsigned int mask = STATX_BASIC_STATS | STATX_BTIME; struct statx xbuf; int ret = bfs_statx(at_fd, at_path, at_flags, mask, &xbuf); if (bfs_stat_retry(ret, flags)) { at_flags |= AT_SYMLINK_NOFOLLOW; ret = bfs_statx(at_fd, at_path, at_flags, mask, &xbuf); } if (ret != 0) { return ret; } // Callers shouldn't have to check anything except the times const unsigned int guaranteed = STATX_BASIC_STATS ^ (STATX_ATIME | STATX_CTIME | STATX_MTIME); if ((xbuf.stx_mask & guaranteed) != guaranteed) { errno = ENOTSUP; return -1; } buf->mask = 0; buf->dev = bfs_makedev(xbuf.stx_dev_major, xbuf.stx_dev_minor); buf->mask |= BFS_STAT_DEV; if (xbuf.stx_mask & STATX_INO) { buf->ino = xbuf.stx_ino; buf->mask |= BFS_STAT_INO; } buf->mode = xbuf.stx_mode; if (xbuf.stx_mask & STATX_TYPE) { buf->mask |= BFS_STAT_TYPE; } if (xbuf.stx_mask & STATX_MODE) { buf->mask |= BFS_STAT_MODE; } if (xbuf.stx_mask & STATX_NLINK) { buf->nlink = xbuf.stx_nlink; buf->mask |= BFS_STAT_NLINK; } if (xbuf.stx_mask & STATX_GID) { buf->gid = xbuf.stx_gid; buf->mask |= BFS_STAT_GID; } if (xbuf.stx_mask & STATX_UID) { buf->uid = xbuf.stx_uid; buf->mask |= BFS_STAT_UID; } if (xbuf.stx_mask & STATX_SIZE) { buf->size = xbuf.stx_size; buf->mask |= BFS_STAT_SIZE; } if (xbuf.stx_mask & STATX_BLOCKS) { buf->blocks = xbuf.stx_blocks; buf->mask |= BFS_STAT_BLOCKS; } buf->rdev = bfs_makedev(xbuf.stx_rdev_major, xbuf.stx_rdev_minor); buf->mask |= BFS_STAT_RDEV; if (xbuf.stx_mask & STATX_ATIME) { buf->atime.tv_sec = xbuf.stx_atime.tv_sec; buf->atime.tv_nsec = xbuf.stx_atime.tv_nsec; buf->mask |= BFS_STAT_ATIME; } if (xbuf.stx_mask & STATX_BTIME) { buf->btime.tv_sec = xbuf.stx_btime.tv_sec; buf->btime.tv_nsec = xbuf.stx_btime.tv_nsec; buf->mask |= BFS_STAT_BTIME; } if (xbuf.stx_mask & STATX_CTIME) { buf->ctime.tv_sec = xbuf.stx_ctime.tv_sec; buf->ctime.tv_nsec = xbuf.stx_ctime.tv_nsec; buf->mask |= BFS_STAT_CTIME; } if (xbuf.stx_mask & STATX_MTIME) { buf->mtime.tv_sec = xbuf.stx_mtime.tv_sec; buf->mtime.tv_nsec = xbuf.stx_mtime.tv_nsec; buf->mask |= BFS_STAT_MTIME; } return ret; } #endif // HAVE_BFS_STATX /** * Allows calling stat with custom at_flags. */ static int bfs_stat_explicit(int at_fd, const char *at_path, int at_flags, enum bfs_stat_flag flags, struct bfs_stat *buf) { #if HAVE_BFS_STATX static bool has_statx = true; if (has_statx) { int ret = bfs_statx_impl(at_fd, at_path, at_flags, flags, buf); 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_stat(int at_fd, const char *at_path, enum bfs_stat_flag flags, struct bfs_stat *buf) { int at_flags = 0; if (flags & BFS_STAT_NOFOLLOW) { at_flags |= AT_SYMLINK_NOFOLLOW; } if (at_path) { return bfs_stat_explicit(at_fd, at_path, at_flags, flags, buf); } #ifdef AT_EMPTY_PATH static bool has_at_ep = true; if (has_at_ep) { at_flags |= AT_EMPTY_PATH; int ret = bfs_stat_explicit(at_fd, "", at_flags, flags, buf); if (ret != 0 && errno == EINVAL) { has_at_ep = false; } else { return ret; } } #endif struct stat statbuf; if (fstat(at_fd, &statbuf) == 0) { bfs_stat_convert(&statbuf, buf); return 0; } else { return -1; } } const struct timespec *bfs_stat_time(const struct bfs_stat *buf, enum bfs_stat_field field) { if (!(buf->mask & field)) { errno = ENOTSUP; return NULL; } switch (field) { case BFS_STAT_ATIME: return &buf->atime; case BFS_STAT_BTIME: return &buf->btime; case BFS_STAT_CTIME: return &buf->ctime; case BFS_STAT_MTIME: return &buf->mtime; default: assert(false); errno = EINVAL; return NULL; } } void bfs_stat_id(const struct bfs_stat *buf, bfs_file_id *id) { memcpy(*id, &buf->dev, sizeof(buf->dev)); memcpy(*id + sizeof(buf->dev), &buf->ino, sizeof(buf->ino)); } /**************************************************************************** * bfs * * Copyright (C) 2019 Tavian Barnes * * * * Permission to use, copy, modify, and/or distribute this software for any * * purpose with or without fee is hereby granted. * * * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * ****************************************************************************/ /** * This is an implementation of a "qp trie," as documented at * https://dotat.at/prog/qp/README.html * * An uncompressed trie over the dataset {AAAA, AADD, ABCD, DDAA, DDDD} would * look like * * A A A A * *--->*--->*--->*--->$ * | | | D D * | | +--->*--->$ * | | B C D * | +--->*--->*--->$ * | D D A A * +--->*--->*--->*--->$ * | D D * +--->*--->$ * * A compressed (PATRICIA) trie collapses internal nodes that have only a single * child, like this: * * A A AA * *--->*--->*---->$ * | | | DD * | | +---->$ * | | BCD * | +----->$ * | DD AA * +---->*---->$ * | DD * +---->$ * * The nodes can be compressed further by dropping the actual compressed * sequences from the nodes, storing it only in the leaves. This is the * technique applied in QP tries, and the crit-bit trees that inspired them * (https://cr.yp.to/critbit.html). Only the index to test, and the values to * branch on, need to be stored in each node. * * A A A * 0--->1--->2--->AAAA * | | | D * | | +--->AADD * | | B * | +--->ABCD * | D A * +--->2--->DDAA * | D * +--->DDDD * * Nodes are represented very compactly. Rather than a dense array of children, * a sparse array of only the non-NULL children directly follows the node in * memory. A bitmap is used to track which children exist; the index of a child * i is found by counting the number of bits below bit i that are set. A tag * bit is used to tell pointers to internal nodes apart from pointers to leaves. * * This implementation tests a whole nibble (half byte/hex digit) at every * branch, so the bitmap takes up 16 bits. The remainder of a machine word is * used to hold the offset, which severely constrains its range on 32-bit * platforms. As a workaround, we store relative instead of absolute offsets, * and insert intermediate singleton "jump" nodes when necessary. */ #include #include #include #include #include #include #if CHAR_BIT != 8 # error "This trie implementation assumes 8-bit bytes." #endif /** Number of bits for the sparse array bitmap, aka the range of a nibble. */ #define BITMAP_BITS 16 /** The number of remaining bits in a word, to hold the offset. */ #define OFFSET_BITS (sizeof(size_t)*CHAR_BIT - BITMAP_BITS) /** The highest representable offset (only 64k on a 32-bit architecture). */ #define OFFSET_MAX (((size_t)1 << OFFSET_BITS) - 1) /** * An internal node of the trie. */ struct trie_node { /** * A bitmap that hold which indices exist in the sparse children array. * Bit i will be set if a child exists at logical index i, and its index * into the array will be popcount(bitmap & ((1 << i) - 1)). */ size_t bitmap : BITMAP_BITS; /** * The offset into the key in nibbles. This is relative to the parent * node, to support offsets larger than OFFSET_MAX. */ size_t offset : OFFSET_BITS; /** * Flexible array of children. Each pointer uses the lowest bit as a * tag to distinguish internal nodes from leaves. This is safe as long * as all dynamic allocations are aligned to more than a single byte. */ uintptr_t children[]; }; /** Check if an encoded pointer is to a leaf. */ static bool trie_is_leaf(uintptr_t ptr) { return ptr & 1; } /** Decode a pointer to a leaf. */ static struct trie_leaf *trie_decode_leaf(uintptr_t ptr) { assert(trie_is_leaf(ptr)); return (struct trie_leaf *)(ptr ^ 1); } /** Encode a pointer to a leaf. */ static uintptr_t trie_encode_leaf(const struct trie_leaf *leaf) { uintptr_t ptr = (uintptr_t)leaf ^ 1; assert(trie_is_leaf(ptr)); return ptr; } /** Decode a pointer to an internal node. */ static struct trie_node *trie_decode_node(uintptr_t ptr) { assert(!trie_is_leaf(ptr)); return (struct trie_node *)ptr; } /** Encode a pointer to an internal node. */ static uintptr_t trie_encode_node(const struct trie_node *node) { uintptr_t ptr = (uintptr_t)node; assert(!trie_is_leaf(ptr)); return ptr; } void trie_init(struct trie *trie) { trie->root = 0; } /** Compute the popcount (Hamming weight) of a bitmap. */ static unsigned int trie_popcount(unsigned int n) { #if __POPCNT__ // Use the x86 instruction if we have it. Otherwise, GCC generates a // library call, so use the below implementation instead. return __builtin_popcount(n); #else // See https://en.wikipedia.org/wiki/Hamming_weight#Efficient_implementation n -= (n >> 1) & 0x5555; n = (n & 0x3333) + ((n >> 2) & 0x3333); n = (n + (n >> 4)) & 0x0F0F; n = (n + (n >> 8)) & 0xFF; return n; #endif } /** Extract the nibble at a certain offset from a byte sequence. */ static unsigned char trie_key_nibble(const void *key, size_t offset) { const unsigned char *bytes = key; size_t byte = offset >> 1; // A branchless version of // if (offset & 1) { // return bytes[byte] >> 4; // } else { // return bytes[byte] & 0xF; // } unsigned int shift = (offset & 1) << 2; return (bytes[byte] >> shift) & 0xF; } /** * Finds a leaf in the trie that matches the key at every branch. If the key * exists in the trie, the representative will match the searched key. But * since only branch points are tested, it can be different from the key. In * that case, the first mismatch between the key and the representative will be * the depth at which to make a new branch to insert the key. */ static struct trie_leaf *trie_representative(const struct trie *trie, const void *key, size_t length) { uintptr_t ptr = trie->root; if (!ptr) { return NULL; } size_t offset = 0; while (!trie_is_leaf(ptr)) { struct trie_node *node = trie_decode_node(ptr); offset += node->offset; unsigned int index = 0; if ((offset >> 1) < length) { unsigned char nibble = trie_key_nibble(key, offset); unsigned int bit = 1U << nibble; if (node->bitmap & bit) { index = trie_popcount(node->bitmap & (bit - 1)); } } ptr = node->children[index]; } return trie_decode_leaf(ptr); } struct trie_leaf *trie_first_leaf(const struct trie *trie) { return trie_representative(trie, NULL, 0); } struct trie_leaf *trie_find_str(const struct trie *trie, const char *key) { return trie_find_mem(trie, key, strlen(key) + 1); } struct trie_leaf *trie_find_mem(const struct trie *trie, const void *key, size_t length) { struct trie_leaf *rep = trie_representative(trie, key, length); if (rep && rep->length == length && memcmp(rep->key, key, length) == 0) { return rep; } else { return NULL; } } struct trie_leaf *trie_find_postfix(const struct trie *trie, const char *key) { size_t length = strlen(key); struct trie_leaf *rep = trie_representative(trie, key, length + 1); if (rep && rep->length >= length && memcmp(rep->key, key, length) == 0) { return rep; } else { return NULL; } } /** * Find a leaf that may end at the current node. */ static struct trie_leaf *trie_terminal_leaf(const struct trie_node *node) { // Finding a terminating NUL byte may take two nibbles for (int i = 0; i < 2; ++i) { if (!(node->bitmap & 1)) { break; } uintptr_t ptr = node->children[0]; if (trie_is_leaf(ptr)) { return trie_decode_leaf(ptr); } else { node = trie_decode_node(ptr); } } return NULL; } /** Check if a leaf is a prefix of a search key. */ static bool trie_check_prefix(struct trie_leaf *leaf, size_t skip, const char *key, size_t length) { if (leaf && leaf->length <= length) { return memcmp(key + skip, leaf->key + skip, leaf->length - skip - 1) == 0; } else { return false; } } struct trie_leaf *trie_find_prefix(const struct trie *trie, const char *key) { uintptr_t ptr = trie->root; if (!ptr) { return NULL; } struct trie_leaf *best = NULL; size_t skip = 0; size_t length = strlen(key) + 1; size_t offset = 0; while (!trie_is_leaf(ptr)) { struct trie_node *node = trie_decode_node(ptr); offset += node->offset; if ((offset >> 1) >= length) { return best; } struct trie_leaf *leaf = trie_terminal_leaf(node); if (trie_check_prefix(leaf, skip, key, length)) { best = leaf; skip = offset >> 1; } unsigned char nibble = trie_key_nibble(key, offset); unsigned int bit = 1U << nibble; if (node->bitmap & bit) { unsigned int index = trie_popcount(node->bitmap & (bit - 1)); ptr = node->children[index]; } else { return best; } } struct trie_leaf *leaf = trie_decode_leaf(ptr); if (trie_check_prefix(leaf, skip, key, length)) { best = leaf; } return best; } /** Create a new leaf, holding a copy of the given key. */ static struct trie_leaf *new_trie_leaf(const void *key, size_t length) { struct trie_leaf *leaf = malloc(sizeof(*leaf) + length); if (leaf) { leaf->value = NULL; leaf->length = length; memcpy(leaf->key, key, length); } return leaf; } /** Compute the size of a trie node with a certain number of children. */ static size_t trie_node_size(unsigned int size) { // Empty nodes aren't supported assert(size > 0); // Node size must be a power of two assert((size & (size - 1)) == 0); return sizeof(struct trie_node) + size*sizeof(uintptr_t); } /** Find the offset of the first nibble that differs between two keys. */ static size_t trie_key_mismatch(const void *key1, const void *key2, size_t length) { const unsigned char *bytes1 = key1; const unsigned char *bytes2 = key2; size_t i = 0; size_t offset = 0; const size_t chunk = sizeof(size_t); for (; i + chunk <= length; i += chunk) { if (memcmp(bytes1 + i, bytes2 + i, chunk) != 0) { break; } } for (; i < length; ++i) { unsigned char b1 = bytes1[i], b2 = bytes2[i]; if (b1 != b2) { offset = (b1 & 0xF) == (b2 & 0xF); break; } } offset |= i << 1; return offset; } /** * Insert a key into a node. The node must not have a child in that position * already. Effectively takes a subtrie like this: * * ptr * | * v X * *--->... * | Z * +--->... * * and transforms it to: * * ptr * | * v X * *--->... * | Y * +--->key * | Z * +--->... */ static struct trie_leaf *trie_node_insert(uintptr_t *ptr, const void *key, size_t length, size_t offset) { struct trie_node *node = trie_decode_node(*ptr); unsigned int size = trie_popcount(node->bitmap); // Double the capacity every power of two if ((size & (size - 1)) == 0) { node = realloc(node, trie_node_size(2*size)); if (!node) { return NULL; } *ptr = trie_encode_node(node); } struct trie_leaf *leaf = new_trie_leaf(key, length); if (!leaf) { return NULL; } unsigned char nibble = trie_key_nibble(key, offset); unsigned int bit = 1U << nibble; // The child must not already be present assert(!(node->bitmap & bit)); node->bitmap |= bit; unsigned int index = trie_popcount(node->bitmap & (bit - 1)); uintptr_t *child = node->children + index; if (index < size) { memmove(child + 1, child, (size - index)*sizeof(*child)); } *child = trie_encode_leaf(leaf); return leaf; } /** * When the current offset exceeds OFFSET_MAX, insert "jump" nodes that bridge * the gap. This function takes a subtrie like this: * * ptr * | * v * *--->rep * * and changes it to: * * ptr ret * | | * v v * *--->*--->rep * * so that a new key can be inserted like: * * ptr ret * | | * v v X * *--->*--->rep * | Y * +--->key */ static uintptr_t *trie_jump(uintptr_t *ptr, const char *key, size_t *offset) { // We only ever need to jump to leaf nodes, since internal nodes are // guaranteed to be within OFFSET_MAX anyway assert(trie_is_leaf(*ptr)); struct trie_node *node = malloc(trie_node_size(1)); if (!node) { return NULL; } *offset += OFFSET_MAX; node->offset = OFFSET_MAX; unsigned char nibble = trie_key_nibble(key, *offset); node->bitmap = 1 << nibble; node->children[0] = *ptr; *ptr = trie_encode_node(node); return node->children; } /** * Split a node in the trie. Changes a subtrie like this: * * ptr * | * v * *...>--->rep * * into this: * * ptr * | * v X * *--->*...>--->rep * | Y * +--->key */ static struct trie_leaf *trie_split(uintptr_t *ptr, const void *key, size_t length, struct trie_leaf *rep, size_t offset, size_t mismatch) { unsigned char key_nibble = trie_key_nibble(key, mismatch); unsigned char rep_nibble = trie_key_nibble(rep->key, mismatch); assert(key_nibble != rep_nibble); struct trie_node *node = malloc(trie_node_size(2)); if (!node) { return NULL; } struct trie_leaf *leaf = new_trie_leaf(key, length); if (!leaf) { free(node); return NULL; } node->bitmap = (1 << key_nibble) | (1 << rep_nibble); size_t delta = mismatch - offset; if (!trie_is_leaf(*ptr)) { struct trie_node *child = trie_decode_node(*ptr); child->offset -= delta; } node->offset = delta; unsigned int key_index = key_nibble > rep_nibble; node->children[key_index] = trie_encode_leaf(leaf); node->children[key_index ^ 1] = *ptr; *ptr = trie_encode_node(node); return leaf; } struct trie_leaf *trie_insert_str(struct trie *trie, const char *key) { return trie_insert_mem(trie, key, strlen(key) + 1); } struct trie_leaf *trie_insert_mem(struct trie *trie, const void *key, size_t length) { struct trie_leaf *rep = trie_representative(trie, key, length); if (!rep) { struct trie_leaf *leaf = new_trie_leaf(key, length); if (leaf) { trie->root = trie_encode_leaf(leaf); } return leaf; } size_t limit = length < rep->length ? length : rep->length; size_t mismatch = trie_key_mismatch(key, rep->key, limit); if ((mismatch >> 1) >= length) { return rep; } size_t offset = 0; uintptr_t *ptr = &trie->root; while (!trie_is_leaf(*ptr)) { struct trie_node *node = trie_decode_node(*ptr); if (offset + node->offset > mismatch) { break; } offset += node->offset; unsigned char nibble = trie_key_nibble(key, offset); unsigned int bit = 1U << nibble; if (node->bitmap & bit) { assert(offset < mismatch); unsigned int index = trie_popcount(node->bitmap & (bit - 1)); ptr = node->children + index; } else { assert(offset == mismatch); return trie_node_insert(ptr, key, length, offset); } } while (mismatch - offset > OFFSET_MAX) { ptr = trie_jump(ptr, key, &offset); if (!ptr) { return NULL; } } return trie_split(ptr, key, length, rep, offset, mismatch); } /** Free a chain of singleton nodes. */ static void trie_free_singletons(uintptr_t ptr) { while (!trie_is_leaf(ptr)) { struct trie_node *node = trie_decode_node(ptr); // Make sure the bitmap is a power of two, i.e. it has just one child assert((node->bitmap & (node->bitmap - 1)) == 0); ptr = node->children[0]; free(node); } free(trie_decode_leaf(ptr)); } /** * Try to collapse a two-child node like: * * parent child * | | * v v * *----->*----->*----->leaf * | * +----->other * * into * * parent * | * v * other */ static int trie_collapse_node(uintptr_t *parent, struct trie_node *parent_node, unsigned int child_index) { uintptr_t other = parent_node->children[child_index ^ 1]; if (!trie_is_leaf(other)) { struct trie_node *other_node = trie_decode_node(other); if (other_node->offset + parent_node->offset <= OFFSET_MAX) { other_node->offset += parent_node->offset; } else { return -1; } } *parent = other; free(parent_node); return 0; } void trie_remove(struct trie *trie, struct trie_leaf *leaf) { uintptr_t *child = &trie->root; uintptr_t *parent = NULL; unsigned int child_bit = 0, child_index = 0; size_t offset = 0; while (!trie_is_leaf(*child)) { struct trie_node *node = trie_decode_node(*child); offset += node->offset; assert((offset >> 1) < leaf->length); unsigned char nibble = trie_key_nibble(leaf->key, offset); unsigned int bit = 1U << nibble; unsigned int bitmap = node->bitmap; assert(bitmap & bit); unsigned int index = trie_popcount(bitmap & (bit - 1)); // Advance the parent pointer, unless this node had only one child if (bitmap & (bitmap - 1)) { parent = child; child_bit = bit; child_index = index; } child = node->children + index; } assert(trie_decode_leaf(*child) == leaf); if (!parent) { trie_free_singletons(trie->root); trie->root = 0; return; } struct trie_node *node = trie_decode_node(*parent); child = node->children + child_index; trie_free_singletons(*child); node->bitmap ^= child_bit; unsigned int parent_size = trie_popcount(node->bitmap); assert(parent_size > 0); if (parent_size == 1 && trie_collapse_node(parent, node, child_index) == 0) { return; } if (child_index < parent_size) { memmove(child, child + 1, (parent_size - child_index)*sizeof(*child)); } if ((parent_size & (parent_size - 1)) == 0) { node = realloc(node, trie_node_size(parent_size)); if (node) { *parent = trie_encode_node(node); } } } /** Free an encoded pointer to a node. */ static void free_trie_ptr(uintptr_t ptr) { if (trie_is_leaf(ptr)) { free(trie_decode_leaf(ptr)); } else { struct trie_node *node = trie_decode_node(ptr); size_t size = trie_popcount(node->bitmap); for (size_t i = 0; i < size; ++i) { free_trie_ptr(node->children[i]); } free(node); } } void trie_destroy(struct trie *trie) { if (trie->root) { free_trie_ptr(trie->root); } } /**************************************************************************** * bfs * * 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 #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 * * Copyright (C) 2016-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 #include #include #include #include #include #include #include #include #include #include #include #if BFS_HAS_SYS_PARAM # include #endif #if BFS_HAS_SYS_SYSMACROS # include #elif BFS_HAS_SYS_MKDEV # include #endif int xreaddir(DIR *dir, struct dirent **de) { while (true) { errno = 0; *de = readdir(dir); if (*de) { const char *name = (*de)->d_name; if (name[0] != '.' || (name[1] != '\0' && (name[1] != '.' || name[2] != '\0'))) { return 0; } } else if (errno != 0) { return -1; } else { return 0; } } } char *xreadlinkat(int fd, const char *path, size_t size) { ++size; // NUL-terminator ssize_t len; char *name = NULL; while (true) { char *new_name = realloc(name, size); if (!new_name) { goto error; } name = new_name; len = readlinkat(fd, path, name, size); if (len < 0) { goto error; } else if (len >= size) { size *= 2; } else { break; } } name[len] = '\0'; return name; error: free(name); return NULL; } bool isopen(int fd) { return fcntl(fd, F_GETFD) >= 0 || errno != EBADF; } int redirect(int fd, const char *path, int flags, ...) { mode_t mode = 0; if (flags & O_CREAT) { va_list args; va_start(args, flags); // Use int rather than mode_t, because va_arg must receive a // fully-promoted type mode = va_arg(args, int); va_end(args); } int ret = open(path, flags, mode); if (ret >= 0 && ret != fd) { int orig = ret; ret = dup2(orig, fd); close(orig); } return ret; } int dup_cloexec(int fd) { #ifdef F_DUPFD_CLOEXEC return fcntl(fd, F_DUPFD_CLOEXEC, 0); #else int ret = dup(fd); if (ret < 0) { return -1; } if (fcntl(ret, F_SETFD, FD_CLOEXEC) == -1) { close(ret); return -1; } return ret; #endif } int pipe_cloexec(int pipefd[2]) { #if __linux__ || (BSD && !__APPLE__) return pipe2(pipefd, O_CLOEXEC); #else if (pipe(pipefd) != 0) { return -1; } if (fcntl(pipefd[0], F_SETFD, FD_CLOEXEC) == -1 || fcntl(pipefd[1], F_SETFD, FD_CLOEXEC) == -1) { int error = errno; close(pipefd[1]); close(pipefd[0]); errno = error; return -1; } return 0; #endif } char *xregerror(int err, const regex_t *regex) { size_t len = regerror(err, regex, NULL, 0); char *str = malloc(len); if (str) { regerror(err, regex, str, len); } return str; } int xlocaltime(const time_t *timep, struct tm *result) { // Should be called before localtime_r() according to POSIX.1-2004 tzset(); if (localtime_r(timep, result)) { return 0; } else { return -1; } } void format_mode(mode_t mode, char str[11]) { strcpy(str, "----------"); switch (bftw_mode_typeflag(mode)) { case BFTW_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; } /** Read a line from standard input. */ static char *xgetline(void) { char *line = dstralloc(0); if (!line) { return NULL; } while (true) { int c = getchar(); if (c == '\n' || c == EOF) { break; } if (dstrapp(&line, c) != 0) { goto error; } } return line; error: dstrfree(line); return NULL; } /** Compile and execute a regular expression for xrpmatch(). */ static int xrpregex(nl_item item, const char *response) { const char *pattern = nl_langinfo(item); if (!pattern) { return REG_BADPAT; } regex_t regex; int ret = regcomp(®ex, pattern, REG_EXTENDED); if (ret != 0) { return ret; } ret = regexec(®ex, response, 0, NULL, 0); regfree(®ex); return ret; } /** Check if a response is affirmative or negative. */ static int xrpmatch(const char *response) { int ret = xrpregex(NOEXPR, response); if (ret == 0) { return 0; } else if (ret != REG_NOMATCH) { return -1; } ret = xrpregex(YESEXPR, response); if (ret == 0) { return 1; } else if (ret != REG_NOMATCH) { return -1; } // Failsafe: always handle y/n char c = response[0]; if (c == 'n' || c == 'N') { return 0; } else if (c == 'y' || c == 'Y') { return 1; } else { return -1; } } int ynprompt() { fflush(stderr); char *line = xgetline(); int ret = line ? xrpmatch(line) : -1; dstrfree(line); return ret; } dev_t bfs_makedev(int ma, int mi) { #ifdef makedev return makedev(ma, mi); #else return (ma << 8) | mi; #endif } int bfs_major(dev_t dev) { #ifdef major return major(dev); #else return dev >> 8; #endif } int bfs_minor(dev_t dev) { #ifdef minor return minor(dev); #else return dev & 0xFF; #endif }