changeset 12998:04c3fb904c79

4685009 logadm(1M) should avoid silent updates of /etc/logadm.conf
author John.Zolnowsky@Sun.COM
date Mon, 02 Aug 2010 10:54:24 -0700
parents 82a41a56372e
children 9e5c3f16523e
files usr/src/Targetdirs usr/src/cmd/logadm/Makefile usr/src/cmd/logadm/conf.c usr/src/cmd/logadm/conf.h usr/src/cmd/logadm/err.c usr/src/cmd/logadm/err.h usr/src/cmd/logadm/main.c usr/src/cmd/logadm/opts.c usr/src/cmd/logadm/opts.h usr/src/cmd/logadm/tester usr/src/pkg/manifests/SUNWcs.mf
diffstat 11 files changed, 628 insertions(+), 334 deletions(-) [+]
line wrap: on
line diff
--- a/usr/src/Targetdirs	Mon Aug 02 08:40:07 2010 -0400
+++ b/usr/src/Targetdirs	Mon Aug 02 10:54:24 2010 -0700
@@ -362,6 +362,7 @@
 	/var/ld \
 	/var/log \
 	/var/log/pool \
+	/var/logadm \
 	/var/mail \
 	/var/news \
 	/var/opt \
--- a/usr/src/cmd/logadm/Makefile	Mon Aug 02 08:40:07 2010 -0400
+++ b/usr/src/cmd/logadm/Makefile	Mon Aug 02 10:54:24 2010 -0700
@@ -56,7 +56,7 @@
 all: $(PROG)
 
 test: $(TESTS) $(PROG)
-	./tester `pwd`
+	$(PERL) -w ./tester `pwd`
 
 $(PROG): $(OBJS)
 	$(LINK.c) -o $@ $(OBJS) $(LDLIBS)
--- a/usr/src/cmd/logadm/conf.c	Mon Aug 02 08:40:07 2010 -0400
+++ b/usr/src/cmd/logadm/conf.c	Mon Aug 02 10:54:24 2010 -0700
@@ -20,12 +20,9 @@
  */
 
 /*
- * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
- * Use is subject to license terms.
+ * Copyright (c) 2001, 2010, Oracle and/or its affiliates. All rights reserved.
  */
 
-#pragma ident	"%Z%%M%	%I%	%E% SMI"
-
 /*
  * logadm/conf.c -- configuration file module
  */
@@ -40,6 +37,7 @@
 #include <strings.h>
 #include <unistd.h>
 #include <stdlib.h>
+#include <limits.h>
 #include "err.h"
 #include "lut.h"
 #include "fn.h"
@@ -47,17 +45,27 @@
 #include "conf.h"
 
 /* forward declarations of functions private to this module */
-static void fillconflist(int lineno, const char *entry, char **args,
+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 *stream);
+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 area */
-static int Conffd = -1;		/* file descriptor for config file */
-static boolean_t Confchanged;	/* true if we need to write changes back */
+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
@@ -67,7 +75,6 @@
 	struct confinfo *cf_next;
 	int cf_lineno;		/* line number in file */
 	const char *cf_entry;	/* name of entry, if line has an entry */
-	char **cf_args;		/* raw rhs of entry */
 	struct opts *cf_opts;	/* parsed rhs of entry */
 	const char *cf_com;	/* any comment text found */
 	int cf_flags;
@@ -82,7 +89,7 @@
 
 /* allocate & fill in another entry in our list */
 static void
