1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2026-10-23 21:28:56 +03:00

5113 Commits

Author SHA1 Message Date
David Steele
2e1c4f2ba7 v2.60.0: Page Checksum Errors and Object Store Prefetch
IMPORTANT NOTE: The restore command will now error and stop if any restored file contains an invalid page checksum, which leaves the cluster incomplete and unable to start, so the restore must be rerun. The expire command will now refuse to expire if the oldest retained backup would contain checksum errors, which also prevents archive from being expired in that repository. Set checksum-page-error=n to warn instead of erroring.

Features:

* Add checksum-page-error option to error on invalid page checksums. (Contributed by Andrew Pogrebnoi. Reviewed by Douglas J Hunley, David Steele, Greg Sabino Mullane, Stefan Fercot. Suggested by Greg Sabino Mullane.)
* Support for GCS Workload Identity Federation. (Contributed by Adol Rodriguez. Reviewed by Douglas J Hunley, David Steele.)
* Prefetch multi-file/range reads from object stores. (Reviewed by Andrew Pogrebnoi, Douglas J Hunley.)

Improvements:

* Reduce keep-alive requests to remote processes. (Reviewed by Douglas J Hunley.)
* Retry Azure batch sub-requests missing from the response. (Reviewed by Douglas J Hunley.)
* Reduce the manifest checks made when reconstructing backup.info. (Reviewed by Douglas J Hunley. Suggested by todeb.)
* Use original size to determine if a file should be bundled. (Reviewed by Douglas J Hunley.)
* Add context to block map read errors during backup. (Reviewed by Stefan Fercot, Douglas J Hunley.)
* Cast current_timestamp to text in the backup start label. (Contributed by Tiago Corcelli de Oliveira. Reviewed by David Steele.)
* Support building on illumos. (Contributed by Kalevi Kolttonen. Reviewed by Douglas J Hunley, David Steele.)
* Support OpenSSL 4. (Reviewed by Douglas J Hunley.)
* Fix clang builds. (Reviewed by Douglas J Hunley.)
* Migrate code generation from C to Python.

Documentation Improvements:

* Modernize the website. Special thanks to Jan for the insightful (and hilarious) logo. (Contributed by David Steele, Jan Wieremjewicz. Reviewed by Steve Atkins, Kai Wagner, Douglas J Hunley, Nikolaos Stamatopoulos, Matt Hudson, Stefan Fercot.)
* Document the release schedule.
* Migrate the documentation tools from C and Perl to Python.

Test Suite Improvements:

* Migrate the test harness from C and Perl to Python.
* Add a Fil-C job to CI.
* Add fuzz testing for the JSON, Ini, and Pack readers.
* Allow test user names that adduser on Debian rejects. (Contributed by Stefan Fercot. Reviewed by David Steele.)
release/2.60.0
2026-10-18 10:31:33 +02:00
David Steele
1911f5f174 End the release note history at upstream integration in CI.
The release notes are checked against git history that ends at the newest commit of HEAD that is also in integration, which leaves out commits made only on a release branch. CI has no local integration branch so the history ended at HEAD and included release branch commits. A release branch commit with the same subject as an integration commit then made the subject ambiguous.

CI now fetches integration from the project repository into refs/remotes/upstream/integration. The history ends at the merge-base with the local integration branch or else with the upstream branch. The origin/integration branch of a fork is not used since it may be stale.

The check that every commit is claimed still runs only when the local integration branch is related to HEAD.
2026-10-18 09:37:15 +02:00
David Steele
28a1e3d627 Adjust keyword styling in light mode.
Inline keywords such as options, paths, and ids are now .9em rather than .8125em. In light mode their box uses a lighter background and border, which brings it closer to the contrast of dark mode.

Use weight 500 for names and commands in light mode since 600 looked too heavy. Dark mode stays at 600.
2026-10-17 23:10:17 +02:00
David Steele
bd8f99be00 Add release note details for v2.33 through v2.60.
Every item in v2.33 through v2.60.0 other than development items now has a summary, and every commit of each release is claimed by an item. Commits that were not mentioned are grouped into development items. Missing and incorrect pull request and issue references were fixed by comparing against GitHub.

