diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/bar.c | 16 | ||||
-rw-r--r-- | src/bfs.h | 25 | ||||
-rw-r--r-- | src/bfstd.c | 243 | ||||
-rw-r--r-- | src/bfstd.h | 68 | ||||
-rw-r--r-- | src/bftw.c | 5 | ||||
-rw-r--r-- | src/bit.h | 54 | ||||
-rw-r--r-- | src/color.c | 292 | ||||
-rw-r--r-- | src/color.h | 2 | ||||
-rw-r--r-- | src/ctx.c | 22 | ||||
-rw-r--r-- | src/ctx.h | 2 | ||||
-rw-r--r-- | src/diag.c | 29 | ||||
-rw-r--r-- | src/diag.h | 86 | ||||
-rw-r--r-- | src/dstring.c | 2 | ||||
-rw-r--r-- | src/eval.c | 53 | ||||
-rw-r--r-- | src/expr.h | 3 | ||||
-rw-r--r-- | src/ioq.c | 22 | ||||
-rw-r--r-- | src/list.h | 31 | ||||
-rw-r--r-- | src/mtab.c | 11 | ||||
-rw-r--r-- | src/opt.c | 19 | ||||
-rw-r--r-- | src/parse.c | 391 | ||||
-rw-r--r-- | src/prelude.h | 3 | ||||
-rw-r--r-- | src/sighook.c | 198 | ||||
-rw-r--r-- | src/sighook.h | 8 | ||||
-rw-r--r-- | src/stat.c | 31 | ||||
-rw-r--r-- | src/stat.h | 9 | ||||
-rw-r--r-- | src/trie.c | 123 | ||||
-rw-r--r-- | src/trie.h | 58 | ||||
-rw-r--r-- | src/xspawn.c | 95 |
28 files changed, 1374 insertions, 527 deletions
@@ -18,7 +18,6 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> -#include <sys/ioctl.h> #include <termios.h> #include <unistd.h> @@ -33,25 +32,14 @@ struct bfs_bar { /** Get the terminal size, if possible. */ static int bfs_bar_getsize(struct bfs_bar *bar) { -#if BFS_HAS_TCGETWINSIZE || defined(TIOCGWINSZ) struct winsize ws; - -# if BFS_HAS_TCGETWINSIZE - int ret = tcgetwinsize(bar->fd, &ws); -# else - int ret = ioctl(bar->fd, TIOCGWINSZ, &ws); -# endif - if (ret != 0) { - return ret; + if (xtcgetwinsize(bar->fd, &ws) != 0) { + return -1; } store(&bar->width, ws.ws_col, relaxed); store(&bar->height, ws.ws_row, relaxed); return 0; -#else - errno = ENOTSUP; - return -1; -#endif } /** Write a string to the status bar (async-signal-safe). */ @@ -202,7 +202,10 @@ extern const char bfs_ldlibs[]; * Disabled on TSan due to https://github.com/google/sanitizers/issues/342. */ #ifndef BFS_USE_TARGET_CLONES -# if __has_attribute(target_clones) && (__GLIBC__ || __FreeBSD__) && !__SANITIZE_THREAD__ +# if __has_attribute(target_clones) \ + && (__GLIBC__ || __FreeBSD__) \ + && !__SANITIZE_THREAD__ \ + && !__SANITIZE_TYPE__ # define BFS_USE_TARGET_CLONES true # else # define BFS_USE_TARGET_CLONES false @@ -218,4 +221,24 @@ extern const char bfs_ldlibs[]; # define _target_clones(...) #endif +/** + * Mark the size of a flexible array member. + */ +#if __has_attribute(counted_by) +# define _counted_by(...) __attribute__((counted_by(__VA_ARGS__))) +#else +# define _counted_by(...) +#endif + +/** + * Optimization hint to not unroll a loop. + */ +#if BFS_HAS_PRAGMA_NOUNROLL +# define _nounroll _Pragma("nounroll") +#elif __GNUC__ && !__clang__ +# define _nounroll _Pragma("GCC unroll 0") +#else +# define _nounroll +#endif + #endif // BFS_H diff --git a/src/bfstd.c b/src/bfstd.c index 738c956..b78af7a 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -17,15 +17,18 @@ #include <locale.h> #include <nl_types.h> #include <pthread.h> +#include <sched.h> #include <stddef.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <sys/ioctl.h> #include <sys/resource.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/wait.h> +#include <termios.h> #include <unistd.h> #include <wchar.h> @@ -186,16 +189,6 @@ char *xgetdelim(FILE *file, char delim) { } } -int open_cterm(int flags) { - char path[L_ctermid]; - if (ctermid(path) == NULL || strlen(path) == 0) { - errno = ENOTTY; - return -1; - } - - return open(path, flags); -} - const char *xgetprogname(void) { const char *cmd = NULL; #if BFS_HAS_GETPROGNAME @@ -242,6 +235,36 @@ static int xstrtox_epilogue(const char *str, char **end, char *endp) { return 0; } +int xstrtos(const char *str, char **end, int base, short *value) { + long n; + if (xstrtol(str, end, base, &n) != 0) { + return -1; + } + + if (n < SHRT_MIN || n > SHRT_MAX) { + errno = ERANGE; + return -1; + } + + *value = n; + return 0; +} + +int xstrtoi(const char *str, char **end, int base, int *value) { + long n; + if (xstrtol(str, end, base, &n) != 0) { + return -1; + } + + if (n < INT_MIN || n > INT_MAX) { + errno = ERANGE; + return -1; + } + + *value = n; + return 0; +} + int xstrtol(const char *str, char **end, int base, long *value) { if (xstrtox_prologue(str) != 0) { return -1; @@ -282,6 +305,70 @@ int xstrtod(const char *str, char **end, double *value) { return xstrtox_epilogue(str, end, endp); } +int xstrtous(const char *str, char **end, int base, unsigned short *value) { + unsigned long n; + if (xstrtoul(str, end, base, &n) != 0) { + return -1; + } + + if (n > USHRT_MAX) { + errno = ERANGE; + return -1; + } + + *value = n; + return 0; +} + +int xstrtoui(const char *str, char **end, int base, unsigned int *value) { + unsigned long n; + if (xstrtoul(str, end, base, &n) != 0) { + return -1; + } + + if (n > UINT_MAX) { + errno = ERANGE; + return -1; + } + + *value = n; + return 0; +} + +/** Common epilogue for xstrtou*() wrappers. */ +static int xstrtoux_epilogue(const char *str, char **end, char *endp) { + if (xstrtox_epilogue(str, end, endp) != 0) { + return -1; + } + + if (str[0] == '-') { + errno = ERANGE; + return -1; + } + + return 0; +} + +int xstrtoul(const char *str, char **end, int base, unsigned long *value) { + if (xstrtox_prologue(str) != 0) { + return -1; + } + + char *endp; + *value = strtoul(str, &endp, base); + return xstrtoux_epilogue(str, end, endp); +} + +int xstrtoull(const char *str, char **end, int base, unsigned long long *value) { + if (xstrtox_prologue(str) != 0) { + return -1; + } + + char *endp; + *value = strtoull(str, &endp, base); + return xstrtoux_epilogue(str, end, endp); +} + /** Compile and execute a regular expression for xrpmatch(). */ static int xrpregex(nl_item item, const char *response) { const char *pattern = nl_langinfo(item); @@ -557,6 +644,32 @@ pid_t xwaitpid(pid_t pid, int *status, int flags) { return ret; } +int open_cterm(int flags) { + char path[L_ctermid]; + if (ctermid(path) == NULL || strlen(path) == 0) { + errno = ENOTTY; + return -1; + } + + return open(path, flags); +} + +int xtcgetwinsize(int fd, struct winsize *ws) { +#if BFS_HAS_TCGETWINSIZE + return tcgetwinsize(fd, ws); +#else + return ioctl(fd, TIOCGWINSZ, ws); +#endif +} + +int xtcsetwinsize(int fd, const struct winsize *ws) { +#if BFS_HAS_TCSETWINSIZE + return tcsetwinsize(fd, ws); +#else + return ioctl(fd, TIOCSWINSZ, ws); +#endif +} + int dup_cloexec(int fd) { #ifdef F_DUPFD_CLOEXEC return fcntl(fd, F_DUPFD_CLOEXEC, 0); @@ -775,41 +888,103 @@ long xsysconf(int name) { return ret; } -size_t asciilen(const char *str) { - return asciinlen(str, strlen(str)); -} +#if BFS_HAS_SCHED_GETAFFINITY +/** Get the CPU count in an affinity mask of the given size. */ +static long bfs_sched_getaffinity(size_t size) { + cpu_set_t set, *pset = &set; -size_t asciinlen(const char *str, size_t n) { - size_t i = 0; + if (size > sizeof(set)) { + pset = malloc(size); + if (!pset) { + return -1; + } + } -#if SIZE_WIDTH % 8 == 0 - // Word-at-a-time isascii() - for (size_t word; i + sizeof(word) <= n; i += sizeof(word)) { - memcpy(&word, str + i, sizeof(word)); + long ret = -1; + if (sched_getaffinity(0, size, pset) == 0) { +# ifdef CPU_COUNT_S + ret = CPU_COUNT_S(size, pset); +# else + bfs_assert(size <= sizeof(set)); + ret = CPU_COUNT(pset); +# endif + } - const size_t mask = (SIZE_MAX / 0xFF) << 7; // 0x808080... - word &= mask; - if (!word) { - continue; - } + if (pset != &set) { + free(pset); + } + return ret; +} +#endif + +long nproc(void) { + long ret = 0; -#if ENDIAN_NATIVE == ENDIAN_BIG - word = bswap(word); -#elif ENDIAN_NATIVE != ENDIAN_LITTLE +#if BFS_HAS_SCHED_GETAFFINITY + size_t size = sizeof(cpu_set_t); + do { + ret = bfs_sched_getaffinity(size); + +# ifdef CPU_COUNT_S + // On Linux, sched_getaffinity(2) says: + // + // When working on systems with large kernel CPU affinity masks, one must + // dynamically allocate the mask argument (see CPU_ALLOC(3)). Currently, + // the only way to do this is by probing for the size of the required mask + // using sched_getaffinity() calls with increasing mask sizes (until the + // call does not fail with the error EINVAL). + size *= 2; +# else + // No support for dynamically-sized CPU masks break; +# endif + } while (ret < 0 && errno == EINVAL); #endif - size_t first = trailing_zeros(word) / 8; - return i + first; + if (ret < 1) { + ret = xsysconf(_SC_NPROCESSORS_ONLN); } -#endif - for (; i < n; ++i) { - if (!xisascii(str[i])) { - break; - } + if (ret < 1) { + ret = 1; } + return ret; +} + +size_t asciilen(const char *str) { + return asciinlen(str, strlen(str)); +} + +size_t asciinlen(const char *str, size_t n) { + const unsigned char *ustr = (const unsigned char *)str; + size_t i = 0; + + // Word-at-a-time isascii() +#define CHUNK(n) CHUNK_(uint##n##_t, load8_leu##n) +#define CHUNK_(type, load8) \ + (n - i >= sizeof(type)) { \ + type word = load8(ustr + i); \ + type mask = (((type)-1) / 0xFF) << 7; /* 0x808080.. */ \ + word &= mask; \ + i += trailing_zeros(word) / 8; \ + if (word) { \ + return i; \ + } \ + } + +#if SIZE_WIDTH >= 64 + while CHUNK(64); + if CHUNK(32); +#else + while CHUNK(32); +#endif + if CHUNK(16); + if CHUNK(8); + +#undef CHUNK_ +#undef CHUNK + return i; } diff --git a/src/bfstd.h b/src/bfstd.h index 84f92ec..15dd949 100644 --- a/src/bfstd.h +++ b/src/bfstd.h @@ -158,16 +158,6 @@ FILE *xfopen(const char *path, int flags); */ char *xgetdelim(FILE *file, char delim); -/** - * Open the controlling terminal. - * - * @flags - * The open() flags. - * @return - * An open file descriptor, or -1 on failure. - */ -int open_cterm(int flags); - // #include <stdlib.h> /** @@ -179,6 +169,16 @@ int open_cterm(int flags); 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); @@ -189,6 +189,26 @@ int xstrtol(const char *str, char **end, int base, long *value); 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); @@ -342,6 +362,29 @@ int xminor(dev_t dev); */ 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> /** @@ -463,6 +506,11 @@ long xsysconf(int name); #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> /** @@ -253,7 +253,7 @@ struct bftw_file { /** The length of the file's name. */ size_t namelen; /** The file's name. */ - char name[]; + char name[]; // _counted_by(namelen + 1) }; /** @@ -1485,7 +1485,8 @@ fail: /** Check if we should stat() a file asynchronously. */ static bool bftw_should_ioq_stat(struct bftw_state *state, struct bftw_file *file) { - // To avoid surprising users too much, process the roots in order + // POSIX wants the root paths to be processed in order + // See https://www.austingroupbugs.net/view.php?id=1859 if (file->depth == 0) { return false; } @@ -148,7 +148,7 @@ # define INTMAX_WIDTH UINTMAX_WIDTH #endif -// C23 polyfill: byte order +// N3022 polyfill: byte order #ifdef __STDC_ENDIAN_LITTLE__ # define ENDIAN_LITTLE __STDC_ENDIAN_LITTLE__ @@ -250,6 +250,58 @@ static inline uint8_t bswap_u8(uint8_t n) { */ #define bswap(n) UINT_SELECT(n, bswap)(n) +#define LOAD8_LEU8(ptr, i, n) ((uint##n##_t)((const unsigned char *)ptr)[(i) / 8] << (i)) +#define LOAD8_BEU8(ptr, i, n) ((uint##n##_t)((const unsigned char *)ptr)[(i) / 8] << (n - (i) - 8)) + +/** Load a little-endian 8-bit word. */ +static inline uint8_t load8_leu8(const void *ptr) { + return LOAD8_LEU8(ptr, 0, 8); +} + +/** Load a big-endian 8-bit word. */ +static inline uint8_t load8_beu8(const void *ptr) { + return LOAD8_BEU8(ptr, 0, 8); +} + +#define LOAD8_LEU16(ptr, i, n) (LOAD8_LEU8(ptr, i, n) | LOAD8_LEU8(ptr, i + 8, n)) +#define LOAD8_BEU16(ptr, i, n) (LOAD8_BEU8(ptr, i, n) | LOAD8_BEU8(ptr, i + 8, n)) + +/** Load a little-endian 16-bit word. */ +static inline uint16_t load8_leu16(const void *ptr) { + return LOAD8_LEU16(ptr, 0, 16); +} + +/** Load a big-endian 16-bit word. */ +static inline uint16_t load8_beu16(const void *ptr) { + return LOAD8_BEU16(ptr, 0, 16); +} + +#define LOAD8_LEU32(ptr, i, n) (LOAD8_LEU16(ptr, i, n) | LOAD8_LEU16(ptr, i + 16, n)) +#define LOAD8_BEU32(ptr, i, n) (LOAD8_BEU16(ptr, i, n) | LOAD8_BEU16(ptr, i + 16, n)) + +/** Load a little-endian 32-bit word. */ +static inline uint32_t load8_leu32(const void *ptr) { + return LOAD8_LEU32(ptr, 0, 32); +} + +/** Load a big-endian 32-bit word. */ +static inline uint32_t load8_beu32(const void *ptr) { + return LOAD8_BEU32(ptr, 0, 32); +} + +#define LOAD8_LEU64(ptr, i, n) (LOAD8_LEU32(ptr, i, n) | LOAD8_LEU32(ptr, i + 32, n)) +#define LOAD8_BEU64(ptr, i, n) (LOAD8_BEU32(ptr, i, n) | LOAD8_BEU32(ptr, i + 32, n)) + +/** Load a little-endian 64-bit word. */ +static inline uint64_t load8_leu64(const void *ptr) { + return LOAD8_LEU64(ptr, 0, 64); +} + +/** Load a big-endian 64-bit word. */ +static inline uint64_t load8_beu64(const void *ptr) { + return LOAD8_BEU64(ptr, 0, 64); +} + // C23 polyfill: bit utilities #if __STDC_VERSION_STDBIT_H__ >= C23 diff --git a/src/color.c b/src/color.c index fdaf2eb..926cf2b 100644 --- a/src/color.c +++ b/src/color.c @@ -32,7 +32,7 @@ struct esc_seq { /** The length of the escape sequence. */ size_t len; /** The escape sequence itself, without a terminating NUL. */ - char seq[]; + char seq[] _counted_by(len); }; /** @@ -48,7 +48,7 @@ struct ext_color { /** Whether the comparison should be case-sensitive. */ bool case_sensitive; /** The extension to match (NUL-terminated). */ - char ext[]; + char ext[]; // _counted_by(len + 1); }; struct colors { @@ -103,6 +103,8 @@ struct colors { struct esc_seq *pipe; struct esc_seq *socket; + struct esc_seq *dataless; + /** A mapping from color names (fi, di, ln, etc.) to struct fields. */ struct trie names; @@ -143,13 +145,7 @@ static int init_esc(struct colors *colors, const char *name, const char *value, *field = esc; - struct trie_leaf *leaf = trie_insert_str(&colors->names, name); - if (!leaf) { - return -1; - } - - leaf->value = field; - return 0; + return trie_set_str(&colors->names, name, field); } /** Check if an escape sequence is equal to a string. */ @@ -159,8 +155,7 @@ static bool esc_eq(const struct esc_seq *esc, const char *str, size_t len) { /** Get an escape sequence from the table. */ static struct esc_seq **get_esc(const struct colors *colors, const char *name) { - const struct trie_leaf *leaf = trie_find_str(&colors->names, name); - return leaf ? leaf->value : NULL; + return trie_get_str(&colors->names, name); } /** Append an escape sequence to a string. */ @@ -168,26 +163,32 @@ static int cat_esc(dchar **dstr, const struct esc_seq *seq) { return dstrxcat(dstr, seq->seq, seq->len); } -/** Set a named escape sequence. */ -static int set_esc(struct colors *colors, const char *name, dchar *value) { - struct esc_seq **field = get_esc(colors, name); - if (!field) { - return 0; +/** Set an escape sequence field. */ +static int set_esc_field(struct colors *colors, struct esc_seq **field, const dchar *value) { + struct esc_seq *seq = NULL; + if (value) { + seq = new_esc(colors, value, dstrlen(value)); + if (!seq) { + return -1; + } } if (*field) { free_esc(colors, *field); - *field = NULL; } + *field = seq; - if (value) { - *field = new_esc(colors, value, dstrlen(value)); - if (!*field) { - return -1; - } + return 0; +} + +/** Set a named escape sequence. */ +static int set_esc(struct colors *colors, const char *name, const dchar *value) { + struct esc_seq **field = get_esc(colors, name); + if (!field) { + return 0; } - return 0; + return set_esc_field(colors, field, value); } /** Reverse a string, to turn suffix matches into prefix matches. */ @@ -225,13 +226,7 @@ static int insert_ext(struct trie *trie, struct ext_color *ext) { } size_t len = ext->len + 1; - leaf = trie_insert_mem(trie, ext->ext, len); - if (!leaf) { - return -1; - } - - leaf->value = ext; - return 0; + return trie_set_mem(trie, ext->ext, len, ext); } /** Set the color for an extension. */ @@ -620,6 +615,109 @@ fail: return ret; } +/** Parse the FreeBSD $LSCOLORS format. */ +static int parse_bsd_ls_colors(struct colors *colors, const char *lscolors) { + static const char *fg_codes[256] = { + // 0-7: deprecated aliases for a-h + ['0'] = "30", ['1'] = "31", ['2'] = "32", ['3'] = "33", + ['4'] = "34", ['5'] = "35", ['6'] = "36", ['7'] = "37", + // a-h: first 8 ANSI foreground colors + ['a'] = "30", ['b'] = "31", ['c'] = "32", ['d'] = "33", + ['e'] = "34", ['f'] = "35", ['g'] = "36", ['h'] = "37", + // x: default foreground + ['x'] = "39", + // A-H: bold foreground colors + ['A'] = "1;30", ['B'] = "1;31", ['C'] = "1;32", ['D'] = "1;33", + ['E'] = "1;34", ['F'] = "1;35", ['G'] = "1;36", ['H'] = "1;37", + // X: bold default foreground + ['X'] = "1;39", + }; + + static const char *bg_codes[256] = { + // 0-7: deprecated aliases for a-h + ['0'] = "40", ['1'] = "41", ['2'] = "42", ['3'] = "43", + ['4'] = "44", ['5'] = "45", ['6'] = "46", ['7'] = "47", + // a-h: first 8 ANSI background colors + ['a'] = "40", ['b'] = "41", ['c'] = "42", ['d'] = "43", + ['e'] = "44", ['f'] = "45", ['g'] = "46", ['h'] = "47", + // x: default background + ['x'] = "49", + // A-H: background colors + underline + ['A'] = "4;40", ['B'] = "4;41", ['C'] = "4;42", ['D'] = "4;43", + ['E'] = "4;44", ['F'] = "4;45", ['G'] = "4;46", ['H'] = "4;47", + // X: default background + underline + ['X'] = "4;49", + }; + + // Please refer to https://man.freebsd.org/cgi/man.cgi?ls(1)#ENVIRONMENT + char complete_colors[] = "exfxcxdxbxegedabagacadah"; + + // For short $LSCOLORS, use the default colors for the rest + size_t max = strlen(complete_colors); + size_t len = strnlen(lscolors, max); + memcpy(complete_colors, lscolors, len); + + struct esc_seq **keys[] = { + &colors->directory, + &colors->link, + &colors->socket, + &colors->pipe, + &colors->executable, + &colors->blockdev, + &colors->chardev, + &colors->setuid, + &colors->setgid, + &colors->sticky_other_writable, + &colors->other_writable, + &colors->dataless, + }; + + dchar *buf = dstralloc(8); + if (!buf) { + return -1; + } + + int ret = -1; + for (size_t i = 0; i < countof(keys); ++i) { + uint8_t fg = complete_colors[2 * i]; + uint8_t bg = complete_colors[2 * i + 1]; + + const char *fg_code = fg_codes[fg]; + const char *bg_code = bg_codes[bg]; + + dstrshrink(buf, 0); + if (fg_code) { + if (dstrcat(&buf, fg_code) != 0) { + goto fail; + } + } + if (fg_code && bg_code) { + if (dstrcat(&buf, ";") != 0) { + goto fail; + } + } + if (bg_code) { + if (dstrcat(&buf, bg_code) != 0) { + goto fail; + } + } + + const dchar *value = dstrlen(buf) > 0 ? buf : NULL; + if (set_esc_field(colors, keys[i], value) != 0) { + goto fail; + } + } + + ret = 0; +fail: + dstrfree(buf); + return ret; +} + +static bool str_isset(const char *str) { + return str && *str; +} + struct colors *parse_colors(void) { struct colors *colors = ALLOC(struct colors); if (!colors) { @@ -685,16 +783,28 @@ struct colors *parse_colors(void) { fail = fail || init_esc(colors, "pi", "33", &colors->pipe); fail = fail || init_esc(colors, "so", "01;35", &colors->socket); + colors->dataless = NULL; + if (fail) { goto fail; } - if (parse_gnu_ls_colors(colors, getenv("LS_COLORS")) != 0) { - goto fail; - } - if (parse_gnu_ls_colors(colors, getenv("BFS_COLORS")) != 0) { - goto fail; + const char *gnu_colors = getenv("LS_COLORS"); + const char *bfs_colors = getenv("BFS_COLORS"); + const char *bsd_colors = getenv("LSCOLORS"); + if (str_isset(gnu_colors) || str_isset(bfs_colors)) { + if (parse_gnu_ls_colors(colors, gnu_colors) != 0) { + goto fail; + } + if (parse_gnu_ls_colors(colors, bfs_colors) != 0) { + goto fail; + } + } else if (str_isset(bsd_colors)) { + if (parse_bsd_ls_colors(colors, bsd_colors) != 0) { + goto fail; + } } + if (build_iext_trie(colors) != 0) { goto fail; } @@ -962,6 +1072,34 @@ static bool cpath_is_broken(const struct cpath *cpath) { } } +/** Check if we need a statbuf to colorize a file. */ +static bool must_stat(const struct colors *colors, enum bfs_type type) { + switch (type) { + case BFS_REG: + if (colors->setuid || colors->setgid || colors->executable || colors->multi_hard) { + return true; + } + +#ifdef ST_DATALESS + if (colors->dataless) { + return true; + } +#endif + + return false; + + case BFS_DIR: + if (colors->sticky_other_writable || colors->other_writable || colors->sticky) { + return true; + } + + return false; + + default: + return false; + } +} + /** Get the color for a file. */ static const struct esc_seq *file_color(const struct colors *colors, const struct cpath *cpath) { enum bfs_type type; @@ -976,17 +1114,17 @@ static const struct esc_seq *file_color(const struct colors *colors, const struc } const struct bfs_stat *statbuf = NULL; + if (must_stat(colors, type)) { + statbuf = cpath_stat(cpath); + if (!statbuf) { + goto error; + } + } + const struct esc_seq *color = NULL; switch (type) { case BFS_REG: - if (colors->setuid || colors->setgid || colors->executable || colors->multi_hard) { - statbuf = cpath_stat(cpath); - if (!statbuf) { - goto error; - } - } - if (colors->setuid && (statbuf->mode & 04000)) { color = colors->setuid; } else if (colors->setgid && (statbuf->mode & 02000)) { @@ -999,6 +1137,12 @@ static const struct esc_seq *file_color(const struct colors *colors, const struc color = colors->multi_hard; } +#ifdef SF_DATALESS + if (!color && colors->dataless && (statbuf->attrs & SF_DATALESS)) { + color = colors->dataless; + } +#endif + if (!color) { const char *name = cpath->path + cpath->nameoff; size_t namelen = cpath->valid - cpath->nameoff; @@ -1012,13 +1156,6 @@ static const struct esc_seq *file_color(const struct colors *colors, const struc break; case BFS_DIR: - if (colors->sticky_other_writable || colors->other_writable || colors->sticky) { - statbuf = cpath_stat(cpath); - 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)) { @@ -1253,6 +1390,33 @@ static int print_link_target(CFILE *cfile, const struct BFTW *ftwbuf) { _printf(2, 3) static int cbuff(CFILE *cfile, const char *format, ...); +/** Print an expression's name, for diagnostics. */ +static int print_expr_name(CFILE *cfile, const struct bfs_expr *expr) { + switch (expr->kind) { + case BFS_FLAG: + return cbuff(cfile, "${cyn}%pq${rs}", expr->argv[0]); + case BFS_OPERATOR: + return cbuff(cfile, "${red}%pq${rs}", expr->argv[0]); + default: + return cbuff(cfile, "${blu}%pq${rs}", expr->argv[0]); + } +} + +/** Print an expression's args, for diagnostics. */ +static int print_expr_args(CFILE *cfile, const struct bfs_expr *expr) { + if (print_expr_name(cfile, expr) != 0) { + return -1; + } + + for (size_t i = 1; i < expr->argc; ++i) { + if (cbuff(cfile, " ${bld}%pq${rs}", expr->argv[i]) < 0) { + return -1; + } + } + + return 0; +} + /** Dump a parsed expression tree, for debugging. */ static int print_expr(CFILE *cfile, const struct bfs_expr *expr, bool verbose, int depth) { if (depth >= 2) { @@ -1267,28 +1431,10 @@ static int print_expr(CFILE *cfile, const struct bfs_expr *expr, bool verbose, i return -1; } - int ret; - switch (expr->kind) { - case BFS_FLAG: - ret = cbuff(cfile, "${cyn}%pq${rs}", expr->argv[0]); - break; - case BFS_OPERATOR: - ret = cbuff(cfile, "${red}%pq${rs}", expr->argv[0]); - break; - default: - ret = cbuff(cfile, "${blu}%pq${rs}", expr->argv[0]); - break; - } - if (ret < 0) { + if (print_expr_args(cfile, expr) != 0) { return -1; } - for (size_t i = 1; i < expr->argc; ++i) { - if (cbuff(cfile, " ${bld}%pq${rs}", expr->argv[i]) < 0) { - return -1; - } - } - if (verbose) { double rate = 0.0, time = 0.0; if (expr->evaluations) { @@ -1426,6 +1572,16 @@ static int cvbuff(CFILE *cfile, const char *format, va_list args) { return -1; } break; + case 'x': + if (print_expr_args(cfile, va_arg(args, const struct bfs_expr *)) != 0) { + return -1; + } + break; + case 'X': + if (print_expr_name(cfile, va_arg(args, const struct bfs_expr *)) != 0) { + return -1; + } + break; default: goto invalid; diff --git a/src/color.h b/src/color.h index 2394af2..aac8b33 100644 --- a/src/color.h +++ b/src/color.h @@ -95,6 +95,8 @@ int cfclose(CFILE *cfile); * %pL: A colored link target, from a const struct BFTW * argument * %pe: Dump a const struct bfs_expr *, for debugging. * %pE: Dump a const struct bfs_expr * in verbose form, for debugging. + * %px: Print a const struct bfs_expr * with syntax highlighting. + * %pX: Print the name of a const struct bfs_expr *, without arguments. * %%: A literal '%' * ${cc}: Change the color to 'cc' * $$: A literal '$' @@ -24,20 +24,6 @@ #include <time.h> #include <unistd.h> -/** Get the initial value for ctx->threads (-j). */ -static int bfs_nproc(void) { - long nproc = xsysconf(_SC_NPROCESSORS_ONLN); - - if (nproc < 1) { - nproc = 1; - } else if (nproc > 8) { - // Not much speedup after 8 threads - nproc = 8; - } - - return nproc; -} - struct bfs_ctx *bfs_ctx_new(void) { struct bfs_ctx *ctx = ZALLOC(struct bfs_ctx); if (!ctx) { @@ -50,9 +36,14 @@ struct bfs_ctx *bfs_ctx_new(void) { ctx->maxdepth = INT_MAX; ctx->flags = BFTW_RECOVER; ctx->strategy = BFTW_BFS; - ctx->threads = bfs_nproc(); ctx->optlevel = 3; + ctx->threads = nproc(); + if (ctx->threads > 8) { + // Not much speedup after 8 threads + ctx->threads = 8; + } + trie_init(&ctx->files); ctx->umask = umask(0); @@ -295,6 +286,7 @@ int bfs_ctx_free(struct bfs_ctx *ctx) { } free(ctx->paths); + free(ctx->kinds); free(ctx->argv); free(ctx); } @@ -29,6 +29,8 @@ struct bfs_ctx { size_t argc; /** The unparsed command line arguments. */ char **argv; + /** The argument token kinds. */ + enum bfs_kind *kinds; /** The root paths. */ const char **paths; @@ -14,27 +14,30 @@ #include <stdarg.h> #include <stdio.h> #include <stdlib.h> - -/** bfs_diagf() implementation. */ -_printf(2, 0) -static void bfs_vdiagf(const struct bfs_loc *loc, const char *format, va_list args) { - fprintf(stderr, "%s: %s@%s:%d: ", xgetprogname(), loc->func, loc->file, loc->line); - vfprintf(stderr, format, args); - fprintf(stderr, "\n"); -} - -void bfs_diagf(const struct bfs_loc *loc, const char *format, ...) { +#include <unistd.h> + +/** + * Print an error using dprintf() if possible, because it's more likely to be + * async-signal-safe in practice. + */ +#if BFS_HAS_DPRINTF +# define veprintf(...) vdprintf(STDERR_FILENO, __VA_ARGS__) +#else +# define veprintf(...) vfprintf(stderr, __VA_ARGS__) +#endif + +void bfs_diagf(const char *format, ...) { va_list args; va_start(args, format); - bfs_vdiagf(loc, format, args); + veprintf(format, args); va_end(args); } _noreturn -void bfs_abortf(const struct bfs_loc *loc, const char *format, ...) { +void bfs_abortf(const char *format, ...) { va_list args; va_start(args, format); - bfs_vdiagf(loc, format, args); + veprintf(format, args); va_end(args); abort(); @@ -14,69 +14,69 @@ #include <stdarg.h> /** - * A source code location. + * Wrap a diagnostic format string so it looks like + * + * bfs: func@src/file.c:0: Message */ -struct bfs_loc { - const char *file; - int line; - const char *func; -}; - -#define BFS_LOC_INIT { .file = __FILE__, .line = __LINE__, .func = __func__ } +// Use (format) ? "..." : "" so the format string is required +#define BFS_DIAG_FORMAT_(format) \ + ((format) ? "%s: %s@%s:%d: " format "%s" : "") /** - * Get the current source code location. + * Add arguments to match a BFS_DIAG_FORMAT string. */ -#if __STDC_VERSION__ >= C23 -# define bfs_location() (&(static const struct bfs_loc)BFS_LOC_INIT) -#else -# define bfs_location() (&(const struct bfs_loc)BFS_LOC_INIT) -#endif +#define BFS_DIAG_ARGS_(...) \ + xgetprogname(), __func__, __FILE__, __LINE__, __VA_ARGS__ "\n" /** - * Print a low-level diagnostic message to standard error, formatted like - * - * bfs: func@src/file.c:0: Message + * Print a low-level diagnostic message to standard error. */ -_printf(2, 3) -void bfs_diagf(const struct bfs_loc *loc, const char *format, ...); +_printf(1, 2) +void bfs_diagf(const char *format, ...); /** * Unconditional diagnostic message. */ -#define bfs_diag(...) bfs_diagf(bfs_location(), __VA_ARGS__) +#define bfs_diag(...) \ + bfs_diag_(__VA_ARGS__, ) + +#define bfs_diag_(format, ...) \ + bfs_diagf(BFS_DIAG_FORMAT_(format), BFS_DIAG_ARGS_(__VA_ARGS__)) /** * Print a diagnostic message including the last error. */ #define bfs_ediag(...) \ - bfs_ediag_("" __VA_ARGS__, errstr()) + bfs_ediag_(__VA_ARGS__, ) #define bfs_ediag_(format, ...) \ - bfs_diag(sizeof(format) > 1 ? format ": %s" : "%s", __VA_ARGS__) + bfs_diag_(format "%s%s", __VA_ARGS__ (sizeof("" format) > 1 ? ": " : ""), errstr(), ) /** * Print a message to standard error and abort. */ _cold -_printf(2, 3) +_printf(1, 2) _noreturn -void bfs_abortf(const struct bfs_loc *loc, const char *format, ...); +void bfs_abortf(const char *format, ...); /** * Unconditional abort with a message. */ #define bfs_abort(...) \ - bfs_abortf(bfs_location(), __VA_ARGS__) + bfs_abort_(__VA_ARGS__, ) + +#define bfs_abort_(format, ...) \ + bfs_abortf(BFS_DIAG_FORMAT_(format), BFS_DIAG_ARGS_(__VA_ARGS__)) /** * Abort with a message including the last error. */ #define bfs_eabort(...) \ - bfs_eabort_("" __VA_ARGS__, errstr()) + bfs_eabort_(__VA_ARGS__, ) #define bfs_eabort_(format, ...) \ - bfs_abort(sizeof(format) > 1 ? format ": %s" : "%s", __VA_ARGS__) + bfs_abort_(format "%s%s", __VA_ARGS__ (sizeof("" format) > 1 ? ": " : ""), errstr(), ) /** * Abort in debug builds; no-op in release builds. @@ -90,30 +90,42 @@ void bfs_abortf(const struct bfs_loc *loc, const char *format, ...); #endif /** + * Get the default assertion message, if no format string was specified. + */ +#define BFS_DIAG_MSG_(format, str) \ + (sizeof(format) > 1 ? "" : str) + +/** * Unconditional assert. */ #define bfs_verify(...) \ - bfs_verify_(#__VA_ARGS__, __VA_ARGS__, "", "") + bfs_verify_(#__VA_ARGS__, __VA_ARGS__, "", ) #define bfs_verify_(str, cond, format, ...) \ - ((cond) ? (void)0 : bfs_abort( \ + ((cond) ? (void)0 : bfs_verify__(format, BFS_DIAG_MSG_(format, str), __VA_ARGS__)) + +#define bfs_verify__(format, ...) \ + bfs_abortf( \ sizeof(format) > 1 \ - ? "%.0s" format "%s%s" \ - : "Assertion failed: `%s`%s", \ - str, __VA_ARGS__)) + ? BFS_DIAG_FORMAT_("%s" format "%s") \ + : BFS_DIAG_FORMAT_("Assertion failed: `%s`"), \ + BFS_DIAG_ARGS_(__VA_ARGS__)) /** * Unconditional assert, including the last error. */ #define bfs_everify(...) \ - bfs_everify_(#__VA_ARGS__, __VA_ARGS__, "", errstr()) + bfs_everify_(#__VA_ARGS__, __VA_ARGS__, "", ) #define bfs_everify_(str, cond, format, ...) \ - ((cond) ? (void)0 : bfs_abort( \ + ((cond) ? (void)0 : bfs_everify__(format, BFS_DIAG_MSG_(format, str), __VA_ARGS__)) + +#define bfs_everify__(format, ...) \ + bfs_abortf( \ sizeof(format) > 1 \ - ? "%.0s" format "%s: %s" \ - : "Assertion failed: `%s`: %s", \ - str, __VA_ARGS__)) + ? BFS_DIAG_FORMAT_("%s" format "%s: %s") \ + : BFS_DIAG_FORMAT_("Assertion failed: `%s`: %s"), \ + BFS_DIAG_ARGS_(__VA_ARGS__ errstr(), )) /** * Assert in debug builds; no-op in release builds. diff --git a/src/dstring.c b/src/dstring.c index 0f08679..678d685 100644 --- a/src/dstring.c +++ b/src/dstring.c @@ -23,7 +23,7 @@ struct dstring { /** Length of the string, *excluding* the terminating NUL. */ size_t len; /** The string itself. */ - alignas(dchar) char str[]; + alignas(dchar) char str[] _counted_by(cap); }; #define DSTR_OFFSET offsetof(struct dstring, str) @@ -408,7 +408,7 @@ static int eval_exec_finish(const struct bfs_expr *expr, const struct bfs_ctx *c if (expr->eval_fn == eval_exec) { if (bfs_exec_finish(expr->exec) != 0) { if (errno != 0) { - bfs_error(ctx, "%s %s: %s.\n", expr->argv[0], expr->argv[1], errstr()); + bfs_error(ctx, "${blu}%pq${rs} ${bld}%pq${rs}: %s.\n", expr->argv[0], expr->argv[1], errstr()); } ret = -1; } @@ -429,7 +429,7 @@ static int eval_exec_finish(const struct bfs_expr *expr, const struct bfs_ctx *c bool eval_exec(const struct bfs_expr *expr, struct bfs_eval *state) { bool ret = bfs_exec(expr->exec, state->ftwbuf) == 0; if (errno != 0) { - eval_error(state, "%s %s: %s.\n", expr->argv[0], expr->argv[1], errstr()); + eval_error(state, "${blu}%pq${rs} ${bld}%pq${rs}: %s.\n", expr->argv[0], expr->argv[1], errstr()); } return ret; } @@ -700,6 +700,34 @@ static int print_owner(FILE *file, const char *name, uintmax_t id, int *width) { } } +/** Print a file's modification time. */ +static int print_time(FILE *file, time_t time, time_t now) { + struct tm tm; + if (!localtime_r(&time, &tm)) { + goto error; + } + + char time_str[256]; + size_t time_ret; + + time_t six_months_ago = now - 6 * 30 * 24 * 60 * 60; + time_t tomorrow = now + 24 * 60 * 60; + if (time <= six_months_ago || time >= tomorrow) { + time_ret = strftime(time_str, sizeof(time_str), "%b %e %Y", &tm); + } else { + time_ret = strftime(time_str, sizeof(time_str), "%b %e %H:%M", &tm); + } + + if (time_ret == 0) { + goto error; + } + + return fprintf(file, " %s", time_str); + +error: + return fprintf(file, " %jd", (intmax_t)time); +} + /** * -f?ls action. */ @@ -756,28 +784,11 @@ bool eval_fls(const struct bfs_expr *expr, struct bfs_eval *state) { time_t time = statbuf->mtime.tv_sec; time_t now = ctx->now.tv_sec; - time_t six_months_ago = now - 6 * 30 * 24 * 60 * 60; - time_t tomorrow = now + 24 * 60 * 60; - struct tm tm; - if (!localtime_r(&time, &tm)) { - goto error; - } - char time_str[256]; - size_t time_ret; - if (time <= six_months_ago || time >= tomorrow) { - time_ret = strftime(time_str, sizeof(time_str), "%b %e %Y", &tm); - } else { - time_ret = strftime(time_str, sizeof(time_str), "%b %e %H:%M", &tm); - } - if (time_ret == 0) { - errno = EOVERFLOW; - goto error; - } - if (cfprintf(cfile, " %s${rs}", time_str) < 0) { + if (print_time(file, time, now) < 0) { goto error; } - if (cfprintf(cfile, " %pP", ftwbuf) < 0) { + if (cfprintf(cfile, "${rs} %pP", ftwbuf) < 0) { goto error; } @@ -19,6 +19,9 @@ * Argument/token/expression kinds. */ enum bfs_kind { + /** A regular argument. */ + BFS_ARG, + /** A flag (-H, -L, etc.). */ BFS_FLAG, @@ -203,7 +203,7 @@ struct ioqq { cache_align atomic size_t tail; /** The circular buffer itself. */ - cache_align ioq_slot slots[]; + cache_align ioq_slot slots[]; // _counted_by(slot_mask + 1) }; /** Destroy an I/O command queue. */ @@ -277,11 +277,13 @@ static struct ioq_monitor *ioq_slot_monitor(struct ioqq *ioqq, ioq_slot *slot) { /** Atomically wait for a slot to change. */ _noinline static uintptr_t ioq_slot_wait(struct ioqq *ioqq, ioq_slot *slot, uintptr_t value) { - // Try spinning a few times before blocking uintptr_t ret; - for (int i = 0; i < 10; ++i) { - // Exponential backoff - for (int j = 0; j < (1 << i); ++j) { + + // Try spinning a few times (with exponential backoff) before blocking + _nounroll + for (int i = 1; i < 1024; i *= 2) { + _nounroll + for (int j = 0; j < i; ++j) { spin_loop(); } @@ -387,7 +389,7 @@ static struct ioq_ent *ioq_slot_pop(struct ioqq *ioqq, ioq_slot *slot, bool bloc // slot is not full, this will prefetch an invalid address, but // experimentally this is worth it on both Intel (Alder Lake) // and AMD (Zen 2). - __builtin_prefetch((void *)(prev << 1)); + __builtin_prefetch((void *)(prev << 1), 1 /* write */); #endif // empty → skip(1) @@ -591,7 +593,7 @@ struct ioq { /** The number of background threads. */ size_t nthreads; /** The background threads themselves. */ - struct ioq_thread threads[]; + struct ioq_thread threads[] _counted_by(nthreads); }; /** Cancel a request if we need to. */ @@ -615,7 +617,7 @@ static void ioq_dispatch_sync(struct ioq *ioq, struct ioq_ent *ent) { case IOQ_NOP: if (ent->nop.type == IOQ_NOP_HEAVY) { // A fast, no-op syscall - getpid(); + getppid(); } ent->result = 0; return; @@ -844,7 +846,7 @@ static struct io_uring_sqe *ioq_dispatch_async(struct ioq_ring_state *state, str sqe = ioq_get_sqe(state); struct ioq_stat *args = &ent->stat; int flags = bfs_statx_flags(args->flags); - unsigned int mask = STATX_BASIC_STATS | STATX_BTIME; + unsigned int mask = bfs_statx_mask(); io_uring_prep_statx(sqe, args->dfd, args->path, flags, mask, args->xbuf); } #endif @@ -1034,7 +1036,7 @@ static int ioq_ring_init(struct ioq *ioq, struct ioq_thread *thread) { ioq_ring_probe_flags(¶ms, IORING_SETUP_SINGLE_ISSUER); # endif # ifdef IORING_SETUP_DEFER_TASKRUN - // Don't interrupt us aggresively with completion events + // Don't interrupt us aggressively with completion events ioq_ring_probe_flags(¶ms, IORING_SETUP_DEFER_TASKRUN); # endif } @@ -82,11 +82,9 @@ #ifndef BFS_LIST_H #define BFS_LIST_H -#include "bfs.h" #include "diag.h" #include <stddef.h> -#include <stdint.h> #include <string.h> /** @@ -374,24 +372,19 @@ #define SLIST_REMOVE_(list, cursor, ...) \ SLIST_REMOVE__((list), (cursor), LIST_NEXT_(__VA_ARGS__)) -// Scratch variables for the type-safe SLIST_REMOVE() implementation. -// Not a pointer type due to https://github.com/llvm/llvm-project/issues/109718. -_maybe_unused -static thread_local uintptr_t _slist_prev, _slist_next; - -/** Suppress -Wunused-value. */ -_maybe_unused -static inline void *_slist_cast(uintptr_t ptr) { - return (void *)ptr; -} - #define SLIST_REMOVE__(list, cursor, next) \ - (_slist_prev = (uintptr_t)(void *)*cursor, \ - _slist_next = (uintptr_t)(void *)(*cursor)->next, \ - (*cursor)->next = NULL, \ - *cursor = (void *)_slist_next, \ - list->tail = *cursor ? list->tail : cursor, \ - _slist_cast(_slist_prev)) + (list->tail = (*cursor)->next ? list->tail : cursor, \ + slist_remove_(*cursor, cursor, &(*cursor)->next, sizeof(*cursor))) + +// Helper for SLIST_REMOVE() +static inline void *slist_remove_(void *ret, void *cursor, void *next, size_t size) { + // ret = *cursor; + // *cursor = ret->next; + memcpy(cursor, next, size); + // ret->next = NULL; + memset(next, 0, size); + return ret; +} /** * Pop the head off a singly-linked list. @@ -256,10 +256,7 @@ static int bfs_mtab_fill_types(struct bfs_mtab *mtab) { continue; } - struct trie_leaf *leaf = trie_insert_mem(&mtab->types, &sb.dev, sizeof(sb.dev)); - if (leaf) { - leaf->value = mount->type; - } else { + if (trie_set_mem(&mtab->types, &sb.mnt_id, sizeof(sb.mnt_id), mount->type) != 0) { goto fail; } } @@ -282,9 +279,9 @@ const char *bfs_fstype(const struct bfs_mtab *mtab, const struct bfs_stat *statb } } - const struct trie_leaf *leaf = trie_find_mem(&mtab->types, &statbuf->dev, sizeof(statbuf->dev)); - if (leaf) { - return leaf->value; + const char *type = trie_get_mem(&mtab->types, &statbuf->mnt_id, sizeof(statbuf->mnt_id)); + if (type) { + return type; } else { return "unknown"; } @@ -1623,14 +1623,19 @@ static void data_flow_icmp(struct bfs_opt *opt, const struct bfs_expr *expr, enu /** Transfer function for -{execut,read,writ}able. */ static struct bfs_expr *data_flow_access(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor) { - if (expr->num & R_OK) { + switch (expr->num) { + case R_OK: data_flow_pred(opt, READABLE_PRED, true); - } - if (expr->num & W_OK) { + break; + case W_OK: data_flow_pred(opt, WRITABLE_PRED, true); - } - if (expr->num & X_OK) { + break; + case X_OK: data_flow_pred(opt, EXECUTABLE_PRED, true); + break; + default: + bfs_bug("Unknown access() mode %lld", expr->num); + break; } return expr; @@ -1655,7 +1660,7 @@ static struct bfs_expr *data_flow_gid(struct bfs_opt *opt, struct bfs_expr *expr gid_t gid = range->min; bool nogroup = !bfs_getgrgid(opt->ctx->groups, gid); if (errno == 0) { - data_flow_pred(opt, NOGROUP_PRED, nogroup); + constrain_pred(&opt->after_true.preds[NOGROUP_PRED], nogroup); } } @@ -1729,7 +1734,7 @@ static struct bfs_expr *data_flow_uid(struct bfs_opt *opt, struct bfs_expr *expr uid_t uid = range->min; bool nouser = !bfs_getpwuid(opt->ctx->users, uid); if (errno == 0) { - data_flow_pred(opt, NOUSER_PRED, nouser); + constrain_pred(&opt->after_true.preds[NOUSER_PRED], nouser); } } diff --git a/src/parse.c b/src/parse.c index 42f71cc..5ec4c0e 100644 --- a/src/parse.c +++ b/src/parse.c @@ -84,8 +84,6 @@ struct bfs_parser { enum use_color use_color; /** Whether a -print action is implied. */ bool implicit_print; - /** Whether the default root "." should be used. */ - bool implicit_root; /** Whether the expression has started. */ bool expr_started; /** Whether an information option like -help or -version was passed. */ @@ -95,20 +93,20 @@ struct bfs_parser { /** The last non-path argument. */ char **last_arg; - /** A "-depth"-type argument, if any. */ - char **depth_arg; - /** A "-limit" argument, if any. */ - char **limit_arg; - /** A "-prune" argument, if any. */ - char **prune_arg; - /** A "-mount" argument, if any. */ - char **mount_arg; - /** An "-xdev" argument, if any. */ - char **xdev_arg; - /** A "-files0-from -" argument, if any. */ - char **files0_stdin_arg; - /** An "-ok"-type expression, if any. */ - const struct bfs_expr *ok_expr; + /** A "-depth"-type expression, if any. */ + const struct bfs_expr *depth_expr; + /** A "-limit" expression, if any. */ + const struct bfs_expr *limit_expr; + /** A "-prune" expression, if any. */ + const struct bfs_expr *prune_expr; + /** A "-mount" expression, if any. */ + const struct bfs_expr *mount_expr; + /** An "-xdev" expression, if any. */ + const struct bfs_expr *xdev_expr; + /** A "-files0-from" expression, if any. */ + const struct bfs_expr *files0_expr; + /** An expression that consumes stdin, if any. */ + const struct bfs_expr *stdin_expr; /** The current time (maybe modified by -daystart). */ struct timespec now; @@ -176,14 +174,14 @@ static void parse_argv_error(const struct bfs_parser *parser, char **argv, size_ /** * Print an error about conflicting command line arguments. */ -_printf(6, 7) -static void parse_conflict_error(const struct bfs_parser *parser, char **argv1, size_t argc1, char **argv2, size_t argc2, const char *format, ...) { +_printf(4, 5) +static void parse_conflict_error(const struct bfs_parser *parser, const struct bfs_expr *expr1, const struct bfs_expr *expr2, const char *format, ...) { const struct bfs_ctx *ctx = parser->ctx; bool highlight[ctx->argc]; init_highlight(ctx, highlight); - highlight_args(ctx, argv1, argc1, highlight); - highlight_args(ctx, argv2, argc2, highlight); + highlight_args(ctx, expr1->argv, expr1->argc, highlight); + highlight_args(ctx, expr2->argv, expr2->argc, highlight); bfs_argv_error(ctx, highlight); va_list args; @@ -231,14 +229,14 @@ static bool parse_warning(const struct bfs_parser *parser, const char *format, . /** * Print a warning about conflicting command line arguments. */ -_printf(6, 7) -static bool parse_conflict_warning(const struct bfs_parser *parser, char **argv1, size_t argc1, char **argv2, size_t argc2, const char *format, ...) { +_printf(4, 5) +static bool parse_conflict_warning(const struct bfs_parser *parser, const struct bfs_expr *expr1, const struct bfs_expr *expr2, const char *format, ...) { const struct bfs_ctx *ctx = parser->ctx; bool highlight[ctx->argc]; init_highlight(ctx, highlight); - highlight_args(ctx, argv1, argc1, highlight); - highlight_args(ctx, argv2, argc2, highlight); + highlight_args(ctx, expr1->argv, expr1->argc, highlight); + highlight_args(ctx, expr2->argv, expr2->argc, highlight); if (!bfs_argv_warning(ctx, highlight)) { return false; } @@ -269,6 +267,21 @@ static bool parse_expr_warning(const struct bfs_parser *parser, const struct bfs } /** + * Report an error if stdin is already consumed, then consume it. + */ +static bool consume_stdin(struct bfs_parser *parser, const struct bfs_expr *expr) { + if (parser->stdin_expr) { + parse_conflict_error(parser, parser->stdin_expr, expr, + "%pX and %pX can't both use standard input.\n", + parser->stdin_expr, expr); + return false; + } + + parser->stdin_expr = expr; + return true; +} + +/** * Allocate a new expression. */ static struct bfs_expr *parse_new_expr(const struct bfs_parser *parser, bfs_eval_fn *eval_fn, size_t argc, char **argv, enum bfs_kind kind) { @@ -383,6 +396,8 @@ static struct bfs_expr *parse_expr(struct bfs_parser *parser); * Advance by a single token. */ static char **parser_advance(struct bfs_parser *parser, enum bfs_kind kind, size_t argc) { + struct bfs_ctx *ctx = parser->ctx; + if (kind != BFS_FLAG && kind != BFS_PATH) { parser->expr_started = true; } @@ -391,6 +406,9 @@ static char **parser_advance(struct bfs_parser *parser, enum bfs_kind kind, size parser->last_arg = parser->argv; } + size_t i = parser->argv - ctx->argv; + ctx->kinds[i] = kind; + char **argv = parser->argv; parser->argv += argc; return argv; @@ -414,7 +432,6 @@ static int parse_root(struct bfs_parser *parser, const char *path) { return -1; } - parser->implicit_root = false; return 0; } @@ -1158,22 +1175,33 @@ static struct bfs_expr *parse_daystart(struct bfs_parser *parser, int arg1, int * Parse -delete. */ static struct bfs_expr *parse_delete(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_expr *expr = parse_nullary_action(parser, eval_delete); + if (!expr) { + return NULL; + } + struct bfs_ctx *ctx = parser->ctx; ctx->flags |= BFTW_POST_ORDER; ctx->dangerous = true; - parser->depth_arg = parser->argv; - - return parse_nullary_action(parser, eval_delete); + parser->depth_expr = expr; + return expr; } /** * Parse -d. */ -static struct bfs_expr *parse_depth(struct bfs_parser *parser, int arg1, int arg2) { +static struct bfs_expr *parse_depth(struct bfs_parser *parser, int flag, int arg2) { + struct bfs_expr *expr = flag + ? parse_nullary_flag(parser) + : parse_nullary_option(parser); + if (!expr) { + return NULL; + } + parser->ctx->flags |= BFTW_POST_ORDER; - parser->depth_arg = parser->argv; - return parse_nullary_flag(parser); + parser->depth_expr = expr; + return expr; } /** @@ -1219,6 +1247,41 @@ static struct bfs_expr *parse_empty(struct bfs_parser *parser, int arg1, int arg return expr; } +/** Check for unsafe relative paths in $PATH. */ +static const char *unsafe_path(const struct bfs_exec *execbuf) { + if (!(execbuf->flags & BFS_EXEC_CHDIR)) { + // Not -execdir or -okdir + return NULL; + } + + const char *exe = execbuf->tmpl_argv[0]; + if (strchr(exe, '/')) { + // No $PATH lookups for /foo or foo/bar + return NULL; + } + + if (strstr(exe, "{}")) { + // Substituted paths always contain a / + return NULL; + } + + const char *path = getenv("PATH"); + while (path) { + if (path[0] != '/') { + // Relative $PATH component! + return path; + } + + path = strchr(path, ':'); + if (path) { + ++path; + } + } + + // No relative components in $PATH + return NULL; +} + /** * Parse -exec(dir)?/-ok(dir)?. */ @@ -1241,29 +1304,21 @@ static struct bfs_expr *parse_exec(struct bfs_parser *parser, int flags, int arg // For pipe() in bfs_spawn() expr->ephemeral_fds = 2; - if (execbuf->flags & BFS_EXEC_CHDIR) { - // Check for relative paths in $PATH - const char *path = getenv("PATH"); - while (path) { - if (*path != '/') { - size_t len = strcspn(path, ":"); - char *comp = strndup(path, len); - if (comp) { - parse_expr_error(parser, expr, - "This action would be unsafe, since ${bld}$$PATH${rs} contains the relative path ${bld}%pq${rs}\n", comp); - free(comp); - } else { - parse_perror(parser, "strndup()"); - } - return NULL; - } - - path = strchr(path, ':'); - if (path) { - ++path; - } + const char *unsafe = unsafe_path(execbuf); + if (unsafe) { + size_t len = strcspn(unsafe, ":"); + char *comp = strndup(unsafe, len); + if (comp) { + parse_expr_error(parser, expr, + "This action would be unsafe, since ${bld}$$PATH${rs} contains the relative path ${bld}%pq${rs}\n", comp); + free(comp); + } else { + parse_perror(parser, "strndup()"); } + return NULL; + } + if (execbuf->flags & BFS_EXEC_CHDIR) { // To dup() the parent directory if (execbuf->flags & BFS_EXEC_MULTI) { ++expr->persistent_fds; @@ -1273,7 +1328,9 @@ static struct bfs_expr *parse_exec(struct bfs_parser *parser, int flags, int arg } if (execbuf->flags & BFS_EXEC_CONFIRM) { - parser->ok_expr = expr; + if (!consume_stdin(parser, expr)) { + return NULL; + } } else { ctx->dangerous = true; } @@ -1304,11 +1361,17 @@ static struct bfs_expr *parse_exit(struct bfs_parser *parser, int arg1, int arg2 * Parse -f PATH. */ static struct bfs_expr *parse_f(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_ctx *ctx = parser->ctx; + struct bfs_expr *expr = parse_unary_flag(parser); if (!expr) { return NULL; } + // Mark the path as a path, not a regular argument + size_t i = expr->argv - ctx->argv; + ctx->kinds[i + 1] = BFS_PATH; + if (parse_root(parser, expr->argv[1]) != 0) { return NULL; } @@ -1325,50 +1388,14 @@ static struct bfs_expr *parse_files0_from(struct bfs_parser *parser, int arg1, i return NULL; } - const char *from = expr->argv[1]; - - FILE *file; - if (strcmp(from, "-") == 0) { - file = stdin; - } else { - file = xfopen(from, O_RDONLY | O_CLOEXEC); - } - if (!file) { - parse_expr_error(parser, expr, "%s.\n", errstr()); - return NULL; - } - - while (true) { - char *path = xgetdelim(file, '\0'); - if (!path) { - if (errno) { - goto fail; - } else { - break; - } - } - - int ret = parse_root(parser, path); - free(path); - if (ret != 0) { - goto fail; - } - } - - if (file == stdin) { - parser->files0_stdin_arg = expr->argv; - } else { - fclose(file); - } - - parser->implicit_root = false; + // For compatibility with GNU find, + // + // bfs -files0-from a -files0-from b + // + // should *only* use b, not a. So stash the expression here and only + // process the last one at the end of parsing. + parser->files0_expr = expr; return expr; - -fail: - if (file != stdin) { - fclose(file); - } - return NULL; } /** @@ -1638,11 +1665,11 @@ static struct bfs_expr *parse_limit(struct bfs_parser *parser, int arg1, int arg } if (expr->num <= 0) { - parse_expr_error(parser, expr, "The ${blu}%s${rs} must be at least ${bld}1${rs}.\n", expr->argv[0]); + parse_expr_error(parser, expr, "The %pX must be at least ${bld}1${rs}.\n", expr); return NULL; } - parser->limit_arg = expr->argv; + parser->limit_expr = expr; return expr; } @@ -1676,7 +1703,7 @@ static struct bfs_expr *parse_mount(struct bfs_parser *parser, int arg1, int arg } parser->ctx->flags |= BFTW_SKIP_MOUNTS; - parser->mount_arg = expr->argv; + parser->mount_expr = expr; return expr; } @@ -1855,9 +1882,15 @@ static struct bfs_expr *parse_nohidden(struct bfs_parser *parser, int arg1, int * Parse -noleaf. */ static struct bfs_expr *parse_noleaf(struct bfs_parser *parser, int arg1, int arg2) { - parse_warning(parser, "${ex}%s${rs} does not apply the optimization that ${blu}%s${rs} inhibits.\n\n", - BFS_COMMAND, parser->argv[0]); - return parse_nullary_option(parser); + struct bfs_expr *expr = parse_nullary_option(parser); + if (!expr) { + return NULL; + } + + parse_expr_warning(parser, expr, + "${ex}%s${rs} does not apply the optimization that %px inhibits.\n\n", + BFS_COMMAND, expr); + return expr; } /** @@ -2193,8 +2226,13 @@ static struct bfs_expr *parse_printx(struct bfs_parser *parser, int arg1, int ar * Parse -prune. */ static struct bfs_expr *parse_prune(struct bfs_parser *parser, int arg1, int arg2) { - parser->prune_arg = parser->argv; - return parse_nullary_action(parser, eval_prune); + struct bfs_expr *expr = parse_nullary_action(parser, eval_prune); + if (!expr) { + return NULL; + } + + parser->prune_expr = expr; + return expr; } /** @@ -2572,9 +2610,14 @@ static struct bfs_expr *parse_xattrname(struct bfs_parser *parser, int arg1, int * Parse -xdev. */ static struct bfs_expr *parse_xdev(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_expr *expr = parse_nullary_option(parser); + if (!expr) { + return NULL; + } + parser->ctx->flags |= BFTW_PRUNE_MOUNTS; - parser->xdev_arg = parser->argv; - return parse_nullary_option(parser); + parser->xdev_expr = expr; + return expr; } /** @@ -3048,10 +3091,10 @@ static const struct table_entry parse_table[] = { {"-context", BFS_TEST, parse_context, true}, {"-csince", BFS_TEST, parse_since, BFS_STAT_CTIME}, {"-ctime", BFS_TEST, parse_time, BFS_STAT_CTIME}, - {"-d", BFS_FLAG, parse_depth}, + {"-d", BFS_FLAG, parse_depth, true}, {"-daystart", BFS_OPTION, parse_daystart}, {"-delete", BFS_ACTION, parse_delete}, - {"-depth", BFS_OPTION, parse_depth_n}, + {"-depth", BFS_OPTION, parse_depth_n, false}, {"-empty", BFS_TEST, parse_empty}, {"-exclude", BFS_OPERATOR}, {"-exec", BFS_ACTION, parse_exec, 0}, @@ -3503,6 +3546,73 @@ static struct bfs_expr *parse_expr(struct bfs_parser *parser) { return expr; } +/** Handle -files0-from after parsing. */ +static int parse_files0_roots(struct bfs_parser *parser) { + const struct bfs_ctx *ctx = parser->ctx; + const struct bfs_expr *expr = parser->files0_expr; + + if (ctx->npaths > 0) { + bool highlight[ctx->argc]; + init_highlight(ctx, highlight); + highlight_args(ctx, expr->argv, expr->argc, highlight); + + for (size_t i = 0; i < ctx->argc; ++i) { + if (ctx->kinds[i] == BFS_PATH) { + highlight[i] = true; + } + } + + bfs_argv_error(ctx, highlight); + bfs_error(ctx, "Cannot combine %pX with explicit root paths.\n", expr); + return -1; + } + + const char *from = expr->argv[1]; + + FILE *file; + if (strcmp(from, "-") == 0) { + if (!consume_stdin(parser, expr)) { + return -1; + } + file = stdin; + } else { + file = xfopen(from, O_RDONLY | O_CLOEXEC); + } + if (!file) { + parse_expr_error(parser, expr, "%s.\n", errstr()); + return -1; + } + + while (true) { + char *path = xgetdelim(file, '\0'); + if (!path) { + if (errno) { + goto fail; + } else { + break; + } + } + + int ret = parse_root(parser, path); + free(path); + if (ret != 0) { + goto fail; + } + } + + if (file != stdin) { + fclose(file); + } + + return 0; + +fail: + if (file != stdin) { + fclose(file); + } + return -1; +} + /** * Parse the top-level expression. */ @@ -3528,12 +3638,22 @@ static struct bfs_expr *parse_whole_expr(struct bfs_parser *parser) { return NULL; } + if (parser->files0_expr) { + if (parse_files0_roots(parser) != 0) { + return NULL; + } + } else if (ctx->npaths == 0) { + if (parse_root(parser, ".") != 0) { + return NULL; + } + } + if (parser->implicit_print) { - char **limit = parser->limit_arg; + const struct bfs_expr *limit = parser->limit_expr; if (limit) { - parse_argv_error(parser, parser->limit_arg, 2, - "With ${blu}%s${rs}, you must specify an action explicitly; for example, ${blu}-print${rs} ${blu}%s${rs} ${bld}%s${rs}.\n", - limit[0], limit[0], limit[1]); + parse_expr_error(parser, limit, + "With %pX, you must specify an action explicitly; for example, ${blu}-print${rs} %px.\n", + limit, limit); return NULL; } @@ -3549,16 +3669,16 @@ static struct bfs_expr *parse_whole_expr(struct bfs_parser *parser) { } } - if (parser->mount_arg && parser->xdev_arg) { - parse_conflict_warning(parser, parser->mount_arg, 1, parser->xdev_arg, 1, - "${blu}%s${rs} is redundant in the presence of ${blu}%s${rs}.\n\n", - parser->xdev_arg[0], parser->mount_arg[0]); + if (parser->mount_expr && parser->xdev_expr) { + parse_conflict_warning(parser, parser->mount_expr, parser->xdev_expr, + "%px is redundant in the presence of %px.\n\n", + parser->xdev_expr, parser->mount_expr); } - if (ctx->warn && parser->depth_arg && parser->prune_arg) { - parse_conflict_warning(parser, parser->depth_arg, 1, parser->prune_arg, 1, - "${blu}%s${rs} does not work in the presence of ${blu}%s${rs}.\n", - parser->prune_arg[0], parser->depth_arg[0]); + if (ctx->warn && parser->depth_expr && parser->prune_expr) { + parse_conflict_warning(parser, parser->depth_expr, parser->prune_expr, + "%px does not work in the presence of %px.\n", + parser->prune_expr, parser->depth_expr); if (ctx->interactive) { bfs_warning(ctx, "Do you want to continue? "); @@ -3570,13 +3690,6 @@ static struct bfs_expr *parse_whole_expr(struct bfs_parser *parser) { fprintf(stderr, "\n"); } - if (parser->ok_expr && parser->files0_stdin_arg) { - parse_conflict_error(parser, parser->ok_expr->argv, parser->ok_expr->argc, parser->files0_stdin_arg, 2, - "${blu}%s${rs} conflicts with ${blu}%s${rs} ${bld}%s${rs}.\n", - parser->ok_expr->argv[0], parser->files0_stdin_arg[0], parser->files0_stdin_arg[1]); - return NULL; - } - return expr; } @@ -3758,6 +3871,12 @@ struct bfs_ctx *bfs_parse_cmdline(int argc, char *argv[]) { goto fail; } + ctx->kinds = ZALLOC_ARRAY(enum bfs_kind, argc); + if (!ctx->kinds) { + perror("zalloc()"); + goto fail; + } + enum use_color use_color = COLOR_AUTO; const char *no_color = getenv("NO_COLOR"); if (no_color && *no_color) { @@ -3806,16 +3925,14 @@ struct bfs_ctx *bfs_parse_cmdline(int argc, char *argv[]) { .stdout_tty = stdout_tty, .use_color = use_color, .implicit_print = true, - .implicit_root = true, .just_info = false, .excluding = false, .last_arg = NULL, - .depth_arg = NULL, - .prune_arg = NULL, - .mount_arg = NULL, - .xdev_arg = NULL, - .files0_stdin_arg = NULL, - .ok_expr = NULL, + .depth_expr = NULL, + .prune_expr = NULL, + .mount_expr = NULL, + .xdev_expr = NULL, + .stdin_expr = NULL, .now = ctx->now, }; @@ -3844,12 +3961,6 @@ struct bfs_ctx *bfs_parse_cmdline(int argc, char *argv[]) { goto fail; } - if (ctx->npaths == 0 && parser.implicit_root) { - if (parse_root(&parser, ".") != 0) { - goto fail; - } - } - if ((ctx->flags & BFTW_FOLLOW_ALL) && !ctx->unique) { // We need bftw() to detect cycles unless -unique does it for us ctx->flags |= BFTW_DETECT_CYCLES; diff --git a/src/prelude.h b/src/prelude.h index de89a6c..3b1c4e5 100644 --- a/src/prelude.h +++ b/src/prelude.h @@ -126,5 +126,8 @@ #if __has_feature(thread_sanitizer) && !defined(__SANITIZE_THREAD__) # define __SANITIZE_THREAD__ true #endif +#if __has_feature(type_sanitizer) && !defined(__SANITIZE_TYPE__) +# define __SANITIZE_TYPE__ true +#endif #endif // BFS_PRELUDE_H diff --git a/src/sighook.c b/src/sighook.c index 6269614..a87bed5 100644 --- a/src/sighook.c +++ b/src/sighook.c @@ -249,64 +249,90 @@ static void *rcu_update(struct rcu *rcu, void *ptr) { return rcu_decode(arc_wait(prev)); } -struct sighook { - /** The signal being hooked, or 0 for atsigexit(). */ - int sig; - /** Signal hook flags. */ - enum sigflags flags; - /** The function to call. */ - sighook_fn *fn; - /** An argument to pass to the function. */ - void *arg; - /** Flag for SH_ONESHOT. */ - atomic bool armed; - - /** The RCU pointer to this hook. */ - struct rcu *self; - /** The next hook in the list. */ - struct rcu next; -}; - /** - * An RCU-protected linked list of signal hooks. + * An RCU-protected linked list. */ -struct siglist { - /** The first hook in the list. */ +struct rcu_list { + /** The first node in the list. */ struct rcu head; /** &last->next */ struct rcu *tail; }; -/** Initialize a siglist. */ -static void siglist_init(struct siglist *list) { +/** + * An rcu_list node. + */ +struct rcu_node { + /** The RCU pointer to this node. */ + struct rcu *self; + /** The next node in the list. */ + struct rcu next; +}; + +/** Initialize an rcu_list. */ +static void rcu_list_init(struct rcu_list *list) { rcu_init(&list->head, NULL); list->tail = &list->head; } -/** Append a hook to a linked list. */ -static void sigpush(struct siglist *list, struct sighook *hook) { - hook->self = list->tail; - list->tail = &hook->next; - rcu_init(&hook->next, NULL); - rcu_update(hook->self, hook); +/** Append to an rcu_list. */ +static void rcu_list_append(struct rcu_list *list, struct rcu_node *node) { + node->self = list->tail; + list->tail = &node->next; + rcu_init(&node->next, NULL); + rcu_update(node->self, node); } -/** Remove a hook from the linked list. */ -static void sigpop(struct siglist *list, struct sighook *hook) { - struct sighook *next = rcu_peek(&hook->next); - rcu_update(hook->self, next); +/** Remove from an rcu_list. */ +static void rcu_list_remove(struct rcu_list *list, struct rcu_node *node) { + struct rcu_node *next = rcu_peek(&node->next); + rcu_update(node->self, next); if (next) { - next->self = hook->self; + next->self = node->self; } else { list->tail = &list->head; } + rcu_destroy(&node->next); } +/** + * Iterate over an rcu_list. + * + * It is save to `break` out of this loop, but `return` or `goto` will lead to + * a missed arc_put(). + */ +#define for_rcu(type, node, list) \ + for_rcu_(type, node, (list), node##_slot_, node##_prev_, node##_done_) + +#define for_rcu_(type, node, list, slot, prev, done) \ + for (struct arc *slot, *prev, **done = NULL; !done; arc_put(slot), done = &slot) \ + for (type *node = rcu_read(&list->head, &slot); \ + node; \ + prev = slot, \ + node = rcu_read(&((struct rcu_node *)node)->next, &slot), \ + arc_put(prev)) + +struct sighook { + /** The RCU list node (must be the first field). */ + struct rcu_node node; + + /** The signal being hooked, or 0 for atsigexit(). */ + int sig; + /** Signal hook flags. */ + enum sigflags flags; + /** The function to call. */ + sighook_fn *fn; + /** An argument to pass to the function. */ + void *arg; + /** Flag for SH_ONESHOT. */ + atomic bool armed; +}; + /** The lists of signal hooks. */ -static struct siglist sighooks[64]; +static struct rcu_list sighooks[64]; /** Get the hook list for a particular signal. */ -static struct siglist *siglist(int sig) { +static struct rcu_list *siglist(int sig) { return &sighooks[sig % countof(sighooks)]; } @@ -342,22 +368,31 @@ static const int FATAL_SIGNALS[] = { SIGHUP, SIGILL, SIGINT, +#ifdef SIGIO + SIGIO, +#endif SIGPIPE, - SIGQUIT, - SIGSEGV, - SIGTERM, - SIGUSR1, - SIGUSR2, #ifdef SIGPOLL SIGPOLL, #endif #ifdef SIGPROF SIGPROF, #endif +#ifdef SIGPWR + SIGPWR, +#endif + SIGQUIT, + SIGSEGV, +#ifdef SIGSTKFLT + SIGSTKFLT, +#endif #ifdef SIGSYS SIGSYS, #endif + SIGTERM, SIGTRAP, + SIGUSR1, + SIGUSR2, #ifdef SIGVTALRM SIGVTALRM, #endif @@ -433,23 +468,16 @@ static bool should_run(int sig, struct sighook *hook) { } /** Find any matching hooks and run them. */ -static enum sigflags run_hooks(struct siglist *list, int sig, siginfo_t *info) { +static enum sigflags run_hooks(struct rcu_list *list, int sig, siginfo_t *info) { enum sigflags ret = 0; - struct arc *slot = NULL; - struct sighook *hook = rcu_read(&list->head, &slot); - while (hook) { + for_rcu (struct sighook, hook, list) { if (should_run(sig, hook)) { hook->fn(sig, info, hook->arg); ret |= hook->flags; } - - struct arc *prev = slot; - hook = rcu_read(&hook->next, &slot); - arc_put(prev); } - arc_put(slot); return ret; } @@ -488,7 +516,7 @@ static void sigdispatch(int sig, siginfo_t *info, void *context) { int error = errno; // Run the normal hooks - struct siglist *list = siglist(sig); + struct rcu_list *list = siglist(sig); enum sigflags flags = run_hooks(list, sig, info); // Run the atsigexit() hooks, if we're exiting @@ -501,6 +529,18 @@ static void sigdispatch(int sig, siginfo_t *info, void *context) { errno = error; } +/** A saved signal handler, for sigreset() to restore. */ +struct sigsave { + struct rcu_node node; + int sig; + struct sigaction action; +}; + +/** The list of saved signal handlers. */ +static struct rcu_list saved; +/** `saved` initialization status (since rcu_list_init() isn't atomic). */ +static atomic bool initialized = false; + /** Make sure our signal handler is installed for a given signal. */ static int siginit(int sig) { #ifdef SA_RESTART @@ -515,19 +555,19 @@ static int siginit(int sig) { }; static sigset_t signals; - static bool initialized = false; - if (!initialized) { + if (!load(&initialized, relaxed)) { if (sigemptyset(&signals) != 0 || sigemptyset(&action.sa_mask) != 0) { return -1; } for (size_t i = 0; i < countof(sighooks); ++i) { - siglist_init(&sighooks[i]); + rcu_list_init(&sighooks[i]); } - initialized = true; + rcu_list_init(&saved); + store(&initialized, true, release); } int installed = sigismember(&signals, sig); @@ -537,14 +577,32 @@ static int siginit(int sig) { return 0; } - if (sigaction(sig, &action, NULL) != 0) { + sigset_t updated = signals; + if (sigaddset(&updated, sig) != 0) { + return -1; + } + + struct sigaction original; + if (sigaction(sig, NULL, &original) != 0) { + return -1; + } + + struct sigsave *save = ALLOC(struct sigsave); + if (!save) { return -1; } - if (sigaddset(&signals, sig) != 0) { + save->sig = sig; + save->action = original; + rcu_list_append(&saved, &save->node); + + if (sigaction(sig, &action, NULL) != 0) { + rcu_list_remove(&saved, &save->node); + free(save); return -1; } + signals = updated; return 0; } @@ -561,8 +619,8 @@ static struct sighook *sighook_impl(int sig, sighook_fn *fn, void *arg, enum sig hook->arg = arg; atomic_init(&hook->armed, true); - struct siglist *list = siglist(sig); - sigpush(list, hook); + struct rcu_list *list = siglist(sig); + rcu_list_append(list, &hook->node); return hook; } @@ -608,11 +666,27 @@ void sigunhook(struct sighook *hook) { mutex_lock(&sigmutex); - struct siglist *list = siglist(hook->sig); - sigpop(list, hook); + struct rcu_list *list = siglist(hook->sig); + rcu_list_remove(list, &hook->node); mutex_unlock(&sigmutex); - rcu_destroy(&hook->next); free(hook); } + +int sigreset(void) { + if (!load(&initialized, acquire)) { + return 0; + } + + int ret = 0; + + for_rcu (struct sigsave, save, &saved) { + if (sigaction(save->sig, &save->action, NULL) != 0) { + ret = -1; + break; + } + } + + return ret; +} diff --git a/src/sighook.h b/src/sighook.h index 87bba4e..7149229 100644 --- a/src/sighook.h +++ b/src/sighook.h @@ -72,4 +72,12 @@ struct sighook *atsigexit(sighook_fn *fn, void *arg); */ void sigunhook(struct sighook *hook); +/** + * Restore all signal handlers to their original dispositions (e.g. after fork()). + * + * @return + * 0 on success, -1 on failure. + */ +int sigreset(void); + #endif // BFS_SIGHOOK_H @@ -51,6 +51,8 @@ const char *bfs_stat_field_name(enum bfs_stat_field field) { return "change time"; case BFS_STAT_MTIME: return "modification time"; + case BFS_STAT_MNT_ID: + return "mount ID"; } bfs_bug("Unrecognized stat field %d", (int)field); @@ -101,6 +103,10 @@ void bfs_stat_convert(struct bfs_stat *dest, const struct stat *src) { dest->rdev = src->st_rdev; dest->mask |= BFS_STAT_RDEV; + // No mount IDs in regular stat(), so use the dev_t as an approximation + dest->mnt_id = dest->dev; + dest->mask |= BFS_STAT_MNT_ID; + #if BFS_HAS_ST_FLAGS dest->attrs = src->st_flags; dest->mask |= BFS_STAT_ATTRS; @@ -169,6 +175,17 @@ int bfs_statx_flags(enum bfs_stat_flags flags) { return ret; } +unsigned int bfs_statx_mask(void) { + unsigned int mask = STATX_BASIC_STATS | STATX_BTIME; +#ifdef STATX_MNT_ID + mask |= STATX_MNT_ID; +#endif +#ifdef STATX_MNT_ID_UNIQUE + mask |= STATX_MNT_ID_UNIQUE; +#endif + return mask; +} + int bfs_statx_convert(struct bfs_stat *dest, const struct statx *src) { // Callers shouldn't have to check anything except the times const unsigned int guaranteed = STATX_BASIC_STATS & ~(STATX_ATIME | STATX_CTIME | STATX_MTIME); @@ -209,6 +226,18 @@ int bfs_statx_convert(struct bfs_stat *dest, const struct statx *src) { dest->attrs = src->stx_attributes; dest->mask |= BFS_STAT_ATTRS; + dest->mnt_id = dest->dev; +#ifdef STATX_MNT_ID + unsigned int mnt_mask = STATX_MNT_ID; +# ifdef STATX_MNT_ID_UNIQUE + mnt_mask |= STATX_MNT_ID_UNIQUE; +# endif + if (src->stx_mask & mnt_mask) { + dest->mnt_id = src->stx_mnt_id; + } +#endif + dest->mask |= BFS_STAT_MNT_ID; + if (src->stx_mask & STATX_ATIME) { dest->atime.tv_sec = src->stx_atime.tv_sec; dest->atime.tv_nsec = src->stx_atime.tv_nsec; @@ -240,7 +269,7 @@ int bfs_statx_convert(struct bfs_stat *dest, const struct statx *src) { * bfs_stat() implementation backed by statx(). */ static int bfs_statx_impl(int at_fd, const char *at_path, int at_flags, struct bfs_stat *buf) { - unsigned int mask = STATX_BASIC_STATS | STATX_BTIME; + unsigned int mask = bfs_statx_mask(); struct statx xbuf; int ret = bfs_statx(at_fd, at_path, at_flags, mask, &xbuf); if (ret != 0) { @@ -14,6 +14,7 @@ #include "bfs.h" +#include <stdint.h> #include <sys/stat.h> #include <sys/types.h> #include <time.h> @@ -56,6 +57,7 @@ enum bfs_stat_field { BFS_STAT_BTIME = 1 << 11, BFS_STAT_CTIME = 1 << 12, BFS_STAT_MTIME = 1 << 13, + BFS_STAT_MNT_ID = 1 << 14, }; /** @@ -102,6 +104,8 @@ struct bfs_stat { blkcnt_t blocks; /** The device ID represented by this file. */ dev_t rdev; + /** The ID of the mount point containing this file. */ + uint64_t mnt_id; /** Attributes/flags set on the file. */ unsigned long long attrs; @@ -150,6 +154,11 @@ void bfs_stat_convert(struct bfs_stat *dest, const struct stat *src); int bfs_statx_flags(enum bfs_stat_flags flags); /** + * Get the default statx() mask. + */ +unsigned int bfs_statx_mask(void); + +/** * Convert struct statx to struct bfs_stat. */ int bfs_statx_convert(struct bfs_stat *dest, const struct statx *src); @@ -129,7 +129,7 @@ struct trie_node { * 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[]; + uintptr_t children[]; // _counted_by(count_ones(bitmap)) }; /** Check if an encoded pointer is to an internal node. */ @@ -173,16 +173,16 @@ void trie_init(struct trie *trie) { /** Extract the nibble at a certain offset from a byte sequence. */ static unsigned char trie_key_nibble(const void *key, size_t length, size_t offset) { const unsigned char *bytes = key; - size_t byte = offset >> 1; + size_t byte = offset / 2; bfs_assert(byte < length); // A branchless version of // if (offset & 1) { - // return bytes[byte] >> 4; - // } else { // return bytes[byte] & 0xF; + // } else { + // return bytes[byte] >> 4; // } - unsigned int shift = (offset & 1) << 2; + unsigned int shift = 4 * ((offset + 1) % 2); return (bytes[byte] >> shift) & 0xF; } @@ -191,6 +191,12 @@ static unsigned char trie_leaf_nibble(const struct trie_leaf *leaf, size_t offse return trie_key_nibble(leaf->key, leaf->length, offset); } +/** Get the number of children of an internal node. */ +_trie_clones +static unsigned int trie_node_size(const struct trie_node *node) { + return count_ones((unsigned int)node->bitmap); +} + /** * 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 @@ -202,19 +208,20 @@ _trie_clones static struct trie_leaf *trie_representative(const struct trie *trie, const void *key, size_t length) { uintptr_t ptr = trie->root; - size_t offset = 0; + size_t offset = 0, limit = 2 * length; while (trie_is_node(ptr)) { struct trie_node *node = trie_decode_node(ptr); offset += node->offset; unsigned int index = 0; - if ((offset >> 1) < length) { + if (offset < limit) { unsigned char nibble = trie_key_nibble(key, length, offset); unsigned int bit = 1U << nibble; - // bits = bitmap & bit ? bitmap & (bit - 1) : 0 - unsigned int mask = -!!(node->bitmap & bit); - unsigned int bits = node->bitmap & (bit - 1) & mask; - index = count_ones(bits); + unsigned int map = node->bitmap; + unsigned int bits = map & (bit - 1); + unsigned int mask = -!!(map & bit); + // index = (map & bit) ? count_ones(bits) : 0; + index = count_ones(bits) & mask; } ptr = node->children[index]; } @@ -240,6 +247,16 @@ struct trie_leaf *trie_find_mem(const struct trie *trie, const void *key, size_t return trie_find_mem_impl(trie, key, length); } +void *trie_get_str(const struct trie *trie, const char *key) { + const struct trie_leaf *leaf = trie_find_str(trie, key); + return leaf ? leaf->value : NULL; +} + +void *trie_get_mem(const struct trie *trie, const void *key, size_t length) { + const struct trie_leaf *leaf = trie_find_mem(trie, key, length); + return leaf ? leaf->value : NULL; +} + _trie_clones static struct trie_leaf *trie_find_postfix_impl(const struct trie *trie, const char *key) { size_t length = strlen(key); @@ -296,18 +313,18 @@ static struct trie_leaf *trie_find_prefix_impl(const struct trie *trie, const ch size_t skip = 0; size_t length = strlen(key) + 1; - size_t offset = 0; + size_t offset = 0, limit = 2 * length; while (trie_is_node(ptr)) { struct trie_node *node = trie_decode_node(ptr); offset += node->offset; - if ((offset >> 1) >= length) { + if (offset >= limit) { return best; } struct trie_leaf *leaf = trie_terminal_leaf(node); if (trie_check_prefix(leaf, skip, key, length)) { best = leaf; - skip = offset >> 1; + skip = offset / 2; } unsigned char nibble = trie_key_nibble(key, length, offset); @@ -370,16 +387,10 @@ static struct trie_node *trie_node_realloc(struct trie *trie, struct trie_node * /** Free a node. */ static void trie_node_free(struct trie *trie, struct trie_node *node, size_t size) { - bfs_assert(size == (size_t)count_ones(node->bitmap)); + bfs_assert(size == trie_node_size(node)); varena_free(&trie->nodes, node, size); } -#if ENDIAN_NATIVE == ENDIAN_LITTLE -# define TRIE_BSWAP(n) (n) -#elif ENDIAN_NATIVE == ENDIAN_BIG -# define TRIE_BSWAP(n) bswap(n) -#endif - /** Find the offset of the first nibble that differs between two keys. */ static size_t trie_mismatch(const struct trie_leaf *rep, const void *key, size_t length) { if (!rep) { @@ -393,32 +404,34 @@ static size_t trie_mismatch(const struct trie_leaf *rep, const void *key, size_t const char *rep_bytes = rep->key; const char *key_bytes = key; - size_t i = 0; - for (size_t chunk = sizeof(chunk); i + chunk <= length; i += chunk) { - size_t rep_chunk, key_chunk; - memcpy(&rep_chunk, rep_bytes + i, sizeof(rep_chunk)); - memcpy(&key_chunk, key_bytes + i, sizeof(key_chunk)); - - if (rep_chunk != key_chunk) { -#ifdef TRIE_BSWAP - size_t diff = TRIE_BSWAP(rep_chunk ^ key_chunk); - i *= 2; - i += trailing_zeros(diff) / 4; - return i; + size_t ret = 0, i = 0; + +#define CHUNK(n) CHUNK_(uint##n##_t, load8_beu##n) +#define CHUNK_(type, load8) \ + (length - i >= sizeof(type)) { \ + type rep_chunk = load8(rep_bytes + i); \ + type key_chunk = load8(key_bytes + i); \ + type diff = rep_chunk ^ key_chunk; \ + ret += leading_zeros(diff) / 4; \ + if (diff) { \ + return ret; \ + } \ + i += sizeof(type); \ + } + +#if SIZE_WIDTH >= 64 + while CHUNK(64); + if CHUNK(32); #else - break; + while CHUNK(32); #endif - } - } + if CHUNK(16); + if CHUNK(8); - for (; i < length; ++i) { - unsigned char diff = rep_bytes[i] ^ key_bytes[i]; - if (diff) { - return 2 * i + !(diff & 0xF); - } - } +#undef CHUNK_ +#undef CHUNK - return 2 * i; + return ret; } /** @@ -446,7 +459,7 @@ static size_t trie_mismatch(const struct trie_leaf *rep, const void *key, size_t _trie_clones static struct trie_leaf *trie_node_insert(struct trie *trie, uintptr_t *ptr, struct trie_leaf *leaf, unsigned char nibble) { struct trie_node *node = trie_decode_node(*ptr); - unsigned int size = count_ones(node->bitmap); + unsigned int size = trie_node_size(node); // Double the capacity every power of two if (has_single_bit(size)) { @@ -626,6 +639,26 @@ struct trie_leaf *trie_insert_mem(struct trie *trie, const void *key, size_t len return trie_insert_mem_impl(trie, key, length); } +int trie_set_str(struct trie *trie, const char *key, const void *value) { + struct trie_leaf *leaf = trie_insert_str(trie, key); + if (leaf) { + leaf->value = (void *)value; + return 0; + } else { + return -1; + } +} + +int trie_set_mem(struct trie *trie, const void *key, size_t length, const void *value) { + struct trie_leaf *leaf = trie_insert_mem(trie, key, length); + if (leaf) { + leaf->value = (void *)value; + return 0; + } else { + return -1; + } +} + /** Free a chain of singleton nodes. */ static void trie_free_singletons(struct trie *trie, uintptr_t ptr) { while (trie_is_node(ptr)) { @@ -711,7 +744,7 @@ static void trie_remove_impl(struct trie *trie, struct trie_leaf *leaf) { struct trie_node *node = trie_decode_node(*parent); trie_free_singletons(trie, node->children[child_index]); - unsigned int parent_size = count_ones(node->bitmap); + unsigned int parent_size = trie_node_size(node); bfs_assert(parent_size > 1); if (parent_size == 2 && trie_collapse_node(trie, parent, node, child_index) == 0) { return; @@ -21,7 +21,7 @@ struct trie_leaf { /** The length of the key in bytes. */ size_t length; /** The key itself, stored inline. */ - char key[]; + char key[] _counted_by(length); }; /** @@ -70,6 +70,32 @@ struct trie_leaf *trie_find_str(const struct trie *trie, const char *key); struct trie_leaf *trie_find_mem(const struct trie *trie, const void *key, size_t length); /** + * Get the value associated with a string key. + * + * @trie + * The trie to search. + * @key + * The key to look up. + * @return + * The found value, or NULL if the key is not present. + */ +void *trie_get_str(const struct trie *trie, const char *key); + +/** + * Get the value associated with a fixed-size key. + * + * @trie + * The trie to search. + * @key + * The key to look up. + * @length + * The length of the key in bytes. + * @return + * The found value, or NULL if the key is not present. + */ +void *trie_get_mem(const struct trie *trie, const void *key, size_t length); + +/** * Find the shortest leaf that starts with a given key. * * @trie @@ -120,6 +146,36 @@ struct trie_leaf *trie_insert_str(struct trie *trie, const char *key); struct trie_leaf *trie_insert_mem(struct trie *trie, const void *key, size_t length); /** + * Set the value for a string key. + * + * @trie + * The trie to modify. + * @key + * The key to insert. + * @value + * The value to set. + * @return + * 0 on success, -1 on error. + */ +int trie_set_str(struct trie *trie, const char *key, const void *value); + +/** + * Set the value for a fixed-size key. + * + * @trie + * The trie to modify. + * @key + * The key to insert. + * @length + * The length of the key in bytes. + * @value + * The value to set. + * @return + * 0 on success, -1 on error. + */ +int trie_set_mem(struct trie *trie, const void *key, size_t length, const void *value); + +/** * Remove a leaf from a trie. * * @trie diff --git a/src/xspawn.c b/src/xspawn.c index b3eed79..ee62c05 100644 --- a/src/xspawn.c +++ b/src/xspawn.c @@ -8,6 +8,7 @@ #include "bfstd.h" #include "diag.h" #include "list.h" +#include "sighook.h" #include <errno.h> #include <fcntl.h> @@ -231,12 +232,28 @@ int bfs_spawn_adddup2(struct bfs_spawn *ctx, int oldfd, int newfd) { */ #define BFS_POSIX_SPAWNP_AFTER_FCHDIR !(__APPLE__ || __NetBSD__) +/** + * NetBSD even resolves the executable before file actions with posix_spawn()! + */ +#define BFS_POSIX_SPAWN_AFTER_FCHDIR !__NetBSD__ + int bfs_spawn_addfchdir(struct bfs_spawn *ctx, int fd) { struct bfs_spawn_action *action = bfs_spawn_action(BFS_SPAWN_FCHDIR); if (!action) { return -1; } +#if __APPLE__ + // macOS has a bug that causes EBADF when an fchdir() action refers to a + // file opened by the file actions + for_slist (struct bfs_spawn_action, prev, ctx) { + if (fd == prev->out_fd) { + bfs_spawn_clear_posix(ctx); + break; + } + } +#endif + #if BFS_HAS_POSIX_SPAWN_ADDFCHDIR # define BFS_POSIX_SPAWN_ADDFCHDIR posix_spawn_file_actions_addfchdir #elif BFS_HAS_POSIX_SPAWN_ADDFCHDIR_NP @@ -400,18 +417,40 @@ static bool bfs_resolve_relative(const struct bfs_resolver *res) { return false; } +/** Check if the actions include fchdir(). */ +static bool bfs_spawn_will_chdir(const struct bfs_spawn *ctx) { + if (ctx) { + for_slist (const struct bfs_spawn_action, action, ctx) { + if (action->op == BFS_SPAWN_FCHDIR) { + return true; + } + } + } + + return false; +} + +/** Check if we can call xfaccessat() before file actions. */ +static bool bfs_can_access_early(const struct bfs_resolver *res, const struct bfs_spawn *ctx) { + if (res->exe[0] == '/') { + return true; + } + + if (bfs_spawn_will_chdir(ctx)) { + return false; + } + + return true; +} + /** Check if we can resolve the executable before file actions. */ static bool bfs_can_resolve_early(const struct bfs_resolver *res, const struct bfs_spawn *ctx) { if (!bfs_resolve_relative(res)) { return true; } - if (ctx) { - for_slist (const struct bfs_spawn_action, action, ctx) { - if (action->op == BFS_SPAWN_FCHDIR) { - return false; - } - } + if (bfs_spawn_will_chdir(ctx)) { + return false; } return true; @@ -441,17 +480,19 @@ static int bfs_resolve_early(struct bfs_resolver *res, const char *exe, const st }; if (bfs_can_skip_resolve(res, ctx)) { - // Do this check eagerly, even though posix_spawn()/execv() also - // would, because: - // - // - faccessat() is faster than fork()/clone() + execv() - // - posix_spawn() is not guaranteed to report ENOENT - if (xfaccessat(AT_FDCWD, exe, X_OK) == 0) { - res->done = true; - return 0; - } else { - return -1; + if (bfs_can_access_early(res, ctx)) { + // Do this check eagerly, even though posix_spawn()/execv() also + // would, because: + // + // - faccessat() is faster than fork()/clone() + execv() + // - posix_spawn() is not guaranteed to report ENOENT + if (xfaccessat(AT_FDCWD, exe, X_OK) != 0) { + return -1; + } } + + res->done = true; + return 0; } res->path = getenv("PATH"); @@ -528,6 +569,12 @@ static bool bfs_use_posix_spawn(const struct bfs_resolver *res, const struct bfs } #endif +#if !BFS_POSIX_SPAWN_AFTER_FCHDIR + if (res->exe[0] != '/' && bfs_spawn_will_chdir(ctx)) { + return false; + } +#endif + return true; } @@ -535,7 +582,7 @@ static bool bfs_use_posix_spawn(const struct bfs_resolver *res, const struct bfs /** Actually exec() the new process. */ _noreturn -static void bfs_spawn_exec(struct bfs_resolver *res, const struct bfs_spawn *ctx, char **argv, char **envp, int pipefd[2]) { +static void bfs_spawn_exec(struct bfs_resolver *res, const struct bfs_spawn *ctx, char **argv, char **envp, const sigset_t *mask, int pipefd[2]) { xclose(pipefd[0]); for_slist (const struct bfs_spawn_action, action, ctx) { @@ -596,6 +643,18 @@ static void bfs_spawn_exec(struct bfs_resolver *res, const struct bfs_spawn *ctx goto fail; } + // Reset signal handlers to their original values before we unblock + // signals, so that handlers don't run in both the parent and the child + if (sigreset() != 0) { + goto fail; + } + + // Restore the original signal mask for the child process + errno = pthread_sigmask(SIG_SETMASK, mask, NULL); + if (errno != 0) { + goto fail; + } + execve(res->exe, argv, envp); fail:; @@ -635,7 +694,7 @@ static pid_t bfs_fork_spawn(struct bfs_resolver *res, const struct bfs_spawn *ct #endif if (pid == 0) { // Child - bfs_spawn_exec(res, ctx, argv, envp, pipefd); + bfs_spawn_exec(res, ctx, argv, envp, &old_mask, pipefd); } // Restore the original signal mask |