#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <libintl.h>
#include <locale.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/filio.h>
#include <sys/sysmacros.h>
#include <time.h>
#include <utime.h>
#include <poll.h>
#include <errno.h>
#include "err.h"
#include "lut.h"
#include "fn.h"
#include "opts.h"
#include "conf.h"
#include "glob.h"
#include "kw.h"
static void usage(const char *msg);
static void commajoin(const char *lhs, void *rhs, void *arg);
static void doaftercmd(const char *lhs, void *rhs, void *arg);
static void dologname(struct fn *fnp, struct opts *clopts);
static boolean_t rotatelog(struct fn *fnp, struct opts *opts);
static void rotateto(struct fn *fnp, struct opts *opts, int n,
struct fn *recentlog, boolean_t isgz);
static void do_delayed_gzip(const char *lhs, void *rhs, void *arg);
static void expirefiles(struct fn *fnp, struct opts *opts);
static void dorm(struct opts *opts, const char *msg, struct fn *fnp);
static void docmd(struct opts *opts, const char *msg, const char *cmd,
const char *arg1, const char *arg2, const char *arg3);
static void docopytruncate(struct opts *opts, const char *file,
const char *file_copy);
static char *Default_conffile = "/etc/logadm.conf";
static char *Default_timestamps = "/var/logadm/timestamps";
static char *Sh = "/bin/sh";
static char *Mv = "/bin/mv";
static char *Rm = "/bin/rm";
static char *Touch = "/bin/touch";
static char *Chmod = "/bin/chmod";
static char *Chown = "/bin/chown";
static char *Gzip = "/bin/gzip";
static char *Mkdir = "/bin/mkdir";
int Debug;
time_t Now;
static struct lut *Beforecmds;
static struct lut *Aftercmds;
static struct lut *Donenames;
static struct lut *Gzipnames = NULL;
#define OPTIONS_NOT_FIRST_FORM "eNrwpPsabcglmoRtzACEST"
#define HELP1 \
"Usage: logadm [options]\n"\
" (processes all entries in /etc/logadm.conf or conffile given by -f)\n"\
" or: logadm [options] logname...\n"\
" (processes the given lognames)\n"\
"\n"\
"General options:\n"\
" -e mailaddr mail errors to given address\n"\
" -F timestamps use timestamps instead of /var/logadm/timestamps\n"\
" -f conffile use conffile instead of /etc/logadm.conf\n"\
" -h display help\n"\
" -N not an error if log file nonexistent\n"\
" -n show actions, don't perform them\n"\
" -r remove logname entry from conffile\n"\
" -V ensure conffile entries exist, correct\n"\
" -v print info about actions happening\n"\
" -w entryname write entry to config file\n"\
"\n"\
"Options which control when a logfile is rotated:\n"\
"(default is: -s1b -p1w if no -s or -p)\n"\
" -p period only rotate if period passed since last rotate\n"\
" -P timestamp used to store rotation date in conffile\n"\
" -s size only rotate if given size or greater\n"\
"\n"
#define HELP2 \
"Options which control how a logfile is rotated:\n"\
"(default is: -t '$file.$n', owner/group/mode taken from log file)\n"\
" -a cmd execute cmd after taking actions\n"\
" -b cmd execute cmd before taking actions\n"\
" -c copy & truncate logfile, don't rename\n"\
" -g group new empty log file group\n"\
" -l rotate log file with local time rather than UTC\n"\
" -m mode new empty log file mode\n"\
" -M cmd execute cmd to rotate the log file\n"\
" -o owner new empty log file owner\n"\
" -R cmd run cmd on file after rotate\n"\
" -t template template for naming old logs\n"\
" -z count gzip old logs except most recent count\n"\
"\n"\
"Options which control the expiration of old logfiles:\n"\
"(default is: -C10 if no -A, -C, or -S)\n"\
" -A age expire logs older than age\n"\
" -C count expire old logs until count remain\n"\
" -E cmd run cmd on file to expire\n"\
" -S size expire until space used is below size \n"\
" -T pattern pattern for finding old logs\n"
int
main(int argc, char *argv[])
{
struct opts *clopts = NULL;
const char *conffile;
const char *timestamps;
struct fn_list *lognames;
struct fn *fnp;
char *val;
char *buf;
int status;
(void) setlocale(LC_ALL, "");
#if !defined(TEXT_DOMAIN)
#define TEXT_DOMAIN "SYS_TEST"
#endif
(void) textdomain(TEXT_DOMAIN);
(void) setlocale(LC_TIME, "C");
err_init(*argv++);
(void) setlinebuf(stdout);
if (putenv("PATH=/bin"))
err(EF_SYS, "putenv PATH");
if (putenv("TZ=UTC"))
err(EF_SYS, "putenv TZ");
tzset();
(void) umask(0);
Now = time(0);
if (val = getenv("_LOGADM_DEFAULT_CONFFILE"))
Default_conffile = val;
if (val = getenv("_LOGADM_DEFAULT_TIMESTAMPS"))
Default_timestamps = val;
if (val = getenv("_LOGADM_DEBUG"))
Debug = atoi(val);
if (val = getenv("_LOGADM_SH"))
Sh = val;
if (val = getenv("_LOGADM_MV"))
Mv = val;
if (val = getenv("_LOGADM_RM"))
Rm = val;
if (val = getenv("_LOGADM_TOUCH"))
Touch = val;
if (val = getenv("_LOGADM_CHMOD"))
Chmod = val;
if (val = getenv("_LOGADM_CHOWN"))
Chown = val;
if (val = getenv("_LOGADM_GZIP"))
Gzip = val;
if (val = getenv("_LOGADM_MKDIR"))
Mkdir = val;
opts_init(Opttable, Opttable_cnt);
if (SETJMP)
usage("bailing out due to command line errors");
else
clopts = opts_parse(NULL, argv, OPTF_CLI);
if (Debug) {
(void) fprintf(stderr, "command line opts:");
opts_print(clopts, stderr, NULL);
(void) fprintf(stderr, "\n");
}
if (opts_count(clopts, "h")) {
(void) fputs(HELP1, stderr);
(void) fputs(HELP2, stderr);
err_done(0);
}
if (opts_count(clopts, "rwV") > 1)
usage("Only one of -r, -w, or -V may be used at a time.");
if (opts_count(clopts, "cM") > 1)
usage("Only one of -c or -M may be used at a time.");
if (opts_count(clopts, "e"))
err_mailto(opts_optarg(clopts, "e"));
if ((conffile = opts_optarg(clopts, "f")) == NULL)
conffile = Default_conffile;
if ((timestamps = opts_optarg(clopts, "F")) == NULL)
timestamps = Default_timestamps;
if (opts_count(clopts, "v"))
(void) out("# loading %s\n", conffile);
status = conf_open(conffile, timestamps, clopts);
if (!status && opts_count(clopts, "V"))
err_done(0);
if (opts_count(clopts, "w")) {
if (Debug)
(void) fprintf(stderr,
"main: add/replace conffile entry: <%s>\n",
opts_optarg(clopts, "w"));
conf_replace(opts_optarg(clopts, "w"), clopts);
conf_close(clopts);
err_done(0);
}
lognames = opts_cmdargs(clopts);
if (fn_list_empty(lognames)) {
if (opts_count(clopts, OPTIONS_NOT_FIRST_FORM))
usage("some options require logname argument");
if (Debug)
(void) fprintf(stderr,
"main: run all entries in conffile\n");
lognames = conf_entries();
}
fn_list_rewind(lognames);
while ((fnp = fn_list_next(lognames)) != NULL) {
buf = fn_s(fnp);
if (buf != NULL && lut_lookup(Donenames, buf) != NULL) {
if (Debug)
(void) fprintf(stderr,
"main: logname already done: <%s>\n",
buf);
continue;
}
if (buf != NULL && SETJMP)
err(EF_FILE, "bailing out on logname \"%s\" "
"due to errors", buf);
else
dologname(fnp, clopts);
}
lut_walk(Aftercmds, doaftercmd, clopts);
lut_walk(Gzipnames, do_delayed_gzip, clopts);
conf_close(clopts);
err_done(0);
return (0);
}
static void
usage(const char *msg)
{
if (msg)
err(0, "%s\nUse \"logadm -h\" for help.", msg);
else
err(EF_RAW, "Use \"logadm -h\" for help.\n");
}
static void
commajoin(const char *lhs, void *rhs, void *arg)
{
struct fn *fnp = (struct fn *)arg;
char *buf;
buf = fn_s(fnp);
if (buf != NULL && *buf)
fn_putc(fnp, ',');
fn_puts(fnp, lhs);
}
static void
doaftercmd(const char *lhs, void *rhs, void *arg)
{
struct opts *opts = (struct opts *)arg;
struct lut *addrs = (struct lut *)rhs;
if (addrs) {
struct fn *fnp = fn_new(NULL);
lut_walk(addrs, commajoin, fnp);
err_mailto(fn_s(fnp));
}
docmd(opts, "-a cmd", Sh, "-c", lhs, NULL);
}
static void
do_delayed_gzip(const char *lhs, void *rhs, void *arg)
{
struct opts *opts = (struct opts *)arg;
if (rhs == NULL) {
if (Debug) {
(void) fprintf(stderr, "do_delayed_gzip: not gzipping "
"expired file <%s>\n", lhs);
}
return;
}
docmd(opts, "compress old log (-z flag)", Gzip, "-f", lhs, NULL);
}
static void
dologname(struct fn *fnp, struct opts *clopts)
{
const char *logname = fn_s(fnp);
struct opts *cfopts;
struct opts *allopts;
struct fn_list *logfiles;
struct fn_list *globbedfiles;
struct fn *nextfnp;
cfopts = conf_opts(logname);
if (opts_count(clopts, "v"))
(void) out("# processing logname: %s\n", logname);
if (Debug) {
if (logname != NULL)
(void) fprintf(stderr, "dologname: logname <%s>\n",
logname);
(void) fprintf(stderr, "conffile opts:");
opts_print(cfopts, stderr, NULL);
(void) fprintf(stderr, "\n");
}
if (opts_count(clopts, "V")) {
if (Debug)
(void) fprintf(stderr,
"dologname: lookup conffile entry\n");
if (conf_lookup(logname)) {
opts_printword(logname, stdout);
opts_print(cfopts, stdout, NULL);
(void) out("\n");
} else
err_exitcode(1);
return;
}
if (opts_count(clopts, "r")) {
if (Debug)
(void) fprintf(stderr,
"dologname: remove conffile entry\n");
if (conf_lookup(logname))
conf_replace(logname, NULL);
else
err_exitcode(1);
return;
}
allopts = opts_merge(cfopts, clopts);
if (opts_count(allopts, "e"))
err_mailto(opts_optarg(allopts, "e"));
else
err_mailto(NULL);
if (opts_count(allopts, "sp") == 0) {
if (opts_count(clopts, "v"))
(void) out(
"# using default rotate rules: -s1b -p1w\n");
(void) opts_set(allopts, "s", "1b");
(void) opts_set(allopts, "p", "1w");
}
if (opts_count(allopts, "ACS") == 0) {
if (opts_count(clopts, "v"))
(void) out("# using default expire rule: -C10\n");
(void) opts_set(allopts, "C", "10");
}
if (opts_count(allopts, "t") == 0) {
if (opts_count(clopts, "v"))
(void) out("# using default template: $file.$n\n");
(void) opts_set(allopts, "t", "$file.$n");
}
if (Debug) {
(void) fprintf(stderr, "merged opts:");
opts_print(allopts, stderr, NULL);
(void) fprintf(stderr, "\n");
}
logfiles = opts_cmdargs(cfopts);
if (Debug) {
char *buf;
(void) fprintf(stderr, "dologname: logfiles from cfopts:\n");
fn_list_rewind(logfiles);
while ((nextfnp = fn_list_next(logfiles)) != NULL) {
buf = fn_s(nextfnp);
if (buf != NULL)
(void) fprintf(stderr, " <%s>\n", buf);
}
}
if (fn_list_empty(logfiles))
globbedfiles = glob_glob(fnp);
else
globbedfiles = glob_glob_list(logfiles);
fn_list_rewind(globbedfiles);
while ((nextfnp = fn_list_next(globbedfiles)) != NULL)
if (rotatelog(nextfnp, allopts))
expirefiles(nextfnp, allopts);
fn_list_free(globbedfiles);
opts_free(allopts);
}
#define TIMESTRMAX 100
#define MAXATTR 100
static boolean_t
rotatelog(struct fn *fnp, struct opts *opts)
{
char *fname = fn_s(fnp);
struct stat stbuf;
char nowstr[TIMESTRMAX];
struct fn *recentlog = fn_new(NULL);
char ownerbuf[MAXATTR];
char groupbuf[MAXATTR];
char modebuf[MAXATTR];
const char *owner;
const char *group;
const char *mode;
if (Debug && fname != NULL)
(void) fprintf(stderr, "rotatelog: fname <%s>\n", fname);
if (opts_count(opts, "p") && opts_optarg_int(opts, "p") == OPTP_NEVER)
return (B_TRUE);
kw_init(fnp, NULL);
if (Debug > 1) {
(void) fprintf(stderr, "rotatelog keywords:\n");
kw_print(stderr);
}
if (lstat(fname, &stbuf) < 0) {
if (opts_count(opts, "N"))
return (1);
err(EF_WARN|EF_SYS, "%s", fname);
return (B_FALSE);
}
if ((stbuf.st_mode & S_IFMT) == S_IFLNK) {
err(EF_WARN, "%s is a symlink", fname);
return (B_FALSE);
}
if ((stbuf.st_mode & S_IFMT) != S_IFREG) {
err(EF_WARN, "%s is not a regular file", fname);
return (B_FALSE);
}
if (opts_count(opts, "s") &&
stbuf.st_size < opts_optarg_int(opts, "s")) {
Donenames = lut_add(Donenames, fname, "1");
return (B_TRUE);
}
if (opts_count(opts, "p")) {
off_t when = opts_optarg_int(opts, "p");
struct opts *cfopts;
if (when != OPTP_NOW) {
if (when >= 60 * 60)
when -= 59;
if (opts_count(opts, "P")) {
off_t last = opts_optarg_int(opts, "P");
if (Now - last < when)
return (B_TRUE);
} else if ((cfopts = conf_opts(fname)) != NULL &&
opts_count(cfopts, "P")) {
off_t last = opts_optarg_int(cfopts, "P");
Donenames = lut_add(Donenames, fname, "1");
if (Now - last < when)
return (B_TRUE);
}
}
}
if (Debug)
(void) fprintf(stderr, "rotatelog: conditions met\n");
if (opts_count(opts, "l")) {
if (putenv("TZ="))
err(EF_SYS, "putenv TZ");
tzset();
Now = time(0);
rotateto(fnp, opts, 0, recentlog, B_FALSE);
if (putenv("TZ=UTC"))
err(EF_SYS, "putenv TZ");
tzset();
Now = time(0);
} else {
rotateto(fnp, opts, 0, recentlog, B_FALSE);
}
if (opts_count(opts, "o"))
(void) strlcpy(ownerbuf, opts_optarg(opts, "o"), MAXATTR);
else {
(void) snprintf(ownerbuf, MAXATTR, "%ld", stbuf.st_uid);
}
owner = ownerbuf;
if (opts_count(opts, "g"))
group = opts_optarg(opts, "g");
else {
(void) snprintf(groupbuf, MAXATTR, "%ld", stbuf.st_gid);
group = groupbuf;
}
(void) strlcat(ownerbuf, ":", MAXATTR - strlen(ownerbuf));
(void) strlcat(ownerbuf, group, MAXATTR - strlen(ownerbuf));
if (opts_count(opts, "m"))
mode = opts_optarg(opts, "m");
else {
(void) snprintf(modebuf, MAXATTR,
"%03lo", stbuf.st_mode & 0777);
mode = modebuf;
}
docmd(opts, NULL, Touch, fname, NULL, NULL);
docmd(opts, NULL, Chown, owner, fname, NULL);
docmd(opts, NULL, Chmod, mode, fname, NULL);
if (opts_count(opts, "R")) {
struct fn *rawcmd = fn_new(opts_optarg(opts, "R"));
struct fn *cmd = fn_new(NULL);
kw_init(recentlog, NULL);
(void) kw_expand(rawcmd, cmd, 0, B_FALSE);
docmd(opts, "-R cmd", Sh, "-c", fn_s(cmd), NULL);
fn_free(rawcmd);
fn_free(cmd);
}
fn_free(recentlog);
if (opts_count(opts, "a")) {
const char *cmd = opts_optarg(opts, "a");
struct lut *addrs = (struct lut *)lut_lookup(Aftercmds, cmd);
if (opts_count(opts, "e"))
addrs = lut_add(addrs, opts_optarg(opts, "e"), NULL);
Aftercmds = lut_add(Aftercmds, opts_optarg(opts, "a"), addrs);
}
(void) strftime(nowstr, sizeof (nowstr),
"%a %b %e %T %Y", gmtime(&Now));
if (opts_count(opts, "v") && fname != NULL)
(void) out("# recording rotation date %s for %s\n",
nowstr, fname);
conf_set(fname, "P", STRDUP(nowstr));
Donenames = lut_add(Donenames, fname, "1");
return (B_TRUE);
}
static void
rotateto(struct fn *fnp, struct opts *opts, int n, struct fn *recentlog,
boolean_t isgz)
{
struct fn *template = fn_new(opts_optarg(opts, "t"));
struct fn *newfile = fn_new(NULL);
struct fn *dirname;
int hasn;
struct stat stbuf;
char *buf1;
char *buf2;
hasn = kw_expand(template, newfile, n, isgz);
buf1 = fn_s(fnp);
buf2 = fn_s(newfile);
if (Debug)
if (buf1 != NULL && buf2 != NULL) {
(void) fprintf(stderr, "rotateto: %s -> %s (%d)\n",
buf1, buf2, n);
}
if (hasn && lstat(buf2, &stbuf) != -1)
rotateto(newfile, opts, n + 1, recentlog, isgz);
else if (hasn && opts_count(opts, "z")) {
struct fn *gzfnp = fn_dup(newfile);
fn_puts(gzfnp, ".gz");
if (lstat(fn_s(gzfnp), &stbuf) != -1)
rotateto(gzfnp, opts, n + 1, recentlog, B_TRUE);
fn_free(gzfnp);
}
if (n == 0 && opts_count(opts, "b")) {
const char *cmd = opts_optarg(opts, "b");
if (lut_lookup(Beforecmds, cmd) == NULL) {
docmd(opts, "-b cmd", Sh, "-c", cmd, NULL);
Beforecmds = lut_add(Beforecmds, cmd, "1");
}
}
dirname = fn_dirname(newfile);
docmd(opts, "verify directory exists", Mkdir, "-p",
fn_s(dirname), NULL);
fn_free(dirname);
if (n == 0 && opts_count(opts, "c") != 0) {
docopytruncate(opts, fn_s(fnp), fn_s(newfile));
} else if (n == 0 && opts_count(opts, "M")) {
struct fn *rawcmd = fn_new(opts_optarg(opts, "M"));
struct fn *cmd = fn_new(NULL);
kw_init(fnp, newfile);
(void) kw_expand(rawcmd, cmd, 0, B_FALSE);
docmd(opts, "-M cmd", Sh, "-c", fn_s(cmd), NULL);
fn_free(rawcmd);
fn_free(cmd);
} else
docmd(opts, "rotate log file", Mv, "-f",
fn_s(fnp), fn_s(newfile));
if (n == 0)
fn_renew(recentlog, fn_s(newfile));
}
static void
expirefiles(struct fn *fnp, struct opts *opts)
{
char *fname = fn_s(fnp);
struct fn *template;
struct fn *pattern;
struct fn_list *files;
struct fn *nextfnp;
off_t count;
off_t size;
if (Debug && fname != NULL)
(void) fprintf(stderr, "expirefiles: fname <%s>\n", fname);
if (opts_count(opts, "zAS") == 0 && opts_optarg_int(opts, "C") == 0)
return;
kw_init(fnp, NULL);
if (Debug > 1) {
(void) fprintf(stderr, "expirefiles keywords:\n");
kw_print(stderr);
}
if (opts_count(opts, "T")) {
template = fn_new(opts_optarg(opts, "T"));
pattern = glob_to_reglob(template);
} else {
template = fn_new(opts_optarg(opts, "t"));
pattern = fn_new(NULL);
(void) kw_expand(template, pattern, -1,
opts_count(opts, "z") != 0);
}
files = glob_reglob(pattern);
if (Debug) {
char *buf;
buf = fn_s(pattern);
if (buf != NULL) {
(void) fprintf(stderr, "expirefiles: pattern <%s>\n",
buf);
}
fn_list_rewind(files);
while ((nextfnp = fn_list_next(files)) != NULL) {
buf = fn_s(nextfnp);
if (buf != NULL)
(void) fprintf(stderr, " <%s>\n", buf);
}
}
if ((count = opts_optarg_int(opts, "C")) > 0) {
int needexpire = fn_list_count(files) - count;
if (Debug)
(void) fprintf(stderr, "expirefiles: needexpire %d\n",
needexpire);
while (needexpire > 0 &&
((nextfnp = fn_list_popoldest(files)) != NULL)) {
dorm(opts, "expire by count rule", nextfnp);
fn_free(nextfnp);
needexpire--;
}
}
if (opts_count(opts, "S") && (size = opts_optarg_int(opts, "S")) > 0) {
while (fn_list_totalsize(files) > size &&
((nextfnp = fn_list_popoldest(files)) != NULL)) {
dorm(opts, "expire by size rule", nextfnp);
fn_free(nextfnp);
}
}
if (opts_count(opts, "A")) {
int mtime = (int)time(0) - (int)opts_optarg_int(opts, "A");
while ((nextfnp = fn_list_popoldest(files)) != NULL) {
if (fn_getstat(nextfnp)->st_mtime < mtime) {
dorm(opts, "expire by age rule", nextfnp);
fn_free(nextfnp);
} else {
fn_list_addfn(files, nextfnp);
break;
}
}
}
if (opts_count(opts, "z")) {
int zcount = (int)opts_optarg_int(opts, "z");
int fcount = fn_list_count(files);
while (fcount > zcount &&
(nextfnp = fn_list_popoldest(files)) != NULL) {
if (!fn_isgz(nextfnp)) {
if (Debug) {
(void) fprintf(stderr,
"will compress %s count %d\n",
fn_s(nextfnp), fcount);
}
Gzipnames = lut_add(Gzipnames,
fn_s(nextfnp), "1");
}
fn_free(nextfnp);
fcount--;
}
}
fn_free(template);
fn_list_free(files);
}
static void
dorm(struct opts *opts, const char *msg, struct fn *fnp)
{
if (opts_count(opts, "E")) {
struct fn *rawcmd = fn_new(opts_optarg(opts, "E"));
struct fn *cmd = fn_new(NULL);
kw_init(fnp, NULL);
(void) kw_expand(rawcmd, cmd, 0, B_FALSE);
docmd(opts, msg, Sh, "-c", fn_s(cmd), NULL);
fn_free(rawcmd);
fn_free(cmd);
} else
docmd(opts, msg, Rm, "-f", fn_s(fnp), NULL);
Gzipnames = lut_add(Gzipnames, fn_s(fnp), NULL);
}
static void
docmd(struct opts *opts, const char *msg, const char *cmd,
const char *arg1, const char *arg2, const char *arg3)
{
int pid;
int errpipe[2];
if (opts_count(opts, "vn")) {
const char *simplecmd;
if ((simplecmd = strrchr(cmd, '/')) == NULL)
simplecmd = cmd;
else
simplecmd++;
(void) out("%s", simplecmd);
if (arg1)
(void) out(" %s", arg1);
if (arg2)
(void) out(" %s", arg2);
if (arg3)
(void) out(" %s", arg3);
if (msg)
(void) out(" # %s", msg);
(void) out("\n");
}
if (opts_count(opts, "n"))
return;
if (pipe(errpipe) < 0)
err(EF_SYS, "pipe");
if ((pid = fork()) < 0)
err(EF_SYS, "fork");
else if (pid) {
int wstat;
struct pollfd pfd;
boolean_t first = B_TRUE;
(void) close(errpipe[1]);
pfd.fd = errpipe[0];
pfd.events = POLLIN | POLLRDNORM | POLLRDBAND | POLLPRI;
for (;;) {
pfd.revents = 0;
if (poll(&pfd, 1, -1) == -1) {
if (errno == EINTR) {
continue;
}
err(EF_SYS, "poll");
break;
}
if ((pfd.events & pfd.revents) != 0) {
if (first) {
err(EF_WARN,
"command failed: %s%s%s%s%s%s%s",
cmd,
(arg1) ? " " : "",
(arg1) ? arg1 : "",
(arg2) ? " " : "",
(arg2) ? arg2 : "",
(arg3) ? " " : "",
(arg3) ? arg3 : "");
first = B_FALSE;
}
err_fromfd(pfd.fd);
}
if ((pfd.revents & (POLLERR | POLLHUP)) != 0) {
break;
}
}
if (waitpid(pid, &wstat, 0) < 0) {
err(EF_SYS, "waitpid");
return;
}
if (!first) {
} else if (WIFSIGNALED(wstat)) {
err(EF_WARN,
"command died, signal %d: %s%s%s%s%s%s%s",
WTERMSIG(wstat),
cmd,
(arg1) ? " " : "",
(arg1) ? arg1 : "",
(arg2) ? " " : "",
(arg2) ? arg2 : "",
(arg3) ? " " : "",
(arg3) ? arg3 : "");
} else if (WIFEXITED(wstat) && WEXITSTATUS(wstat)) {
err(EF_WARN,
"command error, exit %d: %s%s%s%s%s%s%s",
WEXITSTATUS(wstat),
cmd,
(arg1) ? " " : "",
(arg1) ? arg1 : "",
(arg2) ? " " : "",
(arg2) ? arg2 : "",
(arg3) ? " " : "",
(arg3) ? arg3 : "");
}
(void) close(errpipe[0]);
} else {
(void) dup2(errpipe[1], fileno(stderr));
(void) close(errpipe[0]);
(void) execl(cmd, cmd, arg1, arg2, arg3, 0);
perror(cmd);
_exit(1);
}
}
static void
docopytruncate(struct opts *opts, const char *file, const char *file_copy)
{
int fi, fo;
char buf[128 * 1024];
struct stat s;
struct utimbuf times;
off_t written = 0, rem, last = 0, thresh = 1024 * 1024;
ssize_t len = 0;
if (opts_count(opts, "vn") != 0) {
(void) out("# log rotation via atomic copy and truncation"
" (-c flag):\n");
(void) out("# copy %s to %s\n", file, file_copy);
(void) out("# truncate %s\n", file);
}
if (opts_count(opts, "n"))
return;
if ((fi = open(file, O_RDWR)) < 0) {
err(EF_SYS, "cannot open file %s", file);
return;
}
if (fstat(fi, &s) < 0) {
err(EF_SYS, "cannot access: %s", file);
(void) close(fi);
return;
}
if ((fo = open(file_copy, O_CREAT|O_TRUNC|O_WRONLY, s.st_mode)) < 0) {
err(EF_SYS, "cannot create file: %s", file_copy);
(void) close(fi);
return;
}
(void) fchown(fo, s.st_uid, s.st_gid);
do {
if (fstat(fi, &s) < 0) {
err(EF_SYS, "cannot stat: %s", file);
(void) close(fi);
(void) close(fo);
(void) remove(file_copy);
return;
}
if ((rem = s.st_size - written) < thresh) {
if (rem >= 0)
break;
(void) ftruncate(fo, 0);
(void) lseek(fo, 0, SEEK_SET);
(void) lseek(fi, 0, SEEK_SET);
break;
}
if (written != 0 && rem > last) {
break;
}
last = rem;
while (rem > 0) {
if ((len = read(fi, buf, MIN(sizeof (buf), rem))) <= 0)
break;
if (write(fo, buf, len) == len) {
rem -= len;
written += len;
continue;
}
err(EF_SYS, "cannot write into file %s", file_copy);
(void) close(fi);
(void) close(fo);
(void) remove(file_copy);
return;
}
} while (len >= 0);
if (fchmod(fi, s.st_mode|S_ISGID) < 0)
err(EF_SYS, "cannot set mandatory lock bit for: %s", file);
if (lockf(fi, F_LOCK, 0) == -1)
err(EF_SYS, "cannot lock file %s", file);
while ((len = read(fi, buf, sizeof (buf))) > 0)
if (write(fo, buf, len) != len) {
err(EF_SYS, "cannot write into file %s", file_copy);
(void) lockf(fi, F_ULOCK, 0);
(void) fchmod(fi, s.st_mode);
(void) close(fi);
(void) close(fo);
(void) remove(file_copy);
return;
}
(void) ftruncate(fi, 0);
if (lockf(fi, F_ULOCK, 0) == -1)
err(EF_SYS, "cannot unlock file %s", file);
if (fchmod(fi, s.st_mode) < 0)
err(EF_SYS, "cannot reset mandatory lock bit for: %s", file);
(void) close(fi);
(void) close(fo);
times.actime = s.st_atime;
times.modtime = s.st_mtime;
(void) utime(file_copy, ×);
}