When the local integration branch is related to HEAD, a release with details must claim every commit. A commit whose subject is not unique in a release is named with <commit id="..." subject="...">. Commits back-patched to a patch release are listed in <release-backpatch-list> and shown only on a development build. The commits of a patch release are read from its release/x.y.z tag.

A development item can specify the release note of a later release with <release-note version="..." subject="...">. The release note lists the development commits in its details, and on a development build the development item links to the release note. A release in development can be specified by its final version, e.g. 2.61.0 for 2.61.0dev.

Add the tester contributor role, which replaces the comments that recorded testers as reviewers.

Disable scroll anchoring on the page so opening details does not scroll the page in Firefox.
2026-10-17 20:53:47 +02:00
David Steele
e96d03cb38 Bump CI container for PostgreSQL 19rc1. 2026-10-16 08:19:02 +02:00
David Steele
3ae42b9080 Fix incorrect file reference in comment. 2026-10-16 08:18:14 +02:00
David Steele
050ca9e354 Add expandable details to the release notes.
Release items from 2.60 on put the paragraphs after the first and the links to their issues, pull requests, and commits in a details element that is closed by default. The toggle is drawn in place of the bullet: a plus when the details have text and a chevron when they have only links. An item with more than one commit has a line for each commit with its subject. A button on each release opens or closes all of its details. Older releases still join extra paragraphs into one line.

The version pattern for bug fix releases also matched x.y.0, so the commits of a release reached back to v2.53, and 2.54.0 through 2.59.0 were only checked while each was the most recent release. A bug fix release is now x.y.z with z > 0, so a release keeps its commit links after a newer release is added. The 2.55.0 PostgreSQL 18 item now has a commit element because the note was reworded after the commit.

The GCS Workload Identity Federation item now has a commit element because its text does not match the commit subject.
2026-10-13 18:41:57 +02:00
David Steele
a8c1a58116 Generate git-history.cache from git instead of committing it.
The doc build now writes doc/resource/git-history.cache from git log when HEAD is not the newest commit in it. The file is ignored rather than committed, and release.py no longer updates it.

The committed cache had subject fixes that release items matched against. Those items now have a commit element with the subject git reports, and the rendered release notes are unchanged.

The doc and dist workflows fetch the full history since the release notes are checked against it.
2026-10-13 11:48:34 +02:00
David Steele
4c65010e7d Add fuzz testing for the JSON, ini, and pack readers.
Add libFuzzer targets for the JSON, ini, and pack readers in test/fuzz. Each target runs an input through the reader and discards the errors thrown for invalid input, while a failed assertion or a sanitizer report aborts. The targets are linked with the objects of the pgbackrest binary when the fuzz-engine meson option is set.

test/fuzz/build.sh builds the targets and seed corpora in the environment that OSS-Fuzz defines, so an OSS-Fuzz project can call it if pgBackRest is accepted. Until then the fuzz workflow builds the targets with clang, AddressSanitizer, and UndefinedBehaviorSanitizer and runs each one for a fixed number of runs. The seed is derived from the commit hash and ASLR and corpus reloading are disabled, so a failed run can be reproduced.

Fuzzing found or led to the bugs fixed in these commits:

81704daaf Check the mem context stack bounds in production builds.
4b8e3f438 Limit the depth of nested containers when reading JSON.
b623071a2 Fix reads past the end of JSON strings ending in an escape.
2b919843a Reject unexpected commas when reading JSON.
1808295cc Reject \u0000 when reading JSON strings.
b63357ee8 Accept hex digits in JSON \u escapes when skipping.
6963016c7 Error on invalid field ids when reading packs.
0966ebbb3 Check pack field sizes against the bytes remaining in a buffer.
950c1e149 Error on zero-size pack fields.
2026-10-13 11:26:52 +02:00
David Steele
950c1e149f Error on zero-size pack fields.
A pack written by pckWrite always ends with an end tag, so a nested pack is at least one byte. A zero-size pack field from corrupt data was read into an empty buffer with no allocation, which failed the buffer != NULL assertion in pckReadNewC() in debug builds. Production builds failed with unexpected EOF on the first read from the empty pack.

