1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2025-08-10 22:21:39 +02:00

Implement help command in C.

This commit is contained in:
David Steele
2018-01-23 13:34:24 -05:00
parent 404ec30d03
commit b58a45e067
32 changed files with 2117 additions and 2674 deletions

View File

@@ -6,7 +6,7 @@ pgBackRest aims to be a simple, reliable backup and restore system that can seam
Instead of relying on traditional backup tools like tar and rsync, pgBackRest implements all backup features internally and uses a custom protocol for communicating with remote systems. Removing reliance on tar and rsync allows for better solutions to database-specific backup challenges. The custom remote protocol allows for more flexibility and limits the types of connections that are required to perform a backup which increases security.
pgBackRest [v1.26](https://github.com/pgbackrest/pgbackrest/releases/tag/release/1.26) is the current stable release. Release notes are on the [Releases](http://www.pgbackrest.org/release.html) page.
pgBackRest [v1.27](https://github.com/pgbackrest/pgbackrest/releases/tag/release/1.27) is the current stable release. Release notes are on the [Releases](http://www.pgbackrest.org/release.html) page.
## Features

View File

@@ -18,6 +18,8 @@ use pgBackRest::Common::Log;
use pgBackRest::Common::String;
use pgBackRest::Version;
use BackRestDoc::Common::DocConfig;
use pgBackRestBuild::Build::Common;
use pgBackRestBuild::Config::Data;
@@ -107,6 +109,69 @@ sub buildConfigDefineOptionEnum
push @EXPORT, qw(buildConfigDefineOptionEnum);
####################################################################################################################################
# Helper function to format help text
####################################################################################################################################
sub helpFormatText
{
my $oManifest = shift;
my $oDocRender = shift;
my $oText = shift;
my $iIndent = shift;
my $iLength = shift;
# Split the string into lines for processing
my @stryText = split("\n", trim($oManifest->variableReplace($oDocRender->processText($oText))));
my $strText;
my $iIndex = 0;
foreach my $strLine (@stryText)
{
# Add a linefeed if this is not the first line
if (defined($strText))
{
$strText .= "\n";
}
# Escape perl special characters
$strLine =~ s/\"/\\"/g;
my $strPart;
my $bFirst = true;
# Split the line for output if it's too long
do
{
($strPart, $strLine) = stringSplit($strLine, ' ', defined($strPart) ? $iLength - 4 : $iLength);
$strText .= ' ' x $iIndent;
if (!$bFirst)
{
$strText .= " ";
}
$strText .= "\"${strPart}";
if (defined($strLine))
{
$strText .= "\"\n";
}
else
{
$strText .= ($iIndex + 1 < @stryText ? '\n' : '') . '"';
}
$bFirst = false;
}
while (defined($strLine));
$iIndex++;
}
return $strText;
}
####################################################################################################################################
# Helper functions for building optional option data
####################################################################################################################################
@@ -161,6 +226,11 @@ sub renderOptional
{
my $rhOptional = shift;
my $bCommand = shift;
my $rhOptionHelp = shift;
my $oManifest = shift;
my $oDocRender = shift;
my $strCommand = shift;
my $strOption = shift;
my $strIndent = $bCommand ? ' ' : '';
my $strBuildSourceOptional;
@@ -231,6 +301,25 @@ sub renderOptional
$bSingleLine = true;
}
if ($bCommand && defined($rhOptionHelp) && $rhOptionHelp->{&CONFIG_HELP_SOURCE} eq CONFIG_HELP_SOURCE_COMMAND)
{
my $strSummary = helpFormatText($oManifest, $oDocRender, $rhOptionHelp->{&CONFIG_HELP_SUMMARY}, 0, 72);
if (length($strSummary) > 74)
{
confess("summary for command '${strCommand}', option '${strOption}' may not be greater than 72 characters");
}
$strBuildSourceOptional .=
(defined($strBuildSourceOptional) ? "\n" : '') .
"${strIndent} CFGDEFDATA_OPTION_OPTIONAL_HELP_SUMMARY(${strSummary})\n" .
"${strIndent} CFGDEFDATA_OPTION_OPTIONAL_HELP_DESCRIPTION\n" .
"${strIndent} (\n" .
helpFormatText($oManifest, $oDocRender, $rhOptionHelp->{&CONFIG_HELP_DESCRIPTION}, 20, 111) . "\n" .
"${strIndent} )\n";
}
return $strBuildSourceOptional;
}
@@ -239,6 +328,26 @@ sub renderOptional
####################################################################################################################################
sub buildConfigDefine
{
# Load help data
#-------------------------------------------------------------------------------------------------------------------------------
require BackRestDoc::Common::Doc;
require BackRestDoc::Common::DocManifest;
my $strDocPath = abs_path(dirname($0) . '/../doc');
my $oStorageDoc = new pgBackRest::Storage::Local(
$strDocPath, new pgBackRest::Storage::Posix::Driver({bFileSync => false, bPathSync => false}));
my @stryEmpty = [];
my $oManifest = new BackRestDoc::Common::DocManifest(
$oStorageDoc, \@stryEmpty, \@stryEmpty, \@stryEmpty, \@stryEmpty, undef, $strDocPath, false, false);
my $oDocRender = new BackRestDoc::Common::DocRender('text', $oManifest, false);
my $oDocConfig =
new BackRestDoc::Common::DocConfig(
new BackRestDoc::Common::Doc("${strDocPath}/xml/reference.xml"), $oDocRender);
my $hConfigHelp = $oDocConfig->{oConfigHash};
# Build command constants and data
#-------------------------------------------------------------------------------------------------------------------------------
my $rhEnum = $rhBuild->{&BLD_FILE}{&BLDLCL_FILE_DEFINE}{&BLD_ENUM}{&BLDLCL_ENUM_COMMAND};
@@ -249,6 +358,9 @@ sub buildConfigDefine
foreach my $strCommand (cfgDefineCommandList())
{
# Get command help
my $hCommandHelp = $hConfigHelp->{&CONFIG_HELP_COMMAND}{$strCommand};
# Build C enum
my $strCommandEnum = buildConfigDefineCommandEnum($strCommand);
push(@{$rhEnum->{&BLD_LIST}}, $strCommandEnum);
@@ -258,7 +370,35 @@ sub buildConfigDefine
"\n" .
" CFGDEFDATA_COMMAND\n" .
" (\n" .
" CFGDEFDATA_COMMAND_NAME(\"${strCommand}\")\n" .
" CFGDEFDATA_COMMAND_NAME(\"${strCommand}\")\n";
# Output help
if (defined($hCommandHelp))
{
$strBuildSource .=
"\n";
# Output command summary
my $strSummary = helpFormatText($oManifest, $oDocRender, $hCommandHelp->{&CONFIG_HELP_SUMMARY}, 0, 72);
if (length($strSummary) > 74)
{
confess("summary for command '${strCommand}' may not be greater than 72 characters");
}
$strBuildSource .=
" CFGDEFDATA_COMMAND_HELP_SUMMARY(${strSummary})\n";
# Output description
$strBuildSource .=
" CFGDEFDATA_COMMAND_HELP_DESCRIPTION\n" .
" (\n" .
helpFormatText($oManifest, $oDocRender, $hCommandHelp->{&CONFIG_HELP_DESCRIPTION}, 12, 119) . "\n" .
" )\n";
}
$strBuildSource .=
" )\n";
};
@@ -289,6 +429,9 @@ sub buildConfigDefine
foreach my $strOption (sort(keys(%{$rhConfigDefine})))
{
# Get option help
my $hOptionHelp = $hConfigHelp->{&CONFIG_HELP_OPTION}{$strOption};
# Build C enum
my $strOptionEnum = buildConfigDefineOptionEnum($strOption);
push(@{$rhEnum->{&BLD_LIST}}, $strOptionEnum);
@@ -317,7 +460,45 @@ sub buildConfigDefine
"\n" .
" CFGDEFDATA_OPTION_INDEX_TOTAL(" . $rhOption->{&CFGDEF_INDEX_TOTAL} . ")\n" .
" CFGDEFDATA_OPTION_NEGATE(" . ($rhOption->{&CFGDEF_NEGATE} ? 'true' : 'false') . ")\n" .
" CFGDEFDATA_OPTION_SECURE(" . ($rhOption->{&CFGDEF_SECURE} ? 'true' : 'false') . ")\n" .
" CFGDEFDATA_OPTION_SECURE(" . ($rhOption->{&CFGDEF_SECURE} ? 'true' : 'false') . ")\n";
if (defined($hOptionHelp))
{
$strBuildSource .=
"\n";
# Output section
my $strSection =
defined($hOptionHelp->{&CONFIG_HELP_SECTION}) ? $hOptionHelp->{&CONFIG_HELP_SECTION} : 'general';
if (length($strSection) > 72)
{
confess("section for option '${strOption}' may not be greater than 72 characters");
}
$strBuildSource .=
" CFGDEFDATA_OPTION_HELP_SECTION(\"${strSection}\")\n";
# Output summary
my $strSummary = helpFormatText($oManifest, $oDocRender, $hOptionHelp->{&CONFIG_HELP_SUMMARY}, 0, 72);
if (length($strSummary) > 74)
{
confess("summary for option '${strOption}' may not be greater than 72 characters");
}
$strBuildSource .=
" CFGDEFDATA_OPTION_HELP_SUMMARY(${strSummary})\n";
# Output description
$strBuildSource .=
" CFGDEFDATA_OPTION_HELP_DESCRIPTION\n" .
" (\n" .
helpFormatText($oManifest, $oDocRender, $hOptionHelp->{&CONFIG_HELP_DESCRIPTION}, 12, 119) . "\n" .
" )\n";
}
$strBuildSource .=
"\n" .
" CFGDEFDATA_OPTION_COMMAND_LIST\n" .
" (\n";
@@ -345,7 +526,9 @@ sub buildConfigDefine
if (defined($rhCommand))
{
$strBuildSourceOptionalCommand = renderOptional($rhCommand, true);
$strBuildSourceOptionalCommand = renderOptional(
$rhCommand, true, $hConfigHelp->{&CONFIG_HELP_COMMAND}{$strCommand}{&CONFIG_HELP_OPTION}{$strOption},
$oManifest, $oDocRender, $strCommand, $strOption);
if (defined($strBuildSourceOptionalCommand))
{

View File

@@ -240,14 +240,13 @@ eval
if ($oManifest->isBackRest())
{
push(@stryOutput, 'help');
push(@stryOutput, 'man');
}
}
for my $strOutput (@stryOutput)
{
if (!(($strOutput eq 'help' || $strOutput eq 'man') && $oManifest->isBackRest()))
if (!($strOutput eq 'man' && $oManifest->isBackRest()))
{
$oManifest->renderGet($strOutput);
}
@@ -267,7 +266,7 @@ eval
$oMarkdown->process();
}
elsif (($strOutput eq 'help' || $strOutput eq 'man') && $oManifest->isBackRest())
elsif ($strOutput eq 'man' && $oManifest->isBackRest())
{
# Generate the command-line help
my $oRender = new BackRestDoc::Common::DocRender('text', $oManifest, !$bNoExe);
@@ -275,16 +274,9 @@ eval
new BackRestDoc::Common::DocConfig(
new BackRestDoc::Common::Doc("${strBasePath}/xml/reference.xml"), $oRender);
if ($strOutput eq 'help')
{
$oDocConfig->helpDataWrite($oManifest);
}
else
{
$oStorageDoc->pathCreate(
"${strBasePath}/output/man", {strMode => '0770', bIgnoreExists => true, bCreateParent => true});
$oStorageDoc->put("${strBasePath}/output/man/" . lc(BACKREST_NAME) . '.1.txt', $oDocConfig->manGet($oManifest));
}
$oStorageDoc->pathCreate(
"${strBasePath}/output/man", {strMode => '0770', bIgnoreExists => true, bCreateParent => true});
$oStorageDoc->put("${strBasePath}/output/man/" . lc(BACKREST_NAME) . '.1.txt', $oDocConfig->manGet($oManifest));
}
elsif ($strOutput eq 'html')
{

View File

@@ -30,12 +30,16 @@ use constant CONFIG_HELP_NAME => 'name';
use constant CONFIG_HELP_OPTION => 'option';
push @EXPORT, qw(CONFIG_HELP_OPTION);
use constant CONFIG_HELP_SECTION => 'section';
push @EXPORT, qw(CONFIG_HELP_SECTION);
use constant CONFIG_HELP_SUMMARY => 'summary';
push @EXPORT, qw(CONFIG_HELP_SUMMARY);
use constant CONFIG_HELP_SOURCE => 'source';
push @EXPORT, qw(CONFIG_HELP_SOURCE);
use constant CONFIG_HELP_SOURCE_DEFAULT => 'default';
use constant CONFIG_HELP_SOURCE_SECTION => CONFIG_HELP_SECTION;
use constant CONFIG_HELP_SOURCE_COMMAND => CONFIG_HELP_COMMAND;
push @EXPORT, qw(CONFIG_HELP_SOURCE_COMMAND);
####################################################################################################################################
# Config Section Types
@@ -339,263 +343,6 @@ sub process
logDebugReturn($strOperation);
}
####################################################################################################################################
# helpDataWrite
#
# Write help data into a perl module so it can be accessed by backrest for command-line help.
####################################################################################################################################
sub helpDataWrite
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$oManifest
) =
logDebugParam
(
__PACKAGE__ . '->helpDataWrite', \@_,
{name => 'oManifest'}
);
# Iterate options
my $oConfigHash = $self->{oConfigHash};
my $strOptionData;
foreach my $strOption (sort(keys(%{$$oConfigHash{&CONFIG_HELP_OPTION}})))
{
my $oOptionHash = $$oConfigHash{&CONFIG_HELP_OPTION}{$strOption};
if (defined($strOptionData))
{
$strOptionData .= ",\n\n";
}
# Format the option for output
$strOptionData .=
' # ' . uc($strOption) . " Option Help\n" .
' #' . ('-' x 123) . "\n" .
" '${strOption}' =>\n" .
" {\n" .
(defined($$oOptionHash{&CONFIG_HELP_SECTION}) ? ' ' . &CONFIG_HELP_SECTION .
' => \'' . $$oOptionHash{&CONFIG_HELP_SECTION} . "',\n" : '') .
' ' . &CONFIG_HELP_SUMMARY . " =>\n" .
helpDataWriteFormatText($oManifest, $self->{oDocRender}, $$oOptionHash{&CONFIG_HELP_SUMMARY}, 16, 112) . ",\n" .
' ' . &CONFIG_HELP_DESCRIPTION . " =>\n" .
helpDataWriteFormatText($oManifest, $self->{oDocRender}, $$oOptionHash{&CONFIG_HELP_DESCRIPTION}, 16, 112) . "\n" .
" }";
}
# Iterate commands
my $strCommandData;
foreach my $strCommand (sort(keys(%{$$oConfigHash{&CONFIG_HELP_COMMAND}})))
{
my $oCommandHash = $$oConfigHash{&CONFIG_HELP_COMMAND}{$strCommand};
if (defined($strCommandData))
{
$strCommandData .= ",\n\n";
}
# Format the command for output
$strCommandData .=
' # ' . uc($strCommand) . " Command Help\n" .
' #' . ('-' x 123) . "\n" .
" '${strCommand}' =>\n" .
" {\n" .
' ' . &CONFIG_HELP_SUMMARY . " =>\n" .
helpDataWriteFormatText($oManifest, $self->{oDocRender}, $$oCommandHash{&CONFIG_HELP_SUMMARY}, 16, 112) . ",\n" .
' ' . &CONFIG_HELP_DESCRIPTION . " =>\n" .
helpDataWriteFormatText($oManifest, $self->{oDocRender}, $$oCommandHash{&CONFIG_HELP_DESCRIPTION}, 16, 112) . ",\n" .
"\n";
# Iterate options
my $strOptionData;
my $bExtraLinefeed = false;
if (defined($$oCommandHash{&CONFIG_HELP_OPTION}))
{
$strCommandData .=
' ' . CONFIG_HELP_OPTION . " =>\n" .
" {\n";
foreach my $strOption (sort(keys(%{$$oCommandHash{&CONFIG_HELP_OPTION}})))
{
my $oOptionHash = $$oCommandHash{&CONFIG_HELP_OPTION}{$strOption};
if (defined($strOptionData))
{
$strOptionData .= ",\n";
}
# If option came from the command then output details
if ($$oOptionHash{&CONFIG_HELP_SOURCE} eq CONFIG_HELP_SOURCE_COMMAND)
{
if (defined($strOptionData))
{
$strOptionData .= "\n";
}
# Format the command option for output
$strOptionData .=
' # ' . uc($strOption) . " Option Help\n" .
' #' . ('-' x 115) . "\n" .
" '${strOption}' =>\n" .
" {\n" .
' ' . &CONFIG_HELP_SUMMARY . " =>\n" .
helpDataWriteFormatText($oManifest, $self->{oDocRender},
$$oOptionHash{&CONFIG_HELP_SUMMARY}, 24, 104) . ",\n" .
' ' . &CONFIG_HELP_DESCRIPTION . " =>\n" .
helpDataWriteFormatText($oManifest, $self->{oDocRender},
$$oOptionHash{&CONFIG_HELP_DESCRIPTION}, 24, 104) . "\n" .
" }";
$bExtraLinefeed = true;
}
# Else output a reference to indicate the option is in the global option list
else
{
if ($bExtraLinefeed)
{
$strOptionData .= "\n";
$bExtraLinefeed = false;
}
$strOptionData .=
" '${strOption}' => '" . $$oOptionHash{&CONFIG_HELP_SOURCE} . "'";
}
}
$strCommandData .=
$strOptionData . "\n" .
" }\n";
}
$strCommandData .=
" }";
}
# Format the perl module
my $strHelpData =
('#' x 132) . "\n" .
"# CONFIG HELP DATA MODULE\n" .
"#\n" .
"# This module is automatically generated by doc.pl and should never be manually edited.\n" .
('#' x 132) . "\n" .
"package pgBackRest::Config::ConfigHelpData;\n" .
"\n" .
"use strict;\n" .
"use warnings FATAL => qw(all);\n" .
"use Carp qw(confess);\n" .
"\n" .
"use Exporter qw(import);\n" .
" our \@EXPORT = qw();\n" .
"\n" .
('#' x 132) . "\n" .
"# Data used by the ConfigHelp module to generate command-line help\n" .
('#' x 132) . "\n" .
"my \$oConfigHelpData =\n".
"{\n" .
" # Option Help\n" .
' #' . ('-' x 127) . "\n" .
" " . CONFIG_HELP_OPTION . " =>\n" .
" {\n" .
$strOptionData . "\n" .
" },\n" .
"\n" .
" # Command Help\n" .
' #' . ('-' x 127) . "\n" .
" " . CONFIG_HELP_COMMAND . " =>\n" .
" {\n" .
$strCommandData . "\n" .
" }\n" .
"};\n" .
"\n" .
('#' x 132) . "\n" .
"# configHelpDataGet\n" .
('#' x 132) . "\n" .
"sub configHelpDataGet\n" .
"{\n" .
" return \$oConfigHelpData;\n" .
"}\n" .
"\n" .
"push \@EXPORT, qw(configHelpDataGet);\n" .
"\n" .
"1;\n";
# Write the perl module into the lib path
$oManifest->storage()->put(dirname(dirname($0)) . '/lib/pgBackRest/Config/ConfigHelpData.pm', $strHelpData);
# Return from function and log return values if any
logDebugReturn($strOperation);
}
# Helper function for helpDataWrite() used to format text by quoting it and splitting lines so it looks good in the module.
sub helpDataWriteFormatText
{
my $oManifest = shift;
my $oDocRender = shift;
my $oText = shift;
my $iIndent = shift;
my $iLength = shift;
# Split the string into lines for processing
my @stryText = split("\n", trim($oManifest->variableReplace($oDocRender->processText($oText))));
my $strText;
my $iIndex = 0;
foreach my $strLine (@stryText)
{
# Add a linefeed if this is not the first line
if (defined($strText))
{
$strText .= " .\n";
}
# Escape perl special characters
$strLine =~ s/\@/\\@/g;
$strLine =~ s/\$/\\\$/g;
$strLine =~ s/\"/\\"/g;
my $strPart;
my $bFirst = true;
# Split the line for output if it's too long
do
{
($strPart, $strLine) = stringSplit($strLine, ' ', defined($strPart) ? $iLength - 4 : $iLength);
$strText .= ' ' x $iIndent;
if (!$bFirst)
{
$strText .= " ";
}
$strText .= "\"${strPart}";
if (defined($strLine))
{
$strText .= "\" .\n";
}
else
{
$strText .= ($iIndex + 1 < @stryText ? '\n' : '') . '"';
}
$bFirst = false;
}
while (defined($strLine));
$iIndex++;
}
return $strText;
}
####################################################################################################################################
# manGet
#

View File

@@ -851,6 +851,12 @@ sub processText
# $strBuffer =~ s/\_/\\_/g;
}
if ($strType eq 'text')
{
$strBuffer =~ s/\&mdash\;/--/g;
$strBuffer =~ s/\&lt\;/\</g;
}
$strBuffer = $self->variableReplace($strBuffer);
# Return from function and log return values if any

View File

@@ -267,11 +267,11 @@
<!-- CONFIG - REPO SECTION - REPO-PATH KEY -->
<config-key id="repo-path" name="Repository Path">
<summary>Repository path where WAL segments and backups stored.</summary>
<summary>Path where backups and archive are stored.</summary>
<text>The repository is where <backrest/> stores backup and archives WAL segments.
<text>The repository is where <backrest/> stores backups and archives WAL segments.
If you are new to backup then it will be difficult to estimate in advance how much space you'll need. The best thing to do is take some backups then record the size of different types of backups (full/incr/diff) and measure the amount of WAL generated per day. This will give you a general idea of how much space you'll need, though of course requirements will likely change over time as your database evolves.</text>
It may be difficult to estimate in advance how much space you'll need. The best thing to do is take some backups then record the size of different types of backups (full/incr/diff) and measure the amount of WAL generated per day. This will give you a general idea of how much space you'll need, though of course requirements will likely change over time as your database evolves.</text>
<example>/backup/db/backrest</example>
</config-key>
@@ -405,7 +405,7 @@
<config-key-list>
<!-- CONFIG - BACKUP SECTION - ARCHIVE-CHECK -->
<config-key id="archive-check" name="Check Archive">
<summary>Check that WAL segments are present in the archive before backup completes.</summary>
<summary>Check that WAL segments are in the archive before backup completes.</summary>
<text>Checks that all WAL segments required to make the backup consistent are present in the WAL archive. It's a good idea to leave this as the default unless you are using another method for archiving.

View File

@@ -53,6 +53,10 @@
</release-improvement-list>
<release-development-list>
<release-item>
<p>Implement <cmd>help</cmd> command in C.</p>
</release-item>
<release-item>
<p>Implement <cmd>version</cmd> command in C.</p>
</release-item>

View File

@@ -160,6 +160,8 @@ use constant ERROR_MEMORY => ERROR_MIN
push @EXPORT, qw(ERROR_MEMORY);
use constant ERROR_CIPHER => ERROR_MINIMUM + 70;
push @EXPORT, qw(ERROR_CIPHER);
use constant ERROR_PARAM_INVALID => ERROR_MINIMUM + 71;
push @EXPORT, qw(ERROR_PARAM_INVALID);
use constant ERROR_INVALID_VALUE => ERROR_MAXIMUM - 2;
push @EXPORT, qw(ERROR_INVALID_VALUE);

View File

@@ -52,7 +52,6 @@ use constant CFGDEF_SECTION_STANZA => 'stanza';
####################################################################################################################################
my %oOption; # Option hash
my $strCommand; # Command (backup, archive-get, ...)
my $strCommandHelp; # The command that help is being generate for
my $bInitLog = false; # Has logging been initialized yet?
####################################################################################################################################
@@ -133,38 +132,13 @@ sub configLoad
# Get command-line options
my %oOptionTest;
# If nothing was passed on the command line then display help
if (@ARGV == 0)
# Parse command line options
if (!GetOptions(\%oOptionTest, @stryOptionAllow))
{
cfgCommandSet(CFGCMD_HELP);
confess &log(ASSERT, "error parsing command line");
}
# Else process command line options
else
{
# Parse command line options
if (!GetOptions(\%oOptionTest, @stryOptionAllow))
{
$strCommand = cfgCommandName(CFGCMD_HELP);
return false;
}
# Validate and store options
my $bHelp = false;
if (defined($ARGV[0]) && $ARGV[0] eq cfgCommandName(CFGCMD_HELP) && defined($ARGV[1]))
{
$bHelp = true;
$strCommandHelp = $ARGV[1];
$ARGV[0] = $ARGV[1];
}
optionValidate(\%oOptionTest, $bHelp);
if ($bHelp)
{
cfgCommandSet(CFGCMD_HELP);
}
}
optionValidate(\%oOptionTest);
# 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
@@ -348,7 +322,6 @@ sub optionValueGet
sub optionValidate
{
my $oOptionTest = shift;
my $bHelp = shift;
# Check that the command is present and valid
$strCommand = $ARGV[0];
@@ -765,7 +738,7 @@ sub optionValidate
$oOption{$strOption}{value} = $strDefault if !$oOption{$strOption}{negate};
}
# Else check required
elsif (cfgDefOptionRequired($iCommandId, $iOptionId) && !$bHelp)
elsif (cfgDefOptionRequired($iCommandId, $iOptionId))
{
confess &log(ERROR,
"${strCommand} command requires option: ${strOption}" .
@@ -943,12 +916,7 @@ sub cfgOptionValid
# If defined then this is the command help is being generated for so all valid checks should be against that command
my $iCommandId;
if (defined($strCommandHelp))
{
$iCommandId = cfgCommandId($strCommandHelp);
}
# Else try to use the normal command
elsif (defined($strCommand))
if (defined($strCommand))
{
$iCommandId = cfgCommandId($strCommand);
}

View File

@@ -1,424 +0,0 @@
####################################################################################################################################
# CONFIG HELP MODULE
####################################################################################################################################
package pgBackRest::Config::ConfigHelp;
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use Exporter qw(import);
our @EXPORT = qw();
use File::Basename qw(dirname);
use pgBackRest::Common::Exception;
use pgBackRest::Common::Ini;
use pgBackRest::Common::Log;
use pgBackRest::Common::String;
use pgBackRest::Config::Config;
use pgBackRest::Version;
####################################################################################################################################
# Help types
####################################################################################################################################
use constant CONFIG_HELP_COMMAND => 'command';
push @EXPORT, qw(CONFIG_HELP_COMMAND);
use constant CONFIG_HELP_CURRENT => 'current';
push @EXPORT, qw(CONFIG_HELP_CURRENT);
use constant CONFIG_HELP_DEFAULT => 'default';
push @EXPORT, qw(CONFIG_HELP_DEFAULT);
use constant CONFIG_HELP_DESCRIPTION => 'description';
push @EXPORT, qw(CONFIG_HELP_DESCRIPTION);
use constant CONFIG_HELP_OPTION => 'option';
push @EXPORT, qw(CONFIG_HELP_OPTION);
use constant CONFIG_HELP_SECTION => 'section';
push @EXPORT, qw(CONFIG_HELP_SECTION);
use constant CONFIG_HELP_SUMMARY => 'summary';
push @EXPORT, qw(CONFIG_HELP_SUMMARY);
use constant CONFIG_HELP_SOURCE => 'source';
push @EXPORT, qw(CONFIG_HELP_SOURCE);
use constant CONFIG_HELP_SOURCE_DEFAULT => 'default';
push @EXPORT, qw(CONFIG_HELP_SOURCE_DEFAULT);
use constant CONFIG_HELP_SOURCE_SECTION => CONFIG_HELP_SECTION;
push @EXPORT, qw(CONFIG_HELP_SOURCE_SECTION);
use constant CONFIG_HELP_SOURCE_COMMAND => CONFIG_HELP_COMMAND;
push @EXPORT, qw(CONFIG_HELP_SOURCE_COMMAND);
####################################################################################################################################
# Config Section Types
####################################################################################################################################
use constant CONFIG_SECTION_COMMAND => 'command';
push @EXPORT, qw(CONFIG_SECTION_COMMAND);
use constant CONFIG_SECTION_GENERAL => 'general';
push @EXPORT, qw(CONFIG_SECTION_GENERAL);
use constant CONFIG_SECTION_LOG => 'log';
push @EXPORT, qw(CONFIG_SECTION_LOG);
use constant CONFIG_SECTION_EXPIRE => 'expire';
push @EXPORT, qw(CONFIG_SECTION_EXPIRE);
use constant CONFIG_SECTION_REPOSITORY => 'repository';
push @EXPORT, qw(CONFIG_SECTION_REPOSITORY);
####################################################################################################################################
# configHelp
#
# Display command-line help.
####################################################################################################################################
sub configHelp
{
my $strCommand = shift;
my $strOption = shift;
my $bVersion = shift;
my $bConfigResult = shift;
# Load module dynamically
require pgBackRest::Config::ConfigHelpData;
pgBackRest::Config::ConfigHelpData->import();
# Get config data
my $oConfigHelpData = configHelpDataGet();
# Build version
my $strVersion = (!$bConfigResult ? "\n" : '') . BACKREST_NAME . ' ' . BACKREST_VERSION;
# Display version
if ($bVersion)
{
syswrite(*STDOUT, "${strVersion}\n");
return;
}
# Build the title
my $strTitle;
my $strHelp;
# Since there's no command this will be general help
if (!defined($strCommand))
{
$strTitle = "General"
}
# Else check the command
else
{
$strCommand = lc($strCommand);
if (defined($oConfigHelpData->{&CONFIG_HELP_COMMAND}{$strCommand}))
{
$strTitle = "'${strCommand}' command";
# And check the option
if (defined($strOption))
{
$strOption = lc($strOption);
if (defined($$oConfigHelpData{&CONFIG_HELP_COMMAND}{$strCommand}{&CONFIG_HELP_OPTION}{$strOption}))
{
$strTitle .= " - '${strOption}' option";
}
else
{
$strHelp = "Invalid option '${strOption}' for command '${strCommand}'";
}
}
}
else
{
$strHelp = "Invalid command '${strCommand}'";
}
}
# Build the help
my $strMore;
if (!defined($strHelp))
{
my $iScreenWidth = 80;
# General help
if (!defined($strCommand))
{
$strHelp =
"Usage:\n" .
" " . BACKREST_EXE . " [options] [command]\n\n" .
"Commands:\n";
# Find longest command length
my $iCommandLength = 0;
foreach my $strCommand (sort(keys(%{$$oConfigHelpData{&CONFIG_HELP_COMMAND}})))
{
if (length($strCommand) > $iCommandLength)
{
$iCommandLength = length($strCommand);
}
}
# Output commands
foreach my $strCommand (sort(keys(%{$$oConfigHelpData{&CONFIG_HELP_COMMAND}})))
{
my $oCommand = $$oConfigHelpData{&CONFIG_HELP_COMMAND}{$strCommand};
$strHelp .= " ${strCommand}" . (' ' x ($iCommandLength - length($strCommand)));
$strHelp .=
' ' .
configHelpFormatText($$oCommand{&CONFIG_HELP_SUMMARY}, 4 + $iCommandLength + 2, false, $iScreenWidth + 1) .
"\n";
}
$strMore = '[command]';
}
# Else command help
elsif (!defined($strOption))
{
my $oCommand = $$oConfigHelpData{&CONFIG_HELP_COMMAND}{$strCommand};
$strHelp =
configHelpFormatText($$oCommand{&CONFIG_HELP_SUMMARY} . "\n\n" .
$$oCommand{&CONFIG_HELP_DESCRIPTION}, 0, true, $iScreenWidth + 1);
# Find longest option length and unique list of sections
my $iOptionLength = 0;
my $oSection = {};
if (defined($$oCommand{&CONFIG_HELP_OPTION}))
{
foreach my $strOption (sort(keys(%{$$oCommand{&CONFIG_HELP_OPTION}})))
{
if (length($strOption) > $iOptionLength)
{
$iOptionLength = length($strOption);
}
my ($oOption, $strSection) = configHelpOptionFind($oConfigHelpData, $strCommand, $strOption);
$$oSection{$strSection}{$strOption} = $oOption;
}
# Iterate sections
foreach my $strSection (sort(keys(%{$oSection})))
{
$strHelp .=
"\n\n" . ucfirst($strSection) . " Options:\n";
# Iterate options
foreach my $strOption (sort(keys(%{$$oSection{$strSection}})))
{
$strHelp .= "\n";
my $iIndent = 4 + $iOptionLength + 2;
my $oOption = $$oSection{$strSection}{$strOption};
# Set current and default values
my $strDefault = '';
if ($$oOption{&CONFIG_HELP_CURRENT} || $$oOption{&CONFIG_HELP_DEFAULT})
{
$strDefault = undef;
if ($$oOption{&CONFIG_HELP_CURRENT})
{
$strDefault .= 'current=' . $$oOption{&CONFIG_HELP_CURRENT};
}
if ($$oOption{&CONFIG_HELP_DEFAULT})
{
if (defined($strDefault))
{
$strDefault .= ', ';
}
$strDefault .= 'default=' . $$oOption{&CONFIG_HELP_DEFAULT};
}
$strDefault = " [${strDefault}]";
}
# Output help
$strHelp .= " --${strOption}" . (' ' x ($iOptionLength - length($strOption)));
$strHelp .= ' ' . configHelpFormatText(lcfirst(substr($$oOption{&CONFIG_HELP_SUMMARY}, 0,
length($$oOption{&CONFIG_HELP_SUMMARY}) - 1)) .
$strDefault, $iIndent, false, $iScreenWidth + 1);
}
}
$strMore = "${strCommand} [option]";
$strHelp .= "\n";
}
}
# Else option help
else
{
my ($oOption) = configHelpOptionFind($oConfigHelpData, $strCommand, $strOption);
# Set current and default values
my $strDefault = '';
if ($$oOption{&CONFIG_HELP_CURRENT} || $$oOption{&CONFIG_HELP_DEFAULT})
{
$strDefault = undef;
if ($$oOption{&CONFIG_HELP_CURRENT})
{
$strDefault = 'current: ' . $$oOption{&CONFIG_HELP_CURRENT};
}
if ($$oOption{&CONFIG_HELP_DEFAULT})
{
if (defined($strDefault))
{
$strDefault .= "\n";
}
$strDefault .= 'default: ' . $$oOption{&CONFIG_HELP_DEFAULT};
}
$strDefault = "\n\n${strDefault}";
}
# Output help
$strHelp =
configHelpFormatText($$oOption{&CONFIG_HELP_SUMMARY} . "\n\n" . $$oOption{&CONFIG_HELP_DESCRIPTION} .
$strDefault, 0, true, $iScreenWidth + 1);
}
}
# Output help
syswrite(*STDOUT, "${strVersion} -" . (defined($strTitle) ? " ${strTitle}" : '') . " help\n\n${strHelp}\n" .
(defined($strMore) ? 'Use \'' . BACKREST_EXE . " help ${strMore}' for more information.\n" : ''));
}
push @EXPORT, qw(configHelp);
# Helper function for configHelp() to make output look good on a console
sub configHelpFormatText
{
my $strTextIn = shift;
my $iIndent = shift;
my $bIndentFirst = shift;
my $iLength = shift;
my @stryText = split("\n", trim($strTextIn));
my $strText;
my $iIndex = 0;
foreach my $strLine (@stryText)
{
if (defined($strText))
{
$strText .= "\n";
}
my $strPart;
my $bFirst = true;
do
{
($strPart, $strLine) = stringSplit($strLine, ' ', $iLength - $iIndent);
if (!$bFirst || $bIndentFirst)
{
if (!$bFirst)
{
$strText .= "\n";
}
$strText .= ' ' x $iIndent;
}
$strText .= trim($strPart);
$bFirst = false;
}
while (defined($strLine));
$iIndex++;
}
return $strText;
}
# Helper function for configHelp() to find options. The option may be stored with the command or in the option list depending on
# whether it's generic or command-specific
sub configHelpOptionFind
{
my $oConfigHelpData = shift;
my $strCommand = shift;
my $strOption = shift;
my $strSection = CONFIG_HELP_COMMAND;
my $oOption = $$oConfigHelpData{&CONFIG_HELP_COMMAND}{$strCommand}{&CONFIG_HELP_OPTION}{$strOption};
my $iCommandId = cfgCommandId($strCommand);
my $iOptionId = cfgOptionId($strOption);
# If not found then this is an indexed value
if ($iOptionId eq -1)
{
my $strPrefix = substr($strOption, 0, index($strOption, '-'));
$iOptionId = cfgOptionId("${strPrefix}1" . substr($strOption, index($strOption, '-')));
# If still not found then error
if ($iOptionId eq -1)
{
confess &log(ASSERT, "option '${strOption}' not found in help");
}
}
if (ref(\$oOption) eq 'SCALAR')
{
$oOption = $$oConfigHelpData{&CONFIG_HELP_OPTION}{$strOption};
if (defined($$oOption{&CONFIG_HELP_SECTION}))
{
$strSection = $$oOption{&CONFIG_HELP_SECTION};
if ($strSection eq CONFIG_SECTION_COMMAND)
{
$strSection = CONFIG_SECTION_GENERAL;
}
}
else
{
$strSection = CONFIG_SECTION_GENERAL;
}
if (($strSection ne CONFIG_SECTION_GENERAL && $strSection ne CONFIG_SECTION_LOG &&
$strSection ne CONFIG_SECTION_REPOSITORY && $strSection ne CFGDEF_SECTION_STANZA &&
$strSection ne CONFIG_SECTION_EXPIRE) ||
$strSection eq $strCommand)
{
$strSection = CONFIG_HELP_COMMAND;
}
}
# Check if the current set value is default (some defaults are set at runtime and are not in the defines)
if (defined(cfgOption($iOptionId, false, false)) && cfgOptionSource($iOptionId, false) eq CONFIG_HELP_SOURCE_DEFAULT)
{
$oOption->{&CONFIG_HELP_DEFAULT} = cfgOption($iOptionId, true, false);
}
# If no default is set see if there is a default in the defines
if (!defined($oOption->{&CONFIG_HELP_DEFAULT}) && defined(cfgDefOptionDefault($iCommandId, $iOptionId)))
{
$oOption->{&CONFIG_HELP_DEFAULT} = cfgDefOptionDefault($iCommandId, $iOptionId);
}
# Format the default properly if it is a boolean
if (defined($oOption->{&CONFIG_HELP_DEFAULT}) && cfgDefOptionType($iOptionId) eq CFGDEF_TYPE_BOOLEAN)
{
$oOption->{&CONFIG_HELP_DEFAULT} = $oOption->{&CONFIG_HELP_DEFAULT} ? 'y' : 'n';
}
if (defined(cfgOption($iOptionId, false, false)) && cfgOptionSource($iOptionId, false) ne CONFIG_HELP_SOURCE_DEFAULT)
{
$oOption->{&CONFIG_HELP_CURRENT} = cfgOption($iOptionId, true, false);
if (cfgDefOptionType($iOptionId) eq CFGDEF_TYPE_BOOLEAN)
{
$$oOption{&CONFIG_HELP_CURRENT} = $oOption->{&CONFIG_HELP_CURRENT} ? 'y' : 'n';
}
}
return $oOption, $strSection;
}
1;

File diff suppressed because it is too large Load Diff

View File

@@ -41,19 +41,7 @@ sub main
# Load command line parameters and config
############################################################################################################################
backrestBinSet($strBackRestBin);
my $bConfigResult = configLoad();
# Display help and version
if (cfgCommandTest(CFGCMD_HELP) || cfgCommandTest(CFGCMD_VERSION))
{
# Load module dynamically
require pgBackRest::Config::ConfigHelp;
pgBackRest::Config::ConfigHelp->import();
# Generate help and exit
configHelp($ARGV[1], $ARGV[2], cfgCommandTest(CFGCMD_VERSION), $bConfigResult);
exitSafe(0);
}
configLoad();
# Set test options
if (cfgOptionTest(CFGOPT_TEST) && cfgOption(CFGOPT_TEST))

View File

@@ -4,6 +4,7 @@ DESTDIR=
pgbackrest: \
command/archive/push/push.o \
command/help/help.o \
command/command.o \
common/error.o \
common/errorType.o \
@@ -31,6 +32,7 @@ pgbackrest: \
main.o
$(CC) $(CFLAGS) -o pgbackrest \
command/archive/push/push.o \
command/help/help.o \
command/command.o \
common/error.o \
common/errorType.o \

321
src/command/help/help.c Normal file
View File

@@ -0,0 +1,321 @@
/***********************************************************************************************************************************
Help Command
***********************************************************************************************************************************/
#include <string.h>
#include <unistd.h>
#include "common/memContext.h"
#include "common/type.h"
#include "config/config.h"
#include "config/define.h"
#include "version.h"
/***********************************************************************************************************************************
Define the console width - use a fixed with of 80 since this should be safe on virtually all consoles
***********************************************************************************************************************************/
#define CONSOLE_WIDTH 80
/***********************************************************************************************************************************
Helper function for helpRender() to make output look good on a console
***********************************************************************************************************************************/
static String *
helpRenderText(const String *text, int indent, bool indentFirst, int length)
{
String *result = strNew("");
// Split the text into paragraphs
StringList *lineList = strLstNewSplitZ(text, "\n");
// Iterate through each paragraph and split the lines according to the line length
for (unsigned int lineIdx = 0; lineIdx < strLstSize(lineList); lineIdx++)
{
// Add LF if there is already content
if (strSize(result) != 0)
strCat(result, "\n");
// Split the paragraph into lines that don't exceed the line length
StringList *partList = strLstNewSplitSizeZ(strLstGet(lineList, lineIdx), " ", length - indent);
for (unsigned int partIdx = 0; partIdx < strLstSize(partList); partIdx++)
{
// Indent when required
if (partIdx != 0 || indentFirst)
{
if (partIdx != 0)
strCat(result, "\n");
if (strSize(strLstGet(partList, partIdx)))
strCatFmt(result, "%*s", indent, "");
}
// Add the line
strCat(result, strPtr(strLstGet(partList, partIdx)));
}
}
return result;
}
/***********************************************************************************************************************************
Helper function for helpRender() to output values as strings
***********************************************************************************************************************************/
static String *
helpRenderValue(const Variant *value)
{
String *result = NULL;
if (value != NULL)
{
if (varType(value) == varTypeBool)
{
if (varBool(value))
result = strNew("y");
else
result = strNew("n");
}
else
result = varStrForce(value);
}
return result;
}
/***********************************************************************************************************************************
Render help to a string
***********************************************************************************************************************************/
static String *
helpRender()
{
String *result = strNew(PGBACKREST_NAME " " PGBACKREST_VERSION);
MEM_CONTEXT_TEMP_BEGIN()
{
// Message for more help when it is available
String *more = NULL;
// Display general help
if (cfgCommand() == cfgCmdHelp || cfgCommand() == cfgCmdNone)
{
strCat(
result,
" - General help\n"
"\n"
"Usage:\n"
" " PGBACKREST_BIN " [options] [command]\n"
"\n"
"Commands:\n");
// Find size of longest command name
size_t commandSizeMax = 0;
for (ConfigCommand commandId = 0; commandId < CFG_COMMAND_TOTAL; commandId++)
{
if (commandId == cfgCmdNone)
continue;
if (strlen(cfgCommandName(commandId)) > commandSizeMax)
commandSizeMax = strlen(cfgCommandName(commandId));
}
// Output help for each command
for (ConfigCommand commandId = 0; commandId < CFG_COMMAND_TOTAL; commandId++)
{
if (commandId == cfgCmdNone)
continue;
const char *helpSummary = cfgDefCommandHelpSummary(cfgCommandDefIdFromId(commandId));
if (helpSummary != NULL)
{
strCatFmt(
result, " %s%*s%s\n", cfgCommandName(commandId),
(int)(commandSizeMax - strlen(cfgCommandName(commandId)) + 2), "",
strPtr(helpRenderText(strNew(helpSummary), commandSizeMax + 6, false, CONSOLE_WIDTH)));
}
}
// Construct message for more help
more = strNew("[command]");
}
else
{
ConfigCommand commandId = cfgCommand();
ConfigDefineCommand commandDefId = cfgCommandDefIdFromId(commandId);
const char *commandName = cfgCommandName(commandId);
// Output command part of title
strCatFmt(result, " - '%s' command", commandName);
// If no additional params then this is command help
if (strLstSize(cfgCommandParam()) == 0)
{
// Output command summary and description
strCatFmt(
result,
" help\n"
"\n"
"%s\n"
"\n"
"%s\n",
strPtr(helpRenderText(strNew(cfgDefCommandHelpSummary(commandDefId)), 0, true, CONSOLE_WIDTH)),
strPtr(helpRenderText(strNew(cfgDefCommandHelpDescription(commandDefId)), 0, true, CONSOLE_WIDTH)));
// Construct key/value of sections and options
KeyValue *optionKv = kvNew();
size_t optionSizeMax = 0;
for (unsigned int optionDefId = 0; optionDefId < cfgDefOptionTotal(); optionDefId++)
{
if (cfgDefOptionValid(commandDefId, optionDefId) && !cfgDefOptionInternal(optionDefId))
{
String *section = NULL;
if (cfgDefOptionHelpSection(optionDefId) != NULL)
section = strNew(cfgDefOptionHelpSection(optionDefId));
if (section == NULL ||
(!strEqZ(section, "general") && !strEqZ(section, "log") && !strEqZ(section, "repository") &&
!strEqZ(section, "stanza") && !strEqZ(section, "expire")))
{
section = strNew("command");
}
kvAdd(optionKv, varNewStr(section), varNewInt((int)optionDefId));
if (strlen(cfgDefOptionName(optionDefId)) > optionSizeMax)
optionSizeMax = strlen(cfgDefOptionName(optionDefId));
}
}
// Output sections
StringList *sectionList = strLstSort(strLstNewVarLst(kvKeyList(optionKv)), sortOrderAsc);
for (unsigned int sectionIdx = 0; sectionIdx < strLstSize(sectionList); sectionIdx++)
{
const String *section = strLstGet(sectionList, sectionIdx);
strCatFmt(result, "\n%s Options:\n\n", strPtr(strFirstUpper(strDup(section))));
// Output options
VariantList *optionList = kvGetList(optionKv, varNewStr(section));
for (unsigned int optionIdx = 0; optionIdx < varLstSize(optionList); optionIdx++)
{
ConfigDefineOption optionDefId = varInt(varLstGet(optionList, optionIdx));
ConfigOption optionId = cfgOptionIdFromDefId(optionDefId, 0);
// Get option summary
String *summary = strFirstLower(strNewN(
cfgDefOptionHelpSummary(commandDefId, optionDefId),
strlen(cfgDefOptionHelpSummary(commandDefId, optionDefId)) - 1));
// Ouput current and default values if they exist
String *defaultValue = helpRenderValue(cfgOptionDefault(optionId));
String *value = NULL;
if (cfgOptionSource(optionId) != cfgSourceDefault)
value = helpRenderValue(cfgOption(optionId));
if (value != NULL || defaultValue != NULL)
{
strCat(summary, " [");
if (value != NULL)
strCatFmt(summary, "current=%s", strPtr(value));
if (defaultValue != NULL)
{
if (value != NULL)
strCat(summary, ", ");
strCatFmt(summary, "default=%s", strPtr(defaultValue));
}
strCat(summary, "]");
}
// Output option help
strCatFmt(
result, " --%s%*s%s\n",
cfgDefOptionName(optionDefId), (int)(optionSizeMax - strlen(cfgDefOptionName(optionDefId)) + 2), "",
strPtr(helpRenderText(summary, optionSizeMax + 6, false, CONSOLE_WIDTH)));
}
}
// Construct message for more help if there are options
if (optionSizeMax > 0)
more = strNewFmt("%s [option]", commandName);
}
// Else option help for the specified command
else
{
// Make sure only one option was specified
if (strLstSize(cfgCommandParam()) > 1)
THROW(ParamInvalidError, "only one option allowed for option help");
// Ensure the option is valid
const char *optionName = strPtr(strLstGet(cfgCommandParam(), 0));
if (cfgOptionId(optionName) == -1)
THROW(OptionInvalidError, "option '%s' is not valid for command '%s'", optionName, commandName);
// Output option summary and description
ConfigOption optionId = cfgOptionId(optionName);
ConfigDefineOption optionDefId = cfgOptionDefIdFromId(optionId);
strCatFmt(
result,
" - '%s' option help\n"
"\n"
"%s\n"
"\n"
"%s\n",
optionName,
strPtr(helpRenderText(strNew(cfgDefOptionHelpSummary(commandDefId, optionDefId)), 0, true, CONSOLE_WIDTH)),
strPtr(helpRenderText(strNew(cfgDefOptionHelpDescription(commandDefId, optionDefId)), 0, true, CONSOLE_WIDTH)));
// Ouput current and default values if they exist
String *defaultValue = helpRenderValue(cfgOptionDefault(optionId));
String *value = NULL;
if (cfgOptionSource(optionId) != cfgSourceDefault)
value = helpRenderValue(cfgOption(optionId));
if (value != NULL || defaultValue != NULL)
{
strCat(result, "\n");
if (value != NULL)
strCatFmt(result, "current: %s\n", strPtr(value));
if (defaultValue != NULL)
strCatFmt(result, "default: %s\n", strPtr(defaultValue));
}
}
}
// If there is more help available output a message to let the user know
if (more != NULL)
strCatFmt(result, "\nUse '" PGBACKREST_BIN " help %s' for more information.\n", strPtr(more));
}
MEM_CONTEXT_TEMP_END();
return result;
}
/***********************************************************************************************************************************
Render help and output to stdout
***********************************************************************************************************************************/
void
cmdHelp()
{
MEM_CONTEXT_TEMP_BEGIN()
{
String *help = helpRender();
THROW_ON_SYS_ERROR(
write(STDOUT_FILENO, strPtr(help), strSize(help)) != (int)strSize(help), FileWriteError,
"unable to write help to stdout");
}
MEM_CONTEXT_TEMP_END();
}

12
src/command/help/help.h Normal file
View File

@@ -0,0 +1,12 @@
/***********************************************************************************************************************************
Help Command
***********************************************************************************************************************************/
#ifndef COMMAND_HELP_H
#define COMMAND_HELP_H
/***********************************************************************************************************************************
Functions
***********************************************************************************************************************************/
void cmdHelp();
#endif

View File

@@ -45,6 +45,7 @@ ERROR_DEFINE(ERROR_CODE_MIN + 39, FileWriteError, RuntimeError);
ERROR_DEFINE(ERROR_CODE_MIN + 57, ArchiveTimeoutError, RuntimeError);
ERROR_DEFINE(ERROR_CODE_MIN + 69, MemoryError, RuntimeError);
ERROR_DEFINE(ERROR_CODE_MIN + 70, CipherError, FormatError);
ERROR_DEFINE(ERROR_CODE_MIN + 71, ParamInvalidError, RuntimeError);
ERROR_DEFINE(ERROR_CODE_MAX, RuntimeError, RuntimeError);
@@ -70,6 +71,7 @@ static const ErrorType *errorTypeList[] =
&ArchiveTimeoutError,
&MemoryError,
&CipherError,
&ParamInvalidError,
&RuntimeError,

View File

@@ -31,6 +31,7 @@ ERROR_DECLARE(FileWriteError);
ERROR_DECLARE(ArchiveTimeoutError);
ERROR_DECLARE(MemoryError);
ERROR_DECLARE(CipherError);
ERROR_DECLARE(ParamInvalidError);
ERROR_DECLARE(RuntimeError);

View File

@@ -171,6 +171,6 @@ logInternal(LogLevel logLevel, const char *fileName, const char *functionName, i
stream = logHandleStdErr;
THROW_ON_SYS_ERROR(
write(stream, logBuffer, bufferPos) != bufferPos, FileWriteError, "unable to write log to stdout");
write(stream, logBuffer, bufferPos) != bufferPos, FileWriteError, "unable to write log to console");
}
}

View File

@@ -90,6 +90,7 @@ typedef struct ConfigOptionValue
unsigned int source:2;
Variant *value;
Variant *defaultValue;
} ConfigOptionValue;
ConfigOptionValue configOptionValue[CFG_OPTION_TOTAL];
@@ -279,7 +280,86 @@ cfgOptionDefIdFromId(ConfigOption optionId)
}
/***********************************************************************************************************************************
Get total indexed values for option
Get/set option default
***********************************************************************************************************************************/
const Variant *
cfgOptionDefault(ConfigOption optionId)
{
cfgOptionCheck(optionId);
if (configOptionValue[optionId].defaultValue == NULL)
{
ConfigDefineOption optionDefId = cfgOptionDefIdFromId(optionId);
if (cfgDefOptionDefault(cfgCommandDefIdFromId(cfgCommand()), optionDefId) != NULL)
{
MEM_CONTEXT_TEMP_BEGIN()
{
Variant *defaultValue = varNewStrZ(cfgDefOptionDefault(cfgCommandDefIdFromId(cfgCommand()), optionDefId));
MEM_CONTEXT_BEGIN(configMemContext)
{
switch (cfgDefOptionType(optionDefId))
{
case cfgDefOptTypeBoolean:
{
configOptionValue[optionId].defaultValue = varNewBool(varBoolForce(defaultValue));
break;
}
case cfgDefOptTypeFloat:
{
configOptionValue[optionId].defaultValue = varNewDbl(varDblForce(defaultValue));
break;
}
case cfgDefOptTypeInteger:
{
configOptionValue[optionId].defaultValue = varNewInt(varIntForce(defaultValue));
break;
}
case cfgDefOptTypeString:
configOptionValue[optionId].defaultValue = varDup(defaultValue);
break;
default:
THROW( // {uncoverable - others types do not have defaults yet}
AssertError, "type for option '%s' does not support defaults", cfgOptionName(optionId));
}
}
MEM_CONTEXT_END();
}
MEM_CONTEXT_TEMP_END();
}
}
return configOptionValue[optionId].defaultValue;
}
void
cfgOptionDefaultSet(ConfigOption optionId, const Variant *defaultValue)
{
MEM_CONTEXT_BEGIN(configMemContext)
{
if (configOptionValue[optionId].defaultValue != NULL)
varFree(configOptionValue[optionId].defaultValue);
configOptionValue[optionId].defaultValue = varDup(defaultValue);
if (configOptionValue[optionId].source == cfgSourceDefault)
{
if (configOptionValue[optionId].value != NULL)
varFree(configOptionValue[optionId].value);
configOptionValue[optionId].value = varDup(defaultValue);
}
}
MEM_CONTEXT_END();
}
/***********************************************************************************************************************************
Get index for option
***********************************************************************************************************************************/
int
cfgOptionIndex(ConfigOption optionId)

