4428 logadm ignoring -p flag
[unleashed.git] / usr / src / cmd / logadm / conf.c
blob0d22957a5ee0b7fd61a545cd8062042abbf90727
1 /*
2 * CDDL HEADER START
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
19 * CDDL HEADER END
23 * Copyright (c) 2001, 2010, Oracle and/or its affiliates. All rights reserved.
24 * Copyright 2013, Joyent, Inc. All rights reserved.
28 * logadm/conf.c -- configuration file module
31 #include <stdio.h>
32 #include <libintl.h>
33 #include <fcntl.h>
34 #include <sys/types.h>
35 #include <sys/stat.h>
36 #include <sys/mman.h>
37 #include <ctype.h>
38 #include <strings.h>
39 #include <unistd.h>
40 #include <stdlib.h>
41 #include <limits.h>
42 #include "err.h"
43 #include "lut.h"
44 #include "fn.h"
45 #include "opts.h"
46 #include "conf.h"
48 /* forward declarations of functions private to this module */
49 static void fillconflist(int lineno, const char *entry,
50 struct opts *opts, const char *com, int flags);
51 static void fillargs(char *arg);
52 static char *nexttok(char **ptrptr);
53 static void conf_print(FILE *cstream, FILE *tstream);
55 static const char *Confname; /* name of the confile file */
56 static int Conffd = -1; /* file descriptor for config file */
57 static char *Confbuf; /* copy of the config file (a la mmap()) */
58 static int Conflen; /* length of mmap'd config file area */
59 static const char *Timesname; /* name of the timestamps file */
60 static int Timesfd = -1; /* file descriptor for timestamps file */
61 static char *Timesbuf; /* copy of the timestamps file (a la mmap()) */
62 static int Timeslen; /* length of mmap'd timestamps area */
63 static int Singlefile; /* Conf and Times in the same file */
64 static int Changed; /* what changes need to be written back */
65 static int Canchange; /* what changes can be written back */
66 static int Changing; /* what changes have been requested */
67 #define CHG_NONE 0
68 #define CHG_TIMES 1
69 #define CHG_BOTH 3
72 * our structured representation of the configuration file
73 * is made up of a list of these
75 struct confinfo {
76 struct confinfo *cf_next;
77 int cf_lineno; /* line number in file */
78 const char *cf_entry; /* name of entry, if line has an entry */
79 struct opts *cf_opts; /* parsed rhs of entry */
80 const char *cf_com; /* any comment text found */
81 int cf_flags;
84 #define CONFF_DELETED 1 /* entry should be deleted on write back */
86 static struct confinfo *Confinfo; /* the entries in the config file */
87 static struct confinfo *Confinfolast; /* end of list */
88 static struct lut *Conflut; /* lookup table keyed by entry name */
89 static struct fn_list *Confentries; /* list of valid entry names */
91 /* allocate & fill in another entry in our list */
92 static void
93 fillconflist(int lineno, const char *entry,
94 struct opts *opts, const char *com, int flags)
96 struct confinfo *cp = MALLOC(sizeof (*cp));
98 cp->cf_next = NULL;
99 cp->cf_lineno = lineno;
100 cp->cf_entry = entry;
101 cp->cf_opts = opts;
102 cp->cf_com = com;
103 cp->cf_flags = flags;
104 if (entry != NULL) {
105 Conflut = lut_add(Conflut, entry, cp);
106 fn_list_adds(Confentries, entry);
108 if (Confinfo == NULL)
109 Confinfo = Confinfolast = cp;
110 else {
111 Confinfolast->cf_next = cp;
112 Confinfolast = cp;
116 static char **Args; /* static buffer for args */
117 static int ArgsN; /* size of our static buffer */
118 static int ArgsI; /* index into Cmdargs as we walk table */
119 #define CONF_ARGS_INC 1024
121 /* callback for lut_walk to build a cmdargs vector */
122 static void
123 fillargs(char *arg)
125 if (ArgsI >= ArgsN) {
126 /* need bigger table */
127 Args = REALLOC(Args, sizeof (char *) * (ArgsN + CONF_ARGS_INC));
128 ArgsN += CONF_ARGS_INC;
130 Args[ArgsI++] = arg;
133 /* isolate and return the next token */
134 static char *
135 nexttok(char **ptrptr)
137 char *ptr = *ptrptr;
138 char *eptr;
139 char *quote = NULL;
141 while (*ptr && isspace(*ptr))
142 ptr++;
144 if (*ptr == '"' || *ptr == '\'')
145 quote = ptr++;
147 for (eptr = ptr; *eptr; eptr++)
148 if (quote && *eptr == *quote) {
149 /* found end quote */
150 *eptr++ = '\0';
151 *ptrptr = eptr;
152 return (ptr);
153 } else if (!quote && isspace(*eptr)) {
154 /* found end of unquoted area */
155 *eptr++ = '\0';
156 *ptrptr = eptr;
157 return (ptr);
160 if (quote != NULL)
161 err(EF_FILE|EF_JMP, "Unbalanced %c quote", *quote);
162 /*NOTREACHED*/
164 *ptrptr = eptr;
166 if (ptr == eptr)
167 return (NULL);
168 else
169 return (ptr);
173 * scan the memory image of a file
174 * returns: 0: error, 1: ok, 3: -P option found
176 static int
177 conf_scan(const char *fname, char *buf, int buflen, int timescan)
179 int ret = 1;
180 int lineno = 0;
181 char *line;
182 char *eline;
183 char *ebuf;
184 char *entry, *comment;
186 ebuf = &buf[buflen];
188 if (buf[buflen - 1] != '\n')
189 err(EF_WARN|EF_FILE, "file %s doesn't end with newline, "
190 "last line ignored.", fname);
192 for (line = buf; line < ebuf; line = eline) {
193 char *ap;
194 struct opts *opts = NULL;
195 struct confinfo *cp;
197 lineno++;
198 err_fileline(fname, lineno);
199 eline = line;
200 comment = NULL;
201 for (; eline < ebuf; eline++) {
202 /* check for continued lines */
203 if (comment == NULL && *eline == '\\' &&
204 eline + 1 < ebuf && *(eline + 1) == '\n') {
205 *eline = ' ';
206 *(eline + 1) = ' ';
207 lineno++;
208 err_fileline(fname, lineno);
209 continue;
212 /* check for comments */
213 if (comment == NULL && *eline == '#') {
214 *eline = '\0';
215 comment = (eline + 1);
216 continue;
219 /* check for end of line */
220 if (*eline == '\n')
221 break;
223 if (comment >= ebuf)
224 comment = NULL;
225 if (eline >= ebuf) {
226 /* discard trailing unterminated line */
227 continue;
229 *eline++ = '\0';
232 * now we have the entry, if any, at "line"
233 * and the comment, if any, at "comment"
236 /* entry is first token */
237 entry = nexttok(&line);
238 if (entry == NULL) {
239 /* it's just a comment line */
240 if (!timescan)
241 fillconflist(lineno, entry, NULL, comment, 0);
242 continue;
244 if (strcmp(entry, "logadm-version") == 0) {
246 * we somehow opened some future format
247 * conffile that we likely don't understand.
248 * if the given version is "1" then go on,
249 * otherwise someone is mixing versions
250 * and we can't help them other than to
251 * print an error and exit.
253 if ((entry = nexttok(&line)) != NULL &&
254 strcmp(entry, "1") != 0)
255 err(0, "%s version not supported "
256 "by this version of logadm.",
257 fname);
258 continue;
261 /* form an argv array */
262 ArgsI = 0;
263 while (ap = nexttok(&line))
264 fillargs(ap);
265 Args[ArgsI] = NULL;
267 LOCAL_ERR_BEGIN {
268 if (SETJMP) {
269 err(EF_FILE, "cannot process invalid entry %s",
270 entry);
271 ret = 0;
272 LOCAL_ERR_BREAK;
275 if (timescan) {
276 /* append to config options */
277 cp = lut_lookup(Conflut, entry);
278 if (cp != NULL) {
279 opts = cp->cf_opts;
282 opts = opts_parse(opts, Args, OPTF_CONF);
283 if (!timescan || cp == NULL) {
285 * If we're not doing timescan, we track this
286 * entry. If we are doing timescan and have
287 * what looks like an orphaned entry (cp ==
288 * NULL) then we also have to track. See the
289 * comment in rotatelog. We need to allow for
290 * the case where the logname is not the same as
291 * the log file name.
293 fillconflist(lineno, entry, opts, comment, 0);
295 LOCAL_ERR_END }
297 if (ret == 1 && opts && opts_optarg(opts, "P") != NULL)
298 ret = 3;
301 err_fileline(NULL, 0);
302 return (ret);
306 * conf_open -- open the configuration file, lock it if we have write perms
309 conf_open(const char *cfname, const char *tfname, struct opts *cliopts)
311 struct stat stbuf1, stbuf2, stbuf3;
312 struct flock flock;
313 int ret;
315 Confname = cfname;
316 Timesname = tfname;
317 Confentries = fn_list_new(NULL);
318 Changed = CHG_NONE;
320 Changing = CHG_TIMES;
321 if (opts_count(cliopts, "Vn") != 0)
322 Changing = CHG_NONE;
323 else if (opts_count(cliopts, "rw") != 0)
324 Changing = CHG_BOTH;
326 Singlefile = strcmp(Confname, Timesname) == 0;
327 if (Singlefile && Changing == CHG_TIMES)
328 Changing = CHG_BOTH;
330 /* special case this so we don't even try locking the file */
331 if (strcmp(Confname, "/dev/null") == 0)
332 return (0);
334 while (Conffd == -1) {
335 Canchange = CHG_BOTH;
336 if ((Conffd = open(Confname, O_RDWR)) < 0) {
337 if (Changing == CHG_BOTH)
338 err(EF_SYS, "open %s", Confname);
339 Canchange = CHG_TIMES;
340 if ((Conffd = open(Confname, O_RDONLY)) < 0)
341 err(EF_SYS, "open %s", Confname);
344 flock.l_type = (Canchange == CHG_BOTH) ? F_WRLCK : F_RDLCK;
345 flock.l_whence = SEEK_SET;
346 flock.l_start = 0;
347 flock.l_len = 1;
348 if (fcntl(Conffd, F_SETLKW, &flock) < 0)
349 err(EF_SYS, "flock on %s", Confname);
351 /* wait until after file is locked to get filesize */
352 if (fstat(Conffd, &stbuf1) < 0)
353 err(EF_SYS, "fstat on %s", Confname);
355 /* verify that we've got a lock on the active file */
356 if (stat(Confname, &stbuf2) < 0 ||
357 !(stbuf2.st_dev == stbuf1.st_dev &&
358 stbuf2.st_ino == stbuf1.st_ino)) {
359 /* wrong config file, try again */
360 (void) close(Conffd);
361 Conffd = -1;
365 while (!Singlefile && Timesfd == -1) {
366 if ((Timesfd = open(Timesname, O_CREAT|O_RDWR, 0644)) < 0) {
367 if (Changing != CHG_NONE)
368 err(EF_SYS, "open %s", Timesname);
369 Canchange = CHG_NONE;
370 if ((Timesfd = open(Timesname, O_RDONLY)) < 0)
371 err(EF_SYS, "open %s", Timesname);
374 flock.l_type = (Canchange != CHG_NONE) ? F_WRLCK : F_RDLCK;
375 flock.l_whence = SEEK_SET;
376 flock.l_start = 0;
377 flock.l_len = 1;
378 if (fcntl(Timesfd, F_SETLKW, &flock) < 0)
379 err(EF_SYS, "flock on %s", Timesname);
381 /* wait until after file is locked to get filesize */
382 if (fstat(Timesfd, &stbuf2) < 0)
383 err(EF_SYS, "fstat on %s", Timesname);
385 /* verify that we've got a lock on the active file */
386 if (stat(Timesname, &stbuf3) < 0 ||
387 !(stbuf2.st_dev == stbuf3.st_dev &&
388 stbuf2.st_ino == stbuf3.st_ino)) {
389 /* wrong timestamp file, try again */
390 (void) close(Timesfd);
391 Timesfd = -1;
392 continue;
395 /* check that Timesname isn't an alias for Confname */
396 if (stbuf2.st_dev == stbuf1.st_dev &&
397 stbuf2.st_ino == stbuf1.st_ino)
398 err(0, "Timestamp file %s can't refer to "
399 "Configuration file %s", Timesname, Confname);
402 Conflen = stbuf1.st_size;
403 Timeslen = stbuf2.st_size;
405 if (Conflen == 0)
406 return (1); /* empty file, don't bother parsing it */
408 if ((Confbuf = (char *)mmap(0, Conflen,
409 PROT_READ | PROT_WRITE, MAP_PRIVATE, Conffd, 0)) == (char *)-1)
410 err(EF_SYS, "mmap on %s", Confname);
412 ret = conf_scan(Confname, Confbuf, Conflen, 0);
413 if (ret == 3 && !Singlefile && Canchange == CHG_BOTH) {
415 * arrange to transfer any timestamps
416 * from conf_file to timestamps_file
418 Changing = Changed = CHG_BOTH;
421 if (Timesfd != -1 && Timeslen != 0) {
422 if ((Timesbuf = (char *)mmap(0, Timeslen,
423 PROT_READ | PROT_WRITE, MAP_PRIVATE,
424 Timesfd, 0)) == (char *)-1)
425 err(EF_SYS, "mmap on %s", Timesname);
426 ret &= conf_scan(Timesname, Timesbuf, Timeslen, 1);
430 * possible future enhancement: go through and mark any entries:
431 * logfile -P <date>
432 * as DELETED if the logfile doesn't exist
435 return (ret);
439 * conf_close -- close the configuration file
441 void
442 conf_close(struct opts *opts)
444 char cuname[PATH_MAX], tuname[PATH_MAX];
445 int cfd, tfd;
446 FILE *cfp = NULL, *tfp = NULL;
447 boolean_t safe_update = B_TRUE;
449 if (Changed == CHG_NONE || opts_count(opts, "n") != 0) {
450 if (opts_count(opts, "v"))
451 (void) out("# %s and %s unchanged\n",
452 Confname, Timesname);
453 goto cleanup;
456 if (Debug > 1) {
457 (void) fprintf(stderr, "conf_close, saving logadm context:\n");
458 conf_print(stderr, NULL);
461 cuname[0] = tuname[0] = '\0';
462 LOCAL_ERR_BEGIN {
463 if (SETJMP) {
464 safe_update = B_FALSE;
465 LOCAL_ERR_BREAK;
467 if (Changed == CHG_BOTH) {
468 if (Canchange != CHG_BOTH)
469 err(EF_JMP, "internal error: attempting "
470 "to update %s without locking", Confname);
471 (void) snprintf(cuname, sizeof (cuname), "%sXXXXXX",
472 Confname);
473 if ((cfd = mkstemp(cuname)) == -1)
474 err(EF_SYS|EF_JMP, "open %s replacement",
475 Confname);
476 if (opts_count(opts, "v"))
477 (void) out("# writing changes to %s\n", cuname);
478 if (fchmod(cfd, 0644) == -1)
479 err(EF_SYS|EF_JMP, "chmod %s", cuname);
480 if ((cfp = fdopen(cfd, "w")) == NULL)
481 err(EF_SYS|EF_JMP, "fdopen on %s", cuname);
482 } else {
483 /* just toss away the configuration data */
484 cfp = fopen("/dev/null", "w");
486 if (!Singlefile) {
487 if (Canchange == CHG_NONE)
488 err(EF_JMP, "internal error: attempting "
489 "to update %s without locking", Timesname);
490 (void) snprintf(tuname, sizeof (tuname), "%sXXXXXX",
491 Timesname);
492 if ((tfd = mkstemp(tuname)) == -1)
493 err(EF_SYS|EF_JMP, "open %s replacement",
494 Timesname);
495 if (opts_count(opts, "v"))
496 (void) out("# writing changes to %s\n", tuname);
497 if (fchmod(tfd, 0644) == -1)
498 err(EF_SYS|EF_JMP, "chmod %s", tuname);
499 if ((tfp = fdopen(tfd, "w")) == NULL)
500 err(EF_SYS|EF_JMP, "fdopen on %s", tuname);
503 conf_print(cfp, tfp);
504 if (fclose(cfp) < 0)
505 err(EF_SYS|EF_JMP, "fclose on %s", Confname);
506 if (tfp != NULL && fclose(tfp) < 0)
507 err(EF_SYS|EF_JMP, "fclose on %s", Timesname);
508 LOCAL_ERR_END }
510 if (!safe_update) {
511 if (cuname[0] != 0)
512 (void) unlink(cuname);
513 if (tuname[0] != 0)
514 (void) unlink(tuname);
515 err(EF_JMP, "unsafe to update configuration file "
516 "or timestamps");
517 return;
520 /* rename updated files into place */
521 if (cuname[0] != '\0')
522 if (rename(cuname, Confname) < 0)
523 err(EF_SYS, "rename %s to %s", cuname, Confname);
524 if (tuname[0] != '\0')
525 if (rename(tuname, Timesname) < 0)
526 err(EF_SYS, "rename %s to %s", tuname, Timesname);
527 Changed = CHG_NONE;
529 cleanup:
530 if (Conffd != -1) {
531 (void) close(Conffd);
532 Conffd = -1;
534 if (Timesfd != -1) {
535 (void) close(Timesfd);
536 Timesfd = -1;
538 if (Conflut) {
539 lut_free(Conflut, free);
540 Conflut = NULL;
542 if (Confentries) {
543 fn_list_free(Confentries);
544 Confentries = NULL;
549 * conf_lookup -- lookup an entry in the config file
551 void *
552 conf_lookup(const char *lhs)
554 struct confinfo *cp = lut_lookup(Conflut, lhs);
556 if (cp != NULL)
557 err_fileline(Confname, cp->cf_lineno);
558 return (cp);
562 * conf_opts -- return the parsed opts for an entry
564 struct opts *
565 conf_opts(const char *lhs)
567 struct confinfo *cp = lut_lookup(Conflut, lhs);
569 if (cp != NULL)
570 return (cp->cf_opts);
571 return (opts_parse(NULL, NULL, OPTF_CONF));
575 * conf_replace -- replace an entry in the config file
577 void
578 conf_replace(const char *lhs, struct opts *newopts)
580 struct confinfo *cp = lut_lookup(Conflut, lhs);
582 if (Conffd == -1)
583 return;
585 if (cp != NULL) {
586 cp->cf_opts = newopts;
587 /* cp->cf_args = NULL; */
588 if (newopts == NULL)
589 cp->cf_flags |= CONFF_DELETED;
590 } else
591 fillconflist(0, lhs, newopts, NULL, 0);
593 Changed = CHG_BOTH;
597 * conf_set -- set options for an entry in the config file
599 void
600 conf_set(const char *entry, char *o, const char *optarg)
602 struct confinfo *cp = lut_lookup(Conflut, entry);
604 if (Conffd == -1)
605 return;
607 if (cp != NULL) {
608 cp->cf_flags &= ~CONFF_DELETED;
609 } else {
610 fillconflist(0, STRDUP(entry),
611 opts_parse(NULL, NULL, OPTF_CONF), NULL, 0);
612 if ((cp = lut_lookup(Conflut, entry)) == NULL)
613 err(0, "conf_set internal error");
615 (void) opts_set(cp->cf_opts, o, optarg);
616 if (strcmp(o, "P") == 0)
617 Changed |= CHG_TIMES;
618 else
619 Changed = CHG_BOTH;
623 * conf_entries -- list all the entry names
625 struct fn_list *
626 conf_entries(void)
628 return (Confentries);
631 /* print the config file */
632 static void
633 conf_print(FILE *cstream, FILE *tstream)
635 struct confinfo *cp;
636 char *exclude_opts = "PFfhnrvVw";
637 const char *timestamp;
639 if (tstream == NULL) {
640 exclude_opts++; /* -P option goes to config file */
641 } else {
642 (void) fprintf(tstream, gettext(
643 "# This file holds internal data for logadm(1M).\n"
644 "# Do not edit.\n"));
646 for (cp = Confinfo; cp; cp = cp->cf_next) {
647 if (cp->cf_flags & CONFF_DELETED)
648 continue;
649 if (cp->cf_entry) {
650 opts_printword(cp->cf_entry, cstream);
651 if (cp->cf_opts)
652 opts_print(cp->cf_opts, cstream, exclude_opts);
653 /* output timestamps to tstream */
654 if (tstream != NULL && (timestamp =
655 opts_optarg(cp->cf_opts, "P")) != NULL) {
656 opts_printword(cp->cf_entry, tstream);
657 (void) fprintf(tstream, " -P ");
658 opts_printword(timestamp, tstream);
659 (void) fprintf(tstream, "\n");
662 if (cp->cf_com) {
663 if (cp->cf_entry)
664 (void) fprintf(cstream, " ");
665 (void) fprintf(cstream, "#%s", cp->cf_com);
667 (void) fprintf(cstream, "\n");
671 #ifdef TESTMODULE
674 * test main for conf module, usage: a.out conffile
677 main(int argc, char *argv[])
679 struct opts *opts;
681 err_init(argv[0]);
682 setbuf(stdout, NULL);
683 opts_init(Opttable, Opttable_cnt);
685 opts = opts_parse(NULL, NULL, 0);
687 if (argc != 2)
688 err(EF_RAW, "usage: %s conffile\n", argv[0]);
690 conf_open(argv[1], argv[1], opts);
692 printf("conffile <%s>:\n", argv[1]);
693 conf_print(stdout, NULL);
695 conf_close(opts);
697 err_done(0);
698 /* NOTREACHED */
699 return (0);
702 #endif /* TESTMODULE */