Something went wrong. Try again.
A breadth-first version of the UNIX find command tavianator.com/projects/bfs.html
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625// Copyright © Tavian Barnes <tavianator@tavianator.com>// SPDX-License-Identifier: 0BSD
/** * Standard library wrappers and polyfills. */
#ifndef BFS_BFSTD_H#define BFS_BFSTD_H
#include "bfs.h"
#include <stddef.h>
#include <ctype.h>
/** * Work around https://github.com/llvm/llvm-project/issues/65532 by forcing a * function, not a macro, to be called. */#if __FreeBSD__ && __SANITIZE_MEMORY__# define BFS_INTERCEPT(fn) (fn)#else# define BFS_INTERCEPT(fn) fn#endif
/** * Wrap isalpha()/isdigit()/etc. */#define BFS_ISCTYPE(fn, c) BFS_INTERCEPT(fn)((unsigned char)(c))
#define xisalnum(c) BFS_ISCTYPE(isalnum, c)#define xisalpha(c) BFS_ISCTYPE(isalpha, c)#define xisascii(c) BFS_ISCTYPE(isascii, c)#define xiscntrl(c) BFS_ISCTYPE(iscntrl, c)#define xisdigit(c) BFS_ISCTYPE(isdigit, c)#define xislower(c) BFS_ISCTYPE(islower, c)#define xisgraph(c) BFS_ISCTYPE(isgraph, c)#define xisprint(c) BFS_ISCTYPE(isprint, c)#define xispunct(c) BFS_ISCTYPE(ispunct, c)#define xisspace(c) BFS_ISCTYPE(isspace, c)#define xisupper(c) BFS_ISCTYPE(isupper, c)#define xisxdigit(c) BFS_ISCTYPE(isxdigit, c)
// #include <errno.h>
/** * Check if an error code is "like" another one. For example, ENOTDIR is * like ENOENT because they can both be triggered by non-existent paths. * * @error * The error code to check. * @category * The category to test for. Known categories include ENOENT and * ENAMETOOLONG. * @return * Whether the error belongs to the given category. */bool error_is_like(int error, int category);
/** * Equivalent to error_is_like(errno, category). */bool errno_is_like(int category);
/** * Apply the "negative errno" convention. * * @ret * The return value of the attempted operation. * @return * ret, if non-negative, otherwise -errno. */int try(int ret);
#include <fcntl.h>
#ifndef O_EXEC# ifdef O_PATH# define O_EXEC O_PATH# else# define O_EXEC O_RDONLY# endif#endif
#ifndef O_SEARCH# ifdef O_PATH# define O_SEARCH O_PATH# else# define O_SEARCH O_RDONLY# endif#endif
#ifndef O_DIRECTORY# define O_DIRECTORY 0#endif
#include <fnmatch.h>
#if !defined(FNM_CASEFOLD) && defined(FNM_IGNORECASE)# define FNM_CASEFOLD FNM_IGNORECASE#endif
// #include <libgen.h>
/** * Re-entrant dirname() variant that always allocates a copy. * * @path * The path in question. * @return * The parent directory of the path. */char *xdirname(const char *path);
/** * Re-entrant basename() variant that always allocates a copy. * * @path * The path in question. * @return * The final component of the path. */char *xbasename(const char *path);
/** * Find the offset of the final component of a path. * * @path * The path in question. * @return * The offset of the basename. */size_t xbaseoff(const char *path);
#include <stdio.h>
/** * fopen() variant that takes open() style flags. * * @path * The path to open. * @flags * Flags to pass to open(). */FILE *xfopen(const char *path, int flags);
/** * Convenience wrapper for getdelim(). * * @file * The file to read. * @delim * The delimiter character to split on. * @return * The read chunk (without the delimiter), allocated with malloc(). * NULL is returned on error (errno != 0) or end of file (errno == 0). */char *xgetdelim(FILE *file, char delim);
// #include <stdlib.h>
/** * Wrapper for getprogname() or equivalent functionality. * * @return * The basename of the currently running program. */const char *xgetprogname(void);
/** * Like xstrtol(), but for short. */int xstrtos(const char *str, char **end, int base, short *value);
/** * Like xstrtol(), but for int. */int xstrtoi(const char *str, char **end, int base, int *value);
/** * Wrapper for strtol() that forbids leading spaces. */int xstrtol(const char *str, char **end, int base, long *value);
/** * Wrapper for strtoll() that forbids leading spaces. */int xstrtoll(const char *str, char **end, int base, long long *value);
/** * Like xstrtoul(), but for unsigned short. */int xstrtous(const char *str, char **end, int base, unsigned short *value);
/** * Like xstrtoul(), but for unsigned int. */int xstrtoui(const char *str, char **end, int base, unsigned int *value);
/** * Wrapper for strtoul() that forbids leading spaces, negatives. */int xstrtoul(const char *str, char **end, int base, unsigned long *value);
/** * Wrapper for strtoull() that forbids leading spaces, negatives. */int xstrtoull(const char *str, char **end, int base, unsigned long long *value);
/** * Wrapper for strtof() that forbids leading spaces. */int xstrtof(const char *str, char **end, float *value);
/** * Wrapper for strtod() that forbids leading spaces. */int xstrtod(const char *str, char **end, double *value);
/** * Process a yes/no prompt. * * @return 1 for yes, 0 for no, and -1 for unknown. */int ynprompt(void);
// #include <string.h>
/** * Get the length of the pure-ASCII prefix of a string. */size_t asciilen(const char *str);
/** * Get the length of the pure-ASCII prefix of a string. * * @str * The string to check. * @n * The maximum prefix length. */size_t asciinlen(const char *str, size_t n);
/** * Allocate a copy of a region of memory. * * @src * The memory region to copy. * @size * The size of the memory region. * @return * A copy of the region, allocated with malloc(), or NULL on failure. */void *xmemdup(const void *src, size_t size);
/** * A nice string copying function. * * @dest * The NUL terminator of the destination string, or @end if it is * already truncated. * @end * The end of the destination buffer. * @src * The string to copy from. * @return * The new NUL terminator of the destination, or @end on truncation. */char *xstpecpy(char *dest, char *end, const char *src);
/** * A nice string copying function. * * @dest * The NUL terminator of the destination string, or @end if it is * already truncated. * @end * The end of the destination buffer. * @src * The string to copy from. * @n * The maximum number of characters to copy. * @return * The new NUL terminator of the destination, or @end on truncation. */char *xstpencpy(char *dest, char *end, const char *src, size_t n);
/** * Thread-safe strerror(). * * @errnum * An error number. * @return * A string describing that error, which remains valid until the next * xstrerror() call in the same thread. */const char *xstrerror(int errnum);
/** * Shorthand for xstrerror(errno). */const char *errstr(void);
/** * Format a mode like ls -l (e.g. -rw-r--r--). * * @mode * The mode to format. * @str * The string to hold the formatted mode. */void xstrmode(mode_t mode, char str[11]);
#include <sys/resource.h>
/** * Compare two rlim_t values, accounting for infinite limits. */int rlim_cmp(rlim_t a, rlim_t b);
/** * @return The minimum of two rlim_t values. */rlim_t rlim_min(rlim_t a, rlim_t b);
#include <sys/types.h>
/** * Portable version of makedev(). */dev_t xmakedev(int ma, int mi);
/** * Portable version of major(). */int xmajor(dev_t dev);
/** * Portable version of minor(). */int xminor(dev_t dev);
// #include <sys/stat.h>
/** * Get the access/change/modification time from a struct stat. */#if BFS_HAS_ST_ACMTIM# define ST_ATIM(sb) (sb).st_atim# define ST_CTIM(sb) (sb).st_ctim# define ST_MTIM(sb) (sb).st_mtim#elif BFS_HAS_ST_ACMTIMESPEC# define ST_ATIM(sb) (sb).st_atimespec# define ST_CTIM(sb) (sb).st_ctimespec# define ST_MTIM(sb) (sb).st_mtimespec#else# define ST_ATIM(sb) ((struct timespec) { .tv_sec = (sb).st_atime })# define ST_CTIM(sb) ((struct timespec) { .tv_sec = (sb).st_ctime })# define ST_MTIM(sb) ((struct timespec) { .tv_sec = (sb).st_mtime })#endif
// #include <sys/wait.h>
/** * waitpid() wrapper that handles EINTR. */pid_t xwaitpid(pid_t pid, int *status, int flags);
#include <sys/ioctl.h> // May be necessary for struct winsize#include <termios.h>
/** * Open the controlling terminal. * * @flags * The open() flags. * @return * An open file descriptor, or -1 on failure. */int open_cterm(int flags);
/** * tcgetwinsize()/ioctl(TIOCGWINSZ) wrapper. */int xtcgetwinsize(int fd, struct winsize *ws);
/** * tcsetwinsize()/ioctl(TIOCSWINSZ) wrapper. */int xtcsetwinsize(int fd, const struct winsize *ws);
// #include <unistd.h>
/** * Like dup(), but set the FD_CLOEXEC flag. * * @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. * * @pipefd * The array to hold the two file descriptors. * @return * 0 on success, -1 on failure. */int pipe_cloexec(int pipefd[2]);
/** * A safe version of read() that handles interrupted system calls and partial * reads. * * @return * The number of bytes read. A value != nbytes indicates an error * (errno != 0) or end of file (errno == 0). */size_t xread(int fd, void *buf, size_t nbytes);
/** * A safe version of write() that handles interrupted system calls and partial * writes. * * @return * The number of bytes written. A value != nbytes indicates an error. */size_t xwrite(int fd, const void *buf, size_t nbytes);
/** * close() variant that preserves errno. * * @fd * The file descriptor to close. */void close_quietly(int fd);
/** * close() wrapper that asserts the file descriptor is valid. * * @fd * The file descriptor to close. * @return * 0 on success, or -1 on error. */int xclose(int fd);
/** * Wrapper for faccessat() that handles some portability issues. */int xfaccessat(int fd, const char *path, int amode);
/** * readlinkat() wrapper that dynamically allocates the result. * * @fd * The base directory descriptor. * @path * The path to the link, relative to fd. * @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);
/** * Wrapper for confstr() that allocates with malloc(). * * @name * The ID of the confstr to look up. * @return * The value of the confstr, or NULL on failure. */char *xconfstr(int name);
/** * Portability wrapper for strtofflags(). * * @str * The string to parse. The pointee will be advanced to the first * invalid position on error. * @set * The flags that are set in the string. * @clear * The flags that are cleared in the string. * @return * 0 on success, -1 on failure. */int xstrtofflags(const char **str, unsigned long long *set, unsigned long long *clear);
/** * Wrapper for sysconf() that works around an MSan bug. */long xsysconf(int name);
/** * Check for a POSIX option[1] at runtime. * * [1]: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/V1_chap02.html#tag_02_01_06 * * @name * The symbolic name of the POSIX option (e.g. SPAWN). * @return * The value of the option, either -1 or a date like 202405. */#define sysoption(name) \ (_POSIX_##name == 0 ? xsysconf(_SC_##name) : _POSIX_##name)
/** * Get the number of CPU threads available to the current process. */long nproc(void);
#include <wchar.h>
/** * Error-recovering mbrtowc() wrapper. * * @str * The string to convert. * @i * The current index. * @len * The length of the string. * @mb * The multi-byte decoding state. * @return * The wide character at index *i, or WEOF if decoding fails. In either * case, *i will be advanced to the next multi-byte character. */wint_t xmbrtowc(const char *str, size_t *i, size_t len, mbstate_t *mb);
/** * wcswidth() variant that works on narrow strings. * * @str * The string to measure. * @return * The likely width of that string in a terminal. */size_t xstrwidth(const char *str);
/** * wcwidth() wrapper that works around LLVM bug #65532. */#define xwcwidth BFS_INTERCEPT(wcwidth)
#include <wctype.h>
/** * Wrap iswalpha()/iswdigit()/etc. */#define BFS_ISWCTYPE(fn, c) BFS_INTERCEPT(fn)(c)
#define xiswalnum(c) BFS_ISWCTYPE(iswalnum, c)#define xiswalpha(c) BFS_ISWCTYPE(iswalpha, c)#define xiswcntrl(c) BFS_ISWCTYPE(iswcntrl, c)#define xiswdigit(c) BFS_ISWCTYPE(iswdigit, c)#define xiswlower(c) BFS_ISWCTYPE(iswlower, c)#define xiswgraph(c) BFS_ISWCTYPE(iswgraph, c)#define xiswprint(c) BFS_ISWCTYPE(iswprint, c)#define xiswpunct(c) BFS_ISWCTYPE(iswpunct, c)#define xiswspace(c) BFS_ISWCTYPE(iswspace, c)#define xiswupper(c) BFS_ISWCTYPE(iswupper, c)#define xiswxdigit(c) BFS_ISWCTYPE(iswxdigit, c)
// #include <wordexp.h>
/** * Flags for wordesc(). */enum wesc_flags { /** * Escape special characters so that the shell will treat the escaped * string as a single word. */ WESC_SHELL = 1 << 0, /** * Escape special characters so that the escaped string is safe to print * to a TTY. */ WESC_TTY = 1 << 1,};
/** * Escape a string as a single shell word. * * @dest * The destination string to fill. * @end * The end of the destination buffer. * @src * The string to escape. * @flags * Controls which characters to escape. * @return * The new NUL terminator of the destination, or @end on truncation. */char *wordesc(char *dest, char *end, const char *str, enum wesc_flags flags);
/** * Escape a string as a single shell word. * * @dest * The destination string to fill. * @end * The end of the destination buffer. * @src * The string to escape. * @n * The maximum length of the string. * @flags * Controls which characters to escape. * @return * The new NUL terminator of the destination, or @end on truncation. */char *wordnesc(char *dest, char *end, const char *str, size_t n, enum wesc_flags flags);
#endif // BFS_BFSTD_H