1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2025-07-15 01:04:37 +02:00

Config parsing implemented in C.

This commit is contained in:
David Steele
2017-12-22 23:27:49 -05:00
parent d8c575801d
commit 526acca5bd
29 changed files with 1646 additions and 360 deletions

View File

@ -138,6 +138,6 @@ Subsequent code that is uncoverable for the same reason is marked with `// {+unc
Marks code that is not tested for one reason or another. This should be kept to a minimum and an excuse given for each instance.
```
exit(EXIT_FAILURE); // {uncoverable - test harness does not support non-zero exit}
exit(EXIT_FAILURE); // {uncovered - test harness does not support non-zero exit}
```
Subsequent code that is uncovered for the same reason is marked with `// {+uncovered}`.

View File

@ -146,6 +146,7 @@ sub buildConfig
# Add "none" command that is used to initialize the current command before anything is parsed
push(@{$rhEnum->{&BLD_LIST}}, buildConfigCommandEnum('none'));
$iCommandTotal++;
$strBuildSource .=
")\n";

View File

@ -108,15 +108,6 @@ sub buildConfigParse
}
}
# Perl options passed to the C binary should be ignored (unless calling Perl which is done elsewhere)
$strBuildSource .=
" // Perl option is ignored by normal config parsing\n" .
" {\n" .
" .name = \"perl-option\",\n" .
" .has_arg = required_argument,\n" .
" .val = 0,\n" .
" },\n";
# The option list needs to be terminated or getopt_long will just keep on reading
$strBuildSource .=
" // Terminate option list\n" .

View File

@ -182,6 +182,10 @@ use constant CFGOPT_LOG_PATH => 'log-path
use constant CFGOPT_SPOOL_PATH => 'spool-path';
push @EXPORT, qw(CFGOPT_SPOOL_PATH);
# Perl
use constant CFGOPT_PERL_OPTION => 'perl-option';
push @EXPORT, qw(CFGOPT_PERL_OPTION);
# Repository
use constant CFGOPT_REPO_PATH => 'repo-path';
push @EXPORT, qw(CFGOPT_REPO_PATH);
@ -1050,6 +1054,30 @@ my %hConfigDefine =
},
},
&CFGOPT_PERL_OPTION =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,
&CFGDEF_TYPE => CFGDEF_TYPE_LIST,
&CFGDEF_REQUIRED => false,
&CFGDEF_INTERNAL => true,
&CFGDEF_COMMAND =>
{
&CFGCMD_ARCHIVE_GET => {},
&CFGCMD_ARCHIVE_PUSH => {},
&CFGCMD_BACKUP => {},
&CFGCMD_CHECK => {},
&CFGCMD_EXPIRE => {},
&CFGCMD_INFO => {},
&CFGCMD_LOCAL => {},
&CFGCMD_REMOTE => {},
&CFGCMD_RESTORE => {},
&CFGCMD_STANZA_CREATE => {},
&CFGCMD_STANZA_UPGRADE => {},
&CFGCMD_START => {},
&CFGCMD_STOP => {},
},
},
&CFGOPT_PROTOCOL_TIMEOUT =>
{
&CFGDEF_SECTION => CFGDEF_SECTION_GLOBAL,

View File

@ -192,7 +192,7 @@ if (condition)
<p>Marks code that is not tested for one reason or another. This should be kept to a minimum and an excuse given for each instance.</p>
<code-block>
exit(EXIT_FAILURE); // {uncoverable - test harness does not support non-zero exit}
exit(EXIT_FAILURE); // {uncovered - test harness does not support non-zero exit}
</code-block>
<p>Subsequent code that is uncovered for the same reason is marked with `// {+uncovered}`.</p>

View File

@ -41,6 +41,10 @@
<p>Implement <cmd>version</cmd> command in C.</p>
</release-item>
<release-item>
<p>Config parsing implemented in C.</p>
</release-item>
<release-item>
<p>Add <code>Buffer</code>, <code>Ini</code>, <code>KeyValue</code>, <code>List</code>, <code>Storage</code>, <code>String</code>, <code>StringList</code>, <code>Variant</code>, and <code>VariantList</code> objects.</p>
</release-item>
@ -65,10 +69,6 @@
<p>Replace <code>cfgCommandTotal()</code>/<code>cfgOptionTotal()</code> functions with constants. The constants are applicable in more cases and allow the compiler to optimize certain loops more efficiently.</p>
</release-item>
<release-item>
<p>More config parsing in C in preparation for all config parsing in C.</p>
</release-item>
<release-item>
<p>Refactor code to make valgrind happy.</p>
</release-item>

View File

@ -82,6 +82,13 @@ my @stryCFile =
'common/error.c',
'common/errorType.c',
'common/memContext.c',
'common/type/buffer.c',
'common/type/keyValue.c',
'common/type/list.c',
'common/type/string.c',
'common/type/stringList.c',
'common/type/variant.c',
'common/type/variantList.c',
'config/config.c',
'config/define.c',
'postgres/pageChecksum.c',

View File

@ -159,6 +159,7 @@ sub libcAutoExportTag
'CFGOPT_NEUTRAL_UMASK',
'CFGOPT_ONLINE',
'CFGOPT_OUTPUT',
'CFGOPT_PERL_OPTION',
'CFGOPT_PROCESS',
'CFGOPT_PROCESS_MAX',
'CFGOPT_PROTOCOL_TIMEOUT',

View File

@ -73,6 +73,7 @@ Option constants
#define CFGOPT_NEUTRAL_UMASK cfgOptNeutralUmask
#define CFGOPT_ONLINE cfgOptOnline
#define CFGOPT_OUTPUT cfgOptOutput
#define CFGOPT_PERL_OPTION cfgOptPerlOption
#define CFGOPT_PROCESS cfgOptProcess
#define CFGOPT_PROCESS_MAX cfgOptProcessMax
#define CFGOPT_PROTOCOL_TIMEOUT cfgOptProtocolTimeout

View File

@ -32,10 +32,12 @@ ERROR_DEFINE(ERROR_CODE_MIN, AssertError, RuntimeError);
ERROR_DEFINE(ERROR_CODE_MIN + 04, FormatError, RuntimeError);
ERROR_DEFINE(ERROR_CODE_MIN + 05, CommandRequiredError, FormatError);
ERROR_DEFINE(ERROR_CODE_MIN + 06, OptionInvalidError, RuntimeError);
ERROR_DEFINE(ERROR_CODE_MIN + 07, OptionInvalidValueError, RuntimeError);
ERROR_DEFINE(ERROR_CODE_MIN + 12, OptionRequiredError, RuntimeError);
ERROR_DEFINE(ERROR_CODE_MIN + 16, FileOpenError, RuntimeError);
ERROR_DEFINE(ERROR_CODE_MIN + 17, FileReadError, RuntimeError);
ERROR_DEFINE(ERROR_CODE_MIN + 23, CommandInvalidError, FormatError);
ERROR_DEFINE(ERROR_CODE_MIN + 31, OptionInvalidError, FormatError);
ERROR_DEFINE(ERROR_CODE_MIN + 39, FileWriteError, RuntimeError);
ERROR_DEFINE(ERROR_CODE_MIN + 69, MemoryError, RuntimeError);
ERROR_DEFINE(ERROR_CODE_MIN + 70, CipherError, FormatError);

View File

@ -18,10 +18,12 @@ ERROR_DECLARE(AssertError);
ERROR_DECLARE(FormatError);
ERROR_DECLARE(CommandRequiredError);
ERROR_DECLARE(CommandInvalidError);
ERROR_DECLARE(OptionInvalidError);
ERROR_DECLARE(OptionInvalidValueError);
ERROR_DECLARE(OptionRequiredError);
ERROR_DECLARE(FileOpenError);
ERROR_DECLARE(FileReadError);
ERROR_DECLARE(CommandInvalidError);
ERROR_DECLARE(FileWriteError);
ERROR_DECLARE(MemoryError);
ERROR_DECLARE(CipherError);

View File

@ -898,6 +898,14 @@ ConfigOptionData configOptionData[CFG_OPTION_TOTAL] = CONFIG_OPTION_LIST
CONFIG_OPTION_DEFINE_ID(cfgDefOptOutput)
)
//------------------------------------------------------------------------------------------------------------------------------
CONFIG_OPTION
(
CONFIG_OPTION_NAME("perl-option")
CONFIG_OPTION_INDEX(0)
CONFIG_OPTION_DEFINE_ID(cfgDefOptPerlOption)
)
//------------------------------------------------------------------------------------------------------------------------------
CONFIG_OPTION
(

View File

@ -9,12 +9,12 @@ Automatically generated by Build.pm -- do not modify directly.
/***********************************************************************************************************************************
Command constants
***********************************************************************************************************************************/
#define CFG_COMMAND_TOTAL 15
#define CFG_COMMAND_TOTAL 16
/***********************************************************************************************************************************
Option constants
***********************************************************************************************************************************/
#define CFG_OPTION_TOTAL 138
#define CFG_OPTION_TOTAL 139
/***********************************************************************************************************************************
Command enum
@ -89,6 +89,7 @@ typedef enum
cfgOptNeutralUmask,
cfgOptOnline,
cfgOptOutput,
cfgOptPerlOption,
cfgOptProcess,
cfgOptProcessMax,
cfgOptProtocolTimeout,

View File

@ -4,6 +4,7 @@ Command and Option Configuration
#include <string.h>
#include "common/error.h"
#include "common/memContext.h"
#include "config/config.h"
/***********************************************************************************************************************************
@ -52,6 +53,11 @@ Include the automatically generated configuration data
***********************************************************************************************************************************/
#include "config.auto.c"
/***********************************************************************************************************************************
Store the config memory context
***********************************************************************************************************************************/
MemContext *configMemContext = NULL;
/***********************************************************************************************************************************
Store the current command
@ -59,6 +65,67 @@ This is generally set by the command parser but can also be set by during execut
***********************************************************************************************************************************/
ConfigCommand command = cfgCmdNone;
/***********************************************************************************************************************************
Store the location of the executable
***********************************************************************************************************************************/
String *exe = NULL;
/***********************************************************************************************************************************
Was help requested for the command?
***********************************************************************************************************************************/
bool help = false;
/***********************************************************************************************************************************
Store the list of parameters passed to the command
***********************************************************************************************************************************/
StringList *paramList = NULL;
/***********************************************************************************************************************************
Map options names and indexes to option definitions.
***********************************************************************************************************************************/
typedef struct ConfigOptionValue
{
bool valid:1;
bool negate:1;
unsigned int source:2;
Variant *value;
} ConfigOptionValue;
ConfigOptionValue configOptionValue[CFG_OPTION_TOTAL];
/***********************************************************************************************************************************
Initialize or reinitialize the configuration data
***********************************************************************************************************************************/
void
cfgInit()
{
// Reset configuration
command = cfgCmdNone;
exe = NULL;
help = false;
paramList = NULL;
memset(&configOptionValue, 0, sizeof(configOptionValue));
// Free the old context
if (configMemContext != NULL)
{
memContextFree(configMemContext);
configMemContext = NULL;
}
// Allocate configuration context as a child of the top context
MEM_CONTEXT_BEGIN(memContextTop())
{
MEM_CONTEXT_NEW_BEGIN("configuration")
{
configMemContext = MEM_CONTEXT_NEW();
}
MEM_CONTEXT_NEW_END();
}
MEM_CONTEXT_END();
}
/***********************************************************************************************************************************
Get the current command
***********************************************************************************************************************************/
@ -87,6 +154,21 @@ cfgCommandCheck(ConfigCommand commandId)
THROW(AssertError, "command id %d invalid - must be >= 0 and < %d", commandId, CFG_COMMAND_TOTAL);
}
/***********************************************************************************************************************************
Was help requested?
***********************************************************************************************************************************/
bool
cfgCommandHelp()
{
return help;
}
void
cfgCommandHelpSet(bool helpParam)
{
help = helpParam;
}
/***********************************************************************************************************************************
Get the define id for this command
@ -108,11 +190,11 @@ cfgCommandId(const char *commandName)
{
ConfigCommand commandId;
for (commandId = 0; commandId < CFG_COMMAND_TOTAL; commandId++)
for (commandId = 0; commandId < cfgCmdNone; commandId++)
if (strcmp(commandName, configCommandData[commandId].name) == 0)
break;
if (commandId == CFG_COMMAND_TOTAL)
if (commandId == cfgCmdNone)
THROW(AssertError, "invalid command '%s'", commandName);
return commandId;
@ -128,6 +210,54 @@ cfgCommandName(ConfigCommand commandId)
return configCommandData[commandId].name;
}
/***********************************************************************************************************************************
Command parameters, if any
***********************************************************************************************************************************/
const StringList *
cfgCommandParam()
{
if (paramList == NULL)
{
MEM_CONTEXT_BEGIN(configMemContext)
{
paramList = strLstNew();
}
MEM_CONTEXT_END();
}
return paramList;
}
void
cfgCommandParamSet(const StringList *param)
{
MEM_CONTEXT_BEGIN(configMemContext)
{
paramList = strLstDup(param);
}
MEM_CONTEXT_END();
}
/***********************************************************************************************************************************
Command parameters, if any
***********************************************************************************************************************************/
const String *
cfgExe()
{
return exe;
}
void
cfgExeSet(const String *exeParam)
{
MEM_CONTEXT_BEGIN(configMemContext)
{
exe = strDup(exeParam);
}
MEM_CONTEXT_END();
}
/***********************************************************************************************************************************
Ensure that option id is valid
***********************************************************************************************************************************/
@ -211,3 +341,224 @@ cfgOptionName(ConfigOption optionId)
cfgOptionCheck(optionId);
return configOptionData[optionId].name;
}
/***********************************************************************************************************************************
Was the option negated?
***********************************************************************************************************************************/
bool
cfgOptionNegate(ConfigOption optionId)
{
cfgOptionCheck(optionId);
return configOptionValue[optionId].negate;
}
void
cfgOptionNegateSet(ConfigOption optionId, bool negate)
{
cfgOptionCheck(optionId);
configOptionValue[optionId].negate = negate;
}
/***********************************************************************************************************************************
Get and set config options
***********************************************************************************************************************************/
const Variant *
cfgOption(ConfigSource optionId)
{
cfgOptionCheck(optionId);
return configOptionValue[optionId].value;
}
bool
cfgOptionBool(ConfigSource optionId)
{
cfgOptionCheck(optionId);
if (varType(configOptionValue[optionId].value) != varTypeBool)
THROW(AssertError, "option '%s' is not type 'bool'", cfgOptionName(optionId));
return varBool(configOptionValue[optionId].value);
}
double
cfgOptionDbl(ConfigSource optionId)
{
cfgOptionCheck(optionId);
if (varType(configOptionValue[optionId].value) != varTypeDouble)
THROW(AssertError, "option '%s' is not type 'double'", cfgOptionName(optionId));
return varDbl(configOptionValue[optionId].value);
}
int
cfgOptionInt(ConfigSource optionId)
{
cfgOptionCheck(optionId);
if (varType(configOptionValue[optionId].value) != varTypeInt)
THROW(AssertError, "option '%s' is not type 'int'", cfgOptionName(optionId));
return varInt(configOptionValue[optionId].value);
}
const KeyValue *
cfgOptionKv(ConfigSource optionId)
{
cfgOptionCheck(optionId);
if (varType(configOptionValue[optionId].value) != varTypeKeyValue)
THROW(AssertError, "option '%s' is not type 'KeyValue'", cfgOptionName(optionId));
return varKv(configOptionValue[optionId].value);
}
const VariantList *
cfgOptionLst(ConfigSource optionId)
{
cfgOptionCheck(optionId);
if (configOptionValue[optionId].value == NULL)
{
MEM_CONTEXT_BEGIN(configMemContext)
{
configOptionValue[optionId].value = varNewVarLst(varLstNew());
}
MEM_CONTEXT_END();
}
else if (varType(configOptionValue[optionId].value) != varTypeVariantList)
THROW(AssertError, "option '%s' is not type 'VariantList'", cfgOptionName(optionId));
return varVarLst(configOptionValue[optionId].value);
}
const String *
cfgOptionStr(ConfigSource optionId)
{
cfgOptionCheck(optionId);
const String *result = NULL;
if (configOptionValue[optionId].value != NULL)
{
if (varType(configOptionValue[optionId].value) != varTypeString)
THROW(AssertError, "option '%s' is not type 'String'", cfgOptionName(optionId));
result = varStr(configOptionValue[optionId].value);
}
return result;
}
void
cfgOptionSet(ConfigOption optionId, ConfigSource source, const Variant *value)
{
cfgOptionCheck(optionId);
MEM_CONTEXT_BEGIN(configMemContext)
{
// Set the source
configOptionValue[optionId].source = source;
// Store old value
Variant *valueOld = configOptionValue[optionId].value;
// Only set value if it is not null
if (value != NULL)
{
switch (cfgDefOptionType(cfgOptionDefIdFromId(optionId)))
{
case cfgDefOptTypeBoolean:
{
if (varType(value) == varTypeBool)
configOptionValue[optionId].value = varDup(value);
else
configOptionValue[optionId].value = varNewBool(varBoolForce(value));
break;
}
case cfgDefOptTypeFloat:
{
if (varType(value) == varTypeDouble)
configOptionValue[optionId].value = varDup(value);
else
configOptionValue[optionId].value = varNewDbl(varDblForce(value));
break;
}
case cfgDefOptTypeInteger:
{
if (varType(value) == varTypeInt)
configOptionValue[optionId].value = varDup(value);
else
configOptionValue[optionId].value = varNewInt(varIntForce(value));
break;
}
case cfgDefOptTypeHash:
{
if (varType(value) == varTypeKeyValue)
configOptionValue[optionId].value = varDup(value);
else
THROW(AssertError, "option '%s' must be set with KeyValue variant", cfgOptionName(optionId));
break;
}
case cfgDefOptTypeList:
{
if (varType(value) == varTypeVariantList)
configOptionValue[optionId].value = varDup(value);
else
THROW(AssertError, "option '%s' must be set with VariantList variant", cfgOptionName(optionId));
break;
}
case cfgDefOptTypeString:
if (varType(value) == varTypeString)
configOptionValue[optionId].value = varDup(value);
else
THROW(AssertError, "option '%s' must be set with String variant", cfgOptionName(optionId));
break;
}
}
// Free old value
if (valueOld != NULL)
varFree(valueOld);
}
MEM_CONTEXT_END();
}
/***********************************************************************************************************************************
How was the option set (default, param, config)?
***********************************************************************************************************************************/
ConfigSource
cfgOptionSource(ConfigSource optionId)
{
cfgOptionCheck(optionId);
return configOptionValue[optionId].source;
}
/***********************************************************************************************************************************
Is the option valid for this command?
***********************************************************************************************************************************/
bool
cfgOptionValid(ConfigOption optionId)
{
cfgOptionCheck(optionId);
return configOptionValue[optionId].valid;
}
void
cfgOptionValidSet(ConfigOption optionId, bool valid)
{
cfgOptionCheck(optionId);
configOptionValue[optionId].valid = valid;
}

