1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2025-03-05 15:05:48 +02:00

Refactor lock module.

Refactor the lock module to split command-specific logic from the basic file locking functionality. Command specific logic is now in command/lock.c. This will make it easier to implement new features such as repository locking and updating lock file contents on remotes.

This implementation is essentially a drop-in replacement but there are a few differences. First, the lock names no longer require a path (the path is added in the lock module). Second, the timeout functionality has been removed since it was not being used.
This commit is contained in:
David Steele 2024-06-18 10:43:54 +08:00 committed by GitHub
parent ad7377c75b
commit 270dce41b6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
33 changed files with 689 additions and 720 deletions

View File

@ -470,7 +470,7 @@ HRN_FORK_BEGIN()
{ {
HRN_FORK_CHILD_BEGIN() HRN_FORK_CHILD_BEGIN()
{ {
TEST_RESULT_INT_NE(lockAcquireP(), -1, "create backup/expire lock"); TEST_RESULT_BOOL(cmdLockAcquireP(), true, "create backup/expire lock");
// Notify parent that lock has been acquired // Notify parent that lock has been acquired
HRN_FORK_CHILD_NOTIFY_PUT(); HRN_FORK_CHILD_NOTIFY_PUT();
@ -478,7 +478,7 @@ HRN_FORK_BEGIN()
// Wait for parent to allow release lock // Wait for parent to allow release lock
HRN_FORK_CHILD_NOTIFY_GET(); HRN_FORK_CHILD_NOTIFY_GET();
lockRelease(true); cmdLockReleaseP();
} }
HRN_FORK_CHILD_END(); HRN_FORK_CHILD_END();

View File

@ -30,6 +30,7 @@ src_doc = [
'../../src/command/command.c', '../../src/command/command.c',
'../../src/command/exit.c', '../../src/command/exit.c',
'../../src/command/help/help.c', '../../src/command/help/help.c',
'../../src/command/lock.c',
'../../src/common/compress/bz2/common.c', '../../src/common/compress/bz2/common.c',
'../../src/common/compress/bz2/compress.c', '../../src/common/compress/bz2/compress.c',
'../../src/common/compress/bz2/decompress.c', '../../src/common/compress/bz2/decompress.c',

View File

@ -526,7 +526,7 @@ HRN_FORK_BEGIN()
{ {
HRN_FORK_CHILD_BEGIN() HRN_FORK_CHILD_BEGIN()
{ {
TEST_RESULT_INT_NE(lockAcquireP(), -1, "create backup/expire lock"); TEST_RESULT_BOOL(cmdLockAcquireP(), true, "create backup/expire lock");
// Notify parent that lock has been acquired // Notify parent that lock has been acquired
HRN_FORK_CHILD_NOTIFY_PUT(); HRN_FORK_CHILD_NOTIFY_PUT();
@ -534,7 +534,7 @@ HRN_FORK_BEGIN()
// Wait for parent to allow release lock // Wait for parent to allow release lock
HRN_FORK_CHILD_NOTIFY_GET(); HRN_FORK_CHILD_NOTIFY_GET();
lockRelease(true); cmdLockReleaseP();
} }
HRN_FORK_CHILD_END(); HRN_FORK_CHILD_END();

View File

@ -27,5 +27,18 @@
<p>Allow alternative WAL segment sizes for PostgreSQL &amp;le; 10.</p> <p>Allow alternative WAL segment sizes for PostgreSQL &amp;le; 10.</p>
</release-item> </release-item>
</release-improvement-list> </release-improvement-list>
<release-development-list>
<release-item>
<github-pull-request id="2371"/>
<release-item-contributor-list>
<release-item-contributor id="david.steele"/>
<release-item-reviewer id="reid.thompson"/>
</release-item-contributor-list>
<p>Refactor lock module.</p>
</release-item>
</release-development-list>
</release-core-list> </release-core-list>
</release> </release>

View File

@ -78,6 +78,7 @@ SRCS = \
command/control/start.c \ command/control/start.c \
command/control/stop.c \ command/control/stop.c \
command/local/local.c \ command/local/local.c \
command/lock.c \
command/manifest/manifest.c \ command/manifest/manifest.c \
command/repo/common.c \ command/repo/common.c \
command/repo/create.c \ command/repo/create.c \

View File

@ -13,6 +13,7 @@ Archive Get Command
#include "command/archive/get/file.h" #include "command/archive/get/file.h"
#include "command/archive/get/protocol.h" #include "command/archive/get/protocol.h"
#include "command/command.h" #include "command/command.h"
#include "command/lock.h"
#include "common/debug.h" #include "common/debug.h"
#include "common/log.h" #include "common/log.h"
#include "common/memContext.h" #include "common/memContext.h"
@ -730,7 +731,7 @@ cmdArchiveGet(void)
// If the WAL segment has not already been found then start the async process to get it. There's no point in forking // If the WAL segment has not already been found then start the async process to get it. There's no point in forking
// the async process off more than once so track that as well. Use an archive lock to prevent forking if the async // the async process off more than once so track that as well. Use an archive lock to prevent forking if the async
// process was launched by another process. // process was launched by another process.
if (!forked && (!found || !queueFull) && lockAcquireP(.returnOnNoLock = true)) if (!forked && (!found || !queueFull) && cmdLockAcquireP(.returnOnNoLock = true))
{ {
// Get control info // Get control info
const PgControl pgControl = pgControlFromFile(storagePg(), cfgOptionStrNull(cfgOptPgVersionForce)); const PgControl pgControl = pgControlFromFile(storagePg(), cfgOptionStrNull(cfgOptPgVersionForce));
@ -761,7 +762,7 @@ cmdArchiveGet(void)
archiveAsyncErrorClear(archiveModeGet, walSegment); archiveAsyncErrorClear(archiveModeGet, walSegment);
// Release the lock so the child process can acquire it // Release the lock so the child process can acquire it
lockRelease(true); cmdLockReleaseP();
// Execute the async process // Execute the async process
archiveAsyncExec(archiveModeGet, commandExec); archiveAsyncExec(archiveModeGet, commandExec);

View File

@ -11,6 +11,7 @@ Archive Push Command
#include "command/archive/push/protocol.h" #include "command/archive/push/protocol.h"
#include "command/command.h" #include "command/command.h"
#include "command/control/common.h" #include "command/control/common.h"
#include "command/lock.h"
#include "common/compress/helper.h" #include "common/compress/helper.h"
#include "common/debug.h" #include "common/debug.h"
#include "common/log.h" #include "common/log.h"
@ -350,7 +351,7 @@ cmdArchivePush(void)
// If the WAL segment has not already been pushed then start the async process to push it. There's no point in // If the WAL segment has not already been pushed then start the async process to push it. There's no point in
// forking the async process off more than once so track that as well. Use an archive lock to prevent more than one // forking the async process off more than once so track that as well. Use an archive lock to prevent more than one
// async process being launched. // async process being launched.
if (!pushed && !forked && lockAcquireP(.returnOnNoLock = true)) if (!pushed && !forked && cmdLockAcquireP(.returnOnNoLock = true))
{ {
// The async process should not output on the console at all // The async process should not output on the console at all
KeyValue *const optionReplace = kvNew(); KeyValue *const optionReplace = kvNew();
@ -367,7 +368,7 @@ cmdArchivePush(void)
archiveAsyncErrorClear(archiveModePush, archiveFile); archiveAsyncErrorClear(archiveModePush, archiveFile);
// Release the lock so the child process can acquire it // Release the lock so the child process can acquire it
lockRelease(true); cmdLockReleaseP();
// Execute the async process // Execute the async process
archiveAsyncExec(archiveModePush, commandExec); archiveAsyncExec(archiveModePush, commandExec);

View File

@ -15,12 +15,12 @@ Backup Command
#include "command/backup/protocol.h" #include "command/backup/protocol.h"
#include "command/check/common.h" #include "command/check/common.h"
#include "command/control/common.h" #include "command/control/common.h"
#include "command/lock.h"
#include "command/stanza/common.h" #include "command/stanza/common.h"
#include "common/compress/helper.h" #include "common/compress/helper.h"
#include "common/crypto/cipherBlock.h" #include "common/crypto/cipherBlock.h"
#include "common/debug.h" #include "common/debug.h"
#include "common/io/filter/size.h" #include "common/io/filter/size.h"
#include "common/lock.h"
#include "common/log.h" #include "common/log.h"
#include "common/regExp.h" #include "common/regExp.h"
#include "common/time.h" #include "common/time.h"
@ -1604,8 +1604,8 @@ backupJobResult(
if (percentComplete - *currentPercentComplete > 10) if (percentComplete - *currentPercentComplete > 10)
{ {
*currentPercentComplete = percentComplete; *currentPercentComplete = percentComplete;
lockWriteDataP( cmdLockWriteP(
lockTypeBackup, .percentComplete = VARUINT(*currentPercentComplete), .sizeComplete = VARUINT64(*sizeProgress), .percentComplete = VARUINT(*currentPercentComplete), .sizeComplete = VARUINT64(*sizeProgress),
.size = VARUINT64(sizeTotal)); .size = VARUINT64(sizeTotal));
} }
} }
@ -2218,8 +2218,8 @@ backupProcess(const BackupData *const backupData, Manifest *const manifest, cons
// Initialize percent complete and bytes completed/total // Initialize percent complete and bytes completed/total
unsigned int currentPercentComplete = 0; unsigned int currentPercentComplete = 0;
lockWriteDataP( cmdLockWriteP(
lockTypeBackup, .percentComplete = VARUINT(currentPercentComplete), .sizeComplete = VARUINT64(sizeProgress), .percentComplete = VARUINT(currentPercentComplete), .sizeComplete = VARUINT64(sizeProgress),
.size = VARUINT64(sizeTotal)); .size = VARUINT64(sizeTotal));
MEM_CONTEXT_TEMP_RESET_BEGIN() MEM_CONTEXT_TEMP_RESET_BEGIN()

View File

@ -53,20 +53,19 @@ cmdStop(void)
// Find each lock file and send term signals to the processes // Find each lock file and send term signals to the processes
for (unsigned int lockPathFileIdx = 0; lockPathFileIdx < strLstSize(lockPathFileList); lockPathFileIdx++) for (unsigned int lockPathFileIdx = 0; lockPathFileIdx < strLstSize(lockPathFileList); lockPathFileIdx++)
{ {
const String *lockFile = strLstGet(lockPathFileList, lockPathFileIdx); const String *const lockFile = strLstGet(lockPathFileList, lockPathFileIdx);
// Skip any file that is not a lock file. Skip lock files for other stanzas if a stanza is provided. // Skip any file that is not a lock file. Skip lock files for other stanzas if a stanza is provided.
if (!strEndsWithZ(lockFile, LOCK_FILE_EXT) || (stanzaPrefix != NULL && !strBeginsWith(lockFile, stanzaPrefix))) if (!strEndsWithZ(lockFile, LOCK_FILE_EXT) || (stanzaPrefix != NULL && !strBeginsWith(lockFile, stanzaPrefix)))
continue; continue;
// Read the lock file // Read the lock file
lockFile = strNewFmt("%s/%s", strZ(lockPath), strZ(lockFile)); const LockReadResult lockResult = lockReadP(lockFile, .remove = true);
const LockReadResult lockResult = lockReadFileP(lockFile, .remove = true);
// If we cannot read the lock file for any reason then warn and continue to next file // If we cannot read the lock file for any reason then warn and continue to next file
if (lockResult.status != lockReadStatusValid) if (lockResult.status != lockReadStatusValid)
{ {
LOG_WARN_FMT("unable to read lock file %s", strZ(lockFile)); LOG_WARN_FMT("unable to read lock file %s/%s", strZ(lockPath), strZ(lockFile));
continue; continue;
} }

View File

@ -8,8 +8,8 @@ Exit Routines
#include "command/command.h" #include "command/command.h"
#include "command/exit.h" #include "command/exit.h"
#include "command/lock.h"
#include "common/debug.h" #include "common/debug.h"
#include "common/lock.h"
#include "common/log.h" #include "common/log.h"
#include "config/config.h" #include "config/config.h"
#include "protocol/helper.h" #include "protocol/helper.h"
@ -169,7 +169,7 @@ exitSafe(int result, const bool error, const SignalType signalType)
// Release any locks but ignore errors // Release any locks but ignore errors
TRY_BEGIN() TRY_BEGIN()
{ {
lockRelease(false); cmdLockReleaseP(.returnOnNoLock = true);
} }
TRY_END(); TRY_END();

View File

@ -10,10 +10,10 @@ Info Command
#include "command/archive/common.h" #include "command/archive/common.h"
#include "command/info/info.h" #include "command/info/info.h"
#include "command/lock.h"
#include "common/crypto/common.h" #include "common/crypto/common.h"
#include "common/debug.h" #include "common/debug.h"
#include "common/io/fdWrite.h" #include "common/io/fdWrite.h"
#include "common/lock.h"
#include "common/log.h" #include "common/log.h"
#include "common/memContext.h" #include "common/memContext.h"
#include "common/type/json.h" #include "common/type/json.h"
@ -1299,7 +1299,7 @@ infoUpdateStanza(
if (!stanzaRepo->backupLockChecked) if (!stanzaRepo->backupLockChecked)
{ {
// If there is a valid backup lock for this stanza then backup/expire must be running // If there is a valid backup lock for this stanza then backup/expire must be running
const LockReadResult lockResult = lockRead(cfgOptionStr(cfgOptLockPath), stanzaRepo->name, lockTypeBackup); const LockReadResult lockResult = cmdLockRead(lockTypeBackup, stanzaRepo->name);
stanzaRepo->backupLockHeld = lockResult.status == lockReadStatusValid; stanzaRepo->backupLockHeld = lockResult.status == lockReadStatusValid;
stanzaRepo->backupLockChecked = true; stanzaRepo->backupLockChecked = true;

149
src/command/lock.c Normal file
View File

@ -0,0 +1,149 @@
/***********************************************************************************************************************************
Command Lock Handler
***********************************************************************************************************************************/
#include "build.auto.h"
#include "command/lock.h"
#include "common/debug.h"
#include "common/log.h"
#include "config/config.h"
/***********************************************************************************************************************************
Local variables
***********************************************************************************************************************************/
static struct CmdLockLocal
{
bool held; // Is the lock being held?
} cmdLockLocal;
/***********************************************************************************************************************************
Lock type names
***********************************************************************************************************************************/
static const char *const lockTypeName[] =
{
"archive", // lockTypeArchive
"backup", // lockTypeBackup
};
/**********************************************************************************************************************************/
static String *
cmdLockFileName(const String *const stanza, const LockType lockType)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(STRING, stanza);
FUNCTION_TEST_PARAM(ENUM, lockType);
FUNCTION_TEST_END();
ASSERT(stanza != NULL);
ASSERT(lockType < lockTypeAll);
FUNCTION_TEST_RETURN(STRING, strNewFmt("%s-%s" LOCK_FILE_EXT, strZ(stanza), lockTypeName[lockType]));
}
/**********************************************************************************************************************************/
FN_EXTERN bool
cmdLockAcquire(const LockAcquireParam param)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(BOOL, param.returnOnNoLock);
FUNCTION_LOG_END();
bool result = true;
// Don't allow return on no lock when locking more than one file. This makes cleanup difficult and there are no known use cases.
const LockType lockType = cfgLockType();
ASSERT(!param.returnOnNoLock || lockType != lockTypeAll);
// Don't allow another lock if one is already held
if (cmdLockLocal.held)
THROW(AssertError, "lock is already held by this process");
// Lock files
const LockType lockMin = lockType == lockTypeAll ? lockTypeArchive : lockType;
const LockType lockMax = lockType == lockTypeAll ? (lockTypeAll - 1) : lockType;
for (LockType lockIdx = lockMin; lockIdx <= lockMax; lockIdx++)
{
String *const lockFileName = cmdLockFileName(cfgOptionStr(cfgOptStanza), lockIdx);
result = lockAcquire(lockFileName, param);
strFree(lockFileName);
}
// Set lock held flag
cmdLockLocal.held = result;
FUNCTION_LOG_RETURN(BOOL, result);
}
/**********************************************************************************************************************************/
FN_EXTERN void
cmdLockWrite(const LockWriteParam param)
{
FUNCTION_LOG_BEGIN(logLevelTrace);
FUNCTION_LOG_PARAM(VARIANT, param.percentComplete);
FUNCTION_LOG_PARAM(VARIANT, param.sizeComplete);
FUNCTION_LOG_PARAM(VARIANT, param.size);
FUNCTION_LOG_END();
String *const lockFileName = cmdLockFileName(cfgOptionStr(cfgOptStanza), cfgLockType());
lockWrite(lockFileName, param);
strFree(lockFileName);
FUNCTION_LOG_RETURN_VOID();
}
/**********************************************************************************************************************************/
FN_EXTERN LockReadResult
cmdLockRead(const LockType lockType, const String *const stanza)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(ENUM, lockType);
FUNCTION_LOG_PARAM(STRING, stanza);
FUNCTION_LOG_END();
ASSERT(lockType == lockTypeBackup);
ASSERT(stanza != NULL);
FUNCTION_AUDIT_STRUCT();
LockReadResult result = {0};
MEM_CONTEXT_TEMP_BEGIN()
{
const String *const lockFileName = cmdLockFileName(stanza, lockType);
MEM_CONTEXT_PRIOR_BEGIN()
{
result = lockReadP(lockFileName);
}
MEM_CONTEXT_PRIOR_END();
}
MEM_CONTEXT_TEMP_END();
FUNCTION_LOG_RETURN_STRUCT(result);
}
/**********************************************************************************************************************************/
FN_EXTERN bool
cmdLockRelease(const LockReleaseParam param)
{
FUNCTION_LOG_BEGIN(logLevelTrace);
FUNCTION_LOG_PARAM(BOOL, param.returnOnNoLock);
FUNCTION_LOG_END();
bool result = true;
// Release lock(s)
if (cmdLockLocal.held)
{
result = lockRelease(param);
cmdLockLocal.held = false;
}
// Else error when requested
else if (!param.returnOnNoLock)
THROW(AssertError, "no lock is held by this process");
FUNCTION_LOG_RETURN(BOOL, result);
}

35
src/command/lock.h Normal file
View File

@ -0,0 +1,35 @@
/***********************************************************************************************************************************
Command Lock Handler
***********************************************************************************************************************************/
#ifndef COMMAND_LOCK_H
#define COMMAND_LOCK_H
#include "common/lock.h"
#include "config/config.h"
/***********************************************************************************************************************************
Functions
***********************************************************************************************************************************/
// Acquire a command lock. This will involve locking one or more files on disk depending on the lock type. Most operations only
// acquire a single lock type (archive or backup), but the stanza commands all need to lock both.
#define cmdLockAcquireP(...) \
cmdLockAcquire((LockAcquireParam){VAR_PARAM_INIT, __VA_ARGS__})
FN_EXTERN bool cmdLockAcquire(LockAcquireParam param);
// Write data to command lock file
#define cmdLockWriteP(...) \
cmdLockWrite((LockWriteParam){VAR_PARAM_INIT, __VA_ARGS__})
FN_EXTERN void cmdLockWrite(LockWriteParam param);
// Read a command lock file held by another process to get information about what the process is doing
FN_EXTERN LockReadResult cmdLockRead(LockType lockType, const String *stanza);
// Release command lock(s)
#define cmdLockReleaseP(...) \
cmdLockRelease((LockReleaseParam){VAR_PARAM_INIT, __VA_ARGS__})
FN_EXTERN bool cmdLockRelease(LockReleaseParam param);
#endif

View File

@ -8,6 +8,7 @@ Remote Command
#include "command/backup/blockIncr.h" #include "command/backup/blockIncr.h"
#include "command/backup/pageChecksum.h" #include "command/backup/pageChecksum.h"
#include "command/control/common.h" #include "command/control/common.h"
#include "command/lock.h"
#include "command/restore/blockChecksum.h" #include "command/restore/blockChecksum.h"
#include "common/crypto/cipherBlock.h" #include "common/crypto/cipherBlock.h"
#include "common/crypto/hash.h" #include "common/crypto/hash.h"
@ -77,7 +78,7 @@ cmdRemote(ProtocolServer *const server)
lockStopTest(); lockStopTest();
// Acquire the lock // Acquire the lock
lockAcquireP(); cmdLockAcquireP();
} }
} }

View File

@ -38,42 +38,32 @@ Constants
#define LOCK_KEY_SIZE_COMPLETE STRID6("szCplt", 0x50c4286931) #define LOCK_KEY_SIZE_COMPLETE STRID6("szCplt", 0x50c4286931)
#define LOCK_KEY_SIZE STRID5("sz", 0x3530) #define LOCK_KEY_SIZE STRID5("sz", 0x3530)
/***********************************************************************************************************************************
Lock type names
***********************************************************************************************************************************/
static const char *const lockTypeName[] =
{
"archive", // lockTypeArchive
"backup", // lockTypeBackup
};
/*********************************************************************************************************************************** /***********************************************************************************************************************************
Mem context and local variables Mem context and local variables
***********************************************************************************************************************************/ ***********************************************************************************************************************************/
typedef struct LockFile
{
String *name; // Name of lock file
int fd; // File descriptor for lock file
bool written; // Has the lock file been written?
} LockFile;
static struct LockLocal static struct LockLocal
{ {
MemContext *memContext; // Mem context for locks MemContext *memContext; // Mem context for locks
const String *path; // Lock path const String *path; // Lock path
const String *execId; // Process exec id const String *execId; // Process exec id
const String *stanza; // Stanza List *lockList; // List of locks held
LockType type; // Lock type const Storage *storage; // Storage object for lock path
bool held; // Is the lock being held?
struct
{
String *name; // Name of lock file
int fd; // File descriptor for lock file
} file[lockTypeAll];
} lockLocal; } lockLocal;
/**********************************************************************************************************************************/ /**********************************************************************************************************************************/
FN_EXTERN void FN_EXTERN void
lockInit(const String *const path, const String *const execId, const String *const stanza, const LockType type) lockInit(const String *const path, const String *const execId)
{ {
FUNCTION_LOG_BEGIN(logLevelDebug); FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(STRING, path); FUNCTION_LOG_PARAM(STRING, path);
FUNCTION_LOG_PARAM(STRING, execId); FUNCTION_LOG_PARAM(STRING, execId);
FUNCTION_LOG_PARAM(ENUM, type);
FUNCTION_LOG_END(); FUNCTION_LOG_END();
ASSERT(lockLocal.memContext == NULL); ASSERT(lockLocal.memContext == NULL);
@ -86,8 +76,8 @@ lockInit(const String *const path, const String *const execId, const String *con
lockLocal.memContext = MEM_CONTEXT_NEW(); lockLocal.memContext = MEM_CONTEXT_NEW();
lockLocal.path = strDup(path); lockLocal.path = strDup(path);
lockLocal.execId = strDup(execId); lockLocal.execId = strDup(execId);
lockLocal.stanza = strDup(stanza); lockLocal.lockList = lstNewP(sizeof(LockFile), .comparator = lstComparatorStr);
lockLocal.type = type; lockLocal.storage = storagePosixNewP(lockLocal.path, .write = true);
} }
MEM_CONTEXT_NEW_END(); MEM_CONTEXT_NEW_END();
} }
@ -96,38 +86,24 @@ lockInit(const String *const path, const String *const execId, const String *con
FUNCTION_LOG_RETURN_VOID(); FUNCTION_LOG_RETURN_VOID();
} }
/**********************************************************************************************************************************/
FN_EXTERN String *
lockFileName(const String *const stanza, const LockType lockType)
{
FUNCTION_TEST_BEGIN();
FUNCTION_TEST_PARAM(STRING, stanza);
FUNCTION_TEST_PARAM(ENUM, lockType);
FUNCTION_TEST_END();
FUNCTION_TEST_RETURN(STRING, strNewFmt("%s-%s" LOCK_FILE_EXT, strZ(stanza), lockTypeName[lockType]));
}
/*********************************************************************************************************************************** /***********************************************************************************************************************************
Read contents of lock file Read contents of lock file
If a seek is required to get to the beginning of the data, that must be done before calling this function.
***********************************************************************************************************************************/ ***********************************************************************************************************************************/
// Size of initial buffer used to load lock file // Size of initial buffer used to load lock file
#define LOCK_BUFFER_SIZE 128 #define LOCK_BUFFER_SIZE 128
// Helper to read data // Helper to read data
static LockData static LockData
lockReadFileData(const String *const lockFile, const int fd) lockReadData(const String *const lockFileName, const int fd)
{ {
FUNCTION_LOG_BEGIN(logLevelTrace); FUNCTION_LOG_BEGIN(logLevelTrace);
FUNCTION_LOG_PARAM(STRING, lockFile); FUNCTION_LOG_PARAM(STRING, lockFileName);
FUNCTION_LOG_PARAM(INT, fd); FUNCTION_LOG_PARAM(INT, fd);
FUNCTION_LOG_END(); FUNCTION_LOG_END();
FUNCTION_AUDIT_STRUCT(); FUNCTION_AUDIT_STRUCT();
ASSERT(lockFile != NULL); ASSERT(lockFileName != NULL);
ASSERT(fd != -1); ASSERT(fd != -1);
LockData result = {0}; LockData result = {0};
@ -140,7 +116,7 @@ lockReadFileData(const String *const lockFile, const int fd)
Buffer *const buffer = bufNew(LOCK_BUFFER_SIZE); Buffer *const buffer = bufNew(LOCK_BUFFER_SIZE);
IoWrite *const write = ioBufferWriteNewOpen(buffer); IoWrite *const write = ioBufferWriteNewOpen(buffer);
ioCopyP(ioFdReadNewOpen(lockFile, fd, 0), write); ioCopyP(ioFdReadNewOpen(lockFileName, fd, 0), write);
ioWriteClose(write); ioWriteClose(write);
JsonRead *const json = jsonReadNew(strNewBuf(buffer)); JsonRead *const json = jsonReadNew(strNewBuf(buffer));
@ -167,34 +143,165 @@ lockReadFileData(const String *const lockFile, const int fd)
} }
CATCH_ANY() CATCH_ANY()
{ {
THROWP_FMT(errorType(), "unable to read lock file '%s': %s", strZ(lockFile), errorMessage()); THROWP_FMT(errorType(), "unable to read lock file '%s': %s", strZ(lockFileName), errorMessage());
} }
TRY_END(); TRY_END();
FUNCTION_LOG_RETURN_STRUCT(result); FUNCTION_LOG_RETURN_STRUCT(result);
} }
/**********************************************************************************************************************************/
FN_EXTERN bool
lockAcquire(const String *const lockFileName, const LockAcquireParam param)
{
FUNCTION_LOG_BEGIN(logLevelTrace);
FUNCTION_LOG_PARAM(STRING, lockFileName);
FUNCTION_LOG_PARAM(BOOL, param.returnOnNoLock);
FUNCTION_LOG_END();
ASSERT(lockFileName != NULL);
ASSERT(lockLocal.memContext != NULL);
bool result = false;
if (lstFind(lockLocal.lockList, &lockFileName) != NULL)
THROW_FMT(AssertError, "lock on file '%s' already held", strZ(lockFileName));
MEM_CONTEXT_TEMP_BEGIN()
{
const String *const lockFilePath = storagePathP(lockLocal.storage, lockFileName);
bool retry;
int errNo = 0;
int fd = -1;
do
{
// Assume there will be no retry
retry = false;
// Attempt to open the file
if ((fd = open(strZ(lockFilePath), O_RDWR | O_CREAT, STORAGE_MODE_FILE_DEFAULT)) == -1)
{
// Save the error for reporting outside the loop
errNo = errno;
// If the path does not exist then create it
if (errNo == ENOENT)
{
storagePathCreateP(storagePosixNewP(FSLASH_STR, .write = true), strPath(lockFilePath));
retry = true;
}
}
else
{
// Attempt to lock the file
if (flock(fd, LOCK_EX | LOCK_NB) == -1)
{
// Save the error for reporting outside the loop
errNo = errno;
// Get execId from lock file and close it
const String *execId = NULL;
TRY_BEGIN()
{
execId = lockReadData(lockFileName, fd).execId;
}
CATCH_ANY()
{
// Any errors will be reported as unable to acquire a lock. If a process is trying to get a lock but is not
// synchronized with the process holding the actual lock, it is possible that it could see a short read or
// have some other problem reading. Reporting the error will likely be misleading when the actual problem is
// that another process owns the lock file.
}
FINALLY()
{
close(fd);
}
TRY_END();
// Even though we were unable to lock the file, it may be that it is already locked by another process with the
// same exec-id, i.e. spawned by the same original main process. If so, report the lock as successful.
if (strEq(execId, lockLocal.execId))
fd = LOCK_ON_EXEC_ID;
else
fd = -1;
}
}
}
while (fd == -1 && retry);
// If the lock was not successful
if (fd == -1)
{
// Error when requested
if (!param.returnOnNoLock || errNo != EWOULDBLOCK)
{
const String *errorHint = NULL;
if (errNo == EWOULDBLOCK)
errorHint = strNewZ("\nHINT: is another " PROJECT_NAME " process running?");
else if (errNo == EACCES)
{
// Get information for the current user
userInit();
errorHint = strNewFmt(
"\nHINT: does '%s:%s' running " PROJECT_NAME " have permissions on the '%s' file?", strZ(userName()),
strZ(groupName()), strZ(lockFilePath));
}
THROW_FMT(
LockAcquireError, "unable to acquire lock on file '%s': %s%s", strZ(lockFilePath), strerror(errNo),
errorHint == NULL ? "" : strZ(errorHint));
}
}
// Else add lock to list
else
{
MEM_CONTEXT_OBJ_BEGIN(lockLocal.lockList)
{
lstAdd(lockLocal.lockList, &(LockFile){.name = strDup(lockFileName), .fd = fd});
}
MEM_CONTEXT_OBJ_END();
// Write lock data unless lock was acquired by matching execId
if (fd != LOCK_ON_EXEC_ID)
lockWriteP(lockFileName);
// Success
result = true;
}
}
MEM_CONTEXT_TEMP_END();
FUNCTION_LOG_RETURN(BOOL, result);
}
/**********************************************************************************************************************************/ /**********************************************************************************************************************************/
FN_EXTERN LockReadResult FN_EXTERN LockReadResult
lockReadFile(const String *const lockFile, const LockReadFileParam param) lockRead(const String *const lockFileName, const LockReadParam param)
{ {
FUNCTION_LOG_BEGIN(logLevelDebug); FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(STRING, lockFile); FUNCTION_LOG_PARAM(STRING, lockFileName);
FUNCTION_LOG_PARAM(BOOL, param.remove); FUNCTION_LOG_PARAM(BOOL, param.remove);
FUNCTION_LOG_END(); FUNCTION_LOG_END();
FUNCTION_AUDIT_STRUCT(); FUNCTION_AUDIT_STRUCT();
ASSERT(lockFile != NULL); ASSERT(lockLocal.memContext != NULL);
ASSERT(lockFileName != NULL);
LockReadResult result = {.status = lockReadStatusValid}; LockReadResult result = {.status = lockReadStatusValid};
MEM_CONTEXT_TEMP_BEGIN() MEM_CONTEXT_TEMP_BEGIN()
{ {
// If we cannot open the lock file for any reason then warn and continue to next file const String *const lockFilePath = storagePathP(lockLocal.storage, lockFileName);
// If we cannot open the lock file for any reason then warn
int fd = -1; int fd = -1;
if ((fd = open(strZ(lockFile), O_RDONLY, 0)) == -1) if ((fd = open(strZ(lockFilePath), O_RDONLY, 0)) == -1)
{ {
result.status = lockReadStatusMissing; result.status = lockReadStatusMissing;
} }
@ -212,7 +319,7 @@ lockReadFile(const String *const lockFile, const LockReadFileParam param)
{ {
MEM_CONTEXT_PRIOR_BEGIN() MEM_CONTEXT_PRIOR_BEGIN()
{ {
result.data = lockReadFileData(lockFile, fd); result.data = lockReadData(lockFilePath, fd);
} }
MEM_CONTEXT_PRIOR_END(); MEM_CONTEXT_PRIOR_END();
} }
@ -225,7 +332,7 @@ lockReadFile(const String *const lockFile, const LockReadFileParam param)
// Remove lock file if requested but do not report failures // Remove lock file if requested but do not report failures
if (param.remove) if (param.remove)
unlink(strZ(lockFile)); unlink(strZ(lockFilePath));
// Close after unlinking to prevent a race condition where another process creates the file as we remove it // Close after unlinking to prevent a race condition where another process creates the file as we remove it
close(fd); close(fd);
@ -237,50 +344,17 @@ lockReadFile(const String *const lockFile, const LockReadFileParam param)
} }
/**********************************************************************************************************************************/ /**********************************************************************************************************************************/
FN_EXTERN LockReadResult
lockRead(const String *const lockPath, const String *const stanza, const LockType lockType)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(STRING, lockPath);
FUNCTION_LOG_PARAM(STRING, stanza);
FUNCTION_LOG_PARAM(ENUM, lockType);
FUNCTION_LOG_END();
FUNCTION_AUDIT_STRUCT();
LockReadResult result = {0};
MEM_CONTEXT_TEMP_BEGIN()
{
const String *const lockFile = strNewFmt("%s/%s", strZ(lockPath), strZ(lockFileName(stanza, lockType)));
MEM_CONTEXT_PRIOR_BEGIN()
{
result = lockReadFileP(lockFile);
}
MEM_CONTEXT_PRIOR_END();
}
MEM_CONTEXT_TEMP_END();
FUNCTION_LOG_RETURN_STRUCT(result);
}
/***********************************************************************************************************************************
Write contents of lock file
***********************************************************************************************************************************/
FN_EXTERN void FN_EXTERN void
lockWriteData(const LockType lockType, const LockWriteDataParam param) lockWrite(const String *const lockFileName, const LockWriteParam param)
{ {
FUNCTION_LOG_BEGIN(logLevelTrace); FUNCTION_LOG_BEGIN(logLevelTrace);
FUNCTION_LOG_PARAM(ENUM, lockType); FUNCTION_LOG_PARAM(STRING, lockFileName);
FUNCTION_LOG_PARAM(VARIANT, param.percentComplete); FUNCTION_LOG_PARAM(VARIANT, param.percentComplete);
FUNCTION_LOG_PARAM(VARIANT, param.sizeComplete); FUNCTION_LOG_PARAM(VARIANT, param.sizeComplete);
FUNCTION_LOG_PARAM(VARIANT, param.size); FUNCTION_LOG_PARAM(VARIANT, param.size);
FUNCTION_LOG_END(); FUNCTION_LOG_END();
ASSERT(lockType < lockTypeAll); ASSERT(lockFileName != NULL);
ASSERT(lockLocal.file[lockType].name != NULL);
ASSERT(lockLocal.file[lockType].fd != -1);
MEM_CONTEXT_TEMP_BEGIN() MEM_CONTEXT_TEMP_BEGIN()
{ {
@ -303,24 +377,31 @@ lockWriteData(const LockType lockType, const LockWriteDataParam param)
jsonWriteObjectEnd(json); jsonWriteObjectEnd(json);
if (lockType == lockTypeBackup && lockLocal.held) // Write to lock file
{ LockFile *const lockFile = lstFind(lockLocal.lockList, &lockFileName);
// Seek to beginning of backup lock file
THROW_ON_SYS_ERROR_FMT(
lseek(lockLocal.file[lockType].fd, 0, SEEK_SET) == -1, FileOpenError, STORAGE_ERROR_READ_SEEK, (uint64_t)0,
strZ(lockLocal.file[lockType].name));
// In case the current write is ever shorter than the previous one if (lockFile == NULL)
THROW_FMT(AssertError, "lock file '%s' not found", strZ(lockFileName));
const String *const lockFilePath = storagePathP(lockLocal.storage, lockFileName);
if (lockFile->written)
{
// Seek to beginning of lock file
THROW_ON_SYS_ERROR_FMT( THROW_ON_SYS_ERROR_FMT(
ftruncate(lockLocal.file[lockType].fd, 0) == -1, FileWriteError, "unable to truncate '%s'", lseek(lockFile->fd, 0, SEEK_SET) == -1, FileOpenError, STORAGE_ERROR_READ_SEEK, (uint64_t)0, strZ(lockFilePath));
strZ(lockLocal.file[lockType].name));
// In case the current write is shorter than before
THROW_ON_SYS_ERROR_FMT(ftruncate(lockFile->fd, 0) == -1, FileWriteError, "unable to truncate '%s'", strZ(lockFilePath));
} }
// Write lock file data // Write lock file data
IoWrite *const write = ioFdWriteNewOpen(lockLocal.file[lockType].name, lockLocal.file[lockType].fd, 0); IoWrite *const write = ioFdWriteNewOpen(lockFilePath, lockFile->fd, 0);
ioCopyP(ioBufferReadNewOpen(BUFSTR(jsonWriteResult(json))), write); ioCopyP(ioBufferReadNewOpen(BUFSTR(jsonWriteResult(json))), write);
ioWriteClose(write); ioWriteClose(write);
lockFile->written = true;
} }
MEM_CONTEXT_TEMP_END(); MEM_CONTEXT_TEMP_END();
@ -328,219 +409,44 @@ lockWriteData(const LockType lockType, const LockWriteDataParam param)
} }
/**********************************************************************************************************************************/ /**********************************************************************************************************************************/
// Helper to acquire a file lock FN_EXTERN bool
static int lockRelease(const LockReleaseParam param)
lockAcquireFile(const String *const lockFile, const TimeMSec lockTimeout, const bool failOnNoLock)
{ {
FUNCTION_LOG_BEGIN(logLevelTrace); FUNCTION_LOG_BEGIN(logLevelTrace);
FUNCTION_LOG_PARAM(STRING, lockFile);
FUNCTION_LOG_PARAM(TIMEMSEC, lockTimeout);
FUNCTION_LOG_PARAM(BOOL, failOnNoLock);
FUNCTION_LOG_END();
ASSERT(lockFile != NULL);
int result = -1;
MEM_CONTEXT_TEMP_BEGIN()
{
Wait *const wait = waitNew(lockTimeout);
bool retry;
int errNo = 0;
do
{
// Assume there will be no retry
retry = false;
// Attempt to open the file
if ((result = open(strZ(lockFile), O_RDWR | O_CREAT, STORAGE_MODE_FILE_DEFAULT)) == -1)
{
// Save the error for reporting outside the loop
errNo = errno;
// If the path does not exist then create it
if (errNo == ENOENT)
{
storagePathCreateP(storagePosixNewP(FSLASH_STR, .write = true), strPath(lockFile));
retry = true;
}
}
else
{
// Attempt to lock the file
if (flock(result, LOCK_EX | LOCK_NB) == -1)
{
// Save the error for reporting outside the loop
errNo = errno;
// Get execId from lock file and close it
const String *execId = NULL;
TRY_BEGIN()
{
execId = lockReadFileData(lockFile, result).execId;
}
CATCH_ANY()
{
// Any errors will be reported as unable to acquire a lock. If a process is trying to get a lock but is not
// synchronized with the process holding the actual lock, it is possible that it could see a short read or
// have some other problem reading. Reporting the error will likely be misleading when the actual problem is
// that another process owns the lock file.
}
FINALLY()
{
close(result);
}
TRY_END();
// Even though we were unable to lock the file, it may be that it is already locked by another process with the
// same exec-id, i.e. spawned by the same original main process. If so, report the lock as successful.
if (strEq(execId, lockLocal.execId))
result = LOCK_ON_EXEC_ID;
else
result = -1;
}
}
}
while (result == -1 && (waitMore(wait) || retry));
// If the lock was not successful
if (result == -1)
{
// Error when requested
if (failOnNoLock || errNo != EWOULDBLOCK)
{
const String *errorHint = NULL;
if (errNo == EWOULDBLOCK)
errorHint = strNewZ("\nHINT: is another " PROJECT_NAME " process running?");
else if (errNo == EACCES)
{
// Get information for the current user
userInit();
errorHint = strNewFmt(
"\nHINT: does '%s:%s' running " PROJECT_NAME " have permissions on the '%s' file?", strZ(userName()),
strZ(groupName()), strZ(lockFile));
}
THROW_FMT(
LockAcquireError, "unable to acquire lock on file '%s': %s%s", strZ(lockFile), strerror(errNo),
errorHint == NULL ? "" : strZ(errorHint));
}
}
}
MEM_CONTEXT_TEMP_END();
FUNCTION_LOG_RETURN(INT, result);
}
FN_EXTERN bool
lockAcquire(const LockAcquireParam param)
{
FUNCTION_LOG_BEGIN(logLevelDebug);
FUNCTION_LOG_PARAM(TIMEMSEC, param.timeout);
FUNCTION_LOG_PARAM(BOOL, param.returnOnNoLock); FUNCTION_LOG_PARAM(BOOL, param.returnOnNoLock);
FUNCTION_LOG_END(); FUNCTION_LOG_END();
ASSERT(lockLocal.memContext != NULL); ASSERT(lockLocal.memContext != NULL);
bool result = true;
// Don't allow failures when locking more than one file. This makes cleanup difficult and there are no known use cases.
ASSERT(!param.returnOnNoLock || lockLocal.type != lockTypeAll);
// Don't allow another lock if one is already held
if (lockLocal.held)
THROW(AssertError, "lock is already held by this process");
// Lock files
const LockType lockMin = lockLocal.type == lockTypeAll ? lockTypeArchive : lockLocal.type;
const LockType lockMax = lockLocal.type == lockTypeAll ? (lockTypeAll - 1) : lockLocal.type;
for (LockType lockIdx = lockMin; lockIdx <= lockMax; lockIdx++)
{
MEM_CONTEXT_BEGIN(lockLocal.memContext)
{
strFree(lockLocal.file[lockIdx].name);
lockLocal.file[lockIdx].name = strNewFmt("%s/%s", strZ(lockLocal.path), strZ(lockFileName(lockLocal.stanza, lockIdx)));
}
MEM_CONTEXT_END();
lockLocal.file[lockIdx].fd = lockAcquireFile(lockLocal.file[lockIdx].name, param.timeout, !param.returnOnNoLock);
if (lockLocal.file[lockIdx].fd == -1)
{
// Free the lock
lockLocal.held = false;
result = false;
break;
}
// Else write lock data unless we locked an execId match
else if (lockLocal.file[lockIdx].fd != LOCK_ON_EXEC_ID)
lockWriteDataP(lockIdx);
}
if (result)
lockLocal.held = true;
FUNCTION_LOG_RETURN(BOOL, result);
}
/**********************************************************************************************************************************/
// Helper to release a file lock
static void
lockReleaseFile(const int lockFd, const String *const lockFile)
{
FUNCTION_LOG_BEGIN(logLevelTrace);
FUNCTION_LOG_PARAM(INT, lockFd);
FUNCTION_LOG_PARAM(STRING, lockFile);
FUNCTION_LOG_END();
// Can't release lock if there isn't one
ASSERT(lockFd >= 0);
MEM_CONTEXT_TEMP_BEGIN()
{
// Remove file first and then close it to release the lock. If we close it first then another process might grab the lock
// right before the delete which means the file locked by the other process will get deleted.
storageRemoveP(storagePosixNewP(FSLASH_STR, .write = true), lockFile);
close(lockFd);
}
MEM_CONTEXT_TEMP_END();
FUNCTION_LOG_RETURN_VOID();
}
FN_EXTERN bool
lockRelease(bool failOnNoLock)
{
FUNCTION_LOG_BEGIN(logLevelTrace);
FUNCTION_LOG_PARAM(BOOL, failOnNoLock);
FUNCTION_LOG_END();
bool result = false; bool result = false;
if (!lockLocal.held) // Nothing to do if lock list is empty
if (lstEmpty(lockLocal.lockList))
{ {
if (failOnNoLock) // Fail if requested
if (!param.returnOnNoLock)
THROW(AssertError, "no lock is held by this process"); THROW(AssertError, "no lock is held by this process");
} }
// Else release all locks
else else
{ {
// Release locks // Release until list is empty
const LockType lockMin = lockLocal.type == lockTypeAll ? lockTypeArchive : lockLocal.type; while (!lstEmpty(lockLocal.lockList))
const LockType lockMax = lockLocal.type == lockTypeAll ? (lockTypeAll - 1) : lockLocal.type;
for (LockType lockIdx = lockMin; lockIdx <= lockMax; lockIdx++)
{ {
if (lockLocal.file[lockIdx].fd != LOCK_ON_EXEC_ID) LockFile *const lockFile = lstGet(lockLocal.lockList, 0);
lockReleaseFile(lockLocal.file[lockIdx].fd, lockLocal.file[lockIdx].name);
// Remove lock file if this lock was not acquired by matching execId
if (lockFile->fd != LOCK_ON_EXEC_ID)
{
storageRemoveP(lockLocal.storage, lockFile->name);
close(lockFile->fd);
}
strFree(lockFile->name);
lstRemoveIdx(lockLocal.lockList, 0);
} }
// Free the lock context // Success
lockLocal.held = false;
result = true; result = true;
} }

View File

@ -7,16 +7,8 @@ Lock Handler
#include <sys/types.h> #include <sys/types.h>
/*********************************************************************************************************************************** /***********************************************************************************************************************************
Lock types Lock data
***********************************************************************************************************************************/ ***********************************************************************************************************************************/
typedef enum
{
lockTypeArchive,
lockTypeBackup,
lockTypeAll,
lockTypeNone,
} LockType;
#include "common/type/variant.h" #include "common/type/variant.h"
// Lock data // Lock data
@ -40,44 +32,35 @@ Constants
Functions Functions
***********************************************************************************************************************************/ ***********************************************************************************************************************************/
// Initialize lock module // Initialize lock module
FN_EXTERN void lockInit(const String *path, const String *execId, const String *stanza, LockType type); FN_EXTERN void lockInit(const String *path, const String *execId);
// Acquire a lock type. This will involve locking one or more files on disk depending on the lock type. Most operations only take a // Acquire a lock
// single lock (archive or backup), but the stanza commands all need to lock both.
typedef struct LockAcquireParam typedef struct LockAcquireParam
{ {
VAR_PARAM_HEADER; VAR_PARAM_HEADER;
TimeMSec timeout; // Lock timeout
bool returnOnNoLock; // Return when no lock acquired (rather than throw an error) bool returnOnNoLock; // Return when no lock acquired (rather than throw an error)
} LockAcquireParam; } LockAcquireParam;
#define lockAcquireP(...) \ #define lockAcquireP(lockFileName, ...) \
lockAcquire((LockAcquireParam) {VAR_PARAM_INIT, __VA_ARGS__}) lockAcquire(lockFileName, (LockAcquireParam){VAR_PARAM_INIT, __VA_ARGS__})
FN_EXTERN bool lockAcquire(LockAcquireParam param); FN_EXTERN bool lockAcquire(const String *lockFileName, LockAcquireParam param);
// Release a lock
FN_EXTERN bool lockRelease(bool failOnNoLock);
// Build lock file name
FN_EXTERN String *lockFileName(const String *stanza, LockType lockType);
// Write data to a lock file // Write data to a lock file
typedef struct LockWriteDataParam typedef struct LockWriteParam
{ {
VAR_PARAM_HEADER; VAR_PARAM_HEADER;
const Variant *percentComplete; // Percentage of backup complete * 100 (when not NULL) const Variant *percentComplete; // Percentage of backup complete * 100 (when not NULL)
const Variant *sizeComplete; // Completed size of the backup in bytes const Variant *sizeComplete; // Completed size of the backup in bytes
const Variant *size; // Total size of the backup in bytes const Variant *size; // Total size of the backup in bytes
} LockWriteDataParam; } LockWriteParam;
#define lockWriteDataP(lockType, ...) \ #define lockWriteP(lockFileName, ...) \
lockWriteData(lockType, (LockWriteDataParam) {VAR_PARAM_INIT, __VA_ARGS__}) lockWrite(lockFileName, (LockWriteParam){VAR_PARAM_INIT, __VA_ARGS__})
FN_EXTERN void lockWriteData(LockType lockType, LockWriteDataParam param); FN_EXTERN void lockWrite(const String *lockFileName, LockWriteParam param);
// Read a lock file held by another process to get information about what the process is doing. This is a lower-level version to use // Read a lock file held by another process to get information about what the process is doing
// when the lock file name is already known and the lock file may need to be removed.
typedef enum typedef enum
{ {
lockReadStatusMissing, // File is missing lockReadStatusMissing, // File is missing
@ -92,18 +75,27 @@ typedef struct LockReadResult
LockData data; // Lock data LockData data; // Lock data
} LockReadResult; } LockReadResult;
typedef struct LockReadFileParam typedef struct LockReadParam
{ {
VAR_PARAM_HEADER; VAR_PARAM_HEADER;
bool remove; // Remove the lock file after locking/reading bool remove; // Remove the lock file after locking/reading
} LockReadFileParam; } LockReadParam;
#define lockReadFileP(lockFile, ...) \ #define lockReadP(lockFileName, ...) \
lockReadFile(lockFile, (LockReadFileParam){VAR_PARAM_INIT, __VA_ARGS__}) lockRead(lockFileName, (LockReadParam){VAR_PARAM_INIT, __VA_ARGS__})
FN_EXTERN LockReadResult lockReadFile(const String *lockFile, LockReadFileParam param); FN_EXTERN LockReadResult lockRead(const String *lockFileName, LockReadParam param);
// Wrapper that generates the lock filename before calling lockReadFile() // Release lock(s)
FN_EXTERN LockReadResult lockRead(const String *lockPath, const String *stanza, LockType lockType); typedef struct LockReleaseParam
{
VAR_PARAM_HEADER;
bool returnOnNoLock; // Return when no lock is held (rather than throw an error)
} LockReleaseParam;
#define lockReleaseP(...) \
lockRelease((LockReleaseParam){VAR_PARAM_INIT, __VA_ARGS__})
FN_EXTERN bool lockRelease(LockReleaseParam param);
#endif #endif

View File

@ -7,7 +7,6 @@ sets the command and options and determines which options are valid for a comman
#ifndef CONFIG_CONFIG_H #ifndef CONFIG_CONFIG_H
#define CONFIG_CONFIG_H #define CONFIG_CONFIG_H
#include "common/lock.h"
#include "common/log.h" #include "common/log.h"
#include "common/type/stringId.h" #include "common/type/stringId.h"
#include "common/type/stringList.h" #include "common/type/stringList.h"
@ -42,6 +41,17 @@ typedef enum
#define CFG_COMMAND_ROLE_TOTAL 4 #define CFG_COMMAND_ROLE_TOTAL 4
/***********************************************************************************************************************************
Lock types
***********************************************************************************************************************************/
typedef enum
{
lockTypeArchive,
lockTypeBackup,
lockTypeAll,
lockTypeNone,
} LockType;
/*********************************************************************************************************************************** /***********************************************************************************************************************************
Command Functions Command Functions

View File

@ -8,12 +8,12 @@ Configuration Load
#include <unistd.h> #include <unistd.h>
#include "command/command.h" #include "command/command.h"
#include "command/lock.h"
#include "common/compress/helper.intern.h" #include "common/compress/helper.intern.h"
#include "common/crypto/common.h" #include "common/crypto/common.h"
#include "common/debug.h" #include "common/debug.h"
#include "common/io/io.h" #include "common/io/io.h"
#include "common/io/socket/common.h" #include "common/io/socket/common.h"
#include "common/lock.h"
#include "common/log.h" #include "common/log.h"
#include "common/memContext.h" #include "common/memContext.h"
#include "config/config.intern.h" #include "config/config.intern.h"
@ -566,15 +566,13 @@ cfgLoad(const unsigned int argListSize, const char *argList[])
// Begin the command // Begin the command
cmdBegin(); cmdBegin();
// Init lock module if this command can lock // Initialize the lock module
if (cfgLockType() != lockTypeNone && !cfgCommandHelp()) if (cfgOptionTest(cfgOptLockPath))
{ lockInit(cfgOptionStr(cfgOptLockPath), cfgOptionStr(cfgOptExecId));
lockInit(cfgOptionStr(cfgOptLockPath), cfgOptionStr(cfgOptExecId), cfgOptionStr(cfgOptStanza), cfgLockType());
// Acquire a lock if this command requires a lock // Acquire a lock if this command requires a lock
if (cfgLockRequired()) if (cfgLockType() != lockTypeNone && !cfgCommandHelp() && cfgLockRequired())
lockAcquireP(); cmdLockAcquireP();
}
// Update options that have complex rules // Update options that have complex rules
cfgLoadUpdateOption(); cfgLoadUpdateOption();

View File

@ -20,6 +20,7 @@ Main
#include "command/help/help.h" #include "command/help/help.h"
#include "command/info/info.h" #include "command/info/info.h"
#include "command/local/local.h" #include "command/local/local.h"
#include "command/lock.h"
#include "command/manifest/manifest.h" #include "command/manifest/manifest.h"
#include "command/remote/remote.h" #include "command/remote/remote.h"
#include "command/repo/create.h" #include "command/repo/create.h"
@ -180,7 +181,7 @@ main(int argListSize, const char *argList[])
cmdBegin(); cmdBegin();
// Null out any backup percent complete value in the backup lock file // Null out any backup percent complete value in the backup lock file
lockWriteDataP(lockTypeBackup); cmdLockWriteP();
// Run expire // Run expire
cmdExpire(); cmdExpire();

View File

@ -146,6 +146,7 @@ src_pgbackrest = [
'command/control/start.c', 'command/control/start.c',
'command/control/stop.c', 'command/control/stop.c',
'command/local/local.c', 'command/local/local.c',
'command/lock.c',
'command/manifest/manifest.c', 'command/manifest/manifest.c',
'command/repo/common.c', 'command/repo/common.c',
'command/repo/create.c', 'command/repo/create.c',

View File

@ -425,7 +425,7 @@ unit:
# ---------------------------------------------------------------------------------------------------------------------------- # ----------------------------------------------------------------------------------------------------------------------------
- name: lock - name: lock
total: 3 total: 1
harness: harness:
name: config name: config
shim: shim:
@ -441,7 +441,7 @@ unit:
- common/lock - common/lock
depend: depend:
- common/lock - command/lock
- config/common - config/common
- config/config - config/config
- config/parse - config/parse
@ -789,6 +789,14 @@ unit:
- name: command - name: command
test: test:
# ----------------------------------------------------------------------------------------------------------------------------
- name: lock
total: 1
coverage:
- command/lock
# ---------------------------------------------------------------------------------------------------------------------------- # ----------------------------------------------------------------------------------------------------------------------------
- name: control - name: control
total: 4 total: 4
@ -798,6 +806,9 @@ unit:
- command/control/start - command/control/start
- command/control/stop - command/control/stop
include:
- command/lock
# ---------------------------------------------------------------------------------------------------------------------------- # ----------------------------------------------------------------------------------------------------------------------------
- name: annotate - name: annotate
total: 1 total: 1
@ -892,6 +903,9 @@ unit:
coverage: coverage:
- command/info/info - command/info/info
include:
- command/lock
# ---------------------------------------------------------------------------------------------------------------------------- # ----------------------------------------------------------------------------------------------------------------------------
- name: backup - name: backup
total: 12 total: 12

View File

@ -9,7 +9,6 @@ Harness for Creating Test Backups
#include "common/compress/helper.h" #include "common/compress/helper.h"
#include "common/crypto/common.h" #include "common/crypto/common.h"
#include "common/crypto/hash.h" #include "common/crypto/hash.h"
#include "common/lock.h"
#include "config/config.h" #include "config/config.h"
#include "info/infoArchive.h" #include "info/infoArchive.h"
#include "info/manifest.h" #include "info/manifest.h"
@ -153,8 +152,8 @@ hrnCmdBackup(void)
{ {
FUNCTION_HARNESS_VOID(); FUNCTION_HARNESS_VOID();
lockInit(STR(testPath()), cfgOptionStr(cfgOptExecId), cfgOptionStr(cfgOptStanza), lockTypeBackup); lockInit(STR(testPath()), cfgOptionStr(cfgOptExecId));
lockAcquireP(); cmdLockAcquireP();
TRY_BEGIN() TRY_BEGIN()
{ {
@ -162,7 +161,7 @@ hrnCmdBackup(void)
} }
FINALLY() FINALLY()
{ {
lockRelease(true); cmdLockReleaseP();
} }
TRY_END(); TRY_END();

View File

@ -108,8 +108,8 @@ hrnCfgLoad(ConfigCommand commandId, const StringList *argListParam, const HrnCfg
if (cfgOptionValid(cfgOptExecId) && !cfgOptionTest(cfgOptExecId)) if (cfgOptionValid(cfgOptExecId) && !cfgOptionTest(cfgOptExecId))
cfgOptionSet(cfgOptExecId, cfgSourceParam, VARSTRDEF("1-test")); cfgOptionSet(cfgOptExecId, cfgSourceParam, VARSTRDEF("1-test"));
if (cfgOptionTest(cfgOptExecId) && cfgOptionTest(cfgOptLockPath) && cfgOptionTest(cfgOptStanza)) if (cfgOptionTest(cfgOptExecId) && cfgOptionTest(cfgOptLockPath))
lockInit(cfgOptionStr(cfgOptLockPath), cfgOptionStr(cfgOptExecId), cfgOptionStr(cfgOptStanza), cfgLockType()); lockInit(cfgOptionStr(cfgOptLockPath), cfgOptionStr(cfgOptExecId));
else else
hrnLockUnInit(); hrnLockUnInit();

View File

@ -13,17 +13,15 @@ Include shimmed C modules
/**********************************************************************************************************************************/ /**********************************************************************************************************************************/
FN_EXTERN void FN_EXTERN void
lockInit(const String *const path, const String *const execId, const String *const stanza, const LockType type) lockInit(const String *const path, const String *const execId)
{ {
FUNCTION_HARNESS_BEGIN(); FUNCTION_HARNESS_BEGIN();
FUNCTION_HARNESS_PARAM(STRING, path); FUNCTION_HARNESS_PARAM(STRING, path);
FUNCTION_HARNESS_PARAM(STRING, execId); FUNCTION_HARNESS_PARAM(STRING, execId);
FUNCTION_HARNESS_PARAM(STRING, stanza);
FUNCTION_HARNESS_PARAM(ENUM, type);
FUNCTION_HARNESS_END(); FUNCTION_HARNESS_END();
hrnLockUnInit(); hrnLockUnInit();
lockInit_SHIMMED(path, execId, stanza, type); lockInit_SHIMMED(path, execId);
FUNCTION_HARNESS_RETURN_VOID(); FUNCTION_HARNESS_RETURN_VOID();
} }

View File

@ -27,6 +27,7 @@ src_test = [
'../../src/command/command.c', '../../src/command/command.c',
'../../src/command/exit.c', '../../src/command/exit.c',
'../../src/command/help/help.c', '../../src/command/help/help.c',
'../../src/command/lock.c',
'../../src/common/compress/bz2/common.c', '../../src/common/compress/bz2/common.c',
'../../src/common/compress/bz2/decompress.c', '../../src/common/compress/bz2/decompress.c',
'../../src/common/fork.c', '../../src/common/fork.c',

View File

@ -742,8 +742,8 @@ testRun(void)
{ {
HRN_FORK_CHILD_BEGIN() HRN_FORK_CHILD_BEGIN()
{ {
lockInit(cfgOptionStr(cfgOptLockPath), STRDEF("999-dededede"), cfgOptionStr(cfgOptStanza), cfgLockType()); lockInit(cfgOptionStr(cfgOptLockPath), STRDEF("999-dededede"));
TEST_RESULT_VOID(lockAcquireP(.timeout = 30000, .returnOnNoLock = true), "acquire lock"); TEST_RESULT_VOID(cmdLockAcquireP(.returnOnNoLock = true), "acquire lock");
// Notify parent that lock has been acquired // Notify parent that lock has been acquired
HRN_FORK_CHILD_NOTIFY_PUT(); HRN_FORK_CHILD_NOTIFY_PUT();
@ -751,7 +751,7 @@ testRun(void)
// Wait for parent to allow release lock // Wait for parent to allow release lock
HRN_FORK_CHILD_NOTIFY_GET(); HRN_FORK_CHILD_NOTIFY_GET();
lockRelease(true); cmdLockReleaseP();
} }
HRN_FORK_CHILD_END(); HRN_FORK_CHILD_END();

View File

@ -752,8 +752,8 @@ testRun(void)
{ {
HRN_FORK_CHILD_BEGIN() HRN_FORK_CHILD_BEGIN()
{ {
lockInit(cfgOptionStr(cfgOptLockPath), STRDEF("555-fefefefe"), cfgOptionStr(cfgOptStanza), cfgLockType()); lockInit(cfgOptionStr(cfgOptLockPath), STRDEF("555-fefefefe"));
lockAcquireP(.timeout = 30000, .returnOnNoLock = true); cmdLockAcquireP(.returnOnNoLock = true);
// Notify parent that lock has been acquired // Notify parent that lock has been acquired
HRN_FORK_CHILD_NOTIFY_PUT(); HRN_FORK_CHILD_NOTIFY_PUT();
@ -761,7 +761,7 @@ testRun(void)
// Wait for parent to allow release lock // Wait for parent to allow release lock
HRN_FORK_CHILD_NOTIFY_GET(); HRN_FORK_CHILD_NOTIFY_GET();
lockRelease(true); cmdLockReleaseP();
} }
HRN_FORK_CHILD_END(); HRN_FORK_CHILD_END();

View File

@ -1862,15 +1862,15 @@ testRun(void)
uint64_t sizeProgress = 0; uint64_t sizeProgress = 0;
currentPercentComplete = 4567; currentPercentComplete = 4567;
lockInit(TEST_PATH_STR, cfgOptionStr(cfgOptExecId), cfgOptionStr(cfgOptStanza), lockTypeBackup); lockInit(TEST_PATH_STR, cfgOptionStr(cfgOptExecId));
TEST_RESULT_VOID(lockAcquireP(), "acquire backup lock"); TEST_RESULT_VOID(cmdLockAcquireP(), "acquire backup lock");
TEST_RESULT_VOID( TEST_RESULT_VOID(
backupJobResult( backupJobResult(
manifest, STRDEF("host"), storageTest, strLstNew(), job, false, pgPageSize8, 0, &sizeProgress, manifest, STRDEF("host"), storageTest, strLstNew(), job, false, pgPageSize8, 0, &sizeProgress,
&currentPercentComplete), &currentPercentComplete),
"log noop result"); "log noop result");
TEST_RESULT_VOID(lockRelease(true), "release backup lock"); TEST_RESULT_VOID(cmdLockReleaseP(), "release backup lock");
TEST_RESULT_LOG("P00 DETAIL: match file from prior backup host:" TEST_PATH "/test (0B, 100.00%)"); TEST_RESULT_LOG("P00 DETAIL: match file from prior backup host:" TEST_PATH "/test (0B, 100.00%)");
} }

View File

@ -1,13 +1,15 @@
/*********************************************************************************************************************************** /***********************************************************************************************************************************
Test Command Control Test Command Control
***********************************************************************************************************************************/ ***********************************************************************************************************************************/
#include "common/harnessConfig.h" #include "command/lock.h"
#include "common/harnessFork.h"
#include "common/harnessStorage.h"
#include "common/io/fdRead.h" #include "common/io/fdRead.h"
#include "common/io/fdWrite.h" #include "common/io/fdWrite.h"
#include "storage/posix/storage.h" #include "storage/posix/storage.h"
#include "common/harnessConfig.h"
#include "common/harnessFork.h"
#include "common/harnessStorage.h"
/*********************************************************************************************************************************** /***********************************************************************************************************************************
Test Run Test Run
***********************************************************************************************************************************/ ***********************************************************************************************************************************/
@ -253,8 +255,9 @@ testRun(void)
{ {
HRN_FORK_CHILD_BEGIN() HRN_FORK_CHILD_BEGIN()
{ {
lockInit(STRDEF(HRN_PATH "/lock"), cfgOptionStr(cfgOptExecId), cfgOptionStr(cfgOptStanza), lockTypeArchive); String *lockFileName = cmdLockFileName(cfgOptionStr(cfgOptStanza), lockTypeArchive);
TEST_RESULT_BOOL(lockAcquireP(.timeout = 30000), true, "child process acquires lock"); lockInit(STRDEF(HRN_PATH "/lock"), cfgOptionStr(cfgOptExecId));
TEST_RESULT_BOOL(lockAcquireP(lockFileName), true, "create archive lock");
// Notify parent that lock has been acquired // Notify parent that lock has been acquired
HRN_FORK_CHILD_NOTIFY_PUT(); HRN_FORK_CHILD_NOTIFY_PUT();
@ -339,6 +342,7 @@ testRun(void)
HRN_STORAGE_PUT_EMPTY(hrnStorage, "lock/db-archive" LOCK_FILE_EXT, .comment = "create empty archive lock file for stanza"); HRN_STORAGE_PUT_EMPTY(hrnStorage, "lock/db-archive" LOCK_FILE_EXT, .comment = "create empty archive lock file for stanza");
HRN_STORAGE_PUT_Z(hrnStorage, "lock/db1-backup" LOCK_FILE_EXT, " ", .comment = "create non-empty lock file other stanza"); HRN_STORAGE_PUT_Z(hrnStorage, "lock/db1-backup" LOCK_FILE_EXT, " ", .comment = "create non-empty lock file other stanza");
lockInit(STRDEF(HRN_PATH "/lock"), STRDEF("1"));
TEST_RESULT_VOID(cmdStop(), "no stanza, create stop file, ignore non lock file"); TEST_RESULT_VOID(cmdStop(), "no stanza, create stop file, ignore non lock file");
TEST_STORAGE_EXISTS(hrnStorage, "lock/all" STOP_FILE_EXT, .comment = "stanza stop file created"); TEST_STORAGE_EXISTS(hrnStorage, "lock/all" STOP_FILE_EXT, .comment = "stanza stop file created");
TEST_STORAGE_LIST( TEST_STORAGE_LIST(

View File

@ -236,8 +236,9 @@ testRun(void)
{ {
HRN_FORK_CHILD_BEGIN() HRN_FORK_CHILD_BEGIN()
{ {
lockInit(cfgOptionStr(cfgOptLockPath), STRDEF("999-ffffffff"), STRDEF("stanza1"), lockTypeBackup); lockInit(cfgOptionStr(cfgOptLockPath), STRDEF("999-ffffffff"));
TEST_RESULT_INT_NE(lockAcquireP(), -1, "create backup/expire lock"); TEST_RESULT_BOOL(
lockAcquireP(cmdLockFileName(STRDEF("stanza1"), lockTypeBackup)), true, "create backup/expire lock");
// Notify parent that lock has been acquired // Notify parent that lock has been acquired
HRN_FORK_CHILD_NOTIFY_PUT(); HRN_FORK_CHILD_NOTIFY_PUT();
@ -245,7 +246,7 @@ testRun(void)
// Wait for parent to allow release lock // Wait for parent to allow release lock
HRN_FORK_CHILD_NOTIFY_GET(); HRN_FORK_CHILD_NOTIFY_GET();
lockRelease(true); lockReleaseP();
} }
HRN_FORK_CHILD_END(); HRN_FORK_CHILD_END();
@ -428,9 +429,10 @@ testRun(void)
{ {
HRN_FORK_CHILD_BEGIN() HRN_FORK_CHILD_BEGIN()
{ {
lockInit(cfgOptionStr(cfgOptLockPath), STRDEF("777-afafafaf"), STRDEF("stanza1"), lockTypeBackup); String *lockFileName = cmdLockFileName(STRDEF("stanza1"), lockTypeBackup);
TEST_RESULT_INT_NE(lockAcquireP(), -1, "create backup/expire lock"); lockInit(cfgOptionStr(cfgOptLockPath), STRDEF("777-afafafaf"));
TEST_RESULT_VOID(lockWriteDataP(lockTypeBackup), "write lock data"); TEST_RESULT_BOOL(lockAcquireP(lockFileName), true, "create backup/expire lock");
TEST_RESULT_VOID(lockWriteP(lockFileName), "write lock data");
// Notify parent that lock has been acquired // Notify parent that lock has been acquired
HRN_FORK_CHILD_NOTIFY_PUT(); HRN_FORK_CHILD_NOTIFY_PUT();
@ -438,7 +440,7 @@ testRun(void)
// Wait for parent to allow release lock // Wait for parent to allow release lock
HRN_FORK_CHILD_NOTIFY_GET(); HRN_FORK_CHILD_NOTIFY_GET();
lockRelease(true); lockReleaseP();
} }
HRN_FORK_CHILD_END(); HRN_FORK_CHILD_END();
@ -1037,11 +1039,12 @@ testRun(void)
{ {
HRN_FORK_CHILD_BEGIN() HRN_FORK_CHILD_BEGIN()
{ {
lockInit(cfgOptionStr(cfgOptLockPath), STRDEF("999-ffffffff"), STRDEF("stanza2"), lockTypeBackup); String *lockFileName = cmdLockFileName(STRDEF("stanza2"), lockTypeBackup);
TEST_RESULT_INT_NE(lockAcquireP(), -1, "create backup/expire lock"); lockInit(cfgOptionStr(cfgOptLockPath), STRDEF("999-ffffffff"));
TEST_RESULT_BOOL(lockAcquireP(lockFileName), true, "create backup/expire lock");
TEST_RESULT_VOID( TEST_RESULT_VOID(
lockWriteDataP( lockWriteP(
lockTypeBackup, .percentComplete = VARUINT(4545), .sizeComplete = VARUINT64(1435765), lockFileName, .percentComplete = VARUINT(4545), .sizeComplete = VARUINT64(1435765),
.size = VARUINT64(3159000)), .size = VARUINT64(3159000)),
"write lock data"); "write lock data");
@ -1051,7 +1054,7 @@ testRun(void)
// Wait for parent to allow release lock // Wait for parent to allow release lock
HRN_FORK_CHILD_NOTIFY_GET(); HRN_FORK_CHILD_NOTIFY_GET();
lockRelease(true); lockReleaseP();
} }
HRN_FORK_CHILD_END(); HRN_FORK_CHILD_END();
@ -1480,9 +1483,10 @@ testRun(void)
{ {
HRN_FORK_CHILD_BEGIN() HRN_FORK_CHILD_BEGIN()
{ {
lockInit(cfgOptionStr(cfgOptLockPath), STRDEF("999-ffffffff"), STRDEF("stanza2"), lockTypeBackup); String *lockFileName = cmdLockFileName(STRDEF("stanza2"), lockTypeBackup);
TEST_RESULT_INT_NE(lockAcquireP(), -1, "create backup/expire lock"); lockInit(cfgOptionStr(cfgOptLockPath), STRDEF("999-ffffffff"));
TEST_RESULT_VOID(lockWriteDataP(lockTypeBackup, .percentComplete = VARUINT(5555)), "write lock data"); TEST_RESULT_BOOL(lockAcquireP(lockFileName), true, "create backup/expire lock");
TEST_RESULT_VOID(lockWriteP(lockFileName, .percentComplete = VARUINT(5555)), "write lock data");
// Notify parent that lock has been acquired // Notify parent that lock has been acquired
HRN_FORK_CHILD_NOTIFY_PUT(); HRN_FORK_CHILD_NOTIFY_PUT();
@ -1490,7 +1494,7 @@ testRun(void)
// Wait for parent to allow release lock // Wait for parent to allow release lock
HRN_FORK_CHILD_NOTIFY_GET(); HRN_FORK_CHILD_NOTIFY_GET();
lockRelease(true); lockReleaseP();
} }
HRN_FORK_CHILD_END(); HRN_FORK_CHILD_END();

View File

@ -0,0 +1,83 @@
/***********************************************************************************************************************************
Test Command Lock Handler
***********************************************************************************************************************************/
#include "storage/posix/storage.h"
#include "common/harnessConfig.h"
#include "common/harnessStorage.h"
/***********************************************************************************************************************************
Test Run
***********************************************************************************************************************************/
static void
testRun(void)
{
FUNCTION_HARNESS_VOID();
// Create default storage object for testing
Storage *storageTest = storagePosixNewP(TEST_PATH_STR, .write = true);
// *****************************************************************************************************************************
if (testBegin("cmdLock*()"))
{
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("acquire backup command lock");
StringList *argList = strLstNew();
hrnCfgArgRawZ(argList, cfgOptStanza, "test");
hrnCfgArgRawZ(argList, cfgOptPgPath, "/pg1");
hrnCfgArgRawZ(argList, cfgOptLockPath, TEST_PATH);
hrnCfgArgRawZ(argList, cfgOptRepoRetentionFull, "1");
HRN_CFG_LOAD(cfgCmdBackup, argList, .noStd = true);
TEST_RESULT_VOID(cmdLockAcquireP(), "acquire backup lock");
TEST_ERROR(cmdLockAcquireP(), AssertError, "lock is already held by this process");
TEST_STORAGE_LIST(storageTest, NULL, "test-backup.lock\n", .comment = "check lock file");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("write backup data");
TEST_RESULT_VOID(
cmdLockWriteP(.percentComplete = VARUINT(5555), .sizeComplete = VARUINT64(1754824), .size = VARUINT64(3159000)),
"write backup data");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("read backup data");
LockReadResult lockReadResult;
TEST_ASSIGN(lockReadResult, cmdLockRead(lockTypeBackup, STRDEF("test")), "read backup data");
TEST_RESULT_UINT(lockReadResult.status, lockReadStatusValid, "check status");
TEST_RESULT_UINT(varUInt(lockReadResult.data.percentComplete), 5555, "check percent complete");
TEST_RESULT_UINT(varUInt64(lockReadResult.data.sizeComplete), 1754824, "check size complete");
TEST_RESULT_UINT(varUInt64(lockReadResult.data.size), 3159000, "check size");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("lock release");
TEST_RESULT_VOID(cmdLockReleaseP(), "release locks");
TEST_STORAGE_LIST(storageTest, NULL, NULL, .comment = "check lock file does not exist");
TEST_ERROR(cmdLockReleaseP(), AssertError, "no lock is held by this process");
TEST_RESULT_VOID(cmdLockReleaseP(.returnOnNoLock = true), "ignore no lock held");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("acquire stanza-create command lock");
argList = strLstNew();
hrnCfgArgRawZ(argList, cfgOptStanza, "test");
hrnCfgArgRawZ(argList, cfgOptLockPath, TEST_PATH);
hrnCfgArgRawZ(argList, cfgOptPgPath, "/pg1");
HRN_CFG_LOAD(cfgCmdStanzaCreate, argList, .noStd = true);
TEST_RESULT_VOID(cmdLockAcquireP(), "acquire stanza-create lock");
TEST_STORAGE_LIST(storageTest, NULL, "test-archive.lock\ntest-backup.lock\n", .comment = "check lock file");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("lock release");
TEST_RESULT_VOID(cmdLockReleaseP(), "release locks");
TEST_STORAGE_LIST(storageTest, NULL, NULL, .comment = "check lock file does not exist");
}
FUNCTION_HARNESS_RETURN_VOID();
}

View File

@ -1,7 +1,6 @@
/*********************************************************************************************************************************** /***********************************************************************************************************************************
Test Lock Handler Test Lock Handler
***********************************************************************************************************************************/ ***********************************************************************************************************************************/
#include "common/time.h"
#include "storage/posix/storage.h" #include "storage/posix/storage.h"
#include "common/harnessFork.h" #include "common/harnessFork.h"
@ -16,103 +15,108 @@ testRun(void)
FUNCTION_HARNESS_VOID(); FUNCTION_HARNESS_VOID();
// Create default storage object for testing // Create default storage object for testing
Storage *storageTest = storagePosixNewP(TEST_PATH_STR, .write = true); const Storage *const storageTest = storagePosixNewP(TEST_PATH_STR, .write = true);
// ***************************************************************************************************************************** // *****************************************************************************************************************************
if (testBegin("lockAcquireFile() and lockReleaseFile()")) if (testBegin("lock*()"))
{ {
const String *archiveLock = STRDEF(TEST_PATH "/main-archive" LOCK_FILE_EXT); // -------------------------------------------------------------------------------------------------------------------------
int lockFdTest = -1; TEST_TITLE("acquire lock");
lockLocal.execId = STRDEF("1-test"); const String *const lockFile1Name = STRDEF("test1" LOCK_FILE_EXT);
TEST_ASSIGN(lockFdTest, lockAcquireFile(archiveLock, 0, true), "get lock"); TEST_RESULT_VOID(lockInit(TEST_PATH_STR, STRDEF("1-test")), "init lock module");
TEST_RESULT_BOOL(lockFdTest != -1, true, "lock succeeds"); TEST_RESULT_BOOL(lockAcquireP(lockFile1Name), true, "acquire lock");
TEST_RESULT_BOOL(storageExistsP(storageTest, archiveLock), true, "lock file was created"); TEST_ERROR_FMT(lockAcquireP(lockFile1Name), AssertError, "lock on file 'test1.lock' already held");
lockLocal.file[lockTypeArchive].fd = lockFdTest; TEST_ERROR(lockAcquireP(TEST_PATH_STR), LockAcquireError, "unable to acquire lock on file '" TEST_PATH "': Is a directory");
lockLocal.file[lockTypeArchive].name = strDup(archiveLock);
TEST_RESULT_VOID(lockWriteDataP(lockTypeArchive), "write lock data");
lockLocal.execId = STRDEF("2-test");
TEST_ERROR_FMT(
lockAcquireFile(archiveLock, 0, true), LockAcquireError,
"unable to acquire lock on file '%s': Resource temporarily unavailable\n"
"HINT: is another pgBackRest process running?",
strZ(archiveLock));
TEST_RESULT_BOOL(lockAcquireFile(archiveLock, 0, false) == -1, true, "lock is already held");
// ------------------------------------------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("acquire file lock on the same exec-id"); TEST_TITLE("read errors");
lockLocal.execId = STRDEF("1-test"); TEST_ERROR(
lockReadData(STRDEF(BOGUS_STR), 999999999), FileReadError,
"unable to read lock file 'BOGUS': unable to read from BOGUS: [9] Bad file descriptor");
TEST_RESULT_UINT(lockReadP(STRDEF(BOGUS_STR)).status, lockReadStatusMissing, "missing lock file");
TEST_RESULT_INT(lockAcquireFile(archiveLock, 0, true), -2, "allow lock with same exec id"); HRN_STORAGE_PUT_EMPTY(storageTest, "unlocked.lock");
TEST_RESULT_UINT(lockReadP(STRDEF("unlocked.lock"), .remove = true).status, lockReadStatusUnlocked, "unlocked lock file");
// ------------------------------------------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("fail file lock on the same exec-id when lock file is empty"); TEST_TITLE("read lock file (no data set)");
HRN_SYSTEM_FMT("echo '' > %s", strZ(archiveLock)); LockReadResult lockReadResult;
TEST_ASSIGN(lockReadResult, lockReadP(lockFile1Name), "read lock file");
TEST_ERROR_FMT( TEST_RESULT_UINT(lockReadResult.status, lockReadStatusValid, "check status");
lockAcquireFile(archiveLock, 0, true), LockAcquireError, TEST_RESULT_PTR(lockReadResult.data.percentComplete, NULL, "check percent complete is NULL");
"unable to acquire lock on file '%s': Resource temporarily unavailable\n" TEST_RESULT_PTR(lockReadResult.data.sizeComplete, NULL, "check size complete is NULL");
"HINT: is another pgBackRest process running?", TEST_RESULT_PTR(lockReadResult.data.size, NULL, "check size is NULL");
strZ(archiveLock));
// ------------------------------------------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------------------------------------------
TEST_RESULT_VOID(lockReleaseFile(lockFdTest, archiveLock), "release lock"); TEST_TITLE("invalidate lock file by truncating");
TEST_RESULT_VOID(lockReleaseFile(lockFdTest, archiveLock), "release lock"); LockFile *lockFile1 = lstFind(lockLocal.lockList, &lockFile1Name);
TEST_RESULT_BOOL(storageExistsP(storageTest, archiveLock), false, "lock file was removed"); THROW_ON_SYS_ERROR_FMT(
TEST_RESULT_VOID(lockReleaseFile(lockFdTest, archiveLock), "release lock again without error"); ftruncate(lockFile1->fd, 0) == -1, FileWriteError, "unable to truncate '" TEST_PATH "/%s'", strZ(lockFile1Name));
TEST_RESULT_UINT(lockReadP(lockFile1Name).status, lockReadStatusInvalid, "invalid lock file");
// ------------------------------------------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------------------------------------------
String *subPathLock = strNewZ(TEST_PATH "/sub1/sub2/db-backup" LOCK_FILE_EXT); TEST_TITLE("write errors");
TEST_ASSIGN(lockFdTest, lockAcquireFile(subPathLock, 0, true), "get lock in subpath"); TEST_ERROR(lockWriteP(STRDEF(BOGUS_STR)), AssertError, "lock file 'BOGUS' not found");
TEST_RESULT_BOOL(storageExistsP(storageTest, subPathLock), true, "lock file was created");
TEST_RESULT_BOOL(lockFdTest != -1, true, "lock succeeds");
TEST_RESULT_VOID(lockReleaseFile(lockFdTest, subPathLock), "release lock");
TEST_RESULT_BOOL(storageExistsP(storageTest, subPathLock), false, "lock file was removed");
// ------------------------------------------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------------------------------------------
String *dirLock = strNewZ(TEST_PATH "/dir" LOCK_FILE_EXT); TEST_TITLE("write lock file");
HRN_SYSTEM_FMT("mkdir -p %s", strZ(dirLock)); TEST_RESULT_VOID(
lockWriteP(
lockFile1Name, .percentComplete = VARUINT(5555), .sizeComplete = VARUINT64(1754824), .size = VARUINT64(3159000)),
"write lock data");
TEST_ERROR_FMT( // -------------------------------------------------------------------------------------------------------------------------
lockAcquireFile(dirLock, 0, true), LockAcquireError, "unable to acquire lock on file '%s': Is a directory", TEST_TITLE("read lock file (data set)");
strZ(dirLock));
TEST_ASSIGN(lockReadResult, lockReadP(lockFile1Name), "read lock file");
TEST_RESULT_UINT(lockReadResult.status, lockReadStatusValid, "check status");
TEST_RESULT_UINT(varUInt(lockReadResult.data.percentComplete), 5555, "check percent complete");
TEST_RESULT_UINT(varUInt64(lockReadResult.data.sizeComplete), 1754824, "check size complete");
TEST_RESULT_UINT(varUInt64(lockReadResult.data.size), 3159000, "check size");
// ------------------------------------------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("permissions error throw regardless of failOnLock"); TEST_TITLE("permissions error throw regardless of failOnLock");
String *noPermLock = strNewZ(TEST_PATH "/noperm/noperm"); const String *const noPermLock = strNewZ(TEST_PATH "/noperm/noperm");
HRN_SYSTEM_FMT("mkdir -p %s", strZ(strPath(noPermLock))); HRN_SYSTEM_FMT("mkdir -p %s", strZ(strPath(noPermLock)));
HRN_SYSTEM_FMT("chmod 000 %s", strZ(strPath(noPermLock))); HRN_SYSTEM_FMT("chmod 000 %s", strZ(strPath(noPermLock)));
TEST_ERROR_FMT( TEST_ERROR_FMT(
lockAcquireFile(noPermLock, 100, true), LockAcquireError, lockAcquireP(noPermLock), LockAcquireError,
"unable to acquire lock on file '%s': Permission denied\n" "unable to acquire lock on file '%s': Permission denied\n"
"HINT: does '" TEST_USER ":" TEST_GROUP "' running pgBackRest have permissions on the '%s' file?", "HINT: does '" TEST_USER ":" TEST_GROUP "' running pgBackRest have permissions on the '%s' file?",
strZ(noPermLock), strZ(noPermLock)); strZ(noPermLock), strZ(noPermLock));
TEST_ERROR_FMT( TEST_ERROR_FMT(
lockAcquireFile(noPermLock, 100, false), LockAcquireError, lockAcquireP(noPermLock, .returnOnNoLock = true), LockAcquireError,
"unable to acquire lock on file '%s': Permission denied\n" "unable to acquire lock on file '%s': Permission denied\n"
"HINT: does '" TEST_USER ":" TEST_GROUP "' running pgBackRest have permissions on the '%s' file?", "HINT: does '" TEST_USER ":" TEST_GROUP "' running pgBackRest have permissions on the '%s' file?",
strZ(noPermLock), strZ(noPermLock)); strZ(noPermLock), strZ(noPermLock));
// ------------------------------------------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------------------------------------------
String *backupLock = strNewZ(TEST_PATH "/main-backup" LOCK_FILE_EXT); TEST_TITLE("acquire file lock on the same exec-id");
const String *const lockFileExecName = STRDEF("sub/exec" LOCK_FILE_EXT);
const String *const lockFileExec2Name = STRDEF("sub/exec2" LOCK_FILE_EXT);
HRN_FORK_BEGIN() HRN_FORK_BEGIN()
{ {
HRN_FORK_CHILD_BEGIN() HRN_FORK_CHILD_BEGIN()
{ {
TEST_RESULT_INT_NE(lockAcquireFile(backupLock, 0, true), -1, "lock on fork"); TEST_RESULT_BOOL(lockAcquireP(lockFileExecName), true, "lock on fork");
TEST_RESULT_BOOL(lockAcquireP(lockFileExec2Name), true, "lock on fork");
// Corrupt exec2.lock
HRN_SYSTEM_FMT("echo '' > " TEST_PATH "/%s", strZ(lockFileExec2Name));
// Notify parent that lock has been acquired // Notify parent that lock has been acquired
HRN_FORK_CHILD_NOTIFY_PUT(); HRN_FORK_CHILD_NOTIFY_PUT();
@ -130,237 +134,22 @@ testRun(void)
lockLocal.execId = STRDEF("2-test"); lockLocal.execId = STRDEF("2-test");
TEST_ERROR_FMT( TEST_ERROR_FMT(
lockAcquireFile(backupLock, 0, true), LockAcquireError, lockAcquireP(lockFileExecName), LockAcquireError,
"unable to acquire lock on file '%s': Resource temporarily unavailable\n" "unable to acquire lock on file '" TEST_PATH "/%s': Resource temporarily unavailable\n"
"HINT: is another pgBackRest process running?", "HINT: is another pgBackRest process running?",
strZ(backupLock)); strZ(lockFileExecName));
TEST_RESULT_VOID(lockAcquireFile(backupLock, 0, false), "success when failOnLock = false"); TEST_RESULT_BOOL(lockAcquireP(lockFileExecName, .returnOnNoLock = true), false, "fail but return");
// Notify child to release lock lockLocal.execId = STRDEF("1-test");
HRN_FORK_PARENT_NOTIFY_PUT(0);
}
HRN_FORK_PARENT_END();
}
HRN_FORK_END();
}
// ***************************************************************************************************************************** TEST_RESULT_BOOL(lockAcquireP(lockFileExecName), true, "succeed on same execId");
if (testBegin("lockAcquireP(), lockRelease()"))
{
const String *stanza = STRDEF("test");
String *archiveLockFile = strNewFmt(TEST_PATH "/%s-archive" LOCK_FILE_EXT, strZ(stanza));
String *backupLockFile = strNewFmt(TEST_PATH "/%s-backup" LOCK_FILE_EXT, strZ(stanza));
int lockFdTest = -1;
// ------------------------------------------------------------------------------------------------------------------------- TEST_ERROR_FMT(
TEST_ERROR(lockRelease(true), AssertError, "no lock is held by this process"); lockAcquireP(lockFileExec2Name), LockAcquireError,
TEST_RESULT_BOOL(lockRelease(false), false, "release when there is no lock"); "unable to acquire lock on file '" TEST_PATH "/%s': Resource temporarily unavailable\n"
"HINT: is another pgBackRest process running?",
// ------------------------------------------------------------------------------------------------------------------------- strZ(lockFileExec2Name));
lockInit(TEST_PATH_STR, STRDEF("1-test"), stanza, lockTypeArchive);
TEST_ASSIGN(lockFdTest, lockAcquireFile(archiveLockFile, 0, true), "archive lock by file");
TEST_RESULT_BOOL(lockAcquireP(.returnOnNoLock = true), false, "archive already locked");
TEST_ERROR_FMT(
lockAcquireP(), LockAcquireError,
"unable to acquire lock on file '%s': Resource temporarily unavailable\n"
"HINT: is another pgBackRest process running?",
strZ(archiveLockFile));
lockLocal.type = lockTypeAll;
TEST_ERROR_FMT(
lockAcquireP(), LockAcquireError,
"unable to acquire lock on file '%s': Resource temporarily unavailable\n"
"HINT: is another pgBackRest process running?", strZ(archiveLockFile));
TEST_RESULT_VOID(lockReleaseFile(lockFdTest, archiveLockFile), "release lock");
// -------------------------------------------------------------------------------------------------------------------------
lockInit(TEST_PATH_STR, STRDEF("1-test"), stanza, lockTypeArchive);
TEST_RESULT_BOOL(lockAcquireP(), true, "archive lock");
TEST_RESULT_BOOL(storageExistsP(storageTest, archiveLockFile), true, "archive lock file was created");
TEST_ERROR(lockAcquireP(.returnOnNoLock = true), AssertError, "lock is already held by this process");
TEST_RESULT_VOID(lockRelease(true), "release archive lock");
// -------------------------------------------------------------------------------------------------------------------------
lockInit(TEST_PATH_STR, STRDEF("1-test"), stanza, lockTypeBackup);
TEST_ASSIGN(lockFdTest, lockAcquireFile(backupLockFile, 0, true), "backup lock by file");
TEST_ERROR_FMT(
lockAcquireP(), LockAcquireError,
"unable to acquire lock on file '%s': Resource temporarily unavailable\n"
"HINT: is another pgBackRest process running?", strZ(backupLockFile));
lockLocal.type = lockTypeAll;
TEST_ERROR_FMT(
lockAcquireP(), LockAcquireError,
"unable to acquire lock on file '%s': Resource temporarily unavailable\n"
"HINT: is another pgBackRest process running?", strZ(backupLockFile));
TEST_RESULT_VOID(lockReleaseFile(lockFdTest, archiveLockFile), "release archive lock");
TEST_RESULT_VOID(lockReleaseFile(lockFdTest, backupLockFile), "release backup lock");
// -------------------------------------------------------------------------------------------------------------------------
lockInit(TEST_PATH_STR, STRDEF("1-test"), stanza, lockTypeAll);
TEST_RESULT_BOOL(lockAcquireP(), true, "all lock");
TEST_RESULT_BOOL(storageExistsP(storageTest, archiveLockFile), true, "archive lock file was created");
TEST_RESULT_BOOL(storageExistsP(storageTest, backupLockFile), true, "backup lock file was created");
// Seek to start of file before read
THROW_ON_SYS_ERROR_FMT(
lseek(lockLocal.file[lockTypeBackup].fd, 0, SEEK_SET) == -1, FileOpenError, STORAGE_ERROR_READ_SEEK, (uint64_t)0,
strZ(lockLocal.file[lockTypeBackup].name));
TEST_RESULT_STR(
lockReadFileData(backupLockFile, lockLocal.file[lockTypeBackup].fd).execId, STRDEF("1-test"), "verify execId");
TEST_RESULT_VOID(lockWriteDataP(lockTypeBackup), "write lock data");
THROW_ON_SYS_ERROR_FMT(
lseek(lockLocal.file[lockTypeBackup].fd, 0, SEEK_SET) == -1, FileOpenError, STORAGE_ERROR_READ_SEEK, (uint64_t)0,
strZ(lockLocal.file[lockTypeBackup].name));
TEST_RESULT_PTR(
lockReadFileData(backupLockFile, lockLocal.file[lockTypeBackup].fd).percentComplete, NULL, "verify percentComplete");
TEST_RESULT_VOID(
lockWriteDataP(
lockTypeBackup, .percentComplete = VARUINT(5555), .sizeComplete = VARUINT64(1754824), .size = VARUINT64(3159000)),
"write lock data");
THROW_ON_SYS_ERROR_FMT(
lseek(lockLocal.file[lockTypeBackup].fd, 0, SEEK_SET) == -1, FileOpenError, STORAGE_ERROR_READ_SEEK, (uint64_t)0,
strZ(lockLocal.file[lockTypeBackup].name));
LockData lockDataResult = lockReadFileData(backupLockFile, lockLocal.file[lockTypeBackup].fd);
TEST_RESULT_UINT(varUInt(lockDataResult.percentComplete), 5555, "verify percentComplete");
TEST_RESULT_UINT(varUInt64(lockDataResult.sizeComplete), 1754824, "verify sizeProgress");
TEST_RESULT_UINT(varUInt64(lockDataResult.size), 3159000, "verify sizeTotal");
// The size/sizeComplete values should be large enough to overflow a uint32 to ensure uint64 is used for read/write
TEST_RESULT_VOID(
lockWriteDataP(
lockTypeBackup, .percentComplete = VARUINT(8888), .sizeComplete = VARUINT64(3223802441008),
.size = VARUINT64(6126216975438)),
"write lock data");
THROW_ON_SYS_ERROR_FMT(
lseek(lockLocal.file[lockTypeBackup].fd, 0, SEEK_SET) == -1, FileOpenError, STORAGE_ERROR_READ_SEEK, (uint64_t)0,
strZ(lockLocal.file[lockTypeBackup].name));
lockDataResult = lockReadFileData(backupLockFile, lockLocal.file[lockTypeBackup].fd);
TEST_RESULT_UINT(varUInt(lockDataResult.percentComplete), 8888, "verify percentComplete");
TEST_RESULT_UINT(varUInt64(lockDataResult.sizeComplete), 3223802441008, "verify sizeProgress");
TEST_RESULT_UINT(varUInt64(lockDataResult.size), 6126216975438, "verify sizeTotal");
TEST_ERROR(
lockAcquireP(.returnOnNoLock = true), AssertError,
"assertion '!param.returnOnNoLock || lockLocal.type != lockTypeAll' failed");
TEST_RESULT_VOID(lockRelease(true), "release all locks");
TEST_RESULT_BOOL(storageExistsP(storageTest, archiveLockFile), false, "archive lock file was removed");
TEST_RESULT_BOOL(storageExistsP(storageTest, backupLockFile), false, "backup lock file was removed");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("acquire lock on the same exec-id and release");
lockInit(TEST_PATH_STR, STRDEF("1-test"), stanza, lockTypeBackup);
TEST_RESULT_BOOL(lockAcquireP(), true, "backup lock");
// Make it look there is no lock
lockFdTest = lockLocal.file[lockTypeBackup].fd;
String *lockFileTest = strDup(lockLocal.file[lockTypeBackup].name);
lockLocal.held = false;
TEST_RESULT_BOOL(lockAcquireP(), true, "backup lock again");
TEST_RESULT_VOID(lockRelease(true), "release backup lock");
// Release lock manually
lockReleaseFile(lockFdTest, lockFileTest);
}
// *****************************************************************************************************************************
if (testBegin("lockRead*()"))
{
TEST_TITLE("missing lock file");
TEST_RESULT_UINT(lockReadFileP(STRDEF(TEST_PATH "/missing.lock")).status, lockReadStatusMissing, "lock read");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("unlocked file");
HRN_STORAGE_PUT_EMPTY(storageTest, "unlocked.lock");
TEST_RESULT_UINT(lockReadFileP(STRDEF(TEST_PATH "/unlocked.lock")).status, lockReadStatusUnlocked, "lock read");
TEST_STORAGE_LIST(storageTest, NULL, "unlocked.lock\n", .remove = true);
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("execId && pid valid file");
lockInit(TEST_PATH_STR, STRDEF("1-test"), STRDEF("1-test"), lockTypeBackup);
TEST_RESULT_BOOL(lockAcquireP(), true, "backup lock");
TEST_RESULT_BOOL(storageExistsP(storageTest, lockLocal.file[lockTypeBackup].name), true, "backup lock file was created");
// Overwrite backup lock file with execId of 1-test and pid of 12345
THROW_ON_SYS_ERROR_FMT(
lseek(lockLocal.file[lockTypeBackup].fd, 0, SEEK_SET) == -1, FileOpenError, STORAGE_ERROR_READ_SEEK, (uint64_t)0,
strZ(lockLocal.file[lockTypeBackup].name));
TEST_RESULT_INT(ftruncate(lockLocal.file[lockTypeBackup].fd, 0), 0, "truncate lockLocal.file[lockTypeBackup].fd");
IoWrite *const write = ioFdWriteNewOpen(lockLocal.file[lockTypeBackup].name, lockLocal.file[lockTypeBackup].fd, 0);
ioCopyP(ioBufferReadNewOpen(BUFSTRDEF("{\"execId\":\"1-test\",\"pid\":12345}")), write);
ioWriteClose(write);
// Seek to start of file before read
THROW_ON_SYS_ERROR_FMT(
lseek(lockLocal.file[lockTypeBackup].fd, 0, SEEK_SET) == -1, FileOpenError, STORAGE_ERROR_READ_SEEK, (uint64_t)0,
strZ(lockLocal.file[lockTypeBackup].name));
LockReadResult result = {0};
TEST_ASSIGN(result, lockReadFileP(lockLocal.file[lockTypeBackup].name), "lock read");
TEST_RESULT_STR(result.data.execId, STRDEF("1-test"), "lock read execId 1-test");
TEST_RESULT_UINT((uint64_t)result.data.processId, 12345, "lock read pid 12345");
TEST_RESULT_VOID(lockRelease(true), "release backup lock");
// -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("invalid locked file");
HRN_FORK_BEGIN()
{
HRN_FORK_CHILD_BEGIN()
{
lockInit(TEST_PATH_STR, STRDEF("1-test"), STRDEF("test"), lockTypeBackup);
TEST_RESULT_BOOL(lockAcquireP(), true, "acquire lock");
// Overwrite lock file with bogus data
THROW_ON_SYS_ERROR_FMT(
lseek(lockLocal.file[lockTypeBackup].fd, 0, SEEK_SET) == -1, FileOpenError, STORAGE_ERROR_READ_SEEK,
(uint64_t)0, strZ(lockLocal.file[lockTypeBackup].name));
IoWrite *const write = ioFdWriteNewOpen(lockLocal.file[lockTypeBackup].name, lockLocal.file[lockTypeBackup].fd, 0);
ioCopyP(ioBufferReadNewOpen(BUFSTRDEF("BOGUS")), write);
ioWriteClose(write);
// Notify parent that lock has been acquired
HRN_FORK_CHILD_NOTIFY_PUT();
// Wait for parent to allow release lock
HRN_FORK_CHILD_NOTIFY_GET();
}
HRN_FORK_CHILD_END();
HRN_FORK_PARENT_BEGIN()
{
// Wait for child to acquire lock
HRN_FORK_PARENT_NOTIFY_GET(0);
TEST_RESULT_UINT(
lockReadFileP(STRDEF(TEST_PATH "/test-backup.lock"), .remove = true).status, lockReadStatusInvalid,
"lock read");
TEST_STORAGE_LIST(storageTest, NULL, NULL);
// Notify child to release lock // Notify child to release lock
HRN_FORK_PARENT_NOTIFY_PUT(0); HRN_FORK_PARENT_NOTIFY_PUT(0);
@ -370,43 +159,11 @@ testRun(void)
HRN_FORK_END(); HRN_FORK_END();
// ------------------------------------------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("valid locked file"); TEST_TITLE("lock release");
HRN_FORK_BEGIN() TEST_RESULT_VOID(lockReleaseP(), "release locks");
{ TEST_ERROR(lockReleaseP(), AssertError, "no lock is held by this process");
HRN_FORK_CHILD_BEGIN() TEST_RESULT_VOID(lockReleaseP(.returnOnNoLock = true), "ignore no lock held");
{
lockInit(TEST_PATH_STR, STRDEF("1-test"), STRDEF("test"), lockTypeBackup);
TEST_RESULT_BOOL(lockAcquireP(), true, "acquire lock");
// Notify parent that lock has been acquired
HRN_FORK_CHILD_NOTIFY_PUT();
// Wait for parent to allow release lock
HRN_FORK_CHILD_NOTIFY_GET();
}
HRN_FORK_CHILD_END();
HRN_FORK_PARENT_BEGIN()
{
// Wait for child to acquire lock
HRN_FORK_PARENT_NOTIFY_GET(0);
LockReadResult result = {0};
TEST_ASSIGN(result, lockRead(TEST_PATH_STR, STRDEF("test"), lockTypeBackup), "lock read");
TEST_RESULT_BOOL(result.data.processId != 0, true, "check processId");
TEST_RESULT_STR_Z(result.data.execId, "1-test", "check execId");
TEST_STORAGE_LIST(storageTest, NULL, "test-backup.lock\n");
// Notify child to release lock
HRN_FORK_PARENT_NOTIFY_PUT(0);
}
HRN_FORK_PARENT_END();
}
HRN_FORK_END();
} }
FUNCTION_HARNESS_RETURN_VOID(); FUNCTION_HARNESS_RETURN_VOID();

View File

@ -755,7 +755,7 @@ testRun(void)
hrnCfgArgRawZ(argList, cfgOptLogLevelStderr, CFGOPTVAL_ARCHIVE_MODE_OFF_Z); hrnCfgArgRawZ(argList, cfgOptLogLevelStderr, CFGOPTVAL_ARCHIVE_MODE_OFF_Z);
strLstAddZ(argList, CFGCMD_BACKUP); strLstAddZ(argList, CFGCMD_BACKUP);
TEST_RESULT_VOID(cfgLoad(strLstSize(argList), strLstPtr(argList)), "load config for backup"); TEST_RESULT_VOID(cfgLoad(strLstSize(argList), strLstPtr(argList)), "load config for backup");
lockRelease(true); cmdLockReleaseP();
// Only the error case is tested here, success is tested in cfgLoad() // Only the error case is tested here, success is tested in cfgLoad()
TEST_RESULT_VOID(cfgLoadLogFile(), "attempt to open bogus log file"); TEST_RESULT_VOID(cfgLoadLogFile(), "attempt to open bogus log file");
@ -777,7 +777,7 @@ testRun(void)
TEST_RESULT_VOID(cfgLoad(strLstSize(argList), strLstPtr(argList)), "load config"); TEST_RESULT_VOID(cfgLoad(strLstSize(argList), strLstPtr(argList)), "load config");
TEST_RESULT_VOID(storageRepoWrite(), "check writable storage"); TEST_RESULT_VOID(storageRepoWrite(), "check writable storage");
lockRelease(true); cmdLockReleaseP();
// ------------------------------------------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("dry-run valid, dry-run"); TEST_TITLE("dry-run valid, dry-run");
@ -787,7 +787,7 @@ testRun(void)
TEST_RESULT_VOID(cfgLoad(strLstSize(argList), strLstPtr(argList)), "load config"); TEST_RESULT_VOID(cfgLoad(strLstSize(argList), strLstPtr(argList)), "load config");
TEST_ERROR( TEST_ERROR(
storageRepoWrite(), AssertError, "unable to get writable storage in dry-run mode or before dry-run is initialized"); storageRepoWrite(), AssertError, "unable to get writable storage in dry-run mode or before dry-run is initialized");
lockRelease(true); cmdLockReleaseP();
// ------------------------------------------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("command does not have umask and disables keep-alives"); TEST_TITLE("command does not have umask and disables keep-alives");
@ -928,7 +928,7 @@ testRun(void)
TEST_RESULT_INT(socketLocal.tcpKeepAliveIdle, 2222, "check socketLocal.tcpKeepAliveIdle"); TEST_RESULT_INT(socketLocal.tcpKeepAliveIdle, 2222, "check socketLocal.tcpKeepAliveIdle");
TEST_RESULT_INT(socketLocal.tcpKeepAliveInterval, 888, "check socketLocal.tcpKeepAliveInterval"); TEST_RESULT_INT(socketLocal.tcpKeepAliveInterval, 888, "check socketLocal.tcpKeepAliveInterval");
lockRelease(true); cmdLockReleaseP();
// ------------------------------------------------------------------------------------------------------------------------- // -------------------------------------------------------------------------------------------------------------------------
TEST_TITLE("local command opens log file with special filename"); TEST_TITLE("local command opens log file with special filename");
@ -1011,7 +1011,7 @@ testRun(void)
TEST_RESULT_VOID(cfgLoad(strLstSize(argList), strLstPtr(argList)), "open log file"); TEST_RESULT_VOID(cfgLoad(strLstSize(argList), strLstPtr(argList)), "open log file");
TEST_RESULT_INT(lstat(TEST_PATH "/test-archive-get-async.log", &statLog), 0, "check log file exists"); TEST_RESULT_INT(lstat(TEST_PATH "/test-archive-get-async.log", &statLog), 0, "check log file exists");
lockRelease(true); cmdLockReleaseP();
} }
FUNCTION_HARNESS_RETURN_VOID(); FUNCTION_HARNESS_RETURN_VOID();