freebsd-src/lib/libfigpar/figpar.c
Faraz Vahedi f144058b40 Refactor error handling in lseek operations
1. Subtraction was performed on the current position
   regardless of the success of the lseek operation.
   In the event of an error, this resulted in the
   current position being erroneously set to -2,
   which bypassed the intended error handling
   mechanism. The proposed change performs error
   checking immediately following the lseek operation,
   prior to any modification of the current position.
   This ensures that a failed lseek operation will
   correctly trigger the appropriate error handling.

2. The error checking logic was based on the assumption
   that lseek would return `offset - 1` upon failure.
   However, this is not consistent with the behaviour of
   lseek as specified in the POSIX standard, which
   stipulates that lseek shall return -1 in case of
   an error. The code has been updated to reflect this
   standard, improving reliability and compliance.

Reviewed by: imp
Pull Request: https://github.com/freebsd/freebsd-src/pull/1392
2024-09-21 05:25:15 -06:00

465 lines
12 KiB
C

/*-
* Copyright (c) 2002-2015 Devin Teske <dteske@FreeBSD.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/param.h>
#include <ctype.h>
#include <fcntl.h>
#include <fnmatch.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "figpar.h"
#include "string_m.h"
struct figpar_config figpar_dummy_config = {0, NULL, {0}, NULL};
/*
* Search for config option (struct figpar_config) in the array of config
* options, returning the struct whose directive matches the given parameter.
* If no match is found, a pointer to the static dummy array (above) is
* returned.
*
* This is to eliminate dependency on the index position of an item in the
* array, since the index position is more apt to be changed as code grows.
*/
struct figpar_config *
get_config_option(struct figpar_config options[], const char *directive)
{
uint32_t n;
/* Check arguments */
if (options == NULL || directive == NULL)
return (&figpar_dummy_config);
/* Loop through the array, return the index of the first match */
for (n = 0; options[n].directive != NULL; n++)
if (strcmp(options[n].directive, directive) == 0)
return (&(options[n]));
/* Re-initialize the dummy variable in case it was written to */
figpar_dummy_config.directive = NULL;
figpar_dummy_config.type = 0;
figpar_dummy_config.action = NULL;
figpar_dummy_config.value.u_num = 0;
return (&figpar_dummy_config);
}
/*
* Parse the configuration file at `path' and execute the `action' call-back
* functions for any directives defined by the array of config options (first
* argument).
*
* For unknown directives that are encountered, you can optionally pass a
* call-back function for the third argument to be called for unknowns.
*
* Returns zero on success; otherwise returns -1 and errno should be consulted.
*/
int
parse_config(struct figpar_config options[], const char *path,
int (*unknown)(struct figpar_config *option, uint32_t line,
char *directive, char *value), uint16_t processing_options)
{
uint8_t bequals;
uint8_t bsemicolon;
uint8_t case_sensitive;
uint8_t comment = 0;
uint8_t end;
uint8_t found;
uint8_t have_equals = 0;
uint8_t quote;
uint8_t require_equals;
uint8_t strict_equals;
char p[2];
char *directive;
char *t;
char *value;
int error;
int fd;
ssize_t r = 1;
uint32_t dsize;
uint32_t line = 1;
uint32_t n;
uint32_t vsize;
uint32_t x;
off_t charpos;
off_t curpos;
char rpath[PATH_MAX];
/* Sanity check: if no options and no unknown function, return */
if (options == NULL && unknown == NULL)
return (-1);
/* Processing options */
bequals = (processing_options & FIGPAR_BREAK_ON_EQUALS) == 0 ? 0 : 1;
bsemicolon =
(processing_options & FIGPAR_BREAK_ON_SEMICOLON) == 0 ? 0 : 1;
case_sensitive =
(processing_options & FIGPAR_CASE_SENSITIVE) == 0 ? 0 : 1;
require_equals =
(processing_options & FIGPAR_REQUIRE_EQUALS) == 0 ? 0 : 1;
strict_equals =
(processing_options & FIGPAR_STRICT_EQUALS) == 0 ? 0 : 1;
/* Initialize strings */
directive = value = 0;
vsize = dsize = 0;
/* Resolve the file path */
if (realpath(path, rpath) == 0)
return (-1);
/* Open the file */
if ((fd = open(rpath, O_RDONLY)) < 0)
return (-1);
/* Read the file until EOF */
while (r != 0) {
r = read(fd, p, 1);
/* skip to the beginning of a directive */
while (r != 0 && (isspace(*p) || *p == '#' || comment ||
(bsemicolon && *p == ';'))) {
if (*p == '#')
comment = 1;
else if (*p == '\n') {
comment = 0;
line++;
}
r = read(fd, p, 1);
}
/* Test for EOF; if EOF then no directive was found */
if (r == 0) {
close(fd);
return (0);
}
/* Get the current offset */
if ((curpos = lseek(fd, 0, SEEK_CUR)) == -1) {
close(fd);
return (-1);
}
curpos--;
/* Find the length of the directive */
for (n = 0; r != 0; n++) {
if (isspace(*p))
break;
if (bequals && *p == '=') {
have_equals = 1;
break;
}
if (bsemicolon && *p == ';')
break;
r = read(fd, p, 1);
}
/* Test for EOF, if EOF then no directive was found */
if (n == 0 && r == 0) {
close(fd);
return (0);
}
/* Go back to the beginning of the directive */
if (lseek(fd, curpos, SEEK_SET) == -1) {
close(fd);
return (-1);
}
/* Allocate and read the directive into memory */
if (n > dsize) {
if ((directive = realloc(directive, n + 1)) == NULL) {
close(fd);
return (-1);
}
dsize = n;
}
r = read(fd, directive, n);
/* Advance beyond the equals sign if appropriate/desired */
if (bequals && *p == '=') {
if (lseek(fd, 1, SEEK_CUR) != -1)
r = read(fd, p, 1);
if (strict_equals && isspace(*p))
*p = '\n';
}
/* Terminate the string */
directive[n] = '\0';
/* Convert directive to lower case before comparison */
if (!case_sensitive)
strtolower(directive);
/* Move to what may be the start of the value */
if (!(bsemicolon && *p == ';') &&
!(strict_equals && *p == '=')) {
while (r != 0 && isspace(*p) && *p != '\n')
r = read(fd, p, 1);
}
/* An equals sign may have stopped us, should we eat it? */
if (r != 0 && bequals && *p == '=' && !strict_equals) {
have_equals = 1;
r = read(fd, p, 1);
while (r != 0 && isspace(*p) && *p != '\n')
r = read(fd, p, 1);
}
/* If no value, allocate a dummy value and jump to action */
if (r == 0 || *p == '\n' || *p == '#' ||
(bsemicolon && *p == ';')) {
/* Initialize the value if not already done */
if (value == NULL && (value = malloc(1)) == NULL) {
close(fd);
return (-1);
}
value[0] = '\0';
goto call_function;
}
/* Get the current offset */
if ((curpos = lseek(fd, 0, SEEK_CUR)) == -1) {
close(fd);
return (-1);
}
curpos--;
/* Find the end of the value */
quote = 0;
end = 0;
while (r != 0 && end == 0) {
/* Advance to the next character if we know we can */
if (*p != '\"' && *p != '#' && *p != '\n' &&
(!bsemicolon || *p != ';')) {
r = read(fd, p, 1);
continue;
}
/*
* If we get this far, we've hit an end-key
*/
/* Get the current offset */
if ((charpos = lseek(fd, 0, SEEK_CUR)) == -1) {
close(fd);
return (-1);
}
charpos--;
/*
* Go back so we can read the character before the key
* to check if the character is escaped (which means we
* should continue).
*/
if (lseek(fd, -2, SEEK_CUR) == -1) {
close(fd);
return (-1);
}
r = read(fd, p, 1);
/*
* Count how many backslashes there are (an odd number
* means the key is escaped, even means otherwise).
*/
for (n = 1; *p == '\\'; n++) {
/* Move back another offset to read */
if (lseek(fd, -2, SEEK_CUR) == -1) {
close(fd);
return (-1);
}
r = read(fd, p, 1);
}
/* Move offset back to the key and read it */
if (lseek(fd, charpos, SEEK_SET) == -1) {
close(fd);
return (-1);
}
r = read(fd, p, 1);
/*
* If an even number of backslashes was counted meaning
* key is not escaped, we should evaluate what to do.
*/
if ((n & 1) == 1) {
switch (*p) {
case '\"':
/*
* Flag current sequence of characters
* to follow as being quoted (hashes
* are not considered comments).
*/
quote = !quote;
break;
case '#':
/*
* If we aren't in a quoted series, we
* just hit an inline comment and have
* found the end of the value.
*/
if (!quote)
end = 1;
break;
case '\n':
/*
* Newline characters must always be
* escaped, whether inside a quoted
* series or not, otherwise they
* terminate the value.
*/
end = 1;
case ';':
if (!quote && bsemicolon)
end = 1;
break;
}
} else if (*p == '\n')
/* Escaped newline character. increment */
line++;
/* Advance to the next character */
r = read(fd, p, 1);
}
/* Get the current offset */
if ((charpos = lseek(fd, 0, SEEK_CUR)) == -1) {
close(fd);
return (-1);
}
/* Get the length of the value */
n = (uint32_t)(charpos - curpos);
if (r != 0) /* more to read, but don't read ending key */
n--;
/* Move offset back to the beginning of the value */
if (lseek(fd, curpos, SEEK_SET) == -1) {
close(fd);
return (-1);
}
/* Allocate and read the value into memory */
if (n > vsize) {
if ((value = realloc(value, n + 1)) == NULL) {
close(fd);
return (-1);
}
vsize = n;
}
r = read(fd, value, n);
/* Terminate the string */
value[n] = '\0';
/* Cut trailing whitespace off by termination */
t = value + n;
while (isspace(*--t))
*t = '\0';
/* Escape the escaped quotes (replaceall is in string_m.c) */
x = strcount(value, "\\\""); /* in string_m.c */
if (x != 0 && (n + x) > vsize) {
if ((value = realloc(value, n + x + 1)) == NULL) {
close(fd);
return (-1);
}
vsize = n + x;
}
if (replaceall(value, "\\\"", "\\\\\"") < 0) {
/* Replace operation failed for some unknown reason */
close(fd);
return (-1);
}
/* Remove all new line characters */
if (replaceall(value, "\\\n", "") < 0) {
/* Replace operation failed for some unknown reason */
close(fd);
return (-1);
}
/* Resolve escape sequences */
strexpand(value); /* in string_m.c */
call_function:
/* Abort if we're seeking only assignments */
if (require_equals && !have_equals)
return (-1);
found = have_equals = 0; /* reset */
/* If there are no options defined, call unknown and loop */
if (options == NULL && unknown != NULL) {
error = unknown(NULL, line, directive, value);
if (error != 0) {
close(fd);
return (error);
}
continue;
}
/* Loop through the array looking for a match for the value */
for (n = 0; options[n].directive != NULL; n++) {
error = fnmatch(options[n].directive, directive,
FNM_NOESCAPE);
if (error == 0) {
found = 1;
/* Call function for array index item */
if (options[n].action != NULL) {
error = options[n].action(
&options[n],
line, directive, value);
if (error != 0) {
close(fd);
return (error);
}
}
} else if (error != FNM_NOMATCH) {
/* An error has occurred */
close(fd);
return (-1);
}
}
if (!found && unknown != NULL) {
/*
* No match was found for the value we read from the
* file; call function designated for unknown values.
*/
error = unknown(NULL, line, directive, value);
if (error != 0) {
close(fd);
return (error);
}
}
}
close(fd);
return (0);
}