1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2024-12-14 10:13:05 +02:00
pgbackrest/lib/pgBackRest/Config/Config.pm

637 lines
25 KiB
Perl
Raw Normal View History

2014-12-16 00:20:42 +02:00
####################################################################################################################################
# CONFIG MODULE
####################################################################################################################################
New simpler configuration and consistent project/exe/path naming. * The repo-path option now always refers to the repository where backups and archive are stored, whether local or remote, so the repo-remote-path option has been removed. The new spool-path option can be used to define a location for queueing WAL segments when archiving asynchronously. Otherwise, a local repository is no longer required. * Implemented a new config format which should be far simpler to use. See the User Guide and Configuration Reference for details but for a simple configuration all options can now be placed in the stanza section. Options that are shared between stanzas can be placed in the [global] section. More complex configurations can still make use of command sections though this should be a rare use case. * The default configuration filename is now pgbackrest.conf instead of pg_backrest.conf. This was done for consistency with other naming changes but also to prevent old config files from being loaded accidentally. * The default repository name was changed from /var/lib/backup to /var/lib/pgbackrest. * Lock files are now stored in /tmp/pgbackrest by default. These days /run/pgbackrest would be the preferred location but that would require init scripts which are not part of this release. The lock-path option can be used to configure the lock directory. * Log files are now stored in /var/log/pgbackrest by default and no longer have the date appended so they can be managed with logrotate. The log-path option can be used to configure the lock directory. * Executable filename changed from pg_backrest to pgbackrest.
2016-04-14 15:30:54 +02:00
package pgBackRest::Config::Config;
2014-12-16 00:20:42 +02:00
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use English '-no_match_vars';
2014-12-16 00:20:42 +02:00
use Exporter qw(import);
our @EXPORT = qw();
use JSON::PP;
2014-12-16 00:20:42 +02:00
New simpler configuration and consistent project/exe/path naming. * The repo-path option now always refers to the repository where backups and archive are stored, whether local or remote, so the repo-remote-path option has been removed. The new spool-path option can be used to define a location for queueing WAL segments when archiving asynchronously. Otherwise, a local repository is no longer required. * Implemented a new config format which should be far simpler to use. See the User Guide and Configuration Reference for details but for a simple configuration all options can now be placed in the stanza section. Options that are shared between stanzas can be placed in the [global] section. More complex configurations can still make use of command sections though this should be a rare use case. * The default configuration filename is now pgbackrest.conf instead of pg_backrest.conf. This was done for consistency with other naming changes but also to prevent old config files from being loaded accidentally. * The default repository name was changed from /var/lib/backup to /var/lib/pgbackrest. * Lock files are now stored in /tmp/pgbackrest by default. These days /run/pgbackrest would be the preferred location but that would require init scripts which are not part of this release. The lock-path option can be used to configure the lock directory. * Log files are now stored in /var/log/pgbackrest by default and no longer have the date appended so they can be managed with logrotate. The log-path option can be used to configure the lock directory. * Executable filename changed from pg_backrest to pgbackrest.
2016-04-14 15:30:54 +02:00
use pgBackRest::Common::Exception;
use pgBackRest::Common::Ini;
use pgBackRest::Common::Io::Base;
New simpler configuration and consistent project/exe/path naming. * The repo-path option now always refers to the repository where backups and archive are stored, whether local or remote, so the repo-remote-path option has been removed. The new spool-path option can be used to define a location for queueing WAL segments when archiving asynchronously. Otherwise, a local repository is no longer required. * Implemented a new config format which should be far simpler to use. See the User Guide and Configuration Reference for details but for a simple configuration all options can now be placed in the stanza section. Options that are shared between stanzas can be placed in the [global] section. More complex configurations can still make use of command sections though this should be a rare use case. * The default configuration filename is now pgbackrest.conf instead of pg_backrest.conf. This was done for consistency with other naming changes but also to prevent old config files from being loaded accidentally. * The default repository name was changed from /var/lib/backup to /var/lib/pgbackrest. * Lock files are now stored in /tmp/pgbackrest by default. These days /run/pgbackrest would be the preferred location but that would require init scripts which are not part of this release. The lock-path option can be used to configure the lock directory. * Log files are now stored in /var/log/pgbackrest by default and no longer have the date appended so they can be managed with logrotate. The log-path option can be used to configure the lock directory. * Executable filename changed from pg_backrest to pgbackrest.
2016-04-14 15:30:54 +02:00
use pgBackRest::Common::Log;
use pgBackRest::Common::String;
use pgBackRest::Common::Wait;
use pgBackRest::LibC qw(:config :configDefine);
New simpler configuration and consistent project/exe/path naming. * The repo-path option now always refers to the repository where backups and archive are stored, whether local or remote, so the repo-remote-path option has been removed. The new spool-path option can be used to define a location for queueing WAL segments when archiving asynchronously. Otherwise, a local repository is no longer required. * Implemented a new config format which should be far simpler to use. See the User Guide and Configuration Reference for details but for a simple configuration all options can now be placed in the stanza section. Options that are shared between stanzas can be placed in the [global] section. More complex configurations can still make use of command sections though this should be a rare use case. * The default configuration filename is now pgbackrest.conf instead of pg_backrest.conf. This was done for consistency with other naming changes but also to prevent old config files from being loaded accidentally. * The default repository name was changed from /var/lib/backup to /var/lib/pgbackrest. * Lock files are now stored in /tmp/pgbackrest by default. These days /run/pgbackrest would be the preferred location but that would require init scripts which are not part of this release. The lock-path option can be used to configure the lock directory. * Log files are now stored in /var/log/pgbackrest by default and no longer have the date appended so they can be managed with logrotate. The log-path option can be used to configure the lock directory. * Executable filename changed from pg_backrest to pgbackrest.
2016-04-14 15:30:54 +02:00
use pgBackRest::Version;
####################################################################################################################################
# Export config constants and functions
####################################################################################################################################
push(@EXPORT, @{$pgBackRest::LibC::EXPORT_TAGS{config}});
push(@EXPORT, @{$pgBackRest::LibC::EXPORT_TAGS{configDefine}});
2014-12-16 00:20:42 +02:00
####################################################################################################################################
# SOURCE Constants
####################################################################################################################################
use constant CFGDEF_SOURCE_CONFIG => 'config';
push @EXPORT, qw(CFGDEF_SOURCE_CONFIG);
use constant CFGDEF_SOURCE_PARAM => 'param';
push @EXPORT, qw(CFGDEF_SOURCE_PARAM);
use constant CFGDEF_SOURCE_DEFAULT => 'default';
push @EXPORT, qw(CFGDEF_SOURCE_DEFAULT);
2014-12-16 00:20:42 +02:00
####################################################################################################################################
# Configuration section constants
####################################################################################################################################
use constant CFGDEF_SECTION_GLOBAL => 'global';
push @EXPORT, qw(CFGDEF_SECTION_GLOBAL);
use constant CFGDEF_SECTION_STANZA => 'stanza';
push @EXPORT, qw(CFGDEF_SECTION_STANZA);
####################################################################################################################################
# Module variables
####################################################################################################################################
my %oOption; # Option hash
my $strCommand; # Command (backup, archive-get, ...)
my $bInitLog = false; # Has logging been initialized yet?
####################################################################################################################################
# configLogging - configure logging based on options
####################################################################################################################################
sub configLogging
{
my $bLogInitForce = shift;
if ($bInitLog || (defined($bLogInitForce) && $bLogInitForce))
{
logLevelSet(
cfgOptionValid(CFGOPT_LOG_LEVEL_FILE) ? cfgOption(CFGOPT_LOG_LEVEL_FILE) : OFF,
cfgOptionValid(CFGOPT_LOG_LEVEL_CONSOLE) ? cfgOption(CFGOPT_LOG_LEVEL_CONSOLE) : OFF,
cfgOptionValid(CFGOPT_LOG_LEVEL_STDERR) ? cfgOption(CFGOPT_LOG_LEVEL_STDERR) : OFF,
cfgOptionValid(CFGOPT_LOG_TIMESTAMP) ? cfgOption(CFGOPT_LOG_TIMESTAMP) : undef);
$bInitLog = true;
}
}
push @EXPORT, qw(configLogging);
2014-12-16 00:20:42 +02:00
2015-01-31 22:37:59 +02:00
####################################################################################################################################
# configLoad - load configuration
#
# Additional conditions that cannot be codified by the option definitions are also tested here.
2015-01-31 22:37:59 +02:00
####################################################################################################################################
sub configLoad
2015-01-31 22:37:59 +02:00
{
my $bInitLogging = shift;
my $strBackRestBin = shift;
my $strCommandName = shift;
my $strConfigJson = shift;
# Clear option in case it was loaded before
%oOption = ();
# Set backrest bin
backrestBinSet($strBackRestBin);
2015-01-31 22:37:59 +02:00
# Set command
$strCommand = $strCommandName;
# Convert options from JSON to a hash
my $rhOption;
eval
{
# Hacky fix for backslashes that need to be escaped
$strConfigJson =~ s/\\/\\\\/g;
$rhOption = (JSON::PP->new()->allow_nonref())->decode($strConfigJson);
return true;
}
or do
{
confess &log(ASSERT, "$EVAL_ERROR" . (defined($strConfigJson) ? ":\n${strConfigJson}" : "<undef>"));
};
# Load options into final option hash
for (my $iOptionId = 0; $iOptionId < cfgOptionTotal(); $iOptionId++)
2015-01-31 22:37:59 +02:00
{
my $strOptionName = cfgOptionName($iOptionId);
# If option is not defined then it is not valid
if (!defined($rhOption->{$strOptionName}))
{
$oOption{$strOptionName}{valid} = false;
}
# Else set option
else
{
$oOption{$strOptionName}{valid} = true;
$oOption{$strOptionName}{source} =
defined($rhOption->{$strOptionName}{source}) ? $rhOption->{$strOptionName}{source} : CFGDEF_SOURCE_DEFAULT;
$oOption{$strOptionName}{reset} = false;
$oOption{$strOptionName}{negate} = false;
# If option is negated only boolean will have a value
if ($rhOption->{$strOptionName}{negate})
{
$oOption{$strOptionName}{negate} = true;
if (cfgDefOptionType($iOptionId) eq CFGDEF_TYPE_BOOLEAN)
{
$oOption{$strOptionName}{value} = false;
}
}
# Else set the value
else
{
# If option is reset, then indicate --reset should be prepended when passing the option to child processes
if ($rhOption->{$strOptionName}{reset})
{
$oOption{$strOptionName}{reset} = true;
}
if (defined($rhOption->{$strOptionName}{value}))
{
if (cfgDefOptionType($iOptionId) eq CFGDEF_TYPE_BOOLEAN)
{
$oOption{$strOptionName}{value} = $rhOption->{$strOptionName}{value} eq INI_TRUE ? true : false;
}
else
{
$oOption{$strOptionName}{value} = $rhOption->{$strOptionName}{value};
}
}
}
}
2015-01-31 22:37:59 +02:00
}
# If this is not the remote and logging is allowed (to not overwrite log levels for tests) then set the log level so that
# INFO/WARN messages can be displayed (the user may still disable them). This should be run before any WARN logging is
# generated.
if (!defined($bInitLogging) || $bInitLogging)
{
configLogging(true);
}
# Log the command begin
commandBegin();
# Neutralize the umask to make the repository file/path modes more consistent
if (cfgOptionValid(CFGOPT_NEUTRAL_UMASK) && cfgOption(CFGOPT_NEUTRAL_UMASK))
{
umask(0000);
}
# Protocol timeout should be greater than db timeout
if (cfgOptionTest(CFGOPT_DB_TIMEOUT) && cfgOptionTest(CFGOPT_PROTOCOL_TIMEOUT) &&
cfgOption(CFGOPT_PROTOCOL_TIMEOUT) <= cfgOption(CFGOPT_DB_TIMEOUT))
{
# If protocol-timeout is default then increase it to be greater than db-timeout
if (cfgOptionSource(CFGOPT_PROTOCOL_TIMEOUT) eq CFGDEF_SOURCE_DEFAULT)
{
cfgOptionSet(CFGOPT_PROTOCOL_TIMEOUT, cfgOption(CFGOPT_DB_TIMEOUT) + 30);
}
else
{
confess &log(ERROR,
"'" . cfgOption(CFGOPT_PROTOCOL_TIMEOUT) . "' is not valid for '" .
cfgOptionName(CFGOPT_PROTOCOL_TIMEOUT) . "' option\n" .
"HINT: 'protocol-timeout' option (" . cfgOption(CFGOPT_PROTOCOL_TIMEOUT) .
") should be greater than 'db-timeout' option (" . cfgOption(CFGOPT_DB_TIMEOUT) . ").",
ERROR_OPTION_INVALID_VALUE);
}
}
# Make sure that backup and db are not both remote
if (cfgOptionTest(CFGOPT_PG_HOST) && cfgOptionTest(CFGOPT_REPO_HOST))
{
confess &log(ERROR, 'db and backup cannot both be configured as remote', ERROR_CONFIG);
}
# Warn when repo-retention-full is not set
if (cfgOptionValid(CFGOPT_REPO_RETENTION_FULL) && !cfgOptionTest(CFGOPT_REPO_RETENTION_FULL))
{
&log(WARN,
"option " . cfgOptionName(CFGOPT_REPO_RETENTION_FULL) . " is not set, the repository may run out of space\n" .
"HINT: to retain full backups indefinitely (without warning), set option '" .
cfgOptionName(CFGOPT_REPO_RETENTION_FULL) . "' to the maximum.");
}
# If archive retention is valid for the command, then set archive settings
if (cfgOptionValid(CFGOPT_REPO_RETENTION_ARCHIVE))
{
my $strArchiveRetentionType = cfgOption(CFGOPT_REPO_RETENTION_ARCHIVE_TYPE, false);
my $iArchiveRetention = cfgOption(CFGOPT_REPO_RETENTION_ARCHIVE, false);
my $iFullRetention = cfgOption(CFGOPT_REPO_RETENTION_FULL, false);
my $iDifferentialRetention = cfgOption(CFGOPT_REPO_RETENTION_DIFF, false);
my $strMsgArchiveOff = "WAL segments will not be expired: option '" . cfgOptionName(CFGOPT_REPO_RETENTION_ARCHIVE_TYPE) .
"=${strArchiveRetentionType}' but ";
# If the archive retention is not explicitly set then determine what it should be set to so the user does not have to.
if (!defined($iArchiveRetention))
{
# If repo-retention-archive-type is default, then if repo-retention-full is set, set the repo-retention-archive to this
# value, else ignore archiving
if ($strArchiveRetentionType eq CFGOPTVAL_BACKUP_TYPE_FULL)
{
if (defined($iFullRetention))
{
cfgOptionSet(CFGOPT_REPO_RETENTION_ARCHIVE, $iFullRetention);
}
}
elsif ($strArchiveRetentionType eq CFGOPTVAL_BACKUP_TYPE_DIFF)
{
# if repo-retention-diff is set then user must have set it
if (defined($iDifferentialRetention))
{
cfgOptionSet(CFGOPT_REPO_RETENTION_ARCHIVE, $iDifferentialRetention);
}
else
{
&log(WARN,
$strMsgArchiveOff . "neither option '" . cfgOptionName(CFGOPT_REPO_RETENTION_ARCHIVE) .
"' nor option '" . cfgOptionName(CFGOPT_REPO_RETENTION_DIFF) . "' is set");
}
}
elsif ($strArchiveRetentionType eq CFGOPTVAL_BACKUP_TYPE_INCR)
{
&log(WARN, $strMsgArchiveOff . "option '" . cfgOptionName(CFGOPT_REPO_RETENTION_ARCHIVE) . "' is not set");
}
}
else
{
# If repo-retention-archive is set then check repo-retention-archive-type and issue a warning if the corresponding
# setting is UNDEF since UNDEF means backups will not be expired but they should be in the practice of setting this
# value even though expiring the archive itself is OK and will be performed.
if ($strArchiveRetentionType eq CFGOPTVAL_BACKUP_TYPE_DIFF && !defined($iDifferentialRetention))
{
&log(WARN,
"option '" . cfgOptionName(CFGOPT_REPO_RETENTION_DIFF) . "' is not set for '" .
cfgOptionName(CFGOPT_REPO_RETENTION_ARCHIVE_TYPE) . "=" . &CFGOPTVAL_BACKUP_TYPE_DIFF . "' \n" .
"HINT: to retain differential backups indefinitely (without warning), set option '" .
cfgOptionName(CFGOPT_REPO_RETENTION_DIFF) . "' to the maximum.");
}
}
}
return true;
}
push @EXPORT, qw(configLoad);
####################################################################################################################################
# cfgOptionIdFromIndex - return name for options that can be indexed (e.g. pg1-host, pg2-host).
####################################################################################################################################
sub cfgOptionIdFromIndex
{
my $iOptionId = shift;
my $iIndex = shift;
# If the option doesn't have a prefix it can't be indexed
$iIndex = defined($iIndex) ? $iIndex : 1;
my $strPrefix = cfgDefOptionPrefix($iOptionId);
if (!defined($strPrefix))
{
if ($iIndex > 1)
{
confess &log(ASSERT, "'" . cfgOptionName($iOptionId) . "' option does not allow indexing");
}
return $iOptionId;
}
return cfgOptionId("${strPrefix}${iIndex}" . substr(cfgOptionName($iOptionId), index(cfgOptionName($iOptionId), '-')));
}
push @EXPORT, qw(cfgOptionIdFromIndex);
####################################################################################################################################
# cfgOptionSource - how was the option set?
####################################################################################################################################
sub cfgOptionSource
{
my $iOptionId = shift;
cfgOptionValid($iOptionId, true);
return $oOption{cfgOptionName($iOptionId)}{source};
}
push @EXPORT, qw(cfgOptionSource);
####################################################################################################################################
# cfgOptionValid - is the option valid for the current command?
####################################################################################################################################
sub cfgOptionValid
{
my $iOptionId = shift;
my $bError = shift;
# If defined then this is the command help is being generated for so all valid checks should be against that command
my $iCommandId;
2018-01-23 20:34:24 +02:00
if (defined($strCommand))
{
$iCommandId = cfgCommandId($strCommand);
}
if (defined($iCommandId) && cfgDefOptionValid($iCommandId, $iOptionId))
{
return true;
}
if (defined($bError) && $bError)
{
my $strOption = cfgOptionName($iOptionId);
if (!defined($oOption{$strOption}))
{
confess &log(ASSERT, "option '${strOption}' does not exist");
}
confess &log(ASSERT, "option '${strOption}' not valid for command '" . cfgCommandName(cfgCommandGet()) . "'");
}
return false;
}
push @EXPORT, qw(cfgOptionValid);
####################################################################################################################################
# cfgOption - get option value
####################################################################################################################################
sub cfgOption
{
my $iOptionId = shift;
my $bRequired = shift;
cfgOptionValid($iOptionId, true);
my $strOption = cfgOptionName($iOptionId);
if (!defined($oOption{$strOption}{value}) && (!defined($bRequired) || $bRequired))
{
confess &log(ASSERT, "option ${strOption} is required");
}
return $oOption{$strOption}{value};
}
push @EXPORT, qw(cfgOption);
####################################################################################################################################
# cfgOptionDefault - get option default value
####################################################################################################################################
sub cfgOptionDefault
{
my $iOptionId = shift;
cfgOptionValid($iOptionId, true);
return cfgDefOptionDefault(cfgCommandId($strCommand), $iOptionId);
}
push @EXPORT, qw(cfgOptionDefault);
####################################################################################################################################
# cfgOptionSet - set option value and source
####################################################################################################################################
sub cfgOptionSet
{
my $iOptionId = shift;
my $oValue = shift;
my $bForce = shift;
my $strOption = cfgOptionName($iOptionId);
if (!cfgOptionValid($iOptionId, !defined($bForce) || !$bForce))
{
$oOption{$strOption}{valid} = true;
}
$oOption{$strOption}{source} = CFGDEF_SOURCE_PARAM;
$oOption{$strOption}{value} = $oValue;
}
push @EXPORT, qw(cfgOptionSet);
####################################################################################################################################
# cfgOptionTest - test if an option exists or has a specific value
####################################################################################################################################
sub cfgOptionTest
{
my $iOptionId = shift;
my $strValue = shift;
2015-01-31 22:37:59 +02:00
if (!cfgOptionValid($iOptionId))
{
return false;
}
if (defined($strValue))
2015-01-31 22:37:59 +02:00
{
return cfgOption($iOptionId) eq $strValue ? true : false;
2015-01-31 22:37:59 +02:00
}
return defined($oOption{cfgOptionName($iOptionId)}{value}) ? true : false;
}
push @EXPORT, qw(cfgOptionTest);
####################################################################################################################################
# cfgCommandGet - get the current command
####################################################################################################################################
sub cfgCommandGet
{
return cfgCommandId($strCommand);
}
push @EXPORT, qw(cfgCommandGet);
####################################################################################################################################
# cfgCommandTest - test that the current command is equal to the provided value
####################################################################################################################################
sub cfgCommandTest
{
my $iCommandIdTest = shift;
return cfgCommandName($iCommandIdTest) eq $strCommand;
}
push @EXPORT, qw(cfgCommandTest);
####################################################################################################################################
# commandBegin
#
# Log information about the command when it begins.
####################################################################################################################################
sub commandBegin
{
&log(
$strCommand eq cfgCommandName(CFGCMD_INFO) ? DEBUG : INFO,
"${strCommand} command begin " . BACKREST_VERSION . ':' .
cfgCommandWrite(cfgCommandId($strCommand), true, '', false, undef, true));
}
####################################################################################################################################
# commandEnd
#
# Log information about the command that ended.
####################################################################################################################################
sub commandEnd
{
my $iExitCode = shift;
my $strSignal = shift;
if (defined($strCommand))
{
&log(
$strCommand eq cfgCommandName(CFGCMD_INFO) ? DEBUG : INFO,
"${strCommand} command end: " . (defined($iExitCode) && $iExitCode != 0 ?
($iExitCode == ERROR_TERM ? "terminated on signal " .
(defined($strSignal) ? "[SIG${strSignal}]" : 'from child process') :
sprintf('aborted with exception [%03d]', $iExitCode)) :
'completed successfully'));
}
}
push @EXPORT, qw(commandEnd);
####################################################################################################################################
# cfgCommandSet - set current command (usually for triggering follow-on commands)
####################################################################################################################################
sub cfgCommandSet
{
my $iCommandId = shift;
commandEnd();
$strCommand = cfgCommandName($iCommandId);
commandBegin();
}
push @EXPORT, qw(cfgCommandSet);
####################################################################################################################################
# cfgCommandWrite - using the options for the current command, write the command string for another command
#
# For example, this can be used to write the archive-get command for recovery.conf during a restore.
####################################################################################################################################
sub cfgCommandWrite
{
my $iNewCommandId = shift;
my $bIncludeConfig = shift;
my $strExeString = shift;
my $bIncludeCommand = shift;
my $oOptionOverride = shift;
my $bDisplayOnly = shift;
# Set defaults
$strExeString = defined($strExeString) ? $strExeString : backrestBin();
$bIncludeConfig = defined($bIncludeConfig) ? $bIncludeConfig : false;
$bIncludeCommand = defined($bIncludeCommand) ? $bIncludeCommand : true;
# Iterate the options to figure out which ones are not default and need to be written out to the new command string
for (my $iOptionId = 0; $iOptionId < cfgOptionTotal(); $iOptionId++)
{
my $strOption = cfgOptionName($iOptionId);
my $bSecure = cfgDefOptionSecure($iOptionId);
# Skip option if it is secure and should not be output in logs or the command line
next if ($bSecure && !$bDisplayOnly);
# Process any option id overrides first
if (defined($oOptionOverride->{$iOptionId}))
{
if (defined($oOptionOverride->{$iOptionId}{value}))
{
$strExeString .= cfgCommandWriteOptionFormat(
$strOption, false, $bSecure, {value => $oOptionOverride->{$iOptionId}{value}});
}
}
# And process overrides passed by string - this is used by Perl compatibility functions
elsif (defined($oOptionOverride->{$strOption}))
{
if (defined($oOptionOverride->{$strOption}{value}))
{
$strExeString .= cfgCommandWriteOptionFormat(
$strOption, false, $bSecure, {value => $oOptionOverride->{$strOption}{value}});
}
}
# else look for non-default options in the current configuration
elsif (cfgDefOptionValid($iNewCommandId, $iOptionId) &&
defined($oOption{$strOption}{value}) &&
($bIncludeConfig ?
$oOption{$strOption}{source} ne CFGDEF_SOURCE_DEFAULT : $oOption{$strOption}{source} eq CFGDEF_SOURCE_PARAM))
{
my $oValue;
my $bMulti = false;
# If this is a hash then it will break up into multple command-line options
if (ref($oOption{$strOption}{value}) eq 'HASH')
{
$oValue = $oOption{$strOption}{value};
$bMulti = true;
}
# Else a single value but store it in a hash anyway to make processing below simpler
else
{
$oValue = {value => $oOption{$strOption}{value}};
}
$strExeString .= cfgCommandWriteOptionFormat($strOption, $bMulti, $bSecure, $oValue);
}
# Else is reset
elsif (cfgDefOptionValid($iNewCommandId, $iOptionId) && $oOption{$strOption}{reset})
{
$strExeString .= " --reset-${strOption}";
}
}
if ($bIncludeCommand)
{
$strExeString .= ' ' . cfgCommandName($iNewCommandId);
}
return $strExeString;
}
push @EXPORT, qw(cfgCommandWrite);
# Helper function for cfgCommandWrite() to correctly format options for command-line usage
sub cfgCommandWriteOptionFormat
{
my $strOption = shift;
my $bMulti = shift;
my $bSecure = shift;
my $oValue = shift;
# Loops though all keys in the hash
my $strOptionFormat = '';
my $strParam;
foreach my $strKey (sort(keys(%$oValue)))
{
# Get the value - if the original value was a hash then the key must be prefixed
my $strValue = $bSecure ? '<redacted>' : ($bMulti ? "${strKey}=" : '') . $$oValue{$strKey};
# Handle the no- prefix for boolean values
if (cfgDefOptionType(cfgOptionId($strOption)) eq CFGDEF_TYPE_BOOLEAN)
{
$strParam = '--' . ($strValue ? '' : 'no-') . $strOption;
}
else
{
$strParam = "--${strOption}=${strValue}";
}
# Add quotes if the value has spaces in it
$strOptionFormat .= ' ' . (index($strValue, " ") != -1 ? "\"${strParam}\"" : $strParam);
}
return $strOptionFormat;
}
2014-12-16 00:20:42 +02:00
1;