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 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 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 * 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 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 (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 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 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 * 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 * 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 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 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 * 641 conf_entries(void) 642 { 643 return (Confentries); 644 } 645 646 /* print the config file */ 647 static void 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(1M).\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 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