View File

@@ -40,6 +40,8 @@ void cfgExeSet(const String *exeParam);
const Variant *cfgOption(ConfigSource optionId);
bool cfgOptionBool(ConfigSource optionId);
const Variant *cfgOptionDefault(ConfigOption optionId);
void cfgOptionDefaultSet(ConfigOption optionId, const Variant *defaultValue);
ConfigDefineOption cfgOptionDefIdFromId(ConfigOption optionId);
double cfgOptionDbl(ConfigSource optionId);
int cfgOptionId(const char *optionName);

File diff suppressed because it is too large Load Diff

View File

@@ -13,6 +13,9 @@ Map command names to ids and vice versa.
typedef struct ConfigDefineCommandData
{
const char *name; // Command name
const char *helpSummary; // Brief summary of the command
const char *helpDescription; // Full description of the command
} ConfigDefineCommandData;
// Command macros are intended to make the command definitions easy to read and to produce good diffs.
@@ -26,6 +29,11 @@ typedef struct ConfigDefineCommandData
#define CFGDEFDATA_COMMAND_NAME(nameParam) \
.name = nameParam,
#define CFGDEFDATA_COMMAND_HELP_SUMMARY(helpSummaryParam) \
.helpSummary = helpSummaryParam,
#define CFGDEFDATA_COMMAND_HELP_DESCRIPTION(helpDescriptionParam) \
.helpDescription = helpDescriptionParam,
/***********************************************************************************************************************************
Define how an option is parsed and interacts with other options.
***********************************************************************************************************************************/
@@ -39,7 +47,12 @@ typedef struct ConfigDefineOptionData
bool negate:1; // Can the option be negated?
bool required:1; // Is the option required?
bool secure:1; // Does the option need to be redacted on logs and cmd-line?
unsigned int commandValid:15; // Bitmap for commands that is option is valid for
unsigned int commandValid:15; // Bitmap for commands that the option is valid for
const char *helpSection; // Classify the option
const char *helpSummary; // Brief summary of the option
const char *helpDescription; // Full description of the option
const void **data; // Optional data and command overrides
} ConfigDefineOptionData;
@@ -68,6 +81,13 @@ typedef struct ConfigDefineOptionData
#define CFGDEFDATA_OPTION_TYPE(typeParam) \
.type = typeParam,
#define CFGDEFDATA_OPTION_HELP_SECTION(helpSectionParam) \
.helpSection = helpSectionParam,
#define CFGDEFDATA_OPTION_HELP_SUMMARY(helpSummaryParam) \
.helpSummary = helpSummaryParam,
#define CFGDEFDATA_OPTION_HELP_DESCRIPTION(helpDescriptionParam) \
.helpDescription = helpDescriptionParam,
// Define additional types of data that can be associated with an option. Because these types are rare they are not give dedicated
// fields and are instead packed into an array which is read at runtime. This may seem inefficient but they are only accessed a
// single time during parse so space efficiency is more important than performance.
@@ -83,6 +103,8 @@ typedef enum
configDefDataTypeNameAlt,
configDefDataTypePrefix,
configDefDataTypeRequired,
configDefDataTypeHelpSummary,
configDefDataTypeHelpDescription,
} ConfigDefineDataType;
#define CFGDATA_OPTION_OPTIONAL_PUSH_LIST(type, size, data, ...) \
@@ -109,7 +131,7 @@ typedef enum
#define CFGDEFDATA_OPTION_OPTIONAL_ALLOW_RANGE(rangeMinParam, rangeMaxParam) \
CFGDATA_OPTION_OPTIONAL_PUSH_LIST( \
configDefDataTypeAllowRange, 2, 0, (const void *)(intptr_t)(int32)(rangeMinParam * 100), \
configDefDataTypeAllowRange, 2, 0, (const void *)(intptr_t)(int32)(rangeMinParam * 100), \
(const void *)(intptr_t)(int32)(rangeMaxParam * 100)),
#define CFGDEFDATA_OPTION_OPTIONAL_NAME_ALT(nameAltParam) \
@@ -134,6 +156,11 @@ typedef enum
#define CFGDEFDATA_OPTION_OPTIONAL_REQUIRED(commandOptionRequired) \
CFGDATA_OPTION_OPTIONAL_PUSH(configDefDataTypeRequired, 0, commandOptionRequired),
#define CFGDEFDATA_OPTION_OPTIONAL_HELP_SUMMARY(helpSummaryParam) \
CFGDATA_OPTION_OPTIONAL_PUSH_LIST(configDefDataTypeHelpSummary, 1, 0, helpSummaryParam),
#define CFGDEFDATA_OPTION_OPTIONAL_HELP_DESCRIPTION(helpDescriptionParam) \
CFGDATA_OPTION_OPTIONAL_PUSH_LIST(configDefDataTypeHelpDescription, 1, 0, helpDescriptionParam),
/***********************************************************************************************************************************
Include the automatically generated configuration data.
***********************************************************************************************************************************/
@@ -244,6 +271,26 @@ cfgDefCommandOptionCheck(ConfigDefineCommand commandDefId, ConfigDefineOption op
cfgDefOptionCheck(optionDefId);
}
/***********************************************************************************************************************************
Command help description
***********************************************************************************************************************************/
const char *
cfgDefCommandHelpDescription(ConfigDefineCommand commandDefId)
{
cfgDefCommandCheck(commandDefId);
return configDefineCommandData[commandDefId].helpDescription;
}
/***********************************************************************************************************************************
Command help summary
***********************************************************************************************************************************/
const char *
cfgDefCommandHelpSummary(ConfigDefineCommand commandDefId)
{
cfgDefCommandCheck(commandDefId);
return configDefineCommandData[commandDefId].helpSummary;
}
/***********************************************************************************************************************************
Option allow lists
***********************************************************************************************************************************/
@@ -403,6 +450,48 @@ cfgDefOptionDependValueValid(ConfigDefineCommand commandDefId, ConfigDefineOptio
return false;
}
/***********************************************************************************************************************************
Option help description
***********************************************************************************************************************************/
const char *
cfgDefOptionHelpDescription(ConfigDefineCommand commandDefId, ConfigDefineOption optionDefId)
{
cfgDefCommandOptionCheck(commandDefId, optionDefId);
CONFIG_DEFINE_DATA_FIND(commandDefId, optionDefId, configDefDataTypeHelpDescription);
if (dataDefFound)
return (char *)dataDefList[0];
return configDefineOptionData[optionDefId].helpDescription;
}
/***********************************************************************************************************************************
Option help section
***********************************************************************************************************************************/
const char *
cfgDefOptionHelpSection(ConfigDefineOption optionDefId)
{
cfgDefOptionCheck(optionDefId);
return configDefineOptionData[optionDefId].helpSection;
}
/***********************************************************************************************************************************
Option help summary
***********************************************************************************************************************************/
const char *
cfgDefOptionHelpSummary(ConfigDefineCommand commandDefId, ConfigDefineOption optionDefId)
{
cfgDefCommandOptionCheck(commandDefId, optionDefId);
CONFIG_DEFINE_DATA_FIND(commandDefId, optionDefId, configDefDataTypeHelpSummary);
if (dataDefFound)
return (char *)dataDefList[0];
return configDefineOptionData[optionDefId].helpSummary;
}
/***********************************************************************************************************************************
Get total indexed values for option
***********************************************************************************************************************************/

