From 526acca5bd7d86a7d8b34a1eb37ec3e49e042073 Mon Sep 17 00:00:00 2001 From: David Steele Date: Fri, 22 Dec 2017 23:27:49 -0500 Subject: [PATCH] Config parsing implemented in C. --- CODING.md | 2 +- build/lib/pgBackRestBuild/Config/Build.pm | 1 + .../lib/pgBackRestBuild/Config/BuildParse.pm | 9 - build/lib/pgBackRestBuild/Config/Data.pm | 28 + doc/xml/coding.xml | 2 +- doc/xml/release.xml | 8 +- libc/Makefile.PL | 7 + libc/lib/pgBackRest/LibCAuto.pm | 1 + libc/xs/config/config.auto.xsh | 1 + src/common/errorType.c | 4 +- src/common/errorType.h | 4 +- src/config/config.auto.c | 8 + src/config/config.auto.h | 5 +- src/config/config.c | 355 ++++++++- src/config/config.h | 36 +- src/config/define.auto.c | 30 + src/config/define.auto.h | 1 + src/config/define.h | 5 + src/config/parse.auto.c | 15 +- src/config/parse.c | 720 ++++++++++++++---- src/config/parse.h | 23 - src/main.c | 6 +- src/perl/exec.c | 107 ++- src/perl/exec.h | 2 +- test/expect/mock-all-001.log | 2 +- test/lib/pgBackRestTest/Common/DefineTest.pm | 2 +- test/src/module/config/configTest.c | 78 ++ test/src/module/config/parseTest.c | 426 +++++++++-- test/src/module/perl/execTest.c | 118 +-- 29 files changed, 1646 insertions(+), 360 deletions(-) diff --git a/CODING.md b/CODING.md index 7094fbc11..83d84e7ec 100644 --- a/CODING.md +++ b/CODING.md @@ -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}`. diff --git a/build/lib/pgBackRestBuild/Config/Build.pm b/build/lib/pgBackRestBuild/Config/Build.pm index 2c5ae23e3..aaf7cc337 100644 --- a/build/lib/pgBackRestBuild/Config/Build.pm +++ b/build/lib/pgBackRestBuild/Config/Build.pm @@ -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"; diff --git a/build/lib/pgBackRestBuild/Config/BuildParse.pm b/build/lib/pgBackRestBuild/Config/BuildParse.pm index 9bf024962..b7cffcf63 100644 --- a/build/lib/pgBackRestBuild/Config/BuildParse.pm +++ b/build/lib/pgBackRestBuild/Config/BuildParse.pm @@ -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" . diff --git a/build/lib/pgBackRestBuild/Config/Data.pm b/build/lib/pgBackRestBuild/Config/Data.pm index b293c2d7b..8d07a7174 100644 --- a/build/lib/pgBackRestBuild/Config/Data.pm +++ b/build/lib/pgBackRestBuild/Config/Data.pm @@ -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, diff --git a/doc/xml/coding.xml b/doc/xml/coding.xml index 1f9bd5b3a..7874132f1 100644 --- a/doc/xml/coding.xml +++ b/doc/xml/coding.xml @@ -192,7 +192,7 @@ if (condition)

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}`.

diff --git a/doc/xml/release.xml b/doc/xml/release.xml index 010e9f01b..f54413ab2 100644 --- a/doc/xml/release.xml +++ b/doc/xml/release.xml @@ -41,6 +41,10 @@

Implement version command in C.

+ +

Config parsing implemented in C.

+
+

Add Buffer, Ini, KeyValue, List, Storage, String, StringList, Variant, and VariantList objects.

@@ -65,10 +69,6 @@

Replace cfgCommandTotal()/cfgOptionTotal() functions with constants. The constants are applicable in more cases and allow the compiler to optimize certain loops more efficiently.

- -

More config parsing in C in preparation for all config parsing in C.

-
-

Refactor code to make valgrind happy.

