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 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 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 * 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 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 266 /* 267 * If there is no next token on the line, make sure that 268 * we get a non-NULL Args array. 269 */ 270 if (Args == NULL) 271 fillargs(NULL); 272 273 Args[ArgsI] = NULL; 274 275 LOCAL_ERR_BEGIN { 276 if (SETJMP) { 277 err(EF_FILE, "cannot process invalid entry %s", 278 entry); 279 ret = 0; 280 LOCAL_ERR_BREAK; 281 } 282 283 if (timescan) { 284 /* append to config options */ 285 cp = lut_lookup(Conflut, entry); 286 if (cp != NULL) { 287 opts = cp->cf_opts; 288 } 289 } 290 opts = opts_parse(opts, Args, OPTF_CONF); 291 if (!timescan || cp == NULL) { 292 /* 293 * If we're not doing timescan, we track this 294 * entry. If we are doing timescan and have 295 * what looks like an orphaned entry (cp == 296 * NULL) then we also have to track. See the 297 * comment in rotatelog. We need to allow for 298 * the case where the logname is not the same as 299 * the log file name. 300 */ 301 fillconflist(lineno, entry, opts, comment, 0); 302 } 303 LOCAL_ERR_END } 304 305 if (ret == 1 && opts && opts_optarg(opts, "P") != NULL) 306 ret = 3; 307 } 308 309 err_fileline(NULL, 0); 310 return (ret); 311 } 312 313 /* 314 * conf_open -- open the configuration file, lock it if we have write perms 315 */ 316 int 317 conf_open(const char *cfname, const char *tfname, struct opts *cliopts) 318 { 319 struct stat stbuf1, stbuf2, stbuf3; 320 struct flock flock; 321 int ret; 322 323 Confname = cfname; 324 Timesname = tfname; 325 Confentries = fn_list_new(NULL); 326 Changed = CHG_NONE; 327 328 Changing = CHG_TIMES; 329 if (opts_count(cliopts, "Vn") != 0) 330 Changing = CHG_NONE; 331 else if (opts_count(cliopts, "rw") != 0) 332 Changing = CHG_BOTH; 333 334 Singlefile = strcmp(Confname, Timesname) == 0; 335 if (Singlefile && Changing == CHG_TIMES) 336 Changing = CHG_BOTH; 337 338 /* special case this so we don't even try locking the file */ 339 if (strcmp(Confname, "/dev/null") == 0) 340 return (0); 341 342 while (Conffd == -1) { 343 Canchange = CHG_BOTH; 344 if ((Conffd = open(Confname, O_RDWR)) < 0) { 345 if (Changing == CHG_BOTH) 346 err(EF_SYS, "open %s", Confname); 347 Canchange = CHG_TIMES; 348 if ((Conffd = open(Confname, O_RDONLY)) < 0) 349 err(EF_SYS, "open %s", Confname); 350 } 351 352 flock.l_type = (Canchange == CHG_BOTH) ? F_WRLCK : F_RDLCK; 353 flock.l_whence = SEEK_SET; 354 flock.l_start = 0; 355 flock.l_len = 1; 356 if (fcntl(Conffd, F_SETLKW, &flock) < 0) 357 err(EF_SYS, "flock on %s", Confname); 358 359 /* wait until after file is locked to get filesize */ 360 if (fstat(Conffd, &stbuf1) < 0) 361 err(EF_SYS, "fstat on %s", Confname); 362 363 /* verify that we've got a lock on the active file */ 364 if (stat(Confname, &stbuf2) < 0 || 365 !(stbuf2.st_dev == stbuf1.st_dev && 366 stbuf2.st_ino == stbuf1.st_ino)) { 367 /* wrong config file, try again */ 368 (void) close(Conffd); 369 Conffd = -1; 370 } 371 } 372 373 while (!Singlefile && Timesfd == -1) { 374 if ((Timesfd = open(Timesname, O_CREAT|O_RDWR, 0644)) < 0) { 375 if (Changing != CHG_NONE) 376 err(EF_SYS, "open %s", Timesname); 377 Canchange = CHG_NONE; 378 if ((Timesfd = open(Timesname, O_RDONLY)) < 0) 379 err(EF_SYS, "open %s", Timesname); 380 } 381 382 flock.l_type = (Canchange != CHG_NONE) ? F_WRLCK : F_RDLCK; 383 flock.l_whence = SEEK_SET; 384 flock.l_start = 0; 385 flock.l_len = 1; 386 if (fcntl(Timesfd, F_SETLKW, &flock) < 0) 387 err(EF_SYS, "flock on %s", Timesname); 388 389 /* wait until after file is locked to get filesize */ 390 if (fstat(Timesfd, &stbuf2) < 0) 391 err(EF_SYS, "fstat on %s", Timesname); 392 393 /* verify that we've got a lock on the active file */ 394 if (stat(Timesname, &stbuf3) < 0 || 395 !(stbuf2.st_dev == stbuf3.st_dev && 396 stbuf2.st_ino == stbuf3.st_ino)) { 397 /* wrong timestamp file, try again */ 398 (void) close(Timesfd); 399 Timesfd = -1; 400 continue; 401 } 402 403 /* check that Timesname isn't an alias for Confname */ 404 if (stbuf2.st_dev == stbuf1.st_dev && 405 stbuf2.st_ino == stbuf1.st_ino) 406 err(0, "Timestamp file %s can't refer to " 407 "Configuration file %s", Timesname, Confname); 408 } 409 410 Conflen = stbuf1.st_size; 411 Timeslen = stbuf2.st_size; 412 413 if (Conflen == 0) 414 return (1); /* empty file, don't bother parsing it */ 415 416 if ((Confbuf = (char *)mmap(0, Conflen, 417 PROT_READ | PROT_WRITE, MAP_PRIVATE, Conffd, 0)) == (char *)-1) 418 err(EF_SYS, "mmap on %s", Confname); 419 420 ret = conf_scan(Confname, Confbuf, Conflen, 0); 421 if (ret == 3 && !Singlefile && Canchange == CHG_BOTH) { 422 /* 423 * arrange to transfer any timestamps 424 * from conf_file to timestamps_file 425 */ 426 Changing = Changed = CHG_BOTH; 427 } 428 429 if (Timesfd != -1 && Timeslen != 0) { 430 if ((Timesbuf = (char *)mmap(0, Timeslen, 431 PROT_READ | PROT_WRITE, MAP_PRIVATE, 432 Timesfd, 0)) == (char *)-1) 433 err(EF_SYS, "mmap on %s", Timesname); 434 ret &= conf_scan(Timesname, Timesbuf, Timeslen, 1); 435 } 436 437 /* 438 * possible future enhancement: go through and mark any entries: 439 * logfile -P <date> 440 * as DELETED if the logfile doesn't exist 441 */ 442 443 return (ret); 444 } 445 446 /* 447 * conf_close -- close the configuration file 448 */ 449 void 450 conf_close(struct opts *opts) 451 { 452 char cuname[PATH_MAX], tuname[PATH_MAX]; 453 int cfd, tfd; 454 FILE *cfp = NULL, *tfp = NULL; 455 boolean_t safe_update = B_TRUE; 456 457 if (Changed == CHG_NONE || opts_count(opts, "n") != 0) { 458 if (opts_count(opts, "v")) 459 (void) out("# %s and %s unchanged\n", 460 Confname, Timesname); 461 goto cleanup; 462 } 463 464 if (Debug > 1) { 465 (void) fprintf(stderr, "conf_close, saving logadm context:\n"); 466 conf_print(stderr, NULL); 467 } 468 469 cuname[0] = tuname[0] = '\0'; 470 LOCAL_ERR_BEGIN { 471 if (SETJMP) { 472 safe_update = B_FALSE; 473 LOCAL_ERR_BREAK; 474 } 475 if (Changed == CHG_BOTH) { 476 if (Canchange != CHG_BOTH) 477 err(EF_JMP, "internal error: attempting " 478 "to update %s without locking", Confname); 479 (void) snprintf(cuname, sizeof (cuname), "%sXXXXXX", 480 Confname); 481 if ((cfd = mkstemp(cuname)) == -1) 482 err(EF_SYS|EF_JMP, "open %s replacement", 483 Confname); 484 if (opts_count(opts, "v")) 485 (void) out("# writing changes to %s\n", cuname); 486 if (fchmod(cfd, 0644) == -1) 487 err(EF_SYS|EF_JMP, "chmod %s", cuname); 488 if ((cfp = fdopen(cfd, "w")) == NULL) 489 err(EF_SYS|EF_JMP, "fdopen on %s", cuname); 490 } else { 491 /* just toss away the configuration data */ 492 cfp = fopen("/dev/null", "w"); 493 } 494 if (!Singlefile) { 495 if (Canchange == CHG_NONE) 496 err(EF_JMP, "internal error: attempting " 497 "to update %s without locking", Timesname); 498 (void) snprintf(tuname, sizeof (tuname), "%sXXXXXX", 499 Timesname); 500 if ((tfd = mkstemp(tuname)) == -1) 501 err(EF_SYS|EF_JMP, "open %s replacement", 502 Timesname); 503 if (opts_count(opts, "v")) 504 (void) out("# writing changes to %s\n", tuname); 505 if (fchmod(tfd, 0644) == -1) 506 err(EF_SYS|EF_JMP, "chmod %s", tuname); 507 if ((tfp = fdopen(tfd, "w")) == NULL) 508 err(EF_SYS|EF_JMP, "fdopen on %s", tuname); 509 } 510 511 conf_print(cfp, tfp); 512 if (fclose(cfp) < 0) 513 err(EF_SYS|EF_JMP, "fclose on %s", Confname); 514 if (tfp != NULL && fclose(tfp) < 0) 515 err(EF_SYS|EF_JMP, "fclose on %s", Timesname); 516 LOCAL_ERR_END } 517 518 if (!safe_update) { 519 if (cuname[0] != 0) 520 (void) unlink(cuname); 521 if (tuname[0] != 0) 522 (void) unlink(tuname); 523 err(EF_JMP, "unsafe to update configuration file " 524 "or timestamps"); 525 return; 526 } 527 528 /* rename updated files into place */ 529 if (cuname[0] != '\0') 530 if (rename(cuname, Confname) < 0) 531 err(EF_SYS, "rename %s to %s", cuname, Confname); 532 if (tuname[0] != '\0') 533 if (rename(tuname, Timesname) < 0) 534 err(EF_SYS, "rename %s to %s", tuname, Timesname); 535 Changed = CHG_NONE; 536 537 cleanup: 538 if (Conffd != -1) { 539 (void) close(Conffd); 540 Conffd = -1; 541 } 542 if (Timesfd != -1) { 543 (void) close(Timesfd); 544 Timesfd = -1; 545 } 546 if (Conflut) { 547 lut_free(Conflut, free); 548 Conflut = NULL; 549 } 550 if (Confentries) { 551 fn_list_free(Confentries); 552 Confentries = NULL; 553 } 554 } 555 556 /* 557 * conf_lookup -- lookup an entry in the config file 558 */ 559 void * 560 conf_lookup(const char *lhs) 561 { 562 struct confinfo *cp = lut_lookup(Conflut, lhs); 563 564 if (cp != NULL) 565 err_fileline(Confname, cp->cf_lineno); 566 return (cp); 567 } 568 569 /* 570 * conf_opts -- return the parsed opts for an entry 571 */ 572 struct opts * 573 conf_opts(const char *lhs) 574 { 575 struct confinfo *cp = lut_lookup(Conflut, lhs); 576 577 if (cp != NULL) 578 return (cp->cf_opts); 579 return (opts_parse(NULL, NULL, OPTF_CONF)); 580 } 581 582 /* 583 * conf_replace -- replace an entry in the config file 584 */ 585 void 586 conf_replace(const char *lhs, struct opts *newopts) 587 { 588 struct confinfo *cp = lut_lookup(Conflut, lhs); 589 590 if (Conffd == -1) 591 return; 592 593 if (cp != NULL) { 594 cp->cf_opts = newopts; 595 /* cp->cf_args = NULL; */ 596 if (newopts == NULL) 597 cp->cf_flags |= CONFF_DELETED; 598 } else 599 fillconflist(0, lhs, newopts, NULL, 0); 600 601 Changed = CHG_BOTH; 602 } 603 604 /* 605 * conf_set -- set options for an entry in the config file 606 */ 607 void 608 conf_set(const char *entry, char *o, const char *optarg) 609 { 610 struct confinfo *cp = lut_lookup(Conflut, entry); 611 612 if (Conffd == -1) 613 return; 614 615 if (cp != NULL) { 616 cp->cf_flags &= ~CONFF_DELETED; 617 } else { 618 fillconflist(0, STRDUP(entry), 619 opts_parse(NULL, NULL, OPTF_CONF), NULL, 0); 620 if ((cp = lut_lookup(Conflut, entry)) == NULL) 621 err(0, "conf_set internal error"); 622 } 623 (void) opts_set(cp->cf_opts, o, optarg); 624 if (strcmp(o, "P") == 0) 625 Changed |= CHG_TIMES; 626 else 627 Changed = CHG_BOTH; 628 } 629 630 /* 631 * conf_entries -- list all the entry names 632 */ 633 struct fn_list * 634 conf_entries(void) 635 { 636 return (Confentries); 637 } 638 639 /* print the config file */ 640 static void 641 conf_print(FILE *cstream, FILE *tstream) 642 { 643 struct confinfo *cp; 644 char *exclude_opts = "PFfhnrvVw"; 645 const char *timestamp; 646 647 if (tstream == NULL) { 648 exclude_opts++; /* -P option goes to config file */ 649 } else { 650 (void) fprintf(tstream, gettext( 651 "# This file holds internal data for logadm(1M).\n" 652 "# Do not edit.\n")); 653 } 654 for (cp = Confinfo; cp; cp = cp->cf_next) { 655 if (cp->cf_flags & CONFF_DELETED) 656 continue; 657 if (cp->cf_entry) { 658 opts_printword(cp->cf_entry, cstream); 659 if (cp->cf_opts) 660 opts_print(cp->cf_opts, cstream, exclude_opts); 661 /* output timestamps to tstream */ 662 if (tstream != NULL && (timestamp = 663 opts_optarg(cp->cf_opts, "P")) != NULL) { 664 opts_printword(cp->cf_entry, tstream); 665 (void) fprintf(tstream, " -P "); 666 opts_printword(timestamp, tstream); 667 (void) fprintf(tstream, "\n"); 668 } 669 } 670 if (cp->cf_com) { 671 if (cp->cf_entry) 672 (void) fprintf(cstream, " "); 673 (void) fprintf(cstream, "#%s", cp->cf_com); 674 } 675 (void) fprintf(cstream, "\n"); 676 } 677 } 678 679 #ifdef TESTMODULE 680 681 /* 682 * test main for conf module, usage: a.out conffile 683 */ 684 int 685 main(int argc, char *argv[]) 686 { 687 struct opts *opts; 688 689 err_init(argv[0]); 690 setbuf(stdout, NULL); 691 opts_init(Opttable, Opttable_cnt); 692 693 opts = opts_parse(NULL, NULL, 0); 694 695 if (argc != 2) 696 err(EF_RAW, "usage: %s conffile\n", argv[0]); 697 698 conf_open(argv[1], argv[1], opts); 699 700 printf("conffile <%s>:\n", argv[1]); 701 conf_print(stdout, NULL); 702 703 conf_close(opts); 704 705 err_done(0); 706 /* NOTREACHED */ 707 return (0); 708 } 709 710 #endif /* TESTMODULE */ 711