Error with invalid pack size in pckReadPack() and pckReadPackReadConst() when the size of a pack field is zero.
2026-10-12 11:32:44 +02:00
David Steele
0966ebbb3e Check pack field sizes against the bytes remaining in a buffer.
pckReadBin() and pckReadPack() allocate a buffer of the size stored for a field before reading the field. A pack read from a buffer with a corrupt size could allocate far more memory than the pack holds before failing with unexpected EOF.

Error when the size of a field is larger than the bytes remaining in the buffer. A pack read through an IoRead still allocates the size stored for the field because the total size is not known.
2026-10-12 11:16:00 +02:00
David Steele
6963016c77 Error on invalid field ids when reading packs.
pckReadTagNext() cut the high-order bits of the field id delta down to an unsigned int and added it to the last id without checking the result. A large delta could produce UINT_MAX, the id that marks the end of a container, or wrap to an id at or below the last id. Either one left the read out of step with the tags, which asserted in debug builds and misread fields in production builds.

Error when the id is not greater than the last id or is UINT_MAX.
2026-10-12 10:36:16 +02:00
David Steele
b63357ee89 Accept hex digits in JSON \u escapes when skipping.
jsonReadSkipStr() required four decimal digits after \u, so a valid escape with hex digits such as \u003c failed validation even though jsonReadStr() decodes it. Accept hex digits so skipping agrees with reading.

pgBackRest does not write \u escapes, so this only affects JSON from other sources. jsonReadStr() still decodes only \u00XX, as a single byte.
2026-10-12 10:31:13 +02:00
David Steele
1808295ccd Reject \u0000 when reading JSON strings.
jsonReadStr() decoded a \u0000 escape and appended a null character to the string. This asserted in debug builds and created a string with an embedded null in production builds, which is cut short anywhere the string is used as a zero-terminated string.

Error on \u0000. A string cannot store a null character.
2026-10-12 10:27:52 +02:00
David Steele
2b919843ad Reject unexpected commas when reading JSON.
jsonReadTypeNextIgnoreComma() looks past a comma to find the type of the next value, but jsonReadPush() only consumed a comma between values in a container. A comma before a value outside a container, before the first value in a container, or before an object value was left in place. The string readers asserted on it in debug builds and read it as the opening quote of a string in production builds.

Error on a comma in these positions.
2026-10-12 10:25:11 +02:00
David Steele
b623071a2a Fix reads past the end of JSON strings ending in an escape.
jsonReadSkipStr() stepped past the terminating null when a backslash was the last character. It also advanced one character too far after a \uXXXX escape, which skipped the closing quote when the escape was the last character of the string and stepped past the terminating null when the escape was at the end of the input. jsonReadStr() did not check that both digits of a \u00XX escape were present, so a truncated escape at the end of the input was read past the terminating null.

The skip path validates the JSON values in info files and manifests. The read path reads object store responses.
2026-10-12 10:10:20 +02:00
David Steele
4b8e3f4386 Limit the depth of nested containers when reading JSON.
Each level of a nested array switches to a mem context in jsonReadVar() and each level of any container recurses, so deeply nested JSON could fill the mem context stack or exhaust the C stack. JSON is read from repository info files, manifests, lock files, and object store responses, which pgBackRest does not always control.

Error when the depth exceeds 32. The JSON pgBackRest reads does not nest more than a few levels.
2026-10-12 09:50:17 +02:00
David Steele
81704daaf4 Check the mem context stack bounds in production builds.
memContextNew() added to the mem context stack without checking for space. memContextSwitch() asserted on the current stack index, which can be lower than the index being written, and the assert only existed in debug builds. Filling the stack overwrote the memory after it. Nested JSON arrays in a repository info file or an object store response can fill the stack because each level of an array switches to a mem context.

