/*
* Copyright (c) Likewise Software. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* 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, see
* .
*
* LIKEWISE SOFTWARE MAKES THIS SOFTWARE AVAILABLE UNDER OTHER LICENSING
* TERMS AS WELL. IF YOU HAVE ENTERED INTO A SEPARATE LICENSE AGREEMENT
* WITH LIKEWISE SOFTWARE, THEN YOU MAY ELECT TO USE THE SOFTWARE UNDER THE
* TERMS OF THAT SOFTWARE LICENSE AGREEMENT INSTEAD OF THE TERMS OF THE GNU
* GENERAL PUBLIC LICENSE, NOTWITHSTANDING THE ABOVE NOTICE. IF YOU
* HAVE QUESTIONS, OR WISH TO REQUEST A COPY OF THE ALTERNATE LICENSING
* TERMS OFFERED BY LIKEWISE SOFTWARE, PLEASE CONTACT LIKEWISE SOFTWARE AT
* license@likewise.com
*/
/*
* Copyright (C) Likewise Software. All rights reserved.
*
* Module Name:
*
* parser.c
*
* Abstract:
*
* Config (INF Style) parser
*
* Authors: Kyle Stemen
* Scott Salley
*
*/
#include "includes.h"
static
DWORD
UnquoteString(
PSTR pszInput
)
{
DWORD dwError = 0;
int nRead = 0;
PSTR pszOutput = pszInput;
while (pszInput && *pszInput != 0)
{
if (*pszInput == '\\')
{
switch(pszInput[1])
{
case 'n':
*pszOutput = '\n';
pszInput += 2;
break;
case 'r':
*pszOutput = '\r';
pszInput += 2;
break;
case '\\':
*pszOutput = '\\';
pszInput += 2;
break;
case '\"':
*pszOutput = '\"';
pszInput += 2;
break;
case 'x':
nRead = sscanf(pszInput + 2, "%2hhX", pszOutput);
pszInput += 4;
break;
default:
dwError = LW_STATUS_UNMAPPABLE_CHARACTER;
BAIL_ON_UP_ERROR(dwError);
break;
}
}
else
{
*pszOutput = *pszInput;
pszInput++;
}
pszOutput++;
}
if (pszOutput)
{
*pszOutput = 0;
}
cleanup:
return dwError;
error:
goto cleanup;
}
static
DWORD
ReadLine(
PSTR* ppszLine,
FILE *pStream
)
{
DWORD dwError = 0;
ssize_t sSize = 0, sCapacity = 0;
PSTR pszBuffer = NULL;
// Do not free
PSTR pszNewBuffer = NULL;
do
{
// There is not enough space. Allocate a larger buffer
sCapacity = sSize*2 + 10;
pszNewBuffer = LwRtlMemoryRealloc(
pszBuffer,
sCapacity + 1);
if (pszNewBuffer == NULL)
{
dwError = ENOMEM;
BAIL_ON_UP_ERROR(dwError);
}
pszBuffer = pszNewBuffer;
// Read as much as the stream will give us up to the space in the
// buffer.
errno = 0;
if (fgets(pszBuffer + sSize, sCapacity - sSize, pStream) == NULL)
{
dwError = errno;
if (dwError == 0)
{
dwError = LW_STATUS_END_OF_FILE;
}
BAIL_ON_UP_ERROR(dwError);
}
sSize += strlen(pszBuffer + sSize);
}
// While the whole buffer is used and it does not end in a newline
while(sSize == sCapacity - 1 && pszBuffer[sSize-1] != '\n');
if (sSize == 0)
{
dwError = LW_STATUS_END_OF_FILE;
BAIL_ON_UP_ERROR(dwError);
}
*ppszLine = pszBuffer;
cleanup:
return dwError;
error:
LW_RTL_FREE(&pszBuffer);
goto cleanup;
}
static
DWORD
RegComp(
regex_t* pRegex,
PBOOLEAN pbCompiled,
PCSTR pszExpr
)
{
DWORD dwError = regcomp(pRegex, pszExpr, REG_EXTENDED);
if (dwError)
{
dwError = EINVAL;
BAIL_ON_UP_ERROR(EINVAL);
}
*pbCompiled = TRUE;
cleanup:
return dwError;
error:
goto cleanup;
}
DWORD
UpParseConfigFile(
PCSTR pszFilePath,
UpParseConfigSectionHandler pfnSectionHandler,
UpParseConfigNameValueHandler pfnNameValuePairHandler,
PVOID pData
)
{
DWORD dwError = 0;
FILE* fp = NULL;
PSTR pszLine = NULL;
// Do not free.
PSTR pszLinePos = NULL;
regex_t commentExp, sectionExp, nameValueExp, quotedNameValueExp;
BOOLEAN bCommentExpCompiled = FALSE;
BOOLEAN bSectionExpCompiled = FALSE;
BOOLEAN bNameValueExpCompiled = FALSE;
BOOLEAN bQuotedNameValueExpCompiled = FALSE;
BOOLEAN bContinue = TRUE;
PSTR pszCurrentSection = NULL;
regmatch_t matches[5];
DWORD dwLineNumber = 0;
// A comment is started with a hash sign and goes to the end of the line.
// White space may occur before the hash.
// A comment may also be a blank line.
dwError = RegComp(
&commentExp,
&bCommentExpCompiled,
"^([[:space:]]*(#.*)?[[:space:]]*)$");
BAIL_ON_UP_ERROR(dwError);
// A section name is surrounded by []. It may have leading and trailing
// white space, and another statement may appear afterwards.
dwError = RegComp(
§ionExp,
&bSectionExpCompiled,
"^[[:space:]]*\\[([^]]+)\\][[:space:]]*");
BAIL_ON_UP_ERROR(dwError);
// A quoted name value pair follows the form 'name = "value"'. Escape
// sequences are allowed inside of the quotes. Another statement may appear
// afterwards.
dwError = RegComp(
"edNameValueExp,
&bQuotedNameValueExpCompiled,
"^[[:space:]]*([^[:space:]=]+)[[:space:]]*=[[:space:]]*\"((\\.|[^\"\\])*)\"[[:space:]]*");
BAIL_ON_UP_ERROR(dwError);
// An unquoted statement has the form 'name = value'. The value runs to the
// end of the line, except that trailing spaces are removed. The value may
// not contain a # character. If a # appears on the line, it is later
// translated to a comment.
dwError = RegComp(
&nameValueExp,
&bNameValueExpCompiled,
"^[[:space:]]*([^[:space:]=]+)[[:space:]]*=[[:space:]]*(([^#]*[^[:space:]#])?)[[:space:]]*");
BAIL_ON_UP_ERROR(dwError);
if ((fp = fopen(pszFilePath, "r")) == NULL) {
dwError = errno;
BAIL_ON_UP_ERROR(dwError);
}
dwLineNumber = 0;
while (bContinue)
{
dwLineNumber++;
RtlCStringFree(&pszLine);
//fprintf(stderr, "Reading line %lu\n", (unsigned long) dwLineNumber);
dwError = ReadLine(&pszLine, fp);
if (dwError == LW_STATUS_END_OF_FILE)
{
dwError = 0;
break;
}
BAIL_ON_UP_ERROR(dwError);
pszLinePos = pszLine;
while (bContinue && *pszLinePos != 0)
{
if (!regexec(&commentExp,
pszLinePos,
sizeof(matches)/sizeof(matches[0]),
matches,
0))
{
// Null terminate the contents of the comment
pszLinePos[matches[1].rm_eo] = 0;
}
else if (!regexec(§ionExp,
pszLinePos,
sizeof(matches)/sizeof(matches[0]),
matches,
0))
{
// Null terminate the name of the section
pszLinePos[matches[1].rm_eo] = 0;
if (pszCurrentSection)
{
if ( pfnSectionHandler )
{
dwError = pfnSectionHandler(
0,
pszCurrentSection,
pData,
&bContinue);
BAIL_ON_UP_ERROR(dwError);
RtlCStringFree(&pszCurrentSection);
if (!bContinue)
{
break;
}
}
}
dwError = RtlCStringDuplicate(
&pszCurrentSection,
pszLinePos + matches[1].rm_so);
BAIL_ON_UP_ERROR(dwError);
if ( pfnSectionHandler )
{
dwError = pfnSectionHandler(
1,
pszCurrentSection,
pData,
&bContinue);
BAIL_ON_UP_ERROR(dwError);
}
}
else if (!regexec("edNameValueExp,
pszLinePos,
sizeof(matches)/sizeof(matches[0]),
matches,
0))
{
// Null terminate the name of the pair
pszLinePos[matches[1].rm_eo] = 0;
// Null terminate the value of the pair
pszLinePos[matches[2].rm_eo] = 0;
dwError = UnquoteString(
pszLinePos + matches[2].rm_so);
BAIL_ON_UP_ERROR(dwError);
if (pfnNameValuePairHandler)
{
dwError = pfnNameValuePairHandler(
pszCurrentSection,
pszLinePos + matches[1].rm_so,
pszLinePos + matches[2].rm_so,
pData,
&bContinue);
BAIL_ON_UP_ERROR(dwError);
}
}
else if (!regexec(&nameValueExp,
pszLinePos,
sizeof(matches)/sizeof(matches[0]),
matches,
0))
{
// Null terminate the name of the pair
pszLinePos[matches[1].rm_eo] = 0;
// Null terminate the value of the pair
pszLinePos[matches[2].rm_eo] = 0;
if (pfnNameValuePairHandler)
{
dwError = pfnNameValuePairHandler(
pszCurrentSection,
pszLinePos + matches[1].rm_so,
pszLinePos + matches[2].rm_so,
pData,
&bContinue);
BAIL_ON_UP_ERROR(dwError);
}
}
else
{
//RSYS_LOG_ERROR ("Unable to parse line [%s]", pszLinePos);
dwError = LW_ERROR_INVALID_CONFIG;
BAIL_ON_UP_ERROR(dwError);
}
// Go to the first character after the match
pszLinePos += matches[0].rm_eo;
}
}
cleanup:
if (fp) {
fclose(fp);
}
RtlCStringFree(&pszLine);
RtlCStringFree(&pszCurrentSection);
if (bCommentExpCompiled)
{
regfree(&commentExp);
}
if (bSectionExpCompiled)
{
regfree(§ionExp);
}
if (bNameValueExpCompiled)
{
regfree(&nameValueExp);
}
if (bQuotedNameValueExpCompiled)
{
regfree("edNameValueExp);
}
return dwError;
error:
goto cleanup;
}
// Parses configuration files as found in Likewise Open 4.1
DWORD
UpParseSambaConfigFile(
PCSTR pszFilePath,
UpParseConfigSectionHandler pfnSectionHandler,
UpParseConfigNameValueHandler pfnNameValuePairHandler,
PVOID pData
)
{
DWORD dwError = 0;
FILE* fp = NULL;
PSTR pszLine = NULL;
// Do not free.
PSTR pszLinePos = NULL;
regex_t commentExp, sectionExp, nameValueExp;
BOOLEAN bCommentExpCompiled = FALSE;
BOOLEAN bSectionExpCompiled = FALSE;
BOOLEAN bNameValueExpCompiled = FALSE;
BOOLEAN bContinue = TRUE;
PSTR pszCurrentSection = NULL;
regmatch_t matches[5];
DWORD dwLineNumber = 0;
// A comment is started with a hash sign or semicolon
// and goes to the end of the line.
// White space may occur before the hash/semicolon.
// A comment may also be a blank line.
dwError = RegComp(
&commentExp,
&bCommentExpCompiled,
"^([[:space:]]*([#;].*)?[[:space:]]*)$");
BAIL_ON_UP_ERROR(dwError);
// A section name is surrounded by []. It may have leading and trailing
// white space, and another statement may appear afterwards.
dwError = RegComp(
§ionExp,
&bSectionExpCompiled,
"^[[:space:]]*\\[([^]]+)\\][[:space:]]*");
BAIL_ON_UP_ERROR(dwError);
// An unquoted statement has the form 'name = value'. The value runs to the
// end of the line, except that trailing spaces are removed. The value may
// not contain a # character. If a # appears on the line, it is later
// translated to a comment.
dwError = RegComp(
&nameValueExp,
&bNameValueExpCompiled,
"^[[:space:]]*([^=#;]*[^=#; ]+)[[:space:]]*=[[:space:]]*(([^#;]*[^[:space:]#])?)[[:space:]]*");
BAIL_ON_UP_ERROR(dwError);
// Start of line ^
// Spaces Before Name [[:space:]]*
// Name ([^[:space:]=]+) => ([^=#;]+)
// Spaces Just After Name [[:space:]]*
// Equals =
// Spaces Before Value [[:space:]]*
// Value (([^#;]*[^[:space:]#;])?)[[:space:]]*
if ((fp = fopen(pszFilePath, "r")) == NULL) {
dwError = errno;
BAIL_ON_UP_ERROR(dwError);
}
dwLineNumber = 0;
while (bContinue)
{
dwLineNumber++;
RtlCStringFree(&pszLine);
//fprintf(stderr, "Reading line %lu\n", (unsigned long) dwLineNumber);
dwError = ReadLine(&pszLine, fp);
if (dwError == LW_STATUS_END_OF_FILE)
{
dwError = 0;
break;
}
BAIL_ON_UP_ERROR(dwError);
pszLinePos = pszLine;
while (bContinue && *pszLinePos != 0)
{
if (!regexec(&commentExp,
pszLinePos,
sizeof(matches)/sizeof(matches[0]),
matches,
0))
{
// Null terminate the contents of the comment
pszLinePos[matches[1].rm_eo] = 0;
}
else if (!regexec(§ionExp,
pszLinePos,
sizeof(matches)/sizeof(matches[0]),
matches,
0))
{
// Null terminate the name of the section
pszLinePos[matches[1].rm_eo] = 0;
if (pszCurrentSection)
{
if ( pfnSectionHandler )
{
dwError = pfnSectionHandler(
0,
pszCurrentSection,
pData,
&bContinue);
BAIL_ON_UP_ERROR(dwError);
RtlCStringFree(&pszCurrentSection);
if (!bContinue)
{
break;
}
}
}
dwError = RtlCStringDuplicate(
&pszCurrentSection,
pszLinePos + matches[1].rm_so);
BAIL_ON_UP_ERROR(dwError);
if ( pfnSectionHandler )
{
dwError = pfnSectionHandler(
1,
pszCurrentSection,
pData,
&bContinue);
BAIL_ON_UP_ERROR(dwError);
}
}
else if (!regexec(&nameValueExp,
pszLinePos,
sizeof(matches)/sizeof(matches[0]),
matches,
0))
{
// Null terminate the name of the pair
pszLinePos[matches[1].rm_eo] = 0;
// Null terminate the value of the pair
pszLinePos[matches[2].rm_eo] = 0;
if (pfnNameValuePairHandler)
{
dwError = pfnNameValuePairHandler(
pszCurrentSection,
pszLinePos + matches[1].rm_so,
pszLinePos + matches[2].rm_so,
pData,
&bContinue);
BAIL_ON_UP_ERROR(dwError);
}
}
else
{
//RSYS_LOG_ERROR ("Unable to parse line [%s]", pszLinePos);
dwError = LW_ERROR_INVALID_CONFIG;
BAIL_ON_UP_ERROR(dwError);
}
// Go to the first character after the match
pszLinePos += matches[0].rm_eo;
}
}
cleanup:
if (fp) {
fclose(fp);
}
RtlCStringFree(&pszLine);
RtlCStringFree(&pszCurrentSection);
if (bCommentExpCompiled)
{
regfree(&commentExp);
}
if (bSectionExpCompiled)
{
regfree(§ionExp);
}
if (bNameValueExpCompiled)
{
regfree(&nameValueExp);
}
return dwError;
error:
goto cleanup;
}