# # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright (c) 2001, 2010, Oracle and/or its affiliates. All rights reserved. # PROG= logadm TESTS= conftest globtest kwtest luttest optstest OBJS= conf.o err.o fn.o glob.o kw.o lut.o main.o opts.o TESTOBJS= $(TESTS:%=%.o) SRCS= $(OBJS:%.o=%.c) POFILES= $(OBJS:%.o=%.po) CONFIGFILE= logadm.conf include ../Makefile.cmd CPPFLAGS += -D_FILE_OFFSET_BITS=64 CERRWARN += -Wno-parentheses CERRWARN += -Wno-clobbered XGETFLAGS += -a -x logadm.xcl $(ROOTETC)/$(CONFIGFILE): FILEMODE= 644 CLOBBERFILES += $(TESTS) LOGADMDIR = $(ROOT)/etc/logadm.d MFSTFILES = logadm-upgrade.xml MANIFESTDIR = $(ROOT)/lib/svc/manifest/system MANIFEST = $(MFSTFILES:%=$(MANIFESTDIR)/%) $(MANIFEST) : FILEMODE = 0444 METHODFILES = logadm-upgrade METHODDIR = $(ROOT)/lib/svc/method METHOD = $(METHODFILES:%=$(METHODDIR)/%) $(METHOD) : FILEMODE = 0555 .KEEP_STATE: all: $(PROG) test: $(TESTS) $(PROG) $(PERL) -w ./tester `pwd` $(PROG): $(OBJS) $(LINK.c) -o $@ $(OBJS) $(LDLIBS) $(POST_PROCESS) install: all $(ROOTUSRSBINPROG) $(ROOTETC)/$(CONFIGFILE) $(LOGADMDIR) \ $(MANIFESTDIR) $(MANIFEST) $(METHODDIR) $(METHOD) $(LOGADMDIR): $(INS.dir) $(MANIFESTDIR): $(INS.dir) $(MANIFESTDIR)/% : % $(INS.file) $(METHODDIR): $(INS.dir) $(METHODDIR)/% : % $(INS.file) $(POFILE): $(POFILES) $(RM) $@ cat $(POFILES) > $@ clean: $(RM) $(OBJS) $(TESTOBJS) include ../Makefile.targ # # dependencies for various test programs built from same source # %test.o: %.c $(COMPILE.c) -DTESTMODULE -o $@ $< $(POST_PROCESS_O) conftest: conftest.o err.o fn.o lut.o opts.o $(LINK.c) -o conftest conftest.o err.o fn.o lut.o opts.o $(LDLIBS) $(POST_PROCESS) globtest: globtest.o lut.o err.o fn.o $(LINK.c) -o globtest globtest.o lut.o err.o fn.o $(LDLIBS) $(POST_PROCESS) kwtest: kwtest.o err.o fn.o lut.o $(LINK.c) -o kwtest kwtest.o err.o fn.o lut.o $(LDLIBS) $(POST_PROCESS) luttest: luttest.o err.o $(LINK.c) -o luttest luttest.o err.o $(LDLIBS) $(POST_PROCESS) optstest: optstest.o err.o fn.o lut.o $(LINK.c) -o optstest optstest.o err.o fn.o lut.o $(LDLIBS) $(POST_PROCESS) /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2001, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2013, Joyent, Inc. All rights reserved. * Copyright 2018 Sebastian Wiedenroth */ /* * logadm/conf.c -- configuration file module */ #include #include #include #include #include #include #include #include #include #include #include #include "err.h" #include "lut.h" #include "fn.h" #include "opts.h" #include "conf.h" /* forward declarations of functions private to this module */ static void fillconflist(int lineno, const char *entry, struct opts *opts, const char *com, int flags); static void fillargs(char *arg); static char *nexttok(char **ptrptr); static void conf_print(FILE *cstream, FILE *tstream); static const char *Confname; /* name of the confile file */ static int Conffd = -1; /* file descriptor for config file */ static char *Confbuf; /* copy of the config file (a la mmap()) */ static int Conflen; /* length of mmap'd config file area */ static const char *Timesname; /* name of the timestamps file */ static int Timesfd = -1; /* file descriptor for timestamps file */ static char *Timesbuf; /* copy of the timestamps file (a la mmap()) */ static int Timeslen; /* length of mmap'd timestamps area */ static int Singlefile; /* Conf and Times in the same file */ static int Changed; /* what changes need to be written back */ static int Canchange; /* what changes can be written back */ static int Changing; /* what changes have been requested */ #define CHG_NONE 0 #define CHG_TIMES 1 #define CHG_BOTH 3 /* * our structured representation of the configuration file * is made up of a list of these */ struct confinfo { struct confinfo *cf_next; int cf_lineno; /* line number in file */ const char *cf_entry; /* name of entry, if line has an entry */ struct opts *cf_opts; /* parsed rhs of entry */ const char *cf_com; /* any comment text found */ int cf_flags; }; #define CONFF_DELETED 1 /* entry should be deleted on write back */ #define CONFF_TSONLY 2 /* entry should only be in timestamps file */ static struct confinfo *Confinfo; /* the entries in the config file */ static struct confinfo *Confinfolast; /* end of list */ static struct lut *Conflut; /* lookup table keyed by entry name */ static struct fn_list *Confentries; /* list of valid entry names */ /* allocate & fill in another entry in our list */ static void fillconflist(int lineno, const char *entry, struct opts *opts, const char *com, int flags) { struct confinfo *cp = MALLOC(sizeof (*cp)); cp->cf_next = NULL; cp->cf_lineno = lineno; cp->cf_entry = entry; cp->cf_opts = opts; cp->cf_com = com; cp->cf_flags = flags; if (entry != NULL) { Conflut = lut_add(Conflut, entry, cp); fn_list_adds(Confentries, entry); } if (Confinfo == NULL) Confinfo = Confinfolast = cp; else { Confinfolast->cf_next = cp; Confinfolast = cp; } } static char **Args; /* static buffer for args */ static int ArgsN; /* size of our static buffer */ static int ArgsI; /* index into Cmdargs as we walk table */ #define CONF_ARGS_INC 1024 /* callback for lut_walk to build a cmdargs vector */ static void fillargs(char *arg) { if (ArgsI >= ArgsN) { /* need bigger table */ Args = REALLOC(Args, sizeof (char *) * (ArgsN + CONF_ARGS_INC)); ArgsN += CONF_ARGS_INC; } Args[ArgsI++] = arg; } /* isolate and return the next token */ static char * nexttok(char **ptrptr) { char *ptr = *ptrptr; char *eptr; char *quote = NULL; while (*ptr && isspace(*ptr)) ptr++; if (*ptr == '"' || *ptr == '\'') quote = ptr++; for (eptr = ptr; *eptr; eptr++) if (quote && *eptr == *quote) { /* found end quote */ *eptr++ = '\0'; *ptrptr = eptr; return (ptr); } else if (!quote && isspace(*eptr)) { /* found end of unquoted area */ *eptr++ = '\0'; *ptrptr = eptr; return (ptr); } if (quote != NULL) err(EF_FILE|EF_JMP, "Unbalanced %c quote", *quote); /*NOTREACHED*/ *ptrptr = eptr; if (ptr == eptr) return (NULL); else return (ptr); } /* * scan the memory image of a file * returns: 0: error, 1: ok, 3: -P option found */ static int conf_scan(const char *fname, char *buf, int buflen, int timescan) { int ret = 1; int lineno = 0; int flags = 0; char *line; char *eline; char *ebuf; char *entry, *comment; ebuf = &buf[buflen]; if (buf[buflen - 1] != '\n') err(EF_WARN|EF_FILE, "file %s doesn't end with newline, " "last line ignored.", fname); for (line = buf; line < ebuf; line = eline) { char *ap; struct opts *opts = NULL; struct confinfo *cp = NULL; lineno++; err_fileline(fname, lineno); eline = line; comment = NULL; for (; eline < ebuf; eline++) { /* check for continued lines */ if (comment == NULL && *eline == '\\' && eline + 1 < ebuf && *(eline + 1) == '\n') { *eline = ' '; *(eline + 1) = ' '; lineno++; err_fileline(fname, lineno); continue; } /* check for comments */ if (comment == NULL && *eline == '#') { *eline = '\0'; comment = (eline + 1); continue; } /* check for end of line */ if (*eline == '\n') break; } if (comment >= ebuf) comment = NULL; if (eline >= ebuf) { /* discard trailing unterminated line */ continue; } *eline++ = '\0'; /* * now we have the entry, if any, at "line" * and the comment, if any, at "comment" */ /* entry is first token */ entry = nexttok(&line); if (entry == NULL) { /* it's just a comment line */ if (!timescan) fillconflist(lineno, entry, NULL, comment, 0); continue; } if (strcmp(entry, "logadm-version") == 0) { /* * we somehow opened some future format * conffile that we likely don't understand. * if the given version is "1" then go on, * otherwise someone is mixing versions * and we can't help them other than to * print an error and exit. */ if ((entry = nexttok(&line)) != NULL && strcmp(entry, "1") != 0) err(0, "%s version not supported " "by this version of logadm.", fname); continue; } /* form an argv array */ ArgsI = 0; while (ap = nexttok(&line)) fillargs(ap); /* * If there is no next token on the line, make sure that * we get a non-NULL Args array. */ if (Args == NULL) fillargs(NULL); Args[ArgsI] = NULL; LOCAL_ERR_BEGIN { if (SETJMP) { err(EF_FILE, "cannot process invalid entry %s", entry); ret = 0; LOCAL_ERR_BREAK; } if (timescan) { /* append to config options */ cp = lut_lookup(Conflut, entry); if (cp != NULL) { opts = cp->cf_opts; } } opts = opts_parse(opts, Args, OPTF_CONF); if (!timescan || cp == NULL) { /* * If we're not doing timescan, we track this * entry. If we are doing timescan and have * what looks like an orphaned entry (cp == * NULL) then we also have to track. See the * comment in rotatelog. We need to allow for * the case where the logname is not the same as * the log file name. */ flags = 0; if (timescan && cp == NULL) flags = CONFF_TSONLY; fillconflist(lineno, entry, opts, comment, flags); } LOCAL_ERR_END } if (ret == 1 && opts && opts_optarg(opts, "P") != NULL) ret = 3; } err_fileline(NULL, 0); return (ret); } /* * conf_open -- open the configuration file, lock it if we have write perms */ int conf_open(const char *cfname, const char *tfname, struct opts *cliopts) { struct stat stbuf1, stbuf2, stbuf3; struct flock flock; int ret; Confname = cfname; Timesname = tfname; Confentries = fn_list_new(NULL); Changed = CHG_NONE; Changing = CHG_TIMES; if (opts_count(cliopts, "Vn") != 0) Changing = CHG_NONE; else if (opts_count(cliopts, "rw") != 0) Changing = CHG_BOTH; Singlefile = strcmp(Confname, Timesname) == 0; if (Singlefile && Changing == CHG_TIMES) Changing = CHG_BOTH; /* special case this so we don't even try locking the file */ if (strcmp(Confname, "/dev/null") == 0) return (0); while (Conffd == -1) { Canchange = CHG_BOTH; if ((Conffd = open(Confname, O_RDWR)) < 0) { if (Changing == CHG_BOTH) err(EF_SYS, "open %s", Confname); Canchange = CHG_TIMES; if ((Conffd = open(Confname, O_RDONLY)) < 0) err(EF_SYS, "open %s", Confname); } flock.l_type = (Canchange == CHG_BOTH) ? F_WRLCK : F_RDLCK; flock.l_whence = SEEK_SET; flock.l_start = 0; flock.l_len = 1; if (fcntl(Conffd, F_SETLKW, &flock) < 0) err(EF_SYS, "flock on %s", Confname); /* wait until after file is locked to get filesize */ if (fstat(Conffd, &stbuf1) < 0) err(EF_SYS, "fstat on %s", Confname); /* verify that we've got a lock on the active file */ if (stat(Confname, &stbuf2) < 0 || !(stbuf2.st_dev == stbuf1.st_dev && stbuf2.st_ino == stbuf1.st_ino)) { /* wrong config file, try again */ (void) close(Conffd); Conffd = -1; } } while (!Singlefile && Timesfd == -1) { if ((Timesfd = open(Timesname, O_CREAT|O_RDWR, 0644)) < 0) { if (Changing != CHG_NONE) err(EF_SYS, "open %s", Timesname); Canchange = CHG_NONE; if ((Timesfd = open(Timesname, O_RDONLY)) < 0) err(EF_SYS, "open %s", Timesname); } flock.l_type = (Canchange != CHG_NONE) ? F_WRLCK : F_RDLCK; flock.l_whence = SEEK_SET; flock.l_start = 0; flock.l_len = 1; if (fcntl(Timesfd, F_SETLKW, &flock) < 0) err(EF_SYS, "flock on %s", Timesname); /* wait until after file is locked to get filesize */ if (fstat(Timesfd, &stbuf2) < 0) err(EF_SYS, "fstat on %s", Timesname); /* verify that we've got a lock on the active file */ if (stat(Timesname, &stbuf3) < 0 || !(stbuf2.st_dev == stbuf3.st_dev && stbuf2.st_ino == stbuf3.st_ino)) { /* wrong timestamp file, try again */ (void) close(Timesfd); Timesfd = -1; continue; } /* check that Timesname isn't an alias for Confname */ if (stbuf2.st_dev == stbuf1.st_dev && stbuf2.st_ino == stbuf1.st_ino) err(0, "Timestamp file %s can't refer to " "Configuration file %s", Timesname, Confname); } Conflen = stbuf1.st_size; Timeslen = stbuf2.st_size; if (Conflen == 0) return (1); /* empty file, don't bother parsing it */ if ((Confbuf = (char *)mmap(0, Conflen, PROT_READ | PROT_WRITE, MAP_PRIVATE, Conffd, 0)) == (char *)-1) err(EF_SYS, "mmap on %s", Confname); ret = conf_scan(Confname, Confbuf, Conflen, 0); if (ret == 3 && !Singlefile && Canchange == CHG_BOTH) { /* * arrange to transfer any timestamps * from conf_file to timestamps_file */ Changing = Changed = CHG_BOTH; } if (Timesfd != -1 && Timeslen != 0) { if ((Timesbuf = (char *)mmap(0, Timeslen, PROT_READ | PROT_WRITE, MAP_PRIVATE, Timesfd, 0)) == (char *)-1) err(EF_SYS, "mmap on %s", Timesname); ret &= conf_scan(Timesname, Timesbuf, Timeslen, 1); } /* * possible future enhancement: go through and mark any entries: * logfile -P * as DELETED if the logfile doesn't exist */ return (ret); } /* * conf_close -- close the configuration file */ void conf_close(struct opts *opts) { char cuname[PATH_MAX], tuname[PATH_MAX]; int cfd, tfd; FILE *cfp = NULL, *tfp = NULL; boolean_t safe_update = B_TRUE; if (Changed == CHG_NONE || opts_count(opts, "n") != 0) { if (opts_count(opts, "v")) (void) out("# %s and %s unchanged\n", Confname, Timesname); goto cleanup; } if (Debug > 1) { (void) fprintf(stderr, "conf_close, saving logadm context:\n"); conf_print(stderr, NULL); } cuname[0] = tuname[0] = '\0'; LOCAL_ERR_BEGIN { if (SETJMP) { safe_update = B_FALSE; LOCAL_ERR_BREAK; } if (Changed == CHG_BOTH) { if (Canchange != CHG_BOTH) err(EF_JMP, "internal error: attempting " "to update %s without locking", Confname); (void) snprintf(cuname, sizeof (cuname), "%sXXXXXX", Confname); if ((cfd = mkstemp(cuname)) == -1) err(EF_SYS|EF_JMP, "open %s replacement", Confname); if (opts_count(opts, "v")) (void) out("# writing changes to %s\n", cuname); if (fchmod(cfd, 0644) == -1) err(EF_SYS|EF_JMP, "chmod %s", cuname); if ((cfp = fdopen(cfd, "w")) == NULL) err(EF_SYS|EF_JMP, "fdopen on %s", cuname); } else { /* just toss away the configuration data */ cfp = fopen("/dev/null", "w"); } if (!Singlefile) { if (Canchange == CHG_NONE) err(EF_JMP, "internal error: attempting " "to update %s without locking", Timesname); (void) snprintf(tuname, sizeof (tuname), "%sXXXXXX", Timesname); if ((tfd = mkstemp(tuname)) == -1) err(EF_SYS|EF_JMP, "open %s replacement", Timesname); if (opts_count(opts, "v")) (void) out("# writing changes to %s\n", tuname); if (fchmod(tfd, 0644) == -1) err(EF_SYS|EF_JMP, "chmod %s", tuname); if ((tfp = fdopen(tfd, "w")) == NULL) err(EF_SYS|EF_JMP, "fdopen on %s", tuname); } conf_print(cfp, tfp); if (fclose(cfp) < 0) err(EF_SYS|EF_JMP, "fclose on %s", Confname); if (tfp != NULL && fclose(tfp) < 0) err(EF_SYS|EF_JMP, "fclose on %s", Timesname); LOCAL_ERR_END } if (!safe_update) { if (cuname[0] != 0) (void) unlink(cuname); if (tuname[0] != 0) (void) unlink(tuname); err(EF_JMP, "unsafe to update configuration file " "or timestamps"); return; } /* rename updated files into place */ if (cuname[0] != '\0') if (rename(cuname, Confname) < 0) err(EF_SYS, "rename %s to %s", cuname, Confname); if (tuname[0] != '\0') if (rename(tuname, Timesname) < 0) err(EF_SYS, "rename %s to %s", tuname, Timesname); Changed = CHG_NONE; cleanup: if (Conffd != -1) { (void) close(Conffd); Conffd = -1; } if (Timesfd != -1) { (void) close(Timesfd); Timesfd = -1; } if (Conflut) { lut_free(Conflut, free); Conflut = NULL; } if (Confentries) { fn_list_free(Confentries); Confentries = NULL; } } /* * conf_lookup -- lookup an entry in the config file */ void * conf_lookup(const char *lhs) { struct confinfo *cp = lut_lookup(Conflut, lhs); if (cp != NULL) err_fileline(Confname, cp->cf_lineno); return (cp); } /* * conf_opts -- return the parsed opts for an entry */ struct opts * conf_opts(const char *lhs) { struct confinfo *cp = lut_lookup(Conflut, lhs); if (cp != NULL) return (cp->cf_opts); return (opts_parse(NULL, NULL, OPTF_CONF)); } /* * conf_replace -- replace an entry in the config file */ void conf_replace(const char *lhs, struct opts *newopts) { struct confinfo *cp = lut_lookup(Conflut, lhs); if (Conffd == -1) return; if (cp != NULL) { cp->cf_opts = newopts; /* cp->cf_args = NULL; */ if (newopts == NULL) cp->cf_flags |= CONFF_DELETED; } else fillconflist(0, lhs, newopts, NULL, 0); Changed = CHG_BOTH; } /* * conf_set -- set options for an entry in the config file */ void conf_set(const char *entry, char *o, const char *optarg) { struct confinfo *cp = lut_lookup(Conflut, entry); if (Conffd == -1) return; if (cp != NULL) { cp->cf_flags &= ~CONFF_DELETED; } else { fillconflist(0, STRDUP(entry), opts_parse(NULL, NULL, OPTF_CONF), NULL, 0); if ((cp = lut_lookup(Conflut, entry)) == NULL) err(0, "conf_set internal error"); } (void) opts_set(cp->cf_opts, o, optarg); if (strcmp(o, "P") == 0) Changed |= CHG_TIMES; else Changed = CHG_BOTH; } /* * conf_entries -- list all the entry names */ struct fn_list * conf_entries(void) { return (Confentries); } /* print the config file */ static void conf_print(FILE *cstream, FILE *tstream) { struct confinfo *cp; char *exclude_opts = "PFfhnrvVw"; const char *timestamp; if (tstream == NULL) { exclude_opts++; /* -P option goes to config file */ } else { (void) fprintf(tstream, gettext( "# This file holds internal data for logadm(8).\n" "# Do not edit.\n")); } for (cp = Confinfo; cp; cp = cp->cf_next) { if (cp->cf_flags & CONFF_DELETED) continue; if (cp->cf_entry) { /* output timestamps to tstream */ if (tstream != NULL && (timestamp = opts_optarg(cp->cf_opts, "P")) != NULL) { opts_printword(cp->cf_entry, tstream); (void) fprintf(tstream, " -P "); opts_printword(timestamp, tstream); (void) fprintf(tstream, "\n"); } if (cp->cf_flags & CONFF_TSONLY) continue; opts_printword(cp->cf_entry, cstream); if (cp->cf_opts) opts_print(cp->cf_opts, cstream, exclude_opts); } if (cp->cf_com) { if (cp->cf_entry) (void) fprintf(cstream, " "); (void) fprintf(cstream, "#%s", cp->cf_com); } (void) fprintf(cstream, "\n"); } } #ifdef TESTMODULE int Debug; /* * test main for conf module, usage: a.out conffile */ int main(int argc, char *argv[]) { struct opts *opts; err_init(argv[0]); setbuf(stdout, NULL); opts_init(Opttable, Opttable_cnt); opts = opts_parse(NULL, NULL, 0); if (argc != 2) err(EF_RAW, "usage: %s conffile\n", argv[0]); (void) conf_open(argv[1], argv[1], opts); printf("conffile <%s>:\n", argv[1]); conf_print(stdout, NULL); conf_close(opts); err_done(0); /* NOTREACHED */ return (0); } #endif /* TESTMODULE */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2001, 2010, Oracle and/or its affiliates. All rights reserved. * * logadm/conf.h -- public definitions for conf module */ #ifndef _LOGADM_CONF_H #define _LOGADM_CONF_H #ifdef __cplusplus extern "C" { #endif int conf_open(const char *cfname, const char *tfname, struct opts *opts); void conf_close(struct opts *opts); void *conf_lookup(const char *lhs); struct opts *conf_opts(const char *lhs); void conf_replace(const char *lhs, struct opts *newopts); void conf_set(const char *entry, char *o, const char *optarg); struct fn_list *conf_entries(void); #ifdef __cplusplus } #endif #endif /* _LOGADM_CONF_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2001, 2010, Oracle and/or its affiliates. All rights reserved. * * logadm/err.c -- some basic error routines * */ #include #include #include #include #include #include #include #include "err.h" jmp_buf Err_env; jmp_buf *Err_env_ptr; static const char *Myname; static int Exitcode; static FILE *Errorfile; /* * err_init -- initialize the error handling routine * */ void err_init(const char *myname) { char *ptr; if ((ptr = strrchr(myname, '/')) == NULL) Myname = myname; else Myname = ptr + 1; } static const char *File; static int Line; /* * err_fileline -- record the filename/line number for err(EF_FILE, ...) */ void err_fileline(const char *file, int line) { File = file; Line = line; } /* * err -- print an error message and return, exit or longjmp based on flags * * this routine calls gettext() to translate the fmt string. */ /*PRINTFLIKE2*/ void err(int flags, const char *fmt, ...) { va_list ap; int safe_errno = errno; char *errno_msg = NULL; int as_is = 0; int jump = 0; int warning = 0; int fileline = 0; char *prefix = "Error: "; const char *intlfmt; va_start(ap, fmt); intlfmt = gettext(fmt); if (flags & EF_WARN) { warning = 1; prefix = "Warning: "; } if (flags & EF_FILE) { fileline = 1; Exitcode++; } if (flags & EF_SYS) errno_msg = strerror(safe_errno); if (flags & EF_JMP) jump = 1; if (flags & EF_RAW) as_is = 1; /* print a copy to stderr */ if (!as_is) { if (Myname != NULL) { (void) fprintf(stderr, "%s: ", Myname); if (Errorfile) (void) fprintf(Errorfile, "%s: ", Myname); } if (fileline && File) { (void) fprintf(stderr, "%s line %d: ", File, Line); if (Errorfile) (void) fprintf(Errorfile, "%s line %d: ", File, Line); } (void) fputs(gettext(prefix), stderr); if (Errorfile) (void) fputs(gettext(prefix), Errorfile); } (void) vfprintf(stderr, intlfmt, ap); if (Errorfile) (void) vfprintf(Errorfile, intlfmt, ap); if (errno_msg != NULL) { (void) fprintf(stderr, ": %s", errno_msg); if (Errorfile) (void) fprintf(Errorfile, ": %s", errno_msg); } if (!as_is) { (void) fprintf(stderr, "\n"); if (Errorfile) (void) fprintf(Errorfile, "\n"); } (void) fflush(stderr); if (Errorfile) (void) fflush(Errorfile); va_end(ap); if (jump) longjmp(*Err_env_ptr, 1); if (!warning && !fileline) { err_done(1); /*NOTREACHED*/ } } /* * out -- print a message and return * * this routine calls gettext() to translate the fmt string. */ /*PRINTFLIKE1*/ void out(const char *fmt, ...) { va_list ap; va_start(ap, fmt); (void) vfprintf(stdout, gettext(fmt), ap); va_end(ap); } #define CHUNKSIZE 8192 /* for copying stderr */ /* * err_fromfd -- copy data from fd to stderr */ void err_fromfd(int fd) { char buf[CHUNKSIZE]; int count; while ((count = read(fd, buf, CHUNKSIZE)) > 0) { (void) fwrite(buf, 1, count, stderr); if (Errorfile) (void) fwrite(buf, 1, count, Errorfile); } (void) fflush(stderr); if (Errorfile) (void) fflush(Errorfile); } /* * err_done -- exit the program */ void err_done(int exitcode) { /* send error mail if applicable */ err_mailto(NULL); if (exitcode) exit(exitcode); else exit(Exitcode); /*NOTREACHED*/ } #define MAXLINE 8192 /* for tmp file line buffer */ /* * err_mailto -- arrange for error output to be mailed to someone */ void err_mailto(const char *recipient) { static const char *lastrecipient; static char mailcmd[] = "/bin/mailx -s 'logadm error output'"; char *cmd; int len; FILE *pfp; char line[MAXLINE]; if (lastrecipient != NULL) { if (recipient != NULL && strcmp(recipient, lastrecipient) == 0) return; /* keep going, same recipient */ /* stop saving output for lastrecipient and send message */ if (ftell(Errorfile)) { rewind(Errorfile); len = strlen(lastrecipient) + strlen(mailcmd) + 2; cmd = MALLOC(len); (void) snprintf(cmd, len, "%s %s", mailcmd, lastrecipient); if ((pfp = popen(cmd, "w")) == NULL) err(EF_SYS, "popen to mailx"); while (fgets(line, MAXLINE, Errorfile) != NULL) (void) fputs(line, pfp); (void) pclose(pfp); } (void) fclose(Errorfile); Errorfile = NULL; } if (recipient != NULL) { /* start saving error output for this recipient */ if ((Errorfile = tmpfile()) == NULL) err(EF_SYS, "tmpfile"); } lastrecipient = recipient; } /* * err_malloc -- a malloc() with checks * * this routine is typically called via the MALLOC() macro in err.h */ void * err_malloc(int nbytes, const char *fname, int line) { void *retval = malloc(nbytes); if (retval == NULL) err(0, "%s:%d: out of memory", fname, line); return (retval); } /* * err_realloc -- a realloc() with checks * * this routine is typically called via the REALLOC() macro in err.h */ void * err_realloc(void *ptr, int nbytes, const char *fname, int line) { void *retval = realloc(ptr, nbytes); if (retval == NULL) err(0, "%s:%d: out of memory", fname, line); return (retval); } /* * err_strdup -- a strdup() with checks * * this routine is typically called via the STRDUP() macro in err.h */ char * err_strdup(const char *ptr, const char *fname, int line) { char *retval = NULL; if (ptr != NULL) { retval = strdup(ptr); if (retval == NULL) err(0, "%s:%d: out of memory", fname, line); } else err(0, "%s:%d: could not strdup", fname, line); return (retval); } /* * err_free -- a free() with checks * * this routine is typically called via the FREE() macro in err.h */ /*ARGSUSED1*/ void err_free(void *ptr, const char *fname, int line) { /* nothing to check in this version */ free(ptr); } /* * err_exitcode -- set an error exit code for when done(0) is called */ void err_exitcode(int exitcode) { Exitcode = exitcode; } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2001, 2010, Oracle and/or its affiliates. All rights reserved. * * logadm/err.h -- public definitions for error module */ #ifndef _LOGADM_ERR_H #define _LOGADM_ERR_H #include #ifdef __cplusplus extern "C" { #endif /* basic error handling routines */ void err_init(const char *myname); void err_fileline(const char *file, int line); void err(int flags, const char *fmt, ...); void out(const char *fmt, ...); void err_fromfd(int fd); void err_done(int exitcode); void err_exitcode(int exitcode); void err_mailto(const char *recipient); /* flags for err() */ #define EF_WARN 0x01 /* print warning and return */ #define EF_FILE 0x02 /* prepend file:line from last err_fileline() call */ #define EF_SYS 0x04 /* append errno text to message */ #define EF_JMP 0x08 /* longjmp through Error_env after printing error */ #define EF_RAW 0x10 /* don't prepend/append anything to message */ extern jmp_buf Err_env; extern jmp_buf *Err_env_ptr; #define SETJMP setjmp(*(Err_env_ptr = &Err_env)) #define LOCAL_ERR_BEGIN { jmp_buf Err_env, *Save_err_env_ptr = Err_env_ptr; { #define LOCAL_ERR_END } Err_env_break: Err_env_ptr = Save_err_env_ptr; } #define LOCAL_ERR_BREAK goto Err_env_break #define MALLOC(nbytes) err_malloc(nbytes, __FILE__, __LINE__) void *err_malloc(int nbytes, const char *fname, int line); #define REALLOC(ptr, nbytes) err_realloc(ptr, nbytes, __FILE__, __LINE__) void *err_realloc(void *ptr, int nbytes, const char *fname, int line); #define FREE(ptr) err_free(ptr, __FILE__, __LINE__) void err_free(void *ptr, const char *fname, int line); #define STRDUP(ptr) err_strdup(ptr, __FILE__, __LINE__) char *err_strdup(const char *ptr, const char *fname, int line); extern int Debug; /* replace with #define to zero to compile out Debug code */ #ifdef __cplusplus } #endif #endif /* _LOGADM_ERR_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * logadm/fn.c -- "filename" string module * * this file contains routines for the manipulation of filenames. * they aren't particularly fast (at least they weren't designed * for performance), but they are simple and put all the malloc/free * stuff for these strings in a central place. most routines in * logadm that return filenames return a struct fn, and most routines * that return lists of strings return a struct fn_list. */ #include #include #include #include #include #include "err.h" #include "fn.h" #define roundup(x, y) ((((x)+((y)-1))/(y))*(y)) /* * constants controlling how we malloc space. bigger means fewer * calls to malloc. smaller means less wasted space. */ #define FN_MIN 1024 /* initial size of string buffers */ #define FN_MAX 10240 /* maximum size allowed before fatal "overflow" error */ #define FN_INC 1024 /* increments in buffer size as strings grow */ /* info created by fn_new(), private to this module */ struct fn { char *fn_buf; /* first location in buf */ char *fn_buflast; /* last location in buf */ char *fn_rptr; /* read pointer (next unread character) */ char *fn_wptr; /* write pointer (points at null terminator) */ struct fn *fn_next; /* next in list */ struct stat fn_stbuf; int fn_n; }; /* info created by fn_list_new(), private to this module */ struct fn_list { struct fn *fnl_first; /* first element of list */ struct fn *fnl_last; /* last element of list */ struct fn *fnl_rptr; /* read pointer for iterating through list */ }; /* * fn_new -- create a new filename buffer, possibly with initial contents * * use like this: * struct fn *fnp = fn_new("this is a string"); */ struct fn * fn_new(const char *s) { struct fn *fnp = MALLOC(sizeof (struct fn)); fnp->fn_n = -1; bzero(&fnp->fn_stbuf, sizeof (fnp->fn_stbuf)); fnp->fn_next = NULL; /* if passed-in string contains at least 1 non-null character... */ if (s != NULL && *s) { int len = strlen(s); int buflen = roundup(len + 1, FN_INC); /* start with buffer filled with passed-in string */ fnp->fn_buf = MALLOC(buflen); fnp->fn_buflast = &fnp->fn_buf[buflen - 1]; (void) strlcpy(fnp->fn_buf, s, buflen); fnp->fn_rptr = fnp->fn_buf; fnp->fn_wptr = &fnp->fn_buf[len]; } else { /* start with empty buffer */ fnp->fn_buf = MALLOC(FN_MIN); fnp->fn_buflast = &fnp->fn_buf[FN_MIN - 1]; *fnp->fn_buf = '\0'; fnp->fn_rptr = fnp->fn_buf; fnp->fn_wptr = fnp->fn_buf; } return (fnp); } /* * fn_dup -- duplicate a filename buffer */ struct fn * fn_dup(struct fn *fnp) { struct fn *ret = fn_new(fn_s(fnp)); ret->fn_n = fnp->fn_n; ret->fn_stbuf = fnp->fn_stbuf; return (ret); } /* * fn_dirname -- return the dirname part of a filename */ struct fn * fn_dirname(struct fn *fnp) { char *ptr = NULL; struct fn *ret; char *buf; buf = fn_s(fnp); if (buf != NULL) ptr = strrchr(buf, '/'); if (ptr == NULL || buf == NULL) return (fn_new(".")); else { *ptr = '\0'; ret = fn_new(buf); *ptr = '/'; return (ret); } } /* * fn_setn -- set the "n" value for a filename * * the "n" value is initially -1, and is used by logadm to store * the suffix for rotated log files. the function fn_list_popoldest() * looks at these "n" values when sorting filenames to determine which * old log file is the oldest and should be expired first. */ void fn_setn(struct fn *fnp, int n) { fnp->fn_n = n; } /* * fn_setstat -- store a struct stat with a filename * * the glob functions typically fill in these struct stats since they * have to stat while globbing anyway. just turned out to be a common * piece of information that was conveniently stored with the associated * filename. */ void fn_setstat(struct fn *fnp, struct stat *stp) { fnp->fn_stbuf = *stp; } /* * fn_getstat -- return a pointer to the stat info stored by fn_setstat() */ struct stat * fn_getstat(struct fn *fnp) { return (&fnp->fn_stbuf); } /* * fn_free -- free a filename buffer */ void fn_free(struct fn *fnp) { if (fnp) { if (fnp->fn_buf) FREE(fnp->fn_buf); FREE(fnp); } } /* * fn_renew -- reset a filename buffer * * calling fn_renew(fnp, s) is the same as calling: * fn_free(fnp); * fn_new(s); */ void fn_renew(struct fn *fnp, const char *s) { fnp->fn_rptr = fnp->fn_wptr = fnp->fn_buf; fn_puts(fnp, s); } /* * fn_putc -- append a character to a filename * * this is the function that handles growing the filename buffer * automatically and calling err() if it overflows. */ void fn_putc(struct fn *fnp, int c) { if (fnp->fn_wptr >= fnp->fn_buflast) { int buflen = fnp->fn_buflast + 1 - fnp->fn_buf; char *newbuf; char *src; char *dst; /* overflow, allocate more space or die if at FN_MAX */ if (buflen >= FN_MAX) err(0, "fn buffer overflow"); buflen += FN_INC; newbuf = MALLOC(buflen); /* copy string into new buffer */ src = fnp->fn_buf; dst = newbuf; /* just copy up to wptr, rest is history anyway */ while (src < fnp->fn_wptr) *dst++ = *src++; fnp->fn_rptr = &newbuf[fnp->fn_rptr - fnp->fn_buf]; FREE(fnp->fn_buf); fnp->fn_buf = newbuf; fnp->fn_buflast = &fnp->fn_buf[buflen - 1]; fnp->fn_wptr = dst; } *fnp->fn_wptr++ = c; *fnp->fn_wptr = '\0'; } /* * fn_puts -- append a string to a filename */ void fn_puts(struct fn *fnp, const char *s) { /* non-optimal, but simple! */ while (s != NULL && *s) fn_putc(fnp, *s++); } /* * fn_putfn -- append a filename buffer to a filename */ void fn_putfn(struct fn *fnp, struct fn *srcfnp) { int c; fn_rewind(srcfnp); while (c = fn_getc(srcfnp)) fn_putc(fnp, c); } /* * fn_rewind -- reset the "read pointer" to the beginning of a filename */ void fn_rewind(struct fn *fnp) { fnp->fn_rptr = fnp->fn_buf; } /* * fn_getc -- "read" the next character of a filename */ int fn_getc(struct fn *fnp) { if (fnp->fn_rptr > fnp->fn_buflast || *fnp->fn_rptr == '\0') return (0); return (*fnp->fn_rptr++); } /* * fn_peekc -- "peek" at the next character of a filename */ int fn_peekc(struct fn *fnp) { if (fnp->fn_rptr > fnp->fn_buflast || *fnp->fn_rptr == '\0') return (0); return (*fnp->fn_rptr); } /* * fn_s -- return a pointer to a null-terminated string containing the filename */ char * fn_s(struct fn *fnp) { return (fnp->fn_buf); } /* * fn_isgz -- return true if filename is *.gz */ boolean_t fn_isgz(struct fn *fnp) { size_t len; char *name; name = fnp->fn_buf; len = strlen(name); if (len > 3 && strcmp(name + len - 3, ".gz") == 0) return (B_TRUE); else return (B_FALSE); } /* * fn_list_new -- create a new list of filenames * * by convention, an empty list is represented by an allocated * struct fn_list which contains a NULL linked list, rather than * by a NULL fn_list pointer. in other words: * * struct fn_list *fnlp = some_func_returning_a_list(); * if (fn_list_empty(fnlp)) * ... * * is preferable to checking if the fnlp returned is NULL. */ struct fn_list * fn_list_new(const char * const *slist) { struct fn_list *fnlp = MALLOC(sizeof (struct fn_list)); fnlp->fnl_first = fnlp->fnl_last = fnlp->fnl_rptr = NULL; while (slist && *slist) fn_list_adds(fnlp, *slist++); return (fnlp); } /* * fn_list_dup -- duplicate a list of filenames */ struct fn_list * fn_list_dup(struct fn_list *fnlp) { struct fn_list *ret = fn_list_new(NULL); struct fn *fnp; fn_list_rewind(fnlp); while ((fnp = fn_list_next(fnlp)) != NULL) fn_list_addfn(ret, fn_dup(fnp)); return (ret); } /* * fn_list_free -- free a list of filenames */ void fn_list_free(struct fn_list *fnlp) { struct fn *fnp; fn_list_rewind(fnlp); while ((fnp = fn_list_next(fnlp)) != NULL) fn_free(fnp); FREE(fnlp); } /* * fn_list_adds -- add a string to a list of filenames */ void fn_list_adds(struct fn_list *fnlp, const char *s) { fn_list_addfn(fnlp, fn_new(s)); } /* * fn_list_addfn -- add a filename (i.e. struct fn *) to a list of filenames */ void fn_list_addfn(struct fn_list *fnlp, struct fn *fnp) { fnp->fn_next = NULL; if (fnlp->fnl_first == NULL) fnlp->fnl_first = fnlp->fnl_last = fnlp->fnl_rptr = fnp; else { fnlp->fnl_last->fn_next = fnp; fnlp->fnl_last = fnp; } } /* * fn_list_rewind -- reset the "read pointer" to the beginning of the list */ void fn_list_rewind(struct fn_list *fnlp) { fnlp->fnl_rptr = fnlp->fnl_first; } /* * fn_list_next -- return the filename at the read pointer and advance it */ struct fn * fn_list_next(struct fn_list *fnlp) { struct fn *ret = fnlp->fnl_rptr; if (fnlp->fnl_rptr == fnlp->fnl_last) fnlp->fnl_rptr = NULL; else if (fnlp->fnl_rptr != NULL) fnlp->fnl_rptr = fnlp->fnl_rptr->fn_next; return (ret); } /* * fn_list_addfn_list -- move filenames from fnlp2 to end of fnlp * * frees fnlp2 after moving all the filenames off of it. */ void fn_list_addfn_list(struct fn_list *fnlp, struct fn_list *fnlp2) { struct fn *fnp2 = fnlp2->fnl_first; struct fn *nextfnp2; /* for each fn in the second list... */ while (fnp2) { if (fnp2 == fnlp2->fnl_last) nextfnp2 = NULL; else nextfnp2 = fnp2->fn_next; /* append it to the first list */ fn_list_addfn(fnlp, fnp2); fnp2 = nextfnp2; } /* all the fn's were moved off the second list */ fnlp2->fnl_first = fnlp2->fnl_last = fnlp2->fnl_rptr = NULL; /* done with the second list */ fn_list_free(fnlp2); } /* * fn_list_appendrange -- append a range of characters to each filename in list * * range of characters appended is the character at *s up to but not including * the character at *limit. NULL termination is not required. */ void fn_list_appendrange(struct fn_list *fnlp, const char *s, const char *limit) { struct fn *fnp = fnlp->fnl_first; struct fn *nextfnp; const char *ptr; /* for each fn in the list... */ while (fnp != NULL) { if (fnp == fnlp->fnl_last) nextfnp = NULL; else nextfnp = fnp->fn_next; /* append the range */ for (ptr = s; ptr < limit; ptr++) fn_putc(fnp, *ptr); fnp = nextfnp; } } /* * fn_list_totalsize -- sum up all the st_size fields in the stat structs */ off_t fn_list_totalsize(struct fn_list *fnlp) { struct fn *fnp; off_t ret = 0; fn_list_rewind(fnlp); while ((fnp = fn_list_next(fnlp)) != NULL) ret += fnp->fn_stbuf.st_size; return (ret); } /* * fn_list_popoldest -- remove oldest file from list and return it * * this function uses the "n" values (set by fn_setn()) to determine * which file is oldest, or when there's a tie it turns to the modification * times in the stat structs, or when there's still a tie lexical sorting. */ struct fn * fn_list_popoldest(struct fn_list *fnlp) { struct fn *fnp; struct fn *ret = NULL; fn_list_rewind(fnlp); while ((fnp = fn_list_next(fnlp)) != NULL) if (ret == NULL) ret = fnp; else if (fnp->fn_n > ret->fn_n || (fnp->fn_n == ret->fn_n && (fnp->fn_stbuf.st_mtime < ret->fn_stbuf.st_mtime || ((fnp->fn_stbuf.st_mtime == ret->fn_stbuf.st_mtime && strcmp(fnp->fn_buf, ret->fn_buf) > 0))))) ret = fnp; if (ret == NULL) return (NULL); /* oldest file is ret, remove it from list */ if (fnlp->fnl_first == ret) { fnlp->fnl_first = ret->fn_next; } else { fn_list_rewind(fnlp); while ((fnp = fn_list_next(fnlp)) != NULL) { if (fnp->fn_next == ret) { fnp->fn_next = ret->fn_next; if (fnlp->fnl_last == ret) fnlp->fnl_last = fnp; break; } } } ret->fn_next = NULL; return (ret); } /* * fn_list_empty -- true if the list is empty */ boolean_t fn_list_empty(struct fn_list *fnlp) { return (fnlp->fnl_first == NULL); } /* * fn_list_count -- return number of filenames in list */ int fn_list_count(struct fn_list *fnlp) { int ret = 0; /* * if this operation were more common, we'd cache the count * in the struct fn_list, but it isn't very common so we just * count 'em up here */ fn_list_rewind(fnlp); while (fn_list_next(fnlp) != NULL) ret++; return (ret); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * logadm/fn.h -- public definitions for fn module */ #ifndef _LOGADM_FN_H #define _LOGADM_FN_H #ifdef __cplusplus extern "C" { #endif /* functions that deal with single strings */ struct fn *fn_new(const char *s); struct fn *fn_dup(struct fn *fnp); struct fn *fn_dirname(struct fn *fnp); void fn_setn(struct fn *fnp, int n); int fn_getn(struct fn *fnp); void fn_setstat(struct fn *fnp, struct stat *stp); struct stat *fn_getstat(struct fn *fnp); void fn_free(struct fn *fnp); void fn_renew(struct fn *fnp, const char *s); void fn_putc(struct fn *fnp, int c); void fn_puts(struct fn *fnp, const char *s); void fn_putfn(struct fn *fnp, struct fn *srcfnp); void fn_rewind(struct fn *fnp); int fn_getc(struct fn *fnp); int fn_peekc(struct fn *fnp); char *fn_s(struct fn *fnp); boolean_t fn_isgz(struct fn *fnp); /* functions that deal with lists of strings */ struct fn_list *fn_list_new(const char * const *slist); struct fn_list *fn_list_dup(struct fn_list *fnlp); void fn_list_free(struct fn_list *fnlp); void fn_list_adds(struct fn_list *fnlp, const char *s); void fn_list_addfn(struct fn_list *fnlp, struct fn *fnp); void fn_list_rewind(struct fn_list *fnlp); struct fn *fn_list_next(struct fn_list *fnlp); void fn_list_addfn_list(struct fn_list *fnlp, struct fn_list *fnlp2); void fn_list_appendrange(struct fn_list *fnlp, const char *s, const char *limit); void fn_list_print(struct fn_list *fnlp, FILE *stream); off_t fn_list_totalsize(struct fn_list *fnlp); struct fn *fn_list_popoldest(struct fn_list *fnlp); boolean_t fn_list_empty(struct fn_list *fnlp); int fn_list_count(struct fn_list *fnlp); #ifdef __cplusplus } #endif #endif /* _LOGADM_FN_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * logadm/glob.c -- globbing routines * * these routines support two kinds of globs. first, the * usual kind of filename globbing, like: * * *.c * /var/log/syslog.? * log[0-9]*file * /var/apache/logs/x*{access,error}_log * * this is basically the same syntax that csh supports for globs and * is provided by the routine glob_glob() which takes a filename and * returns a list of filenames that match the glob. * * the second type is something called a "reglob" which is a pathname * where the components are regular expressions as described in regex(3c). * some examples: * * .*\.c * /var/log/syslog\.. * log[0-9].*file * /var/log/syslog\.([0-9]+)$0 * * the last example uses the ()$n form to assign a numeric extension * on a filename to the "n" value kept by the fn routines with each * filename (see fn_setn() in fn.c). logadm uses this mechanism to * correctly sort lognames when templates containing $n are used. * * the routine glob_reglob() is used to expand reglobs. glob_glob() * is implemented by expanding the curly braces, converting the globs * to reglobs, and then passing the work to glob_reglob(). * * finally, since expanding globs and reglobs requires doing a stat(2) * on the files, we store the resulting stat information in the filename * struct (see fn_setstat() in fn.c). * * the glob(3c) routines are not used here since they don't support * braces, and don't support the more powerful reglobs required by logadm. */ #include #include #include #include #include #include #include #include #include #include "err.h" #include "fn.h" #include "glob.h" /* forward declarations for functions used internally by this module */ static struct fn_list *glob_debrace(struct fn *fnp); static struct fn_list *glob_reglob_list(struct fn_list *fnlp); static boolean_t glob_magic(struct fn *fnp); /* expand curly braces (like file{one,two,three}name) */ static struct fn_list * glob_debrace(struct fn *fnp) { struct fn_list *ret = fn_list_new(NULL); struct fn_list *newret; char *sp = fn_s(fnp); char *left; char *right; char *comma; /* start with an empty string in the list */ fn_list_adds(ret, ""); /* while braces remain... */ while (sp != NULL && (left = strchr(sp, '{')) != NULL) if ((right = strchr(left, '}')) == NULL) { err(EF_FILE|EF_JMP, "Missing }"); fn_list_free(ret); return (NULL); } else { /* stuff before "left" is finished */ fn_list_appendrange(ret, sp, left); /* stuff after "right" still need processing */ sp = right + 1; if (left + 1 == right) continue; /* just an empty {} */ /* stuff between "left" and "right" is comma-sep list */ left++; newret = fn_list_new(NULL); while ((comma = strchr(left, ',')) != NULL) { struct fn_list *dup = fn_list_dup(ret); /* stuff from left to comma is one variant */ fn_list_appendrange(dup, left, comma); fn_list_addfn_list(newret, dup); left = comma + 1; } /* what's left is the last item in the list */ fn_list_appendrange(ret, left, right); fn_list_addfn_list(newret, ret); ret = newret; } /* anything remaining in "s" is finished */ fn_list_appendrange(ret, sp, &sp[strlen(sp)]); return (ret); } /* return true if filename contains any "magic" characters (*,?,[) */ static boolean_t glob_magic(struct fn *fnp) { char *s = fn_s(fnp); for (; s != NULL && *s; s++) if (*s == '*' || *s == '?' || *s == '[') return (B_TRUE); return (B_FALSE); } /* * glob_glob -- given a filename glob, return the list of matching filenames * * fn_setn() and fn_setstat() are called to set the "n" and stat information * for the resulting filenames. */ struct fn_list * glob_glob(struct fn *fnp) { struct fn_list *tmplist = glob_debrace(fnp); struct fn_list *ret; struct fn *nextfnp; struct fn *newfnp; int magic = 0; /* debracing produced NULL list? */ if (tmplist == NULL) return (NULL); /* see if anything in list contains magic characters */ fn_list_rewind(tmplist); while ((nextfnp = fn_list_next(tmplist)) != NULL) if (glob_magic(nextfnp)) { magic = 1; break; } if (!magic) return (tmplist); /* no globs to expand */ /* foreach name in the list, call glob_glob() to expand it */ fn_list_rewind(tmplist); ret = fn_list_new(NULL); while ((nextfnp = fn_list_next(tmplist)) != NULL) { newfnp = glob_to_reglob(nextfnp); fn_list_addfn(ret, newfnp); } fn_list_free(tmplist); tmplist = ret; ret = glob_reglob_list(tmplist); fn_list_free(tmplist); return (ret); } /* * glob_glob_list -- given a list of filename globs, return all matches */ struct fn_list * glob_glob_list(struct fn_list *fnlp) { struct fn_list *ret = fn_list_new(NULL); struct fn *fnp; fn_list_rewind(fnlp); while ((fnp = fn_list_next(fnlp)) != NULL) fn_list_addfn_list(ret, glob_glob(fnp)); return (ret); } /* * glob_reglob -- given a filename reglob, return a list of matching filenames * * this routine does all the hard work in this module. */ struct fn_list * glob_reglob(struct fn *fnp) { struct fn_list *ret = fn_list_new(NULL); struct fn_list *newret; struct fn *nextfnp; char *mys = STRDUP(fn_s(fnp)); char *sp = mys; char *slash; int skipdotfiles; char *re; char ret0[MAXPATHLEN]; /* start with the initial directory in the list */ if (*sp == '/') { fn_list_adds(ret, "/"); while (*sp == '/') sp++; } else fn_list_adds(ret, "./"); /* while components remain... */ do { if ((slash = strchr(sp, '/')) != NULL) { *slash++ = '\0'; /* skip superfluous slashes */ while (*slash == '/') slash++; } /* dot files are skipped unless a dot was specifically given */ if (sp[0] == '\\' && sp[1] == '.') skipdotfiles = 0; else skipdotfiles = 1; /* compile the regex */ if ((re = regcmp("^", sp, "$", (char *)0)) == NULL) err(EF_FILE|EF_JMP, "regcmp failed on <%s>", sp); /* apply regex to every filename we've matched so far */ newret = fn_list_new(NULL); fn_list_rewind(ret); while ((nextfnp = fn_list_next(ret)) != NULL) { DIR *dirp; struct dirent *dp; /* go through directory looking for matches */ if ((dirp = opendir(fn_s(nextfnp))) == NULL) continue; while ((dp = readdir(dirp)) != NULL) { if (skipdotfiles && dp->d_name[0] == '.') continue; *ret0 = '\0'; if (regex(re, dp->d_name, ret0)) { struct fn *matchfnp = fn_dup(nextfnp); struct stat stbuf; int n; fn_puts(matchfnp, dp->d_name); if (stat(fn_s(matchfnp), &stbuf) < 0) { fn_free(matchfnp); continue; } /* skip non-dirs if more components */ if (slash && (stbuf.st_mode & S_IFMT) != S_IFDIR) { fn_free(matchfnp); continue; } /* * component matched, fill in "n" * value, stat information, and * append component to directory * name just searched. */ if (*ret0) n = atoi(ret0); else n = -1; fn_setn(matchfnp, n); fn_setstat(matchfnp, &stbuf); if (slash) fn_putc(matchfnp, '/'); fn_list_addfn(newret, matchfnp); } } (void) closedir(dirp); } fn_list_free(ret); ret = newret; sp = slash; } while (slash); FREE(mys); return (ret); } /* reglob a list of filenames */ static struct fn_list * glob_reglob_list(struct fn_list *fnlp) { struct fn_list *ret = fn_list_new(NULL); struct fn *fnp; fn_list_rewind(fnlp); while ((fnp = fn_list_next(fnlp)) != NULL) fn_list_addfn_list(ret, glob_reglob(fnp)); return (ret); } /* * glob_to_reglob -- convert a glob (*, ?, etc) to a reglob (.*, ., etc.) */ struct fn * glob_to_reglob(struct fn *fnp) { int c; struct fn *ret = fn_new(NULL); fn_rewind(fnp); while ((c = fn_getc(fnp)) != '\0') switch (c) { case '.': case '(': case ')': case '^': case '+': case '{': case '}': case '$': /* magic characters need backslash */ fn_putc(ret, '\\'); fn_putc(ret, c); break; case '?': /* change '?' to a single dot */ fn_putc(ret, '.'); break; case '*': /* change '*' to ".*" */ fn_putc(ret, '.'); fn_putc(ret, '*'); break; default: fn_putc(ret, c); } return (ret); } #ifdef TESTMODULE /* * test main for glob module, usage: a.out [-r] [pattern...] * -r means the patterns are reglobs instead of globs */ int main(int argc, char *argv[]) { int i; int reglobs = 0; struct fn *argfnp = fn_new(NULL); struct fn *fnp; struct fn_list *fnlp; err_init(argv[0]); setbuf(stdout, NULL); for (i = 1; i < argc; i++) { if (strcmp(argv[i], "-r") == 0) { reglobs = 1; continue; } if (SETJMP) { printf(" skipped due to errors\n"); continue; } else { printf("<%s>:\n", argv[i]); fn_renew(argfnp, argv[i]); if (reglobs) fnlp = glob_reglob(argfnp); else fnlp = glob_glob(argfnp); } fn_list_rewind(fnlp); while ((fnp = fn_list_next(fnlp)) != NULL) printf(" <%s>\n", fn_s(fnp)); printf("total size: %lld\n", fn_list_totalsize(fnlp)); while ((fnp = fn_list_popoldest(fnlp)) != NULL) { printf(" oldest <%s>\n", fn_s(fnp)); fn_free(fnp); } fn_list_free(fnlp); } fn_free(argfnp); err_done(0); /* NOTREACHED */ return (0); } #endif /* TESTMODULE */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2001 by Sun Microsystems, Inc. * All rights reserved. * * logadm/glob.h -- public definitions for glob module */ #ifndef _LOGADM_GLOB_H #define _LOGADM_GLOB_H #ifdef __cplusplus extern "C" { #endif struct fn_list *glob_glob(struct fn *fnp); struct fn_list *glob_glob_list(struct fn_list *fnlp); struct fn_list *glob_reglob(struct fn *fnp); struct fn *glob_to_reglob(struct fn *fnp); #ifdef __cplusplus } #endif #endif /* _LOGADM_GLOB_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * logadm/kw.c -- manage keywords table * * this module expands things like $file.$n in "templates". * calling kw_init() sets the current "filename" used for * $file, $dirname, and $basename. * * any time-based expansions, like $secs, or all the strftime() * percent sequences, are based on the exact same point in time. * so calling kw_expand() on something like "file-%T" will return * the same thing when called multiple times during the same logadm run. */ #include #include #include #include #include #include #include #include #include #include #include #include "err.h" #include "lut.h" #include "fn.h" #include "kw.h" /* forward declarations for functions used internally by this module */ static void kw_printer(const char *lhs, void *rhs, void *arg); /* * absurdly long length to hold sprintf of a %d, * or strftime() expansion of a *single* percent sequence */ #define MAXDIGITS 100 static struct lut *Keywords; /* lookup table for keywords */ extern time_t Now; /* time used for keyword expansions */ /* * kw_init -- initialize keywords based on given filename */ void kw_init(struct fn *fnp, struct fn *nfnp) { static char *fullpath; static char *nfullpath; static char *splitpath; static char secs[MAXDIGITS]; static struct utsname un; static char platform[SYS_NMLN]; static char isa[SYS_NMLN]; static char domain[256]; static char *home; static char *user; static char *logname; static char zonename[ZONENAME_MAX]; static zoneid_t zoneid; static int initialized; char *ptr; /* make a copy of the string for $file */ if (fullpath) FREE(fullpath); fullpath = STRDUP(fn_s(fnp)); Keywords = lut_add(Keywords, "file", fullpath); /* make a copy of the string for $nfile */ if (nfullpath) FREE(nfullpath); if (nfnp == NULL) { nfullpath = NULL; Keywords = lut_add(Keywords, "nfile", ""); } else { nfullpath = STRDUP(fn_s(nfnp)); Keywords = lut_add(Keywords, "nfile", nfullpath); } /* make a copy of the string for $dirname/$basename */ if (splitpath) FREE(splitpath); splitpath = STRDUP(fn_s(fnp)); if ((ptr = strrchr(splitpath, '/')) == NULL) { Keywords = lut_add(Keywords, "basename", splitpath); Keywords = lut_add(Keywords, "dirname", "."); } else { *ptr++ = '\0'; Keywords = lut_add(Keywords, "basename", ptr); Keywords = lut_add(Keywords, "dirname", splitpath); } if (initialized) return; /* rest of the keywords don't change */ (void) snprintf(secs, MAXDIGITS, "%d", (int)Now); Keywords = lut_add(Keywords, "secs", secs); if (uname(&un) < 0) err(EF_SYS, "uname"); Keywords = lut_add(Keywords, "nodename", un.nodename); Keywords = lut_add(Keywords, "release", un.release); Keywords = lut_add(Keywords, "machine", un.machine); if (sysinfo(SI_ARCHITECTURE, isa, sizeof (isa)) == -1) err(EF_WARN|EF_SYS, "sysinfo(SI_ARCHITECTURE) failed."); else Keywords = lut_add(Keywords, "isa", isa); if (sysinfo(SI_PLATFORM, platform, sizeof (platform)) == -1) err(EF_WARN|EF_SYS, "sysinfo(SI_PLATFORM) failed."); else Keywords = lut_add(Keywords, "platform", platform); if (sysinfo(SI_SRPC_DOMAIN, domain, sizeof (domain)) == -1) err(EF_WARN|EF_SYS, "sysinfo(SI_SRPC_DOMAIN) failed."); else Keywords = lut_add(Keywords, "domain", domain); if ((home = getenv("HOME")) != NULL) Keywords = lut_add(Keywords, "home", STRDUP(home)); if ((user = getenv("USER")) != NULL) Keywords = lut_add(Keywords, "user", STRDUP(user)); if ((logname = getenv("LOGNAME")) != NULL) Keywords = lut_add(Keywords, "logname", STRDUP(logname)); zoneid = getzoneid(); if ((getzonenamebyid(zoneid, zonename, sizeof (zonename))) == -1) err(EF_WARN|EF_SYS, "getzonenamebyid() failed."); else Keywords = lut_add(Keywords, "zonename", STRDUP(zonename)); initialized = 1; } /* helper function for kw_print() */ static void kw_printer(const char *lhs, void *rhs, void *arg) { FILE *stream = (FILE *)arg; (void) fprintf(stream, "%20.20s %s\n", lhs, (char *)rhs); } /* * kw_print -- spew the entire keywords table to stream * * this routine is used to dump the keywords table for debugging. */ void kw_print(FILE *stream) { lut_walk(Keywords, kw_printer, stream); } /* * kw_expand -- expand src into dst with given n value for $n (or $N) * * n == -1 means expand src into a reglob * if gz is true, include ".gz" extension * * returns true if template contains $n or $N (implying rotation of files) */ boolean_t kw_expand(struct fn *src, struct fn *dst, int n, boolean_t gz) { int c; char buf[MAXDIGITS]; boolean_t hasn = B_FALSE; struct fn *kw = fn_new(NULL); char *ptr; struct tm *gmt_tm = localtime(&Now); while ((c = fn_getc(src)) != '\0') switch (c) { case '.': case '(': case ')': case '^': case '+': case '{': case '}': /* when building an re, escape with a backslash */ if (n < 0) fn_putc(dst, '\\'); fn_putc(dst, c); break; case '?': /* when building an re, change '?' to a single dot */ if (n < 0) fn_putc(dst, '.'); break; case '*': /* when building an re, change '*' to ".*" */ if (n < 0) fn_putc(dst, '.'); fn_putc(dst, '*'); break; case '$': /* '$' marks the start of a keyword */ switch (c = fn_getc(src)) { case '$': /* double '$' stands for a single '$' */ if (n < 0) fn_putc(dst, '\\'); fn_putc(dst, '$'); break; case '#': /* * $# expands to nothing, but forces an end * of keyword, allow juxtaposition of a * keyword with lower-case characters */ break; case 'n': case 'N': if (c == 'N' || !islower(fn_peekc(src))) { /* * we've found $n or $N, if we're * building an re, build one that * matches a number, otherwise * expand the keyword to the n * passed in to this function */ hasn = B_TRUE; if (n < 0) fn_puts(dst, "([0-9]+)$0"); else { (void) snprintf(buf, MAXDIGITS, "%d", (c == 'n') ? n : n + 1); fn_puts(dst, buf); } break; } /*FALLTHROUGH*/ default: /* gather up the keyword name */ fn_renew(kw, NULL); fn_putc(kw, c); while (islower(fn_peekc(src))) fn_putc(kw, fn_getc(src)); /* lookup keyword */ if ((ptr = (char *)lut_lookup(Keywords, fn_s(kw))) == NULL) { /* nope, copy it unexpanded */ if (n < 0) fn_putc(dst, '\\'); fn_putc(dst, '$'); fn_putfn(dst, kw); } else fn_puts(dst, ptr); } break; case '%': /* * % sequence for strftime(), if we're building * an re, we take our best guess at the re for * this sequence, otherwise we pass it to strftime() */ if (n < 0) { /* * the regex for a percent sequence is * usually just ".*" unless it is one * of the common cases we know about * that are numeric. in those cases, we * tighten up the regex to just match digits. * * while it is gross that we embed knowledge * of strftime() sequences here, they are * specified in a standard so aren't * expected to change often, and it *really* * cuts down on the possibility that we'll * expire a file that isn't an old log file. */ if ((c = fn_getc(src)) == 'E' || c == 'O') { c = fn_getc(src); fn_puts(dst, ".*"); } else switch (c) { case 'd': case 'g': case 'G': case 'H': case 'I': case 'j': case 'm': case 'M': case 'S': case 'u': case 'U': case 'V': case 'w': case 'W': case 'y': case 'Y': /* pure numeric cases */ fn_puts(dst, "[0-9]+"); break; case 'e': case 'k': case 'l': /* possible space then num */ fn_puts(dst, " *[0-9]+"); break; case 'D': /* %m/%d/%y */ /* adds slashes! */ fn_puts(dst, "[0-9]+/[0-9]+/[0-9]+"); break; case 'R': /* %H:%M */ fn_puts(dst, "[0-9]+:[0-9]+"); break; case 'T': /* %H:%M:%S */ fn_puts(dst, "[0-9]+:[0-9]+:[0-9]+"); break; default: fn_puts(dst, ".*"); } } else { char tbuf[4]; /* copy % sequence to tbuf */ tbuf[0] = '%'; tbuf[1] = fn_getc(src); if (tbuf[1] == 'E' || tbuf[1] == 'O') { /* "extended" sequence */ tbuf[2] = fn_getc(src); tbuf[3] = '\0'; } else tbuf[2] = '\0'; if (strftime(buf, MAXDIGITS, tbuf, gmt_tm) == 0) /* just copy %x */ fn_puts(dst, tbuf); else fn_puts(dst, buf); } break; default: /* nothing special, just copy it */ fn_putc(dst, c); } if (gz) { if (n < 0) fn_puts(dst, "(\\.gz){0,1}"); else fn_puts(dst, ".gz"); } fn_free(kw); return (hasn); } #ifdef TESTMODULE time_t Now; /* * test main for kw module, usage: a.out fname [template...] */ int main(int argc, char *argv[]) { int i; struct fn *src = fn_new(NULL); struct fn *dst = fn_new(NULL); err_init(argv[0]); setbuf(stdout, NULL); Now = time(0); if (argc < 2) err(0, "first arg must be fname"); kw_init(fn_new(argv[1]), NULL); kw_print(stdout); for (i = 2; i < argc; i++) { int n; for (n = -1; n < 2; n++) { fn_renew(src, argv[i]); fn_renew(dst, NULL); printf("expand<%s> n %d hasn %d ", argv[i], n, kw_expand(src, dst, n, B_FALSE)); printf("result <%s>\n", fn_s(dst)); } } err_done(0); /* NOTREACHED */ return (0); } #endif /* TESTMODULE */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * logadm/kw.h -- public definitions for kw module */ #ifndef _LOGADM_KW_H #define _LOGADM_KW_H #ifdef __cplusplus extern "C" { #endif void kw_init(struct fn *fnp, struct fn *nfnp); boolean_t kw_expand(struct fn *src, struct fn *dst, int n, boolean_t gz); void kw_print(FILE *stream); #ifdef __cplusplus } #endif #endif /* _LOGADM_KW_H */ #!/sbin/sh # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. # . /lib/svc/share/smf_include.sh LOGADM=/etc/logadm.conf LOGADM_D=${LOGADM%conf}d LS=/bin/ls AWK=/bin/awk GREP=/bin/grep # # This is a temporary service to allow addition (only) to /etc/logadm.conf # It is temporary in the sense that logadm(8) should have its configuration # migrated to SMF in the future. # # # Display error message and exits with error code # msg_exit() { exit_code=$1 msg=$2 echo "${msg}" exit ${exit_code} } # # If there is no /etc/logadm.d we can bail # if [ ! -d ${LOGADM_D} ]; then exit ${SMF_EXIT_OK} fi # # Cache files # files=$(${LS} -t ${LOGADM} ${LOGADM_D}/*) # # If there is no /etc/logadm.conf create it and make sure it has the # right ownership and permissions. # Make sure this is done AFTER $files is set. Otherwise /etc/logadm.conf will be # newer than all files is /etc/logadm.d and they will be skipped. # if [ ! -f ${LOGADM} ]; then touch ${LOGADM} chmod 644 ${LOGADM} chown root:sys ${LOGADM} fi for f in ${files} do # # If it is not a file, we skip it. # if [ ! -f ${f} ]; then continue fi # # We stop when files at /etc/logadm.d are older than /etc/logadm.conf # if [ ${f} = ${LOGADM} ]; then break fi # # We ignore files that are not owned by root, group sys # and have permissions different than 444 # perm=$(${LS} -l ${f} | ${AWK} '{printf("%s %s:%s", $1, $3, $4)}') if [ $? != 0 ]; then msg_exit ${SMF_EXIT_ERR_FATAL} "${perm}" fi if [ "${perm}" != "-r--r--r-- root:sys" ]; then echo "Unexpected permission/ownership for ${f}" echo " expected -r--r--r-- root:sys but got ${perm}" echo " skipping ${f}" continue fi # # Discard comments (lines starting with #) # ${GREP} -v '^#' ${f} | while read entry do sig=$(echo ${entry} | ${AWK} '{printf("%s\>", $1);}' 2>&1) if [ $? != 0 ]; then # only happens if awk(1) fails msg_exit ${SMF_EXIT_ERR_FATAL} "${sig}" fi # # if ${sig} is null but the previous command succeeded, we skip # if [ ! ${sig} ]; then continue; fi err_msg=$(${GREP} ^${sig} ${LOGADM} 2>&1) case $? in '1') echo "${entry}" >> ${LOGADM} ;; '0') ;; *) msg_exit ${SMF_EXIT_ERR_FATAL} "${err_msg}" esac done done exit ${SMF_EXIT_OK} # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # Copyright (c) 2001, 2010, Oracle and/or its affiliates. All rights reserved. # # logadm.conf # # Default settings for system log file management. # The -w option to logadm(8) is the preferred way to write to this file, # but if you do edit it by hand, use "logadm -V" to check it for errors. # # The format of lines in this file is: # # For each logname listed here, the default options to logadm # are given. Options given on the logadm command line override # the defaults contained in this file. # # logadm typically runs early every morning via an entry in # root's crontab (see crontab(1)). # /var/log/syslog -C 8 -a 'kill -HUP `cat /var/run/syslog.pid`' /var/adm/messages -C 4 -a 'kill -HUP `cat /var/run/syslog.pid`' /var/cron/log -c -s 512k -t /var/cron/olog /var/lp/logs/lpsched -C 2 -N -t '$file.$N' /var/fm/fmd/errlog -N -s 2m -M '/usr/sbin/fmadm -q rotate errlog && mv /var/fm/fmd/errlog.0- $nfile' /var/fm/fmd/fltlog -N -A 6m -s 10m -M '/usr/sbin/fmadm -q rotate fltlog && mv /var/fm/fmd/fltlog.0- $nfile' smf_logs /var/svc/log/*.log -C 8 -s 1m -c # # The entry below is used by turnacct(8) # /var/adm/pacct -C 0 -N -a '/usr/lib/acct/accton pacct' -g adm -m 664 -o adm -p never # # The entry below manages the Dynamic Resource Pools daemon (poold(8)) logfile. # /var/log/pool/poold -N -s 512k -a 'pkill -HUP poold; true' # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright 2006 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # #ident "%Z%%M% %I% %E% SMI" # msgid " <%s>\n" msgid " " msgid " # %s" msgid " %s" msgid " %s%s" msgid " *[0-9]+" msgid "" msgid "#%s" msgid "$" msgid "$file.$n" msgid "%03lo" msgid "%20.20s %s\n" msgid "%a %b %e %T %Y" msgid "%a %b %e %T %Z %Y" msgid "%c" msgid "%d" msgid "%ld" msgid "%s %s" msgid "%s" msgid "%s%s%s" msgid "%s: " msgid "'" msgid "([0-9]+)$0" msgid "(\\.gz){0,1}" msgid "-" msgid "-c" msgid "." msgid ".*" msgid "./" msgid ".gz" msgid "/" msgid "/bin/chmod" msgid "/bin/chown" msgid "/bin/cp" msgid "/bin/gzip" msgid "/bin/mv" msgid "/bin/rm" msgid "/bin/sh" msgid "/bin/touch" msgid "/dev/null" msgid "/etc/logadm.conf" msgid "1" msgid "10" msgid "1b" msgid "1w" msgid ": %s" msgid ":" msgid "A" msgid "ACS" msgid "AS" msgid "C" msgid "E" msgid "HOME" msgid "LOGNAME" msgid "N" msgid "P" msgid "PATH=/bin" msgid "R" msgid "S" msgid "T" msgid "TZ=UTC" msgid "USER" msgid "V" msgid "Vn" msgid "[0-9]+" msgid "[0-9]+/[0-9]+/[0-9]+" msgid "[0-9]+:[0-9]+" msgid "[0-9]+:[0-9]+:[0-9]+" msgid "\"" msgid "\n" msgid "^" msgid "_LOGADM_CHMOD" msgid "_LOGADM_CHOWN" msgid "_LOGADM_CP" msgid "_LOGADM_DEBUG" msgid "_LOGADM_DEFAULT_CONFFILE" msgid "_LOGADM_GZIP" msgid "_LOGADM_MV" msgid "_LOGADM_RM" msgid "_LOGADM_SH" msgid "_LOGADM_TOUCH" msgid "a" msgid "b" msgid "basename" msgid "c" msgid "conf.c" msgid "dirname" msgid "domain" msgid "e" msgid "eNrwpPsabcgmoRtzACEST" msgid "err.c" msgid "f" msgid "fhnrvVw" msgid "file" msgid "fn.c" msgid "fork" msgid "g" msgid "glob.c" msgid "h" msgid "home" msgid "isa" msgid "kw.c" msgid "logadm-version" msgid "logname" msgid "lut.c" msgid "m" msgid "machine" msgid "main.c" msgid "n" msgid "never" msgid "nodename" msgid "now" msgid "o" msgid "opts.c" msgid "p" msgid "pipe" msgid "platform" msgid "putenv PATH" msgid "putenv TZ" msgid "r" msgid "release" msgid "rwV" msgid "s" msgid "secs" msgid "sp" msgid "t" msgid "tmpfile" msgid "uname" msgid "user" msgid "v" msgid "vn" msgid "w" msgid "waitpid" msgid "z" msgid "zonename" /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * logadm/lut.c -- simple lookup table module * * this file contains a very simple lookup table (lut) implementation. * the tables only support insert & lookup, no delete, and can * only be walked in lexical order. if the key already exists * for something being inserted, the previous entry is simply * replaced. the left-hand-side (lhs), which is the key, is * copied into malloc'd memory. the right-hand-side (rhs), which * is the datum, is not copied (in fact, the lut routines don't * know the size or type of the datum, just the void * pointer to it). * * yea, this module could be much fancier and do much more, but * the idea was to keep it really simple and just provide what * was needed by logadm for options processing. */ #include #include #include "err.h" #include "lut.h" /* forward declarations of functions private to this module */ static void dooper(const char *lhs, void *rhs, void *arg); /* info created by lut_add() and lut_dup(), private to this module */ struct lut { struct lut *lut_left; struct lut *lut_right; char *lut_lhs; /* search key */ void *lut_rhs; /* the datum */ }; /* * lut_add -- add an entry to the table * * use it like this: * struct lut *root = NULL; * root = lut_add(root, "key", value); * * the key string gets strdup'd by lut_add(), but the memory holding * the *value should not be freed until the lut is freed by lut_free(). */ struct lut * lut_add(struct lut *root, const char *lhs, void *rhs) { int diff = 0; if (root == NULL) { /* not in tree, create new node */ root = MALLOC(sizeof (*root)); root->lut_lhs = STRDUP(lhs); root->lut_rhs = rhs; root->lut_left = root->lut_right = NULL; } else if (lhs != NULL && (diff = strcmp(root->lut_lhs, lhs)) == 0) { /* already in tree, replace node */ root->lut_rhs = rhs; } else if (diff > 0) root->lut_left = lut_add(root->lut_left, lhs, rhs); else root->lut_right = lut_add(root->lut_right, lhs, rhs); return (root); } /* helper function for lut_dup() */ static void dooper(const char *lhs, void *rhs, void *arg) { struct lut **rootp = (struct lut **)arg; *rootp = lut_add(*rootp, lhs, rhs); } /* * lut_dup -- duplicate a lookup table * * caller is responsible for keeping track of how many tables are keeping * pointers to the void * datum values. */ struct lut * lut_dup(struct lut *root) { struct lut *ret = NULL; lut_walk(root, dooper, &ret); return (ret); } /* * lut_lookup -- find an entry */ void * lut_lookup(struct lut *root, const char *lhs) { int diff; if (root == NULL || lhs == NULL) return (NULL); else if ((diff = strcmp(root->lut_lhs, lhs)) == 0) return (root->lut_rhs); else if (diff > 0) return (lut_lookup(root->lut_left, lhs)); else return (lut_lookup(root->lut_right, lhs)); } /* * lut_walk -- walk the table in lexical order */ void lut_walk(struct lut *root, void (*callback)(const char *lhs, void *rhs, void *arg), void *arg) { if (root) { lut_walk(root->lut_left, callback, arg); (*callback)(root->lut_lhs, root->lut_rhs, arg); lut_walk(root->lut_right, callback, arg); } } /* * lut_free -- free a lut * * if callback is provided, it is called for each value in the table. * it the values are things that the caller malloc'd, then you can do: * lut_free(root, free); */ void lut_free(struct lut *root, void (*callback)(void *rhs)) { if (root != NULL) { lut_free(root->lut_left, callback); lut_free(root->lut_right, callback); FREE(root->lut_lhs); if (callback) (*callback)(root->lut_rhs); FREE(root); } } #ifdef TESTMODULE void printer(const char *lhs, void *rhs, void *arg) { printf("<%s> <%s> (<%s>)\n", lhs, (char *)rhs, (char *)lut_lookup(arg, lhs)); } /* * test main for lut module, usage: a.out [lhs[=rhs]...] */ int main(int argc, char *argv[]) { struct lut *r = NULL; struct lut *dupr = NULL; char *equals; err_init(argv[0]); setbuf(stdout, NULL); for (argv++; *argv; argv++) if ((equals = strchr(*argv, '=')) != NULL) { *equals++ = '\0'; r = lut_add(r, *argv, equals); } else r = lut_add(r, *argv, "NULL"); printf("lut contains:\n"); lut_walk(r, printer, r); dupr = lut_dup(r); lut_free(r, NULL); printf("dup lut contains:\n"); lut_walk(dupr, printer, dupr); lut_free(dupr, NULL); err_done(0); /* NOTREACHED */ return (0); } #endif /* TESTMODULE */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2001 by Sun Microsystems, Inc. * All rights reserved. * * logadm/lut.h -- public definitions for lookup table module */ #ifndef _LOGADM_LUT_H #define _LOGADM_LUT_H #ifdef __cplusplus extern "C" { #endif struct lut *lut_add(struct lut *root, const char *lhs, void *rhs); struct lut *lut_dup(struct lut *root); void *lut_lookup(struct lut *root, const char *lhs); void lut_walk(struct lut *root, void (*callback)(const char *lhs, void *rhs, void *arg), void *arg); void lut_free(struct lut *root, void (*callback)(void *rhs)); #ifdef __cplusplus } #endif #endif /* _LOGADM_LUT_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2001, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2019 Joyent, Inc. * * logadm/main.c -- main routines for logadm * * this program is 90% argument processing, 10% actions... */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "err.h" #include "lut.h" #include "fn.h" #include "opts.h" #include "conf.h" #include "glob.h" #include "kw.h" /* forward declarations for functions in this file */ 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); /* our configuration file, unless otherwise specified by -f */ static char *Default_conffile = "/etc/logadm.conf"; /* our timestamps file, unless otherwise specified by -F */ static char *Default_timestamps = "/var/logadm/timestamps"; /* default pathnames to the commands we invoke */ 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; /* return from time(0), gathered early on to avoid slewed timestamps */ time_t Now; /* list of before commands that have been executed */ static struct lut *Beforecmds; /* list of after commands to execute before exiting */ static struct lut *Aftercmds; /* list of conffile entry names that are considered "done" */ static struct lut *Donenames; /* A list of names of files to be gzipped */ static struct lut *Gzipnames = NULL; /* * only the "FfhnVv" options are allowed in the first form of this command, * so this defines the list of options that are an error in they appear * in the first form. In other words, it is not allowed to run logadm * with any of these options unless at least one logname is also provided. */ #define OPTIONS_NOT_FIRST_FORM "eNrwpPsabcglmoRtzACEST" /* text that we spew with the -h flag */ #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" /* * main -- where it all begins */ /*ARGSUSED*/ int main(int argc, char *argv[]) { struct opts *clopts = NULL; /* from parsing command line */ const char *conffile; /* our configuration file */ const char *timestamps; /* our timestamps file */ struct fn_list *lognames; /* list of lognames we're processing */ struct fn *fnp; char *val; char *buf; int status; (void) setlocale(LC_ALL, ""); #if !defined(TEXT_DOMAIN) #define TEXT_DOMAIN "SYS_TEST" /* only used if Makefiles don't define it */ #endif (void) textdomain(TEXT_DOMAIN); /* we only print times into the timestamps file, so make them uniform */ (void) setlocale(LC_TIME, "C"); /* give our name to error routines & skip it for arg parsing */ 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); /* check for (undocumented) debugging environment variables */ 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); /* parse command line arguments */ 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"); } /* * There are many moods of logadm: * * 1. "-h" for help was given. We spew a canned help * message and exit, regardless of any other options given. * * 2. "-r" or "-w" asking us to write to the conffile. Lots * of argument checking, then we make the change to conffile * and exit. (-r processing actually happens in dologname().) * * 3. "-V" to search/verify the conffile was given. We do * the appropriate run through the conffile and exit. * (-V processing actually happens in dologname().) * * 4. No lognames were given, so we're being asked to go through * every entry in conffile. We verify that only the options * that make sense for this form of the command are present * and fall into the main processing loop below. * * 5. lognames were given, so we fall into the main processing * loop below to work our way through them. * * The last two cases are where the option processing gets more * complex. Each time around the main processing loop, we're * in one of these cases: * * A. No cmdargs were found (we're in case 4), the entry * in conffile supplies no log file names, so the entry * name itself is the logfile name (or names, if it globs * to multiple file names). * * B. No cmdargs were found (we're in case 4), the entry * in conffile gives log file names that we then loop * through and rotate/expire. In this case, the entry * name is specifically NOT one of the log file names. * * C. We're going through the cmdargs (we're in case 5), * the entry in conffile either doesn't exist or it exists * but supplies no log file names, so the cmdarg itself * is the log file name. * * D. We're going through the cmdargs (we're in case 5), * a matching entry in conffile supplies log file names * that we then loop through and rotate/expire. In this * case the entry name is specifically NOT one of the log * file names. * * As we're doing all this, any options given on the command line * override any found in the conffile, and we apply the defaults * for rotation conditions and expiration conditions, etc. at the * last opportunity, when we're sure they haven't been overridden * by an option somewhere along the way. * */ /* help option overrides anything else */ if (opts_count(clopts, "h")) { (void) fputs(HELP1, stderr); (void) fputs(HELP2, stderr); err_done(0); /*NOTREACHED*/ } /* detect illegal option combinations */ 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."); /* arrange for error output to be mailed if clopts includes -e */ if (opts_count(clopts, "e")) err_mailto(opts_optarg(clopts, "e")); /* this implements the default conffile and timestamps */ 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); /* handle conffile write option */ 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); /*NOTREACHED*/ } /* * lognames is either a list supplied on the command line, * or every entry in the conffile if none were supplied. */ lognames = opts_cmdargs(clopts); if (fn_list_empty(lognames)) { /* * being asked to do all entries in conffile * * check to see if any options were given that only * make sense when lognames are given specifically * on the command line. */ 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(); } /* foreach logname... */ 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); } /* execute any after commands */ lut_walk(Aftercmds, doaftercmd, clopts); /* execute any gzip commands */ lut_walk(Gzipnames, do_delayed_gzip, clopts); /* write out any conffile changes */ conf_close(clopts); err_done(0); /*NOTREACHED*/ return (0); /* for lint's little mind */ } /* spew a message, then a usage message, then exit */ 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"); } /* helper function used by doaftercmd() to join mail addrs with commas */ /*ARGSUSED1*/ 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); } /* helper function used by main() to run "after" commands */ 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); /* * addrs contains list of email addrs that should get * the error output when this after command is executed. */ lut_walk(addrs, commajoin, fnp); err_mailto(fn_s(fnp)); } docmd(opts, "-a cmd", Sh, "-c", lhs, NULL); } /* perform delayed gzip */ 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); } /* main logname processing */ 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; /* look up options set by config file */ 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"); } /* handle conffile lookup option */ if (opts_count(clopts, "V")) { /* lookup an entry in conffile */ 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; } /* handle conffile removal option */ 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; } /* generate combined options */ allopts = opts_merge(cfopts, clopts); /* arrange for error output to be mailed if allopts includes -e */ if (opts_count(allopts, "e")) err_mailto(opts_optarg(allopts, "e")); else err_mailto(NULL); /* this implements the default rotation rules */ 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"); } /* this implements the default expiration rules */ 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"); } /* this implements the default template */ 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"); } /* * if the conffile entry supplied log file names, then * logname is NOT one of the log file names (it was just * the entry name in conffile). */ 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); /* go through the list produced by glob expansion */ 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); } /* absurdly long buffer lengths for holding user/group/mode strings */ #define TIMESTRMAX 100 #define MAXATTR 100 /* rotate a log file if necessary, returns true if ok to go on to expire step */ 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); /* for -R cmd */ 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); /* "-p never" forced no rotate */ /* prepare the keywords */ 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); } /* even if size condition is not met, this entry is "done" */ if (opts_count(opts, "s") && stbuf.st_size < opts_optarg_int(opts, "s")) { Donenames = lut_add(Donenames, fname, "1"); return (B_TRUE); } /* see if age condition is present, and return if not met */ if (opts_count(opts, "p")) { off_t when = opts_optarg_int(opts, "p"); struct opts *cfopts; /* unless rotate forced by "-p now", see if period has passed */ if (when != OPTP_NOW) { /* * "when" holds the number of seconds that must have * passed since the last time this log was rotated. * of course, running logadm can take a little time * (typically a second or two, but longer if the * conffile has lots of stuff in it) and that amount * of time is variable, depending on system load, etc. * so we want to allow a little "slop" in the value of * "when". this way, if a log should be rotated every * week, and the number of seconds passed is really a * few seconds short of a week, we'll go ahead and * rotate the log as expected. * */ if (when >= 60 * 60) when -= 59; /* * last rotation is recorded as argument to -P, * but if logname isn't the same as log file name * then the timestamp would be recorded on a * separate line in the timestamp file. so if we * haven't seen a -P already, we check to see if * it is part of a specific entry for the log * file name. this handles the case where the * logname is "apache", it supplies a log file * name like "/var/apache/logs/[a-z]*_log", * which expands to multiple file names. if one * of the file names is "/var/apache/logs/access_log" * the the -P will be attached to a line with that * logname in the timestamp file. */ if (opts_count(opts, "P")) { off_t last = opts_optarg_int(opts, "P"); /* return if not enough time has passed */ 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"); /* * just checking this means this entry * is now "done" if we're going through * the entire conffile */ Donenames = lut_add(Donenames, fname, "1"); /* return if not enough time has passed */ if (Now - last < when) return (B_TRUE); } } } if (Debug) (void) fprintf(stderr, "rotatelog: conditions met\n"); if (opts_count(opts, "l")) { /* Change the time zone to local time zone */ if (putenv("TZ=")) err(EF_SYS, "putenv TZ"); tzset(); Now = time(0); /* rename the log file */ rotateto(fnp, opts, 0, recentlog, B_FALSE); /* Change the time zone to UTC */ if (putenv("TZ=UTC")) err(EF_SYS, "putenv TZ"); tzset(); Now = time(0); } else { /* rename the log file */ rotateto(fnp, opts, 0, recentlog, B_FALSE); } /* determine owner, group, mode for empty log file */ 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; } /* create the empty log file */ docmd(opts, NULL, Touch, fname, NULL, NULL); docmd(opts, NULL, Chown, owner, fname, NULL); docmd(opts, NULL, Chmod, mode, fname, NULL); /* execute post-rotation command */ 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); /* * add "after" command to list of after commands. we also record * the email address, if any, where the error output of the after * command should be sent. if the after command is already on * our list, add the email addr to the list the email addrs for * that command (the after command will only be executed once, * so the error output gets mailed to every address we've come * across associated with this command). */ 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); } /* record the rotation date */ (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); } /* rotate files "up" according to current template */ 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; /* expand template to figure out new filename */ 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 filename is there already, rotate "up" */ 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); /* * since we're compressing old files, see if we * about to rotate into one. */ fn_puts(gzfnp, ".gz"); if (lstat(fn_s(gzfnp), &stbuf) != -1) rotateto(gzfnp, opts, n + 1, recentlog, B_TRUE); fn_free(gzfnp); } /* first time through run "before" cmd if not run already */ 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"); } } /* ensure destination directory exists */ dirname = fn_dirname(newfile); docmd(opts, "verify directory exists", Mkdir, "-p", fn_s(dirname), NULL); fn_free(dirname); /* do the rename */ 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); /* use specified command to mv the log file */ 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 /* common case: we call "mv" to handle the actual rename */ docmd(opts, "rotate log file", Mv, "-f", fn_s(fnp), fn_s(newfile)); /* first time through, gather interesting info for caller */ if (n == 0) fn_renew(recentlog, fn_s(newfile)); } /* expire phase of logname processing */ 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); /* return if no potential expire conditions */ 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); } /* see if pattern was supplied by user */ if (opts_count(opts, "T")) { template = fn_new(opts_optarg(opts, "T")); pattern = glob_to_reglob(template); } else { /* nope, generate pattern based on rotation template */ template = fn_new(opts_optarg(opts, "t")); pattern = fn_new(NULL); (void) kw_expand(template, pattern, -1, opts_count(opts, "z") != 0); } /* match all old log files (hopefully not any others as well!) */ 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); } } /* see if count causes expiration */ 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--; } } /* see if total size causes expiration */ 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); } } /* see if age causes expiration */ 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; } } } /* record old log files to be gzip'ed according to -z count */ 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)) { /* * Don't gzip the old log file yet - * it takes too long. Just remember that we * need to gzip. */ 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); } /* execute a command to remove an expired log file */ 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); /* user supplied cmd, expand $file */ 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); } /* execute a command, producing -n and -v output as necessary */ 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]; /* print info about command if necessary */ 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; /* -n means don't really do it */ /* * Run the cmd and see if it failed. This function is *not* a generic * command runner. The command name must be an absolute pathname, and * two args must be supplied (the second may be NULL, or they may both * be NULL). Any output (stdout and stderr) from the child process is * logged to stderr and perhaps sent to an email recipient. */ 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; /* parent */ (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) { /* Assume the command gave a useful error */ } 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 { /* child */ (void) dup2(errpipe[1], fileno(stderr)); (void) close(errpipe[0]); (void) execl(cmd, cmd, arg1, arg2, arg3, 0); perror(cmd); _exit(1); } } /* do internal atomic file copy and truncation */ 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; /* print info if necessary */ 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; /* -n means don't really do it */ /* open log file to be rotated and remember its chmod mask */ 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; } /* create new file for copy destination with correct attributes */ 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); /* * Now we'll loop, reading the log file and writing it to our copy * until the bytes remaining are beneath our atomicity threshold -- at * which point we'll lock the file and copy the remainder atomically. * The body of this loop is non-atomic with respect to writers, the * rationale being that total atomicity (that is, locking the file for * the entire duration of the copy) comes at too great a cost for a * large log file, as the writer (i.e., the daemon whose log is being * rolled) can be blocked for an unacceptable duration. (For one * particularly loquacious daemon, this period was observed to be * several minutes in length -- a time so long that it induced * additional failures in dependent components.) Note that this means * that if the log file is not always appended to -- if it is opened * without O_APPEND or otherwise truncated outside of logadm -- this * will result in our log snapshot being incorrect. But of course, in * either of these cases, the use of logadm at all is itself * suspect... */ 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; /* * If the file became smaller, something fishy is going * on; we'll truncate our copy, reset our seek offset * and break into the atomic copy. */ (void) ftruncate(fo, 0); (void) lseek(fo, 0, SEEK_SET); (void) lseek(fi, 0, SEEK_SET); break; } if (written != 0 && rem > last) { /* * We're falling behind -- this file is getting bigger * faster than we're able to write it; break out and * lock the file to block the writer. */ 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); /* lock log file so that nobody can write into it before we are done */ 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); /* do atomic copy and truncation */ 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); /* unlock log file */ 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); /* keep times from original file */ times.actime = s.st_atime; times.modtime = s.st_mtime; (void) utime(file_copy, ×); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2001, 2010, Oracle and/or its affiliates. All rights reserved. */ /* * logadm/opts.c -- options handling routines */ #include #include #include #include #include #include #include #include #include #include "err.h" #include "lut.h" #include "fn.h" #include "opts.h" /* forward declarations for private functions */ static struct optinfo *opt_info(int c); static void opts_setcmdarg(struct opts *opts, const char *cmdarg); /* info created by opts_parse(), private to this module */ struct opts { struct lut *op_raw; /* the raw text for the options */ struct lut *op_ints; /* the int values for the options */ struct fn_list *op_cmdargs; /* the op_cmdargs */ }; static off_t opts_parse_ctime(const char *o, const char *optarg); static off_t opts_parse_bytes(const char *o, const char *optarg); static off_t opts_parse_atopi(const char *o, const char *optarg); static off_t opts_parse_seconds(const char *o, const char *optarg); static struct lut *Info; /* table driving parsing */ /* table that drives argument parsing */ struct optinfo Opttable[] = { { "e", OPTTYPE_STRING, NULL, OPTF_CLI|OPTF_CONF }, { "F", OPTTYPE_STRING, NULL, OPTF_CLI }, { "f", OPTTYPE_STRING, NULL, OPTF_CLI }, { "h", OPTTYPE_BOOLEAN, NULL, OPTF_CLI }, { "l", OPTTYPE_BOOLEAN, NULL, OPTF_CLI|OPTF_CONF }, { "N", OPTTYPE_BOOLEAN, NULL, OPTF_CLI|OPTF_CONF }, { "n", OPTTYPE_BOOLEAN, NULL, OPTF_CLI }, { "r", OPTTYPE_BOOLEAN, NULL, OPTF_CLI }, { "V", OPTTYPE_BOOLEAN, NULL, OPTF_CLI }, { "v", OPTTYPE_BOOLEAN, NULL, OPTF_CLI }, { "w", OPTTYPE_STRING, NULL, OPTF_CLI }, { "p", OPTTYPE_INT, opts_parse_seconds, OPTF_CLI|OPTF_CONF }, { "P", OPTTYPE_INT, opts_parse_ctime, OPTF_CLI|OPTF_CONF }, { "s", OPTTYPE_INT, opts_parse_bytes, OPTF_CLI|OPTF_CONF }, { "a", OPTTYPE_STRING, NULL, OPTF_CLI|OPTF_CONF }, { "b", OPTTYPE_STRING, NULL, OPTF_CLI|OPTF_CONF }, { "c", OPTTYPE_BOOLEAN, NULL, OPTF_CLI|OPTF_CONF }, { "g", OPTTYPE_STRING, NULL, OPTF_CLI|OPTF_CONF }, { "m", OPTTYPE_INT, opts_parse_atopi, OPTF_CLI|OPTF_CONF }, { "M", OPTTYPE_STRING, NULL, OPTF_CLI|OPTF_CONF }, { "o", OPTTYPE_STRING, NULL, OPTF_CLI|OPTF_CONF }, { "R", OPTTYPE_STRING, NULL, OPTF_CLI|OPTF_CONF }, { "t", OPTTYPE_STRING, NULL, OPTF_CLI|OPTF_CONF }, { "z", OPTTYPE_INT, opts_parse_atopi, OPTF_CLI|OPTF_CONF }, { "A", OPTTYPE_INT, opts_parse_seconds, OPTF_CLI|OPTF_CONF }, { "C", OPTTYPE_INT, opts_parse_atopi, OPTF_CLI|OPTF_CONF }, { "E", OPTTYPE_STRING, NULL, OPTF_CLI|OPTF_CONF }, { "S", OPTTYPE_INT, opts_parse_bytes, OPTF_CLI|OPTF_CONF }, { "T", OPTTYPE_STRING, NULL, OPTF_CLI|OPTF_CONF }, }; int Opttable_cnt = sizeof (Opttable) / sizeof (struct optinfo); /* * opts_init -- set current options parsing table */ void opts_init(struct optinfo *table, int numentries) { while (numentries-- > 0) { Info = lut_add(Info, table->oi_o, table); table++; } } /* * opt_info -- fetch the optinfo struct for the given option */ static struct optinfo * opt_info(int c) { char lhs[2]; lhs[0] = c; lhs[1] = '\0'; return ((struct optinfo *)lut_lookup(Info, lhs)); } /* * opts_parse -- parse an argv-style list of options * * prints a message to stderr and calls err(EF_FILE|EF_JMP, ...) on error */ struct opts * opts_parse(struct opts *opts, char **argv, int flags) { int dashdash = 0; char *ptr; if (opts == NULL) { opts = MALLOC(sizeof (*opts)); opts->op_raw = opts->op_ints = NULL; opts->op_cmdargs = fn_list_new(NULL); } /* no words to process, just return empty opts struct */ if (argv == NULL) return (opts); /* foreach word... */ for (; (ptr = *argv) != NULL; argv++) { if (dashdash || *ptr != '-') { /* found a cmdarg */ opts_setcmdarg(opts, ptr); continue; } if (*++ptr == '\0') err(EF_FILE|EF_JMP, "Illegal option: dash by itself"); if (*ptr == '-') { /* (here's where support for --longname would go) */ if (*(ptr + 1) != '\0') err(EF_FILE|EF_JMP, "Illegal option: -%s", ptr); dashdash++; continue; } for (; *ptr; ptr++) { struct optinfo *info = opt_info(*ptr); /* see if option was in our parsing table */ if (info == NULL) err(EF_FILE|EF_JMP, "Illegal option: %c", *ptr); /* see if context allows this option */ if ((flags & OPTF_CLI) && (info->oi_flags & OPTF_CLI) == 0) err(EF_FILE|EF_JMP, "Option '%c' not allowed on " "command line", *ptr); if ((flags & OPTF_CONF) && (info->oi_flags & OPTF_CONF) == 0) err(EF_FILE|EF_JMP, "Option '%c' not allowed in " "configuration file", *ptr); /* for boolean options, we have all the info we need */ if (info->oi_t == OPTTYPE_BOOLEAN) { (void) opts_set(opts, info->oi_o, ""); continue; } /* option expects argument */ if (*++ptr == '\0' && ((ptr = *++argv) == NULL || *ptr == '-')) err(EF_FILE|EF_JMP, "Option '%c' requires an argument", info->oi_o[0]); opts_set(opts, info->oi_o, ptr); break; } } return (opts); } /* * opts_free -- free a struct opts previously allocated by opts_parse() */ void opts_free(struct opts *opts) { if (opts) { lut_free(opts->op_raw, NULL); lut_free(opts->op_ints, NULL); fn_list_free(opts->op_cmdargs); FREE(opts); } } /* * opts_set -- set an option */ void opts_set(struct opts *opts, const char *o, const char *optarg) { off_t *rval; struct optinfo *info = opt_info(*o); rval = MALLOC(sizeof (off_t)); opts->op_raw = lut_add(opts->op_raw, o, (void *)optarg); if (info->oi_parser) { *rval = (*info->oi_parser)(o, optarg); opts->op_ints = lut_add(opts->op_ints, o, (void *)rval); } } /* * opts_setcmdarg -- add a cmdarg to the list of op_cmdargs */ static void opts_setcmdarg(struct opts *opts, const char *cmdarg) { fn_list_adds(opts->op_cmdargs, cmdarg); } /* * opts_count -- return count of the options in *options that are set */ int opts_count(struct opts *opts, const char *options) { int count = 0; for (; *options; options++) { char lhs[2]; lhs[0] = *options; lhs[1] = '\0'; if (lut_lookup(opts->op_raw, lhs)) count++; } return (count); } /* * opts_optarg -- return the optarg for the given option, NULL if not set */ const char * opts_optarg(struct opts *opts, const char *o) { return ((char *)lut_lookup(opts->op_raw, o)); } /* * opts_optarg_int -- return the int value for the given option */ off_t opts_optarg_int(struct opts *opts, const char *o) { off_t *ret; ret = (off_t *)lut_lookup(opts->op_ints, o); if (ret != NULL) return (*ret); return (0); } /* * opts_cmdargs -- return list of op_cmdargs */ struct fn_list * opts_cmdargs(struct opts *opts) { return (opts->op_cmdargs); } static void merger(const char *lhs, void *rhs, void *arg) { struct lut **destlutp = (struct lut **)arg; *destlutp = lut_add(*destlutp, lhs, rhs); } /* * opts_merge -- merge two option lists together */ struct opts * opts_merge(struct opts *back, struct opts *front) { struct opts *ret = MALLOC(sizeof (struct opts)); ret->op_raw = lut_dup(back->op_raw); lut_walk(front->op_raw, merger, &(ret->op_raw)); ret->op_ints = lut_dup(back->op_ints); lut_walk(front->op_ints, merger, &(ret->op_ints)); ret->op_cmdargs = fn_list_dup(back->op_cmdargs); return (ret); } /* * opts_parse_ctime -- parse a ctime format optarg */ static off_t opts_parse_ctime(const char *o, const char *optarg) { struct tm tm; off_t ret; if (strptime(optarg, "%a %b %e %T %Z %Y", &tm) == NULL && strptime(optarg, "%c", &tm) == NULL) err(EF_FILE|EF_JMP, "Option '%c' requires ctime-style time", *o); errno = 0; if ((ret = (off_t)mktime(&tm)) == -1 && errno) err(EF_FILE|EF_SYS|EF_JMP, "Option '%c' Illegal time", *o); return (ret); } /* * opts_parse_atopi -- parse a positive integer format optarg */ static off_t opts_parse_atopi(const char *o, const char *optarg) { off_t ret = atoll(optarg); while (isdigit(*optarg)) optarg++; if (*optarg) err(EF_FILE|EF_JMP, "Option '%c' requires non-negative number", *o); return (ret); } /* * opts_parse_atopi -- parse a size format optarg into bytes */ static off_t opts_parse_bytes(const char *o, const char *optarg) { off_t ret = atoll(optarg); while (isdigit(*optarg)) optarg++; switch (*optarg) { case 'g': case 'G': ret *= 1024; /*FALLTHROUGH*/ case 'm': case 'M': ret *= 1024; /*FALLTHROUGH*/ case 'k': case 'K': ret *= 1024; /*FALLTHROUGH*/ case 'b': case 'B': if (optarg[1] == '\0') return (ret); } err(EF_FILE|EF_JMP, "Option '%c' requires number with suffix from [bkmg]", *o); /*NOTREACHED*/ return (0); } /* * opts_parse_seconds -- parse a time format optarg into seconds */ static off_t opts_parse_seconds(const char *o, const char *optarg) { off_t ret; if (strcasecmp(optarg, "now") == 0) return (OPTP_NOW); if (strcasecmp(optarg, "never") == 0) return (OPTP_NEVER); ret = atoll(optarg); while (isdigit(*optarg)) optarg++; if (optarg[1] == '\0') switch (*optarg) { case 'h': case 'H': ret *= 60 * 60; return (ret); case 'd': case 'D': ret *= 60 * 60 * 24; return (ret); case 'w': case 'W': ret *= 60 * 60 * 24 * 7; return (ret); case 'm': case 'M': ret *= 60 * 60 * 24 * 30; return (ret); case 'y': case 'Y': ret *= 60 * 60 * 24 * 365; return (ret); } err(EF_FILE|EF_JMP, "Option '%c' requires number with suffix from [hdwmy]", *o); /*NOTREACHED*/ return (0); } /* info passed between opts_print() and printer() */ struct printerinfo { FILE *stream; int isswitch; char *exclude; }; /* helper function for opts_print() */ static void printer(const char *lhs, void *rhs, void *arg) { struct printerinfo *pip = (struct printerinfo *)arg; char *s = (char *)rhs; if (pip->isswitch) { char *ep = pip->exclude; while (ep && *ep) if (*ep++ == *lhs) return; } (void) fprintf(pip->stream, " %s%s", (pip->isswitch) ? "-" : "", lhs); if (s && *s) { (void) fprintf(pip->stream, " "); opts_printword(s, pip->stream); } } /* * opts_printword -- print a word, quoting as necessary */ void opts_printword(const char *word, FILE *stream) { char *q = ""; if (word != NULL) { if (strchr(word, ' ') || strchr(word, '\t') || strchr(word, '$') || strchr(word, '[') || strchr(word, '?') || strchr(word, '{') || strchr(word, '`') || strchr(word, ';')) { if (strchr(word, '\'') == NULL) q = "'"; else if (strchr(word, '"') == NULL) q = "\""; else err(EF_FILE|EF_JMP, "Can't protect quotes in <%s>", word); (void) fprintf(stream, "%s%s%s", q, word, q); } else (void) fprintf(stream, "%s", word); } } /* * opts_print -- print options to stream, leaving out those in "exclude" */ void opts_print(struct opts *opts, FILE *stream, char *exclude) { struct printerinfo pi; struct fn *fnp; pi.stream = stream; pi.isswitch = 1; pi.exclude = exclude; lut_walk(opts->op_raw, printer, &pi); fn_list_rewind(opts->op_cmdargs); while ((fnp = fn_list_next(opts->op_cmdargs)) != NULL) { (void) fprintf(stream, " "); opts_printword(fn_s(fnp), stream); } } #ifdef TESTMODULE /* table that drives argument parsing */ static struct optinfo Testopttable[] = { { "a", OPTTYPE_BOOLEAN, NULL, OPTF_CLI }, { "b", OPTTYPE_STRING, NULL, OPTF_CLI }, { "c", OPTTYPE_INT, opts_parse_seconds, OPTF_CLI|OPTF_CONF }, { "d", OPTTYPE_INT, opts_parse_ctime, OPTF_CLI|OPTF_CONF }, { "e", OPTTYPE_INT, opts_parse_bytes, OPTF_CLI|OPTF_CONF }, { "f", OPTTYPE_INT, opts_parse_atopi, OPTF_CLI|OPTF_CONF }, }; /* * test main for opts module, usage: a.out options... */ int main(int argc, char *argv[]) { struct opts *opts = NULL; err_init(argv[0]); setbuf(stdout, NULL); opts_init(Testopttable, sizeof (Testopttable) / sizeof (struct optinfo)); argv++; if (SETJMP) err(0, "opts parsing failed"); else opts = opts_parse(NULL, argv, OPTF_CLI); printf("options:"); opts_print(opts, stdout, NULL); printf("\n"); err_done(0); /* NOTREACHED */ return (0); } #endif /* TESTMODULE */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2001, 2010, Oracle and/or its affiliates. All rights reserved. * * logadm/opts.h -- public definitions for opts module */ #ifndef _LOGADM_OPTS_H #define _LOGADM_OPTS_H #ifdef __cplusplus extern "C" { #endif /* various types of options we allow */ enum opttype { OPTTYPE_BOOLEAN, /* simple boolean flag */ OPTTYPE_INT, /* simple number */ OPTTYPE_STRING /* string (like a pathname) */ }; struct opts; /* info that drives option parsing (table of these is passed to opts_init()) */ struct optinfo { char *oi_o; /* the option */ enum opttype oi_t; /* the type of this option */ /* parser, if set, is called to parse optarg */ off_t (*oi_parser)(const char *o, const char *optarg); int oi_flags; }; /* flags for struct optinfo */ #define OPTF_CLI 1 #define OPTF_CONF 2 void opts_init(struct optinfo *table, int numentries); struct opts *opts_parse(struct opts *, char **args, int flags); void opts_free(struct opts *opts); void opts_set(struct opts *opts, const char *o, const char *optarg); int opts_count(struct opts *opts, const char *options); const char *opts_optarg(struct opts *opts, const char *o); off_t opts_optarg_int(struct opts *opts, const char *o); struct fn_list *opts_cmdargs(struct opts *opts); struct opts *opts_merge(struct opts *back, struct opts *front); #define OPTP_NOW (-1) #define OPTP_NEVER (-2) void opts_print(struct opts *opts, FILE *stream, char *exclude); void opts_printword(const char *word, FILE *stream); extern struct optinfo Opttable[]; extern int Opttable_cnt; #ifdef __cplusplus } #endif #endif /* _LOGADM_OPTS_H */ #!/usr/bin/perl -w # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright (c) 2001, 2010, Oracle and/or its affiliates. All rights reserved. # Copyright 2019 Joyent, Inc. # # # tester - run logadm tests # # requires a argument to say where the various logadm # binaries live (conftest, globtest, kwtest, luttest, optstest, and # logadm itself). # # to run all the tests: # tester [-f] # # to run just a few tests, given their names: # tester [-f] globtest1 luttest1 # # to setup a test and stop so you can run it by hand: # tester [-f] -s globtest1 # # tester will tell you what tmp directory it created for # the test. to run it, cd there and run: # sh runtest # to check the results, run: # sh checktest # # -f means "fast" -- without it, watchmalloc(3MALLOC) is setup for # each test and they run a zillion times slower and produce core # dumps when malloc/free problems are detected. # $watchmalloc=1; # default is to use watchmalloc ${ENV} = "/bin"; umask 002; # list of tests we run by default @tests = ( "conftest1", "conftest2", "globtest1", "globtest2", "kwtest1", "kwtest2", "luttest1", "optstest1", "optstest2", "logadmV1", "logadmV2", "logadmr", "logadmw", "logadm1", "logadm1c", "logadm2", "logadm3", "logadm4", "logadm5", "logadm6", "logadm7", "logadm8", "logadm9", "logadm9d", "logadm10", "logadm11", "logadm12", "logadm13", "logadm14", "logadm15", "logadm16", "logadm17", "logadm18", "logadm19", "logadm20", "logadm21", "logadm22", "logadm23", "stderr1" ); use Getopt::Std; use File::Find; $usage_summary = '[-s test-name] [-d dir] bindir [test-name...]'; $usage_getopts = 'fd:s:'; %usage = ( d=>'use dir for tests rather than creating one in /tmp', s=>'setup only, do not run test'); # spew usage message, plus any given message, and exit sub usage { my $msg = shift; if ($msg) { chomp $msg; warn "$0: $msg\n" if $msg; } warn "Usage: $0 $usage_summary\n"; foreach (sort keys %usage) { warn " -$_ $usage{$_}\n"; } exit 1; } # # basic argument processing # $myname = $0; $myname =~ s/.*\///; # just show last component in error mesages getopts($usage_getopts) or usage; $bindir = shift or usage; usage("$bindir does not exist") unless -d $bindir; usage("cannot list more than one test with -s option") if $opt_s && @ARGV; @tests = @ARGV if @ARGV; print "Fast mode\n" if $opt_f; $watchmalloc = 0 if $opt_f; $mydir=`pwd`; chomp $mydir; $dir = $opt_d; $dir = "/tmp/logadmtest$$" unless $dir = $opt_d; if (!-d $dir) { mkdir $dir, 0777 or die "$myname: mkdir $dir: $!\n"; $needrmdir = 1; } chdir $dir or die "$myname: $dir: $!\n"; # common commands in runtest by tests if ($watchmalloc) { $envsetup = "HOME=$dir export HOME; " . "LD_PRELOAD=watchmalloc.so.1 export LD_PRELOAD; " . "MALLOC_DEBUG=RW export MALLOC_DEBUG"; } else { $envsetup = "HOME=$dir export HOME; "; } $| = 1; # a.k.a. setbuf(stdout, NULL) if ($opt_s) { # # just setup the test, explain how to use it, and exit # $testname = $opt_s; eval "&$opt_s"; die "$myname: ERROR: $@" if $@; print "$myname: $testname setup complete, to run, cd to:\n"; print " $dir\n"; print "and run the command:\n"; print " sh runtest\n"; print "to check the results, run the command:\n"; print " sh checktest\n"; exit 0; } else { # # run all the tests # foreach (@tests) { $testname = $_; print "Running $testname..."; eval "&$_"; if ($@) { print " SETUP FAILURE\n"; print STDERR "$myname: ERROR: $@"; exit 1; } eval "runner('runtest')"; if ($@) { print " RUNTEST FAILURE\n"; print STDERR "$myname: ERROR: $@"; print STDERR "results captured in directory $dir\n"; print STDERR " or use: $myname -s $testname $bindir\n"; print STDERR " to do a fresh setup of this test.\n"; exit 1; } eval "runner('checktest', '-x', '> checktest.out 2>&1')"; if ($@) { print " CHECKTEST FAILURE\n"; print STDERR "$myname: ERROR: $@"; print STDERR "results captured in file $dir/checktest.out\n"; print STDERR " or use: $myname -s $testname $bindir\n"; print STDERR " to do a fresh setup of this test.\n"; exit 1; } print "pass\n"; # sanity... die "unexpected dir $dir" unless $dir =~ m,/.+/,; system("/bin/rm -rf $dir/*"); } } # if we were the ones who created $dir, remove it if ($needrmdir) { chdir $mydir; rmdir $dir || die "$myname: rmdir $dir: $!\n"; } exit 0; # # run a shell script and check for failure # # the shell scripts generated by this program always "exec" the binary # under test so checking here are for exit code, signals, and core dump # is actually checking the program under test and not /bin/sh # sub runner { my ($cmd, $prefix, $suffix) = (@_, '', ''); my $fullcmd = "/bin/sh $prefix $cmd $suffix"; my $rc = 0xffff & system("$fullcmd"); if ($rc == 0) { return; # cmd completed normally } elsif ($rc == 0xff00) { die "command \"$cmd\" failed: $!\n"; } elsif (($rc & 0xff) == 0) { $rc >>= 8; die "command \"$cmd\" exit $rc\n"; } else { my $coremsg; $coremsg = " (core dumped)" if ($rc & 0x80); $rc &= ~0x80; die "command \"$cmd\" signal $rc$coremsg\n" ; } } # # set_file(filename [, contents]) -- create a file, optionally with contents # sub set_file { my $file = shift; my $contents = shift; open SF, ">$file" or die "create \"$file\": $!\n"; print SF $contents if defined($contents); close SF; } ############# ############# ############# THE TESTS START AFTER HERE... ############# ############# # common setup step -- create a testfile.conf sub set_testconffile { my $fname = shift; $fname = 'testfile.conf' unless defined($fname); set_file($fname, <<'EOF'); # # logadm.conf # # Default settings for system log file management. # The -w option to logadm(8) is the preferred way to write to this file, # but if you do edit it by hand, use "logadm -V" to check it for errors. # # The format of lines in this file is: # # For each logname listed here, the default options to logadm # are given. Options given on the logadm command line override # the defaults contained in this file. # # logadm typically runs early every morning via an entry in # root's crontab (see crontab(1)). # /var/adm/messages -C 4 -P 'Thu Nov 1 16:56:42 2001' -a 'kill -HUP `cat /var/run/*syslog*pid`' /var/cron/log -s 512k -t /var/cron/olog /var/lp/logs/lpsched -C 2 -N -t '$file.$N' # # The entry below is used by turnacct(8) # /var/adm/pacct -C 0 -a '/usr/lib/acct/accton pacct' -g adm -m 664 -o adm -p never apache -C 24 -a '/usr/apache/bin/apachectl graceful' -p 1m -t '/var/apache/old-logs/$basename.%Y-%m' '/var/apache/logs/*{access,error}_log' /var/log/syslog -C 8 -P 'Thu Nov 1 09:16:38 2001' -a 'kill -HUP `cat /var/run/*syslog*pid`' /var/apache/logs/access_log -P 'Thu Nov 1 08:27:56 2001' /var/apache/logs/error_log -P 'Thu Nov 1 08:27:56 2001' /var/apache/logs/suexec_log -P 'Thu Nov 1 08:27:56 2001' /var/apache/logs/mod_jserv.log -P 'Thu Nov 1 08:27:56 2001' /var/apache/logs/jserv.log -P 'Thu Nov 1 08:27:56 2001' EOF } ########################################################################### # # conftest1 -- minimal basic test of the conf.c code # ########################################################################### sub conftest1 { set_testconffile; set_file('checktest', <<'EOF'); [ -s std.err ] && { cat std.err; exit 1; } /bin/sed '/^conffile :$/d' sed.out exec /bin/diff testfile.conf sed.out EOF set_file('runtest', <<"EOF"); # test "conftest1" $envsetup exec $bindir/conftest testfile.conf >std.out 2>std.err EOF } ########################################################################### # # conftest2 -- error path through conf.c # ########################################################################### sub conftest2 { set_file('testfile.conf', 'line fragment'); set_file('std.err.expect', <<'EOF'); conftest: Warning: file testfile.conf doesn't end with newline, last line ignored. EOF set_file('checktest', <<'EOF'); exec /bin/diff std.err.expect std.err EOF set_file('runtest', <<"EOF"); # test "conftest2" $envsetup $bindir/conftest testfile.conf >std.out 2>std.err || exit 0 exit 1 EOF } ########################################################################### # # globtest1 -- minimal basic test of the glob.c code # ########################################################################### sub globtest1 { set_file('fileBname12'); sleep 2; # ensure above name is odler than below name set_file('fileAname12'); set_file('fileAname1'); set_file('fileAname3'); set_file('fileAname5'); set_file('fileAname7'); set_file('fileAname9'); set_file('fileAname11'); set_file('fileBname0'); set_file('fileBname2'); set_file('fileBname4'); set_file('fileBname6'); set_file('fileBname8'); set_file('fileBname10'); mkdir 'dir1', 0777 or die "mkdir dir1: $!\n"; mkdir 'dir2', 0777 or die "mkdir dir2: $!\n"; mkdir 'dir3', 0777 or die "mkdir dir3: $!\n"; mkdir 'dir1/dirA', 0777 or die "mkdir dir1/dirA: $!\n"; mkdir 'dir1/dirB', 0777 or die "mkdir dir1/dirB: $!\n"; mkdir 'dir1/dirC', 0777 or die "mkdir dir1/dirC: $!\n"; mkdir 'dir2/dirA', 0777 or die "mkdir dir2/dirA: $!\n"; mkdir 'dir2/dirB', 0777 or die "mkdir dir2/dirB: $!\n"; mkdir 'dir2/dirC', 0777 or die "mkdir dir2/dirC: $!\n"; set_file('dir1/fileAname1'); set_file('dir1/fileAname2'); set_file('dir1/fileAname3'); set_file('dir1/fileAname4'); set_file('dir1/fileAname5'); set_file('dir1/fileBname1'); set_file('dir1/fileBname2'); set_file('dir1/fileBname3'); set_file('dir1/fileBname4'); set_file('dir1/fileBname5'); # supply some varying sizes to produce different total size values set_file('dir1/dirA/fileAname4', '4444'); sleep 2; # ensure above file is oldest in dirA set_file('dir1/dirA/fileAname1', '1'); set_file('dir1/dirA/fileAname2', '22'); set_file('dir1/dirA/fileAname3', '333'); set_file('dir1/dirA/fileAname5', '55555'); set_file('dir1/dirA/fileBname1', '1'); set_file('dir1/dirA/fileBname2', '22'); set_file('dir1/dirA/fileBname3', '333'); set_file('dir1/dirA/fileBname4', '4444'); set_file('dir1/dirA/fileBname5', '55555'); set_file('dir1/dirB/fileAname1', '1'); set_file('dir1/dirB/fileAname2', '22'); set_file('dir1/dirB/fileAname3', '333'); set_file('dir1/dirB/fileAname4', '4444'); set_file('dir1/dirB/fileAname5', '55555'); set_file('dir1/dirB/fileBname1', '1'); set_file('dir1/dirB/fileBname2', '22'); set_file('dir1/dirB/fileBname3', '333'); set_file('dir1/dirB/fileBname4', '4444'); set_file('dir1/dirB/fileBname5', '55555'); set_file('dir1/dirC/fileAname10', '12345678901'); set_file('dir1/dirC/fileAname20', '123456789022'); set_file('dir1/dirC/fileAname30', '1234567890333'); set_file('dir1/dirC/fileAname40', '12345678904444'); set_file('dir1/dirC/fileAname50', '123456789055555'); set_file('dir1/dirC/fileBname10', '12345678901'); set_file('dir1/dirC/fileBname20', '123456789022'); set_file('dir1/dirC/fileBname30', '1234567890333'); set_file('dir1/dirC/fileBname40', '12345678904444'); set_file('dir1/dirC/fileBname50', '123456789055555'); set_file('std.out.expect', <<'EOF'); : <./fileAname12> <./fileAname1> <./fileAname3> <./fileAname5> <./fileAname7> <./fileAname9> <./fileAname11> <./fileBname12> <./fileBname0> <./fileBname2> <./fileBname4> <./fileBname6> <./fileBname8> <./fileBname10> total size: 0 oldest <./fileBname12> oldest <./fileBname8> oldest <./fileBname6> oldest <./fileBname4> oldest <./fileBname2> oldest <./fileBname10> oldest <./fileBname0> oldest <./fileAname9> oldest <./fileAname7> oldest <./fileAname5> oldest <./fileAname3> oldest <./fileAname12> oldest <./fileAname11> oldest <./fileAname1> : total size: 0 oldest oldest oldest : <./dir1/dirA/fileAname4> <./dir1/dirA/fileAname1> <./dir1/dirA/fileAname2> <./dir1/dirA/fileAname3> <./dir1/dirA/fileAname5> <./dir1/dirA/fileBname1> <./dir1/dirA/fileBname2> <./dir1/dirA/fileBname3> <./dir1/dirA/fileBname4> <./dir1/dirA/fileBname5> total size: 30 oldest <./dir1/dirA/fileAname4> oldest <./dir1/dirA/fileBname5> oldest <./dir1/dirA/fileBname4> oldest <./dir1/dirA/fileBname3> oldest <./dir1/dirA/fileBname2> oldest <./dir1/dirA/fileBname1> oldest <./dir1/dirA/fileAname5> oldest <./dir1/dirA/fileAname3> oldest <./dir1/dirA/fileAname2> oldest <./dir1/dirA/fileAname1> : <./dir1/fileAname1> <./dir1/fileAname2> <./dir1/fileAname3> <./dir1/fileAname4> <./dir1/fileAname5> <./dir1/fileBname1> <./dir1/fileBname2> <./dir1/fileBname3> <./dir1/fileBname4> <./dir1/fileBname5> total size: 0 oldest <./dir1/fileBname5> oldest <./dir1/fileBname4> oldest <./dir1/fileBname3> oldest <./dir1/fileBname2> oldest <./dir1/fileBname1> oldest <./dir1/fileAname5> oldest <./dir1/fileAname4> oldest <./dir1/fileAname3> oldest <./dir1/fileAname2> oldest <./dir1/fileAname1> : <./dir1/dirA/fileBname2> <./dir1/dirA/fileBname3> <./dir1/dirA/fileBname4> <./dir1/dirA/fileBname5> total size: 14 oldest <./dir1/dirA/fileBname5> oldest <./dir1/dirA/fileBname4> oldest <./dir1/dirA/fileBname3> oldest <./dir1/dirA/fileBname2> : <./fileBname12> <./fileAname12> <./fileAname1> <./fileAname3> <./fileAname5> <./fileAname7> <./fileAname9> <./fileAname11> <./fileBname0> <./fileBname2> <./fileBname4> <./fileBname6> <./fileBname8> <./fileBname10> total size: 0 oldest <./fileBname12> oldest <./fileAname12> oldest <./fileAname11> oldest <./fileBname10> oldest <./fileAname9> oldest <./fileBname8> oldest <./fileAname7> oldest <./fileBname6> oldest <./fileAname5> oldest <./fileBname4> oldest <./fileAname3> oldest <./fileBname2> oldest <./fileAname1> oldest <./fileBname0> EOF set_file('checktest', <<'EOF'); [ -s std.err ] && { cat std.err; exit 1; } exec /bin/diff std.out.expect std.out EOF $testglobs='\'file{A,B,C}name*\' \'file{A,B,C}name\' \'dir1/dirA/file*\' \'dir[13]/[e-z]*\' \'dir?/dir[AC]/fileBname[2-9]\' -r \'file[A-Z]n.*e([0-9]+)$0\''; set_file('runtest', <<"EOF"); # test "globtest1" $envsetup exec $bindir/globtest $testglobs >std.out 2>std.err EOF } ########################################################################### # # globtest2 -- error path through glob.c # ########################################################################### sub globtest2 { set_file('std.err.expect', <<'EOF'); globtest: Error: Missing } EOF set_file('checktest', <<'EOF'); exec /bin/diff std.err.expect std.err EOF set_file('runtest', <<"EOF"); # test "globtest2" $envsetup $bindir/globtest 'hello{there' >std.out 2>std.err || exit 0 exit 1 EOF } ########################################################################### # # kwtest1 -- minimal basic test of the kw.c code # ########################################################################### sub kwtest1 { $domainname = `/bin/domainname`; chomp $domainname; $isa = `/bin/uname -p`; chomp $isa; $platform = `/bin/uname -i`; chomp $platform; $nodename = `/bin/uname -n`; chomp $nodename; $machine = `/bin/uname -m`; chomp $machine; $release = `/bin/uname -r`; chomp $release; # /bin/zonename is in SUNWzoneu and so may not be present if (-f "/bin/zonename") { $zonename = `/bin/zonename`; chomp $zonename; } else { $zonename = "global"; } $secondblob=<<'EOF'; expand<$file.$n> n -1 hasn 1 result expand<$file.$n> n 0 hasn 1 result expand<$file.$n> n 1 hasn 1 result expand n -1 hasn 1 result expand n 0 hasn 1 result expand n 1 hasn 1 result expand n -1 hasn 1 result expand n 0 hasn 1 result expand n 1 hasn 1 result EOF $percentd = `/bin/env TZ=UTC /bin/date +%d`; chomp $percentd; $percentY = `/bin/env TZ=UTC /bin/date +%Y`; chomp $percentY; $secondblob =~ s/%d/$percentd/mg; $secondblob =~ s/%Y/$percentY/mg; $secondblob =~ s/ISA/$isa/mg; $utcenv = "TZ=UTC export TZ"; chomp $secondblob; set_file('sed.out.expect', <<"EOF"); basename syslog dirname /var/log domain $domainname file /var/log/syslog home $dir isa $isa logname $ENV{LOGNAME} machine $machine nfile nodename $nodename platform $platform release $release user $ENV{USER} zonename $zonename $secondblob EOF set_file('checktest', <<'EOF'); [ -s std.err ] && { cat std.err; exit 1; } /bin/sed -e '/^ *secs [0-9][0-9]*$/d'\ -e "s/%d/`/bin/env TZ=UTC /bin/date +%d`/g"\ -e "s/%Y/`/bin/env TZ=UTC /bin/date +%Y`/g"\ sed.out exec /bin/diff sed.out.expect sed.out EOF $kwtest='kwtest /var/log/syslog \'$file.$n\' \'moose%d.$n\' \'/var/logs-%Y/moose-$isa$#porklips%d.$n\''; set_file('runtest', <<"EOF"); # test "kwtest1" $envsetup $utcenv exec $bindir/$kwtest >std.out 2>std.err EOF } ########################################################################### # # kwtest2 -- NULL environment variables test of the kw.c code # ########################################################################### sub kwtest2 { $domainname = `/bin/domainname`; chomp $domainname; $isa = `/bin/uname -p`; chomp $isa; $platform = `/bin/uname -i`; chomp $platform; $nodename = `/bin/uname -n`; chomp $nodename; $machine = `/bin/uname -m`; chomp $machine; $release = `/bin/uname -r`; chomp $release; # /bin/zonename is in SUNWzoneu and so may not be present if (-f "/bin/zonename") { $zonename = `/bin/zonename`; chomp $zonename; } else { $zonename = "global"; } $secondblob=<<'EOF'; expand<$file.$n> n -1 hasn 1 result expand<$file.$n> n 0 hasn 1 result expand<$file.$n> n 1 hasn 1 result expand n -1 hasn 1 result expand n 0 hasn 1 result expand n 1 hasn 1 result expand n -1 hasn 1 result expand n 0 hasn 1 result expand n 1 hasn 1 result EOF $percentd = `/bin/env TZ=UTC /bin/date +%d`; chomp $percentd; $percentY = `/bin/env TZ=UTC /bin/date +%Y`; chomp $percentY; $secondblob =~ s/%d/$percentd/mg; $secondblob =~ s/%Y/$percentY/mg; $secondblob =~ s/ISA/$isa/mg; chomp $secondblob; set_file('sed.out.expect', <<"EOF"); basename syslog dirname /var/log domain $domainname file /var/log/syslog home isa $isa logname machine $machine nfile nodename $nodename platform $platform release $release user zonename $zonename $secondblob EOF set_file('checktest', <<'EOF'); [ -s std.err ] && { cat std.err; exit 1; } /bin/sed -e '/^ *secs [0-9][0-9]*$/d'\ -e "s/%d/`/bin/env TZ=UTC /bin/date +%d`/g"\ -e "s/%Y/`/bin/env TZ=UTC /bin/date +%Y`/g"\ sed.out exec /bin/diff sed.out.expect sed.out EOF $kwtest='kwtest /var/log/syslog \'$file.$n\' \'moose%d.$n\' \'/var/logs-%Y/moose-$isa$#porklips%d.$n\''; set_file('runtest', <<"EOF"); # test "kwtest2" $envsetup LOGNAME= export LOGNAME HOME= export HOME USER= export USER TZ=UTC export TZ exec $bindir/$kwtest >std.out 2>std.err EOF } ########################################################################### # # luttest1 -- minimal basic test of the lut.c code # ########################################################################### sub luttest1 { set_file('std.out.expect', <<'EOF'); lut contains: () () () () () dup lut contains: () () () () () EOF set_file('checktest', <<'EOF'); [ -s std.err ] && { cat std.err; exit 1; } exec /bin/diff std.out.expect std.out EOF set_file('runtest', <<"EOF"); # test "luttest1" $envsetup exec $bindir/luttest one=two three=four fix six seven=eight >std.out 2>std.err EOF } ########################################################################### # # optstest1 -- minimal basic test of the opts.c code # ########################################################################### sub optstest1 { $options="-a -b moose -c 1h -d 'Fri Nov 2 13:19:55 2001' -e 1k -f 2 one two three"; set_file('std.out.expect', <<"EOF"); options: $options EOF set_file('checktest', <<'EOF'); [ -s std.err ] && { cat std.err; exit 1; } exec /bin/diff std.out.expect std.out EOF set_file('runtest', <<"EOF"); # test "optstest1" $envsetup exec $bindir/optstest $options >std.out 2>std.err EOF } ########################################################################### # # optstest2 -- error path through opts.c code # ########################################################################### sub optstest2 { $options="-a -b -c 1h -d 'Fri Nov 2 13:19:55 2001' -e 1k -f 2 one two three"; set_file('std.err.expect', <<'EOF'); optstest: Error: Option 'b' requires an argument optstest: Error: opts parsing failed EOF set_file('checktest', <<'EOF'); [ -s std.out ] && exit 1 exec /bin/diff std.err.expect std.err EOF set_file('runtest', <<"EOF"); # test "optstest2" $envsetup $bindir/optstest $options >std.out 2>std.err || exit 0 exit 1 EOF } ########################################################################### # # logadmV1 -- test of "logadm -V" # ########################################################################### sub logadmV1 { set_testconffile; set_file('std.out.expect', <<'EOF'); /var/adm/messages -C 4 -P 'Thu Nov 1 16:56:42 2001' -a 'kill -HUP `cat /var/run/*syslog*pid`' /var/cron/log -s 512k -t /var/cron/olog /var/lp/logs/lpsched -C 2 -N -t '$file.$N' /var/adm/pacct -C 0 -a '/usr/lib/acct/accton pacct' -g adm -m 664 -o adm -p never apache -C 24 -a '/usr/apache/bin/apachectl graceful' -p 1m -t '/var/apache/old-logs/$basename.%Y-%m' '/var/apache/logs/*{access,error}_log' /var/log/syslog -C 8 -P 'Thu Nov 1 09:16:38 2001' -a 'kill -HUP `cat /var/run/*syslog*pid`' /var/apache/logs/access_log -P 'Thu Nov 1 08:27:56 2001' /var/apache/logs/error_log -P 'Thu Nov 1 08:27:56 2001' /var/apache/logs/suexec_log -P 'Thu Nov 1 08:27:56 2001' /var/apache/logs/mod_jserv.log -P 'Thu Nov 1 08:27:56 2001' /var/apache/logs/jserv.log -P 'Thu Nov 1 08:27:56 2001' EOF set_file('checktest', <<'EOF'); [ -s std.err ] && { cat std.err; exit 1; } exec /bin/diff std.out.expect std.out EOF set_file('runtest', <<"EOF"); # test "logadmV1" $envsetup exec $bindir/logadm -f testfile.conf -F testfile.conf -V >std.out 2>std.err EOF } ########################################################################### # # logadmV2 -- test of "logadm -V " # ########################################################################### sub logadmV2 { set_testconffile; set_file('std.out.expect', <<'EOF'); /var/cron/log -s 512k -t /var/cron/olog /var/adm/pacct -C 0 -a '/usr/lib/acct/accton pacct' -g adm -m 664 -o adm -p never EOF set_file('checktest', <<'EOF'); [ -s std.err ] && { cat std.err; exit 1; } exec /bin/diff std.out.expect std.out EOF set_file('runtest', <<"EOF"); # test "logadmV2" $envsetup exec $bindir/logadm -f testfile.conf -F testfile.conf -V /var/cron/log /var/adm/pacct >std.out 2>std.err EOF } ########################################################################### # # logadmr -- test of "logadm -r " # ########################################################################### sub logadmr { set_testconffile; set_testconffile('testfile.conf.orig'); set_file('diff.out.expect', <<'EOF'); 18d17 < /var/cron/log -s 512k -t /var/cron/olog 23d21 < /var/adm/pacct -C 0 -a '/usr/lib/acct/accton pacct' -g adm -m 664 -o adm -p never EOF set_file('checktest', <<'EOF'); [ -s std.err ] && { cat std.err; exit 1; } /bin/diff testfile.conf.orig testfile.conf > diff.out exec /bin/diff diff.out.expect diff.out EOF set_file('runtest', <<"EOF"); # test "logadmr" $envsetup exec $bindir/logadm -f testfile.conf -F testfile.conf -r /var/cron/log /var/adm/pacct >std.out 2>std.err EOF } ########################################################################### # # logadmw -- test of "logadm -w " # ########################################################################### sub logadmw { set_testconffile; set_testconffile('testfile.conf.orig'); set_file('diff.out.expect', <<'EOF'); 30a31 > moose -C 20 -a moose_after_cmd -g pig -m 664 -o cow -p never /moose/file EOF set_file('checktest', <<'EOF'); [ -s std.err ] && { cat std.err; exit 1; } /bin/diff testfile.conf.orig testfile.conf > diff.out exec /bin/diff diff.out.expect diff.out EOF set_file('runtest', <<"EOF"); # test "logadmw" $envsetup exec $bindir/logadm -f testfile.conf -F testfile.conf -w moose -C 20 -a moose_after_cmd -g pig -m 664 -o cow -p never /moose/file >std.out 2>std.err EOF } ########################################################################### # # logadm1 -- minimal basic test of logadm rotation # ########################################################################### sub logadm1 { set_file('logfile', 'initially logfile'); set_file('logfile.0', 'initially logfile.0'); my ($stdev, $stino, $stmode, $stnlink, $stuid, $stgid, $strdev, $stsize, $statime, $stmtime, $stctime, $stblksize, $stblocks) = lstat 'logfile' or die "lstat logfile: $!\n"; set_file('checktest', <<"EOF"); [ -s std.err ] && { cat std.err; exit 1; } [ -s std.out ] && exit 1 [ -s logfile ] && exit 1 [ -f logfile.0 ] || exit 1 [ "xinitially logfile" = "x`/bin/cat logfile.0`" ] || exit 1 [ "`/bin/ls -i logfile.0 | /bin/awk '{ print \$1; }'`" = "$stino" ] || exit 1 [ -f logfile.1 ] || exit 1 [ "xinitially logfile.0" = "x`/bin/cat logfile.1`" ] || exit 1 exit 0 EOF set_file('runtest', <<"EOF"); # test "logadm1" $envsetup exec $bindir/logadm -f /dev/null -p now logfile >std.out 2>std.err EOF } ########################################################################### # # logadm1c -- same as logadm1 but with -c option # ########################################################################### sub logadm1c { set_file('logfile', 'initially logfile'); set_file('logfile.0', 'initially logfile.0'); my ($stdev, $stino, $stmode, $stnlink, $stuid, $stgid, $strdev, $stsize, $statime, $stmtime, $stctime, $stblksize, $stblocks) = lstat 'logfile' or die "lstat logfile: $!\n"; set_file('checktest', <<"EOF"); [ -s std.err ] && { cat std.err; exit 1; } [ -s std.out ] && exit 1 [ -s logfile ] && exit 1 [ -f logfile.0 ] || exit 1 [ "xinitially logfile" = "x`/bin/cat logfile.0`" ] || exit 1 [ "`/bin/ls -i logfile.0 | /bin/awk '{ print \$1; }'`" = "$stino" ] && exit 1 [ -f logfile.1 ] || exit 1 [ "xinitially logfile.0" = "x`/bin/cat logfile.1`" ] || exit 1 exit 0 EOF set_file('runtest', <<"EOF"); # test "logadm1c" $envsetup exec $bindir/logadm -f /dev/null -p now -c logfile >std.out 2>std.err EOF } ########################################################################### # # logadm2 -- minimal basic test of logadm expiration # ########################################################################### sub logadm2 { set_file('logfile', 'initially logfile'); set_file('logfile.0', 'initially logfile.0'); set_file('logfile.1', 'initially logfile.1'); set_file('checktest', <<'EOF'); [ -s std.err ] && { cat std.err; exit 1; } [ -s std.out ] && exit 1 [ -s logfile ] && exit 1 [ -f logfile.0 ] || exit 1 [ "xinitially logfile" = "x`/bin/cat logfile.0`" ] || exit 1 [ -f logfile.1 ] || exit 1 [ "xinitially logfile.0" = "x`/bin/cat logfile.1`" ] || exit 1 [ -f logfile.2 ] && exit 1 exit 0 EOF set_file('runtest', <<"EOF"); # test "logadm2" $envsetup exec $bindir/logadm -f /dev/null -p now logfile -C2 >std.out 2>std.err EOF } ########################################################################### # # logadm3 -- minimal basic test of logadm pre/post-commands # ########################################################################### sub logadm3 { set_file('logfile', 'initially logfile'); set_file('logfile.0', 'initially logfile.0'); set_file('logfile.1', 'initially logfile.1'); set_file('checktest', <<'EOF'); [ -s std.err ] && { cat std.err; exit 1; } [ -s std.out ] && exit 1 [ -s logfile ] && exit 1 [ -f logfile.0 ] || exit 1 [ "xinitially logfile" = "x`/bin/cat logfile.0`" ] || exit 1 [ -f logfile.1 ] || exit 1 [ "xinitially logfile.0" = "x`/bin/cat logfile.1`" ] || exit 1 [ -f logfile.2 ] && exit 1 [ -f pre.out ] || exit 1 [ "xpre-command-stuff" = "x`/bin/cat pre.out`" ] || exit 1 [ -f post.out ] || exit 1 [ "xpost-command-stuff" = "x`/bin/cat post.out`" ] || exit 1 exit 0 EOF set_file('runtest', <<"EOF"); # test "logadm3" $envsetup exec $bindir/logadm -f /dev/null -p now logfile -C2 -b 'echo pre-command-stuff > pre.out' -a 'echo post-command-stuff > post.out' >std.out 2>std.err EOF } ########################################################################### # # logadm4 -- test of -t template # ########################################################################### sub logadm4 { set_file('logfile', 'initially logfile'); set_file('checktest', <<'EOF'); [ -s std.err ] && { cat std.err; exit 1; } [ -s std.out ] && exit 1 [ -s logfile ] && exit 1 TZ=UTC export TZ d=`/bin/date +%d` [ -f logfile.$d ] || exit 1 [ "xinitially logfile" = "x`/bin/cat logfile.$d`" ] || exit 1 exit 0 EOF set_file('runtest', <<"EOF"); # test "logadm4" $envsetup exec $bindir/logadm -f /dev/null -p now logfile -t '\$file.\%d' >std.out 2>std.err EOF } ########################################################################### # # logadm5 -- test of -R cmd and -E cmd # ########################################################################### sub logadm5 { set_file('logfile', 'initially logfile'); set_file('logfile.0', 'initially logfile.0'); set_file('cmd.out.expect', <<'EOF'); just rotated: initially logfile just expired: initially logfile.0 EOF set_file('checktest', <<'EOF'); [ -s std.err ] && { cat std.err; exit 1; } [ -s std.out ] && exit 1 [ -s logfile ] && exit 1 [ -f logfile.0 ] || exit 1 [ "xinitially logfile" = "x`/bin/cat logfile.0`" ] || exit 1 [ -f logfile.1 ] || exit 1 [ "xinitially logfile.0" = "x`/bin/cat logfile.1`" ] || exit 1 exec /bin/diff cmd.out.expect cmd.out EOF set_file('runtest', <<"EOF"); # test "logadm5" $envsetup exec $bindir/logadm -f /dev/null -p now logfile -C1 -R 'echo just rotated: `/bin/cat \$file` >>cmd.out' -E 'echo just expired: `/bin/cat \$file` >>cmd.out' >std.out 2>std.err EOF } ########################################################################### # # logadm6 -- test of -m, -o, -g # ########################################################################### sub logadm6 { set_file('logfile', 'initially logfile'); set_file('std.err.expect', <<'EOF'); logadm: Warning: command failed: /bin/chown _nonexistentuser_:_nonexistentgroup_ logfile chown: unknown group id _nonexistentgroup_ EOF set_file('checktest', <<'EOF'); [ -s std.err ] || exit 1; [ -s std.out ] && exit 1 [ -s logfile ] && exit 1 [ -f logfile.0 ] || exit 1 [ "xinitially logfile" = "x`/bin/cat logfile.0`" ] || exit 1 [ "`/bin/ls -l logfile | /bin/awk '{ print $1; }'`" = "-r----x--x" ] || exit 1 exec /bin/diff std.err.expect std.err EOF set_file('runtest', <<"EOF"); # test "logadm6" $envsetup exec $bindir/logadm -f /dev/null -p now logfile -m 411 -o _nonexistentuser_ -g _nonexistentgroup_ >std.out 2>std.err EOF } ########################################################################### # # logadm7 -- test running through a conffile # ########################################################################### sub logadm7 { mkdir 'dir1', 0777 or die "mkdir dir1: $!\n"; set_file('dir1/syslog', 'initially dir1/syslog'); set_file('dir1/syslog.0', 'initially dir1/syslog.0'); set_file('dir1/syslog.1', 'initially dir1/syslog.1'); set_file('dir1/syslog.2', 'initially dir1/syslog.2'); set_file('dir1/syslog.3', 'initially dir1/syslog.3'); set_file('dir1/syslog.4', 'initially dir1/syslog.4'); set_file('dir1/syslog.5', 'initially dir1/syslog.5'); set_file('dir1/syslog.6', 'initially dir1/syslog.6'); set_file('dir1/syslog.7', 'initially dir1/syslog.7'); mkdir 'dir2', 0777 or die "mkdir dir2: $!\n"; set_file('dir2/messages', 'initially dir2/messages'); set_file('dir2/messages.0', 'initially dir2/messages.0'); set_file('dir2/messages.1', 'initially dir2/messages.1'); set_file('dir2/messages.2', 'initially dir2/messages.2'); set_file('dir2/messages.3', 'initially dir2/messages.3'); set_file('logadm.conf', <<'EOF'); # # logadm.conf # # this comment # has at least another #-sign in it #... # # Default settings for system log file management. # The -w option to logadm(8) is the preferred way to write to this file, # but if you do edit it by hand, use "logadm -V" to check it for errors. # but if you do edit it by hand, use "logadm -V" to check it for errors. # # The format of lines in this file is: # # For each logname listed here, the default options to logadm # are given. Options given on the logadm command line override # the defaults contained in this file. # # logadm typically runs early every morning via an entry in # root's crontab (see crontab(1)). # dir1/syslog -C 8 -a 'echo kill -HUP `cat /var/run/*syslog*pid` >> cmd.out' dir2/messages -C 4 -a 'echo kill -HUP `cat /var/run/*syslog*pid` >> cmd.out' # # The entry below is used by turnacct(8) # /var/adm/pacct -C 0 -a '/usr/lib/acct/accton pacct' -g adm -m 664 -o adm -p never EOF system("/bin/cp logadm.conf logadm.conf.orig"); $pid=`cat /var/run/*syslog*pid`; chomp $pid; set_file('cmd.out.expect', <<"EOF"); kill -HUP $pid second kill -HUP $pid EOF set_file('sed.out.expect', <<'EOF'); # This file holds internal data for logadm(8). # Do not edit. dir1/syslog dir2/messages EOF set_file('checktest', <<'EOF'); [ -s std.err ] && { cat std.err; exit 1; } [ -s std.out ] && exit 1 [ -s logadm.timestamps ] || exit 1 [ -s std.err2 ] && exit 1 [ -s std.out2 ] && exit 1 [ -s std.err3 ] && exit 1 [ -s std.out3 ] && exit 1 [ -s std.err4 ] && exit 1 [ -s std.out4 ] && exit 1 [ -f dir1/syslog ] || exit 1 [ "xsomething" = "x`/bin/cat dir1/syslog`" ] || exit 1 [ -f dir1/syslog.0 ] || exit 1 [ "xinitially dir1/syslog" = "x`/bin/cat dir1/syslog.0`" ] || exit 1 [ -f dir1/syslog.1 ] || exit 1 [ "xinitially dir1/syslog.0" = "x`/bin/cat dir1/syslog.1`" ] || exit 1 [ -f dir1/syslog.2 ] || exit 1 [ "xinitially dir1/syslog.1" = "x`/bin/cat dir1/syslog.2`" ] || exit 1 [ -f dir1/syslog.3 ] || exit 1 [ "xinitially dir1/syslog.2" = "x`/bin/cat dir1/syslog.3`" ] || exit 1 [ -f dir1/syslog.4 ] || exit 1 [ "xinitially dir1/syslog.3" = "x`/bin/cat dir1/syslog.4`" ] || exit 1 [ -f dir1/syslog.5 ] || exit 1 [ "xinitially dir1/syslog.4" = "x`/bin/cat dir1/syslog.5`" ] || exit 1 [ -f dir1/syslog.6 ] || exit 1 [ "xinitially dir1/syslog.5" = "x`/bin/cat dir1/syslog.6`" ] || exit 1 [ -f dir1/syslog.7 ] || exit 1 [ "xinitially dir1/syslog.6" = "x`/bin/cat dir1/syslog.7`" ] || exit 1 [ -f dir1/syslog.8 ] && exit 1 [ -s dir2/messages ] && exit 1 [ -f dir2/messages.0 ] || exit 1 [ "xsomething" = "x`/bin/cat dir2/messages.0`" ] || exit 1 [ -f dir2/messages.1 ] || exit 1 [ "xinitially dir2/messages" = "x`/bin/cat dir2/messages.1`" ] || exit 1 [ -f dir2/messages.2 ] || exit 1 [ "xinitially dir2/messages.0" = "x`/bin/cat dir2/messages.2`" ] || exit 1 [ -f dir2/messages.3 ] || exit 1 [ "xinitially dir2/messages.1" = "x`/bin/cat dir2/messages.3`" ] || exit 1 [ -f dir2/messages.4 ] && exit 1 /bin/sed "s/ -P '[^']*' *//" < logadm.timestamps > sed.out /bin/diff sed.out.expect sed.out || exit 1 exec /bin/diff logadm.conf.orig logadm.conf EOF # first logadm call will rotate both syslog and messages # second one won't because size is zero # third one won't because of -P timestamps stored in conffile # fourth one will do messages because of -p now on command line set_file('runtest', <<"EOF"); # test "logadm7" $envsetup $bindir/logadm -f logadm.conf -F logadm.timestamps >std.out 2>std.err || exit 1 $bindir/logadm -f logadm.conf -F logadm.timestamps dir1/syslog dir2/messages >std.out2 2>std.err2 || exit 1 echo something > dir1/syslog echo something > dir2/messages $bindir/logadm -f logadm.conf -F logadm.timestamps >std.out3 2>std.err3 || exit 1 exec $bindir/logadm -f logadm.conf -F logadm.timestamps dir2/messages -p now -a 'echo second kill -HUP `cat /var/run/*syslog*pid` >> cmd.out' >std.out4 2>std.err4 EOF } ########################################################################### # # logadm8 -- test of -z # ########################################################################### sub logadm8 { mkdir 'dir1', 0777 or die "mkdir dir1: $!\n"; set_file('dir1/syslog', 'initially dir1/syslog'); set_file('dir1/syslog.0', 'initially dir1/syslog.0'); set_file('dir1/syslog.1', 'initially dir1/syslog.1'); set_file('dir1/syslog.2', 'initially dir1/syslog.2'); system("/bin/gzip dir1/syslog.2"); die "gzip dir1/syslog.2 didn't work\n" unless -f 'dir1/syslog.2.gz'; set_file('dir1/syslog.3', 'initially dir1/syslog.3'); system("/bin/gzip dir1/syslog.3"); die "gzip dir1/syslog.3 didn't work\n" unless -f 'dir1/syslog.3.gz'; set_file('dir1/syslog.4', 'initially dir1/syslog.4'); system("/bin/gzip dir1/syslog.4"); die "gzip dir1/syslog.4 didn't work\n" unless -f 'dir1/syslog.4.gz'; set_file('dir1/syslog.5', 'initially dir1/syslog.5'); system("/bin/gzip dir1/syslog.5"); die "gzip dir1/syslog.5 didn't work\n" unless -f 'dir1/syslog.5.gz'; set_file('dir1/syslog.6', 'initially dir1/syslog.6'); system("/bin/gzip dir1/syslog.6"); die "gzip dir1/syslog.6 didn't work\n" unless -f 'dir1/syslog.6.gz'; set_file('dir1/syslog.7', 'initially dir1/syslog.7'); system("/bin/gzip dir1/syslog.7"); die "gzip dir1/syslog.7 didn't work\n" unless -f 'dir1/syslog.7.gz'; set_file('checktest', <<'EOF'); [ -s std.err ] && { cat std.err; exit 1; } [ -s std.out ] && exit 1 [ -f dir1/syslog ] || exit 1 [ -s dir1/syslog ] && exit 1 [ -f dir1/syslog.0 ] || exit 1 [ "xinitially dir1/syslog" = "x`/bin/cat dir1/syslog.0`" ] || exit 1 [ -f dir1/syslog.1 ] || exit 1 [ "xinitially dir1/syslog.0" = "x`/bin/cat dir1/syslog.1`" ] || exit 1 [ -f dir1/syslog.2.gz ] || exit 1 [ "xinitially dir1/syslog.1" = "x`/bin/gzcat dir1/syslog.2.gz`" ] || exit 1 [ -f dir1/syslog.3.gz ] || exit 1 [ "xinitially dir1/syslog.2" = "x`/bin/gzcat dir1/syslog.3.gz`" ] || exit 1 [ -f dir1/syslog.4.gz ] || exit 1 [ "xinitially dir1/syslog.3" = "x`/bin/gzcat dir1/syslog.4.gz`" ] || exit 1 [ -f dir1/syslog.5.gz ] || exit 1 [ "xinitially dir1/syslog.4" = "x`/bin/gzcat dir1/syslog.5.gz`" ] || exit 1 [ -f dir1/syslog.6.gz ] || exit 1 [ "xinitially dir1/syslog.5" = "x`/bin/gzcat dir1/syslog.6.gz`" ] || exit 1 [ -f dir1/syslog.7.gz ] || exit 1 [ "xinitially dir1/syslog.6" = "x`/bin/gzcat dir1/syslog.7.gz`" ] || exit 1 [ -f dir1/syslog.8 ] && exit 1 [ -f dir1/syslog.8.gz ] && exit 1 exit 0 EOF set_file('runtest', <<"EOF"); # test "logadm8" $envsetup exec $bindir/logadm -f /dev/null dir1/syslog -z 2 -C 8 >std.out 2>std.err EOF } ########################################################################### # # logadm9 -- test of age check # ########################################################################### sub logadm9 { mkdir 'dir1', 0777 or die "mkdir dir1: $!\n"; set_file('dir1/syslog', 'initially dir1/syslog'); set_file('dir1/syslog.0', 'initially dir1/syslog.0'); set_file('dir1/syslog.1', 'initially dir1/syslog.1'); set_file('dir1/syslog.2', 'initially dir1/syslog.2'); set_file('dir1/syslog.3', 'initially dir1/syslog.3'); set_file('dir1/syslog.4', 'initially dir1/syslog.4'); set_file('dir1/syslog.5', 'initially dir1/syslog.5'); set_file('dir1/syslog.6', 'initially dir1/syslog.6'); set_file('dir1/syslog.7', 'initially dir1/syslog.7'); set_file('dir1/notes', 'initially dir1/notes'); mkdir 'dir2', 0777 or die "mkdir dir2: $!\n"; set_file('dir2/messages', 'initially dir2/messages'); set_file('dir2/messages.0', 'initially dir2/messages.0'); set_file('dir2/messages.1', 'initially dir2/messages.1'); set_file('dir2/messages.2', 'initially dir2/messages.2'); set_file('dir2/messages.3', 'initially dir2/messages.3'); set_file('dir2/log', 'initially dir2/log'); $now = time; $nowstr = gmtime($now); # a week minus 30 seconds ago... # technically not a full week, but the heuristic used by logadm # should think this is "close enough" to a full week $closetoweeksecs = $now - (60 * 60 * 24 * 7 - 30); $closetoweek = gmtime($closetoweeksecs); # a week minus six hours ago... $lessthanweeksecs = $now - (60 * 60 * 24 * 7 - 60 * 60 * 6); $lessthanweek = gmtime($lessthanweeksecs); set_file('logadm.conf', <<"EOF"); # now: $nowstr is $now # $closetoweek is $closetoweeksecs dir1/syslog -C 8 -P '$closetoweek' dir2/log -C 4 # $lessthanweek is $lessthanweeksecs dir1/notes -C 2 -P '$lessthanweek' dir2/messages -C 4 EOF set_file('logadm.timestamps', <<"EOF"); dir2/log -P '$closetoweek' dir2/messages -P '$lessthanweek' EOF set_file('sed.out.expect', <<"EOF"); # This file holds internal data for logadm(8). # Do not edit. dir1/syslog dir2/log dir1/notes dir2/messages EOF set_file('checktest', <<'EOF'); [ -s std.err ] && { cat std.err; exit 1; } [ -s std.out ] && exit 1 [ -f dir1/syslog ] || exit 1 [ -s dir1/syslog ] && exit 1 [ -f dir1/syslog.0 ] || exit 1 [ "xinitially dir1/syslog" = "x`/bin/cat dir1/syslog.0`" ] || exit 1 [ -f dir1/syslog.1 ] || exit 1 [ "xinitially dir1/syslog.0" = "x`/bin/cat dir1/syslog.1`" ] || exit 1 [ -f dir1/syslog.2 ] || exit 1 [ "xinitially dir1/syslog.1" = "x`/bin/cat dir1/syslog.2`" ] || exit 1 [ -f dir1/syslog.3 ] || exit 1 [ "xinitially dir1/syslog.2" = "x`/bin/cat dir1/syslog.3`" ] || exit 1 [ -f dir1/syslog.4 ] || exit 1 [ "xinitially dir1/syslog.3" = "x`/bin/cat dir1/syslog.4`" ] || exit 1 [ -f dir1/syslog.5 ] || exit 1 [ "xinitially dir1/syslog.4" = "x`/bin/cat dir1/syslog.5`" ] || exit 1 [ -f dir1/syslog.6 ] || exit 1 [ "xinitially dir1/syslog.5" = "x`/bin/cat dir1/syslog.6`" ] || exit 1 [ -f dir1/syslog.7 ] || exit 1 [ "xinitially dir1/syslog.6" = "x`/bin/cat dir1/syslog.7`" ] || exit 1 [ -f dir1/syslog.8 ] && exit 1 [ -s dir1/notes ] || exit 1 [ "xinitially dir1/notes" = "x`/bin/cat dir1/notes`" ] || exit 1 [ -f dir1/notes.0 ] && exit 1 [ -f dir2/messages ] || exit 1 [ "xinitially dir2/messages" = "x`/bin/cat dir2/messages`" ] || exit 1 [ -f dir2/messages.0 ] || exit 1 [ "xinitially dir2/messages.0" = "x`/bin/cat dir2/messages.0`" ] || exit 1 [ -f dir2/messages.1 ] || exit 1 [ "xinitially dir2/messages.1" = "x`/bin/cat dir2/messages.1`" ] || exit 1 [ -f dir2/messages.2 ] || exit 1 [ "xinitially dir2/messages.2" = "x`/bin/cat dir2/messages.2`" ] || exit 1 [ -f dir2/messages.3 ] || exit 1 [ "xinitially dir2/messages.3" = "x`/bin/cat dir2/messages.3`" ] || exit 1 [ -f dir2/messages.4 ] && exit 1 [ -f dir2/log ] || exit 1 [ -s dir2/log ] && exit 1 [ -f dir2/log.0 ] || exit 1 [ "xinitially dir2/log" = "x`/bin/cat dir2/log.0`" ] || exit 1 [ -f dir2/log.1 ] && exit 1 /bin/sed "s/ -P '[^']*' *//" < logadm.timestamps > sed.out /bin/diff sed.out.expect sed.out || exit 1 /bin/sed -n "s/ -P '[^']*' */<&>/p" < logadm.conf > sed.out [ -s sed.out ] && exit 1 exit 0 EOF set_file('runtest', <<"EOF"); # test "logadm9" $envsetup exec $bindir/logadm -f logadm.conf -F logadm.timestamps >std.out 2>std.err EOF } ########################################################################### # # logadm9d -- test of age check like logadm9, but age is a couple days # ########################################################################### sub logadm9d { mkdir 'dir1', 0777 or die "mkdir dir1: $!\n"; set_file('dir1/syslog', 'initially dir1/syslog'); set_file('dir1/syslog.0', 'initially dir1/syslog.0'); set_file('dir1/syslog.1', 'initially dir1/syslog.1'); set_file('dir1/syslog.2', 'initially dir1/syslog.2'); set_file('dir1/syslog.3', 'initially dir1/syslog.3'); set_file('dir1/syslog.4', 'initially dir1/syslog.4'); set_file('dir1/syslog.5', 'initially dir1/syslog.5'); set_file('dir1/syslog.6', 'initially dir1/syslog.6'); set_file('dir1/syslog.7', 'initially dir1/syslog.7'); mkdir 'dir2', 0777 or die "mkdir dir2: $!\n"; set_file('dir2/messages', 'initially dir2/messages'); set_file('dir2/messages.0', 'initially dir2/messages.0'); set_file('dir2/messages.1', 'initially dir2/messages.1'); set_file('dir2/messages.2', 'initially dir2/messages.2'); set_file('dir2/messages.3', 'initially dir2/messages.3'); $now = time; $nowstr = gmtime($now); # a day minus 30 seconds ago... $closetodaysecs = $now - (60 * 60 * 24 - 30); $closetoday = gmtime($closetodaysecs); # a day minus six hours ago... $lessthandaysecs = $now - (60 * 60 * 24 - 60 * 60 * 6); $lessthanday = gmtime($lessthandaysecs); set_file('logadm.conf', <<"EOF"); # now: $nowstr is $now # $closetoday is $closetodaysecs dir1/syslog -p 1d -C 8 -P '$closetoday' # $lessthanday is $lessthandaysecs dir2/messages -p 1d -C 4 -P '$lessthanday' EOF set_file('checktest', <<'EOF'); [ -s std.err ] && { cat std.err; exit 1; } [ -s std.out ] && exit 1 [ -f dir1/syslog ] || exit 1 [ -s dir1/syslog ] && exit 1 [ -f dir1/syslog.0 ] || exit 1 [ "xinitially dir1/syslog" = "x`/bin/cat dir1/syslog.0`" ] || exit 1 [ -f dir1/syslog.1 ] || exit 1 [ "xinitially dir1/syslog.0" = "x`/bin/cat dir1/syslog.1`" ] || exit 1 [ -f dir1/syslog.2 ] || exit 1 [ "xinitially dir1/syslog.1" = "x`/bin/cat dir1/syslog.2`" ] || exit 1 [ -f dir1/syslog.3 ] || exit 1 [ "xinitially dir1/syslog.2" = "x`/bin/cat dir1/syslog.3`" ] || exit 1 [ -f dir1/syslog.4 ] || exit 1 [ "xinitially dir1/syslog.3" = "x`/bin/cat dir1/syslog.4`" ] || exit 1 [ -f dir1/syslog.5 ] || exit 1 [ "xinitially dir1/syslog.4" = "x`/bin/cat dir1/syslog.5`" ] || exit 1 [ -f dir1/syslog.6 ] || exit 1 [ "xinitially dir1/syslog.5" = "x`/bin/cat dir1/syslog.6`" ] || exit 1 [ -f dir1/syslog.7 ] || exit 1 [ "xinitially dir1/syslog.6" = "x`/bin/cat dir1/syslog.7`" ] || exit 1 [ -f dir1/syslog.8 ] && exit 1 [ -f dir2/messages ] || exit 1 [ "xinitially dir2/messages" = "x`/bin/cat dir2/messages`" ] || exit 1 [ -f dir2/messages.0 ] || exit 1 [ "xinitially dir2/messages.0" = "x`/bin/cat dir2/messages.0`" ] || exit 1 [ -f dir2/messages.1 ] || exit 1 [ "xinitially dir2/messages.1" = "x`/bin/cat dir2/messages.1`" ] || exit 1 [ -f dir2/messages.2 ] || exit 1 [ "xinitially dir2/messages.2" = "x`/bin/cat dir2/messages.2`" ] || exit 1 [ -f dir2/messages.3 ] || exit 1 [ "xinitially dir2/messages.3" = "x`/bin/cat dir2/messages.3`" ] || exit 1 [ -f dir2/messages.4 ] && exit 1 exit 0 EOF set_file('runtest', <<"EOF"); # test "logadm9d" $envsetup exec $bindir/logadm -f logadm.conf -F logadm.timestamps >std.out 2>std.err EOF } ########################################################################### # # logadm10 -- test of size-based rotation check # ########################################################################### sub logadm10 { mkdir 'dir1', 0777 or die "mkdir dir1: $!\n"; set_file('dir1/syslog', 'initially dir1/syslogXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'); set_file('dir1/syslog.0', 'initially dir1/syslog.0'); set_file('dir1/syslog.1', 'initially dir1/syslog.1'); set_file('dir1/syslog.2', 'initially dir1/syslog.2'); set_file('dir1/syslog.3', 'initially dir1/syslog.3'); set_file('dir1/syslog.4', 'initially dir1/syslog.4'); set_file('dir1/syslog.5', 'initially dir1/syslog.5'); set_file('dir1/syslog.6', 'initially dir1/syslog.6'); set_file('dir1/syslog.7', 'initially dir1/syslog.7'); mkdir 'dir2', 0777 or die "mkdir dir2: $!\n"; set_file('dir2/messages', 'initially dir2/messages'); set_file('dir2/messages.0', 'initially dir2/messages.0'); set_file('dir2/messages.1', 'initially dir2/messages.1'); set_file('dir2/messages.2', 'initially dir2/messages.2'); set_file('dir2/messages.3', 'initially dir2/messages.3'); set_file('logadm.conf', <<"EOF"); dir1/syslog -C 8 -s 30b dir2/messages -C 4 -s 30b EOF set_file('checktest', <<'EOF'); [ -s std.err ] && { cat std.err; exit 1; } [ -s std.out ] && exit 1 [ -f dir1/syslog ] || exit 1 [ -s dir1/syslog ] && exit 1 [ -f dir1/syslog.0 ] || exit 1 [ "xinitially dir1/syslogXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" = "x`/bin/cat dir1/syslog.0`" ] || exit 1 [ -f dir1/syslog.1 ] || exit 1 [ "xinitially dir1/syslog.0" = "x`/bin/cat dir1/syslog.1`" ] || exit 1 [ -f dir1/syslog.2 ] || exit 1 [ "xinitially dir1/syslog.1" = "x`/bin/cat dir1/syslog.2`" ] || exit 1 [ -f dir1/syslog.3 ] || exit 1 [ "xinitially dir1/syslog.2" = "x`/bin/cat dir1/syslog.3`" ] || exit 1 [ -f dir1/syslog.4 ] || exit 1 [ "xinitially dir1/syslog.3" = "x`/bin/cat dir1/syslog.4`" ] || exit 1 [ -f dir1/syslog.5 ] || exit 1 [ "xinitially dir1/syslog.4" = "x`/bin/cat dir1/syslog.5`" ] || exit 1 [ -f dir1/syslog.6 ] || exit 1 [ "xinitially dir1/syslog.5" = "x`/bin/cat dir1/syslog.6`" ] || exit 1 [ -f dir1/syslog.7 ] || exit 1 [ "xinitially dir1/syslog.6" = "x`/bin/cat dir1/syslog.7`" ] || exit 1 [ -f dir1/syslog.8 ] && exit 1 [ -f dir2/messages ] || exit 1 [ "xinitially dir2/messages" = "x`/bin/cat dir2/messages`" ] || exit 1 [ -f dir2/messages.0 ] || exit 1 [ "xinitially dir2/messages.0" = "x`/bin/cat dir2/messages.0`" ] || exit 1 [ -f dir2/messages.1 ] || exit 1 [ "xinitially dir2/messages.1" = "x`/bin/cat dir2/messages.1`" ] || exit 1 [ -f dir2/messages.2 ] || exit 1 [ "xinitially dir2/messages.2" = "x`/bin/cat dir2/messages.2`" ] || exit 1 [ -f dir2/messages.3 ] || exit 1 [ "xinitially dir2/messages.3" = "x`/bin/cat dir2/messages.3`" ] || exit 1 [ -f dir2/messages.4 ] && exit 1 exit 0 EOF set_file('runtest', <<"EOF"); # test "logadm10" $envsetup exec $bindir/logadm -f logadm.conf -F logadm.timestamps >std.out 2>std.err EOF } ########################################################################### # # logadm11 -- test of size-based expiration check # ########################################################################### sub logadm11 { mkdir 'dir1', 0777 or die "mkdir dir1: $!\n"; set_file('dir1/syslog', 'initially dir1/syslog'); set_file('dir1/syslog.0', 'initially dir1/syslog.0'); set_file('dir1/syslog.1', 'initially dir1/syslog.1'); set_file('dir1/syslog.2', 'initially dir1/syslog.2'); set_file('dir1/syslog.3', 'initially dir1/syslog.3'); set_file('dir1/syslog.4', 'initially dir1/syslog.4'); set_file('dir1/syslog.5', 'initially dir1/syslog.5'); set_file('dir1/syslog.6', 'initially dir1/syslog.6'); set_file('dir1/syslog.7', 'initially dir1/syslog.7'); mkdir 'dir2', 0777 or die "mkdir dir2: $!\n"; set_file('dir2/messages', 'initially dir2/messages'); set_file('dir2/messages.0', 'initially dir2/messages.0'); set_file('dir2/messages.1', 'initially dir2/messages.1'); set_file('dir2/messages.2', 'initially dir2/messages.2'); set_file('dir2/messages.3', 'initially dir2/messages.3'); set_file('logadm.conf', <<"EOF"); dir1/syslog -C 8 -s 30b -S 75b dir2/messages -C 4 -s 30b -S 75b EOF set_file('checktest', <<'EOF'); [ -s std.err ] && { cat std.err; exit 1; } [ -s std.out ] && exit 1 [ -f dir1/syslog ] || exit 1 [ "xinitially dir1/syslog" = "x`/bin/cat dir1/syslog`" ] || exit 1 [ -f dir1/syslog.0 ] || exit 1 [ "xinitially dir1/syslog.0" = "x`/bin/cat dir1/syslog.0`" ] || exit 1 [ -f dir1/syslog.1 ] || exit 1 [ "xinitially dir1/syslog.1" = "x`/bin/cat dir1/syslog.1`" ] || exit 1 [ -f dir1/syslog.2 ] || exit 1 [ "xinitially dir1/syslog.2" = "x`/bin/cat dir1/syslog.2`" ] || exit 1 [ -f dir1/syslog.3 ] && exit 1 [ -f dir1/syslog.4 ] && exit 1 [ -f dir1/syslog.5 ] && exit 1 [ -f dir1/syslog.6 ] && exit 1 [ -f dir1/syslog.7 ] && exit 1 [ -f dir1/syslog.8 ] && exit 1 [ -f dir2/messages ] || exit 1 [ "xinitially dir2/messages" = "x`/bin/cat dir2/messages`" ] || exit 1 [ -f dir2/messages.0 ] || exit 1 [ "xinitially dir2/messages.0" = "x`/bin/cat dir2/messages.0`" ] || exit 1 [ -f dir2/messages.1 ] || exit 1 [ "xinitially dir2/messages.1" = "x`/bin/cat dir2/messages.1`" ] || exit 1 [ -f dir2/messages.2 ] || exit 1 [ "xinitially dir2/messages.2" = "x`/bin/cat dir2/messages.2`" ] || exit 1 [ -f dir2/messages.3 ] && exit 1 [ -f dir2/messages.4 ] && exit 1 exit 0 EOF set_file('runtest', <<"EOF"); # test "logadm11" $envsetup exec $bindir/logadm -f logadm.conf -F logadm.timestamps >std.out 2>std.err EOF } ########################################################################### # # logadm12 -- ENOENT error path # ########################################################################### sub logadm12 { set_file('std.err.expect', <<'EOF'); logadm: Warning: logfile: No such file or directory EOF set_file('checktest', <<"EOF"); [ -s std.out ] && exit 1 exec /bin/diff std.err.expect std.err EOF set_file('runtest', <<"EOF"); # test "logadm12" $envsetup exec $bindir/logadm -f /dev/null logfile >std.out 2>std.err EOF } ########################################################################### # # logadm13 -- ENOENT error path with -N flag # ########################################################################### sub logadm13 { set_file('checktest', <<"EOF"); [ -s std.err ] && { cat std.err; exit 1; } [ -s std.out ] && exit 1 exit 0 EOF set_file('runtest', <<"EOF"); # test "logadm13" $envsetup exec $bindir/logadm -N -f /dev/null logfile >std.out 2>std.err EOF } ########################################################################### # # logadm14 -- test of -n and -v flags # ########################################################################### sub logadm14 { mkdir 'dir1', 0777 or die "mkdir dir1: $!\n"; set_file('dir1/syslog', 'initially dir1/syslog'); set_file('dir1/syslog.0', 'initially dir1/syslog.0'); set_file('dir1/syslog.1', 'initially dir1/syslog.1'); set_file('dir1/syslog.2', 'initially dir1/syslog.2'); set_file('dir1/syslog.3', 'initially dir1/syslog.3'); set_file('dir1/syslog.4', 'initially dir1/syslog.4'); set_file('dir1/syslog.5', 'initially dir1/syslog.5'); set_file('dir1/syslog.6', 'initially dir1/syslog.6'); set_file('dir1/syslog.7', 'initially dir1/syslog.7'); mkdir 'dir2', 0777 or die "mkdir dir2: $!\n"; set_file('dir2/messages', 'initially dir2/messages'); set_file('dir2/messages.0', 'initially dir2/messages.0'); set_file('dir2/messages.1', 'initially dir2/messages.1'); set_file('dir2/messages.2', 'initially dir2/messages.2'); set_file('dir2/messages.3', 'initially dir2/messages.3'); set_file('logadm.conf', <<'EOF'); # # logadm.conf # # Default settings for system log file management. # The -w option to logadm(8) is the preferred way to write to this file, # but if you do edit it by hand, use "logadm -V" to check it for errors. # but if you do edit it by hand, use "logadm -V" to check it for errors. # # The format of lines in this file is: # # For each logname listed here, the default options to logadm # are given. Options given on the logadm command line override # the defaults contained in this file. # # logadm typically runs early every morning via an entry in # root's crontab (see crontab(1)). # dir1/syslog -C 8 -a 'echo kill -HUP `cat /var/run/*syslog*pid` >> cmd.out' dir2/messages -C 4 -a 'echo kill -HUP `cat /var/run/*syslog*pid` >> cmd.out' # # The entry below is used by turnacct(8) # /var/adm/pacct -C 0 -a '/usr/lib/acct/accton pacct' -g adm -m 664 -o adm -p never EOF $gid = $); $gid =~ s/ .*//; set_file('grep.out.expect', <<'EOF'.<<"EOF".<<'EOF'.<<"EOF".<<'EOF'); # loading logadm.conf # processing logname: dir1/syslog # using default rotate rules: -s1b -p1w # using default template: $file.$n mkdir -p dir1 # verify directory exists mv -f dir1/syslog.7 dir1/syslog.8 # rotate log file mkdir -p dir1 # verify directory exists mv -f dir1/syslog.6 dir1/syslog.7 # rotate log file mkdir -p dir1 # verify directory exists mv -f dir1/syslog.5 dir1/syslog.6 # rotate log file mkdir -p dir1 # verify directory exists mv -f dir1/syslog.4 dir1/syslog.5 # rotate log file mkdir -p dir1 # verify directory exists mv -f dir1/syslog.3 dir1/syslog.4 # rotate log file mkdir -p dir1 # verify directory exists mv -f dir1/syslog.2 dir1/syslog.3 # rotate log file mkdir -p dir1 # verify directory exists mv -f dir1/syslog.1 dir1/syslog.2 # rotate log file mkdir -p dir1 # verify directory exists mv -f dir1/syslog.0 dir1/syslog.1 # rotate log file mkdir -p dir1 # verify directory exists mv -f dir1/syslog dir1/syslog.0 # rotate log file touch dir1/syslog EOF chown $>:$gid dir1/syslog EOF chmod 664 dir1/syslog # processing logname: dir2/messages # using default rotate rules: -s1b -p1w # using default template: $file.$n mkdir -p dir2 # verify directory exists mv -f dir2/messages.3 dir2/messages.4 # rotate log file mkdir -p dir2 # verify directory exists mv -f dir2/messages.2 dir2/messages.3 # rotate log file mkdir -p dir2 # verify directory exists mv -f dir2/messages.1 dir2/messages.2 # rotate log file mkdir -p dir2 # verify directory exists mv -f dir2/messages.0 dir2/messages.1 # rotate log file mkdir -p dir2 # verify directory exists mv -f dir2/messages dir2/messages.0 # rotate log file touch dir2/messages EOF chown $>:$gid dir2/messages EOF chmod 664 dir2/messages # processing logname: /var/adm/pacct # using default template: $file.$n sh -c echo kill -HUP `cat /var/run/*syslog*pid` >> cmd.out # -a cmd # logadm.conf and logadm.timestamps unchanged EOF set_file('checktest', <<'EOF'); [ -s std.err ] && { cat std.err; exit 1; } [ -f std.out ] || exit 1 [ -f dir1/syslog ] || exit 1 [ "xinitially dir1/syslog" = "x`/bin/cat dir1/syslog`" ] || exit 1 [ -f dir1/syslog.0 ] || exit 1 [ "xinitially dir1/syslog.0" = "x`/bin/cat dir1/syslog.0`" ] || exit 1 [ -f dir1/syslog.1 ] || exit 1 [ "xinitially dir1/syslog.1" = "x`/bin/cat dir1/syslog.1`" ] || exit 1 [ -f dir1/syslog.2 ] || exit 1 [ "xinitially dir1/syslog.2" = "x`/bin/cat dir1/syslog.2`" ] || exit 1 [ -f dir1/syslog.3 ] || exit 1 [ "xinitially dir1/syslog.3" = "x`/bin/cat dir1/syslog.3`" ] || exit 1 [ -f dir1/syslog.4 ] || exit 1 [ "xinitially dir1/syslog.4" = "x`/bin/cat dir1/syslog.4`" ] || exit 1 [ -f dir1/syslog.5 ] || exit 1 [ "xinitially dir1/syslog.5" = "x`/bin/cat dir1/syslog.5`" ] || exit 1 [ -f dir1/syslog.6 ] || exit 1 [ "xinitially dir1/syslog.6" = "x`/bin/cat dir1/syslog.6`" ] || exit 1 [ -f dir1/syslog.7 ] || exit 1 [ "xinitially dir1/syslog.7" = "x`/bin/cat dir1/syslog.7`" ] || exit 1 [ -f dir1/syslog.8 ] && exit 1 [ -f dir2/messages ] || exit 1 [ "xinitially dir2/messages" = "x`/bin/cat dir2/messages`" ] || exit 1 [ -f dir2/messages.0 ] || exit 1 [ "xinitially dir2/messages.0" = "x`/bin/cat dir2/messages.0`" ] || exit 1 [ -f dir2/messages.1 ] || exit 1 [ "xinitially dir2/messages.1" = "x`/bin/cat dir2/messages.1`" ] || exit 1 [ -f dir2/messages.2 ] || exit 1 [ "xinitially dir2/messages.2" = "x`/bin/cat dir2/messages.2`" ] || exit 1 [ -f dir2/messages.3 ] || exit 1 [ "xinitially dir2/messages.3" = "x`/bin/cat dir2/messages.3`" ] || exit 1 [ -f dir2/messages.4 ] && exit 1 /bin/grep -v 'recording rotation date' std.out > grep.out exec /bin/diff grep.out.expect grep.out EOF set_file('runtest', <<"EOF"); # test "logadm14" $envsetup exec $bindir/logadm -nv -f logadm.conf -F logadm.timestamps >std.out 2>std.err EOF } ########################################################################### # # logadm15 -- test of -T # ########################################################################### sub logadm15 { set_file('logfile', ''); set_file('logfile.0', 'initially logfile.0'); set_file('logfile.1', 'initially logfile.1'); set_file('logfile.2', 'initially logfile.2'); set_file('logfile.3', 'initially logfile.3'); set_file('logfile.4', 'initially logfile.4'); set_file('logfile.5', 'initially logfile.5'); set_file('logfile.6', 'initially logfile.6'); set_file('logfile.7', 'initially logfile.7'); set_file('logfile.8', 'initially logfile.8'); set_file('logfile.9', 'initially logfile.9'); set_file('checktest', <<'EOF'); [ -s std.err ] && { cat std.err; exit 1; } [ -s std.out ] && exit 1 [ -f logfile ] || exit 1 [ "x" = "x`/bin/cat logfile`" ] || exit 1 [ -f logfile.0 ] || exit 1 [ "xinitially logfile.0" = "x`/bin/cat logfile.0`" ] || exit 1 [ -f logfile.1 ] || exit 1 [ "xinitially logfile.1" = "x`/bin/cat logfile.1`" ] || exit 1 [ -f logfile.2 ] || exit 1 [ "xinitially logfile.2" = "x`/bin/cat logfile.2`" ] || exit 1 [ -f logfile.3 ] && exit 1 [ -f logfile.4 ] || exit 1 [ "xinitially logfile.4" = "x`/bin/cat logfile.4`" ] || exit 1 [ -f logfile.5 ] && exit 1 [ -f logfile.6 ] || exit 1 [ "xinitially logfile.6" = "x`/bin/cat logfile.6`" ] || exit 1 [ -f logfile.7 ] && exit 1 [ -f logfile.8 ] || exit 1 [ "xinitially logfile.8" = "x`/bin/cat logfile.8`" ] || exit 1 [ -f logfile.9 ] && exit 1 [ -f logfile.10 ] && exit 1 exit 0 EOF set_file('runtest', <<"EOF"); # test "logadm15" $envsetup exec $bindir/logadm -f /dev/null logfile -C1 -T '*.[13579]'>std.out 2>std.err EOF } ########################################################################### # # logadm16 -- test of -h # ########################################################################### sub logadm16 { set_file('std.err.expect', <<'EOF'); Usage: logadm [options] (processes all entries in /etc/logadm.conf or conffile given by -f) or: logadm [options] logname... (processes the given lognames) General options: -e mailaddr mail errors to given address -F timestamps use timestamps instead of /var/logadm/timestamps -f conffile use conffile instead of /etc/logadm.conf -h display help -N not an error if log file nonexistent -n show actions, don't perform them -r remove logname entry from conffile -V ensure conffile entries exist, correct -v print info about actions happening -w entryname write entry to config file Options which control when a logfile is rotated: (default is: -s1b -p1w if no -s or -p) -p period only rotate if period passed since last rotate -P timestamp used to store rotation date in conffile -s size only rotate if given size or greater Options which control how a logfile is rotated: (default is: -t '$file.$n', owner/group/mode taken from log file) -a cmd execute cmd after taking actions -b cmd execute cmd before taking actions -c copy & truncate logfile, don't rename -g group new empty log file group -l rotate log file with local time rather than UTC -m mode new empty log file mode -M cmd execute cmd to rotate the log file -o owner new empty log file owner -R cmd run cmd on file after rotate -t template template for naming old logs -z count gzip old logs except most recent count Options which control the expiration of old logfiles: (default is: -C10 if no -A, -C, or -S) -A age expire logs older than age -C count expire old logs until count remain -E cmd run cmd on file to expire -S size expire until space used is below size -T pattern pattern for finding old logs EOF set_file('checktest', <<'EOF'); [ -s std.out ] && exit 1 exec /bin/diff std.err.expect std.err EOF set_file('runtest', <<"EOF"); # test "logadm16" $envsetup exec $bindir/logadm -h >std.out 2>std.err EOF } ########################################################################### # # logadm17 -- test that mkdir -p happens as necessary # ########################################################################### sub logadm17 { set_file('logfile', 'initially logfile'); set_file('checktest', <<'EOF'); [ -s std.err ] && { cat std.err; exit 1; } [ -s std.out ] && exit 1 [ -f dir1/dir2/logfile ] || exit 1 [ -f logfile ] || exit 1 [ "xinitially logfile" = "x`/bin/cat dir1/dir2/logfile`" ] || exit 1 exit 0 EOF set_file('runtest', <<"EOF"); # test "logadm17" $envsetup exec $bindir/logadm -f /dev/null -t 'dir1/dir2/\$basename' logfile -p now >std.out 2>std.err EOF } ########################################################################### # # logadm18 -- test of -M option # ########################################################################### sub logadm18 { mkdir 'dir1', 0777 or die "mkdir dir1: $!\n"; set_file('dir1/syslog', 'initially dir1/syslog'); set_file('dir1/syslog.0', 'initially dir1/syslog.0'); set_file('dir1/syslog.1', 'initially dir1/syslog.1'); set_file('dir1/syslog.2', 'initially dir1/syslog.2'); set_file('dir1/syslog.3', 'initially dir1/syslog.3'); set_file('dir1/syslog.4', 'initially dir1/syslog.4'); set_file('dir1/syslog.5', 'initially dir1/syslog.5'); set_file('dir1/syslog.6', 'initially dir1/syslog.6'); set_file('dir1/syslog.7', 'initially dir1/syslog.7'); set_file('logadm.conf', <<"EOF"); dir1/syslog -C 8 -s 1b -M '/bin/tr [a-z] [A-Z] < \$file > \$nfile; /bin/rm -f \$file' EOF set_file('checktest', <<'EOF'); [ -s std.err ] && { cat std.err; exit 1; } [ -s std.out ] && exit 1 [ -f dir1/syslog ] || exit 1 [ -s dir1/syslog ] && exit 1 [ -f dir1/syslog.0 ] || exit 1 [ "xINITIALLY DIR1/SYSLOG" = "x`/bin/cat dir1/syslog.0`" ] || exit 1 [ -f dir1/syslog.1 ] || exit 1 [ "xinitially dir1/syslog.0" = "x`/bin/cat dir1/syslog.1`" ] || exit 1 [ -f dir1/syslog.2 ] || exit 1 [ "xinitially dir1/syslog.1" = "x`/bin/cat dir1/syslog.2`" ] || exit 1 [ -f dir1/syslog.3 ] || exit 1 [ "xinitially dir1/syslog.2" = "x`/bin/cat dir1/syslog.3`" ] || exit 1 [ -f dir1/syslog.4 ] || exit 1 [ "xinitially dir1/syslog.3" = "x`/bin/cat dir1/syslog.4`" ] || exit 1 [ -f dir1/syslog.5 ] || exit 1 [ "xinitially dir1/syslog.4" = "x`/bin/cat dir1/syslog.5`" ] || exit 1 [ -f dir1/syslog.6 ] || exit 1 [ "xinitially dir1/syslog.5" = "x`/bin/cat dir1/syslog.6`" ] || exit 1 [ -f dir1/syslog.7 ] || exit 1 [ "xinitially dir1/syslog.6" = "x`/bin/cat dir1/syslog.7`" ] || exit 1 [ -f dir1/syslog.8 ] && exit 1 exit 0 EOF set_file('runtest', <<"EOF"); # test "logadm18" $envsetup exec $bindir/logadm -f logadm.conf -F logadm.timestamps >std.out 2>std.err EOF } ############################################################################# # # logadm19 -- test of -l # ############################################################################# sub logadm19 { set_file('logfile', 'initially logfile'); set_file('checktest', <<'EOF'); [ -s std.err ] && { cat std.err; exit 1; } [ -s std.out ] && exit 1 [ -s logfile ] && exit 1 TZ= export TZ d=`/bin/date +\%d\%H\%M` [ -f logfile.$d ] || exit 1 [ "xinitially logfile" = "x`/bin/cat logfile.$d`" ] || exit 1 exit 0 EOF set_file('runtest', <<"EOF"); # test "logadm19" $envsetup exec $bindir/logadm -f /dev/null -l -p now logfile -t '\$file.\%d\%H\%M' >std.out 2>std.err EOF } ############################################################################# # # logadm20 -- test of unquotables/error handling # ############################################################################# sub logadm20 { set_file('logadm.conf', <<'EOF'); # non-trivial entry /var/log/syslog -C 8 -a 'kill -HUP `cat /var/run/*syslog*pid`' EOF set_file('std.err.expect', <<'EOF'); logadm: Error: Can't protect quotes in logadm: Error: unsafe to update configuration file or timestamps logadm: Error: bailing out due to command line errors Use "logadm -h" for help. exit=1 EOF set_file('checktest', <<'EOF'); [ -s std.err ] || exit 1 [ -s std.out ] && exit 1 [ -f logadm.conf????? ] && exit 1 [ -f logadm.timestamps????? ] && exit 1 exec /bin/diff std.err.expect std.err EOF set_file('runtest', <<"EOF"); # test "logadm20" $envsetup $bindir/logadm -f logadm.conf -F logadm.timestamps -w /a/b/c -p 1w -l -b "/bin/echo \\"She can't take anymore, Cap'n!\\"" >std.out 2>std.err echo exit=\$? >>std.err EOF } ############################################################################# # # logadm21 -- test of busted configuration file # ############################################################################# sub logadm21 { set_file('logadm.conf', <<'EOF'); poop EOF set_file('checktest', <<'EOF'); [ -s std.err ] || exit 1 grep Warning std.err > /dev/null EOF set_file('runtest', <<"EOF"); # test "logadm21" $envsetup $bindir/logadm -f logadm.conf -F /dev/null 2>std.err exit 0 EOF } ############################################################################# # # logadm22 - test for keeping timestamps in timestamps file on -w # ############################################################################# sub logadm22 { set_file('logadm.conf', <<'EOF'); wildcard_test -A 3d dir1/*.log regular_test -A 3d dir2/test.log EOF set_file('logadm.conf.expect', <<'EOF'); wildcard_test -A 3d dir1/*.log regular_test -A 3d dir2/test.log dir3/test.log -A 3d EOF set_file('logadm.timestamps', <<'EOF'); # This file holds internal data for logadm(8). # Do not edit. dir1/foo.log -P 'Thu Nov 1 16:56:42 2001' dir2/test.log -P 'Thu Nov 1 16:56:42 2001' EOF system("/bin/cp logadm.timestamps logadm.timestamps.expect"); set_file('checktest', <<'EOF'); [ -s std.err ] && { cat std.err; exit 1; } /bin/diff logadm.conf.expect logadm.conf || exit 1 /bin/diff logadm.timestamps.expect logadm.timestamps || exit 1 EOF set_file('runtest', <<"EOF"); # test "logadm22" $envsetup $bindir/logadm -f logadm.conf -F logadm.timestamps -w dir3/test.log -A 3d 2>std.err exit 0 EOF } ############################################################################# # # logadm23 - test for keeping timestamps in timestamps file on -r # ############################################################################# sub logadm23 { set_file('logadm.conf', <<'EOF'); wildcard_test -A 3d dir1/*.log regular_test -A 3d dir2/test.log EOF set_file('logadm.conf.expect', <<'EOF'); wildcard_test -A 3d dir1/*.log EOF set_file('logadm.timestamps', <<'EOF'); # This file holds internal data for logadm(8). # Do not edit. dir1/foo.log -P 'Thu Nov 1 16:56:42 2001' dir2/test.log -P 'Thu Nov 1 16:56:42 2001' EOF system("/bin/cp logadm.timestamps logadm.timestamps.expect"); set_file('checktest', <<'EOF'); [ -s std.err ] && { cat std.err; exit 1; } /bin/diff logadm.conf.expect logadm.conf || exit 1 /bin/diff logadm.timestamps.expect logadm.timestamps || exit 1 EOF set_file('runtest', <<"EOF"); # test "logadm23" $envsetup $bindir/logadm -f logadm.conf -F logadm.timestamps -r regular_test 2>std.err exit 0 EOF } ########################################################################### # # stderr1 -- ensure verbose stderr does not deadlock # ########################################################################### sub stderr1 { set_file('logfile', 'initially logfile'); set_file('std.err.uniq.expect', <<'EOF'); 1 logadm: Warning: command failed: /bin/sh -c exec 1>&2; for i in {1..250000}; do echo pre-command-stuff; done 250000 pre-command-stuff 1 logadm: Warning: command failed: /bin/sh -c exec 1>&2; for i in {1..250000}; do echo post-command-stuff; done 250000 post-command-stuff EOF set_file('checktest', <<'EOF'); [ -s std.out ] && exit 1 /bin/diff -u std.err.uniq.expect std.err.uniq || exit 1 exit 0 EOF # The output redirection below looks wrong, but it is not. The redirect # of stderr before the redirect of stdout causes stderr to be piped to # uniq. set_file('runtest', <<"EOF"); # test "stderr1" $envsetup exec $bindir/logadm -f /dev/null -p now logfile \\ -b 'exec 1>&2; for i in {1..250000}; do echo pre-command-stuff; done' \\ -a 'exec 1>&2; for i in {1..250000}; do echo post-command-stuff; done' \\ 2>&1 >std.out | uniq -c >std.err.uniq EOF }