1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2025-07-15 01:04:37 +02:00

Add verify output and verbose options.

These options allow the user to control how the verify results will be output to the console and log.
This commit is contained in:
Reid Thompson
2022-05-06 11:11:36 -04:00
committed by GitHub
parent f405fc6ae2
commit 65d22e4325
9 changed files with 907 additions and 203 deletions

View File

@ -72,6 +72,21 @@
<p>Backup file bundling for improved small file support.</p> <p>Backup file bundling for improved small file support.</p>
</release-item> </release-item>
<release-item>
<commit subject="Add verify output and verbose options.">
<github-pull-request id="1607"/>
</commit>
<release-item-contributor-list>
<release-item-contributor id="cynthia.shang"/>
<release-item-contributor id="reid.thompson"/>
<release-item-reviewer id="david.steele"/>
<release-item-reviewer id="stefan.fercot"/>
</release-item-contributor-list>
<p>Verify command to validate the contents of a repository.</p>
</release-item>
<release-item> <release-item>
<commit subject="Remove dependency on pg_database.datlastsysoid."> <commit subject="Remove dependency on pg_database.datlastsysoid.">
<github-pull-request id="1735"/> <github-pull-request id="1735"/>

View File

@ -151,7 +151,6 @@ command:
command-role: command-role:
local: {} local: {}
remote: {} remote: {}
internal: true
version: version:
log-file: false log-file: false
@ -306,6 +305,11 @@ option:
command: command:
info: {} info: {}
repo-ls: {} repo-ls: {}
verify:
default: none
allow-list:
- none
- text
allow-list: allow-list:
- text - text
- json - json
@ -453,6 +457,14 @@ option:
command-role: command-role:
main: {} main: {}
verbose:
type: boolean
default: false
command:
verify: {}
command-role:
main: {}
# Command-line only local/remote options # Command-line only local/remote options
#--------------------------------------------------------------------------------------------------------------------------------- #---------------------------------------------------------------------------------------------------------------------------------
exec-id: exec-id:

View File

@ -2411,10 +2411,10 @@
<!-- OPERATION - VERIFY COMMAND --> <!-- OPERATION - VERIFY COMMAND -->
<command id="verify" name="Verify"> <command id="verify" name="Verify">
<summary>Verify the contents of the repository.</summary> <summary>Verify contents of the repository.</summary>
<text> <text>
<p>Verify will attempt to determine if the backups and archives in the repository are valid.</p> <p>Verify determines if the backups and archives in the repository are valid.</p>
</text> </text>
<option-list> <option-list>
@ -2428,6 +2428,27 @@
<example>20150131-153358F_20150131-153401I</example> <example>20150131-153358F_20150131-153401I</example>
</option> </option>
<!-- ======================================================================================================= -->
<option id="output" name="Output">
<summary>Output type.</summary>
<text> <p>Output may be none (default) or text. Requesting text generates ouput to stdout.</p>
</text>
<example>text</example>
</option>
<!-- ======================================================================================================= -->
<option id="verbose" name="Verbose">
<summary>Verbose output.</summary>
<text>
<p>Verbose defaults to false, providing a minimal response with important information about errors in the repository. Specifying true provides more information about what was successfully verified.</p>
</text>
<example>y</example>
</option>
</option-list> </option-list>
</command> </command>

View File

