1 /* 2 * Copyright (c) 2000, Boris Popov 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 3. All advertising materials mentioning features or use of this software 14 * must display the following acknowledgement: 15 * This product includes software developed by Boris Popov. 16 * 4. Neither the name of the author nor the names of any co-contributors 17 * may be used to endorse or promote products derived from this software 18 * without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 23 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 * SUCH DAMAGE. 31 * 32 * $Id: rcfile.c,v 1.5 2001/04/16 12:46:46 bp Exp $ 33 */ 34 35 #include <sys/cdefs.h> 36 __FBSDID("$FreeBSD$"); 37 38 #include <sys/types.h> 39 #include <sys/queue.h> 40 #include <ctype.h> 41 #include <errno.h> 42 #include <stdio.h> 43 #include <string.h> 44 #include <stdlib.h> 45 #include <pwd.h> 46 #include <unistd.h> 47 #include <err.h> 48 49 #include <cflib.h> 50 #include "rcfile_priv.h" 51 52 SLIST_HEAD(rcfile_head, rcfile); 53 static struct rcfile_head pf_head = {NULL}; 54 55 static struct rcfile* rc_cachelookup(const char *filename); 56 static struct rcsection *rc_findsect(struct rcfile *rcp, const char *sectname); 57 static struct rcsection *rc_addsect(struct rcfile *rcp, const char *sectname); 58 static int rc_freesect(struct rcfile *rcp, struct rcsection *rsp); 59 static struct rckey *rc_sect_findkey(struct rcsection *rsp, const char *keyname); 60 static struct rckey *rc_sect_addkey(struct rcsection *rsp, const char *name, const char *value); 61 static void rc_key_free(struct rckey *p); 62 static void rc_parse(struct rcfile *rcp); 63 64 65 /* 66 * open rcfile and load its content, if already open - return previous handle 67 */ 68 int 69 rc_open(const char *filename, const char *mode, struct rcfile **rcfile) 70 { 71 struct rcfile *rcp; 72 FILE *f; 73 74 rcp = rc_cachelookup(filename); 75 if (rcp) { 76 *rcfile = rcp; 77 return 0; 78 } 79 f = fopen(filename, mode); 80 if (f == NULL) 81 return errno; 82 rcp = malloc(sizeof(struct rcfile)); 83 if (rcp == NULL) { 84 fclose(f); 85 return ENOMEM; 86 } 87 bzero(rcp, sizeof(struct rcfile)); 88 rcp->rf_name = strdup(filename); 89 rcp->rf_f = f; 90 SLIST_INSERT_HEAD(&pf_head, rcp, rf_next); 91 rc_parse(rcp); 92 *rcfile = rcp; 93 return 0; 94 } 95 96 int 97 rc_merge(const char *filename, struct rcfile **rcfile) 98 { 99 struct rcfile *rcp = *rcfile; 100 FILE *f, *t; 101 102 if (rcp == NULL) { 103 return rc_open(filename, "r", rcfile); 104 } 105 f = fopen (filename, "r"); 106 if (f == NULL) 107 return errno; 108 t = rcp->rf_f; 109 rcp->rf_f = f; 110 rc_parse(rcp); 111 rcp->rf_f = t; 112 fclose(f); 113 return 0; 114 } 115 116 int 117 rc_close(struct rcfile *rcp) 118 { 119 struct rcsection *p, *n; 120 121 fclose(rcp->rf_f); 122 for(p = SLIST_FIRST(&rcp->rf_sect); p;) { 123 n = p; 124 p = SLIST_NEXT(p,rs_next); 125 rc_freesect(rcp, n); 126 } 127 free(rcp->rf_name); 128 SLIST_REMOVE(&pf_head, rcp, rcfile, rf_next); 129 free(rcp); 130 return 0; 131 } 132 133 static struct rcfile* 134 rc_cachelookup(const char *filename) 135 { 136 struct rcfile *p; 137 138 SLIST_FOREACH(p, &pf_head, rf_next) 139 if (strcmp (filename, p->rf_name) == 0) 140 return p; 141 return 0; 142 } 143 144 static struct rcsection * 145 rc_findsect(struct rcfile *rcp, const char *sectname) 146 { 147 struct rcsection *p; 148 149 SLIST_FOREACH(p, &rcp->rf_sect, rs_next) 150 if (strcmp(p->rs_name, sectname)==0) 151 return p; 152 return NULL; 153 } 154 155 static struct rcsection * 156 rc_addsect(struct rcfile *rcp, const char *sectname) 157 { 158 struct rcsection *p; 159 160 p = rc_findsect(rcp, sectname); 161 if (p) return p; 162 p = malloc(sizeof(*p)); 163 if (!p) return NULL; 164 p->rs_name = strdup(sectname); 165 SLIST_INIT(&p->rs_keys); 166 SLIST_INSERT_HEAD(&rcp->rf_sect, p, rs_next); 167 return p; 168 } 169 170 static int 171 rc_freesect(struct rcfile *rcp, struct rcsection *rsp) 172 { 173 struct rckey *p,*n; 174 175 SLIST_REMOVE(&rcp->rf_sect, rsp, rcsection, rs_next); 176 for(p = SLIST_FIRST(&rsp->rs_keys);p;) { 177 n = p; 178 p = SLIST_NEXT(p,rk_next); 179 rc_key_free(n); 180 } 181 free(rsp->rs_name); 182 free(rsp); 183 return 0; 184 } 185 186 static struct rckey * 187 rc_sect_findkey(struct rcsection *rsp, const char *keyname) 188 { 189 struct rckey *p; 190 191 SLIST_FOREACH(p, &rsp->rs_keys, rk_next) 192 if (strcmp(p->rk_name, keyname)==0) 193 return p; 194 return NULL; 195 } 196 197 static struct rckey * 198 rc_sect_addkey(struct rcsection *rsp, const char *name, const char *value) 199 { 200 struct rckey *p; 201 202 p = rc_sect_findkey(rsp, name); 203 if (p) { 204 free(p->rk_value); 205 } else { 206 p = malloc(sizeof(*p)); 207 if (!p) return NULL; 208 SLIST_INSERT_HEAD(&rsp->rs_keys, p, rk_next); 209 p->rk_name = strdup(name); 210 } 211 p->rk_value = value ? strdup(value) : strdup(""); 212 return p; 213 } 214 215 #if 0 216 void 217 rc_sect_delkey(struct rcsection *rsp, struct rckey *p) 218 { 219 220 SLIST_REMOVE(&rsp->rs_keys, p, rckey, rk_next); 221 rc_key_free(p); 222 return; 223 } 224 #endif 225 226 static void 227 rc_key_free(struct rckey *p) 228 { 229 free(p->rk_value); 230 free(p->rk_name); 231 free(p); 232 } 233 234 enum { stNewLine, stHeader, stSkipToEOL, stGetKey, stGetValue}; 235 236 static void 237 rc_parse(struct rcfile *rcp) 238 { 239 FILE *f = rcp->rf_f; 240 int state = stNewLine, c; 241 struct rcsection *rsp = NULL; 242 struct rckey *rkp = NULL; 243 char buf[2048]; 244 char *next = buf, *last = &buf[sizeof(buf)-1]; 245 246 while ((c = getc (f)) != EOF) { 247 if (c == '\r') 248 continue; 249 if (state == stNewLine) { 250 next = buf; 251 if (isspace(c)) 252 continue; /* skip leading junk */ 253 if (c == '[') { 254 state = stHeader; 255 rsp = NULL; 256 continue; 257 } 258 if (c == '#' || c == ';') { 259 state = stSkipToEOL; 260 } else { /* something meaningfull */ 261 state = stGetKey; 262 } 263 } 264 if (state == stSkipToEOL || next == last) {/* ignore long lines */ 265 if (c == '\n'){ 266 state = stNewLine; 267 next = buf; 268 } 269 continue; 270 } 271 if (state == stHeader) { 272 if (c == ']') { 273 *next = 0; 274 next = buf; 275 rsp = rc_addsect(rcp, buf); 276 state = stSkipToEOL; 277 } else 278 *next++ = c; 279 continue; 280 } 281 if (state == stGetKey) { 282 if (c == ' ' || c == '\t')/* side effect: 'key name='*/ 283 continue; /* become 'keyname=' */ 284 if (c == '\n') { /* silently ignore ... */ 285 state = stNewLine; 286 continue; 287 } 288 if (c != '=') { 289 *next++ = c; 290 continue; 291 } 292 *next = 0; 293 if (rsp == NULL) { 294 fprintf(stderr, "Key '%s' defined before section\n", buf); 295 state = stSkipToEOL; 296 continue; 297 } 298 rkp = rc_sect_addkey(rsp, buf, NULL); 299 next = buf; 300 state = stGetValue; 301 continue; 302 } 303 /* only stGetValue left */ 304 if (state != stGetValue) { 305 fprintf(stderr, "Well, I can't parse file '%s'\n",rcp->rf_name); 306 state = stSkipToEOL; 307 } 308 if (c != '\n') { 309 *next++ = c; 310 continue; 311 } 312 *next = 0; 313 rkp->rk_value = strdup(buf); 314 state = stNewLine; 315 rkp = NULL; 316 } /* while */ 317 if (c == EOF && state == stGetValue) { 318 *next = 0; 319 rkp->rk_value = strdup(buf); 320 } 321 return; 322 } 323 324 int 325 rc_getstringptr(struct rcfile *rcp, const char *section, const char *key, 326 char **dest) 327 { 328 struct rcsection *rsp; 329 struct rckey *rkp; 330 331 *dest = NULL; 332 rsp = rc_findsect(rcp, section); 333 if (!rsp) return ENOENT; 334 rkp = rc_sect_findkey(rsp,key); 335 if (!rkp) return ENOENT; 336 *dest = rkp->rk_value; 337 return 0; 338 } 339 340 int 341 rc_getstring(struct rcfile *rcp, const char *section, const char *key, 342 size_t maxlen, char *dest) 343 { 344 char *value; 345 int error; 346 347 error = rc_getstringptr(rcp, section, key, &value); 348 if (error) 349 return error; 350 if (strlen(value) >= maxlen) { 351 warnx("line too long for key '%s' in section '%s', max = %zd\n", key, section, maxlen); 352 return EINVAL; 353 } 354 strcpy(dest, value); 355 return 0; 356 } 357 358 int 359 rc_getint(struct rcfile *rcp, const char *section, const char *key, int *value) 360 { 361 struct rcsection *rsp; 362 struct rckey *rkp; 363 364 rsp = rc_findsect(rcp, section); 365 if (!rsp) 366 return ENOENT; 367 rkp = rc_sect_findkey(rsp, key); 368 if (!rkp) 369 return ENOENT; 370 errno = 0; 371 *value = strtol(rkp->rk_value, NULL, 0); 372 if (errno) { 373 warnx("invalid int value '%s' for key '%s' in section '%s'\n", rkp->rk_value, key, section); 374 return errno; 375 } 376 return 0; 377 } 378 379 /* 380 * 1,yes,true 381 * 0,no,false 382 */ 383 int 384 rc_getbool(struct rcfile *rcp, const char *section, const char *key, int *value) 385 { 386 struct rcsection *rsp; 387 struct rckey *rkp; 388 char *p; 389 390 rsp = rc_findsect(rcp, section); 391 if (!rsp) return ENOENT; 392 rkp = rc_sect_findkey(rsp,key); 393 if (!rkp) return ENOENT; 394 p = rkp->rk_value; 395 while (*p && isspace(*p)) p++; 396 if (*p == '0' || strcasecmp(p,"no") == 0 || strcasecmp(p,"false") == 0) { 397 *value = 0; 398 return 0; 399 } 400 if (*p == '1' || strcasecmp(p,"yes") == 0 || strcasecmp(p,"true") == 0) { 401 *value = 1; 402 return 0; 403 } 404 fprintf(stderr, "invalid boolean value '%s' for key '%s' in section '%s' \n",p, key, section); 405 return EINVAL; 406 } 407 408 /* 409 * Unified command line/rc file parser 410 */ 411 int 412 opt_args_parse(struct rcfile *rcp, struct opt_args *ap, const char *sect, 413 opt_callback_t *callback) 414 { 415 int len, error; 416 417 for (; ap->opt; ap++) { 418 switch (ap->type) { 419 case OPTARG_STR: 420 if (rc_getstringptr(rcp, sect, ap->name, &ap->str) != 0) 421 break; 422 len = strlen(ap->str); 423 if (len > ap->ival) { 424 warnx("rc: argument for option '%c' (%s) too long\n", ap->opt, ap->name); 425 return EINVAL; 426 } 427 callback(ap); 428 break; 429 case OPTARG_BOOL: 430 error = rc_getbool(rcp, sect, ap->name, &ap->ival); 431 if (error == ENOENT) 432 break; 433 if (error) 434 return EINVAL; 435 callback(ap); 436 break; 437 case OPTARG_INT: 438 if (rc_getint(rcp, sect, ap->name, &ap->ival) != 0) 439 break; 440 if (((ap->flag & OPTFL_HAVEMIN) && ap->ival < ap->min) || 441 ((ap->flag & OPTFL_HAVEMAX) && ap->ival > ap->max)) { 442 warnx("rc: argument for option '%c' (%s) should be in [%d-%d] range\n", 443 ap->opt, ap->name, ap->min, ap->max); 444 return EINVAL; 445 } 446 callback(ap); 447 break; 448 default: 449 break; 450 } 451 } 452 return 0; 453 } 454 455 int 456 opt_args_parseopt(struct opt_args *ap, int opt, char *arg, 457 opt_callback_t *callback) 458 { 459 int len; 460 461 for (; ap->opt; ap++) { 462 if (ap->opt != opt) 463 continue; 464 switch (ap->type) { 465 case OPTARG_STR: 466 ap->str = arg; 467 if (arg) { 468 len = strlen(ap->str); 469 if (len > ap->ival) { 470 warnx("opt: Argument for option '%c' (%s) too long\n", ap->opt, ap->name); 471 return EINVAL; 472 } 473 callback(ap); 474 } 475 break; 476 case OPTARG_BOOL: 477 ap->ival = 0; 478 callback(ap); 479 break; 480 case OPTARG_INT: 481 errno = 0; 482 ap->ival = strtol(arg, NULL, 0); 483 if (errno) { 484 warnx("opt: Invalid integer value for option '%c' (%s).\n",ap->opt,ap->name); 485 return EINVAL; 486 } 487 if (((ap->flag & OPTFL_HAVEMIN) && 488 (ap->ival < ap->min)) || 489 ((ap->flag & OPTFL_HAVEMAX) && 490 (ap->ival > ap->max))) { 491 warnx("opt: Argument for option '%c' (%s) should be in [%d-%d] range\n",ap->opt,ap->name,ap->min,ap->max); 492 return EINVAL; 493 } 494 callback(ap); 495 break; 496 default: 497 break; 498 } 499 break; 500 } 501 return 0; 502 } 503 504