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 */
26
27 /*
28 * logadm/conf.c -- configuration file module
29 */
30
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"
47
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);
54
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
70
71 /*
72 * our structured representation of the configuration file
73 * is made up of a list of these
74 */
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;
82 };
83
84 #define CONFF_DELETED 1 /* entry should be deleted on write back */
85
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 */
90
91 /* allocate & fill in another entry in our list */
92 static void
fillconflist(int lineno,const char * entry,struct opts * opts,const char * com,int flags)93 fillconflist(int lineno, const char *entry,
94 struct opts *opts, const char *com, int flags)
95 {
96 struct confinfo *cp = MALLOC(sizeof (*cp));
97
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);
107 }
108 if (Confinfo == NULL)
109 Confinfo = Confinfolast = cp;
110 else {
111 Confinfolast->cf_next = cp;
112 Confinfolast = cp;
113 }
114 }
115
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
120
121 /* callback for lut_walk to build a cmdargs vector */
122 static void
fillargs(char * arg)123 fillargs(char *arg)
124 {
125 if (ArgsI >= ArgsN) {
126 /* need bigger table */
127 Args = REALLOC(Args, sizeof (char *) * (ArgsN + CONF_ARGS_INC));
128 ArgsN += CONF_ARGS_INC;
129 }
130 Args[ArgsI++] = arg;
131 }
132
133 /* isolate and return the next token */
134 static char *
nexttok(char ** ptrptr)135 nexttok(char **ptrptr)
136 {
137 char *ptr = *ptrptr;
138 char *eptr;
139 char *quote = NULL;
140
141 while (*ptr && isspace(*ptr))
142 ptr++;
143
144 if (*ptr == '"' || *ptr == '\'')
145 quote = ptr++;
146
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);
158 }
159
160 if (quote != NULL)
161 err(EF_FILE|EF_JMP, "Unbalanced %c quote", *quote);
162 /*NOTREACHED*/
163
164 *ptrptr = eptr;
165
166 if (ptr == eptr)
167 return (NULL);
168 else
169 return (ptr);
170 }
171
172 /*
173 * scan the memory image of a file
174 * returns: 0: error, 1: ok, 3: -P option found
175 */
176 static int
conf_scan(const char * fname,char * buf,int buflen,int timescan)177 conf_scan(const char *fname, char *buf, int buflen, int timescan)
178 {
179 int ret = 1;
180 int lineno = 0;
181 char *line;
182 char *eline;
183 char *ebuf;
184 char *entry, *comment;
185
186 ebuf = &buf[buflen];
187
188 if (buf[buflen - 1] != '\n')
189 err(EF_WARN|EF_FILE, "file %s doesn't end with newline, "
190 "last line ignored.", fname);
191
192 for (line = buf; line < ebuf; line = eline) {
193 char *ap;
194 struct opts *opts = NULL;
195 struct confinfo *cp;
196
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;
210 }
211
212 /* check for comments */
213 if (comment == NULL && *eline == '#') {
214 *eline = '\0';
215 comment = (eline + 1);
216 continue;
217 }
218
219 /* check for end of line */
220 if (*eline == '\n')
221 break;
222 }
223 if (comment >= ebuf)
224 comment = NULL;
225 if (eline >= ebuf) {
226 /* discard trailing unterminated line */
227 continue;
228 }
229 *eline++ = '\0';
230
231 /*
232 * now we have the entry, if any, at "line"
233 * and the comment, if any, at "comment"
234 */
235
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;
243 }
244 if (strcmp(entry, "logadm-version") == 0) {
245 /*
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.
252 */
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;
259 }
260
261 /* form an argv array */
262 ArgsI = 0;
263 while (ap = nexttok(&line))
264 fillargs(ap);
265 Args[ArgsI] = NULL;
266
267 LOCAL_ERR_BEGIN {
268 if (SETJMP) {
269 err(EF_FILE, "cannot process invalid entry %s",
270 entry);
271 ret = 0;
272 LOCAL_ERR_BREAK;
273 }
274
275 if (timescan) {
276 /* append to config options */
277 cp = lut_lookup(Conflut, entry);
278 if (cp != NULL) {
279 opts = cp->cf_opts;
280 }
281 }
282 opts = opts_parse(opts, Args, OPTF_CONF);
283 if (!timescan || cp == NULL) {
284 /*
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
291 * the log file name.
292 */
293 fillconflist(lineno, entry, opts, comment, 0);
294 }
295 LOCAL_ERR_END }
296
297 if (ret == 1 && opts && opts_optarg(opts, "P") != NULL)
298 ret = 3;
299 }
300
301 err_fileline(NULL, 0);
302 return (ret);
303 }
304
305 /*
306 * conf_open -- open the configuration file, lock it if we have write perms
307 */
308 int
conf_open(const char * cfname,const char * tfname,struct opts * cliopts)309 conf_open(const char *cfname, const char *tfname, struct opts *cliopts)
310 {
311 struct stat stbuf1, stbuf2, stbuf3;
312 struct flock flock;
313 int ret;
314
315 Confname = cfname;
316 Timesname = tfname;
317 Confentries = fn_list_new(NULL);
318 Changed = CHG_NONE;
319
320 Changing = CHG_TIMES;
321 if (opts_count(cliopts, "Vn") != 0)
322 Changing = CHG_NONE;
323 else if (opts_count(cliopts, "rw") != 0)
324 Changing = CHG_BOTH;
325
326 Singlefile = strcmp(Confname, Timesname) == 0;
327 if (Singlefile && Changing == CHG_TIMES)
328 Changing = CHG_BOTH;
329
330 /* special case this so we don't even try locking the file */
331 if (strcmp(Confname, "/dev/null") == 0)
332 return (0);
333
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);
342 }
343
344 flock.l_type = (Canchange == CHG_BOTH) ? F_WRLCK : F_RDLCK;
345 flock.l_whence = SEEK_SET;
346 flock.l_start = 0;
347 flock.l_len = 1;
348 if (fcntl(Conffd, F_SETLKW, &flock) < 0)
349 err(EF_SYS, "flock on %s", Confname);
350
351 /* wait until after file is locked to get filesize */
352 if (fstat(Conffd, &stbuf1) < 0)
353 err(EF_SYS, "fstat on %s", Confname);
354
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);
361 Conffd = -1;
362 }
363 }
364
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);
372 }
373
374 flock.l_type = (Canchange != CHG_NONE) ? F_WRLCK : F_RDLCK;
375 flock.l_whence = SEEK_SET;
376 flock.l_start = 0;
377 flock.l_len = 1;
378 if (fcntl(Timesfd, F_SETLKW, &flock) < 0)
379 err(EF_SYS, "flock on %s", Timesname);
380
381 /* wait until after file is locked to get filesize */
382 if (fstat(Timesfd, &stbuf2) < 0)
383 err(EF_SYS, "fstat on %s", Timesname);
384
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);
391 Timesfd = -1;
392 continue;
393 }
394
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);
400 }
401
402 Conflen = stbuf1.st_size;
403 Timeslen = stbuf2.st_size;
404
405 if (Conflen == 0)
406 return (1); /* empty file, don't bother parsing it */
407
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);
411
412 ret = conf_scan(Confname, Confbuf, Conflen, 0);
413 if (ret == 3 && !Singlefile && Canchange == CHG_BOTH) {
414 /*
415 * arrange to transfer any timestamps
416 * from conf_file to timestamps_file
417 */
418 Changing = Changed = CHG_BOTH;
419 }
420
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);
427 }
428
429 /*
430 * possible future enhancement: go through and mark any entries:
431 * logfile -P <date>
432 * as DELETED if the logfile doesn't exist
433 */
434
435 return (ret);
436 }
437
438 /*
439 * conf_close -- close the configuration file
440 */
441 void
conf_close(struct opts * opts)442 conf_close(struct opts *opts)
443 {
444 char cuname[PATH_MAX], tuname[PATH_MAX];
445 int cfd, tfd;
446 FILE *cfp = NULL, *tfp = NULL;
447 boolean_t safe_update = B_TRUE;
448
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);
453 goto cleanup;
454 }
455
456 if (Debug > 1) {
457 (void) fprintf(stderr, "conf_close, saving logadm context:\n");
458 conf_print(stderr, NULL);
459 }
460
461 cuname[0] = tuname[0] = '\0';
462 LOCAL_ERR_BEGIN {
463 if (SETJMP) {
464 safe_update = B_FALSE;
465 LOCAL_ERR_BREAK;
466 }
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",
472 Confname);
473 if ((cfd = mkstemp(cuname)) == -1)
474 err(EF_SYS|EF_JMP, "open %s replacement",
475 Confname);
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);
482 } else {
483 /* just toss away the configuration data */
484 cfp = fopen("/dev/null", "w");
485 }
486 if (!Singlefile) {
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",
491 Timesname);
492 if ((tfd = mkstemp(tuname)) == -1)
493 err(EF_SYS|EF_JMP, "open %s replacement",
494 Timesname);
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);
501 }
502
503 conf_print(cfp, tfp);
504 if (fclose(cfp) < 0)
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);
508 LOCAL_ERR_END }
509
510 if (!safe_update) {
511 if (cuname[0] != 0)
512 (void) unlink(cuname);
513 if (tuname[0] != 0)
514 (void) unlink(tuname);
515 err(EF_JMP, "unsafe to update configuration file "
516 "or timestamps");
517 return;
518 }
519
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);
527 Changed = CHG_NONE;
528
529 cleanup:
530 if (Conffd != -1) {
531 (void) close(Conffd);
532 Conffd = -1;
533 }
534 if (Timesfd != -1) {
535 (void) close(Timesfd);
536 Timesfd = -1;
537 }
538 if (Conflut) {
539 lut_free(Conflut, free);
540 Conflut = NULL;
541 }
542 if (Confentries) {
543 fn_list_free(Confentries);
544 Confentries = NULL;
545 }
546 }
547
548 /*
549 * conf_lookup -- lookup an entry in the config file
550 */
551 void *
conf_lookup(const char * lhs)552 conf_lookup(const char *lhs)
553 {
554 struct confinfo *cp = lut_lookup(Conflut, lhs);
555
556 if (cp != NULL)
557 err_fileline(Confname, cp->cf_lineno);
558 return (cp);
559 }
560
561 /*
562 * conf_opts -- return the parsed opts for an entry
563 */
564 struct opts *
conf_opts(const char * lhs)565 conf_opts(const char *lhs)
566 {
567 struct confinfo *cp = lut_lookup(Conflut, lhs);
568
569 if (cp != NULL)
570 return (cp->cf_opts);
571 return (opts_parse(NULL, NULL, OPTF_CONF));
572 }
573
574 /*
575 * conf_replace -- replace an entry in the config file
576 */
577 void
conf_replace(const char * lhs,struct opts * newopts)578 conf_replace(const char *lhs, struct opts *newopts)
579 {
580 struct confinfo *cp = lut_lookup(Conflut, lhs);
581
582 if (Conffd == -1)
583 return;
584
585 if (cp != NULL) {
586 cp->cf_opts = newopts;
587 /* cp->cf_args = NULL; */
588 if (newopts == NULL)
589 cp->cf_flags |= CONFF_DELETED;
590 } else
591 fillconflist(0, lhs, newopts, NULL, 0);
592
593 Changed = CHG_BOTH;
594 }
595
596 /*
597 * conf_set -- set options for an entry in the config file
598 */
599 void
conf_set(const char * entry,char * o,const char * optarg)600 conf_set(const char *entry, char *o, const char *optarg)
601 {
602 struct confinfo *cp = lut_lookup(Conflut, entry);
603
604 if (Conffd == -1)
605 return;
606
607 if (cp != NULL) {
608 cp->cf_flags &= ~CONFF_DELETED;
609 } else {
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");
614 }
615 (void) opts_set(cp->cf_opts, o, optarg);
616 if (strcmp(o, "P") == 0)
617 Changed |= CHG_TIMES;
618 else
619 Changed = CHG_BOTH;
620 }
621
622 /*
623 * conf_entries -- list all the entry names
624 */
625 struct fn_list *
conf_entries(void)626 conf_entries(void)
627 {
628 return (Confentries);
629 }
630
631 /* print the config file */
632 static void
conf_print(FILE * cstream,FILE * tstream)633 conf_print(FILE *cstream, FILE *tstream)
634 {
635 struct confinfo *cp;
636 char *exclude_opts = "PFfhnrvVw";
637 const char *timestamp;
638
639 if (tstream == NULL) {
640 exclude_opts++; /* -P option goes to config file */
641 } else {
642 (void) fprintf(tstream, gettext(
643 "# This file holds internal data for logadm(1M).\n"
644 "# Do not edit.\n"));
645 }
646 for (cp = Confinfo; cp; cp = cp->cf_next) {
647 if (cp->cf_flags & CONFF_DELETED)
648 continue;
649 if (cp->cf_entry) {
650 opts_printword(cp->cf_entry, cstream);
651 if (cp->cf_opts)
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");
660 }
661 }
662 if (cp->cf_com) {
663 if (cp->cf_entry)
664 (void) fprintf(cstream, " ");
665 (void) fprintf(cstream, "#%s", cp->cf_com);
666 }
667 (void) fprintf(cstream, "\n");
668 }
669 }
670
671 #ifdef TESTMODULE
672
673 /*
674 * test main for conf module, usage: a.out conffile
675 */
676 int
main(int argc,char * argv[])677 main(int argc, char *argv[])
678 {
679 struct opts *opts;
680
681 err_init(argv[0]);
682 setbuf(stdout, NULL);
683 opts_init(Opttable, Opttable_cnt);
684
685 opts = opts_parse(NULL, NULL, 0);
686
687 if (argc != 2)
688 err(EF_RAW, "usage: %s conffile\n", argv[0]);
689
690 conf_open(argv[1], argv[1], opts);
691
692 printf("conffile <%s>:\n", argv[1]);
693 conf_print(stdout, NULL);
694
695 conf_close(opts);
696
697 err_done(0);
698 /* NOTREACHED */
699 return (0);
700 }
701
702 #endif /* TESTMODULE */
703