diff options
Diffstat (limited to 'printf.c')
-rw-r--r-- | printf.c | 768 |
1 files changed, 768 insertions, 0 deletions
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); + } +} |