diff --git a/documentation/new-notes/PR-558.md b/documentation/new-notes/PR-558.md index 0c2d7cba6..17f8a90cf 100644 --- a/documentation/new-notes/PR-558.md +++ b/documentation/new-notes/PR-558.md @@ -1,8 +1,8 @@ -### New `atInit` IOC Shell Command Added +### New `afterIocRunning` IOC Shell Command Added -This release incorporates [PR #558](https://github.com/epics-base/epics-base/pull/558) which added a new IOC shell command `atInit`. This command allows startup scripts to schedule arbitrary commands to be executed automatically after the IOC initialization phase (`iocInit`). +This release incorporates [PR #558](https://github.com/epics-base/epics-base/pull/558) which added a new IOC shell command `afterIocRunning`. This command allows startup scripts to schedule arbitrary commands to be executed automatically after the IOC initialization phase (`iocInit`). -`atInit` allows you to write better-structured IOC shell files to include in your startup scripts without tracking where `iocInit` is located (and how IOC is deployed) e.g.: +`afterIocRunning` allows you to write better-structured IOC shell files to include in your startup scripts without tracking where `iocInit` is located (and how IOC is deployed) e.g.: - to achieve the best maintainability (e.g. encapsulation of the context into one file), - to improve writing boot sequences, - to improve IOC startup flexibility and scripting capabilities, @@ -17,7 +17,7 @@ This release incorporates [PR #558](https://github.com/epics-base/epics-base/pul - Executes following `iocInit` and `autosave` initialization (important for proper PV configuration). - Supports any valid IOC shell command as an argument. - Example usages: - - `atInit "dbpf "` - - `atInit "date"` - - `atInit "dbpf $(P)EvtClkSource-Sel 'Upstream (fanout)'"` - - `atInit "dbpf $(P)Enable-Sel Enabled"` + - `afterIocRunning "dbpf "` + - `afterIocRunning "date"` + - `afterIocRunning "dbpf $(P)EvtClkSource-Sel 'Upstream (fanout)'"` + - `afterIocRunning "dbpf $(P)Enable-Sel Enabled"` diff --git a/modules/libcom/src/iocsh/Makefile b/modules/libcom/src/iocsh/Makefile index 1001296e7..0fb688857 100644 --- a/modules/libcom/src/iocsh/Makefile +++ b/modules/libcom/src/iocsh/Makefile @@ -16,7 +16,7 @@ Com_SRCS += iocsh.cpp Com_SRCS += initHooks.c Com_SRCS += registry.c Com_SRCS += libComRegister.c -Com_SRCS += atInit.c +Com_SRCS += afterIocRunning.c iocsh_CXXFLAGS += -DEPICS_COMMANDLINE_LIBRARY=EPICS_COMMANDLINE_LIBRARY_$(COMMANDLINE_LIBRARY) iocsh_INCLUDES += $(INCLUDES_$(COMMANDLINE_LIBRARY)) diff --git a/modules/libcom/src/iocsh/atInit.c b/modules/libcom/src/iocsh/afterIocRunning.c similarity index 63% rename from modules/libcom/src/iocsh/atInit.c rename to modules/libcom/src/iocsh/afterIocRunning.c index 9018f3ff5..846f6e897 100644 --- a/modules/libcom/src/iocsh/atInit.c +++ b/modules/libcom/src/iocsh/afterIocRunning.c @@ -13,18 +13,20 @@ #include #include -#include "atInit.h" +#include "afterIocRunning.h" -static const iocshArg atInitArg0 = {"command (before iocInit)", iocshArgString}; -static const iocshArg *const atInitArgs[] = {&atInitArg0}; -static const iocshFuncDef atInitDef = { - "atInit", +static const iocshArg afterIocRunningArg0 = {"command (before iocInit)", iocshArgString}; +static const iocshArg *const afterIocRunningArgs[] = {&afterIocRunningArg0}; +static const iocshFuncDef afterIocRunningDef = { + "afterIocRunning", 1, - atInitArgs, + afterIocRunningArgs, "Allows you to define commands to be run after the iocInit\n" "Example commands:\n" - " atInit \"dbpf \"\n" - " atInit \"date\"\n"}; + " afterIocRunning \"dbpf \"\n" + " afterIocRunning \"date\"\n" + " afterIocRunning \"dbpf $(P)EvtClkSource-Sel 'Upstream (fanout)'\"\n" + " afterIocRunning \"dbpf $(P)Enable-Sel Enabled\"\n"}; struct cmditem { ELLNODE node; @@ -34,7 +36,7 @@ struct cmditem { static ELLLIST cmdList = ELLLIST_INIT; static int initEndFlag = 0; // Defines the end of the initialization -static void atInitHook(const initHookState state) +static void afterIocRunningHook(const initHookState state) { struct cmditem *item = NULL; @@ -42,10 +44,10 @@ static void atInitHook(const initHookState state) return; while ((item = (struct cmditem *)ellGet(&cmdList))) { - printf(ANSI_GREEN("atInit:") " %s\n", item->cmd); + printf(ANSI_GREEN("afterIocRunning:") " %s\n", item->cmd); if (iocshCmd(item->cmd)) - printf(ERL_ERROR " atInit: " + printf(ERL_ERROR " afterIocRunning: " "command '%s' failed to run\n", item->cmd); @@ -58,7 +60,7 @@ static void atInitHook(const initHookState state) static struct cmditem *newItem(const char *cmd) { const size_t cmd_len = strlen(cmd) + 1; - struct cmditem *const item = mallocMustSucceed(sizeof(struct cmditem) + cmd_len, "atInit"); + struct cmditem *const item = mallocMustSucceed(sizeof(struct cmditem) + cmd_len, "afterIocRunning"); item->cmd = (char *)(item + 1); memcpy(item->cmd, cmd, cmd_len); @@ -67,18 +69,18 @@ static struct cmditem *newItem(const char *cmd) return item; } -static void atInitFunc(const iocshArgBuf *args) +static void afterIocRunningFunc(const iocshArgBuf *args) { const char *const cmd = args[0].sval; if (initEndFlag) { - printf(ERL_WARNING " atInit: " + printf(ERL_WARNING " afterIocRunning: " "can only be used before 'iocInit'\n"); return; } if (!cmd || !cmd[0]) { - printf(ERL_ERROR " atInit: " + printf(ERL_ERROR " afterIocRunning: " "received an empty 'command' argument\n"); return; } @@ -86,12 +88,12 @@ static void atInitFunc(const iocshArgBuf *args) newItem(cmd); } -void atInitRegister(void) +void afterIocRunningRegister(void) { static int first_time = 1; if (first_time) { first_time = 0; - iocshRegister(&atInitDef, atInitFunc); - initHookRegister(atInitHook); + iocshRegister(&afterIocRunningDef, afterIocRunningFunc); + initHookRegister(afterIocRunningHook); } } diff --git a/modules/libcom/src/iocsh/atInit.h b/modules/libcom/src/iocsh/afterIocRunning.h similarity index 76% rename from modules/libcom/src/iocsh/atInit.h rename to modules/libcom/src/iocsh/afterIocRunning.h index fb247d601..18b77257a 100644 --- a/modules/libcom/src/iocsh/atInit.h +++ b/modules/libcom/src/iocsh/afterIocRunning.h @@ -6,9 +6,9 @@ * in file LICENSE that is included with this distribution. \*************************************************************************/ -#ifndef INC_atInit_H -#define INC_atInit_H +#ifndef INC_afterIocRunning_H +#define INC_afterIocRunning_H -void atInitRegister(void); +void afterIocRunningRegister(void); -#endif /* INC_atInit_H */ +#endif /* INC_afterIocRunning_H */ diff --git a/modules/libcom/src/iocsh/libComRegister.c b/modules/libcom/src/iocsh/libComRegister.c index 601a5020e..fd7c232de 100644 --- a/modules/libcom/src/iocsh/libComRegister.c +++ b/modules/libcom/src/iocsh/libComRegister.c @@ -27,7 +27,7 @@ #include "epicsGeneralTime.h" #include "freeList.h" #include "libComRegister.h" -#include "atInit.h" +#include "afterIocRunning.h" /* Register the PWD environment variable when the cd IOC shell function is * registered. This variable contains the current directory path. @@ -513,7 +513,7 @@ void epicsStdCall libComRegister(void) iocshRegister(&generalTimeReportFuncDef,generalTimeReportCallFunc); iocshRegister(&installLastResortEventProviderFuncDef, installLastResortEventProviderCallFunc); - atInitRegister(); + afterIocRunningRegister(); comDefs[0].pval = &asCheckClientIP; comDefs[1].pval = &freeListBypass; diff --git a/modules/libcom/test/Makefile b/modules/libcom/test/Makefile index ffee73fe4..59114f13a 100644 --- a/modules/libcom/test/Makefile +++ b/modules/libcom/test/Makefile @@ -256,10 +256,10 @@ osiSockTest_SRCS += osiSockTest.c testHarness_SRCS += osiSockTest.c TESTS += osiSockTest -TESTPROD_HOST += atInitTest -atInitTest_SRCS += atInitTest.cpp -TESTS += atInitTest -testHarness_SRCS += atInitTest.cpp +TESTPROD_HOST += afterIocRunningTest +afterIocRunningTest_SRCS += afterIocRunningTest.cpp +TESTS += afterIocRunningTest +testHarness_SRCS += afterIocRunningTest.cpp TESTPROD_HOST += testexecname testexecname_SRCS += testexecname.c diff --git a/modules/libcom/test/afterIocRunningTest.cpp b/modules/libcom/test/afterIocRunningTest.cpp new file mode 100644 index 000000000..133adfbda --- /dev/null +++ b/modules/libcom/test/afterIocRunningTest.cpp @@ -0,0 +1,80 @@ +/*************************************************************************\ +* SPDX-License-Identifier: EPICS +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include + +static void testOkEnv(const char *varName, const char *varValue) +{ + const char *val = getenv(varName); + testOk(val && strcmp(val, varValue) == 0, + "%s=%s", varName, val ? val : "(null)"); +} + +static void iocshCmdDebug(const char *cmd) +{ + printf("%s\n", cmd); + iocshCmd(cmd); +} + +MAIN(afterIocRunningTest) +{ + testPlan(12); + + libComRegister(); + + // Reset environment variables + iocshCmdDebug("epicsEnvSet \"TEST_VAR\" \"BeforeIocInit\""); + iocshCmdDebug("epicsEnvSet \"TEST_VAR_ONE\" \"Before(Ioc)Init\""); + iocshCmdDebug("epicsEnvSet 'TEST_VAR_SPACES' 'Before Ioc Init'"); + + printf("Test whether the variables are initialized correctly.\n"); + testOkEnv("TEST_VAR", "BeforeIocInit"); + testOkEnv("TEST_VAR_ONE", "Before(Ioc)Init"); + testOkEnv("TEST_VAR_SPACES", "Before Ioc Init"); + printf("===================\n"); + epicsThreadSleep(1.0); + // Basic test + iocshCmdDebug("afterIocRunning \"epicsEnvSet TEST_VAR AfterIocInit\""); + iocshCmdDebug("afterIocRunning \"epicsEnvSet TEST_VAR_ONE 'After(Ioc)Init'\""); + iocshCmdDebug("afterIocRunning \"epicsEnvSet TEST_VAR_SPACES 'After Ioc Init'\""); + iocshCmdDebug("afterIocRunning \"date\""); + // Verify error handling and robustness + iocshCmdDebug("afterIocRunning \"nonexistentCommand arg1 arg2\""); + iocshCmdDebug("afterIocRunning \"\""); // empty string + iocshCmdDebug("afterIocRunning \" \""); // only spaces + printf("===================\n"); + + epicsThreadSleep(1.0); + printf("Test whether the variables remain unchanged after 'afterIocRunning' and before 'iocInit'.\n"); + testOkEnv("TEST_VAR", "BeforeIocInit"); + testOkEnv("TEST_VAR_ONE", "Before(Ioc)Init"); + testOkEnv("TEST_VAR_SPACES", "Before Ioc Init"); + + // Simulate iocInit + printf("===================\n" + "iocInit: Simulation\n" + "===================\n"); + initHookAnnounce(initHookAfterIocRunning); + epicsThreadSleep(1.0); + + // Verify the results + printf("Test whether the variables are correctly initialized after 'iocInit'.\n"); + testOkEnv("TEST_VAR", "AfterIocInit"); + testOkEnv("TEST_VAR_ONE", "After(Ioc)Init"); + testOkEnv("TEST_VAR_SPACES", "After Ioc Init"); + testPass("Command 'date' executed"); + testPass("Invalid command did not crash IOC"); + testPass("Empty afterIocRunning commands do not cause failure"); + + return testDone(); +} diff --git a/modules/libcom/test/atInitTest.cpp b/modules/libcom/test/atInitTest.cpp deleted file mode 100644 index 7031c2acd..000000000 --- a/modules/libcom/test/atInitTest.cpp +++ /dev/null @@ -1,72 +0,0 @@ -/*************************************************************************\ -* SPDX-License-Identifier: EPICS -* EPICS BASE is distributed subject to a Software License Agreement found -* in file LICENSE that is included with this distribution. -\*************************************************************************/ - -#include -#include -#include -#include -#include -#include -#include -#include - -void testOkEnv(const char *varName, const char *varValue) -{ - const char *val = getenv(varName); - testOk(val && strcmp(val, varValue) == 0, - "%s=%s", varName, val ? val : "(null)"); -} - -MAIN(atInitTest) -{ - testPlan(12); - - libComRegister(); - - // Reset environment variables - iocshCmd("epicsEnvSet \"ATINIT_TEST_VAR\" \"BeforeIocInit\""); - iocshCmd("epicsEnvSet \"ATINIT_TEST_VAR_ONE\" \"BeforeIocInit\""); - iocshCmd("epicsEnvSet 'ATINIT_TEST_VAR_SPACES' 'Before Ioc Init'"); - - printf("Test whether the variables are initialized correctly.\n"); - testOkEnv("ATINIT_TEST_VAR", "BeforeIocInit"); - testOkEnv("ATINIT_TEST_VAR_ONE", "BeforeIocInit"); - testOkEnv("ATINIT_TEST_VAR_SPACES", "Before Ioc Init"); - - // Basic test - iocshCmd("atInit \"epicsEnvSet ATINIT_TEST_VAR AfterIocInit\""); - iocshCmd("atInit \"epicsEnvSet ATINIT_TEST_VAR_ONE AfterIocInit\""); - iocshCmd("atInit \"epicsEnvSet ATINIT_TEST_VAR_SPACES 'After Ioc Init'\""); - iocshCmd("atInit \"date\""); - - epicsThreadSleep(1.0); - // Verify error handling and robustness - iocshCmd("atInit \"nonexistentCommand arg1 arg2\""); - iocshCmd("atInit \"\""); // empty string - iocshCmd("atInit \" \""); // only spaces - - printf("Test whether the variables remain unchanged after 'atInit' and before 'iocInit'.\n"); - testOkEnv("ATINIT_TEST_VAR", "BeforeIocInit"); - testOkEnv("ATINIT_TEST_VAR_ONE", "BeforeIocInit"); - testOkEnv("ATINIT_TEST_VAR_SPACES", "Before Ioc Init"); - - // Simulate iocInit - initHookAnnounce(initHookAfterIocRunning); - printf("=== iocInit Simulation ===\n"); - epicsThreadSleep(1.0); - - // Verify the results - printf("Test whether the variables are correctly initialized after 'iocInit'.\n"); - testOkEnv("ATINIT_TEST_VAR", "AfterIocInit"); - testOkEnv("ATINIT_TEST_VAR_ONE", "AfterIocInit"); - testOkEnv("ATINIT_TEST_VAR_SPACES", "After Ioc Init"); - testPass("Command 'date' executed"); - - testPass("Invalid command did not crash IOC"); - testPass("Empty atInit commands do not cause failure"); - - return testDone(); -}