diff options
author | Tavian Barnes <tavianator@tavianator.com> | 2017-01-14 16:38:08 -0500 |
---|---|---|
committer | Tavian Barnes <tavianator@tavianator.com> | 2017-02-05 19:02:25 -0500 |
commit | a6f94c132c425bbab543e98fcd19f4ff7519d1b7 (patch) | |
tree | 215135837c0335946b70593877ec5411ba8e6c17 | |
parent | 9f1863d45fe596e258596a4b4cc9a4064bcb11d3 (diff) |
Implement -printf/-fprintf
Based on a patch by Fangrui Song <i@maskray.me>.
Closes #16.
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | bfs.h | 9 | ||||
-rw-r--r-- | bftw.c | 1 | ||||
-rw-r--r-- | bftw.h | 2 | ||||
-rw-r--r-- | eval.c | 77 | ||||
-rw-r--r-- | parse.c | 72 | ||||
-rw-r--r-- | printf.c | 768 | ||||
-rw-r--r-- | printf.h | 62 | ||||
-rwxr-xr-x | tests.sh | 51 | ||||
-rw-r--r-- | tests/test_0130.out | 19 | ||||
-rw-r--r-- | tests/test_0131.out | 1 | ||||
-rw-r--r-- | tests/test_0132.out | 1 | ||||
-rw-r--r-- | tests/test_0133.out | 19 | ||||
-rw-r--r-- | tests/test_0134.out | 19 | ||||
-rw-r--r-- | tests/test_0135.out | 19 | ||||
-rw-r--r-- | tests/test_0136.out | 10 | ||||
-rw-r--r-- | tests/test_0137.out | 1 |
17 files changed, 1087 insertions, 46 deletions
@@ -43,7 +43,7 @@ ALL_LDFLAGS = $(ALL_CFLAGS) $(LDFLAGS) all: bfs -bfs: bftw.o color.o dstring.o eval.o main.o parse.o typo.o util.o +bfs: bftw.o color.o dstring.o eval.o main.o parse.o printf.o typo.o util.o $(CC) $(ALL_LDFLAGS) $^ -o $@ release: CFLAGS := -O3 -flto -Wall -DNDEBUG @@ -13,6 +13,7 @@ #define BFS_H #include "color.h" +#include "printf.h" #include <regex.h> #include <stdbool.h> #include <stddef.h> @@ -248,6 +249,9 @@ struct expr { /** Optional compiled regex. */ regex_t *regex; + /** Optional printf command. */ + struct bfs_printf *printf; + /** Optional integer data for this expression. */ long long idata; @@ -310,9 +314,10 @@ bool eval_regex(const struct expr *expr, struct eval_state *state); bool eval_delete(const struct expr *expr, struct eval_state *state); bool eval_exec(const struct expr *expr, struct eval_state *state); bool eval_nohidden(const struct expr *expr, struct eval_state *state); -bool eval_print(const struct expr *expr, struct eval_state *state); bool eval_fprint(const struct expr *expr, struct eval_state *state); -bool eval_print0(const struct expr *expr, struct eval_state *state); +bool eval_fprint0(const struct expr *expr, struct eval_state *state); +bool eval_fprintf(const struct expr *expr, struct eval_state *state); +bool eval_print(const struct expr *expr, struct eval_state *state); bool eval_prune(const struct expr *expr, struct eval_state *state); bool eval_quit(const struct expr *expr, struct eval_state *state); @@ -755,6 +755,7 @@ static size_t basename_offset(const char *path) { static void bftw_init_buffers(struct bftw_state *state, const struct dirent *de) { struct BFTW *ftwbuf = &state->ftwbuf; ftwbuf->path = state->path; + ftwbuf->root = state->root; ftwbuf->error = 0; ftwbuf->visit = (state->status == BFTW_GC ? BFTW_POST : BFTW_PRE); ftwbuf->statbuf = NULL; @@ -64,6 +64,8 @@ struct BFTW { /** The string offset of the filename. */ size_t nameoff; + /** The root path passed to bftw(). */ + const char *root; /** The depth of this file in the traversal. */ size_t depth; /** Which visit this is. */ @@ -37,7 +37,7 @@ struct eval_state { /** The bftw() callback return value. */ enum bftw_action action; /** The eval_cmdline() return value. */ - int ret; + int *ret; /** A stat() buffer, if necessary. */ struct stat statbuf; }; @@ -58,7 +58,7 @@ static void eval_error(struct eval_state *state) { if (!eval_should_ignore(state, errno)) { pretty_error(state->cmdline->stderr_colors, "'%s': %s\n", state->ftwbuf->path, strerror(errno)); - state->ret = -1; + *state->ret = -1; } } @@ -557,14 +557,6 @@ done: } /** - * -prune action. - */ -bool eval_prune(const struct expr *expr, struct eval_state *state) { - state->action = BFTW_SKIP_SUBTREE; - return true; -} - -/** * -hidden test. */ bool eval_hidden(const struct expr *expr, struct eval_state *state) { @@ -709,22 +701,6 @@ bool eval_perm(const struct expr *expr, struct eval_state *state) { } /** - * -print action. - */ -bool eval_print(const struct expr *expr, struct eval_state *state) { - const struct colors *colors = state->cmdline->stdout_colors; - if (colors) { - fill_statbuf(state); - } - - if (pretty_print(colors, state->ftwbuf) != 0) { - eval_error(state); - } - - return true; -} - -/** * -fprint action. */ bool eval_fprint(const struct expr *expr, struct eval_state *state) { @@ -745,7 +721,7 @@ error: /** * -f?print0 action. */ -bool eval_print0(const struct expr *expr, struct eval_state *state) { +bool eval_fprint0(const struct expr *expr, struct eval_state *state) { const char *path = state->ftwbuf->path; size_t length = strlen(path) + 1; if (fwrite(path, 1, length, expr->file) != length) { @@ -755,6 +731,48 @@ bool eval_print0(const struct expr *expr, struct eval_state *state) { } /** + * -f?printf action. + */ +bool eval_fprintf(const struct expr *expr, struct eval_state *state) { + if (expr->printf->needs_stat) { + if (!fill_statbuf(state)) { + goto done; + } + } + + if (bfs_printf(expr->file, expr->printf, state->ftwbuf) != 0) { + eval_error(state); + } + +done: + return true; +} + +/** + * -print action. + */ +bool eval_print(const struct expr *expr, struct eval_state *state) { + const struct colors *colors = state->cmdline->stdout_colors; + if (colors) { + fill_statbuf(state); + } + + if (pretty_print(colors, state->ftwbuf) != 0) { + eval_error(state); + } + + return true; +} + +/** + * -prune action. + */ +bool eval_prune(const struct expr *expr, struct eval_state *state) { + state->action = BFTW_SKIP_SUBTREE; + return true; +} + +/** * -quit action. */ bool eval_quit(const struct expr *expr, struct eval_state *state) { @@ -1054,12 +1072,12 @@ static enum bftw_action cmdline_callback(struct BFTW *ftwbuf, void *ptr) { .ftwbuf = ftwbuf, .cmdline = cmdline, .action = BFTW_CONTINUE, - .ret = args->ret, + .ret = &args->ret, }; if (ftwbuf->typeflag == BFTW_ERROR) { if (!eval_should_ignore(&state, ftwbuf->error)) { - state.ret = -1; + args->ret = -1; pretty_error(cmdline->stderr_colors, "'%s': %s\n", ftwbuf->path, strerror(ftwbuf->error)); } state.action = BFTW_SKIP_SUBTREE; @@ -1089,7 +1107,6 @@ done: debug_stat(&state); } - args->ret = state.ret; return state.action; } @@ -10,6 +10,7 @@ *********************************************************************/ #include "bfs.h" +#include "printf.h" #include "typo.h" #include "util.h" #include <ctype.h> @@ -77,6 +78,8 @@ static void free_expr(struct expr *expr) { free(expr->regex); } + free_bfs_printf(expr->printf); + free_expr(expr->lhs); free_expr(expr->rhs); free(expr); @@ -101,6 +104,7 @@ static struct expr *new_expr(eval_fn *eval, bool pure, size_t argc, char **argv) expr->argv = argv; expr->file = NULL; expr->regex = NULL; + expr->printf = NULL; } return expr; } @@ -964,7 +968,7 @@ static struct expr *parse_fprint(struct parser_state *state, int arg1, int arg2) * Parse -fprint0 FILE. */ static struct expr *parse_fprint0(struct parser_state *state, int arg1, int arg2) { - struct expr *expr = parse_unary_action(state, eval_print0); + struct expr *expr = parse_unary_action(state, eval_fprint0); if (expr) { if (expr_open(state, expr, expr->sdata) != 0) { return NULL; @@ -974,6 +978,44 @@ static struct expr *parse_fprint0(struct parser_state *state, int arg1, int arg2 } /** + * Parse -fprintf FILE FORMAT. + */ +static struct expr *parse_fprintf(struct parser_state *state, int arg1, int arg2) { + const char *arg = state->argv[0]; + + const char *file = state->argv[1]; + if (!file) { + pretty_error(state->cmdline->stderr_colors, + "error: %s needs a file.\n", arg); + return NULL; + } + + const char *format = state->argv[2]; + if (!format) { + pretty_error(state->cmdline->stderr_colors, + "error: %s needs a format string.\n", arg); + return NULL; + } + + struct expr *expr = parse_action(state, eval_fprintf, 3); + if (!expr) { + return NULL; + } + + if (expr_open(state, expr, file) != 0) { + return NULL; + } + + expr->printf = parse_bfs_printf(format, state->cmdline->stderr_colors); + if (!expr->printf) { + free_expr(expr); + return NULL; + } + + return expr; +} + +/** * Parse -gid/-group. */ static struct expr *parse_group(struct parser_state *state, int arg1, int arg2) { @@ -1495,7 +1537,7 @@ static struct expr *parse_print(struct parser_state *state, int arg1, int arg2) * Parse -print0. */ static struct expr *parse_print0(struct parser_state *state, int arg1, int arg2) { - struct expr *expr = parse_nullary_action(state, eval_print0); + struct expr *expr = parse_nullary_action(state, eval_fprint0); if (expr) { expr->file = stdout; } @@ -1503,6 +1545,26 @@ static struct expr *parse_print0(struct parser_state *state, int arg1, int arg2) } /** + * Parse -printf FORMAT. + */ +static struct expr *parse_printf(struct parser_state *state, int arg1, int arg2) { + struct expr *expr = parse_unary_action(state, eval_fprintf); + if (!expr) { + return NULL; + } + + expr->file = stdout; + + expr->printf = parse_bfs_printf(expr->sdata, state->cmdline->stderr_colors); + if (!expr->printf) { + free_expr(expr); + return NULL; + } + + return expr; +} + +/** * Parse -prune. */ static struct expr *parse_prune(struct parser_state *state, int arg1, int arg2) { @@ -1775,8 +1837,8 @@ static struct expr *parse_help(struct parser_state *state, int arg1, int arg2) { printf(" -amin, -anewer, -cmin, -cnewer, -mmin, -empty, -false, -gid, -ilname, -iname,\n"); printf(" -inum, -ipath, -iwholename, -iregex, -lname, -newerXY, -wholename, -regex,\n"); printf(" -readable, -writable, -executable, -samefile, -true, -uid, -used, -xtype,\n"); - printf(" -delete, -execdir ... ;, -okdir ... ;, -print0, -fprint, -fprint0, -quit,\n"); - printf(" -help, -version\n\n"); + printf(" -delete, -execdir ... ;, -okdir ... ;, -print0, -printf, -fprint, -fprint0,\n"); + printf(" -fprintf, -quit, -help, -version\n\n"); printf("BSD find features:\n"); printf(" -E, -d, -x, -depth N, -gid NAME, -uid NAME, -size N[ckMGTP], -sparse\n\n"); @@ -1848,6 +1910,7 @@ static const struct table_entry parse_table[] = { {"follow", false, parse_follow, BFTW_LOGICAL | BFTW_DETECT_CYCLES, true}, {"fprint", false, parse_fprint}, {"fprint0", false, parse_fprint0}, + {"fprintf", false, parse_fprintf}, {"gid", false, parse_group}, {"group", false, parse_group}, {"help", false, parse_help}, @@ -1886,6 +1949,7 @@ static const struct table_entry parse_table[] = { {"perm", false, parse_perm}, {"print", false, parse_print}, {"print0", false, parse_print0}, + {"printf", false, parse_printf}, {"prune", false, parse_prune}, {"quit", false, parse_quit}, {"readable", false, parse_access, R_OK}, diff --git a/printf.c b/printf.c new file mode 100644 index 0000000..7ee0dc1 --- /dev/null +++ b/printf.c @@ -0,0 +1,768 @@ +/********************************************************************* + * bfs * + * Copyright (C) 2017 Tavian Barnes <tavianator@tavianator.com> * + * * + * This program is free software. It comes without any warranty, to * + * the extent permitted by applicable law. You can redistribute it * + * and/or modify it under the terms of the Do What The Fuck You Want * + * To Public License, Version 2, as published by Sam Hocevar. See * + * the COPYING file or http://www.wtfpl.net/ for more details. * + *********************************************************************/ + +#include "printf.h" +#include "color.h" +#include "dstring.h" +#include "util.h" +#include <assert.h> +#include <errno.h> +#include <grp.h> +#include <pwd.h> +#include <stdlib.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> +#include <sys/stat.h> +#include <time.h> + +typedef int bfs_printf_fn(FILE *file, const struct bfs_printf_directive *directive, const struct BFTW *ftwbuf); + +/** + * A single directive in a printf command. + */ +struct bfs_printf_directive { + /** The printing function to invoke. */ + bfs_printf_fn *fn; + /** String data associated with this directive. */ + char *str; + /** The next printf directive in the chain. */ + struct bfs_printf_directive *next; +}; + +/** Print some text as-is. */ +static int bfs_printf_literal(FILE *file, const struct bfs_printf_directive *directive, const struct BFTW *ftwbuf) { + return fprintf(file, "%s", directive->str); +} + +/** \c: flush */ +static int bfs_printf_flush(FILE *file, const struct bfs_printf_directive *directive, const struct BFTW *ftwbuf) { + return fflush(file); +} + +/** + * Print a value to a temporary buffer before formatting it. + */ +#define BFS_PRINTF_BUF(buf, format, ...) \ + char buf[256]; \ + int ret = snprintf(buf, sizeof(buf), format, __VA_ARGS__); \ + assert(ret >= 0 && ret < sizeof(buf)); \ + (void)ret + +/** + * Print a ctime()-style string, for %a, %c, and %t. + */ +static int bfs_printf_ctime(FILE *file, const struct bfs_printf_directive *directive, const struct timespec *ts) { + // Not using ctime() itself because GNU find adds nanoseconds + static const char *days[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; + static const char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; + + const struct tm *tm = localtime(&ts->tv_sec); + BFS_PRINTF_BUF(buf, "%s %s %2d %.2d:%.2d:%.2d.%09ld0 %4d", + days[tm->tm_wday], + months[tm->tm_mon], + tm->tm_mday, + tm->tm_hour, + tm->tm_min, + tm->tm_sec, + (long)ts->tv_nsec, + 1900 + tm->tm_year); + + return fprintf(file, directive->str, buf); +} + +/** %a: access time */ +static int bfs_printf_a(FILE *file, const struct bfs_printf_directive *directive, const struct BFTW *ftwbuf) { + return bfs_printf_ctime(file, directive, &ftwbuf->statbuf->st_atim); +} + +/** %b: blocks */ +static int bfs_printf_b(FILE *file, const struct bfs_printf_directive *directive, const struct BFTW *ftwbuf) { + BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)ftwbuf->statbuf->st_blocks); + return fprintf(file, directive->str, buf); +} + +/** %c: change time */ +static int bfs_printf_c(FILE *file, const struct bfs_printf_directive *directive, const struct BFTW *ftwbuf) { + return bfs_printf_ctime(file, directive, &ftwbuf->statbuf->st_ctim); +} + +/** %d: depth */ +static int bfs_printf_d(FILE *file, const struct bfs_printf_directive *directive, const struct BFTW *ftwbuf) { + return fprintf(file, directive->str, (intmax_t)ftwbuf->depth); +} + +/** %D: device */ +static int bfs_printf_D(FILE *file, const struct bfs_printf_directive *directive, const struct BFTW *ftwbuf) { + BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)ftwbuf->statbuf->st_dev); + return fprintf(file, directive->str, buf); +} + +/** %f: file name */ +static int bfs_printf_f(FILE *file, const struct bfs_printf_directive *directive, const struct BFTW *ftwbuf) { + return fprintf(file, directive->str, ftwbuf->path + ftwbuf->nameoff); +} + +/** %G: gid */ +static int bfs_printf_G(FILE *file, const struct bfs_printf_directive *directive, const struct BFTW *ftwbuf) { + BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)ftwbuf->statbuf->st_gid); + return fprintf(file, directive->str, buf); +} + +/** %g: group name */ +static int bfs_printf_g(FILE *file, const struct bfs_printf_directive *directive, const struct BFTW *ftwbuf) { + struct group *grp = getgrgid(ftwbuf->statbuf->st_gid); + if (!grp) { + return bfs_printf_G(file, directive, ftwbuf); + } + + return fprintf(file, directive->str, grp->gr_name); +} + +/** %h: leading directories */ +static int bfs_printf_h(FILE *file, const struct bfs_printf_directive *directive, const struct BFTW *ftwbuf) { + char *copy = NULL; + const char *buf; + + if (ftwbuf->nameoff > 0) { + size_t len = ftwbuf->nameoff; + if (len > 1) { + --len; + } + + buf = copy = strndup(ftwbuf->path, len); + } else if (ftwbuf->path[0] == '/') { + buf = "/"; + } else { + buf = "."; + } + + int ret = fprintf(file, directive->str, buf); + free(copy); + return ret; +} + +/** %H: current root */ +static int bfs_printf_H(FILE *file, const struct bfs_printf_directive *directive, const struct BFTW *ftwbuf) { + return fprintf(file, directive->str, ftwbuf->root); +} + +/** %i: inode */ +static int bfs_printf_i(FILE *file, const struct bfs_printf_directive *directive, const struct BFTW *ftwbuf) { + BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)ftwbuf->statbuf->st_ino); + return fprintf(file, directive->str, buf); +} + +/** %k: 1K blocks */ +static int bfs_printf_k(FILE *file, const struct bfs_printf_directive *directive, const struct BFTW *ftwbuf) { + BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)(ftwbuf->statbuf->st_blocks + 1)/2); + return fprintf(file, directive->str, buf); +} + +/** %l: link target */ +static int bfs_printf_l(FILE *file, const struct bfs_printf_directive *directive, const struct BFTW *ftwbuf) { + if (ftwbuf->typeflag != BFTW_LNK) { + return 0; + } + + char *target = xreadlinkat(ftwbuf->at_fd, ftwbuf->at_path, 0); + if (!target) { + return -1; + } + + int ret = fprintf(file, directive->str, target); + free(target); + return ret; +} + +/** %m: mode */ +static int bfs_printf_m(FILE *file, const struct bfs_printf_directive *directive, const struct BFTW *ftwbuf) { + return fprintf(file, directive->str, (unsigned int)(ftwbuf->statbuf->st_mode & 07777)); +} + +/** %M: symbolic mode */ +static int bfs_printf_M(FILE *file, const struct bfs_printf_directive *directive, const struct BFTW *ftwbuf) { + char buf[] = "----------"; + + switch (ftwbuf->typeflag) { + case BFTW_BLK: + buf[0] = 'b'; + break; + case BFTW_CHR: + buf[0] = 'c'; + break; + case BFTW_DIR: + buf[0] = 'd'; + break; + case BFTW_DOOR: + buf[0] = 'D'; + break; + case BFTW_FIFO: + buf[0] = 'p'; + break; + case BFTW_LNK: + buf[0] = 'l'; + break; + case BFTW_SOCK: + buf[0] = 's'; + break; + default: + break; + } + + mode_t mode = ftwbuf->statbuf->st_mode; + + if (mode & 00400) { + buf[1] = 'r'; + } + if (mode & 00200) { + buf[2] = 'w'; + } + if ((mode & 04100) == 04000) { + buf[3] = 'S'; + } else if (mode & 04000) { + buf[3] = 's'; + } else if (mode & 00100) { + buf[3] = 'x'; + } + + if (mode & 00040) { + buf[4] = 'r'; + } + if (mode & 00020) { + buf[5] = 'w'; + } + if ((mode & 02010) == 02000) { + buf[6] = 'S'; + } else if (mode & 02000) { + buf[6] = 's'; + } else if (mode & 00010) { + buf[6] = 'x'; + } + + if (mode & 00004) { + buf[7] = 'r'; + } + if (mode & 00002) { + buf[8] = 'w'; + } + if ((mode & 01001) == 01000) { + buf[9] = 'T'; + } else if (mode & 01000) { + buf[9] = 't'; + } else if (mode & 00001) { + buf[9] = 'x'; + } + + return fprintf(file, directive->str, buf); +} + +/** %n: link count */ +static int bfs_printf_n(FILE *file, const struct bfs_printf_directive *directive, const struct BFTW *ftwbuf) { + BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)ftwbuf->statbuf->st_nlink); + return fprintf(file, directive->str, buf); +} + +/** %p: full path */ +static int bfs_printf_p(FILE *file, const struct bfs_printf_directive *directive, const struct BFTW *ftwbuf) { + return fprintf(file, directive->str, ftwbuf->path); +} + +/** %P: path after root */ +static int bfs_printf_P(FILE *file, const struct bfs_printf_directive *directive, const struct BFTW *ftwbuf) { + const char *path = ftwbuf->path + strlen(ftwbuf->root); + if (path[0] == '/') { + ++path; + } + return fprintf(file, directive->str, path); +} + +/** %s: size */ +static int bfs_printf_s(FILE *file, const struct bfs_printf_directive *directive, const struct BFTW *ftwbuf) { + BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)ftwbuf->statbuf->st_size); + return fprintf(file, directive->str, buf); +} + +/** %S: sparseness */ +static int bfs_printf_S(FILE *file, const struct bfs_printf_directive *directive, const struct BFTW *ftwbuf) { + double sparsity = 512.0 * ftwbuf->statbuf->st_blocks / ftwbuf->statbuf->st_size; + return fprintf(file, directive->str, sparsity); +} + +/** %t: modification time */ +static int bfs_printf_t(FILE *file, const struct bfs_printf_directive *directive, const struct BFTW *ftwbuf) { + return bfs_printf_ctime(file, directive, &ftwbuf->statbuf->st_mtim); +} + +/** %U: uid */ +static int bfs_printf_U(FILE *file, const struct bfs_printf_directive *directive, const struct BFTW *ftwbuf) { + BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)ftwbuf->statbuf->st_uid); + return fprintf(file, directive->str, buf); +} + +/** %u: user name */ +static int bfs_printf_u(FILE *file, const struct bfs_printf_directive *directive, const struct BFTW *ftwbuf) { + struct passwd *pwd = getpwuid(ftwbuf->statbuf->st_uid); + if (!pwd) { + return bfs_printf_U(file, directive, ftwbuf); + } + + return fprintf(file, directive->str, pwd->pw_name); +} + +/** %y: type */ +static int bfs_printf_y(FILE *file, const struct bfs_printf_directive *directive, const struct BFTW *ftwbuf) { + const char *type; + switch (ftwbuf->typeflag) { + case BFTW_BLK: + type = "b"; + break; + case BFTW_CHR: + type = "c"; + break; + case BFTW_DIR: + type = "d"; + break; + case BFTW_DOOR: + type = "D"; + break; + case BFTW_FIFO: + type = "p"; + break; + case BFTW_LNK: + type = "l"; + break; + case BFTW_REG: + type = "f"; + break; + case BFTW_SOCK: + type = "s"; + break; + default: + type = "U"; + break; + } + + return fprintf(file, directive->str, type); +} + +/** %Y: target type */ +static int bfs_printf_Y(FILE *file, const struct bfs_printf_directive *directive, const struct BFTW *ftwbuf) { + if (ftwbuf->typeflag != BFTW_LNK) { + return bfs_printf_y(file, directive, ftwbuf); + } + + const char *type = "U"; + + struct stat sb; + if (fstatat(ftwbuf->at_fd, ftwbuf->at_path, &sb, 0) == 0) { + switch (sb.st_mode & S_IFMT) { +#ifdef S_IFBLK + case S_IFBLK: + type = "b"; + break; +#endif +#ifdef S_IFCHR + case S_IFCHR: + type = "c"; + break; +#endif +#ifdef S_IFDIR + case S_IFDIR: + type = "d"; + break; +#endif +#ifdef S_IFDOOR + case S_IFDOOR: + type = "D"; + break; +#endif +#ifdef S_IFIFO + case S_IFIFO: + type = "p"; + break; +#endif +#ifdef S_IFLNK + case S_IFLNK: + type = "l"; + break; +#endif +#ifdef S_IFREG + case S_IFREG: + type = "f"; + break; +#endif +#ifdef S_IFSOCK + case S_IFSOCK: + type = "s"; + break; +#endif + } + } else { + switch (errno) { + case ELOOP: + type = "L"; + break; + case ENOENT: + type = "N"; + break; + } + } + + return fprintf(file, directive->str, type); +} + +/** + * Append a printf directive to the chain. + */ +static int append_directive(struct bfs_printf_directive ***tail, bfs_printf_fn *fn, char *str) { + struct bfs_printf_directive *directive = malloc(sizeof(*directive)); + if (!directive) { + perror("malloc()"); + return -1; + } + + directive->fn = fn; + directive->str = str; + directive->next = NULL; + **tail = directive; + *tail = &directive->next; + return 0; +} + +/** + * Append a literal string to the chain. + */ +static int append_literal(struct bfs_printf_directive ***tail, char **literal, bool last) { + if (!*literal || dstrlen(*literal) == 0) { + return 0; + } + + if (append_directive(tail, bfs_printf_literal, *literal) != 0) { + return -1; + } + + if (last) { + *literal = NULL; + } else { + *literal = dstralloc(0); + if (!*literal) { + perror("dstralloc()"); + return -1; + } + } + + return 0; +} + +struct bfs_printf *parse_bfs_printf(const char *format, const struct colors *stderr_colors) { + struct bfs_printf *command = malloc(sizeof(*command)); + if (!command) { + return NULL; + } + + command->directives = NULL; + command->needs_stat = false; + struct bfs_printf_directive **tail = &command->directives; + + char *literal = dstralloc(0); + + for (const char *i = format; *i; ++i) { + char c = *i; + + if (c == '\\') { + c = *++i; + + if (c >= '0' && c < '8') { + c = 0; + for (int j = 0; j < 3 && *i >= '0' && *i < '8'; ++i, ++j) { + c *= 8; + c += *i - '0'; + } + --i; + goto one_char; + } + + switch (c) { + case 'a': c = '\a'; break; + case 'b': c = '\b'; break; + case 'f': c = '\f'; break; + case 'n': c = '\n'; break; + case 'r': c = '\r'; break; + case 't': c = '\t'; break; + case 'v': c = '\v'; break; + case '\\': c = '\\'; break; + + case 'c': + if (append_literal(&tail, &literal, true) != 0) { + goto error; + } + if (append_directive(&tail, bfs_printf_flush, NULL) != 0) { + goto error; + } + goto done; + + case '\0': + pretty_error(stderr_colors, + "error: '%s': Incomplete escape sequence '\\'.\n", + format); + goto error; + + default: + pretty_error(stderr_colors, + "error: '%s': Unrecognized escape sequence '\\%c'.\n", + format, c); + goto error; + } + } else if (c == '%') { + bfs_printf_fn *fn; + + char *directive = dstralloc(2); + if (!directive) { + perror("dstralloc()"); + goto directive_error; + } + dstrncat(&directive, &c, 1); + + const char *specifier = "s"; + + // Parse any flags + bool must_be_int = false; + while (true) { + c = *++i; + + switch (c) { + case '#': + case '0': + case '+': + must_be_int = true; + case ' ': + case '-': + if (strchr(directive, c)) { + pretty_error(stderr_colors, + "error: '%s': Duplicate flag '%c'.\n", + format, c); + goto directive_error; + } + if (dstrncat(&directive, &c, 1) != 0) { + perror("dstrncat()"); + goto directive_error; + } + continue; + } + + break; + } + + // Parse the field width + while (c >= '0' && c <= '9') { + if (dstrncat(&directive, &c, 1) != 0) { + perror("dstrncat()"); + goto directive_error; + } + c = *++i; + } + + // Parse the precision + if (c == '.') { + do { + if (dstrncat(&directive, &c, 1) != 0) { + perror("dstrncat()"); + goto directive_error; + } + c = *++i; + } while (c >= '0' && c <= '9'); + } + + switch (c) { + case '%': + dstrfree(directive); + goto one_char; + case 'a': + fn = bfs_printf_a; + command->needs_stat = true; + break; + case 'b': + fn = bfs_printf_b; + command->needs_stat = true; + break; + case 'c': + fn = bfs_printf_c; + command->needs_stat = true; + break; + case 'd': + fn = bfs_printf_d; + specifier = "jd"; + break; + case 'D': + fn = bfs_printf_D; + command->needs_stat = true; + break; + case 'f': + fn = bfs_printf_f; + break; + case 'g': + fn = bfs_printf_g; + command->needs_stat = true; + break; + case 'G': + fn = bfs_printf_G; + command->needs_stat = true; + break; + case 'h': + fn = bfs_printf_h; + break; + case 'H': + fn = bfs_printf_H; + break; + case 'i': + fn = bfs_printf_i; + command->needs_stat = true; + break; + case 'k': + fn = bfs_printf_k; + command->needs_stat = true; + break; + case 'l': + fn = bfs_printf_l; + break; + case 'm': + fn = bfs_printf_m; + specifier = "o"; + command->needs_stat = true; + break; + case 'M': + fn = bfs_printf_M; + command->needs_stat = true; + break; + case 'n': + fn = bfs_printf_n; + command->needs_stat = true; + break; + case 'p': + fn = bfs_printf_p; + break; + case 'P': + fn = bfs_printf_P; + break; + case 's': + fn = bfs_printf_s; + command->needs_stat = true; + break; + case 'S': + fn = bfs_printf_S; + specifier = "g"; + command->needs_stat = true; + break; + case 't': + fn = bfs_printf_t; + command->needs_stat = true; + break; + case 'u': + fn = bfs_printf_u; + command->needs_stat = true; + break; + case 'U': + fn = bfs_printf_U; + command->needs_stat = true; + break; + case 'y': + fn = bfs_printf_y; + break; + case 'Y': + fn = bfs_printf_Y; + break; + + case '\0': + pretty_error(stderr_colors, + "error: '%s': Incomplete format specifier '%s'.\n", + format, directive); + goto directive_error; + + default: + pretty_error(stderr_colors, + "error: '%s': Unrecognized format specifier '%%%c'.\n", + format, c); + goto directive_error; + } + + if (must_be_int && strcmp(specifier, "s") == 0) { + pretty_error(stderr_colors, + |