View File

@@ -27,6 +27,8 @@ Functions
***********************************************************************************************************************************/
unsigned int cfgDefCommandTotal();
void cfgDefCommandCheck(ConfigDefineCommand commandDefId);
const char *cfgDefCommandHelpDescription(ConfigDefineCommand commandDefId);
const char *cfgDefCommandHelpSummary(ConfigDefineCommand commandDefId);
bool cfgDefOptionAllowList(ConfigDefineCommand commandDefId, ConfigDefineOption optionDefId);
int cfgDefOptionAllowListValueTotal(ConfigDefineCommand commandDefId, ConfigDefineOption optionDefId);
@@ -42,6 +44,9 @@ ConfigDefineOption cfgDefOptionDependOption(ConfigDefineCommand commandDefId, Co
int cfgDefOptionDependValueTotal(ConfigDefineCommand commandDefId, ConfigDefineOption optionDefId);
bool cfgDefOptionDependValueValid(ConfigDefineCommand commandDefId, ConfigDefineOption optionDefId, const char *value);
const char *cfgDefOptionDependValue(ConfigDefineCommand commandDefId, ConfigDefineOption optionDefId, int valueId);
const char *cfgDefOptionHelpDescription(ConfigDefineCommand commandDefId, ConfigDefineOption optionDefId);
const char *cfgDefOptionHelpSection(ConfigDefineOption optionDefId);
const char *cfgDefOptionHelpSummary(ConfigDefineCommand commandDefId, ConfigDefineOption optionDefId);
int cfgDefOptionIndexTotal(ConfigDefineOption optionDefId);
bool cfgDefOptionInternal(ConfigDefineOption optionDefId);
const char *cfgDefOptionName(ConfigDefineOption optionDefId);

View File

@@ -34,6 +34,22 @@ cfgLoad(int argListSize, const char *argList[])
logTimestamp = cfgOptionBool(cfgOptLogTimestamp);
logInit(logLevelConsole, logLevelStdErr, logTimestamp);
// Set default for backup-cmd
if (cfgOptionValid(cfgOptBackupHost) && cfgOption(cfgOptBackupHost) != NULL &&
cfgOptionSource(cfgOptBackupCmd) == cfgSourceDefault)
{
cfgOptionDefaultSet(cfgOptBackupCmd, varNewStr(cfgExe()));
}
if (cfgOptionValid(cfgOptDbCmd))
{
for (int optionIdx = 0; optionIdx <= cfgOptionIndexTotal(cfgOptDbHost); optionIdx++)
{
if (cfgOption(cfgOptDbHost + optionIdx) != NULL && cfgOptionSource(cfgOptDbCmd + optionIdx) == cfgSourceDefault)
cfgOptionDefaultSet(cfgOptDbCmd + optionIdx, varNewStr(cfgExe()));
}
}
}
MEM_CONTEXT_TEMP_END();
}

