You've already forked pgbackrest
							
							
				mirror of
				https://github.com/pgbackrest/pgbackrest.git
				synced 2025-10-30 23:37:45 +02:00 
			
		
		
		
	This header does not contain a type but is used to define types so this seems like a better location.
		
			
				
	
	
		
			3085 lines
		
	
	
		
			126 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			3085 lines
		
	
	
		
			126 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /***********************************************************************************************************************************
 | |
| Backup Manifest Handler
 | |
| ***********************************************************************************************************************************/
 | |
| #include "build.auto.h"
 | |
| 
 | |
| #include <ctype.h>
 | |
| #include <string.h>
 | |
| #include <time.h>
 | |
| 
 | |
| #include "common/crypto/cipherBlock.h"
 | |
| #include "common/debug.h"
 | |
| #include "common/log.h"
 | |
| #include "common/regExp.h"
 | |
| #include "common/type/json.h"
 | |
| #include "common/type/list.h"
 | |
| #include "common/type/mcv.h"
 | |
| #include "common/type/object.h"
 | |
| #include "info/info.h"
 | |
| #include "info/manifest.h"
 | |
| #include "postgres/interface.h"
 | |
| #include "postgres/version.h"
 | |
| #include "storage/storage.h"
 | |
| #include "version.h"
 | |
| 
 | |
| /***********************************************************************************************************************************
 | |
| Constants
 | |
| ***********************************************************************************************************************************/
 | |
| STRING_EXTERN(BACKUP_MANIFEST_FILE_STR,                             BACKUP_MANIFEST_FILE);
 | |
| 
 | |
| STRING_EXTERN(MANIFEST_TARGET_PGDATA_STR,                           MANIFEST_TARGET_PGDATA);
 | |
| STRING_EXTERN(MANIFEST_TARGET_PGTBLSPC_STR,                         MANIFEST_TARGET_PGTBLSPC);
 | |
| 
 | |
| STRING_STATIC(MANIFEST_TARGET_TYPE_LINK_STR,                        "link");
 | |
| STRING_STATIC(MANIFEST_TARGET_TYPE_PATH_STR,                        "path");
 | |
| 
 | |
| STRING_STATIC(MANIFEST_SECTION_BACKUP_STR,                          "backup");
 | |
| STRING_STATIC(MANIFEST_SECTION_BACKUP_DB_STR,                       "backup:db");
 | |
| STRING_STATIC(MANIFEST_SECTION_BACKUP_OPTION_STR,                   "backup:option");
 | |
| STRING_STATIC(MANIFEST_SECTION_BACKUP_TARGET_STR,                   "backup:target");
 | |
| 
 | |
| STRING_STATIC(MANIFEST_SECTION_DB_STR,                              "db");
 | |
| 
 | |
| STRING_STATIC(MANIFEST_SECTION_TARGET_FILE_STR,                     "target:file");
 | |
| STRING_STATIC(MANIFEST_SECTION_TARGET_FILE_DEFAULT_STR,             "target:file:default");
 | |
| 
 | |
| STRING_STATIC(MANIFEST_SECTION_TARGET_LINK_STR,                     "target:link");
 | |
| STRING_STATIC(MANIFEST_SECTION_TARGET_LINK_DEFAULT_STR,             "target:link:default");
 | |
| 
 | |
| STRING_STATIC(MANIFEST_SECTION_TARGET_PATH_STR,                     "target:path");
 | |
| STRING_STATIC(MANIFEST_SECTION_TARGET_PATH_DEFAULT_STR,             "target:path:default");
 | |
| 
 | |
| #define MANIFEST_KEY_BACKUP_ARCHIVE_START                           "backup-archive-start"
 | |
|     STRING_STATIC(MANIFEST_KEY_BACKUP_ARCHIVE_START_STR,            MANIFEST_KEY_BACKUP_ARCHIVE_START);
 | |
| #define MANIFEST_KEY_BACKUP_ARCHIVE_STOP                            "backup-archive-stop"
 | |
|     STRING_STATIC(MANIFEST_KEY_BACKUP_ARCHIVE_STOP_STR,             MANIFEST_KEY_BACKUP_ARCHIVE_STOP);
 | |
| #define MANIFEST_KEY_BACKUP_LABEL                                   "backup-label"
 | |
|     STRING_STATIC(MANIFEST_KEY_BACKUP_LABEL_STR,                    MANIFEST_KEY_BACKUP_LABEL);
 | |
| #define MANIFEST_KEY_BACKUP_LSN_START                               "backup-lsn-start"
 | |
|     STRING_STATIC(MANIFEST_KEY_BACKUP_LSN_START_STR,                MANIFEST_KEY_BACKUP_LSN_START);
 | |
| #define MANIFEST_KEY_BACKUP_LSN_STOP                                "backup-lsn-stop"
 | |
|     STRING_STATIC(MANIFEST_KEY_BACKUP_LSN_STOP_STR,                 MANIFEST_KEY_BACKUP_LSN_STOP);
 | |
| #define MANIFEST_KEY_BACKUP_PRIOR                                   "backup-prior"
 | |
|     STRING_STATIC(MANIFEST_KEY_BACKUP_PRIOR_STR,                    MANIFEST_KEY_BACKUP_PRIOR);
 | |
| #define MANIFEST_KEY_BACKUP_TIMESTAMP_COPY_START                    "backup-timestamp-copy-start"
 | |
|     STRING_STATIC(MANIFEST_KEY_BACKUP_TIMESTAMP_COPY_START_STR,     MANIFEST_KEY_BACKUP_TIMESTAMP_COPY_START);
 | |
| #define MANIFEST_KEY_BACKUP_TIMESTAMP_START                         "backup-timestamp-start"
 | |
|     STRING_STATIC(MANIFEST_KEY_BACKUP_TIMESTAMP_START_STR,          MANIFEST_KEY_BACKUP_TIMESTAMP_START);
 | |
| #define MANIFEST_KEY_BACKUP_TIMESTAMP_STOP                          "backup-timestamp-stop"
 | |
|     STRING_STATIC(MANIFEST_KEY_BACKUP_TIMESTAMP_STOP_STR,           MANIFEST_KEY_BACKUP_TIMESTAMP_STOP);
 | |
| #define MANIFEST_KEY_BACKUP_TYPE                                    "backup-type"
 | |
|     STRING_STATIC(MANIFEST_KEY_BACKUP_TYPE_STR,                     MANIFEST_KEY_BACKUP_TYPE);
 | |
| #define MANIFEST_KEY_CHECKSUM                                       "checksum"
 | |
|     VARIANT_STRDEF_STATIC(MANIFEST_KEY_CHECKSUM_VAR,                MANIFEST_KEY_CHECKSUM);
 | |
| #define MANIFEST_KEY_CHECKSUM_PAGE                                  "checksum-page"
 | |
|     VARIANT_STRDEF_STATIC(MANIFEST_KEY_CHECKSUM_PAGE_VAR,           MANIFEST_KEY_CHECKSUM_PAGE);
 | |
| #define MANIFEST_KEY_CHECKSUM_PAGE_ERROR                            "checksum-page-error"
 | |
|     VARIANT_STRDEF_STATIC(MANIFEST_KEY_CHECKSUM_PAGE_ERROR_VAR,     MANIFEST_KEY_CHECKSUM_PAGE_ERROR);
 | |
| #define MANIFEST_KEY_DB_ID                                          "db-id"
 | |
|     STRING_STATIC(MANIFEST_KEY_DB_ID_STR,                           MANIFEST_KEY_DB_ID);
 | |
|     VARIANT_STRDEF_STATIC(MANIFEST_KEY_DB_ID_VAR,                   MANIFEST_KEY_DB_ID);
 | |
| #define MANIFEST_KEY_DB_LAST_SYSTEM_ID                              "db-last-system-id"
 | |
|     VARIANT_STRDEF_STATIC(MANIFEST_KEY_DB_LAST_SYSTEM_ID_VAR,       MANIFEST_KEY_DB_LAST_SYSTEM_ID);
 | |
| #define MANIFEST_KEY_DB_SYSTEM_ID                                   "db-system-id"
 | |
|     STRING_STATIC(MANIFEST_KEY_DB_SYSTEM_ID_STR,                    MANIFEST_KEY_DB_SYSTEM_ID);
 | |
| #define MANIFEST_KEY_DB_VERSION                                     "db-version"
 | |
|     STRING_STATIC(MANIFEST_KEY_DB_VERSION_STR,                      MANIFEST_KEY_DB_VERSION);
 | |
| #define MANIFEST_KEY_DESTINATION                                    "destination"
 | |
|     VARIANT_STRDEF_STATIC(MANIFEST_KEY_DESTINATION_VAR,             MANIFEST_KEY_DESTINATION);
 | |
| #define MANIFEST_KEY_FILE                                           "file"
 | |
|     VARIANT_STRDEF_STATIC(MANIFEST_KEY_FILE_VAR,                    MANIFEST_KEY_FILE);
 | |
| #define MANIFEST_KEY_GROUP                                          "group"
 | |
|     STRING_STATIC(MANIFEST_KEY_GROUP_STR,                           MANIFEST_KEY_GROUP);
 | |
|     VARIANT_STRDEF_STATIC(MANIFEST_KEY_GROUP_VAR,                   MANIFEST_KEY_GROUP);
 | |
| #define MANIFEST_KEY_PRIMARY                                        "ma" "st" "er"
 | |
|     STRING_STATIC(MANIFEST_KEY_PRIMARY_STR,                         MANIFEST_KEY_PRIMARY);
 | |
|     VARIANT_STRDEF_STATIC(MANIFEST_KEY_PRIMARY_VAR,                 MANIFEST_KEY_PRIMARY);
 | |
| #define MANIFEST_KEY_MODE                                           "mode"
 | |
|     STRING_STATIC(MANIFEST_KEY_MODE_STR,                            MANIFEST_KEY_MODE);
 | |
|     VARIANT_STRDEF_STATIC(MANIFEST_KEY_MODE_VAR,                    MANIFEST_KEY_MODE);
 | |
| #define MANIFEST_KEY_PATH                                           "path"
 | |
|     VARIANT_STRDEF_STATIC(MANIFEST_KEY_PATH_VAR,                    MANIFEST_KEY_PATH);
 | |
| #define MANIFEST_KEY_REFERENCE                                      "reference"
 | |
|     VARIANT_STRDEF_STATIC(MANIFEST_KEY_REFERENCE_VAR,               MANIFEST_KEY_REFERENCE);
 | |
| #define MANIFEST_KEY_SIZE                                           "size"
 | |
|     VARIANT_STRDEF_STATIC(MANIFEST_KEY_SIZE_VAR,                    MANIFEST_KEY_SIZE);
 | |
| #define MANIFEST_KEY_SIZE_REPO                                      "repo-size"
 | |
|     VARIANT_STRDEF_STATIC(MANIFEST_KEY_SIZE_REPO_VAR,               MANIFEST_KEY_SIZE_REPO);
 | |
| #define MANIFEST_KEY_TABLESPACE_ID                                  "tablespace-id"
 | |
|     VARIANT_STRDEF_STATIC(MANIFEST_KEY_TABLESPACE_ID_VAR,           MANIFEST_KEY_TABLESPACE_ID);
 | |
| #define MANIFEST_KEY_TABLESPACE_NAME                                "tablespace-name"
 | |
|     VARIANT_STRDEF_STATIC(MANIFEST_KEY_TABLESPACE_NAME_VAR,         MANIFEST_KEY_TABLESPACE_NAME);
 | |
| #define MANIFEST_KEY_TIMESTAMP                                      "timestamp"
 | |
|     VARIANT_STRDEF_STATIC(MANIFEST_KEY_TIMESTAMP_VAR,               MANIFEST_KEY_TIMESTAMP);
 | |
| #define MANIFEST_KEY_TYPE                                           "type"
 | |
|     VARIANT_STRDEF_STATIC(MANIFEST_KEY_TYPE_VAR,                    MANIFEST_KEY_TYPE);
 | |
| #define MANIFEST_KEY_USER                                           "user"
 | |
|     STRING_STATIC(MANIFEST_KEY_USER_STR,                            MANIFEST_KEY_USER);
 | |
|     VARIANT_STRDEF_STATIC(MANIFEST_KEY_USER_VAR,                    MANIFEST_KEY_USER);
 | |
| 
 | |
| #define MANIFEST_KEY_OPTION_ARCHIVE_CHECK                           "option-archive-check"
 | |
|     STRING_STATIC(MANIFEST_KEY_OPTION_ARCHIVE_CHECK_STR,            MANIFEST_KEY_OPTION_ARCHIVE_CHECK);
 | |
| #define MANIFEST_KEY_OPTION_ARCHIVE_COPY                            "option-archive-copy"
 | |
|     STRING_STATIC(MANIFEST_KEY_OPTION_ARCHIVE_COPY_STR,             MANIFEST_KEY_OPTION_ARCHIVE_COPY);
 | |
| #define MANIFEST_KEY_OPTION_BACKUP_STANDBY                          "option-backup-standby"
 | |
|     STRING_STATIC(MANIFEST_KEY_OPTION_BACKUP_STANDBY_STR,           MANIFEST_KEY_OPTION_BACKUP_STANDBY);
 | |
| #define MANIFEST_KEY_OPTION_BUFFER_SIZE                             "option-buffer-size"
 | |
|     STRING_STATIC(MANIFEST_KEY_OPTION_BUFFER_SIZE_STR,              MANIFEST_KEY_OPTION_BUFFER_SIZE);
 | |
| #define MANIFEST_KEY_OPTION_CHECKSUM_PAGE                           "option-checksum-page"
 | |
|     STRING_STATIC(MANIFEST_KEY_OPTION_CHECKSUM_PAGE_STR,            MANIFEST_KEY_OPTION_CHECKSUM_PAGE);
 | |
| #define MANIFEST_KEY_OPTION_COMPRESS                                "option-compress"
 | |
|     STRING_STATIC(MANIFEST_KEY_OPTION_COMPRESS_STR,                 MANIFEST_KEY_OPTION_COMPRESS);
 | |
| #define MANIFEST_KEY_OPTION_COMPRESS_TYPE                           "option-compress-type"
 | |
|     STRING_STATIC(MANIFEST_KEY_OPTION_COMPRESS_TYPE_STR,            MANIFEST_KEY_OPTION_COMPRESS_TYPE);
 | |
| #define MANIFEST_KEY_OPTION_COMPRESS_LEVEL                          "option-compress-level"
 | |
|     STRING_STATIC(MANIFEST_KEY_OPTION_COMPRESS_LEVEL_STR,           MANIFEST_KEY_OPTION_COMPRESS_LEVEL);
 | |
| #define MANIFEST_KEY_OPTION_COMPRESS_LEVEL_NETWORK                  "option-compress-level-network"
 | |
|     STRING_STATIC(MANIFEST_KEY_OPTION_COMPRESS_LEVEL_NETWORK_STR,   MANIFEST_KEY_OPTION_COMPRESS_LEVEL_NETWORK);
 | |
| #define MANIFEST_KEY_OPTION_DELTA                                   "option-delta"
 | |
|     STRING_STATIC(MANIFEST_KEY_OPTION_DELTA_STR,                    MANIFEST_KEY_OPTION_DELTA);
 | |
| #define MANIFEST_KEY_OPTION_HARDLINK                                "option-hardlink"
 | |
|     STRING_STATIC(MANIFEST_KEY_OPTION_HARDLINK_STR,                 MANIFEST_KEY_OPTION_HARDLINK);
 | |
| #define MANIFEST_KEY_OPTION_ONLINE                                  "option-online"
 | |
|     STRING_STATIC(MANIFEST_KEY_OPTION_ONLINE_STR,                   MANIFEST_KEY_OPTION_ONLINE);
 | |
| #define MANIFEST_KEY_OPTION_PROCESS_MAX                             "option-process-max"
 | |
|     STRING_STATIC(MANIFEST_KEY_OPTION_PROCESS_MAX_STR,              MANIFEST_KEY_OPTION_PROCESS_MAX);
 | |
| 
 | |
| /***********************************************************************************************************************************
 | |
| Object type
 | |
| ***********************************************************************************************************************************/
 | |
| struct Manifest
 | |
| {
 | |
|     MemContext *memContext;                                         // Context that contains the Manifest
 | |
| 
 | |
|     Info *info;                                                     // Base info object
 | |
|     StringList *ownerList;                                          // List of users/groups
 | |
|     StringList *referenceList;                                      // List of file references
 | |
| 
 | |
|     ManifestData data;                                              // Manifest data and options
 | |
|     List *targetList;                                               // List of targets
 | |
|     List *pathList;                                                 // List of paths
 | |
|     List *fileList;                                                 // List of files
 | |
|     List *linkList;                                                 // List of links
 | |
|     List *dbList;                                                   // List of databases
 | |
| };
 | |
| 
 | |
| OBJECT_DEFINE_MOVE(MANIFEST);
 | |
| 
 | |
| /***********************************************************************************************************************************
 | |
| Internal functions to add types to their lists
 | |
| ***********************************************************************************************************************************/
 | |
| // Helper to add owner to the owner list if it is not there already and return the pointer.  This saves a lot of space.
 | |
| static const String *
 | |
| manifestOwnerCache(Manifest *this, const String *owner)
 | |
| {
 | |
|     FUNCTION_TEST_BEGIN();
 | |
|         FUNCTION_TEST_PARAM(MANIFEST, this);
 | |
|         FUNCTION_TEST_PARAM(STRING, owner);
 | |
|     FUNCTION_TEST_END();
 | |
| 
 | |
|     ASSERT(this != NULL);
 | |
| 
 | |
|     if (owner != NULL)
 | |
|         FUNCTION_TEST_RETURN(strLstAddIfMissing(this->ownerList, owner));
 | |
| 
 | |
|     FUNCTION_TEST_RETURN(NULL);
 | |
| }
 | |
| 
 | |
| static void
 | |
| manifestDbAdd(Manifest *this, const ManifestDb *db)
 | |
| {
 | |
|     FUNCTION_TEST_BEGIN();
 | |
|         FUNCTION_TEST_PARAM(MANIFEST, this);
 | |
|         FUNCTION_TEST_PARAM(MANIFEST_DB, db);
 | |
|     FUNCTION_TEST_END();
 | |
| 
 | |
|     ASSERT(this != NULL);
 | |
|     ASSERT(db != NULL);
 | |
|     ASSERT(db->name != NULL);
 | |
| 
 | |
|     MEM_CONTEXT_BEGIN(lstMemContext(this->dbList))
 | |
|     {
 | |
|         ManifestDb dbAdd =
 | |
|         {
 | |
|             .id = db->id,
 | |
|             .lastSystemId = db->lastSystemId,
 | |
|             .name = strDup(db->name),
 | |
|         };
 | |
| 
 | |
|         lstAdd(this->dbList, &dbAdd);
 | |
|     }
 | |
|     MEM_CONTEXT_END();
 | |
| 
 | |
|     FUNCTION_TEST_RETURN_VOID();
 | |
| }
 | |
| 
 | |
| void
 | |
| manifestFileAdd(Manifest *this, const ManifestFile *file)
 | |
