summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTavian Barnes <tavianator@tavianator.com>2017-01-14 16:38:08 -0500
committerTavian Barnes <tavianator@tavianator.com>2017-02-05 19:02:25 -0500
commita6f94c132c425bbab543e98fcd19f4ff7519d1b7 (patch)
tree215135837c0335946b70593877ec5411ba8e6c17
parent9f1863d45fe596e258596a4b4cc9a4064bcb11d3 (diff)
downloadbfs-a6f94c132c425bbab543e98fcd19f4ff7519d1b7.tar.xz
Implement -printf/-fprintf
Based on a patch by Fangrui Song <i@maskray.me>. Closes #16.
-rw-r--r--Makefile2
-rw-r--r--bfs.h9
-rw-r--r--bftw.c1
-rw-r--r--bftw.h2
-rw-r--r--eval.c77
-rw-r--r--parse.c72
-rw-r--r--printf.c768
-rw-r--r--printf.h62
-rwxr-xr-xtests.sh51
-rw-r--r--tests/test_0130.out19
-rw-r--r--tests/test_0131.out1
-rw-r--r--tests/test_0132.out1
-rw-r--r--tests/test_0133.out19
-rw-r--r--tests/test_0134.out19
-rw-r--r--tests/test_0135.out19
-rw-r--r--tests/test_0136.out10
-rw-r--r--tests/test_0137.out1
17 files changed, 1087 insertions, 46 deletions
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 <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);
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;
}
}
@@ -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;
}
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 <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,
+ "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 <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. *
+ *********************************************************************/
+
+#ifndef BFS_PRINTF_H
+#define BFS_PRINTF_H
+
+#include "bftw.h"
+#include "color.h"
+#include <stdbool.h>
+#include <stdio.h>
+
+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