@ -1,7 +1,7 @@
/*********************************************************************************************************************************** /***********************************************************************************************************************************
Verify Command Verify Command
Verify the contents of the repository. Verify contents of the repository.
***********************************************************************************************************************************/ ***********************************************************************************************************************************/
#include "build.auto.h" #include "build.auto.h"
@ -13,6 +13,7 @@ Verify the contents of the repository.
#include "command/check/common.h" #include "command/check/common.h"
#include "command/verify/file.h" #include "command/verify/file.h"
#include "command/verify/protocol.h" #include "command/verify/protocol.h"
#include "command/verify/verify.h"
#include "common/compress/helper.h" #include "common/compress/helper.h"
#include "common/crypto/cipherBlock.h" #include "common/crypto/cipherBlock.h"
#include "common/debug.h" #include "common/debug.h"
@ -29,6 +30,12 @@ Verify the contents of the repository.
#include "protocol/parallel.h" #include "protocol/parallel.h"
#include "storage/helper.h" #include "storage/helper.h"
/***********************************************************************************************************************************
Constants
***********************************************************************************************************************************/
#define VERIFY_STATUS_OK "ok"
#define VERIFY_STATUS_ERROR "error"
/*********************************************************************************************************************************** /***********************************************************************************************************************************
Data Types and Structures Data Types and Structures
***********************************************************************************************************************************/ ***********************************************************************************************************************************/
@ -233,7 +240,7 @@ verifyInfoFile(const String *pathFileName, bool keepFile, const String *cipherPa
if (result.errorCode == errorTypeCode(&ChecksumError)) if (result.errorCode == errorTypeCode(&ChecksumError))
strCat(errorMsg, strNewFmt(" %s", strZ(pathFileName))); strCat(errorMsg, strNewFmt(" %s", strZ(pathFileName)));
LOG_WARN(strZ(errorMsg)); LOG_DETAIL(strZ(errorMsg));
} }
TRY_END(); TRY_END();
} }
@ -273,7 +280,7 @@ verifyArchiveInfoFile(void)
// If the info and info.copy checksums don't match each other than one (or both) of the files could be corrupt so // If the info and info.copy checksums don't match each other than one (or both) of the files could be corrupt so
// log a warning but must trust main // log a warning but must trust main
if (!strEq(verifyArchiveInfo.checksum, verifyArchiveInfoCopy.checksum)) if (!strEq(verifyArchiveInfo.checksum, verifyArchiveInfoCopy.checksum))
LOG_WARN("archive.info.copy does not match archive.info"); LOG_DETAIL("archive.info.copy does not match archive.info");
} }
} }
else else
@ -326,7 +333,7 @@ verifyBackupInfoFile(void)
// If the info and info.copy checksums don't match each other than one (or both) of the files could be corrupt so // If the info and info.copy checksums don't match each other than one (or both) of the files could be corrupt so
// log a warning but must trust main // log a warning but must trust main
if (!strEq(verifyBackupInfo.checksum, verifyBackupInfoCopy.checksum)) if (!strEq(verifyBackupInfo.checksum, verifyBackupInfoCopy.checksum))
LOG_WARN("backup.info.copy does not match backup.info"); LOG_DETAIL("backup.info.copy does not match backup.info");
} }
} }
else else
@ -393,7 +400,7 @@ verifyManifestFile(
// If the manifest and manifest.copy checksums don't match each other than one (or both) of the files could be // If the manifest and manifest.copy checksums don't match each other than one (or both) of the files could be
// corrupt so log a warning but trust main // corrupt so log a warning but trust main
if (!strEq(verifyManifestInfo.checksum, verifyManifestInfoCopy.checksum)) if (!strEq(verifyManifestInfo.checksum, verifyManifestInfoCopy.checksum))
LOG_WARN_FMT("backup '%s' manifest.copy does not match manifest", strZ(backupResult->backupLabel)); LOG_DETAIL_FMT("backup '%s' manifest.copy does not match manifest", strZ(backupResult->backupLabel));
} }
} }
else else
@ -411,7 +418,7 @@ verifyManifestFile(
// If loaded successfully, then return the copy as usable // If loaded successfully, then return the copy as usable
if (verifyManifestInfoCopy.errorCode == 0) if (verifyManifestInfoCopy.errorCode == 0)
{ {
LOG_WARN_FMT("%s/backup.manifest is missing or unusable, using copy", strZ(backupResult->backupLabel)); LOG_DETAIL_FMT("%s/backup.manifest is missing or unusable, using copy", strZ(backupResult->backupLabel));
result = verifyManifestInfoCopy.manifest; result = verifyManifestInfoCopy.manifest;
} }
@ -420,7 +427,7 @@ verifyManifestFile(
{ {
backupResult->status = backupMissingManifest; backupResult->status = backupMissingManifest;
LOG_WARN_FMT("manifest missing for '%s' - backup may have expired", strZ(backupResult->backupLabel)); LOG_DETAIL_FMT("manifest missing for '%s' - backup may have expired", strZ(backupResult->backupLabel));
} }
} }
else else
@ -453,8 +460,7 @@ verifyManifestFile(
// If the PG data is not found in the backup.info history, then error and reset the result // If the PG data is not found in the backup.info history, then error and reset the result
if (!found) if (!found)
{ {
LOG_ERROR_FMT( LOG_INFO_FMT(
errorTypeCode(&FileInvalidError),
"'%s' may not be recoverable - PG data (id %u, version %s, system-id %" PRIu64 ") is not in the backup.info" "'%s' may not be recoverable - PG data (id %u, version %s, system-id %" PRIu64 ") is not in the backup.info"
" history, skipping", " history, skipping",
strZ(backupResult->backupLabel), manData->pgId, strZ(pgVersionToStr(manData->pgVersion)), manData->pgSystemId); strZ(backupResult->backupLabel), manData->pgId, strZ(pgVersionToStr(manData->pgVersion)), manData->pgSystemId);
@ -562,9 +568,7 @@ verifyCreateArchiveIdRange(VerifyArchiveResult *archiveIdResult, StringList *wal
// PostgreSQL will ignore it // PostgreSQL will ignore it
if (archiveIdResult->pgWalInfo.version <= PG_VERSION_92 && strEndsWithZ(walSegment, "FF")) if (archiveIdResult->pgWalInfo.version <= PG_VERSION_92 && strEndsWithZ(walSegment, "FF"))
{ {
LOG_ERROR_FMT( LOG_INFO_FMT("invalid WAL '%s' for '%s' exists, skipping", strZ(walSegment), strZ(archiveIdResult->archiveId));
errorTypeCode(&FileInvalidError), "invalid WAL '%s' for '%s' exists, skipping", strZ(walSegment),
strZ(archiveIdResult->archiveId));
(*jobErrorTotal)++; (*jobErrorTotal)++;
@ -578,9 +582,7 @@ verifyCreateArchiveIdRange(VerifyArchiveResult *archiveIdResult, StringList *wal
{ {
if (strEq(walSegment, strSubN(strLstGet(walFileList, walFileIdx + 1), 0, WAL_SEGMENT_NAME_SIZE))) if (strEq(walSegment, strSubN(strLstGet(walFileList, walFileIdx + 1), 0, WAL_SEGMENT_NAME_SIZE)))
{ {
LOG_ERROR_FMT( LOG_INFO_FMT("duplicate WAL '%s' for '%s' exists, skipping", strZ(walSegment), strZ(archiveIdResult->archiveId));
errorTypeCode(&FileInvalidError), "duplicate WAL '%s' for '%s' exists, skipping", strZ(walSegment),
strZ(archiveIdResult->archiveId));
(*jobErrorTotal)++; (*jobErrorTotal)++;
@ -783,7 +785,7 @@ verifyArchive(VerifyJobData *const jobData)
else else
{ {
// No valid WAL to process (may be only duplicates or nothing in WAL path) - remove WAL path from the list // No valid WAL to process (may be only duplicates or nothing in WAL path) - remove WAL path from the list
LOG_WARN_FMT( LOG_DETAIL_FMT(
"path '%s/%s' does not contain any valid WAL to be processed", strZ(archiveResult->archiveId), "path '%s/%s' does not contain any valid WAL to be processed", strZ(archiveResult->archiveId),
strZ(walPath)); strZ(walPath));
strLstRemoveIdx(jobData->walPathList, 0); strLstRemoveIdx(jobData->walPathList, 0);
@ -806,7 +808,7 @@ verifyArchive(VerifyJobData *const jobData)
else else
{ {
// Log that no WAL paths exist in the archive Id dir - remove the archive Id from the list (nothing to process) // Log that no WAL paths exist in the archive Id dir - remove the archive Id from the list (nothing to process)
LOG_WARN_FMT("archive path '%s' is empty", strZ(strLstGet(jobData->archiveIdList, 0))); LOG_DETAIL_FMT("archive path '%s' is empty", strZ(strLstGet(jobData->archiveIdList, 0)));
strLstRemoveIdx(jobData->archiveIdList, 0); strLstRemoveIdx(jobData->archiveIdList, 0);
} }
} }
@ -836,7 +838,8 @@ verifyBackup(VerifyJobData *const jobData)
// If result list is empty or the last processed is not equal to the backup being processed, then initialize the backup // If result list is empty or the last processed is not equal to the backup being processed, then initialize the backup
// data and results // data and results
if (lstEmpty(jobData->backupResultList) || if (lstEmpty(jobData->backupResultList) ||
!strEq(((VerifyBackupResult *)lstGetLast(jobData->backupResultList))->backupLabel, strLstGet(jobData->backupList, 0))) !strEq(
((VerifyBackupResult *)lstGetLast(jobData->backupResultList))->backupLabel, strLstGet(jobData->backupList, 0)))
{ {
MEM_CONTEXT_BEGIN(lstMemContext(jobData->backupResultList)) MEM_CONTEXT_BEGIN(lstMemContext(jobData->backupResultList))
{ {
@ -1045,9 +1048,7 @@ verifyBackup(VerifyJobData *const jobData)
{ {
// Nothing to process so report an error, free the manifest, set the status, and remove the backup from processing // Nothing to process so report an error, free the manifest, set the status, and remove the backup from processing
// list // list
LOG_ERROR_FMT( LOG_INFO_FMT("backup '%s' manifest does not contain any target files to verify", strZ(backupResult->backupLabel));
errorTypeCode(&FileInvalidError), "backup '%s' manifest does not contain any target files to verify",
strZ(backupResult->backupLabel));
jobData->jobErrorTotal++; jobData->jobErrorTotal++;
@ -1157,8 +1158,7 @@ verifyLogInvalidResult(const String *fileType, VerifyResult verifyResult, unsign
FUNCTION_TEST_RETURN(UINT, 0); FUNCTION_TEST_RETURN(UINT, 0);
} }
LOG_ERROR_PID_FMT( LOG_INFO_PID_FMT(processId, "%s '%s'", verifyErrorMsg(verifyResult), strZ(filePathName));
processId, errorTypeCode(&FileInvalidError), "%s '%s'", verifyErrorMsg(verifyResult), strZ(filePathName));
FUNCTION_TEST_RETURN(UINT, 1); FUNCTION_TEST_RETURN(UINT, 1);
} }
@ -1224,9 +1224,7 @@ verifySetBackupCheckArchive(
if (!strEmpty(missingFromHistory)) if (!strEmpty(missingFromHistory))
{ {
LOG_ERROR_FMT( LOG_INFO_FMT("archiveIds '%s' are not in the archive.info history list", strZ(missingFromHistory));
errorTypeCode(&ArchiveMismatchError), "archiveIds '%s' are not in the archive.info history list",
strZ(missingFromHistory));
(*jobErrorTotal)++; (*jobErrorTotal)++;
} }
@ -1275,33 +1273,74 @@ verifyAddInvalidWalFile(List *walRangeList, VerifyResult fileResult, const Strin
FUNCTION_TEST_RETURN_VOID(); FUNCTION_TEST_RETURN_VOID();
} }
/***********************************************************************************************************************************
Create file errors string
***********************************************************************************************************************************/
static String *
verifyCreateFileErrorsStr(
const unsigned int errMissing, const unsigned int errChecksum, const unsigned int errSize, const unsigned int errOther,
const bool verboseText)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(UINT, errMissing); // Number of files missing
FUNCTION_TEST_PARAM(UINT, errChecksum); // Number of files with checksum errors
FUNCTION_TEST_PARAM(UINT, errSize); // Number of files with invalid size
FUNCTION_TEST_PARAM(UINT, errOther); // Number of files with other errors
FUNCTION_TEST_PARAM(BOOL, verboseText); // Is verbose output requested
FUNCTION_TEST_END();
String *result = strNew();
MEM_CONTEXT_TEMP_BEGIN()
{
// List all if verbose text, otherwise only list if type has errors
strCatFmt(
result, "\n %s%s%s%s",
verboseText || errMissing ? strZ(strNewFmt("missing: %u, ", errMissing)) : "",
verboseText || errChecksum ? strZ(strNewFmt("checksum invalid: %u, ", errChecksum)) : "",
verboseText || errSize ? strZ(strNewFmt("size invalid: %u, ", errSize)) : "",
verboseText || errOther ? strZ(strNewFmt("other: %u", errOther)) : "");
// Clean up trailing comma when necessary
if (strEndsWithZ(result, ", "))
strTrunc(result, (int)strSize(result) - 2);
}
MEM_CONTEXT_TEMP_END();
FUNCTION_TEST_RETURN(STRING, result);
}
/*********************************************************************************************************************************** /***********************************************************************************************************************************
Render the results of the verify command Render the results of the verify command
***********************************************************************************************************************************/ ***********************************************************************************************************************************/
static String * static String *
verifyRender(List *archiveIdResultList, List *backupResultList) verifyRender(const List *const archiveIdResultList, const List *const backupResultList, const bool verboseText)
{ {
FUNCTION_TEST_BEGIN(); FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(LIST, archiveIdResultList); // Result list for all archive Ids in the repo FUNCTION_TEST_PARAM(LIST, archiveIdResultList); // Result list for all archive Ids in the repo
FUNCTION_TEST_PARAM(LIST, backupResultList); // Result list for all backups in the repo FUNCTION_TEST_PARAM(LIST, backupResultList); // Result list for all backups in the repo
FUNCTION_TEST_PARAM(BOOL, verboseText); // Is verbose output requested?
FUNCTION_TEST_END(); FUNCTION_TEST_END();
ASSERT(archiveIdResultList != NULL); ASSERT(archiveIdResultList != NULL);
ASSERT(backupResultList != NULL); ASSERT(backupResultList != NULL);
String *result = strCatZ(strNew(), "Results:"); String *result = strNew();
// Render archive results // Render archive results
if (lstEmpty(archiveIdResultList)) if (verboseText && lstEmpty(archiveIdResultList))
strCatZ(result, "\n archiveId: none found"); strCatZ(result, "\n archiveId: none found");
else else
{ {
for (unsigned int archiveIdx = 0; archiveIdx < lstSize(archiveIdResultList); archiveIdx++) for (unsigned int archiveIdx = 0; archiveIdx < lstSize(archiveIdResultList); archiveIdx++)
{ {
VerifyArchiveResult *archiveIdResult = lstGet(archiveIdResultList, archiveIdx); VerifyArchiveResult *archiveIdResult = lstGet(archiveIdResultList, archiveIdx);
strCatFmt( if (verboseText || archiveIdResult->totalWalFile - archiveIdResult->totalValidWal != 0)
result, "\n archiveId: %s, total WAL checked: %u, total valid WAL: %u", strZ(archiveIdResult->archiveId), {
archiveIdResult->totalWalFile, archiveIdResult->totalValidWal); strCatFmt(
result, "\n archiveId: %s, total WAL checked: %u, total valid WAL: %u", strZ(archiveIdResult->archiveId),
archiveIdResult->totalWalFile, archiveIdResult->totalValidWal);
}
if (archiveIdResult->totalWalFile > 0) if (archiveIdResult->totalWalFile > 0)
{ {
@ -1337,15 +1376,15 @@ verifyRender(List *archiveIdResultList, List *backupResultList)
} }
} }
strCatFmt( // Create/append file errors string
result, "\n missing: %u, checksum invalid: %u, size invalid: %u, other: %u", errMissing, errChecksum, if (verboseText || errMissing + errChecksum + errSize + errOther > 0)
errSize, errOther); strCat(result, verifyCreateFileErrorsStr(errMissing, errChecksum, errSize, errOther, verboseText));
} }
} }
} }
// Render backup results // Render backup results
if (lstEmpty(backupResultList)) if (verboseText && lstEmpty(backupResultList))
strCatZ(result, "\n backup: none found"); strCatZ(result, "\n backup: none found");
else else
{ {
@ -1377,9 +1416,12 @@ verifyRender(List *archiveIdResultList, List *backupResultList)
} }
} }
strCatFmt( if (verboseText || (strcmp(status, "valid") != 0 && strcmp(status, "in-progress") != 0))
result, "\n backup: %s, status: %s, total files checked: %u, total valid files: %u", {
strZ(backupResult->backupLabel), status, backupResult->totalFileVerify, backupResult->totalFileValid); strCatFmt(
result, "\n backup: %s, status: %s, total files checked: %u, total valid files: %u",
strZ(backupResult->backupLabel), status, backupResult->totalFileVerify, backupResult->totalFileValid);
}
if (backupResult->totalFileVerify > 0) if (backupResult->totalFileVerify > 0)
{ {
@ -1402,9 +1444,9 @@ verifyRender(List *archiveIdResultList, List *backupResultList)
errOther++; errOther++;
} }
strCatFmt( // Create/append file errors string
result, "\n missing: %u, checksum invalid: %u, size invalid: %u, other: %u", errMissing, errChecksum, if (verboseText || errMissing + errChecksum + errSize + errOther > 0)
errSize, errOther); strCat(result, verifyCreateFileErrorsStr(errMissing, errChecksum, errSize, errOther, verboseText));
} }
} }
} }
@ -1416,16 +1458,17 @@ verifyRender(List *archiveIdResultList, List *backupResultList)
Process the verify command Process the verify command
***********************************************************************************************************************************/ ***********************************************************************************************************************************/
static String * static String *
verifyProcess(unsigned int *errorTotal) verifyProcess(const bool verboseText)
{ {
FUNCTION_LOG_BEGIN(logLevelDebug); FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_TEST_PARAM_P(UINT, errorTotal); // Pointer to overall job error total FUNCTION_TEST_PARAM(BOOL, verboseText); // Is verbose output requested?
FUNCTION_LOG_END(); FUNCTION_LOG_END();
String *result = NULL; String *const result = strNew();
MEM_CONTEXT_TEMP_BEGIN() MEM_CONTEXT_TEMP_BEGIN()
{ {
unsigned int errorTotal = 0;
String *resultStr = strNew(); String *resultStr = strNew();
// Get the repo storage in case it is remote and encryption settings need to be pulled down // Get the repo storage in case it is remote and encryption settings need to be pulled down
@ -1437,8 +1480,8 @@ verifyProcess(unsigned int *errorTotal)
// If a usable backup.info file is not found, then report an error in the log // If a usable backup.info file is not found, then report an error in the log
if (backupInfo == NULL) if (backupInfo == NULL)
{ {
LOG_ERROR(errorTypeCode(&FormatError), "No usable backup.info file"); strCatZ(resultStr, "\n No usable backup.info file");
(*errorTotal)++; errorTotal++;
} }
// Get a usable archive info file // Get a usable archive info file
@ -1447,8 +1490,8 @@ verifyProcess(unsigned int *errorTotal)
// If a usable archive.info file is not found, then report an error in the log // If a usable archive.info file is not found, then report an error in the log
if (archiveInfo == NULL) if (archiveInfo == NULL)
{ {
LOG_ERROR(errorTypeCode(&FormatError), "No usable archive.info file"); strCatZ(resultStr, "\n No usable archive.info file");
(*errorTotal)++; errorTotal++;
} }
// If both a usable archive info and backup info file were found, then proceed with verification // If both a usable archive info and backup info file were found, then proceed with verification
@ -1461,14 +1504,14 @@ verifyProcess(unsigned int *errorTotal)
} }
CATCH_ANY() CATCH_ANY()
{ {
LOG_ERROR(errorTypeCode(&FormatError), errorMessage()); strCatFmt(resultStr, "\n%s", errorMessage());
(*errorTotal)++; errorTotal++;
} }
TRY_END(); TRY_END();
} }
// If valid info files, then begin process of checking backups and archives in the repo // If valid info files, then begin process of checking backups and archives in the repo
if ((*errorTotal) == 0) if (errorTotal == 0)
{ {
// Initialize the job data // Initialize the job data
VerifyJobData jobData = VerifyJobData jobData =
@ -1503,7 +1546,7 @@ verifyProcess(unsigned int *errorTotal)
// Warn if there are no archives or there are no backups in the repo so that the callback need not try to // Warn if there are no archives or there are no backups in the repo so that the callback need not try to
// distinguish between having processed all of the list or if the list was missing in the first place // distinguish between having processed all of the list or if the list was missing in the first place
if (strLstEmpty(jobData.archiveIdList) || strLstEmpty(jobData.backupList)) if (strLstEmpty(jobData.archiveIdList) || strLstEmpty(jobData.backupList))
LOG_WARN_FMT("no %s exist in the repo", strLstEmpty(jobData.archiveIdList) ? "archives" : "backups"); LOG_DETAIL_FMT("no %s exist in the repo", strLstEmpty(jobData.archiveIdList) ? "archives" : "backups");
// If there are no archives to process, then set the processing flag to skip to processing the backups // If there are no archives to process, then set the processing flag to skip to processing the backups
if (strLstEmpty(jobData.archiveIdList)) if (strLstEmpty(jobData.archiveIdList))
@ -1603,8 +1646,8 @@ verifyProcess(unsigned int *errorTotal)
else else
{ {
// Log a protocol error and increment the jobErrorTotal // Log a protocol error and increment the jobErrorTotal
LOG_ERROR_PID_FMT( LOG_INFO_PID_FMT(
processId, errorTypeCode(&ProtocolError), processId,
"%s %s: [%d] %s", verifyErrorMsg(verifyOtherError), strZ(filePathName), "%s %s: [%d] %s", verifyErrorMsg(verifyOtherError), strZ(filePathName),
protocolParallelJobErrorCode(job), strZ(protocolParallelJobErrorMessage(job))); protocolParallelJobErrorCode(job), strZ(protocolParallelJobErrorMessage(job)));
@ -1646,19 +1689,21 @@ verifyProcess(unsigned int *errorTotal)
// ??? Need to do the final reconciliation - checking backup required WAL against, valid WAL // ??? Need to do the final reconciliation - checking backup required WAL against, valid WAL
// Report results // Report results
resultStr = verifyRender(jobData.archiveIdResultList, jobData.backupResultList); resultStr = verifyRender(jobData.archiveIdResultList, jobData.backupResultList, verboseText);
} }
else else
LOG_WARN("no archives or backups exist in the repo"); strCatZ(resultStr, "\n no archives or backups exist in the repo");
(*errorTotal) += jobData.jobErrorTotal; errorTotal += jobData.jobErrorTotal;
} }
MEM_CONTEXT_PRIOR_BEGIN() // If verbose output or errors then output results
if (verboseText || errorTotal > 0)
{ {
result = strDup(resultStr); strCatFmt(
result, "stanza: %s\nstatus: %s%s", strZ(cfgOptionStr(cfgOptStanza)),
errorTotal > 0 ? VERIFY_STATUS_ERROR : VERIFY_STATUS_OK, strZ(resultStr));
} }
MEM_CONTEXT_PRIOR_END();
} }
MEM_CONTEXT_TEMP_END(); MEM_CONTEXT_TEMP_END();
@ -1673,16 +1718,21 @@ cmdVerify(void)
MEM_CONTEXT_TEMP_BEGIN() MEM_CONTEXT_TEMP_BEGIN()
{ {
unsigned int errorTotal = 0; const String *const result = verifyProcess(cfgOptionBool(cfgOptVerbose));
String *result = verifyProcess(&errorTotal);
// Output results if any // Output results if any
if (strSize(result) > 0) if (!strEmpty(result))
{
// Log results
LOG_INFO_FMT("%s", strZ(result)); LOG_INFO_FMT("%s", strZ(result));
// Throw an error if any encountered // Output to console when requested
if (errorTotal > 0) if (cfgOptionStrId(cfgOptOutput) == CFGOPTVAL_OUTPUT_TEXT)
THROW_FMT(RuntimeError, "%u fatal errors encountered, see log for details", errorTotal); {
ioFdWriteOneStr(STDOUT_FILENO, result);
ioFdWriteOneStr(STDOUT_FILENO, LF_STR);
}
}
} }
MEM_CONTEXT_TEMP_END(); MEM_CONTEXT_TEMP_END();

View File

@ -126,8 +126,9 @@ Option constants
#define CFGOPT_TLS_SERVER_KEY_FILE "tls-server-key-file" #define CFGOPT_TLS_SERVER_KEY_FILE "tls-server-key-file"
#define CFGOPT_TLS_SERVER_PORT "tls-server-port" #define CFGOPT_TLS_SERVER_PORT "tls-server-port"
#define CFGOPT_TYPE "type" #define CFGOPT_TYPE "type"
#define CFGOPT_VERBOSE "verbose"
#define CFG_OPTION_TOTAL 154 #define CFG_OPTION_TOTAL 155
/*********************************************************************************************************************************** /***********************************************************************************************************************************
Option value constants Option value constants
@ -195,6 +196,8 @@ Option value constants
#define CFGOPTVAL_OUTPUT_JSON STRID5("json", 0x73e6a0) #define CFGOPTVAL_OUTPUT_JSON STRID5("json", 0x73e6a0)
#define CFGOPTVAL_OUTPUT_JSON_Z "json" #define CFGOPTVAL_OUTPUT_JSON_Z "json"
#define CFGOPTVAL_OUTPUT_NONE STRID5("none", 0x2b9ee0)
#define CFGOPTVAL_OUTPUT_NONE_Z "none"
#define CFGOPTVAL_OUTPUT_TEXT STRID5("text", 0xa60b40) #define CFGOPTVAL_OUTPUT_TEXT STRID5("text", 0xa60b40)
#define CFGOPTVAL_OUTPUT_TEXT_Z "text" #define CFGOPTVAL_OUTPUT_TEXT_Z "text"
@ -507,6 +510,7 @@ typedef enum
cfgOptTlsServerKeyFile, cfgOptTlsServerKeyFile,
cfgOptTlsServerPort, cfgOptTlsServerPort,
cfgOptType, cfgOptType,
cfgOptVerbose,
} ConfigOption; } ConfigOption;
#endif #endif

View File

@ -3068,10 +3068,31 @@ static const ParseRuleOption parseRuleOption[CFG_OPTION_TOTAL] =
( (
PARSE_RULE_OPTION_COMMAND(cfgCmdInfo) PARSE_RULE_OPTION_COMMAND(cfgCmdInfo)
PARSE_RULE_OPTION_COMMAND(cfgCmdRepoLs) PARSE_RULE_OPTION_COMMAND(cfgCmdRepoLs)
PARSE_RULE_OPTION_COMMAND(cfgCmdVerify)
), ),
PARSE_RULE_OPTIONAL PARSE_RULE_OPTIONAL
( (
PARSE_RULE_OPTIONAL_GROUP
(
PARSE_RULE_FILTER_CMD
(
PARSE_RULE_VAL_CMD(cfgCmdVerify),
),
PARSE_RULE_OPTIONAL_ALLOW_LIST
(
PARSE_RULE_VAL_STRID(parseRuleValStrIdNone),
PARSE_RULE_VAL_STRID(parseRuleValStrIdText),
),
PARSE_RULE_OPTIONAL_DEFAULT
(
PARSE_RULE_VAL_STRID(parseRuleValStrIdNone),
PARSE_RULE_VAL_STR(parseRuleValStrQT_none_QT),
),
),
PARSE_RULE_OPTIONAL_GROUP PARSE_RULE_OPTIONAL_GROUP
( (
PARSE_RULE_OPTIONAL_ALLOW_LIST PARSE_RULE_OPTIONAL_ALLOW_LIST
@ -8948,6 +8969,31 @@ static const ParseRuleOption parseRuleOption[CFG_OPTION_TOTAL] =
), ),
), ),
), ),
// -----------------------------------------------------------------------------------------------------------------------------
PARSE_RULE_OPTION
(
PARSE_RULE_OPTION_NAME("verbose"),
PARSE_RULE_OPTION_TYPE(cfgOptTypeBoolean),
PARSE_RULE_OPTION_REQUIRED(true),
PARSE_RULE_OPTION_SECTION(cfgSectionCommandLine),
PARSE_RULE_OPTION_COMMAND_ROLE_MAIN_VALID_LIST
(
PARSE_RULE_OPTION_COMMAND(cfgCmdVerify)
),
PARSE_RULE_OPTIONAL
(
PARSE_RULE_OPTIONAL_GROUP
(
PARSE_RULE_OPTIONAL_DEFAULT
(
PARSE_RULE_VAL_BOOL_FALSE,
),
),
),
),
}; };
/*********************************************************************************************************************************** /***********************************************************************************************************************************
@ -9342,6 +9388,7 @@ static const uint8_t optionResolveOrder[] =
cfgOptTlsServerKeyFile, cfgOptTlsServerKeyFile,
cfgOptTlsServerPort, cfgOptTlsServerPort,
cfgOptType, cfgOptType,
cfgOptVerbose,
cfgOptArchiveCheck, cfgOptArchiveCheck,
cfgOptArchiveCopy, cfgOptArchiveCopy,
cfgOptArchiveModeCheck, cfgOptArchiveModeCheck,

View File

@ -836,7 +836,7 @@ unit:
# ---------------------------------------------------------------------------------------------------------------------------- # ----------------------------------------------------------------------------------------------------------------------------
- name: verify - name: verify
total: 8 total: 12
coverage: coverage:
- command/verify/file - command/verify/file

View File

@ -69,6 +69,7 @@ testRun(void)
" stanza-upgrade Upgrade a stanza.\n" " stanza-upgrade Upgrade a stanza.\n"
" start Allow pgBackRest processes to run.\n" " start Allow pgBackRest processes to run.\n"
" stop Stop pgBackRest processes from running.\n" " stop Stop pgBackRest processes from running.\n"
" verify Verify contents of the repository.\n"
" version Get version.\n" " version Get version.\n"
"\n" "\n"
"Use 'pgbackrest help [command]' for more information.\n", "Use 'pgbackrest help [command]' for more information.\n",

File diff suppressed because it is too large Load Diff