8322 nl: misleading-indentation
[unleashed/tickless.git] / usr / src / cmd / logadm / conf.c
blobaa877d3dd29fa95c5a2e2a3b82eb3236a27c4882
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);
267 * If there is no next token on the line, make sure that
268 * we get a non-NULL Args array.
270 if (Args == NULL)
271 fillargs(NULL);
273 Args[ArgsI] = NULL;
275 LOCAL_ERR_BEGIN {
276 if (SETJMP) {
277 err(EF_FILE, "cannot process invalid entry %s",
278 entry);
279 ret = 0;
280 LOCAL_ERR_BREAK;
283 if (timescan) {
284 /* append to config options */
285 cp = lut_lookup(Conflut, entry);
286 if (cp != NULL) {
287 opts = cp->cf_opts;
290 opts = opts_parse(opts, Args, OPTF_CONF);
291 if (!timescan || cp == NULL) {
293 * If we're not doing timescan, we track this
294 * entry. If we are doing timescan and have
295 * what looks like an orphaned entry (cp ==
296 * NULL) then we also have to track. See the
297 * comment in rotatelog. We need to allow for
298 * the case where the logname is not the same as
299 * the log file name.
301 fillconflist(lineno, entry, opts, comment, 0);
303 LOCAL_ERR_END }
305 if (ret == 1 && opts && opts_optarg(opts, "P") != NULL)
306 ret = 3;
309 err_fileline(NULL, 0);
310 return (ret);
314 * conf_open -- open the configuration file, lock it if we have write perms
317 conf_open(const char *cfname, const char *tfname, struct opts *cliopts)
319 struct stat stbuf1, stbuf2, stbuf3;
320 struct flock flock;
321 int ret;
323 Confname = cfname;
324 Timesname = tfname;
325 Confentries = fn_list_new(NULL);
326 Changed = CHG_NONE;
328 Changing = CHG_TIMES;
329 if (opts_count(cliopts, "Vn") != 0)
330 Changing = CHG_NONE;
331 else if (opts_count(cliopts, "rw") != 0)
332 Changing = CHG_BOTH;
334 Singlefile = strcmp(Confname, Timesname) == 0;
335 if (Singlefile && Changing == CHG_TIMES)
336 Changing = CHG_BOTH;
338 /* special case this so we don't even try locking the file */
339 if (strcmp(Confname, "/dev/null") == 0)
340 return (0);
342 while (Conffd == -1) {
343 Canchange = CHG_BOTH;
344 if ((Conffd = open(Confname, O_RDWR)) < 0) {
345 if (Changing == CHG_BOTH)
346 err(EF_SYS, "open %s", Confname);
347 Canchange = CHG_TIMES;
348 if ((Conffd = open(Confname, O_RDONLY)) < 0)
349 err(EF_SYS, "open %s", Confname);
352 flock.l_type = (Canchange == CHG_BOTH) ? F_WRLCK : F_RDLCK;
353 flock.l_whence = SEEK_SET;
354 flock.l_start = 0;
355 flock.l_len = 1;
356 if (fcntl(Conffd, F_SETLKW, &flock) < 0)
357 err(EF_SYS, "flock on %s", Confname);
359 /* wait until after file is locked to get filesize */
360 if (fstat(Conffd, &stbuf1) < 0)
361 err(EF_SYS, "fstat on %s", Confname);
363 /* verify that we've got a lock on the active file */
364 if (stat(Confname, &stbuf2) < 0 ||
365 !(stbuf2.st_dev == stbuf1.st_dev &&
366 stbuf2.st_ino == stbuf1.st_ino)) {
367 /* wrong config file, try again */
368 (void) close(Conffd);
369 Conffd = -1;
373 while (!Singlefile && Timesfd == -1) {
374 if ((Timesfd = open(Timesname, O_CREAT|O_RDWR, 0644)) < 0) {
375 if (Changing != CHG_NONE)
376 err(EF_SYS, "open %s", Timesname);
377 Canchange = CHG_NONE;
378 if ((Timesfd = open(Timesname, O_RDONLY)) < 0)
379 err(EF_SYS, "open %s", Timesname);
382 flock.l_type = (Canchange != CHG_NONE) ? F_WRLCK : F_RDLCK;
383 flock.l_whence = SEEK_SET;
384 flock.l_start = 0;
385 flock.l_len = 1;
386 if (fcntl(Timesfd, F_SETLKW, &flock) < 0)
387 err(EF_SYS, "flock on %s", Timesname);
389 /* wait until after file is locked to get filesize */
390 if (fstat(Timesfd, &stbuf2) < 0)
391 err(EF_SYS, "fstat on %s", Timesname);
393 /* verify that we've got a lock on the active file */
394 if (stat(Timesname, &stbuf3) < 0 ||
395 !(stbuf2.st_dev == stbuf3.st_dev &&
396 stbuf2.st_ino == stbuf3.st_ino)) {
397 /* wrong timestamp file, try again */
398 (void) close(Timesfd);
399 Timesfd = -1;
400 continue;
403 /* check that Timesname isn't an alias for Confname */
404 if (stbuf2.st_dev == stbuf1.st_dev &&
405 stbuf2.st_ino == stbuf1.st_ino)
406 err(0, "Timestamp file %s can't refer to "
407 "Configuration file %s", Timesname, Confname);
410 Conflen = stbuf1.st_size;
411 Timeslen = stbuf2.st_size;
413 if (Conflen == 0)
414 return (1); /* empty file, don't bother parsing it */
416 if ((Confbuf = (char *)mmap(0, Conflen,
417 PROT_READ | PROT_WRITE, MAP_PRIVATE, Conffd, 0)) == (char *)-1)
418 err(EF_SYS, "mmap on %s", Confname);
420 ret = conf_scan(Confname, Confbuf, Conflen, 0);
421 if (ret == 3 && !Singlefile && Canchange == CHG_BOTH) {
423 * arrange to transfer any timestamps
424 * from conf_file to timestamps_file
426 Changing = Changed = CHG_BOTH;
429 if (Timesfd != -1 && Timeslen != 0) {
430 if ((Timesbuf = (char *)mmap(0, Timeslen,
431 PROT_READ | PROT_WRITE, MAP_PRIVATE,
432 Timesfd, 0)) == (char *)-1)
433 err(EF_SYS, "mmap on %s", Timesname);
434 ret &= conf_scan(Timesname, Timesbuf, Timeslen, 1);
438 * possible future enhancement: go through and mark any entries:
439 * logfile -P <date>
440 * as DELETED if the logfile doesn't exist
443 return (ret);
447 * conf_close -- close the configuration file
449 void
450 conf_close(struct opts *opts)
452 char cuname[PATH_MAX], tuname[PATH_MAX];
453 int cfd, tfd;
454 FILE *cfp = NULL, *tfp = NULL;
455 boolean_t safe_update = B_TRUE;
457 if (Changed == CHG_NONE || opts_count(opts, "n") != 0) {
458 if (opts_count(opts, "v"))
459 (void) out("# %s and %s unchanged\n",
460 Confname, Timesname);
461 goto cleanup;
464 if (Debug > 1) {
465 (void) fprintf(stderr, "conf_close, saving logadm context:\n");
466 conf_print(stderr, NULL);
469 cuname[0] = tuname[0] = '\0';
470 LOCAL_ERR_BEGIN {
471 if (SETJMP) {
472 safe_update = B_FALSE;
473 LOCAL_ERR_BREAK;
475 if (Changed == CHG_BOTH) {
476 if (Canchange != CHG_BOTH)
477 err(EF_JMP, "internal error: attempting "
478 "to update %s without locking", Confname);
479 (void) snprintf(cuname, sizeof (cuname), "%sXXXXXX",
480 Confname);
481 if ((cfd = mkstemp(cuname)) == -1)
482 err(EF_SYS|EF_JMP, "open %s replacement",
483 Confname);
484 if (opts_count(opts, "v"))
485 (void) out("# writing changes to %s\n", cuname);
486 if (fchmod(cfd, 0644) == -1)
487 err(EF_SYS|EF_JMP, "chmod %s", cuname);
488 if ((cfp = fdopen(cfd, "w")) == NULL)
489 err(EF_SYS|EF_JMP, "fdopen on %s", cuname);
490 } else {
491 /* just toss away the configuration data */
492 cfp = fopen("/dev/null", "w");
494 if (!Singlefile) {
495 if (Canchange == CHG_NONE)
496 err(EF_JMP, "internal error: attempting "
497 "to update %s without locking", Timesname);
498 (void) snprintf(tuname, sizeof (tuname), "%sXXXXXX",
499 Timesname);
500 if ((tfd = mkstemp(tuname)) == -1)
501 err(EF_SYS|EF_JMP, "open %s replacement",
502 Timesname);
503 if (opts_count(opts, "v"))
504 (void) out("# writing changes to %s\n", tuname);
505 if (fchmod(tfd, 0644) == -1)
506 err(EF_SYS|EF_JMP, "chmod %s", tuname);
507 if ((tfp = fdopen(tfd, "w")) == NULL)
508 err(EF_SYS|EF_JMP, "fdopen on %s", tuname);
511 conf_print(cfp, tfp);
512 if (fclose(cfp) < 0)
513 err(EF_SYS|EF_JMP, "fclose on %s", Confname);
514 if (tfp != NULL && fclose(tfp) < 0)
515 err(EF_SYS|EF_JMP, "fclose on %s", Timesname);
516 LOCAL_ERR_END }
518 if (!safe_update) {
519 if (cuname[0] != 0)
520 (void) unlink(cuname);
521 if (tuname[0] != 0)
522 (void) unlink(tuname);
523 err(EF_JMP, "unsafe to update configuration file "
524 "or timestamps");
525 return;
528 /* rename updated files into place */
529 if (cuname[0] != '\0')
530 if (rename(cuname, Confname) < 0)
531 err(EF_SYS, "rename %s to %s", cuname, Confname);
532 if (tuname[0] != '\0')
533 if (rename(tuname, Timesname) < 0)
534 err(EF_SYS, "rename %s to %s", tuname, Timesname);
535 Changed = CHG_NONE;
537 cleanup:
538 if (Conffd != -1) {
539 (void) close(Conffd);
540 Conffd = -1;
542 if (Timesfd != -1) {
543 (void) close(Timesfd);
544 Timesfd = -1;
546 if (Conflut) {
547 lut_free(Conflut, free);
548 Conflut = NULL;
550 if (Confentries) {
551 fn_list_free(Confentries);
552 Confentries = NULL;
557 * conf_lookup -- lookup an entry in the config file
559 void *
560 conf_lookup(const char *lhs)
562 struct confinfo *cp = lut_lookup(Conflut, lhs);
564 if (cp != NULL)
565 err_fileline(Confname, cp->cf_lineno);
566 return (cp);
570 * conf_opts -- return the parsed opts for an entry
572 struct opts *
573 conf_opts(const char *lhs)
575 struct confinfo *cp = lut_lookup(Conflut, lhs);
577 if (cp != NULL)
578 return (cp->cf_opts);
579 return (opts_parse(NULL, NULL, OPTF_CONF));
583 * conf_replace -- replace an entry in the config file
585 void
586 conf_replace(const char *lhs, struct opts *newopts)
588 struct confinfo *cp = lut_lookup(Conflut, lhs);
590 if (Conffd == -1)
591 return;
593 if (cp != NULL) {
594 cp->cf_opts = newopts;
595 /* cp->cf_args = NULL; */
596 if (newopts == NULL)
597 cp->cf_flags |= CONFF_DELETED;
598 } else
599 fillconflist(0, lhs, newopts, NULL, 0);
601 Changed = CHG_BOTH;
605 * conf_set -- set options for an entry in the config file
607 void
608 conf_set(const char *entry, char *o, const char *optarg)
610 struct confinfo *cp = lut_lookup(Conflut, entry);
612 if (Conffd == -1)
613 return;
615 if (cp != NULL) {
616 cp->cf_flags &= ~CONFF_DELETED;
617 } else {
618 fillconflist(0, STRDUP(entry),
619 opts_parse(NULL, NULL, OPTF_CONF), NULL, 0);
620 if ((cp = lut_lookup(Conflut, entry)) == NULL)
621 err(0, "conf_set internal error");
623 (void) opts_set(cp->cf_opts, o, optarg);
624 if (strcmp(o, "P") == 0)
625 Changed |= CHG_TIMES;
626 else
627 Changed = CHG_BOTH;
631 * conf_entries -- list all the entry names
633 struct fn_list *
634 conf_entries(void)
636 return (Confentries);
639 /* print the config file */
640 static void
641 conf_print(FILE *cstream, FILE *tstream)
643 struct confinfo *cp;
644 char *exclude_opts = "PFfhnrvVw";
645 const char *timestamp;
647 if (tstream == NULL) {
648 exclude_opts++; /* -P option goes to config file */
649 } else {
650 (void) fprintf(tstream, gettext(
651 "# This file holds internal data for logadm(1M).\n"
652 "# Do not edit.\n"));
654 for (cp = Confinfo; cp; cp = cp->cf_next) {
655 if (cp->cf_flags & CONFF_DELETED)
656 continue;
657 if (cp->cf_entry) {
658 opts_printword(cp->cf_entry, cstream);
659 if (cp->cf_opts)
660 opts_print(cp->cf_opts, cstream, exclude_opts);
661 /* output timestamps to tstream */
662 if (tstream != NULL && (timestamp =
663 opts_optarg(cp->cf_opts, "P")) != NULL) {
664 opts_printword(cp->cf_entry, tstream);
665 (void) fprintf(tstream, " -P ");
666 opts_printword(timestamp, tstream);
667 (void) fprintf(tstream, "\n");
670 if (cp->cf_com) {
671 if (cp->cf_entry)
672 (void) fprintf(cstream, " ");
673 (void) fprintf(cstream, "#%s", cp->cf_com);
675 (void) fprintf(cstream, "\n");
679 #ifdef TESTMODULE
682 * test main for conf module, usage: a.out conffile
685 main(int argc, char *argv[])
687 struct opts *opts;
689 err_init(argv[0]);
690 setbuf(stdout, NULL);
691 opts_init(Opttable, Opttable_cnt);
693 opts = opts_parse(NULL, NULL, 0);
695 if (argc != 2)
696 err(EF_RAW, "usage: %s conffile\n", argv[0]);
698 conf_open(argv[1], argv[1], opts);
700 printf("conffile <%s>:\n", argv[1]);
701 conf_print(stdout, NULL);
703 conf_close(opts);
705 err_done(0);
706 /* NOTREACHED */
707 return (0);
710 #endif /* TESTMODULE */