View File

@@ -5,6 +5,7 @@ Main
#include <stdlib.h>
#include "command/archive/push/push.h"
#include "command/help/help.h"
#include "command/command.h"
#include "common/error.h"
#include "common/exit.h"
@@ -21,9 +22,17 @@ int main(int argListSize, const char *argList[])
// -------------------------------------------------------------------------------------------------------------------------
cfgLoad(argListSize, argList);
// Display help
// -------------------------------------------------------------------------------------------------------------------------
if (cfgCommandHelp())
{
cmdHelp();
exit(0);
}
// Display version
// -------------------------------------------------------------------------------------------------------------------------
if (!cfgCommandHelp() && cfgCommand() == cfgCmdVersion)
if (cfgCommand() == cfgCmdVersion)
{
printf(PGBACKREST_NAME " " PGBACKREST_VERSION "\n");
fflush(stdout);

View File

@@ -9,6 +9,11 @@ Official name of the software, also used for Perl package name
***********************************************************************************************************************************/
#define PGBACKREST_NAME "pgBackRest"
/***********************************************************************************************************************************
Standard binary name
***********************************************************************************************************************************/
#define PGBACKREST_BIN "pgbackrest"
/***********************************************************************************************************************************
Version of the software. Currently this value is maintained in Version.pm and updated by test.pl.
***********************************************************************************************************************************/

View File

@@ -93,8 +93,8 @@ Repository Options:
[default=backrest]
--repo-cipher-pass repository cipher passphrase
--repo-cipher-type cipher used to encrypt the repository [default=none]
--repo-path repository path where WAL segments and backups
stored [default=/var/lib/pgbackrest]
--repo-path path where backups and archive are stored
[default=/var/lib/pgbackrest]
--repo-s3-bucket s3 repository bucket
--repo-s3-ca-file s3 SSL CA File
--repo-s3-ca-path s3 SSL CA Path
@@ -147,8 +147,8 @@ before running the check command.
Command Options:
--archive-check check that WAL segments are present in the archive
before backup completes [default=y]
--archive-check check that WAL segments are in the archive before
backup completes [default=y]
--archive-timeout archive timeout [default=60]
--backup-standby backup from the standby cluster [default=n]
--online check an online cluster [default=y]
@@ -188,8 +188,8 @@ Repository Options:
[default=backrest]
--repo-cipher-pass repository cipher passphrase
--repo-cipher-type cipher used to encrypt the repository [default=none]
--repo-path repository path where WAL segments and backups
stored [default=/var/lib/pgbackrest]
--repo-path path where backups and archive are stored
[default=/var/lib/pgbackrest]
--repo-s3-bucket s3 repository bucket
--repo-s3-ca-file s3 SSL CA File
--repo-s3-ca-path s3 SSL CA Path

View File

@@ -400,6 +400,25 @@ my $oTestDef =
},
]
},
# Help tests
{
&TESTDEF_NAME => 'help',
&TESTDEF_CONTAINER => true,
&TESTDEF_TEST =>
[
{
&TESTDEF_NAME => 'help',
&TESTDEF_TOTAL => 4,
&TESTDEF_C => true,
&TESTDEF_COVERAGE =>
{
'command/help/help' => TESTDEF_COVERAGE_FULL,
},
}
]
},
# Config tests
{
&TESTDEF_NAME => 'config',
@@ -409,7 +428,7 @@ my $oTestDef =
[
{
&TESTDEF_NAME => 'define',
&TESTDEF_TOTAL => 1,
&TESTDEF_TOTAL => 2,
&TESTDEF_C => true,
&TESTDEF_COVERAGE =>
@@ -420,7 +439,7 @@ my $oTestDef =
},
{
&TESTDEF_NAME => 'config',
&TESTDEF_TOTAL => 2,
&TESTDEF_TOTAL => 3,
&TESTDEF_C => true,
&TESTDEF_COVERAGE =>

View File

@@ -156,4 +156,29 @@ void testRun()
TEST_RESULT_VOID(cfgInit(), "config init resets value");
TEST_RESULT_INT(cfgCommand(), cfgCmdNone, "command begins as none");
}
// *****************************************************************************************************************************
if (testBegin("cfgOptionDefault() and cfgOptionDefaultSet()"))
{
TEST_RESULT_VOID(cfgInit(), "config init");
TEST_RESULT_VOID(cfgCommandSet(cfgCmdBackup), "backup command");
TEST_RESULT_STR(strPtr(varStr(cfgOptionDefault(cfgOptType))), "incr", "backup type default");
TEST_RESULT_BOOL(varBool(cfgOptionDefault(cfgOptCompress)), "true", "backup compress default");
TEST_RESULT_DOUBLE(varDbl(cfgOptionDefault(cfgOptProtocolTimeout)), 1830, "backup protocol-timeout default");
TEST_RESULT_INT(varInt(cfgOptionDefault(cfgOptCompressLevel)), 6, "backup compress-level default");
TEST_RESULT_PTR(cfgOptionDefault(cfgOptDbInclude), NULL, "backup db-include default is null");
TEST_RESULT_VOID(cfgOptionSet(cfgOptDbHost, cfgSourceParam, varNewStrZ("backup")), "backup host set");
TEST_RESULT_VOID(cfgOptionDefaultSet(cfgOptDbHost, varNewStrZ("backup-default")), "backup host default");
TEST_RESULT_VOID(cfgOptionDefaultSet(cfgOptDbHost, varNewStrZ("backup-default2")), "reset backup host default");
TEST_RESULT_STR(strPtr(varStr(cfgOption(cfgOptDbHost))), "backup", "backup host value");
TEST_RESULT_STR(strPtr(varStr(cfgOptionDefault(cfgOptDbHost))), "backup-default2", "backup host default");
TEST_RESULT_VOID(cfgOptionSet(cfgOptDbSocketPath, cfgSourceDefault, NULL), "backup db-socket-path set");
TEST_RESULT_VOID(cfgOptionDefaultSet(cfgOptDbSocketPath, varNewStrZ("/to/socket")), "backup db-socket-path default");
TEST_RESULT_VOID(cfgOptionDefaultSet(cfgOptDbSocketPath, varNewStrZ("/to/socket2")), "reset backup db-socket-path default");
TEST_RESULT_STR(strPtr(varStr(cfgOption(cfgOptDbSocketPath))), "/to/socket2", "backup db-socket-path value");
TEST_RESULT_STR(strPtr(varStr(cfgOptionDefault(cfgOptDbSocketPath))), "/to/socket2", "backup db-socket-path value default");
}
}

View File

@@ -125,4 +125,32 @@ void testRun()
TEST_RESULT_BOOL(cfgDefOptionValid(cfgDefCmdBackup, cfgDefOptType), true, "option valid");
TEST_RESULT_BOOL(cfgDefOptionValid(cfgDefCmdInfo, cfgDefOptType), false, "option not valid");
}
// -----------------------------------------------------------------------------------------------------------------------------
if (testBegin("cfgDefCommandHelp*() and cfgDefOptionHelp*()"))
{
TEST_RESULT_STR(cfgDefCommandHelpSummary(cfgDefCmdBackup), "Backup a database cluster.", "backup command help summary");
TEST_RESULT_STR(
cfgDefCommandHelpDescription(cfgDefCmdBackup),
"pgBackRest does not have a built-in scheduler so it's best to run it from cron or some other scheduling mechanism.",
"backup command help description");
TEST_RESULT_STR(cfgDefOptionHelpSection(cfgDefOptCompress), "general", "compress option help section");
TEST_RESULT_STR(
cfgDefOptionHelpSummary(cfgDefCmdBackup, cfgDefOptCompress), "Use gzip file compression.",
"backup command, compress option help summary");
TEST_RESULT_STR(
cfgDefOptionHelpDescription(cfgDefCmdBackup, cfgDefOptCompress),
"Backup files are compatible with command-line gzip tools.", "backup command, compress option help description");
TEST_RESULT_STR(
cfgDefOptionHelpSummary(cfgDefCmdBackup, cfgDefOptType), "Backup type.", "backup command, type option help summary");
TEST_RESULT_STR(
cfgDefOptionHelpDescription(cfgDefCmdBackup, cfgDefOptType),
"The following backup types are supported:\n"
"\n"
"* full - all database cluster files will be copied and there will be no dependencies on previous backups.\n"
"* incr - incremental from the last successful backup.\n"
"* diff - like an incremental backup but always based on the last full backup.",
"backup command, type option help description");
}
}

