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]
23 * Copyright (c) 2001, 2010, Oracle and/or its affiliates. All rights reserved.
24 * Copyright 2013, Joyent, Inc. All rights reserved.
25 * Copyright 2018 Sebastian Wiedenroth
29 * logadm/conf.c -- configuration file module
35 #include <sys/types.h>
49 /* forward declarations of functions private to this module */
50 static void fillconflist(int lineno
, const char *entry
,
51 struct opts
*opts
, const char *com
, int flags
);
52 static void fillargs(char *arg
);
53 static char *nexttok(char **ptrptr
);
54 static void conf_print(FILE *cstream
, FILE *tstream
);
56 static const char *Confname
; /* name of the confile file */
57 static int Conffd
= -1; /* file descriptor for config file */
58 static char *Confbuf
; /* copy of the config file (a la mmap()) */
59 static int Conflen
; /* length of mmap'd config file area */
60 static const char *Timesname
; /* name of the timestamps file */
61 static int Timesfd
= -1; /* file descriptor for timestamps file */
62 static char *Timesbuf
; /* copy of the timestamps file (a la mmap()) */
63 static int Timeslen
; /* length of mmap'd timestamps area */
64 static int Singlefile
; /* Conf and Times in the same file */
65 static int Changed
; /* what changes need to be written back */
66 static int Canchange
; /* what changes can be written back */
67 static int Changing
; /* what changes have been requested */
73 * our structured representation of the configuration file
74 * is made up of a list of these
77 struct confinfo
*cf_next
;
78 int cf_lineno
; /* line number in file */
79 const char *cf_entry
; /* name of entry, if line has an entry */
80 struct opts
*cf_opts
; /* parsed rhs of entry */
81 const char *cf_com
; /* any comment text found */
85 #define CONFF_DELETED 1 /* entry should be deleted on write back */
86 #define CONFF_TSONLY 2 /* entry should only be in timestamps file */
88 static struct confinfo
*Confinfo
; /* the entries in the config file */
89 static struct confinfo
*Confinfolast
; /* end of list */
90 static struct lut
*Conflut
; /* lookup table keyed by entry name */
91 static struct fn_list
*Confentries
; /* list of valid entry names */
93 /* allocate & fill in another entry in our list */
95 fillconflist(int lineno
, const char *entry
,
96 struct opts
*opts
, const char *com
, int flags
)
98 struct confinfo
*cp
= MALLOC(sizeof (*cp
));
101 cp
->cf_lineno
= lineno
;
102 cp
->cf_entry
= entry
;
105 cp
->cf_flags
= flags
;
107 Conflut
= lut_add(Conflut
, entry
, cp
);
108 fn_list_adds(Confentries
, entry
);
110 if (Confinfo
== NULL
)
111 Confinfo
= Confinfolast
= cp
;
113 Confinfolast
->cf_next
= cp
;
118 static char **Args
; /* static buffer for args */
119 static int ArgsN
; /* size of our static buffer */
120 static int ArgsI
; /* index into Cmdargs as we walk table */
121 #define CONF_ARGS_INC 1024
123 /* callback for lut_walk to build a cmdargs vector */
127 if (ArgsI
>= ArgsN
) {
128 /* need bigger table */
129 Args
= REALLOC(Args
, sizeof (char *) * (ArgsN
+ CONF_ARGS_INC
));
130 ArgsN
+= CONF_ARGS_INC
;
135 /* isolate and return the next token */
137 nexttok(char **ptrptr
)
143 while (*ptr
&& isspace(*ptr
))
146 if (*ptr
== '"' || *ptr
== '\'')
149 for (eptr
= ptr
; *eptr
; eptr
++)
150 if (quote
&& *eptr
== *quote
) {
151 /* found end quote */
155 } else if (!quote
&& isspace(*eptr
)) {
156 /* found end of unquoted area */
163 err(EF_FILE
|EF_JMP
, "Unbalanced %c quote", *quote
);
175 * scan the memory image of a file
176 * returns: 0: error, 1: ok, 3: -P option found
179 conf_scan(const char *fname
, char *buf
, int buflen
, int timescan
)
187 char *entry
, *comment
;
191 if (buf
[buflen
- 1] != '\n')
192 err(EF_WARN
|EF_FILE
, "file %s doesn't end with newline, "
193 "last line ignored.", fname
);
195 for (line
= buf
; line
< ebuf
; line
= eline
) {
197 struct opts
*opts
= NULL
;
201 err_fileline(fname
, lineno
);
204 for (; eline
< ebuf
; eline
++) {
205 /* check for continued lines */
206 if (comment
== NULL
&& *eline
== '\\' &&
207 eline
+ 1 < ebuf
&& *(eline
+ 1) == '\n') {
211 err_fileline(fname
, lineno
);
215 /* check for comments */
216 if (comment
== NULL
&& *eline
== '#') {
218 comment
= (eline
+ 1);
222 /* check for end of line */
229 /* discard trailing unterminated line */
235 * now we have the entry, if any, at "line"
236 * and the comment, if any, at "comment"
239 /* entry is first token */
240 entry
= nexttok(&line
);
242 /* it's just a comment line */
244 fillconflist(lineno
, entry
, NULL
, comment
, 0);
247 if (strcmp(entry
, "logadm-version") == 0) {
249 * we somehow opened some future format
250 * conffile that we likely don't understand.
251 * if the given version is "1" then go on,
252 * otherwise someone is mixing versions
253 * and we can't help them other than to
254 * print an error and exit.
256 if ((entry
= nexttok(&line
)) != NULL
&&
257 strcmp(entry
, "1") != 0)
258 err(0, "%s version not supported "
259 "by this version of logadm.",
264 /* form an argv array */
266 while (ap
= nexttok(&line
))
270 * If there is no next token on the line, make sure that
271 * we get a non-NULL Args array.
280 err(EF_FILE
, "cannot process invalid entry %s",
287 /* append to config options */
288 cp
= lut_lookup(Conflut
, entry
);
293 opts
= opts_parse(opts
, Args
, OPTF_CONF
);
294 if (!timescan
|| cp
== NULL
) {
296 * If we're not doing timescan, we track this
297 * entry. If we are doing timescan and have
298 * what looks like an orphaned entry (cp ==
299 * NULL) then we also have to track. See the
300 * comment in rotatelog. We need to allow for
301 * the case where the logname is not the same as
306 flags
= CONFF_TSONLY
;
307 fillconflist(lineno
, entry
, opts
, comment
,
312 if (ret
== 1 && opts
&& opts_optarg(opts
, "P") != NULL
)
316 err_fileline(NULL
, 0);
321 * conf_open -- open the configuration file, lock it if we have write perms
324 conf_open(const char *cfname
, const char *tfname
, struct opts
*cliopts
)
326 struct stat stbuf1
, stbuf2
, stbuf3
;
332 Confentries
= fn_list_new(NULL
);
335 Changing
= CHG_TIMES
;
336 if (opts_count(cliopts
, "Vn") != 0)
338 else if (opts_count(cliopts
, "rw") != 0)
341 Singlefile
= strcmp(Confname
, Timesname
) == 0;
342 if (Singlefile
&& Changing
== CHG_TIMES
)
345 /* special case this so we don't even try locking the file */
346 if (strcmp(Confname
, "/dev/null") == 0)
349 while (Conffd
== -1) {
350 Canchange
= CHG_BOTH
;
351 if ((Conffd
= open(Confname
, O_RDWR
)) < 0) {
352 if (Changing
== CHG_BOTH
)
353 err(EF_SYS
, "open %s", Confname
);
354 Canchange
= CHG_TIMES
;
355 if ((Conffd
= open(Confname
, O_RDONLY
)) < 0)
356 err(EF_SYS
, "open %s", Confname
);
359 flock
.l_type
= (Canchange
== CHG_BOTH
) ? F_WRLCK
: F_RDLCK
;
360 flock
.l_whence
= SEEK_SET
;
363 if (fcntl(Conffd
, F_SETLKW
, &flock
) < 0)
364 err(EF_SYS
, "flock on %s", Confname
);
366 /* wait until after file is locked to get filesize */
367 if (fstat(Conffd
, &stbuf1
) < 0)
368 err(EF_SYS
, "fstat on %s", Confname
);
370 /* verify that we've got a lock on the active file */
371 if (stat(Confname
, &stbuf2
) < 0 ||
372 !(stbuf2
.st_dev
== stbuf1
.st_dev
&&
373 stbuf2
.st_ino
== stbuf1
.st_ino
)) {
374 /* wrong config file, try again */
375 (void) close(Conffd
);
380 while (!Singlefile
&& Timesfd
== -1) {
381 if ((Timesfd
= open(Timesname
, O_CREAT
|O_RDWR
, 0644)) < 0) {
382 if (Changing
!= CHG_NONE
)
383 err(EF_SYS
, "open %s", Timesname
);
384 Canchange
= CHG_NONE
;
385 if ((Timesfd
= open(Timesname
, O_RDONLY
)) < 0)
386 err(EF_SYS
, "open %s", Timesname
);
389 flock
.l_type
= (Canchange
!= CHG_NONE
) ? F_WRLCK
: F_RDLCK
;
390 flock
.l_whence
= SEEK_SET
;
393 if (fcntl(Timesfd
, F_SETLKW
, &flock
) < 0)
394 err(EF_SYS
, "flock on %s", Timesname
);
396 /* wait until after file is locked to get filesize */
397 if (fstat(Timesfd
, &stbuf2
) < 0)
398 err(EF_SYS
, "fstat on %s", Timesname
);
400 /* verify that we've got a lock on the active file */
401 if (stat(Timesname
, &stbuf3
) < 0 ||
402 !(stbuf2
.st_dev
== stbuf3
.st_dev
&&
403 stbuf2
.st_ino
== stbuf3
.st_ino
)) {
404 /* wrong timestamp file, try again */
405 (void) close(Timesfd
);
410 /* check that Timesname isn't an alias for Confname */
411 if (stbuf2
.st_dev
== stbuf1
.st_dev
&&
412 stbuf2
.st_ino
== stbuf1
.st_ino
)
413 err(0, "Timestamp file %s can't refer to "
414 "Configuration file %s", Timesname
, Confname
);
417 Conflen
= stbuf1
.st_size
;
418 Timeslen
= stbuf2
.st_size
;
421 return (1); /* empty file, don't bother parsing it */
423 if ((Confbuf
= (char *)mmap(0, Conflen
,
424 PROT_READ
| PROT_WRITE
, MAP_PRIVATE
, Conffd
, 0)) == (char *)-1)
425 err(EF_SYS
, "mmap on %s", Confname
);
427 ret
= conf_scan(Confname
, Confbuf
, Conflen
, 0);
428 if (ret
== 3 && !Singlefile
&& Canchange
== CHG_BOTH
) {
430 * arrange to transfer any timestamps
431 * from conf_file to timestamps_file
433 Changing
= Changed
= CHG_BOTH
;
436 if (Timesfd
!= -1 && Timeslen
!= 0) {
437 if ((Timesbuf
= (char *)mmap(0, Timeslen
,
438 PROT_READ
| PROT_WRITE
, MAP_PRIVATE
,
439 Timesfd
, 0)) == (char *)-1)
440 err(EF_SYS
, "mmap on %s", Timesname
);
441 ret
&= conf_scan(Timesname
, Timesbuf
, Timeslen
, 1);
445 * possible future enhancement: go through and mark any entries:
447 * as DELETED if the logfile doesn't exist
454 * conf_close -- close the configuration file
457 conf_close(struct opts
*opts
)
459 char cuname
[PATH_MAX
], tuname
[PATH_MAX
];
461 FILE *cfp
= NULL
, *tfp
= NULL
;
462 boolean_t safe_update
= B_TRUE
;
464 if (Changed
== CHG_NONE
|| opts_count(opts
, "n") != 0) {
465 if (opts_count(opts
, "v"))
466 (void) out("# %s and %s unchanged\n",
467 Confname
, Timesname
);
472 (void) fprintf(stderr
, "conf_close, saving logadm context:\n");
473 conf_print(stderr
, NULL
);
476 cuname
[0] = tuname
[0] = '\0';
479 safe_update
= B_FALSE
;
482 if (Changed
== CHG_BOTH
) {
483 if (Canchange
!= CHG_BOTH
)
484 err(EF_JMP
, "internal error: attempting "
485 "to update %s without locking", Confname
);
486 (void) snprintf(cuname
, sizeof (cuname
), "%sXXXXXX",
488 if ((cfd
= mkstemp(cuname
)) == -1)
489 err(EF_SYS
|EF_JMP
, "open %s replacement",
491 if (opts_count(opts
, "v"))
492 (void) out("# writing changes to %s\n", cuname
);
493 if (fchmod(cfd
, 0644) == -1)
494 err(EF_SYS
|EF_JMP
, "chmod %s", cuname
);
495 if ((cfp
= fdopen(cfd
, "w")) == NULL
)
496 err(EF_SYS
|EF_JMP
, "fdopen on %s", cuname
);
498 /* just toss away the configuration data */
499 cfp
= fopen("/dev/null", "w");
502 if (Canchange
== CHG_NONE
)
503 err(EF_JMP
, "internal error: attempting "
504 "to update %s without locking", Timesname
);
505 (void) snprintf(tuname
, sizeof (tuname
), "%sXXXXXX",
507 if ((tfd
= mkstemp(tuname
)) == -1)
508 err(EF_SYS
|EF_JMP
, "open %s replacement",
510 if (opts_count(opts
, "v"))
511 (void) out("# writing changes to %s\n", tuname
);
512 if (fchmod(tfd
, 0644) == -1)
513 err(EF_SYS
|EF_JMP
, "chmod %s", tuname
);
514 if ((tfp
= fdopen(tfd
, "w")) == NULL
)
515 err(EF_SYS
|EF_JMP
, "fdopen on %s", tuname
);
518 conf_print(cfp
, tfp
);
520 err(EF_SYS
|EF_JMP
, "fclose on %s", Confname
);
521 if (tfp
!= NULL
&& fclose(tfp
) < 0)
522 err(EF_SYS
|EF_JMP
, "fclose on %s", Timesname
);
527 (void) unlink(cuname
);
529 (void) unlink(tuname
);
530 err(EF_JMP
, "unsafe to update configuration file "
535 /* rename updated files into place */
536 if (cuname
[0] != '\0')
537 if (rename(cuname
, Confname
) < 0)
538 err(EF_SYS
, "rename %s to %s", cuname
, Confname
);
539 if (tuname
[0] != '\0')
540 if (rename(tuname
, Timesname
) < 0)
541 err(EF_SYS
, "rename %s to %s", tuname
, Timesname
);
546 (void) close(Conffd
);
550 (void) close(Timesfd
);
554 lut_free(Conflut
, free
);
558 fn_list_free(Confentries
);
564 * conf_lookup -- lookup an entry in the config file
567 conf_lookup(const char *lhs
)
569 struct confinfo
*cp
= lut_lookup(Conflut
, lhs
);
572 err_fileline(Confname
, cp
->cf_lineno
);
577 * conf_opts -- return the parsed opts for an entry
580 conf_opts(const char *lhs
)
582 struct confinfo
*cp
= lut_lookup(Conflut
, lhs
);
585 return (cp
->cf_opts
);
586 return (opts_parse(NULL
, NULL
, OPTF_CONF
));
590 * conf_replace -- replace an entry in the config file
593 conf_replace(const char *lhs
, struct opts
*newopts
)
595 struct confinfo
*cp
= lut_lookup(Conflut
, lhs
);
601 cp
->cf_opts
= newopts
;
602 /* cp->cf_args = NULL; */
604 cp
->cf_flags
|= CONFF_DELETED
;
606 fillconflist(0, lhs
, newopts
, NULL
, 0);
612 * conf_set -- set options for an entry in the config file
615 conf_set(const char *entry
, char *o
, const char *optarg
)
617 struct confinfo
*cp
= lut_lookup(Conflut
, entry
);
623 cp
->cf_flags
&= ~CONFF_DELETED
;
625 fillconflist(0, STRDUP(entry
),
626 opts_parse(NULL
, NULL
, OPTF_CONF
), NULL
, 0);
627 if ((cp
= lut_lookup(Conflut
, entry
)) == NULL
)
628 err(0, "conf_set internal error");
630 (void) opts_set(cp
->cf_opts
, o
, optarg
);
631 if (strcmp(o
, "P") == 0)
632 Changed
|= CHG_TIMES
;
638 * conf_entries -- list all the entry names
643 return (Confentries
);
646 /* print the config file */
648 conf_print(FILE *cstream
, FILE *tstream
)
651 char *exclude_opts
= "PFfhnrvVw";
652 const char *timestamp
;
654 if (tstream
== NULL
) {
655 exclude_opts
++; /* -P option goes to config file */
657 (void) fprintf(tstream
, gettext(
658 "# This file holds internal data for logadm(1M).\n"
659 "# Do not edit.\n"));
661 for (cp
= Confinfo
; cp
; cp
= cp
->cf_next
) {
662 if (cp
->cf_flags
& CONFF_DELETED
)
665 /* output timestamps to tstream */
666 if (tstream
!= NULL
&& (timestamp
=
667 opts_optarg(cp
->cf_opts
, "P")) != NULL
) {
668 opts_printword(cp
->cf_entry
, tstream
);
669 (void) fprintf(tstream
, " -P ");
670 opts_printword(timestamp
, tstream
);
671 (void) fprintf(tstream
, "\n");
673 if (cp
->cf_flags
& CONFF_TSONLY
)
676 opts_printword(cp
->cf_entry
, cstream
);
678 opts_print(cp
->cf_opts
, cstream
, exclude_opts
);
682 (void) fprintf(cstream
, " ");
683 (void) fprintf(cstream
, "#%s", cp
->cf_com
);
685 (void) fprintf(cstream
, "\n");
692 * test main for conf module, usage: a.out conffile
695 main(int argc
, char *argv
[])
700 setbuf(stdout
, NULL
);
701 opts_init(Opttable
, Opttable_cnt
);
703 opts
= opts_parse(NULL
, NULL
, 0);
706 err(EF_RAW
, "usage: %s conffile\n", argv
[0]);
708 conf_open(argv
[1], argv
[1], opts
);
710 printf("conffile <%s>:\n", argv
[1]);
711 conf_print(stdout
, NULL
);
720 #endif /* TESTMODULE */