// Copyright © Tavian Barnes <tavianator@tavianator.com>
// SPDX-License-Identifier: 0BSD
#include "printf.h"
#include "alloc.h"
#include "bfstd.h"
#include "bftw.h"
#include "color.h"
#include "config.h"
#include "ctx.h"
#include "diag.h"
#include "dir.h"
#include "dstring.h"
#include "expr.h"
#include "fsade.h"
#include "mtab.h"
#include "pwcache.h"
#include "stat.h"
#include <errno.h>
#include <grp.h>
#include <pwd.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
struct bfs_fmt;
/**
* A function implementing a printf directive.
*/
typedef int bfs_printf_fn(CFILE *cfile, const struct bfs_fmt *fmt, const struct BFTW *ftwbuf);
/**
* A single formatting directive like %f or %#4m.
*/
struct bfs_fmt {
/** The printing function to invoke. */
bfs_printf_fn *fn;
/** String data associated with this directive. */
dchar *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. */
void *ptr;
};
/**
* An entire format string.
*/
struct bfs_printf {
/** An array of formatting directives. */
struct bfs_fmt *fmts;
/** The number of directives. */
size_t nfmts;
};
/** Print some text as-is. */
static int bfs_printf_literal(CFILE *cfile, const struct bfs_fmt *fmt, const struct BFTW *ftwbuf) {
size_t len = dstrlen(fmt->str);
if (fwrite(fmt->str, 1, len, cfile->file) == len) {
return 0;
} else {
return -1;
}
}
/** \c: flush */
static int bfs_printf_flush(CFILE *cfile, const struct bfs_fmt *fmt, 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_fmt *fmt) {
return cfile->colors && strcmp(fmt->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__); \
bfs_assert(ret >= 0 && (size_t)ret < sizeof(buf)); \
(void)ret
/**
* Common entry point for fprintf() with a dynamic format string.
*/
static int dyn_fprintf(FILE *file, const struct bfs_fmt *fmt, ...) {
va_list args;
va_start(args, fmt);
#if __GNUC__
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wformat-nonliteral"
#endif
int ret = vfprintf(file, fmt->str, args);
#if __GNUC__
# pragma GCC diagnostic pop
#endif
va_end(args);
return ret;
}
/** %a, %c, %t: ctime() */
static int bfs_printf_ctime(CFILE *cfile, const struct bfs_fmt *fmt, 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, fmt->stat_field);
if (!ts) {
return -1;
}
struct tm tm;
if (!localtime_r(&ts->tv_sec, &tm)) {
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 dyn_fprintf(cfile->file, fmt, buf);
}
/** %A, %B/%W, %C, %T: strftime() */
static int bfs_printf_strftime(CFILE *cfile, const struct bfs_fmt *fmt, 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, fmt->stat_field);
if (