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 */
25
26 /*
27 * logadm/conf.c -- configuration file module
28 */
29
30 #include <stdio.h>
31 #include <libintl.h>
32 #include <fcntl.h>
33 #include <sys/types.h>
34 #include <sys/stat.h>
35 #include <sys/mman.h>
36 #include <ctype.h>
37 #include <strings.h>
38 #include <unistd.h>
39 #include <stdlib.h>
40 #include <limits.h>
41 #include "err.h"
42 #include "lut.h"
43 #include "fn.h"
44 #include "opts.h"
45 #include "conf.h"
46
47 /* forward declarations of functions private to this module */
48 static void fillconflist(int lineno, const char *entry,
49 struct opts *opts, const char *com, int flags);
50 static void fillargs(char *arg);
51 static char *nexttok(char **ptrptr);
52 static void conf_print(FILE *cstream, FILE *tstream);
53
54 static const char *Confname; /* name of the confile file */
55 static int Conffd = -1; /* file descriptor for config file */
56 static char *Confbuf; /* copy of the config file (a la mmap()) */
57 static int Conflen; /* length of mmap'd config file area */
58 static const char *Timesname; /* name of the timestamps file */
59 static int Timesfd = -1; /* file descriptor for timestamps file */
60 static char *Timesbuf; /* copy of the timestamps file (a la mmap()) */
61 static int Timeslen; /* length of mmap'd timestamps area */
62 static int Singlefile; /* Conf and Times in the same file */
63 static int Changed; /* what changes need to be written back */
64 static int Canchange; /* what changes can be written back */
65 static int Changing; /* what changes have been requested */
66 #define CHG_NONE 0
67 #define CHG_TIMES 1
68 #define CHG_BOTH 3
69
70 /*
71 * our structured representation of the configuration file
72 * is made up of a list of these
73 */
74 struct confinfo {
75 struct confinfo *cf_next;
76 int cf_lineno; /* line number in file */
77 const char *cf_entry; /* name of entry, if line has an entry */
78 struct opts *cf_opts; /* parsed rhs of entry */
79 const char *cf_com; /* any comment text found */
80 int cf_flags;
81 };
82
83 #define CONFF_DELETED 1 /* entry should be deleted on write back */
84
85 static struct confinfo *Confinfo; /* the entries in the config file */
86 static struct confinfo *Confinfolast; /* end of list */
87 static struct lut *Conflut; /* lookup table keyed by entry name */
88 static struct fn_list *Confentries; /* list of valid entry names */
89
90 /* allocate & fill in another entry in our list */
91 static void
fillconflist(int lineno,const char * entry,struct opts * opts,const char * com,int flags)92 fillconflist(int lineno, const char *entry,
93 struct opts *opts, const char *com, int flags)
94 {
95 struct confinfo *cp = MALLOC(sizeof (*cp));
96
97 cp->cf_next = NULL;
98 cp->cf_lineno = lineno;
99 cp->cf_entry = entry;
100 cp->cf_opts = opts;
101 cp->cf_com = com;
102 cp->cf_flags = flags;
103 if (entry != NULL) {
104 Conflut = lut_add(Conflut, entry, cp);
105 fn_list_adds(Confentries, entry);
106 }
107 if (Confinfo == NULL)
108 Confinfo = Confinfolast = cp;
109 else {
110 Confinfolast->cf_next = cp;
111 Confinfolast = cp;
112 }
113 }
114
115 static char **Args; /* static buffer for args */
116 static int ArgsN; /* size of our static buffer */
117 static int ArgsI; /* index into Cmdargs as we walk table */
118 #define CONF_ARGS_INC 1024
119
120 /* callback for lut_walk to build a cmdargs vector */
121 static void
fillargs(char * arg)122 fillargs(char *arg)
123 {
124 if (ArgsI >= ArgsN) {
125 /* need bigger table */
126 Args = REALLOC(Args, sizeof (char *) * (ArgsN + CONF_ARGS_INC));
127 ArgsN += CONF_ARGS_INC;
128 }
129 Args[ArgsI++] = arg;
130 }
131
132 /* isolate and return the next token */
133 static char *
nexttok(char ** ptrptr)134 nexttok(char **ptrptr)
135 {
136 char *ptr = *ptrptr;
137 char *eptr;
138 char *quote = NULL;
139
140 while (*ptr && isspace(*ptr))
141 ptr++;
142
143 if (*ptr == '"' || *ptr == '\'')
144 quote = ptr++;
145
146 for (eptr = ptr; *eptr; eptr++)
147 if (quote && *eptr == *quote) {
148 /* found end quote */
149 *eptr++ = '\0';
150 *ptrptr = eptr;
151 return (ptr);
152 } else if (!quote && isspace(*eptr)) {
153 /* found end of unquoted area */
154 *eptr++ = '\0';
155 *ptrptr = eptr;
156 return (ptr);
157 }
158
159 if (quote != NULL)
160 err(EF_FILE|EF_JMP, "Unbalanced %c quote", *quote);
161 /*NOTREACHED*/
162
163 *ptrptr = eptr;
164
165 if (ptr == eptr)
166 return (NULL);
167 else
168 return (ptr);
169 }
170
171 /*
172 * scan the memory image of a file
173 * returns: 0: error, 1: ok, 3: -P option found
174 */
175 static int
conf_scan(const char * fname,char * buf,int buflen,int timescan,struct opts * cliopts)176 conf_scan(const char *fname, char *buf, int buflen, int timescan,
177 struct opts *cliopts)
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 /* orphaned entry */
280 if (opts_count(cliopts, "v"))
281 err(EF_FILE, "stale timestamp "
282 "for %s", entry);
283 LOCAL_ERR_BREAK;
284 }
285 opts = cp->cf_opts;
286 }
287 opts = opts_parse(opts, Args, OPTF_CONF);
288 if (!timescan) {
289 fillconflist(lineno, entry, opts, comment, 0);
290 }
291 LOCAL_ERR_END }
292
293 if (ret == 1 && opts && opts_optarg(opts, "P") != NULL)
294 ret = 3;
295 }
296
297 err_fileline(NULL, 0);
298 return (ret);
299 }
300
301 /*
302 * conf_open -- open the configuration file, lock it if we have write perms
303 */
304 int
conf_open(const char * cfname,const char * tfname,struct opts * cliopts)305 conf_open(const char *cfname, const char *tfname, struct opts *cliopts)
306 {
307 struct stat stbuf1, stbuf2, stbuf3;
308 struct flock flock;
309 int ret;
310
311 Confname = cfname;
312 Timesname = tfname;
313 Confentries = fn_list_new(NULL);
314 Changed = CHG_NONE;
315
316 Changing = CHG_TIMES;
317 if (opts_count(cliopts, "Vn") != 0)
318 Changing = CHG_NONE;
319 else if (opts_count(cliopts, "rw") != 0)
320 Changing = CHG_BOTH;
321
322 Singlefile = strcmp(Confname, Timesname) == 0;
323 if (Singlefile && Changing == CHG_TIMES)
324 Changing = CHG_BOTH;
325
326 /* special case this so we don't even try locking the file */
327 if (strcmp(Confname, "/dev/null") == 0)
328 return (0);
329
330 while (Conffd == -1) {
331 Canchange = CHG_BOTH;
332 if ((Conffd = open(Confname, O_RDWR)) < 0) {
333 if (Changing == CHG_BOTH)
334 err(EF_SYS, "open %s", Confname);
335 Canchange = CHG_TIMES;
336 if ((Conffd = open(Confname, O_RDONLY)) < 0)
337 err(EF_SYS, "open %s", Confname);
338 }
339
340 flock.l_type = (Canchange == CHG_BOTH) ? F_WRLCK : F_RDLCK;
341 flock.l_whence = SEEK_SET;
342 flock.l_start = 0;
343 flock.l_len = 1;
344 if (fcntl(Conffd, F_SETLKW, &flock) < 0)
345 err(EF_SYS, "flock on %s", Confname);
346
347 /* wait until after file is locked to get filesize */
348 if (fstat(Conffd, &stbuf1) < 0)
349 err(EF_SYS, "fstat on %s", Confname);
350
351 /* verify that we've got a lock on the active file */
352 if (stat(Confname, &stbuf2) < 0 ||
353 !(stbuf2.st_dev == stbuf1.st_dev &&
354 stbuf2.st_ino == stbuf1.st_ino)) {
355 /* wrong config file, try again */
356 (void) close(Conffd);
357 Conffd = -1;
358 }
359 }
360
361 while (!Singlefile && Timesfd == -1) {
362 if ((Timesfd = open(Timesname, O_CREAT|O_RDWR, 0644)) < 0) {
363 if (Changing != CHG_NONE)
364 err(EF_SYS, "open %s", Timesname);
365 Canchange = CHG_NONE;
366 if ((Timesfd = open(Timesname, O_RDONLY)) < 0)
367 err(EF_SYS, "open %s", Timesname);
368 }
369
370 flock.l_type = (Canchange != CHG_NONE) ? F_WRLCK : F_RDLCK;
371 flock.l_whence = SEEK_SET;
372 flock.l_start = 0;
373 flock.l_len = 1;
374 if (fcntl(Timesfd, F_SETLKW, &flock) < 0)
375 err(EF_SYS, "flock on %s", Timesname);
376
377 /* wait until after file is locked to get filesize */
378 if (fstat(Timesfd, &stbuf2) < 0)
379 err(EF_SYS, "fstat on %s", Timesname);
380
381 /* verify that we've got a lock on the active file */
382 if (stat(Timesname, &stbuf3) < 0 ||
383 !(stbuf2.st_dev == stbuf3.st_dev &&
384 stbuf2.st_ino == stbuf3.st_ino)) {
385 /* wrong timestamp file, try again */
386 (void) close(Timesfd);
387 Timesfd = -1;
388 continue;
389 }
390
391 /* check that Timesname isn't an alias for Confname */
392 if (stbuf2.st_dev == stbuf1.st_dev &&
393 stbuf2.st_ino == stbuf1.st_ino)
394 err(0, "Timestamp file %s can't refer to "
395 "Configuration file %s", Timesname, Confname);
396 }
397
398 Conflen = stbuf1.st_size;
399 Timeslen = stbuf2.st_size;
400
401 if (Conflen == 0)
402 return (1); /* empty file, don't bother parsing it */
403
404 if ((Confbuf = (char *)mmap(0, Conflen,
405 PROT_READ | PROT_WRITE, MAP_PRIVATE, Conffd, 0)) == (char *)-1)
406 err(EF_SYS, "mmap on %s", Confname);
407
408 ret = conf_scan(Confname, Confbuf, Conflen, 0, cliopts);
409 if (ret == 3 && !Singlefile && Canchange == CHG_BOTH) {
410 /*
411 * arrange to transfer any timestamps
412 * from conf_file to timestamps_file
413 */
414 Changing = Changed = CHG_BOTH;
415 }
416
417 if (Timesfd != -1 && Timeslen != 0) {
418 if ((Timesbuf = (char *)mmap(0, Timeslen,
419 PROT_READ | PROT_WRITE, MAP_PRIVATE,
420 Timesfd, 0)) == (char *)-1)
421 err(EF_SYS, "mmap on %s", Timesname);
422 ret &= conf_scan(Timesname, Timesbuf, Timeslen, 1, cliopts);
423 }
424
425 /*
426 * possible future enhancement: go through and mark any entries:
427 * logfile -P <date>
428 * as DELETED if the logfile doesn't exist
429 */
430
431 return (ret);
432 }
433
434 /*
435 * conf_close -- close the configuration file
436 */
437 void
conf_close(struct opts * opts)438 conf_close(struct opts *opts)
439 {
440 char cuname[PATH_MAX], tuname[PATH_MAX];
441 int cfd, tfd;
442 FILE *cfp = NULL, *tfp = NULL;
443 boolean_t safe_update = B_TRUE;
444
445 if (Changed == CHG_NONE || opts_count(opts, "n") != 0) {
446 if (opts_count(opts, "v"))
447 (void) out("# %s and %s unchanged\n",
448 Confname, Timesname);
449 goto cleanup;
450 }
451
452 if (Debug > 1) {
453 (void) fprintf(stderr, "conf_close, saving logadm context:\n");
454 conf_print(stderr, NULL);
455 }
456
457 cuname[0] = tuname[0] = '\0';
458 LOCAL_ERR_BEGIN {
459 if (SETJMP) {
460 safe_update = B_FALSE;
461 LOCAL_ERR_BREAK;
462 }
463 if (Changed == CHG_BOTH) {
464 if (Canchange != CHG_BOTH)
465 err(EF_JMP, "internal error: attempting "
466 "to update %s without locking", Confname);
467 (void) snprintf(cuname, sizeof (cuname), "%sXXXXXX",
468 Confname);
469 if ((cfd = mkstemp(cuname)) == -1)
470 err(EF_SYS|EF_JMP, "open %s replacement",
471 Confname);
472 if (opts_count(opts, "v"))
473 (void) out("# writing changes to %s\n", cuname);
474 if (fchmod(cfd, 0644) == -1)
475 err(EF_SYS|EF_JMP, "chmod %s", cuname);
476 if ((cfp = fdopen(cfd, "w")) == NULL)
477 err(EF_SYS|EF_JMP, "fdopen on %s", cuname);
478 } else {
479 /* just toss away the configuration data */
480 cfp = fopen("/dev/null", "w");
481 }
482 if (!Singlefile) {
483 if (Canchange == CHG_NONE)
484 err(EF_JMP, "internal error: attempting "
485 "to update %s without locking", Timesname);
486 (void) snprintf(tuname, sizeof (tuname), "%sXXXXXX",
487 Timesname);
488 if ((tfd = mkstemp(tuname)) == -1)
489 err(EF_SYS|EF_JMP, "open %s replacement",
490 Timesname);
491 if (opts_count(opts, "v"))
492 (void) out("# writing changes to %s\n", tuname);
493 if (fchmod(tfd, 0644) == -1)
494 err(EF_SYS|EF_JMP, "chmod %s", tuname);
495 if ((tfp = fdopen(tfd, "w")) == NULL)
496 err(EF_SYS|EF_JMP, "fdopen on %s", tuname);
497 }
498
499 conf_print(cfp, tfp);
500 if (fclose(cfp) < 0)
501 err(EF_SYS|EF_JMP, "fclose on %s", Confname);
502 if (tfp != NULL && fclose(tfp) < 0)
503 err(EF_SYS|EF_JMP, "fclose on %s", Timesname);
504 LOCAL_ERR_END }
505
506 if (!safe_update) {
507 if (cuname[0] != 0)
508 (void) unlink(cuname);
509 if (tuname[0] != 0)
510 (void) unlink(tuname);
511 err(EF_JMP, "unsafe to update configuration file "
512 "or timestamps");
513 return;
514 }
515
516 /* rename updated files into place */
517 if (cuname[0] != '\0')
518 if (rename(cuname, Confname) < 0)
519 err(EF_SYS, "rename %s to %s", cuname, Confname);
520 if (tuname[0] != '\0')
521 if (rename(tuname, Timesname) < 0)
522 err(EF_SYS, "rename %s to %s", tuname, Timesname);
523 Changed = CHG_NONE;
524
525 cleanup:
526 if (Conffd != -1) {
527 (void) close(Conffd);
528 Conffd = -1;
529 }
530 if (Timesfd != -1) {
531 (void) close(Timesfd);
532 Timesfd = -1;
533 }
534 if (Conflut) {
535 lut_free(Conflut, free);
536 Conflut = NULL;
537 }
538 if (Confentries) {
539 fn_list_free(Confentries);
540 Confentries = NULL;
541 }
542 }
543
544 /*
545 * conf_lookup -- lookup an entry in the config file
546 */
547 void *
conf_lookup(const char * lhs)548 conf_lookup(const char *lhs)
549 {
550 struct confinfo *cp = lut_lookup(Conflut, lhs);
551
552 if (cp != NULL)
553 err_fileline(Confname, cp->cf_lineno);
554 return (cp);
555 }
556
557 /*
558 * conf_opts -- return the parsed opts for an entry
559 */
560 struct opts *
conf_opts(const char * lhs)561 conf_opts(const char *lhs)
562 {
563 struct confinfo *cp = lut_lookup(Conflut, lhs);
564
565 if (cp != NULL)
566 return (cp->cf_opts);
567 return (opts_parse(NULL, NULL, OPTF_CONF));
568 }
569
570 /*
571 * conf_replace -- replace an entry in the config file
572 */
573 void
conf_replace(const char * lhs,struct opts * newopts)574 conf_replace(const char *lhs, struct opts *newopts)
575 {
576 struct confinfo *cp = lut_lookup(Conflut, lhs);
577
578 if (Conffd == -1)
579 return;
580
581 if (cp != NULL) {
582 cp->cf_opts = newopts;
583 /* cp->cf_args = NULL; */
584 if (newopts == NULL)
585 cp->cf_flags |= CONFF_DELETED;
586 } else
587 fillconflist(0, lhs, newopts, NULL, 0);
588
589 Changed = CHG_BOTH;
590 }
591
592 /*
593 * conf_set -- set options for an entry in the config file
594 */
595 void
conf_set(const char * entry,char * o,const char * optarg)596 conf_set(const char *entry, char *o, const char *optarg)
597 {
598 struct confinfo *cp = lut_lookup(Conflut, entry);
599
600 if (Conffd == -1)
601 return;
602
603 if (cp != NULL) {
604 cp->cf_flags &= ~CONFF_DELETED;
605 } else {
606 fillconflist(0, STRDUP(entry),
607 opts_parse(NULL, NULL, OPTF_CONF), NULL, 0);
608 if ((cp = lut_lookup(Conflut, entry)) == NULL)
609 err(0, "conf_set internal error");
610 }
611 (void) opts_set(cp->cf_opts, o, optarg);
612 if (strcmp(o, "P") == 0)
613 Changed |= CHG_TIMES;
614 else
615 Changed = CHG_BOTH;
616 }
617
618 /*
619 * conf_entries -- list all the entry names
620 */
621 struct fn_list *
conf_entries(void)622 conf_entries(void)
623 {
624 return (Confentries);
625 }
626
627 /* print the config file */
628 static void
conf_print(FILE * cstream,FILE * tstream)629 conf_print(FILE *cstream, FILE *tstream)
630 {
631 struct confinfo *cp;
632 char *exclude_opts = "PFfhnrvVw";
633 const char *timestamp;
634
635 if (tstream == NULL) {
636 exclude_opts++; /* -P option goes to config file */
637 } else {
638 (void) fprintf(tstream, gettext(
639 "# This file holds internal data for logadm(1M).\n"
640 "# Do not edit.\n"));
641 }
642 for (cp = Confinfo; cp; cp = cp->cf_next) {
643 if (cp->cf_flags & CONFF_DELETED)
644 continue;
645 if (cp->cf_entry) {
646 opts_printword(cp->cf_entry, cstream);
647 if (cp->cf_opts)
648 opts_print(cp->cf_opts, cstream, exclude_opts);
649 /* output timestamps to tstream */
650 if (tstream != NULL && (timestamp =
651 opts_optarg(cp->cf_opts, "P")) != NULL) {
652 opts_printword(cp->cf_entry, tstream);
653 (void) fprintf(tstream, " -P ");
654 opts_printword(timestamp, tstream);
655 (void) fprintf(tstream, "\n");
656 }
657 }
658 if (cp->cf_com) {
659 if (cp->cf_entry)
660 (void) fprintf(cstream, " ");
661 (void) fprintf(cstream, "#%s", cp->cf_com);
662 }
663 (void) fprintf(cstream, "\n");
664 }
665 }
666
667 #ifdef TESTMODULE
668
669 /*
670 * test main for conf module, usage: a.out conffile
671 */
672 int
main(int argc,char * argv[])673 main(int argc, char *argv[])
674 {
675 struct opts *opts;
676
677 err_init(argv[0]);
678 setbuf(stdout, NULL);
679 opts_init(Opttable, Opttable_cnt);
680
681 opts = opts_parse(NULL, NULL, 0);
682
683 if (argc != 2)
684 err(EF_RAW, "usage: %s conffile\n", argv[0]);
685
686 conf_open(argv[1], argv[1], opts);
687
688 printf("conffile <%s>:\n", argv[1]);
689 conf_print(stdout, NULL);
690
691 conf_close(opts);
692
693 err_done(0);
694 /* NOTREACHED */
695 return (0);
696 }
697
698 #endif /* TESTMODULE */
699