diff --git a/doc/xml/release/2023/2.48.xml b/doc/xml/release/2023/2.48.xml index 96a11aed6..5bd6051ad 100644 --- a/doc/xml/release/2023/2.48.xml +++ b/doc/xml/release/2023/2.48.xml @@ -1,5 +1,22 @@ + + + + + + + + + + + + + +

Fix issue restoring block incremental without a block list.

+
+
+ diff --git a/doc/xml/release/contributor.xml b/doc/xml/release/contributor.xml index 817c5de39..037016d4b 100644 --- a/doc/xml/release/contributor.xml +++ b/doc/xml/release/contributor.xml @@ -940,6 +940,11 @@ seadba + + Burak Yurdakul + threenotrump + + ucando ucando diff --git a/src/command/restore/file.c b/src/command/restore/file.c index b748bc924..70652e7c2 100644 --- a/src/command/restore/file.c +++ b/src/command/restore/file.c @@ -19,6 +19,7 @@ Restore File #include "common/io/filter/group.h" #include "common/io/filter/size.h" #include "common/io/io.h" +#include "common/io/limitRead.h" #include "common/log.h" #include "config/config.h" #include "info/manifest.h" @@ -265,15 +266,6 @@ restoreFile( .compressible = repoFileCompressType == compressTypeNone && cipherPass == NULL, .offset = file->offset, .limit = repoFileLimit != 0 ? VARUINT64(repoFileLimit) : NULL); - // Add decryption filter for block incremental map - if (cipherPass != NULL && file->blockIncrMapSize != 0) - { - ioFilterGroupAdd( - ioReadFilterGroup(storageReadIo(repoFileRead)), - cipherBlockNewP( - cipherModeDecrypt, cipherTypeAes256Cbc, BUFSTR(cipherPass), .raw = true)); - } - ioReadOpen(storageReadIo(repoFileRead)); } MEM_CONTEXT_PRIOR_END(); @@ -294,8 +286,19 @@ restoreFile( // Read block map. This will be compared to the block checksum list already created to determine which // blocks need to be fetched from the repository. If we got here there must be at least one block to fetch. + IoRead *const blockMapRead = ioLimitReadNew(storageReadIo(repoFileRead), varUInt64(file->limit)); + + if (cipherPass != NULL) + { + ioFilterGroupAdd( + ioReadFilterGroup(blockMapRead), + cipherBlockNewP(cipherModeDecrypt, cipherTypeAes256Cbc, BUFSTR(cipherPass), .raw = true)); + } + + ioReadOpen(blockMapRead); + const BlockMap *const blockMap = blockMapNewRead( - storageReadIo(repoFileRead), file->blockIncrSize, file->blockIncrChecksumSize); + blockMapRead, file->blockIncrSize, file->blockIncrChecksumSize); // The repo file needs to be closed so that block lists can be read from the remote protocol ioReadClose(storageReadIo(repoFileRead)); diff --git a/test/src/module/command/restoreTest.c b/test/src/module/command/restoreTest.c index 473e216df..416d729dd 100644 --- a/test/src/module/command/restoreTest.c +++ b/test/src/module/command/restoreTest.c @@ -3101,14 +3101,15 @@ testRun(void) TEST_TITLE("full backup with block incr"); // Zeroed file large enough to use block incr + time_t timeBase = time(NULL); Buffer *relation = bufNew(256 * 1024); memset(bufPtr(relation), 0, bufSize(relation)); bufUsedSet(relation, bufSize(relation)); - HRN_STORAGE_PUT(storagePgWrite(), PG_PATH_BASE "/1/2", relation); + HRN_STORAGE_PUT(storagePgWrite(), PG_PATH_BASE "/1/2", relation, .timeModified = timeBase - 2); // Add postgresql.auto.conf to contain recovery settings - HRN_STORAGE_PUT_EMPTY(storagePgWrite(), PG_FILE_POSTGRESQLAUTOCONF); + HRN_STORAGE_PUT_EMPTY(storagePgWrite(), PG_FILE_POSTGRESQLAUTOCONF, .timeModified = timeBase - 1); // Backup argList = strLstNew(); @@ -3126,6 +3127,32 @@ testRun(void) TEST_RESULT_VOID(hrnCmdBackup(), "backup"); + // ------------------------------------------------------------------------------------------------------------------------- + TEST_TITLE("diff backup with block incr"); + + // Update timestamps so base/1/3 is slightly older than base/1/2. This will cause base/1/2 to be placed after base/1/3 in + // the bundle and since base/1/2 did not change it will be stored as a map only, which will cause restore to include it in a + // single read with base/1/3 (which is too small to be block incremental). This is to test for regression in the fix to + // separately decrypt block maps when they are in the middle/end of a bundle read. + HRN_STORAGE_TIME(storagePgWrite(), PG_PATH_BASE "/1/2", timeBase); + HRN_STORAGE_PUT_Z(storagePgWrite(), PG_PATH_BASE "/1/3", "contents", .timeModified = timeBase - 1); + + // Backup + argList = strLstNew(); + hrnCfgArgRawZ(argList, cfgOptStanza, "test1"); + hrnCfgArgRaw(argList, cfgOptRepoPath, repoPath); + hrnCfgArgRaw(argList, cfgOptPgPath, pgPath); + hrnCfgArgRawZ(argList, cfgOptRepoRetentionFull, "1"); + hrnCfgArgRawStrId(argList, cfgOptType, backupTypeDiff); + hrnCfgArgRawBool(argList, cfgOptRepoBundle, true); + hrnCfgArgRawBool(argList, cfgOptRepoBlock, true); + hrnCfgArgRawBool(argList, cfgOptOnline, false); + hrnCfgArgRawZ(argList, cfgOptRepoCipherType, "aes-256-cbc"); + hrnCfgEnvRawZ(cfgOptRepoCipherPass, TEST_CIPHER_PASS); + HRN_CFG_LOAD(cfgCmdBackup, argList); + + TEST_RESULT_VOID(hrnCmdBackup(), "backup"); + // ------------------------------------------------------------------------------------------------------------------------- TEST_TITLE("restore with block incr"); @@ -3148,6 +3175,7 @@ testRun(void) "base/\n" "base/1/\n" "base/1/2\n" + "base/1/3\n" "global/\n" "global/pg_control\n" "postgresql.auto.conf\n",