1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2026-06-20 01:17:49 +02:00

Add repo-symlink option to suppress creation of repository symlinks.

Some otherwise Posix-compatible filesystems do not support symlinks. To support this allow symlinks to be disabled.
This commit is contained in:
David Steele
2025-10-13 11:20:18 +02:00
committed by GitHub
parent 4767f16214
commit bfd9790731
13 changed files with 130 additions and 12 deletions
+15
View File
@@ -25,6 +25,21 @@
</release-item> </release-item>
</release-bug-list> </release-bug-list>
<release-feature-list>
<release-item>
<github-issue id="2673"/>
<github-pull-request id="2674"/>
<release-item-contributor-list>
<release-item-ideator id="ron.johnson"/>
<release-item-contributor id="david.steele"/>
<release-item-reviewer id="douglas.j.hunley"/>
</release-item-contributor-list>
<p>Add <setting>repo-symlink</setting> option to suppress creation of repository symlinks.</p>
</release-item>
</release-feature-list>
<release-improvement-list> <release-improvement-list>
<release-item> <release-item>
<github-pull-request id="2661"/> <github-pull-request id="2661"/>
+5
View File
@@ -965,6 +965,11 @@
<contributor-id type="github">rohitrav33ndran</contributor-id> <contributor-id type="github">rohitrav33ndran</contributor-id>
</contributor> </contributor>
<contributor id="ron.johnson">
<contributor-name-display>Ron Johnson</contributor-name-display>
<contributor-id type="github">RonJohn2</contributor-id>
</contributor>
<contributor id="ronan.dunklau"> <contributor id="ronan.dunklau">
<contributor-name-display>Ronan Dunklau</contributor-name-display> <contributor-name-display>Ronan Dunklau</contributor-name-display>
<contributor-id type="github">rdunklau</contributor-id> <contributor-id type="github">rdunklau</contributor-id>
+16
View File
@@ -2125,6 +2125,22 @@ option:
required: false required: false
command: repo-type command: repo-type
repo-symlink:
section: global
group: repo
type: boolean
default: true
command:
backup: {}
expire: {}
command-role:
main: {}
depend:
option: repo-type
default: false
list:
- posix
repo-hardlink: repo-hardlink:
section: global section: global
group: repo group: repo
+12
View File
@@ -1272,6 +1272,18 @@
<example>n</example> <example>n</example>
</config-key> </config-key>
<config-key id="repo-symlink" name="Repository Symlink">
<summary>Create symlinks within the repository.</summary>
<text>
<p>Enable creation of the <file>lastest</file> and tablespace symlinks. These symlinks are most useful when using snapshots to do in-place recovery in the repository, which is an uncommon use case.</p>
<p>While this feature is likely not useful for the vast majority of users it remains on by default for legacy purposes. However, it may be useful to disable symlinks for Posix-like storage that does not support them.</p>
</text>
<example>n</example>
</config-key>
<config-key id="repo-target-time" name="Target Time for Repository"> <config-key id="repo-target-time" name="Target Time for Repository">
<summary>Target time for repository.</summary> <summary>Target time for repository.</summary>
+2 -1
View File
@@ -140,7 +140,7 @@ Option constants
#define CFGOPT_VERBOSE "verbose" #define CFGOPT_VERBOSE "verbose"
#define CFGOPT_VERSION "version" #define CFGOPT_VERSION "version"
#define CFG_OPTION_TOTAL 187 #define CFG_OPTION_TOTAL 188
/*********************************************************************************************************************************** /***********************************************************************************************************************************
Option value constants Option value constants
@@ -557,6 +557,7 @@ typedef enum
cfgOptRepoStorageTag, cfgOptRepoStorageTag,
cfgOptRepoStorageUploadChunkSize, cfgOptRepoStorageUploadChunkSize,
cfgOptRepoStorageVerifyTls, cfgOptRepoStorageVerifyTls,
cfgOptRepoSymlink,
cfgOptRepoTargetTime, cfgOptRepoTargetTime,
cfgOptRepoType, cfgOptRepoType,
cfgOptReport, cfgOptReport,
+36
View File
@@ -9965,6 +9965,41 @@ static const ParseRuleOption parseRuleOption[CFG_OPTION_TOTAL] =
), // opt/repo-storage-verify-tls ), // opt/repo-storage-verify-tls
), // opt/repo-storage-verify-tls ), // opt/repo-storage-verify-tls
// ----------------------------------------------------------------------------------------------------------------------------- // -----------------------------------------------------------------------------------------------------------------------------
PARSE_RULE_OPTION // opt/repo-symlink
( // opt/repo-symlink
PARSE_RULE_OPTION_NAME("repo-symlink"), // opt/repo-symlink
PARSE_RULE_OPTION_TYPE(Boolean), // opt/repo-symlink
PARSE_RULE_OPTION_NEGATE(true), // opt/repo-symlink
PARSE_RULE_OPTION_RESET(true), // opt/repo-symlink
PARSE_RULE_OPTION_REQUIRED(true), // opt/repo-symlink
PARSE_RULE_OPTION_SECTION(Global), // opt/repo-symlink
PARSE_RULE_OPTION_GROUP_ID(Repo), // opt/repo-symlink
// opt/repo-symlink
PARSE_RULE_OPTION_COMMAND_ROLE_MAIN_VALID_LIST // opt/repo-symlink
( // opt/repo-symlink
PARSE_RULE_OPTION_COMMAND(Backup) // opt/repo-symlink
PARSE_RULE_OPTION_COMMAND(Expire) // opt/repo-symlink
), // opt/repo-symlink
// opt/repo-symlink
PARSE_RULE_OPTIONAL // opt/repo-symlink
( // opt/repo-symlink
PARSE_RULE_OPTIONAL_GROUP // opt/repo-symlink
( // opt/repo-symlink
PARSE_RULE_OPTIONAL_DEPEND // opt/repo-symlink
( // opt/repo-symlink
PARSE_RULE_OPTIONAL_DEPEND_DEFAULT(PARSE_RULE_VAL_BOOL_FALSE), // opt/repo-symlink
PARSE_RULE_VAL_OPT(RepoType), // opt/repo-symlink
PARSE_RULE_VAL_STRID(Posix), // opt/repo-symlink
), // opt/repo-symlink
// opt/repo-symlink
PARSE_RULE_OPTIONAL_DEFAULT // opt/repo-symlink
( // opt/repo-symlink
PARSE_RULE_VAL_BOOL_TRUE, // opt/repo-symlink
), // opt/repo-symlink
), // opt/repo-symlink
), // opt/repo-symlink
), // opt/repo-symlink
// -----------------------------------------------------------------------------------------------------------------------------
PARSE_RULE_OPTION // opt/repo-target-time PARSE_RULE_OPTION // opt/repo-target-time
( // opt/repo-target-time ( // opt/repo-target-time
PARSE_RULE_OPTION_NAME("repo-target-time"), // opt/repo-target-time PARSE_RULE_OPTION_NAME("repo-target-time"), // opt/repo-target-time
@@ -11678,6 +11713,7 @@ static const uint8_t optionResolveOrder[] =
cfgOptRepoStorageTag, // opt-resolve-order cfgOptRepoStorageTag, // opt-resolve-order
cfgOptRepoStorageUploadChunkSize, // opt-resolve-order cfgOptRepoStorageUploadChunkSize, // opt-resolve-order
cfgOptRepoStorageVerifyTls, // opt-resolve-order cfgOptRepoStorageVerifyTls, // opt-resolve-order
cfgOptRepoSymlink, // opt-resolve-order
cfgOptTarget, // opt-resolve-order cfgOptTarget, // opt-resolve-order
cfgOptTargetAction, // opt-resolve-order cfgOptTargetAction, // opt-resolve-order
cfgOptTargetExclusive, // opt-resolve-order cfgOptTargetExclusive, // opt-resolve-order
+1 -1
View File
@@ -24,5 +24,5 @@ storageCifsNew(
FUNCTION_LOG_END(); FUNCTION_LOG_END();
FUNCTION_LOG_RETURN( FUNCTION_LOG_RETURN(
STORAGE, storagePosixNewInternal(STORAGE_CIFS_TYPE, path, modeFile, modePath, write, pathExpressionFunction, false)); STORAGE, storagePosixNewInternal(STORAGE_CIFS_TYPE, path, modeFile, modePath, write, pathExpressionFunction, false, false));
} }
+2 -1
View File
@@ -391,7 +391,8 @@ storageRepoGet(const unsigned int repoIdx, const bool write)
CHECK(AssertError, type == STORAGE_POSIX_TYPE, "invalid storage type"); CHECK(AssertError, type == STORAGE_POSIX_TYPE, "invalid storage type");
result = storagePosixNewP( result = storagePosixNewP(
cfgOptionIdxStr(cfgOptRepoPath, repoIdx), .write = write, .pathExpressionFunction = storageRepoPathExpression); cfgOptionIdxStr(cfgOptRepoPath, repoIdx), .write = write, .pathExpressionFunction = storageRepoPathExpression,
.noSymLink = cfgOptionValid(cfgOptRepoSymlink) ? !cfgOptionIdxBool(cfgOptRepoSymlink, repoIdx) : true);
} }
} }
+8 -3
View File
@@ -594,7 +594,7 @@ static const StorageInterface storageInterfacePosix =
FN_EXTERN Storage * FN_EXTERN Storage *
storagePosixNewInternal( storagePosixNewInternal(
const StringId type, const String *const path, const mode_t modeFile, const mode_t modePath, const bool write, const StringId type, const String *const path, const mode_t modeFile, const mode_t modePath, const bool write,
StoragePathExpressionCallback pathExpressionFunction, const bool pathSync) StoragePathExpressionCallback pathExpressionFunction, const bool pathSync, const bool symLink)
{ {
FUNCTION_LOG_BEGIN(logLevelDebug); FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(STRING_ID, type); FUNCTION_LOG_PARAM(STRING_ID, type);
@@ -604,6 +604,7 @@ storagePosixNewInternal(
FUNCTION_LOG_PARAM(BOOL, write); FUNCTION_LOG_PARAM(BOOL, write);
FUNCTION_LOG_PARAM(FUNCTIONP, pathExpressionFunction); FUNCTION_LOG_PARAM(FUNCTIONP, pathExpressionFunction);
FUNCTION_LOG_PARAM(BOOL, pathSync); FUNCTION_LOG_PARAM(BOOL, pathSync);
FUNCTION_LOG_PARAM(BOOL, symLink);
FUNCTION_LOG_END(); FUNCTION_LOG_END();
ASSERT(type != 0); ASSERT(type != 0);
@@ -628,9 +629,11 @@ storagePosixNewInternal(
// If this is a posix driver then add link features // If this is a posix driver then add link features
if (type == STORAGE_POSIX_TYPE) if (type == STORAGE_POSIX_TYPE)
{
this->interface.feature |= this->interface.feature |=
1 << storageFeatureHardLink | 1 << storageFeatureSymLink | 1 << storageFeaturePathSync | 1 << storageFeatureHardLink | (unsigned int)symLink << storageFeatureSymLink | 1 << storageFeaturePathSync |
1 << storageFeatureInfoDetail; 1 << storageFeatureInfoDetail;
}
} }
OBJ_NEW_END(); OBJ_NEW_END();
@@ -646,6 +649,7 @@ storagePosixNew(const String *const path, const StoragePosixNewParam param)
FUNCTION_LOG_PARAM(MODE, param.modeFile); FUNCTION_LOG_PARAM(MODE, param.modeFile);
FUNCTION_LOG_PARAM(MODE, param.modePath); FUNCTION_LOG_PARAM(MODE, param.modePath);
FUNCTION_LOG_PARAM(BOOL, param.write); FUNCTION_LOG_PARAM(BOOL, param.write);
FUNCTION_LOG_PARAM(BOOL, param.noSymLink);
FUNCTION_LOG_PARAM(FUNCTIONP, param.pathExpressionFunction); FUNCTION_LOG_PARAM(FUNCTIONP, param.pathExpressionFunction);
FUNCTION_LOG_END(); FUNCTION_LOG_END();
@@ -653,5 +657,6 @@ storagePosixNew(const String *const path, const StoragePosixNewParam param)
STORAGE, STORAGE,
storagePosixNewInternal( storagePosixNewInternal(
STORAGE_POSIX_TYPE, path, param.modeFile == 0 ? STORAGE_MODE_FILE_DEFAULT : param.modeFile, STORAGE_POSIX_TYPE, path, param.modeFile == 0 ? STORAGE_MODE_FILE_DEFAULT : param.modeFile,
param.modePath == 0 ? STORAGE_MODE_PATH_DEFAULT : param.modePath, param.write, param.pathExpressionFunction, true)); param.modePath == 0 ? STORAGE_MODE_PATH_DEFAULT : param.modePath, param.write, param.pathExpressionFunction, true,
!param.noSymLink));
} }
+1
View File
@@ -18,6 +18,7 @@ typedef struct StoragePosixNewParam
{ {
VAR_PARAM_HEADER; VAR_PARAM_HEADER;
bool write; bool write;
bool noSymLink; // Do not create symlinks on this storage
mode_t modeFile; mode_t modeFile;
mode_t modePath; mode_t modePath;
StoragePathExpressionCallback *pathExpressionFunction; StoragePathExpressionCallback *pathExpressionFunction;
+1 -1
View File
@@ -16,7 +16,7 @@ Constructors
***********************************************************************************************************************************/ ***********************************************************************************************************************************/
FN_EXTERN Storage *storagePosixNewInternal( FN_EXTERN Storage *storagePosixNewInternal(
StringId type, const String *path, mode_t modeFile, mode_t modePath, bool write, StringId type, const String *path, mode_t modeFile, mode_t modePath, bool write,
StoragePathExpressionCallback pathExpressionFunction, bool pathSync); StoragePathExpressionCallback pathExpressionFunction, bool pathSync, bool symLink);
/*********************************************************************************************************************************** /***********************************************************************************************************************************
Macros for function logging Macros for function logging
+1 -4
View File
@@ -2918,6 +2918,7 @@ testRun(void)
hrnCfgArgRaw(argList, cfgOptRepoPath, repoPath); hrnCfgArgRaw(argList, cfgOptRepoPath, repoPath);
hrnCfgArgRaw(argList, cfgOptPgPath, pg1Path); hrnCfgArgRaw(argList, cfgOptPgPath, pg1Path);
hrnCfgArgRawZ(argList, cfgOptRepoRetentionFull, "1"); hrnCfgArgRawZ(argList, cfgOptRepoRetentionFull, "1");
hrnCfgArgRawBool(argList, cfgOptRepoSymlink, false);
hrnCfgArgRawStrId(argList, cfgOptType, backupTypeFull); hrnCfgArgRawStrId(argList, cfgOptType, backupTypeFull);
hrnCfgArgRawBool(argList, cfgOptRepoHardlink, true); hrnCfgArgRawBool(argList, cfgOptRepoHardlink, true);
hrnCfgArgRawZ(argList, cfgOptManifestSaveThreshold, "1"); hrnCfgArgRawZ(argList, cfgOptManifestSaveThreshold, "1");
@@ -2988,9 +2989,6 @@ testRun(void)
#pragma GCC diagnostic push #pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wcast-qual" #pragma GCC diagnostic ignored "-Wcast-qual"
// Disable storageFeatureSymLink so tablespace (and latest) symlinks will not be created
((Storage *)storageRepoWrite())->pub.interface.feature ^= 1 << storageFeatureSymLink;
// Disable storageFeatureHardLink so hardlinks will not be created // Disable storageFeatureHardLink so hardlinks will not be created
((Storage *)storageRepoWrite())->pub.interface.feature ^= 1 << storageFeatureHardLink; ((Storage *)storageRepoWrite())->pub.interface.feature ^= 1 << storageFeatureHardLink;
@@ -2999,7 +2997,6 @@ testRun(void)
TEST_RESULT_VOID(hrnCmdBackup(), "backup"); TEST_RESULT_VOID(hrnCmdBackup(), "backup");
// Reset storage features // Reset storage features
((Storage *)storageRepoWrite())->pub.interface.feature |= 1 << storageFeatureSymLink;
((Storage *)storageRepoWrite())->pub.interface.feature |= 1 << storageFeatureHardLink; ((Storage *)storageRepoWrite())->pub.interface.feature |= 1 << storageFeatureHardLink;
#pragma GCC diagnostic pop #pragma GCC diagnostic pop
+30 -1
View File
@@ -894,7 +894,7 @@ testRun(void)
int invalidLinkType = 9; int invalidLinkType = 9;
// ------------------------------------------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("create soft link to BACKUPLABEL"); TEST_TITLE("soft link success/fail");
TEST_RESULT_VOID(storagePathCreateP(storageTest, backupLabel), "create path to link to"); TEST_RESULT_VOID(storagePathCreateP(storageTest, backupLabel), "create path to link to");
TEST_RESULT_VOID(storageLinkCreateP(storageTest, backupLabel, latestLabel), "create symlink"); TEST_RESULT_VOID(storageLinkCreateP(storageTest, backupLabel, latestLabel), "create symlink");
@@ -907,6 +907,14 @@ testRun(void)
TEST_RESULT_STR(info.linkDestination, backupLabel, "match link destination"); TEST_RESULT_STR(info.linkDestination, backupLabel, "match link destination");
TEST_RESULT_VOID(storageRemoveP(storageTest, latestLabel), "remove symlink"); TEST_RESULT_VOID(storageRemoveP(storageTest, latestLabel), "remove symlink");
TEST_ERROR(
storageLinkCreateP(
storagePosixNewP(STRDEF("/"), .write = true, .noSymLink = true), backupLabel, latestLabel,
.linkType = storageLinkSym),
AssertError,
"assertion '(param.linkType == storageLinkSym && storageFeature(this, storageFeatureSymLink)) ||"
" (param.linkType == storageLinkHard && storageFeature(this, storageFeatureHardLink))' failed");
// ------------------------------------------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("hardlink success/fail"); TEST_TITLE("hardlink success/fail");
@@ -1636,6 +1644,27 @@ testRun(void)
TEST_ERROR(storageSpool(), AssertError, "stanza cannot be NULL for this storage object"); TEST_ERROR(storageSpool(), AssertError, "stanza cannot be NULL for this storage object");
TEST_ERROR(storageSpoolWrite(), AssertError, "stanza cannot be NULL for this storage object"); TEST_ERROR(storageSpoolWrite(), AssertError, "stanza cannot be NULL for this storage object");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("enable/disable symlink");
argList = strLstNew();
hrnCfgArgRawZ(argList, cfgOptStanza, "db");
hrnCfgArgRawZ(argList, cfgOptPgPath, TEST_PATH "/db");
hrnCfgArgKeyRawZ(argList, cfgOptRepoPath, 1, TEST_PATH "/repo1");
hrnCfgArgKeyRawZ(argList, cfgOptRepoRetentionFull, 1, "1");
hrnCfgArgKeyRawZ(argList, cfgOptRepoPath, 2, TEST_PATH "/repo2");
hrnCfgArgKeyRawZ(argList, cfgOptRepoRetentionFull, 2, "1");
HRN_CFG_LOAD(cfgCmdBackup, argList);
TEST_RESULT_BOOL(storageFeature(storageRepoIdxWrite(0), storageFeatureSymLink), true, "repo1 symlink enabled");
TEST_RESULT_BOOL(storageFeature(storageRepoIdxWrite(1), storageFeatureSymLink), true, "repo2 symlink enabled");
hrnCfgArgKeyRawBool(argList, cfgOptRepoSymlink, 2, false);
HRN_CFG_LOAD(cfgCmdBackup, argList);
TEST_RESULT_BOOL(storageFeature(storageRepoIdxWrite(0), storageFeatureSymLink), true, "repo1 symlink enabled");
TEST_RESULT_BOOL(storageFeature(storageRepoIdxWrite(1), storageFeatureSymLink), false, "repo2 symlink disabled");
} }
// ***************************************************************************************************************************** // *****************************************************************************************************************************