diff options
author | トトも <85485984+ElectronicsArchiver@users.noreply.github.com> | 2022-04-16 20:18:56 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-04-16 14:18:56 -0400 |
commit | 33cc3b9dd7bf3dae1c6cf86e46bb4923f96e7fff (patch) | |
tree | 02fb808d19aee560ac9d381ca5a52509881cdd44 /src/printf.c | |
parent | 8f5a73a6585bd425807430fd80ce1e3a737f4c5f (diff) | |
download | bfs-33cc3b9dd7bf3dae1c6cf86e46bb4923f96e7fff.tar.xz |
Source / Include Folder (#88)
Moved Source Files Into `src` Folder
Diffstat (limited to 'src/printf.c')
-rw-r--r-- | src/printf.c | 927 |
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", ©buf); + } 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); +} |