diff options
Diffstat (limited to 'usr.bin/find/printf.c')
-rw-r--r-- | usr.bin/find/printf.c | 348 |
1 files changed, 348 insertions, 0 deletions
diff --git a/usr.bin/find/printf.c b/usr.bin/find/printf.c new file mode 100644 index 000000000000..c1be04376156 --- /dev/null +++ b/usr.bin/find/printf.c @@ -0,0 +1,348 @@ +/*- + * Copyright (c) 2023, Netflix, Inc + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include <sys/types.h> + +#include <err.h> +#include <errno.h> +#include <fts.h> +#include <grp.h> +#include <pwd.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +#include "find.h" + +/* translate \X to proper escape, or to itself if no special meaning */ +static const char *esc = "\a\bcde\fghijklm\nopq\rs\tu\v"; + +static inline bool +isoct(char c) +{ + return (c >= '0' && c <= '7'); +} + +static inline bool +isesc(char c) +{ + return (c >= 'a' && c <= 'v' && esc[c - 'a'] != c); +} + +static char * +escape(const char *str, bool *flush, bool *warned) +{ + char c; + int value; + char *tmpstr; + size_t tmplen; + FILE *fp; + + fp = open_memstream(&tmpstr, &tmplen); + + /* + * Copy the str string into a new struct sbuf and return that expanding + * the different ANSI escape sequences. + */ + *flush = false; + for (c = *str++; c; c = *str++) { + if (c != '\\') { + putc(c, fp); + continue; + } + c = *str++; + + /* + * User error \ at end of string + */ + if (c == '\0') { + putc('\\', fp); + break; + } + + /* + * \c terminates output now and is supposed to flush the output + * too... + */ + if (c == 'c') { + *flush = true; + break; + } + + /* + * Is it octal? If so, decode up to 3 octal characters. + */ + if (isoct(c)) { + value = 0; + for (int i = 3; i-- > 0 && isoct(c); + c = *str++) { + value <<= 3; + value += c - '0'; + } + str--; + putc((char)value, fp); + continue; + } + + /* + * It's an ANSI X3.159-1989 escape, use the mini-escape lookup + * table to translate. + */ + if (isesc(c)) { + putc(esc[c - 'a'], fp); + continue; + } + + /* + * Otherwise, it's self inserting. gnu find specifically says + * not to rely on this behavior though. gnu find will issue + * a warning here, while printf(1) won't. + */ + if (!*warned) { + warn("Unknown character %c after \\.", c); + *warned = true; + } + putc(c, fp); + } + fclose(fp); + + return (tmpstr); +} + +static void +fp_ctime(FILE *fp, time_t t) +{ + char s[26]; + + ctime_r(&t, s); + s[24] = '\0'; /* kill newline, though gnu find info silent on issue */ + fputs(s, fp); +} + +/* + * Assumes all times are displayed in UTC rather than local time, gnu find info + * page silent on the issue. + * + * Also assumes that gnu find doesn't support multiple character escape sequences, + * which it's info page is also silent on. + */ +static void +fp_strftime(FILE *fp, time_t t, char mod) +{ + struct tm tm; + char buffer[128]; + char fmt[3] = "% "; + + /* + * Gnu libc extension we don't yet support -- seconds since epoch + * Used in Linux kernel build, so we kinda have to support it here + */ + if (mod == '@') { + fprintf(fp, "%ju", (uintmax_t)t); + return; + } + + gmtime_r(&t, &tm); + fmt[1] = mod; + if (strftime(buffer, sizeof(buffer), fmt, &tm) == 0) + errx(1, "Format bad or data too long for buffer"); /* Can't really happen ??? */ + fputs(buffer, fp); +} + +void +do_printf(PLAN *plan, FTSENT *entry, FILE *fout) +{ + char buf[4096]; + struct stat sb; + struct stat *sp; + const char *path, *pend; + char *all, *fmt; + ssize_t ret; + int c; + bool flush, warned; + + warned = (plan->flags & F_HAS_WARNED) != 0; + all = fmt = escape(plan->c_data, &flush, &warned); + if (warned) + plan->flags |= F_HAS_WARNED; + for (c = *fmt++; c; c = *fmt++) { + sp = entry->fts_statp; + if (c != '%') { + putc(c, fout); + continue; + } + c = *fmt++; + /* Style(9) deviation: case order same as gnu find info doc */ + switch (c) { + case '%': + putc(c, fout); + break; + case 'p': /* Path to file */ + fputs(entry->fts_path, fout); + break; + case 'f': /* filename w/o dirs */ + fputs(entry->fts_name, fout); + break; + case 'h': + /* + * path, relative to the starting point, of the file, or + * '.' if that's empty for some reason. + */ + path = entry->fts_path; + pend = strrchr(path, '/'); + if (pend == NULL) + putc('.', fout); + else + fwrite(path, pend - path, 1, fout); + break; + case 'P': /* file with command line arg rm'd -- HOW? fts_parent? */ + errx(1, "%%%c is unimplemented", c); + case 'H': /* Command line arg -- HOW? */ + errx(1, "%%%c is unimplemented", c); + case 'g': /* gid human readable */ + fputs(group_from_gid(sp->st_gid, 0), fout); + break; + case 'G': /* gid numeric */ + fprintf(fout, "%d", sp->st_gid); + break; + case 'u': /* uid human readable */ + fputs(user_from_uid(sp->st_uid, 0), fout); + break; + case 'U': /* uid numeric */ + fprintf(fout, "%d", sp->st_uid); + break; + case 'm': /* mode in octal */ + fprintf(fout, "%o", sp->st_mode & 07777); + break; + case 'M': /* Mode in ls-standard form */ + strmode(sp->st_mode, buf); + fwrite(buf, 10, 1, fout); + break; + case 'k': /* kbytes used by file */ + fprintf(fout, "%jd", (intmax_t)sp->st_blocks / 2); + break; + case 'b': /* blocks used by file */ + fprintf(fout, "%jd", (intmax_t)sp->st_blocks); + break; + case 's': /* size in bytes of file */ + fprintf(fout, "%ju", (uintmax_t)sp->st_size); + break; + case 'S': /* sparseness of file */ + fprintf(fout, "%3.1f", + (float)sp->st_blocks * 512 / (float)sp->st_size); + break; + case 'd': /* Depth in tree */ + fprintf(fout, "%ld", entry->fts_level); + break; + case 'D': /* device number */ + fprintf(fout, "%ju", (uintmax_t)sp->st_dev); + break; + case 'F': /* Filesystem type */ + errx(1, "%%%c is unimplemented", c); + case 'l': /* object of symbolic link */ + ret = readlink(entry->fts_accpath, buf, sizeof(buf)); + if (ret > 0) + fwrite(buf, ret, 1, fout); + break; + case 'i': /* inode # */ + fprintf(fout, "%ju", (uintmax_t)sp->st_ino); + break; + case 'n': /* number of hard links */ + fprintf(fout, "%ju", (uintmax_t)sp->st_nlink); + break; + case 'Y': /* -type of file, following 'l' types L loop ? error */ + if (S_ISLNK(sp->st_mode)) { + if (stat(entry->fts_accpath, &sb) != 0) { + switch (errno) { + case ELOOP: + putc('L', fout); + break; + case ENOENT: + putc('N', fout); + break; + default: + putc('?', fout); + break; + } + break; + } + sp = &sb; + } + /* FALLTHROUGH */ + case 'y': /* -type of file, incl 'l' */ + switch (sp->st_mode & S_IFMT) { + case S_IFIFO: + putc('p', fout); + break; + case S_IFCHR: + putc('c', fout); + break; + case S_IFDIR: + putc('d', fout); + break; + case S_IFBLK: + putc('b', fout); + break; + case S_IFREG: + putc('f', fout); + break; + case S_IFLNK: + putc('l', fout); + break; + case S_IFSOCK: + putc('s', fout); + break; + case S_IFWHT: + putc('w', fout); + break; + default: + putc('U', fout); + break; + } + break; + case 'a': /* access time ctime */ + fp_ctime(fout, sp->st_atime); + break; + case 'A': /* access time with next char strftime format */ + fp_strftime(fout, sp->st_atime, *fmt++); + break; + case 'B': /* birth time with next char strftime format */ +#ifdef HAVE_STRUCT_STAT_ST_BIRTHTIME + if (sp->st_birthtime != 0) + fp_strftime(fout, sp->st_birthtime, *fmt); +#endif + fmt++; + break; /* blank on systems that don't support it */ + case 'c': /* status change time ctime */ + fp_ctime(fout, sp->st_ctime); + break; + case 'C': /* status change time with next char strftime format */ + fp_strftime(fout, sp->st_ctime, *fmt++); + break; + case 't': /* modification change time ctime */ + fp_ctime(fout, sp->st_mtime); + break; + case 'T': /* modification time with next char strftime format */ + fp_strftime(fout, sp->st_mtime, *fmt++); + break; + case 'Z': /* empty string for compat SELinux context string */ + break; + /* Modifier parsing here, but also need to modify above somehow */ + case '#': case '-': case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': case '.': + errx(1, "Format modifier %c not yet supported: '%s'", c, all); + /* Any FeeeBSD-specific modifications here -- none yet */ + default: + errx(1, "Unknown format %c '%s'", c, all); + } + } + if (flush) + fflush(fout); + free(all); +} |