mirror of
https://github.com/pgbackrest/pgbackrest.git
synced 2024-12-14 10:13:05 +02:00
Validate configuration options in a single pass.
By pre-calculating and storing the option dependencies in parse.auto.c validation can be completed in a single pass, which is both simpler and faster.
This commit is contained in:
parent
f06bf9e832
commit
cd5df3570b
@ -28,6 +28,7 @@ use pgBackRestBuild::Config::Data;
|
||||
use constant BLDLCL_FILE_DEFINE => 'parse';
|
||||
|
||||
use constant BLDLCL_DATA_OPTION => '01-dataOption';
|
||||
use constant BLDLCL_DATA_OPTION_RESOLVE => '01-dataOptionResolve';
|
||||
|
||||
####################################################################################################################################
|
||||
# Definitions for constants and data to build
|
||||
@ -48,6 +49,11 @@ my $rhBuild =
|
||||
{
|
||||
&BLD_SUMMARY => 'Option parse data',
|
||||
},
|
||||
|
||||
&BLDLCL_DATA_OPTION_RESOLVE =>
|
||||
{
|
||||
&BLD_SUMMARY => 'Order for option parse resolution',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -171,6 +177,75 @@ sub buildConfigParse
|
||||
|
||||
$rhBuild->{&BLD_FILE}{&BLDLCL_FILE_DEFINE}{&BLD_DATA}{&BLDLCL_DATA_OPTION}{&BLD_SOURCE} = $strBuildSource;
|
||||
|
||||
# Build option resolve order list. This allows the option validation in C to take place in a single pass.
|
||||
#
|
||||
# Always process the stanza option first since confusing error message are produced if it is missing.
|
||||
#-------------------------------------------------------------------------------------------------------------------------------
|
||||
my @stryResolveOrder = (buildConfigOptionEnum(CFGOPT_STANZA));
|
||||
my $rhResolved = {&CFGOPT_STANZA => true};
|
||||
my $bAllResolved;
|
||||
|
||||
do
|
||||
{
|
||||
# Assume that we will finish on this loop
|
||||
$bAllResolved = true;
|
||||
|
||||
# Loop through all options
|
||||
foreach my $strOption (sort(keys(%{$rhConfigDefine})))
|
||||
{
|
||||
my $bSkip = false;
|
||||
|
||||
# Check the default depend
|
||||
my $strOptionDepend =
|
||||
ref($rhConfigDefine->{$strOption}{&CFGDEF_DEPEND}) eq 'HASH' ?
|
||||
$rhConfigDefine->{$strOption}{&CFGDEF_DEPEND}{&CFGDEF_DEPEND_OPTION} :
|
||||
$rhConfigDefine->{$strOption}{&CFGDEF_DEPEND};
|
||||
|
||||
if (defined($strOptionDepend) && !$rhResolved->{$strOptionDepend})
|
||||
{
|
||||
# &log(WARN, "$strOptionDepend is not resolved");
|
||||
$bSkip = true;
|
||||
}
|
||||
|
||||
# Check the command depends
|
||||
foreach my $strCommand (sort(keys(%{$rhConfigDefine->{$strOption}{&CFGDEF_COMMAND}})))
|
||||
{
|
||||
my $strOptionDepend =
|
||||
ref($rhConfigDefine->{$strOption}{&CFGDEF_COMMAND}{$strCommand}{&CFGDEF_DEPEND}) eq 'HASH' ?
|
||||
$rhConfigDefine->{$strOption}{&CFGDEF_COMMAND}{$strCommand}{&CFGDEF_DEPEND}{&CFGDEF_DEPEND_OPTION} :
|
||||
$rhConfigDefine->{$strOption}{&CFGDEF_COMMAND}{$strCommand}{&CFGDEF_DEPEND};
|
||||
|
||||
if (defined($strOptionDepend) && !$rhResolved->{$strOptionDepend})
|
||||
{
|
||||
$bSkip = true;
|
||||
}
|
||||
}
|
||||
|
||||
# If dependency was not found try again on the next loop
|
||||
if ($bSkip)
|
||||
{
|
||||
$bAllResolved = false;
|
||||
}
|
||||
# Else add option to resolve order list
|
||||
elsif (!$rhResolved->{$strOption})
|
||||
{
|
||||
$rhResolved->{$strOption} = true;
|
||||
|
||||
for (my $iIndex = 0; $iIndex < $rhConfigDefine->{$strOption}{&CFGDEF_INDEX_TOTAL}; $iIndex++)
|
||||
{
|
||||
push(@stryResolveOrder, buildConfigOptionEnum($strOption) . ($iIndex == 0 ? '' : " + $iIndex"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
while (!$bAllResolved);
|
||||
|
||||
$rhBuild->{&BLD_FILE}{&BLDLCL_FILE_DEFINE}{&BLD_DATA}{&BLDLCL_DATA_OPTION_RESOLVE}{&BLD_SOURCE} =
|
||||
"static const ConfigOption optionResolveOrder[CFG_OPTION_TOTAL] =\n" .
|
||||
"{\n" .
|
||||
" " . join(",\n ", @stryResolveOrder) . ",\n" .
|
||||
"};\n";
|
||||
|
||||
return $rhBuild;
|
||||
}
|
||||
|
||||
|
@ -45,6 +45,10 @@
|
||||
</release-improvement-list>
|
||||
|
||||
<release-development-list>
|
||||
<release-item>
|
||||
<p>Validate configuration options in a single pass. By pre-calculating and storing the option dependencies in <file>parse.auto.c</file> validation can be completed in a single pass, which is both simpler and faster.</p>
|
||||
</release-item>
|
||||
|
||||
<release-item>
|
||||
<release-item-contributor-list>
|
||||
<release-item-ideator id="frost.stephen"/>
|
||||
|
@ -2190,3 +2190,171 @@ static const struct option optionList[] =
|
||||
.name = NULL
|
||||
}
|
||||
};
|
||||
|
||||
/***********************************************************************************************************************************
|
||||
Order for option parse resolution
|
||||
***********************************************************************************************************************************/
|
||||
static const ConfigOption optionResolveOrder[CFG_OPTION_TOTAL] =
|
||||
{
|
||||
cfgOptStanza,
|
||||
cfgOptArchiveAsync,
|
||||
cfgOptArchiveGetQueueMax,
|
||||
cfgOptArchivePushQueueMax,
|
||||
cfgOptArchiveTimeout,
|
||||
cfgOptBackupStandby,
|
||||
cfgOptBufferSize,
|
||||
cfgOptChecksumPage,
|
||||
cfgOptCmdSsh,
|
||||
cfgOptCommand,
|
||||
cfgOptCompress,
|
||||
cfgOptCompressLevel,
|
||||
cfgOptCompressLevelNetwork,
|
||||
cfgOptConfig,
|
||||
cfgOptConfigIncludePath,
|
||||
cfgOptConfigPath,
|
||||
cfgOptDbInclude,
|
||||
cfgOptDbTimeout,
|
||||
cfgOptDelta,
|
||||
cfgOptHostId,
|
||||
cfgOptLinkAll,
|
||||
cfgOptLinkMap,
|
||||
cfgOptLockPath,
|
||||
cfgOptLogLevelConsole,
|
||||
cfgOptLogLevelFile,
|
||||
cfgOptLogLevelStderr,
|
||||
cfgOptLogPath,
|
||||
cfgOptLogTimestamp,
|
||||
cfgOptManifestSaveThreshold,
|
||||
cfgOptNeutralUmask,
|
||||
cfgOptOnline,
|
||||
cfgOptOutput,
|
||||
cfgOptPerlOption,
|
||||
cfgOptPgHost,
|
||||
cfgOptPgHost + 1,
|
||||
cfgOptPgHost + 2,
|
||||
cfgOptPgHost + 3,
|
||||
cfgOptPgHost + 4,
|
||||
cfgOptPgHost + 5,
|
||||
cfgOptPgHost + 6,
|
||||
cfgOptPgHost + 7,
|
||||
cfgOptPgHostCmd,
|
||||
cfgOptPgHostCmd + 1,
|
||||
cfgOptPgHostCmd + 2,
|
||||
cfgOptPgHostCmd + 3,
|
||||
cfgOptPgHostCmd + 4,
|
||||
cfgOptPgHostCmd + 5,
|
||||
cfgOptPgHostCmd + 6,
|
||||
cfgOptPgHostCmd + 7,
|
||||
cfgOptPgHostConfig,
|
||||
cfgOptPgHostConfig + 1,
|
||||
cfgOptPgHostConfig + 2,
|
||||
cfgOptPgHostConfig + 3,
|
||||
cfgOptPgHostConfig + 4,
|
||||
cfgOptPgHostConfig + 5,
|
||||
cfgOptPgHostConfig + 6,
|
||||
cfgOptPgHostConfig + 7,
|
||||
cfgOptPgHostConfigIncludePath,
|
||||
cfgOptPgHostConfigIncludePath + 1,
|
||||
cfgOptPgHostConfigIncludePath + 2,
|
||||
cfgOptPgHostConfigIncludePath + 3,
|
||||
cfgOptPgHostConfigIncludePath + 4,
|
||||
cfgOptPgHostConfigIncludePath + 5,
|
||||
cfgOptPgHostConfigIncludePath + 6,
|
||||
cfgOptPgHostConfigIncludePath + 7,
|
||||
cfgOptPgHostConfigPath,
|
||||
cfgOptPgHostConfigPath + 1,
|
||||
cfgOptPgHostConfigPath + 2,
|
||||
cfgOptPgHostConfigPath + 3,
|
||||
cfgOptPgHostConfigPath + 4,
|
||||
cfgOptPgHostConfigPath + 5,
|
||||
cfgOptPgHostConfigPath + 6,
|
||||
cfgOptPgHostConfigPath + 7,
|
||||
cfgOptPgHostPort,
|
||||
cfgOptPgHostPort + 1,
|
||||
cfgOptPgHostPort + 2,
|
||||
cfgOptPgHostPort + 3,
|
||||
cfgOptPgHostPort + 4,
|
||||
cfgOptPgHostPort + 5,
|
||||
cfgOptPgHostPort + 6,
|
||||
cfgOptPgHostPort + 7,
|
||||
cfgOptPgHostUser,
|
||||
cfgOptPgHostUser + 1,
|
||||
cfgOptPgHostUser + 2,
|
||||
cfgOptPgHostUser + 3,
|
||||
cfgOptPgHostUser + 4,
|
||||
cfgOptPgHostUser + 5,
|
||||
cfgOptPgHostUser + 6,
|
||||
cfgOptPgHostUser + 7,
|
||||
cfgOptPgPath,
|
||||
cfgOptPgPath + 1,
|
||||
cfgOptPgPath + 2,
|
||||
cfgOptPgPath + 3,
|
||||
cfgOptPgPath + 4,
|
||||
cfgOptPgPath + 5,
|
||||
cfgOptPgPath + 6,
|
||||
cfgOptPgPath + 7,
|
||||
cfgOptPgPort,
|
||||
cfgOptPgPort + 1,
|
||||
cfgOptPgPort + 2,
|
||||
cfgOptPgPort + 3,
|
||||
cfgOptPgPort + 4,
|
||||
cfgOptPgPort + 5,
|
||||
cfgOptPgPort + 6,
|
||||
cfgOptPgPort + 7,
|
||||
cfgOptPgSocketPath,
|
||||
cfgOptPgSocketPath + 1,
|
||||
cfgOptPgSocketPath + 2,
|
||||
cfgOptPgSocketPath + 3,
|
||||
cfgOptPgSocketPath + 4,
|
||||
cfgOptPgSocketPath + 5,
|
||||
cfgOptPgSocketPath + 6,
|
||||
cfgOptPgSocketPath + 7,
|
||||
cfgOptProcess,
|
||||
cfgOptProcessMax,
|
||||
cfgOptProtocolTimeout,
|
||||
cfgOptRepoCipherType,
|
||||
cfgOptRepoHardlink,
|
||||
cfgOptRepoHost,
|
||||
cfgOptRepoHostCmd,
|
||||
cfgOptRepoHostConfig,
|
||||
cfgOptRepoHostConfigIncludePath,
|
||||
cfgOptRepoHostConfigPath,
|
||||
cfgOptRepoHostPort,
|
||||
cfgOptRepoHostUser,
|
||||
cfgOptRepoPath,
|
||||
cfgOptRepoRetentionArchive,
|
||||
cfgOptRepoRetentionArchiveType,
|
||||
cfgOptRepoRetentionDiff,
|
||||
cfgOptRepoRetentionFull,
|
||||
cfgOptRepoType,
|
||||
cfgOptResume,
|
||||
cfgOptSet,
|
||||
cfgOptSpoolPath,
|
||||
cfgOptStartFast,
|
||||
cfgOptStopAuto,
|
||||
cfgOptTablespaceMap,
|
||||
cfgOptTablespaceMapAll,
|
||||
cfgOptTest,
|
||||
cfgOptTestDelay,
|
||||
cfgOptTestPoint,
|
||||
cfgOptType,
|
||||
cfgOptArchiveCheck,
|
||||
cfgOptArchiveCopy,
|
||||
cfgOptForce,
|
||||
cfgOptRecoveryOption,
|
||||
cfgOptRepoCipherPass,
|
||||
cfgOptRepoS3Bucket,
|
||||
cfgOptRepoS3CaFile,
|
||||
cfgOptRepoS3CaPath,
|
||||
cfgOptRepoS3Endpoint,
|
||||
cfgOptRepoS3Host,
|
||||
cfgOptRepoS3Key,
|
||||
cfgOptRepoS3KeySecret,
|
||||
cfgOptRepoS3Region,
|
||||
cfgOptRepoS3Token,
|
||||
cfgOptRepoS3VerifySsl,
|
||||
cfgOptTarget,
|
||||
cfgOptTargetAction,
|
||||
cfgOptTargetExclusive,
|
||||
cfgOptTargetTimeline,
|
||||
};
|
||||
|
@ -674,204 +674,178 @@ configParse(unsigned int argListSize, const char *argList[], bool resetLogLevel)
|
||||
|
||||
// Phase 3: validate option definitions and load into configuration
|
||||
// ---------------------------------------------------------------------------------------------------------------------
|
||||
bool allResolved = false;
|
||||
bool optionResolved[CFG_OPTION_TOTAL] = {false};
|
||||
ConfigOption optionId = 0;
|
||||
|
||||
// If stanza is required but not set then many other options will likely not be set unless they were specified on the
|
||||
// command line, which leads to confusing errors like 'command requires option: pg1-path' when what is really missing is
|
||||
// the stanza. In this case start start the loop at the stanza option so the user gets a sensible error.
|
||||
if (!parseOptionList[cfgOptStanza].found && cfgDefOptionRequired(commandDefId, cfgDefOptStanza))
|
||||
optionId = cfgOptStanza;
|
||||
|
||||
do
|
||||
for (unsigned int optionOrderIdx = 0; optionOrderIdx < CFG_OPTION_TOTAL; optionOrderIdx++)
|
||||
{
|
||||
// If first loop starts partway through we know that all options cannot possibly be resolved on the first loop.
|
||||
// After that we'll assume that all options will be resolved in the next loop and set allResolved = false if we find
|
||||
// something that can't be resolved yet.
|
||||
if (optionId == 0)
|
||||
allResolved = true;
|
||||
// Validate options based on the option resolve order. This allows resolving all options in a single pass.
|
||||
ConfigOption optionId = optionResolveOrder[optionOrderIdx];
|
||||
|
||||
// Loop through all options
|
||||
for (; 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);
|
||||
|
||||
// Error if the option is not valid for this command
|
||||
if (parseOption->found && !cfgDefOptionValid(commandDefId, optionDefId))
|
||||
{
|
||||
// Get the option data parsed from the command-line
|
||||
ParseOption *parseOption = &parseOptionList[optionId];
|
||||
THROW_FMT(
|
||||
OptionInvalidError, "option '%s' not valid for command '%s'", cfgOptionName(optionId),
|
||||
cfgCommandName(cfgCommand()));
|
||||
}
|
||||
|
||||
// Get the option definition id -- will be used to look up option rules
|
||||
ConfigDefineOption optionDefId = cfgOptionDefIdFromId(optionId);
|
||||
ConfigDefineOptionType optionDefType = cfgDefOptionType(optionDefId);
|
||||
// Error if this option is secure and cannot be passed on the command line
|
||||
if (parseOption->found && parseOption->source == cfgSourceParam && cfgDefOptionSecure(optionDefId))
|
||||
{
|
||||
THROW_FMT(
|
||||
OptionInvalidError,
|
||||
"option '%s' is not allowed on the command-line\n"
|
||||
"HINT: this option could expose secrets in the process list.\n"
|
||||
"HINT: specify the option in '%s' instead.",
|
||||
cfgOptionName(optionId), cfgDefOptionDefault(commandDefId, cfgDefOptConfig));
|
||||
}
|
||||
|
||||
// Skip this option if it has already been resolved
|
||||
if (optionResolved[optionId])
|
||||
continue;
|
||||
// Error if this option does not allow multiple arguments
|
||||
if (parseOption->valueList != NULL && strLstSize(parseOption->valueList) > 1 &&
|
||||
!(cfgDefOptionType(cfgOptionDefIdFromId(optionId)) == cfgDefOptTypeHash ||
|
||||
cfgDefOptionType(cfgOptionDefIdFromId(optionId)) == cfgDefOptTypeList))
|
||||
{
|
||||
THROW_FMT(OptionInvalidError, "option '%s' cannot have multiple arguments", cfgOptionName(optionId));
|
||||
}
|
||||
|
||||
// Error if the option is not valid for this command
|
||||
if (parseOption->found && !cfgDefOptionValid(commandDefId, optionDefId))
|
||||
// Is the option valid for this command? If not, there is nothing more to do.
|
||||
cfgOptionValidSet(optionId, cfgDefOptionValid(commandDefId, optionDefId));
|
||||
|
||||
if (!cfgOptionValid(optionId))
|
||||
continue;
|
||||
|
||||
// Is the value set for this option?
|
||||
bool optionSet =
|
||||
parseOption->found && (optionDefType == cfgDefOptTypeBoolean || !parseOption->negate) &&
|
||||
!parseOption->reset;
|
||||
|
||||
// Set negate flag
|
||||
cfgOptionNegateSet(optionId, parseOption->negate);
|
||||
|
||||
// Set reset flag
|
||||
cfgOptionResetSet(optionId, parseOption->reset);
|
||||
|
||||
// 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);
|
||||
|
||||
// Get the depend option value
|
||||
const Variant *dependValue = cfgOption(dependOptionId);
|
||||
|
||||
if (dependValue != NULL)
|
||||
{
|
||||
THROW_FMT(
|
||||
OptionInvalidError, "option '%s' not valid for command '%s'", cfgOptionName(optionId),
|
||||
cfgCommandName(cfgCommand()));
|
||||
}
|
||||
|
||||
// Error if this option is secure and cannot be passed on the command line
|
||||
if (parseOption->found && parseOption->source == cfgSourceParam && cfgDefOptionSecure(optionDefId))
|
||||
{
|
||||
THROW_FMT(
|
||||
OptionInvalidError,
|
||||
"option '%s' is not allowed on the command-line\n"
|
||||
"HINT: this option could expose secrets in the process list.\n"
|
||||
"HINT: specify the option in '%s' instead.",
|
||||
cfgOptionName(optionId), cfgDefOptionDefault(commandDefId, cfgDefOptConfig));
|
||||
}
|
||||
|
||||
// Error if this option does not allow multiple arguments
|
||||
if (parseOption->valueList != NULL && strLstSize(parseOption->valueList) > 1 &&
|
||||
!(cfgDefOptionType(cfgOptionDefIdFromId(optionId)) == cfgDefOptTypeHash ||
|
||||
cfgDefOptionType(cfgOptionDefIdFromId(optionId)) == cfgDefOptTypeList))
|
||||
{
|
||||
THROW_FMT(OptionInvalidError, "option '%s' cannot have multiple arguments", cfgOptionName(optionId));
|
||||
}
|
||||
|
||||
// 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) &&
|
||||
!parseOption->reset;
|
||||
|
||||
// Set negate flag
|
||||
cfgOptionNegateSet(optionId, parseOption->negate);
|
||||
|
||||
// Set reset flag
|
||||
cfgOptionResetSet(optionId, parseOption->reset);
|
||||
|
||||
// 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])
|
||||
if (dependOptionDefType == cfgDefOptTypeBoolean)
|
||||
{
|
||||
allResolved = false;
|
||||
continue;
|
||||
if (cfgOptionBool(dependOptionId))
|
||||
dependValue = varNewStrZ("1");
|
||||
else
|
||||
dependValue = varNewStrZ("0");
|
||||
}
|
||||
}
|
||||
|
||||
// Get the depend option value
|
||||
const Variant *dependValue = cfgOption(dependOptionId);
|
||||
// Can't resolve if the depend option value is null
|
||||
if (dependValue == NULL)
|
||||
{
|
||||
dependResolved = false;
|
||||
|
||||
if (dependValue != NULL)
|
||||
// If depend not resolved and option value is set on the command-line then error. See unresolved list
|
||||
// depend below for a detailed explanation.
|
||||
if (optionSet && parseOption->source == cfgSourceParam)
|
||||
{
|
||||
if (dependOptionDefType == cfgDefOptTypeBoolean)
|
||||
{
|
||||
if (cfgOptionBool(dependOptionId))
|
||||
dependValue = varNewStrZ("1");
|
||||
else
|
||||
dependValue = varNewStrZ("0");
|
||||
}
|
||||
THROW_FMT(
|
||||
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)));
|
||||
|
||||
// Can't resolve if the depend option value is null
|
||||
if (dependValue == NULL)
|
||||
// If depend not resolved and option value is set on the command-line then error. It's OK to have
|
||||
// unresolved options in the config file because they may be there for another command. For instance,
|
||||
// spool-path is only loaded for the archive-push command when archive-async=y, and the presense of
|
||||
// spool-path in the config file should not cause an error here, it will just end up null.
|
||||
if (!dependResolved && optionSet && parseOption->source == cfgSourceParam)
|
||||
{
|
||||
dependResolved = false;
|
||||
// Get the depend option name
|
||||
String *dependOptionName = strNew(cfgOptionName(dependOptionId));
|
||||
|
||||
// if (optionSet)
|
||||
if (optionSet && parseOption->source == cfgSourceParam)
|
||||
// Build the list of possible depend values
|
||||
StringList *dependValueList = strLstNew();
|
||||
|
||||
for (unsigned int listIdx = 0;
|
||||
listIdx < cfgDefOptionDependValueTotal(commandDefId, optionDefId); listIdx++)
|
||||
{
|
||||
THROW_FMT(
|
||||
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)));
|
||||
const char *dependValue = cfgDefOptionDependValue(commandDefId, optionDefId, listIdx);
|
||||
|
||||
// If depend not resolved and option value is set on the command-line then error. It's OK to have
|
||||
// unresolved options in the config file because they may be there for another command. For instance,
|
||||
// spool-path is only loaded for the archive-push command when archive-async=y, and the presense of
|
||||
// spool-path in the config file should not cause an error here, it will just end up null.
|
||||
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 (unsigned int listIdx = 0;
|
||||
listIdx < cfgDefOptionDependValueTotal(commandDefId, optionDefId); listIdx++)
|
||||
// Build list based on depend option type
|
||||
switch (dependOptionDefType)
|
||||
{
|
||||
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:
|
||||
{
|
||||
// Boolean outputs depend option name as no-* when false
|
||||
case cfgDefOptTypeBoolean:
|
||||
{
|
||||
if (strcmp(dependValue, "0") == 0)
|
||||
dependOptionName = strNewFmt("no-%s", cfgOptionName(dependOptionId));
|
||||
if (strcmp(dependValue, "0") == 0)
|
||||
dependOptionName = strNewFmt("no-%s", cfgOptionName(dependOptionId));
|
||||
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// String is output with quotes
|
||||
case cfgDefOptTypeString:
|
||||
{
|
||||
strLstAdd(dependValueList, strNewFmt("'%s'", dependValue));
|
||||
break;
|
||||
}
|
||||
// String is output with quotes
|
||||
case cfgDefOptTypeString:
|
||||
{
|
||||
strLstAdd(dependValueList, strNewFmt("'%s'", dependValue));
|
||||
break;
|
||||
}
|
||||
|
||||
// Other types are output plain
|
||||
case cfgDefOptTypeFloat: // {uncovered - no depends of other types}
|
||||
case cfgDefOptTypeHash:
|
||||
case cfgDefOptTypeInteger:
|
||||
case cfgDefOptTypeList:
|
||||
case cfgDefOptTypeSize:
|
||||
{
|
||||
strLstAddZ(dependValueList, dependValue); // {+uncovered}
|
||||
break; // {+uncovered}
|
||||
}
|
||||
// Other types are output plain
|
||||
case cfgDefOptTypeFloat: // {uncovered - no depends of other types}
|
||||
case cfgDefOptTypeHash:
|
||||
case cfgDefOptTypeInteger:
|
||||
case cfgDefOptTypeList:
|
||||
case cfgDefOptTypeSize:
|
||||
{
|
||||
strLstAddZ(dependValueList, dependValue); // {+uncovered}
|
||||
break; // {+uncovered}
|
||||
}
|
||||
}
|
||||
|
||||
// Build the error string
|
||||
String *errorValue = strNew("");
|
||||
|
||||
if (strLstSize(dependValueList) == 1)
|
||||
errorValue = strNewFmt(" = %s", strPtr(strLstGet(dependValueList, 0)));
|
||||
else if (strLstSize(dependValueList) > 1)
|
||||
errorValue = strNewFmt(" in (%s)", strPtr(strLstJoin(dependValueList, ", ")));
|
||||
|
||||
// Throw the error
|
||||
THROW(
|
||||
OptionInvalidError,
|
||||
strPtr(
|
||||
strNewFmt(
|
||||
"option '%s' not valid without option '%s'%s", cfgOptionName(optionId),
|
||||
strPtr(dependOptionName), strPtr(errorValue))));
|
||||
}
|
||||
|
||||
// Build the error string
|
||||
String *errorValue = strNew("");
|
||||
|
||||
if (strLstSize(dependValueList) == 1)
|
||||
errorValue = strNewFmt(" = %s", strPtr(strLstGet(dependValueList, 0)));
|
||||
else if (strLstSize(dependValueList) > 1)
|
||||
errorValue = strNewFmt(" in (%s)", strPtr(strLstJoin(dependValueList, ", ")));
|
||||
|
||||
// Throw the error
|
||||
THROW(
|
||||
OptionInvalidError,
|
||||
strPtr(
|
||||
strNewFmt(
|
||||
"option '%s' not valid without option '%s'%s", cfgOptionName(optionId),
|
||||
strPtr(dependOptionName), strPtr(errorValue))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Is the option defined?
|
||||
if (optionSet && dependResolved)
|
||||
// Is the option resolved?
|
||||
if (dependResolved)
|
||||
{
|
||||
// Is the option set?
|
||||
if (optionSet)
|
||||
{
|
||||
if (optionDefType == cfgDefOptTypeBoolean)
|
||||
{
|
||||
@ -962,10 +936,10 @@ configParse(unsigned int argListSize, const char *argList[], bool resetLogLevel)
|
||||
cfgOptionSet(optionId, parseOption->source, varNewStr(value));
|
||||
}
|
||||
}
|
||||
else if (dependResolved && parseOption->negate)
|
||||
else if (parseOption->negate)
|
||||
cfgOptionSet(optionId, parseOption->source, NULL);
|
||||
// Else try to set a default
|
||||
else if (dependResolved)
|
||||
else
|
||||
{
|
||||
// Get the default value for this option
|
||||
const char *value = cfgDefOptionDefault(commandDefId, optionDefId);
|
||||
@ -985,15 +959,8 @@ configParse(unsigned int argListSize, const char *argList[], bool resetLogLevel)
|
||||
cfgOptionName(optionId), hint);
|
||||
}
|
||||
}
|
||||
|
||||
// Option is now resolved
|
||||
optionResolved[optionId] = true;
|
||||
}
|
||||
|
||||
// Restart option loop
|
||||
optionId = 0;
|
||||
}
|
||||
while (!allResolved);
|
||||
}
|
||||
}
|
||||
MEM_CONTEXT_TEMP_END();
|
||||
|
@ -552,6 +552,10 @@ testRun(void)
|
||||
StringList *argList = NULL;
|
||||
String *configFile = strNewFmt("%s/test.config", testPath());
|
||||
|
||||
TEST_RESULT_INT(
|
||||
sizeof(optionResolveOrder) / sizeof(ConfigOption), CFG_OPTION_TOTAL,
|
||||
"check that the option resolve list contains an entry for every option");
|
||||
|
||||
// -------------------------------------------------------------------------------------------------------------------------
|
||||
argList = strLstNew();
|
||||
strLstAdd(argList, strNew(TEST_BACKREST_EXE));
|
||||
@ -718,6 +722,8 @@ testRun(void)
|
||||
strLstAdd(argList, strNew("--stanza=db"));
|
||||
strLstAdd(argList, strNew("--repo1-type=s3"));
|
||||
strLstAdd(argList, strNew("--repo1-s3-key=xxx"));
|
||||
strLstAdd(argList, strNew("--repo1-s3-bucket=xxx"));
|
||||
strLstAdd(argList, strNew("--repo1-s3-endpoint=xxx"));
|
||||
strLstAdd(argList, strNew(TEST_COMMAND_BACKUP));
|
||||
TEST_ERROR(
|
||||
configParse(strLstSize(argList), strLstPtr(argList), false), OptionInvalidError,
|
||||
|
Loading…
Reference in New Issue
Block a user