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