View File

@@ -49,5 +49,53 @@ void testRun()
TEST_RESULT_INT(logLevelStdOut, logLevelWarn, "console logging is off");
TEST_RESULT_INT(logLevelStdErr, logLevelOff, "stderr logging is off");
// -------------------------------------------------------------------------------------------------------------------------
argList = strLstNew();
strLstAdd(argList, strNew("pgbackrest"));
strLstAdd(argList, strNew("--stanza=db"));
strLstAdd(argList, strNew("archive-push"));
TEST_RESULT_VOID(cfgLoad(strLstSize(argList), strLstPtr(argList)), "load archive-push config");
TEST_RESULT_PTR(cfgOptionDefault(cfgOptBackupCmd), NULL, " command archive-push, option backup-cmd default");
// -------------------------------------------------------------------------------------------------------------------------
argList = strLstNew();
strLstAdd(argList, strNew("pgbackrest"));
strLstAdd(argList, strNew("--stanza=db"));
strLstAdd(argList, strNew("--backup-host=backup"));
strLstAdd(argList, strNew("archive-push"));
TEST_RESULT_VOID(cfgLoad(strLstSize(argList), strLstPtr(argList)), "load archive-push config");
TEST_RESULT_STR(
strPtr(varStr(cfgOptionDefault(cfgOptBackupCmd))), strPtr(cfgExe()),
" command archive-push, option backup-cmd default");
// -------------------------------------------------------------------------------------------------------------------------
argList = strLstNew();
strLstAdd(argList, strNew("pgbackrest"));
strLstAdd(argList, strNew("--db-path=/path/to/db"));
strLstAdd(argList, strNew("--stanza=db"));
strLstAdd(argList, strNew("backup"));
TEST_RESULT_VOID(cfgLoad(strLstSize(argList), strLstPtr(argList)), "load backup config");
TEST_RESULT_PTR(cfgOptionDefault(cfgOptDbCmd), NULL, " command backup, option db1-cmd default");
// -------------------------------------------------------------------------------------------------------------------------
argList = strLstNew();
strLstAdd(argList, strNew("pgbackrest"));
strLstAdd(argList, strNew("--db-host=db"));
strLstAdd(argList, strNew("--db-path=/path/to/db"));
strLstAdd(argList, strNew("--db3-host=db"));
strLstAdd(argList, strNew("--db3-path=/path/to/db"));
strLstAdd(argList, strNew("--stanza=db"));
strLstAdd(argList, strNew("backup"));
TEST_RESULT_VOID(cfgLoad(strLstSize(argList), strLstPtr(argList)), "load backup config");
TEST_RESULT_STR(
strPtr(varStr(cfgOptionDefault(cfgOptDbCmd))), strPtr(cfgExe()), " command backup, option db1-cmd default");
TEST_RESULT_PTR(cfgOptionDefault(cfgOptDbCmd + 1), NULL, " command backup, option db2-cmd default");
TEST_RESULT_STR(
strPtr(varStr(cfgOptionDefault(cfgOptDbCmd + 2))), strPtr(cfgExe()), " command backup, option db3-cmd default");
}
}

