1 /*
2 * CDDL HEADER START
3 *
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.
7 *
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.
12 *
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]
18 *
19 * CDDL HEADER END
20 */
21
22 /*
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
26 */
27
28 /*
29 * logadm/conf.c -- configuration file module
30 */
31
32 #include <stdio.h>
33 #include <libintl.h>
34 #include <fcntl.h>
35 #include <sys/types.h>
36 #include <sys/stat.h>
37 #include <sys/mman.h>
38 #include <ctype.h>
39 #include <strings.h>
40 #include <unistd.h>
41 #include <stdlib.h>
42 #include <limits.h>
43 #include "err.h"
44 #include "lut.h"
45 #include "fn.h"
46 #include "opts.h"
47 #include "conf.h"
48
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);
55
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 */
68 #define CHG_NONE 0
69 #define CHG_TIMES 1
70 #define CHG_BOTH 3
71
72 /*
73 * our structured representation of the configuration file
74 * is made up of a list of these
75 */
76 struct confinfo {
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 */
82 int cf_flags;
83 };
84
85 #define CONFF_DELETED 1 /* entry should be deleted on write back */
86 #define CONFF_TSONLY 2 /* entry should only be in timestamps file */
87
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 */
92
93 /* allocate & fill in another entry in our list */
94 static void
fillconflist(int lineno,const char * entry,struct opts * opts,const char * com,int flags)95 fillconflist(int lineno, const char *entry,
96 struct opts *opts, const char *com, int flags)
97 {
98 struct confinfo *cp = MALLOC(sizeof (*cp));
99
100 cp->cf_next = NULL;
101 cp->cf_lineno = lineno;
102 cp->cf_entry = entry;
103 cp->cf_opts = opts;
104 cp->cf_com = com;
105 cp->cf_flags = flags;
106 if (entry != NULL) {
107 Conflut = lut_add(Conflut, entry, cp);
108 fn_list_adds(Confentries, entry);
109 }
110 if (Confinfo == NULL)
111 Confinfo = Confinfolast = cp;
112 else {
113 Confinfolast->cf_next = cp;
114 Confinfolast = cp;
115 }
116 }
117
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
122
123 /* callback for lut_walk to build a cmdargs vector */
124 static void
fillargs(char * arg)125 fillargs(char *arg)
126 {
127 if (ArgsI >= ArgsN) {
128 /* need bigger table */
129 Args = REALLOC(Args, sizeof (char *) * (ArgsN + CONF_ARGS_INC));
130 ArgsN += CONF_ARGS_INC;
131 }
132 Args[ArgsI++] = arg;
133 }
134
135 /* isolate and return the next token */
136 static char *
nexttok(char ** ptrptr)137 nexttok(char **ptrptr)
138 {
139 char *ptr = *ptrptr;
140 char *eptr;
141 char *quote = NULL;
142
143 while (*ptr && isspace(*ptr))
144 ptr++;
145
146 if (*ptr == '"' || *ptr == '\'')
147 quote = ptr++;
148
149 for (eptr = ptr; *eptr; eptr++)
150 if (quote && *eptr == *quote) {
151 /* found end quote */
152 *eptr++ = '\0';
153 *ptrptr = eptr;
154 return (ptr);
155 } else if (!quote && isspace(*eptr)) {
156 /* found end of unquoted area */
157 *eptr++ = '\0';
158 *ptrptr = eptr;
159 return (ptr);
160 }
161
162 if (quote != NULL)
163 err(EF_FILE|EF_JMP, "Unbalanced %c quote", *quote);
164 /*NOTREACHED*/
165
166 *ptrptr = eptr;
167
168 if (ptr == eptr)
169 return (NULL);
170 else
171 return (ptr);
172 }
173
174 /*
175 * scan the memory image of a file
176 * returns: 0: error, 1: ok, 3: -P option found
177 */
178 static int
conf_scan(const char * fname,char * buf,int buflen,int timescan)179 conf_scan(const char *fname, char *buf, int buflen, int timescan)
180 {
181 int ret = 1;
182 int lineno = 0;
183 int flags = 0;
184 char *line;
185 char *eline;
186 char *ebuf;
187 char *entry, *comment;
188
189 ebuf = &buf[buflen];
190
191 if (buf[buflen - 1] != '\n')
192 err(EF_WARN|EF_FILE, "file %s doesn't end with newline, "
193 "last line ignored.", fname);
194
195 for (line = buf; line < ebuf; line = eline) {
196 char *ap;
197 struct opts *opts = NULL;
198 struct confinfo *cp = NULL;
199
200 lineno++;
201 err_fileline(fname, lineno);
202 eline = line;
203 comment = NULL;
204 for (; eline < ebuf; eline++) {
205 /* check for continued lines */
206 if (comment == NULL && *eline == '\\' &&
207 eline + 1 < ebuf && *(eline + 1) == '\n') {
208 *eline = ' ';
209 *(eline + 1) = ' ';
210 lineno++;
211 err_fileline(fname, lineno);
212 continue;
213 }
214
215 /* check for comments */
216 if (comment == NULL && *eline == '#') {
217 *eline = '\0';
218 comment = (eline + 1);
219 continue;
220 }
221
222 /* check for end of line */
223 if (*eline == '\n')
224 break;
225 }
226 if (comment >= ebuf)
227 comment = NULL;
228 if (eline >= ebuf) {
229 /* discard trailing unterminated line */
230 continue;
231 }
232 *eline++ = '\0';
233
234 /*
235 * now we have the entry, if any, at "line"
236 * and the comment, if any, at "comment"
237 */
238
239 /* entry is first token */
240 entry = nexttok(&line);
241 if (entry == NULL) {
242 /* it's just a comment line */
243 if (!timescan)
244 fillconflist(lineno, entry, NULL, comment, 0);
245 continue;
246 }
247 if (strcmp(entry, "logadm-version") == 0) {
248 /*
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.
255 */
256 if ((entry = nexttok(&line)) != NULL &&
257 strcmp(entry, "1") != 0)
258 err(0, "%s version not supported "
259 "by this version of logadm.",
260 fname);
261 continue;
262 }
263
264 /* form an argv array */
265 ArgsI = 0;
266 while (ap = nexttok(&line))
267 fillargs(ap);
268
269 /*
270 * If there is no next token on the line, make sure that
271 * we get a non-NULL Args array.
272 */
273 if (Args == NULL)
274 fillargs(NULL);
275
276 Args[ArgsI] = NULL;
277
278 LOCAL_ERR_BEGIN {
279 if (SETJMP) {
280 err(EF_FILE, "cannot process invalid entry %s",
281 entry);
282 ret = 0;
283 LOCAL_ERR_BREAK;
284 }
285
286 if (timescan) {
287 /* append to config options */
288 cp = lut_lookup(Conflut, entry);
289 if (cp != NULL) {
290 opts = cp->cf_opts;
291 }
292 }
293 opts = opts_parse(opts, Args, OPTF_CONF);
294 if (!timescan || cp == NULL) {
295 /*
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
302 * the log file name.
303 */
304 flags = 0;
305 if (timescan && cp == NULL)
306 flags = CONFF_TSONLY;
307 fillconflist(lineno, entry, opts, comment,
308 flags);
309 }
310 LOCAL_ERR_END }
311
312 if (ret == 1 && opts && opts_optarg(opts, "P") != NULL)
313 ret = 3;
314 }
315
316 err_fileline(NULL, 0);
317 return (ret);
318 }
319
320 /*
321 * conf_open -- open the configuration file, lock it if we have write perms
322 */
323 int
conf_open(const char * cfname,const char * tfname,struct opts * cliopts)324 conf_open(const char *cfname, const char *tfname, struct opts *cliopts)
325 {
326 struct stat stbuf1, stbuf2, stbuf3;
327 struct flock flock;
328 int ret;
329
330 Confname = cfname;
331 Timesname = tfname;
332 Confentries = fn_list_new(NULL);
333 Changed = CHG_NONE;
334
335 Changing = CHG_TIMES;
336 if (opts_count(cliopts, "Vn") != 0)
337 Changing = CHG_NONE;
338 else if (opts_count(cliopts, "rw") != 0)
339 Changing = CHG_BOTH;
340
341 Singlefile = strcmp(Confname, Timesname) == 0;
342 if (Singlefile && Changing == CHG_TIMES)
343 Changing = CHG_BOTH;
344
345 /* special case this so we don't even try locking the file */
346 if (strcmp(Confname, "/dev/null") == 0)
347 return (0);
348
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);
357 }
358
359 flock.l_type = (Canchange == CHG_BOTH) ? F_WRLCK : F_RDLCK;
360 flock.l_whence = SEEK_SET;
361 flock.l_start = 0;
362 flock.l_len = 1;
363 if (fcntl(Conffd, F_SETLKW, &flock) < 0)
364 err(EF_SYS, "flock on %s", Confname);
365
366 /* wait until after file is locked to get filesize */
367 if (fstat(Conffd, &stbuf1) < 0)
368 err(EF_SYS, "fstat on %s", Confname);
369
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);
376 Conffd = -1;
377 }
378 }
379
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);
387 }
388
389 flock.l_type = (Canchange != CHG_NONE) ? F_WRLCK : F_RDLCK;
390 flock.l_whence = SEEK_SET;
391 flock.l_start = 0;
392 flock.l_len = 1;
393 if (fcntl(Timesfd, F_SETLKW, &flock) < 0)
394 err(EF_SYS, "flock on %s", Timesname);
395
396 /* wait until after file is locked to get filesize */
397 if (fstat(Timesfd, &stbuf2) < 0)
398 err(EF_SYS, "fstat on %s", Timesname);
399
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);
406 Timesfd = -1;
407 continue;
408 }
409
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);
415 }
416
417 Conflen = stbuf1.st_size;
418 Timeslen = stbuf2.st_size;
419
420 if (Conflen == 0)
421 return (1); /* empty file, don't bother parsing it */
422
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);
426
427 ret = conf_scan(Confname, Confbuf, Conflen, 0);
428 if (ret == 3 && !Singlefile && Canchange == CHG_BOTH) {
429 /*
430 * arrange to transfer any timestamps
431 * from conf_file to timestamps_file
432 */
433 Changing = Changed = CHG_BOTH;
434 }
435
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);
442 }
443
444 /*
445 * possible future enhancement: go through and mark any entries:
446 * logfile -P <date>
447 * as DELETED if the logfile doesn't exist
448 */
449
450 return (ret);
451 }
452
453 /*
454 * conf_close -- close the configuration file
455 */
456 void
conf_close(struct opts * opts)457 conf_close(struct opts *opts)
458 {
459 char cuname[PATH_MAX], tuname[PATH_MAX];
460 int cfd, tfd;
461 FILE *cfp = NULL, *tfp = NULL;
462 boolean_t safe_update = B_TRUE;
463
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);
468 goto cleanup;
469 }
470
471 if (Debug > 1) {
472 (void) fprintf(stderr, "conf_close, saving logadm context:\n");
473 conf_print(stderr, NULL);
474 }
475
476 cuname[0] = tuname[0] = '\0';
477 LOCAL_ERR_BEGIN {
478 if (SETJMP) {
479 safe_update = B_FALSE;
480 LOCAL_ERR_BREAK;
481 }
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",
487 Confname);
488 if ((cfd = mkstemp(cuname)) == -1)
489 err(EF_SYS|EF_JMP, "open %s replacement",
490 Confname);
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);
497 } else {
498 /* just toss away the configuration data */
499 cfp = fopen("/dev/null", "w");
500 }
501 if (!Singlefile) {
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",
506 Timesname);
507 if ((tfd = mkstemp(tuname)) == -1)
508 err(EF_SYS|EF_JMP, "open %s replacement",
509 Timesname);
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);
516 }
517
518 conf_print(cfp, tfp);
519 if (fclose(cfp) < 0)
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);
523 LOCAL_ERR_END }
524
525 if (!safe_update) {
526 if (cuname[0] != 0)
527 (void) unlink(cuname);
528 if (tuname[0] != 0)
529 (void) unlink(tuname);
530 err(EF_JMP, "unsafe to update configuration file "
531 "or timestamps");
532 return;
533 }
534
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);
542 Changed = CHG_NONE;
543
544 cleanup:
545 if (Conffd != -1) {
546 (void) close(Conffd);
547 Conffd = -1;
548 }
549 if (Timesfd != -1) {
550 (void) close(Timesfd);
551 Timesfd = -1;
552 }
553 if (Conflut) {
554 lut_free(Conflut, free);
555 Conflut = NULL;
556 }
557 if (Confentries) {
558 fn_list_free(Confentries);
559 Confentries = NULL;
560 }
561 }
562
563 /*
564 * conf_lookup -- lookup an entry in the config file
565 */
566 void *
conf_lookup(const char * lhs)567 conf_lookup(const char *lhs)
568 {
569 struct confinfo *cp = lut_lookup(Conflut, lhs);
570
571 if (cp != NULL)
572 err_fileline(Confname, cp->cf_lineno);
573 return (cp);
574 }
575
576 /*
577 * conf_opts -- return the parsed opts for an entry
578 */
579 struct opts *
conf_opts(const char * lhs)580 conf_opts(const char *lhs)
581 {
582 struct confinfo *cp = lut_lookup(Conflut, lhs);
583
584 if (cp != NULL)
585 return (cp->cf_opts);
586 return (opts_parse(NULL, NULL, OPTF_CONF));
587 }
588
589 /*
590 * conf_replace -- replace an entry in the config file
591 */
592 void
conf_replace(const char * lhs,struct opts * newopts)593 conf_replace(const char *lhs, struct opts *newopts)
594 {
595 struct confinfo *cp = lut_lookup(Conflut, lhs);
596
597 if (Conffd == -1)
598 return;
599
600 if (cp != NULL) {
601 cp->cf_opts = newopts;
602 /* cp->cf_args = NULL; */
603 if (newopts == NULL)
604 cp->cf_flags |= CONFF_DELETED;
605 } else
606 fillconflist(0, lhs, newopts, NULL, 0);
607
608 Changed = CHG_BOTH;
609 }
610
611 /*
612 * conf_set -- set options for an entry in the config file
613 */
614 void
conf_set(const char * entry,char * o,const char * optarg)615 conf_set(const char *entry, char *o, const char *optarg)
616 {
617 struct confinfo *cp = lut_lookup(Conflut, entry);
618
619 if (Conffd == -1)
620 return;
621
622 if (cp != NULL) {
623 cp->cf_flags &= ~CONFF_DELETED;
624 } else {
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");
629 }
630 (void) opts_set(cp->cf_opts, o, optarg);
631 if (strcmp(o, "P") == 0)
632 Changed |= CHG_TIMES;
633 else
634 Changed = CHG_BOTH;
635 }
636
637 /*
638 * conf_entries -- list all the entry names
639 */
640 struct fn_list *
conf_entries(void)641 conf_entries(void)
642 {
643 return (Confentries);
644 }
645
646 /* print the config file */
647 static void
conf_print(FILE * cstream,FILE * tstream)648 conf_print(FILE *cstream, FILE *tstream)
649 {
650 struct confinfo *cp;
651 char *exclude_opts = "PFfhnrvVw";
652 const char *timestamp;
653
654 if (tstream == NULL) {
655 exclude_opts++; /* -P option goes to config file */
656 } else {
657 (void) fprintf(tstream, gettext(
658 "# This file holds internal data for logadm(8).\n"
659 "# Do not edit.\n"));
660 }
661 for (cp = Confinfo; cp; cp = cp->cf_next) {
662 if (cp->cf_flags & CONFF_DELETED)
663 continue;
664 if (cp->cf_entry) {
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");
672 }
673 if (cp->cf_flags & CONFF_TSONLY)
674 continue;
675
676 opts_printword(cp->cf_entry, cstream);
677 if (cp->cf_opts)
678 opts_print(cp->cf_opts, cstream, exclude_opts);
679 }
680 if (cp->cf_com) {
681 if (cp->cf_entry)
682 (void) fprintf(cstream, " ");
683 (void) fprintf(cstream, "#%s", cp->cf_com);
684 }
685 (void) fprintf(cstream, "\n");
686 }
687 }
688
689 #ifdef TESTMODULE
690
691 int Debug;
692
693 /*
694 * test main for conf module, usage: a.out conffile
695 */
696 int
main(int argc,char * argv[])697 main(int argc, char *argv[])
698 {
699 struct opts *opts;
700
701 err_init(argv[0]);
702 setbuf(stdout, NULL);
703 opts_init(Opttable, Opttable_cnt);
704
705 opts = opts_parse(NULL, NULL, 0);
706
707 if (argc != 2)
708 err(EF_RAW, "usage: %s conffile\n", argv[0]);
709
710 (void) conf_open(argv[1], argv[1], opts);
711
712 printf("conffile <%s>:\n", argv[1]);
713 conf_print(stdout, NULL);
714
715 conf_close(opts);
716
717 err_done(0);
718 /* NOTREACHED */
719 return (0);
720 }
721
722 #endif /* TESTMODULE */
723