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 2006 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 #pragma ident "%Z%%M% %I% %E% SMI" 28 29 /* 30 * logadm/conf.c -- configuration file module 31 */ 32 33 #include <stdio.h> 34 #include <libintl.h> 35 #include <fcntl.h> 36 #include <sys/types.h> 37 #include <sys/stat.h> 38 #include <sys/mman.h> 39 #include <ctype.h> 40 #include <strings.h> 41 #include <unistd.h> 42 #include <stdlib.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, char **args, 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 *stream); 55 56 static const char *Confname; /* name of the confile file */ 57 static char *Confbuf; /* copy of the config file (a la mmap()) */ 58 static int Conflen; /* length of mmap'd area */ 59 static int Conffd = -1; /* file descriptor for config file */ 60 static boolean_t Confchanged; /* true if we need to write changes back */ 61 62 /* 63 * our structured representation of the configuration file 64 * is made up of a list of these 65 */ 66 struct confinfo { 67 struct confinfo *cf_next; 68 int cf_lineno; /* line number in file */ 69 const char *cf_entry; /* name of entry, if line has an entry */ 70 char **cf_args; /* raw rhs of entry */ 71 struct opts *cf_opts; /* parsed rhs of entry */ 72 const char *cf_com; /* any comment text found */ 73 int cf_flags; 74 }; 75 76 #define CONFF_DELETED 1 /* entry should be deleted on write back */ 77 78 static struct confinfo *Confinfo; /* the entries in the config file */ 79 static struct confinfo *Confinfolast; /* end of list */ 80 static struct lut *Conflut; /* lookup table keyed by entry name */ 81 static struct fn_list *Confentries; /* list of valid entry names */ 82 83 /* allocate & fill in another entry in our list */ 84 static void 85 fillconflist(int lineno, const char *entry, char **args, 86 struct opts *opts, const char *com, int flags) 87 { 88 struct confinfo *cp = MALLOC(sizeof (*cp)); 89 90 cp->cf_next = NULL; 91 cp->cf_lineno = lineno; 92 cp->cf_entry = entry; 93 cp->cf_args = args; 94 cp->cf_opts = opts; 95 cp->cf_com = com; 96 cp->cf_flags = flags; 97 if (entry != NULL) { 98 Conflut = lut_add(Conflut, entry, cp); 99 fn_list_adds(Confentries, entry); 100 } 101 if (Confinfo == NULL) 102 Confinfo = Confinfolast = cp; 103 else { 104 Confinfolast->cf_next = cp; 105 Confinfolast = cp; 106 } 107 } 108 109 static char **Args; /* static buffer for args */ 110 static int ArgsN; /* size of our static buffer */ 111 static int ArgsI; /* index into Cmdargs as we walk table */ 112 #define CONF_ARGS_INC 1024 113 114 /* callback for lut_walk to build a cmdargs vector */ 115 static void 116 fillargs(char *arg) 117 { 118 if (ArgsI >= ArgsN) { 119 /* need bigger table */ 120 Args = REALLOC(Args, sizeof (char *) * (ArgsN + CONF_ARGS_INC)); 121 ArgsN += CONF_ARGS_INC; 122 } 123 Args[ArgsI++] = arg; 124 } 125 126 /* isolate and return the next token */ 127 static char * 128 nexttok(char **ptrptr) 129 { 130 char *ptr = *ptrptr; 131 char *eptr; 132 char *quote = NULL; 133 134 while (*ptr && isspace(*ptr)) 135 ptr++; 136 137 if (*ptr == '"' || *ptr == '\'') 138 quote = ptr++; 139 140 for (eptr = ptr; *eptr; eptr++) 141 if (quote && *eptr == *quote) { 142 /* found end quote */ 143 *eptr++ = '\0'; 144 *ptrptr = eptr; 145 return (ptr); 146 } else if (!quote && isspace(*eptr)) { 147 /* found end of unquoted area */ 148 *eptr++ = '\0'; 149 *ptrptr = eptr; 150 return (ptr); 151 } 152 153 if (quote != NULL) 154 err(EF_FILE|EF_JMP, "Unbalanced %c quote", *quote); 155 /*NOTREACHED*/ 156 157 *ptrptr = eptr; 158 159 if (ptr == eptr) 160 return (NULL); 161 else 162 return (ptr); 163 } 164 165 /* 166 * conf_open -- open the configuration file, lock it if we have write perms 167 */ 168 void 169 conf_open(const char *fname, int needwrite) 170 { 171 struct stat stbuf; 172 int lineno = 0; 173 char *line; 174 char *eline; 175 char *ebuf; 176 char *comment; 177 178 Confname = fname; 179 Confentries = fn_list_new(NULL); 180 181 /* special case this so we don't even try locking the file */ 182 if (strcmp(Confname, "/dev/null") == 0) 183 return; 184 185 if ((Conffd = open(Confname, (needwrite) ? O_RDWR : O_RDONLY)) < 0) 186 err(EF_SYS, "%s", Confname); 187 188 if (fstat(Conffd, &stbuf) < 0) 189 err(EF_SYS, "fstat on %s", Confname); 190 191 if (needwrite && lockf(Conffd, F_LOCK, 0) < 0) 192 err(EF_SYS, "lockf on %s", Confname); 193 194 if (stbuf.st_size == 0) 195 return; /* empty file, don't bother parsing it */ 196 197 if ((Confbuf = (char *)mmap(0, stbuf.st_size, 198 PROT_READ | PROT_WRITE, MAP_PRIVATE, Conffd, 0)) == (char *)-1) 199 err(EF_SYS, "mmap on %s", Confname); 200 201 Conflen = stbuf.st_size; 202 Confchanged = B_FALSE; 203 204 ebuf = &Confbuf[Conflen]; 205 206 if (Confbuf[Conflen - 1] != '\n') 207 err(EF_WARN|EF_FILE, "config file doesn't end with " 208 "newline, last line ignored."); 209 210 line = Confbuf; 211 while (line < ebuf) { 212 lineno++; 213 err_fileline(Confname, lineno); 214 eline = line; 215 comment = NULL; 216 for (; eline < ebuf; eline++) { 217 /* check for continued lines */ 218 if (comment == NULL && *eline == '\\' && 219 eline + 1 < ebuf && *(eline + 1) == '\n') { 220 *eline = ' '; 221 *(eline + 1) = ' '; 222 lineno++; 223 err_fileline(Confname, lineno); 224 continue; 225 } 226 227 /* check for comments */ 228 if (comment == NULL && *eline == '#') { 229 *eline = '\0'; 230 comment = (eline + 1); 231 continue; 232 } 233 234 /* check for end of line */ 235 if (*eline == '\n') 236 break; 237 } 238 if (comment >= ebuf) 239 comment = NULL; 240 if (eline < ebuf) { 241 char *entry; 242 243 *eline++ = '\0'; 244 245 /* 246 * now we have the entry, if any, at "line" 247 * and the comment, if any, at "comment" 248 */ 249 250 /* entry is first token */ 251 if ((entry = nexttok(&line)) != NULL && 252 strcmp(entry, "logadm-version") == 0) { 253 /* 254 * we somehow opened some future format 255 * conffile that we likely don't understand. 256 * if the given version is "1" then go on, 257 * otherwise someone is mixing versions 258 * and we can't help them other than to 259 * print an error and exit. 260 */ 261 if ((entry = nexttok(&line)) != NULL && 262 strcmp(entry, "1") != 0) 263 err(0, "%s version not " 264 "supported by " 265 "this version of logadm.", 266 Confname); 267 } else if (entry) { 268 char *ap; 269 char **args; 270 int i; 271 272 ArgsI = 0; 273 while (ap = nexttok(&line)) 274 fillargs(ap); 275 if (ArgsI == 0) { 276 /* short entry allowed */ 277 fillconflist(lineno, entry, 278 NULL, NULL, comment, 0); 279 } else { 280 Args[ArgsI++] = NULL; 281 args = MALLOC(sizeof (char *) * ArgsI); 282 for (i = 0; i < ArgsI; i++) 283 args[i] = Args[i]; 284 fillconflist(lineno, entry, 285 args, NULL, comment, 0); 286 } 287 } else 288 fillconflist(lineno, entry, NULL, NULL, 289 comment, 0); 290 } 291 line = eline; 292 } 293 /* 294 * possible future enhancement: go through and mark any entries: 295 * logfile -P <date> 296 * as DELETED if the logfile doesn't exist 297 */ 298 } 299 300 /* 301 * conf_close -- close the configuration file 302 */ 303 void 304 conf_close(struct opts *opts) 305 { 306 FILE *fp; 307 308 if (Confchanged && opts_count(opts, "n") == 0 && Conffd != -1) { 309 if (opts_count(opts, "v")) 310 (void) out("# writing changes to %s\n", Confname); 311 if (Debug > 1) { 312 (void) fprintf(stderr, "conf_close, %s changed to:\n", 313 Confname); 314 conf_print(stderr); 315 } 316 if (lseek(Conffd, (off_t)0, SEEK_SET) < 0) 317 err(EF_SYS, "lseek on %s", Confname); 318 if (ftruncate(Conffd, (off_t)0) < 0) 319 err(EF_SYS, "ftruncate on %s", Confname); 320 if ((fp = fdopen(Conffd, "w")) == NULL) 321 err(EF_SYS, "fdopen on %s", Confname); 322 conf_print(fp); 323 if (fclose(fp) < 0) 324 err(EF_SYS, "fclose on %s", Confname); 325 Conffd = -1; 326 Confchanged = B_FALSE; 327 } else if (opts_count(opts, "v")) { 328 (void) out("# %s unchanged\n", Confname); 329 } 330 331 if (Conffd != -1) { 332 (void) close(Conffd); 333 Conffd = -1; 334 } 335 if (Conflut) { 336 lut_free(Conflut, free); 337 Conflut = NULL; 338 } 339 if (Confentries) { 340 fn_list_free(Confentries); 341 Confentries = NULL; 342 } 343 } 344 345 /* 346 * conf_lookup -- lookup an entry in the config file 347 */ 348 char ** 349 conf_lookup(const char *lhs) 350 { 351 struct confinfo *cp = lut_lookup(Conflut, lhs); 352 353 if (cp != NULL) { 354 err_fileline(Confname, cp->cf_lineno); 355 return (cp->cf_args); 356 } else 357 return (NULL); 358 } 359 360 /* 361 * conf_opts -- return the parsed opts for an entry 362 */ 363 struct opts * 364 conf_opts(const char *lhs) 365 { 366 struct confinfo *cp = lut_lookup(Conflut, lhs); 367 368 if (cp != NULL) { 369 if (cp->cf_opts) 370 return (cp->cf_opts); /* already parsed */ 371 err_fileline(Confname, cp->cf_lineno); 372 cp->cf_opts = opts_parse(cp->cf_args, OPTF_CONF); 373 return (cp->cf_opts); 374 } 375 return (opts_parse(NULL, OPTF_CONF)); 376 } 377 378 /* 379 * conf_replace -- replace an entry in the config file 380 */ 381 void 382 conf_replace(const char *lhs, struct opts *newopts) 383 { 384 struct confinfo *cp = lut_lookup(Conflut, lhs); 385 386 if (Conffd == -1) 387 return; 388 389 if (cp != NULL) { 390 cp->cf_opts = newopts; 391 cp->cf_args = NULL; 392 if (newopts == NULL) 393 cp->cf_flags |= CONFF_DELETED; 394 } else 395 fillconflist(0, lhs, NULL, newopts, NULL, 0); 396 Confchanged = B_TRUE; 397 } 398 399 /* 400 * conf_set -- set options for an entry in the config file 401 */ 402 void 403 conf_set(const char *entry, char *o, const char *optarg) 404 { 405 struct confinfo *cp = lut_lookup(Conflut, entry); 406 407 if (Conffd == -1) 408 return; 409 410 if (cp != NULL) { 411 if (cp->cf_opts == NULL) 412 cp->cf_opts = opts_parse(cp->cf_args, OPTF_CONF); 413 cp->cf_flags &= ~CONFF_DELETED; 414 } else { 415 fillconflist(0, STRDUP(entry), NULL, 416 opts_parse(NULL, OPTF_CONF), NULL, 0); 417 if ((cp = lut_lookup(Conflut, entry)) == NULL) 418 err(0, "conf_set internal error"); 419 } 420 (void) opts_set(cp->cf_opts, o, optarg); 421 Confchanged = B_TRUE; 422 } 423 424 /* 425 * conf_entries -- list all the entry names 426 */ 427 struct fn_list * 428 conf_entries(void) 429 { 430 return (Confentries); 431 } 432 433 /* print the config file */ 434 static void 435 conf_print(FILE *stream) 436 { 437 struct confinfo *cp; 438 439 for (cp = Confinfo; cp; cp = cp->cf_next) { 440 if (cp->cf_flags & CONFF_DELETED) 441 continue; 442 if (cp->cf_entry) { 443 char **p; 444 445 opts_printword(cp->cf_entry, stream); 446 if (cp->cf_opts) { 447 /* existence of opts overrides args */ 448 opts_print(cp->cf_opts, stream, "fhnrvVw"); 449 } else if (cp->cf_args) { 450 for (p = cp->cf_args; *p; p++) { 451 (void) fprintf(stream, " "); 452 opts_printword(*p, stream); 453 } 454 } 455 } 456 if (cp->cf_com) { 457 if (cp->cf_entry) 458 (void) fprintf(stream, " "); 459 (void) fprintf(stream, "#%s", cp->cf_com); 460 } 461 (void) fprintf(stream, "\n"); 462 } 463 } 464 465 #ifdef TESTMODULE 466 467 /* 468 * test main for conf module, usage: a.out conffile 469 */ 470 int 471 main(int argc, char *argv[]) 472 { 473 err_init(argv[0]); 474 setbuf(stdout, NULL); 475 476 if (argc != 2) 477 err(EF_RAW, "usage: %s conffile\n", argv[0]); 478 479 conf_open(argv[1], 1); 480 481 printf("conffile <%s>:\n", argv[1]); 482 conf_print(stdout); 483 484 conf_close(opts_parse(NULL, 0)); 485 486 err_done(0); 487 /* NOTREACHED */ 488 return (0); 489 } 490 491 #endif /* TESTMODULE */ 492