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.
28 * logadm/conf.c -- configuration file module
34 #include <sys/types.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 */
72 * our structured representation of the configuration file
73 * is made up of a list of these
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 */
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 */
93 fillconflist(int lineno
, const char *entry
,
94 struct opts
*opts
, const char *com
, int flags
)
96 struct confinfo
*cp
= MALLOC(sizeof (*cp
));
99 cp
->cf_lineno
= lineno
;
100 cp
->cf_entry
= entry
;
103 cp
->cf_flags
= flags
;
105 Conflut
= lut_add(Conflut
, entry
, cp
);
106 fn_list_adds(Confentries
, entry
);
108 if (Confinfo
== NULL
)
109 Confinfo
= Confinfolast
= cp
;
111 Confinfolast
->cf_next
= 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 */
125 if (ArgsI
>= ArgsN
) {
126 /* need bigger table */
127 Args
= REALLOC(Args
, sizeof (char *) * (ArgsN
+ CONF_ARGS_INC
));
128 ArgsN
+= CONF_ARGS_INC
;
133 /* isolate and return the next token */
135 nexttok(char **ptrptr
)
141 while (*ptr
&& isspace(*ptr
))
144 if (*ptr
== '"' || *ptr
== '\'')
147 for (eptr
= ptr
; *eptr
; eptr
++)
148 if (quote
&& *eptr
== *quote
) {
149 /* found end quote */
153 } else if (!quote
&& isspace(*eptr
)) {
154 /* found end of unquoted area */
161 err(EF_FILE
|EF_JMP
, "Unbalanced %c quote", *quote
);
173 * scan the memory image of a file
174 * returns: 0: error, 1: ok, 3: -P option found
177 conf_scan(const char *fname
, char *buf
, int buflen
, int timescan
)
184 char *entry
, *comment
;
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
) {
194 struct opts
*opts
= NULL
;
198 err_fileline(fname
, lineno
);
201 for (; eline
< ebuf
; eline
++) {
202 /* check for continued lines */
203 if (comment
== NULL
&& *eline
== '\\' &&
204 eline
+ 1 < ebuf
&& *(eline
+ 1) == '\n') {
208 err_fileline(fname
, lineno
);
212 /* check for comments */
213 if (comment
== NULL
&& *eline
== '#') {
215 comment
= (eline
+ 1);
219 /* check for end of line */
226 /* discard trailing unterminated line */
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
);
239 /* it's just a comment line */
241 fillconflist(lineno
, entry
, NULL
, comment
, 0);
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.",
261 /* form an argv array */
263 while (ap
= nexttok(&line
))
267 * If there is no next token on the line, make sure that
268 * we get a non-NULL Args array.
277 err(EF_FILE
, "cannot process invalid entry %s",
284 /* append to config options */
285 cp
= lut_lookup(Conflut
, entry
);
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
301 fillconflist(lineno
, entry
, opts
, comment
, 0);
305 if (ret
== 1 && opts
&& opts_optarg(opts
, "P") != NULL
)
309 err_fileline(NULL
, 0);
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
;
325 Confentries
= fn_list_new(NULL
);
328 Changing
= CHG_TIMES
;
329 if (opts_count(cliopts
, "Vn") != 0)
331 else if (opts_count(cliopts
, "rw") != 0)
334 Singlefile
= strcmp(Confname
, Timesname
) == 0;
335 if (Singlefile
&& Changing
== CHG_TIMES
)
338 /* special case this so we don't even try locking the file */
339 if (strcmp(Confname
, "/dev/null") == 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
;
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
);
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
;
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
);
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
;
414 return (1); /* empty file, don't bother parsing it */
416 if ((Confbuf
= mmap(NULL
, 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
= mmap(NULL
, 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:
440 * as DELETED if the logfile doesn't exist
447 * conf_close -- close the configuration file
450 conf_close(struct opts
*opts
)
452 char cuname
[PATH_MAX
], tuname
[PATH_MAX
];
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
);
465 (void) fprintf(stderr
, "conf_close, saving logadm context:\n");
466 conf_print(stderr
, NULL
);
469 cuname
[0] = tuname
[0] = '\0';
472 safe_update
= B_FALSE
;
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",
481 if ((cfd
= mkstemp(cuname
)) == -1)
482 err(EF_SYS
|EF_JMP
, "open %s replacement",
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
);
491 /* just toss away the configuration data */
492 cfp
= fopen("/dev/null", "w");
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",
500 if ((tfd
= mkstemp(tuname
)) == -1)
501 err(EF_SYS
|EF_JMP
, "open %s replacement",
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
);
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
);
520 (void) unlink(cuname
);
522 (void) unlink(tuname
);
523 err(EF_JMP
, "unsafe to update configuration file "
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
);
539 (void) close(Conffd
);
543 (void) close(Timesfd
);
547 lut_free(Conflut
, free
);
551 fn_list_free(Confentries
);
557 * conf_lookup -- lookup an entry in the config file
560 conf_lookup(const char *lhs
)
562 struct confinfo
*cp
= lut_lookup(Conflut
, lhs
);
565 err_fileline(Confname
, cp
->cf_lineno
);
570 * conf_opts -- return the parsed opts for an entry
573 conf_opts(const char *lhs
)
575 struct confinfo
*cp
= lut_lookup(Conflut
, lhs
);
578 return (cp
->cf_opts
);
579 return (opts_parse(NULL
, NULL
, OPTF_CONF
));
583 * conf_replace -- replace an entry in the config file
586 conf_replace(const char *lhs
, struct opts
*newopts
)
588 struct confinfo
*cp
= lut_lookup(Conflut
, lhs
);
594 cp
->cf_opts
= newopts
;
595 /* cp->cf_args = NULL; */
597 cp
->cf_flags
|= CONFF_DELETED
;
599 fillconflist(0, lhs
, newopts
, NULL
, 0);
605 * conf_set -- set options for an entry in the config file
608 conf_set(const char *entry
, char *o
, const char *optarg
)
610 struct confinfo
*cp
= lut_lookup(Conflut
, entry
);
616 cp
->cf_flags
&= ~CONFF_DELETED
;
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
;
631 * conf_entries -- list all the entry names
636 return (Confentries
);
639 /* print the config file */
641 conf_print(FILE *cstream
, FILE *tstream
)
644 char *exclude_opts
= "PFfhnrvVw";
645 const char *timestamp
;
647 if (tstream
== NULL
) {
648 exclude_opts
++; /* -P option goes to config file */
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
)
658 opts_printword(cp
->cf_entry
, cstream
);
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");
672 (void) fprintf(cstream
, " ");
673 (void) fprintf(cstream
, "#%s", cp
->cf_com
);
675 (void) fprintf(cstream
, "\n");
682 * test main for conf module, usage: a.out conffile
685 main(int argc
, char *argv
[])
690 setbuf(stdout
, NULL
);
691 opts_init(Opttable
, Opttable_cnt
);
693 opts
= opts_parse(NULL
, NULL
, 0);
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
);
710 #endif /* TESTMODULE */