Check the max stack index in both functions with CHECK() so the error is also reported in production builds.
2026-10-12 09:32:10 +02:00
David Steele
80d46a0bef Add UNLIKELY() macro for error checks.
Add UNLIKELY(), based on __builtin_expect() when the compiler supports it, and use it in CHECK(), ASSERT(), and the THROW*_ON_SYS_ERROR*() macros. The condition in these macros is an error, so the compiler can lay out the code for the path where the condition is false.

The ASSERT() redefinition in the test harness uses UNLIKELY() as well so modules that include the same code compile it with the same branches.
2026-10-12 09:20:47 +02:00
Tiago Corcelli de Oliveira
86936e1fa6 Cast current_timestamp to text in the backup start label.
A user-defined implicit cast from timestamptz to text makes the || operator in the backup label ambiguous and the backup fails with "operator is not unique: unknown || timestamp with time zone". Casting current_timestamp to text resolves the operator to text || text. The label text is unchanged.
2026-10-11 09:18:23 +02:00
retrodood
ad72e5815f Support building on illumos.
The illumos headers hide POSIX and extension functions when compiling with -std=c11 unless a feature test macro is defined, which left kill(), sigaction(), gmtime_r(), localtime_r(), nanosleep(), and flock() undeclared.

Define _POSIX_C_SOURCE=200809L and __EXTENSIONS__ in meson when the host system is sunos, as is done for Linux and MacOS. _POSIX_C_SOURCE alone does not expose flock(). Also link libsocket, which is separate from libc on illumos. Meson reports sunos for Solaris as well, but Solaris has not been tested.

Add an OmniOS job to CI that runs selected unit tests and the smoke test against the oldest and newest available PostgreSQL versions.
2026-10-09 17:31:57 +02:00
David Steele
c179f3f7d6 Update release numbering in the release notes intro.
Release numbers have three parts: major, minor, and patch. Patch releases contain bug fixes for the most recent minor release.
2026-10-08 19:02:09 +02:00
David Steele
9bca69a591 Document the release schedule.
Feature releases are on the third Monday of Jan, Apr, Jul, and Oct. Patch releases are on the third Monday of any other month as required.
2026-10-08 18:51:30 +02:00
David Steele
8429046135 Restore the archive.info save to backup.
Archive key rotation moved the archive.info save from backup to expire, which changed when the archive.info timestamps are refreshed for object store lifecycle rules. With expire-auto disabled, or when expire errors before the save, the timestamps were no longer refreshed after a backup.

