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
))
269 err(EF_FILE
, "cannot process invalid entry %s",
276 /* append to config options */
277 cp
= lut_lookup(Conflut
, entry
);
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
293 fillconflist(lineno
, entry
, opts
, comment
, 0);
297 if (ret
== 1 && opts
&& opts_optarg(opts
, "P") != NULL
)
301 err_fileline(NULL
, 0);
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
;
317 Confentries
= fn_list_new(NULL
);
320 Changing
= CHG_TIMES
;
321 if (opts_count(cliopts
, "Vn") != 0)
323 else if (opts_count(cliopts
, "rw") != 0)
326 Singlefile
= strcmp(Confname
, Timesname
) == 0;
327 if (Singlefile
&& Changing
== CHG_TIMES
)
330 /* special case this so we don't even try locking the file */
331 if (strcmp(Confname
, "/dev/null") == 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
;
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
);
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
;
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
);
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
;
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:
432 * as DELETED if the logfile doesn't exist
439 * conf_close -- close the configuration file
442 conf_close(struct opts
*opts
)
444 char cuname
[PATH_MAX
], tuname
[PATH_MAX
];
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
);
457 (void) fprintf(stderr
, "conf_close, saving logadm context:\n");
458 conf_print(stderr
, NULL
);
461 cuname
[0] = tuname
[0] = '\0';
464 safe_update
= B_FALSE
;
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",
473 if ((cfd
= mkstemp(cuname
)) == -1)
474 err(EF_SYS
|EF_JMP
, "open %s replacement",
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
);
483 /* just toss away the configuration data */
484 cfp
= fopen("/dev/null", "w");
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",
492 if ((tfd
= mkstemp(tuname
)) == -1)
493 err(EF_SYS
|EF_JMP
, "open %s replacement",
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
);
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
);
512 (void) unlink(cuname
);
514 (void) unlink(tuname
);
515 err(EF_JMP
, "unsafe to update configuration file "
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
);
531 (void) close(Conffd
);
535 (void) close(Timesfd
);
539 lut_free(Conflut
, free
);
543 fn_list_free(Confentries
);
549 * conf_lookup -- lookup an entry in the config file
552 conf_lookup(const char *lhs
)
554 struct confinfo
*cp
= lut_lookup(Conflut
, lhs
);
557 err_fileline(Confname
, cp
->cf_lineno
);
562 * conf_opts -- return the parsed opts for an entry
565 conf_opts(const char *lhs
)
567 struct confinfo
*cp
= lut_lookup(Conflut
, lhs
);
570 return (cp
->cf_opts
);
571 return (opts_parse(NULL
, NULL
, OPTF_CONF
));
575 * conf_replace -- replace an entry in the config file
578 conf_replace(const char *lhs
, struct opts
*newopts
)
580 struct confinfo
*cp
= lut_lookup(Conflut
, lhs
);
586 cp
->cf_opts
= newopts
;
587 /* cp->cf_args = NULL; */
589 cp
->cf_flags
|= CONFF_DELETED
;
591 fillconflist(0, lhs
, newopts
, NULL
, 0);
597 * conf_set -- set options for an entry in the config file
600 conf_set(const char *entry
, char *o
, const char *optarg
)
602 struct confinfo
*cp
= lut_lookup(Conflut
, entry
);
608 cp
->cf_flags
&= ~CONFF_DELETED
;
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
;
623 * conf_entries -- list all the entry names
628 return (Confentries
);
631 /* print the config file */
633 conf_print(FILE *cstream
, FILE *tstream
)
636 char *exclude_opts
= "PFfhnrvVw";
637 const char *timestamp
;
639 if (tstream
== NULL
) {
640 exclude_opts
++; /* -P option goes to config file */
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
)
650 opts_printword(cp
->cf_entry
, cstream
);
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");
664 (void) fprintf(cstream
, " ");
665 (void) fprintf(cstream
, "#%s", cp
->cf_com
);
667 (void) fprintf(cstream
, "\n");
674 * test main for conf module, usage: a.out conffile
677 main(int argc
, char *argv
[])
682 setbuf(stdout
, NULL
);
683 opts_init(Opttable
, Opttable_cnt
);
685 opts
= opts_parse(NULL
, NULL
, 0);
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
);
702 #endif /* TESTMODULE */