diff --git a/libc/Makefile.PL b/libc/Makefile.PL index 636cfe712..5688cc7ca 100644 --- a/libc/Makefile.PL +++ b/libc/Makefile.PL @@ -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', diff --git a/libc/lib/pgBackRest/LibCAuto.pm b/libc/lib/pgBackRest/LibCAuto.pm index 639f5cedc..7fc2842e6 100644 --- a/libc/lib/pgBackRest/LibCAuto.pm +++ b/libc/lib/pgBackRest/LibCAuto.pm @@ -159,6 +159,7 @@ sub libcAutoExportTag 'CFGOPT_NEUTRAL_UMASK', 'CFGOPT_ONLINE', 'CFGOPT_OUTPUT', + 'CFGOPT_PERL_OPTION', 'CFGOPT_PROCESS', 'CFGOPT_PROCESS_MAX', 'CFGOPT_PROTOCOL_TIMEOUT', diff --git a/libc/xs/config/config.auto.xsh b/libc/xs/config/config.auto.xsh index 6750f5fd3..87a6ff361 100644 --- a/libc/xs/config/config.auto.xsh +++ b/libc/xs/config/config.auto.xsh @@ -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 diff --git a/src/common/errorType.c b/src/common/errorType.c index fbb7da173..2ac1d4ed7 100644 --- a/src/common/errorType.c +++ b/src/common/errorType.c @@ -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); diff --git a/src/common/errorType.h b/src/common/errorType.h index 836c6f4b1..e568366cd 100644 --- a/src/common/errorType.h +++ b/src/common/errorType.h @@ -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); diff --git a/src/config/config.auto.c b/src/config/config.auto.c index 0c69087e0..1a69f4490 100644 --- a/src/config/config.auto.c +++ b/src/config/config.auto.c @@ -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 ( diff --git a/src/config/config.auto.h b/src/config/config.auto.h index 3632946ee..8c082f8fd 100644 --- a/src/config/config.auto.h +++ b/src/config/config.auto.h @@ -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, diff --git a/src/config/config.c b/src/config/config.c index 692abfd7f..00d944f08 100644 --- a/src/config/config.c +++ b/src/config/config.c @@ -4,6 +4,7 @@ Command and Option Configuration #include #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; +} diff --git a/src/config/config.h b/src/config/config.h index 3c86710f2..2600524be 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -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 diff --git a/src/config/define.auto.c b/src/config/define.auto.c index 9c3c46b98..30991c60c 100644 --- a/src/config/define.auto.c +++ b/src/config/define.auto.c @@ -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 ( diff --git a/src/config/define.auto.h b/src/config/define.auto.h index 7cbafde28..0e2b5a773 100644 --- a/src/config/define.auto.h +++ b/src/config/define.auto.h @@ -91,6 +91,7 @@ typedef enum cfgDefOptNeutralUmask, cfgDefOptOnline, cfgDefOptOutput, + cfgDefOptPerlOption, cfgDefOptProcess, cfgDefOptProcessMax, cfgDefOptProtocolTimeout, diff --git a/src/config/define.h b/src/config/define.h index 2cdadbd16..11e137ed9 100644 --- a/src/config/define.h +++ b/src/config/define.h @@ -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 ***********************************************************************************************************************************/ diff --git a/src/config/parse.auto.c b/src/config/parse.auto.c index 355d3231b..ef40189fb 100644 --- a/src/config/parse.auto.c +++ b/src/config/parse.auto.c @@ -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 diff --git a/src/config/parse.c b/src/config/parse.c index 1e3a9ef8f..23a689d5a 100644 --- a/src/config/parse.c +++ b/src/config/parse.c @@ -4,167 +4,41 @@ Command and Option Parse #include #include #include +#include #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) +#define PARSE_OPTION_FLAG (1 << 31) // Add a flag for negation rather than checking "-no-" -#define PARSE_NEGATE_FLAG (1 << 30) +#define PARSE_NEGATE_FLAG (1 << 30) // 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 +#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)); - - // Reset optind to 1 in case getopt_long has been called before - optind = 1; - - // Don't error automatically on unknown options - they will be processed in the loop below - opterr = false; - - // 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]); - } - // 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(); - } - // Additioal arguments are command arguments - else - { - if (parseData->commandArgList == NULL) - parseData->commandArgList = strLstNew(); - - strLstAdd(parseData->commandArgList, strNew(argList[optind - 1])); - } - - commandSet = true; - break; - - // If the option is unknown then error - case '?': - THROW(OptionInvalidError, "invalid option '%s'", argList[optind - 1]); - break; // {uncoverable - case statement does not return} - - // If the option is missing an argument then error - case ':': - THROW(OptionInvalidError, "option '%s' requires argument", argList[optind - 1]); - break; // {uncoverable - case statement does not return} - - // Parse valid option - default: - // Get option id and flags from the option code - optionId = option & PARSE_OPTION_MASK; - negate = option & PARSE_NEGATE_FLAG; - - // Make sure the option id is valid - assert(optionId < CFG_OPTION_TOTAL); - - // If the the option has not been found yet then set it - if (!parseData->parseOptionList[optionId].found) - { - parseData->parseOptionList[optionId].found = true; - parseData->parseOptionList[optionId].negate = negate; - - // Only set the argument if the option requires one - if (optionList[optionListIdx].has_arg == required_argument) - parseData->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) - 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) - 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) - if (!(cfgDefOptionType(cfgOptionDefIdFromId(optionId)) == cfgDefOptTypeHash || - cfgDefOptionType(cfgOptionDefIdFromId(optionId)) == cfgDefOptTypeList)) - { - THROW(OptionInvalidError, "option '%s' cannot have multiple arguments", cfgOptionName(optionId)); - } - - // Add the argument - strLstAdd(parseData->parseOptionList[optionId].valueList, strNew(optarg)); - } - - break; - } - - // Arg has been found - argFound = true; - } - - // Handle command not found - if (!commandSet) - { - // If there are args then error - if (argFound) - THROW(CommandRequiredError, "no command found"); - - // Otherwise set the comand to help - parseData->command = cfgCmdHelp; - } - - // Return the parse data - return 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 @@ -172,12 +46,560 @@ 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); + // Initialize configuration + cfgInit(); - // Set the command - cfgCommandSet(parseData->command); + MEM_CONTEXT_TEMP_BEGIN() + { + // Set the exe + cfgExeSet(strNew(argList[0])); - // Free the parse data - memFree(parseData); + // 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; + + // 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; + + while ((option = getopt_long(argListSize, (char **)argList, "-:", optionList, &optionListIdx)) != -1) + { + switch (option) + { + // 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() + { + 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. + CATCH(AssertError) + { + THROW(CommandInvalidError, "invalid command '%s'", argList[optind - 1]); + } + TRY_END(); + + if (cfgCommand() == cfgCmdHelp) + cfgCommandHelpSet(true); + else + commandSet = true; + } + // Additional arguments are command arguments + else + { + if (commandParamList == NULL) + commandParamList = strLstNew(); + + strLstAdd(commandParamList, strNew(argList[optind - 1])); + } + + break; + } + + // If the option is unknown then error + case '?': + THROW(OptionInvalidError, "invalid option '%s'", argList[optind - 1]); + break; // {uncoverable - case statement does not return} + + // If the option is missing an argument then error + case ':': + THROW(OptionInvalidError, "option '%s' requires argument", argList[optind - 1]); + break; // {uncoverable - case statement does not return} + + // Parse valid option + default: + // Get option id and flags from the option code + optionId = option & PARSE_OPTION_MASK; + negate = option & PARSE_NEGATE_FLAG; + + // Make sure the option id is valid + assert(optionId < CFG_OPTION_TOTAL); + + // If the the option has not been found yet then set it + if (!parseOptionList[optionId].found) + { + 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) + 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 (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 (parseOptionList[optionId].negate != negate) + THROW(OptionInvalidError, "option '%s' cannot be set and negated", cfgOptionName(optionId)); + + // Error if this option does not allow multiple arguments + if (!(cfgDefOptionType(cfgOptionDefIdFromId(optionId)) == cfgDefOptTypeHash || + cfgDefOptionType(cfgOptionDefIdFromId(optionId)) == cfgDefOptTypeList)) + { + THROW(OptionInvalidError, "option '%s' cannot have multiple arguments", cfgOptionName(optionId)); + } + + // Add the argument + strLstAdd(parseOptionList[optionId].valueList, strNew(optarg)); + } + + break; + } + + // Arg has been found + argFound = true; + } + + // Handle command not found + if (!commandSet && !cfgCommandHelp()) + { + // If there are args then error + if (argFound) + THROW(CommandRequiredError, "no command found"); + + // Otherwise set the comand to help + cfgCommandHelpSet(true); + } + + // 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(); } diff --git a/src/config/parse.h b/src/config/parse.h index 1f3388f65..b87e7abb2 100644 --- a/src/config/parse.h +++ b/src/config/parse.h @@ -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 ***********************************************************************************************************************************/ diff --git a/src/main.c b/src/main.c index 352629ca5..fb0d4d142 100644 --- a/src/main.c +++ b/src/main.c @@ -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()); } diff --git a/src/perl/exec.c b/src/perl/exec.c index 409e0db43..6a8e1f3a3 100644 --- a/src/perl/exec.c +++ b/src/perl/exec.c @@ -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 (cfgOptionNegate(optionId)) + strCatFmt(mainCallParam, ", '--no-%s'", cfgOptionName(optionId)); + // Else not negated + else { - // 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 - else + switch (cfgDefOptionType(cfgOptionDefIdFromId(optionId))) { - for (unsigned int argIdx = 0; argIdx < strLstSize(option->valueList); argIdx++) + 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) + { + for (unsigned int argIdx = 0; argIdx < strLstSize(perlOptionList); argIdx++) { - // Add to bin option list - strCatFmt(binPerlOption, " --" PARAM_PERL_OPTION "=\"%s\"", strPtr(strLstGet(parseData->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)); diff --git a/src/perl/exec.h b/src/perl/exec.h index ee3e4230b..b37696744 100644 --- a/src/perl/exec.h +++ b/src/perl/exec.h @@ -9,7 +9,7 @@ Execute Perl for Legacy Functionality /*********************************************************************************************************************************** Functions ***********************************************************************************************************************************/ -StringList *perlCommand(int argListSize, const char *argList[]); +StringList *perlCommand(); void perlExec(StringList *perlArgList); #endif diff --git a/test/expect/mock-all-001.log b/test/expect/mock-all-001.log index a922185a8..8a53b2b24 100644 --- a/test/expect/mock-all-001.log +++ b/test/expect/mock-all-001.log @@ -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) diff --git a/test/lib/pgBackRestTest/Common/DefineTest.pm b/test/lib/pgBackRestTest/Common/DefineTest.pm index fc3bdc05c..927fcce23 100644 --- a/test/lib/pgBackRestTest/Common/DefineTest.pm +++ b/test/lib/pgBackRestTest/Common/DefineTest.pm @@ -394,7 +394,7 @@ my $oTestDef = }, { &TESTDEF_NAME => 'parse', - &TESTDEF_TOTAL => 2, + &TESTDEF_TOTAL => 1, &TESTDEF_C => true, &TESTDEF_COVERAGE => diff --git a/test/src/module/config/configTest.c b/test/src/module/config/configTest.c index 661c02fc2..5c87d5094 100644 --- a/test/src/module/config/configTest.c +++ b/test/src/module/config/configTest.c @@ -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"); } } diff --git a/test/src/module/config/parseTest.c b/test/src/module/config/parseTest.c index 7171c302a..df0d1b29f 100644 --- a/test/src/module/config/parseTest.c +++ b/test/src/module/config/parseTest.c @@ -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"); } } diff --git a/test/src/module/perl/execTest.c b/test/src/module/perl/execTest.c index 4a3bb7528..6a61df619 100644 --- a/test/src/module/perl/execTest.c +++ b/test/src/module/perl/execTest.c @@ -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"); } // -----------------------------------------------------------------------------------------------------------------------------