From 89784a114e96e019d73c76620e1dfbc22503b79b Mon Sep 17 00:00:00 2001 From: Minijackson Date: Wed, 25 Feb 2026 13:01:09 +0000 Subject: [PATCH 01/16] setup codeowners for the CODEOWNERS and documentation files --- .github/CODEOWNERS | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..9b588f5c2 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,2 @@ +/.github/CODEOWNERS @epics-base/core-devs +/documentation/ @epics-base/documentation From 5b23dcb46652e5f359af1f72477987e21868abd5 Mon Sep 17 00:00:00 2001 From: ReBarsotti-HZB Date: Thu, 5 Mar 2026 18:43:14 +0100 Subject: [PATCH 02/16] Added Doxygen annotations to iocInit.h (#804) Doxygen annotations for iocInit.h --- documentation/Makefile.headers | 1 + modules/database/src/ioc/misc/iocInit.h | 20 ++++++++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/documentation/Makefile.headers b/documentation/Makefile.headers index bc14cf525..136a6dffc 100644 --- a/documentation/Makefile.headers +++ b/documentation/Makefile.headers @@ -83,6 +83,7 @@ database_HEADERS += dbUnitTest database_HEADERS += devSup database_HEADERS += drvSup database_HEADERS += initHooks +database_HEADERS += iocInit record_HEADERS += aaiRecord record_HEADERS += aaoRecord diff --git a/modules/database/src/ioc/misc/iocInit.h b/modules/database/src/ioc/misc/iocInit.h index 1acd8ee8f..7c9048428 100644 --- a/modules/database/src/ioc/misc/iocInit.h +++ b/modules/database/src/ioc/misc/iocInit.h @@ -7,7 +7,8 @@ * EPICS BASE is distributed subject to a Software License Agreement found * in file LICENSE that is included with this distribution. \*************************************************************************/ -/* iocInit.h ioc initialization */ +/** @file iocInit.h + * @brief ioc initialization */ #ifndef INCiocInith #define INCiocInith @@ -22,15 +23,30 @@ enum iocStateEnum { extern "C" { #endif -/** Query present IOC run state +/** Query the present IOC run state * @since 3.15.8 */ DBCORE_API enum iocStateEnum getIocState(void); + +/** @brief Initalizes the IOC in two distinct parts - iocBuild and iocRun + */ DBCORE_API int iocInit(void); + +/** @brief Puts the IOC into a quiescent state without allowing the various internal threads it starts to actually run */ DBCORE_API int iocBuild(void); +/** @brief Allows to start an IOC without its Channel Access parts. It can then be shutdown cleanly using iocShutdown. + * Feature only intended to be used for test programs, not production IOCs. */ DBCORE_API int iocBuildIsolated(void); +/** @brief Brings the IOC online after iocBuild or iocPause*/ DBCORE_API int iocRun(void); + +/** @brief Freezes all internal operations + * + * the iocRun command can restart it from where it left off or the ioc can be shut down. Pausing may not always be safe as not all drivers and device support may have been written with the possibility of pausing an IOC in mind. + */ DBCORE_API int iocPause(void); + +/** @brief Exits the program, shutsdown the IOC */ DBCORE_API int iocShutdown(void); #ifdef __cplusplus From 01e10731632de3ff966568d81c19e5d62a4d821e Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 10 Mar 2026 12:03:23 -0500 Subject: [PATCH 03/16] Fix editorconfig failure --- modules/database/src/ioc/misc/iocInit.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/database/src/ioc/misc/iocInit.h b/modules/database/src/ioc/misc/iocInit.h index 7c9048428..cf7cda3f7 100644 --- a/modules/database/src/ioc/misc/iocInit.h +++ b/modules/database/src/ioc/misc/iocInit.h @@ -7,7 +7,7 @@ * EPICS BASE is distributed subject to a Software License Agreement found * in file LICENSE that is included with this distribution. \*************************************************************************/ -/** @file iocInit.h +/** @file iocInit.h * @brief ioc initialization */ #ifndef INCiocInith @@ -34,7 +34,7 @@ DBCORE_API int iocInit(void); /** @brief Puts the IOC into a quiescent state without allowing the various internal threads it starts to actually run */ DBCORE_API int iocBuild(void); -/** @brief Allows to start an IOC without its Channel Access parts. It can then be shutdown cleanly using iocShutdown. +/** @brief Allows to start an IOC without its Channel Access parts. It can then be shutdown cleanly using iocShutdown. * Feature only intended to be used for test programs, not production IOCs. */ DBCORE_API int iocBuildIsolated(void); /** @brief Brings the IOC online after iocBuild or iocPause*/ From e271752158dd23b6ca9aa0819a3914f9d1b52486 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 3 Mar 2026 13:29:17 -0600 Subject: [PATCH 04/16] Added -d option to caRepeater, sets debug level Added debug messages for client verification. With -d the repeater shows client events, e.g. $ caRepeater -d CA Repeater: Attached and initialized New client on port 60307 Verified 1 active clients New client on port 52541 Verified 2 active clients Client on port 60307 refused message Deleted client on port 60307 Client on port 52541 refused message Deleted client on port 52541 ^C Running "caRepeater -dd" also shows beacon forwarding and individual client verification: $ caRepeater -dd CA Repeater: Attached and initialized New client on port 49968 Client on port 49968 is alive Verified 1 active clients Sent to port 49968 Sent to port 49968 Sent to port 49968 New client on port 65377 Sent to port 49968 Client on port 49968 is alive Client on port 65377 is alive Verified 2 active clients Sent to port 49968 Sent to port 65377 Sent to port 49968 Sent to port 65377 Client on port 49968 refused message Deleted client on port 49968 Sent to port 65377 Sent to port 65377 Client on port 65377 refused message Deleted client on port 65377 ^C --- modules/ca/src/client/caRepeater.cpp | 13 ++++--- modules/ca/src/client/repeater.cpp | 54 +++++++++++++++++----------- modules/ca/src/client/udpiiu.h | 2 +- 3 files changed, 43 insertions(+), 26 deletions(-) diff --git a/modules/ca/src/client/caRepeater.cpp b/modules/ca/src/client/caRepeater.cpp index c8385ba3b..5d7695057 100644 --- a/modules/ca/src/client/caRepeater.cpp +++ b/modules/ca/src/client/caRepeater.cpp @@ -47,19 +47,21 @@ static void usage(char* argv[]) { - fprintf(stderr, "Usage: %s -hv\n" + fprintf(stderr, "Usage: %s -hdv\n" "\n" " -h - Print this message\n" - " -v - Do not replace stdin/out/err with /dev/null\n", + " -d - Increment debug level and set verbose\n" + " -v - Verbose, don't suppress messages\n", argv[0]); } int main(int argc, char* argv[]) { + int debug = 0; bool detachinout = true; int opt; - while ((opt = getopt(argc, argv, "hv")) != -1) { + while ((opt = getopt(argc, argv, "dhv")) != -1) { switch (opt) { default: usage(argv); @@ -68,6 +70,9 @@ int main(int argc, char* argv[]) case 'h': usage(argv); return 0; + case 'd': + debug++; + // Fall through case 'v': detachinout = false; break; @@ -91,7 +96,7 @@ int main(int argc, char* argv[]) #endif (void)! chdir ( "/" ); - ca_repeater (); + ca_repeater ( debug ); return ( 0 ); } diff --git a/modules/ca/src/client/repeater.cpp b/modules/ca/src/client/repeater.cpp index c7b019051..9ccf071e3 100644 --- a/modules/ca/src/client/repeater.cpp +++ b/modules/ca/src/client/repeater.cpp @@ -70,6 +70,7 @@ #include "taskwd.h" #include "errlog.h" +#define DEBUG // Tell iocinf.h to define debugPrint #include "iocinf.h" #include "caProto.h" #include "udpiiu.h" @@ -85,6 +86,8 @@ static tsDLList < repeaterClient > client_list; static const unsigned short PORT_ANY = 0u; +static int debug = 0; + /* * makeSocket() */ @@ -128,10 +131,10 @@ static int makeSocket ( unsigned short port, bool reuseAddr, SOCKET * pSock ) repeaterClient::repeaterClient ( const osiSockAddr &fromIn ) : from ( fromIn ), sock ( INVALID_SOCKET ) { -#ifdef DEBUG - unsigned port = ntohs ( from.ia.sin_port ); - debugPrintf ( ( "new client %u\n", port ) ); -#endif + if ( debug ) { + unsigned port = ntohs ( from.ia.sin_port ); + debugPrintf ( ( "New client on port %u\n", port ) ); + } } bool repeaterClient::connect () @@ -193,19 +196,19 @@ bool repeaterClient::sendMessage ( const void *pBuf, unsigned bufSize ) status = send ( this->sock, (char *) pBuf, bufSize, 0 ); if ( status >= 0 ) { assert ( static_cast ( status ) == bufSize ); -#ifdef DEBUG - epicsUInt16 port = ntohs ( this->from.ia.sin_port ); - debugPrintf ( ("Sent to %u\n", port ) ); -#endif + if ( debug > 1 ) { + epicsUInt16 port = ntohs ( this->from.ia.sin_port ); + debugPrintf ( ("Sent to port %u\n", port ) ); + } return true; } else { int errnoCpy = SOCKERRNO; if ( errnoCpy == SOCK_ECONNREFUSED ) { -#ifdef DEBUG - epicsUInt16 port = ntohs ( this->from.ia.sin_port ); - debugPrintf ( ("Client refused message %u\n", port ) ); -#endif + if ( debug ) { + epicsUInt16 port = ntohs ( this->from.ia.sin_port ); + debugPrintf ( ("Client on port %u refused message\n", port ) ); + } } else { char sockErrBuf[64]; @@ -221,10 +224,10 @@ repeaterClient::~repeaterClient () if ( this->sock != INVALID_SOCKET ) { epicsSocketDestroy ( this->sock ); } -#ifdef DEBUG - epicsUInt16 port = ntohs ( this->from.ia.sin_port ); - debugPrintf ( ( "Deleted client %u\n", port ) ); -#endif + if ( debug ) { + epicsUInt16 port = ntohs ( this->from.ia.sin_port ); + debugPrintf ( ( "Deleted client on port %u\n", port ) ); + } } void repeaterClient::operator delete ( void * ) @@ -286,6 +289,10 @@ bool repeaterClient::verify () if ( sockerrno == SOCK_EADDRINUSE ) { // Normal result, client using port + if ( debug > 1 ) { + epicsUInt16 port = ntohs ( this->from.ia.sin_port ); + debugPrintf ( ("Client on port %u is alive\n", port ) ); + } return true; } @@ -321,6 +328,9 @@ static void verifyClients ( tsFreeList < repeaterClient, 0x20 > & freeList ) freeList.release ( pclient ); } } + if ( debug ) { + debugPrintf ( ( "Verified %u active clients\n", theClients.count() ) ); + } client_list.add ( theClients ); } @@ -444,11 +454,11 @@ static void register_new_client ( osiSockAddr & from, client_list.remove ( *pNewClient ); pNewClient->~repeaterClient (); freeList.release ( pNewClient ); -# ifdef DEBUG + if ( debug ) { epicsUInt16 port = ntohs ( from.ia.sin_port ); - debugPrintf ( ( "Deleted repeater client=%u (error while sending ack)\n", + debugPrintf ( ( "Deleted repeater client on port %u, error sending ack\n", port ) ); -# endif + } } /* @@ -480,7 +490,7 @@ static void register_new_client ( osiSockAddr & from, /* * ca_repeater () */ -void ca_repeater () +void ca_repeater ( int setDebug ) { tsFreeList < repeaterClient, 0x20 > freeList; int size; @@ -489,6 +499,8 @@ void ca_repeater () unsigned short port; char * pBuf; + debug = setDebug; + pBuf = new char [MAX_UDP_RECV]; { @@ -504,7 +516,7 @@ void ca_repeater () */ if ( sockerrno == SOCK_EADDRINUSE ) { osiSockRelease (); - debugPrintf ( ( "CA Repeater: exiting because a repeater is already running\n" ) ); + debugPrintf ( ( "CA Repeater: Exiting, a repeater is already running\n" ) ); delete [] pBuf; return; } diff --git a/modules/ca/src/client/udpiiu.h b/modules/ca/src/client/udpiiu.h index 3b1177ece..f7964c5ab 100644 --- a/modules/ca/src/client/udpiiu.h +++ b/modules/ca/src/client/udpiiu.h @@ -59,7 +59,7 @@ LIBCA_API void epicsStdCall caRepeaterRegistrationMessage ( SOCKET sock, unsigned repeaterPort, unsigned attemptNumber ); extern "C" LIBCA_API void caRepeaterThread ( void * pDummy ); -LIBCA_API void ca_repeater ( void ); +LIBCA_API void ca_repeater ( int setDebug = 0 ); class cac; class cacContextNotify; From f4ccf7bc8ca6800aa3568d0b9d1ce4f7086760f1 Mon Sep 17 00:00:00 2001 From: Irie Railton <175622981+irierr@users.noreply.github.com> Date: Tue, 10 Mar 2026 17:48:05 +0000 Subject: [PATCH 05/16] iocsh command dbCreateRecord (#812) * skeleton for implementation of dbCreateRecord iocsh exposure * Add functionality of dbCreateRecordCallFunc Includes adding dependancy to iocInit.h and a new error type in dbStaticLib.h * Fix error message for case when adding record to IOC post init * add new error message for when no record name is passed and check for empty strings * add release note for dbCreateRecord IOC shell command * fix issues from codeql in release notes * fix documentation md based on codacy styling --- documentation/new-notes/PR-812.md | 16 ++++++ .../src/ioc/dbStatic/dbStaticIocRegister.c | 50 +++++++++++++++++++ .../database/src/ioc/dbStatic/dbStaticLib.h | 30 +++++------ 3 files changed, 82 insertions(+), 14 deletions(-) create mode 100644 documentation/new-notes/PR-812.md diff --git a/documentation/new-notes/PR-812.md b/documentation/new-notes/PR-812.md new file mode 100644 index 000000000..a15eabcca --- /dev/null +++ b/documentation/new-notes/PR-812.md @@ -0,0 +1,16 @@ +### Expose `dbCreateRecord` in IOC shell + +Add a new IOC shell command `dbCreateRecord` allowing for records to be created +from the shell. + +Only works when called before `iocInit` and will fail if called afterwards. + +The pdbbase variable, a record type, and the new record's name should be passed +as arguments. + +Example usage: + +```sh +dbCreateRecord(pdbbase, "ai", "example:ai") +iocInit() +``` diff --git a/modules/database/src/ioc/dbStatic/dbStaticIocRegister.c b/modules/database/src/ioc/dbStatic/dbStaticIocRegister.c index 26493f67a..c3dce5885 100644 --- a/modules/database/src/ioc/dbStatic/dbStaticIocRegister.c +++ b/modules/database/src/ioc/dbStatic/dbStaticIocRegister.c @@ -9,6 +9,7 @@ \*************************************************************************/ #include "iocsh.h" +#include "iocInit.h" #include "errSymTbl.h" #include "errlog.h" @@ -260,6 +261,54 @@ static void dbCreateAliasCallFunc(const iocshArgBuf *args) } } +static const iocshArg dbCreateRecordArg0 = { "recordType", iocshArgString}; +static const iocshArg dbCreateRecordArg1 = { "recordName", iocshArgString}; +static const iocshArg * const dbCreateRecordArgs[] = {&argPdbbase, &dbCreateRecordArg0, &dbCreateRecordArg1}; +static const iocshFuncDef dbCreateRecordFuncDef = { + "dbCreateRecord", + 3, + dbCreateRecordArgs, + "Add a new record to the database.\n" + "\n" + "Example: dbCreateRecord pdbbase ai record:name\n", +}; + +/** + * @brief Call function for dbCreateRecord iocsh command. + * Creates a new record in the database with the specified record type and record name. + * @param args The arguments passed from iocsh, where args[0] is the pointer to the database, + * args[1] is the record type name, and args[2] is the record name. + */ +static void dbCreateRecordCallFunc(const iocshArgBuf *args) +{ + DBENTRY ent; + long status; + enum iocStateEnum iocState; + + iocState = getIocState(); + if(iocState != iocVoid) { + status = S_dbLib_postInitRecRegister; + } + else { + dbInitEntry(*iocshPpdbbase, &ent); + if(!args[2].sval || (args[2].sval[0] == '\0')) { + status = S_dbLib_recordNameMissing; + } else if(!args[1].sval) { + status = S_dbLib_recordTypeNotFound; + } else { + status = dbFindRecordType(&ent, args[1].sval); + if(!status) { + status = dbCreateRecord(&ent, args[2].sval); + } + } + dbFinishEntry(&ent); + } + if(status) { + fprintf(stderr, ERL_ERROR ": %ld %s\n", status, errSymMsg(status)); + iocshSetError(1); + } +} + void dbStaticIocRegister(void) { iocshRegister(&dbDumpPathFuncDef, dbDumpPathCallFunc); @@ -278,4 +327,5 @@ void dbStaticIocRegister(void) iocshRegister(&dbPvdTableSizeFuncDef,dbPvdTableSizeCallFunc); iocshRegister(&dbReportDeviceConfigFuncDef, dbReportDeviceConfigCallFunc); iocshRegister(&dbCreateAliasFuncDef, dbCreateAliasCallFunc); + iocshRegister(&dbCreateRecordFuncDef, dbCreateRecordCallFunc); } diff --git a/modules/database/src/ioc/dbStatic/dbStaticLib.h b/modules/database/src/ioc/dbStatic/dbStaticLib.h index 6f772f27d..48f770dd5 100644 --- a/modules/database/src/ioc/dbStatic/dbStaticLib.h +++ b/modules/database/src/ioc/dbStatic/dbStaticLib.h @@ -252,20 +252,22 @@ DBCORE_API void dbCatString(char **string, int *stringLength, extern int dbStaticDebug; extern int dbConvertStrict; -#define S_dbLib_recordTypeNotFound (M_dbLib|1) /* Record Type does not exist */ -#define S_dbLib_recExists (M_dbLib|3) /* Record Already exists */ -#define S_dbLib_recNotFound (M_dbLib|5) /* Record Not Found */ -#define S_dbLib_flddesNotFound (M_dbLib|7) /* Field Description Not Found */ -#define S_dbLib_fieldNotFound (M_dbLib|9) /* Field Not Found */ -#define S_dbLib_badField (M_dbLib|11) /* Bad Field value */ -#define S_dbLib_menuNotFound (M_dbLib|13) /* Menu not found */ -#define S_dbLib_badLink (M_dbLib|15) /* Bad Link Field */ -#define S_dbLib_nameLength (M_dbLib|17) /* Record Name is too long */ -#define S_dbLib_noRecSup (M_dbLib|19) /* Record support not found */ -#define S_dbLib_strLen (M_dbLib|21) /* String is too long */ -#define S_dbLib_noSizeOffset (M_dbLib|23) /* Missing SizeOffset Routine - No record support? */ -#define S_dbLib_outMem (M_dbLib|27) /* Out of memory */ -#define S_dbLib_infoNotFound (M_dbLib|29) /* Info item Not Found */ +#define S_dbLib_recordTypeNotFound (M_dbLib|1) /* Record Type does not exist */ +#define S_dbLib_recExists (M_dbLib|3) /* Record Already exists */ +#define S_dbLib_recNotFound (M_dbLib|5) /* Record Not Found */ +#define S_dbLib_flddesNotFound (M_dbLib|7) /* Field Description Not Found */ +#define S_dbLib_fieldNotFound (M_dbLib|9) /* Field Not Found */ +#define S_dbLib_badField (M_dbLib|11) /* Bad Field value */ +#define S_dbLib_menuNotFound (M_dbLib|13) /* Menu not found */ +#define S_dbLib_badLink (M_dbLib|15) /* Bad Link Field */ +#define S_dbLib_nameLength (M_dbLib|17) /* Record Name is too long */ +#define S_dbLib_noRecSup (M_dbLib|19) /* Record support not found */ +#define S_dbLib_strLen (M_dbLib|21) /* String is too long */ +#define S_dbLib_noSizeOffset (M_dbLib|23) /* Missing SizeOffset Routine - No record support? */ +#define S_dbLib_outMem (M_dbLib|27) /* Out of memory */ +#define S_dbLib_infoNotFound (M_dbLib|29) /* Info item Not Found */ +#define S_dbLib_postInitRecRegister (M_dbLib|31) /* IOC already initialized - No new records can be added */ +#define S_dbLib_recordNameMissing (M_dbLib|33) /* Record name is required */ #ifdef __cplusplus } From 2d8a6f91a902b4cc0e8d9e11accb070275dd98fe Mon Sep 17 00:00:00 2001 From: ronaldomercado Date: Wed, 18 Mar 2026 15:45:25 +0000 Subject: [PATCH 06/16] Use consistent language for string termination (#806) Avoid hyphen, prefer space: "null terminated" --- modules/ca/src/client/CAref.html | 2 +- modules/ca/src/client/cadef.h | 2 +- modules/ca/src/client/db_access.h | 2 +- modules/database/src/ioc/db/dbChannel.h | 4 +-- modules/database/src/ioc/db/dbConvert.c | 4 +-- modules/database/src/ioc/db/dbLink.h | 2 +- modules/libcom/src/dbmf/dbmf.h | 6 ++-- modules/libcom/src/env/envDefs.h | 2 +- modules/libcom/src/macLib/macCore.c | 2 +- modules/libcom/src/macLib/macLib.h | 4 +-- modules/libcom/src/macLib/macLibREADME | 4 +-- modules/libcom/src/misc/epicsString.h | 10 +++---- modules/libcom/src/osi/epicsStdio.h | 2 +- modules/libcom/src/yajl/yajl_gen.h | 2 +- modules/libcom/src/yajl/yajl_parse.h | 2 +- modules/libcom/test/epicsStringTest.c | 40 ++++++++++++------------- 16 files changed, 45 insertions(+), 45 deletions(-) diff --git a/modules/ca/src/client/CAref.html b/modules/ca/src/client/CAref.html index 9b5c95bb3..3d8568e9b 100644 --- a/modules/ca/src/client/CAref.html +++ b/modules/ca/src/client/CAref.html @@ -2751,7 +2751,7 @@ time.

Arguments

PVNAME
-
A nil terminated process variable name string. EPICS process control +
A null terminated process variable name string. EPICS process control function block database variable names are of the form "<record name>.<field name>". If the field name and the period separator are omitted then the "VAL" field is implicit. For example "RFHV01" and diff --git a/modules/ca/src/client/cadef.h b/modules/ca/src/client/cadef.h index d03328ed1..65afb5fee 100644 --- a/modules/ca/src/client/cadef.h +++ b/modules/ca/src/client/cadef.h @@ -465,7 +465,7 @@ typedef unsigned capri; * * See `caExample.c` in the example application created by `makeBaseApp.pl`. * - * \param[in] pChanName A nil terminated process variable name string. + * \param[in] pChanName A null terminated process variable name string. * \parblock * EPICS process control * function block database variable names are of the * form `.`. If the field name and the period diff --git a/modules/ca/src/client/db_access.h b/modules/ca/src/client/db_access.h index 62d693bea..75debf0b5 100644 --- a/modules/ca/src/client/db_access.h +++ b/modules/ca/src/client/db_access.h @@ -128,7 +128,7 @@ LIBCA_API extern const int epicsTypeToDBR_XXXX [lastEpicsType+1]; LIBCA_API extern const epicsType DBR_XXXXToEpicsType [LAST_BUFFER_TYPE+1]; /* values returned for each field type - * DBR_STRING returns a NULL terminated string + * DBR_STRING returns a null terminated string * DBR_SHORT returns an unsigned short * DBR_INT returns an unsigned short * DBR_FLOAT returns an IEEE floating point value diff --git a/modules/database/src/ioc/db/dbChannel.h b/modules/database/src/ioc/db/dbChannel.h index 56ce8c4d6..0daed5a9c 100644 --- a/modules/database/src/ioc/db/dbChannel.h +++ b/modules/database/src/ioc/db/dbChannel.h @@ -206,7 +206,7 @@ typedef struct chFilterIf { * * Optional. * \param filter Pointer to instance data. - * \param stringVal Value, not zero-terminated. + * \param stringVal Value, not null terminated. * \param stringLen Number of chars in \p stringVal. * \returns \p parse_stop on error, or \p parse_continue */ @@ -226,7 +226,7 @@ typedef struct chFilterIf { * * Optional. * \param filter Pointer to instance data. - * \param key Value not zero-terminated. + * \param key Value not null terminated. * \param stringLen Number of chars in \p key * \returns \p parse_stop on error, or \p parse_continue */ diff --git a/modules/database/src/ioc/db/dbConvert.c b/modules/database/src/ioc/db/dbConvert.c index 2b17358c4..2592c90fc 100644 --- a/modules/database/src/ioc/db/dbConvert.c +++ b/modules/database/src/ioc/db/dbConvert.c @@ -444,7 +444,7 @@ static long getCharChar(const dbAddr *paddr, if (paddr->pfldDes && paddr->pfldDes->field_type == DBF_STRING) { /* This is a DBF_STRING field being read as a long string. - * The buffer we return must be zero-terminated. + * The buffer we return must be null terminated. */ pdst[--nRequest] = 0; if (nRequest == 0) @@ -466,7 +466,7 @@ static long getCharUchar(const dbAddr *paddr, if (paddr->pfldDes && paddr->pfldDes->field_type == DBF_STRING) { /* This is a DBF_STRING field being read as a long string. - * The buffer we return must be zero-terminated. + * The buffer we return must be null terminated. */ pdst[--nRequest] = 0; if (nRequest == 0) diff --git a/modules/database/src/ioc/db/dbLink.h b/modules/database/src/ioc/db/dbLink.h index e53133541..3c374655b 100644 --- a/modules/database/src/ioc/db/dbLink.h +++ b/modules/database/src/ioc/db/dbLink.h @@ -111,7 +111,7 @@ typedef struct lset { /** @brief load constant long string from link type * * Usually called during IOC initialization, constant link types must copy a - * nil-terminated string up to size characters long to the buffer provided, + * null terminated string up to size characters long to the buffer provided, * and write the length of that string to the plen location. A non-constant * link type can use this as an early hint that subsequent calls to * dbGetLink() will request long string data, although this might change. diff --git a/modules/libcom/src/dbmf/dbmf.h b/modules/libcom/src/dbmf/dbmf.h index f1df3d31c..dd62b988d 100644 --- a/modules/libcom/src/dbmf/dbmf.h +++ b/modules/libcom/src/dbmf/dbmf.h @@ -64,7 +64,7 @@ LIBCOM_API void * dbmfMalloc(size_t bytes); * \brief Duplicate a string. * * Create a copy of the input string. - * \param str Pointer to the null-terminated string to be copied. + * \param str Pointer to the null terminated string to be copied. * \return A pointer to the new copy, or NULL on failure. */ LIBCOM_API char * dbmfStrdup(const char *str); @@ -73,7 +73,7 @@ LIBCOM_API char * dbmfStrdup(const char *str); * * Copy at most len bytes of the input string into a new buffer. If no nil * terminator is seen in the first \c len bytes a nil terminator is added. - * \param str Pointer to the null-terminated string to be copied. + * \param str Pointer to the null terminated string to be copied. * \param len Max number of bytes to copy. * \return A pointer to the new string, or NULL on failure. */ @@ -81,7 +81,7 @@ LIBCOM_API char * dbmfStrndup(const char *str, size_t len); /** * \brief Concatenate three strings. - * Returns a pointer to a null-terminated string made by concatenating the + * Returns a pointer to a null terminated string made by concatenating the * three input strings. * \param lhs Start string to which the others get concatenated to (left part). * \param mid Next string to be concatenated to the lhs (mid part). diff --git a/modules/libcom/src/env/envDefs.h b/modules/libcom/src/env/envDefs.h index a768d13c8..ec336704c 100644 --- a/modules/libcom/src/env/envDefs.h +++ b/modules/libcom/src/env/envDefs.h @@ -104,7 +104,7 @@ struct in_addr; * \return Pointer to the environment variable value string, or * NULL if no parameter value and default value was empty. * - * \post A terminating nil will be written to pBuf. + * \post A terminating '\0' will be written to pBuf to ensure it is null terminated. */ LIBCOM_API char * epicsStdCall envGetConfigParam(const ENV_PARAM *pParam, int bufDim, char *pBuf); diff --git a/modules/libcom/src/macLib/macCore.c b/modules/libcom/src/macLib/macCore.c index 34910b015..cd19988f5 100644 --- a/modules/libcom/src/macLib/macCore.c +++ b/modules/libcom/src/macLib/macCore.c @@ -794,7 +794,7 @@ static void refer ( MAC_HANDLE *handle, MAC_ENTRY *entry, int level, r++; /* translate name (may contain macro references); truncated - quietly if too long but always guaranteed zero-terminated */ + quietly if too long but always guaranteed to be null terminated */ trans( handle, entry, level + 1, macEnd, &r, &rn, rn + MAC_SIZE ); refname[MAC_SIZE] = '\0'; diff --git a/modules/libcom/src/macLib/macLib.h b/modules/libcom/src/macLib/macLib.h index 4ed7ead3c..45c8ad984 100644 --- a/modules/libcom/src/macLib/macLib.h +++ b/modules/libcom/src/macLib/macLib.h @@ -126,7 +126,7 @@ epicsStdCall macPutValue( * \brief Returns the value of a macro * \return Returns the length of the value string, <0 if undefined * - * \c value will be zero-terminated if the length of the value is less than + * \c value will be null terminated if the length of the value is less than * \c capacity. The return value is the number of characters copied to * \c value (see below for behavior if the macro is undefined). If \c capacity * is zero, no characters will be copied to \c value (which may be NULL) @@ -277,7 +277,7 @@ epicsStdCall macInstallMacros( * environment variables. It parses the string looking for such * references and passes them to macGetValue() for translation. It uses * malloc() to allocate space for the expanded string and returns a - * pointer to this null-terminated string. It returns NULL if the source + * pointer to this null terminated string. It returns NULL if the source * string contains any undefined references. */ LIBCOM_API char * diff --git a/modules/libcom/src/macLib/macLibREADME b/modules/libcom/src/macLib/macLibREADME index 9efcb9a20..66fc95ea5 100644 --- a/modules/libcom/src/macLib/macLibREADME +++ b/modules/libcom/src/macLib/macLibREADME @@ -36,7 +36,7 @@ c) long macGetValue ( MAC_HANDLE *handle, char *name, char *value, long maxlen ); Returns up to maxlen characters of the value of macro "name". "value" - will be zero-terminated if the length of the value is less than + will be null terminated if the length of the value is less than maxlen. The function value will be the number of characters copied to "value" (see below for behavior if the macro is undefined). If maxlen is zero, no characters will be copied to "value" (which can be NULL) @@ -161,7 +161,7 @@ d) char *macEnvExpand( char *src ); environment variables. It parses the string looking for such references and passes them to macGetValue() for translation. It uses malloc() to allocate space for the expanded string and returns a - pointer to this null-terminated string. It returns NULL if the source + pointer to this null terminated string. It returns NULL if the source string contains any undefined references. e) char *macDefExpand( char *src, MAC_HANDLE *macros ); diff --git a/modules/libcom/src/misc/epicsString.h b/modules/libcom/src/misc/epicsString.h index 8f7f2b001..eee26553a 100644 --- a/modules/libcom/src/misc/epicsString.h +++ b/modules/libcom/src/misc/epicsString.h @@ -37,7 +37,7 @@ extern "C" { * \p inbuf and \p outbuf to point to the same buffer and \p outsize and \p inlen * be equal, thus performing the character translation in-place. * - * \param outbuf buffer to copy string to. The resulting string will be zero-terminated as long as + * \param outbuf buffer to copy string to. The resulting string will be null terminated as long as * \p outsize is non-zero. * \param outsize length of output buffer not including the null-terminator. * \param inbuf buffer to copy from. Null byte terminates the string. @@ -59,7 +59,7 @@ LIBCOM_API int epicsStrnRawFromEscaped(char *outbuf, size_t outsize, * All other non-printable characters appear in form `\xHH` where HH are two hex digits. * Non-printable characters are determined by the C runtime library’s isprint() function. * - * \param outbuf buffer to copy string to. The resulting string will be zero-terminated as long as + * \param outbuf buffer to copy string to. The resulting string will be null terminated as long as * @p outsize is non-zero. * \param outsize length of output buffer not including the null-terminator. * \param inbuf buffer to copy from. Null byte will not terminates the string. @@ -159,12 +159,12 @@ LIBCOM_API int epicsStrnGlobMatch(const char *str, size_t len, const char *patte */ LIBCOM_API char * epicsStrtok_r(char *s, const char *delim, char **lasts); -/** \brief Calculates a hash of a null-terminated string +/** \brief Calculates a hash of a null terminated string * - * Calculates a hash of a null-terminated string. Initial seed may be provided which + * Calculates a hash of a null terminated string. Initial seed may be provided which * permits multiple strings to be combined into a single hash result. * - *\param str null-terminated string + *\param str null terminated string *\param seed Optionally provide seed to combine multiple strings in a single hash. Otherwise * set to 0. * diff --git a/modules/libcom/src/osi/epicsStdio.h b/modules/libcom/src/osi/epicsStdio.h index b2bda61a3..1dd560040 100644 --- a/modules/libcom/src/osi/epicsStdio.h +++ b/modules/libcom/src/osi/epicsStdio.h @@ -125,7 +125,7 @@ extern "C" { * correctly return the number of characters that would have been written when * the output gets truncated, it is not worth trying to fix this as long as * they return `size-1` instead; the resulting string must always be correctly - * terminated with a zero byte. + * null terminated. * * In some scenarios the epicsSnprintf() implementation may not provide the * correct C99 semantics for the return value when `size` is given as zero. diff --git a/modules/libcom/src/yajl/yajl_gen.h b/modules/libcom/src/yajl/yajl_gen.h index 50b163cd0..974e364f8 100644 --- a/modules/libcom/src/yajl/yajl_gen.h +++ b/modules/libcom/src/yajl/yajl_gen.h @@ -196,7 +196,7 @@ extern "C" { /** Finish generating a JSON array. */ YAJL_API yajl_gen_status yajl_gen_array_close(yajl_gen hand); - /** Access the zero-terminated generator buffer. If incrementally + /** Access the null terminated generator buffer. If incrementally * outputting JSON, one should call yajl_gen_clear() to clear the * buffer. This allows stream generation. */ YAJL_API yajl_gen_status yajl_gen_get_buf(yajl_gen hand, diff --git a/modules/libcom/src/yajl/yajl_parse.h b/modules/libcom/src/yajl/yajl_parse.h index 2e5c2e34c..428e405a4 100644 --- a/modules/libcom/src/yajl/yajl_parse.h +++ b/modules/libcom/src/yajl/yajl_parse.h @@ -79,7 +79,7 @@ extern "C" { size_t numberLen); /** Strings are returned as pointers into the JSON text when - * possible. As a result they are _not_ zero-terminated. */ + * possible. As a result they are _not_ null terminated. */ int (* yajl_string)(void * ctx, const unsigned char * stringVal, size_t stringLen); diff --git a/modules/libcom/test/epicsStringTest.c b/modules/libcom/test/epicsStringTest.c index 08ede829b..77c53f041 100644 --- a/modules/libcom/test/epicsStringTest.c +++ b/modules/libcom/test/epicsStringTest.c @@ -313,13 +313,13 @@ MAIN(epicsStringTest) memset(result, 'x', sizeof(result)); status = epicsStrnEscapedFromRaw(result, 1, ABCD, 4); testOk(status == 4, "epicsStrnEscapedFromRaw(out, 1) -> %d (exp. 4)", status); - testOk(result[0] == 0, " 0-terminated"); + testOk(result[0] == 0, " null terminated"); testOk(result[1] == 'x', " No overrun"); memset(result, 'x', sizeof(result)); status = epicsStrnRawFromEscaped(result, 1, ABCD, 4); testOk(status == 0, "epicsStrnRawFromEscaped(out, 1) -> %d (exp. 0)", status); - testOk(result[0] == 0, " 0-terminated"); + testOk(result[0] == 0, " null terminated"); testOk(result[1] == 'x', " No overrun"); testDiag("Testing size = epicsStrnEscapedFromRawSize"); @@ -336,19 +336,19 @@ MAIN(epicsStringTest) memset(result, 'x', sizeof(result)); status = epicsStrnEscapedFromRaw(result, 4, ABCD, 3); testOk(status == 3, "esc(\"ABCD\", 3) -> %d (exp. 3)", status); - testOk(result[3] == 0, " 0-terminated"); + testOk(result[3] == 0, " null terminated"); testOk(result[4] == 'x', " No overrun"); memset(result, 'x', sizeof(result)); status = epicsStrnEscapedFromRaw(result, 4, ABCD, 4); testOk(status == 4, "esc(\"ABCD\", 4) -> %d (exp. 4)", status); - testOk(result[3] == 0, " 0-terminated"); + testOk(result[3] == 0, " null terminated"); testOk(result[4] == 'x', " No overrun"); memset(result, 'x', sizeof(result)); status = epicsStrnEscapedFromRaw(result, 4, ABCD, 5); testOk(status == 6, "esc(\"ABCD\", 5) -> %d (exp. 8)", status); - testOk(result[3] == 0, " 0-terminated"); + testOk(result[3] == 0, " null terminated"); testOk(result[4] == 'x', " No overrun"); testDiag("Testing raw = epicsStrnRawFromEscaped(out, 4, ...)"); @@ -356,20 +356,20 @@ MAIN(epicsStringTest) memset(result, 'x', sizeof(result)); status = epicsStrnRawFromEscaped(result, 4, ABCD, 0); testOk(status == 0, "raw(\"ABCD\", 0) -> %d (exp. 0)", status); - testOk(result[0] == 0, " 0-terminated"); + testOk(result[0] == 0, " null terminated"); memset(result, 'x', sizeof(result)); status = epicsStrnRawFromEscaped(result, 4, ABCD, 1); testOk(status == 1, "raw(\"ABCD\", 1) -> %d (exp. 1)", status); testOk(result[0] == 'A', " Char '%c' (exp. 'A')", result[0]); - testOk(result[1] == 0, " 0-terminated"); + testOk(result[1] == 0, " null terminated"); memset(result, 'x', sizeof(result)); status = epicsStrnRawFromEscaped(result, 4, ABCD, 2); testOk(status == 2, "raw(\"ABCD\", 2) -> %d (exp. 2)", status); testOk(result[0] == 'A', " Char '%c' (exp. 'A')", result[0]); testOk(result[1] == 'B', " Char '%c' (exp. 'B')", result[1]); - testOk(result[2] == 0, " 0-terminated"); + testOk(result[2] == 0, " null terminated"); memset(result, 'x', sizeof(result)); status = epicsStrnRawFromEscaped(result, 4, ABCD, 3); @@ -377,7 +377,7 @@ MAIN(epicsStringTest) testOk(result[0] == 'A', " Char '%c' (exp. 'A')", result[0]); testOk(result[1] == 'B', " Char '%c' (exp. 'B')", result[1]); testOk(result[2] == 'C', " Char '%c' (exp. 'C')", result[2]); - testOk(result[3] == 0, " 0-terminated"); + testOk(result[3] == 0, " null terminated"); testOk(result[4] == 'x', " No write outside buffer"); memset(result, 'x', sizeof(result)); @@ -386,7 +386,7 @@ MAIN(epicsStringTest) testOk(result[0] == 'A', " Char '%c' (exp. 'A')", result[0]); testOk(result[1] == 'B', " Char '%c' (exp. 'B')", result[1]); testOk(result[2] == 'C', " Char '%c' (exp. 'C')", result[2]); - testOk(result[3] == 0, " 0-terminated"); + testOk(result[3] == 0, " null terminated"); testOk(result[4] == 'x', " No write outside buffer"); memset(result, 'x', sizeof(result)); @@ -395,49 +395,49 @@ MAIN(epicsStringTest) testOk(result[0] == 'A', " Char '%c' (exp. 'A')", result[0]); testOk(result[1] == 'B', " Char '%c' (exp. 'B')", result[1]); testOk(result[2] == 'C', " Char '%c' (exp. 'C')", result[2]); - testOk(result[3] == 0, " 0-terminated"); + testOk(result[3] == 0, " null terminated"); testOk(result[4] == 'x', " No write outside buffer"); memset(result, 'x', sizeof(result)); status = epicsStrnRawFromEscaped(result, 4, "A", 2); testOk(status == 1, "raw(\"A\", 2) -> %d (exp. 1)", status); testOk(result[0] == 'A', " Char '%c' (exp. 'A')", result[0]); - testOk(result[status] == 0, " 0-terminated"); + testOk(result[status] == 0, " null terminated"); memset(result, 'x', sizeof(result)); status = epicsStrnRawFromEscaped(result, 4, "\\x12", 1); testOk(status == 0, "raw(\"\\x12\", 1) -> %d (exp. 0)", status); - testOk(result[status] == 0, " 0-terminated"); + testOk(result[status] == 0, " null terminated"); memset(result, 'x', sizeof(result)); status = epicsStrnRawFromEscaped(result, 4, "\\x12", 2); testOk(status == 0, "raw(\"\\x12\", 2) -> %d (exp. 0)", status); - testOk(result[status] == 0, " 0-terminated"); + testOk(result[status] == 0, " null terminated"); memset(result, 'x', sizeof(result)); status = epicsStrnRawFromEscaped(result, 4, "\\x12", 3); testOk(status == 1, "raw(\"\\x12\", 3) -> %d (exp. 1)", status); testOk(result[0] == 1, " Hex escape (got \\x%x)", result[0]); - testOk(result[status] == 0, " 0-terminated"); + testOk(result[status] == 0, " null terminated"); memset(result, 'x', sizeof(result)); status = epicsStrnRawFromEscaped(result, 4, "\\x12", 4); testOk(status == 1, "raw(\"\\x12\", 4) -> %d (exp. 1)", status); testOk(result[0] == 0x12," Hex escape (got \\x%x)", result[0]); - testOk(result[status] == 0, " 0-terminated"); + testOk(result[status] == 0, " null terminated"); memset(result, 'x', sizeof(result)); status = epicsStrnRawFromEscaped(result, 4, "\\xaF", 4); testOk(status == 1, "raw(\"\\xaF\", 4) -> %d (exp. 1)", status); testOk(result[0] == '\xaF'," Hex escape (got \\x%x)", result[0] & 0xFF); - testOk(result[status] == 0, " 0-terminated"); + testOk(result[status] == 0, " null terminated"); memset(result, 'x', sizeof(result)); status = epicsStrnRawFromEscaped(result, 4, "\\x012", 5); testOk(status == 2, "raw(\"\\x012\", 5) -> %d (exp. 2)", status); testOk(result[0] == 0x1," Hex escape (got \\x%x)", result[0]); testOk(result[1] == '2', " Terminator char got '%c'", result[1]); - testOk(result[status] == 0, " 0-terminated"); + testOk(result[status] == 0, " null terminated"); memset(result, 'x', sizeof(result)); status = epicsStrnRawFromEscaped(result, 4, "\\x0012", 6); @@ -445,14 +445,14 @@ MAIN(epicsStringTest) testOk(result[0] == 0," Hex escape (got \\x%x)", result[0]); testOk(result[1] == '1', " Terminator char got '%c'", result[1]); testOk(result[2] == '2', " Following char got '%c'", result[2]); - testOk(result[status] == 0, " 0-terminated"); + testOk(result[status] == 0, " null terminated"); memset(result, 'x', sizeof(result)); status = epicsStrnRawFromEscaped(result, 4, "\\x1g", 4); testOk(status == 2, "raw(\"\\x1g\", 4) -> %d (exp. 2)", status); testOk(result[0] == 1, " Hex escape (got \\x%x)", result[0]); testOk(result[1] == 'g', " Terminator char got '%c'", result[1]); - testOk(result[status] == 0, " 0-terminated"); + testOk(result[status] == 0, " null terminated"); testDistance(); testStrTok(); From 4dfd5c7694d5275338373a4818f6ae0b9453d1c6 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Tue, 31 Mar 2026 14:17:50 -0700 Subject: [PATCH 07/16] skip readline interactive setup when not interactive --- modules/libcom/src/iocsh/iocsh.cpp | 33 +++++++++++++++++------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/modules/libcom/src/iocsh/iocsh.cpp b/modules/libcom/src/iocsh/iocsh.cpp index 1ea0d631b..37658c8f9 100644 --- a/modules/libcom/src/iocsh/iocsh.cpp +++ b/modules/libcom/src/iocsh/iocsh.cpp @@ -605,6 +605,7 @@ char** iocsh_attempt_completion(const char* word, int start, int end) struct ReadlineContext { void *context; + FILE *context_fp; #ifdef USE_READLINE // readline on BSD/OSX missing 'rl_completion_func_t' typedef, and 'const' char* prev_rl_readline_name; @@ -618,12 +619,14 @@ struct ReadlineContext { ReadlineContext() :context(NULL) + ,context_fp(NULL) {} bool setup(FILE *fp) { + context_fp = fp; context = epicsReadlineBegin(fp); #ifdef USE_READLINE - if(context) { + if(context && !fp) { prev_rl_readline_name = (char*)rl_readline_name; prev_rl_basic_word_break_characters = (char*)rl_basic_word_break_characters; prev_rl_completer_word_break_characters = (char*)rl_completer_word_break_characters; @@ -656,21 +659,23 @@ struct ReadlineContext { ~ReadlineContext() { if(context) { #ifdef USE_READLINE - if(!hist_file.empty()) { - if(int err = write_history(hist_file.c_str())) { - fprintf(epicsGetStderr(), - ERL_ERROR " %s (%d) writing '%s'\n", - strerror(err), err, hist_file.c_str()); + if(!context_fp) { + if(!hist_file.empty()) { + if(int err = write_history(hist_file.c_str())) { + fprintf(epicsGetStderr(), + ERL_ERROR " %s (%d) writing '%s'\n", + strerror(err), err, hist_file.c_str()); + } } + rl_readline_name = prev_rl_readline_name; + rl_basic_word_break_characters = prev_rl_basic_word_break_characters; + rl_completer_word_break_characters = prev_rl_completer_word_break_characters; + rl_basic_quote_characters = prev_rl_basic_quote_characters; + rl_completer_quote_characters = prev_rl_completer_quote_characters; + rl_attempted_completion_function = prev_rl_attempted_completion_function; + // cf. osdReadlineBegin() in gnuReadline.c + rl_bind_key('\t', rl_insert); } - rl_readline_name = prev_rl_readline_name; - rl_basic_word_break_characters = prev_rl_basic_word_break_characters; - rl_completer_word_break_characters = prev_rl_completer_word_break_characters; - rl_basic_quote_characters = prev_rl_basic_quote_characters; - rl_completer_quote_characters = prev_rl_completer_quote_characters; - rl_attempted_completion_function = prev_rl_attempted_completion_function; - // cf. osdReadlineBegin() in gnuReadline.c - rl_bind_key('\t', rl_insert); #endif epicsReadlineEnd(context); } From e181517de11640ae90a82fad8fa799f9deb48a47 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Tue, 31 Mar 2026 15:57:19 -0700 Subject: [PATCH 08/16] readline history, clear before append read_history() appends to the global history array. Clear before each read to prevent duplication. --- modules/libcom/src/iocsh/iocsh.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/libcom/src/iocsh/iocsh.cpp b/modules/libcom/src/iocsh/iocsh.cpp index 37658c8f9..3182a57b2 100644 --- a/modules/libcom/src/iocsh/iocsh.cpp +++ b/modules/libcom/src/iocsh/iocsh.cpp @@ -643,6 +643,7 @@ struct ReadlineContext { rl_bind_key('\t', rl_complete); compute_hist_file(); if(!hist_file.empty()) { + clear_history(); // clear as read_history() will append if(int err = read_history(hist_file.c_str())) { if(err!=ENOENT) fprintf(epicsGetStderr(), From d0c7992c698a67050533f094585a88cbd1579de5 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Wed, 25 Mar 2026 10:11:54 -0500 Subject: [PATCH 09/16] Check build arch's are known before creating build dirs --- configure/RULES_ARCHS | 3 +++ 1 file changed, 3 insertions(+) diff --git a/configure/RULES_ARCHS b/configure/RULES_ARCHS index 0d4d944d4..2346e1b13 100644 --- a/configure/RULES_ARCHS +++ b/configure/RULES_ARCHS @@ -64,6 +64,9 @@ $(BUILD_ARCHS) : % : O.% O.Common $(ACTIONS) : % : $(foreach arch, $(BUILD_ARCHS), %$(DIVIDER)$(arch)) $(buildDirs): + $(if $(wildcard $(CONFIG)/os/CONFIG.Common.$(archPart) \ + $(CONFIG)/os/CONFIG.$(EPICS_HOST_ARCH).$(archPart)),, \ + $(error Base not configured to build target $(archPart))) $(PERL) $(TOOLS)/makeMakefile.pl $@ $(TOP)/.. O.Common: From 6ef94d744f4b6c16f534935a39aadfaf5494f129 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89rico=20Nogueira?= Date: Thu, 19 Mar 2026 11:03:15 -0300 Subject: [PATCH 10/16] Use deterministic timezones in epicsTimeZoneTest The DST characteristics of the POSIX EST5EDT and CST6CDT timezones depend on whether the implementation uses the built-in POSIX definition or reads an updated version from the tzdata files, and for POSIX timezones, there is no specified behavior [1]. To avoid any plaform-specific behavior, we can specify the desired DST ourselves. We used the timezone values available in configure/CONFIG_SITE_ENV, which already included DST information, and means the tests are consistent with our own usage. [1] https://www.openwall.com/lists/musl/2024/03/05/2 --- modules/libcom/test/epicsTimeZoneTest.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/libcom/test/epicsTimeZoneTest.c b/modules/libcom/test/epicsTimeZoneTest.c index f10688765..8437a33ad 100644 --- a/modules/libcom/test/epicsTimeZoneTest.c +++ b/modules/libcom/test/epicsTimeZoneTest.c @@ -89,10 +89,10 @@ MAIN(epicsTimeZoneTest) * Mon Oct 19 13:00:16 2015 UTC */ testDiag("POSIX 1445259616"); - setTZ("EST5EDT"); + setTZ("EST5EDT,M3.2.0/2,M11.1.0/2"); test_localtime(1445259616ul, 16, 0, 9, 19, 9, 2015, 1, 291, 1); test_gmtime (1445259616ul, 16, 0, 13, 19, 9, 2015, 1, 291, 0); - setTZ("CST6CDT"); + setTZ("CST6CDT,M3.2.0/2,M11.1.0/2"); test_localtime(1445259616ul, 16, 0, 8, 19, 9, 2015, 1, 291, 1); test_gmtime (1445259616ul, 16, 0, 13, 19, 9, 2015, 1, 291, 0); #if defined(__rtems__) @@ -112,10 +112,10 @@ MAIN(epicsTimeZoneTest) * Wed Jan 14 14:15:31 2015 UTC */ testDiag("POSIX 1421244931"); - setTZ("EST5EDT"); + setTZ("EST5EDT,M3.2.0/2,M11.1.0/2"); test_localtime(1421244931ul, 31, 15, 9, 14, 0, 2015, 3, 13, 0); test_gmtime (1421244931ul, 31, 15, 14, 14, 0, 2015, 3, 13, 0); - setTZ("CST6CDT"); + setTZ("CST6CDT,M3.2.0/2,M11.1.0/2"); test_localtime(1421244931ul, 31, 15, 8, 14, 0, 2015, 3, 13, 0); test_gmtime (1421244931ul, 31, 15, 14, 14, 0, 2015, 3, 13, 0); #if defined(__rtems__) From 77a3b8110fa0daba3b015b195436f27d9d1eac55 Mon Sep 17 00:00:00 2001 From: Evan Daykin Date: Wed, 18 Mar 2026 16:12:21 -0400 Subject: [PATCH 11/16] use (s)rand() to generate RNDM calc values with a system clock nanosecond seed --- modules/libcom/src/calc/calcPerform.c | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/modules/libcom/src/calc/calcPerform.c b/modules/libcom/src/calc/calcPerform.c index 340078f82..14bc73bf8 100644 --- a/modules/libcom/src/calc/calcPerform.c +++ b/modules/libcom/src/calc/calcPerform.c @@ -20,6 +20,7 @@ #include "osiUnistd.h" #include "dbDefs.h" #include "epicsMath.h" +#include "epicsTime.h" #include "epicsTypes.h" #include "errlog.h" #include "postfix.h" @@ -505,22 +506,18 @@ calcArgUsage(const char *pinst, unsigned long *pinputs, unsigned long *pstores) return 0; } -/* Generate a random number between 0 and 1 using the algorithm - * seed = (multy * seed) + addy Random Number Generator by Knuth - * SemiNumerical Algorithms - * Chapter 1 - * randy = seed / 65535.0 To normalize the number between 0 - 1 - */ -static unsigned short seed = 0xa3bf; -static unsigned short multy = 191 * 8 + 5; /* 191 % 8 == 5 */ -static unsigned short addy = 0x3141; - static double calcRandom(void) { - seed = (seed * multy) + addy; - + static int seeded = 0; + static struct epicsTimeStamp ts; + if(!seeded) + { + epicsTimeGetMonotonic(&ts); + srand((unsigned) ts.nsec); + seeded = 1; + } /* between 0 - 1 */ - return (double) seed / 65535.0; + return (double) rand() / RAND_MAX; } /* Search the instruction stream for a matching operator, skipping any From 1c9ef9481872e3e62955f3c3d8ad8d1b72ebedda Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Mon, 16 Mar 2026 12:15:50 -0500 Subject: [PATCH 12/16] Fix the bug reported in #834, but behaves differently Adds tests and checks for unterminated macros, which are now reported but not expanded. --- modules/libcom/src/macLib/macCore.c | 32 +++++++++++++++++++++-------- modules/libcom/test/macLibTest.c | 16 ++++++++++++++- 2 files changed, 39 insertions(+), 9 deletions(-) diff --git a/modules/libcom/src/macLib/macCore.c b/modules/libcom/src/macLib/macCore.c index cd19988f5..7a1f241f3 100644 --- a/modules/libcom/src/macLib/macCore.c +++ b/modules/libcom/src/macLib/macCore.c @@ -773,13 +773,14 @@ static void trans( MAC_HANDLE *handle, MAC_ENTRY *entry, int level, static void refer ( MAC_HANDLE *handle, MAC_ENTRY *entry, int level, const char **rawval, char **value, char *valend ) { - const char *r = *rawval; + const char *r = *rawval + 1; /* step over '$' */ + const char *macEnd = (*r++ == '(') ? "=,)" : "=,}"; + const char close = macEnd[2]; /* Matching ')' or '}' */ char *v = *value; char refname[MAC_SIZE + 1] = {'\0'}; char *rn = refname; MAC_ENTRY *refentry; const char *defval = NULL; - const char *macEnd; const char *errval = NULL; int pop = FALSE; @@ -788,14 +789,14 @@ static void refer ( MAC_HANDLE *handle, MAC_ENTRY *entry, int level, printf( "refer-> entry = %p, level = %d, capacity = %u, rawval = %s\n", entry, level, (unsigned int)(valend - *value), *rawval ); - /* step over '$(' or '${' */ - r++; - macEnd = ( *r == '(' ) ? "=,)" : "=,}"; - r++; - /* translate name (may contain macro references); truncated quietly if too long but always guaranteed to be null terminated */ - trans( handle, entry, level + 1, macEnd, &r, &rn, rn + MAC_SIZE ); + { + int flags = handle->flags; + handle->flags |= FLAG_SUPPRESS_WARNINGS; + trans( handle, entry, level + 1, macEnd, &r, &rn, rn + MAC_SIZE ); + handle->flags = flags; + } refname[MAC_SIZE] = '\0'; /* Is there a default value? */ @@ -858,6 +859,21 @@ static void refer ( MAC_HANDLE *handle, MAC_ENTRY *entry, int level, handle->flags = flags; } + /* If the closing delimiter doesn't match the opener, pass through verbatim */ + if ( *r != close ) { + const char *p; + v = *value; + for ( p = *rawval; p <= r && v < valend; p++ ) + *v++ = *p; + *v = '\0'; + entry->error = TRUE; + if ( (handle->flags & FLAG_SUPPRESS_WARNINGS) == 0 ) + errlogPrintf( "macLib: " ANSI_MAGENTA("unterminated") + " macro reference in %s %s\n", + entry->type, entry->name ); + goto cleanup; + } + /* Now we can look up the translated name */ refentry = lookup( handle, refname, FALSE ); diff --git a/modules/libcom/test/macLibTest.c b/modules/libcom/test/macLibTest.c index eea6268dd..4c5f6824c 100644 --- a/modules/libcom/test/macLibTest.c +++ b/modules/libcom/test/macLibTest.c @@ -66,13 +66,25 @@ static void ovcheck(void) MAIN(macLibTest) { - testPlan(93); + testPlan(105); if (macCreateHandle(&h, NULL)) testAbort("macCreateHandle() failed"); eltc(0); check("FOO", " FOO"); + check("F(O){O}", " F(O){O}"); + check("F(O}{O)", " F(O}{O)"); + + check("x${FOO)", "!x${FOO)"); + check("x${FOO=bar)", "!x${FOO=bar)"); + check("x${FOO,bar)", "!x${FOO,bar)"); + check("x${FOO ${BAR", "!x${FOO ${BAR"); + + check("x$(FOO}", "!x$(FOO}"); + check("x$(FOO=bar}", "!x$(FOO=bar}"); + check("x$(FOO,bar}", "!x$(FOO,bar}"); + check("x$(FOO $(BAR", "!x$(FOO $(BAR"); check("$(FOO)", "!$(FOO,undefined)"); check("${FOO}", "!$(FOO,undefined)"); @@ -115,6 +127,8 @@ MAIN(macLibTest) check("${=BAR}", " BAR"); check("x${=BAR}y", " xBARy"); + check("x(${=)}y", " x()y"); + check("x{$(=})y", " x{}y"); check("${FOO=BAR}", " BAR"); check("x${FOO=BAR}y", " xBARy"); From 05c27e453c08ffddd0f366e989173a1f6a5771f3 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Mon, 16 Mar 2026 12:16:39 -0500 Subject: [PATCH 13/16] Color the keywords in macLib errors --- modules/libcom/src/macLib/macCore.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/modules/libcom/src/macLib/macCore.c b/modules/libcom/src/macLib/macCore.c index 7a1f241f3..165d2583a 100644 --- a/modules/libcom/src/macLib/macCore.c +++ b/modules/libcom/src/macLib/macCore.c @@ -897,7 +897,8 @@ static void refer ( MAC_HANDLE *handle, MAC_ENTRY *entry, int level, entry->error = TRUE; errval = ",recursive)"; if ( (handle->flags & FLAG_SUPPRESS_WARNINGS) == 0 ) { - errlogPrintf( "macLib: %s %s is recursive (expanding %s %s)\n", + errlogPrintf( "macLib: %s %s is " ANSI_MAGENTA("recursive") + " (expanding %s %s)\n", entry->type, entry->name, refentry->type, refentry->name ); } @@ -911,8 +912,9 @@ static void refer ( MAC_HANDLE *handle, MAC_ENTRY *entry, int level, entry->error = TRUE; errval = ",undefined)"; if ( (handle->flags & FLAG_SUPPRESS_WARNINGS) == 0 ) { - errlogPrintf( "macLib: macro %s is " ANSI_MAGENTA("undefined") " (expanding %s %s)\n", - refname, entry->type, entry->name ); + errlogPrintf( "macLib: macro %s is " ANSI_MAGENTA("undefined") + " (expanding %s %s)\n", + refname, entry->type, entry->name ); } } From fb09dc0df99dafdf6e5ef751a911282a22ba4eb8 Mon Sep 17 00:00:00 2001 From: Freddie Akeroyd Date: Tue, 17 Mar 2026 19:52:34 +0000 Subject: [PATCH 14/16] Fix memory overwrite in macParseDefns --- modules/libcom/src/macLib/macUtil.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/libcom/src/macLib/macUtil.c b/modules/libcom/src/macLib/macUtil.c index 920ac006f..5c04c9075 100644 --- a/modules/libcom/src/macLib/macUtil.c +++ b/modules/libcom/src/macLib/macUtil.c @@ -62,8 +62,11 @@ epicsStdCall macParseDefns( printf( "macParseDefns( %s )\n", defns ); /* allocate temporary pointer arrays; in worst case they need to have - as many entries as the length of the defns string */ - numMax = strlen( defns ); + as many entries as the length of the defns string + 2 + for defns="1,3,5" give 3 pairs so num will be 6 (strlen(defns)+1) + but as num is used as an index e.g. del[num] we need + numMax to be strlen(defns) + 2 */ + numMax = strlen( defns ) + 2; if ( numMax < altNumMax ) numMax = altNumMax; ptr = (const char **) calloc( numMax, sizeof( char * ) ); From acf53d31eebfcce694c093141cf0ba6d88189263 Mon Sep 17 00:00:00 2001 From: Freddie Akeroyd Date: Fri, 20 Mar 2026 17:20:23 +0000 Subject: [PATCH 15/16] Add test case --- modules/libcom/test/macDefExpandTest.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/modules/libcom/test/macDefExpandTest.c b/modules/libcom/test/macDefExpandTest.c index 37f11f9f4..14cec45b9 100644 --- a/modules/libcom/test/macDefExpandTest.c +++ b/modules/libcom/test/macDefExpandTest.c @@ -61,6 +61,27 @@ static void macEnvScope(void) checkMac(handle, "E", "15"); checkMac(handle, "F", NULL); + { + /* test worst case for short definitions + * https://github.com/epics-base/epics-base/pull/838 + */ + macParseDefns(NULL, "1,3,5", &defines); /* smoke */ + +#define CASE(I,V) \ + testOk(defines[I] && strcmp(defines[I], V)==0, "defines[%u] \"%s\" == \"" V "\"", I, defines[I]) + CASE(0,"1"); + testOk1(defines[1]==NULL); + CASE(2,"3"); + testOk1(defines[3]==NULL); + CASE(4,"5"); + testOk1(defines[5]==NULL); + testOk1(defines[6]==NULL); +#undef CASE + + free(defines); + defines = NULL; + } + { macPushScope(handle); From 631c8831b942ee5533a2f7016085dcef52c2f3ba Mon Sep 17 00:00:00 2001 From: Freddie Akeroyd Date: Fri, 20 Mar 2026 21:41:19 +0000 Subject: [PATCH 16/16] Fix number of tests --- modules/libcom/test/macDefExpandTest.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/libcom/test/macDefExpandTest.c b/modules/libcom/test/macDefExpandTest.c index 14cec45b9..a93727a58 100644 --- a/modules/libcom/test/macDefExpandTest.c +++ b/modules/libcom/test/macDefExpandTest.c @@ -169,7 +169,7 @@ static void check(const char *str, const char *macros, const char *expect) MAIN(macDefExpandTest) { eltc(0); - testPlan(97); + testPlan(104); check("FOO", "", "FOO");