-fillconflist(int lineno, const char *entry, char **args,
+fillconflist(int lineno, const char *entry,
     struct opts *opts, const char *com, int flags)
 {
 	struct confinfo *cp = MALLOC(sizeof (*cp));
@@ -90,7 +97,6 @@
 	cp->cf_next = NULL;
 	cp->cf_lineno = lineno;
 	cp->cf_entry = entry;
-	cp->cf_args = args;
 	cp->cf_opts = opts;
 	cp->cf_com = com;
 	cp->cf_flags = flags;
@@ -163,54 +169,33 @@
 }
 
 /*
- * conf_open -- open the configuration file, lock it if we have write perms
+ * scan the memory image of a file
+ *	returns: 0: error, 1: ok, 3: -P option found
  */
-void
-conf_open(const char *fname, int needwrite)
+static int
+conf_scan(const char *fname, char *buf, int buflen, int timescan,
+    struct opts *cliopts)
 {
-	struct stat stbuf;
+	int ret = 1;
 	int lineno = 0;
 	char *line;
 	char *eline;
 	char *ebuf;
-	char *comment;
+	char *entry, *comment;
 
-	Confname = fname;
-	Confentries = fn_list_new(NULL);
-
-	/* special case this so we don't even try locking the file */
-	if (strcmp(Confname, "/dev/null") == 0)
-		return;
+	ebuf = &buf[buflen];
 
-	if ((Conffd = open(Confname, (needwrite) ? O_RDWR : O_RDONLY)) < 0)
-		err(EF_SYS, "%s", Confname);
-
-	if (fstat(Conffd, &stbuf) < 0)
-		err(EF_SYS, "fstat on %s", Confname);
-
-	if (needwrite && lockf(Conffd, F_LOCK, 0) < 0)
-		err(EF_SYS, "lockf on %s", Confname);
+	if (buf[buflen - 1] != '\n')
+		err(EF_WARN|EF_FILE, "file %s doesn't end with newline, "
+		    "last line ignored.", fname);
 
-	if (stbuf.st_size == 0)
-		return;	/* empty file, don't bother parsing it */
-
-	if ((Confbuf = (char *)mmap(0, stbuf.st_size,
-	    PROT_READ | PROT_WRITE, MAP_PRIVATE, Conffd, 0)) == (char *)-1)
-		err(EF_SYS, "mmap on %s", Confname);
-
-	Conflen = stbuf.st_size;
-	Confchanged = B_FALSE;
+	for (line = buf; line < ebuf; line = eline) {
+		char *ap;
+		struct opts *opts = NULL;
+		struct confinfo *cp;
 
-	ebuf = &Confbuf[Conflen];
-
-	if (Confbuf[Conflen - 1] != '\n')
-		err(EF_WARN|EF_FILE, "config file doesn't end with "
-		    "newline, last line ignored.");
-
-	line = Confbuf;
-	while (line < ebuf) {
 		lineno++;
-		err_fileline(Confname, lineno);
+		err_fileline(fname, lineno);
 		eline = line;
 		comment = NULL;
 		for (; eline < ebuf; eline++) {
@@ -220,7 +205,7 @@
 				*eline = ' ';
 				*(eline + 1) = ' ';
 				lineno++;
-				err_fileline(Confname, lineno);
+				err_fileline(fname, lineno);
 				continue;
 			}
 
@@ -237,64 +222,213 @@
 		}
 		if (comment >= ebuf)
 			comment = NULL;
-		if (eline < ebuf) {
-			char *entry;
+		if (eline >= ebuf) {
+			/* discard trailing unterminated line */
+			continue;
+		}
+		*eline++ = '\0';
 
-			*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) {
 			/*
-			 * now we have the entry, if any, at "line"
-			 * and the comment, if any, at "comment"
+			 * 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.
 			 */
-
-			/* entry is first token */
 			if ((entry = nexttok(&line)) != NULL &&
-			    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.",
-					    Confname);
-			} else if (entry) {
-				char *ap;
-				char **args;
-				int i;
+			    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);
+		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) {
+					/* orphaned entry */
+					if (opts_count(cliopts, "v"))
+						err(EF_FILE, "stale timestamp "
+						    "for %s", entry);
+					LOCAL_ERR_BREAK;
+				}
+				opts = cp->cf_opts;
+			}
+			opts = opts_parse(opts, Args, OPTF_CONF);
+			if (!timescan) {
+				fillconflist(lineno, entry, opts, comment, 0);
+			}
+		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);
+		}
 
-				ArgsI = 0;
-				while (ap = nexttok(&line))
-					fillargs(ap);
-				if (ArgsI == 0) {
-					/* short entry allowed */
-					fillconflist(lineno, entry,
-					    NULL, NULL, comment, 0);
-				} else {
-					Args[ArgsI++] = NULL;
-					args = MALLOC(sizeof (char *) * ArgsI);
-					for (i = 0; i < ArgsI; i++)
-						args[i] = Args[i];
-					fillconflist(lineno, entry,
-					    args, NULL, comment, 0);
-				}
-			} else
-				fillconflist(lineno, entry, NULL, NULL,
-				    comment, 0);
+		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);
 		}
-		line = eline;
+
+		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, cliopts);
+	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, cliopts);
+	}
+
 	/*
 	 * possible future enhancement:  go through and mark any entries:
 	 * 		logfile -P <date>
 	 * as DELETED if the logfile doesn't exist
 	 */
+
+	return (ret);
 }
 
 /*
@@ -303,35 +437,100 @@
 void
 conf_close(struct opts *opts)
 {
-	FILE *fp;
+	char cuname[PATH_MAX], tuname[PATH_MAX];
+	int cfd, tfd;
+	FILE *cfp = NULL, *tfp = NULL;
+	boolean_t safe_update = B_TRUE;
 
-	if (Confchanged && opts_count(opts, "n") == 0 && Conffd != -1) {
+	if (Changed == CHG_NONE || opts_count(opts, "n") != 0) {
 		if (opts_count(opts, "v"))
-			(void) out("# writing changes to %s\n", Confname);
-		if (Debug > 1) {
-			(void) fprintf(stderr, "conf_close, %s changed to:\n",
-			    Confname);
-			conf_print(stderr);
-		}
-		if (lseek(Conffd, (off_t)0, SEEK_SET) < 0)
-			err(EF_SYS, "lseek on %s", Confname);
-		if (ftruncate(Conffd, (off_t)0) < 0)
-			err(EF_SYS, "ftruncate on %s", Confname);
-		if ((fp = fdopen(Conffd, "w")) == NULL)
-			err(EF_SYS, "fdopen on %s", Confname);
-		conf_print(fp);
-		if (fclose(fp) < 0)
-			err(EF_SYS, "fclose on %s", Confname);
-		Conffd = -1;
-		Confchanged = B_FALSE;
-	} else if (opts_count(opts, "v")) {
-		(void) out("# %s unchanged\n", Confname);
+			(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;
@@ -345,16 +544,14 @@
 /*
  * conf_lookup -- lookup an entry in the config file
  */
-char **
+void *
 conf_lookup(const char *lhs)
 {
 	struct confinfo *cp = lut_lookup(Conflut, lhs);
 
-	if (cp != NULL) {
+	if (cp != NULL)
 		err_fileline(Confname, cp->cf_lineno);
-		return (cp->cf_args);
-	} else
-		return (NULL);
+	return (cp);
 }
 
 /*
@@ -365,14 +562,9 @@
 {
 	struct confinfo *cp = lut_lookup(Conflut, lhs);
 
-	if (cp != NULL) {
-		if (cp->cf_opts)
-			return (cp->cf_opts);	/* already parsed */
-		err_fileline(Confname, cp->cf_lineno);
-		cp->cf_opts = opts_parse(cp->cf_args, OPTF_CONF);
+	if (cp != NULL)
 		return (cp->cf_opts);
