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