#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"
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);
*flush = false;
for (c = *str++; c; c = *str++) {
if (c != '\\') {
putc(c, fp);
continue;
}
c = *str++;
if (c == '\0') {
putc('\\', fp);
break;
}
if (c == 'c') {
*flush = true;
break;
}
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;
}
if (isesc(c)) {
putc(esc[c - 'a'], fp);
continue;
}
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';
fputs(s, fp);
}
static void
fp_strftime(FILE *fp, time_t t, char mod)
{
struct tm tm;
char buffer[128];
char fmt[3] = "% ";
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");
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++;
switch (c) {
case '%':
putc(c, fout);
break;
case 'p':
fputs(entry->fts_path, fout);
break;
case 'f':
fputs(entry->fts_name, fout);
break;
case 'h':
path = entry->fts_path;
pend = strrchr(path, '/');
if (pend == NULL)
putc('.', fout);
else
fwrite(path, pend - path, 1, fout);
break;
case 'P':
errx(1, "%%%c is unimplemented", c);
case 'H':
errx(1, "%%%c is unimplemented", c);
case 'g':
fputs(group_from_gid(sp->st_gid, 0), fout);
break;
case 'G':
fprintf(fout, "%d", sp->st_gid);
break;
case 'u':
fputs(user_from_uid(sp->st_uid, 0), fout);
break;
case 'U':
fprintf(fout, "%d", sp->st_uid);
break;
case 'm':
fprintf(fout, "%o", sp->st_mode & 07777);
break;
case 'M':
strmode(sp->st_mode, buf);
fwrite(buf, 10, 1, fout);
break;
case 'k':
fprintf(fout, "%jd", (intmax_t)sp->st_blocks / 2);
break;
case 'b':
fprintf(fout, "%jd", (intmax_t)sp->st_blocks);
break;
case 's':
fprintf(fout, "%ju", (uintmax_t)sp->st_size);
break;
case 'S':
fprintf(fout, "%3.1f",
(float)sp->st_blocks * 512 / (float)sp->st_size);
break;
case 'd':
fprintf(fout, "%ld", entry->fts_level);
break;
case 'D':
fprintf(fout, "%ju", (uintmax_t)sp->st_dev);
break;
case 'F':
errx(1, "%%%c is unimplemented", c);
case 'l':
ret = readlink(entry->fts_accpath, buf, sizeof(buf));
if (ret > 0)
fwrite(buf, ret, 1, fout);
break;
case 'i':
fprintf(fout, "%ju", (uintmax_t)sp->st_ino);
break;
case 'n':
fprintf(fout, "%ju", (uintmax_t)sp->st_nlink);
break;
case 'Y':
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;
}
case 'y':
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':
fp_ctime(fout, sp->st_atime);
break;
case 'A':
fp_strftime(fout, sp->st_atime, *fmt++);
break;
case 'B':
#ifdef HAVE_STRUCT_STAT_ST_BIRTHTIME
if (sp->st_birthtime != 0)
fp_strftime(fout, sp->st_birthtime, *fmt);
#endif
fmt++;
break;
case 'c':
fp_ctime(fout, sp->st_ctime);
break;
case 'C':
fp_strftime(fout, sp->st_ctime, *fmt++);
break;
case 't':
fp_ctime(fout, sp->st_mtime);
break;
case 'T':
fp_strftime(fout, sp->st_mtime, *fmt++);
break;
case 'Z':
break;
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);
default:
errx(1, "Unknown format %c '%s'", c, all);
}
}
if (flush)
fflush(fout);
free(all);
}