-	}
-	return (opts_parse(NULL, OPTF_CONF));
+	return (opts_parse(NULL, NULL, OPTF_CONF));
 }
 
 /*
@@ -388,12 +580,13 @@
 
 	if (cp != NULL) {
 		cp->cf_opts = newopts;
-		cp->cf_args = NULL;
+		/* cp->cf_args = NULL; */
 		if (newopts == NULL)
 			cp->cf_flags |= CONFF_DELETED;
 	} else
-		fillconflist(0, lhs, NULL, newopts, NULL, 0);
-	Confchanged = B_TRUE;
+		fillconflist(0, lhs, newopts, NULL, 0);
+
+	Changed = CHG_BOTH;
 }
 
 /*
@@ -408,17 +601,18 @@
 		return;
 
 	if (cp != NULL) {
-		if (cp->cf_opts == NULL)
-			cp->cf_opts = opts_parse(cp->cf_args, OPTF_CONF);
 		cp->cf_flags &= ~CONFF_DELETED;
 	} else {
-		fillconflist(0, STRDUP(entry), NULL,
-		    opts_parse(NULL, OPTF_CONF), NULL, 0);
+		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);
-	Confchanged = B_TRUE;
+	if (strcmp(o, "P") == 0)
+		Changed |= CHG_TIMES;
+	else
+		Changed = CHG_BOTH;
 }
 
 /*
@@ -432,33 +626,41 @@
 
 /* print the config file */
 static void