View File

@@ -0,0 +1,271 @@
/***********************************************************************************************************************************
Test Help Command
***********************************************************************************************************************************/
#include "config/parse.h"
#include "storage/storage.h"
#include "version.h"
/***********************************************************************************************************************************
Test Run
***********************************************************************************************************************************/
void testRun()
{
// Program name a version are used multiple times
const char *helpVersion = PGBACKREST_NAME " " PGBACKREST_VERSION;
// General help text is used in more than one test
const char *generalHelp = strPtr(strNewFmt(
"%s - General help\n"
"\n"
"Usage:\n"
" pgbackrest [options] [command]\n"
"\n"
"Commands:\n"
" archive-get Get a WAL segment from the archive.\n"
" archive-push Push a WAL segment to the archive.\n"
" backup Backup a database cluster.\n"
" check Check the configuration.\n"
" expire Expire backups that exceed retention.\n"
" help Get help.\n"
" info Retrieve information about backups.\n"
" restore Restore a database cluster.\n"
" stanza-create Create the required stanza data.\n"
" stanza-delete Delete a stanza.\n"
" stanza-upgrade Upgrade a stanza.\n"
" start Allow pgBackRest processes to run.\n"
" stop Stop pgBackRest processes from running.\n"
" version Get version.\n"
"\n"
"Use 'pgbackrest help [command]' for more information.\n",
helpVersion));
// *****************************************************************************************************************************
if (testBegin("helpRenderText()"))
{
TEST_RESULT_STR(
strPtr(helpRenderText(strNew("this is a short sentence"), 0, false, 80)), "this is a short sentence", "one line");
TEST_RESULT_STR(
strPtr(helpRenderText(strNew("this is a short sentence"), 4, false, 14)),
"this is a\n"
" short\n"
" sentence",
"three lines, no indent first");
TEST_RESULT_STR(
strPtr(helpRenderText(strNew("This is a short paragraph.\n\nHere is another one."), 2, true, 16)),
" This is a\n"
" short\n"
" paragraph.\n"
"\n"
" Here is\n"
" another one.",
"two paragraphs, indent first");
}
// *****************************************************************************************************************************
if (testBegin("helpRenderValue()"))
{
TEST_RESULT_STR(strPtr(helpRenderValue(varNewBool(true))), "y", "boolean y");
TEST_RESULT_STR(strPtr(helpRenderValue(varNewBool(false))), "n", "boolean n");
TEST_RESULT_STR(strPtr(helpRenderValue(varNewStrZ("test-string"))), "test-string", "string");
TEST_RESULT_STR(strPtr(helpRenderValue(varNewDbl(1.234))), "1.234", "double");
TEST_RESULT_STR(strPtr(helpRenderValue(varNewInt(1234))), "1234", "int");
}
// *****************************************************************************************************************************
if (testBegin("helpRender()"))
{
StringList *argList = NULL;
// -------------------------------------------------------------------------------------------------------------------------
argList = strLstNew();
strLstAddZ(argList, "/path/to/pgbackrest");
TEST_RESULT_VOID(configParse(strLstSize(argList), strLstPtr(argList)), "help from empty command line");
TEST_RESULT_STR(strPtr(helpRender()), generalHelp, " check text");
argList = strLstNew();
strLstAddZ(argList, "/path/to/pgbackrest");
strLstAddZ(argList, "help");
TEST_RESULT_VOID(configParse(strLstSize(argList), strLstPtr(argList)), "help from help command");
TEST_RESULT_STR(strPtr(helpRender()), generalHelp, " check text");
// -------------------------------------------------------------------------------------------------------------------------
const char *commandHelp = strPtr(strNewFmt(
"%s - 'archive-push' command help\n"
"\n"
"Push a WAL segment to the archive.\n"
"\n"
"The WAL segment may be pushed immediately to the archive or stored locally\n"
"depending on the value of archive-async\n"
"\n"
"Command Options:\n"
"\n"
" --archive-async archive WAL segments asynchronously [default=n]\n"
" --archive-queue-max limit size (in bytes) of the PostgreSQL archive\n"
" queue\n"
" --archive-timeout archive timeout [default=60]\n"
"\n"
"General Options:\n"
"\n"
" --buffer-size buffer size for file operations [current=32768,\n"
" default=4194304]\n"
" --cmd-ssh path to ssh client executable [default=ssh]\n"
" --compress use gzip file compression [default=y]\n"
" --compress-level compression level for stored files [default=6]\n"
" --compress-level-network compression level for network transfer when\n"
" compress=n [default=3]\n"
" --config pgBackRest configuration file\n"
" [default=/etc/pgbackrest.conf]\n"
" --db-timeout database query timeout [default=1800]\n"
" --lock-path path where lock files are stored\n"
" [default=/tmp/pgbackrest]\n"
" --neutral-umask use a neutral umask [default=y]\n"
" --perl-bin path of Perl binary\n"
" --process-max max processes to use for compress/transfer\n"
" [default=1]\n"
" --protocol-timeout protocol timeout [default=1830]\n"
" --spool-path path where WAL segments are spooled during async\n"
" archiving [default=/var/spool/pgbackrest]\n"
" --stanza defines the stanza\n"
"\n"
"Log Options:\n"
"\n"
" --log-level-console level for console logging [default=warn]\n"
" --log-level-file level for file logging [default=info]\n"
" --log-level-stderr level for stderr logging [default=warn]\n"
" --log-path path where log files are stored\n"
" [default=/var/log/pgbackrest]\n"
" --log-timestamp enable timestamp in logging [default=y]\n"
"\n"
"Repository Options:\n"
"\n"
" --backup-cmd pgBackRest exe path on the backup host\n"
" --backup-config pgBackRest backup host configuration file\n"
" [default=/etc/pgbackrest.conf]\n"
" --backup-host backup host when operating remotely via SSH\n"
" [current=backup.example.net]\n"
" --backup-ssh-port backup server SSH port when backup-host is set\n"
" --backup-user backup host user when backup-host is set\n"
" [default=backrest]\n"
" --repo-cipher-pass repository cipher passphrase\n"
" --repo-cipher-type cipher used to encrypt the repository [default=none]\n"
" --repo-path path where backups and archive are stored\n"
" [default=/var/lib/pgbackrest]\n"
" --repo-s3-bucket s3 repository bucket\n"
" --repo-s3-ca-file s3 SSL CA File\n"
" --repo-s3-ca-path s3 SSL CA Path\n"
" --repo-s3-endpoint s3 repository endpoint\n"
" --repo-s3-host s3 repository host\n"
" --repo-s3-key s3 repository access key\n"
" --repo-s3-key-secret s3 repository secret access key\n"
" --repo-s3-region s3 repository region\n"
" --repo-s3-verify-ssl verify S3 server certificate [default=y]\n"
" --repo-type type of storage used for the repository\n"
" [default=posix]\n"
"\n"
"Stanza Options:\n"
"\n"
" --db-host cluster host for operating remotely via SSH\n"
" --db-path cluster data directory\n"
" --db-ssh-port database server SSH port when db-host is set\n"
"\n"
"Use 'pgbackrest help archive-push [option]' for more information.\n",
helpVersion));
argList = strLstNew();
strLstAddZ(argList, "/path/to/pgbackrest");
strLstAddZ(argList, "help");
strLstAddZ(argList, "archive-push");
strLstAddZ(argList, "--buffer-size=32768");
strLstAddZ(argList, "--backup-host=backup.example.net");
TEST_RESULT_VOID(configParse(strLstSize(argList), strLstPtr(argList)), "help for archive-push command");
TEST_RESULT_STR(strPtr(helpRender()), commandHelp, " check text");
// -------------------------------------------------------------------------------------------------------------------------
argList = strLstNew();
strLstAddZ(argList, "/path/to/pgbackrest");
strLstAddZ(argList, "help");
strLstAddZ(argList, "archive-push");
strLstAddZ(argList, "buffer-size");
strLstAddZ(argList, "buffer-size");
TEST_RESULT_VOID(configParse(strLstSize(argList), strLstPtr(argList)), "parse too many options");
TEST_ERROR(helpRender(), ParamInvalidError, "only one option allowed for option help");
argList = strLstNew();
strLstAddZ(argList, "/path/to/pgbackrest");
strLstAddZ(argList, "help");
strLstAddZ(argList, "archive-push");
strLstAddZ(argList, BOGUS_STR);
TEST_RESULT_VOID(configParse(strLstSize(argList), strLstPtr(argList)), "parse bogus option");
TEST_ERROR(helpRender(), OptionInvalidError, "option 'BOGUS' is not valid for command 'archive-push'");
// -------------------------------------------------------------------------------------------------------------------------
const char *optionHelp = strPtr(strNewFmt(
"%s - 'archive-push' command - 'buffer-size' option help\n"
"\n"
"Buffer size for file operations.\n"
"\n"
"Set the buffer size used for copy, compress, and uncompress functions. A\n"
"maximum of 3 buffers will be in use at a time per process. An additional\n"
"maximum of 256K per process may be used for zlib buffers.\n",
helpVersion));
argList = strLstNew();
strLstAddZ(argList, "/path/to/pgbackrest");
strLstAddZ(argList, "help");
strLstAddZ(argList, "archive-push");
strLstAddZ(argList, "buffer-size");
TEST_RESULT_VOID(configParse(strLstSize(argList), strLstPtr(argList)), "help for archive-push command, buffer-size option");
TEST_RESULT_STR(strPtr(helpRender()), strPtr(strNewFmt("%s\ndefault: 4194304\n", optionHelp)), " check text");
argList = strLstNew();
strLstAddZ(argList, "/path/to/pgbackrest");
strLstAddZ(argList, "help");
strLstAddZ(argList, "archive-push");
strLstAddZ(argList, "buffer-size");
strLstAddZ(argList, "--buffer-size=32768");
TEST_RESULT_VOID(configParse(strLstSize(argList), strLstPtr(argList)), "help for archive-push command, buffer-size option");
TEST_RESULT_STR(
strPtr(helpRender()), strPtr(strNewFmt("%s\ncurrent: 32768\ndefault: 4194304\n", optionHelp)), " check text");
// -------------------------------------------------------------------------------------------------------------------------
optionHelp = strPtr(strNewFmt(
"%s - 'archive-push' command - 'perl-bin' option help\n"
"\n"
"Path of Perl binary.\n"
"\n"
"Path of the Perl binary if /usr/bin/env perl won't work.\n",
helpVersion));
argList = strLstNew();
strLstAddZ(argList, "/path/to/pgbackrest");
strLstAddZ(argList, "help");
strLstAddZ(argList, "archive-push");
strLstAddZ(argList, "perl-bin");
TEST_RESULT_VOID(configParse(strLstSize(argList), strLstPtr(argList)), "help for archive-push command, perl-bin option");
TEST_RESULT_STR(strPtr(helpRender()), optionHelp, " check text");
}
// *****************************************************************************************************************************
if (testBegin("cmdHelp()"))
{
StringList *argList = strLstNew();
strLstAddZ(argList, "/path/to/pgbackrest");
TEST_RESULT_VOID(configParse(strLstSize(argList), strLstPtr(argList)), "parse help from empty command line");
// Redirect stdout to a file
int stdoutSave = dup(STDOUT_FILENO);
String *stdoutFile = strNewFmt("%s/stdout.help", testPath());
THROW_ON_SYS_ERROR(freopen(strPtr(stdoutFile), "w", stdout) == NULL, FileWriteError, "unable to reopen stdout");
// Not in a test wrapper to avoid writing to stdout
cmdHelp();
// Restore normal stdout
dup2(stdoutSave, STDOUT_FILENO);
Storage *storage = storageNew(strNew(testPath()), 0750, 65536, NULL);
TEST_RESULT_STR(strPtr(strNewBuf(storageGet(storage, stdoutFile, false))), generalHelp, " check text");
}
}