/* Get backup directory name */ if (optind >= argc)
{
pg_log_error("no backup directory specified");
pg_log_error_hint("Try \"%s --help\" for more information.", progname); exit(1);
}
context.backup_directory = pstrdup(argv[optind++]);
canonicalize_path(context.backup_directory);
/* Complain if any arguments remain */ if (optind < argc)
{
pg_log_error("too many command-line arguments (first is \"%s\")",
argv[optind]);
pg_log_error_hint("Try \"%s --help\" for more information.", progname); exit(1);
}
/* Complain if the specified arguments conflict */ if (show_progress && quiet)
pg_fatal("cannot specify both %s and %s", "-P/--progress", "-q/--quiet");
/* Unless --no-parse-wal was specified, we will need pg_waldump. */ if (!no_parse_wal)
{ int ret;
pg_waldump_path = pg_malloc(MAXPGPATH);
ret = find_other_exec(argv[0], "pg_waldump", "pg_waldump (PostgreSQL) " PG_VERSION "\n",
pg_waldump_path); if (ret < 0)
{ char full_path[MAXPGPATH];
if (find_my_exec(argv[0], full_path) < 0)
strlcpy(full_path, progname, sizeof(full_path));
if (ret == -1)
pg_fatal("program \"%s\" is needed by %s but was not found in the same directory as \"%s\"", "pg_waldump", "pg_verifybackup", full_path); else
pg_fatal("program \"%s\" was found by \"%s\" but was not the same version as %s", "pg_waldump", full_path, "pg_verifybackup");
}
}
/* By default, look for the manifest in the backup directory. */ if (manifest_path == NULL)
manifest_path = psprintf("%s/backup_manifest",
context.backup_directory);
/* By default, look for the WAL in the backup directory, too. */ if (wal_directory == NULL)
wal_directory = psprintf("%s/pg_wal", context.backup_directory);
/* *Ifthebackupdirectorycannotbefound,treatthisasafatalerror.
*/
dir = opendir(context.backup_directory); if (dir == NULL)
report_fatal_error("could not open directory \"%s\": %m",
context.backup_directory);
/* *Atthispoint,weknowthatthebackupdirectoryexists,soit'snow *reasonabletocheckforfilesimmediatelyinsideit.Thus,beforegoing *further,iftheuserdidnotspecifythebackupformat,checkfor *PG_VERSIONtodistinguishbetweentarandplainformat.
*/ if (context.format == '\0')
{ struct stat sb; char *path;
path = psprintf("%s/%s", context.backup_directory, "PG_VERSION"); if (stat(path, &sb) == 0)
context.format = 'p'; elseif (errno != ENOENT)
{
pg_log_error("could not stat file \"%s\": %m", path); exit(1);
} else
{ /* No PG_VERSION, so assume tar format. */
context.format = 't';
}
pfree(path);
}
/* *XXX:Inthefuture,weshouldconsiderenhancingpg_waldumptoreadWAL *filesfromanarchive.
*/ if (!no_parse_wal && context.format == 't')
{
pg_log_error("pg_waldump cannot read tar files");
pg_log_error_hint("You must use -n/--no-parse-wal when verifying a tar-format backup."); exit(1);
}
/* *Parseamanifestfileandreturnadatastructuredescribingthecontents.
*/ static manifest_data *
parse_manifest_file(char *manifest_path)
{ int fd; struct stat statbuf;
off_t estimate;
uint32 initial_size;
manifest_files_hash *ht; char *buffer; int rc;
JsonManifestParseContext context;
manifest_data *result;
int chunk_size = READ_CHUNK_SIZE;
/* Open the manifest file. */ if ((fd = open(manifest_path, O_RDONLY | PG_BINARY, 0)) < 0)
report_fatal_error("could not open file \"%s\": %m", manifest_path);
/* Figure out how big the manifest is. */ if (fstat(fd, &statbuf) != 0)
report_fatal_error("could not stat file \"%s\": %m", manifest_path);
/* Guess how large to make the hash table based on the manifest size. */
estimate = statbuf.st_size / ESTIMATED_BYTES_PER_MANIFEST_LINE;
initial_size = Min(PG_UINT32_MAX, Max(estimate, 256));
/* Create the hash table. */
ht = manifest_files_create(initial_size, NULL);
/* Make a new entry in the hash table for this file. */
m = manifest_files_insert(ht, pathname, &found); if (found)
report_fatal_error("duplicate path name in backup manifest: \"%s\"",
pathname);
/* Allocate and initialize a struct describing this WAL range. */
range = palloc(sizeof(manifest_wal_range));
range->tli = tli;
range->start_lsn = start_lsn;
range->end_lsn = end_lsn;
range->prev = manifest->last_wal_range;
range->next = NULL;
/* Add it to the end of the list. */ if (manifest->first_wal_range == NULL)
manifest->first_wal_range = range; else
manifest->last_wal_range->next = range;
manifest->last_wal_range = range;
}
/* Open the directory unless the caller did it. */ if (dir == NULL && ((dir = opendir(fullpath)) == NULL))
{
report_backup_error(context, "could not open directory \"%s\": %m", fullpath);
simple_string_list_append(&context->ignore_list, relpath);
/* If it's a directory, just recurse. */ if (S_ISDIR(sb.st_mode))
{
verify_plain_backup_directory(context, relpath, fullpath, NULL); return;
}
/* If it's not a directory, it should be a regular file. */ if (!S_ISREG(sb.st_mode))
{
report_backup_error(context, "\"%s\" is not a regular file or directory",
relpath); return;
}
/* Check whether there's an entry in the manifest hash. */
m = manifest_files_lookup(context->manifest->files, relpath); if (m == NULL)
{
report_backup_error(context, "\"%s\" is present on disk but not in the manifest",
relpath); return;
}
/* Flag this entry as having been encountered in the filesystem. */
m->matched = true;
/* Check that the size matches. */ if (m->size != sb.st_size)
{
report_backup_error(context, "\"%s\" has size %llu on disk but size %llu in the manifest",
relpath, (unsignedlonglong) sb.st_size,
(unsignedlonglong) m->size);
m->bad = true;
}
/* Control file contents not meaningful if CRC is bad. */ if (!crc_ok)
report_fatal_error("%s: CRC is incorrect", controlpath);
/* Can't interpret control file if not current version. */ if (control_file->pg_control_version != PG_CONTROL_VERSION)
report_fatal_error("%s: unexpected control file version",
controlpath);
/* System identifiers should match. */ if (manifest_system_identifier != control_file->system_identifier)
report_fatal_error("%s: manifest system identifier is %" PRIu64 ", but control file has %" PRIu64,
controlpath,
manifest_system_identifier,
control_file->system_identifier);
if (closedir(dir))
{
report_backup_error(context, "could not close directory \"%s\": %m",
context->backup_directory); return;
}
/* Second pass: Perform the final verification of the tar contents. */ for (cell = tarfiles.head; cell != NULL; cell = cell->next)
{
tar_file *tar = (tar_file *) cell->ptr;
astreamer *streamer; char *fullpath;
/* Should be tar format backup */
Assert(context->format == 't');
/* Get file information */ if (stat(fullpath, &sb) != 0)
{
report_backup_error(context, "could not stat file or directory \"%s\": %m",
relpath); return;
}
/* In a tar format backup, we expect only regular files. */ if (!S_ISREG(sb.st_mode))
{
report_backup_error(context, "file \"%s\" is not a regular file",
relpath); return;
}
/* *Reportanerrorifwedidn'tconsumeatleastonecharacter,ifthe *resultis0,orifthevalueistoolargetobeavalidOID.
*/ if (suffix == NULL || num <= 0 || num > OID_MAX)
{
report_backup_error(context, "file \"%s\" is not expected in a tar format backup",
relpath); return;
}
tblspc_oid = (Oid) num;
}
/* Now, check the compression type of the tar */ if (strcmp(suffix, ".tar") == 0)
compress_algorithm = PG_COMPRESSION_NONE; elseif (strcmp(suffix, ".tgz") == 0)
compress_algorithm = PG_COMPRESSION_GZIP; elseif (strcmp(suffix, ".tar.gz") == 0)
compress_algorithm = PG_COMPRESSION_GZIP; elseif (strcmp(suffix, ".tar.lz4") == 0)
compress_algorithm = PG_COMPRESSION_LZ4; elseif (strcmp(suffix, ".tar.zst") == 0)
compress_algorithm = PG_COMPRESSION_ZSTD; else
{
report_backup_error(context, "file \"%s\" is not expected in a tar format backup",
relpath); return;
}
manifest_files_start_iterate(manifest->files, &it); while ((m = manifest_files_iterate(manifest->files, &it)) != NULL) if (!m->matched && !should_ignore_relpath(context, m->pathname))
report_backup_error(context, "\"%s\" is present in the manifest but not on disk",
m->pathname);
}
/* Open the target file. */ if ((fd = open(fullpath, O_RDONLY | PG_BINARY, 0)) < 0)
{
report_backup_error(context, "could not open file \"%s\": %m",
relpath); return;
}
/* Initialize checksum context. */ if (pg_checksum_init(&checksum_ctx, m->checksum_type) < 0)
{
report_backup_error(context, "could not initialize checksum of file \"%s\"",
relpath);
close(fd); return;
}
/* Read the file chunk by chunk, updating the checksum as we go. */ while ((rc = read(fd, buffer, READ_CHUNK_SIZE)) > 0)
{
bytes_read += rc; if (pg_checksum_update(&checksum_ctx, buffer, rc) < 0)
{
report_backup_error(context, "could not update checksum of file \"%s\"",
relpath);
close(fd); return;
}
/* Close the file. */ if (close(fd) != 0)
{
report_backup_error(context, "could not close file \"%s\": %m",
relpath); return;
}
/* If we didn't manage to read the whole file, bail out now. */ if (rc < 0) return;
/* *Double-checkthatwereadtheexpectednumberofbytesfromthefile. *Normally,mismatcheswouldbecaughtinverify_plain_backup_fileand *thischeckwouldneverbereached,butthisprovidesadditionalsafety *andclarityintheeventofconcurrentmodificationsorfilesystem *misbehavior.
*/ if (bytes_read != m->size)
{
report_backup_error(context, "file \"%s\" should contain %" PRIu64 " bytes, but read %" PRIu64,
relpath, m->size, bytes_read); return;
}
/* Get the final checksum. */
checksumlen = pg_checksum_final(&checksum_ctx, checksumbuf); if (checksumlen < 0)
{
report_backup_error(context, "could not finalize checksum of file \"%s\"",
relpath); return;
}
/* And check it against the manifest. */ if (checksumlen != m->checksum_length)
report_backup_error(context, "file \"%s\" has checksum of length %d, but expected %d",
relpath, m->checksum_length, checksumlen); elseif (memcmp(checksumbuf, m->checksum_payload, checksumlen) != 0)
report_backup_error(context, "checksum mismatch for file \"%s\"",
relpath);
}
/* Should be here only for tar backup */
Assert(context->format == 't');
/* Last step is the actual verification. */
streamer = astreamer_verify_content_new(streamer, context, archive_name,
tblspc_oid);
/* Before that we must parse the tar file. */
streamer = astreamer_tar_parser_new(streamer);
/* Before that we must decompress, if archive is compressed. */ if (compress_algo == PG_COMPRESSION_GZIP)
streamer = astreamer_gzip_decompressor_new(streamer); elseif (compress_algo == PG_COMPRESSION_LZ4)
streamer = astreamer_lz4_decompressor_new(streamer); elseif (compress_algo == PG_COMPRESSION_ZSTD)
streamer = astreamer_zstd_decompressor_new(streamer);
/* *Printoutusageinformationandexit.
*/ staticvoid
usage(void)
{
printf(_("%s verifies a backup against the backup manifest.\n\n"), progname);
printf(_("Usage:\n %s [OPTION]... BACKUPDIR\n\n"), progname);
printf(_("Options:\n"));
printf(_(" -e, --exit-on-error exit immediately on error\n"));
printf(_(" -F, --format=p|t backup format (plain, tar)\n"));
printf(_(" -i, --ignore=RELATIVE_PATH ignore indicated path\n"));
printf(_(" -m, --manifest-path=PATH use specified path for manifest\n"));
printf(_(" -n, --no-parse-wal do not try to parse WAL files\n"));
printf(_(" -P, --progress show progress information\n"));
printf(_(" -q, --quiet do not print any output, except for errors\n"));
printf(_(" -s, --skip-checksums skip checksum verification\n"));
printf(_(" -w, --wal-directory=PATH use specified path for WAL files\n"));
printf(_(" -V, --version output version information, then exit\n"));
printf(_(" -?, --help show this help, then exit\n"));
printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.24 Sekunden
(vorverarbeitet am 2026-08-08)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.