| {
 | |
|     FUNCTION_TEST_BEGIN();
 | |
|         FUNCTION_TEST_PARAM(MANIFEST, this);
 | |
|         FUNCTION_TEST_PARAM(MANIFEST_FILE, file);
 | |
|     FUNCTION_TEST_END();
 | |
| 
 | |
|     ASSERT(this != NULL);
 | |
|     ASSERT(file != NULL);
 | |
|     ASSERT(file->name != NULL);
 | |
| 
 | |
|     MEM_CONTEXT_BEGIN(lstMemContext(this->fileList))
 | |
|     {
 | |
|         ManifestFile fileAdd =
 | |
|         {
 | |
|             .checksumPage = file->checksumPage,
 | |
|             .checksumPageError = file->checksumPageError,
 | |
|             .checksumPageErrorList = varLstDup(file->checksumPageErrorList),
 | |
|             .group = manifestOwnerCache(this, file->group),
 | |
|             .mode = file->mode,
 | |
|             .name = strDup(file->name),
 | |
|             .primary = file->primary,
 | |
|             .size = file->size,
 | |
|             .sizeRepo = file->sizeRepo,
 | |
|             .timestamp = file->timestamp,
 | |
|             .user = manifestOwnerCache(this, file->user),
 | |
|         };
 | |
| 
 | |
|         memcpy(fileAdd.checksumSha1, file->checksumSha1, HASH_TYPE_SHA1_SIZE_HEX + 1);
 | |
| 
 | |
|         if (file->reference != NULL)
 | |
|         {
 | |
|             // Search for the reference in the list
 | |
|             for (unsigned int referenceIdx = 0; referenceIdx < strLstSize(this->referenceList); referenceIdx++)
 | |
|             {
 | |
|                 const String *found = strLstGet(this->referenceList, referenceIdx);
 | |
| 
 | |
|                 if (strEq(file->reference, found))
 | |
|                 {
 | |
|                     fileAdd.reference = found;
 | |
|                     break;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             // If not found then add it
 | |
|             if (fileAdd.reference == NULL)
 | |
|             {
 | |
|                 strLstAdd(this->referenceList, file->reference);
 | |
|                 fileAdd.reference = strLstGet(this->referenceList, strLstSize(this->referenceList) - 1);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         lstAdd(this->fileList, &fileAdd);
 | |
|     }
 | |
|     MEM_CONTEXT_END();
 | |
| 
 | |
|     FUNCTION_TEST_RETURN_VOID();
 | |
| }
 | |
| 
 | |
| static void
 | |
| manifestLinkAdd(Manifest *this, const ManifestLink *link)
 | |
| {
 | |
|     FUNCTION_TEST_BEGIN();
 | |
|         FUNCTION_TEST_PARAM(MANIFEST, this);
 | |
|         FUNCTION_TEST_PARAM(MANIFEST_LINK, link);
 | |
|     FUNCTION_TEST_END();
 | |
| 
 | |
|     ASSERT(this != NULL);
 | |
|     ASSERT(link != NULL);
 | |
|     ASSERT(link->name != NULL);
 | |
|     ASSERT(link->destination != NULL);
 | |
| 
 | |
|     MEM_CONTEXT_BEGIN(lstMemContext(this->linkList))
 | |
|     {
 | |
|         ManifestLink linkAdd =
 | |
|         {
 | |
|             .destination = strDup(link->destination),
 | |
|             .name = strDup(link->name),
 | |
|             .group = manifestOwnerCache(this, link->group),
 | |
|             .user = manifestOwnerCache(this, link->user),
 | |
|         };
 | |
| 
 | |
|         lstAdd(this->linkList, &linkAdd);
 | |
|     }
 | |
|     MEM_CONTEXT_END();
 | |
| 
 | |
|     FUNCTION_TEST_RETURN_VOID();
 | |
| }
 | |
| 
 | |
| static void
 | |
| manifestPathAdd(Manifest *this, const ManifestPath *path)
 | |
| {
 | |
|     FUNCTION_TEST_BEGIN();
 | |
|         FUNCTION_TEST_PARAM(MANIFEST, this);
 | |
|         FUNCTION_TEST_PARAM(MANIFEST_PATH, path);
 | |
|     FUNCTION_TEST_END();
 | |
| 
 | |
|     ASSERT(this != NULL);
 | |
|     ASSERT(path != NULL);
 | |
|     ASSERT(path->name != NULL);
 | |
| 
 | |
|     MEM_CONTEXT_BEGIN(lstMemContext(this->pathList))
 | |
|     {
 | |
|         ManifestPath pathAdd =
 | |
|         {
 | |
|             .mode = path->mode,
 | |
|             .name = strDup(path->name),
 | |
|             .group = manifestOwnerCache(this, path->group),
 | |
|             .user = manifestOwnerCache(this, path->user),
 | |
|         };
 | |
| 
 | |
|         lstAdd(this->pathList, &pathAdd);
 | |
|     }
 | |
|     MEM_CONTEXT_END();
 | |
| 
 | |
|     FUNCTION_TEST_RETURN_VOID();
 | |
| }
 | |
| 
 | |
| static void
 | |
| manifestTargetAdd(Manifest *this, const ManifestTarget *target)
 | |
| {
 | |
|     FUNCTION_TEST_BEGIN();
 | |
|         FUNCTION_TEST_PARAM(MANIFEST, this);
 | |
|         FUNCTION_TEST_PARAM(MANIFEST_TARGET, target);
 | |
|     FUNCTION_TEST_END();
 | |
| 
 | |
|     ASSERT(this != NULL);
 | |
|     ASSERT(target != NULL);
 | |
|     ASSERT(target->path != NULL);
 | |
|     ASSERT(target->name != NULL);
 | |
| 
 | |
|     MEM_CONTEXT_BEGIN(lstMemContext(this->targetList))
 | |
|     {
 | |
|         ManifestTarget targetAdd =
 | |
|         {
 | |
|             .file = strDup(target->file),
 | |
|             .name = strDup(target->name),
 | |
|             .path = strDup(target->path),
 | |
|             .tablespaceId = target->tablespaceId,
 | |
|             .tablespaceName = strDup(target->tablespaceName),
 | |
|             .type = target->type,
 | |
|         };
 | |
| 
 | |
|         lstAdd(this->targetList, &targetAdd);
 | |
|     }
 | |
|     MEM_CONTEXT_END();
 | |
| 
 | |
|     FUNCTION_TEST_RETURN_VOID();
 | |
| }
 | |
| 
 | |
| /***********************************************************************************************************************************
 | |
| Create new object
 | |
| ***********************************************************************************************************************************/
 | |
| static Manifest *
 | |
| manifestNewInternal(void)
 | |
| {
 | |
|     FUNCTION_TEST_VOID();
 | |
| 
 | |
|     Manifest *this = memNew(sizeof(Manifest));
 | |
| 
 | |
|     *this = (Manifest)
 | |
|     {
 | |
|         .memContext = memContextCurrent(),
 | |
|         .dbList = lstNewP(sizeof(ManifestDb), .comparator = lstComparatorStr),
 | |
|         .fileList = lstNewP(sizeof(ManifestFile), .comparator =  lstComparatorStr),
 | |
|         .linkList = lstNewP(sizeof(ManifestLink), .comparator =  lstComparatorStr),
 | |
|         .pathList = lstNewP(sizeof(ManifestPath), .comparator =  lstComparatorStr),
 | |
|         .ownerList = strLstNew(),
 | |
|         .referenceList = strLstNew(),
 | |
|         .targetList = lstNewP(sizeof(ManifestTarget), .comparator =  lstComparatorStr),
 | |
|     };
 | |
| 
 | |
|     FUNCTION_TEST_RETURN(this);
 | |
| }
 | |
| 
 | |
| /**********************************************************************************************************************************/
 | |
| typedef struct ManifestBuildData
 | |
| {
 | |
|     Manifest *manifest;
 | |
|     const Storage *storagePg;
 | |
|     const String *tablespaceId;                                     // Tablespace id if PostgreSQL version has one
 | |
|     bool online;                                                    // Is this an online backup?
 | |
|     bool checksumPage;                                              // Are page checksums being checked?
 | |
|     const String *manifestWalName;                                  // Wal manifest name for this version of PostgreSQL
 | |
|     RegExp *dbPathExp;                                              // Identify paths containing relations
 | |
|     RegExp *tempRelationExp;                                        // Identify temp relations
 | |
|     RegExp *standbyExp;                                             // Identify files that must be copied from the primary
 | |
|     const VariantList *tablespaceList;                              // List of tablespaces in the database
 | |
|     StringList *excludeContent;                                     // Exclude contents of directories
 | |
|     StringList *excludeSingle;                                      // Exclude a single file/link/path
 | |
| 
 | |
|     // These change with each level of recursion
 | |
|     const String *manifestParentName;                               // Manifest name of this file/link/path's parent
 | |
|     const String *pgPath;                                           // Current path in the PostgreSQL data directory
 | |
|     bool dbPath;                                                    // Does this path contain relations?
 | |
| } ManifestBuildData;
 | |
| 
 | |
| // Callback to process files/links/paths and add them to the manifest
 | |
| static void
 | |
| manifestBuildCallback(void *data, const StorageInfo *info)
 | |
| {
 | |
|     FUNCTION_TEST_BEGIN();
 | |
|         FUNCTION_TEST_PARAM_P(VOID, data);
 | |
|         FUNCTION_TEST_PARAM(STORAGE_INFO, *storageInfo);
 | |
|     FUNCTION_TEST_END();
 | |
| 
 | |
|     ASSERT(data != NULL);
 | |
|     ASSERT(info != NULL);
 | |
| 
 | |
|     // Skip all . paths because they have already been recorded on the previous level of recursion
 | |
|     if (strEq(info->name, DOT_STR))
 | |
|     {
 | |
|         FUNCTION_TEST_RETURN_VOID();
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     // Skip any path/file/link that begins with pgsql_tmp.  The files are removed when the server is restarted and the directories
 | |
|     // are recreated.
 | |
|     if (strBeginsWithZ(info->name, PG_PREFIX_PGSQLTMP))
 | |
|     {
 | |
|         FUNCTION_TEST_RETURN_VOID();
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     // Get build data
 | |
|     ManifestBuildData buildData = *(ManifestBuildData *)data;
 | |
|     unsigned int pgVersion = buildData.manifest->data.pgVersion;
 | |
| 
 | |
|     // Contruct the name used to identify this file/link/path in the manifest
 | |
|     const String *manifestName = strNewFmt("%s/%s", strPtr(buildData.manifestParentName), strPtr(info->name));
 | |
| 
 | |
|     // Skip excluded files/links/paths
 | |
|     if (buildData.excludeSingle != NULL && strLstExists(buildData.excludeSingle, manifestName))
 | |
|     {
 | |
|         LOG_INFO_FMT(
 | |
|             "exclude '%s/%s' from backup using '%s' exclusion", strPtr(buildData.pgPath), strPtr(info->name),
 | |
|             strPtr(strSub(manifestName, sizeof(MANIFEST_TARGET_PGDATA))));
 | |
| 
 | |
|         FUNCTION_TEST_RETURN_VOID();
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     // Process storage types
 | |
|     switch (info->type)
 | |
|     {
 | |
|         // Add paths
 | |
|         // -------------------------------------------------------------------------------------------------------------------------
 | |
|         case storageTypePath:
 | |
|         {
 | |
|             // There should not be any paths in pg_tblspc
 | |
|             if (strEqZ(buildData.manifestParentName, MANIFEST_TARGET_PGDATA "/" MANIFEST_TARGET_PGTBLSPC))
 | |
|             {
 | |
|                 THROW_FMT(
 | |
|                     LinkExpectedError, "'%s' is not a symlink - " MANIFEST_TARGET_PGTBLSPC " should contain only symlinks",
 | |
|                     strPtr(manifestName));
 | |
|             }
 | |
| 
 | |
|             // Add path to manifest
 | |
|             ManifestPath path =
 | |
|             {
 | |
|                 .name = manifestName,
 | |
|                 .mode = info->mode,
 | |
|                 .user = info->user,
 | |
|                 .group = info->group,
 | |
|             };
 | |
| 
 | |
|             manifestPathAdd(buildData.manifest, &path);
 | |
| 
 | |
|             // Skip excluded path content
 | |
|             if (buildData.excludeContent != NULL && strLstExists(buildData.excludeContent, manifestName))
 | |
|             {
 | |
|                 LOG_INFO_FMT(
 | |
|                     "exclude contents of '%s/%s' from backup using '%s/' exclusion", strPtr(buildData.pgPath), strPtr(info->name),
 | |
|                     strPtr(strSub(manifestName, sizeof(MANIFEST_TARGET_PGDATA))));
 | |
| 
 | |
|                 FUNCTION_TEST_RETURN_VOID();
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             // Skip the contents of these paths if they exist in the base path since they won't be reused after recovery
 | |
|             if (strEq(buildData.manifestParentName, MANIFEST_TARGET_PGDATA_STR))
 | |
|             {
 | |
|                 // Skip pg_dynshmem/* since these files cannot be reused on recovery
 | |
|                 if (strEqZ(info->name, PG_PATH_PGDYNSHMEM) && pgVersion >= PG_VERSION_94)
 | |
|                 {
 | |
|                     FUNCTION_TEST_RETURN_VOID();
 | |
|                     return;
 | |
|                 }
 | |
| 
 | |
|                 // Skip pg_notify/* since these files cannot be reused on recovery
 | |
|                 if (strEqZ(info->name, PG_PATH_PGNOTIFY) && pgVersion >= PG_VERSION_90)
 | |
|                 {
 | |
|                     FUNCTION_TEST_RETURN_VOID();
 | |
|                     return;
 | |
|                 }
 | |
| 
 | |
|                 // Skip pg_replslot/* since these files are generally not useful after a restore
 | |
|                 if (strEqZ(info->name, PG_PATH_PGREPLSLOT) && pgVersion >= PG_VERSION_94)
 | |
|                 {
 | |
|                     FUNCTION_TEST_RETURN_VOID();
 | |
|                     return;
 | |
|                 }
 | |
| 
 | |
|                 // Skip pg_serial/* since these files are reset
 | |
|                 if (strEqZ(info->name, PG_PATH_PGSERIAL) && pgVersion >= PG_VERSION_91)
 | |
|                 {
 | |
|                     FUNCTION_TEST_RETURN_VOID();
 | |
|                     return;
 | |
|                 }
 | |
| 
 | |
|                 // Skip pg_snapshots/* since these files cannot be reused on recovery
 | |
|                 if (strEqZ(info->name, PG_PATH_PGSNAPSHOTS) && pgVersion >= PG_VERSION_92)
 | |
|                 {
 | |
|                     FUNCTION_TEST_RETURN_VOID();
 | |
|                     return;
 | |
|                 }
 | |
| 
 | |
|                 // Skip temporary statistics in pg_stat_tmp even when stats_temp_directory is set because PGSS_TEXT_FILE is always
 | |
|                 // created there
 | |
|                 if (strEqZ(info->name, PG_PATH_PGSTATTMP) && pgVersion >= PG_VERSION_84)
 | |
|                 {
 | |
|                     FUNCTION_TEST_RETURN_VOID();
 | |
|                     return;
 | |
|                 }
 | |
| 
 | |
|                 // Skip pg_subtrans/* since these files are reset
 | |
|                 if (strEqZ(info->name, PG_PATH_PGSUBTRANS))
 | |
|                 {
 | |
|                     FUNCTION_TEST_RETURN_VOID();
 | |
|                     return;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             // Skip the contents of archive_status when online
 | |
|             if (buildData.online && strEq(buildData.manifestParentName, buildData.manifestWalName) &&
 | |
|                 strEqZ(info->name, PG_PATH_ARCHIVE_STATUS))
 | |
|             {
 | |
|                 FUNCTION_TEST_RETURN_VOID();
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             // Recurse into the path
 | |
|             ManifestBuildData buildDataSub = buildData;
 | |
|             buildDataSub.manifestParentName = manifestName;
 | |
|             buildDataSub.pgPath = strNewFmt("%s/%s", strPtr(buildData.pgPath), strPtr(info->name));
 | |
| 
 | |
|             if (buildData.dbPathExp != NULL)
 | |
|                 buildDataSub.dbPath = regExpMatch(buildData.dbPathExp, manifestName);
 | |
| 
 | |
|             storageInfoListP(
 | |
|                 buildDataSub.storagePg, buildDataSub.pgPath, manifestBuildCallback, &buildDataSub, .sortOrder = sortOrderAsc);
 | |
| 
 | |
|             break;
 | |
|         }
 | |
| 
 | |
|         // Add files
 | |
|         // -------------------------------------------------------------------------------------------------------------------------
 | |
|         case storageTypeFile:
 | |
|         {
 | |
|             // There should not be any files in pg_tblspc
 | |
|             if (strEqZ(buildData.manifestParentName, MANIFEST_TARGET_PGDATA "/" MANIFEST_TARGET_PGTBLSPC))
 | |
|             {
 | |
|                 THROW_FMT(
 | |
|                     LinkExpectedError, "'%s' is not a symlink - " MANIFEST_TARGET_PGTBLSPC " should contain only symlinks",
 | |
|                     strPtr(manifestName));
 | |
|             }
 | |
| 
 | |
|             // Skip pg_internal.init since it is recreated on startup.  It's also possible, (though unlikely) that a temp file with
 | |
|             // the creating process id as the extension can exist so skip that as well.  This seems to be a bug in PostgreSQL since
 | |
|             // the temp file should be removed on startup.  Use regExpMatchOne() here instead of preparing a regexp in advance since
 | |
|             // the likelihood of needing the regexp should be very small.
 | |
|             if ((pgVersion <= PG_VERSION_84 || buildData.dbPath) && strBeginsWithZ(info->name, PG_FILE_PGINTERNALINIT) &&
 | |
|                 (strSize(info->name) == sizeof(PG_FILE_PGINTERNALINIT) - 1 ||
 | |
|                     regExpMatchOne(STRDEF("\\.[0-9]+"), strSub(info->name, sizeof(PG_FILE_PGINTERNALINIT) - 1))))
 | |
|             {
 | |
|                 FUNCTION_TEST_RETURN_VOID();
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             // Skip files in the root data path
 | |
|             if (strEq(buildData.manifestParentName, MANIFEST_TARGET_PGDATA_STR))
 | |
|             {
 | |
|                 // Skip recovery files
 | |
|                 if (((strEqZ(info->name, PG_FILE_RECOVERYSIGNAL) || strEqZ(info->name, PG_FILE_STANDBYSIGNAL)) &&
 | |
|                         pgVersion >= PG_VERSION_12) ||
 | |
|                     ((strEqZ(info->name, PG_FILE_RECOVERYCONF) || strEqZ(info->name, PG_FILE_RECOVERYDONE)) &&
 | |
|                         pgVersion < PG_VERSION_12) ||
 | |
|                     // Skip temp file for safely writing postgresql.auto.conf
 | |
|                     (strEqZ(info->name, PG_FILE_POSTGRESQLAUTOCONFTMP) && pgVersion >= PG_VERSION_94) ||
 | |
|                     // Skip backup_label in versions where non-exclusive backup is supported
 | |
|                     (strEqZ(info->name, PG_FILE_BACKUPLABEL) && pgVersion >= PG_VERSION_96) ||
 | |
|                     // Skip old backup labels
 | |
|                     strEqZ(info->name, PG_FILE_BACKUPLABELOLD) ||
 | |
|                     // Skip running postmaster options
 | |
|                     strEqZ(info->name, PG_FILE_POSTMASTEROPTS) ||
 | |
|                     // Skip process id file to avoid confusing postgres after restore
 | |
|                     strEqZ(info->name, PG_FILE_POSTMASTERPID))
 | |
|                 {
 | |
|                     FUNCTION_TEST_RETURN_VOID();
 | |
|                     return;
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             // Skip the contents of the wal path when online. WAL will be restored from the archive or stored in the wal directory
 | |
|             // at the end of the backup if the archive-copy option is set.
 | |
|             if (buildData.online && strEq(buildData.manifestParentName, buildData.manifestWalName))
 | |
|             {
 | |
|                 FUNCTION_TEST_RETURN_VOID();
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             // Skip temp relations in db paths
 | |
|             if (buildData.dbPath && regExpMatch(buildData.tempRelationExp, info->name))
 | |
|             {
 | |
|                 FUNCTION_TEST_RETURN_VOID();
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             // Add file to manifest
 | |
|             ManifestFile file =
 | |
|             {
 | |
|                 .name = manifestName,
 | |
|                 .mode = info->mode,
 | |
|                 .user = info->user,
 | |
|                 .group = info->group,
 | |
|                 .size = info->size,
 | |
|                 .sizeRepo = info->size,
 | |
|                 .timestamp = info->timeModified,
 | |
|             };
 | |
| 
 | |
|             // Set a flag to indicate if this file must be copied from the primary
 | |
|             file.primary =
 | |
|                 strEqZ(manifestName, MANIFEST_TARGET_PGDATA "/" PG_PATH_GLOBAL "/" PG_FILE_PGCONTROL) ||
 | |
|                 !regExpMatch(buildData.standbyExp, manifestName);
 | |
| 
 | |
|             // Determine if this file should be page checksummed
 | |
|             if (buildData.dbPath && buildData.checksumPage)
 | |
|             {
 | |
|                 file.checksumPage =
 | |
|                     !strEndsWithZ(manifestName, "/" PG_FILE_PGFILENODEMAP) && !strEndsWithZ(manifestName, "/" PG_FILE_PGVERSION) &&
 | |
|                     !strEqZ(manifestName, MANIFEST_TARGET_PGDATA "/" PG_PATH_GLOBAL "/" PG_FILE_PGCONTROL);
 | |
|             }
 | |
| 
 | |
|             manifestFileAdd(buildData.manifest, &file);
 | |
|             break;
 | |
|         }
 | |
| 
 | |
|         // Add links
 | |
|         // -------------------------------------------------------------------------------------------------------------------------
 | |
|         case storageTypeLink:
 | |
|         {
 | |
|             // If the destination is another link then error.  In the future we'll allow this by following the link chain to the
 | |
|             // eventual destination but for now we are trying to maintain compatibility during the migration.  To do this check we
 | |
|             // need to read outside of the data directory but it is a read-only operation so is considered safe.
 | |
|             const String *linkDestinationAbsolute = strPathAbsolute(info->linkDestination, buildData.pgPath);
 | |
| 
 | |
|             StorageInfo linkedCheck = storageInfoP(
 | |
|                 buildData.storagePg, linkDestinationAbsolute, .ignoreMissing = true, .noPathEnforce = true);
 | |
| 
 | |
|             if (linkedCheck.exists && linkedCheck.type == storageTypeLink)
 | |
|             {
 | |
|                 THROW_FMT(
 | |
|                     LinkDestinationError, "link '%s/%s' cannot reference another link '%s'", strPtr(buildData.pgPath),
 | |
|                     strPtr(info->name), strPtr(linkDestinationAbsolute));
 | |
|             }
 | |
| 
 | |
|             // Initialize link and target
 | |
|             ManifestLink link =
 | |
|             {
 | |
|                 .name = manifestName,
 | |
|                 .user = info->user,
 | |
|                 .group = info->group,
 | |
|                 .destination = info->linkDestination,
 | |
|             };
 | |
| 
 | |
|             ManifestTarget target =
 | |
|             {
 | |
|                 .name = manifestName,
 | |
|                 .type = manifestTargetTypeLink,
 | |
|             };
 | |
| 
 | |
|             // Make a copy of the link name because it will need to be modified when there are tablespace ids
 | |
|             const String *linkName = info->name;
 | |
| 
 | |
|             // Is this a tablespace?
 | |
|             if (strEq(buildData.manifestParentName, STRDEF(MANIFEST_TARGET_PGDATA "/" MANIFEST_TARGET_PGTBLSPC)))
 | |
|             {
 | |
|                 // Strip pg_data off the manifest name so it begins with pg_tblspc instead.  This reflects how the files are stored
 | |
|                 // in the backup directory.
 | |
|                 manifestName = strSub(manifestName, sizeof(MANIFEST_TARGET_PGDATA));
 | |
| 
 | |
|                 // Identify this target as a tablespace
 | |
|                 target.name = manifestName;
 | |
|                 target.tablespaceId = cvtZToUInt(strPtr(info->name));
 | |
| 
 | |
|                 // Look for this tablespace in the provided list (list may be null for off-line backup)
 | |
|                 if (buildData.tablespaceList != NULL)
 | |
|                 {
 | |
|                     // Search list
 | |
|                     for (unsigned int tablespaceIdx = 0; tablespaceIdx < varLstSize(buildData.tablespaceList); tablespaceIdx++)
 | |
|                     {
 | |
|                         const VariantList *tablespace = varVarLst(varLstGet(buildData.tablespaceList, tablespaceIdx));
 | |
| 
 | |
|                         if (target.tablespaceId == varUIntForce(varLstGet(tablespace, 0)))
 | |
|                             target.tablespaceName = varStr(varLstGet(tablespace, 1));
 | |
|                     }
 | |
| 
 | |
|                     // Error if the tablespace could not be found.  ??? This seems excessive, perhaps just warn here?
 | |
|                     if (target.tablespaceName == NULL)
 | |
|                     {
 | |
|                         THROW_FMT(
 | |
|                             AssertError,
 | |
|                             "tablespace with oid %u not found in tablespace map\n"
 | |
|                             "HINT: was a tablespace created or dropped during the backup?",
 | |
|                             target.tablespaceId);
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 // If no tablespace name was found then create one
 | |
|                 if (target.tablespaceName == NULL)
 | |
|                     target.tablespaceName = strNewFmt("ts%s", strPtr(info->name));
 | |
| 
 | |
|                 // Add a dummy pg_tblspc path entry if it does not already exist.  This entry will be ignored by restore but it is
 | |
|                 // part of the original manifest format so we need to have it.
 | |
|                 lstSort(buildData.manifest->pathList, sortOrderAsc);
 | |
|                 const ManifestPath *pathBase = manifestPathFind(buildData.manifest, MANIFEST_TARGET_PGDATA_STR);
 | |
| 
 | |
|                 if (manifestPathFindDefault(buildData.manifest, MANIFEST_TARGET_PGTBLSPC_STR, NULL) == NULL)
 | |
|                 {
 | |
|                     ManifestPath path =
 | |
|                     {
 | |
|                         .name = MANIFEST_TARGET_PGTBLSPC_STR,
 | |
|                         .mode = pathBase->mode,
 | |
|                         .user = pathBase->user,
 | |
|                         .group = pathBase->group,
 | |
|                     };
 | |
| 
 | |
|                     manifestPathAdd(buildData.manifest, &path);
 | |
|                 }
 | |
| 
 | |
|                 // If the tablespace id is present then the tablespace link destination path is not the path where data will be
 | |
|                 // stored so we can just store it as dummy path.
 | |
|                 if (buildData.tablespaceId != NULL)
 | |
|                 {
 | |
|                     const ManifestPath *pathTblSpc = manifestPathFind(
 | |
|                         buildData.manifest, STRDEF(MANIFEST_TARGET_PGDATA "/" MANIFEST_TARGET_PGTBLSPC));
 | |
| 
 | |
|                     ManifestPath path =
 | |
|                     {
 | |
|                         .name = manifestName,
 | |
|                         .mode = pathTblSpc->mode,
 | |
|                         .user = pathTblSpc->user,
 | |
|                         .group = pathTblSpc->group,
 | |
|                     };
 | |
| 
 | |
|                     manifestPathAdd(buildData.manifest, &path);
 | |
| 
 | |
|                     // Update build structure to reflect the path added above and the tablespace id
 | |
|                     buildData.manifestParentName = manifestName;
 | |
|                     manifestName = strNewFmt("%s/%s", strPtr(manifestName), strPtr(buildData.tablespaceId));
 | |
|                     buildData.pgPath = strNewFmt("%s/%s", strPtr(buildData.pgPath), strPtr(info->name));
 | |
|                     linkName = buildData.tablespaceId;
 | |
|                 }
 | |
|                 // If no tablespace id then parent manifest name is the tablespace directory
 | |
|                 else
 | |
|                     buildData.manifestParentName = MANIFEST_TARGET_PGTBLSPC_STR;
 | |
|             }
 | |
| 
 | |
|             // Add info about the linked file/path
 | |
|             const String *linkPgPath = strNewFmt("%s/%s", strPtr(buildData.pgPath), strPtr(linkName));
 | |
|             StorageInfo linkedInfo = storageInfoP(
 | |
|                 buildData.storagePg, linkPgPath, .followLink = true, .ignoreMissing = true);
 | |
|             linkedInfo.name = linkName;
 | |
| 
 | |
|             // If the link destination exists then proceed as usual
 | |
|             if (linkedInfo.exists)
 | |
|             {
 | |
|                 // If a path link then recurse
 | |
|                 if (linkedInfo.type == storageTypePath)
 | |
|                 {
 | |
|                     target.path = info->linkDestination;
 | |
|                 }
 | |
|                 // Else it must be a file or special (since we have already checked if it is a link)
 | |
|                 else
 | |
|                 {
 | |
|                     // Tablespace links should never be to a file
 | |
|                     CHECK(target.tablespaceId == 0);
 | |
| 
 | |
|                     // Identify target as a file
 | |
|                     target.path = strPath(info->linkDestination);
 | |
|                     target.file = strBase(info->linkDestination);
 | |
|                 }
 | |
| 
 | |
|                 // Use the callback to add and do all related checks
 | |
|                 manifestBuildCallback(&buildData, &linkedInfo);
 | |
|             }
 | |
|             // Else dummy up the target with a destination so manifestLinkCheck() can be run.  This is so errors about links with
 | |
|             // destinations in PGDATA will take precedence over missing a destination.  We will probably simplify this once the
 | |
|             // migration is done and it doesn't matter which error takes precedence.
 | |
|             else
 | |
|                 target.path = info->linkDestination;
 | |
| 
 | |
|             // Add target and link
 | |
|             manifestTargetAdd(buildData.manifest, &target);
 | |
|             manifestLinkAdd(buildData.manifest, &link);
 | |
| 
 | |
|             // Make sure the link is valid
 | |
|             manifestLinkCheck(buildData.manifest);
 | |
| 
 | |
|             // If the link check was successful but the destination does not exist then check it again to generate an error
 | |
|             if (!linkedInfo.exists)
 | |
|                 storageInfoP(buildData.storagePg, linkPgPath, .followLink = true);
 | |
| 
 | |
|             break;
 | |
|         }
 | |
| 
 | |
|         // Skip special files
 | |
|         // -------------------------------------------------------------------------------------------------------------------------
 | |
|         case storageTypeSpecial:
 | |
|         {
 | |
|             LOG_WARN_FMT("exclude special file '%s/%s' from backup", strPtr(buildData.pgPath), strPtr(info->name));
 | |
|             break;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     FUNCTION_TEST_RETURN_VOID();
 | |
| }
 | |
| 
 | |
| // Regular expression constants
 | |
| #define RELATION_EXP                                                "[0-9]+(|_(fsm|vm)){0,1}(\\.[0-9]+){0,1}"
 | |
| #define DB_PATH_EXP                                                                                                                \
 | |
|     "(" MANIFEST_TARGET_PGDATA "/(" PG_PATH_GLOBAL "|" PG_PATH_BASE "/[0-9]+)|" MANIFEST_TARGET_PGTBLSPC "/[0-9]+/%s/[0-9]+)"
 | |
| 
 | |
| Manifest *
 | |
| manifestNewBuild(
 | |
|     const Storage *storagePg, unsigned int pgVersion, bool online, bool checksumPage, const StringList *excludeList,
 | |
|     const VariantList *tablespaceList)
 | |
| {
 | |
|     FUNCTION_LOG_BEGIN(logLevelDebug);
 | |
|         FUNCTION_LOG_PARAM(STORAGE, storagePg);
 | |
|         FUNCTION_LOG_PARAM(UINT, pgVersion);
 | |
|         FUNCTION_LOG_PARAM(BOOL, online);
 | |
|         FUNCTION_LOG_PARAM(BOOL, checksumPage);
 | |
|         FUNCTION_LOG_PARAM(STRING_LIST, excludeList);
 | |
|         FUNCTION_LOG_PARAM(VARIANT_LIST, tablespaceList);
 | |
|     FUNCTION_LOG_END();
 | |
| 
 | |
|     ASSERT(storagePg != NULL);
 | |
|     ASSERT(pgVersion != 0);
 | |
|     ASSERT(!checksumPage || pgVersion >= PG_VERSION_93);
 | |
| 
 | |
|     Manifest *this = NULL;
 | |
| 
 | |
|     MEM_CONTEXT_NEW_BEGIN("Manifest")
 | |
|     {
 | |
|         this = manifestNewInternal();
 | |
|         this->info = infoNew(NULL);
 | |
|         this->data.backrestVersion = strNew(PROJECT_VERSION);
 | |
|         this->data.pgVersion = pgVersion;
 | |
|         this->data.backupOptionOnline = online;
 | |
|         this->data.backupOptionChecksumPage = varNewBool(checksumPage);
 | |
| 
 | |
|         // Data needed to build the manifest
 | |
|         ManifestBuildData buildData =
 | |
|         {
 | |
|             .manifest = this,
 | |
|             .storagePg = storagePg,
 | |
|             .tablespaceId = pgTablespaceId(pgVersion),
 | |
|             .online = online,
 | |
|             .checksumPage = checksumPage,
 | |
|             .tablespaceList = tablespaceList,
 | |
|             .manifestParentName = MANIFEST_TARGET_PGDATA_STR,
 | |
|             .manifestWalName = strNewFmt(MANIFEST_TARGET_PGDATA "/%s", strPtr(pgWalPath(pgVersion))),
 | |
|             .pgPath = storagePathP(storagePg, NULL),
 | |
|         };
 | |
| 
 | |
|         // We won't identify db paths for PostgreSQL < 9.0.  This means that temp relations will not be excluded but it doesn't seem
 | |
|         // worth supporting this feature on such old versions of PostgreSQL.
 | |
|         // -------------------------------------------------------------------------------------------------------------------------
 | |
|         if (pgVersion >= PG_VERSION_90)
 | |
|         {
 | |
|             ASSERT(buildData.tablespaceId != NULL);
 | |
| 
 | |
|             // Expression to identify database paths
 | |
|             buildData.dbPathExp = regExpNew(strNewFmt("^" DB_PATH_EXP "$", strPtr(buildData.tablespaceId)));
 | |
| 
 | |
|             // Expression to find temp relations
 | |
|             buildData.tempRelationExp = regExpNew(STRDEF("^t[0-9]+_" RELATION_EXP "$"));
 | |
|         }
 | |
| 
 | |
|         // Build expression to identify files that can be copied from the standby when standby backup is supported
 | |
|         // -------------------------------------------------------------------------------------------------------------------------
 | |
|         buildData.standbyExp = regExpNew(
 | |
|             strNewFmt(
 | |
|                 "^((" MANIFEST_TARGET_PGDATA "/(" PG_PATH_BASE "|" PG_PATH_GLOBAL "|%s|" PG_PATH_PGMULTIXACT "))|"
 | |
|                     MANIFEST_TARGET_PGTBLSPC ")/",
 | |
|                 strPtr(pgXactPath(pgVersion))));
 | |
| 
 | |
|         // Build list of exclusions
 | |
|         // -------------------------------------------------------------------------------------------------------------------------
 | |
|         if (excludeList != NULL)
 | |
|         {
 | |
|             for (unsigned int excludeIdx = 0; excludeIdx < strLstSize(excludeList); excludeIdx++)
 | |
|             {
 | |
|                 const String *exclude = strNewFmt(MANIFEST_TARGET_PGDATA "/%s", strPtr(strLstGet(excludeList, excludeIdx)));
 | |
| 
 | |
|                 // If the exclusions refers to the contents of a path
 | |
|                 if (strEndsWithZ(exclude, "/"))
 | |
|                 {
 | |
|                     if (buildData.excludeContent == NULL)
 | |
|                         buildData.excludeContent = strLstNew();
 | |
| 
 | |
|                     strLstAdd(buildData.excludeContent, strSubN(exclude, 0, strSize(exclude) - 1));
 | |
|                 }
 | |
|                 // Otherwise exclude a single file/link/path
 | |
|                 else
 | |
|                 {
 | |
|                     if (buildData.excludeSingle == NULL)
 | |
|                         buildData.excludeSingle = strLstNew();
 | |
| 
 | |
|                     strLstAdd(buildData.excludeSingle, exclude);
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         // Build manifest
 | |
|         // -------------------------------------------------------------------------------------------------------------------------
 | |
|         StorageInfo info = storageInfoP(storagePg, buildData.pgPath, .followLink = true);
 | |
| 
 | |
|         ManifestPath path =
 | |
|         {
 | |
|             .name = MANIFEST_TARGET_PGDATA_STR,
 | |
|             .mode = info.mode,
 | |
|             .user = info.user,
 | |
|             .group = info.group,
 | |
|         };
 | |
| 
 | |
|         manifestPathAdd(this, &path);
 | |
| 
 | |
|         ManifestTarget target =
 | |
|         {
 | |
|             .name = MANIFEST_TARGET_PGDATA_STR,
 | |
|             .path = buildData.pgPath,
 | |
|             .type = manifestTargetTypePath,
 | |
|         };
 | |
| 
 | |
|         manifestTargetAdd(this, &target);
 | |
| 
 | |
|         // Gather info for the rest of the files/links/paths
 | |
|         storageInfoListP(
 | |
|             storagePg, buildData.pgPath, manifestBuildCallback, &buildData, .errorOnMissing = true, .sortOrder = sortOrderAsc);
 | |
| 
 | |
|         // These may not be in order even if the incoming data was sorted
 | |
|         lstSort(this->fileList, sortOrderAsc);
 | |
|         lstSort(this->linkList, sortOrderAsc);
 | |
|         lstSort(this->pathList, sortOrderAsc);
 | |
|         lstSort(this->targetList, sortOrderAsc);
 | |
| 
 | |
|         // Remove unlogged relations from the manifest.  This can't be done during the initial build because of the requirement to
 | |
|         // check for _init files which will sort after the vast majority of the relation files.  We could check storage for each
 | |
|         // _init file but that would be expensive.
 | |
|         // -------------------------------------------------------------------------------------------------------------------------
 | |
|         if (pgVersion >= PG_VERSION_91)
 | |
|         {
 | |
|             RegExp *relationExp = regExpNew(strNewFmt("^" DB_PATH_EXP "/" RELATION_EXP "$", strPtr(buildData.tablespaceId)));
 | |
|             unsigned int fileIdx = 0;
 | |
|             const String *lastRelationFileId = NULL;
 | |
|             bool lastRelationFileIdUnlogged = false;
 | |
| 
 | |
|             while (fileIdx < manifestFileTotal(this))
 | |
|             {
 | |
|                 const ManifestFile *file = manifestFile(this, fileIdx);
 | |
| 
 | |
|                 // If this file looks like a relation.  Note that this never matches on _init forks.
 | |
|                 if (regExpMatch(relationExp, file->name))
 | |
|                 {
 | |
|                     String *fileName = strBase(file->name);
 | |
|                     String *relationFileId = strNew("");
 | |
| 
 | |
|                     // Strip off the numeric part of the relation
 | |
|                     for (unsigned int nameIdx = 0; nameIdx < strSize(fileName); nameIdx++)
 | |
|                     {
 | |
|                         char nameChr = strPtr(fileName)[nameIdx];
 | |
| 
 | |
|                         if (!isdigit(nameChr))
 | |
|                             break;
 | |
| 
 | |
|                         strCatChr(relationFileId, nameChr);
 | |
|                     }
 | |
| 
 | |
|                     // Store the last relation so it does not need to be found everytime
 | |
|                     if (lastRelationFileId == NULL || !strEq(lastRelationFileId, relationFileId))
 | |
|                     {
 | |
|                         // Determine if the relation is unlogged
 | |
|                         const String *relationInit = strNewFmt("%s/%s_init", strPtr(strPath(file->name)), strPtr(relationFileId));
 | |
|                         lastRelationFileId = relationFileId;
 | |
|                         lastRelationFileIdUnlogged = manifestFileFindDefault(this, relationInit, NULL) != NULL;
 | |
|                     }
 | |
| 
 | |
|                     // If relation is unlogged then remove it
 | |
|                     if (lastRelationFileIdUnlogged)
 | |
|                     {
 | |
|                         manifestFileRemove(this, file->name);
 | |
|                         continue;
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 fileIdx++;
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     MEM_CONTEXT_NEW_END();
 | |
| 
 | |
|     FUNCTION_LOG_RETURN(MANIFEST, this);
 | |
| }
 | |
| 
 | |
| /**********************************************************************************************************************************/
 | |
| void
 | |
| manifestBuildValidate(Manifest *this, bool delta, time_t copyStart, CompressType compressType)
 | |
| {
 | |
|     FUNCTION_LOG_BEGIN(logLevelDebug);
 | |
|         FUNCTION_LOG_PARAM(MANIFEST, this);
 | |
|         FUNCTION_LOG_PARAM(BOOL, delta);
 | |
|         FUNCTION_LOG_PARAM(TIME, copyStart);
 | |
|         FUNCTION_LOG_PARAM(ENUM, compressType);
 | |
|     FUNCTION_LOG_END();
 | |
| 
 | |
|     ASSERT(this != NULL);
 | |
|     ASSERT(copyStart > 0);
 | |
| 
 | |
|     MEM_CONTEXT_BEGIN(this->memContext)
 | |
|     {
 | |
|         // Store the delta option.  If true we can skip checks that automatically enable delta.
 | |
|         this->data.backupOptionDelta = varNewBool(delta);
 | |
| 
 | |
|         // If online then add one second to the copy start time to allow for database updates during the last second that the
 | |
|         // manifest was being built.  It's up to the caller to actually wait the remainder of the second, but for comparison
 | |
|         // purposes we want the time when the waiting started.
 | |
|         this->data.backupTimestampCopyStart = copyStart + (this->data.backupOptionOnline ? 1 : 0);
 | |
| 
 | |
|         // This value is not needed in this function, but it is needed for resumed manifests and this is last place to set it before
 | |
|         // processing begins
 | |
|         this->data.backupOptionCompressType = compressType;
 | |
|     }
 | |
|     MEM_CONTEXT_END();
 | |
| 
 | |
|     // Check the manifest for timestamp anomalies that require a delta backup (if delta is not already specified)
 | |
|     if (!varBool(this->data.backupOptionDelta))
 | |
|     {
 | |
|         MEM_CONTEXT_TEMP_BEGIN()
 | |
|         {
 | |
|             for (unsigned int fileIdx = 0; fileIdx < manifestFileTotal(this); fileIdx++)
 | |
|             {
 | |
|                 const ManifestFile *file = manifestFile(this, fileIdx);
 | |
| 
 | |
|                 // Check for timestamp in the future
 | |
|                 if (file->timestamp > copyStart)
 | |
|                 {
 | |
|                     LOG_WARN_FMT(
 | |
|                         "file '%s' has timestamp in the future, enabling delta checksum", strPtr(manifestPathPg(file->name)));
 | |
| 
 | |
|                     this->data.backupOptionDelta = BOOL_TRUE_VAR;
 | |
|                     break;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|         MEM_CONTEXT_TEMP_END();
 | |
|     }
 | |
| 
 | |
|     FUNCTION_LOG_RETURN_VOID();
 | |
| }
 | |
| 
 | |
| /**********************************************************************************************************************************/
 | |
| void
 | |
| manifestBuildIncr(Manifest *this, const Manifest *manifestPrior, BackupType type, const String *archiveStart)
 | |
| {
 | |
|     FUNCTION_LOG_BEGIN(logLevelDebug);
 | |
|         FUNCTION_LOG_PARAM(MANIFEST, this);
 | |
|         FUNCTION_LOG_PARAM(MANIFEST, manifestPrior);
 | |
|         FUNCTION_LOG_PARAM(ENUM, type);
 | |
|         FUNCTION_LOG_PARAM(STRING, archiveStart);
 | |
|     FUNCTION_LOG_END();
 | |
| 
 | |
|     ASSERT(this != NULL);
 | |
|     ASSERT(manifestPrior != NULL);
 | |
|     ASSERT(type == backupTypeDiff || type == backupTypeIncr);
 | |
|     ASSERT(type != backupTypeDiff || manifestPrior->data.backupType == backupTypeFull);
 | |
|     ASSERT(archiveStart == NULL || strSize(archiveStart) == 24);
 | |
| 
 | |
|     MEM_CONTEXT_BEGIN(this->memContext)
 | |
|     {
 | |
|         // Set prior backup label
 | |
|         this->data.backupLabelPrior = strDup(manifestPrior->data.backupLabel);
 | |
| 
 | |
|         // Set diff/incr backup type
 | |
|         this->data.backupType = type;
 | |
|     }
 | |
|     MEM_CONTEXT_END();
 | |
| 
 | |
|     MEM_CONTEXT_TEMP_BEGIN()
 | |
|     {
 | |
|         // Enable delta if timelines differ
 | |
|         if (archiveStart != NULL && manifestData(manifestPrior)->archiveStop != NULL &&
 | |
|             !strEq(strSubN(archiveStart, 0, 8), strSubN(manifestData(manifestPrior)->archiveStop, 0, 8)))
 | |
|         {
 | |
|             LOG_WARN_FMT(
 | |
|                 "a timeline switch has occurred since the %s backup, enabling delta checksum",
 | |
|                 strPtr(manifestData(manifestPrior)->backupLabel));
 | |
| 
 | |
|             this->data.backupOptionDelta = BOOL_TRUE_VAR;
 | |
|         }
 | |
|         // Else enable delta if online differs
 | |
|         else if (manifestData(manifestPrior)->backupOptionOnline != this->data.backupOptionOnline)
 | |
|         {
 | |
|             LOG_WARN_FMT(
 | |
|                 "the online option has changed since the %s backup, enabling delta checksum",
 | |
|                 strPtr(manifestData(manifestPrior)->backupLabel));
 | |
| 
 | |
|             this->data.backupOptionDelta = BOOL_TRUE_VAR;
 | |
|         }
 | |
| 
 | |
|         // Check for anomalies between manifests if delta is not already enabled.  This can't be combined with the main comparison
 | |
|         // loop below because delta changes the behavior of that loop.
 | |
|         if (!varBool(this->data.backupOptionDelta))
 | |
|         {
 | |
|             for (unsigned int fileIdx = 0; fileIdx < manifestFileTotal(this); fileIdx++)
 | |
|             {
 | |
|                 const ManifestFile *file = manifestFile(this, fileIdx);
 | |
|                 const ManifestFile *filePrior = manifestFileFindDefault(manifestPrior, file->name, NULL);
 | |
| 
 | |
|                 // If file was found in prior manifest then perform checks
 | |
|                 if (filePrior != NULL)
 | |
|                 {
 | |
|                     // Check for timestamp earlier than the prior backup
 | |
|                     if (file->timestamp < filePrior->timestamp)
 | |
|                     {
 | |
|                         LOG_WARN_FMT(
 | |
|                             "file '%s' has timestamp earlier than prior backup, enabling delta checksum",
 | |
|                             strPtr(manifestPathPg(file->name)));
 | |
| 
 | |
|                         this->data.backupOptionDelta = BOOL_TRUE_VAR;
 | |
|                         break;
 | |
|                     }
 | |
| 
 | |
|                     // Check for size change with no timestamp change
 | |
|                     if (file->size != filePrior->size && file->timestamp == filePrior->timestamp)
 | |
|                     {
 | |
|                         LOG_WARN_FMT(
 | |
|                             "file '%s' has same timestamp as prior but different size, enabling delta checksum",
 | |
|                             strPtr(manifestPathPg(file->name)));
 | |
| 
 | |
|                         this->data.backupOptionDelta = BOOL_TRUE_VAR;
 | |
|                         break;
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         // Find files to reference in the prior manifest:
 | |
|         // 1) that don't need to be copied because delta is disabled and the size and timestamp match or size matches and is zero
 | |
|         // 2) where delta is enabled and size matches so checksum will be verified during backup and the file copied on mismatch
 | |
|         bool delta = varBool(this->data.backupOptionDelta);
 | |
| 
 | |
|         for (unsigned int fileIdx = 0; fileIdx < lstSize(this->fileList); fileIdx++)
 | |
|         {
 | |
|             const ManifestFile *file = manifestFile(this, fileIdx);
 | |
|             const ManifestFile *filePrior = manifestFileFindDefault(manifestPrior, file->name, NULL);
 | |
| 
 | |
|             // Check if prior file can be used
 | |
|             if (filePrior != NULL && file->size == filePrior->size &&
 | |
|                 (delta || file->size == 0 || file->timestamp == filePrior->timestamp))
 | |
|             {
 | |
|                 manifestFileUpdate(
 | |
|                     this, file->name, file->size, filePrior->sizeRepo, filePrior->checksumSha1,
 | |
|                     VARSTR(filePrior->reference != NULL ? filePrior->reference : manifestPrior->data.backupLabel),
 | |
|                     filePrior->checksumPage, filePrior->checksumPageError, filePrior->checksumPageErrorList);
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     MEM_CONTEXT_TEMP_END();
 | |
| 
 | |
|     FUNCTION_LOG_RETURN_VOID();
 | |
| }
 | |
| 
 | |
| /**********************************************************************************************************************************/
 | |
| void
 | |
| manifestBuildComplete(
 | |
|     Manifest *this, time_t timestampStart, const String *lsnStart, const String *archiveStart, time_t timestampStop,
 | |
|     const String *lsnStop, const String *archiveStop, unsigned int pgId, uint64_t pgSystemId, const VariantList *dbList,
 | |
|     bool optionArchiveCheck, bool optionArchiveCopy, size_t optionBufferSize, unsigned int optionCompressLevel,
 | |
|     unsigned int optionCompressLevelNetwork, bool optionHardLink, unsigned int optionProcessMax,
 | |
|     bool optionStandby)
 | |
| {
 | |
|     FUNCTION_LOG_BEGIN(logLevelDebug);
 | |
|         FUNCTION_LOG_PARAM(MANIFEST, this);
 | |
|         FUNCTION_LOG_PARAM(TIME, timestampStart);
 | |
|         FUNCTION_LOG_PARAM(STRING, lsnStart);
 | |
|         FUNCTION_LOG_PARAM(STRING, archiveStart);
 | |
|         FUNCTION_LOG_PARAM(TIME, timestampStop);
 | |
|         FUNCTION_LOG_PARAM(STRING, lsnStop);
 | |
|         FUNCTION_LOG_PARAM(STRING, archiveStop);
 | |
|         FUNCTION_LOG_PARAM(UINT, pgId);
 | |
|         FUNCTION_LOG_PARAM(UINT64, pgSystemId);
 | |
|         FUNCTION_LOG_PARAM(VARIANT_LIST, dbList);
 | |
|         FUNCTION_LOG_PARAM(BOOL, optionArchiveCheck);
 | |
|         FUNCTION_LOG_PARAM(BOOL, optionArchiveCopy);
 | |
|         FUNCTION_LOG_PARAM(SIZE, optionBufferSize);
 | |
|         FUNCTION_LOG_PARAM(UINT, optionCompressLevel);
 | |
|         FUNCTION_LOG_PARAM(UINT, optionCompressLevelNetwork);
 | |
|         FUNCTION_LOG_PARAM(BOOL, optionHardLink);
 | |
|         FUNCTION_LOG_PARAM(UINT, optionProcessMax);
 | |
|         FUNCTION_LOG_PARAM(BOOL, optionStandby);
 | |
|     FUNCTION_LOG_END();
 | |
| 
 | |
|     MEM_CONTEXT_BEGIN(this->memContext)
 | |
|     {
 | |
|         // Save info
 | |
|         this->data.backupTimestampStart = timestampStart;
 | |
|         this->data.lsnStart = strDup(lsnStart);
 | |
|         this->data.archiveStart = strDup(archiveStart);
 | |
|         this->data.backupTimestampStop = timestampStop;
 | |
|         this->data.lsnStop = strDup(lsnStop);
 | |
|         this->data.archiveStop = strDup(archiveStop);
 | |
|         this->data.pgId = pgId;
 | |
|         this->data.pgSystemId = pgSystemId;
 | |
| 
 | |
|         // Save db list
 | |
|         if (dbList != NULL)
 | |
|         {
 | |
|             for (unsigned int dbIdx = 0; dbIdx < varLstSize(dbList); dbIdx++)
 | |
|             {
 | |
|                 const VariantList *dbRow = varVarLst(varLstGet(dbList, dbIdx));
 | |
| 
 | |
|                 ManifestDb db =
 | |
|                 {
 | |
|                     .id = varUIntForce(varLstGet(dbRow, 0)),
 | |
|                     .name = varStr(varLstGet(dbRow, 1)),
 | |
|                     .lastSystemId = varUIntForce(varLstGet(dbRow, 2)),
 | |
|                 };
 | |
| 
 | |
|                 manifestDbAdd(this, &db);
 | |
|             }
 | |
| 
 | |
|             lstSort(this->dbList, sortOrderAsc);
 | |
|         }
 | |
| 
 | |
|         // Save options
 | |
|         this->data.backupOptionArchiveCheck = optionArchiveCheck;
 | |
|         this->data.backupOptionArchiveCopy = optionArchiveCopy;
 | |
|         this->data.backupOptionBufferSize = varNewUInt64(optionBufferSize);
 | |
|         this->data.backupOptionCompressLevel = varNewUInt(optionCompressLevel);
 | |
|         this->data.backupOptionCompressLevelNetwork = varNewUInt(optionCompressLevelNetwork);
 | |
|         this->data.backupOptionHardLink = optionHardLink;
 | |
|         this->data.backupOptionProcessMax = varNewUInt(optionProcessMax);
 | |
|         this->data.backupOptionStandby = varNewBool(optionStandby);
 | |
|     }
 | |
|     MEM_CONTEXT_END();
 | |
| 
 | |
|     FUNCTION_LOG_RETURN_VOID();
 | |
| }
 | |
| 
 | |
| /***********************************************************************************************************************************
 | |
| Load manifest
 | |
| ***********************************************************************************************************************************/
 | |
| // Keep track of which values were found during load and which need to be loaded from defaults. There is no point in having
 | |
| // multiple structs since most of the fields are the same and the size shouldn't be more than 4/8 bytes.
 | |
| typedef struct ManifestLoadFound
 | |
| {
 | |
|     bool group:1;
 | |
|     bool mode:1;
 | |
|     bool primary:1;
 | |
|     bool user:1;
 | |
| } ManifestLoadFound;
 | |
| 
 | |
| typedef struct ManifestLoadData
 | |
| {
 | |
|     MemContext *memContext;                                         // Mem context for data needed only during load
 | |
|     Manifest *manifest;                                             // Manifest info
 | |
| 
 | |
|     List *fileFoundList;                                            // Values found in files
 | |
|     const Variant *fileGroupDefault;                                // File default group
 | |
|     mode_t fileModeDefault;                                         // File default mode
 | |
|     bool filePrimaryDefault;                                        // File default primary
 | |
|     const Variant *fileUserDefault;                                 // File default user
 | |
| 
 | |
|     List *linkFoundList;                                            // Values found in links
 | |
|     const Variant *linkGroupDefault;                                // Link default group
 | |
|     const Variant *linkUserDefault;                                 // Link default user
 | |
| 
 | |
|     List *pathFoundList;                                            // Values found in paths
 | |
|     const Variant *pathGroupDefault;                                // Path default group
 | |
|     mode_t pathModeDefault;                                         // Path default mode
 | |
|     const Variant *pathUserDefault;                                 // Path default user
 | |
| } ManifestLoadData;
 | |
| 
 | |
| // Helper to transform a variant that could be boolean or string into a string.  If the boolean is false return NULL else return
 | |
| // the string.  The boolean cannot be true.
 | |
| static const String *
 | |
| manifestOwnerGet(const Variant *owner)
 | |
| {
 | |
|     FUNCTION_TEST_BEGIN();
 | |
|         FUNCTION_TEST_PARAM(VARIANT, owner);
 | |
|     FUNCTION_TEST_END();
 | |
| 
 | |
|     ASSERT(owner != NULL);
 | |
| 
 | |
|     // If bool then it should be false.  This indicates that the owner could not be mapped to a name during the backup.
 | |
|     if (varType(owner) == varTypeBool)
 | |
|     {
 | |
|         CHECK(!varBool(owner));
 | |
|         FUNCTION_TEST_RETURN(NULL);
 | |
|     }
 | |
| 
 | |
|     FUNCTION_TEST_RETURN(varStr(owner));
 | |
| }
 | |
| 
 | |
| // Helper to convert default owner to a variant.  Input could be boolean false (meaning there is no owner) or a string (there is an
 | |
| // owner).
 | |
| static const Variant *
 | |
| manifestOwnerDefaultGet(const String *ownerDefault)
 | |
| {
 | |
|     FUNCTION_TEST_BEGIN();
 | |
|         FUNCTION_TEST_PARAM(STRING, ownerDefault);
 | |
|     FUNCTION_TEST_END();
 | |
| 
 | |
|     ASSERT(ownerDefault != NULL);
 | |
| 
 | |
|     FUNCTION_TEST_RETURN(strEq(ownerDefault, FALSE_STR) ? BOOL_FALSE_VAR : varNewStr(jsonToStr(ownerDefault)));
 | |
| }
 | |
| 
 | |
| static void
 | |
| manifestLoadCallback(void *callbackData, const String *section, const String *key, const String *value)
 | |
| {
 | |
|     FUNCTION_TEST_BEGIN();
 | |
|         FUNCTION_TEST_PARAM_P(VOID, callbackData);
 | |
|         FUNCTION_TEST_PARAM(STRING, section);
 | |
|         FUNCTION_TEST_PARAM(STRING, key);
 | |
|         FUNCTION_TEST_PARAM(STRING, value);
 | |
|     FUNCTION_TEST_END();
 | |
| 
 | |
|     ASSERT(callbackData != NULL);
 | |
|     ASSERT(section != NULL);
 | |
|     ASSERT(key != NULL);
 | |
|     ASSERT(value != NULL);
 | |
| 
 | |
|     ManifestLoadData *loadData = (ManifestLoadData *)callbackData;
 | |
|     Manifest *manifest = loadData->manifest;
 | |
| 
 | |
|     // -----------------------------------------------------------------------------------------------------------------------------
 | |
|     if (strEq(section, MANIFEST_SECTION_TARGET_FILE_STR))
 | |
|     {
 | |
|         KeyValue *fileKv = varKv(jsonToVar(value));
 | |
| 
 | |
|         MEM_CONTEXT_BEGIN(lstMemContext(manifest->fileList))
 | |
|         {
 | |
|             ManifestLoadFound valueFound = {0};
 | |
| 
 | |
|             ManifestFile file =
 | |
|             {
 | |
|                 .name = key,
 | |
|                 .reference = varStr(kvGetDefault(fileKv, MANIFEST_KEY_REFERENCE_VAR, NULL)),
 | |
|             };
 | |
| 
 | |
|             // Timestamp is required so error if it is not present
 | |
|             const Variant *timestamp = kvGet(fileKv, MANIFEST_KEY_TIMESTAMP_VAR);
 | |
| 
 | |
|             if (timestamp == NULL)
 | |
|                 THROW_FMT(FormatError, "missing timestamp for file '%s'", strPtr(key));
 | |
| 
 | |
|             file.timestamp = (time_t)varUInt64(timestamp);
 | |
| 
 | |
|             // Size is required so error if it is not present.  Older versions removed the size before the backup to ensure that the
 | |
|             // manifest was updated during the backup, so size can be missing in partial manifests.  This error will prevent older
 | |
|             // partials from being resumed.
 | |
|             const Variant *size = kvGet(fileKv, MANIFEST_KEY_SIZE_VAR);
 | |
| 
 | |
|             if (size == NULL)
 | |
|                 THROW_FMT(FormatError, "missing size for file '%s'", strPtr(key));
 | |
| 
 | |
|             file.size = varUInt64(size);
 | |
| 
 | |
|             // If "repo-size" is not present in the manifest file, then it is the same as size (i.e. uncompressed) - to save space,
 | |
|             // the repo-size is only stored in the manifest file if it is different than size.
 | |
|             file.sizeRepo = varUInt64(kvGetDefault(fileKv, MANIFEST_KEY_SIZE_REPO_VAR, VARUINT64(file.size)));
 | |
| 
 | |
|             // If file size is zero then assign the static zero hash
 | |
|             if (file.size == 0)
 | |
|             {
 | |
|                 memcpy(file.checksumSha1, HASH_TYPE_SHA1_ZERO, HASH_TYPE_SHA1_SIZE_HEX + 1);
 | |
|             }
 | |
|             // Else if the key exists then load it.  The key might not exist if this is a partial save that was done during the
 | |
|             // backup to preserve checksums for already backed up files.
 | |
|             else if (kvKeyExists(fileKv, MANIFEST_KEY_CHECKSUM_VAR))
 | |
|             {
 | |
|                 memcpy(
 | |
|                     file.checksumSha1, strPtr(varStr(kvGet(fileKv, MANIFEST_KEY_CHECKSUM_VAR))), HASH_TYPE_SHA1_SIZE_HEX + 1);
 | |
|             }
 | |
| 
 | |
|             const Variant *checksumPage = kvGetDefault(fileKv, MANIFEST_KEY_CHECKSUM_PAGE_VAR, NULL);
 | |
| 
 | |
|             if (checksumPage != NULL)
 | |
|             {
 | |
|                 file.checksumPage = true;
 | |
|                 file.checksumPageError = !varBool(checksumPage);
 | |
| 
 | |
|                 const Variant *checksumPageErrorList = kvGetDefault(fileKv, MANIFEST_KEY_CHECKSUM_PAGE_ERROR_VAR, NULL);
 | |
| 
 | |
|                 if (checksumPageErrorList != NULL)
 | |
|                     file.checksumPageErrorList = varVarLst(checksumPageErrorList);
 | |
|             }
 | |
| 
 | |
|             if (kvKeyExists(fileKv, MANIFEST_KEY_GROUP_VAR))
 | |
|             {
 | |
|                 valueFound.group = true;
 | |
|                 file.group = manifestOwnerGet(kvGet(fileKv, MANIFEST_KEY_GROUP_VAR));
 | |
|             }
 | |
| 
 | |
|             if (kvKeyExists(fileKv, MANIFEST_KEY_MODE_VAR))
 | |
|             {
 | |
|                 valueFound.mode = true;
 | |
|                 file.mode = cvtZToMode(strPtr(varStr(kvGet(fileKv, MANIFEST_KEY_MODE_VAR))));
 | |
|             }
 | |
| 
 | |
|             if (kvKeyExists(fileKv, MANIFEST_KEY_PRIMARY_VAR))
 | |
|             {
 | |
|                 valueFound.primary = true;
 | |
|                 file.primary = varBool(kvGet(fileKv, MANIFEST_KEY_PRIMARY_VAR));
 | |
|             }
 | |
| 
 | |
|             if (kvKeyExists(fileKv, MANIFEST_KEY_USER_VAR))
 | |
|             {
 | |
|                 valueFound.user = true;
 | |
|                 file.user = manifestOwnerGet(kvGet(fileKv, MANIFEST_KEY_USER_VAR));
 | |
|             }
 | |
| 
 | |
|             lstAdd(loadData->fileFoundList, &valueFound);
 | |
|             manifestFileAdd(manifest, &file);
 | |
|         }
 | |
|         MEM_CONTEXT_END();
 | |
|     }
 | |
| 
 | |
|     // -----------------------------------------------------------------------------------------------------------------------------
 | |
|     else if (strEq(section, MANIFEST_SECTION_TARGET_PATH_STR))
 | |
|     {
 | |
|         KeyValue *pathKv = varKv(jsonToVar(value));
 | |
| 
 | |
|         MEM_CONTEXT_BEGIN(lstMemContext(manifest->pathList))
 | |
|         {
 | |
|             ManifestLoadFound valueFound = {0};
 | |
| 
 | |
|             ManifestPath path =
 | |
|             {
 | |
|                 .name = key,
 | |
|             };
 | |
| 
 | |
|             if (kvKeyExists(pathKv, MANIFEST_KEY_GROUP_VAR))
 | |
|             {
 | |
|                 valueFound.group = true;
 | |
|                 path.group = manifestOwnerGet(kvGet(pathKv, MANIFEST_KEY_GROUP_VAR));
 | |
|             }
 | |
| 
 | |
|             if (kvKeyExists(pathKv, MANIFEST_KEY_MODE_VAR))
 | |
|             {
 | |
|                 valueFound.mode = true;
 | |
|                 path.mode = cvtZToMode(strPtr(varStr(kvGet(pathKv, MANIFEST_KEY_MODE_VAR))));
 | |
|             }
 | |
| 
 | |
|             if (kvKeyExists(pathKv, MANIFEST_KEY_USER_VAR))
 | |
|             {
 | |
|                 valueFound.user = true;
 | |
|                 path.user = manifestOwnerGet(kvGet(pathKv, MANIFEST_KEY_USER_VAR));
 | |
|             }
 | |
| 
 | |
|             lstAdd(loadData->pathFoundList, &valueFound);
 | |
|             manifestPathAdd(manifest, &path);
 | |
|         }
 | |
|         MEM_CONTEXT_END();
 | |
|     }
 | |
| 
 | |
|     // -----------------------------------------------------------------------------------------------------------------------------
 | |
|     else if (strEq(section, MANIFEST_SECTION_TARGET_LINK_STR))
 | |
|     {
 | |
|         KeyValue *linkKv = varKv(jsonToVar(value));
 | |
| 
 | |
|         MEM_CONTEXT_BEGIN(lstMemContext(manifest->linkList))
 | |
|         {
 | |
|             ManifestLoadFound valueFound = {0};
 | |
| 
 | |
|             ManifestLink link =
 | |
|             {
 | |
|                 .name = key,
 | |
|                 .destination = varStr(kvGet(linkKv, MANIFEST_KEY_DESTINATION_VAR)),
 | |
|             };
 | |
| 
 | |
|             if (kvKeyExists(linkKv, MANIFEST_KEY_GROUP_VAR))
 | |
|             {
 | |
|                 valueFound.group = true;
 | |
|                 link.group = manifestOwnerGet(kvGet(linkKv, MANIFEST_KEY_GROUP_VAR));
 | |
|             }
 | |
| 
 | |
|             if (kvKeyExists(linkKv, MANIFEST_KEY_USER_VAR))
 | |
|             {
 | |
|                 valueFound.user = true;
 | |
|                 link.user = manifestOwnerGet(kvGet(linkKv, MANIFEST_KEY_USER_VAR));
 | |
|             }
 | |
| 
 | |
|             lstAdd(loadData->linkFoundList, &valueFound);
 | |
|             manifestLinkAdd(manifest, &link);
 | |
|         }
 | |
|         MEM_CONTEXT_END();
 | |
|     }
 | |
| 
 | |
|     // -----------------------------------------------------------------------------------------------------------------------------
 | |
|     else if (strEq(section, MANIFEST_SECTION_TARGET_FILE_DEFAULT_STR))
 | |
|     {
 | |
|         MEM_CONTEXT_BEGIN(loadData->memContext)
 | |
|         {
 | |
|             if (strEq(key, MANIFEST_KEY_GROUP_STR))
 | |
|                 loadData->fileGroupDefault = manifestOwnerDefaultGet(value);
 | |
|             else if (strEq(key, MANIFEST_KEY_MODE_STR))
 | |
|                 loadData->fileModeDefault = cvtZToMode(strPtr(jsonToStr(value)));
 | |
|             else if (strEq(key, MANIFEST_KEY_PRIMARY_STR))
 | |
|                 loadData->filePrimaryDefault = jsonToBool(value);
 | |
|             else if (strEq(key, MANIFEST_KEY_USER_STR))
 | |
|                 loadData->fileUserDefault = manifestOwnerDefaultGet(value);
 | |
|         }
 | |
|         MEM_CONTEXT_END();
 | |
|     }
 | |
| 
 | |
|     // -----------------------------------------------------------------------------------------------------------------------------
 | |
|     else if (strEq(section, MANIFEST_SECTION_TARGET_PATH_DEFAULT_STR))
 | |
|     {
 | |
|         MEM_CONTEXT_BEGIN(loadData->memContext)
 | |
|         {
 | |
|             if (strEq(key, MANIFEST_KEY_GROUP_STR))
 | |
|                 loadData->pathGroupDefault = manifestOwnerDefaultGet(value);
 | |
|             else if (strEq(key, MANIFEST_KEY_MODE_STR))
 | |
|                 loadData->pathModeDefault = cvtZToMode(strPtr(jsonToStr(value)));
 | |
|             else if (strEq(key, MANIFEST_KEY_USER_STR))
 | |
|                 loadData->pathUserDefault = manifestOwnerDefaultGet(value);
 | |
|         }
 | |
|         MEM_CONTEXT_END();
 | |
|     }
 | |
| 
 | |
|     // -----------------------------------------------------------------------------------------------------------------------------
 | |
|     else if (strEq(section, MANIFEST_SECTION_TARGET_LINK_DEFAULT_STR))
 | |
|     {
 | |
|         MEM_CONTEXT_BEGIN(loadData->memContext)
 | |
|         {
 | |
|             if (strEq(key, MANIFEST_KEY_GROUP_STR))
 | |
|                 loadData->linkGroupDefault = manifestOwnerDefaultGet(value);
 | |
|             else if (strEq(key, MANIFEST_KEY_USER_STR))
 | |
|                 loadData->linkUserDefault = manifestOwnerDefaultGet(value);
 | |
|         }
 | |
|         MEM_CONTEXT_END();
 | |
|     }
 | |
| 
 | |
|     // -----------------------------------------------------------------------------------------------------------------------------
 | |
|     else if (strEq(section, MANIFEST_SECTION_BACKUP_TARGET_STR))
 | |
|     {
 | |
|         KeyValue *targetKv = varKv(jsonToVar(value));
 | |
|         const String *targetType = varStr(kvGet(targetKv, MANIFEST_KEY_TYPE_VAR));
 | |
| 
 | |
|         ASSERT(strEq(targetType, MANIFEST_TARGET_TYPE_LINK_STR) || strEq(targetType, MANIFEST_TARGET_TYPE_PATH_STR));
 | |
| 
 | |
|         ManifestTarget target =
 | |
|         {
 | |
|             .name = key,
 | |
|             .file = varStr(kvGetDefault(targetKv, MANIFEST_KEY_FILE_VAR, NULL)),
 | |
|             .path = varStr(kvGet(targetKv, MANIFEST_KEY_PATH_VAR)),
 | |
|             .tablespaceId =
 | |
|                 cvtZToUInt(strPtr(varStr(kvGetDefault(targetKv, MANIFEST_KEY_TABLESPACE_ID_VAR, VARSTRDEF("0"))))),
 | |
|             .tablespaceName = varStr(kvGetDefault(targetKv, MANIFEST_KEY_TABLESPACE_NAME_VAR, NULL)),
 | |
|             .type = strEq(targetType, MANIFEST_TARGET_TYPE_PATH_STR) ? manifestTargetTypePath : manifestTargetTypeLink,
 | |
|         };
 | |
| 
 | |
|         manifestTargetAdd(manifest, &target);
 | |
|     }
 | |
| 
 | |
|     // -----------------------------------------------------------------------------------------------------------------------------
 | |
|     else if (strEq(section, MANIFEST_SECTION_DB_STR))
 | |
|     {
 | |
|         KeyValue *dbKv = varKv(jsonToVar(value));
 | |
| 
 | |
|         MEM_CONTEXT_BEGIN(lstMemContext(manifest->dbList))
 | |
|         {
 | |
|             ManifestDb db =
 | |
|             {
 | |
|                 .name = strDup(key),
 | |
|                 .id = varUIntForce(kvGet(dbKv, MANIFEST_KEY_DB_ID_VAR)),
 | |
|                 .lastSystemId = varUIntForce(kvGet(dbKv, MANIFEST_KEY_DB_LAST_SYSTEM_ID_VAR)),
 | |
|             };
 | |
| 
 | |
|             manifestDbAdd(manifest, &db);
 | |
|         }
 | |
|         MEM_CONTEXT_END();
 | |
|     }
 | |
| 
 | |
|     // -----------------------------------------------------------------------------------------------------------------------------
 | |
|     else if (strEq(section, MANIFEST_SECTION_BACKUP_STR))
 | |
|     {
 | |
|         MEM_CONTEXT_BEGIN(manifest->memContext)
 | |
|         {
 | |
|             if (strEq(key, MANIFEST_KEY_BACKUP_ARCHIVE_START_STR))
 | |
|                 manifest->data.archiveStart = jsonToStr(value);
 | |
|             else if (strEq(key, MANIFEST_KEY_BACKUP_ARCHIVE_STOP_STR))
 | |
|                 manifest->data.archiveStop = jsonToStr(value);
 | |
|             else if (strEq(key, MANIFEST_KEY_BACKUP_LABEL_STR))
 | |
|                 manifest->data.backupLabel = jsonToStr(value);
 | |
|             else if (strEq(key, MANIFEST_KEY_BACKUP_LSN_START_STR))
 | |
|                 manifest->data.lsnStart = jsonToStr(value);
 | |
|             else if (strEq(key, MANIFEST_KEY_BACKUP_LSN_STOP_STR))
 | |
|                 manifest->data.lsnStop = jsonToStr(value);
 | |
|             else if (strEq(key, MANIFEST_KEY_BACKUP_PRIOR_STR))
 | |
|                 manifest->data.backupLabelPrior = jsonToStr(value);
 | |
|             else if (strEq(key, MANIFEST_KEY_BACKUP_TIMESTAMP_COPY_START_STR))
 | |
|                 manifest->data.backupTimestampCopyStart = (time_t)jsonToUInt64(value);
 | |
|             else if (strEq(key, MANIFEST_KEY_BACKUP_TIMESTAMP_START_STR))
 | |
|                 manifest->data.backupTimestampStart = (time_t)jsonToUInt64(value);
 | |
|             else if (strEq(key, MANIFEST_KEY_BACKUP_TIMESTAMP_STOP_STR))
 | |
|                 manifest->data.backupTimestampStop = (time_t)jsonToUInt64(value);
 | |
|             else if (strEq(key, MANIFEST_KEY_BACKUP_TYPE_STR))
 | |
|                 manifest->data.backupType = backupType(jsonToStr(value));
 | |
|         }
 | |
|         MEM_CONTEXT_END();
 | |
|     }
 | |
| 
 | |
|     // -----------------------------------------------------------------------------------------------------------------------------
 | |
|     else if (strEq(section, MANIFEST_SECTION_BACKUP_DB_STR))
 | |
|     {
 | |
|         if (strEq(key, MANIFEST_KEY_DB_ID_STR))
 | |
|             manifest->data.pgId = jsonToUInt(value);
 | |
|         else if (strEq(key, MANIFEST_KEY_DB_SYSTEM_ID_STR))
 | |
|             manifest->data.pgSystemId = jsonToUInt64(value);
 | |
|         else if (strEq(key, MANIFEST_KEY_DB_VERSION_STR))
 | |
|             manifest->data.pgVersion = pgVersionFromStr(jsonToStr(value));
 | |
|     }
 | |
| 
 | |
|     // -----------------------------------------------------------------------------------------------------------------------------
 | |
|     else if (strEq(section, MANIFEST_SECTION_BACKUP_OPTION_STR))
 | |
|     {
 | |
|         MEM_CONTEXT_BEGIN(manifest->memContext)
 | |
|         {
 | |
|             // Required options
 | |
|             if (strEq(key, MANIFEST_KEY_OPTION_ARCHIVE_CHECK_STR))
 | |
|                 manifest->data.backupOptionArchiveCheck = jsonToBool(value);
 | |
|             else if (strEq(key, MANIFEST_KEY_OPTION_ARCHIVE_COPY_STR))
 | |
|                 manifest->data.backupOptionArchiveCopy = jsonToBool(value);
 | |
|             // Historically this option meant to add gz compression
 | |
|             else if (strEq(key, MANIFEST_KEY_OPTION_COMPRESS_STR))
 | |
|                 manifest->data.backupOptionCompressType = jsonToBool(value) ? compressTypeGz : compressTypeNone;
 | |
|             // This new option allows any type of compression to be specified.  It must be parsed after the option above so the
 | |
|             // value does not get overwritten.  Since options are stored in alpha order this should always be true.
 | |
|             else if (strEq(key, MANIFEST_KEY_OPTION_COMPRESS_TYPE_STR))
 | |
|                 manifest->data.backupOptionCompressType = compressTypeEnum(jsonToStr(value));
 | |
|             else if (strEq(key, MANIFEST_KEY_OPTION_HARDLINK_STR))
 | |
|                 manifest->data.backupOptionHardLink = jsonToBool(value);
 | |
|             else if (strEq(key, MANIFEST_KEY_OPTION_ONLINE_STR))
 | |
|                 manifest->data.backupOptionOnline = jsonToBool(value);
 | |
| 
 | |
|             // Options that were added after v1.00 and may not be present in every manifest
 | |
|             else if (strEq(key, MANIFEST_KEY_OPTION_BACKUP_STANDBY_STR))
 | |
|                 manifest->data.backupOptionStandby = varNewBool(jsonToBool(value));
 | |
|             else if (strEq(key, MANIFEST_KEY_OPTION_BUFFER_SIZE_STR))
 | |
|                 manifest->data.backupOptionBufferSize = varNewUInt(jsonToUInt(value));
 | |
|             else if (strEq(key, MANIFEST_KEY_OPTION_CHECKSUM_PAGE_STR))
 | |
|                 manifest->data.backupOptionChecksumPage = varNewBool(jsonToBool(value));
 | |
|             else if (strEq(key, MANIFEST_KEY_OPTION_COMPRESS_LEVEL_STR))
 | |
|                 manifest->data.backupOptionCompressLevel = varNewUInt(jsonToUInt(value));
 | |
|             else if (strEq(key, MANIFEST_KEY_OPTION_COMPRESS_LEVEL_NETWORK_STR))
 | |
|                 manifest->data.backupOptionCompressLevelNetwork = varNewUInt(jsonToUInt(value));
 | |
|             else if (strEq(key, MANIFEST_KEY_OPTION_DELTA_STR))
 | |
|                 manifest->data.backupOptionDelta = varNewBool(jsonToBool(value));
 | |
|             else if (strEq(key, MANIFEST_KEY_OPTION_PROCESS_MAX_STR))
 | |
|                 manifest->data.backupOptionProcessMax = varNewUInt(jsonToUInt(value));
 | |
|         }
 | |
|         MEM_CONTEXT_END();
 | |
|     }
 | |
| 
 | |
|     FUNCTION_TEST_RETURN_VOID();
 | |
| }
 | |
| 
 | |
| Manifest *
 | |
| manifestNewLoad(IoRead *read)
 | |
| {
 | |
|     FUNCTION_LOG_BEGIN(logLevelDebug);
 | |
|         FUNCTION_LOG_PARAM(IO_READ, read);
 | |
|     FUNCTION_LOG_END();
 | |
| 
 | |
|     ASSERT(read != NULL);
 | |
| 
 | |
|     Manifest *this = NULL;
 | |
| 
 | |
|     MEM_CONTEXT_NEW_BEGIN("Manifest")
 | |
|     {
 | |
|         this = manifestNewInternal();
 | |
| 
 | |
|         // Load the manifest
 | |
|         ManifestLoadData loadData =
 | |
|         {
 | |
|             .memContext = memContextNew("load"),
 | |
|             .manifest = this,
 | |
|         };
 | |
| 
 | |
|         MEM_CONTEXT_BEGIN(loadData.memContext)
 | |
|         {
 | |
|             loadData.fileFoundList = lstNew(sizeof(ManifestLoadFound));
 | |
|             loadData.linkFoundList = lstNew(sizeof(ManifestLoadFound));
 | |
|             loadData.pathFoundList = lstNew(sizeof(ManifestLoadFound));
 | |
|         }
 | |
|         MEM_CONTEXT_END();
 | |
| 
 | |
|         this->info = infoNewLoad(read, manifestLoadCallback, &loadData);
 | |
|         this->data.backrestVersion = infoBackrestVersion(this->info);
 | |
| 
 | |
|         // Process file defaults
 | |
|         for (unsigned int fileIdx = 0; fileIdx < manifestFileTotal(this); fileIdx++)
 | |
|         {
 | |
|             ManifestFile *file = lstGet(this->fileList, fileIdx);
 | |
|             ManifestLoadFound *found = lstGet(loadData.fileFoundList, fileIdx);
 | |
| 
 | |
|             if (!found->group)
 | |
|                 file->group = manifestOwnerCache(this, manifestOwnerGet(loadData.fileGroupDefault));
 | |
| 
 | |
|             if (!found->mode)
 | |
|                 file->mode = loadData.fileModeDefault;
 | |
| 
 | |
|             if (!found->primary)
 | |
|                 file->primary = loadData.filePrimaryDefault;
 | |
| 
 | |
|             if (!found->user)
 | |
|                 file->user = manifestOwnerCache(this, manifestOwnerGet(loadData.fileUserDefault));
 | |
|         }
 | |
| 
 | |
|         // Process link defaults
 | |
|         for (unsigned int linkIdx = 0; linkIdx < manifestLinkTotal(this); linkIdx++)
 | |
|         {
 | |
|             ManifestLink *link = lstGet(this->linkList, linkIdx);
 | |
|             ManifestLoadFound *found = lstGet(loadData.linkFoundList, linkIdx);
 | |
| 
 | |
|             if (!found->group)
 | |
|                 link->group = manifestOwnerCache(this, manifestOwnerGet(loadData.linkGroupDefault));
 | |
| 
 | |
|             if (!found->user)
 | |
|                 link->user = manifestOwnerCache(this, manifestOwnerGet(loadData.linkUserDefault));
 | |
|         }
 | |
| 
 | |
|         // Process path defaults
 | |
|         for (unsigned int pathIdx = 0; pathIdx < manifestPathTotal(this); pathIdx++)
 | |
|         {
 | |
|             ManifestPath *path = lstGet(this->pathList, pathIdx);
 | |
|             ManifestLoadFound *found = lstGet(loadData.pathFoundList, pathIdx);
 | |
| 
 | |
|             if (!found->group)
 | |
|                 path->group = manifestOwnerCache(this, manifestOwnerGet(loadData.pathGroupDefault));
 | |
| 
 | |
|             if (!found->mode)
 | |
|                 path->mode = loadData.pathModeDefault;
 | |
| 
 | |
|             if (!found->user)
 | |
|                 path->user = manifestOwnerCache(this, manifestOwnerGet(loadData.pathUserDefault));
 | |
|         }
 | |
| 
 | |
|         // Sort the lists.  They should already be sorted in the file but it is possible that this system has a different collation
 | |
|         // that renders that sort useless.
 | |
|         //
 | |
|         // This must happen *after* the default processing because found lists are in natural file order and it is not worth writing
 | |
|         // comparator routines for them.
 | |
|         lstSort(this->dbList, sortOrderAsc);
 | |
|         lstSort(this->fileList, sortOrderAsc);
 | |
|         lstSort(this->linkList, sortOrderAsc);
 | |
|         lstSort(this->pathList, sortOrderAsc);
 | |
|         lstSort(this->targetList, sortOrderAsc);
 | |
| 
 | |
|         // Make sure the base path exists
 | |
|         manifestTargetBase(this);
 | |
| 
 | |
|         // Discard the context holding temporary load data
 | |
|         memContextDiscard();
 | |
|     }
 | |
|     MEM_CONTEXT_NEW_END();
 | |
| 
 | |
|     FUNCTION_LOG_RETURN(MANIFEST, this);
 | |
| }
 | |
| 
 | |
| /***********************************************************************************************************************************
 | |
| Save manifest
 | |
| ***********************************************************************************************************************************/
 | |
| typedef struct ManifestSaveData
 | |
| {
 | |
|     Manifest *manifest;                                             // Manifest object to be saved
 | |
| 
 | |
|     const Variant *fileGroupDefault;                                // File default group
 | |
|     mode_t fileModeDefault;                                         // File default mode
 | |
|     bool filePrimaryDefault;                                        // File default primary
 | |
|     const Variant *fileUserDefault;                                 // File default user
 | |
| 
 | |
|     const Variant *linkGroupDefault;                                // Link default group
 | |
|     const Variant *linkUserDefault;                                 // Link default user
 | |
| 
 | |
|     const Variant *pathGroupDefault;                                // Path default group
 | |
|     mode_t pathModeDefault;                                         // Path default mode
 | |
|     const Variant *pathUserDefault;                                 // Path default user
 | |
| } ManifestSaveData;
 | |
| 
 | |
| // Helper to convert the owner MCV to a default.  If the input is NULL boolean false should be returned, else the owner string.
 | |
| static const Variant *
 | |
| manifestOwnerVar(const String *ownerDefault)
 | |
| {
 | |
|     FUNCTION_TEST_BEGIN();
 | |
|         FUNCTION_TEST_PARAM(STRING, ownerDefault);
 | |
|     FUNCTION_TEST_END();
 | |
| 
 | |
|     FUNCTION_TEST_RETURN(ownerDefault == NULL ? BOOL_FALSE_VAR : varNewStr(ownerDefault));
 | |
| }
 | |
| 
 | |
| static void
 | |
| manifestSaveCallback(void *callbackData, const String *sectionNext, InfoSave *infoSaveData)
 | |
| {
 | |
|     FUNCTION_TEST_BEGIN();
 | |
|         FUNCTION_TEST_PARAM_P(VOID, callbackData);
 | |
|         FUNCTION_TEST_PARAM(STRING, sectionNext);
 | |
|         FUNCTION_TEST_PARAM(INFO_SAVE, infoSaveData);
 | |
|     FUNCTION_TEST_END();
 | |
| 
 | |
|     ASSERT(callbackData != NULL);
 | |
|     ASSERT(infoSaveData != NULL);
 | |
| 
 | |
|     ManifestSaveData *saveData = (ManifestSaveData *)callbackData;
 | |
|     Manifest *manifest = saveData->manifest;
 | |
| 
 | |
|     // -----------------------------------------------------------------------------------------------------------------------------
 | |
|     if (infoSaveSection(infoSaveData, MANIFEST_SECTION_BACKUP_STR, sectionNext))
 | |
|     {
 | |
|         if (manifest->data.archiveStart != NULL)
 | |
|         {
 | |
|             infoSaveValue(
 | |
|                 infoSaveData, MANIFEST_SECTION_BACKUP_STR, MANIFEST_KEY_BACKUP_ARCHIVE_START_STR,
 | |
|                 jsonFromStr(manifest->data.archiveStart));
 | |
|         }
 | |
| 
 | |
|         if (manifest->data.archiveStop != NULL)
 | |
|         {
 | |
|             infoSaveValue(
 | |
|                 infoSaveData, MANIFEST_SECTION_BACKUP_STR, MANIFEST_KEY_BACKUP_ARCHIVE_STOP_STR,
 | |
|                 jsonFromStr(manifest->data.archiveStop));
 | |
|         }
 | |
| 
 | |
|         infoSaveValue(
 | |
|             infoSaveData, MANIFEST_SECTION_BACKUP_STR, MANIFEST_KEY_BACKUP_LABEL_STR,
 | |
|             jsonFromStr(manifest->data.backupLabel));
 | |
| 
 | |
|         if (manifest->data.lsnStart != NULL)
 | |
|         {
 | |
|             infoSaveValue(
 | |
|                 infoSaveData, MANIFEST_SECTION_BACKUP_STR, MANIFEST_KEY_BACKUP_LSN_START_STR,
 | |
|                 jsonFromStr(manifest->data.lsnStart));
 | |
|         }
 | |
| 
 | |
|         if (manifest->data.lsnStop != NULL)
 | |
|         {
 | |
|             infoSaveValue(
 | |
|                 infoSaveData, MANIFEST_SECTION_BACKUP_STR, MANIFEST_KEY_BACKUP_LSN_STOP_STR,
 | |
|                 jsonFromStr(manifest->data.lsnStop));
 | |
|         }
 | |
| 
 | |
|         if (manifest->data.backupLabelPrior != NULL)
 | |
|         {
 | |
|             infoSaveValue(
 | |
|                 infoSaveData, MANIFEST_SECTION_BACKUP_STR, MANIFEST_KEY_BACKUP_PRIOR_STR,
 | |
|                 jsonFromStr(manifest->data.backupLabelPrior));
 | |
|         }
 | |
| 
 | |
|         infoSaveValue(
 | |
|             infoSaveData, MANIFEST_SECTION_BACKUP_STR, MANIFEST_KEY_BACKUP_TIMESTAMP_COPY_START_STR,
 | |
|             jsonFromInt64(manifest->data.backupTimestampCopyStart));
 | |
|         infoSaveValue(
 | |
|             infoSaveData, MANIFEST_SECTION_BACKUP_STR, MANIFEST_KEY_BACKUP_TIMESTAMP_START_STR,
 | |
|             jsonFromInt64(manifest->data.backupTimestampStart));
 | |
|         infoSaveValue(
 | |
|             infoSaveData, MANIFEST_SECTION_BACKUP_STR, MANIFEST_KEY_BACKUP_TIMESTAMP_STOP_STR,
 | |
|             jsonFromInt64(manifest->data.backupTimestampStop));
 | |
|         infoSaveValue(
 | |
|             infoSaveData, MANIFEST_SECTION_BACKUP_STR, MANIFEST_KEY_BACKUP_TYPE_STR,
 | |
|             jsonFromStr(backupTypeStr(manifest->data.backupType)));
 | |
|     }
 | |
| 
 | |
|     // -----------------------------------------------------------------------------------------------------------------------------
 | |
|     if (infoSaveSection(infoSaveData, MANIFEST_SECTION_BACKUP_DB_STR, sectionNext))
 | |
|     {
 | |
|         infoSaveValue(
 | |
|             infoSaveData, MANIFEST_SECTION_BACKUP_DB_STR, STRDEF("db-catalog-version"),
 | |
|             jsonFromUInt(pgCatalogVersion(manifest->data.pgVersion)));
 | |
|         infoSaveValue(
 | |
|             infoSaveData, MANIFEST_SECTION_BACKUP_DB_STR, STRDEF("db-control-version"),
 | |
|             jsonFromUInt(pgControlVersion(manifest->data.pgVersion)));
 | |
|         infoSaveValue(
 | |
|             infoSaveData, MANIFEST_SECTION_BACKUP_DB_STR, MANIFEST_KEY_DB_ID_STR, jsonFromUInt(manifest->data.pgId));
 | |
|         infoSaveValue(
 | |
|             infoSaveData, MANIFEST_SECTION_BACKUP_DB_STR, MANIFEST_KEY_DB_SYSTEM_ID_STR,
 | |
|             jsonFromUInt64(manifest->data.pgSystemId));
 | |
|         infoSaveValue(
 | |
|             infoSaveData, MANIFEST_SECTION_BACKUP_DB_STR, MANIFEST_KEY_DB_VERSION_STR,
 | |
|             jsonFromStr(pgVersionToStr(manifest->data.pgVersion)));
 | |
|     }
 | |
| 
 | |
|     // -----------------------------------------------------------------------------------------------------------------------------
 | |
|     if (infoSaveSection(infoSaveData, MANIFEST_SECTION_BACKUP_OPTION_STR, sectionNext))
 | |
|     {
 | |
|         infoSaveValue(
 | |
|             infoSaveData, MANIFEST_SECTION_BACKUP_OPTION_STR, MANIFEST_KEY_OPTION_ARCHIVE_CHECK_STR,
 | |
|             jsonFromBool(manifest->data.backupOptionArchiveCheck));
 | |
|         infoSaveValue(
 | |
|             infoSaveData, MANIFEST_SECTION_BACKUP_OPTION_STR, MANIFEST_KEY_OPTION_ARCHIVE_COPY_STR,
 | |
|             jsonFromBool(manifest->data.backupOptionArchiveCopy));
 | |
| 
 | |
|         if (manifest->data.backupOptionStandby != NULL)
 | |
|         {
 | |
|             infoSaveValue(
 | |
|                 infoSaveData, MANIFEST_SECTION_BACKUP_OPTION_STR, MANIFEST_KEY_OPTION_BACKUP_STANDBY_STR,
 | |
|                 jsonFromVar(manifest->data.backupOptionStandby));
 | |
|         }
 | |
| 
 | |
|         if (manifest->data.backupOptionBufferSize != NULL)
 | |
|         {
 | |
|             infoSaveValue(
 | |
|                 infoSaveData, MANIFEST_SECTION_BACKUP_OPTION_STR, MANIFEST_KEY_OPTION_BUFFER_SIZE_STR,
 | |
|                 jsonFromVar(manifest->data.backupOptionBufferSize));
 | |
|         }
 | |
| 
 | |
|         if (manifest->data.backupOptionChecksumPage != NULL)
 | |
|         {
 | |
|             infoSaveValue(
 | |
|                 infoSaveData, MANIFEST_SECTION_BACKUP_OPTION_STR, MANIFEST_KEY_OPTION_CHECKSUM_PAGE_STR,
 | |
|                 jsonFromVar(manifest->data.backupOptionChecksumPage));
 | |
|         }
 | |
| 
 | |
|         // Set the option when compression is turned on.  In older versions this also implied gz compression but in newer versions
 | |
|         // the type option must also be set if compression is not gz.
 | |
|         infoSaveValue(
 | |
|             infoSaveData, MANIFEST_SECTION_BACKUP_OPTION_STR, MANIFEST_KEY_OPTION_COMPRESS_STR,
 | |
|             jsonFromBool(manifest->data.backupOptionCompressType != compressTypeNone));
 | |
| 
 | |
|         if (manifest->data.backupOptionCompressLevel != NULL)
 | |
|         {
 | |
|             infoSaveValue(
 | |
|                 infoSaveData, MANIFEST_SECTION_BACKUP_OPTION_STR, MANIFEST_KEY_OPTION_COMPRESS_LEVEL_STR,
 | |
|                 jsonFromVar(manifest->data.backupOptionCompressLevel));
 | |
|         }
 | |
| 
 | |
|         if (manifest->data.backupOptionCompressLevelNetwork != NULL)
 | |
|         {
 | |
|             infoSaveValue(
 | |
|                 infoSaveData, MANIFEST_SECTION_BACKUP_OPTION_STR, MANIFEST_KEY_OPTION_COMPRESS_LEVEL_NETWORK_STR,
 | |
|                 jsonFromVar(manifest->data.backupOptionCompressLevelNetwork));
 | |
|         }
 | |
| 
 | |
|         // Set the compression type.  Older versions will ignore this and assume gz compression if the compress option is set.
 | |
|         infoSaveValue(
 | |
|             infoSaveData, MANIFEST_SECTION_BACKUP_OPTION_STR, MANIFEST_KEY_OPTION_COMPRESS_TYPE_STR,
 | |
|             jsonFromStr(compressTypeStr(manifest->data.backupOptionCompressType)));
 | |
| 
 | |
|         if (manifest->data.backupOptionDelta != NULL)
 | |
|         {
 | |
|             infoSaveValue(
 | |
|                 infoSaveData, MANIFEST_SECTION_BACKUP_OPTION_STR, MANIFEST_KEY_OPTION_DELTA_STR,
 | |
|                 jsonFromVar(manifest->data.backupOptionDelta));
 | |
|         }
 | |
| 
 | |
|         infoSaveValue(
 | |
|             infoSaveData, MANIFEST_SECTION_BACKUP_OPTION_STR, MANIFEST_KEY_OPTION_HARDLINK_STR,
 | |
|             jsonFromBool(manifest->data.backupOptionHardLink));
 | |
|         infoSaveValue(
 | |
|             infoSaveData, MANIFEST_SECTION_BACKUP_OPTION_STR, MANIFEST_KEY_OPTION_ONLINE_STR,
 | |
|             jsonFromBool(manifest->data.backupOptionOnline));
 | |
| 
 | |
|         if (manifest->data.backupOptionProcessMax != NULL)
 | |
|         {
 | |
|             infoSaveValue(
 | |
|                 infoSaveData, MANIFEST_SECTION_BACKUP_OPTION_STR, MANIFEST_KEY_OPTION_PROCESS_MAX_STR,
 | |
|                 jsonFromVar(manifest->data.backupOptionProcessMax));
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // -----------------------------------------------------------------------------------------------------------------------------
 | |
|     if (infoSaveSection(infoSaveData, MANIFEST_SECTION_BACKUP_TARGET_STR, sectionNext))
 | |
|     {
 | |
|         MEM_CONTEXT_TEMP_RESET_BEGIN()
 | |
|         {
 | |
|             for (unsigned int targetIdx = 0; targetIdx < manifestTargetTotal(manifest); targetIdx++)
 | |
|             {
 | |
|                 const ManifestTarget *target = manifestTarget(manifest, targetIdx);
 | |
|                 KeyValue *targetKv = kvNew();
 | |
| 
 | |
|                 if (target->file != NULL)
 | |
|                     kvPut(targetKv, MANIFEST_KEY_FILE_VAR, VARSTR(target->file));
 | |
| 
 | |
|                 kvPut(targetKv, MANIFEST_KEY_PATH_VAR, VARSTR(target->path));
 | |
| 
 | |
|                 if (target->tablespaceId != 0)
 | |
|                     kvPut(targetKv, MANIFEST_KEY_TABLESPACE_ID_VAR, VARSTR(strNewFmt("%u", target->tablespaceId)));
 | |
| 
 | |
|                 if (target->tablespaceName != NULL)
 | |
|                     kvPut(targetKv, MANIFEST_KEY_TABLESPACE_NAME_VAR, VARSTR(target->tablespaceName));
 | |
| 
 | |
|                 kvPut(
 | |
|                     targetKv, MANIFEST_KEY_TYPE_VAR,
 | |
|                     VARSTR(
 | |
|                         target->type == manifestTargetTypePath ?
 | |
|                             MANIFEST_TARGET_TYPE_PATH_STR : MANIFEST_TARGET_TYPE_LINK_STR));
 | |
| 
 | |
|                 infoSaveValue(infoSaveData, MANIFEST_SECTION_BACKUP_TARGET_STR, target->name, jsonFromKv(targetKv));
 | |
| 
 | |
|                 MEM_CONTEXT_TEMP_RESET(1000);
 | |
|             }
 | |
|         }
 | |
|         MEM_CONTEXT_TEMP_END();
 | |
|     }
 | |
| 
 | |
|     // -----------------------------------------------------------------------------------------------------------------------------
 | |
|     if (infoSaveSection(infoSaveData, MANIFEST_SECTION_DB_STR, sectionNext))
 | |
|     {
 | |
|         MEM_CONTEXT_TEMP_RESET_BEGIN()
 | |
|         {
 | |
|             for (unsigned int dbIdx = 0; dbIdx < manifestDbTotal(manifest); dbIdx++)
 | |
|             {
 | |
|                 const ManifestDb *db = manifestDb(manifest, dbIdx);
 | |
|                 KeyValue *dbKv = kvNew();
 | |
| 
 | |
|                 kvPut(dbKv, MANIFEST_KEY_DB_ID_VAR, VARUINT(db->id));
 | |
|                 kvPut(dbKv, MANIFEST_KEY_DB_LAST_SYSTEM_ID_VAR, VARUINT(db->lastSystemId));
 | |
| 
 | |
|                 infoSaveValue(infoSaveData, MANIFEST_SECTION_DB_STR, db->name, jsonFromKv(dbKv));
 | |
| 
 | |
|                 MEM_CONTEXT_TEMP_RESET(1000);
 | |
|             }
 | |
|         }
 | |
|         MEM_CONTEXT_TEMP_END();
 | |
|     }
 | |
| 
 | |
|     // -----------------------------------------------------------------------------------------------------------------------------
 | |
|     if (infoSaveSection(infoSaveData, MANIFEST_SECTION_TARGET_FILE_STR, sectionNext))
 | |
|     {
 | |
|         MEM_CONTEXT_TEMP_RESET_BEGIN()
 | |
|         {
 | |
|             for (unsigned int fileIdx = 0; fileIdx < manifestFileTotal(manifest); fileIdx++)
 | |
|             {
 | |
|                 const ManifestFile *file = manifestFile(manifest, fileIdx);
 | |
|                 KeyValue *fileKv = kvNew();
 | |
| 
 | |
|                 // Save if the file size is not zero and the checksum exists.  The checksum might not exist if this is a partial
 | |
|                 // save performed during a backup.
 | |
|                 if (file->size != 0 && file->checksumSha1[0] != 0)
 | |
|                     kvPut(fileKv, MANIFEST_KEY_CHECKSUM_VAR, VARSTRZ(file->checksumSha1));
 | |
| 
 | |
|                 if (file->checksumPage)
 | |
|                 {
 | |
|                     kvPut(fileKv, MANIFEST_KEY_CHECKSUM_PAGE_VAR, VARBOOL(!file->checksumPageError));
 | |
| 
 | |
|                     if (file->checksumPageErrorList != NULL)
 | |
|                         kvPut(fileKv, MANIFEST_KEY_CHECKSUM_PAGE_ERROR_VAR, varNewVarLst(file->checksumPageErrorList));
 | |
|                 }
 | |
| 
 | |
|                 if (!varEq(manifestOwnerVar(file->group), saveData->fileGroupDefault))
 | |
|                     kvPut(fileKv, MANIFEST_KEY_GROUP_VAR, manifestOwnerVar(file->group));
 | |
| 
 | |
|                 if (file->primary != saveData->filePrimaryDefault)
 | |
|                     kvPut(fileKv, MANIFEST_KEY_PRIMARY_VAR, VARBOOL(file->primary));
 | |
| 
 | |
|                 if (file->mode != saveData->fileModeDefault)
 | |
|                     kvPut(fileKv, MANIFEST_KEY_MODE_VAR, VARSTR(strNewFmt("%04o", file->mode)));
 | |
| 
 | |
|                 if (file->reference != NULL)
 | |
|                     kvPut(fileKv, MANIFEST_KEY_REFERENCE_VAR, VARSTR(file->reference));
 | |
| 
 | |
|                 if (file->sizeRepo != file->size)
 | |
|                     kvPut(fileKv, MANIFEST_KEY_SIZE_REPO_VAR, varNewUInt64(file->sizeRepo));
 | |
| 
 | |
|                 kvPut(fileKv, MANIFEST_KEY_SIZE_VAR, varNewUInt64(file->size));
 | |
| 
 | |
|                 kvPut(fileKv, MANIFEST_KEY_TIMESTAMP_VAR, varNewUInt64((uint64_t)file->timestamp));
 | |
| 
 | |
|                 if (!varEq(manifestOwnerVar(file->user), saveData->fileUserDefault))
 | |
|                     kvPut(fileKv, MANIFEST_KEY_USER_VAR, manifestOwnerVar(file->user));
 | |
| 
 | |
|                 infoSaveValue(infoSaveData, MANIFEST_SECTION_TARGET_FILE_STR, file->name, jsonFromKv(fileKv));
 | |
| 
 | |
|                 MEM_CONTEXT_TEMP_RESET(1000);
 | |
|             }
 | |
|         }
 | |
|         MEM_CONTEXT_TEMP_END();
 | |
|     }
 | |
| 
 | |
|     // -----------------------------------------------------------------------------------------------------------------------------
 | |
|     if (infoSaveSection(infoSaveData, MANIFEST_SECTION_TARGET_FILE_DEFAULT_STR, sectionNext))
 | |
|     {
 | |
|         infoSaveValue(
 | |
|             infoSaveData, MANIFEST_SECTION_TARGET_FILE_DEFAULT_STR, MANIFEST_KEY_GROUP_STR,
 | |
|             jsonFromVar(saveData->fileGroupDefault));
 | |
|         infoSaveValue(
 | |
|             infoSaveData, MANIFEST_SECTION_TARGET_FILE_DEFAULT_STR, MANIFEST_KEY_PRIMARY_STR,
 | |
|             jsonFromBool(saveData->filePrimaryDefault));
 | |
|         infoSaveValue(
 | |
|             infoSaveData, MANIFEST_SECTION_TARGET_FILE_DEFAULT_STR, MANIFEST_KEY_MODE_STR,
 | |
|             jsonFromStr(strNewFmt("%04o", saveData->fileModeDefault)));
 | |
|         infoSaveValue(
 | |
|             infoSaveData, MANIFEST_SECTION_TARGET_FILE_DEFAULT_STR, MANIFEST_KEY_USER_STR,
 | |
|             jsonFromVar(saveData->fileUserDefault));
 | |
|     }
 | |
| 
 | |
|     // -----------------------------------------------------------------------------------------------------------------------------
 | |
|     if (infoSaveSection(infoSaveData, MANIFEST_SECTION_TARGET_LINK_STR, sectionNext))
 | |
|     {
 | |
|         MEM_CONTEXT_TEMP_RESET_BEGIN()
 | |
|         {
 | |
|             for (unsigned int linkIdx = 0; linkIdx < manifestLinkTotal(manifest); linkIdx++)
 | |
|             {
 | |
|                 const ManifestLink *link = manifestLink(manifest, linkIdx);
 | |
|                 KeyValue *linkKv = kvNew();
 | |
| 
 | |
|                 if (!varEq(manifestOwnerVar(link->user), saveData->linkUserDefault))
 | |
|                     kvPut(linkKv, MANIFEST_KEY_USER_VAR, manifestOwnerVar(link->user));
 | |
| 
 | |
|                 if (!varEq(manifestOwnerVar(link->group), saveData->linkGroupDefault))
 | |
|                     kvPut(linkKv, MANIFEST_KEY_GROUP_VAR, manifestOwnerVar(link->group));
 | |
| 
 | |
|                 kvPut(linkKv, MANIFEST_KEY_DESTINATION_VAR, VARSTR(link->destination));
 | |
| 
 | |
|                 infoSaveValue(infoSaveData, MANIFEST_SECTION_TARGET_LINK_STR, link->name, jsonFromKv(linkKv));
 | |
| 
 | |
|                 MEM_CONTEXT_TEMP_RESET(1000);
 | |
|             }
 | |
|         }
 | |
|         MEM_CONTEXT_TEMP_END();
 | |
|     }
 | |
| 
 | |
|     // -----------------------------------------------------------------------------------------------------------------------------
 | |
|     if (infoSaveSection(infoSaveData, MANIFEST_SECTION_TARGET_LINK_DEFAULT_STR, sectionNext))
 | |
|     {
 | |
|         if (manifestLinkTotal(manifest) > 0)
 | |
|         {
 | |
|             infoSaveValue(
 | |
|                 infoSaveData, MANIFEST_SECTION_TARGET_LINK_DEFAULT_STR, MANIFEST_KEY_GROUP_STR,
 | |
|                 jsonFromVar(saveData->linkGroupDefault));
 | |
|             infoSaveValue(
 | |
|                 infoSaveData, MANIFEST_SECTION_TARGET_LINK_DEFAULT_STR, MANIFEST_KEY_USER_STR,
 | |
|                 jsonFromVar(saveData->linkUserDefault));
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     // -----------------------------------------------------------------------------------------------------------------------------
 | |
|     if (infoSaveSection(infoSaveData, MANIFEST_SECTION_TARGET_PATH_STR, sectionNext))
 | |
|     {
 | |
|         MEM_CONTEXT_TEMP_RESET_BEGIN()
 | |
|         {
 | |
|             for (unsigned int pathIdx = 0; pathIdx < manifestPathTotal(manifest); pathIdx++)
 | |
|             {
 | |
|                 const ManifestPath *path = manifestPath(manifest, pathIdx);
 | |
|                 KeyValue *pathKv = kvNew();
 | |
| 
 | |
|                 if (!varEq(manifestOwnerVar(path->group), saveData->pathGroupDefault))
 | |
|                     kvPut(pathKv, MANIFEST_KEY_GROUP_VAR, manifestOwnerVar(path->group));
 | |
| 
 | |
|                 if (path->mode != saveData->pathModeDefault)
 | |
|                     kvPut(pathKv, MANIFEST_KEY_MODE_VAR, VARSTR(strNewFmt("%04o", path->mode)));
 | |
| 
 | |
|                 if (!varEq(manifestOwnerVar(path->user), saveData->pathUserDefault))
 | |
|                     kvPut(pathKv, MANIFEST_KEY_USER_VAR, manifestOwnerVar(path->user));
 | |
| 
 | |
|                 infoSaveValue(infoSaveData, MANIFEST_SECTION_TARGET_PATH_STR, path->name, jsonFromKv(pathKv));
 | |
| 
 | |
|                 MEM_CONTEXT_TEMP_RESET(1000);
 | |
|             }
 | |
|         }
 | |
|         MEM_CONTEXT_TEMP_END();
 | |
|     }
 | |
| 
 | |
|     // -----------------------------------------------------------------------------------------------------------------------------
 | |
|     if (infoSaveSection(infoSaveData, MANIFEST_SECTION_TARGET_PATH_DEFAULT_STR, sectionNext))
 | |
|     {
 | |
|         infoSaveValue(
 | |
|             infoSaveData, MANIFEST_SECTION_TARGET_PATH_DEFAULT_STR, MANIFEST_KEY_GROUP_STR,
 | |
|             jsonFromVar(saveData->pathGroupDefault));
 | |
|         infoSaveValue(
 | |
|             infoSaveData, MANIFEST_SECTION_TARGET_PATH_DEFAULT_STR, MANIFEST_KEY_MODE_STR,
 | |
|             jsonFromStr(strNewFmt("%04o", saveData->pathModeDefault)));
 | |
|         infoSaveValue(
 | |
|             infoSaveData, MANIFEST_SECTION_TARGET_PATH_DEFAULT_STR, MANIFEST_KEY_USER_STR,
 | |
|             jsonFromVar(saveData->pathUserDefault));
 | |
|     }
 | |
| 
 | |
|     FUNCTION_TEST_RETURN_VOID();
 | |
| }
 | |
| 
 | |
| void
 | |
| manifestSave(Manifest *this, IoWrite *write)
 | |
| {
 | |
|     FUNCTION_LOG_BEGIN(logLevelDebug);
 | |
|         FUNCTION_LOG_PARAM(MANIFEST, this);
 | |
|         FUNCTION_LOG_PARAM(IO_WRITE, write);
 | |
|     FUNCTION_LOG_END();
 | |
| 
 | |
|     ASSERT(this != NULL);
 | |
|     ASSERT(write != NULL);
 | |
| 
 | |
|     MEM_CONTEXT_TEMP_BEGIN()
 | |
|     {
 | |
|         // Files can be added from outside the manifest so make sure they are sorted
 | |
|         lstSort(this->fileList, sortOrderAsc);
 | |
| 
 | |
|         ManifestSaveData saveData =
 | |
|         {
 | |
|             .manifest = this,
 | |
|         };
 | |
| 
 | |
|         // Get default file values
 | |
|         MostCommonValue *fileGroupMcv = mcvNew();
 | |
|         MostCommonValue *fileModeMcv = mcvNew();
 | |
|         MostCommonValue *filePrimaryMcv = mcvNew();
 | |
|         MostCommonValue *fileUserMcv = mcvNew();
 | |
| 
 | |
|         ASSERT(manifestFileTotal(this) > 0);
 | |
| 
 | |
|         for (unsigned int fileIdx = 0; fileIdx < manifestFileTotal(this); fileIdx++)
 | |
|         {
 | |
|             const ManifestFile *file = manifestFile(this, fileIdx);
 | |
| 
 | |
|             mcvUpdate(fileGroupMcv, VARSTR(file->group));
 | |
|             mcvUpdate(fileModeMcv, VARUINT(file->mode));
 | |
|             mcvUpdate(filePrimaryMcv, VARBOOL(file->primary));
 | |
|             mcvUpdate(fileUserMcv, VARSTR(file->user));
 | |
|         }
 | |
| 
 | |
|         saveData.fileGroupDefault = manifestOwnerVar(varStr(mcvResult(fileGroupMcv)));
 | |
|         saveData.fileModeDefault = varUInt(mcvResult(fileModeMcv));
 | |
|         saveData.filePrimaryDefault = varBool(mcvResult(filePrimaryMcv));
 | |
|         saveData.fileUserDefault = manifestOwnerVar(varStr(mcvResult(fileUserMcv)));
 | |
| 
 | |
|         // Get default link values
 | |
|         if (manifestLinkTotal(this) > 0)
 | |
|         {
 | |
|             MostCommonValue *linkGroupMcv = mcvNew();
 | |
|             MostCommonValue *linkUserMcv = mcvNew();
 | |
| 
 | |
|             for (unsigned int linkIdx = 0; linkIdx < manifestLinkTotal(this); linkIdx++)
 | |
|             {
 | |
|                 const ManifestLink *link = manifestLink(this, linkIdx);
 | |
| 
 | |
|                 mcvUpdate(linkGroupMcv, VARSTR(link->group));
 | |
|                 mcvUpdate(linkUserMcv, VARSTR(link->user));
 | |
|             }
 | |
| 
 | |
|             saveData.linkGroupDefault = manifestOwnerVar(varStr(mcvResult(linkGroupMcv)));
 | |
|             saveData.linkUserDefault = manifestOwnerVar(varStr(mcvResult(linkUserMcv)));
 | |
|         }
 | |
| 
 | |
|         // Get default path values
 | |
|         MostCommonValue *pathGroupMcv = mcvNew();
 | |
|         MostCommonValue *pathModeMcv = mcvNew();
 | |
|         MostCommonValue *pathUserMcv = mcvNew();
 | |
| 
 | |
|         ASSERT(manifestPathTotal(this) > 0);
 | |
| 
 | |
|         for (unsigned int pathIdx = 0; pathIdx < manifestPathTotal(this); pathIdx++)
 | |
|         {
 | |
|             const ManifestPath *path = manifestPath(this, pathIdx);
 | |
| 
 | |
|             mcvUpdate(pathGroupMcv, VARSTR(path->group));
 | |
|             mcvUpdate(pathModeMcv, VARUINT(path->mode));
 | |
|             mcvUpdate(pathUserMcv, VARSTR(path->user));
 | |
|         }
 | |
| 
 | |
|         saveData.pathGroupDefault = manifestOwnerVar(varStr(mcvResult(pathGroupMcv)));
 | |
|         saveData.pathModeDefault = varUInt(mcvResult(pathModeMcv));
 | |
|         saveData.pathUserDefault = manifestOwnerVar(varStr(mcvResult(pathUserMcv)));
 | |
| 
 | |
|         // Save manifest
 | |
|         infoSave(this->info, write, manifestSaveCallback, &saveData);
 | |
|     }
 | |
|     MEM_CONTEXT_TEMP_END();
 | |
| 
 | |
|     FUNCTION_LOG_RETURN_VOID();
 | |
| }
 | |
| 
 | |
| /**********************************************************************************************************************************/
 | |
| void
 | |
| manifestValidate(Manifest *this, bool strict)
 | |
| {
 | |
|     FUNCTION_LOG_BEGIN(logLevelDebug);
 | |
|         FUNCTION_LOG_PARAM(MANIFEST, this);
 | |
|         FUNCTION_LOG_PARAM(BOOL, strict);
 | |
|     FUNCTION_LOG_END();
 | |
| 
 | |
|     ASSERT(this != NULL);
 | |
| 
 | |
|     MEM_CONTEXT_TEMP_BEGIN()
 | |
|     {
 | |
|         String *error = strNew("");
 | |
| 
 | |
|         // Validate files
 | |
|         for (unsigned int fileIdx = 0; fileIdx < manifestFileTotal(this); fileIdx++)
 | |
|         {
 | |
|             const ManifestFile *file = manifestFile(this, fileIdx);
 | |
| 
 | |
|             // All files must have a checksum
 | |
|             if (file->checksumSha1[0] == '\0')
 | |
|                 strCatFmt(error, "\nmissing checksum for file '%s'", strPtr(file->name));
 | |
| 
 | |
|             // These are strict checks to be performed only after a backup and before the final manifest save
 | |
|             if (strict)
 | |
|             {
 | |
|                 // Zero-length files must have a specific checksum
 | |
|                 if (file->size == 0 && !strEqZ(HASH_TYPE_SHA1_ZERO_STR, file->checksumSha1))
 | |
|                     strCatFmt(error, "\ninvalid checksum '%s' for zero size file '%s'", file->checksumSha1, strPtr(file->name));
 | |
| 
 | |
|                 // Non-zero size files must have non-zero repo size
 | |
|                 if (file->sizeRepo == 0 && file->size != 0)
 | |
|                     strCatFmt(error, "\nrepo size must be > 0 for file '%s'", strPtr(file->name));
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         // Throw exception when there are errors
 | |
|         if (strSize(error) > 0)
 | |
|             THROW_FMT(FormatError, "manifest validation failed:%s", strPtr(error));
 | |
|     }
 | |
|     MEM_CONTEXT_TEMP_END();
 | |
| 
 | |
|     FUNCTION_LOG_RETURN_VOID();
 | |
| }
 | |
| 
 | |
| /***********************************************************************************************************************************
 | |
| Ensure that symlinks do not point to the same directory or a subdirectory of another link
 | |
| ***********************************************************************************************************************************/
 | |
| void
 | |
| manifestLinkCheck(const Manifest *this)
 | |
| {
 | |
|     FUNCTION_LOG_BEGIN(logLevelDebug);
 | |
|         FUNCTION_LOG_PARAM(MANIFEST, this);
 | |
|     FUNCTION_LOG_END();
 | |
| 
 | |
|     ASSERT(this != NULL);
 | |
| 
 | |
|     // Base path used for link checks
 | |
|     const ManifestTarget *base = manifestTargetFind(this, MANIFEST_TARGET_PGDATA_STR);
 | |
| 
 | |
|     for (unsigned int linkIdx1 = 0; linkIdx1 < manifestTargetTotal(this); linkIdx1++)
 | |
|     {
 | |
|         const ManifestTarget *link1 = manifestTarget(this, linkIdx1);
 | |
| 
 | |
|         // Only compare links
 | |
|         if (link1->type == manifestTargetTypeLink)
 | |
|         {
 | |
|             // Check that the link is not inside the base data path
 | |
|             if (strBeginsWith(
 | |
|                     strNewFmt("%s/", strPtr(manifestTargetPath(this, link1))),
 | |
|                     strNewFmt("%s/", strPtr(manifestTargetPath(this, base)))))
 | |
|             {
 | |
|                 THROW_FMT(
 | |
|                     LinkDestinationError,
 | |
|                     "link '%s' destination '%s' is in PGDATA",
 | |
|                     strPtr(manifestPathPg(link1->name)), strPtr(manifestTargetPath(this, link1)));
 | |
|             }
 | |
| 
 | |
|             // Check that no link is a subpath of another link
 | |
|             for (unsigned int linkIdx2 = 0; linkIdx2 < manifestTargetTotal(this); linkIdx2++)
 | |
|             {
 | |
|                 const ManifestTarget *link2 = manifestTarget(this, linkIdx2);
 | |
| 
 | |
|                 if (link2->type == manifestTargetTypeLink && link1 != link2)
 | |
|                 {
 | |
|                     if (!(link1->file != NULL && link2->file != NULL) &&
 | |
|                         strBeginsWith(
 | |
|                             strNewFmt("%s/", strPtr(manifestTargetPath(this, link1))),
 | |
|                             strNewFmt("%s/", strPtr(manifestTargetPath(this, link2)))))
 | |
|                     {
 | |
|                         THROW_FMT(
 | |
|                             LinkDestinationError,
 | |
|                             "link '%s' (%s) destination is a subdirectory of or the same directory as link '%s' (%s)",
 | |
|                             strPtr(manifestPathPg(link1->name)), strPtr(manifestTargetPath(this, link1)),
 | |
|                             strPtr(manifestPathPg(link2->name)), strPtr(manifestTargetPath(this, link2)));
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     FUNCTION_LOG_RETURN_VOID();
 | |
| }
 | |
| 
 | |
| /***********************************************************************************************************************************
 | |
| Db functions and getters/setters
 | |
| ***********************************************************************************************************************************/
 | |
| const ManifestDb *
 | |
| manifestDb(const Manifest *this, unsigned int dbIdx)
 | |
| {
 | |
|     FUNCTION_TEST_BEGIN();
 | |
|         FUNCTION_TEST_PARAM(MANIFEST, this);
 | |
|         FUNCTION_TEST_PARAM(UINT, dbIdx);
 | |
|     FUNCTION_TEST_END();
 | |
| 
 | |
|     ASSERT(this != NULL);
 | |
| 
 | |
|     FUNCTION_TEST_RETURN(lstGet(this->dbList, dbIdx));
 | |
| }
 | |
| 
 | |
| const ManifestDb *
 | |
| manifestDbFind(const Manifest *this, const String *name)
 | |
| {
 | |
|     FUNCTION_TEST_BEGIN();
 | |
|         FUNCTION_TEST_PARAM(MANIFEST, this);
 | |
|         FUNCTION_TEST_PARAM(STRING, name);
 | |
|     FUNCTION_TEST_END();
 | |
| 
 | |
|     ASSERT(this != NULL);
 | |
|     ASSERT(name != NULL);
 | |
| 
 | |
|     const ManifestDb *result = lstFind(this->dbList, &name);
 | |
| 
 | |
|     if (result == NULL)
 | |
|         THROW_FMT(AssertError, "unable to find '%s' in manifest db list", strPtr(name));
 | |
| 
 | |
|     FUNCTION_TEST_RETURN(result);
 | |
| }
 | |
| 
 | |
| // If the database requested is not found in the list, return the default passed rather than throw an error
 | |
| const ManifestDb *
 | |
| manifestDbFindDefault(const Manifest *this, const String *name, const ManifestDb *dbDefault)
 | |
| {
 | |
|     FUNCTION_TEST_BEGIN();
 | |
|         FUNCTION_TEST_PARAM(MANIFEST, this);
 | |
|         FUNCTION_TEST_PARAM(STRING, name);
 | |
|         FUNCTION_TEST_PARAM(MANIFEST_DB, dbDefault);
 | |
|     FUNCTION_TEST_END();
 | |
| 
 | |
|     ASSERT(this != NULL);
 | |
|     ASSERT(name != NULL);
 | |
| 
 | |
|     FUNCTION_TEST_RETURN(lstFindDefault(this->dbList, &name, (void *)dbDefault));
 | |
| }
 | |
| 
 | |
| unsigned int
 | |
| manifestDbTotal(const Manifest *this)
 | |
| {
 | |
|     FUNCTION_TEST_BEGIN();
 | |
|         FUNCTION_TEST_PARAM(MANIFEST, this);
 | |
|     FUNCTION_TEST_END();
 | |
| 
 | |
|     ASSERT(this != NULL);
 | |
| 
 | |
|     FUNCTION_TEST_RETURN(lstSize(this->dbList));
 | |
| }
 | |
| 
 | |
| /***********************************************************************************************************************************
 | |
| File functions and getters/setters
 | |
| ***********************************************************************************************************************************/
 | |
| const ManifestFile *
 | |
| manifestFile(const Manifest *this, unsigned int fileIdx)
 | |
| {
 | |
|     FUNCTION_TEST_BEGIN();
 | |
|         FUNCTION_TEST_PARAM(MANIFEST, this);
 | |
|         FUNCTION_TEST_PARAM(UINT, fileIdx);
 | |
|     FUNCTION_TEST_END();
 | |
| 
 | |
|     ASSERT(this != NULL);
 | |
| 
 | |
|     FUNCTION_TEST_RETURN(lstGet(this->fileList, fileIdx));
 | |
| }
 | |
| 
 | |
| const ManifestFile *
 | |
| manifestFileFind(const Manifest *this, const String *name)
 | |
| {
 | |
|     FUNCTION_TEST_BEGIN();
 | |
|         FUNCTION_TEST_PARAM(MANIFEST, this);
 | |
|         FUNCTION_TEST_PARAM(STRING, name);
 | |
|     FUNCTION_TEST_END();
 | |
| 
 | |
|     ASSERT(this != NULL);
 | |
|     ASSERT(name != NULL);
 | |
| 
 | |
|     const ManifestFile *result = lstFind(this->fileList, &name);
 | |
| 
 | |
|     if (result == NULL)
 | |
|         THROW_FMT(AssertError, "unable to find '%s' in manifest file list", strPtr(name));
 | |
| 
 | |
|     FUNCTION_TEST_RETURN(result);
 | |
| }
 | |
| 
 | |
| // If the file requested is not found in the list, return the default passed rather than throw an error
 | |
| const ManifestFile *
 | |
| manifestFileFindDefault(const Manifest *this, const String *name, const ManifestFile *fileDefault)
 | |
| {
 | |
|     FUNCTION_TEST_BEGIN();
 | |
|         FUNCTION_TEST_PARAM(MANIFEST, this);
 | |
|         FUNCTION_TEST_PARAM(STRING, name);
 | |
|         FUNCTION_TEST_PARAM(MANIFEST_TARGET, fileDefault);
 | |
|     FUNCTION_TEST_END();
 | |
| 
 | |
|     ASSERT(this != NULL);
 | |
|     ASSERT(name != NULL);
 | |
| 
 | |
|     FUNCTION_TEST_RETURN(lstFindDefault(this->fileList, &name, (void *)fileDefault));
 | |
| }
 | |
| 
 | |
| void
 | |
| manifestFileRemove(const Manifest *this, const String *name)
 | |
| {
 | |
|     FUNCTION_TEST_BEGIN();
 | |
|         FUNCTION_TEST_PARAM(MANIFEST, this);
 | |
|         FUNCTION_TEST_PARAM(STRING, name);
 | |
|     FUNCTION_TEST_END();
 | |
| 
 | |
|     ASSERT(this != NULL);
 | |
|     ASSERT(name != NULL);
 | |
| 
 | |
|     if (!lstRemove(this->fileList, &name))
 | |
|         THROW_FMT(AssertError, "unable to remove '%s' from manifest file list", strPtr(name));
 | |
| 
 | |
|     FUNCTION_TEST_RETURN_VOID();
 | |
| }
 | |
| 
 | |
| unsigned int
 | |
| manifestFileTotal(const Manifest *this)
 | |
| {
 | |
|     FUNCTION_TEST_BEGIN();
 | |
|         FUNCTION_TEST_PARAM(MANIFEST, this);
 | |
|     FUNCTION_TEST_END();
 | |
| 
 | |
|     ASSERT(this != NULL);
 | |
| 
 | |
|     FUNCTION_TEST_RETURN(lstSize(this->fileList));
 | |
| }
 | |
| 
 | |
| void
 | |
| manifestFileUpdate(
 | |
|     Manifest *this, const String *name, uint64_t size, uint64_t sizeRepo, const char *checksumSha1, const Variant *reference,
 | |
|     bool checksumPage, bool checksumPageError, const VariantList *checksumPageErrorList)
 | |
| {
 | |
|     FUNCTION_TEST_BEGIN();
 | |
|         FUNCTION_TEST_PARAM(MANIFEST, this);
 | |
|         FUNCTION_TEST_PARAM(STRING, name);
 | |
|         FUNCTION_TEST_PARAM(UINT64, size);
 | |
|         FUNCTION_TEST_PARAM(UINT64, sizeRepo);
 | |
|         FUNCTION_TEST_PARAM(STRINGZ, checksumSha1);
 | |
|         FUNCTION_TEST_PARAM(VARIANT, reference);
 | |
|         FUNCTION_TEST_PARAM(BOOL, checksumPage);
 | |
|         FUNCTION_TEST_PARAM(BOOL, checksumPageError);
 | |
|         FUNCTION_TEST_PARAM(VARIANT_LIST, checksumPageErrorList);
 | |
|     FUNCTION_TEST_END();
 | |
| 
 | |
|     ASSERT(this != NULL);
 | |
|     ASSERT(name != NULL);
 | |
|     ASSERT(
 | |
|         (!checksumPage && !checksumPageError && checksumPageErrorList == NULL) ||
 | |
|         (checksumPage && !checksumPageError && checksumPageErrorList == NULL) || (checksumPage && checksumPageError));
 | |
| 
 | |
|     ManifestFile *file = (ManifestFile *)manifestFileFind(this, name);
 | |
| 
 | |
|     MEM_CONTEXT_BEGIN(lstMemContext(this->fileList))
 | |
|     {
 | |
|         // Update reference if set
 | |
|         if (reference != NULL)
 | |
|         {
 | |
|             if (varStr(reference) == NULL)
 | |
|                 file->reference = NULL;
 | |
|             else
 | |
|                 file->reference = strLstAddIfMissing(this->referenceList, varStr(reference));
 | |
|         }
 | |
| 
 | |
|         // Update checksum if set
 | |
|         if (checksumSha1 != NULL)
 | |
|             memcpy(file->checksumSha1, checksumSha1, HASH_TYPE_SHA1_SIZE_HEX + 1);
 | |
| 
 | |
|         // Update repo size
 | |
|         file->size = size;
 | |
|         file->sizeRepo = sizeRepo;
 | |
| 
 | |
|         // Update checksum page info
 | |
|         file->checksumPage = checksumPage;
 | |
|         file->checksumPageError = checksumPageError;
 | |
|         file->checksumPageErrorList = varLstDup(checksumPageErrorList);
 | |
|     }
 | |
|     MEM_CONTEXT_END();
 | |
| 
 | |
|     FUNCTION_TEST_RETURN_VOID();
 | |
| }
 | |
| 
 | |
| /***********************************************************************************************************************************
 | |
| Link functions and getters/setters
 | |
| ***********************************************************************************************************************************/
 | |
| const ManifestLink *
 | |
| manifestLink(const Manifest *this, unsigned int linkIdx)
 | |
| {
 | |
|     FUNCTION_TEST_BEGIN();
 | |
|         FUNCTION_TEST_PARAM(MANIFEST, this);
 | |
|         FUNCTION_TEST_PARAM(UINT, linkIdx);
 | |
|     FUNCTION_TEST_END();
 | |
| 
 | |
|     ASSERT(this != NULL);
 | |
| 
 | |
|     FUNCTION_TEST_RETURN(lstGet(this->linkList, linkIdx));
 | |
| }
 | |
| 
 | |
| const ManifestLink *
 | |
| manifestLinkFind(const Manifest *this, const String *name)
 | |
| {
 | |
|     FUNCTION_TEST_BEGIN();
 | |
|         FUNCTION_TEST_PARAM(MANIFEST, this);
 | |
|         FUNCTION_TEST_PARAM(STRING, name);
 | |
|     FUNCTION_TEST_END();
 | |
| 
 | |
|     ASSERT(this != NULL);
 | |
|     ASSERT(name != NULL);
 | |
| 
 | |
|     const ManifestLink *result = lstFind(this->linkList, &name);
 | |
| 
 | |
|     if (result == NULL)
 | |
|         THROW_FMT(AssertError, "unable to find '%s' in manifest link list", strPtr(name));
 | |
| 
 | |
|     FUNCTION_TEST_RETURN(result);
 | |
| }
 | |
| 
 | |
| // If the link requested is not found in the list, return the default passed rather than throw an error
 | |
| const ManifestLink *
 | |
| manifestLinkFindDefault(const Manifest *this, const String *name, const ManifestLink *linkDefault)
 | |
| {
 | |
|     FUNCTION_TEST_BEGIN();
 | |
|         FUNCTION_TEST_PARAM(MANIFEST, this);
 | |
|         FUNCTION_TEST_PARAM(STRING, name);
 | |
|         FUNCTION_TEST_PARAM(MANIFEST_TARGET, linkDefault);
 | |
|     FUNCTION_TEST_END();
 | |
| 
 | |
|     ASSERT(this != NULL);
 | |
|     ASSERT(name != NULL);
 | |
| 
 | |
|     FUNCTION_TEST_RETURN(lstFindDefault(this->linkList, &name, (void *)linkDefault));
 | |
| }
 | |
| 
 | |
| void
 | |
| manifestLinkRemove(const Manifest *this, const String *name)
 | |
| {
 | |
|     FUNCTION_TEST_BEGIN();
 | |
|         FUNCTION_TEST_PARAM(MANIFEST, this);
 | |
|         FUNCTION_TEST_PARAM(STRING, name);
 | |
|     FUNCTION_TEST_END();
 | |
| 
 | |
|     ASSERT(this != NULL);
 | |
|     ASSERT(name != NULL);
 | |
| 
 | |
|     if (!lstRemove(this->linkList, &name))
 | |
|         THROW_FMT(AssertError, "unable to remove '%s' from manifest link list", strPtr(name));
 | |
| 
 | |
|     FUNCTION_TEST_RETURN_VOID();
 | |
| }
 | |
| 
 | |
| unsigned int
 | |
| manifestLinkTotal(const Manifest *this)
 | |
| {
 | |
|     FUNCTION_TEST_BEGIN();
 | |
|         FUNCTION_TEST_PARAM(MANIFEST, this);
 | |
|     FUNCTION_TEST_END();
 | |
| 
 | |
|     ASSERT(this != NULL);
 | |
| 
 | |
|     FUNCTION_TEST_RETURN(lstSize(this->linkList));
 | |
| }
 | |
| 
 | |
| void
 | |
| manifestLinkUpdate(const Manifest *this, const String *name, const String *destination)
 | |
| {
 | |
|     FUNCTION_TEST_BEGIN();
 | |
|         FUNCTION_TEST_PARAM(MANIFEST, this);
 | |
|         FUNCTION_TEST_PARAM(STRING, name);
 | |
|         FUNCTION_TEST_PARAM(STRING, destination);
 | |
|     FUNCTION_TEST_END();
 | |
| 
 | |
|     ASSERT(this != NULL);
 | |
|     ASSERT(name != NULL);
 | |
|     ASSERT(destination != NULL);
 | |
| 
 | |
|     ManifestLink *link = (ManifestLink *)manifestLinkFind(this, name);
 | |
| 
 | |
|     MEM_CONTEXT_BEGIN(lstMemContext(this->linkList))
 | |
|     {
 | |
|         if (!strEq(link->destination, destination))
 | |
|             link->destination = strDup(destination);
 | |
|     }
 | |
|     MEM_CONTEXT_END();
 | |
| 
 | |
|     FUNCTION_TEST_RETURN_VOID();
 | |
| }
 | |
| 
 | |
| /***********************************************************************************************************************************
 | |
| Path functions and getters/setters
 | |
| ***********************************************************************************************************************************/
 | |
| const ManifestPath *
 | |
| manifestPath(const Manifest *this, unsigned int pathIdx)
 | |
| {
 | |
|     FUNCTION_TEST_BEGIN();
 | |
|         FUNCTION_TEST_PARAM(MANIFEST, this);
 | |
|         FUNCTION_TEST_PARAM(UINT, pathIdx);
 | |
|     FUNCTION_TEST_END();
 | |
| 
 | |
|     ASSERT(this != NULL);
 | |
| 
 | |
|     FUNCTION_TEST_RETURN(lstGet(this->pathList, pathIdx));
 | |
| }
 | |
| 
 | |
| const ManifestPath *
 | |
| manifestPathFind(const Manifest *this, const String *name)
 | |
| {
 | |
|     FUNCTION_TEST_BEGIN();
 | |
|         FUNCTION_TEST_PARAM(MANIFEST, this);
 | |
|         FUNCTION_TEST_PARAM(STRING, name);
 | |
|     FUNCTION_TEST_END();
 | |
| 
 | |
|     ASSERT(this != NULL);
 | |
|     ASSERT(name != NULL);
 | |
| 
 | |
|     const ManifestPath *result = lstFind(this->pathList, &name);
 | |
| 
 | |
|     if (result == NULL)
 | |
|         THROW_FMT(AssertError, "unable to find '%s' in manifest path list", strPtr(name));
 | |
| 
 | |
|     FUNCTION_TEST_RETURN(result);
 | |
| }
 | |
| 
 | |
| // If the path requested is not found in the list, return the default passed rather than throw an error
 | |
| const ManifestPath *
 | |
| manifestPathFindDefault(const Manifest *this, const String *name, const ManifestPath *pathDefault)
 | |
| {
 | |
|     FUNCTION_TEST_BEGIN();
 | |
|         FUNCTION_TEST_PARAM(MANIFEST, this);
 | |
|         FUNCTION_TEST_PARAM(STRING, name);
 | |
|         FUNCTION_TEST_PARAM(MANIFEST_TARGET, pathDefault);
 | |
|     FUNCTION_TEST_END();
 | |
| 
 | |
|     ASSERT(this != NULL);
 | |
|     ASSERT(name != NULL);
 | |
| 
 | |
|     FUNCTION_TEST_RETURN(lstFindDefault(this->pathList, &name, (void *)pathDefault));
 | |
| }
 | |
| 
 | |
| String *
 | |
| manifestPathPg(const String *manifestPath)
 | |
| {
 | |
|     FUNCTION_TEST_BEGIN();
 | |
|         FUNCTION_TEST_PARAM(STRING, manifestPath);
 | |
|     FUNCTION_TEST_END();
 | |
| 
 | |
|     ASSERT(manifestPath != NULL);
 | |
| 
 | |
|     // If something in pg_data/
 | |
|     if (strBeginsWith(manifestPath, STRDEF(MANIFEST_TARGET_PGDATA "/")))
 | |
|     {
 | |
|         FUNCTION_TEST_RETURN(strNew(strPtr(manifestPath) + sizeof(MANIFEST_TARGET_PGDATA)));
 | |
|     }
 | |
|     // Else not pg_data (this is faster since the length of everything else will be different than pg_data)
 | |
|     else if (!strEq(manifestPath, MANIFEST_TARGET_PGDATA_STR))
 | |
|     {
 | |
|         // A tablespace target is the only valid option if not pg_data or pg_data/
 | |
|         ASSERT(
 | |
|             strEq(manifestPath, MANIFEST_TARGET_PGTBLSPC_STR) || strBeginsWith(manifestPath, STRDEF(MANIFEST_TARGET_PGTBLSPC "/")));
 | |
| 
 | |
|         FUNCTION_TEST_RETURN(strDup(manifestPath));
 | |
|     }
 | |
| 
 | |
|     FUNCTION_TEST_RETURN(NULL);
 | |
| }
 | |
| 
 | |
| unsigned int
 | |
| manifestPathTotal(const Manifest *this)
 | |
| {
 | |
|     FUNCTION_TEST_BEGIN();
 | |
|         FUNCTION_TEST_PARAM(MANIFEST, this);
 | |
|     FUNCTION_TEST_END();
 | |
| 
 | |
|     ASSERT(this != NULL);
 | |
| 
 | |
|     FUNCTION_TEST_RETURN(lstSize(this->pathList));
 | |
| }
 | |
| 
 | |
| /***********************************************************************************************************************************
 | |
| Target functions and getters/setters
 | |
| ***********************************************************************************************************************************/
 | |
| const ManifestTarget *
 | |
| manifestTarget(const Manifest *this, unsigned int targetIdx)
 | |
| {
 | |
|     FUNCTION_TEST_BEGIN();
 | |
|         FUNCTION_TEST_PARAM(MANIFEST, this);
 | |
|         FUNCTION_TEST_PARAM(UINT, targetIdx);
 | |
|     FUNCTION_TEST_END();
 | |
| 
 | |
|     ASSERT(this != NULL);
 | |
| 
 | |
|     FUNCTION_TEST_RETURN(lstGet(this->targetList, targetIdx));
 | |
| }
 | |
| 
 | |
| const ManifestTarget *
 | |
| manifestTargetBase(const Manifest *this)
 | |
| {
 | |
|     FUNCTION_TEST_BEGIN();
 | |
|         FUNCTION_TEST_PARAM(MANIFEST, this);
 | |
|     FUNCTION_TEST_END();
 | |
| 
 | |
|     ASSERT(this != NULL);
 | |
| 
 | |
|     FUNCTION_TEST_RETURN(manifestTargetFind(this, MANIFEST_TARGET_PGDATA_STR));
 | |
| }
 | |
| 
 | |
| const ManifestTarget *
 | |
| manifestTargetFind(const Manifest *this, const String *name)
 | |
| {
 | |
|     FUNCTION_TEST_BEGIN();
 | |
|         FUNCTION_TEST_PARAM(MANIFEST, this);
 | |
|         FUNCTION_TEST_PARAM(STRING, name);
 | |
|     FUNCTION_TEST_END();
 | |
| 
 | |
|     ASSERT(this != NULL);
 | |
|     ASSERT(name != NULL);
 | |
| 
 | |
|     const ManifestTarget *result = lstFind(this->targetList, &name);
 | |
| 
 | |
|     if (result == NULL)
 | |
|         THROW_FMT(AssertError, "unable to find '%s' in manifest target list", strPtr(name));
 | |
| 
 | |
|     FUNCTION_TEST_RETURN(result);
 | |
| }
 | |
| 
 | |
| String *
 | |
| manifestTargetPath(const Manifest *this, const ManifestTarget *target)
 | |
| {
 | |
|     FUNCTION_TEST_BEGIN();
 | |
|         FUNCTION_TEST_PARAM(MANIFEST, this);
 | |
|         FUNCTION_TEST_PARAM(MANIFEST_TARGET, target);
 | |
|     FUNCTION_TEST_END();
 | |
| 
 | |
|     ASSERT(this != NULL);
 | |
|     ASSERT(target != NULL);
 | |
| 
 | |
|     // If the target path is already absolute then just return it
 | |
|     if (strBeginsWith(target->path, FSLASH_STR))
 | |
|         FUNCTION_TEST_RETURN(strDup(target->path));
 | |
| 
 | |
|     // Construct it from the base pg path and a relative path
 | |
|     String *result = NULL;
 | |
| 
 | |
|     MEM_CONTEXT_TEMP_BEGIN()
 | |
|     {
 | |
|         String *pgPath = strPath(manifestPathPg(target->name));
 | |
| 
 | |
|         if (strSize(pgPath) != 0)
 | |
|             strCat(pgPath, "/");
 | |
| 
 | |
|         strCat(pgPath, strPtr(target->path));
 | |
| 
 | |
|         MEM_CONTEXT_PRIOR_BEGIN()
 | |
|         {
 | |
|             result = strPathAbsolute(pgPath, manifestTargetBase(this)->path);
 | |
|         }
 | |
|         MEM_CONTEXT_PRIOR_END();
 | |
|     }
 | |
|     MEM_CONTEXT_TEMP_END();
 | |
| 
 | |
|     FUNCTION_TEST_RETURN(result);
 | |
| }
 | |
| 
 | |
| void
 | |
| manifestTargetRemove(const Manifest *this, const String *name)
 | |
| {
 | |
|     FUNCTION_TEST_BEGIN();
 | |
|         FUNCTION_TEST_PARAM(MANIFEST, this);
 | |
|         FUNCTION_TEST_PARAM(STRING, name);
 | |
|     FUNCTION_TEST_END();
 | |
| 
 | |
|     ASSERT(this != NULL);
 | |
|     ASSERT(name != NULL);
 | |
| 
 | |
|     if (!lstRemove(this->targetList, &name))
 | |
|         THROW_FMT(AssertError, "unable to remove '%s' from manifest target list", strPtr(name));
 | |
| 
 | |
|     FUNCTION_TEST_RETURN_VOID();
 | |
| }
 | |
| 
 | |
| unsigned int
 | |
| manifestTargetTotal(const Manifest *this)
 | |
| {
 | |
|     FUNCTION_TEST_BEGIN();
 | |
|         FUNCTION_TEST_PARAM(MANIFEST, this);
 | |
|     FUNCTION_TEST_END();
 | |
| 
 | |
|     ASSERT(this != NULL);
 | |
| 
 | |
|     FUNCTION_TEST_RETURN(lstSize(this->targetList));
 | |
| }
 | |
| 
 | |
| void
 | |
| manifestTargetUpdate(const Manifest *this, const String *name, const String *path, const String *file)
 | |
| {
 | |
|     FUNCTION_TEST_BEGIN();
 | |
|         FUNCTION_TEST_PARAM(MANIFEST, this);
 | |
|         FUNCTION_TEST_PARAM(STRING, name);
 | |
|         FUNCTION_TEST_PARAM(STRING, path);
 | |
|         FUNCTION_TEST_PARAM(STRING, file);
 | |
|     FUNCTION_TEST_END();
 | |
| 
 | |
|     ASSERT(this != NULL);
 | |
|     ASSERT(name != NULL);
 | |
|     ASSERT(path != NULL);
 | |
| 
 | |
|     ManifestTarget *target = (ManifestTarget *)manifestTargetFind(this, name);
 | |
| 
 | |
|     ASSERT((target->file == NULL && file == NULL) || (target->file != NULL && file != NULL));
 | |
| 
 | |
|     MEM_CONTEXT_BEGIN(lstMemContext(this->targetList))
 | |
|     {
 | |
|         if (!strEq(target->path, path))
 | |
|             target->path = strDup(path);
 | |
| 
 | |
|         if (!strEq(target->file, file))
 | |
|             target->file = strDup(file);
 | |
|     }
 | |
|     MEM_CONTEXT_END();
 | |
| 
 | |
|     FUNCTION_TEST_RETURN_VOID();
 | |
| }
 | |
| 
 | |
| /***********************************************************************************************************************************
 | |
| Getters/Setters
 | |
| ***********************************************************************************************************************************/
 | |
| const String *
 | |
| manifestCipherSubPass(const Manifest *this)
 | |
| {
 | |
|     FUNCTION_TEST_BEGIN();
 | |
|         FUNCTION_TEST_PARAM(MANIFEST, this);
 | |
|     FUNCTION_TEST_END();
 | |
| 
 | |
|     ASSERT(this != NULL);
 | |
| 
 | |
|     FUNCTION_TEST_RETURN(infoCipherPass(this->info));
 | |
| }
 | |
| 
 | |
| void
 | |
| manifestCipherSubPassSet(Manifest *this, const String *cipherSubPass)
 | |
| {
 | |
|     FUNCTION_TEST_BEGIN();
 | |
|         FUNCTION_TEST_PARAM(MANIFEST, this);
 | |
|         FUNCTION_TEST_PARAM(STRING, cipherSubPass);
 | |
|     FUNCTION_TEST_END();
 | |
| 
 | |
|     ASSERT(this != NULL);
 | |
| 
 | |
|     infoCipherPassSet(this->info, cipherSubPass);
 | |
| 
 | |
|     FUNCTION_TEST_RETURN_VOID();
 | |
| }
 | |
| 
 | |
| const ManifestData *
 | |
| manifestData(const Manifest *this)
 | |
| {
 | |
|     FUNCTION_TEST_BEGIN();
 | |
|         FUNCTION_TEST_PARAM(MANIFEST, this);
 | |
|     FUNCTION_TEST_END();
 | |
| 
 | |
|     ASSERT(this != NULL);
 | |
| 
 | |
|     FUNCTION_TEST_RETURN(&this->data);
 | |
| }
 | |
| 
 | |
| void
 | |
| manifestBackupLabelSet(Manifest *this, const String *backupLabel)
 | |
| {
 | |
|     FUNCTION_TEST_BEGIN();
 | |
|         FUNCTION_TEST_PARAM(MANIFEST, this);
 | |
|         FUNCTION_TEST_PARAM(STRING, backupLabel);
 | |
|     FUNCTION_TEST_END();
 | |
| 
 | |
|     ASSERT(this != NULL);
 | |
| 
 | |
|     MEM_CONTEXT_BEGIN(this->memContext)
 | |
|     {
 | |
|         this->data.backupLabel = strDup(backupLabel);
 | |
|     }
 | |
|     MEM_CONTEXT_END();
 | |
| 
 | |
|     FUNCTION_TEST_RETURN_VOID();
 | |
| }
 | |
| 
 | |
| /***********************************************************************************************************************************
 | |
| Helper function to load backup manifest files
 | |
| ***********************************************************************************************************************************/
 | |
| typedef struct ManifestLoadFileData
 | |
| {
 | |
|     MemContext *memContext;                                         // Mem context
 | |
|     const Storage *storage;                                         // Storage to load from
 | |
|     const String *fileName;                                         // Base filename
 | |
|     CipherType cipherType;                                          // Cipher type
 | |
|     const String *cipherPass;                                       // Cipher passphrase
 | |
|     Manifest *manifest;                                             // Loaded manifest object
 | |
| } ManifestLoadFileData;
 | |
| 
 | |
| static bool
 | |
| manifestLoadFileCallback(void *data, unsigned int try)
 | |
| {
 | |
|     FUNCTION_LOG_BEGIN(logLevelTrace);
 | |
|         FUNCTION_LOG_PARAM_P(VOID, data);
 | |
|         FUNCTION_LOG_PARAM(UINT, try);
 | |
|     FUNCTION_LOG_END();
 | |
| 
 | |
|     ASSERT(data != NULL);
 | |
| 
 | |
|     ManifestLoadFileData *loadData = (ManifestLoadFileData *)data;
 | |
|     bool result = false;
 | |
| 
 | |
|     if (try < 2)
 | |
|     {
 | |
|         // Construct filename based on try
 | |
|         const String *fileName = try == 0 ? loadData->fileName : strNewFmt("%s" INFO_COPY_EXT, strPtr(loadData->fileName));
 | |
| 
 | |
|         // Attempt to load the file
 | |
|         IoRead *read = storageReadIo(storageNewReadP(loadData->storage, fileName));
 | |
|         cipherBlockFilterGroupAdd(ioReadFilterGroup(read), loadData->cipherType, cipherModeDecrypt, loadData->cipherPass);
 | |
| 
 | |
|         MEM_CONTEXT_BEGIN(loadData->memContext)
 | |
|         {
 | |
|             loadData->manifest = manifestNewLoad(read);
 | |
|             result = true;
 | |
|         }
 | |
|         MEM_CONTEXT_END();
 | |
|     }
 | |
| 
 | |
|     FUNCTION_LOG_RETURN(BOOL, result);
 | |
| }
 | |
| 
 | |
| Manifest *
 | |
| manifestLoadFile(const Storage *storage, const String *fileName, CipherType cipherType, const String *cipherPass)
 | |
| {
 | |
|     FUNCTION_LOG_BEGIN(logLevelDebug);
 | |
|         FUNCTION_LOG_PARAM(STORAGE, storage);
 | |
|         FUNCTION_LOG_PARAM(STRING, fileName);
 | |
|         FUNCTION_LOG_PARAM(ENUM, cipherType);
 | |
|         FUNCTION_TEST_PARAM(STRING, cipherPass);
 | |
|     FUNCTION_LOG_END();
 | |
| 
 | |
|     ASSERT(storage != NULL);
 | |
|     ASSERT(fileName != NULL);
 | |
|     ASSERT((cipherType == cipherTypeNone && cipherPass == NULL) || (cipherType != cipherTypeNone && cipherPass != NULL));
 | |
| 
 | |
|     ManifestLoadFileData data =
 | |
|     {
 | |
|         .memContext = memContextCurrent(),
 | |
|         .storage = storage,
 | |
|         .fileName = fileName,
 | |
|         .cipherType = cipherType,
 | |
|         .cipherPass = cipherPass,
 | |
|     };
 | |
| 
 | |
|     MEM_CONTEXT_TEMP_BEGIN()
 | |
|     {
 | |
|         const char *fileNamePath = strPtr(storagePathP(storage, fileName));
 | |
| 
 | |
|         infoLoad(
 | |
|             strNewFmt("unable to load backup manifest file '%s' or '%s" INFO_COPY_EXT "'", fileNamePath, fileNamePath),
 | |
|             manifestLoadFileCallback, &data);
 | |
|     }
 | |
|     MEM_CONTEXT_TEMP_END();
 | |
| 
 | |
|     FUNCTION_LOG_RETURN(MANIFEST, data.manifest);
 | |
| }
 |