From a6f94c132c425bbab543e98fcd19f4ff7519d1b7 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sat, 14 Jan 2017 16:38:08 -0500 Subject: Implement -printf/-fprintf Based on a patch by Fangrui Song . Closes #16. --- Makefile | 2 +- bfs.h | 9 +- bftw.c | 1 + bftw.h | 2 + eval.c | 77 ++++-- parse.c | 72 ++++- printf.c | 768 ++++++++++++++++++++++++++++++++++++++++++++++++++++ printf.h | 62 +++++ tests.sh | 51 +++- tests/test_0130.out | 19 ++ tests/test_0131.out | 1 + tests/test_0132.out | 1 + tests/test_0133.out | 19 ++ tests/test_0134.out | 19 ++ tests/test_0135.out | 19 ++ tests/test_0136.out | 10 + tests/test_0137.out | 1 + 17 files changed, 1087 insertions(+), 46 deletions(-) create mode 100644 printf.c create mode 100644 printf.h create mode 100644 tests/test_0130.out create mode 100644 tests/test_0131.out create mode 100644 tests/test_0132.out create mode 100644 tests/test_0133.out create mode 100644 tests/test_0134.out create mode 100644 tests/test_0135.out create mode 100644 tests/test_0136.out create mode 100644 tests/test_0137.out diff --git a/Makefile b/Makefile index 3962b97..e1d302f 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/bfs.h b/bfs.h index 84874d6..63766e7 100644 --- a/bfs.h +++ b/bfs.h @@ -13,6 +13,7 @@ #define BFS_H #include "color.h" +#include "printf.h" #include #include #include @@ -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); diff --git a/bftw.c b/bftw.c index eb4f0c0..70826ea 100644 --- a/bftw.c +++ b/bftw.c @@ -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; diff --git a/bftw.h b/bftw.h index 80a66c9..2fb1ef0 100644 --- a/bftw.h +++ b/bftw.h @@ -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. */ diff --git a/eval.c b/eval.c index 35501e0..f24c7c4 100644 --- a/eval.c +++ b/eval.c @@ -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; } } @@ -556,14 +556,6 @@ done: return ret; } -/** - * -prune action. - */ -bool eval_prune(const struct expr *expr, struct eval_state *state) { - state->action = BFTW_SKIP_SUBTREE; - return true; -} - /** * -hidden test. */ @@ -708,22 +700,6 @@ bool eval_perm(const struct expr *expr, struct eval_state *state) { return false; } -/** - * -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. */ @@ -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) { @@ -754,6 +730,48 @@ bool eval_print0(const struct expr *expr, struct eval_state *state) { return true; } +/** + * -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. */ @@ -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; } diff --git a/parse.c b/parse.c index d0168a9..b2a4cc3 100644 --- a/parse.c +++ b/parse.c @@ -10,6 +10,7 @@ *********************************************************************/ #include "bfs.h" +#include "printf.h" #include "typo.h" #include "util.h" #include @@ -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; @@ -973,6 +977,44 @@ static struct expr *parse_fprint0(struct parser_state *state, int arg1, int arg2 return expr; } +/** + * 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. */ @@ -1495,13 +1537,33 @@ 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; } return expr; } +/** + * Parse -printf FORMAT. + */ +static struct expr *parse_printf(struct parser_state *state, int arg1, int arg2) { + struct expr *expr = parse_unary_action(state, eval_fprintf); + if (!expr) { + return NULL; + } + + 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. */ @@ -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 * + * * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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, + "error: '%s': Invalid flags '%s' for string format '%%%c'.\n", + format, directive + 1, c); + goto directive_error; + } + + if (dstrcat(&directive, specifier) != 0) { + perror("dstrcat()"); + goto directive_error; + } + + if (append_literal(&tail, &literal, false) != 0) { + goto directive_error; + } + if (append_directive(&tail, fn, directive) != 0) { + goto directive_error; + } + continue; + + directive_error: + dstrfree(directive); + goto error; + } + + one_char: + if (dstrncat(&literal, &c, 1) != 0) { + perror("dstrncat()"); + goto error; + } + } + +done: + if (append_literal(&tail, &literal, true) != 0) { + goto error; + } + + return command; + +error: + dstrfree(literal); + free_bfs_printf(command); + return NULL; +} + +int bfs_printf(FILE *file, const struct bfs_printf *command, const struct BFTW *ftwbuf) { + int ret = -1; + + for (struct bfs_printf_directive *directive = command->directives; directive; directive = directive->next) { + if (directive->fn(file, directive, ftwbuf) < 0) { + goto done; + } + } + + ret = 0; +done: + return ret; +} + +void free_bfs_printf(struct bfs_printf *command) { + if (command) { + struct bfs_printf_directive *directive = command->directives; + while (directive) { + struct bfs_printf_directive *next = directive->next; + dstrfree(directive->str); + free(directive); + directive = next; + } + + free(command); + } +} diff --git a/printf.h b/printf.h new file mode 100644 index 0000000..34eda0a --- /dev/null +++ b/printf.h @@ -0,0 +1,62 @@ +/********************************************************************* + * bfs * + * Copyright (C) 2017 Tavian Barnes * + * * + * 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. * + *********************************************************************/ + +#ifndef BFS_PRINTF_H +#define BFS_PRINTF_H + +#include "bftw.h" +#include "color.h" +#include +#include + +struct bfs_printf_directive; + +/** + * A printf command, the result of parsing a single format string. + */ +struct bfs_printf { + /** The chain of printf directives. */ + struct bfs_printf_directive *directives; + /** Whether the struct stat must be filled in. */ + bool needs_stat; +}; + +/** + * Parse a -printf format string. + * + * @param format + * The format string to parse. + * @param stderr_colors + * Color table for printing error messages. + * @return The parsed printf command, or NULL on failure. + */ +struct bfs_printf *parse_bfs_printf(const char *format, const struct colors *stderr_colors); + +/** + * Evaluate a parsed format string. + * + * @param file + * The FILE to print to. + * @param command + * The parsed printf format. + * @param ftwbuf + * The bftw() data for the current file. If needs_stat is true, statbuf + * must be non-NULL. + * @return 0 on success, -1 on failure. + */ +int bfs_printf(FILE *file, const struct bfs_printf *command, const struct BFTW *ftwbuf); + +/** + * Free a parsed format string. + */ +void free_bfs_printf(struct bfs_printf *command); + +#endif // BFS_PRINTF_H diff --git a/tests.sh b/tests.sh index 5a7e650..7e85cff 100755 --- a/tests.sh +++ b/tests.sh @@ -145,7 +145,7 @@ for arg; do done function bfs_sort() { - awk -F/ '{ print NF - 1 " " $0 }' | sort -n | awk '{ print $2 }' + awk -F/ '{ print NF - 1 " " $0 }' | sort -n | cut -d' ' -f2- } function bfs_diff() { @@ -787,13 +787,6 @@ function test_0127() { bfs_diff basic -inum "$inode" } -function test_0127() { - [ "$BSD" -o "$GNU" ] || return 0 - - local inode="$(ls -id basic/k/foo/bar | cut -f1 -d' ')" - bfs_diff basic -inum "$inode" -} - function test_0128() { [ "$BSD" -o "$GNU" ] || return 0 bfs_diff basic -nogroup @@ -804,9 +797,49 @@ function test_0129() { bfs_diff basic -nouser } +function test_0130() { + [ "$GNU" ] || return 0 + bfs_diff basic -printf '%%p(%p) %%d(%d) %%f(%f) %%h(%h) %%H(%H) %%P(%P) %%m(%m) %%M(%M) %%y(%y)\n' +} + +function test_0131() { + [ "$GNU" ] || return 0 + bfs_diff / -maxdepth 0 -printf '(%h)/(%f)\n' +} + +function test_0132() { + [ "$GNU" ] || return 0 + bfs_diff /// -maxdepth 0 -printf '(%h)/(%f)\n' +} + +function test_0133() { + [ "$GNU" ] || return 0 + bfs_diff basic/ -printf '(%h)/(%f)\n' +} + +function test_0134() { + [ "$GNU" ] || return 0 + bfs_diff basic/// -printf '(%h)/(%f)\n' +} + +function test_0135() { + [ "$GNU" ] || return 0 + bfs_diff basic -printf '|%- 10.10p| %+03d %#4m\n' +} + +function test_0136() { + [ "$GNU" ] || return 0 + bfs_diff links -printf '(%p) (%l) %y %Y\n' +} + +function test_0137() { + [ "$GNU" ] || return 0 + bfs_diff basic -maxdepth 0 -printf '\18\118\1118\11118\n\cfoo' +} + result=0 -for i in {1..129}; do +for i in {1..137}; do test="test_$(printf '%04d' $i)" if [ -t 1 ]; then diff --git a/tests/test_0130.out b/tests/test_0130.out new file mode 100644 index 0000000..801ddbb --- /dev/null +++ b/tests/test_0130.out @@ -0,0 +1,19 @@ +%p(basic) %d(0) %f(basic) %h(.) %H(basic) %P() %m(755) %M(drwxr-xr-x) %y(d) +%p(basic/a) %d(1) %f(a) %h(basic) %H(basic) %P(a) %m(644) %M(-rw-r--r--) %y(f) +%p(basic/b) %d(1) %f(b) %h(basic) %H(basic) %P(b) %m(644) %M(-rw-r--r--) %y(f) +%p(basic/c) %d(1) %f(c) %h(basic) %H(basic) %P(c) %m(755) %M(drwxr-xr-x) %y(d) +%p(basic/e) %d(1) %f(e) %h(basic) %H(basic) %P(e) %m(755) %M(drwxr-xr-x) %y(d) +%p(basic/g) %d(1) %f(g) %h(basic) %H(basic) %P(g) %m(755) %M(drwxr-xr-x) %y(d) +%p(basic/i) %d(1) %f(i) %h(basic) %H(basic) %P(i) %m(755) %M(drwxr-xr-x) %y(d) +%p(basic/j) %d(1) %f(j) %h(basic) %H(basic) %P(j) %m(755) %M(drwxr-xr-x) %y(d) +%p(basic/k) %d(1) %f(k) %h(basic) %H(basic) %P(k) %m(755) %M(drwxr-xr-x) %y(d) +%p(basic/l) %d(1) %f(l) %h(basic) %H(basic) %P(l) %m(755) %M(drwxr-xr-x) %y(d) +%p(basic/c/d) %d(2) %f(d) %h(basic/c) %H(basic) %P(c/d) %m(644) %M(-rw-r--r--) %y(f) +%p(basic/e/f) %d(2) %f(f) %h(basic/e) %H(basic) %P(e/f) %m(644) %M(-rw-r--r--) %y(f) +%p(basic/g/h) %d(2) %f(h) %h(basic/g) %H(basic) %P(g/h) %m(755) %M(drwxr-xr-x) %y(d) +%p(basic/j/foo) %d(2) %f(foo) %h(basic/j) %H(basic) %P(j/foo) %m(644) %M(-rw-r--r--) %y(f) +%p(basic/k/foo) %d(2) %f(foo) %h(basic/k) %H(basic) %P(k/foo) %m(755) %M(drwxr-xr-x) %y(d) +%p(basic/l/foo) %d(2) %f(foo) %h(basic/l) %H(basic) %P(l/foo) %m(755) %M(drwxr-xr-x) %y(d) +%p(basic/k/foo/bar) %d(3) %f(bar) %h(basic/k/foo) %H(basic) %P(k/foo/bar) %m(644) %M(-rw-r--r--) %y(f) +%p(basic/l/foo/bar) %d(3) %f(bar) %h(basic/l/foo) %H(basic) %P(l/foo/bar) %m(755) %M(drwxr-xr-x) %y(d) +%p(basic/l/foo/bar/baz) %d(4) %f(baz) %h(basic/l/foo/bar) %H(basic) %P(l/foo/bar/baz) %m(644) %M(-rw-r--r--) %y(f) diff --git a/tests/test_0131.out b/tests/test_0131.out new file mode 100644 index 0000000..5571971 --- /dev/null +++ b/tests/test_0131.out @@ -0,0 +1 @@ +(/)/(/) diff --git a/tests/test_0132.out b/tests/test_0132.out new file mode 100644 index 0000000..60710e5 --- /dev/null +++ b/tests/test_0132.out @@ -0,0 +1 @@ +(/)/(///) diff --git a/tests/test_0133.out b/tests/test_0133.out new file mode 100644 index 0000000..0aa4ffc --- /dev/null +++ b/tests/test_0133.out @@ -0,0 +1,19 @@ +(basic)/(a) +(basic)/(b) +(basic)/(c) +(basic)/(e) +(basic)/(g) +(basic)/(i) +(basic)/(j) +(basic)/(k) +(basic)/(l) +(.)/(basic/) +(basic/c)/(d) +(basic/e)/(f) +(basic/g)/(h) +(basic/j)/(foo) +(basic/k)/(foo) +(basic/l)/(foo) +(basic/k/foo)/(bar) +(basic/l/foo)/(bar) +(basic/l/foo/bar)/(baz) diff --git a/tests/test_0134.out b/tests/test_0134.out new file mode 100644 index 0000000..cbb54a8 --- /dev/null +++ b/tests/test_0134.out @@ -0,0 +1,19 @@ +(basic//)/(a) +(basic//)/(b) +(basic//)/(c) +(basic//)/(e) +(basic//)/(g) +(basic//)/(i) +(basic//)/(j) +(basic//)/(k) +(basic//)/(l) +(.)/(basic///) +(basic///c)/(d) +(basic///e)/(f) +(basic///g)/(h) +(basic///j)/(foo) +(basic///k)/(foo) +(basic///l)/(foo) +(basic///k/foo)/(bar) +(basic///l/foo)/(bar) +(basic///l/foo/bar)/(baz) diff --git a/tests/test_0135.out b/tests/test_0135.out new file mode 100644 index 0000000..1a92b6e --- /dev/null +++ b/tests/test_0135.out @@ -0,0 +1,19 @@ +|basic | +00 0755 +|basic/a | +01 0644 +|basic/b | +01 0644 +|basic/c | +01 0755 +|basic/e | +01 0755 +|basic/g | +01 0755 +|basic/i | +01 0755 +|basic/j | +01 0755 +|basic/k | +01 0755 +|basic/l | +01 0755 +|basic/c/d | +02 0644 +|basic/e/f | +02 0644 +|basic/g/h | +02 0755 +|basic/j/fo| +02 0644 +|basic/k/fo| +02 0755 +|basic/k/fo| +03 0644 +|basic/l/fo| +02 0755 +|basic/l/fo| +03 0755 +|basic/l/fo| +04 0644 diff --git a/tests/test_0136.out b/tests/test_0136.out new file mode 100644 index 0000000..94fa833 --- /dev/null +++ b/tests/test_0136.out @@ -0,0 +1,10 @@ +(links) () d d +(links/a) () f f +(links/b) (a) l f +(links/c) () f f +(links/d) () d d +(links/d/e) () d d +(links/h) (d/e) l d +(links/d/e/f) () d d +(links/d/e/i) (q) l N +(links/d/e/g) (../../d) l d diff --git a/tests/test_0137.out b/tests/test_0137.out new file mode 100644 index 0000000..20ea120 --- /dev/null +++ b/tests/test_0137.out @@ -0,0 +1 @@ +8 8I8I18 -- cgit v1.2.3