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 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 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 * 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 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 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 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 * 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 * 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 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 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 * 622 conf_entries(void) 623 { 624 return (Confentries); 625 } 626 627 /* print the config file */ 628 static void 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 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