1 /*- 2 * Copyright (c) 2002-2015 Devin Teske <dteske@FreeBSD.org> 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 * 14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 * SUCH DAMAGE. 25 */ 26 27 #include <sys/cdefs.h> 28 __FBSDID("$FreeBSD$"); 29 30 #include <sys/param.h> 31 32 #include <ctype.h> 33 #include <errno.h> 34 #include <fcntl.h> 35 #include <fnmatch.h> 36 #include <stdlib.h> 37 #include <string.h> 38 #include <unistd.h> 39 40 #include "figpar.h" 41 #include "string_m.h" 42 43 struct figpar_config figpar_dummy_config = {0, NULL, {0}, NULL}; 44 45 /* 46 * Search for config option (struct figpar_config) in the array of config 47 * options, returning the struct whose directive matches the given parameter. 48 * If no match is found, a pointer to the static dummy array (above) is 49 * returned. 50 * 51 * This is to eliminate dependency on the index position of an item in the 52 * array, since the index position is more apt to be changed as code grows. 53 */ 54 struct figpar_config * 55 get_config_option(struct figpar_config options[], const char *directive) 56 { 57 uint32_t n; 58 59 /* Check arguments */ 60 if (options == NULL || directive == NULL) 61 return (&figpar_dummy_config); 62 63 /* Loop through the array, return the index of the first match */ 64 for (n = 0; options[n].directive != NULL; n++) 65 if (strcmp(options[n].directive, directive) == 0) 66 return (&(options[n])); 67 68 /* Re-initialize the dummy variable in case it was written to */ 69 figpar_dummy_config.directive = NULL; 70 figpar_dummy_config.type = 0; 71 figpar_dummy_config.action = NULL; 72 figpar_dummy_config.value.u_num = 0; 73 74 return (&figpar_dummy_config); 75 } 76 77 /* 78 * Parse the configuration file at `path' and execute the `action' call-back 79 * functions for any directives defined by the array of config options (first 80 * argument). 81 * 82 * For unknown directives that are encountered, you can optionally pass a 83 * call-back function for the third argument to be called for unknowns. 84 * 85 * Returns zero on success; otherwise returns -1 and errno should be consulted. 86 */ 87 int 88 parse_config(struct figpar_config options[], const char *path, 89 int (*unknown)(struct figpar_config *option, uint32_t line, 90 char *directive, char *value), uint16_t processing_options) 91 { 92 uint8_t bequals; 93 uint8_t bsemicolon; 94 uint8_t case_sensitive; 95 uint8_t comment = 0; 96 uint8_t end; 97 uint8_t found; 98 uint8_t have_equals = 0; 99 uint8_t quote; 100 uint8_t require_equals; 101 uint8_t strict_equals; 102 char p[2]; 103 char *directive; 104 char *t; 105 char *value; 106 int error; 107 int fd; 108 ssize_t r = 1; 109 uint32_t dsize; 110 uint32_t line = 1; 111 uint32_t n; 112 uint32_t vsize; 113 uint32_t x; 114 off_t charpos; 115 off_t curpos; 116 char rpath[PATH_MAX]; 117 118 /* Sanity check: if no options and no unknown function, return */ 119 if (options == NULL && unknown == NULL) 120 return (-1); 121 122 /* Processing options */ 123 bequals = (processing_options & FIGPAR_BREAK_ON_EQUALS) == 0 ? 0 : 1; 124 bsemicolon = 125 (processing_options & FIGPAR_BREAK_ON_SEMICOLON) == 0 ? 0 : 1; 126 case_sensitive = 127 (processing_options & FIGPAR_CASE_SENSITIVE) == 0 ? 0 : 1; 128 require_equals = 129 (processing_options & FIGPAR_REQUIRE_EQUALS) == 0 ? 0 : 1; 130 strict_equals = 131 (processing_options & FIGPAR_STRICT_EQUALS) == 0 ? 0 : 1; 132 133 /* Initialize strings */ 134 directive = value = 0; 135 vsize = dsize = 0; 136 137 /* Resolve the file path */ 138 if (realpath(path, rpath) == 0) 139 return (-1); 140 141 /* Open the file */ 142 if ((fd = open(rpath, O_RDONLY)) < 0) 143 return (-1); 144 145 /* Read the file until EOF */ 146 while (r != 0) { 147 r = read(fd, p, 1); 148 149 /* skip to the beginning of a directive */ 150 while (r != 0 && (isspace(*p) || *p == '#' || comment || 151 (bsemicolon && *p == ';'))) { 152 if (*p == '#') 153 comment = 1; 154 else if (*p == '\n') { 155 comment = 0; 156 line++; 157 } 158 r = read(fd, p, 1); 159 } 160 /* Test for EOF; if EOF then no directive was found */ 161 if (r == 0) { 162 close(fd); 163 return (0); 164 } 165 166 /* Get the current offset */ 167 curpos = lseek(fd, 0, SEEK_CUR) - 1; 168 if (curpos == -1) { 169 close(fd); 170 return (-1); 171 } 172 173 /* Find the length of the directive */ 174 for (n = 0; r != 0; n++) { 175 if (isspace(*p)) 176 break; 177 if (bequals && *p == '=') { 178 have_equals = 1; 179 break; 180 } 181 if (bsemicolon && *p == ';') 182 break; 183 r = read(fd, p, 1); 184 } 185 186 /* Test for EOF, if EOF then no directive was found */ 187 if (n == 0 && r == 0) { 188 close(fd); 189 return (0); 190 } 191 192 /* Go back to the beginning of the directive */ 193 error = (int)lseek(fd, curpos, SEEK_SET); 194 if (error == (curpos - 1)) { 195 close(fd); 196 return (-1); 197 } 198 199 /* Allocate and read the directive into memory */ 200 if (n > dsize) { 201 if ((directive = realloc(directive, n + 1)) == NULL) { 202 close(fd); 203 return (-1); 204 } 205 dsize = n; 206 } 207 r = read(fd, directive, n); 208 209 /* Advance beyond the equals sign if appropriate/desired */ 210 if (bequals && *p == '=') { 211 if (lseek(fd, 1, SEEK_CUR) != -1) 212 r = read(fd, p, 1); 213 if (strict_equals && isspace(*p)) 214 *p = '\n'; 215 } 216 217 /* Terminate the string */ 218 directive[n] = '\0'; 219 220 /* Convert directive to lower case before comparison */ 221 if (!case_sensitive) 222 strtolower(directive); 223 224 /* Move to what may be the start of the value */ 225 if (!(bsemicolon && *p == ';') && 226 !(strict_equals && *p == '=')) { 227 while (r != 0 && isspace(*p) && *p != '\n') 228 r = read(fd, p, 1); 229 } 230 231 /* An equals sign may have stopped us, should we eat it? */ 232 if (r != 0 && bequals && *p == '=' && !strict_equals) { 233 have_equals = 1; 234 r = read(fd, p, 1); 235 while (r != 0 && isspace(*p) && *p != '\n') 236 r = read(fd, p, 1); 237 } 238 239 /* If no value, allocate a dummy value and jump to action */ 240 if (r == 0 || *p == '\n' || *p == '#' || 241 (bsemicolon && *p == ';')) { 242 /* Initialize the value if not already done */ 243 if (value == NULL && (value = malloc(1)) == NULL) { 244 close(fd); 245 return (-1); 246 } 247 value[0] = '\0'; 248 goto call_function; 249 } 250 251 /* Get the current offset */ 252 curpos = lseek(fd, 0, SEEK_CUR) - 1; 253 if (curpos == -1) { 254 close(fd); 255 return (-1); 256 } 257 258 /* Find the end of the value */ 259 quote = 0; 260 end = 0; 261 while (r != 0 && end == 0) { 262 /* Advance to the next character if we know we can */ 263 if (*p != '\"' && *p != '#' && *p != '\n' && 264 (!bsemicolon || *p != ';')) { 265 r = read(fd, p, 1); 266 continue; 267 } 268 269 /* 270 * If we get this far, we've hit an end-key 271 */ 272 273 /* Get the current offset */ 274 charpos = lseek(fd, 0, SEEK_CUR) - 1; 275 if (charpos == -1) { 276 close(fd); 277 return (-1); 278 } 279 280 /* 281 * Go back so we can read the character before the key 282 * to check if the character is escaped (which means we 283 * should continue). 284 */ 285 error = (int)lseek(fd, -2, SEEK_CUR); 286 if (error == -3) { 287 close(fd); 288 return (-1); 289 } 290 r = read(fd, p, 1); 291 292 /* 293 * Count how many backslashes there are (an odd number 294 * means the key is escaped, even means otherwise). 295 */ 296 for (n = 1; *p == '\\'; n++) { 297 /* Move back another offset to read */ 298 error = (int)lseek(fd, -2, SEEK_CUR); 299 if (error == -3) { 300 close(fd); 301 return (-1); 302 } 303 r = read(fd, p, 1); 304 } 305 306 /* Move offset back to the key and read it */ 307 error = (int)lseek(fd, charpos, SEEK_SET); 308 if (error == (charpos - 1)) { 309 close(fd); 310 return (-1); 311 } 312 r = read(fd, p, 1); 313 314 /* 315 * If an even number of backslashes was counted meaning 316 * key is not escaped, we should evaluate what to do. 317 */ 318 if ((n & 1) == 1) { 319 switch (*p) { 320 case '\"': 321 /* 322 * Flag current sequence of characters 323 * to follow as being quoted (hashes 324 * are not considered comments). 325 */ 326 quote = !quote; 327 break; 328 case '#': 329 /* 330 * If we aren't in a quoted series, we 331 * just hit an inline comment and have 332 * found the end of the value. 333 */ 334 if (!quote) 335 end = 1; 336 break; 337 case '\n': 338 /* 339 * Newline characters must always be 340 * escaped, whether inside a quoted 341 * series or not, otherwise they 342 * terminate the value. 343 */ 344 end = 1; 345 case ';': 346 if (!quote && bsemicolon) 347 end = 1; 348 break; 349 } 350 } else if (*p == '\n') 351 /* Escaped newline character. increment */ 352 line++; 353 354 /* Advance to the next character */ 355 r = read(fd, p, 1); 356 } 357 358 /* Get the current offset */ 359 charpos = lseek(fd, 0, SEEK_CUR) - 1; 360 if (charpos == -1) { 361 close(fd); 362 return (-1); 363 } 364 365 /* Get the length of the value */ 366 n = (uint32_t)(charpos - curpos); 367 if (r != 0) /* more to read, but don't read ending key */ 368 n--; 369 370 /* Move offset back to the beginning of the value */ 371 error = (int)lseek(fd, curpos, SEEK_SET); 372 if (error == (curpos - 1)) { 373 close(fd); 374 return (-1); 375 } 376 377 /* Allocate and read the value into memory */ 378 if (n > vsize) { 379 if ((value = realloc(value, n + 1)) == NULL) { 380 close(fd); 381 return (-1); 382 } 383 vsize = n; 384 } 385 r = read(fd, value, n); 386 387 /* Terminate the string */ 388 value[n] = '\0'; 389 390 /* Cut trailing whitespace off by termination */ 391 t = value + n; 392 while (isspace(*--t)) 393 *t = '\0'; 394 395 /* Escape the escaped quotes (replaceall is in string_m.c) */ 396 x = strcount(value, "\\\""); /* in string_m.c */ 397 if (x != 0 && (n + x) > vsize) { 398 if ((value = realloc(value, n + x + 1)) == NULL) { 399 close(fd); 400 return (-1); 401 } 402 vsize = n + x; 403 } 404 if (replaceall(value, "\\\"", "\\\\\"") < 0) { 405 /* Replace operation failed for some unknown reason */ 406 close(fd); 407 return (-1); 408 } 409 410 /* Remove all new line characters */ 411 if (replaceall(value, "\\\n", "") < 0) { 412 /* Replace operation failed for some unknown reason */ 413 close(fd); 414 return (-1); 415 } 416 417 /* Resolve escape sequences */ 418 strexpand(value); /* in string_m.c */ 419 420 call_function: 421 /* Abort if we're seeking only assignments */ 422 if (require_equals && !have_equals) 423 return (-1); 424 425 found = have_equals = 0; /* reset */ 426 427 /* If there are no options defined, call unknown and loop */ 428 if (options == NULL && unknown != NULL) { 429 error = unknown(NULL, line, directive, value); 430 if (error != 0) { 431 close(fd); 432 return (error); 433 } 434 continue; 435 } 436 437 /* Loop through the array looking for a match for the value */ 438 for (n = 0; options[n].directive != NULL; n++) { 439 error = fnmatch(options[n].directive, directive, 440 FNM_NOESCAPE); 441 if (error == 0) { 442 found = 1; 443 /* Call function for array index item */ 444 if (options[n].action != NULL) { 445 error = options[n].action( 446 &options[n], 447 line, directive, value); 448 if (error != 0) { 449 close(fd); 450 return (error); 451 } 452 } 453 } else if (error != FNM_NOMATCH) { 454 /* An error has occurred */ 455 close(fd); 456 return (-1); 457 } 458 } 459 if (!found && unknown != NULL) { 460 /* 461 * No match was found for the value we read from the 462 * file; call function designated for unknown values. 463 */ 464 error = unknown(NULL, line, directive, value); 465 if (error != 0) { 466 close(fd); 467 return (error); 468 } 469 } 470 } 471 472 close(fd); 473 return (0); 474 } 475