diff --git a/doc/xml/release.xml b/doc/xml/release.xml index a0a759e87..f56d57ed9 100644 --- a/doc/xml/release.xml +++ b/doc/xml/release.xml @@ -13,6 +13,17 @@ + + + + + + + +

The start/stop commands are implemented entirely in C.

+
+
+
diff --git a/lib/pgBackRest/Main.pm b/lib/pgBackRest/Main.pm index ba2083d8f..3a8c39c2d 100644 --- a/lib/pgBackRest/Main.pm +++ b/lib/pgBackRest/Main.pm @@ -117,17 +117,6 @@ sub main $iResult = new pgBackRest::Check::Check()->process(); } - - # Process start/stop commands - # -------------------------------------------------------------------------------------------------------------------------- - elsif (cfgCommandTest(CFGCMD_START)) - { - lockStart(); - } - elsif (cfgCommandTest(CFGCMD_STOP)) - { - lockStop(); - } else { # Check that the repo path exists diff --git a/src/Makefile.in b/src/Makefile.in index 418849cd1..b4e038b51 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -61,6 +61,8 @@ SRCS = \ command/info/info.c \ command/command.c \ command/control/common.c \ + command/control/start.c \ + command/control/stop.c \ command/local/local.c \ command/restore/file.c \ command/restore/protocol.c \ @@ -244,6 +246,12 @@ command/command.o: command/command.c build.auto.h common/assert.h common/debug.h command/control/common.o: command/control/common.c build.auto.h command/control/common.h common/assert.h common/debug.h common/error.auto.h common/error.h common/io/filter/filter.h common/io/filter/group.h common/io/read.h common/io/write.h common/lock.h common/log.h common/logLevel.h common/memContext.h common/stackTrace.h common/time.h common/type/buffer.h common/type/convert.h common/type/keyValue.h common/type/string.h common/type/stringList.h common/type/variant.h common/type/variantList.h config/config.auto.h config/config.h config/define.auto.h config/define.h storage/helper.h storage/info.h storage/read.h storage/storage.h storage/write.h $(CC) $(CPPFLAGS) $(CFLAGS) $(CMAKE) -c command/control/common.c -o command/control/common.o +command/control/start.o: command/control/start.c build.auto.h command/control/common.h common/assert.h common/debug.h common/error.auto.h common/error.h common/io/filter/filter.h common/io/filter/group.h common/io/read.h common/io/write.h common/lock.h common/log.h common/logLevel.h common/memContext.h common/stackTrace.h common/time.h common/type/buffer.h common/type/convert.h common/type/keyValue.h common/type/string.h common/type/stringList.h common/type/variant.h common/type/variantList.h config/config.auto.h config/config.h config/define.auto.h config/define.h storage/helper.h storage/info.h storage/read.h storage/storage.h storage/write.h + $(CC) $(CPPFLAGS) $(CFLAGS) $(CMAKE) -c command/control/start.c -o command/control/start.o + +command/control/stop.o: command/control/stop.c build.auto.h command/control/common.h common/assert.h common/debug.h common/error.auto.h common/error.h common/io/filter/filter.h common/io/filter/group.h common/io/read.h common/io/read.intern.h common/io/write.h common/io/write.intern.h common/lock.h common/log.h common/logLevel.h common/memContext.h common/stackTrace.h common/time.h common/type/buffer.h common/type/convert.h common/type/keyValue.h common/type/string.h common/type/stringList.h common/type/variant.h common/type/variantList.h config/config.auto.h config/config.h config/define.auto.h config/define.h storage/helper.h storage/info.h storage/read.h storage/read.intern.h storage/storage.h storage/storage.intern.h storage/write.h storage/write.intern.h version.h + $(CC) $(CPPFLAGS) $(CFLAGS) $(CMAKE) -c command/control/stop.c -o command/control/stop.o + command/expire/expire.o: command/expire/expire.c build.auto.h command/archive/common.h command/backup/common.h common/assert.h common/crypto/common.h common/debug.h common/error.auto.h common/error.h common/ini.h common/io/filter/filter.h common/io/filter/group.h common/io/read.h common/io/write.h common/lock.h common/log.h common/logLevel.h common/memContext.h common/regExp.h common/stackTrace.h common/time.h common/type/buffer.h common/type/convert.h common/type/keyValue.h common/type/list.h common/type/string.h common/type/stringList.h common/type/variant.h common/type/variantList.h config/config.auto.h config/config.h config/define.auto.h config/define.h info/info.h info/infoArchive.h info/infoBackup.h info/infoManifest.h info/infoPg.h storage/helper.h storage/info.h storage/read.h storage/storage.h storage/write.h $(CC) $(CPPFLAGS) $(CFLAGS) $(CMAKE) -c command/expire/expire.c -o command/expire/expire.o @@ -457,7 +465,7 @@ info/infoManifest.o: info/infoManifest.c build.auto.h common/error.auto.h common info/infoPg.o: info/infoPg.c build.auto.h common/assert.h common/crypto/common.h common/debug.h common/error.auto.h common/error.h common/ini.h common/io/filter/filter.h common/io/filter/group.h common/io/read.h common/io/write.h common/log.h common/logLevel.h common/macro.h common/memContext.h common/object.h common/stackTrace.h common/time.h common/type/buffer.h common/type/convert.h common/type/json.h common/type/keyValue.h common/type/list.h common/type/string.h common/type/stringList.h common/type/variant.h common/type/variantList.h info/info.h info/infoPg.h postgres/interface.h postgres/version.h storage/helper.h storage/info.h storage/read.h storage/storage.h storage/write.h $(CC) $(CPPFLAGS) $(CFLAGS) $(CMAKE) -c info/infoPg.c -o info/infoPg.o -main.o: main.c build.auto.h command/archive/get/get.h command/archive/push/push.h command/check/check.h command/command.h command/expire/expire.h command/help/help.h command/info/info.h command/local/local.h command/remote/remote.h command/storage/list.h common/assert.h common/debug.h common/error.auto.h common/error.h common/exit.h common/lock.h common/log.h common/logLevel.h common/memContext.h common/stackTrace.h common/time.h common/type/buffer.h common/type/convert.h common/type/keyValue.h common/type/string.h common/type/stringList.h common/type/variant.h common/type/variantList.h config/config.auto.h config/config.h config/define.auto.h config/define.h config/load.h perl/exec.h postgres/interface.h version.h +main.o: main.c build.auto.h command/archive/get/get.h command/archive/push/push.h command/check/check.h command/command.h command/control/start.h command/control/stop.h command/expire/expire.h command/help/help.h command/info/info.h command/local/local.h command/remote/remote.h command/storage/list.h common/assert.h common/debug.h common/error.auto.h common/error.h common/exit.h common/lock.h common/log.h common/logLevel.h common/memContext.h common/stackTrace.h common/time.h common/type/buffer.h common/type/convert.h common/type/keyValue.h common/type/string.h common/type/stringList.h common/type/variant.h common/type/variantList.h config/config.auto.h config/config.h config/define.auto.h config/define.h config/load.h perl/exec.h postgres/interface.h version.h $(CC) $(CPPFLAGS) $(CFLAGS) $(CMAKE) -c main.c -o main.o perl/config.o: perl/config.c build.auto.h common/assert.h common/debug.h common/error.auto.h common/error.h common/lock.h common/log.h common/logLevel.h common/memContext.h common/stackTrace.h common/time.h common/type/buffer.h common/type/convert.h common/type/json.h common/type/keyValue.h common/type/string.h common/type/stringList.h common/type/variant.h common/type/variantList.h config/config.auto.h config/config.h config/define.auto.h config/define.h diff --git a/src/command/control/common.c b/src/command/control/common.c index aaf03bb0a..46c21afea 100644 --- a/src/command/control/common.c +++ b/src/command/control/common.c @@ -18,7 +18,8 @@ lockStopFileName(const String *stanza) FUNCTION_TEST_PARAM(STRING, stanza); FUNCTION_TEST_END(); - String *result = strNewFmt("%s/%s.stop", strPtr(cfgOptionStr(cfgOptLockPath)), stanza != NULL ? strPtr(stanza) : "all"); + String *result = strNewFmt( + "%s/%s" STOP_FILE_EXT, strPtr(cfgOptionStr(cfgOptLockPath)), stanza != NULL ? strPtr(stanza) : "all"); FUNCTION_TEST_RETURN(result); } diff --git a/src/command/control/common.h b/src/command/control/common.h index 9f8679689..0165fdc47 100644 --- a/src/command/control/common.h +++ b/src/command/control/common.h @@ -6,6 +6,11 @@ Common Handler for Control Commands #include "common/type/string.h" +/*********************************************************************************************************************************** +Constants +***********************************************************************************************************************************/ +#define STOP_FILE_EXT ".stop" + /*********************************************************************************************************************************** Functions ***********************************************************************************************************************************/ diff --git a/src/command/control/start.c b/src/command/control/start.c new file mode 100644 index 000000000..592180115 --- /dev/null +++ b/src/command/control/start.c @@ -0,0 +1,37 @@ +/*********************************************************************************************************************************** +Start Command +***********************************************************************************************************************************/ +#include "build.auto.h" + +#include "command/control/common.h" +#include "common/debug.h" +#include "config/config.h" +#include "storage/helper.h" +#include "storage/storage.h" + +void +cmdStart(void) +{ + FUNCTION_LOG_VOID(logLevelDebug); + + MEM_CONTEXT_TEMP_BEGIN() + { + // Remove the stop file so processes can run + String *stopFile = lockStopFileName(cfgOptionStr(cfgOptStanza)); + + // If the stop file exists, then remove it + if (storageExistsNP(storageLocal(), stopFile)) + { + // If the file cannot be removed, storageRemove() will throw an error if the error is not ENOENT + storageRemoveNP(storageLocalWrite(), stopFile); + } + else + { + LOG_WARN("stop file does not exist%s", + (cfgOptionTest(cfgOptStanza) ? strPtr(strNewFmt(" for stanza %s", strPtr(cfgOptionStr(cfgOptStanza)))) : "")); + } + } + MEM_CONTEXT_TEMP_END(); + + FUNCTION_LOG_RETURN_VOID(); +} diff --git a/src/command/control/start.h b/src/command/control/start.h new file mode 100644 index 000000000..40e3e0257 --- /dev/null +++ b/src/command/control/start.h @@ -0,0 +1,12 @@ +/*********************************************************************************************************************************** +Start Command +***********************************************************************************************************************************/ +#ifndef COMMAND_CONTROL_START_H +#define COMMAND_CONTROL_START_H + +/*********************************************************************************************************************************** +Functions +***********************************************************************************************************************************/ +void cmdStart(void); + +#endif diff --git a/src/command/control/stop.c b/src/command/control/stop.c new file mode 100644 index 000000000..b03c140ce --- /dev/null +++ b/src/command/control/stop.c @@ -0,0 +1,108 @@ +/*********************************************************************************************************************************** +Stop Command +***********************************************************************************************************************************/ +#include "build.auto.h" + +#include +#include +#include +#include +#include +#include + +#include "command/control/common.h" +#include "common/debug.h" +#include "common/type/convert.h" +#include "config/config.h" +#include "storage/helper.h" +#include "storage/storage.h" +#include "storage/storage.intern.h" + +void +cmdStop(void) +{ + FUNCTION_LOG_VOID(logLevelDebug); + + MEM_CONTEXT_TEMP_BEGIN() + { + String *stopFile = lockStopFileName(cfgOptionStr(cfgOptStanza)); + + // If the stop file does not already exist, then create it + if (!storageExistsNP(storageLocal(), stopFile)) + { + // Create the lock path (ignore if already created) + storagePathCreateP(storageLocalWrite(), strPath(stopFile), .mode = 0770); + + // Create the stop file with Read/Write and Create only - do not use Truncate + int fileHandle = -1; + THROW_ON_SYS_ERROR_FMT( + ((fileHandle = open(strPtr(stopFile), O_WRONLY | O_CREAT, STORAGE_MODE_FILE_DEFAULT)) == -1), FileOpenError, + "unable to open stop file '%s'", strPtr(stopFile)); + + // Close the file + close(fileHandle); + + // If --force was specified then send term signals to running processes + if (cfgOptionBool(cfgOptForce)) + { + const String *lockPath = cfgOptionStr(cfgOptLockPath); + StringList *lockPathFileList = strLstSort( + storageListP(storageLocal(), lockPath, .errorOnMissing = true), sortOrderAsc); + + // Find each lock file and send term signals to the processes + for (unsigned int lockPathFileIdx = 0; lockPathFileIdx < strLstSize(lockPathFileList); lockPathFileIdx++) + { + String *lockFile = strNewFmt("%s/%s", strPtr(lockPath), strPtr(strLstGet(lockPathFileList, lockPathFileIdx))); + + // Skip any file that is not a lock file + if (!strEndsWithZ(lockFile, LOCK_FILE_EXT)) + continue; + + // If we cannot open the lock file for any reason then warn and continue to next file + if ((fileHandle = open(strPtr(lockFile), O_RDONLY, 0)) == -1) + { + LOG_WARN( "unable to open lock file %s", strPtr(lockFile)); + continue; + } + + // Attempt a lock on the file - if a lock can be acquired that means the original process died without removing + // the lock file so remove it now + if (flock(fileHandle, LOCK_EX | LOCK_NB) == 0) + { + unlink(strPtr(lockFile)); + close(fileHandle); + continue; + } + + // The file is locked so that means there is a running process - read the process id and send it a term signal + char contents[64]; + ssize_t actualBytes = read(fileHandle, contents, sizeof(contents)); + String *processId = actualBytes > 0 ? strTrim(strNewN(contents, (size_t)actualBytes)) : NULL; + + // If the process id is defined then assume this is a valid lock file + if (processId != NULL && strSize(processId) > 0) + { + if (kill(cvtZToInt(strPtr(processId)), SIGTERM) != 0) + LOG_WARN("unable to send term signal to process %s", strPtr(processId)); + else + LOG_INFO("sent term signal to process %s", strPtr(processId)); + } + else + { + unlink(strPtr(lockFile)); + close(fileHandle); + } + } + } + } + else + { + LOG_WARN( + "stop file already exists for %s", + cfgOptionTest(cfgOptStanza) ? strPtr(strNewFmt("stanza %s", strPtr(cfgOptionStr(cfgOptStanza)))) : "all stanzas"); + } + } + MEM_CONTEXT_TEMP_END(); + + FUNCTION_LOG_RETURN_VOID(); +} diff --git a/src/command/control/stop.h b/src/command/control/stop.h new file mode 100644 index 000000000..89b58b246 --- /dev/null +++ b/src/command/control/stop.h @@ -0,0 +1,12 @@ +/*********************************************************************************************************************************** +Stop Command +***********************************************************************************************************************************/ +#ifndef COMMAND_CONTROL_STOP_H +#define COMMAND_CONTROL_STOP_H + +/*********************************************************************************************************************************** +Functions +***********************************************************************************************************************************/ +void cmdStop(void); + +#endif diff --git a/src/main.c b/src/main.c index d0f515b43..9798fe62d 100644 --- a/src/main.c +++ b/src/main.c @@ -11,6 +11,8 @@ Main #include "command/archive/push/push.h" #include "command/check/check.h" #include "command/command.h" +#include "command/control/start.h" +#include "command/control/stop.h" #include "command/expire/expire.h" #include "command/help/help.h" #include "command/info/info.h" @@ -215,7 +217,7 @@ main(int argListSize, const char *argList[]) // ----------------------------------------------------------------------------------------------------------------- case cfgCmdStart: { - perlExec(); + cmdStart(); break; } @@ -223,7 +225,7 @@ main(int argListSize, const char *argList[]) // ----------------------------------------------------------------------------------------------------------------- case cfgCmdStop: { - perlExec(); + cmdStop(); break; } diff --git a/src/perl/embed.auto.c b/src/perl/embed.auto.c index 44592be36..2d3b89801 100644 --- a/src/perl/embed.auto.c +++ b/src/perl/embed.auto.c @@ -8357,15 +8357,6 @@ static const EmbeddedModule embeddedModule[] = "\n" "$iResult = new pgBackRest::Check::Check()->process();\n" "}\n" - "\n\n\n" - "elsif (cfgCommandTest(CFGCMD_START))\n" - "{\n" - "lockStart();\n" - "}\n" - "elsif (cfgCommandTest(CFGCMD_STOP))\n" - "{\n" - "lockStop();\n" - "}\n" "else\n" "{\n" "\n" diff --git a/test/define.yaml b/test/define.yaml index 554a8863b..f97f9e33b 100644 --- a/test/define.yaml +++ b/test/define.yaml @@ -627,10 +627,12 @@ unit: # ---------------------------------------------------------------------------------------------------------------------------- - name: control - total: 2 + total: 3 coverage: command/control/common: full + command/control/start: full + command/control/stop: full # ---------------------------------------------------------------------------------------------------------------------------- - name: expire diff --git a/test/src/module/command/controlTest.c b/test/src/module/command/controlTest.c index ad9ed0004..74d34e0f3 100644 --- a/test/src/module/command/controlTest.c +++ b/test/src/module/command/controlTest.c @@ -2,6 +2,9 @@ Test Command Control ***********************************************************************************************************************************/ #include "common/harnessConfig.h" +#include "common/harnessFork.h" +#include "common/io/handleRead.h" +#include "common/io/handleWrite.h" #include "storage/posix/storage.h" /*********************************************************************************************************************************** @@ -27,12 +30,12 @@ testRun(void) strLstAddZ(argList, "archive-get"); harnessCfgLoad(strLstSize(argList), strLstPtr(argList)); - TEST_RESULT_STR(strPtr(lockStopFileName(NULL)), "/path/to/lock/all.stop", "stop file for all stanzas"); - TEST_RESULT_STR(strPtr(lockStopFileName(strNew("db"))), "/path/to/lock/db.stop", "stop file for on stanza"); + TEST_RESULT_STR(strPtr(lockStopFileName(NULL)), "/path/to/lock/all" STOP_FILE_EXT, "stop file for all stanzas"); + TEST_RESULT_STR(strPtr(lockStopFileName(strNew("db"))), "/path/to/lock/db" STOP_FILE_EXT, "stop file for on stanza"); } // ***************************************************************************************************************************** - if (testBegin("lockStopTest()")) + if (testBegin("lockStopTest(), cmdStart()")) { StringList *argList = strLstNew(); strLstAddZ(argList, "pgbackrest"); @@ -41,6 +44,12 @@ testRun(void) harnessCfgLoad(strLstSize(argList), strLstPtr(argList)); TEST_RESULT_VOID(lockStopTest(), "no stop files without stanza"); + TEST_RESULT_VOID(cmdStart(), " cmdStart - no stanza, no stop files"); + harnessLogResult("P00 WARN: stop file does not exist"); + + TEST_RESULT_VOID(storagePutNP(storageNewWriteNP(storageTest, strNew("all" STOP_FILE_EXT)), NULL), "create stop file"); + TEST_RESULT_VOID(cmdStart(), " cmdStart - no stanza, stop file exists"); + TEST_RESULT_BOOL(storageExistsNP(storageTest, strNew("all" STOP_FILE_EXT)), false, " stop file removed"); // ------------------------------------------------------------------------------------------------------------------------- argList = strLstNew(); @@ -51,12 +60,308 @@ testRun(void) harnessCfgLoad(strLstSize(argList), strLstPtr(argList)); TEST_RESULT_VOID(lockStopTest(), "no stop files with stanza"); + TEST_RESULT_VOID(cmdStart(), " cmdStart - stanza, no stop files"); + harnessLogResult("P00 WARN: stop file does not exist for stanza db"); - storagePutNP(storageNewWriteNP(storageTest, strNew("all.stop")), NULL); + storagePutNP(storageNewWriteNP(storageTest, strNew("all" STOP_FILE_EXT)), NULL); TEST_ERROR(lockStopTest(), StopError, "stop file exists for all stanzas"); - storagePutNP(storageNewWriteNP(storageTest, strNew("db.stop")), NULL); + storagePutNP(storageNewWriteNP(storageTest, strNew("db" STOP_FILE_EXT)), NULL); TEST_ERROR(lockStopTest(), StopError, "stop file exists for stanza db"); + + TEST_RESULT_VOID(cmdStart(), "cmdStart - stanza, stop file exists"); + TEST_RESULT_BOOL(storageExistsNP(storageTest, strNew("db" STOP_FILE_EXT)), false, " stanza stop file removed"); + TEST_RESULT_BOOL(storageExistsNP(storageTest, strNew("all" STOP_FILE_EXT)), true, " all stop file not removed"); + } + + // ***************************************************************************************************************************** + if (testBegin("cmdStop()")) + { + String *lockPath = strNewFmt("%s/lockpath", testPath()); + StringList *argList = strLstNew(); + strLstAddZ(argList, "pgbackrest"); + strLstAdd(argList, strNewFmt("--lock-path=%s", strPtr(lockPath))); + strLstAddZ(argList, "stop"); + harnessCfgLoad(strLstSize(argList), strLstPtr(argList)); + + TEST_RESULT_VOID(cmdStop(), "no stanza, create stop file"); + StorageInfo info = {0}; + TEST_ASSIGN(info, storageInfoNP(storageTest, lockPath), " get path info"); + TEST_RESULT_INT(info.mode, 0770, " check path mode"); + TEST_RESULT_BOOL( + storageExistsNP(storageTest, strNewFmt("%s/all" STOP_FILE_EXT, strPtr(lockPath))), true, " all stop file created"); + TEST_ASSIGN(info, storageInfoNP(storageTest, strNewFmt("%s/all" STOP_FILE_EXT, strPtr(lockPath))), " get file info"); + TEST_RESULT_INT(info.mode, 0640, " check file mode"); + + // ------------------------------------------------------------------------------------------------------------------------- + TEST_RESULT_VOID(cmdStop(), "no stanza, stop file already exists"); + harnessLogResult("P00 WARN: stop file already exists for all stanzas"); + + // ------------------------------------------------------------------------------------------------------------------------- + TEST_RESULT_VOID(storageRemoveNP(storageTest, strNew("lockpath/all" STOP_FILE_EXT)), "remove stop file"); + TEST_RESULT_INT(system(strPtr(strNewFmt("sudo chmod 444 %s", strPtr(lockPath)))), 0, "change perms"); + TEST_ERROR_FMT( + cmdStop(), FileOpenError, "unable to stat '%s/all.stop': [13] Permission denied", strPtr(lockPath)); + TEST_RESULT_VOID( + storagePathRemoveP(storageTest, lockPath, .recurse = true, .errorOnMissing = true), " remove the lock path"); + + // ------------------------------------------------------------------------------------------------------------------------- + String *stanzaStopFile = strNewFmt("%s/db" STOP_FILE_EXT, strPtr(lockPath)); + strLstAddZ(argList, "--stanza=db"); + harnessCfgLoad(strLstSize(argList), strLstPtr(argList)); + + TEST_RESULT_VOID(cmdStop(), "stanza, create stop file"); + TEST_RESULT_BOOL(storageExistsNP(storageTest, stanzaStopFile), true, " stanza stop file created"); + + StringList *lockPathList = NULL; + TEST_ASSIGN(lockPathList, storageListP(storageTest, strNew("lockpath"), .errorOnMissing = true), " get file list"); + TEST_RESULT_INT(strLstSize(lockPathList), 1, " only file in lock path"); + TEST_RESULT_STR(strPtr(strLstGet(lockPathList, 0)), "db" STOP_FILE_EXT, " stanza stop exists"); + + // ------------------------------------------------------------------------------------------------------------------------- + TEST_RESULT_VOID(cmdStop(), "stanza, stop file already exists"); + harnessLogResult("P00 WARN: stop file already exists for stanza db"); + TEST_RESULT_VOID(storageRemoveNP(storageTest, stanzaStopFile), " remove stop file"); + + // ------------------------------------------------------------------------------------------------------------------------- + strLstAddZ(argList, "--force"); + harnessCfgLoad(strLstSize(argList), strLstPtr(argList)); + TEST_RESULT_VOID(cmdStop(), "stanza, create stop file, force"); + TEST_RESULT_VOID(storageRemoveNP(storageTest, stanzaStopFile), " remove stop file"); + + // ------------------------------------------------------------------------------------------------------------------------- + TEST_RESULT_VOID( + storagePutNP( + storageNewWriteP(storageTest, strNewFmt("%s/bad" LOCK_FILE_EXT, strPtr(lockPath)), .modeFile = 0222), NULL), + "create a lock file that cannot be opened"); + TEST_RESULT_VOID(cmdStop(), " stanza, create stop file but unable to open lock file"); + harnessLogResult(strPtr(strNewFmt("P00 WARN: unable to open lock file %s/bad" LOCK_FILE_EXT, strPtr(lockPath)))); + TEST_RESULT_VOID( + storagePathRemoveP(storageTest, lockPath, .recurse = true, .errorOnMissing = true), " remove the lock path"); + + // ------------------------------------------------------------------------------------------------------------------------- + TEST_RESULT_VOID( + storagePutNP(storageNewWriteNP(storageTest, strNewFmt("%s/empty" LOCK_FILE_EXT, strPtr(lockPath))), NULL), + "create empty lock file"); + TEST_RESULT_VOID(cmdStop(), " stanza, create stop file, force - empty lock file"); + TEST_RESULT_BOOL(storageExistsNP(storageTest, stanzaStopFile), true, " stanza stop file created"); + TEST_RESULT_BOOL( + storageExistsNP(storageTest, strNewFmt("%s/empty" LOCK_FILE_EXT, strPtr(lockPath))), false, + " no other process lock, lock file was removed"); + + // empty lock file with another process lock, processId == NULL + // ------------------------------------------------------------------------------------------------------------------------- + TEST_RESULT_VOID(storageRemoveNP(storageTest, stanzaStopFile), "remove stop file"); + TEST_RESULT_VOID( + storagePutNP(storageNewWriteNP(storageTest, strNewFmt("%s/empty" LOCK_FILE_EXT, strPtr(lockPath))), NULL), + " create empty lock file"); + + HARNESS_FORK_BEGIN() + { + HARNESS_FORK_CHILD_BEGIN(0, true) + { + IoRead *read = ioHandleReadNew(strNew("child read"), HARNESS_FORK_CHILD_READ(), 2000); + ioReadOpen(read); + IoWrite *write = ioHandleWriteNew(strNew("child write"), HARNESS_FORK_CHILD_WRITE()); + ioWriteOpen(write); + + int lockHandle = open(strPtr(strNewFmt("%s/empty" LOCK_FILE_EXT, strPtr(lockPath))), O_RDONLY, 0); + TEST_RESULT_BOOL(lockHandle != -1, true, " file handle aquired"); + TEST_RESULT_INT(flock(lockHandle, LOCK_EX | LOCK_NB), 0, " lock the empty file"); + + // Let the parent know the lock has been acquired and wait for the parent to allow lock release + ioWriteStrLine(write, strNew("")); + // All writes are buffered so need to flush because buffer is not full + ioWriteFlush(write); + // Wait for a linefeed from the parent ioWriteLine below + ioReadLine(read); + + // Parent remove the file so just close the handle + close(lockHandle); + } + HARNESS_FORK_CHILD_END(); + + HARNESS_FORK_PARENT_BEGIN() + { + IoRead *read = ioHandleReadNew(strNew("parent read"), HARNESS_FORK_PARENT_READ_PROCESS(0), 2000); + ioReadOpen(read); + IoWrite *write = ioHandleWriteNew(strNew("parent write"), HARNESS_FORK_PARENT_WRITE_PROCESS(0)); + ioWriteOpen(write); + + // Wait for the child to acquire the lock + ioReadLine(read); + + TEST_RESULT_VOID( + cmdStop(), + " stanza, create stop file, force - empty lock file with another process lock, processId == NULL"); + TEST_RESULT_BOOL( + storageExistsNP(storageTest, strNewFmt("%s/empty" LOCK_FILE_EXT, strPtr(lockPath))), false, + " lock file was removed"); + + // Notify the child to release the lock + ioWriteLine(write, bufNew(0)); + ioWriteFlush(write); + } + HARNESS_FORK_PARENT_END(); + } + HARNESS_FORK_END(); + + // not empty lock file with another process lock, processId size trimmed to 0 + // ------------------------------------------------------------------------------------------------------------------------- + TEST_RESULT_VOID(storageRemoveNP(storageTest, stanzaStopFile), "remove stop file"); + TEST_RESULT_VOID( + storagePutNP(storageNewWriteNP(storageTest, strNewFmt("%s/empty" LOCK_FILE_EXT, strPtr(lockPath))), BUFSTRDEF(" ")), + " create non-empty lock file"); + + HARNESS_FORK_BEGIN() + { + HARNESS_FORK_CHILD_BEGIN(0, true) + { + IoRead *read = ioHandleReadNew(strNew("child read"), HARNESS_FORK_CHILD_READ(), 2000); + ioReadOpen(read); + IoWrite *write = ioHandleWriteNew(strNew("child write"), HARNESS_FORK_CHILD_WRITE()); + ioWriteOpen(write); + + int lockHandle = open(strPtr(strNewFmt("%s/empty" LOCK_FILE_EXT, strPtr(lockPath))), O_RDONLY, 0); + TEST_RESULT_BOOL(lockHandle != -1, true, " file handle aquired"); + TEST_RESULT_INT(flock(lockHandle, LOCK_EX | LOCK_NB), 0, " lock the non-empty file"); + + // Let the parent know the lock has been acquired and wait for the parent to allow lock release + ioWriteStrLine(write, strNew("")); + // All writes are buffered so need to flush because buffer is not full + ioWriteFlush(write); + // Wait for a linefeed from the parent ioWriteLine below + ioReadLine(read); + + // Parent remove the file so just close the handle + close(lockHandle); + } + HARNESS_FORK_CHILD_END(); + + HARNESS_FORK_PARENT_BEGIN() + { + IoRead *read = ioHandleReadNew(strNew("parent read"), HARNESS_FORK_PARENT_READ_PROCESS(0), 2000); + ioReadOpen(read); + IoWrite *write = ioHandleWriteNew(strNew("parent write"), HARNESS_FORK_PARENT_WRITE_PROCESS(0)); + ioWriteOpen(write); + + // Wait for the child to acquire the lock + ioReadLine(read); + + TEST_RESULT_VOID( + cmdStop(), " stanza, create stop file, force - empty lock file with another process lock, processId size 0"); + TEST_RESULT_BOOL( + storageExistsNP(storageTest, strNewFmt("%s/empty" LOCK_FILE_EXT, strPtr(lockPath))), false, + " lock file was removed"); + + // Notify the child to release the lock + ioWriteLine(write, bufNew(0)); + ioWriteFlush(write); + } + HARNESS_FORK_PARENT_END(); + } + HARNESS_FORK_END(); + + // lock file with another process lock, processId is valid + // ------------------------------------------------------------------------------------------------------------------------- + TEST_RESULT_VOID(storageRemoveNP(storageTest, stanzaStopFile), "remove stop file"); + HARNESS_FORK_BEGIN() + { + HARNESS_FORK_CHILD_BEGIN(0, true) + { + IoRead *read = ioHandleReadNew(strNew("child read"), HARNESS_FORK_CHILD_READ(), 2000); + ioReadOpen(read); + IoWrite *write = ioHandleWriteNew(strNew("child write"), HARNESS_FORK_CHILD_WRITE()); + ioWriteOpen(write); + + TEST_RESULT_BOOL( + lockAcquire(lockPath, cfgOptionStr(cfgOptStanza), 0, 30000, true), true," child process aquires lock"); + + // Let the parent know the lock has been acquired and wait for the parent to allow lock release + ioWriteStrLine(write, strNew("")); + // All writes are buffered so need to flush because buffer is not full + ioWriteFlush(write); + // Wait for a linefeed from the parent but it will not arrive before the process is terminated + ioReadLine(read); + } + HARNESS_FORK_CHILD_END(); + + HARNESS_FORK_PARENT_BEGIN() + { + IoRead *read = ioHandleReadNew(strNew("parent read"), HARNESS_FORK_PARENT_READ_PROCESS(0), 2000); + ioReadOpen(read); + IoWrite *write = ioHandleWriteNew(strNew("parent write"), HARNESS_FORK_PARENT_WRITE_PROCESS(0)); + ioWriteOpen(write); + + // Wait for the child to acquire the lock + ioReadLine(read); + + TEST_RESULT_VOID( + cmdStop(), + " stanza, create stop file, force - lock file with another process lock, processId is valid"); + + harnessLogResult(strPtr(strNewFmt("P00 INFO: sent term signal to process %d", HARNESS_FORK_PROCESS_ID(0)))); + } + HARNESS_FORK_PARENT_END(); + } + HARNESS_FORK_END(); + + // lock file with another process lock, processId is invalid + // ------------------------------------------------------------------------------------------------------------------------- + TEST_RESULT_VOID(storageRemoveNP(storageTest, stanzaStopFile), "remove stop file"); + TEST_RESULT_VOID( + storagePutNP( + storageNewWriteNP(storageTest, strNewFmt("%s/badpid" LOCK_FILE_EXT, strPtr(lockPath))), BUFSTRDEF("-32768")), + "create lock file with invalid PID"); + + HARNESS_FORK_BEGIN() + { + HARNESS_FORK_CHILD_BEGIN(0, true) + { + IoRead *read = ioHandleReadNew(strNew("child read"), HARNESS_FORK_CHILD_READ(), 2000); + ioReadOpen(read); + IoWrite *write = ioHandleWriteNew(strNew("child write"), HARNESS_FORK_CHILD_WRITE()); + ioWriteOpen(write); + + int lockHandle = open(strPtr(strNewFmt("%s/badpid" LOCK_FILE_EXT, strPtr(lockPath))), O_RDONLY, 0); + TEST_RESULT_BOOL(lockHandle != -1, true, " file handle aquired"); + TEST_RESULT_INT(flock(lockHandle, LOCK_EX | LOCK_NB), 0, " lock the badpid file"); + + // Let the parent know the lock has been acquired and wait for the parent to allow lock release + ioWriteStrLine(write, strNew("")); + // All writes are buffered so need to flush because buffer is not full + ioWriteFlush(write); + // Wait for a linefeed from the parent ioWriteLine below + ioReadLine(read); + + // Remove the file and close the handle + storageRemoveNP(storageTest, strNewFmt("%s/badpid" LOCK_FILE_EXT, strPtr(lockPath))); + close(lockHandle); + } + HARNESS_FORK_CHILD_END(); + + HARNESS_FORK_PARENT_BEGIN() + { + IoRead *read = ioHandleReadNew(strNew("parent read"), HARNESS_FORK_PARENT_READ_PROCESS(0), 2000); + ioReadOpen(read); + IoWrite *write = ioHandleWriteNew(strNew("parent write"), HARNESS_FORK_PARENT_WRITE_PROCESS(0)); + ioWriteOpen(write); + + // Wait for the child to acquire the lock + ioReadLine(read); + + TEST_RESULT_VOID( + cmdStop(), " stanza, create stop file, force - lock file with another process lock, processId is invalid"); + harnessLogResult("P00 WARN: unable to send term signal to process -32768"); + TEST_RESULT_BOOL(storageExistsNP(storageTest, stanzaStopFile), true, " stanza stop file not removed"); + + // Notify the child to release the lock + ioWriteLine(write, bufNew(0)); + ioWriteFlush(write); + } + HARNESS_FORK_PARENT_END(); + } + HARNESS_FORK_END(); } FUNCTION_HARNESS_RESULT_VOID();