1 /***********************************************************************************************************************************
2 Check Common Handler
3 ***********************************************************************************************************************************/
4 #include "build.auto.h"
5 
6 #include <string.h>
7 
8 #include "command/check/common.h"
9 #include "common/debug.h"
10 #include "config/config.h"
11 #include "db/helper.h"
12 #include "info/infoArchive.h"
13 #include "info/infoBackup.h"
14 #include "postgres/interface.h"
15 #include "storage/helper.h"
16 #include "version.h"
17 
18 /***********************************************************************************************************************************
19 Helper function
20 ***********************************************************************************************************************************/
21 static bool
checkArchiveCommand(const String * archiveCommand)22 checkArchiveCommand(const String *archiveCommand)
23 {
24     FUNCTION_TEST_BEGIN();
25         FUNCTION_TEST_PARAM(STRING, archiveCommand);
26     FUNCTION_TEST_END();
27 
28     bool result = archiveCommand != NULL;
29 
30     if (result && strstr(strZ(archiveCommand), PROJECT_BIN) == NULL)
31         result = false;
32 
33     if (!result)
34     {
35         THROW_FMT(
36             ArchiveCommandInvalidError, "archive_command '%s' must contain %s",
37             archiveCommand != NULL ? strZ(archiveCommand) : "[" NULL_Z "]", PROJECT_BIN);
38     }
39 
40     FUNCTION_TEST_RETURN(result);
41 }
42 
43 /**********************************************************************************************************************************/
44 void
checkDbConfig(const unsigned int pgVersion,const unsigned int pgIdx,const Db * dbObject,bool isStandby)45 checkDbConfig(const unsigned int pgVersion, const unsigned int pgIdx, const Db *dbObject, bool isStandby)
46 {
47     FUNCTION_TEST_BEGIN();
48         FUNCTION_TEST_PARAM(UINT, pgVersion);
49         FUNCTION_TEST_PARAM(UINT, pgIdx);
50         FUNCTION_TEST_PARAM(DB, dbObject);
51         FUNCTION_TEST_PARAM(BOOL, isStandby);
52     FUNCTION_TEST_END();
53 
54     ASSERT(dbObject != NULL);
55 
56     MEM_CONTEXT_TEMP_BEGIN()
57     {
58         unsigned int dbVersion = dbPgVersion(dbObject);
59         const String *dbPath = dbPgDataPath(dbObject);
60 
61         // Error if the version from the control file and the configured pg-path do not match the values obtained from the database
62         if (pgVersion != dbVersion || strCmp(cfgOptionIdxStr(cfgOptPgPath, pgIdx), dbPath) != 0)
63         {
64             THROW_FMT(
65                 DbMismatchError, "version '%s' and path '%s' queried from cluster do not match version '%s' and '%s' read from '%s/"
66                 PG_PATH_GLOBAL "/" PG_FILE_PGCONTROL "'\nHINT: the %s and %s settings likely reference different clusters.",
67                 strZ(pgVersionToStr(dbVersion)), strZ(dbPath), strZ(pgVersionToStr(pgVersion)),
68                 strZ(cfgOptionIdxDisplay(cfgOptPgPath, pgIdx)), strZ(cfgOptionIdxDisplay(cfgOptPgPath, pgIdx)),
69                 cfgOptionIdxName(cfgOptPgPath, pgIdx), cfgOptionIdxName(cfgOptPgPort, pgIdx));
70         }
71 
72         // Check archive configuration if option is valid for the command and set
73         if (!isStandby && cfgOptionValid(cfgOptArchiveCheck) && cfgOptionBool(cfgOptArchiveCheck))
74         {
75             // Error if archive_mode = off since pg_start_backup () will fail
76             if (strCmpZ(dbArchiveMode(dbObject), "off") == 0)
77             {
78                 THROW(ArchiveDisabledError, "archive_mode must be enabled");
79             }
80 
81             // Error if archive_mode = always unless check is disabled (support has not been added yet)
82             if (cfgOptionBool(cfgOptArchiveModeCheck) && strCmpZ(dbArchiveMode(dbObject), "always") == 0)
83             {
84                 THROW(FeatureNotSupportedError, "archive_mode=always not supported");
85             }
86 
87             // Check if archive_command is set and is valid
88             checkArchiveCommand(dbArchiveCommand(dbObject));
89         }
90     }
91     MEM_CONTEXT_TEMP_END();
92 
93     FUNCTION_TEST_RETURN_VOID();
94 }
95 
96 /**********************************************************************************************************************************/
97 void
checkStanzaInfo(const InfoPgData * archiveInfo,const InfoPgData * backupInfo)98 checkStanzaInfo(const InfoPgData *archiveInfo, const InfoPgData *backupInfo)
99 {
100     FUNCTION_TEST_BEGIN();
101         FUNCTION_TEST_PARAM_P(INFO_PG_DATA, archiveInfo);
102         FUNCTION_TEST_PARAM_P(INFO_PG_DATA, backupInfo);
103     FUNCTION_TEST_END();
104 
105     ASSERT(archiveInfo != NULL);
106     ASSERT(backupInfo != NULL);
107 
108     // Error if there is a mismatch between the archive and backup info files
109     if (archiveInfo->id != backupInfo->id || archiveInfo->systemId != backupInfo->systemId ||
110         archiveInfo->version != backupInfo->version)
111     {
112         THROW_FMT(
113             FileInvalidError, "backup info file and archive info file do not match\n"
114             "archive: id = %u, version = %s, system-id = %" PRIu64 "\n"
115             "backup : id = %u, version = %s, system-id = %" PRIu64 "\n"
116             "HINT: this may be a symptom of repository corruption!",
117             archiveInfo->id, strZ(pgVersionToStr(archiveInfo->version)), archiveInfo->systemId, backupInfo->id,
118             strZ(pgVersionToStr(backupInfo->version)), backupInfo->systemId);
119     }
120 
121     FUNCTION_TEST_RETURN_VOID();
122 }
123 
124 /**********************************************************************************************************************************/
125 void
checkStanzaInfoPg(const Storage * storage,const unsigned int pgVersion,const uint64_t pgSystemId,CipherType cipherType,const String * cipherPass)126 checkStanzaInfoPg(
127     const Storage *storage, const unsigned int pgVersion, const uint64_t pgSystemId, CipherType cipherType,
128     const String *cipherPass)
129 {
130     FUNCTION_TEST_BEGIN();
131         FUNCTION_LOG_PARAM(STORAGE, storage);
132         FUNCTION_LOG_PARAM(UINT, pgVersion);
133         FUNCTION_LOG_PARAM(UINT64, pgSystemId);
134         FUNCTION_LOG_PARAM(STRING_ID, cipherType);
135         FUNCTION_TEST_PARAM(STRING, cipherPass);
136     FUNCTION_TEST_END();
137 
138     ASSERT(storage != NULL);
139 
140     MEM_CONTEXT_TEMP_BEGIN()
141     {
142         // Check that the backup and archive info files exist
143         InfoArchive *infoArchive = infoArchiveLoadFile(storage, INFO_ARCHIVE_PATH_FILE_STR, cipherType, cipherPass);
144         InfoPgData archiveInfoPg = infoPgData(infoArchivePg(infoArchive), infoPgDataCurrentId(infoArchivePg(infoArchive)));
145         InfoBackup *infoBackup = infoBackupLoadFile(storage, INFO_BACKUP_PATH_FILE_STR, cipherType, cipherPass);
146         InfoPgData backupInfoPg = infoPgData(infoBackupPg(infoBackup), infoPgDataCurrentId(infoBackupPg(infoBackup)));
147 
148         // Check that the info files pg data match each other
149         checkStanzaInfo(&archiveInfoPg, &backupInfoPg);
150 
151         // Check that the version and system id match the current database
152         if (pgVersion != archiveInfoPg.version || pgSystemId != archiveInfoPg.systemId)
153         {
154             THROW(FileInvalidError, "backup and archive info files exist but do not match the database\n"
155                 "HINT: is this the correct stanza?\n"
156                 "HINT: did an error occur during stanza-upgrade?");
157         }
158     }
159     MEM_CONTEXT_TEMP_END();
160 
161     FUNCTION_TEST_RETURN_VOID();
162 }
163