View File

@ -9,20 +9,54 @@ Command and Option Configuration
#include "config/config.auto.h"
/***********************************************************************************************************************************
Option source enum - defines where an option value came from
***********************************************************************************************************************************/
typedef enum
{
cfgSourceDefault, // Default value
cfgSourceParam, // Passed as command-line parameter
cfgSourceConfig, // From configuration file
} ConfigSource;
/***********************************************************************************************************************************
Functions
***********************************************************************************************************************************/
void cfgInit();
ConfigCommand cfgCommand();
bool cfgCommandHelp();
int cfgCommandId(const char *commandName);
const char *cfgCommandName(ConfigCommand commandId);
ConfigDefineCommand cfgCommandDefIdFromId(ConfigCommand commandId);
const StringList *cfgCommandParam();
void cfgCommandHelpSet(bool helpParam);
void cfgCommandParamSet(const StringList *param);
void cfgCommandSet(ConfigCommand commandParam);
const String *cfgExe();
void cfgExeSet(const String *exeParam);
const Variant *cfgOption(ConfigSource optionId);
bool cfgOptionBool(ConfigSource optionId);
ConfigDefineOption cfgOptionDefIdFromId(ConfigOption optionId);
double cfgOptionDbl(ConfigSource optionId);
int cfgOptionId(const char *optionName);
ConfigOption cfgOptionIdFromDefId(ConfigDefineOption optionDefId, int index);
int cfgOptionIndex(ConfigOption optionId);
int cfgOptionIndexTotal(ConfigOption optionDefId);
int cfgOptionInt(ConfigSource optionId);
const KeyValue *cfgOptionKv(ConfigSource optionId);
const VariantList *cfgOptionLst(ConfigSource optionId);
const char *cfgOptionName(ConfigOption optionId);
ConfigDefineOption cfgOptionDefIdFromId(ConfigOption optionId);
bool cfgOptionNegate(ConfigOption optionId);
ConfigSource cfgOptionSource(ConfigSource optionId);
const String *cfgOptionStr(ConfigSource optionId);
bool cfgOptionValid(ConfigOption optionId);
void cfgOptionNegateSet(ConfigOption optionId, bool negate);
void cfgOptionSet(ConfigOption optionId, ConfigSource source, const Variant *value);
void cfgOptionValidSet(ConfigOption optionId, bool valid);
#endif

View File

@ -1495,6 +1495,36 @@ ConfigDefineOptionData configDefineOptionData[] = CFGDEFDATA_OPTION_LIST
)
)
// -----------------------------------------------------------------------------------------------------------------------------
CFGDEFDATA_OPTION
(
CFGDEFDATA_OPTION_NAME("perl-option")
CFGDEFDATA_OPTION_REQUIRED(false)
CFGDEFDATA_OPTION_SECTION(cfgDefSectionGlobal)
CFGDEFDATA_OPTION_TYPE(cfgDefOptTypeList)
CFGDEFDATA_OPTION_INDEX_TOTAL(1)
CFGDEFDATA_OPTION_NEGATE(true)
CFGDEFDATA_OPTION_SECURE(false)
CFGDEFDATA_OPTION_COMMAND_LIST
(
CFGDEFDATA_OPTION_COMMAND(cfgDefCmdArchiveGet)
CFGDEFDATA_OPTION_COMMAND(cfgDefCmdArchivePush)
CFGDEFDATA_OPTION_COMMAND(cfgDefCmdBackup)
CFGDEFDATA_OPTION_COMMAND(cfgDefCmdCheck)
CFGDEFDATA_OPTION_COMMAND(cfgDefCmdExpire)
CFGDEFDATA_OPTION_COMMAND(cfgDefCmdInfo)
CFGDEFDATA_OPTION_COMMAND(cfgDefCmdLocal)
CFGDEFDATA_OPTION_COMMAND(cfgDefCmdRemote)
CFGDEFDATA_OPTION_COMMAND(cfgDefCmdRestore)
CFGDEFDATA_OPTION_COMMAND(cfgDefCmdStanzaCreate)
CFGDEFDATA_OPTION_COMMAND(cfgDefCmdStanzaUpgrade)
CFGDEFDATA_OPTION_COMMAND(cfgDefCmdStart)
CFGDEFDATA_OPTION_COMMAND(cfgDefCmdStop)
)
)
// -----------------------------------------------------------------------------------------------------------------------------
CFGDEFDATA_OPTION
(

View File

@ -91,6 +91,7 @@ typedef enum
cfgDefOptNeutralUmask,
cfgDefOptOnline,
cfgDefOptOutput,
cfgDefOptPerlOption,
cfgDefOptProcess,
cfgDefOptProcessMax,
cfgDefOptProtocolTimeout,

View File

@ -17,6 +17,11 @@ typedef enum
cfgDefSectionStanza, // command-line of in any config stanza section
} ConfigDefSection;
/***********************************************************************************************************************************
Define global section name
***********************************************************************************************************************************/
#define CFGDEF_SECTION_GLOBAL "global"
/***********************************************************************************************************************************
Functions
***********************************************************************************************************************************/

View File

