summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/bar.c16
-rw-r--r--src/bfs.h14
-rw-r--r--src/bfstd.c132
-rw-r--r--src/bfstd.h63
-rw-r--r--src/bftw.c5
-rw-r--r--src/color.c273
-rw-r--r--src/color.h2
-rw-r--r--src/ctx.c1
-rw-r--r--src/ctx.h2
-rw-r--r--src/diag.h4
-rw-r--r--src/dstring.c2
-rw-r--r--src/eval.c4
-rw-r--r--src/expr.h3
-rw-r--r--src/ioq.c4
-rw-r--r--src/list.h31
-rw-r--r--src/opt.c19
-rw-r--r--src/parse.c391
-rw-r--r--src/prelude.h3
-rw-r--r--src/trie.c2
-rw-r--r--src/trie.h2
-rw-r--r--src/xspawn.c78
21 files changed, 771 insertions, 280 deletions
diff --git a/src/bar.c b/src/bar.c
index 3258df0..1b0691a 100644
--- a/src/bar.c
+++ b/src/bar.c
@@ -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). */
diff --git a/src/bfs.h b/src/bfs.h
index 32dbbae..70a7282 100644
--- a/src/bfs.h
+++ b/src/bfs.h
@@ -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
@@ -219,6 +222,15 @@ extern const char bfs_ldlibs[];
#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
diff --git a/src/bfstd.c b/src/bfstd.c
index 82663eb..b78af7a 100644
--- a/src/bfstd.c
+++ b/src/bfstd.c
@@ -23,10 +23,12 @@
#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>
@@ -187,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
@@ -243,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;
@@ -283,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);
@@ -558,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);
diff --git a/src/bfstd.h b/src/bfstd.h
index 28f473e..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>
/**
diff --git a/src/bftw.c b/src/bftw.c
index f822456..0ca6f34 100644
--- a/src/bftw.c
+++ b/src/bftw.c
@@ -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;
}
diff --git a/src/color.c b/src/color.c
index 0cc950b..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;
@@ -161,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. */
@@ -607,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) {
@@ -672,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;
}
@@ -949,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;
@@ -963,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)) {
@@ -986,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;
@@ -999,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)) {
@@ -1240,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) {
@@ -1254,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) {
@@ -1413,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 '$'
diff --git a/src/ctx.c b/src/ctx.c
index d92d8ba..05baa1d 100644
--- a/src/ctx.c
+++ b/src/ctx.c
@@ -286,6 +286,7 @@ int bfs_ctx_free(struct bfs_ctx *ctx) {
}
free(ctx->paths);
+ free(ctx->kinds);
free(ctx->argv);
free(ctx);
}
diff --git a/src/ctx.h b/src/ctx.h
index be6e2af..908338f 100644
--- a/src/ctx.h
+++ b/src/ctx.h
@@ -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;
diff --git a/src/diag.h b/src/diag.h
index 645dbb1..0a4d3b9 100644
--- a/src/diag.h
+++ b/src/diag.h
@@ -18,6 +18,7 @@
*
* bfs: func@src/file.c:0: Message
*/
+// Use (format) ? "..." : "" so the format string is required
#define BFS_DIAG_FORMAT_(format) \
((format) ? "%s: %s@%s:%d: " format "%s" : "")
@@ -75,7 +76,7 @@ void bfs_abortf(const char *format, ...);
bfs_eabort_(__VA_ARGS__, )
#define bfs_eabort_(format, ...) \
- ((format) ? bfs_abort_(format ": %s", __VA_ARGS__ errstr(), ) : (void)0)
+ bfs_abort_(format "%s%s", __VA_ARGS__ (sizeof("" format) > 1 ? ": " : ""), errstr(), )
/**
* Abort in debug builds; no-op in release builds.
@@ -116,7 +117,6 @@ void bfs_abortf(const char *format, ...);
#define bfs_everify(...) \
bfs_everify_(#__VA_ARGS__, __VA_ARGS__, "", )
-
#define bfs_everify_(str, cond, format, ...) \
((cond) ? (void)0 : bfs_everify__(format, BFS_DIAG_MSG_(format, str), __VA_ARGS__))
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)
diff --git a/src/eval.c b/src/eval.c
index 7c9da97..0d1bf68 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -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;
}
diff --git a/src/expr.h b/src/expr.h
index 871b120..c116778 100644
--- a/src/expr.h
+++ b/src/expr.h
@@ -19,6 +19,9 @@
* Argument/token/expression kinds.
*/
enum bfs_kind {
+ /** A regular argument. */
+ BFS_ARG,
+
/** A flag (-H, -L, etc.). */
BFS_FLAG,
diff --git a/src/ioq.c b/src/ioq.c
index 1efedd7..57eb4a5 100644
--- a/src/ioq.c
+++ b/src/ioq.c
@@ -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. */
@@ -593,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. */
diff --git a/src/list.h b/src/list.h
index 15c37a8..276c610 100644
--- a/src/list.h
+++ b/src/list.h
@@ -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.
diff --git a/src/opt.c b/src/opt.c
index 49e8873..9094794 100644
--- a/src/opt.c
+++ b/src/opt.c
@@ -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/trie.c b/src/trie.c
index 4e0944a..6aac17f 100644
--- a/src/trie.c
+++ b/src/trie.c
@@ -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. */
diff --git a/src/trie.h b/src/trie.h
index d8cecab..19bd81d 100644
--- a/src/trie.h
+++ b/src/trie.h
@@ -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);
};
/**
diff --git a/src/xspawn.c b/src/xspawn.c
index 3fa4e60..ee62c05 100644
--- a/src/xspawn.c
+++ b/src/xspawn.c
@@ -232,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
@@ -401,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;
@@ -442,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");
@@ -529,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;
}