summaryrefslogtreecommitdiffstats
path: root/src/printf.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/printf.c')
-rw-r--r--src/printf.c927
1 files changed, 927 insertions, 0 deletions
diff --git a/src/printf.c b/src/printf.c
new file mode 100644
index 0000000..8fdde41
--- /dev/null
+++ b/src/printf.c
@@ -0,0 +1,927 @@
+/****************************************************************************
+ * bfs *
+ * Copyright (C) 2017-2022 Tavian Barnes <tavianator@tavianator.com> *
+ * *
+ * Permission to use, copy, modify, and/or distribute this software for any *
+ * purpose with or without fee is hereby granted. *
+ * *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES *
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF *
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR *
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES *
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN *
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF *
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. *
+ ****************************************************************************/
+
+#include "printf.h"
+#include "bftw.h"
+#include "color.h"
+#include "ctx.h"
+#include "darray.h"
+#include "diag.h"
+#include "dir.h"
+#include "dstring.h"
+#include "expr.h"
+#include "mtab.h"
+#include "pwcache.h"
+#include "stat.h"
+#include "util.h"
+#include "xtime.h"
+#include <assert.h>
+#include <errno.h>
+#include <grp.h>
+#include <pwd.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+/**
+ * A function implementing a printf directive.
+ */
+typedef int bfs_printf_fn(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf);
+
+/**
+ * A single printf directive like %f or %#4m. The whole format string is stored
+ * as a darray of these.
+ */
+struct bfs_printf {
+ /** The printing function to invoke. */
+ bfs_printf_fn *fn;
+ /** String data associated with this directive. */
+ char *str;
+ /** The stat field to print. */
+ enum bfs_stat_field stat_field;
+ /** Character data associated with this directive. */
+ char c;
+ /** Some data used by the directive. */
+ const void *ptr;
+};
+
+/** Print some text as-is. */
+static int bfs_printf_literal(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) {
+ size_t len = dstrlen(directive->str);
+ if (fwrite(directive->str, 1, len, cfile->file) == len) {
+ return 0;
+ } else {
+ return -1;
+ }
+}
+
+/** \c: flush */
+static int bfs_printf_flush(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) {
+ return fflush(cfile->file);
+}
+
+/** Check if we can safely colorize this directive. */
+static bool should_color(CFILE *cfile, const struct bfs_printf *directive) {
+ return cfile->colors && strcmp(directive->str, "%s") == 0;
+}
+
+/**
+ * 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 && (size_t)ret < sizeof(buf)); \
+ (void)ret
+
+/** %a, %c, %t: ctime() */
+static int bfs_printf_ctime(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) {
+ // 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 bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags);
+ if (!statbuf) {
+ return -1;
+ }
+
+ const struct timespec *ts = bfs_stat_time(statbuf, directive->stat_field);
+ if (!ts) {
+ return -1;
+ }
+
+ struct tm tm;
+ if (xlocaltime(&ts->tv_sec, &tm) != 0) {
+ return -1;
+ }
+
+ 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(cfile->file, directive->str, buf);
+}
+
+/** %A, %B/%W, %C, %T: strftime() */
+static int bfs_printf_strftime(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) {
+ const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags);
+ if (!statbuf) {
+ return -1;
+ }
+
+ const struct timespec *ts = bfs_stat_time(statbuf, directive->stat_field);
+ if (!ts) {
+ return -1;
+ }
+
+ struct tm tm;
+ if (xlocaltime(&ts->tv_sec, &tm) != 0) {
+ return -1;
+ }
+
+ int ret;
+ char buf[256];
+ char format[] = "% ";
+ switch (directive->c) {
+ // Non-POSIX strftime() features
+ case '@':
+ ret = snprintf(buf, sizeof(buf), "%lld.%09ld0", (long long)ts->tv_sec, (long)ts->tv_nsec);
+ break;
+ case '+':
+ ret = snprintf(buf, sizeof(buf), "%4d-%.2d-%.2d+%.2d:%.2d:%.2d.%09ld0",
+ 1900 + tm.tm_year,
+ tm.tm_mon + 1,
+ tm.tm_mday,
+ tm.tm_hour,
+ tm.tm_min,
+ tm.tm_sec,
+ (long)ts->tv_nsec);
+ break;
+ case 'k':
+ ret = snprintf(buf, sizeof(buf), "%2d", tm.tm_hour);
+ break;
+ case 'l':
+ ret = snprintf(buf, sizeof(buf), "%2d", (tm.tm_hour + 11)%12 + 1);
+ break;
+ case 's':
+ ret = snprintf(buf, sizeof(buf), "%lld", (long long)ts->tv_sec);
+ break;
+ case 'S':
+ ret = snprintf(buf, sizeof(buf), "%.2d.%09ld0", tm.tm_sec, (long)ts->tv_nsec);
+ break;
+ case 'T':
+ ret = snprintf(buf, sizeof(buf), "%.2d:%.2d:%.2d.%09ld0",
+ tm.tm_hour,
+ tm.tm_min,
+ tm.tm_sec,
+ (long)ts->tv_nsec);
+ break;
+
+ // POSIX strftime() features
+ default:
+ format[1] = directive->c;
+ ret = strftime(buf, sizeof(buf), format, &tm);
+ break;
+ }
+
+ assert(ret >= 0 && (size_t)ret < sizeof(buf));
+ (void)ret;
+
+ return fprintf(cfile->file, directive->str, buf);
+}
+
+/** %b: blocks */
+static int bfs_printf_b(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) {
+ const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags);
+ if (!statbuf) {
+ return -1;
+ }
+
+ uintmax_t blocks = ((uintmax_t)statbuf->blocks*BFS_STAT_BLKSIZE + 511)/512;
+ BFS_PRINTF_BUF(buf, "%ju", blocks);
+ return fprintf(cfile->file, directive->str, buf);
+}
+
+/** %d: depth */
+static int bfs_printf_d(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) {
+ return fprintf(cfile->file, directive->str, (intmax_t)ftwbuf->depth);
+}
+
+/** %D: device */
+static int bfs_printf_D(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) {
+ const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags);
+ if (!statbuf) {
+ return -1;
+ }
+
+ BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)statbuf->dev);
+ return fprintf(cfile->file, directive->str, buf);
+}
+
+/** %f: file name */
+static int bfs_printf_f(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) {
+ if (should_color(cfile, directive)) {
+ return cfprintf(cfile, "%pF", ftwbuf);
+ } else {
+ return fprintf(cfile->file, directive->str, ftwbuf->path + ftwbuf->nameoff);
+ }
+}
+
+/** %F: file system type */
+static int bfs_printf_F(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) {
+ const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags);
+ if (!statbuf) {
+ return -1;
+ }
+
+ const char *type = bfs_fstype(directive->ptr, statbuf);
+ return fprintf(cfile->file, directive->str, type);
+}
+
+/** %G: gid */
+static int bfs_printf_G(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) {
+ const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags);
+ if (!statbuf) {
+ return -1;
+ }
+
+ BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)statbuf->gid);
+ return fprintf(cfile->file, directive->str, buf);
+}
+
+/** %g: group name */
+static int bfs_printf_g(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) {
+ const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags);
+ if (!statbuf) {
+ return -1;
+ }
+
+ const struct bfs_groups *groups = directive->ptr;
+ const struct group *grp = groups ? bfs_getgrgid(groups, statbuf->gid) : NULL;
+ if (!grp) {
+ return bfs_printf_G(cfile, directive, ftwbuf);
+ }
+
+ return fprintf(cfile->file, directive->str, grp->gr_name);
+}
+
+/** %h: leading directories */
+static int bfs_printf_h(CFILE *cfile, const struct bfs_printf *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 = ".";
+ }
+
+ if (!buf) {
+ return -1;
+ }
+
+ int ret;
+ if (should_color(cfile, directive)) {
+ ret = cfprintf(cfile, "${di}%s${rs}", buf);
+ } else {
+ ret = fprintf(cfile->file, directive->str, buf);
+ }
+
+ free(copy);
+ return ret;
+}
+
+/** %H: current root */
+static int bfs_printf_H(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) {
+ if (should_color(cfile, directive)) {
+ if (ftwbuf->depth == 0) {
+ return cfprintf(cfile, "%pP", ftwbuf);
+ } else {
+ return cfprintf(cfile, "${di}%s${rs}", ftwbuf->root);
+ }
+ } else {
+ return fprintf(cfile->file, directive->str, ftwbuf->root);
+ }
+}
+
+/** %i: inode */
+static int bfs_printf_i(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) {
+ const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags);
+ if (!statbuf) {
+ return -1;
+ }
+
+ BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)statbuf->ino);
+ return fprintf(cfile->file, directive->str, buf);
+}
+
+/** %k: 1K blocks */
+static int bfs_printf_k(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) {
+ const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags);
+ if (!statbuf) {
+ return -1;
+ }
+
+ uintmax_t blocks = ((uintmax_t)statbuf->blocks*BFS_STAT_BLKSIZE + 1023)/1024;
+ BFS_PRINTF_BUF(buf, "%ju", blocks);
+ return fprintf(cfile->file, directive->str, buf);
+}
+
+/** %l: link target */
+static int bfs_printf_l(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) {
+ char *buf = NULL;
+ const char *target = "";
+
+ if (ftwbuf->type == BFS_LNK) {
+ if (should_color(cfile, directive)) {
+ return cfprintf(cfile, "%pL", ftwbuf);
+ }
+
+ const struct bfs_stat *statbuf = bftw_cached_stat(ftwbuf, BFS_STAT_NOFOLLOW);
+ size_t len = statbuf ? statbuf->size : 0;
+
+ target = buf = xreadlinkat(ftwbuf->at_fd, ftwbuf->at_path, len);
+ if (!target) {
+ return -1;
+ }
+ }
+
+ int ret = fprintf(cfile->file, directive->str, target);
+ free(buf);
+ return ret;
+}
+
+/** %m: mode */
+static int bfs_printf_m(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) {
+ const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags);
+ if (!statbuf) {
+ return -1;
+ }
+
+ return fprintf(cfile->file, directive->str, (unsigned int)(statbuf->mode & 07777));
+}
+
+/** %M: symbolic mode */
+static int bfs_printf_M(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) {
+ const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags);
+ if (!statbuf) {
+ return -1;
+ }
+
+ char buf[11];
+ xstrmode(statbuf->mode, buf);
+ return fprintf(cfile->file, directive->str, buf);
+}
+
+/** %n: link count */
+static int bfs_printf_n(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) {
+ const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags);
+ if (!statbuf) {
+ return -1;
+ }
+
+ BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)statbuf->nlink);
+ return fprintf(cfile->file, directive->str, buf);
+}
+
+/** %p: full path */
+static int bfs_printf_p(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) {
+ if (should_color(cfile, directive)) {
+ return cfprintf(cfile, "%pP", ftwbuf);
+ } else {
+ return fprintf(cfile->file, directive->str, ftwbuf->path);
+ }
+}
+
+/** %P: path after root */
+static int bfs_printf_P(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) {
+ size_t offset = strlen(ftwbuf->root);
+ if (ftwbuf->path[offset] == '/') {
+ ++offset;
+ }
+
+ if (should_color(cfile, directive)) {
+ if (ftwbuf->depth == 0) {
+ return 0;
+ }
+
+ struct BFTW copybuf = *ftwbuf;
+ copybuf.path += offset;
+ copybuf.nameoff -= offset;
+ return cfprintf(cfile, "%pP", &copybuf);
+ } else {
+ return fprintf(cfile->file, directive->str, ftwbuf->path + offset);
+ }
+}
+
+/** %s: size */
+static int bfs_printf_s(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) {
+ const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags);
+ if (!statbuf) {
+ return -1;
+ }
+
+ BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)statbuf->size);
+ return fprintf(cfile->file, directive->str, buf);
+}
+
+/** %S: sparseness */
+static int bfs_printf_S(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) {
+ const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags);
+ if (!statbuf) {
+ return -1;
+ }
+
+ double sparsity;
+ if (statbuf->size == 0 && statbuf->blocks == 0) {
+ sparsity = 1.0;
+ } else {
+ sparsity = (double)BFS_STAT_BLKSIZE*statbuf->blocks/statbuf->size;
+ }
+ return fprintf(cfile->file, directive->str, sparsity);
+}
+
+/** %U: uid */
+static int bfs_printf_U(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) {
+ const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags);
+ if (!statbuf) {
+ return -1;
+ }
+
+ BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)statbuf->uid);
+ return fprintf(cfile->file, directive->str, buf);
+}
+
+/** %u: user name */
+static int bfs_printf_u(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) {
+ const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags);
+ if (!statbuf) {
+ return -1;
+ }
+
+ const struct bfs_users *users = directive->ptr;
+ const struct passwd *pwd = users ? bfs_getpwuid(users, statbuf->uid) : NULL;
+ if (!pwd) {
+ return bfs_printf_U(cfile, directive, ftwbuf);
+ }
+
+ return fprintf(cfile->file, directive->str, pwd->pw_name);
+}
+
+static const char *bfs_printf_type(enum bfs_type type) {
+ switch (type) {
+ case BFS_BLK:
+ return "b";
+ case BFS_CHR:
+ return "c";
+ case BFS_DIR:
+ return "d";
+ case BFS_DOOR:
+ return "D";
+ case BFS_FIFO:
+ return "p";
+ case BFS_LNK:
+ return "l";
+ case BFS_REG:
+ return "f";
+ case BFS_SOCK:
+ return "s";
+ default:
+ return "U";
+ }
+}
+
+/** %y: type */
+static int bfs_printf_y(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) {
+ const char *type = bfs_printf_type(ftwbuf->type);
+ return fprintf(cfile->file, directive->str, type);
+}
+
+/** %Y: target type */
+static int bfs_printf_Y(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) {
+ int error = 0;
+
+ if (ftwbuf->type != BFS_LNK) {
+ return bfs_printf_y(cfile, directive, ftwbuf);
+ }
+
+ const char *type = "U";
+
+ const struct bfs_stat *statbuf = bftw_stat(ftwbuf, BFS_STAT_FOLLOW);
+ if (statbuf) {
+ type = bfs_printf_type(bfs_mode_to_type(statbuf->mode));
+ } else {
+ switch (errno) {
+ case ELOOP:
+ type = "L";
+ break;
+ case ENOENT:
+ case ENOTDIR:
+ type = "N";
+ break;
+ default:
+ type = "?";
+ error = errno;
+ break;
+ }
+ }
+
+ int ret = fprintf(cfile->file, directive->str, type);
+ if (error != 0) {
+ ret = -1;
+ errno = error;
+ }
+ return ret;
+}
+
+/**
+ * Append a literal string to the chain.
+ */
+static int append_literal(const struct bfs_ctx *ctx, struct bfs_printf **format, char **literal) {
+ if (dstrlen(*literal) == 0) {
+ return 0;
+ }
+
+ struct bfs_printf directive = {
+ .fn = bfs_printf_literal,
+ .str = *literal,
+ };
+
+ if (DARRAY_PUSH(format, &directive) != 0) {
+ bfs_perror(ctx, "DARRAY_PUSH()");
+ return -1;
+ }
+
+ *literal = dstralloc(0);
+ if (!*literal) {
+ bfs_perror(ctx, "dstralloc()");
+ return -1;
+ }
+
+ return 0;
+}
+
+/**
+ * Append a printf directive to the chain.
+ */
+static int append_directive(const struct bfs_ctx *ctx, struct bfs_printf **format, char **literal, struct bfs_printf *directive) {
+ if (append_literal(ctx, format, literal) != 0) {
+ return -1;
+ }
+
+ if (DARRAY_PUSH(format, directive) != 0) {
+ bfs_perror(ctx, "DARRAY_PUSH()");
+ return -1;
+ }
+
+ return 0;
+}
+
+int bfs_printf_parse(const struct bfs_ctx *ctx, struct bfs_expr *expr, const char *format) {
+ expr->printf = NULL;
+
+ char *literal = dstralloc(0);
+ if (!literal) {
+ bfs_perror(ctx, "dstralloc()");
+ goto error;
+ }
+
+ 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':
+ {
+ struct bfs_printf directive = {
+ .fn = bfs_printf_flush,
+ };
+ if (append_directive(ctx, &expr->printf, &literal, &directive) != 0) {
+ goto error;
+ }
+ goto done;
+ }
+
+ case '\0':
+ bfs_expr_error(ctx, expr);
+ bfs_error(ctx, "Incomplete escape sequence '\\'.\n");
+ goto error;
+
+ default:
+ bfs_expr_error(ctx, expr);
+ bfs_error(ctx, "Unrecognized escape sequence '\\%c'.\n", c);
+ goto error;
+ }
+ } else if (c == '%') {
+ if (i[1] == '%') {
+ c = *++i;
+ goto one_char;
+ }
+
+ struct bfs_printf directive = {
+ .str = dstralloc(2),
+ };
+ if (!directive.str) {
+ goto directive_error;
+ }
+ if (dstrapp(&directive.str, c) != 0) {
+ bfs_perror(ctx, "dstrapp()");
+ goto directive_error;
+ }
+
+ const char *specifier = "s";
+
+ // Parse any flags
+ bool must_be_numeric = false;
+ while (true) {
+ c = *++i;
+
+ switch (c) {
+ case '#':
+ case '0':
+ case '+':
+ must_be_numeric = true;
+ BFS_FALLTHROUGH;
+ case ' ':
+ case '-':
+ if (strchr(directive.str, c)) {
+ bfs_expr_error(ctx, expr);
+ bfs_error(ctx, "Duplicate flag '%c'.\n", c);
+ goto directive_error;
+ }
+ if (dstrapp(&directive.str, c) != 0) {
+ bfs_perror(ctx, "dstrapp()");
+ goto directive_error;
+ }
+ continue;
+ }
+
+ break;
+ }
+
+ // Parse the field width
+ while (c >= '0' && c <= '9') {
+ if (dstrapp(&directive.str, c) != 0) {
+ bfs_perror(ctx, "dstrapp()");
+ goto directive_error;
+ }
+ c = *++i;
+ }
+
+ // Parse the precision
+ if (c == '.') {
+ do {
+ if (dstrapp(&directive.str, c) != 0) {
+ bfs_perror(ctx, "dstrapp()");
+ goto directive_error;
+ }
+ c = *++i;
+ } while (c >= '0' && c <= '9');
+ }
+
+ switch (c) {
+ case 'a':
+ directive.fn = bfs_printf_ctime;
+ directive.stat_field = BFS_STAT_ATIME;
+ break;
+ case 'b':
+ directive.fn = bfs_printf_b;
+ break;
+ case 'c':
+ directive.fn = bfs_printf_ctime;
+ directive.stat_field = BFS_STAT_CTIME;
+ break;
+ case 'd':
+ directive.fn = bfs_printf_d;
+ specifier = "jd";
+ break;
+ case 'D':
+ directive.fn = bfs_printf_D;
+ break;
+ case 'f':
+ directive.fn = bfs_printf_f;
+ break;
+ case 'F':
+ directive.ptr = bfs_ctx_mtab(ctx);
+ if (!directive.ptr) {
+ int error = errno;
+ bfs_expr_error(ctx, expr);
+ bfs_error(ctx, "Couldn't parse the mount table: %s.\n", strerror(error));
+ goto directive_error;
+ }
+ directive.fn = bfs_printf_F;
+ break;
+ case 'g':
+ directive.ptr = bfs_ctx_groups(ctx);
+ if (!directive.ptr) {
+ int error = errno;
+ bfs_expr_error(ctx, expr);
+ bfs_error(ctx, "Couldn't parse the group table: %s.\n", strerror(error));
+ goto directive_error;
+ }
+ directive.fn = bfs_printf_g;
+ break;
+ case 'G':
+ directive.fn = bfs_printf_G;
+ break;
+ case 'h':
+ directive.fn = bfs_printf_h;
+ break;
+ case 'H':
+ directive.fn = bfs_printf_H;
+ break;
+ case 'i':
+ directive.fn = bfs_printf_i;
+ break;
+ case 'k':
+ directive.fn = bfs_printf_k;
+ break;
+ case 'l':
+ directive.fn = bfs_printf_l;
+ break;
+ case 'm':
+ directive.fn = bfs_printf_m;
+ specifier = "o";
+ break;
+ case 'M':
+ directive.fn = bfs_printf_M;
+ break;
+ case 'n':
+ directive.fn = bfs_printf_n;
+ break;
+ case 'p':
+ directive.fn = bfs_printf_p;
+ break;
+ case 'P':
+ directive.fn = bfs_printf_P;
+ break;
+ case 's':
+ directive.fn = bfs_printf_s;
+ break;
+ case 'S':
+ directive.fn = bfs_printf_S;
+ specifier = "g";
+ break;
+ case 't':
+ directive.fn = bfs_printf_ctime;
+ directive.stat_field = BFS_STAT_MTIME;
+ break;
+ case 'u':
+ directive.ptr = bfs_ctx_users(ctx);
+ if (!directive.ptr) {
+ int error = errno;
+ bfs_expr_error(ctx, expr);
+ bfs_error(ctx, "Couldn't parse the user table: %s.\n", strerror(error));
+ goto directive_error;
+ }
+ directive.fn = bfs_printf_u;
+ break;
+ case 'U':
+ directive.fn = bfs_printf_U;
+ break;
+ case 'w':
+ directive.fn = bfs_printf_ctime;
+ directive.stat_field = BFS_STAT_BTIME;
+ break;
+ case 'y':
+ directive.fn = bfs_printf_y;
+ break;
+ case 'Y':
+ directive.fn = bfs_printf_Y;
+ break;
+
+ case 'A':
+ directive.stat_field = BFS_STAT_ATIME;
+ goto directive_strftime;
+ case 'B':
+ case 'W':
+ directive.stat_field = BFS_STAT_BTIME;
+ goto directive_strftime;
+ case 'C':
+ directive.stat_field = BFS_STAT_CTIME;
+ goto directive_strftime;
+ case 'T':
+ directive.stat_field = BFS_STAT_MTIME;
+ goto directive_strftime;
+
+ directive_strftime:
+ directive.fn = bfs_printf_strftime;
+ c = *++i;
+ if (!c) {
+ bfs_expr_error(ctx, expr);
+ bfs_error(ctx, "Incomplete time specifier '%s%c'.\n", directive.str, i[-1]);
+ goto directive_error;
+ } else if (strchr("%+@aAbBcCdDeFgGhHIjklmMnprRsStTuUVwWxXyYzZ", c)) {
+ directive.c = c;
+ } else {
+ bfs_expr_error(ctx, expr);
+ bfs_error(ctx, "Unrecognized time specifier '%%%c%c'.\n", i[-1], c);
+ goto directive_error;
+ }
+ break;
+
+ case '\0':
+ bfs_expr_error(ctx, expr);
+ bfs_error(ctx, "Incomplete format specifier '%s'.\n", directive.str);
+ goto directive_error;
+
+ default:
+ bfs_expr_error(ctx, expr);
+ bfs_error(ctx, "Unrecognized format specifier '%%%c'.\n", c);
+ goto directive_error;
+ }
+
+ if (must_be_numeric && strcmp(specifier, "s") == 0) {
+ bfs_expr_error(ctx, expr);
+ bfs_error(ctx, "Invalid flags '%s' for string format '%%%c'.\n", directive.str + 1, c);
+ goto directive_error;
+ }
+
+ if (dstrcat(&directive.str, specifier) != 0) {
+ bfs_perror(ctx, "dstrcat()");
+ goto directive_error;
+ }
+
+ if (append_directive(ctx, &expr->printf, &literal, &directive) != 0) {
+ goto directive_error;
+ }
+
+ continue;
+
+ directive_error:
+ dstrfree(directive.str);
+ goto error;
+ }
+
+ one_char:
+ if (dstrapp(&literal, c) != 0) {
+ bfs_perror(ctx, "dstrapp()");
+ goto error;
+ }
+ }
+
+done:
+ if (append_literal(ctx, &expr->printf, &literal) != 0) {
+ goto error;
+ }
+ dstrfree(literal);
+ return 0;
+
+error:
+ dstrfree(literal);
+ bfs_printf_free(expr->printf);
+ expr->printf = NULL;
+ return -1;
+}
+
+int bfs_printf(CFILE *cfile, const struct bfs_printf *format, const struct BFTW *ftwbuf) {
+ int ret = 0, error = 0;
+
+ for (size_t i = 0; i < darray_length(format); ++i) {
+ const struct bfs_printf *directive = &format[i];
+ if (directive->fn(cfile, directive, ftwbuf) < 0) {
+ ret = -1;
+ error = errno;
+ }
+ }
+
+ errno = error;
+ return ret;
+}
+
+void bfs_printf_free(struct bfs_printf *format) {
+ for (size_t i = 0; i < darray_length(format); ++i) {
+ dstrfree(format[i].str);
+ }
+ darray_free(format);
+}