blob: c2096818afd59b84bccab7b62e257f08d956017e [file] [log] [blame]
/* libnih
*
* config.c - configuration file parsing
*
* Copyright © 2009 Scott James Remnant <scott@netsplit.com>.
* Copyright © 2009 Canonical Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2, as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif /* HAVE_CONFIG_H */
#include <sys/types.h>
#include <limits.h>
#include <string.h>
#include <nih/macros.h>
#include <nih/alloc.h>
#include <nih/string.h>
#include <nih/file.h>
#include <nih/config.h>
#include <nih/logging.h>
#include <nih/error.h>
#include <nih/errors.h>
/* Prototypes for static functions */
static int nih_config_block_end (const char *file, size_t len,
size_t *lineno, size_t *pos,
const char *type,
size_t *endpos)
__attribute__ ((warn_unused_result));
static NihConfigStanza *nih_config_get_stanza (const char *name,
NihConfigStanza *stanzas);
/**
* nih_config_has_token:
* @file: file or string to parse,
* @len: length of @file,
* @pos: offset within @file,
* @lineno: line number.
*
* Checks the current position in @file to see whether it has a parseable
* token at this position; ie. we're not at the end of file, and the
* current character is neither a comment or newline character.
*
* If this returns FALSE, it's normal to call nih_config_skip_comment()
* to move to the next parseable point and check again.
*
* @file may be a memory mapped file, in which case @pos should be given
* as the offset within and @len should be the length of the file as a
* whole.
*
* @pos is used as the offset within @file to begin, otherwise the start
* is assumed.
*
* Returns: TRUE if the current character is before the end of file and
* is neither a comment or newline, FALSE otherwise.
**/
int
nih_config_has_token (const char *file,
size_t len,
size_t *pos,
size_t *lineno)
{
size_t p;
nih_assert (file != NULL);
p = (pos ? *pos : 0);
if ((p < len) && (! strchr (NIH_CONFIG_CNL, file[p]))) {
return TRUE;
} else {
return FALSE;
}
}
/**
* nih_config_token:
* @file: file or string to parse,
* @len: length of @file,
* @pos: offset within @file,
* @lineno: line number,
* @dest: destination to copy to,
* @delim: characters to stop on,
* @dequote: remove quotes and escapes.
* @toklen: pointer to store token length in.
*
* Parses a single token from @file which is stopped when any character
* in @delim is encountered outside of a quoted string and not escaped
* using a backslash. The length of the parsed token is stored in @toklen
* if given.
*
* @file may be a memory mapped file, in which case @pos should be given
* as the offset within and @len should be the length of the file as a
* whole. Usually when @dest is given, @file is instead the pointer to
* the start of the token and @len is the difference between the start
* and end of the token (NOT the return value from this function).
*
* If @pos is given then it will be used as the offset within @file to
* begin (otherwise the start is assumed), and will be updated to point
* to @delim or past the end of the file.
*
* If @lineno is given it will be incremented each time a new line is
* discovered in the file.
*
* To copy the token into another string, collapsing any newlines and
* surrounding whitespace to a single space, pass @dest which should be
* pre-allocated to the right size (obtained by calling this function
* with NULL).
*
* If you also want quotes to be removed and escaped characters to be
* replaced with the character itself, set @dequote to TRUE.
*
* Returns: zero on success, negative value on raised error.
**/
int
nih_config_token (const char *file,
size_t len,
size_t *pos,
size_t *lineno,
char *dest,
const char *delim,
int dequote,
size_t *toklen)
{
size_t p, ws = 0, nlws = 0, qc = 0, i = 0;
int slash = FALSE, quote = 0, nl = FALSE, ret = 0;
nih_assert (file != NULL);
nih_assert (delim != NULL);
/* We keep track of the following:
* slash whether a \ is in effect
* quote whether " or ' is in effect (set to which)
* ws number of consecutive whitespace chars so far
* nlws number of whitespace/newline chars
* nl TRUE if we need to copy ws into nlws at first non-WS
* qc number of quote characters that need removing.
*/
for (p = (pos ? *pos : 0); p < len; p++) {
int extra = 0, isq = FALSE;
if (slash) {
slash = FALSE;
/* Escaped newline */
if (file[p] == '\n') {
nlws++;
nl = TRUE;
if (lineno)
(*lineno)++;
continue;
} else if ((file[p] == '\\')
|| strchr (NIH_CONFIG_WS, file[p])) {
extra++;
if (dequote)
qc++;
} else if (dest) {
dest[i++] = '\\';
}
} else if (file[p] == '\\') {
slash = TRUE;
continue;
} else if (quote) {
if (file[p] == quote) {
quote = 0;
isq = TRUE;
} else if (file[p] == '\n') {
nl = TRUE;
if (lineno)
(*lineno)++;
continue;
} else if (strchr (NIH_CONFIG_WS, file[p])) {
ws++;
continue;
}
} else if ((file[p] == '\"') || (file[p] == '\'')) {
quote = file[p];
isq = TRUE;
} else if (strchr (delim, file[p])) {
break;
} else if (strchr (NIH_CONFIG_WS, file[p])) {
ws++;
continue;
}
if (nl) {
/* Newline is recorded as a single space;
* any surrounding whitespace is lost.
*/
nlws += ws;
if (dest)
dest[i++] = ' ';
} else if (ws && dest) {
/* Whitespace that we've encountered to date is
* copied as it is.
*/
memcpy (dest + i, file + p - ws - extra, ws);
i += ws;
}
/* Extra characters (the slash) needs to be copied
* unless we're dequoting the string
*/
if (extra && dest && (! dequote)) {
memcpy (dest + i, file + p - extra, extra);
i += extra;
}
if (dest && (! (isq && dequote)))
dest[i++] = file[p];
if (isq && dequote)
qc++;
ws = 0;
nl = FALSE;
extra = 0;
}
/* Add the NULL byte */
if (dest)
dest[i++] = '\0';
/* A trailing slash on the end of the file makes no sense. */
if (slash) {
nih_error_raise (NIH_CONFIG_TRAILING_SLASH,
_(NIH_CONFIG_TRAILING_SLASH_STR));
ret = -1;
goto finish;
}
/* Leaving quotes open is also generally bad. */
if (quote) {
nih_error_raise (NIH_CONFIG_UNTERMINATED_QUOTE,
_(NIH_CONFIG_UNTERMINATED_QUOTE_STR));
ret = -1;
goto finish;
}
/* The token length we return is the length of the token with any
* newlines and surrounding whitespace converted to a single
* character and any trailing whitespace removed.
*
* The actual end of the text read is returned in *pos.
*/
if (toklen)
*toklen = p - (pos ? *pos : 0) - ws - nlws - qc;
finish:
if (pos)
*pos = p;
return ret;
}
/**
* nih_config_next_token:
* @parent: parent object for returned token,
* @file: file or string to parse,
* @len: length of @file,
* @pos: offset within @file,
* @lineno: line number,
* @delim: characters to stop on,
* @dequote: remove quotes and escapes.
*
* Extracts a single token from @file which is stopped when any character
* in @delim is encountered outside of a quoted string and not escaped
* using a backslash. If @delim contains any whitespace character, then
* all whitespace after the token is also consumed, but not returned,
* including that with escaped newlines within it.
*
* @file may be a memory mapped file, in which case @pos should be given
* as the offset within and @len should be the length of the file as a
* whole.
*
* If @pos is given then it will be used as the offset within @file to
* begin (otherwise the start is assumed), and will be updated to point
* to @delim or past the end of the file.
*
* If @lineno is given it will be incremented each time a new line is
* discovered in the file.
*
* If you also want quotes to be removed and escaped characters to be
* replaced with the character itself, set @dequote to TRUE.
*
* If @parent is not NULL, it should be a pointer to another object which
* will be used as a parent for the returned token. When all parents
* of the returned token are freed, the returned token will also be
* freed.
*
* Returns: the token found or NULL on raised error.
**/
char *
nih_config_next_token (const void *parent,
const char *file,
size_t len,
size_t *pos,
size_t *lineno,
const char *delim,
int dequote)
{
size_t p, arg_start, arg_len, arg_end;
char *arg = NULL;
nih_assert (file != NULL);
p = (pos ? *pos : 0);
arg_start = p;
if (nih_config_token (file, len, &p, lineno, NULL, delim, dequote,
&arg_len) < 0)
goto finish;
arg_end = p;
if (! arg_len) {
nih_error_raise (NIH_CONFIG_EXPECTED_TOKEN,
_(NIH_CONFIG_EXPECTED_TOKEN_STR));
goto finish;
}
nih_config_skip_whitespace (file, len, &p, lineno);
/* Copy in the new token */
arg = nih_alloc (parent, arg_len + 1);
if (! arg)
nih_return_system_error (NULL);
if (nih_config_token (file + arg_start, arg_end - arg_start, NULL,
NULL, arg, delim, dequote, NULL) < 0)
goto finish;
finish:
if (pos)
*pos = p;
return arg;
}
/**
* nih_config_next_arg:
* @parent: parent object for returned argument,
* @file: file or string to parse,
* @len: length of @file,
* @pos: offset within @file,
* @lineno: line number.
*
* Extracts a single argument from @file, a dequoted token that is stopped
* on any comment, space or newline character that is not quoted or escaped
* with a backslash. Any whitespace after the argument is also consumed,
* but not returned, including that with escaped newlines within it.
*
* @file may be a memory mapped file, in which case @pos should be given
* as the offset within and @len should be the length of the file as a
* whole.
*
* If @pos is given then it will be used as the offset within @file to
* begin (otherwise the start is assumed), and will be updated to point
* to @delim or past the end of the file.
*
* If @lineno is given it will be incremented each time a new line is
* discovered in the file.
*
* If @parent is not NULL, it should be a pointer to another object which
* will be used as a parent for the returned argument. When all parents
* of the returned argument are freed, the returned argument will also be
* freed.
*
* Returns: the argument found or NULL on raised error.
**/
char *
nih_config_next_arg (const void *parent,
const char *file,
size_t len,
size_t *pos,
size_t *lineno)
{
nih_assert (file != NULL);
return nih_config_next_token (parent, file, len, pos, lineno,
NIH_CONFIG_CNLWS, TRUE);
}
/**
* nih_config_next_line:
* @file: file or string to parse,
* @len: length of @file,
* @pos: offset within @file,
* @lineno: line number.
*
* Skips to the end of the current line in @file, ignoring any tokens,
* comments, etc. along the way. If you want to ensure that no arguments
* are missed, use nih_config_skip_comment() instead.
*
* @file may be a memory mapped file, in which case @pos should be given
* as the offset within and @len should be the length of the file as a
* whole.
*
* @pos is used as the offset within @file to begin, and will be updated
* to point to past the end of the line or file.
*
* If @lineno is given it will be incremented each time a new line is
* discovered in the file.
**/
void
nih_config_next_line (const char *file,
size_t len,
size_t *pos,
size_t *lineno)
{
nih_assert (file != NULL);
nih_assert (pos != NULL);
/* Spool forwards until the end of the line */
while ((*pos < len) && (file[*pos] != '\n'))
(*pos)++;
/* Step over it */
if (*pos < len) {
if (lineno)
(*lineno)++;
(*pos)++;
}
}
/**
* nih_config_skip_whitespace:
* @file: file or string to parse,
* @len: length of @file,
* @pos: offset within @file,
* @lineno: line number.
*
* Skips an amount of whitespace and finds either the next token or the end
* of the current line in @file. Escaped newlines within the whitespace
* are treated as whitespace.
*
* @file may be a memory mapped file, in which case @pos should be given
* as the offset within and @len should be the length of the file as a
* whole.
*
* @pos is used as the offset within @file to begin, and will be updated
* to point to past the end of the line or file.
*
* If @lineno is given it will be incremented each time a new line is
* discovered in the file.
**/
void
nih_config_skip_whitespace (const char *file,
size_t len,
size_t *pos,
size_t *lineno)
{
nih_assert (file != NULL);
nih_assert (pos != NULL);
/* Skip any amount of whitespace between them, we also need to
* detect an escaped newline here.
*/
while (*pos < len) {
if (file[*pos] == '\\') {
/* Escape character, only continue scanning if
* the next character is newline
*/
if ((len - *pos > 1) && (file[*pos + 1] == '\n')) {
(*pos)++;
} else {
break;
}
} else if (! strchr (NIH_CONFIG_WS, file[*pos])) {
break;
}
if (file[*pos] == '\n')
if (lineno)
(*lineno)++;
/* Whitespace characer */
(*pos)++;
}
}
/**
* nih_config_skip_comment:
* @file: file or string to parse,
* @len: length of @file,
* @pos: offset within @file,
* @lineno: line number.
*
* Skips a comment and finds the end of the current line in @file. If the
* current position does not point to the end of a line, or a comment,
* then an error is raised.
*
* @file may be a memory mapped file, in which case @pos should be given
* as the offset within and @len should be the length of the file as a
* whole.
*
* @pos is used as the offset within @file to begin, and will be updated
* to point to past the end of the line or file.
*
* If @lineno is given it will be incremented each time a new line is
* discovered in the file.
*
* Returns: zero on success, negative value on raised error.
**/
int
nih_config_skip_comment (const char *file,
size_t len,
size_t *pos,
size_t *lineno)
{
nih_assert (file != NULL);
nih_assert (pos != NULL);
if (nih_config_has_token (file, len, pos, lineno)) {
nih_error_raise (NIH_CONFIG_UNEXPECTED_TOKEN,
_(NIH_CONFIG_UNEXPECTED_TOKEN_STR));
return -1;
}
nih_config_next_line (file, len, pos, lineno);
return 0;
}
/**
* nih_config_parse_args:
* @parent: parent object for returned array,
* @file: file or string to parse,
* @len: length of @file,
* @pos: offset within @file,
* @lineno: line number.
*
* Extracts a list of arguments from @file, each argument is separated
* by whitespace and parsing is stopped when a newline is encountered
* outside of a quoted string and not escaped using a backslash.
*
* @file may be a memory mapped file, in which case @pos should be given
* as the offset within and @len should be the length of the file as a
* whole.
*
* If @pos is given then it will be used as the offset within @file to
* begin (otherwise the start is assumed), and will be updated to point
* past the end of the line or the end of the file.
*
* If @lineno is given it will be incremented each time a new line is
* discovered in the file.
*
* The arguments are returned as a NULL-terminated array, with each argument
* dequoted before being returned.
*
* If @parent is not NULL, it should be a pointer to another object which
* will be used as a parent for the returned array. When all parents
* of the returned array are freed, the returned array will also be
* freed.
*
* Returns: the list of arguments found or NULL on raised error.
**/
char **
nih_config_parse_args (const void *parent,
const char *file,
size_t len,
size_t *pos,
size_t *lineno)
{
char **args;
size_t p, nargs;
nih_assert (file != NULL);
/* Begin with an empty array */
nargs = 0;
args = nih_str_array_new (parent);
if (! args)
nih_return_system_error (NULL);
/* Loop through the arguments until we hit a comment or newline */
p = (pos ? *pos : 0);
while (nih_config_has_token (file, len, &p, lineno)) {
char *arg;
arg = nih_config_next_arg (args, file, len, &p, lineno);
if (! arg) {
nih_free (args);
args = NULL;
goto finish;
}
if (! nih_str_array_addp (&args, parent, &nargs, arg)) {
nih_error_raise_system ();
nih_free (args);
return NULL;
}
}
/* nih_config_has_token has returned FALSE, we must be either past
* the end of the file, or at a comment or newline.
*/
if (nih_config_skip_comment (file, len, &p, lineno) < 0)
nih_assert_not_reached ();
finish:
if (pos)
*pos = p;
return args;
}
/**
* nih_config_parse_command:
* @parent: parent object for returned string,
* @file: file or string to parse,
* @len: length of @file,
* @pos: offset within @file,
* @lineno: line number,
*
* Extracts a command and its arguments from @file, stopping when a
* newline is encountered outside of a quoted string and not escaped
* using a blackslash.
*
* @file may be a memory mapped file, in which case @pos should be given
* as the offset within and @len should be the length of the file as a
* whole.
*
* If @pos is given then it will be used as the offset within @file to
* begin (otherwise the start is assumed), and will be updated to point
* past the end of the line or the end of the file.
*
* If @lineno is given it will be incremented each time a new line is
* discovered in the file.
*
* The command is returned as a string allocated with nih_alloc().
*
* If @parent is not NULL, it should be a pointer to another object which
* will be used as a parent for the returned string. When all parents
* of the returned string are freed, the returned string will also be
* freed.
*
* Returns: the command found or NULL on raised error.
**/
char *
nih_config_parse_command (const void *parent,
const char *file,
size_t len,
size_t *pos,
size_t *lineno)
{
char *cmd = NULL;
size_t p, cmd_start, cmd_len, cmd_end;
nih_assert (file != NULL);
/* Find the length of string up to the first unescaped comment
* or newline.
*/
p = (pos ? *pos : 0);
cmd_start = p;
if (nih_config_token (file, len, &p, lineno, NULL,
NIH_CONFIG_CNL, FALSE, &cmd_len) < 0)
goto finish;
cmd_end = p;
/* nih_config_token will eat up to the end of the file, a comment
* or a newline; so this must always succeed.
*/
if (nih_config_skip_comment (file, len, &p, lineno) < 0)
nih_assert_not_reached ();
/* Now copy the string into the destination. */
cmd = nih_alloc (parent, cmd_len + 1);
if (! cmd)
nih_return_system_error (NULL);
if (nih_config_token (file + cmd_start, cmd_end - cmd_start, NULL,
NULL, cmd, NIH_CONFIG_CNL, FALSE, NULL) < 0)
goto finish;
finish:
if (pos)
*pos = p;
return cmd;
}
/**
* nih_config_parse_block:
* @parent: parent object for returned string,
* @file: file or string to parse,
* @len: length of @file,
* @pos: offset within @file,
* @lineno: line number,
* @type: block identifier.
*
* Extracts a block of text from @line, stopping when the pharse "end @type"
* is encountered without any quotes or blackslash escaping within it.
*
* @file may be a memory mapped file, in which case @pos should be given
* as the offset within and @len should be the length of the file as a
* whole.
*
* If @pos is given then it will be used as the offset within @file to
* begin (otherwise the start is assumed), and will be updated to point
* past the end of the block or the end of the file.
*
* Either @file or @pos should point to the start of the block, after the
* opening stanza, rather than the start of the stanza that opens it.
*
* If @lineno is given it will be incremented each time a new line is
* discovered in the file.
*
* The block is returned as a string allocated with nih_alloc().
*
* If @parent is not NULL, it should be a pointer to another object which
* will be used as a parent for the returned string. When all parents
* of the returned string are freed, the returned string will also be
* freed.
*
* Returns: the text contained within the block or NULL on raised error.
**/
char *
nih_config_parse_block (const void *parent,
const char *file,
size_t len,
size_t *pos,
size_t *lineno,
const char *type)
{
char *block = NULL;
size_t p, pp, sh_start, sh_len, sh_end, ws;
int lines;
nih_assert (file != NULL);
nih_assert (type != NULL);
/* We need to find the end of the block which is a line that looks
* like:
*
* WS? end WS @type CNLWS?
*
* Just to make things more difficult for ourselves, we work out the
* common whitespace on the start of the block lines and remember
* not to copy those out later
*/
p = (pos ? *pos : 0);
sh_start = p;
sh_end = 0;
ws = 0;
lines = 0;
while (! nih_config_block_end (file, len, &p, lineno, type, &sh_end)) {
size_t line_start;
lines++;
line_start = p;
if (lines == 1) {
/* Count whitespace on the first line */
while ((p < len) && strchr (NIH_CONFIG_WS, file[p]))
p++;
ws = p - line_start;
} else {
/* Compare how much whitespace matches the
* first line; and decrease the count if it's
* not as much.
*/
while ((p < len) && (p - line_start < ws)
&& (file[sh_start + p - line_start] == file[p]))
p++;
if (p - line_start < ws)
ws = p - line_start;
}
nih_config_next_line (file, len, &p, lineno);
if (p >= len) {
nih_error_raise (NIH_CONFIG_UNTERMINATED_BLOCK,
_(NIH_CONFIG_UNTERMINATED_BLOCK_STR));
goto finish;
}
}
/* Copy the fragment into a string, removing common whitespace from
* the start. We can be less strict here because we already know
* the contents, etc.
*/
sh_len = sh_end - sh_start - (ws * lines);
block = nih_alloc (parent, sh_len + 1);
if (! block)
nih_return_system_error (NULL);
block[0] = '\0';
pp = sh_start;
while (pp < sh_end) {
size_t line_start;
pp += ws;
line_start = pp;
while (file[pp++] != '\n')
;
strncat (block, file + line_start, pp - line_start);
}
finish:
if (pos)
*pos = p;
return block;
}
/**
* nih_config_skip_block:
* @file: file or string to parse,
* @len: length of @file,
* @pos: offset within @file,
* @lineno: line number,
* @type: block identifier,
* @endpos: pointer to end of block.
*
* Skips over a block of text from @file, stopping when the phrase
* "end @type" is encountered without any quotes or blackslash escaping
* within it.
*
* @file may be a memory mapped file, in which case @pos should be given
* as the offset within and @len should be the length of the file as a
* whole.
*
* If @pos is given then it will be used as the offset within @file to
* begin (otherwise the start is assumed), and will be updated to point
* past the end of the block and block marker or the end of the file.
*
* Either @file or @pos should point to the start of the block, after the
* opening stanza, rather than the start of the stanza that opens it.
*
* If @lineno is given it will be incremented each time a new line is
* discovered in the file.
*
* @endpos will be set to the end of the block and the start of the block
* marker, this is useful for determining the length of the block skipped,
* to parse it for example.
*
* Returns: zero on success, negative value on raised error.
**/
int
nih_config_skip_block (const char *file,
size_t len,
size_t *pos,
size_t *lineno,
const char *type,
size_t *endpos)
{
size_t p;
int ret = 0;
nih_assert (file != NULL);
nih_assert (type != NULL);
p = (pos ? *pos : 0);
while (! nih_config_block_end (file, len, &p, lineno, type, endpos)) {
nih_config_next_line (file, len, &p, lineno);
if (p >= len) {
nih_error_raise (NIH_CONFIG_UNTERMINATED_BLOCK,
_(NIH_CONFIG_UNTERMINATED_BLOCK_STR));
ret = -1;
goto finish;
}
}
finish:
if (pos)
*pos = p;
return ret;
}
/**
* nih_config_block_end:
* @file: file or string to parse,
* @len: length of @file,
* @pos: offset within @file,
* @lineno: line number,
* @type: block identifier,
* @endpos: pointer to end of block.
*
* Determines whether the current line contains an end of block marker,
* and if so, sets @endpos to the end of the block.
*
* @file may be a memory mapped file, in which case @pos should be given
* as the offset within and @len should be the length of the file. @pos
* will be updated to point past the end of the block and the end block
* marker or the end of the file.
*
* @lineno will be incremented each time a new line is discovered.
*
* Returns: TRUE if at the end of the block, FALSE otherwise.
**/
static int
nih_config_block_end (const char *file,
size_t len,
size_t *pos,
size_t *lineno,
const char *type,
size_t *endpos)
{
size_t p;
nih_assert (file != NULL);
nih_assert (pos != NULL);
nih_assert (type != NULL);
p = *pos;
/* Skip initial whitespace */
while ((p < len) && strchr (NIH_CONFIG_WS, file[p]))
p++;
/* Check the first word (check we have at least 4 chars because of
* the need for whitespace immediately after)
*/
if ((len - p < 4) || strncmp (file + p, "end", 3))
return FALSE;
/* Must be whitespace after */
if (! strchr (NIH_CONFIG_WS, file[p + 3]))
return FALSE;
/* Find the second word */
p += 3;
while ((p < len) && strchr (NIH_CONFIG_WS, file[p]))
p++;
/* Check the second word */
if ((len - p < strlen (type))
|| strncmp (file + p, type, strlen (type)))
return FALSE;
/* May be followed by whitespace */
p += strlen (type);
while ((p < len) && strchr (NIH_CONFIG_WS, file[p]))
p++;
/* May be a comment, in which case eat up to the newline
*/
if ((p < len) && (file[p] == '#')) {
while ((p < len) && (file[p] != '\n'))
p++;
}
/* Should be end of string, or a newline */
if ((p < len) && (file[p] != '\n'))
return FALSE;
/* Point past the new line */
if (p < len) {
if (lineno)
(*lineno)++;
p++;
}
/* Set endpos to the beginning of the line (which is the end of the
* script) but update pos to point past this line.
*/
if (endpos)
*endpos = *pos;
*pos = p;
return TRUE;
}
/**
* nih_config_get_stanza:
* @name: name of stanza,
* @stanzas: table of stanza handlers.
*
* Locates the handler for the @name stanza in the @stanzas table. The
* last entry in the table should have NULL for both the name and handler
* function pointers.
*
* If any entry exists with the stanza name "", this is returned instead
* of NULL if no specific entry is found.
*
* Returns: stanza found or NULL if no handler for @name.
**/
static NihConfigStanza *
nih_config_get_stanza (const char *name,
NihConfigStanza *stanzas)
{
NihConfigStanza *stanza, *catch = NULL;
for (stanza = stanzas; (stanza->name && stanza->handler); stanza++) {
if (! strlen (stanza->name))
catch = stanza;
if (! strcmp (stanza->name, name))
return stanza;
}
return catch;
}
/**
* nih_config_parse_stanza:
* @file: file or string to parse,
* @len: length of @file,
* @pos: offset within @file,
* @lineno: line number,
* @stanzas: table of stanza handlers,
* @data: pointer to pass to stanza handler.
*
* Extracts a configuration stanza from @file and calls the handler
* function for that stanza found in the @stanzas table to handle the
* rest of the line from thereon in.
*
* @file may be a memory mapped file, in which case @pos should be given
* as the offset within and @len should be the length of the file as a
* whole.
*
* If @pos is given then it will be used as the offset within @file to
* begin (otherwise the start is assumed), and will be updated to point
* to @delim or past the end of the file.
*
* If @lineno is given it will be incremented each time a new line is
* discovered in the file.
*
* Returns: zero on success or negative value on raised error.
**/
int
nih_config_parse_stanza (const char *file,
size_t len,
size_t *pos,
size_t *lineno,
NihConfigStanza *stanzas,
void *data)
{
NihConfigStanza *stanza;
nih_local char *name = NULL;
size_t p;
int ret = -1;
nih_assert (file != NULL);
nih_assert (stanzas != NULL);
p = (pos ? *pos : 0);
/* Get the next dequoted argument from the file */
name = nih_config_next_token (NULL, file, len, &p, lineno,
NIH_CONFIG_CNLWS, FALSE);
if (! name)
goto finish;
/* Lookup the stanza for it */
stanza = nih_config_get_stanza (name, stanzas);
if (! stanza)
nih_return_error (-1, NIH_CONFIG_UNKNOWN_STANZA,
_(NIH_CONFIG_UNKNOWN_STANZA_STR));
ret = stanza->handler (data, stanza, file, len, &p, lineno);
finish:
if (pos)
*pos = p;
return ret;
}
/**
* nih_config_parse_file:
* @file: file or string to parse,
* @len: length of @file,
* @pos: offset within @file,
* @lineno: line number,
* @stanzas: table of stanza handlers,
* @data: pointer to pass to stanza handler.
*
* Parses configuration file lines from @file, skipping initial whitespace,
* blank lines and comments while calling nih_config_parse_stanza() for
* anything else.
*
* @file may be a memory mapped file, in which case @pos should be given
* as the offset within and @len should be the length of the file as a
* whole.
*
* If @pos is given then it will be used as the offset within @file to
* begin (otherwise the start is assumed), and will be updated to point
* to @delim or past the end of the file.
*
* If @lineno is given it will be incremented each time a new line is
* discovered in the file.
*
* Returns: zero on success, negative value on raised error.
**/
int
nih_config_parse_file (const char *file,
size_t len,
size_t *pos,
size_t *lineno,
NihConfigStanza *stanzas,
void *data)
{
int ret = -1;
size_t p;
nih_assert (file != NULL);
nih_assert (stanzas != NULL);
p = (pos ? *pos : 0);
while (p < len) {
/* Skip initial whitespace */
while ((p < len) && strchr (NIH_CONFIG_WS, file[p]))
p++;
/* Skip lines with only comments in them; because has_token
* returns FALSE we know we're either past the end of the
* file, at a comment, or a newline.
*/
if (! nih_config_has_token (file, len, &p, lineno)) {
if (nih_config_skip_comment (file, len,
&p, lineno) < 0)
nih_assert_not_reached ();
continue;
}
/* Must have a stanza, parse it */
if (nih_config_parse_stanza (file, len, &p, lineno,
stanzas, data) < 0)
goto finish;
}
ret = 0;
finish:
if (pos)
*pos = p;
return ret;
}
/**
* nih_config_parse:
* @filename: name of file to parse,
* @pos: offset within @file,
* @lineno: line number,
* @stanzas: table of stanza handlers,
* @data: pointer to pass to stanza handler.
*
* Reads @filename into memory and them parses configuration lines from it
* using nih_config_parse_file().
*
* If @pos is given then it will be used as the offset within @file to
* begin (otherwise the start is assumed), and will be updated to point
* to @delim or past the end of the file.
*
* If @lineno is given it will be incremented each time a new line is
* discovered in the file.
*
* Returns: zero on success, negative value on raised error.
**/
int
nih_config_parse (const char *filename,
size_t *pos,
size_t *lineno,
NihConfigStanza *stanzas,
void *data)
{
nih_local char *file = NULL;
size_t len;
int ret;
nih_assert (filename != NULL);
file = nih_file_read (NULL, filename, &len);
if (! file)
return -1;
if (lineno)
*lineno = 1;
ret = nih_config_parse_file (file, len, pos, lineno, stanzas, data);
return ret;
}