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_FILE_DEFINE => 'parse';
|
||||||
|
|
||||||
use constant BLDLCL_DATA_OPTION => '01-dataOption';
|
use constant BLDLCL_DATA_OPTION => '01-dataOption';
|
||||||
|
use constant BLDLCL_DATA_OPTION_RESOLVE => '01-dataOptionResolve';
|
||||||
|
|
||||||
####################################################################################################################################
|
####################################################################################################################################
|
||||||
# Definitions for constants and data to build
|
# Definitions for constants and data to build
|
||||||
@ -48,6 +49,11 @@ my $rhBuild =
|
|||||||
{
|
{
|
||||||
&BLD_SUMMARY => 'Option parse data',
|
&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;
|
$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;
|
return $rhBuild;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,6 +45,10 @@
|
|||||||
</release-improvement-list>
|
</release-improvement-list>
|
||||||
|
|
||||||
<release-development-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>
|
||||||
<release-item-contributor-list>
|
<release-item-contributor-list>
|
||||||
<release-item-ideator id="frost.stephen"/>
|
<release-item-ideator id="frost.stephen"/>
|
||||||
|
@ -2190,3 +2190,171 @@ static const struct option optionList[] =
|
|||||||
.name = NULL
|
.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
|
// Phase 3: validate option definitions and load into configuration
|
||||||
// ---------------------------------------------------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------------------------------------------------
|
||||||
bool allResolved = false;
|
for (unsigned int optionOrderIdx = 0; optionOrderIdx < CFG_OPTION_TOTAL; optionOrderIdx++)
|
||||||
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
|
|
||||||
{
|
{
|
||||||
// If first loop starts partway through we know that all options cannot possibly be resolved on the first loop.
|
// Validate options based on the option resolve order. This allows resolving all options in a single pass.
|
||||||
// After that we'll assume that all options will be resolved in the next loop and set allResolved = false if we find
|
ConfigOption optionId = optionResolveOrder[optionOrderIdx];
|
||||||
// something that can't be resolved yet.
|
|
||||||
if (optionId == 0)
|
|
||||||
allResolved = true;
|
|
||||||
|
|
||||||
// Loop through all options
|
// Get the option data parsed from the command-line
|
||||||
for (; optionId < CFG_OPTION_TOTAL; optionId++)
|
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
|
THROW_FMT(
|
||||||
ParseOption *parseOption = &parseOptionList[optionId];
|
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
|
// Error if this option is secure and cannot be passed on the command line
|
||||||
ConfigDefineOption optionDefId = cfgOptionDefIdFromId(optionId);
|
if (parseOption->found && parseOption->source == cfgSourceParam && cfgDefOptionSecure(optionDefId))
|
||||||
ConfigDefineOptionType optionDefType = cfgDefOptionType(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
|
// Error if this option does not allow multiple arguments
|
||||||
if (optionResolved[optionId])
|
if (parseOption->valueList != NULL && strLstSize(parseOption->valueList) > 1 &&
|
||||||
continue;
|
!(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
|
// Is the option valid for this command? If not, there is nothing more to do.
|
||||||
if (parseOption->found && !cfgDefOptionValid(commandDefId, optionDefId))
|
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(
|
if (dependOptionDefType == cfgDefOptTypeBoolean)
|
||||||
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])
|
|
||||||
{
|
{
|
||||||
allResolved = false;
|
if (cfgOptionBool(dependOptionId))
|
||||||
continue;
|
dependValue = varNewStrZ("1");
|
||||||
|
else
|
||||||
|
dependValue = varNewStrZ("0");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Get the depend option value
|
// Can't resolve if the depend option value is null
|
||||||
const Variant *dependValue = cfgOption(dependOptionId);
|
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)
|
THROW_FMT(
|
||||||
{
|
OptionInvalidError, "option '%s' not valid without option '%s'", cfgOptionName(optionId),
|
||||||
if (cfgOptionBool(dependOptionId))
|
cfgOptionName(dependOptionId));
|
||||||
dependValue = varNewStrZ("1");
|
|
||||||
else
|
|
||||||
dependValue = varNewStrZ("0");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
// 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 depend not resolved and option value is set on the command-line then error. It's OK to have
|
||||||
if (dependValue == NULL)
|
// 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)
|
// Build the list of possible depend values
|
||||||
if (optionSet && parseOption->source == cfgSourceParam)
|
StringList *dependValueList = strLstNew();
|
||||||
|
|
||||||
|
for (unsigned int listIdx = 0;
|
||||||
|
listIdx < cfgDefOptionDependValueTotal(commandDefId, optionDefId); listIdx++)
|
||||||
{
|
{
|
||||||
THROW_FMT(
|
const char *dependValue = cfgDefOptionDependValue(commandDefId, optionDefId, listIdx);
|
||||||
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 depend not resolved and option value is set on the command-line then error. It's OK to have
|
// Build list based on depend option type
|
||||||
// unresolved options in the config file because they may be there for another command. For instance,
|
switch (dependOptionDefType)
|
||||||
// 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++)
|
|
||||||
{
|
{
|
||||||
const char *dependValue = cfgDefOptionDependValue(commandDefId, optionDefId, listIdx);
|
// Boolean outputs depend option name as no-* when false
|
||||||
|
case cfgDefOptTypeBoolean:
|
||||||
// Build list based on depend option type
|
|
||||||
switch (dependOptionDefType)
|
|
||||||
{
|
{
|
||||||
// Boolean outputs depend option name as no-* when false
|
if (strcmp(dependValue, "0") == 0)
|
||||||
case cfgDefOptTypeBoolean:
|
dependOptionName = strNewFmt("no-%s", cfgOptionName(dependOptionId));
|
||||||
{
|
|
||||||
if (strcmp(dependValue, "0") == 0)
|
|
||||||
dependOptionName = strNewFmt("no-%s", cfgOptionName(dependOptionId));
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// String is output with quotes
|
// String is output with quotes
|
||||||
case cfgDefOptTypeString:
|
case cfgDefOptTypeString:
|
||||||
{
|
{
|
||||||
strLstAdd(dependValueList, strNewFmt("'%s'", dependValue));
|
strLstAdd(dependValueList, strNewFmt("'%s'", dependValue));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Other types are output plain
|
// Other types are output plain
|
||||||
case cfgDefOptTypeFloat: // {uncovered - no depends of other types}
|
case cfgDefOptTypeFloat: // {uncovered - no depends of other types}
|
||||||
case cfgDefOptTypeHash:
|
case cfgDefOptTypeHash:
|
||||||
case cfgDefOptTypeInteger:
|
case cfgDefOptTypeInteger:
|
||||||
case cfgDefOptTypeList:
|
case cfgDefOptTypeList:
|
||||||
case cfgDefOptTypeSize:
|
case cfgDefOptTypeSize:
|
||||||
{
|
{
|
||||||
strLstAddZ(dependValueList, dependValue); // {+uncovered}
|
strLstAddZ(dependValueList, dependValue); // {+uncovered}
|
||||||
break; // {+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?
|
// Is the option resolved?
|
||||||
if (optionSet && dependResolved)
|
if (dependResolved)
|
||||||
|
{
|
||||||
|
// Is the option set?
|
||||||
|
if (optionSet)
|
||||||
{
|
{
|
||||||
if (optionDefType == cfgDefOptTypeBoolean)
|
if (optionDefType == cfgDefOptTypeBoolean)
|
||||||
{
|
{
|
||||||
@ -962,10 +936,10 @@ configParse(unsigned int argListSize, const char *argList[], bool resetLogLevel)
|
|||||||
cfgOptionSet(optionId, parseOption->source, varNewStr(value));
|
cfgOptionSet(optionId, parseOption->source, varNewStr(value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (dependResolved && parseOption->negate)
|
else if (parseOption->negate)
|
||||||
cfgOptionSet(optionId, parseOption->source, NULL);
|
cfgOptionSet(optionId, parseOption->source, NULL);
|
||||||
// Else try to set a default
|
// Else try to set a default
|
||||||
else if (dependResolved)
|
else
|
||||||
{
|
{
|
||||||
// Get the default value for this option
|
// Get the default value for this option
|
||||||
const char *value = cfgDefOptionDefault(commandDefId, optionDefId);
|
const char *value = cfgDefOptionDefault(commandDefId, optionDefId);
|
||||||
@ -985,15 +959,8 @@ configParse(unsigned int argListSize, const char *argList[], bool resetLogLevel)
|
|||||||
cfgOptionName(optionId), hint);
|
cfgOptionName(optionId), hint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Option is now resolved
|
|
||||||
optionResolved[optionId] = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restart option loop
|
|
||||||
optionId = 0;
|
|
||||||
}
|
}
|
||||||
while (!allResolved);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MEM_CONTEXT_TEMP_END();
|
MEM_CONTEXT_TEMP_END();
|
||||||
|
@ -552,6 +552,10 @@ testRun(void)
|
|||||||
StringList *argList = NULL;
|
StringList *argList = NULL;
|
||||||
String *configFile = strNewFmt("%s/test.config", testPath());
|
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();
|
argList = strLstNew();
|
||||||
strLstAdd(argList, strNew(TEST_BACKREST_EXE));
|
strLstAdd(argList, strNew(TEST_BACKREST_EXE));
|
||||||
@ -718,6 +722,8 @@ testRun(void)
|
|||||||
strLstAdd(argList, strNew("--stanza=db"));
|
strLstAdd(argList, strNew("--stanza=db"));
|
||||||
strLstAdd(argList, strNew("--repo1-type=s3"));
|
strLstAdd(argList, strNew("--repo1-type=s3"));
|
||||||
strLstAdd(argList, strNew("--repo1-s3-key=xxx"));
|
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));
|
strLstAdd(argList, strNew(TEST_COMMAND_BACKUP));
|
||||||
TEST_ERROR(
|
TEST_ERROR(
|
||||||
configParse(strLstSize(argList), strLstPtr(argList), false), OptionInvalidError,
|
configParse(strLstSize(argList), strLstPtr(argList), false), OptionInvalidError,
|
||||||
|
Loading…
Reference in New Issue
Block a user