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