aboutsummaryrefslogtreecommitdiff
path: root/usr.bin/find/printf.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr.bin/find/printf.c')
-rw-r--r--usr.bin/find/printf.c348
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);
+}