mirror of
https://github.com/pgbackrest/pgbackrest.git
synced 2025-02-21 19:48:29 +02:00
Move link creation to storage interface.
Direct link creation via Posix functions has been moved to the Posix driver. This change allows adding SFTP softlink creation in the SFTP driver using the standard interface.
This commit is contained in:
parent
2a4137ed2e
commit
01b81f9d37
@ -32,6 +32,20 @@
|
||||
<p>Fix memory leak in file bundle <cmd>backup</cmd>/<cmd>restore</cmd>.</p>
|
||||
</release-item>
|
||||
</release-bug-list>
|
||||
|
||||
<release-development-list>
|
||||
<release-item>
|
||||
<github-pull-request id="1879"/>
|
||||
|
||||
<release-item-contributor-list>
|
||||
<release-item-contributor id="reid.thompson"/>
|
||||
<release-item-reviewer id="david.steele"/>
|
||||
<release-item-reviewer id="john.morris"/>
|
||||
</release-item-contributor-list>
|
||||
|
||||
<p>Move link creation to storage interface.</p>
|
||||
</release-item>
|
||||
</release-development-list>
|
||||
</release-core-list>
|
||||
</release>
|
||||
|
||||
|
@ -1842,9 +1842,7 @@ backupProcess(
|
||||
const String *const linkDestination = strNewFmt(
|
||||
"../../" MANIFEST_TARGET_PGTBLSPC "/%u", target->tablespaceId);
|
||||
|
||||
THROW_ON_SYS_ERROR_FMT(
|
||||
symlink(strZ(linkDestination), strZ(link)) == -1, FileOpenError,
|
||||
"unable to create symlink '%s' to '%s'", strZ(link), strZ(linkDestination));
|
||||
storageLinkCreateP(storageRepoWrite(), linkDestination, link);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1955,9 +1953,7 @@ backupProcess(
|
||||
storageRepo(),
|
||||
strNewFmt(STORAGE_REPO_BACKUP "/%s/%s%s", strZ(file.reference), strZ(file.name), compressExt));
|
||||
|
||||
THROW_ON_SYS_ERROR_FMT(
|
||||
link(strZ(linkDestination), strZ(linkName)) == -1, FileOpenError,
|
||||
"unable to create hardlink '%s' to '%s'", strZ(linkName), strZ(linkDestination));
|
||||
storageLinkCreateP(storageRepoWrite(), linkDestination, linkName, .linkType = storageLinkHard);
|
||||
}
|
||||
// Else log the reference. With delta, it is possible that references may have been removed if a file needed to be
|
||||
// recopied.
|
||||
|
@ -137,11 +137,7 @@ backupLinkLatest(const String *backupLabel, unsigned int repoIdx)
|
||||
storageRemoveP(storageRepoIdxWrite(repoIdx), latestLink);
|
||||
|
||||
if (storageFeature(storageRepoIdxWrite(repoIdx), storageFeatureSymLink))
|
||||
{
|
||||
THROW_ON_SYS_ERROR_FMT(
|
||||
symlink(strZ(backupLabel), strZ(latestLink)) == -1, FileOpenError, "unable to create symlink '%s' to '%s'",
|
||||
strZ(latestLink), strZ(backupLabel));
|
||||
}
|
||||
storageLinkCreateP(storageRepoIdxWrite(repoIdx), backupLabel, latestLink);
|
||||
|
||||
// Sync backup path if required
|
||||
if (storageFeature(storageRepoIdxWrite(repoIdx), storageFeaturePathSync))
|
||||
|
@ -1265,9 +1265,7 @@ restoreCleanBuild(const Manifest *const manifest, const String *const rootReplac
|
||||
{
|
||||
LOG_DETAIL_FMT("create symlink '%s' to '%s'", strZ(pgPath), strZ(link->destination));
|
||||
|
||||
THROW_ON_SYS_ERROR_FMT(
|
||||
symlink(strZ(link->destination), strZ(pgPath)) == -1, FileOpenError,
|
||||
"unable to create symlink '%s' to '%s'", strZ(pgPath), strZ(link->destination));
|
||||
storageLinkCreateP(storagePgWrite(), link->destination, pgPath);
|
||||
restoreCleanOwnership(
|
||||
pgPath, link->user, rootReplaceUser, link->group, rootReplaceGroup, userId(), groupId(), true);
|
||||
}
|
||||
@ -1305,9 +1303,7 @@ restoreCleanBuild(const Manifest *const manifest, const String *const rootReplac
|
||||
{
|
||||
LOG_DETAIL_FMT("create symlink '%s' to '%s'", strZ(pgPath), strZ(link->destination));
|
||||
|
||||
THROW_ON_SYS_ERROR_FMT(
|
||||
symlink(strZ(link->destination), strZ(pgPath)) == -1, FileOpenError,
|
||||
"unable to create symlink '%s' to '%s'", strZ(pgPath), strZ(link->destination));
|
||||
storageLinkCreateP(storagePgWrite(), link->destination, pgPath);
|
||||
restoreCleanOwnership(
|
||||
pgPath, link->user, rootReplaceUser, link->group, rootReplaceGroup, userId(), groupId(), true);
|
||||
}
|
||||
|
@ -112,6 +112,44 @@ storagePosixInfo(THIS_VOID, const String *file, StorageInfoLevel level, StorageI
|
||||
FUNCTION_LOG_RETURN(STORAGE_INFO, result);
|
||||
}
|
||||
|
||||
/**********************************************************************************************************************************/
|
||||
void
|
||||
storagePosixLinkCreate(
|
||||
THIS_VOID, const String *const target, const String *const linkPath, const StorageInterfaceLinkCreateParam param)
|
||||
{
|
||||
THIS(StoragePosix);
|
||||
|
||||
FUNCTION_LOG_BEGIN(logLevelTrace);
|
||||
FUNCTION_LOG_PARAM(STORAGE_POSIX, this);
|
||||
FUNCTION_LOG_PARAM(STRING, target);
|
||||
FUNCTION_LOG_PARAM(STRING, linkPath);
|
||||
FUNCTION_LOG_PARAM(ENUM, param.linkType);
|
||||
FUNCTION_LOG_END();
|
||||
|
||||
ASSERT(this != NULL);
|
||||
ASSERT(target != NULL);
|
||||
ASSERT(linkPath != NULL);
|
||||
|
||||
// Create symlink
|
||||
if (param.linkType == storageLinkSym)
|
||||
{
|
||||
THROW_ON_SYS_ERROR_FMT(
|
||||
symlink(strZ(target), strZ(linkPath)) == -1, FileOpenError, "unable to create symlink '%s' to '%s'", strZ(linkPath),
|
||||
strZ(target));
|
||||
}
|
||||
// Else create hardlink
|
||||
else
|
||||
{
|
||||
ASSERT(param.linkType == storageLinkHard);
|
||||
|
||||
THROW_ON_SYS_ERROR_FMT(
|
||||
link(strZ(target), strZ(linkPath)) == -1, FileOpenError, "unable to create hardlink '%s' to '%s'", strZ(linkPath),
|
||||
strZ(target));
|
||||
}
|
||||
|
||||
FUNCTION_LOG_RETURN_VOID();
|
||||
}
|
||||
|
||||
/**********************************************************************************************************************************/
|
||||
// Helper function to get info for a file if it exists. This logic can't live directly in storagePosixList() because there is a race
|
||||
// condition where a file might exist while listing the directory but it is gone before stat() can be called. In order to get
|
||||
@ -534,6 +572,7 @@ static const StorageInterface storageInterfacePosix =
|
||||
.feature = 1 << storageFeaturePath,
|
||||
|
||||
.info = storagePosixInfo,
|
||||
.linkCreate = storagePosixLinkCreate,
|
||||
.list = storagePosixList,
|
||||
.move = storagePosixMove,
|
||||
.newRead = storagePosixNewRead,
|
||||
|
@ -17,6 +17,7 @@ Storage *storagePosixNewInternal(
|
||||
/***********************************************************************************************************************************
|
||||
Functions
|
||||
***********************************************************************************************************************************/
|
||||
void storagePosixLinkCreate(THIS_VOID, const String *target, const String *linkPath, StorageInterfaceLinkCreateParam param);
|
||||
void storagePosixPathCreate(
|
||||
THIS_VOID, const String *path, bool errorOnExists, bool noParentCreate, mode_t mode, StorageInterfacePathCreateParam param);
|
||||
void storagePosixPathSync(THIS_VOID, const String *path, StorageInterfacePathSyncParam param);
|
||||
|
@ -279,6 +279,33 @@ storageRemoteInfoProtocol(PackRead *const param, ProtocolServer *const server)
|
||||
FUNCTION_LOG_RETURN_VOID();
|
||||
}
|
||||
|
||||
/**********************************************************************************************************************************/
|
||||
void
|
||||
storageRemoteLinkCreateProtocol(PackRead *const param, ProtocolServer *const server)
|
||||
{
|
||||
FUNCTION_LOG_BEGIN(logLevelDebug);
|
||||
FUNCTION_LOG_PARAM(PACK_READ, param);
|
||||
FUNCTION_LOG_PARAM(PROTOCOL_SERVER, server);
|
||||
FUNCTION_LOG_END();
|
||||
|
||||
ASSERT(param != NULL);
|
||||
ASSERT(server != NULL);
|
||||
ASSERT(storageRemoteProtocolLocal.driver != NULL);
|
||||
|
||||
MEM_CONTEXT_TEMP_BEGIN()
|
||||
{
|
||||
const String *const target = pckReadStrP(param);
|
||||
const String *const linkPath = pckReadStrP(param);
|
||||
const StorageLinkType linkType = (StorageLinkType)pckReadU32P(param);
|
||||
|
||||
storageInterfaceLinkCreateP(storageRemoteProtocolLocal.driver, target, linkPath, .linkType = linkType);
|
||||
protocolServerDataEndPut(server);
|
||||
}
|
||||
MEM_CONTEXT_TEMP_END();
|
||||
|
||||
FUNCTION_LOG_RETURN_VOID();
|
||||
}
|
||||
|
||||
/**********************************************************************************************************************************/
|
||||
void
|
||||
storageRemoteListProtocol(PackRead *const param, ProtocolServer *const server)
|
||||
|
@ -13,6 +13,7 @@ Functions
|
||||
// Process storage protocol requests
|
||||
void storageRemoteFeatureProtocol(PackRead *param, ProtocolServer *server);
|
||||
void storageRemoteInfoProtocol(PackRead *param, ProtocolServer *server);
|
||||
void storageRemoteLinkCreateProtocol(PackRead *param, ProtocolServer *server);
|
||||
void storageRemoteListProtocol(PackRead *param, ProtocolServer *server);
|
||||
void storageRemoteOpenReadProtocol(PackRead *param, ProtocolServer *server);
|
||||
void storageRemoteOpenWriteProtocol(PackRead *param, ProtocolServer *server);
|
||||
@ -26,6 +27,7 @@ Protocol commands for ProtocolServerHandler arrays passed to protocolServerProce
|
||||
***********************************************************************************************************************************/
|
||||
#define PROTOCOL_COMMAND_STORAGE_FEATURE STRID5("s-f", 0x1b730)
|
||||
#define PROTOCOL_COMMAND_STORAGE_INFO STRID5("s-i", 0x27730)
|
||||
#define PROTOCOL_COMMAND_STORAGE_LINK_CREATE STRID5("s-lc", 0x1b3730)
|
||||
#define PROTOCOL_COMMAND_STORAGE_LIST STRID5("s-l", 0x33730)
|
||||
#define PROTOCOL_COMMAND_STORAGE_OPEN_READ STRID5("s-or", 0x93f730)
|
||||
#define PROTOCOL_COMMAND_STORAGE_OPEN_WRITE STRID5("s-ow", 0xbbf730)
|
||||
@ -37,6 +39,7 @@ Protocol commands for ProtocolServerHandler arrays passed to protocolServerProce
|
||||
#define PROTOCOL_SERVER_HANDLER_STORAGE_REMOTE_LIST \
|
||||
{.command = PROTOCOL_COMMAND_STORAGE_FEATURE, .handler = storageRemoteFeatureProtocol}, \
|
||||
{.command = PROTOCOL_COMMAND_STORAGE_INFO, .handler = storageRemoteInfoProtocol}, \
|
||||
{.command = PROTOCOL_COMMAND_STORAGE_LINK_CREATE, .handler = storageRemoteLinkCreateProtocol}, \
|
||||
{.command = PROTOCOL_COMMAND_STORAGE_LIST, .handler = storageRemoteListProtocol}, \
|
||||
{.command = PROTOCOL_COMMAND_STORAGE_OPEN_READ, .handler = storageRemoteOpenReadProtocol}, \
|
||||
{.command = PROTOCOL_COMMAND_STORAGE_OPEN_WRITE, .handler = storageRemoteOpenWriteProtocol}, \
|
||||
|
@ -167,6 +167,40 @@ storageRemoteInfo(THIS_VOID, const String *file, StorageInfoLevel level, Storage
|
||||
FUNCTION_LOG_RETURN(STORAGE_INFO, result);
|
||||
}
|
||||
|
||||
/**********************************************************************************************************************************/
|
||||
static void
|
||||
storageRemoteLinkCreate(
|
||||
THIS_VOID, const String *const target, const String *const linkPath, const StorageInterfaceLinkCreateParam param)
|
||||
{
|
||||
THIS(StorageRemote);
|
||||
|
||||
FUNCTION_LOG_BEGIN(logLevelDebug);
|
||||
FUNCTION_LOG_PARAM(STORAGE_REMOTE, this);
|
||||
FUNCTION_LOG_PARAM(STRING, target);
|
||||
FUNCTION_LOG_PARAM(STRING, linkPath);
|
||||
FUNCTION_LOG_PARAM(ENUM, param.linkType);
|
||||
FUNCTION_LOG_END();
|
||||
|
||||
ASSERT(this != NULL);
|
||||
ASSERT(target != NULL);
|
||||
ASSERT(linkPath != NULL);
|
||||
|
||||
MEM_CONTEXT_TEMP_BEGIN()
|
||||
{
|
||||
ProtocolCommand *const command = protocolCommandNew(PROTOCOL_COMMAND_STORAGE_LINK_CREATE);
|
||||
PackWrite *const commandParam = protocolCommandParam(command);
|
||||
|
||||
pckWriteStrP(commandParam, target);
|
||||
pckWriteStrP(commandParam, linkPath);
|
||||
pckWriteU32P(commandParam, param.linkType);
|
||||
|
||||
protocolClientExecute(this->client, command, false);
|
||||
}
|
||||
MEM_CONTEXT_TEMP_END();
|
||||
|
||||
FUNCTION_LOG_RETURN_VOID();
|
||||
}
|
||||
|
||||
/**********************************************************************************************************************************/
|
||||
static StorageList *
|
||||
storageRemoteList(THIS_VOID, const String *const path, const StorageInfoLevel level, const StorageInterfaceListParam param)
|
||||
@ -427,6 +461,7 @@ static const StorageInterface storageInterfaceRemote =
|
||||
.pathRemove = storageRemotePathRemove,
|
||||
.pathSync = storageRemotePathSync,
|
||||
.remove = storageRemoteRemove,
|
||||
.linkCreate = storageRemoteLinkCreate,
|
||||
};
|
||||
|
||||
Storage *
|
||||
|
@ -89,6 +89,13 @@ storageNew(
|
||||
AssertError, !storageFeature(this, storageFeatureSymLink) || storageFeature(this, storageFeaturePath),
|
||||
"path feature required");
|
||||
|
||||
// If link features are enabled then linkCreate must be implemented
|
||||
CHECK(
|
||||
AssertError,
|
||||
(!storageFeature(this, storageFeatureSymLink) && !storageFeature(this, storageFeatureHardLink)) ||
|
||||
interface.linkCreate != NULL,
|
||||
"linkCreate required");
|
||||
|
||||
FUNCTION_LOG_RETURN(STORAGE, this);
|
||||
}
|
||||
|
||||
@ -317,6 +324,35 @@ storageNewItr(const Storage *const this, const String *const pathExp, StorageNew
|
||||
FUNCTION_LOG_RETURN(STORAGE_ITERATOR, result);
|
||||
}
|
||||
|
||||
/**********************************************************************************************************************************/
|
||||
void storageLinkCreate(
|
||||
const Storage *const this, const String *const target, const String *const linkPath, const StorageLinkCreateParam param)
|
||||
{
|
||||
FUNCTION_LOG_BEGIN(logLevelDebug);
|
||||
FUNCTION_LOG_PARAM(STORAGE, this);
|
||||
FUNCTION_LOG_PARAM(STRING, target);
|
||||
FUNCTION_LOG_PARAM(STRING, linkPath);
|
||||
FUNCTION_LOG_PARAM(ENUM, param.linkType);
|
||||
FUNCTION_LOG_END();
|
||||
|
||||
ASSERT(this != NULL);
|
||||
ASSERT(this->write);
|
||||
ASSERT(target != NULL);
|
||||
ASSERT(linkPath != NULL);
|
||||
ASSERT(this->pub.interface.linkCreate != NULL);
|
||||
ASSERT(
|
||||
(param.linkType == storageLinkSym && storageFeature(this, storageFeatureSymLink)) ||
|
||||
(param.linkType == storageLinkHard && storageFeature(this, storageFeatureHardLink)));
|
||||
|
||||
MEM_CONTEXT_TEMP_BEGIN()
|
||||
{
|
||||
storageInterfaceLinkCreateP(storageDriver(this), target, linkPath, .linkType = param.linkType);
|
||||
}
|
||||
MEM_CONTEXT_TEMP_END();
|
||||
|
||||
FUNCTION_LOG_RETURN_VOID();
|
||||
}
|
||||
|
||||
/**********************************************************************************************************************************/
|
||||
StringList *
|
||||
storageList(const Storage *this, const String *pathExp, StorageListParam param)
|
||||
|
@ -6,6 +6,18 @@ Storage Interface
|
||||
|
||||
#include <sys/types.h>
|
||||
|
||||
/***********************************************************************************************************************************
|
||||
Storage link type
|
||||
***********************************************************************************************************************************/
|
||||
typedef enum
|
||||
{
|
||||
// Symbolic (or soft) link
|
||||
storageLinkSym,
|
||||
|
||||
// Hard link
|
||||
storageLinkHard,
|
||||
} StorageLinkType;
|
||||
|
||||
/***********************************************************************************************************************************
|
||||
Object type
|
||||
***********************************************************************************************************************************/
|
||||
@ -241,6 +253,20 @@ typedef struct StorageRemoveParam
|
||||
|
||||
void storageRemove(const Storage *this, const String *fileExp, StorageRemoveParam param);
|
||||
|
||||
// Create a hard or symbolic link
|
||||
typedef struct StorageLinkCreateParam
|
||||
{
|
||||
VAR_PARAM_HEADER;
|
||||
|
||||
// Flag to create hard or symbolic link
|
||||
StorageLinkType linkType;
|
||||
} StorageLinkCreateParam;
|
||||
|
||||
#define storageLinkCreateP(this, target, linkPath, ...) \
|
||||
storageLinkCreate(this, target, linkPath, (StorageLinkCreateParam){VAR_PARAM_INIT, __VA_ARGS__})
|
||||
|
||||
void storageLinkCreate(const Storage *this, const String *target, const String *linkPath, StorageLinkCreateParam param);
|
||||
|
||||
/***********************************************************************************************************************************
|
||||
Getters/Setters
|
||||
***********************************************************************************************************************************/
|
||||
|
@ -86,6 +86,23 @@ typedef StorageInfo StorageInterfaceInfo(
|
||||
#define storageInterfaceInfoP(thisVoid, file, level, ...) \
|
||||
STORAGE_COMMON_INTERFACE(thisVoid).info(thisVoid, file, level, (StorageInterfaceInfoParam){VAR_PARAM_INIT, __VA_ARGS__})
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------------------
|
||||
// Create a hard or symbolic link
|
||||
typedef struct StorageInterfaceLinkCreateParam
|
||||
{
|
||||
VAR_PARAM_HEADER;
|
||||
|
||||
// Flag to create hard or symbolic link
|
||||
StorageLinkType linkType;
|
||||
} StorageInterfaceLinkCreateParam;
|
||||
|
||||
typedef void StorageInterfaceLinkCreate(
|
||||
void *thisVoid, const String *target, const String *linkPath, StorageInterfaceLinkCreateParam param);
|
||||
|
||||
#define storageInterfaceLinkCreateP(thisVoid, target, linkPath, ...) \
|
||||
STORAGE_COMMON_INTERFACE(thisVoid).linkCreate(thisVoid, target, linkPath, \
|
||||
(StorageInterfaceLinkCreateParam){VAR_PARAM_INIT, __VA_ARGS__})
|
||||
|
||||
// ---------------------------------------------------------------------------------------------------------------------------------
|
||||
// Create a file read object. The file should not be opened immediately -- open() will be called on the IoRead interface when the
|
||||
// file needs to be opened.
|
||||
@ -275,6 +292,7 @@ typedef struct StorageInterface
|
||||
StorageInterfaceRemove *remove;
|
||||
|
||||
// Optional functions
|
||||
StorageInterfaceLinkCreate *linkCreate;
|
||||
StorageInterfaceMove *move;
|
||||
StorageInterfacePathCreate *pathCreate;
|
||||
StorageInterfacePathSync *pathSync;
|
||||
|
@ -483,7 +483,7 @@ unit:
|
||||
test:
|
||||
# ----------------------------------------------------------------------------------------------------------------------------
|
||||
- name: posix
|
||||
total: 22
|
||||
total: 23
|
||||
|
||||
coverage:
|
||||
- storage/cifs/helper
|
||||
@ -500,7 +500,7 @@ unit:
|
||||
|
||||
# ----------------------------------------------------------------------------------------------------------------------------
|
||||
- name: remote
|
||||
total: 9
|
||||
total: 10
|
||||
|
||||
coverage:
|
||||
- storage/remote/read
|
||||
|
@ -860,6 +860,50 @@ testRun(void)
|
||||
TEST_RESULT_VOID(storagePathSyncP(storageTest, pathName), "sync path");
|
||||
}
|
||||
|
||||
// *****************************************************************************************************************************
|
||||
if (testBegin("storageLinkCreate()"))
|
||||
{
|
||||
StorageInfo info = {0};
|
||||
const String *backupLabel = STRDEF("20181119-152138F");
|
||||
const String *latestLabel = STRDEF("latest");
|
||||
int invalidLinkType = 9;
|
||||
|
||||
// -------------------------------------------------------------------------------------------------------------------------
|
||||
TEST_TITLE("create soft link to BACKUPLABEL");
|
||||
|
||||
TEST_RESULT_VOID(storagePathCreateP(storageTest, backupLabel), "create path to link to");
|
||||
TEST_RESULT_VOID(storageLinkCreateP(storageTest, backupLabel, latestLabel), "create symlink");
|
||||
TEST_ASSIGN(info, storageInfoP(storageTest, latestLabel, .ignoreMissing = false), "get link info");
|
||||
TEST_RESULT_STR(info.linkDestination, backupLabel, "match link destination");
|
||||
TEST_RESULT_VOID(storageRemoveP(storageTest, latestLabel), "remove symlink");
|
||||
|
||||
TEST_RESULT_VOID(storageLinkCreateP(storageTest, backupLabel, latestLabel, .linkType = storageLinkSym), "create symlink");
|
||||
TEST_ASSIGN(info, storageInfoP(storageTest, latestLabel, .ignoreMissing = false), "get link info");
|
||||
TEST_RESULT_STR(info.linkDestination, backupLabel, "match link destination");
|
||||
TEST_RESULT_VOID(storageRemoveP(storageTest, latestLabel), "remove symlink");
|
||||
|
||||
// -------------------------------------------------------------------------------------------------------------------------
|
||||
TEST_TITLE("hardlink success/fail");
|
||||
|
||||
const String *emptyFile = STRDEF(TEST_PATH "/test.empty");
|
||||
TEST_RESULT_VOID(storagePutP(storageNewWriteP(storageTest, emptyFile), NULL), "put empty file");
|
||||
TEST_RESULT_VOID(
|
||||
storageLinkCreateP(storageTest, emptyFile, latestLabel, .linkType = storageLinkHard), "hardlink to empty file");
|
||||
TEST_RESULT_VOID(storageRemoveP(storageTest, emptyFile), "remove empty file");
|
||||
TEST_RESULT_VOID(storageRemoveP(storageTest, latestLabel), "remove latest label");
|
||||
|
||||
TEST_ERROR(
|
||||
storageLinkCreateP(storageTest, backupLabel, latestLabel, .linkType = storageLinkHard), FileOpenError,
|
||||
"unable to create hardlink \'latest\' to \'20181119-152138F\': [1] Operation not permitted");
|
||||
TEST_ASSIGN(info, storageInfoP(storageTest, latestLabel, .ignoreMissing = true), "get link info");
|
||||
TEST_RESULT_STR(info.linkDestination, NULL, "no link destination");
|
||||
TEST_RESULT_VOID(storagePathRemoveP(storageTest, backupLabel), "remove backup path");
|
||||
TEST_ERROR(
|
||||
storageLinkCreateP(storageTest, backupLabel, latestLabel, .linkType = invalidLinkType), AssertError,
|
||||
"assertion '(param.linkType == storageLinkSym && storageFeature(this, storageFeatureSymLink)) ||"
|
||||
" (param.linkType == storageLinkHard && storageFeature(this, storageFeatureHardLink))' failed");
|
||||
}
|
||||
|
||||
// *****************************************************************************************************************************
|
||||
if (testBegin("storageNewRead()"))
|
||||
{
|
||||
|
@ -524,6 +524,131 @@ testRun(void)
|
||||
TEST_RESULT_VOID(storagePathSyncP(storageRepoWrite, path), "sync path");
|
||||
}
|
||||
|
||||
// *****************************************************************************************************************************
|
||||
if (testBegin("storageLinkCreate()"))
|
||||
{
|
||||
StorageInfo info = {0};
|
||||
const String *path = STRDEF("20181119-152138F");
|
||||
const String *latestLabel = STRDEF("latest");
|
||||
const String *testFile = strNewFmt("%s/%s", strZ(storagePathP(storageRepo, NULL)), "test.file");
|
||||
|
||||
// -------------------------------------------------------------------------------------------------------------------------
|
||||
TEST_TITLE("create/remove symlink to path and to file");
|
||||
|
||||
// Create the path and symlink via the remote
|
||||
TEST_RESULT_VOID(storagePathCreateP(storageRepoWrite, path), "remote create path to link to");
|
||||
TEST_RESULT_VOID(
|
||||
storageLinkCreateP(
|
||||
storageRepoWrite, path, strNewFmt("%s/%s", strZ(storagePathP(storageRepo, NULL)), strZ(latestLabel))),
|
||||
"remote create path symlink");
|
||||
|
||||
// Check the repo via the local test storage to ensure the remote wrote to it, then remove via remote and confirm removed
|
||||
TEST_RESULT_BOOL(
|
||||
storageInfoP(storageTest, strNewFmt("repo128/%s", strZ(path)), .ignoreMissing = true).exists, true, "path exists");
|
||||
TEST_RESULT_BOOL(
|
||||
storageInfoP(
|
||||
storageTest, strNewFmt("repo128/%s", strZ(latestLabel)), .ignoreMissing = true).exists, true,
|
||||
"symlink to path exists");
|
||||
|
||||
// Verify link mapping via the local test storage
|
||||
TEST_ASSIGN(
|
||||
info,
|
||||
storageInfoP(storageTest, strNewFmt("repo128/%s", strZ(latestLabel)), .ignoreMissing = false), "get symlink info");
|
||||
TEST_RESULT_STR(info.linkDestination, path, "match link destination");
|
||||
TEST_RESULT_INT(info.type, storageTypeLink, "check type is link");
|
||||
|
||||
TEST_RESULT_VOID(storageRemoveP(storageRepoWrite, latestLabel), "remote remove symlink");
|
||||
TEST_RESULT_BOOL(
|
||||
storageInfoP(
|
||||
storageTest, strNewFmt("repo128/%s", strZ(latestLabel)), .ignoreMissing = true).exists, false,
|
||||
"symlink to path removed");
|
||||
|
||||
// Create a file and sym link to it via the remote,
|
||||
TEST_RESULT_VOID(storagePutP(storageNewWriteP(storageRepoWrite, testFile), BUFSTRDEF("TESTME")), "put test file");
|
||||
TEST_RESULT_VOID(
|
||||
storageLinkCreateP(
|
||||
storageRepoWrite, testFile, strNewFmt("%s/%s", strZ(storagePathP(storageRepo, NULL)), strZ(latestLabel))),
|
||||
"remote create file symlink");
|
||||
|
||||
// Check the repo via the local test storage to ensure the remote wrote to it, then remove via remote and confirm removed
|
||||
TEST_RESULT_BOOL(
|
||||
storageInfoP(storageTest, strNewFmt("repo128/test.file"), .ignoreMissing = true).exists, true, "test file exists");
|
||||
TEST_RESULT_BOOL(
|
||||
storageInfoP(
|
||||
storageTest, strNewFmt("repo128/%s", strZ(latestLabel)), .ignoreMissing = true).exists, true,
|
||||
"symlink to file exists");
|
||||
|
||||
// Verify link mapping via the local test storage
|
||||
TEST_ASSIGN(
|
||||
info,
|
||||
storageInfoP(storageTest, strNewFmt("repo128/%s", strZ(latestLabel)), .ignoreMissing = false), "get symlink info");
|
||||
TEST_RESULT_STR(info.linkDestination, testFile, "match link destination");
|
||||
TEST_RESULT_INT(info.type, storageTypeLink, "check type is link");
|
||||
|
||||
// Verify file and link contents match
|
||||
Buffer *testFileBuffer = storageGetP(storageNewReadP(storageTest, STRDEF("repo128/test.file")));
|
||||
Buffer *symLinkBuffer = storageGetP(storageNewReadP(storageTest, strNewFmt("repo128/%s", strZ(latestLabel))));
|
||||
TEST_RESULT_BOOL(bufEq(testFileBuffer, BUFSTRDEF("TESTME")), true, "file contents match test buffer");
|
||||
TEST_RESULT_BOOL(bufEq(testFileBuffer, symLinkBuffer), true, "symlink and file contents match each other");
|
||||
|
||||
TEST_RESULT_VOID(storageRemoveP(storageRepoWrite, testFile), "remote remove test file");
|
||||
TEST_RESULT_BOOL(
|
||||
storageInfoP(storageTest, STRDEF("repo128/test.file"), .ignoreMissing = true).exists, false, "test file removed");
|
||||
TEST_RESULT_VOID(storageRemoveP(storageRepoWrite, latestLabel), "remote remove symlink");
|
||||
TEST_RESULT_BOOL(
|
||||
storageInfoP(
|
||||
storageTest, strNewFmt("repo128/%s", strZ(latestLabel)), .ignoreMissing = true).exists, false,
|
||||
"symlink to file removed");
|
||||
|
||||
// -------------------------------------------------------------------------------------------------------------------------
|
||||
TEST_TITLE("hardlink success/fail");
|
||||
|
||||
// Create a file and hard link to it via the remote,
|
||||
TEST_RESULT_VOID(storagePutP(storageNewWriteP(storageRepoWrite, testFile), BUFSTRDEF("TESTME")), "put test file");
|
||||
TEST_RESULT_VOID(
|
||||
storageLinkCreateP(
|
||||
storageRepoWrite, testFile, strNewFmt("%s/%s", strZ(storagePathP(storageRepo, NULL)), strZ(latestLabel)),
|
||||
.linkType = storageLinkHard),
|
||||
"hardlink to test file");
|
||||
|
||||
// Check the repo via the local test storage to ensure the remote wrote to it, check that files and link match, then remove
|
||||
// via remote and confirm removed
|
||||
TEST_RESULT_BOOL(
|
||||
storageInfoP(storageTest, STRDEF("repo128/test.file"), .ignoreMissing = true).exists, true, "test file exists");
|
||||
TEST_RESULT_BOOL(
|
||||
storageInfoP(
|
||||
storageTest, strNewFmt("repo128/%s", strZ(latestLabel)), .ignoreMissing = true).exists, true, "hard link exists");
|
||||
TEST_ASSIGN(
|
||||
info,
|
||||
storageInfoP(storageTest, strNewFmt("repo128/%s", strZ(latestLabel)), .ignoreMissing = false), "get hard link info");
|
||||
TEST_RESULT_INT(info.type, storageTypeFile, "check type is file");
|
||||
|
||||
// Verify file and link contents match
|
||||
testFileBuffer = storageGetP(storageNewReadP(storageTest, STRDEF("repo128/test.file")));
|
||||
Buffer *hardLinkBuffer = storageGetP(storageNewReadP(storageTest, strNewFmt("repo128/%s", strZ(latestLabel))));
|
||||
TEST_RESULT_BOOL(bufEq(testFileBuffer, BUFSTRDEF("TESTME")), true, "file contents match test buffer");
|
||||
TEST_RESULT_BOOL(bufEq(testFileBuffer, hardLinkBuffer), true, "hard link and file contents match each other");
|
||||
|
||||
TEST_RESULT_VOID(storageRemoveP(storageRepoWrite, testFile), "remove test file");
|
||||
TEST_RESULT_VOID(storageRemoveP(storageRepoWrite, latestLabel), "remove hard link");
|
||||
TEST_RESULT_BOOL(
|
||||
storageInfoP(storageTest, STRDEF("repo128/test.file"), .ignoreMissing = true).exists, false, "test file removed");
|
||||
TEST_RESULT_BOOL(
|
||||
storageInfoP(
|
||||
storageTest, strNewFmt("repo128/%s", strZ(latestLabel)), .ignoreMissing = true).exists, false, "hard link removed");
|
||||
|
||||
// Hard link to directory not permitted
|
||||
TEST_ERROR(
|
||||
storageLinkCreateP(
|
||||
storageRepoWrite,
|
||||
strNewFmt("%s/%s", strZ(storagePathP(storageRepo, NULL)), strZ(path)),
|
||||
strNewFmt("%s/%s", strZ(storagePathP(storageRepo, NULL)), strZ(latestLabel)), .linkType = storageLinkHard),
|
||||
FileOpenError,
|
||||
"raised from remote-0 shim protocol: unable to create hardlink '" TEST_PATH "/repo128/latest' to"
|
||||
" '" TEST_PATH "/repo128/20181119-152138F': [1] Operation not permitted");
|
||||
|
||||
}
|
||||
|
||||
// When clients are freed by protocolClientFree() they do not wait for a response. We need to wait for a response here to be
|
||||
// sure coverage data has been written by the remote. We also need to make sure that the mem context callback is cleared so that
|
||||
// protocolClientFreeResource() will not be called and send another exit. protocolFree() is still required to free the client
|
||||
|
Loading…
x
Reference in New Issue
Block a user