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 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 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 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 * 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 * 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 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 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 * 626 conf_entries(void) 627 { 628 return (Confentries); 629 } 630 631 /* print the config file */ 632 static void 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 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