Backup now saves archive.info as before. Expire loads archive.info only when repo-cipher-rotate is set and saves it only after a rotation.
2026-10-07 15:27:00 +02:00
David Steele
5a78374f54 Skip deploying pages that differ from the site only in the release date.
A deploy previously replaced every page in the site and in the prior version path, so pages with no changes other than the date in the footer showed as changed. Pages that differ from the copy on the site only in the release date and year are now left as they are. This applies to the site and to the prior version path, so a text-only change deployed after a release updates only the affected pages in both.
2026-10-07 15:07:30 +02:00
David Steele
269764c5b7 Make the repo-format and repo-cipher-rotate options internal.
Repository format 6 and archive key rotation are not complete enough to release. Internal options are left out of the documentation and help but are still accepted, which allows format 6 to be tested with this release.
2026-10-07 13:58:08 +02:00
Adol Rodriguez
1d217e6263 Add web-id key type for GCS Workload Identity Federation.
repo-gcs-key-type=web-id authenticates using an external account credential file (https://google.aip.dev/auth/4117) as generated by gcloud iam workload-identity-pools create-cred-config. The file is specified by repo-gcs-key and defines the audience, the STS token url, and the file that contains the web identity token. The web identity token is exchanged at the STS endpoint for an access token that authenticates the federated principal directly, without a service account key.

The web identity token file is reloaded on each exchange to pick up rotated tokens, e.g. Kubernetes projected service account tokens.

Service account impersonation (service_account_impersonation_url) is not supported. Only the file credential source is supported, and when credential_source.format is specified the type must be text.

Authentication token responses that are not JSON (e.g. an HTML error page) or that contain a nested error object are now reported with httpRequestError() rather than failing while parsing the response. This applies to all GCS key types that request a token.
2026-10-06 10:06:32 +02:00
David Steele
08fbb97b85 Merge v2.59.3 release. 2026-10-06 09:22:02 +02:00
j0uinim
0d2b88de32 Fix possible weak encryption subkeys/salts.
The result of RAND_bytes() was ignored in cryptoRandomBytes(). When OpenSSL is unable to get random data from the operating system, RAND_bytes() fails and leaves the buffer unchanged, so a subkey was generated from uninitialized stack memory and a salt from data already in the output buffer. No error was reported and the repository was written with weak subkeys and salts.

Throw a CryptoError when RAND_bytes() fails, as other OpenSSL failures do. Test the error with a RAND_bytes() shim in the test module.
2026-10-02 15:40:33 +02:00
Stefan Fercot
2a805e22b6 Check the result of EVP_BytesToKey() and HMAC().
The results were ignored, so a failure would go unnoticed and leave the key and initialization vector or the HMAC uninitialized. Throw a CryptoError instead, as other OpenSSL failures do.
2026-10-02 15:10:22 +02:00
j0uinim
cb79ce7452 Fix possible checksum error loading encrypted info and manifest files.
A read that does not block stops as soon as it has output, and end of file is set on a later read that gets no output. When a filter needed the same input again the read did not stop, so end of file could be set while output remained in the internal buffer used by the line and small readers. A read that loops until end of file then lost whatever remained in that buffer.

The block cipher filter needs the same input again on flush when the buffer has less room than the final block. A large encrypted info or manifest file could lose its last lines on load, which fails the checksum. This requires the lost lines to fit in the final block, so it is unlikely in practice. A filter that holds its output until the end of its input, such as the authenticated cipher proposed in #2866, would hit this on any file larger than the buffer.

Stop a read that does not block as soon as it has output, whether or not a filter needs the same input again.
2026-10-02 10:07:17 +02:00
David Steele
6e28c0445f Add archive key rotation for repository format 6.
At format 6 the repository can hold more than one encryption key. This is used to rotate the archive key, which is currently the only bulk data key that never changes.

Keys are stored by id in the new CipherSpecMap, with one key marked current for encrypting new files. At format 6 the cipher section of an info file stores every key by id along with the digest it derives with, e.g. cipher-pass={"1":{"digest":"sha256","key":"..."}}, and archive.info also records the current key id in cipher-pass-current. Format 5 files keep their single key, which is loaded as the key with no id.

archive-push writes the id of the archive key it used into the header of each WAL file. The readers use that id to select the key: archive-get, verify, the restore timeline read, the backup archive copy, and repo-get. A pusher that loaded archive.info before a rotation keeps working since each file records the key it was encrypted with. The filter that writes the header now also works through a repository host.

stanza-create for format 6 creates the first archive key with id 1. stanza-upgrade from format 5 keeps the existing archive key as id 0 so WAL already archived stays readable, and adds a key at id 1 for new WAL.

The new repo-cipher-rotate option sets how old the current archive key may be before expire replaces it, with a default of 90 days and a minimum of 15. Expire adds the new key, makes it current, and records the time of the rotation in archive.info as cipher-pass-rotate. At format 5 the option has no effect and warns only when set explicitly.

Expire now saves archive.info on every run. Backup did this only to refresh the timestamps for object store lifecycle rules and no longer writes archive.info, so with expire-auto disabled the timestamps are refreshed only when expire runs.
2026-10-02 08:37:45 +02:00
David Steele
3b9f27c086 Remove the default digest from CipherSpec.
CipherSpec defaulted to SHA-256 when no digest was provided, the default for repository format 6. To work around this, the repository passphrase explicitly set SHA-1, and the format filter ignored the digest on the spec entirely in favor of the digest for the format of the file. That made it hard to tell when the digest on a spec was authoritative.

Now a spec with no digest determines it from the format of the file being read or written, and a digest set on the spec is always used. The block cipher and Info both assert that a digest has been set, so a missing digest is caught right away instead of quietly producing the wrong key.

There is no change in behavior. Tests that use the block cipher directly now set the digest explicitly.
2026-09-30 08:47:43 +03:00
David Steele
9405fd92e2 Check that every code module is covered by a test module.
The linter now reports C source in src and python modules in the tool libraries that are not in a coverage list in test/define.yaml. Generated .auto.c.inc modules are exempt, and modules that no test fully covers are on a skip list: src/main.c, the prototype manifest command, and vendored xxHash.

The mapping from a coverage entry to a file moves from the coverage command to define.py for use by the linter.

The C coverage filter no longer matches a harness .c.inc under test/src/harness as the module with the same path in src.
2026-09-28 18:31:23 +03:00
David Steele
82b3f91b4b Add missing modules to unit test coverage.
The info/manifest .c.inc modules, command/backup/resume.c.inc, and common/exec.c were not in any coverage list in test/define.yaml, so their coverage was never checked. The .c.inc modules were already fully covered.

The exec harness includes common/exec.c but execOne() had no tests, so add tests for output, an error exit, and a signal.
2026-09-28 18:29:41 +03:00
David Steele
a729ac881f Fix compilation with OpenSSL 4.
X509_get_subject_name() returns const X509_NAME * in OpenSSL 4. Make the subject name const and cast away the const for X509_NAME_get_index_by_NID(), which does not accept a const name before OpenSSL 3.

OpenSSL 4 also renamed the SSLv3 alert reason strings, e.g. "sslv3 alert handshake failure" is now "tls alert handshake failure". Add the new message to the TLS tests.
2026-09-28 09:39:58 +03:00
David Steele
0b9d7f66db Merge coding standards into the contributing guide.
The coding standards in CODING.md and the Coding section of CONTRIBUTING.md covered the same ground, e.g. objects were described in one while memory contexts and logging were described in the other. Move the standards into the Coding section of CONTRIBUTING.md, move the uncoverable/uncovered code markers into Writing a Unit Test, and remove CODING.md.

Section depth is limited to three, so Naming is now a peer of Standards and Braces and Hints, Warnings, and Errors are now part of Standards.
2026-09-27 18:08:51 +03:00
David Steele
fe2c883c23 Allow test.py options before the command.
When a command is given, argparse parses the options of the command into a new namespace and copies every value, including defaults, over the values the main parser set from options before the command. As a result, `test.py --vm=u24 vm-build` ran vm-build with --vm=none.

Move the command to the front of the argument list before parsing so the command parser parses every option. An option before the command that the command does not define is now an error instead of being ignored.
2026-09-27 17:42:33 +03:00
David Steele
36afca4636 Merge v2.59.2 release. 2026-09-27 17:07:37 +03:00
David Steele
3c821e6ba2 PostgreSQL 19beta4 support.
Beta4 reverted online data checksum transitions, which removed the in-progress checksum states and the checkpoint checksum state and bumped PG_CONTROL_VERSION:

https://git.postgresql.org/gitweb/?p=postgresql.git;a=commitdiff;h=c05d5ce12366f1008e95b09a0a9b08a49818b0e1

A follow-up to the revert bumped XLOG_PAGE_MAGIC:

https://git.postgresql.org/gitweb/?p=postgresql.git;a=commitdiff;h=4a9a6c5a69c45ba74c694ccb1ff5921dd09a6676

Beta4 also added data_checksum_version_init to pg_control:

https://git.postgresql.org/gitweb/?p=postgresql.git;a=commitdiff;h=aaf8b9989f7aaeb6594122caee1eea6c87e10655

The page checksum version in pg_control is again limited to 0 and 1, so check it against PG_DATA_CHECKSUM_VERSION for all versions and remove the handling of the in-progress states added for PostgreSQL 19.
2026-09-27 12:11:20 +03:00
David Steele
95a14644f3 Allow time option values greater than 49 days.
Time option values are stored in milliseconds but the table of time values in the generated parse rules was declared unsigned int, which limits a value to about 49.7 days. A default or allow-range bound larger than that overflows and fails to compile. The parser already reads time values, range bounds, and option values as int64_t, so declare the table as int64_t to match the table of size values.

All existing time option values fit in either type, so none of them change.
2026-09-24 11:19:15 +03:00
Stefan Fercot
ce19dae6a2 Allow test user names that adduser on Debian rejects.
The test container is built with the same user name and id as the user on the host so files created inside it are owned by that user. Debian's adduser only accepts a name that matches NAME_REGEX, which excludes a dot, so vm-build failed on the Debian-based images for a host user named e.g. first.last.

Pass the flag that skips the check, since the name comes from the account running the build rather than from outside. The flag was renamed from --force-badname to --allow-bad-names and Ubuntu 22.04 ships a version that only knows the old spelling, so the spelling is taken from adduser --help when the container is built rather than hardcoded.
2026-09-17 10:59:27 +03:00
Andrew Kroh
0a6b047292 Fix async archive-push stalling on a zero-size backup history file.
The archive-push-batch-size option converts the batch size to a segment count using the size of the first file in the ready list, but that list also contains backup and timeline history files. PostgreSQL does not sync backup history files, so a cluster copied from a storage snapshot may contain one with zero size, which caused every asynchronous run to error and prevented any WAL from being archived.

Use the size of the first ready WAL segment instead and skip the batch size when no segment is ready, since the remaining files are small. A zero-size WAL segment is still an error.
2026-09-16 21:57:13 +03:00
Andrew Pogrebnoi
d7f54b2245 Add checksum-page-error option to error on invalid page checksums.
The new checksum-page-error option determines whether invalid page checksums are reported as a warning or an error. It is available for the backup, restore, and expire commands.

For backup the option defaults to false, which preserves the prior behavior of emitting a warning for each file that contains invalid page checksums. A warning with the total number of checksum warnings is now emitted at the end of the backup so they are less likely to be missed in the log. When the option is enabled the backup terminates with an error as soon as an invalid page checksum is found. Page misalignment is handled the same way since it prevents checksum validation.

For restore the option defaults to true. When the option is enabled the restore terminates with an error as soon as a file with invalid page checksums is restored. Since this happens before pg_control is renamed, the cluster cannot be started. When the option is disabled a warning is emitted for each file that contains invalid page checksums and the total is reported at the end of the restore. Previously the restore command did not report invalid page checksums at all.

For expire the option defaults to true. If the backup that would become the oldest retained backup contains invalid page checksums then expire terminates with an error before removing anything. When the option is disabled expire proceeds and emits a warning if the oldest retained backup contains invalid page checksums. An adhoc expire proceeds in either case but still emits the warning.
2026-09-15 10:56:36 +03:00
David Steele
917f27c7b3 Fix pagination of S3 version listing.
Listing with a repository target time uses the ListObjectVersions API but pagination read NextContinuationToken from the response, which is only returned by ListObjectsV2. A version listing truncated at the maximum key count failed with "unable to find child 'NextContinuationToken':0 in node 'ListVersionsResult'", so a repository with more than a thousand versions under a prefix could not be read at a target time.

Paginate the version listing with NextKeyMarker and NextVersionIdMarker instead. NextVersionIdMarker is missing or empty when the listing is truncated at a common prefix, in which case the next page starts at the key after the key marker, which is what is needed.

Build the query for each page from the base query so a marker set for one page is not reused for the next.

The mock response used to test version listing pagination returned a continuation token rather than markers, so the error was not caught.
2026-09-15 10:33:08 +03:00
David Steele
ce590e9d9b Fix mem context in GCS service key parsing.
The parsed key JSON, the file read buffer, and intermediate strings were allocated in the object context rather than a temp context, so they stayed allocated for the life of the storage object.
2026-09-14 11:35:23 +03:00
David Steele
700722b8b5 Derive the cipher key with SHA-256 at repository format 6.
Format 6 derives encryption keys from the passphrase with SHA-256 rather than SHA-1. Older repositories keep SHA-1, since the migration does not rewrite the files they already encrypted. The digest is picked when a passphrase is generated rather than at each use, so it is stored with the passphrase in CipherSpec and defaults to SHA-256.

A passphrase outlives the format of the file it is stored in, so the format cannot decide how the key is derived. Info files at format 6 store cipher-digest beside cipher-pass, and a passphrase stored without one derives with SHA-1. stanza-upgrade writes back the digest the passphrase already had, so archives and backups from before the migration still load. Only a new passphrase gets SHA-256: the one for a stanza created at format 6, or for the manifest of a full backup written at it.

The info file has the same problem in reverse. Its format sits inside the content that the passphrase decrypts, so a reader would have to decrypt the file to find out how to decrypt it. Format 6 puts a plaintext header in front instead: PGBR, the three-digit format, and a spare byte for later. It goes where the OpenSSL Salted__ magic used to go and is the same eight bytes, so the salt still starts in the same place. A file that starts with the magic came from format 5, the only format older than the header. The header format is checked before anything is decrypted, then compared against the format found in the content, so a file stitched together from two others is rejected rather than half read.

The format constants move out of version.h into a new common/format, along with repoFormatValidate(), repoFormatDigest(), and CipherBlockFormat. That filter reads the header, builds a block cipher with the digest the format calls for, and streams the rest of the file through it. The block cipher no longer knows anything about repositories: it takes the digest from the spec and takes what sits in front of the content as a parameter.

Verify checksums info files as stored rather than as decrypted, since the header has to come off before decryption can start. A file and its copy are written from the same bytes, so comparing the two still works. In an encrypted repository created by a Perl version the file and its copy are separate encryptions, so their checksums differ and verify reports it at detail level. A log line is better than a separate code path for repositories with seven year old backups.
2026-09-11 18:25:19 +03:00
David Steele
567a245124 Retry Azure batch sub-requests missing from the response.
Response parts were mapped to sub-requests by iterating the parts, so when the service returned fewer parts than were requested the remaining sub-requests were silently treated as complete. Azure fails the entire batch when it is unable to parse the request and reports the failure as a single response part, so only one file per batch was removed while the command reported success.

Iterate the sub-requests instead and retry any that have no response part, just as parts that report an error are retried. More response parts than sub-requests is now an error.
2026-09-11 11:20:23 +03:00
David Steele
777d69de1a Set the Azure version header for SAS authentication.
The version header was not set for SAS since a SAS specifies a version in the sv parameter. That is sufficient for normal requests but batch requires the version to be set explicitly and rejects the entire batch with 400 InvalidInput when it is missing.

Azure reports the rejection as a single response part with no content-id, so it appeared that one sub-request had failed rather than that none had run. That part was retried individually and the remaining files in the batch were silently not deleted, leaving expired backups and WAL in the repository while expire reported success.
2026-09-09 18:00:36 +03:00
David Steele
5440bedafc Disable read retry when filters are passed to the driver.
When a read is retried the driver is freed and recreated but the filter group is not set on the new driver. For drivers that require the filter group (i.e. remote) this causes a segfault when the read is opened because the filter group is NULL.

This was introduced in a953aa74b, which changed retry to recreate the driver rather than reopen it, added the filter group callback that is only set when the read is created, and replaced the driver retry flag with the storageFeatureReadRetry feature, which remote storage inherits from the repository host. Before that commit retry was never enabled for remote reads.

Even with the filter group set the retry could not work because the filters are passed to the remote when the read is opened so they cannot be run again, and bytes read are counted after the filters have been applied so the offset to restart the read cannot be determined.

Read retry is only enabled for object stores so this happened when the repository was on an object store accessed via a repository host. The repository host still retries the object store read, where the filters are applied above the driver and the retry works correctly, so the only retry lost is a redundant second attempt by the client.
2026-09-03 20:36:40 +03:00