-conf_print(FILE *stream)
+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(1M).\n"
+		    "# Do not edit.\n"));
+	}
 	for (cp = Confinfo; cp; cp = cp->cf_next) {
 		if (cp->cf_flags & CONFF_DELETED)
 			continue;
 		if (cp->cf_entry) {
-			char **p;
-
-			opts_printword(cp->cf_entry, stream);
-			if (cp->cf_opts) {
-				/* existence of opts overrides args */
-				opts_print(cp->cf_opts, stream, "fhnrvVw");
-			} else if (cp->cf_args) {
-				for (p = cp->cf_args; *p; p++) {
-					(void) fprintf(stream, " ");
-					opts_printword(*p, stream);
-				}
+			opts_printword(cp->cf_entry, cstream);
+			if (cp->cf_opts)
+				opts_print(cp->cf_opts, cstream, exclude_opts);
+			/* 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_com) {
 			if (cp->cf_entry)
-				(void) fprintf(stream, " ");
-			(void) fprintf(stream, "#%s", cp->cf_com);
+				(void) fprintf(cstream, " ");
+			(void) fprintf(cstream, "#%s", cp->cf_com);
 		}
-		(void) fprintf(stream, "\n");
+		(void) fprintf(cstream, "\n");
 	}
 }
 
@@ -470,18 +672,23 @@
 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]);
 
-	conf_open(argv[1], 1);
+	conf_open(argv[1], argv[1], opts);
 
 	printf("conffile <%s>:\n", argv[1]);
-	conf_print(stdout);
+	conf_print(stdout, NULL);
 
-	conf_close(opts_parse(NULL, 0));
+	conf_close(opts);
 
 	err_done(0);
 	/* NOTREACHED */
--- a/usr/src/cmd/logadm/conf.h	Mon Aug 02 08:40:07 2010 -0400
+++ b/usr/src/cmd/logadm/conf.h	Mon Aug 02 10:54:24 2010 -0700
@@ -2,9 +2,8 @@
  * 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.
+ * 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.
@@ -20,8 +19,7 @@
  * CDDL HEADER END
  */
 /*
- * Copyright (c) 2001 by Sun Microsystems, Inc.
- * All rights reserved.
+ * Copyright (c) 2001, 2010, Oracle and/or its affiliates. All rights reserved.
  *
  * logadm/conf.h -- public definitions for conf module
  */
@@ -29,15 +27,13 @@
 #ifndef	_LOGADM_CONF_H
 #define	_LOGADM_CONF_H
 
-#pragma ident	"%Z%%M%	%I%	%E% SMI"
-
 #ifdef	__cplusplus
 extern "C" {
 #endif
 
-void conf_open(const char *fname, int needwrite);
+int conf_open(const char *cfname, const char *tfname, struct opts *opts);
 void conf_close(struct opts *opts);
-char **conf_lookup(const char *lhs);
+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);
--- a/usr/src/cmd/logadm/err.c	Mon Aug 02 08:40:07 2010 -0400
+++ b/usr/src/cmd/logadm/err.c	Mon Aug 02 10:54:24 2010 -0700
@@ -19,15 +19,12 @@
  * CDDL HEADER END
  */
 /*
- * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
- * Use is subject to license terms.
+ * Copyright (c) 2001, 2010, Oracle and/or its affiliates. All rights reserved.
  *
  * logadm/err.c -- some basic error routines
  *
  */
 
-#pragma ident	"%Z%%M%	%I%	%E% SMI"
-
 #include <stdio.h>
 #include <unistd.h>
 #include <libintl.h>
@@ -37,6 +34,7 @@
 #include <errno.h>
 #include "err.h"
 
+jmp_buf	*Err_env_ptr;
 static const char *Myname;
 static int Exitcode;
 static FILE *Errorfile;
@@ -143,7 +141,7 @@
 	va_end(ap);
 
 	if (jump)
-		longjmp(Err_env, 1);
+		longjmp(*Err_env_ptr, 1);
 
 	if (!warning && !fileline) {
 		err_done(1);
--- a/usr/src/cmd/logadm/err.h	Mon Aug 02 08:40:07 2010 -0400
+++ b/usr/src/cmd/logadm/err.h	Mon Aug 02 10:54:24 2010 -0700
@@ -2,9 +2,8 @@
  * 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.
+ * 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.
@@ -20,8 +19,7 @@
  * CDDL HEADER END
  */
 /*
- * Copyright (c) 2001 by Sun Microsystems, Inc.
- * All rights reserved.
+ * Copyright (c) 2001, 2010, Oracle and/or its affiliates. All rights reserved.
  *
  * logadm/err.h -- public definitions for error module
  */
@@ -29,8 +27,6 @@
 #ifndef	_LOGADM_ERR_H
 #define	_LOGADM_ERR_H
 
-#pragma ident	"%Z%%M%	%I%	%E% SMI"
-
 #include <setjmp.h>
 
 #ifdef	__cplusplus
@@ -55,8 +51,12 @@
 #define	EF_RAW	0x10	/* don't prepend/append anything to message */
 
 jmp_buf Err_env;
+extern jmp_buf *Err_env_ptr;
 
-#define	SETJMP	setjmp(Err_env)
+#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);
--- a/usr/src/cmd/logadm/main.c	Mon Aug 02 08:40:07 2010 -0400
+++ b/usr/src/cmd/logadm/main.c	Mon Aug 02 10:54:24 2010 -0700
@@ -19,8 +19,7 @@
  * CDDL HEADER END
  */
 /*
- * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
- * Use is subject to license terms.
+ * Copyright (c) 2001, 2010, Oracle and/or its affiliates. All rights reserved.
  *
  * logadm/main.c -- main routines for logadm
  *
@@ -66,6 +65,8 @@
 
 /* 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";
@@ -92,40 +93,8 @@
 /* A list of names of files to be gzipped */
 static struct lut *Gzipnames = NULL;
 
-/* table that drives argument parsing */
-static struct optinfo Opttable[] = {
-	{ "e", OPTTYPE_STRING,	NULL,			OPTF_CLI|OPTF_CONF },
-	{ "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 },
-};
-
 /*
- * only the "fhnVv" options are allowed in the first form of this command,
+ * 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.
@@ -141,6 +110,7 @@
 "\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"\
@@ -188,10 +158,12 @@
 {
 	struct opts *clopts;		/* 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, "");
 
@@ -201,7 +173,7 @@
 
 	(void) textdomain(TEXT_DOMAIN);
 
-	/* we only print times into the conffile, so make them uniform */
+	/* 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 */
@@ -221,6 +193,8 @@
 	/* 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"))
@@ -240,13 +214,13 @@
 	if (val = getenv("_LOGADM_MKDIR"))
 		Mkdir = val;
 
-	opts_init(Opttable, sizeof (Opttable) / sizeof (struct optinfo));
+	opts_init(Opttable, Opttable_cnt);
 
 	/* parse command line arguments */
 	if (SETJMP)
 		usage("bailing out due to command line errors");
 	else
-		clopts = opts_parse(argv, OPTF_CLI);
+		clopts = opts_parse(NULL, argv, OPTF_CLI);
 
 	if (Debug) {
 		(void) fprintf(stderr, "command line opts:");
@@ -327,12 +301,16 @@
 	if (opts_count(clopts, "e"))
 		err_mailto(opts_optarg(clopts, "e"));
 
-	/* this implements the default conffile */
+	/* 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);
-	conf_open(conffile, opts_count(clopts, "Vn") == 0);
+	status = conf_open(conffile, timestamps, clopts);
+	if (!status && opts_count(clopts, "V"))
+		err_done(0);
 
 	/* handle conffile write option */
 	if (opts_count(clopts, "w")) {
--- a/usr/src/cmd/logadm/opts.c	Mon Aug 02 08:40:07 2010 -0400
+++ b/usr/src/cmd/logadm/opts.c	Mon Aug 02 10:54:24 2010 -0700
@@ -20,12 +20,9 @@
  */
 
 /*
- * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
- * Use is subject to license terms.
+ * Copyright (c) 2001, 2010, Oracle and/or its affiliates. All rights reserved.
  */
 
-#pragma ident	"%Z%%M%	%I%	%E% SMI"
-
 /*
  * logadm/opts.c -- options handling routines
  */
@@ -55,8 +52,48 @@
 	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
  */
@@ -87,24 +124,26 @@
  * prints a message to stderr and calls err(EF_FILE|EF_JMP, ...) on error
  */
 struct opts *
-opts_parse(char **argv, int flags)
+opts_parse(struct opts *opts, char **argv, int flags)
 {
-	struct opts *ret = MALLOC(sizeof (*ret));
 	int dashdash = 0;
 	char *ptr;
 
-	ret->op_raw = ret->op_ints = NULL;
-	ret->op_cmdargs = fn_list_new(NULL);
+	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 (ret);
+		return (opts);
 
 	/* foreach word... */
 	for (; (ptr = *argv) != NULL; argv++) {
 		if (dashdash || *ptr != '-') {
 			/* found a cmdarg */
-			opts_setcmdarg(ret, ptr);
+			opts_setcmdarg(opts, ptr);
 			continue;
 		}
 		if (*++ptr == '\0')
@@ -138,7 +177,7 @@
 
 			/* for boolean options, we have all the info we need */
 			if (info->oi_t == OPTTYPE_BOOLEAN) {
-				(void) opts_set(ret, info->oi_o, "");
+				(void) opts_set(opts, info->oi_o, "");
 				continue;
 			}
 
@@ -148,12 +187,12 @@
 				err(EF_FILE|EF_JMP,
 				    "Option '%c' requires an argument",
 				    info->oi_o[0]);
-			opts_set(ret, info->oi_o, ptr);
+			opts_set(opts, info->oi_o, ptr);
 			break;
 		}
 	}
 
-	return (ret);
+	return (opts);
 }
 
 /*
@@ -277,7 +316,7 @@
 /*
  * opts_parse_ctime -- parse a ctime format optarg
  */
-off_t
+static off_t
 opts_parse_ctime(const char *o, const char *optarg)
 {
 	struct tm tm;
@@ -297,7 +336,7 @@
 /*
  * opts_parse_atopi -- parse a positive integer format optarg
  */
-off_t
+static off_t
 opts_parse_atopi(const char *o, const char *optarg)
 {
 	off_t ret = atoll(optarg);
@@ -315,7 +354,7 @@
 /*
  * opts_parse_atopi -- parse a size format optarg into bytes
  */
-off_t
+static off_t
 opts_parse_bytes(const char *o, const char *optarg)
 {
 	off_t ret = atoll(optarg);
@@ -350,7 +389,7 @@
 /*
  * opts_parse_seconds -- parse a time format optarg into seconds
  */
-off_t
+static off_t
 opts_parse_seconds(const char *o, const char *optarg)
 {
 	off_t ret;
@@ -436,13 +475,13 @@
 		    strchr(word, '$') || strchr(word, '[') ||
 		    strchr(word, '?') || strchr(word, '{') ||
 		    strchr(word, '`') || strchr(word, ';')) {
-			if (strchr(word, '\''))
+			if (strchr(word, '\'') == NULL)
+				q = "'";
+			else if (strchr(word, '"') == NULL)
 				q = "\"";
-			else if (strchr(word, '"'))
+			else
 				err(EF_FILE|EF_JMP,
 				    "Can't protect quotes in <%s>", word);
-			else
-				q = "'";
 			(void) fprintf(stream, "%s%s%s", q, word, q);
 		} else
 			(void) fprintf(stream, "%s", word);
@@ -474,7 +513,7 @@
 #ifdef	TESTMODULE
 
 /* table that drives argument parsing */
-static struct optinfo Opttable[] = {
+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 },
@@ -494,14 +533,15 @@
 	err_init(argv[0]);
 	setbuf(stdout, NULL);
 
-	opts_init(Opttable, sizeof (Opttable) / sizeof (struct optinfo));
+	opts_init(Testopttable,
+	    sizeof (Testopttable) / sizeof (struct optinfo));
 
 	argv++;
 
 	if (SETJMP)
 		err(0, "opts parsing failed");
 	else
-		opts = opts_parse(argv, OPTF_CLI);
+		opts = opts_parse(NULL, argv, OPTF_CLI);
 
 	printf("options:");
 	opts_print(opts, stdout, NULL);
--- a/usr/src/cmd/logadm/opts.h	Mon Aug 02 08:40:07 2010 -0400
+++ b/usr/src/cmd/logadm/opts.h	Mon Aug 02 10:54:24 2010 -0700
@@ -19,8 +19,7 @@
  * CDDL HEADER END
  */
 /*
- * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
- * Use is subject to license terms.
+ * Copyright (c) 2001, 2010, Oracle and/or its affiliates. All rights reserved.
  *
  * logadm/opts.h -- public definitions for opts module
  */
@@ -28,8 +27,6 @@
 #ifndef	_LOGADM_OPTS_H
 #define	_LOGADM_OPTS_H
 
-#pragma ident	"%Z%%M%	%I%	%E% SMI"
-
 #ifdef	__cplusplus
 extern "C" {
 #endif
@@ -57,7 +54,7 @@
 #define	OPTF_CONF	2
 
 void opts_init(struct optinfo *table, int numentries);
-struct opts *opts_parse(char **args, int flags);
+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);
@@ -69,14 +66,12 @@
 #define	OPTP_NOW (-1)
 #define	OPTP_NEVER (-2)
 
-off_t opts_parse_ctime(const char *o, const char *optarg);
-off_t opts_parse_bytes(const char *o, const char *optarg);
-off_t opts_parse_atopi(const char *o, const char *optarg);
-off_t opts_parse_seconds(const char *o, const char *optarg);
-
 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
--- a/usr/src/cmd/logadm/tester	Mon Aug 02 08:40:07 2010 -0400
+++ b/usr/src/cmd/logadm/tester	Mon Aug 02 10:54:24 2010 -0700
@@ -20,10 +20,7 @@
 # CDDL HEADER END
 #
 #
-# Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
-# Use is subject to license terms.
-#
-#ident	"%Z%%M%	%I%	%E% SMI"
+# Copyright (c) 2001, 2010, Oracle and/or its affiliates. All rights reserved.
 #
 #
 # tester - run logadm tests
@@ -91,6 +88,7 @@
 	"logadm17",
 	"logadm18",
 	"logadm19",
+	"logadm20",
 );
 
 use Getopt::Std;
@@ -191,11 +189,11 @@
 			print STDERR "  to do a fresh setup of this test.\n";
 			exit 1;
 		}
-		eval "runner('checktest')";
+		eval "runner('checktest', '-x', '> checktest.out 2>&1')";
 		if ($@) {
 			print " CHECKTEST FAILURE\n";
 			print STDERR "$myname: ERROR: $@";
-			print STDERR "results captured in directory $dir\n";
+			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;
@@ -223,8 +221,9 @@
 # is actually checking the program under test and not /bin/sh
 #
 sub runner {
-	my $cmd = shift;
-	my $fullcmd = "/bin/sh $cmd";
+	my ($cmd, $prefix, $suffix) = (@_, '', '');
+
+	my $fullcmd = "/bin/sh $prefix $cmd $suffix";
 	my $rc = 0xffff & system("$fullcmd");
 
 	if ($rc == 0) {
@@ -309,9 +308,9 @@
 	set_testconffile;
 
 	set_file('checktest', <<'EOF');
-[ -s std.err ] && exit 1
+[ -s std.err ] && { cat std.err; exit 1; }
 /bin/sed '/^conffile <testfile.conf>:$/d' <std.out >sed.out
-exec /bin/diff sed.out testfile.conf
+exec /bin/diff testfile.conf sed.out
 EOF
 
 	set_file('runtest', <<"EOF");
@@ -330,11 +329,11 @@
 	set_file('testfile.conf', 'line fragment');
 
 	set_file('std.err.expect', <<'EOF');
-conftest: Warning: config file doesn't end with newline, last line ignored.
+conftest: Warning: file testfile.conf doesn't end with newline, last line ignored.
 EOF
 
 	set_file('checktest', <<'EOF');
-exec /bin/diff std.err std.err.expect
+exec /bin/diff std.err.expect std.err
 EOF
 
 	set_file('runtest', <<"EOF");
@@ -544,8 +543,8 @@
 EOF
 
 	set_file('checktest', <<'EOF');
-[ -s std.err ] && exit 1
-exec /bin/diff std.out std.out.expect
+[ -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\'';
@@ -568,7 +567,7 @@
 EOF
 
 	set_file('checktest', <<'EOF');
-exec /bin/diff std.err std.err.expect
+exec /bin/diff std.err.expect std.err
 EOF
 
 	set_file('runtest', <<"EOF");
@@ -634,12 +633,12 @@
 EOF
 
 	set_file('checktest', <<'EOF');
-[ -s std.err ] && exit 1
+[ -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"\
 	<std.out >sed.out
-exec /bin/diff sed.out sed.out.expect
+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\'';
@@ -705,12 +704,12 @@
 EOF
 
 	set_file('checktest', <<'EOF');
-[ -s std.err ] && exit 1
+[ -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"\
 	<std.out >sed.out
-exec /bin/diff sed.out sed.out.expect
+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\'';
@@ -723,6 +722,8 @@
 export HOME
 USER=
 export USER
+TZ=UTC
+export TZ
 exec $bindir/$kwtest >std.out 2>std.err
 EOF
 }
@@ -749,8 +750,8 @@
 EOF
 
 	set_file('checktest', <<'EOF');
-[ -s std.err ] && exit 1
-exec /bin/diff std.out std.out.expect
+[ -s std.err ] && { cat std.err; exit 1; }
+exec /bin/diff std.out.expect std.out
 EOF
 
 	set_file('runtest', <<"EOF");
@@ -772,8 +773,8 @@
 EOF
 
 	set_file('checktest', <<'EOF');
-[ -s std.err ] && exit 1
-exec /bin/diff std.out std.out.expect
+[ -s std.err ] && { cat std.err; exit 1; }
+exec /bin/diff std.out.expect std.out
 EOF
 
 	set_file('runtest', <<"EOF");
@@ -797,7 +798,7 @@
 
 	set_file('checktest', <<'EOF');
 [ -s std.out ] && exit 1
-exec /bin/diff std.err std.err.expect
+exec /bin/diff std.err.expect std.err
 EOF
 
 	set_file('runtest', <<"EOF");
@@ -831,14 +832,14 @@
 EOF
 
 	set_file('checktest', <<'EOF');
-[ -s std.err ] && exit 1
-exec /bin/diff std.out std.out.expect
+[ -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 -V >std.out 2>std.err
+exec $bindir/logadm -f testfile.conf -F testfile.conf -V >std.out 2>std.err
 EOF
 }
 
@@ -856,14 +857,14 @@
 EOF
 
 	set_file('checktest', <<'EOF');
-[ -s std.err ] && exit 1
-exec /bin/diff std.out std.out.expect
+[ -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 -V /var/cron/log /var/adm/pacct >std.out 2>std.err
+exec $bindir/logadm -f testfile.conf -F testfile.conf -V /var/cron/log /var/adm/pacct >std.out 2>std.err
 EOF
 }
 
@@ -877,22 +878,22 @@
 	set_testconffile('testfile.conf.orig');
 
 	set_file('diff.out.expect', <<'EOF');
-17a18
-> /var/cron/log -s 512k -t /var/cron/olog
-21a23
-> /var/adm/pacct -C 0 -a '/usr/lib/acct/accton pacct' -g adm -m 664 -o adm -p never
+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 ] && exit 1
-/bin/diff testfile.conf testfile.conf.orig > diff.out
-exec /bin/diff diff.out diff.out.expect
+[ -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 -r /var/cron/log /var/adm/pacct >std.out 2>std.err
+exec $bindir/logadm -f testfile.conf -F testfile.conf -r /var/cron/log /var/adm/pacct >std.out 2>std.err
 EOF
 }
 
@@ -906,20 +907,20 @@
 	set_testconffile('testfile.conf.orig');
 
 	set_file('diff.out.expect', <<'EOF');
-31d30
-< moose -C 20 -a moose_after_cmd -g pig -m 664 -o cow -p never /moose/file
+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 ] && exit 1
-/bin/diff testfile.conf testfile.conf.orig > diff.out
-exec /bin/diff diff.out diff.out.expect
+[ -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 -w moose -C 20 -a moose_after_cmd -g pig -m 664 -o cow -p never /moose/file >std.out 2>std.err
+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
 }
 
@@ -936,7 +937,7 @@
 		lstat 'logfile' or die "lstat logfile: $!\n";
 
 	set_file('checktest', <<"EOF");
-[ -s std.err ] && exit 1
+[ -s std.err ] && { cat std.err; exit 1; }
 [ -s std.out ] && exit 1
 [ -s logfile ] && exit 1
 [ -f logfile.0 ] || exit 1
@@ -967,7 +968,7 @@
 		lstat 'logfile' or die "lstat logfile: $!\n";
 
 	set_file('checktest', <<"EOF");
-[ -s std.err ] && exit 1
+[ -s std.err ] && { cat std.err; exit 1; }
 [ -s std.out ] && exit 1
 [ -s logfile ] && exit 1
 [ -f logfile.0 ] || exit 1
@@ -996,7 +997,7 @@
 	set_file('logfile.1', 'initially logfile.1');
 
 	set_file('checktest', <<'EOF');
-[ -s std.err ] && exit 1
+[ -s std.err ] && { cat std.err; exit 1; }
 [ -s std.out ] && exit 1
 [ -s logfile ] && exit 1
 [ -f logfile.0 ] || exit 1
@@ -1025,7 +1026,7 @@
 	set_file('logfile.1', 'initially logfile.1');
 
 	set_file('checktest', <<'EOF');
-[ -s std.err ] && exit 1
+[ -s std.err ] && { cat std.err; exit 1; }
 [ -s std.out ] && exit 1
 [ -s logfile ] && exit 1
 [ -f logfile.0 ] || exit 1
@@ -1056,7 +1057,7 @@
 	set_file('logfile', 'initially logfile');
 
 	set_file('checktest', <<'EOF');
-[ -s std.err ] && exit 1
+[ -s std.err ] && { cat std.err; exit 1; }
 [ -s std.out ] && exit 1
 [ -s logfile ] && exit 1
 TZ=UTC export TZ
@@ -1088,14 +1089,14 @@
 EOF
 
 	set_file('checktest', <<'EOF');
-[ -s std.err ] && exit 1
+[ -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 cmd.out.expect
+exec /bin/diff cmd.out.expect cmd.out
 EOF
 
 	set_file('runtest', <<"EOF");
@@ -1119,13 +1120,13 @@
 EOF
 
         set_file('checktest', <<'EOF');
-[ -s std.err ] || exit 1
+[ -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 std.err.expect
+exec /bin/diff std.err.expect std.err
 EOF
 
         set_file('runtest', <<"EOF");
@@ -1195,9 +1196,17 @@
 second kill -HUP $pid
 EOF
 
+	set_file('sed.out.expect', <<'EOF');
+# This file holds internal data for logadm(1M).
+# Do not edit.
+dir1/syslog
+dir2/messages
+EOF
+
 	set_file('checktest', <<'EOF');
-[ -s std.err ] && exit 1
+[ -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
@@ -1234,8 +1243,9 @@
 [ -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.conf > sed.out
-exec /bin/diff sed.out logadm.conf.orig
+/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
@@ -1245,12 +1255,12 @@
         set_file('runtest', <<"EOF");
 # test "logadm7"
 $envsetup
-$bindir/logadm -f logadm.conf >std.out 2>std.err || exit 1
-$bindir/logadm -f logadm.conf dir1/syslog dir2/messages >std.out2 2>std.err2 || exit 1
+$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 >std.out3 2>std.err3 || exit 1
-exec $bindir/logadm -f logadm.conf dir2/messages -p now -a 'echo second kill -HUP `cat /etc/syslog.pid` >> cmd.out' >std.out4 2>std.err4
+$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 /etc/syslog.pid` >> cmd.out' >std.out4 2>std.err4
 EOF
 }
 
@@ -1284,7 +1294,7 @@
 	die "gzip dir1/syslog.7 didn't work\n" unless -f 'dir1/syslog.7.gz';
 
 	set_file('checktest', <<'EOF');
-[ -s std.err ] && exit 1
+[ -s std.err ] && { cat std.err; exit 1; }
 [ -s std.out ] && exit 1
 [ -f dir1/syslog ] || exit 1
 [ -s dir1/syslog ] && exit 1
@@ -1332,12 +1342,14 @@
 	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);
@@ -1354,12 +1366,27 @@
 # now: $nowstr is $now
 # $closetoweek is $closetoweeksecs
 dir1/syslog -C 8 -P '$closetoweek'
+dir2/log -C 4
 # $lessthanweek is $lessthanweeksecs
-dir2/messages -C 4 -P '$lessthanweek'
+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(1M).
+# Do not edit.
+dir1/syslog
+dir2/log
+dir1/notes
+dir2/messages
 EOF
 
 	set_file('checktest', <<'EOF');
-[ -s std.err ] && exit 1
+[ -s std.err ] && { cat std.err; exit 1; }
 [ -s std.out ] && exit 1
 [ -f dir1/syslog ] || exit 1
 [ -s dir1/syslog ] && exit 1
@@ -1381,6 +1408,10 @@
 [ "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
@@ -1392,13 +1423,24 @@
 [ -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 >std.out 2>std.err
+exec $bindir/logadm -f logadm.conf -F logadm.timestamps >std.out 2>std.err
 EOF
 }
 
@@ -1443,7 +1485,7 @@
 EOF
 
 	set_file('checktest', <<'EOF');
-[ -s std.err ] && exit 1
+[ -s std.err ] && { cat std.err; exit 1; }
 [ -s std.out ] && exit 1
 [ -f dir1/syslog ] || exit 1
 [ -s dir1/syslog ] && exit 1
@@ -1482,7 +1524,7 @@
         set_file('runtest', <<"EOF");
 # test "logadm9d"
 $envsetup
-exec $bindir/logadm -f logadm.conf >std.out 2>std.err
+exec $bindir/logadm -f logadm.conf -F logadm.timestamps >std.out 2>std.err
 EOF
 }
 
@@ -1515,7 +1557,7 @@
 EOF
 
 	set_file('checktest', <<'EOF');
-[ -s std.err ] && exit 1
+[ -s std.err ] && { cat std.err; exit 1; }
 [ -s std.out ] && exit 1
 [ -f dir1/syslog ] || exit 1
 [ -s dir1/syslog ] && exit 1
@@ -1554,7 +1596,7 @@
         set_file('runtest', <<"EOF");
 # test "logadm10"
 $envsetup
-exec $bindir/logadm -f logadm.conf >std.out 2>std.err
+exec $bindir/logadm -f logadm.conf -F logadm.timestamps >std.out 2>std.err
 EOF
 }
 
@@ -1587,7 +1629,7 @@
 EOF
 
 	set_file('checktest', <<'EOF');
-[ -s std.err ] && exit 1
+[ -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
@@ -1620,7 +1662,7 @@
         set_file('runtest', <<"EOF");
 # test "logadm11"
 $envsetup
-exec $bindir/logadm -f logadm.conf >std.out 2>std.err
+exec $bindir/logadm -f logadm.conf -F logadm.timestamps >std.out 2>std.err
 EOF
 }
 
@@ -1636,7 +1678,7 @@
 
 	set_file('checktest', <<"EOF");
 [ -s std.out ] && exit 1
-exec /bin/diff std.err std.err.expect
+exec /bin/diff std.err.expect std.err
 EOF
 
 	set_file('runtest', <<"EOF");
@@ -1653,8 +1695,8 @@
 ###########################################################################
 sub logadm13 {
 	set_file('checktest', <<"EOF");
+[ -s std.err ] && { cat std.err; exit 1; }
 [ -s std.out ] && exit 1
-[ -s std.err ] && exit 1
 exit 0
 EOF
 
@@ -1765,11 +1807,11 @@
 # processing logname: /var/adm/pacct
 #     using default template: $file.$n
 sh -c echo kill -HUP `cat /etc/syslog.pid` >> cmd.out # -a cmd
-# logadm.conf unchanged
+# logadm.conf and logadm.timestamps unchanged
 EOF
 
 	set_file('checktest', <<'EOF');
-[ -s std.err ] && exit 1
+[ -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
@@ -1803,13 +1845,13 @@
 [ "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 grep.out.expect
+exec /bin/diff grep.out.expect grep.out
 EOF
 
         set_file('runtest', <<"EOF");
 # test "logadm14"
 $envsetup
-exec $bindir/logadm -nv -f logadm.conf >std.out 2>std.err
+exec $bindir/logadm -nv -f logadm.conf -F logadm.timestamps >std.out 2>std.err
 EOF
 }
 
@@ -1832,7 +1874,7 @@
 	set_file('logfile.9', 'initially logfile.9');
 
 	set_file('checktest', <<'EOF');
-[ -s std.err ] && exit 1
+[ -s std.err ] && { cat std.err; exit 1; }
 [ -s std.out ] && exit 1
 [ -f logfile ] || exit 1
 [ "x" = "x`/bin/cat logfile`" ] || exit 1
@@ -1877,6 +1919,7 @@
 
 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
@@ -1917,7 +1960,7 @@
 
 	set_file('checktest', <<'EOF');
 [ -s std.out ] && exit 1
-exec /bin/diff std.err std.err.expect
+exec /bin/diff std.err.expect std.err
 EOF
 
 	set_file('runtest', <<"EOF");
@@ -1936,7 +1979,7 @@
 	set_file('logfile', 'initially logfile');
 
 	set_file('checktest', <<'EOF');
-[ -s std.err ] && exit 1
+[ -s std.err ] && { cat std.err; exit 1; }
 [ -s std.out ] && exit 1
 [ -f dir1/dir2/logfile ] || exit 1
 [ -f logfile ] || exit 1
@@ -1973,7 +2016,7 @@
 EOF
 
 	set_file('checktest', <<'EOF');
-[ -s std.err ] && exit 1
+[ -s std.err ] && { cat std.err; exit 1; }
 [ -s std.out ] && exit 1
 [ -f dir1/syslog ] || exit 1
 [ -s dir1/syslog ] && exit 1
@@ -2001,7 +2044,7 @@
         set_file('runtest', <<"EOF");
 # test "logadm18"
 $envsetup
-exec $bindir/logadm -f logadm.conf >std.out 2>std.err
+exec $bindir/logadm -f logadm.conf -F logadm.timestamps >std.out 2>std.err
 EOF
 }
 
@@ -2014,7 +2057,7 @@
         set_file('logfile', 'initially logfile');
 
         set_file('checktest', <<'EOF');
-[ -s std.err ] && exit 1
+[ -s std.err ] && { cat std.err; exit 1; }
 [ -s std.out ] && exit 1
 [ -s logfile ] && exit 1
 TZ= export TZ
@@ -2025,8 +2068,43 @@
 EOF
 
         set_file('runtest', <<"EOF");
-# test "logadm4"
+# 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 </bin/echo "She can't take anymore, Cap'n!">
+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
+}
--- a/usr/src/pkg/manifests/SUNWcs.mf	Mon Aug 02 08:40:07 2010 -0400
+++ b/usr/src/pkg/manifests/SUNWcs.mf	Mon Aug 02 10:54:24 2010 -0700
@@ -289,6 +289,7 @@
 dir path=var/ld
 dir path=var/ld/$(ARCH64)
 dir path=var/log group=sys
+dir path=var/logadm
 dir path=var/mail group=mail mode=1777
 dir path=var/mail/:saved group=mail mode=0775
 dir path=var/news