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");
}
// -----------------------------------------------------------------------------------------------------------------------------