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