@ -957,6 +957,15 @@ static const struct option optionList[] =
.has_arg = required_argument,
.val = PARSE_OPTION_FLAG | cfgOptOutput,
},
{
.name = "perl-option",
.has_arg = required_argument,
.val = PARSE_OPTION_FLAG | cfgOptPerlOption,
},
{
.name = "no-perl-option",
.val = PARSE_OPTION_FLAG | PARSE_NEGATE_FLAG | cfgOptPerlOption,
},
{
.name = "process",
.has_arg = required_argument,
@ -1240,12 +1249,6 @@ static const struct option optionList[] =
.has_arg = required_argument,
.val = PARSE_OPTION_FLAG | cfgOptType,
},
// Perl option is ignored by normal config parsing
{
.name = "perl-option",
.has_arg = required_argument,
.val = 0,
},
// Terminate option list
{
.name = NULL

View File

@ -4,13 +4,16 @@ Command and Option Parse
#include <assert.h>
#include <getopt.h>
#include <string.h>
#include <strings.h>
#include "common/error.h"
#include "common/ini.h"
#include "common/memContext.h"
#include "config/parse.h"
#include "storage/storage.h"
/***********************************************************************************************************************************
Include the automatically generated configuration data
Parse option flags
***********************************************************************************************************************************/
// Offset the option values so they don't conflict with getopt_long return codes
#define PARSE_OPTION_FLAG (1 << 31)
@ -21,19 +24,44 @@ Include the automatically generated configuration data
// Mask to exclude all flags and get at the actual option id (only 12 bits allowed for option id, the rest reserved for flags)
#define PARSE_OPTION_MASK 0xFFF
/***********************************************************************************************************************************
Include automatically generated data structure for getopt_long()
***********************************************************************************************************************************/
#include "parse.auto.c"
/***********************************************************************************************************************************
Parse the command-line arguments and produce preliminary parse data
This function only checks for obvious errors. Dependencies, types, etc, are checked later when data from the config file is
available.
Struct to hold options parsed from the command line
***********************************************************************************************************************************/
ParseData *
configParseArg(int argListSize, const char *argList[])
typedef struct ParseOption
{
// Allocate memory for the parse data
ParseData *parseData = memNew(sizeof(ParseData));
bool found:1; // Was the option found on the command line?
bool negate:1; // Was the option negated on the command line?
unsigned int source:2; // Where was to option found?
StringList *valueList; // List of values found
} ParseOption;
/***********************************************************************************************************************************
Parse the command-line arguments and config file to produce final config data
***********************************************************************************************************************************/
void
configParse(int argListSize, const char *argList[])
{
// Initialize configuration
cfgInit();
MEM_CONTEXT_TEMP_BEGIN()
{
// Set the exe
cfgExeSet(strNew(argList[0]));
// Phase 1: parse command line parameters
// -------------------------------------------------------------------------------------------------------------------------
int option; // Code returned by getopt_long
int optionListIdx; // Index of option is list (if an option was returned)
ConfigOption optionId; // Option id extracted from option var
bool negate; // Option is being negated
bool argFound = false; // Track args found to decide on error or help at the end
StringList *commandParamList = NULL; // List of command parameters
// Reset optind to 1 in case getopt_long has been called before
optind = 1;
@ -41,57 +69,52 @@ configParseArg(int argListSize, const char *argList[])
// Don't error automatically on unknown options - they will be processed in the loop below
opterr = false;
// List of parsed options
ParseOption parseOptionList[CFG_OPTION_TOTAL];
memset(&parseOptionList, 0, sizeof(parseOptionList));
// Only the first non-option parameter should be treated as a command so track if the command has been set
bool commandSet = false;
// Parse options
int option; // Code returned by getopt_long
int optionListIdx; // Index of option is list (if an option was returned)
ConfigOption optionId; // Option id extracted from option var
bool negate; // Option is being negated
bool argFound = false; // Track args found to decide on error or help at the end
while ((option = getopt_long(argListSize, (char **)argList, "-:", optionList, &optionListIdx)) != -1)
{
switch (option)
{
// Add perl options (if any) to the list
case 0:
if (parseData->perlOptionList == NULL)
parseData->perlOptionList = strLstNew();
strLstAdd(parseData->perlOptionList, strNew(optarg));
break;
// Parse arguments that are not options, i.e. commands and parameters passed to commands
case 1:
{
// The first argument should be the command
if (!commandSet)
{
// Try getting the command from the valid command list
TRY_BEGIN()
{
parseData->command = cfgCommandId(argList[optind - 1]);
cfgCommandSet(cfgCommandId(argList[optind - 1]));
}
// Assert error means the command does not exist, which is correct for all usages but this one (since we don't
// have any control over what the user passes), so modify the error code and message.
// Assert error means the command does not exist, which is correct for all usages but this one (since we
// don't have any control over what the user passes), so modify the error code and message.
CATCH(AssertError)
{
THROW(CommandInvalidError, "invalid command '%s'", argList[optind - 1]);
}
TRY_END();
if (cfgCommand() == cfgCmdHelp)
cfgCommandHelpSet(true);
else
commandSet = true;
}
// Additioal arguments are command arguments
// Additional arguments are command arguments
else
{
if (parseData->commandArgList == NULL)
parseData->commandArgList = strLstNew();
if (commandParamList == NULL)
commandParamList = strLstNew();
strLstAdd(parseData->commandArgList, strNew(argList[optind - 1]));
strLstAdd(commandParamList, strNew(argList[optind - 1]));
}
commandSet = true;
break;
}
// If the option is unknown then error
case '?':
@ -113,27 +136,28 @@ configParseArg(int argListSize, const char *argList[])
assert(optionId < CFG_OPTION_TOTAL);
// If the the option has not been found yet then set it
if (!parseData->parseOptionList[optionId].found)
if (!parseOptionList[optionId].found)
{
parseData->parseOptionList[optionId].found = true;
parseData->parseOptionList[optionId].negate = negate;
parseOptionList[optionId].found = true;
parseOptionList[optionId].negate = negate;
parseOptionList[optionId].source = cfgSourceParam;
// Only set the argument if the option requires one
if (optionList[optionListIdx].has_arg == required_argument)
parseData->parseOptionList[optionId].valueList = strLstAdd(strLstNew(), strNew(optarg));
parseOptionList[optionId].valueList = strLstAdd(strLstNew(), strNew(optarg));
}
else
{
// Make sure option is not negated more than once. It probably wouldn't hurt anything to accept this case but
// there's no point in allowing the user to be sloppy.
if (parseData->parseOptionList[optionId].negate && negate)
// Make sure option is not negated more than once. It probably wouldn't hurt anything to accept this case
// but there's no point in allowing the user to be sloppy.
if (parseOptionList[optionId].negate && negate)
THROW(OptionInvalidError, "option '%s' is negated multiple times", cfgOptionName(optionId));
// Don't allow an option to be both set and negated
if (parseData->parseOptionList[optionId].negate != negate)
if (parseOptionList[optionId].negate != negate)
THROW(OptionInvalidError, "option '%s' cannot be set and negated", cfgOptionName(optionId));
// Error if this option does not allow multiple arguments (!!! IMPLEMENT THIS IN DEFINE.C)
// Error if this option does not allow multiple arguments
if (!(cfgDefOptionType(cfgOptionDefIdFromId(optionId)) == cfgDefOptTypeHash ||
cfgDefOptionType(cfgOptionDefIdFromId(optionId)) == cfgDefOptTypeList))
{
@ -141,7 +165,7 @@ configParseArg(int argListSize, const char *argList[])
}
// Add the argument
strLstAdd(parseData->parseOptionList[optionId].valueList, strNew(optarg));
strLstAdd(parseOptionList[optionId].valueList, strNew(optarg));
}
break;
@ -152,32 +176,430 @@ configParseArg(int argListSize, const char *argList[])
}
// Handle command not found
if (!commandSet)
if (!commandSet && !cfgCommandHelp())
{
// If there are args then error
if (argFound)
THROW(CommandRequiredError, "no command found");
// Otherwise set the comand to help
parseData->command = cfgCmdHelp;
cfgCommandHelpSet(true);
}
// Return the parse data
return parseData;
}
/***********************************************************************************************************************************
Parse the command-line arguments and config file to produce final config data
***********************************************************************************************************************************/
void
configParse(int argListSize, const char *argList[])
{
// Parse the command line
ParseData *parseData = configParseArg(argListSize, argList);
// Set the command
cfgCommandSet(parseData->command);
// Free the parse data
memFree(parseData);
// Set command params
if (commandParamList != NULL)
cfgCommandParamSet(commandParamList);
// Parse options from config file unless --no-config passed
if (cfgCommand() != cfgCmdNone &&
cfgCommand() != cfgCmdVersion &&
cfgCommand() != cfgCmdHelp)
{
// Get the command definition id
ConfigDefineCommand commandDefId = cfgCommandDefIdFromId(cfgCommand());
// Phase 2: parse config file
// ---------------------------------------------------------------------------------------------------------------------
if (!parseOptionList[cfgOptConfig].negate)
{
// Get the config file name from the command-line if it exists else default
const String *configFile = NULL;
if (parseOptionList[cfgOptConfig].found)
configFile = strLstGet(parseOptionList[cfgOptConfig].valueList, 0);
else
configFile = strNew(cfgDefOptionDefault(commandDefId, cfgOptionDefIdFromId(cfgOptConfig)));
// Load the ini file
Buffer *buffer = storageGet(storageLocal(), configFile, !parseOptionList[cfgOptConfig].found);
// Load the config file if it was found
if (buffer != NULL)
{
// Parse the ini file
Ini *config = iniNew();
iniParse(config, strNewBuf(buffer));
// Get the stanza name
String *stanza = NULL;
if (parseOptionList[cfgOptStanza].found)
stanza = strLstGet(parseOptionList[cfgOptStanza].valueList, 0);
// Build list of sections to search for options
StringList *sectionList = strLstNew();
if (stanza != NULL)
{
strLstAdd(sectionList, strNewFmt("%s:%s", strPtr(stanza), cfgCommandName(cfgCommand())));
strLstAdd(sectionList, stanza);
}
strLstAdd(sectionList, strNewFmt(CFGDEF_SECTION_GLOBAL ":%s", cfgCommandName(cfgCommand())));
strLstAdd(sectionList, strNew(CFGDEF_SECTION_GLOBAL));
// Loop through sections to search for options
for (unsigned int sectionIdx = 0; sectionIdx < strLstSize(sectionList); sectionIdx++)
{
String *section = strLstGet(sectionList, sectionIdx);
StringList *keyList = iniSectionKeyList(config, section);
// Loop through keys to search for options
for (unsigned int keyIdx = 0; keyIdx < strLstSize(keyList); keyIdx++)
{
String *key = strLstGet(keyList, keyIdx);
// Find the optionName in the main list
unsigned int optionIdx = 0;
while (optionList[optionIdx].name != NULL)
{
if (strcmp(strPtr(key), optionList[optionIdx].name) == 0)
break;
optionIdx++;
}
// Warn if the option not found
if (optionList[optionIdx].name == NULL)
{
/// ??? Put warning here once there is a logging system
continue;
}
// Warn if negate option found in config
else if (optionList[optionIdx].val & PARSE_NEGATE_FLAG)
{
/// ??? Put warning here once there is a logging system
continue;
}
optionId = optionList[optionIdx].val & PARSE_OPTION_MASK;
ConfigDefineOption optionDefId = cfgOptionDefIdFromId(optionId);
/// Warn if this option should be command-line only
if (cfgDefOptionSection(optionDefId) == cfgDefSectionCommandLine)
{
/// ??? Put warning here once there is a logging system
continue;
}
// Continue if this option has already been found
if (parseOptionList[optionId].found)
continue;
// Continue if the option is not valid for this command
if (!cfgDefOptionValid(commandDefId, optionDefId))
{
// Warn if it is in a command section
if (sectionIdx % 2 == 0)
{
// ??? Put warning here once there is a logging system (and remove continue and braces)
continue;
}
continue;
}
// Only get the option if it is valid for this section
if ((stanza != NULL && sectionIdx < 2) || cfgDefOptionSection(optionDefId) == cfgDefSectionGlobal)
{
const Variant *value = iniGetDefault(config, section, key, NULL);
if (varType(value) == varTypeString && strSize(varStr(value)) == 0)
THROW(OptionInvalidValueError, "section '%s', key '%s' must have a value", strPtr(section),
strPtr(key));
parseOptionList[optionId].found = true;
parseOptionList[optionId].source = cfgSourceConfig;
// Convert boolean to string
if (cfgDefOptionType(optionDefId) == cfgDefOptTypeBoolean)
{
if (strcasecmp(strPtr(varStr(value)), "n") == 0)
parseOptionList[optionId].negate = true;
else if (strcasecmp(strPtr(varStr(value)), "y") != 0)
THROW(OptionInvalidError, "boolean option '%s' must be 'y' or 'n'", strPtr(key));
}
// Else add the string value
else if (varType(value) == varTypeString)
{
parseOptionList[optionId].valueList = strLstNew();
strLstAdd(parseOptionList[optionId].valueList, varStr(value));
}
// Else add the string list
else
parseOptionList[optionId].valueList = strLstNewVarLst(varVarLst(value));
}
}
}
}
}
// Phase 3: validate option definitions and load into configuration
// ---------------------------------------------------------------------------------------------------------------------
bool allResolved;
bool optionResolved[CFG_OPTION_TOTAL] = {false};
do
{
// Assume that all dependencies will be resolved in this loop. This probably won't be true the first few times, but
// eventually it will be or the do loop would never exit.
allResolved = true;
// Loop through all options
for (ConfigOption optionId = 0; optionId < CFG_OPTION_TOTAL; optionId++)
{
// Get the option data parsed from the command-line
ParseOption *parseOption = &parseOptionList[optionId];
// Get the option definition id -- will be used to look up option rules
ConfigDefineOption optionDefId = cfgOptionDefIdFromId(optionId);
ConfigDefineOptionType optionDefType = cfgDefOptionType(optionDefId);
// Skip this option if it has already been resolved
if (optionResolved[optionId])
continue;
// Error if the option is not valid for this command
if (parseOption->found && !cfgDefOptionValid(commandDefId, optionDefId))
{
THROW(
OptionInvalidError, "option '%s' not valid for command '%s'", cfgOptionName(optionId),
cfgCommandName(cfgCommand()));
}
// Is the option valid for this command? If not, mark it as resolved since there is nothing more to do.
cfgOptionValidSet(optionId, cfgDefOptionValid(commandDefId, optionDefId));
if (!cfgOptionValid(optionId))
{
optionResolved[optionId] = true;
continue;
}
// Is the value set for this option?
bool optionSet = parseOption->found && (optionDefType == cfgDefOptTypeBoolean || !parseOption->negate);
// Set negate flag
cfgOptionNegateSet(optionId, parseOption->negate);
// Check option dependencies
bool dependResolved = true;
if (cfgDefOptionDepend(commandDefId, optionDefId))
{
ConfigOption dependOptionId =
cfgOptionIdFromDefId(cfgDefOptionDependOption(commandDefId, optionDefId), cfgOptionIndex(optionId));
ConfigDefineOption dependOptionDefId = cfgOptionDefIdFromId(dependOptionId);
ConfigDefineOptionType dependOptionDefType = cfgDefOptionType(dependOptionDefId);
// Make sure the depend option has been resolved, otherwise skip this option for now
if (!optionResolved[dependOptionId])
{
allResolved = false;
continue;
}
// Get the depend option value
const Variant *dependValue = cfgOption(dependOptionId);
if (dependValue != NULL)
{
if (dependOptionDefType == cfgDefOptTypeBoolean)
{
if (cfgOptionBool(dependOptionId))
dependValue = varNewStrZ("1");
else
dependValue = varNewStrZ("0");
}
}
// Can't resolve if the depend option value is null
if (dependValue == NULL)
{
dependResolved = false;
// if (optionSet)
if (optionSet && parseOption->source == cfgSourceParam)
{
THROW(
OptionInvalidError, "option '%s' not valid without option '%s'", cfgOptionName(optionId),
cfgOptionName(dependOptionId));
}
}
// If a depend list exists, make sure the value is in the list
else if (cfgDefOptionDependValueTotal(commandDefId, optionDefId) > 0)
{
dependResolved = cfgDefOptionDependValueValid(commandDefId, optionDefId, strPtr(varStr(dependValue)));
// If not depend not resolved and option value is set then error
if (!dependResolved && optionSet && parseOption->source == cfgSourceParam)
{
// Get the depend option name
String *dependOptionName = strNew(cfgOptionName(dependOptionId));
// Build the list of possible depend values
StringList *dependValueList = strLstNew();
for (int listIdx = 0; listIdx < cfgDefOptionDependValueTotal(commandDefId, optionDefId); listIdx++)
{
const char *dependValue = cfgDefOptionDependValue(commandDefId, optionDefId, listIdx);
// Build list based on depend option type
switch (dependOptionDefType)
{
// Boolean outputs depend option name as no-* when false
case cfgDefOptTypeBoolean:
{
if (strcmp(dependValue, "0") == 0)
dependOptionName = strNewFmt("no-%s", cfgOptionName(dependOptionId));
break;
}
// String is output with quotes
case cfgDefOptTypeString:
{
strLstAdd(dependValueList, strNewFmt("'%s'", dependValue));
break;
}
// Other types are output plain
default:
{
strLstAddZ(dependValueList, dependValue); // {uncovered - no depends of other types}
break; // {+uncovered}
}
}
}
// Build the error string
String *error = strNew("option '%s' not valid without option '%s'");
if (strLstSize(dependValueList) == 1)
strCat(error, " = %s");
else if (strLstSize(dependValueList) > 1)
strCat(error, " in (%s)");
// Throw the error
THROW(
OptionInvalidError, strPtr(error), cfgOptionName(optionId), strPtr(dependOptionName),
strPtr(strLstJoin(dependValueList, ", ")));
}
}
}
// Is the option defined?
if (optionSet && dependResolved)
{
if (optionDefType == cfgDefOptTypeBoolean)
{
cfgOptionSet(optionId, parseOption->source, varNewBool(!parseOption->negate));
}
else if (optionDefType == cfgDefOptTypeHash)
{
Variant *value = varNewKv();
KeyValue *keyValue = varKv(value);
for (unsigned int listIdx = 0; listIdx < strLstSize(parseOption->valueList); listIdx++)
{
const char *pair = strPtr(strLstGet(parseOption->valueList, listIdx));
const char *equal = strchr(pair, '=');
if (equal == NULL)
{
THROW(
OptionInvalidError, "key/value '%s' not valid for '%s' option",
strPtr(strLstGet(parseOption->valueList, listIdx)), cfgOptionName(optionId));
}
kvPut(keyValue, varNewStr(strNewSzN(pair, equal - pair)), varNewStr(strNew(equal + 1)));
}
cfgOptionSet(optionId, parseOption->source, value);
}
else if (optionDefType == cfgDefOptTypeList)
{
cfgOptionSet(optionId, parseOption->source, varNewVarLst(varLstNewStrLst(parseOption->valueList)));
}
else
{
String *value = strLstGet(parseOption->valueList, 0);
// If the option has an allow list then check it
if (cfgDefOptionAllowList(commandDefId, optionDefId) &&
!cfgDefOptionAllowListValueValid(commandDefId, optionDefId, strPtr(value)))
{
THROW(
OptionInvalidValueError, "'%s' is not valid for '%s' option", strPtr(value),
cfgOptionName(optionId));
}
// If a numeric type check that the value is valid
if (optionDefType == cfgDefOptTypeInteger || optionDefType == cfgDefOptTypeFloat)
{
double valueDbl = 0;
// Check that the value can be converted
TRY_BEGIN()
{
if (optionDefType == cfgDefOptTypeInteger)
valueDbl = varIntForce(varNewStr(value));
else
valueDbl = varDblForce(varNewStr(value));
}
CATCH_ANY()
{
THROW(
OptionInvalidValueError, "'%s' is not valid for '%s' option", strPtr(value),
cfgOptionName(optionId));
}
TRY_END();
// Check value range
if (cfgDefOptionAllowRange(commandDefId, optionDefId) &&
(valueDbl < cfgDefOptionAllowRangeMin(commandDefId, optionDefId) ||
valueDbl > cfgDefOptionAllowRangeMax(commandDefId, optionDefId)))
{
THROW(
OptionInvalidValueError, "'%s' is not valid for '%s' option", strPtr(value),
cfgOptionName(optionId));
}
}
cfgOptionSet(optionId, parseOption->source, varNewStr(value));
}
}
else if (dependResolved && parseOption->negate)
cfgOptionSet(optionId, parseOption->source, NULL);
// Else try to set a default
else if (dependResolved)
{
// Get the default value for this option
const char *value = cfgDefOptionDefault(commandDefId, optionDefId);
if (value != NULL)
cfgOptionSet(optionId, cfgSourceDefault, varNewStrZ(value));
else if (cfgOptionIndex(optionId) == 0 && cfgDefOptionRequired(commandDefId, optionDefId) &&
!cfgCommandHelp())
{
const char *hint = "";
if (cfgDefOptionSection(optionDefId) == cfgDefSectionStanza)
hint = "\nHINT: does this stanza exist?";
THROW(
OptionRequiredError, "%s command requires option: %s%s", cfgCommandName(cfgCommand()),
cfgOptionName(optionId), hint);
}
}
// Option is now resolved
optionResolved[optionId] = true;
}
}
while (!allResolved);
}
}
MEM_CONTEXT_TEMP_END();
}

View File

@ -6,29 +6,6 @@ Parse Configuration
#include "config/config.h"
/***********************************************************************************************************************************
Struct to hold options parsed from the command line (??? move back to parse.c once perl exec works on full config data)
***********************************************************************************************************************************/
typedef struct ParseOption
{
bool found:1; // Was the option found on the command line?
bool negate:1; // Was the option negated on the command line?
StringList *valueList; // List of values found
} ParseOption;
/***********************************************************************************************************************************
Struct to hold all parsed data (??? move back to parse.c once perl exec works on full config data)
***********************************************************************************************************************************/
typedef struct ParseData
{
ConfigCommand command; // Command found
StringList *perlOptionList; // List of perl options
StringList *commandArgList; // List of command arguments
ParseOption parseOptionList[CFG_OPTION_TOTAL]; // List of parsed options
} ParseData;
ParseData *configParseArg(int argListSize, const char *argList[]);
/***********************************************************************************************************************************
Functions
***********************************************************************************************************************************/

View File

@ -18,7 +18,7 @@ int main(int argListSize, const char *argList[])
configParse(argListSize, argList);
// Display version
if (cfgCommand() == cfgCmdVersion)
if (!cfgCommandHelp() && cfgCommand() == cfgCmdVersion)
{
printf(PGBACKREST_NAME " " PGBACKREST_VERSION "\n");
fflush(stdout);
@ -26,11 +26,11 @@ int main(int argListSize, const char *argList[])
}
// Execute Perl for commands not implemented in C
perlExec(perlCommand(argListSize, argList));
perlExec(perlCommand());
}
CATCH_ANY()
{
fprintf(stderr, "ERROR: [%03d]: %s\n", errorCode(), errorMessage());
fprintf(stderr, "ERROR [%03d]: %s\n", errorCode(), errorMessage());
fflush(stderr);
exit(errorCode());
}

View File

@ -11,7 +11,7 @@ Execute Perl for Legacy Functionality
#include "common/error.h"
#include "common/memContext.h"
#include "common/type.h"
#include "config/parse.h"
#include "config/config.h"
/***********************************************************************************************************************************
Constants used to build perl options
@ -27,10 +27,8 @@ Constants used to build perl options
/***********************************************************************************************************************************
Build list of perl options to use for exec
***********************************************************************************************************************************/
StringList *perlCommand(int argListSize, const char *argList[])
StringList *perlCommand()
{
ParseData *parseData = configParseArg(argListSize, argList);
// Begin arg list for perl exec
StringList *perlArgList = strLstNew();
strLstAdd(perlArgList, strNew(ENV_EXE));
@ -41,52 +39,107 @@ StringList *perlCommand(int argListSize, const char *argList[])
for (ConfigOption optionId = 0; optionId < CFG_OPTION_TOTAL; optionId++)
{
ParseOption *option = &parseData->parseOptionList[optionId];
// Skip the option if it is not valid or not a command line option
if (!cfgOptionValid(optionId) || cfgOptionSource(optionId) != cfgSourceParam)
continue;
// If option was found
if (option->found)
{
// If option was negated
if (option->negate)
strCatFmt(mainCallParam, ", '--no-%s\'", cfgOptionName(optionId));
// Else option with no arguments
else if (option->valueList == NULL)
strCatFmt(mainCallParam, ", '--%s'", cfgOptionName(optionId));
// Else options with arguments
if (cfgOptionNegate(optionId))
strCatFmt(mainCallParam, ", '--no-%s'", cfgOptionName(optionId));
// Else not negated
else
{
for (unsigned int argIdx = 0; argIdx < strLstSize(option->valueList); argIdx++)
switch (cfgDefOptionType(cfgOptionDefIdFromId(optionId)))
{
case cfgDefOptTypeBoolean:
{
strCatFmt(mainCallParam, ", '--%s'", cfgOptionName(optionId));
strCatFmt(mainCallParam, ", '%s'", strPtr(strLstGet(option->valueList, argIdx)));
break;
}
case cfgDefOptTypeFloat:
{
strCatFmt(mainCallParam, ", '--%s'", cfgOptionName(optionId));
strCatFmt(mainCallParam, ", '%lg'", cfgOptionDbl(optionId));
break;
}
case cfgDefOptTypeHash:
{
const KeyValue *valueKv = cfgOptionKv(optionId);
const VariantList *keyList = kvKeyList(valueKv);
for (unsigned int listIdx = 0; listIdx < varLstSize(keyList); listIdx++)
{
strCatFmt(mainCallParam, ", '--%s'", cfgOptionName(optionId));
strCatFmt(
mainCallParam, ", '%s=%s'", strPtr(varStr(varLstGet(keyList, listIdx))),
strPtr(varStr(kvGet(valueKv, varLstGet(keyList, listIdx)))));
}
break;
}
case cfgDefOptTypeInteger:
{
strCatFmt(mainCallParam, ", '--%s'", cfgOptionName(optionId));
strCatFmt(mainCallParam, ", '%d'", cfgOptionInt(optionId));
break;
}
case cfgDefOptTypeList:
{
StringList *valueList = strLstNewVarLst(cfgOptionLst(optionId));
for (unsigned int listIdx = 0; listIdx < strLstSize(valueList); listIdx++)
{
strCatFmt(mainCallParam, ", '--%s'", cfgOptionName(optionId));
strCatFmt(mainCallParam, ", '%s'", strPtr(strLstGet(valueList, listIdx)));
}
break;
}
case cfgDefOptTypeString:
{
strCatFmt(mainCallParam, ", '--%s'", cfgOptionName(optionId));
strCatFmt(mainCallParam, ", '%s'", strPtr(cfgOptionStr(optionId)));
break;
}
}
}
}
// Add help command if it was set
if (cfgCommandHelp())
strCatFmt(mainCallParam, ", '%s'", cfgCommandName(cfgCmdHelp));
// Add command to pass to main
strCatFmt(mainCallParam, ", '%s'", cfgCommandName(parseData->command));
if (cfgCommand() != cfgCmdNone && cfgCommand() != cfgCmdHelp)
strCatFmt(mainCallParam, ", '%s'", cfgCommandName(cfgCommand()));
// Add command arguments to pass to main
if (parseData->commandArgList != NULL)
for (unsigned int argIdx = 0; argIdx < strLstSize(parseData->commandArgList); argIdx++)
strCatFmt(mainCallParam, ", '%s'", strPtr(strLstGet(parseData->commandArgList, argIdx)));
for (unsigned int paramIdx = 0; paramIdx < strLstSize(cfgCommandParam()); paramIdx++)
strCatFmt(mainCallParam, ", '%s'", strPtr(strLstGet(cfgCommandParam(), paramIdx)));
// Construct perl option list to add to bin
StringList *perlOptionList = strLstNewVarLst(cfgOptionLst(cfgOptPerlOption));
String *binPerlOption = strNew("");
if (parseData->perlOptionList != NULL)
for (unsigned int argIdx = 0; argIdx < strLstSize(parseData->perlOptionList); argIdx++)
if (perlOptionList != NULL)
{
// Add to bin option list
strCatFmt(binPerlOption, " --" PARAM_PERL_OPTION "=\"%s\"", strPtr(strLstGet(parseData->perlOptionList, argIdx)));
for (unsigned int argIdx = 0; argIdx < strLstSize(perlOptionList); argIdx++)
{
// // Add to bin option list
// strCatFmt(binPerlOption, " --" PARAM_PERL_OPTION "=\"%s\"", strPtr(strLstGet(perlOptionList, argIdx)));
// Add to list that will be passed to exec
strLstAdd(perlArgList, strLstGet(parseData->perlOptionList, argIdx));
strLstAdd(perlArgList, strLstGet(perlOptionList, argIdx));
}
}
// Construct Perl main call
String *mainCall = strNewFmt(PGBACKREST_MAIN "('%s%s'%s)", argList[0], strPtr(binPerlOption), strPtr(mainCallParam));
String *mainCall = strNewFmt(PGBACKREST_MAIN "('%s%s'%s)", strPtr(cfgExe()), strPtr(binPerlOption), strPtr(mainCallParam));
// End arg list for perl exec
strLstAdd(perlArgList, strNew("-M" PGBACKREST_MODULE));

View File

@ -9,7 +9,7 @@ Execute Perl for Legacy Functionality
/***********************************************************************************************************************************
Functions
***********************************************************************************************************************************/
StringList *perlCommand(int argListSize, const char *argList[]);
StringList *perlCommand();
void perlExec(StringList *perlArgList);
#endif

View File

@ -440,7 +440,7 @@ full backup - invalid cmd line (db-master host)
> [CONTAINER-EXEC] db-master [BACKREST-BIN] --config=[TEST_PATH]/db-master/pgbackrest.conf --no-online --type=full --stanza=bogus backup
------------------------------------------------------------------------------------------------------------------------------------
STDERR:
ERROR [037]: : backup command requires option: db1-path
ERROR [037]: backup command requires option: db1-path
HINT: does this stanza exist?
stop all stanzas (db-master host)

View File

@ -394,7 +394,7 @@ my $oTestDef =
},
{
&TESTDEF_NAME => 'parse',
&TESTDEF_TOTAL => 2,
&TESTDEF_TOTAL => 1,
&TESTDEF_C => true,
&TESTDEF_COVERAGE =>

View File

@ -71,8 +71,86 @@ void testRun()
// -----------------------------------------------------------------------------------------------------------------------------
if (testBegin("configuration"))
{
TEST_RESULT_VOID(cfgInit(), "config init");
// -------------------------------------------------------------------------------------------------------------------------
TEST_RESULT_INT(cfgCommand(), cfgCmdNone, "command begins as none");
TEST_RESULT_VOID(cfgCommandSet(cfgCmdBackup), "command set to backup");
TEST_RESULT_INT(cfgCommand(), cfgCmdBackup, "command is backup");
// -------------------------------------------------------------------------------------------------------------------------
TEST_RESULT_BOOL(cfgCommandHelp(), false, "command help defaults to false");
TEST_RESULT_VOID(cfgCommandHelpSet(true), "set command help");
TEST_RESULT_BOOL(cfgCommandHelp(), true, "command help is set");
// -------------------------------------------------------------------------------------------------------------------------
TEST_RESULT_INT(strLstSize(cfgCommandParam()), 0, "command param list defaults to empty");
TEST_RESULT_VOID(cfgCommandParamSet(strLstAddZ(strLstNew(), "param")), "set command param list");
TEST_RESULT_INT(strLstSize(cfgCommandParam()), 1, "command param list is set");
// -------------------------------------------------------------------------------------------------------------------------
TEST_RESULT_PTR(cfgExe(), NULL, "exe defaults to null");
TEST_RESULT_VOID(cfgExeSet(strNew("/path/to/exe")), "set exe");
TEST_RESULT_STR(strPtr(cfgExe()), "/path/to/exe", "exe is set");
// -------------------------------------------------------------------------------------------------------------------------
TEST_RESULT_BOOL(cfgOptionNegate(cfgOptConfig), false, "negate defaults to false");
TEST_RESULT_VOID(cfgOptionNegateSet(cfgOptConfig, true), "set negate");
TEST_RESULT_BOOL(cfgOptionNegate(cfgOptConfig), true, "negate is set");
// -------------------------------------------------------------------------------------------------------------------------
TEST_RESULT_BOOL(cfgOptionValid(cfgOptConfig), false, "valid defaults to false");
TEST_RESULT_VOID(cfgOptionValidSet(cfgOptConfig, true), "set valid");
TEST_RESULT_BOOL(cfgOptionValid(cfgOptConfig), true, "valid is set");
// -------------------------------------------------------------------------------------------------------------------------
TEST_RESULT_PTR(cfgOption(cfgOptOnline), NULL, "online is null");
TEST_RESULT_VOID(cfgOptionSet(cfgOptOnline, cfgSourceParam, varNewBool(false)), "set online");
TEST_RESULT_BOOL(cfgOptionBool(cfgOptOnline), false, "online is set");
TEST_RESULT_VOID(cfgOptionSet(cfgOptOnline, cfgSourceParam, varNewStrZ("1")), "set online");
TEST_RESULT_BOOL(cfgOptionBool(cfgOptOnline), true, "online is set");
TEST_RESULT_INT(cfgOptionSource(cfgOptOnline), cfgSourceParam, "online source is set");
TEST_ERROR(cfgOptionDbl(cfgOptOnline), AssertError, "option 'online' is not type 'double'");
TEST_RESULT_VOID(cfgOptionSet(cfgOptCompressLevel, cfgSourceParam, varNewInt(1)), "set compress-level");
TEST_RESULT_INT(cfgOptionInt(cfgOptCompressLevel), 1, "compress-level is set");
TEST_RESULT_VOID(cfgOptionSet(cfgOptCompressLevel, cfgSourceDefault, varNewStrZ("3")), "set compress-level");
TEST_RESULT_INT(cfgOptionInt(cfgOptCompressLevel), 3, "compress-level is set");
TEST_RESULT_INT(cfgOptionSource(cfgOptCompressLevel), cfgSourceDefault, "compress source is set");
TEST_ERROR(cfgOptionBool(cfgOptCompressLevel), AssertError, "option 'compress-level' is not type 'bool'");
TEST_RESULT_VOID(cfgOptionSet(cfgOptProtocolTimeout, cfgSourceParam, varNewDbl(1.1)), "set protocol-timeout");
TEST_RESULT_DOUBLE(cfgOptionDbl(cfgOptProtocolTimeout), 1.1, "protocol-timeout is set");
TEST_RESULT_VOID(cfgOptionSet(cfgOptProtocolTimeout, cfgSourceConfig, varNewStrZ("3.3")), "set protocol-timeout");
TEST_RESULT_DOUBLE(cfgOptionDbl(cfgOptProtocolTimeout), 3.3, "protocol-timeout is set");
TEST_RESULT_INT(cfgOptionSource(cfgOptProtocolTimeout), cfgSourceConfig, "protocol-timeout source is set");
TEST_ERROR(cfgOptionKv(cfgOptProtocolTimeout), AssertError, "option 'protocol-timeout' is not type 'KeyValue'");
TEST_ERROR(
cfgOptionSet(cfgOptRecoveryOption, cfgSourceParam, varNewDbl(1.1)), AssertError,
"option 'recovery-option' must be set with KeyValue variant");
TEST_RESULT_VOID(cfgOptionSet(cfgOptRecoveryOption, cfgSourceConfig, varNewKv()), "set recovery-option");
TEST_RESULT_INT(varLstSize(kvKeyList(cfgOptionKv(cfgOptRecoveryOption))), 0, "recovery-option is set");
TEST_ERROR(cfgOptionLst(cfgOptRecoveryOption), AssertError, "option 'recovery-option' is not type 'VariantList'");
TEST_RESULT_INT(varLstSize(cfgOptionLst(cfgOptDbInclude)), 0, "db-include defaults to empty");
TEST_ERROR(
cfgOptionSet(cfgOptDbInclude, cfgSourceParam, varNewDbl(1.1)), AssertError,
"option 'db-include' must be set with VariantList variant");
TEST_RESULT_VOID(cfgOptionSet(cfgOptDbInclude, cfgSourceConfig, varNewVarLstEmpty()), "set db-include");
TEST_RESULT_INT(varLstSize(cfgOptionLst(cfgOptDbInclude)), 0, "db-include is set");
TEST_ERROR(cfgOptionStr(cfgOptDbInclude), AssertError, "option 'db-include' is not type 'String'");
TEST_RESULT_PTR(cfgOptionStr(cfgOptStanza), NULL, "stanza defaults to null");
TEST_ERROR(
cfgOptionSet(cfgOptStanza, cfgSourceParam, varNewDbl(1.1)), AssertError,
"option 'stanza' must be set with String variant");
TEST_RESULT_VOID(cfgOptionSet(cfgOptStanza, cfgSourceConfig, varNewStrZ("db")), "set stanza");
TEST_RESULT_STR(strPtr(cfgOptionStr(cfgOptStanza)), "db", "stanza is set");
TEST_ERROR(cfgOptionInt(cfgOptStanza), AssertError, "option 'stanza' is not type 'int'");
// -------------------------------------------------------------------------------------------------------------------------
TEST_RESULT_VOID(cfgInit(), "config init resets value");
TEST_RESULT_INT(cfgCommand(), cfgCmdNone, "command begins as none");
}
}

View File

@ -1,7 +1,6 @@
/***********************************************************************************************************************************
Test Configuration Parse
***********************************************************************************************************************************/
#define TEST_BACKREST_EXE "pgbackrest"
#define TEST_COMMAND_ARCHIVE_GET "archive-get"
@ -16,71 +15,37 @@ Test run
void testRun()
{
// -----------------------------------------------------------------------------------------------------------------------------
if (testBegin("configParseArg()"))
if (testBegin("configParse()"))
{
ParseData *parseData = NULL;
StringList *argList = strLstNew();
strLstAdd(argList, strNew(TEST_BACKREST_EXE));
TEST_ASSIGN(parseData, configParseArg(strLstSize(argList), strLstPtr(argList)), "no command, no args - set help command");
TEST_RESULT_INT(parseData->command, cfgCmdHelp, " command is " TEST_COMMAND_HELP);
// -------------------------------------------------------------------------------------------------------------------------
strLstAdd(argList, strNew("--online"));
TEST_ERROR(configParseArg(strLstSize(argList), strLstPtr(argList)), CommandRequiredError, "no command found");
// -------------------------------------------------------------------------------------------------------------------------
strLstAdd(argList, strNew(TEST_COMMAND_VERSION));
TEST_ASSIGN(parseData, configParseArg(strLstSize(argList), strLstPtr(argList)), TEST_COMMAND_VERSION " command");
TEST_RESULT_INT(parseData->command, cfgCmdVersion, " command is " TEST_COMMAND_VERSION);
TEST_RESULT_PTR(parseData->perlOptionList, NULL, " no perl options");
TEST_RESULT_PTR(parseData->commandArgList, NULL, " no command arguments");
// -------------------------------------------------------------------------------------------------------------------------
strLstAdd(argList, strNew("--perl-option=value"));
TEST_ASSIGN(parseData, configParseArg(strLstSize(argList), strLstPtr(argList)), "perl option");
TEST_RESULT_STR(strPtr(strLstJoin(parseData->perlOptionList, ",")), "value", "check perl option");
// -------------------------------------------------------------------------------------------------------------------------
strLstAdd(argList, strNewFmt("--%s", cfgOptionName(cfgOptDelta)));
TEST_ASSIGN(parseData, configParseArg(strLstSize(argList), strLstPtr(argList)), "delta option");
TEST_RESULT_BOOL(parseData->parseOptionList[cfgOptDelta].found, true, " option found");
TEST_RESULT_BOOL(parseData->parseOptionList[cfgOptDelta].negate, false, " option not negated");
TEST_RESULT_PTR(parseData->parseOptionList[cfgOptDelta].valueList, NULL, " no values");
// -------------------------------------------------------------------------------------------------------------------------
strLstAdd(argList, strNew("--" BOGUS_STR));
TEST_ERROR(configParseArg(strLstSize(argList), strLstPtr(argList)), OptionInvalidError, "invalid option '--BOGUS'");
// -------------------------------------------------------------------------------------------------------------------------
argList = strLstNew();
strLstAdd(argList, strNew(TEST_BACKREST_EXE));
strLstAdd(argList, strNew("--no-archive-check"));
strLstAdd(argList, strNew(TEST_COMMAND_HELP));
TEST_ASSIGN(parseData, configParseArg(strLstSize(argList), strLstPtr(argList)), TEST_COMMAND_HELP " command");
TEST_RESULT_BOOL(parseData->parseOptionList[cfgOptArchiveCheck].negate, true, " option negated");
StringList *argList = NULL;
String *configFile = strNewFmt("%s/test.config", testPath());
// -------------------------------------------------------------------------------------------------------------------------
argList = strLstNew();
strLstAdd(argList, strNew(TEST_BACKREST_EXE));
strLstAdd(argList, strNew(BOGUS_STR));
TEST_ERROR(configParseArg(strLstSize(argList), strLstPtr(argList)), CommandInvalidError, "invalid command 'BOGUS'");
TEST_ERROR(configParse(strLstSize(argList), strLstPtr(argList)), CommandInvalidError, "invalid command 'BOGUS'");
// -------------------------------------------------------------------------------------------------------------------------
argList = strLstNew();
strLstAdd(argList, strNew(TEST_BACKREST_EXE));
strLstAdd(argList, strNew("--no-online"));
strLstAdd(argList, strNew("--no-online"));
TEST_ERROR(
configParseArg(strLstSize(argList), strLstPtr(argList)), OptionInvalidError,
"option 'online' is negated multiple times");
strLstAdd(argList, strNew("--" BOGUS_STR));
TEST_ERROR(configParse(strLstSize(argList), strLstPtr(argList)), OptionInvalidError, "invalid option '--BOGUS'");
// -------------------------------------------------------------------------------------------------------------------------
argList = strLstNew();
strLstAdd(argList, strNew(TEST_BACKREST_EXE));
strLstAdd(argList, strNew("--db-host"));
TEST_ERROR(
configParseArg(strLstSize(argList), strLstPtr(argList)), OptionInvalidError, "option '--db-host' requires argument");
configParse(strLstSize(argList), strLstPtr(argList)), OptionInvalidError, "option '--db-host' requires argument");
// -------------------------------------------------------------------------------------------------------------------------
argList = strLstNew();
strLstAdd(argList, strNew(TEST_BACKREST_EXE));
strLstAdd(argList, strNew("--no-online"));
strLstAdd(argList, strNew("--no-online"));
TEST_ERROR(
configParse(strLstSize(argList), strLstPtr(argList)), OptionInvalidError, "option 'online' is negated multiple times");
// -------------------------------------------------------------------------------------------------------------------------
argList = strLstNew();
@ -88,8 +53,7 @@ void testRun()
strLstAdd(argList, strNew("--config=/etc/config"));
strLstAdd(argList, strNew("--no-config"));
TEST_ERROR(
configParseArg(strLstSize(argList), strLstPtr(argList)), OptionInvalidError,
"option 'config' cannot be set and negated");
configParse(strLstSize(argList), strLstPtr(argList)), OptionInvalidError, "option 'config' cannot be set and negated");
// -------------------------------------------------------------------------------------------------------------------------
argList = strLstNew();
@ -97,48 +61,362 @@ void testRun()
strLstAdd(argList, strNew("--compress-level=3"));
strLstAdd(argList, strNew("--compress-level=3"));
TEST_ERROR(
configParseArg(strLstSize(argList), strLstPtr(argList)), OptionInvalidError,
configParse(strLstSize(argList), strLstPtr(argList)), OptionInvalidError,
"option 'compress-level' cannot have multiple arguments");
// -------------------------------------------------------------------------------------------------------------------------
argList = strLstNew();
strLstAdd(argList, strNew(TEST_BACKREST_EXE));
strLstAdd(argList, strNew("--online"));
TEST_ERROR(configParse(strLstSize(argList), strLstPtr(argList)), CommandRequiredError, "no command found");
// -------------------------------------------------------------------------------------------------------------------------
argList = strLstNew();
strLstAdd(argList, strNew(TEST_BACKREST_EXE));
strLstAdd(argList, strNew(TEST_COMMAND_BACKUP));
TEST_ERROR(
configParse(strLstSize(argList), strLstPtr(argList)), OptionRequiredError,
"backup command requires option: db1-path\nHINT: does this stanza exist?");
// -------------------------------------------------------------------------------------------------------------------------
argList = strLstNew();
strLstAdd(argList, strNew(TEST_BACKREST_EXE));
strLstAdd(argList, strNew("--db-path=/path/to/db"));
strLstAdd(argList, strNew(TEST_COMMAND_BACKUP));
TEST_ERROR(
configParse(strLstSize(argList), strLstPtr(argList)), OptionRequiredError,
"backup command requires option: stanza");
// -------------------------------------------------------------------------------------------------------------------------
argList = strLstNew();
strLstAdd(argList, strNew(TEST_BACKREST_EXE));
strLstAdd(argList, strNew("--db-path=/path/to/db"));
strLstAdd(argList, strNew("--no-config"));
strLstAdd(argList, strNew("--stanza=db"));
strLstAdd(argList, strNew("--repo-s3-key=xxx"));
strLstAdd(argList, strNew(TEST_COMMAND_BACKUP));
TEST_ERROR(
configParse(strLstSize(argList), strLstPtr(argList)), OptionInvalidError,
"option 'repo-s3-key' not valid without option 'repo-type' = 's3'");
// -------------------------------------------------------------------------------------------------------------------------
argList = strLstNew();
strLstAdd(argList, strNew(TEST_BACKREST_EXE));
strLstAdd(argList, strNew("--db-path=/path/to/db"));
strLstAdd(argList, strNew("--backup-user=xxx"));
strLstAdd(argList, strNew("--stanza=db"));
strLstAdd(argList, strNew(TEST_COMMAND_BACKUP));
TEST_ERROR(
configParse(strLstSize(argList), strLstPtr(argList)), OptionInvalidError,
"option 'backup-user' not valid without option 'backup-host'");
// -------------------------------------------------------------------------------------------------------------------------
argList = strLstNew();
strLstAdd(argList, strNew(TEST_BACKREST_EXE));
strLstAdd(argList, strNew("--db-path=/path/to/db"));
strLstAdd(argList, strNew("--stanza=db"));
strLstAdd(argList, strNew("--force"));
strLstAdd(argList, strNew(TEST_COMMAND_BACKUP));
TEST_ERROR(
configParse(strLstSize(argList), strLstPtr(argList)), OptionInvalidError,
"option 'force' not valid without option 'no-online'");
// -------------------------------------------------------------------------------------------------------------------------
argList = strLstNew();
strLstAdd(argList, strNew(TEST_BACKREST_EXE));
strLstAdd(argList, strNew("--db-path=/path/to/db"));
strLstAdd(argList, strNew("--stanza=db"));
strLstAdd(argList, strNew("--test-delay=1"));
strLstAdd(argList, strNew(TEST_COMMAND_BACKUP));
TEST_ERROR(
configParse(strLstSize(argList), strLstPtr(argList)), OptionInvalidError,
"option 'test-delay' not valid without option 'test'");
// -------------------------------------------------------------------------------------------------------------------------
argList = strLstNew();
strLstAdd(argList, strNew(TEST_BACKREST_EXE));
strLstAdd(argList, strNew("--db-path=/path/to/db"));
strLstAdd(argList, strNew("--stanza=db"));
strLstAdd(argList, strNew("--recovery-option=a=b"));
strLstAdd(argList, strNew(TEST_COMMAND_BACKUP));
TEST_ERROR(
configParse(strLstSize(argList), strLstPtr(argList)), OptionInvalidError,
"option 'recovery-option' not valid for command 'backup'");
// -------------------------------------------------------------------------------------------------------------------------
argList = strLstNew();
strLstAdd(argList, strNew(TEST_BACKREST_EXE));
strLstAdd(argList, strNew("--db-path=/path/to/db"));
strLstAdd(argList, strNew("--stanza=db"));
strLstAdd(argList, strNew("--target-exclusive"));
strLstAdd(argList, strNew(TEST_COMMAND_RESTORE));
TEST_ERROR(
configParse(strLstSize(argList), strLstPtr(argList)), OptionInvalidError,
"option 'target-exclusive' not valid without option 'type' in ('time', 'xid')");
// -------------------------------------------------------------------------------------------------------------------------
argList = strLstNew();
strLstAdd(argList, strNew(TEST_BACKREST_EXE));
strLstAdd(argList, strNew("--db-path=/path/to/db"));
strLstAdd(argList, strNew("--stanza=db"));
strLstAdd(argList, strNew("--type=bogus"));
strLstAdd(argList, strNew(TEST_COMMAND_RESTORE));
TEST_ERROR(
configParse(strLstSize(argList), strLstPtr(argList)), OptionInvalidValueError,
"'bogus' is not valid for 'type' option");
// -------------------------------------------------------------------------------------------------------------------------
argList = strLstNew();
strLstAdd(argList, strNew(TEST_BACKREST_EXE));
strLstAdd(argList, strNew("--db-path=/path/to/db"));
strLstAdd(argList, strNew("--stanza=db"));
strLstAdd(argList, strNew("--process-max=0"));
strLstAdd(argList, strNew(TEST_COMMAND_RESTORE));
TEST_ERROR(
configParse(strLstSize(argList), strLstPtr(argList)), OptionInvalidValueError,
"'0' is not valid for 'process-max' option");
// -------------------------------------------------------------------------------------------------------------------------
argList = strLstNew();
strLstAdd(argList, strNew(TEST_BACKREST_EXE));
strLstAdd(argList, strNew("--db-path=/path/to/db"));
strLstAdd(argList, strNew("--stanza=db"));
strLstAdd(argList, strNew("--process-max=bogus"));
strLstAdd(argList, strNew(TEST_COMMAND_RESTORE));
TEST_ERROR(
configParse(strLstSize(argList), strLstPtr(argList)), OptionInvalidValueError,
"'bogus' is not valid for 'process-max' option");
// -------------------------------------------------------------------------------------------------------------------------
argList = strLstNew();
strLstAdd(argList, strNew(TEST_BACKREST_EXE));
strLstAdd(argList, strNew("--db-path=/path/to/db"));
strLstAdd(argList, strNew("--stanza=db"));
strLstAdd(argList, strNew("--protocol-timeout=.01"));
strLstAdd(argList, strNew(TEST_COMMAND_RESTORE));
TEST_ERROR(
configParse(strLstSize(argList), strLstPtr(argList)), OptionInvalidValueError,
"'.01' is not valid for 'protocol-timeout' option");
// -------------------------------------------------------------------------------------------------------------------------
argList = strLstNew();
strLstAdd(argList, strNew(TEST_BACKREST_EXE));
strLstAdd(argList, strNew("--db-path=/path/to/db"));
strLstAdd(argList, strNew("--stanza=db"));
strLstAdd(argList, strNew("--protocol-timeout=bogus"));
strLstAdd(argList, strNew(TEST_COMMAND_RESTORE));
TEST_ERROR(
configParse(strLstSize(argList), strLstPtr(argList)), OptionInvalidValueError,
"'bogus' is not valid for 'protocol-timeout' option");
// -------------------------------------------------------------------------------------------------------------------------
argList = strLstNew();
strLstAdd(argList, strNew(TEST_BACKREST_EXE));
strLstAdd(argList, strNew("--stanza=db"));
strLstAdd(argList, strNewFmt("--config=%s", strPtr(configFile)));
strLstAdd(argList, strNew(TEST_COMMAND_BACKUP));
storagePut(storageLocal(), configFile, bufNewStr(strNew(
"[global]\n"
"compress=bogus\n"
)));
TEST_ERROR(configParse(
strLstSize(argList), strLstPtr(argList)), OptionInvalidError, "boolean option 'compress' must be 'y' or 'n'");
// -------------------------------------------------------------------------------------------------------------------------
argList = strLstNew();
strLstAdd(argList, strNew(TEST_BACKREST_EXE));
strLstAdd(argList, strNew("--stanza=db"));
strLstAdd(argList, strNewFmt("--config=%s", strPtr(configFile)));
strLstAdd(argList, strNew(TEST_COMMAND_BACKUP));
storagePut(storageLocal(), configFile, bufNewStr(strNew(
"[global]\n"
"compress=\n"
)));
TEST_ERROR(configParse(
strLstSize(argList), strLstPtr(argList)), OptionInvalidValueError,
"section 'global', key 'compress' must have a value");
// -------------------------------------------------------------------------------------------------------------------------
argList = strLstNew();
strLstAdd(argList, strNew(TEST_BACKREST_EXE));
TEST_RESULT_VOID(configParse(strLstSize(argList), strLstPtr(argList)), "no command");
TEST_RESULT_BOOL(cfgCommandHelp(), true, " help is set");
TEST_RESULT_INT(cfgCommand(), cfgCmdNone, " command is none");
// -------------------------------------------------------------------------------------------------------------------------
argList = strLstNew();
strLstAdd(argList, strNew(TEST_BACKREST_EXE));
strLstAdd(argList, strNew("help"));
TEST_RESULT_VOID(configParse(strLstSize(argList), strLstPtr(argList)), "help command");
TEST_RESULT_BOOL(cfgCommandHelp(), true, " help is set");
TEST_RESULT_INT(cfgCommand(), cfgCmdHelp, " command is help");
// -------------------------------------------------------------------------------------------------------------------------
argList = strLstNew();
strLstAdd(argList, strNew(TEST_BACKREST_EXE));
strLstAdd(argList, strNew("help"));
strLstAdd(argList, strNew("version"));
TEST_RESULT_VOID(configParse(strLstSize(argList), strLstPtr(argList)), "help for version command");
TEST_RESULT_BOOL(cfgCommandHelp(), true, " help is set");
TEST_RESULT_INT(cfgCommand(), cfgCmdVersion, " command is version");
// -------------------------------------------------------------------------------------------------------------------------
argList = strLstNew();
strLstAdd(argList, strNew(TEST_BACKREST_EXE));
strLstAdd(argList, strNew("--stanza=db"));
strLstAdd(argList, strNew(TEST_COMMAND_ARCHIVE_GET));
strLstAdd(argList, strNew("000000010000000200000003"));
strLstAdd(argList, strNew("/path/to/wal/RECOVERYWAL"));
TEST_ASSIGN(parseData, configParseArg(strLstSize(argList), strLstPtr(argList)), "command arguments");
TEST_RESULT_VOID(configParse(strLstSize(argList), strLstPtr(argList)), "command arguments");
TEST_RESULT_STR(
strPtr(strLstJoin(parseData->commandArgList, "|")), "000000010000000200000003|/path/to/wal/RECOVERYWAL",
strPtr(strLstJoin(cfgCommandParam(), "|")), "000000010000000200000003|/path/to/wal/RECOVERYWAL",
" check command arguments");
// -------------------------------------------------------------------------------------------------------------------------
argList = strLstNew();
strLstAdd(argList, strNew(TEST_BACKREST_EXE));
strLstAdd(argList, strNew("--db-host=db1.test.org"));
strLstAdd(argList, strNew("--stanza=db"));
strLstAdd(argList, strNew("--backup-host=backup"));
strLstAdd(argList, strNew("--backup-user=pgbackrest"));
strLstAdd(argList, strNew("--db-path=/path/to/db"));
strLstAdd(argList, strNew("--no-online"));
strLstAdd(argList, strNew(TEST_COMMAND_BACKUP));
TEST_ASSIGN(parseData, configParseArg(strLstSize(argList), strLstPtr(argList)), "single valid option");
TEST_RESULT_STR(
strPtr(strLstJoin(parseData->parseOptionList[cfgOptDbHost].valueList, "|")), "db1.test.org", " check db-host option");
TEST_RESULT_VOID(configParse(strLstSize(argList), strLstPtr(argList)), TEST_COMMAND_BACKUP " command");
TEST_RESULT_INT(cfgCommand(), cfgCmdBackup, " command is " TEST_COMMAND_BACKUP);
TEST_RESULT_STR(strPtr(cfgExe()), TEST_BACKREST_EXE, " exe is set");
TEST_RESULT_STR(strPtr(cfgOptionStr(cfgOptStanza)), "db", " stanza is set");
TEST_RESULT_INT(cfgOptionSource(cfgOptStanza), cfgSourceParam, " stanza is source param");
TEST_RESULT_STR(strPtr(cfgOptionStr(cfgOptDbPath)), "/path/to/db", " db-path is set");
TEST_RESULT_INT(cfgOptionSource(cfgOptDbPath), cfgSourceParam, " db-path is source param");
TEST_RESULT_BOOL(cfgOptionBool(cfgOptOnline), false, " online is not set");
TEST_RESULT_INT(cfgOptionSource(cfgOptOnline), cfgSourceParam, " online is source default");
TEST_RESULT_BOOL(cfgOptionBool(cfgOptCompress), true, " compress is set");
TEST_RESULT_INT(cfgOptionSource(cfgOptCompress), cfgSourceDefault, " compress is source default");
// -------------------------------------------------------------------------------------------------------------------------
argList = strLstNew();
strLstAdd(argList, strNew(TEST_BACKREST_EXE));
strLstAdd(argList, strNew("--link-map=ts1=/path/to/ts1"));
strLstAdd(argList, strNew("--link-map=ts2=/path/to/ts2"));
strLstAdd(argList, strNew(TEST_COMMAND_RESTORE));
TEST_ASSIGN(parseData, configParseArg(strLstSize(argList), strLstPtr(argList)), "multiple valid options");
TEST_RESULT_STR(
strPtr(strLstJoin(parseData->parseOptionList[cfgOptLinkMap].valueList, "|")), "ts1=/path/to/ts1|ts2=/path/to/ts2",
" check link-map options");
}
strLstAdd(argList, strNew("--stanza=db"));
strLstAdd(argList, strNewFmt("--config=%s", strPtr(configFile)));
strLstAdd(argList, strNew("--no-online"));
strLstAdd(argList, strNew("--db1-host=db"));
strLstAdd(argList, strNew(TEST_COMMAND_BACKUP));
// -----------------------------------------------------------------------------------------------------------------------------
if (testBegin("configParse()"))
{
StringList *argList = strLstNew();
storagePut(storageLocal(), configFile, bufNewStr(strNew(
"[global]\n"
"compress-level=3\n"
"\n"
"[global:backup]\n"
"hardlink=y\n"
"bogus=bogus\n"
"no-compress=y\n"
"archive-copy=y\n"
"online=y\n"
"\n"
"[db:backup]\n"
"compress=n\n"
"recovery-option=a=b\n"
"\n"
"[db]\n"
"db1-host=db\n"
"db1-path=/path/to/db\n"
"recovery-option=c=d\n"
)));
TEST_RESULT_VOID(configParse(strLstSize(argList), strLstPtr(argList)), TEST_COMMAND_BACKUP " command");
TEST_RESULT_STR(strPtr(cfgOptionStr(cfgOptDbPath)), "/path/to/db", " db-path is set");
TEST_RESULT_INT(cfgOptionSource(cfgOptDbPath), cfgSourceConfig, " db-path is source config");
TEST_RESULT_BOOL(cfgOptionBool(cfgOptCompress), false, " compress not is set");
TEST_RESULT_INT(cfgOptionSource(cfgOptCompress), cfgSourceConfig, " compress is source config");
TEST_RESULT_PTR(cfgOption(cfgOptArchiveCheck), NULL, " archive-check is not set");
TEST_RESULT_PTR(cfgOption(cfgOptArchiveCopy), NULL, " archive-copy is not set");
TEST_RESULT_BOOL(cfgOptionBool(cfgOptHardlink), true, " hardlink is set");
TEST_RESULT_INT(cfgOptionSource(cfgOptHardlink), cfgSourceConfig, " hardlink is source config");
TEST_RESULT_INT(cfgOptionInt(cfgOptCompressLevel), 3, " compress-level is set");
TEST_RESULT_INT(cfgOptionSource(cfgOptCompressLevel), cfgSourceConfig, " compress-level is source config");
// -------------------------------------------------------------------------------------------------------------------------
argList = strLstNew();
strLstAdd(argList, strNew(TEST_BACKREST_EXE));
strLstAdd(argList, strNew(TEST_COMMAND_HELP));
TEST_RESULT_VOID(configParse(strLstSize(argList), strLstPtr(argList)), TEST_COMMAND_HELP " command");
TEST_RESULT_INT(cfgCommand(), cfgCmdHelp, " command is " TEST_COMMAND_HELP);
strLstAdd(argList, strNew("--db-path=/path/to/db"));
strLstAdd(argList, strNew("--recovery-option=a"));
strLstAdd(argList, strNew("--stanza=db"));
strLstAdd(argList, strNew(TEST_COMMAND_RESTORE));
TEST_ERROR(
configParse(strLstSize(argList), strLstPtr(argList)), OptionInvalidError,
"key/value 'a' not valid for 'recovery-option' option");
// -------------------------------------------------------------------------------------------------------------------------
argList = strLstNew();
strLstAdd(argList, strNew(TEST_BACKREST_EXE));
strLstAdd(argList, strNew("--db-path=/path/to/db"));
strLstAdd(argList, strNew("--recovery-option=a"));
strLstAdd(argList, strNew("--stanza=db"));
strLstAdd(argList, strNew(TEST_COMMAND_RESTORE));
TEST_ERROR(
configParse(strLstSize(argList), strLstPtr(argList)), OptionInvalidError,
"key/value 'a' not valid for 'recovery-option' option");
// -------------------------------------------------------------------------------------------------------------------------
argList = strLstNew();
strLstAdd(argList, strNew(TEST_BACKREST_EXE));
strLstAdd(argList, strNew("--db-path=/path/to/db"));
strLstAdd(argList, strNew("--db-include=abc"));
strLstAdd(argList, strNew("--db-include=def"));
strLstAdd(argList, strNew("--stanza=db"));
strLstAdd(argList, strNew(TEST_COMMAND_RESTORE));
TEST_RESULT_VOID(configParse(strLstSize(argList), strLstPtr(argList)), TEST_COMMAND_RESTORE " command");
const VariantList *includeList = NULL;
TEST_ASSIGN(includeList, cfgOptionLst(cfgOptDbInclude), "get db include options");
TEST_RESULT_STR(strPtr(varStr(varLstGet(includeList, 0))), "abc", "check db include option");
TEST_RESULT_STR(strPtr(varStr(varLstGet(includeList, 1))), "def", "check db include option");
// -------------------------------------------------------------------------------------------------------------------------
argList = strLstNew();
strLstAdd(argList, strNew(TEST_BACKREST_EXE));
strLstAdd(argList, strNew("--db-path=/path/to/db"));
strLstAdd(argList, strNew("--recovery-option=a=b"));
strLstAdd(argList, strNew("--recovery-option=c=de=fg hi"));
strLstAdd(argList, strNew("--stanza=db"));
strLstAdd(argList, strNew(TEST_COMMAND_RESTORE));
TEST_RESULT_VOID(configParse(strLstSize(argList), strLstPtr(argList)), TEST_COMMAND_RESTORE " command");
const KeyValue *recoveryKv = NULL;
TEST_ASSIGN(recoveryKv, cfgOptionKv(cfgOptRecoveryOption), "get recovery options");
TEST_RESULT_STR(strPtr(varStr(kvGet(recoveryKv, varNewStr(strNew("a"))))), "b", "check recovery option");
TEST_RESULT_STR(strPtr(varStr(kvGet(recoveryKv, varNewStr(strNew("c"))))), "de=fg hi", "check recovery option");
// -------------------------------------------------------------------------------------------------------------------------
argList = strLstNew();
strLstAdd(argList, strNew(TEST_BACKREST_EXE));
strLstAdd(argList, strNewFmt("--config=%s", strPtr(configFile)));
strLstAdd(argList, strNew("--stanza=db"));
strLstAdd(argList, strNew(TEST_COMMAND_RESTORE));
storagePut(storageLocal(), configFile, bufNewStr(strNew(
"[global:restore]\n"
"recovery-option=f=g\n"
"recovery-option=hijk=l\n"
"\n"
"[db]\n"
"db1-path=/path/to/db\n"
)));
TEST_RESULT_VOID(configParse(strLstSize(argList), strLstPtr(argList)), TEST_COMMAND_RESTORE " command");
TEST_ASSIGN(recoveryKv, cfgOptionKv(cfgOptRecoveryOption), "get recovery options");
TEST_RESULT_STR(strPtr(varStr(kvGet(recoveryKv, varNewStr(strNew("f"))))), "g", "check recovery option");
TEST_RESULT_STR(strPtr(varStr(kvGet(recoveryKv, varNewStr(strNew("hijk"))))), "l", "check recovery option");
}
}

View File

@ -1,6 +1,7 @@
/***********************************************************************************************************************************
Test Perl Exec
***********************************************************************************************************************************/
#include "config/config.h"
#define TEST_ENV_EXE "/usr/bin/env"
#define TEST_PERL_EXE "perl"
@ -17,69 +18,80 @@ void testRun()
if (testBegin("perlCommand()"))
{
// -------------------------------------------------------------------------------------------------------------------------
const char *cmdLineParam[128];
int cmdLineParamSize = 0;
cmdLineParam[cmdLineParamSize++] = TEST_BACKREST_EXE;
cmdLineParam[cmdLineParamSize++] = "backup";
cfgInit();
cfgCommandSet(cfgCmdInfo);
cfgExeSet(strNew(TEST_BACKREST_EXE));
TEST_RESULT_STR(
strPtr(strLstJoin(perlCommand(cmdLineParamSize, cmdLineParam), "|")),
TEST_ENV_EXE "|" TEST_PERL_EXE "|" TEST_PERL_MAIN "', 'backup')|[NULL]", "simple command");
strPtr(strLstJoin(perlCommand(), "|")),
TEST_ENV_EXE "|" TEST_PERL_EXE "|" TEST_PERL_MAIN "', 'info')|[NULL]", "command with no options");
// -------------------------------------------------------------------------------------------------------------------------
cmdLineParam[cmdLineParamSize++] = "--compress";
cfgInit();
cfgCommandSet(cfgCmdBackup);
cfgExeSet(strNew(TEST_BACKREST_EXE));
cfgOptionValidSet(cfgOptCompress, true);
cfgOptionSet(cfgOptCompress, cfgSourceParam, varNewBool(true));
cfgOptionValidSet(cfgOptOnline, true);
cfgOptionNegateSet(cfgOptOnline, true);
cfgOptionSet(cfgOptOnline, cfgSourceParam, varNewBool(false));
cfgOptionValidSet(cfgOptProtocolTimeout, true);
cfgOptionSet(cfgOptProtocolTimeout, cfgSourceParam, varNewDbl(1.1));
cfgOptionValidSet(cfgOptCompressLevel, true);
cfgOptionSet(cfgOptCompressLevel, cfgSourceParam, varNewInt(3));
cfgOptionValidSet(cfgOptStanza, true);
cfgOptionSet(cfgOptStanza, cfgSourceParam, varNewStr(strNew("db")));
TEST_RESULT_STR(
strPtr(strLstJoin(perlCommand(cmdLineParamSize, cmdLineParam), "|")),
TEST_ENV_EXE "|" TEST_PERL_EXE "|" TEST_PERL_MAIN "', '--compress', 'backup')|[NULL]", "simple option");
strPtr(strLstJoin(perlCommand(), "|")),
TEST_ENV_EXE "|" TEST_PERL_EXE "|" TEST_PERL_MAIN "'"
", '--compress', '--compress-level', '3', '--no-online', '--protocol-timeout', '1.1', '--stanza', 'db'"
", 'backup')|[NULL]", "simple options");
// -------------------------------------------------------------------------------------------------------------------------
cmdLineParam[cmdLineParamSize++] = "--db-host=db1";
cfgInit();
cfgCommandHelpSet(true);
cfgCommandSet(cfgCmdRestore);
cfgExeSet(strNew(TEST_BACKREST_EXE));
cfgOptionValidSet(cfgOptDbInclude, true);
StringList *includeList = strLstNew();
strLstAdd(includeList, strNew("db1"));
strLstAdd(includeList, strNew("db2"));
cfgOptionSet(cfgOptDbInclude, cfgSourceParam, varNewVarLst(varLstNewStrLst(includeList)));
cfgOptionValidSet(cfgOptRecoveryOption, true);
// !!! WHY DO WE STILL NEED TO CREATE THE VAR KV EMPTY?
Variant *recoveryVar = varNewKv();
KeyValue *recoveryKv = varKv(recoveryVar);
kvPut(recoveryKv, varNewStr(strNew("standby_mode")), varNewStr(strNew("on")));
kvPut(recoveryKv, varNewStr(strNew("primary_conn_info")), varNewStr(strNew("blah")));
cfgOptionSet(cfgOptRecoveryOption, cfgSourceParam, recoveryVar);
StringList *commandParamList = strLstNew();
strLstAdd(commandParamList, strNew("param1"));
strLstAdd(commandParamList, strNew("param2"));
cfgCommandParamSet(commandParamList);
cfgOptionValidSet(cfgOptPerlOption, true);
// !!! WHY DO WE STILL NEED TO CREATE THE VAR KV EMPTY?
StringList *perlList = strLstNew();
strLstAdd(perlList, strNew("-I."));
strLstAdd(perlList, strNew("-MDevel::Cover=-silent,1"));
cfgOptionSet(cfgOptPerlOption, cfgSourceParam, varNewVarLst(varLstNewStrLst(perlList)));
TEST_RESULT_STR(
strPtr(strLstJoin(perlCommand(cmdLineParamSize, cmdLineParam), "|")),
TEST_ENV_EXE "|" TEST_PERL_EXE "|" TEST_PERL_MAIN "', '--compress', '--db1-host', 'db1', 'backup')|[NULL]",
"option with = before value");
// -------------------------------------------------------------------------------------------------------------------------
cmdLineParam[cmdLineParamSize++] = "--db-user";
cmdLineParam[cmdLineParamSize++] = "postgres";
TEST_RESULT_STR(
strPtr(strLstJoin(perlCommand(cmdLineParamSize, cmdLineParam), "|")),
TEST_ENV_EXE "|" TEST_PERL_EXE "|" TEST_PERL_MAIN
"', '--compress', '--db1-host', 'db1', '--db1-user', 'postgres', 'backup')|[NULL]",
"option with space before value");
// -------------------------------------------------------------------------------------------------------------------------
cmdLineParam[cmdLineParamSize++] = "--perl-option=-I.";
TEST_RESULT_STR(
strPtr(strLstJoin(perlCommand(cmdLineParamSize, cmdLineParam), "|")),
TEST_ENV_EXE "|" TEST_PERL_EXE "|-I.|" TEST_PERL_MAIN
" --perl-option=\"-I.\"', '--compress', '--db1-host', 'db1', '--db1-user', 'postgres', 'backup')|[NULL]",
"perl option");
// -------------------------------------------------------------------------------------------------------------------------
cmdLineParam[cmdLineParamSize++] = "--no-online";
TEST_RESULT_STR(
strPtr(strLstJoin(perlCommand(cmdLineParamSize, cmdLineParam), "|")),
TEST_ENV_EXE "|" TEST_PERL_EXE "|-I.|" TEST_PERL_MAIN
" --perl-option=\"-I.\"', '--compress', '--db1-host', 'db1', '--db1-user', 'postgres', '--no-online',"
" 'backup')|[NULL]",
"perl option");
// -------------------------------------------------------------------------------------------------------------------------
cmdLineParam[cmdLineParamSize++] = "cmdarg1";
TEST_RESULT_STR(
strPtr(strLstJoin(perlCommand(cmdLineParamSize, cmdLineParam), "|")),
TEST_ENV_EXE "|" TEST_PERL_EXE "|-I.|" TEST_PERL_MAIN
" --perl-option=\"-I.\"', '--compress', '--db1-host', 'db1', '--db1-user', 'postgres', '--no-online', 'backup',"
" 'cmdarg1')|[NULL]",
"perl option");
strPtr(strLstJoin(perlCommand(), "|")),
TEST_ENV_EXE "|" TEST_PERL_EXE "|-I.|-MDevel::Cover=-silent,1|" TEST_PERL_MAIN "'"
", '--db-include', 'db1', '--db-include', 'db2'"
", '--perl-option', '-I.', '--perl-option', '-MDevel::Cover=-silent,1'"
", '--recovery-option', 'standby_mode=on', '--recovery-option', 'primary_conn_info=blah'"
", 'help', 'restore', 'param1', 'param2')|[NULL]", "complex options");
}
// -----------------------------------------------------------------------------------------------------------------------------