From c042b08ab062df1eda71aad141a81ba2145d2913 Mon Sep 17 00:00:00 2001 From: Jure Varlec Date: Tue, 10 May 2022 18:42:05 +0200 Subject: [PATCH 01/29] Extend timestamp filter, giving access to the record timestamp --- modules/database/src/ioc/db/db_field_log.h | 17 +- .../database/src/std/filters/filters.dbd.pod | 92 +++++- modules/database/src/std/filters/ts.c | 311 +++++++++++++++++- modules/database/test/std/filters/tsTest.c | 265 ++++++++++++--- 4 files changed, 608 insertions(+), 77 deletions(-) diff --git a/modules/database/src/ioc/db/db_field_log.h b/modules/database/src/ioc/db/db_field_log.h index 6f57859e2..87fd238d4 100644 --- a/modules/database/src/ioc/db/db_field_log.h +++ b/modules/database/src/ioc/db/db_field_log.h @@ -39,12 +39,17 @@ extern "C" { * will adjust automatically, it just compares field sizes. */ union native_value { - epicsInt8 dbf_char; - epicsInt16 dbf_short; - epicsEnum16 dbf_enum; - epicsInt32 dbf_long; - epicsFloat32 dbf_float; - epicsFloat64 dbf_double; + epicsInt8 dbf_char; + epicsUInt8 dbf_uchar; + epicsInt16 dbf_short; + epicsUInt16 dbf_ushort; + epicsEnum16 dbf_enum; + epicsInt32 dbf_long; + epicsUInt32 dbf_ulong; + epicsInt64 dbf_int64; + epicsUInt64 dbf_uint64; + epicsFloat32 dbf_float; + epicsFloat64 dbf_double; #ifdef DB_EVENT_LOG_STRINGS char dbf_string[MAX_STRING_SIZE]; #endif diff --git a/modules/database/src/std/filters/filters.dbd.pod b/modules/database/src/std/filters/filters.dbd.pod index 7f72ebe6c..5f727a959 100644 --- a/modules/database/src/std/filters/filters.dbd.pod +++ b/modules/database/src/std/filters/filters.dbd.pod @@ -216,22 +216,96 @@ registrar(tsInitialize) =head3 TimeStamp Filter C<"ts"> -This filter replaces the timestamp in the data fetched through the channel with -the time the value was fetched (or an update was sent). The record's timestamp +This filter is used for two purposes: + +=over + +=item * to retrieve the timestamp of the record as a value in several different + formats; + +=item * to retrieve the record value as normal, but replace the timestamp with + the time the value was fetched. + +=back + +=head4 Parameters + +=head4 No parameters (an empty pair of braces) + +Retrieve the record value as normal, but replace the timestamp with the time the +value was fetched (or an update was sent). This is useful for clients that can't +handle timestamps that are far in the past. Normally, the record's timestamp indicates when the record last processed, which could have been days or even weeks ago for some records, or set to the EPICS epoch if the record has never processed. -=head4 Parameters +=head4 Numeric type C<"num"> -None, use an empty pair of braces. +The following values are accepted for this parameter: -=head4 Example +=over - Hal$ caget -a 'test:channel.{"ts":{}}' - test:channel.{"ts":{}} 2012-08-28 22:10:31.192547 0 UDF INVALID - Hal$ caget -a 'test:channel' - test:channel 0 UDF INVALID +=item * C<"dbl"> requests the timestamp as C representing the + non-integral number of seconds since epoch. This format is convenient, + but loses precision, depending on which epoch is used. + +=item * C<"sec"> requests the number of seconds since epoch as C. + +=item * C<"nsec"> requests the number of nanoseconds since epoch as + C. + +=item * C<"ts"> requests the entire timestamp. It is provided as a two-element + array of C representing seconds and nanoseconds. + +=back + +Note that C cannot be transferred over Channel Access; in that +case, the value will be converted to C. + +=head4 String type C<"str"> + +The following values are accepted for this parameter: + +=over + +=item * C<"epics"> requests the timestamp as a string in the format used by + tools such as C. + +=item * C<"iso"> requests the timestamp as a string in the ISO8601 format. + +=back + +=head4 Epoch adjustment C<"epoch"> + +The following values are accepted for this parameter: + +=over + +=item * C<"epics"> keeps the EPICS epoch (1990-01-01) and is the default if the + C<"epoch"> parameter is not specified. + +=item * C<"unix"> converts the timestamp to the UNIX/POSIX epoch (1970-01-01). + +=back + +=head4 Examples + + Hal$ caget -a 'test:invalid_ts.{"ts":{}}' + test:invalid_ts.{"ts":{}} 2012-08-28 22:10:31.192547 0 UDF INVALID + Hal$ caget -a 'test:invalid_ts' + test:invalid_ts 0 UDF INVALID + Hal$ caget -a test:channel + test:channel 2021-03-11 18:23:48.265386 42 + Hal$ caget 'test:channel.{"ts": {"str": "epics"}}' + test:channel.{"ts": {"str": "epics"}} 2021-03-11 18:23:48.265386 + Hal$ caget 'test:channel.{"ts": {"str": "iso"}}' + test:channel.{"ts": {"str": "iso"}} 2021-03-11T18:23:48.265386+0100 + Hal$ caget -f9 'test:channel.{"ts": {"num": "dbl"}}' + test:channel.{"ts": {"num": "dbl"}} 984331428.265386105 + Hal$ caget -f1 'test:channel.{"ts": {"num": "ts"}}' + test:channel.{"ts": {"num": "ts"}} 2 984331428.0 265386163.0 + Hal$ caget -f1 'test:channel.{"ts": {"num": "ts", "epoch": "unix"}}' + test:channel.{"ts": {"num": "ts", "epoch": "unix"}} 2 1615483428.0 265386163.0 =cut diff --git a/modules/database/src/std/filters/ts.c b/modules/database/src/std/filters/ts.c index 0a0a4d3ba..b98f3e19c 100644 --- a/modules/database/src/std/filters/ts.c +++ b/modules/database/src/std/filters/ts.c @@ -1,4 +1,5 @@ /*************************************************************************\ +* Copyright (c) 2021 Cosylab d.d * Copyright (c) 2010 Brookhaven National Laboratory. * Copyright (c) 2010 Helmholtz-Zentrum Berlin * fuer Materialien und Energie GmbH. @@ -9,6 +10,7 @@ /* * Author: Ralph Lange + * Author: Jure Varlec */ #include @@ -16,20 +18,122 @@ #include #include "chfPlugin.h" -#include "db_field_log.h" #include "dbExtractArray.h" #include "dbLock.h" +#include "db_field_log.h" #include "epicsExport.h" +#include "freeList.h" +#include "cantProceed.h" -/* - * The size of the data is different for each channel, and can even - * change at runtime, so a freeList doesn't make much sense here. - */ +/* Allocation size for freelists */ +#define ALLOC_NUM_ELEMENTS 32 + + +/* Filter settings */ + +enum tsMode { + tsModeInvalid = 0, + tsModeGenerate = 1, + tsModeDouble = 2, + tsModeSec = 3, + tsModeNsec = 4, + tsModeArray = 5, + tsModeString = 6, +}; + +static const chfPluginEnumType ts_numeric_enum[] = { + {"dbl", 2}, {"sec", 3}, {"nsec", 4}, {"ts", 5}}; + +enum tsEpoch { + tsEpochEpics = 0, + tsEpochUnix = 1, +}; + +static const chfPluginEnumType ts_epoch_enum[] = {{"epics", 0}, {"unix", 1}}; + +enum tsString { + tsStringInvalid = 0, + tsStringEpics = 1, + tsStringIso = 2, +}; + +static const chfPluginEnumType ts_string_enum[] = {{"epics", 1}, {"iso", 2}}; + +typedef struct tsPrivate { + enum tsMode mode; + enum tsEpoch epoch; + enum tsString str; +} tsPrivate; + +static const chfPluginArgDef ts_args[] = { + chfEnum(tsPrivate, mode, "num", 0, 0, ts_numeric_enum), + chfEnum(tsPrivate, epoch, "epoch", 0, 0, ts_epoch_enum), + chfEnum(tsPrivate, str, "str", 0, 0, ts_string_enum), + chfPluginArgEnd +}; + +static int parse_finished(void *pvt) { + tsPrivate *settings = (tsPrivate *)pvt; + if (settings->str != tsStringInvalid) { + settings->mode = tsModeString; +#if defined _MSC_VER && _MSC_VER <= 1700 + // VS 2012 crashes in ISO mode, doesn't support timezones + if (settings->str == tsStringIso) { + return -1; + } +#endif + } else if (settings->mode == tsModeInvalid) { + settings->mode = tsModeGenerate; + } + return 0; +} + + +/* Allocation of filter settings */ +static void *private_free_list; + +static void * allocPvt() { + return freeListCalloc(private_free_list); +} + +static void freePvt(void *pvt) { + freeListFree(private_free_list, pvt); +} + + +/* Allocation of two-element arrays for second+nanosecond pairs */ +static void *ts_array_free_list; + +static void *allocTsArray() { + return freeListCalloc(ts_array_free_list); +} + +static void freeTsArray(db_field_log *pfl) { + freeListFree(ts_array_free_list, pfl->u.r.field); +} + +/* Allocation of strings */ +static void *string_free_list; + +static void *allocString() { + return freeListCalloc(string_free_list); +} + +static void freeString(db_field_log *pfl) { + freeListFree(string_free_list, pfl->u.r.field); +} + + +/* The dtor for waveform data for the case when we have to copy it. */ static void freeArray(db_field_log *pfl) { + /* + * The size of the data is different for each channel, and can even + * change at runtime, so a freeList doesn't make much sense here. + */ free(pfl->u.r.field); } -static db_field_log* filter(void* pvt, dbChannel *chan, db_field_log *pfl) { +static db_field_log* generate(void* pvt, dbChannel *chan, db_field_log *pfl) { epicsTimeStamp now; epicsTimeGetCurrent(&now); @@ -44,7 +148,7 @@ static db_field_log* filter(void* pvt, dbChannel *chan, db_field_log *pfl) { dbScanLock(dbChannelRecord(chan)); dbChannelGetArrayInfo(chan, &pSource, &nSource, &offset); dbExtractArray(pSource, pTarget, pfl->field_size, - nSource, pfl->no_elements, offset, 1); + nSource, pfl->no_elements, offset, 1); pfl->u.r.field = pTarget; pfl->dtor = freeArray; pfl->u.r.pvt = pvt; @@ -56,34 +160,207 @@ static db_field_log* filter(void* pvt, dbChannel *chan, db_field_log *pfl) { return pfl; } -static void channelRegisterPre(dbChannel *chan, void *pvt, - chPostEventFunc **cb_out, void **arg_out, db_field_log *probe) -{ +static db_field_log *replace_fl_value(tsPrivate const *pvt, + db_field_log *pfl, + void (*func)(tsPrivate const *, + db_field_log *)) { + /* Get rid of the old value */ + if (pfl->type == dbfl_type_ref && pfl->u.r.dtor) { + pfl->u.r.dtor(pfl); + } + pfl->no_elements = 1; + pfl->type = dbfl_type_val; + + func(pvt, pfl); + return pfl; +} + +static void ts_to_array(tsPrivate const *settings, + epicsTimeStamp const *ts, + epicsUInt32 arr[2]) { + arr[0] = ts->secPastEpoch; + arr[1] = ts->nsec; + if (settings->epoch == tsEpochUnix) { + /* Cannot use epicsTimeToWhatever because Whatever uses signed ints */ + arr[0] += POSIX_TIME_AT_EPICS_EPOCH; + } +} + +static void ts_seconds(tsPrivate const *settings, db_field_log *pfl) { + epicsUInt32 arr[2]; + ts_to_array(settings, &pfl->time, arr); + pfl->field_type = DBF_ULONG; + pfl->field_size = sizeof(epicsUInt32); + pfl->u.v.field.dbf_ulong = arr[0]; +} + +static void ts_nanos(tsPrivate const *settings, db_field_log *pfl) { + epicsUInt32 arr[2]; + ts_to_array(settings, &pfl->time, arr); + pfl->field_type = DBF_ULONG; + pfl->field_size = sizeof(epicsUInt32); + pfl->u.v.field.dbf_ulong = arr[1]; +} + +static void ts_double(tsPrivate const *settings, db_field_log *pfl) { + epicsUInt32 arr[2]; + ts_to_array(settings, &pfl->time, arr); + pfl->field_type = DBF_DOUBLE; + pfl->field_size = sizeof(epicsFloat64); + pfl->u.v.field.dbf_double = arr[0] + arr[1] * 1e-9; +} + +static void ts_array(tsPrivate const *settings, db_field_log *pfl) { + pfl->field_type = DBF_ULONG; + pfl->field_size = sizeof(epicsUInt32); + pfl->no_elements = 2; + pfl->type = dbfl_type_ref; + pfl->u.r.pvt = NULL; + pfl->u.r.field = allocTsArray(); + pfl->u.r.dtor = freeTsArray; + ts_to_array(settings, &pfl->time, (epicsUInt32*)pfl->u.r.field); +} + +static void ts_string(tsPrivate const *settings, db_field_log *pfl) { + char const *fmt; + char *field; + size_t n; + + pfl->field_type = DBF_STRING; + pfl->field_size = MAX_STRING_SIZE; + pfl->type = dbfl_type_ref; + pfl->u.r.pvt = NULL; + pfl->u.r.field = allocString(); + pfl->u.r.dtor = freeString; + + switch (settings->str) { + case tsStringEpics: + fmt = "%Y-%m-%d %H:%M:%S.%06f"; + break; + case tsStringIso: + fmt = "%Y-%m-%dT%H:%M:%S.%06f%z"; + break; + case tsStringInvalid: + default: + fmt = ""; // Silence compiler warning. + cantProceed("Logic error: invalid state encountered in ts filter"); + } + + field = (char *)pfl->u.r.field; + n = epicsTimeToStrftime(field, MAX_STRING_SIZE, fmt, &pfl->time); + if (!n) { + field[0] = 0; + } +} + +static db_field_log *filter(void *pvt, dbChannel *chan, db_field_log *pfl) { + tsPrivate *settings = (tsPrivate *)pvt; + + switch (settings->mode) { + case tsModeDouble: + return replace_fl_value(pvt, pfl, ts_double); + case tsModeSec: + return replace_fl_value(pvt, pfl, ts_seconds); + case tsModeNsec: + return replace_fl_value(pvt, pfl, ts_nanos); + case tsModeArray: + return replace_fl_value(pvt, pfl, ts_array); + case tsModeString: + return replace_fl_value(pvt, pfl, ts_string); + case tsModeGenerate: + case tsModeInvalid: + cantProceed("Logic error: invalid state encountered in ts filter"); + } + + return pfl; +} + + +/* Only the "generate" mode is registered for the pre-queue chain as it creates + it's own timestamp which should be as close to the event as possible */ +static void channelRegisterPre(dbChannel * chan, void *pvt, + chPostEventFunc **cb_out, void **arg_out, + db_field_log *probe) { + tsPrivate *settings = (tsPrivate *)pvt; + *cb_out = settings->mode == tsModeGenerate ? generate : NULL; +} + +/* For other modes, the post-chain is fine as they only manipulate existing + timestamps */ +static void channelRegisterPost(dbChannel *chan, void *pvt, + chPostEventFunc **cb_out, void **arg_out, + db_field_log *probe) { + tsPrivate *settings = (tsPrivate *)pvt; + + if (settings->mode == tsModeGenerate || settings->mode == tsModeInvalid) { + *cb_out = NULL; + return; + } + *cb_out = filter; + *arg_out = pvt; + + /* Get rid of the value of the probe because we will be changing the + datatype */ + if (probe->type == dbfl_type_ref && probe->u.r.dtor) { + probe->u.r.dtor(probe); + } + probe->no_elements = 1; + probe->type = dbfl_type_val; + + switch (settings->mode) { + case tsModeArray: + probe->no_elements = 2; + /* fallthrough */ + case tsModeSec: + case tsModeNsec: + probe->field_type = DBF_ULONG; + probe->field_size = sizeof(epicsUInt32); + break; + case tsModeDouble: + probe->field_type = DBF_DOUBLE; + probe->field_size = sizeof(epicsFloat64); + break; + case tsModeString: + probe->field_type = DBF_STRING; + probe->field_size = MAX_STRING_SIZE; + break; + case tsModeGenerate: + case tsModeInvalid: + cantProceed("Logic error: invalid state encountered in ts filter"); + } } static void channel_report(dbChannel *chan, void *pvt, int level, const unsigned short indent) { - printf("%*sTimestamp (ts)\n", indent, ""); + tsPrivate *settings = (tsPrivate *)pvt; + printf("%*sTimestamp (ts): mode: %d, epoch: %d, str: %d\n", + indent, "", settings->mode, settings->epoch, settings->str); } static chfPluginIf pif = { - NULL, /* allocPvt, */ - NULL, /* freePvt, */ + allocPvt, + freePvt, - NULL, /* parse_error, */ - NULL, /* parse_ok, */ + NULL, /* parse_error, */ + parse_finished, NULL, /* channel_open, */ channelRegisterPre, - NULL, /* channelRegisterPost, */ + channelRegisterPost, channel_report, NULL /* channel_close */ }; static void tsInitialize(void) { - chfPluginRegister("ts", &pif, NULL); + freeListInitPvt(&private_free_list, sizeof(tsPrivate), + ALLOC_NUM_ELEMENTS); + freeListInitPvt(&ts_array_free_list, 2 * sizeof(epicsUInt32), + ALLOC_NUM_ELEMENTS); + freeListInitPvt(&string_free_list, MAX_STRING_SIZE, + ALLOC_NUM_ELEMENTS); + chfPluginRegister("ts", &pif, ts_args); } epicsExportRegistrar(tsInitialize); diff --git a/modules/database/test/std/filters/tsTest.c b/modules/database/test/std/filters/tsTest.c index e26dd41d1..697d17559 100644 --- a/modules/database/test/std/filters/tsTest.c +++ b/modules/database/test/std/filters/tsTest.c @@ -1,4 +1,5 @@ /*************************************************************************\ +* Copyright (c) 2021 Cosylab d.d * Copyright (c) 2010 Brookhaven National Laboratory. * Copyright (c) 2010 Helmholtz-Zentrum Berlin * fuer Materialien und Energie GmbH. @@ -9,9 +10,11 @@ /* * Author: Ralph Lange + * Author: Jure Varlec */ #include +#include #include "dbStaticLib.h" #include "dbAccessDefs.h" @@ -25,11 +28,24 @@ #include "testMain.h" #include "osiFileName.h" +/* A fill pattern for setting a field log to something "random". */ #define PATTERN 0x55 -void filterTest_registerRecordDeviceDriver(struct dbBase *); +/* Use a "normal" timestamp for testing. What results from filling the field log + with the above pattern is a timestamp that causes problems with + epicsTimeToStrftime() on some platforms. */ +static epicsTimeStamp const test_ts = { 616600420, 998425354 }; -static db_field_log fl; +typedef int (*TypeCheck)(const db_field_log *pfl); +typedef int (*ValueCheck)(const db_field_log *pfl, const epicsTimeStamp *ts); + +typedef struct { + char const *channel; + TypeCheck type_check; + ValueCheck value_check; +} TestSpec; + +void filterTest_registerRecordDeviceDriver(struct dbBase *); static int fl_equal(const db_field_log *pfl1, const db_field_log *pfl2) { return !(memcmp(pfl1, pfl2, sizeof(db_field_log))); @@ -42,21 +58,209 @@ static int fl_equal_ex_ts(const db_field_log *pfl1, const db_field_log *pfl2) { return fl_equal(&fl1, pfl2); } -MAIN(tsTest) -{ +static void fl_reset(db_field_log *pfl) { + memset(pfl, PATTERN, sizeof(*pfl)); + pfl->time = test_ts; +} + +static void test_generate_filter(const chFilterPlugin *plug) { dbChannel *pch; chFilter *filter; - const chFilterPlugin *plug; - char ts[] = "ts"; - ELLNODE *node; - chPostEventFunc *cb_out = NULL; - void *arg_out = NULL; + db_field_log fl; db_field_log fl1; db_field_log *pfl2; epicsTimeStamp stamp, now; - dbEventCtx evtctx; + ELLNODE *node; + chPostEventFunc *cb_out = NULL; + void *arg_out = NULL; + char const *chan_name = "x.VAL{ts:{}}"; - testPlan(12); + testOk(!!(pch = dbChannelCreate(chan_name)), + "dbChannel with plugin ts created"); + testOk((ellCount(&pch->filters) == 1), "channel has one plugin"); + + fl_reset(&fl); + fl1 = fl; + node = ellFirst(&pch->filters); + filter = CONTAINER(node, chFilter, list_node); + plug->fif->channel_register_post(filter, &cb_out, &arg_out, &fl1); + plug->fif->channel_register_pre(filter, &cb_out, &arg_out, &fl1); + testOk(!!(cb_out) && !(arg_out), + "register_pre registers one filter w/o argument"); + testOk(fl_equal(&fl1, &fl), + "register_pre does not change field_log data type"); + + testOk(!(dbChannelOpen(pch)), "dbChannel with plugin ts opened"); + node = ellFirst(&pch->pre_chain); + filter = CONTAINER(node, chFilter, pre_node); + testOk((ellCount(&pch->pre_chain) == 1 && filter->pre_arg == NULL), + "ts has one filter w/o argument in pre chain"); + testOk((ellCount(&pch->post_chain) == 0), "ts has no filter in post chain"); + + fl_reset(&fl); + fl1 = fl; + pfl2 = dbChannelRunPreChain(pch, &fl1); + testOk(pfl2 == &fl1, "ts filter does not drop or replace field_log"); + testOk(fl_equal_ex_ts(&fl1, pfl2), + "ts filter does not change field_log data"); + + testOk(!!(pfl2 = db_create_read_log(pch)), "create field log from channel"); + stamp = pfl2->time; + db_delete_field_log(pfl2); + + pfl2 = dbChannelRunPreChain(pch, &fl1); + epicsTimeGetCurrent(&now); + testOk(epicsTimeDiffInSeconds(&pfl2->time, &stamp) >= 0 && + epicsTimeDiffInSeconds(&now, &pfl2->time) >= 0, + "ts filter sets time stamp to \"now\""); + + dbChannelDelete(pch); +} + +static void test_value_filter(const chFilterPlugin *plug, const char *chan_name, + TypeCheck tc_func, ValueCheck vc_func) { + dbChannel *pch; + chFilter *filter; + db_field_log fl; + db_field_log fl2; + db_field_log *pfl; + epicsTimeStamp ts; + ELLNODE *node; + chPostEventFunc *cb_out = NULL; + void *arg_out = NULL; + + testDiag("Channel %s", chan_name); + + testOk(!!(pch = dbChannelCreate(chan_name)), + "dbChannel with plugin ts created"); + testOk((ellCount(&pch->filters) == 1), "channel has one plugin"); + + fl_reset(&fl); + fl.type = dbfl_type_val; + node = ellFirst(&pch->filters); + filter = CONTAINER(node, chFilter, list_node); + plug->fif->channel_register_pre(filter, &cb_out, &arg_out, &fl); + plug->fif->channel_register_post(filter, &cb_out, &arg_out, &fl); + testOk(!!(cb_out) && arg_out, + "register_post registers one filter with argument"); + testOk(tc_func(&fl), "register_post gives correct field type"); + + testOk(!(dbChannelOpen(pch)), "dbChannel with plugin ts opened"); + node = ellFirst(&pch->post_chain); + filter = CONTAINER(node, chFilter, post_node); + testOk((ellCount(&pch->post_chain) == 1 && filter->post_arg != NULL), + "ts has one filter with argument in post chain"); + testOk((ellCount(&pch->pre_chain) == 0), "ts has no filter in pre chain"); + + fl_reset(&fl); + fl.type = dbfl_type_val; + ts = fl.time; + fl2 = fl; + pfl = dbChannelRunPostChain(pch, &fl); + testOk(pfl == &fl, "ts filter does not drop or replace field_log"); + testOk(tc_func(pfl), "ts filter gives correct field type"); + testOk((pfl->time.secPastEpoch == fl2.time.secPastEpoch && + pfl->time.nsec == fl2.time.nsec && pfl->stat == fl2.stat && + pfl->sevr == fl2.sevr), + "ts filter does not touch non-value fields of field_log"); + testOk(vc_func(pfl, &ts), "ts filter gives correct field value"); + + dbChannelDelete(pch); +} + +static int type_check_double(const db_field_log *pfl) { + return pfl->type == dbfl_type_val + && pfl->field_type == DBR_DOUBLE + && pfl->field_size == sizeof(epicsFloat64) + && pfl->no_elements == 1; +} + +static int value_check_double(const db_field_log *pfl, const epicsTimeStamp *ts) { + epicsFloat64 flt = pfl->u.v.field.dbf_double; + epicsFloat64 nsec = (flt - (epicsUInt32)(flt)) * 1e9; + return ts->secPastEpoch == (epicsUInt32)(flt) + && fabs(ts->nsec - nsec) < 1000.; /* allow loss of precision */ +} + +static int type_check_sec_nsec(const db_field_log *pfl) { + return pfl->type == dbfl_type_val + && pfl->field_type == DBR_ULONG + && pfl->field_size == sizeof(epicsUInt32) + && pfl->no_elements == 1; +} + +static int value_check_sec(const db_field_log *pfl, const epicsTimeStamp *ts) { + return ts->secPastEpoch == pfl->u.v.field.dbf_ulong; +} + +static int value_check_nsec(const db_field_log *pfl, const epicsTimeStamp *ts) { + return ts->nsec == pfl->u.v.field.dbf_ulong; +} + +static int type_check_array(const db_field_log *pfl) { + return pfl->field_type == DBR_ULONG + && pfl->field_size == sizeof(epicsUInt32) + && pfl->no_elements == 2; +} + +static int value_check_array(const db_field_log *pfl, const epicsTimeStamp *ts) { + epicsUInt32 *arr = (epicsUInt32*)pfl->u.r.field; + return pfl->type == dbfl_type_ref + && pfl->u.r.field != NULL + && pfl->u.r.dtor != NULL + && pfl->u.r.pvt == NULL + && ts->secPastEpoch == arr[0] + && ts->nsec == arr[1]; +} + +static int value_check_unix(const db_field_log *pfl, const epicsTimeStamp *ts) { + epicsUInt32 *arr = (epicsUInt32 *)pfl->u.r.field; + return pfl->type == dbfl_type_ref + && pfl->u.r.field != NULL + && pfl->u.r.dtor != NULL + && pfl->u.r.pvt == NULL + && ts->secPastEpoch == arr[0] - POSIX_TIME_AT_EPICS_EPOCH + && ts->nsec == arr[1]; +} + +static int type_check_string(const db_field_log *pfl) { + return pfl->field_type == DBR_STRING + && pfl->field_size == MAX_STRING_SIZE + && pfl->no_elements == 1; +} + +static int value_check_string(const db_field_log *pfl, const epicsTimeStamp *ts) { + /* We can only verify the type, not the value, because using strptime() + might be problematic. */ + return pfl->type == dbfl_type_ref + && pfl->u.r.field != NULL + && pfl->u.r.dtor != NULL + && pfl->u.r.pvt == NULL; +} + +MAIN(tsTest) { + int i; + char ts[] = "ts"; + dbEventCtx evtctx; + const chFilterPlugin *plug; + + static TestSpec const tests[] = { + {"x.VAL{ts:{\"num\": \"dbl\"}}", type_check_double, value_check_double}, + {"x.VAL{ts:{\"num\": \"sec\"}}", type_check_sec_nsec, value_check_sec}, + {"x.VAL{ts:{\"num\": \"nsec\"}}", type_check_sec_nsec, value_check_nsec}, + {"x.VAL{ts:{\"num\": \"ts\"}}", type_check_array, value_check_array}, + {"x.VAL{ts:{\"num\": \"ts\", \"epoch\": \"epics\"}}", type_check_array, value_check_array}, + {"x.VAL{ts:{\"num\": \"ts\", \"epoch\": \"unix\"}}", type_check_array, value_check_unix}, + {"x.VAL{ts:{\"str\": \"epics\"}}", type_check_string, value_check_string}, +#if !(defined _MSC_VER && _MSC_VER <= 1700) + {"x.VAL{ts:{\"str\": \"iso\"}}", type_check_string, value_check_string}, +#endif + }; + + static int const num_value_tests = sizeof(tests) / sizeof(tests[0]); + + testPlan(12 /* test_generate_filter() */ + + num_value_tests * 11 /* test_value_filter() */); testdbPrepare(); @@ -77,41 +281,12 @@ MAIN(tsTest) testAbort("plugin '%s' not registered", ts); testPass("plugin '%s' registered correctly", ts); - testOk(!!(pch = dbChannelCreate("x.VAL{ts:{}}")), "dbChannel with plugin ts created"); - testOk((ellCount(&pch->filters) == 1), "channel has one plugin"); + test_generate_filter(plug); - memset(&fl, PATTERN, sizeof(fl)); - fl1 = fl; - node = ellFirst(&pch->filters); - filter = CONTAINER(node, chFilter, list_node); - plug->fif->channel_register_pre(filter, &cb_out, &arg_out, &fl1); - testOk(!!(cb_out) && !(arg_out), "register_pre registers one filter w/o argument"); - testOk(fl_equal(&fl1, &fl), "register_pre does not change field_log data type"); - - testOk(!(dbChannelOpen(pch)), "dbChannel with plugin ts opened"); - node = ellFirst(&pch->pre_chain); - filter = CONTAINER(node, chFilter, pre_node); - testOk((ellCount(&pch->pre_chain) == 1 && filter->pre_arg == NULL), - "ts has one filter w/o argument in pre chain"); - testOk((ellCount(&pch->post_chain) == 0), "ts has no filter in post chain"); - - memset(&fl, PATTERN, sizeof(fl)); - fl1 = fl; - pfl2 = dbChannelRunPreChain(pch, &fl1); - testOk(pfl2 == &fl1, "ts filter does not drop or replace field_log"); - testOk(fl_equal_ex_ts(&fl1, pfl2), "ts filter does not change field_log data"); - - testOk(!!(pfl2 = db_create_read_log(pch)), "create field log from channel"); - stamp = pfl2->time; - db_delete_field_log(pfl2); - - pfl2 = dbChannelRunPreChain(pch, &fl1); - epicsTimeGetCurrent(&now); - testOk(epicsTimeDiffInSeconds(&pfl2->time, &stamp) >= 0 && - epicsTimeDiffInSeconds(&now, &pfl2->time) >= 0, - "ts filter sets time stamp to \"now\""); - - dbChannelDelete(pch); + for (i = 0; i < num_value_tests; ++i) { + TestSpec const *t = &tests[i]; + test_value_filter(plug, t->channel, t->type_check, t->value_check); + } db_close_events(evtctx); From e10dcede7d4b1d2f4c3b6aa45610de0c07a6b538 Mon Sep 17 00:00:00 2001 From: Jure Varlec Date: Thu, 19 May 2022 10:10:45 +0200 Subject: [PATCH 02/29] ts filter: clear the dtor field after destruction --- modules/database/src/std/filters/ts.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/database/src/std/filters/ts.c b/modules/database/src/std/filters/ts.c index b98f3e19c..24520195c 100644 --- a/modules/database/src/std/filters/ts.c +++ b/modules/database/src/std/filters/ts.c @@ -167,6 +167,7 @@ static db_field_log *replace_fl_value(tsPrivate const *pvt, /* Get rid of the old value */ if (pfl->type == dbfl_type_ref && pfl->u.r.dtor) { pfl->u.r.dtor(pfl); + pfl->u.r.dtor = NULL; } pfl->no_elements = 1; pfl->type = dbfl_type_val; @@ -304,6 +305,7 @@ static void channelRegisterPost(dbChannel *chan, void *pvt, datatype */ if (probe->type == dbfl_type_ref && probe->u.r.dtor) { probe->u.r.dtor(probe); + probe->u.r.dtor = NULL; } probe->no_elements = 1; probe->type = dbfl_type_val; From d5959ca20ae956c2f9c68ef356f446c604f245ce Mon Sep 17 00:00:00 2001 From: Jure Varlec Date: Thu, 19 May 2022 10:52:51 +0200 Subject: [PATCH 03/29] ts filter: handle calloc failures --- modules/database/src/std/filters/ts.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/modules/database/src/std/filters/ts.c b/modules/database/src/std/filters/ts.c index 24520195c..6d4ea7ba2 100644 --- a/modules/database/src/std/filters/ts.c +++ b/modules/database/src/std/filters/ts.c @@ -214,12 +214,17 @@ static void ts_double(tsPrivate const *settings, db_field_log *pfl) { static void ts_array(tsPrivate const *settings, db_field_log *pfl) { pfl->field_type = DBF_ULONG; pfl->field_size = sizeof(epicsUInt32); - pfl->no_elements = 2; pfl->type = dbfl_type_ref; pfl->u.r.pvt = NULL; pfl->u.r.field = allocTsArray(); - pfl->u.r.dtor = freeTsArray; - ts_to_array(settings, &pfl->time, (epicsUInt32*)pfl->u.r.field); + if (pfl->u.r.field) { + pfl->no_elements = 2; + pfl->u.r.dtor = freeTsArray; + ts_to_array(settings, &pfl->time, (epicsUInt32*)pfl->u.r.field); + } else { + pfl->no_elements = 0; + pfl->u.r.dtor = NULL; + } } static void ts_string(tsPrivate const *settings, db_field_log *pfl) { @@ -232,6 +237,13 @@ static void ts_string(tsPrivate const *settings, db_field_log *pfl) { pfl->type = dbfl_type_ref; pfl->u.r.pvt = NULL; pfl->u.r.field = allocString(); + + if (!pfl->u.r.field) { + pfl->no_elements = 0; + pfl->u.r.dtor = NULL; + return; + } + pfl->u.r.dtor = freeString; switch (settings->str) { From bd1af9ac95d3a6160c358644192f5f3b446bcd4f Mon Sep 17 00:00:00 2001 From: Jure Varlec Date: Thu, 19 May 2022 10:56:10 +0200 Subject: [PATCH 04/29] ts filter: fix unused variable warnings --- modules/database/src/std/filters/ts.c | 9 +++++++++ modules/database/test/std/filters/tsTest.c | 1 + 2 files changed, 10 insertions(+) diff --git a/modules/database/src/std/filters/ts.c b/modules/database/src/std/filters/ts.c index 6d4ea7ba2..b3c747dc6 100644 --- a/modules/database/src/std/filters/ts.c +++ b/modules/database/src/std/filters/ts.c @@ -268,6 +268,7 @@ static void ts_string(tsPrivate const *settings, db_field_log *pfl) { static db_field_log *filter(void *pvt, dbChannel *chan, db_field_log *pfl) { tsPrivate *settings = (tsPrivate *)pvt; + (void)chan; switch (settings->mode) { case tsModeDouble: @@ -295,6 +296,10 @@ static void channelRegisterPre(dbChannel * chan, void *pvt, chPostEventFunc **cb_out, void **arg_out, db_field_log *probe) { tsPrivate *settings = (tsPrivate *)pvt; + (void)chan; + (void)arg_out; + (void)probe; + *cb_out = settings->mode == tsModeGenerate ? generate : NULL; } @@ -304,6 +309,7 @@ static void channelRegisterPost(dbChannel *chan, void *pvt, chPostEventFunc **cb_out, void **arg_out, db_field_log *probe) { tsPrivate *settings = (tsPrivate *)pvt; + (void)chan; if (settings->mode == tsModeGenerate || settings->mode == tsModeInvalid) { *cb_out = NULL; @@ -348,6 +354,9 @@ static void channelRegisterPost(dbChannel *chan, void *pvt, static void channel_report(dbChannel *chan, void *pvt, int level, const unsigned short indent) { tsPrivate *settings = (tsPrivate *)pvt; + (void)chan; + (void)level; + printf("%*sTimestamp (ts): mode: %d, epoch: %d, str: %d\n", indent, "", settings->mode, settings->epoch, settings->str); } diff --git a/modules/database/test/std/filters/tsTest.c b/modules/database/test/std/filters/tsTest.c index 697d17559..67d42ebba 100644 --- a/modules/database/test/std/filters/tsTest.c +++ b/modules/database/test/std/filters/tsTest.c @@ -232,6 +232,7 @@ static int type_check_string(const db_field_log *pfl) { static int value_check_string(const db_field_log *pfl, const epicsTimeStamp *ts) { /* We can only verify the type, not the value, because using strptime() might be problematic. */ + (void)ts; return pfl->type == dbfl_type_ref && pfl->u.r.field != NULL && pfl->u.r.dtor != NULL From 8a3020033e4abe287a1b050172134dca5caf75ad Mon Sep 17 00:00:00 2001 From: Jure Varlec Date: Wed, 15 Jun 2022 16:10:31 +0200 Subject: [PATCH 05/29] ts filter: replace cantProceed with a non-fatal error msg --- modules/database/src/std/filters/ts.c | 72 +++++++++++++++++---------- 1 file changed, 45 insertions(+), 27 deletions(-) diff --git a/modules/database/src/std/filters/ts.c b/modules/database/src/std/filters/ts.c index b3c747dc6..3c2eea2fd 100644 --- a/modules/database/src/std/filters/ts.c +++ b/modules/database/src/std/filters/ts.c @@ -23,11 +23,13 @@ #include "db_field_log.h" #include "epicsExport.h" #include "freeList.h" -#include "cantProceed.h" +#include "errlog.h" /* Allocation size for freelists */ #define ALLOC_NUM_ELEMENTS 32 +#define logicErrorMessage() \ + errMessage(-1, "Logic error: invalid state encountered in ts filter") /* Filter settings */ @@ -162,8 +164,8 @@ static db_field_log* generate(void* pvt, dbChannel *chan, db_field_log *pfl) { static db_field_log *replace_fl_value(tsPrivate const *pvt, db_field_log *pfl, - void (*func)(tsPrivate const *, - db_field_log *)) { + int (*func)(tsPrivate const *, + db_field_log *)) { /* Get rid of the old value */ if (pfl->type == dbfl_type_ref && pfl->u.r.dtor) { pfl->u.r.dtor(pfl); @@ -172,7 +174,11 @@ static db_field_log *replace_fl_value(tsPrivate const *pvt, pfl->no_elements = 1; pfl->type = dbfl_type_val; - func(pvt, pfl); + if (func(pvt, pfl)) { + db_delete_field_log(pfl); + pfl = NULL; + } + return pfl; } @@ -187,31 +193,34 @@ static void ts_to_array(tsPrivate const *settings, } } -static void ts_seconds(tsPrivate const *settings, db_field_log *pfl) { +static int ts_seconds(tsPrivate const *settings, db_field_log *pfl) { epicsUInt32 arr[2]; ts_to_array(settings, &pfl->time, arr); pfl->field_type = DBF_ULONG; pfl->field_size = sizeof(epicsUInt32); pfl->u.v.field.dbf_ulong = arr[0]; + return 0; } -static void ts_nanos(tsPrivate const *settings, db_field_log *pfl) { +static int ts_nanos(tsPrivate const *settings, db_field_log *pfl) { epicsUInt32 arr[2]; ts_to_array(settings, &pfl->time, arr); pfl->field_type = DBF_ULONG; pfl->field_size = sizeof(epicsUInt32); pfl->u.v.field.dbf_ulong = arr[1]; + return 0; } -static void ts_double(tsPrivate const *settings, db_field_log *pfl) { +static int ts_double(tsPrivate const *settings, db_field_log *pfl) { epicsUInt32 arr[2]; ts_to_array(settings, &pfl->time, arr); pfl->field_type = DBF_DOUBLE; pfl->field_size = sizeof(epicsFloat64); pfl->u.v.field.dbf_double = arr[0] + arr[1] * 1e-9; + return 0; } -static void ts_array(tsPrivate const *settings, db_field_log *pfl) { +static int ts_array(tsPrivate const *settings, db_field_log *pfl) { pfl->field_type = DBF_ULONG; pfl->field_size = sizeof(epicsUInt32); pfl->type = dbfl_type_ref; @@ -225,27 +234,14 @@ static void ts_array(tsPrivate const *settings, db_field_log *pfl) { pfl->no_elements = 0; pfl->u.r.dtor = NULL; } + return 0; } -static void ts_string(tsPrivate const *settings, db_field_log *pfl) { +static int ts_string(tsPrivate const *settings, db_field_log *pfl) { char const *fmt; char *field; size_t n; - pfl->field_type = DBF_STRING; - pfl->field_size = MAX_STRING_SIZE; - pfl->type = dbfl_type_ref; - pfl->u.r.pvt = NULL; - pfl->u.r.field = allocString(); - - if (!pfl->u.r.field) { - pfl->no_elements = 0; - pfl->u.r.dtor = NULL; - return; - } - - pfl->u.r.dtor = freeString; - switch (settings->str) { case tsStringEpics: fmt = "%Y-%m-%d %H:%M:%S.%06f"; @@ -255,15 +251,31 @@ static void ts_string(tsPrivate const *settings, db_field_log *pfl) { break; case tsStringInvalid: default: - fmt = ""; // Silence compiler warning. - cantProceed("Logic error: invalid state encountered in ts filter"); + logicErrorMessage(); + return 1; } + pfl->field_type = DBF_STRING; + pfl->field_size = MAX_STRING_SIZE; + pfl->type = dbfl_type_ref; + pfl->u.r.pvt = NULL; + pfl->u.r.field = allocString(); + + if (!pfl->u.r.field) { + pfl->no_elements = 0; + pfl->u.r.dtor = NULL; + return 0; + } + + pfl->u.r.dtor = freeString; + field = (char *)pfl->u.r.field; n = epicsTimeToStrftime(field, MAX_STRING_SIZE, fmt, &pfl->time); if (!n) { field[0] = 0; } + + return 0; } static db_field_log *filter(void *pvt, dbChannel *chan, db_field_log *pfl) { @@ -283,7 +295,10 @@ static db_field_log *filter(void *pvt, dbChannel *chan, db_field_log *pfl) { return replace_fl_value(pvt, pfl, ts_string); case tsModeGenerate: case tsModeInvalid: - cantProceed("Logic error: invalid state encountered in ts filter"); + default: + logicErrorMessage(); + db_delete_field_log(pfl); + pfl = NULL; } return pfl; @@ -347,7 +362,10 @@ static void channelRegisterPost(dbChannel *chan, void *pvt, break; case tsModeGenerate: case tsModeInvalid: - cantProceed("Logic error: invalid state encountered in ts filter"); + // Already handled above, added here for completeness. + default: + logicErrorMessage(); + *cb_out = NULL; } } From e11f88017da959e7ed5a2481819b93b2376f3f5b Mon Sep 17 00:00:00 2001 From: Jure Varlec Date: Tue, 18 Oct 2022 17:05:05 +0200 Subject: [PATCH 06/29] ts filter: port to the new db_field_log --- modules/database/src/std/filters/ts.c | 20 ++++++++++---------- modules/database/test/std/filters/tsTest.c | 6 +++--- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/modules/database/src/std/filters/ts.c b/modules/database/src/std/filters/ts.c index 3c2eea2fd..056b23d8b 100644 --- a/modules/database/src/std/filters/ts.c +++ b/modules/database/src/std/filters/ts.c @@ -167,9 +167,9 @@ static db_field_log *replace_fl_value(tsPrivate const *pvt, int (*func)(tsPrivate const *, db_field_log *)) { /* Get rid of the old value */ - if (pfl->type == dbfl_type_ref && pfl->u.r.dtor) { - pfl->u.r.dtor(pfl); - pfl->u.r.dtor = NULL; + if (pfl->type == dbfl_type_ref && pfl->dtor) { + pfl->dtor(pfl); + pfl->dtor = NULL; } pfl->no_elements = 1; pfl->type = dbfl_type_val; @@ -228,11 +228,11 @@ static int ts_array(tsPrivate const *settings, db_field_log *pfl) { pfl->u.r.field = allocTsArray(); if (pfl->u.r.field) { pfl->no_elements = 2; - pfl->u.r.dtor = freeTsArray; + pfl->dtor = freeTsArray; ts_to_array(settings, &pfl->time, (epicsUInt32*)pfl->u.r.field); } else { pfl->no_elements = 0; - pfl->u.r.dtor = NULL; + pfl->dtor = NULL; } return 0; } @@ -263,11 +263,11 @@ static int ts_string(tsPrivate const *settings, db_field_log *pfl) { if (!pfl->u.r.field) { pfl->no_elements = 0; - pfl->u.r.dtor = NULL; + pfl->dtor = NULL; return 0; } - pfl->u.r.dtor = freeString; + pfl->dtor = freeString; field = (char *)pfl->u.r.field; n = epicsTimeToStrftime(field, MAX_STRING_SIZE, fmt, &pfl->time); @@ -336,9 +336,9 @@ static void channelRegisterPost(dbChannel *chan, void *pvt, /* Get rid of the value of the probe because we will be changing the datatype */ - if (probe->type == dbfl_type_ref && probe->u.r.dtor) { - probe->u.r.dtor(probe); - probe->u.r.dtor = NULL; + if (probe->type == dbfl_type_ref && probe->dtor) { + probe->dtor(probe); + probe->dtor = NULL; } probe->no_elements = 1; probe->type = dbfl_type_val; diff --git a/modules/database/test/std/filters/tsTest.c b/modules/database/test/std/filters/tsTest.c index 67d42ebba..9f4036330 100644 --- a/modules/database/test/std/filters/tsTest.c +++ b/modules/database/test/std/filters/tsTest.c @@ -207,7 +207,7 @@ static int value_check_array(const db_field_log *pfl, const epicsTimeStamp *ts) epicsUInt32 *arr = (epicsUInt32*)pfl->u.r.field; return pfl->type == dbfl_type_ref && pfl->u.r.field != NULL - && pfl->u.r.dtor != NULL + && pfl->dtor != NULL && pfl->u.r.pvt == NULL && ts->secPastEpoch == arr[0] && ts->nsec == arr[1]; @@ -217,7 +217,7 @@ static int value_check_unix(const db_field_log *pfl, const epicsTimeStamp *ts) { epicsUInt32 *arr = (epicsUInt32 *)pfl->u.r.field; return pfl->type == dbfl_type_ref && pfl->u.r.field != NULL - && pfl->u.r.dtor != NULL + && pfl->dtor != NULL && pfl->u.r.pvt == NULL && ts->secPastEpoch == arr[0] - POSIX_TIME_AT_EPICS_EPOCH && ts->nsec == arr[1]; @@ -235,7 +235,7 @@ static int value_check_string(const db_field_log *pfl, const epicsTimeStamp *ts) (void)ts; return pfl->type == dbfl_type_ref && pfl->u.r.field != NULL - && pfl->u.r.dtor != NULL + && pfl->dtor != NULL && pfl->u.r.pvt == NULL; } From 9655b78e1108d2f805a134c396c71fc7d9d69791 Mon Sep 17 00:00:00 2001 From: Jure Varlec Date: Tue, 18 Oct 2022 16:52:38 +0200 Subject: [PATCH 07/29] Update release notes: ts filter --- documentation/RELEASE_NOTES.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/documentation/RELEASE_NOTES.md b/documentation/RELEASE_NOTES.md index c8c3d327d..4af0dbbc6 100644 --- a/documentation/RELEASE_NOTES.md +++ b/documentation/RELEASE_NOTES.md @@ -48,6 +48,23 @@ The compress record now supports the use of partially-filled buffers when using any of the N-to-one algorithms. This is achieved by setting the new field `PBUF` to `YES`. +### Extended timestamp channel filter + +The `"ts"` filter can now retrieve the record's timestamp in several numeric +and string formats, some of which support full nanosecond precision. + + Hal$ caget -a test:channel + test:channel 2021-03-11 18:23:48.265386 42 + Hal$ caget -f9 'test:channel.{"ts": {"num": "dbl"}}' + test:channel.{"ts": {"num": "dbl"}} 984331428.265386105 + Hal$ caget 'test:channel.{"ts": {"str": "iso"}}' + test:channel.{"ts": {"str": "iso"}} 2021-03-11T18:23:48.265386+0100 + Hal$ caget -f1 'test:channel.{"ts": {"num": "ts"}}' + test:channel.{"ts": {"num": "ts"}} 2 984331428.0 265386163.0 + +More information is included in the filters documentation, which can be found in +the `html/filters.html` document that is generated during the build + ### Add conditional output (OOPT) to the longout record The longout record can now be configured using its new OOPT and OOCH fields From bdaca51d9696249bdac10fdcb75086324e4a3d53 Mon Sep 17 00:00:00 2001 From: Jure Varlec Date: Thu, 5 Jan 2023 16:54:56 +0100 Subject: [PATCH 08/29] Update shareLib API docs, directing the reader to makeAPIheader.pl --- modules/libcom/src/misc/epicsExport.h | 24 +++++++++++++++--------- modules/libcom/src/misc/shareLib.h | 6 ++++++ 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/modules/libcom/src/misc/epicsExport.h b/modules/libcom/src/misc/epicsExport.h index 07e7e7623..7c084a602 100644 --- a/modules/libcom/src/misc/epicsExport.h +++ b/modules/libcom/src/misc/epicsExport.h @@ -14,16 +14,22 @@ /** \file epicsExport.h * \brief Exporting IOC objects. * - * This header is unique, as it defines epicsExportSharedSymbols and thus - * triggers a transition between importing declarations from other libraries, - * to exporting symbols from our own library. The comments in shareLib.h - * provide more information. + * This header defines macros that allow registering IOC shell commands, + * subroutines, device support, etc. * - * This header should be included with a trailing comment to make it stand - * out from other includes, something like this: - \code - #include // defines epicsExportSharedSymbols - \endcode + * Because this header defines the `epicsExportSharedSymbols` macro, it triggers + * a transition between importing declarations from other libraries, to + * exporting symbols from our own library. For this reason, it must be included + * *last*, after all other EPICS-related includes. The comments in shareLib.h + * provide more information. It is recommended to mark this with a comment, e.g. + * \code + * #include // defines epicsExportSharedSymbols, do not move + * \endcode + * + * \note Do not use this header solely to enable exporting of symbols. If you + * are implementing a library and need to handle the differences in shared + * libraries between Linux and Windows, refer to the documentation within + * [**makeAPIheader.pl**](makeAPIheader.html) or run `makeAPIheader.pl -h`. */ #define epicsExportSharedSymbols diff --git a/modules/libcom/src/misc/shareLib.h b/modules/libcom/src/misc/shareLib.h index 3b049439b..cb7adba73 100644 --- a/modules/libcom/src/misc/shareLib.h +++ b/modules/libcom/src/misc/shareLib.h @@ -19,6 +19,12 @@ * * These are needed to properly create DLLs on MS Windows. * + * \note This header file is deprecated. A newer mechanism is available that + * automatically handles the differences in shared libraries between Linux and + * Windows while avoiding the pitfalls of the `epicsExportSharedSymbols` macro. + * If you are implementing a library, refer to the documentation within + * [**makeAPIheader.pl**](makeAPIheader.html) or run `makeAPIheader.pl -h`. + * * ### USAGE * * There are two distinct classes of keywords in this file: From 625c2ef159b735a9a64d7da6ad4c10e81b464fae Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Mon, 9 Jan 2023 13:14:47 -0800 Subject: [PATCH 09/29] epicsThreadPerform: time epicsThreadOnce() --- modules/libcom/test/epicsThreadPerform.cpp | 37 ++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/modules/libcom/test/epicsThreadPerform.cpp b/modules/libcom/test/epicsThreadPerform.cpp index 07238cb33..3b7baef2d 100644 --- a/modules/libcom/test/epicsThreadPerform.cpp +++ b/modules/libcom/test/epicsThreadPerform.cpp @@ -216,6 +216,42 @@ static void timeEpicsThreadPrivateGet () printf("epicsThreadPrivateGet() takes %f microseconds\n", delay); } +static void onceAction(void*) {} + +static void timeOnce () +{ +#define NITER 100 + epicsThreadOnceId once[NITER]; + double tSlow = 0.0, tFast = 0.0, + tSlow2= 0.0, tFast2= 0.0; + unsigned i; + + for(i=0; i Date: Mon, 9 Jan 2023 12:01:29 -0800 Subject: [PATCH 10/29] posix: optimize epicsThreadOnce() Use atomic ops to short circuit when already initialized --- modules/libcom/src/osi/os/posix/osdThread.c | 27 ++++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/modules/libcom/src/osi/os/posix/osdThread.c b/modules/libcom/src/osi/os/posix/osdThread.c index da0342813..5fa85d4a5 100644 --- a/modules/libcom/src/osi/os/posix/osdThread.c +++ b/modules/libcom/src/osi/os/posix/osdThread.c @@ -523,11 +523,16 @@ LIBCOM_API unsigned int epicsStdCall epicsThreadGetStackSize (epicsThreadStackSi LIBCOM_API void epicsStdCall epicsThreadOnce(epicsThreadOnceId *id, void (*func)(void *), void *arg) { - static struct epicsThreadOSD threadOnceComplete; - #define EPICS_THREAD_ONCE_DONE &threadOnceComplete + static const struct epicsThreadOSD threadOnceComplete; + #define EPICS_THREAD_ONCE_DONE (void*)&threadOnceComplete int status; + void *prev, *self; - epicsThreadInit(); + if(epicsAtomicGetPtrT((void**)id) == EPICS_THREAD_ONCE_DONE) { + return; /* fast path. global init already done. No need to lock */ + } + + self = epicsThreadGetIdSelf(); status = mutexLock(&onceLock); if(status) { fprintf(stderr,"epicsThreadOnce: pthread_mutex_lock returned %s.\n", @@ -535,21 +540,25 @@ LIBCOM_API void epicsStdCall epicsThreadOnce(epicsThreadOnceId *id, void (*func) exit(-1); } - if (*id != EPICS_THREAD_ONCE_DONE) { - if (*id == EPICS_THREAD_ONCE_INIT) { /* first call */ - *id = epicsThreadGetIdSelf(); /* mark active */ + /* slow path, check again and attempt to begin */ + prev = epicsAtomicCmpAndSwapPtrT((void**)id, EPICS_THREAD_ONCE_INIT, self); + + if (prev != EPICS_THREAD_ONCE_DONE) { + if (prev == EPICS_THREAD_ONCE_INIT) { /* first call, already marked active */ status = pthread_mutex_unlock(&onceLock); checkStatusQuit(status,"pthread_mutex_unlock", "epicsThreadOnce"); func(arg); status = mutexLock(&onceLock); checkStatusQuit(status,"pthread_mutex_lock", "epicsThreadOnce"); - *id = EPICS_THREAD_ONCE_DONE; /* mark done */ - } else if (*id == epicsThreadGetIdSelf()) { + epicsAtomicSetPtrT((void**)id, EPICS_THREAD_ONCE_DONE); /* mark done */ + + } else if (prev == self) { status = pthread_mutex_unlock(&onceLock); checkStatusQuit(status,"pthread_mutex_unlock", "epicsThreadOnce"); cantProceed("Recursive epicsThreadOnce() initialization\n"); + } else - while (*id != EPICS_THREAD_ONCE_DONE) { + while ((prev = epicsAtomicGetPtrT((void**)id)) != EPICS_THREAD_ONCE_DONE) { /* Another thread is in the above func(arg) call. */ status = pthread_mutex_unlock(&onceLock); checkStatusQuit(status,"pthread_mutex_unlock", "epicsThreadOnce"); From 52b18d56a068b97d745be1ade02fc6b09780f73c Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Sun, 15 Jan 2023 10:02:43 -0800 Subject: [PATCH 11/29] dbCreateAlias fixup error handling --- modules/database/src/ioc/dbStatic/dbStaticLib.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/modules/database/src/ioc/dbStatic/dbStaticLib.c b/modules/database/src/ioc/dbStatic/dbStaticLib.c index 6f171a5e4..81556e0b7 100644 --- a/modules/database/src/ioc/dbStatic/dbStaticLib.c +++ b/modules/database/src/ioc/dbStatic/dbStaticLib.c @@ -1652,6 +1652,7 @@ long dbCreateAlias(DBENTRY *pdbentry, const char *alias) dbRecordNode *pnewnode; DBENTRY tempEntry; PVDENTRY *ppvd; + long status; if (!precordType) return S_dbLib_recordTypeNotFound; @@ -1664,9 +1665,10 @@ long dbCreateAlias(DBENTRY *pdbentry, const char *alias) return S_dbLib_recNotFound; dbInitEntry(pdbentry->pdbbase, &tempEntry); - if (!dbFindRecord(&tempEntry, alias)) - return S_dbLib_recExists; + status = dbFindRecord(&tempEntry, alias); dbFinishEntry(&tempEntry); + if (!status) + return S_dbLib_recExists; pnewnode = dbCalloc(1, sizeof(dbRecordNode)); pnewnode->recordname = epicsStrDup(alias); @@ -1676,15 +1678,16 @@ long dbCreateAlias(DBENTRY *pdbentry, const char *alias) precnode->flags |= DBRN_FLAGS_HASALIAS; ellInit(&pnewnode->infoList); - ellAdd(&precordType->recList, &pnewnode->node); - precordType->no_aliases++; - ppvd = dbPvdAdd(pdbentry->pdbbase, precordType, pnewnode); if (!ppvd) { errMessage(-1, "dbCreateAlias: Add to PVD failed"); + free(pnewnode); return -1; } + ellAdd(&precordType->recList, &pnewnode->node); + precordType->no_aliases++; + return 0; } From 3500a020343c4771cad94ed4a83615bc283718a4 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Mon, 16 Jan 2023 09:01:30 -0800 Subject: [PATCH 12/29] iocsh: expose dbCreateAlias --- .../src/ioc/dbStatic/dbStaticIocRegister.c | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/modules/database/src/ioc/dbStatic/dbStaticIocRegister.c b/modules/database/src/ioc/dbStatic/dbStaticIocRegister.c index 751b7b250..4ff7e2e66 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 "errSymTbl.h" #include "dbStaticIocRegister.h" #include "dbStaticLib.h" @@ -217,6 +218,40 @@ static void dbReportDeviceConfigCallFunc(const iocshArgBuf *args) dbReportDeviceConfig(*iocshPpdbbase,stdout); } + +static const iocshArg dbCreateAliasArg0 = { "record",iocshArgStringRecord}; +static const iocshArg dbCreateAliasArg1 = { "alias",iocshArgStringRecord}; +static const iocshArg * const dbCreateAliasArgs[] = {&argPdbbase,&dbCreateAliasArg0, &dbCreateAliasArg1}; +static const iocshFuncDef dbCreateAliasFuncDef = { + "dbCreateAlias", + 3, + dbCreateAliasArgs, + "Add a new record alias.\n" + "\n" + "Example: dbCreateAlias pdbbase record:name new:alias\n", +}; +static void dbCreateAliasCallFunc(const iocshArgBuf *args) +{ + DBENTRY ent; + long status; + + dbInitEntry(*iocshPpdbbase, &ent); + if(!args[1].sval || !args[2].sval) { + status = S_dbLib_recNotFound; + + } else { + status = dbFindRecord(&ent, args[1].sval); + if(!status) { + status = dbCreateAlias(&ent, args[2].sval); + } + } + dbFinishEntry(&ent); + if(status) { + fprintf(stderr, "Error: %ld %s\n", status, errSymMsg(status)); + iocshSetError(1); + } +} + void dbStaticIocRegister(void) { iocshRegister(&dbDumpPathFuncDef, dbDumpPathCallFunc); @@ -234,4 +269,5 @@ void dbStaticIocRegister(void) iocshRegister(&dbPvdDumpFuncDef, dbPvdDumpCallFunc); iocshRegister(&dbPvdTableSizeFuncDef,dbPvdTableSizeCallFunc); iocshRegister(&dbReportDeviceConfigFuncDef, dbReportDeviceConfigCallFunc); + iocshRegister(&dbCreateAliasFuncDef, dbCreateAliasCallFunc); } From 17ad04505e52f1d712c9073c66cfb6ae77ff47ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torsten=20B=C3=B6gershausen?= Date: Tue, 11 Apr 2023 12:44:51 +0200 Subject: [PATCH 13/29] Change compiler for FreeBSD 13: Use clang FreeBSD 13 uses clang, not gcc, any more. GNU_DIR must be set to /usr/local Note: This change touches both the x86 and the x86_64 files. It was tested on 'amd64' system only, which is x86_64 --- configure/os/CONFIG.Common.freebsdCommon | 4 ++++ configure/os/CONFIG.freebsd-x86.Common | 1 + configure/os/CONFIG.freebsd-x86.freebsd-x86 | 1 + configure/os/CONFIG.freebsd-x86_64.freebsd-x86_64 | 1 + 4 files changed, 7 insertions(+) diff --git a/configure/os/CONFIG.Common.freebsdCommon b/configure/os/CONFIG.Common.freebsdCommon index 36796b758..df2a10a49 100644 --- a/configure/os/CONFIG.Common.freebsdCommon +++ b/configure/os/CONFIG.Common.freebsdCommon @@ -7,6 +7,10 @@ # Include definitions common to all Unix targets include $(CONFIG)/os/CONFIG.Common.UnixCommon +GNU = NO +CMPLR_CLASS = clang +CC = clang +CCC = clang++ OS_CLASS = freebsd diff --git a/configure/os/CONFIG.freebsd-x86.Common b/configure/os/CONFIG.freebsd-x86.Common index 1bd10de2d..a936b7a4c 100644 --- a/configure/os/CONFIG.freebsd-x86.Common +++ b/configure/os/CONFIG.freebsd-x86.Common @@ -5,3 +5,4 @@ #Include definitions common to unix hosts include $(CONFIG)/os/CONFIG.UnixCommon.Common +CMPLR_CLASS = clang diff --git a/configure/os/CONFIG.freebsd-x86.freebsd-x86 b/configure/os/CONFIG.freebsd-x86.freebsd-x86 index b5e2bcff4..a183b34c1 100644 --- a/configure/os/CONFIG.freebsd-x86.freebsd-x86 +++ b/configure/os/CONFIG.freebsd-x86.freebsd-x86 @@ -2,6 +2,7 @@ # Definitions for freebsd-x86 host - freebsd-x86 target builds # Sites may override these definitions in CONFIG_SITE.freebsd-x86.freebsd-x86 #------------------------------------------------------- +GNU_DIR=/usr/local # Include common gnu compiler definitions include $(CONFIG)/CONFIG.gnuCommon diff --git a/configure/os/CONFIG.freebsd-x86_64.freebsd-x86_64 b/configure/os/CONFIG.freebsd-x86_64.freebsd-x86_64 index dcc8888ce..0ad2bfcf8 100644 --- a/configure/os/CONFIG.freebsd-x86_64.freebsd-x86_64 +++ b/configure/os/CONFIG.freebsd-x86_64.freebsd-x86_64 @@ -2,6 +2,7 @@ # Definitions for freebsd-x86_64 host - freebsd-x86_64 target builds # Sites may override these definitions in CONFIG_SITE.freebsd-x86_64.freebsd-x86_64 #------------------------------------------------------- +GNU_DIR=/usr/local # Include common gnu compiler definitions include $(CONFIG)/CONFIG.gnuCommon From 216359974c97b26ed9e8b4e5931baa3af2f7a2a6 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Tue, 18 Apr 2023 13:11:59 -0500 Subject: [PATCH 14/29] update release notes --- documentation/RELEASE_NOTES.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/documentation/RELEASE_NOTES.md b/documentation/RELEASE_NOTES.md index 4af0dbbc6..94b0dd446 100644 --- a/documentation/RELEASE_NOTES.md +++ b/documentation/RELEASE_NOTES.md @@ -15,6 +15,21 @@ should also be read to understand what has changed since earlier releases. ## Changes made on the 7.0 branch since 7.0.7 +### Change compiler for FreeBSD to clang + +The default compiler for FreeBSD targets changes from GCC to clang. + +### Expose `dbCreateAlias` in IOC shell + +Add a new IOC shell command `dbCreateAlias` allow record aliases to be added. +Intended for use before `iocInit`. eg. to add an alias "bar" for a record "foo". + +``` +dbLoadRecords("some.db") # includes: record(ai, "foo") { ... +dbCreateAlias("foo", "bar") +iocInit() +``` + ### dbEvent eventsRemaining missed on cancel In some cases, RSRV may queue a subscription update, but not flush it. From f41f11c7f6e4c27a6e25eec36e3cbe3cf6a79688 Mon Sep 17 00:00:00 2001 From: Dirk Zimoch Date: Tue, 7 Mar 2023 17:47:36 +0100 Subject: [PATCH 15/29] fix compiler warning on 32 bit systems --- modules/libcom/src/osi/os/posix/osdElfFindAddr.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/libcom/src/osi/os/posix/osdElfFindAddr.c b/modules/libcom/src/osi/os/posix/osdElfFindAddr.c index f1c275370..1b92ef7bc 100644 --- a/modules/libcom/src/osi/os/posix/osdElfFindAddr.c +++ b/modules/libcom/src/osi/os/posix/osdElfFindAddr.c @@ -639,7 +639,7 @@ epicsFindAddr(void *addr, epicsSymbol *sym_p) if ( nearest.raw && ( (idx = ARR(c,nearest,0,st_name)) < es->strMap->max ) ) { sym_p->s_nam = strtab + idx; - sym_p->s_val = (char*) ARR(c, nearest, 0, st_value) + es->addr; + sym_p->s_val = (char*)(uintptr_t) ARR(c, nearest, 0, st_value) + es->addr; } return 0; From b878295d06de383125693ca3fd533cf5c11d5a01 Mon Sep 17 00:00:00 2001 From: Eva Lott Date: Tue, 7 Mar 2023 16:22:41 +0000 Subject: [PATCH 16/29] Added the new annotation EPICS_PRINTF_FMT --- .github/workflows/ci-scripts-build.yml | 6 ++++-- modules/database/src/ioc/db/recGbl.h | 2 +- modules/database/src/ioc/dbStatic/dbStaticPvt.h | 4 +++- modules/libcom/src/error/errlog.h | 16 ++++++++++------ modules/libcom/src/misc/cantProceed.h | 5 +++-- modules/libcom/src/misc/epicsUnitTest.h | 10 +++++----- .../src/osi/compiler/msvc/compilerSpecific.h | 6 ++++++ modules/libcom/src/osi/compilerDependencies.h | 7 +++++++ modules/libcom/src/osi/epicsStdio.h | 3 ++- 9 files changed, 41 insertions(+), 18 deletions(-) diff --git a/.github/workflows/ci-scripts-build.yml b/.github/workflows/ci-scripts-build.yml index acd8b63cb..5bd280772 100644 --- a/.github/workflows/ci-scripts-build.yml +++ b/.github/workflows/ci-scripts-build.yml @@ -133,13 +133,15 @@ jobs: - os: windows-2019 cmp: vs2019 - configuration: default + configuration: debug name: "Win2019 MSC-19" + extra: "CMD_CXXFLAGS=-analysis" - os: windows-2019 cmp: vs2019 - configuration: static + configuration: static-debug name: "Win2019 MSC-19, static" + extra: "CMD_CXXFLAGS=-analysis" - os: windows-2019 cmp: vs2019 diff --git a/modules/database/src/ioc/db/recGbl.h b/modules/database/src/ioc/db/recGbl.h index e362b8bd4..cf3db4bc5 100644 --- a/modules/database/src/ioc/db/recGbl.h +++ b/modules/database/src/ioc/db/recGbl.h @@ -75,7 +75,7 @@ DBCORE_API void recGblInheritSevr(int msMode, void *precord, epicsEnum16 stat, epicsEnum16 sevr); DBCORE_API int recGblSetSevrMsg(void *precord, epicsEnum16 new_stat, epicsEnum16 new_sevr, - const char *msg, ...) EPICS_PRINTF_STYLE(4,5); + EPICS_PRINTF_FMT(const char *msg), ...) EPICS_PRINTF_STYLE(4,5); DBCORE_API int recGblSetSevrVMsg(void *precord, epicsEnum16 new_stat, epicsEnum16 new_sevr, const char *msg, va_list args); diff --git a/modules/database/src/ioc/dbStatic/dbStaticPvt.h b/modules/database/src/ioc/dbStatic/dbStaticPvt.h index cce4d8cb8..033fde40c 100644 --- a/modules/database/src/ioc/dbStatic/dbStaticPvt.h +++ b/modules/database/src/ioc/dbStatic/dbStaticPvt.h @@ -39,7 +39,9 @@ char *dbRecordName(DBENTRY *pdbentry); char *dbGetStringNum(DBENTRY *pdbentry); long dbPutStringNum(DBENTRY *pdbentry,const char *pstring); -void dbMsgPrint(DBENTRY *pdbentry, const char *fmt, ...) EPICS_PRINTF_STYLE(2,3); +void dbMsgPrint( + DBENTRY *pdbentry, EPICS_PRINTF_FMT(const char *fmt), ... +) EPICS_PRINTF_STYLE(2,3); void dbPutStringSuggest(DBENTRY *pdbentry, const char *pstring); diff --git a/modules/libcom/src/error/errlog.h b/modules/libcom/src/error/errlog.h index 7cec39653..32e2da7a5 100644 --- a/modules/libcom/src/error/errlog.h +++ b/modules/libcom/src/error/errlog.h @@ -97,7 +97,7 @@ LIBCOM_API extern int errVerbose; * that the output is sent to the errlog task. Unless configured not to, the output * will appear on the console as well. */ -LIBCOM_API int errlogPrintf(const char *pformat, ...) +LIBCOM_API int errlogPrintf(EPICS_PRINTF_FMT(const char *pformat), ...) EPICS_PRINTF_STYLE(1,2); /** @@ -118,7 +118,8 @@ LIBCOM_API int errlogVprintf(const char *pformat, va_list pvar); * \return int Consult printf documentation in C standard library */ LIBCOM_API int errlogSevPrintf(const errlogSevEnum severity, - const char *pformat, ...) EPICS_PRINTF_STYLE(2,3); + EPICS_PRINTF_FMT(const char *pformat), ... +) EPICS_PRINTF_STYLE(2,3); /** * This function is like ::errlogVprintf except that it adds the severity to the beginning @@ -239,11 +240,14 @@ LIBCOM_API void errlogFlush(void); * The remaining arguments are just like the arguments to the C printf routine. * ::errVerbose determines if the filename and line number are shown. */ -LIBCOM_API void errPrintf(long status, const char *pFileName, int lineno, - const char *pformat, ...) EPICS_PRINTF_STYLE(4,5); +LIBCOM_API void errPrintf( + long status, const char *pFileName, int lineno, + EPICS_PRINTF_FMT(const char *pformat), ... +) EPICS_PRINTF_STYLE(4,5); -LIBCOM_API int errlogPrintfNoConsole(const char *pformat, ...) - EPICS_PRINTF_STYLE(1,2); +LIBCOM_API int errlogPrintfNoConsole( + EPICS_PRINTF_FMT(const char *pformat), ... +) EPICS_PRINTF_STYLE(1,2); LIBCOM_API int errlogVprintfNoConsole(const char *pformat,va_list pvar); /** diff --git a/modules/libcom/src/misc/cantProceed.h b/modules/libcom/src/misc/cantProceed.h index 7c201e6d2..0232a8611 100644 --- a/modules/libcom/src/misc/cantProceed.h +++ b/modules/libcom/src/misc/cantProceed.h @@ -43,8 +43,9 @@ extern "C" { * \param errorMessage A printf-style error message describing the error. * \param ... Any parameters required for the error message. */ -LIBCOM_API void cantProceed(const char *errorMessage, ...) - EPICS_PRINTF_STYLE(1,2); +LIBCOM_API void cantProceed( + EPICS_PRINTF_FMT(const char *errorMessage), ... +) EPICS_PRINTF_STYLE(1,2); /** \name Memory Allocation Functions * These versions of calloc() and malloc() never fail, they suspend the diff --git a/modules/libcom/src/misc/epicsUnitTest.h b/modules/libcom/src/misc/epicsUnitTest.h index 600924466..eea53335f 100644 --- a/modules/libcom/src/misc/epicsUnitTest.h +++ b/modules/libcom/src/misc/epicsUnitTest.h @@ -174,7 +174,7 @@ LIBCOM_API void testPlan(int tests); * \param ... Any parameters required for the format string. * \return The value of \p pass. */ -LIBCOM_API int testOk(int pass, const char *fmt, ...) +LIBCOM_API int testOk(int pass, EPICS_PRINTF_FMT(const char *fmt), ...) EPICS_PRINTF_STYLE(2, 3); /** \brief Test result using expression as description * \param cond Expression to be evaluated and displayed. @@ -192,13 +192,13 @@ LIBCOM_API int testOkV(int pass, const char *fmt, va_list pvar); * \param fmt A printf-style format string describing the test. * \param ... Any parameters required for the format string. */ -LIBCOM_API void testPass(const char *fmt, ...) +LIBCOM_API void testPass(EPICS_PRINTF_FMT(const char *fmt), ...) EPICS_PRINTF_STYLE(1, 2); /** \brief Failing test result with printf-style description. * \param fmt A printf-style format string describing the test. * \param ... Any parameters required for the format string. */ -LIBCOM_API void testFail(const char *fmt, ...) +LIBCOM_API void testFail(EPICS_PRINTF_FMT(const char *fmt), ...) EPICS_PRINTF_STYLE(1, 2); /** @} */ @@ -223,7 +223,7 @@ LIBCOM_API void testTodoEnd(void); * \param fmt A printf-style format string giving the reason for stopping. * \param ... Any parameters required for the format string. */ -LIBCOM_API void testAbort(const char *fmt, ...) +LIBCOM_API void testAbort(EPICS_PRINTF_FMT(const char *fmt), ...) EPICS_PRINTF_STYLE(1, 2); /** @} */ @@ -231,7 +231,7 @@ LIBCOM_API void testAbort(const char *fmt, ...) * \param fmt A printf-style format string containing diagnostic information. * \param ... Any parameters required for the format string. */ -LIBCOM_API int testDiag(const char *fmt, ...) +LIBCOM_API int testDiag(EPICS_PRINTF_FMT(const char *fmt), ...) EPICS_PRINTF_STYLE(1, 2); /** \brief Mark the end of testing. */ diff --git a/modules/libcom/src/osi/compiler/msvc/compilerSpecific.h b/modules/libcom/src/osi/compiler/msvc/compilerSpecific.h index 7e5bfe609..631da4f38 100644 --- a/modules/libcom/src/osi/compiler/msvc/compilerSpecific.h +++ b/modules/libcom/src/osi/compiler/msvc/compilerSpecific.h @@ -44,5 +44,11 @@ #endif /* __cplusplus */ +/* + * Enable format-string checking if compiler supports it (if msvc is 2015 or newer) + */ +#if _MSC_VER >= 1900 +# define EPICS_PRINTF_FMT(a) _Printf_format_string_ a +#endif #endif /* ifndef compilerSpecific_h */ diff --git a/modules/libcom/src/osi/compilerDependencies.h b/modules/libcom/src/osi/compilerDependencies.h index 97c8f7f77..dc574c44a 100644 --- a/modules/libcom/src/osi/compilerDependencies.h +++ b/modules/libcom/src/osi/compilerDependencies.h @@ -61,4 +61,11 @@ #endif #endif +#ifndef EPICS_PRINTF_FMT +/* + * No format-string checking annotation + */ +# define EPICS_PRINTF_FMT(a) a +#endif + #endif /* ifndef compilerDependencies_h */ diff --git a/modules/libcom/src/osi/epicsStdio.h b/modules/libcom/src/osi/epicsStdio.h index b63472003..c8a882b3f 100644 --- a/modules/libcom/src/osi/epicsStdio.h +++ b/modules/libcom/src/osi/epicsStdio.h @@ -130,7 +130,8 @@ extern "C" { * output has been truncated if the return value is `size` or more. */ LIBCOM_API int epicsStdCall epicsSnprintf( - char *str, size_t size, const char *format, ...) EPICS_PRINTF_STYLE(3,4); + char *str, size_t size, EPICS_PRINTF_FMT(const char *format), ... +) EPICS_PRINTF_STYLE(3,4); /** * \brief epicsVsnprintf() is meant to have the same semantics as the C99 * function vsnprintf() From acf2241fd03bf77e618e4b054d049817dc654bb2 Mon Sep 17 00:00:00 2001 From: Dirk Zimoch Date: Wed, 8 Mar 2023 13:27:28 +0100 Subject: [PATCH 17/29] Some archs define ECHO in termios.h --- modules/libcom/src/as/asLibRoutines.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/libcom/src/as/asLibRoutines.c b/modules/libcom/src/as/asLibRoutines.c index fce4a41c2..cb00ca6c7 100644 --- a/modules/libcom/src/as/asLibRoutines.c +++ b/modules/libcom/src/as/asLibRoutines.c @@ -29,6 +29,8 @@ #include "postfix.h" #include "asLib.h" +#undef ECHO /* from termios.h */ + int asCheckClientIP; static epicsMutexId asLock; From 912a82c0b521d8e894b0930d25582e98fcfe5686 Mon Sep 17 00:00:00 2001 From: Dirk Zimoch Date: Wed, 8 Mar 2023 11:03:43 +0100 Subject: [PATCH 18/29] replace deprecated decrementing volatile with atomic decrement --- modules/libcom/test/epicsTimerTest.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/libcom/test/epicsTimerTest.cpp b/modules/libcom/test/epicsTimerTest.cpp index 74e1919b0..e8826cc49 100644 --- a/modules/libcom/test/epicsTimerTest.cpp +++ b/modules/libcom/test/epicsTimerTest.cpp @@ -21,6 +21,7 @@ #include "epicsEvent.h" #include "epicsAssert.h" #include "epicsGuard.h" +#include "epicsAtomic.h" #include "tsFreeList.h" #include "epicsUnitTest.h" #include "testMain.h" @@ -84,7 +85,7 @@ private: delayVerify & operator = ( const delayVerify & ); }; -static volatile unsigned expireCount; +static int expireCount; static epicsEvent expireEvent; delayVerify::delayVerify ( double expectedDelayIn, epicsTimerQueue &queueIn ) : @@ -128,7 +129,7 @@ inline void delayVerify::start ( const epicsTime &expireTime ) epicsTimerNotify::expireStatus delayVerify::expire ( const epicsTime ¤tTime ) { this->expireStamp = currentTime; - if ( --expireCount == 0u ) { + if ( epics::atomic::decrement ( expireCount ) == 0u ) { expireEvent.signal (); } return noRestart; @@ -155,7 +156,7 @@ void testAccuracy () } testOk1 ( timerCount == nTimers ); - expireCount = nTimers; + epics::atomic::set ( expireCount, nTimers ); for ( i = 0u; i < nTimers; i++ ) { epicsTime cur = epicsTime::getCurrent (); pTimers[i]->setBegin ( cur ); From 9f97f25669d9fd05b0fa9f21f5dc2487906eada5 Mon Sep 17 00:00:00 2001 From: Minijackson Date: Tue, 7 Mar 2023 14:46:35 +0000 Subject: [PATCH 19/29] ci: add cross-compilation tests for aarch64, arm soft and hard float --- .github/workflows/ci-scripts-build.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/.github/workflows/ci-scripts-build.yml b/.github/workflows/ci-scripts-build.yml index 5bd280772..5a66f1d7b 100644 --- a/.github/workflows/ci-scripts-build.yml +++ b/.github/workflows/ci-scripts-build.yml @@ -153,6 +153,26 @@ jobs: configuration: default name: "Win2019 mingw" + # Cross builds + + - os: ubuntu-latest + cmp: gcc + configuration: default + name: "Cross linux-aarch64" + cross: linux-aarch64 + + - os: ubuntu-latest + cmp: gcc + configuration: default + name: "Cross linux-arm gnueabi" + cross: linux-arm@arm-linux-gnueabi + + - os: ubuntu-latest + cmp: gcc + configuration: default + name: "Cross linux-arm gnueabihf" + cross: linux-arm@arm-linux-gnueabihf + steps: - uses: actions/checkout@v3 with: From 07d18c55ba2bbbd3c41d89dadce406db862fd8f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89rico=20Nogueira?= Date: Tue, 18 Apr 2023 14:57:07 -0300 Subject: [PATCH 20/29] Clean whitespace in makeBaseApp template README --- modules/database/src/template/top/exampleBoot/ioc/README@Common | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/database/src/template/top/exampleBoot/ioc/README@Common b/modules/database/src/template/top/exampleBoot/ioc/README@Common index f6dc23be8..572570df5 100644 --- a/modules/database/src/template/top/exampleBoot/ioc/README@Common +++ b/modules/database/src/template/top/exampleBoot/ioc/README@Common @@ -3,7 +3,7 @@ To start the ioc from this directory execute the command Alternatively make the st.cmd file directly executable with chmod +x st.cmd -and check the executable name on the first line of the st.cmd file +and check the executable name on the first line of the st.cmd file You may need to change the name of the .dbd file given in the st.cmd's dbLoadDatabase() command before starting the ioc. From f56412d6a56226524be57620f00b63519638b6aa Mon Sep 17 00:00:00 2001 From: Freddie Akeroyd Date: Sun, 30 Apr 2023 12:42:05 -0700 Subject: [PATCH 21/29] WIN32: use FlsAlloc() to cleanup epicsThreadOSD Adjust macros for compiling for older MSVC/Win SDK versions Try to cover missing fibres include in 7.0 SDK Support Windows XP and above. Also removed explicit define of _WIN32_WINNT in code if it has not been passed on compile line. This is possibly a matter for further discussion --- documentation/RELEASE_NOTES.md | 35 +++++++ modules/libcom/src/osi/os/WIN32/osdMutex.c | 21 +--- modules/libcom/src/osi/os/WIN32/osdThread.c | 108 ++++++++++++++------ 3 files changed, 114 insertions(+), 50 deletions(-) diff --git a/documentation/RELEASE_NOTES.md b/documentation/RELEASE_NOTES.md index 94b0dd446..984011fe5 100644 --- a/documentation/RELEASE_NOTES.md +++ b/documentation/RELEASE_NOTES.md @@ -15,6 +15,15 @@ should also be read to understand what has changed since earlier releases. ## Changes made on the 7.0 branch since 7.0.7 +### Fixed leak from a non-EPICS thread on WIN32 + +On Windows targets, if a thread not created by `epicsThreadCreate*()` directly +or indirectly calls an `epicsThread*()` function, a specific tracking struct +is allocated. Prior to this release the allocation would not be `free()`d, +resulting in a memory leak. + +A similar issue on POSIX targets was previously fixed. + ### Change compiler for FreeBSD to clang The default compiler for FreeBSD targets changes from GCC to clang. @@ -259,6 +268,32 @@ This release fixed the leak on POSIX targets. See the associated github [issue 241](https://github.com/epics-base/epics-base/issues/241) for WIN32 status. +### Fixed leak from a non-EPICS thread + +On some targets, if a thread not created by `epicsThreadCreate*()` directly +or indirectly calls an `epicsThread*()` function, a specific tracking struct +is allocated. + +Prior to this release, on POSIX and WIN32 targets, this +allocation would not be `free()`d, resulting in a memory leak. + +This release fixed the leak on POSIX and WIN32 targets (excluding +MSVC before vs2012, and the WINE runtime). + +### Fixed leak from a non-EPICS thread + +On some targets, if a thread not created by `epicsThreadCreate*()` directly +or indirectly calls an `epicsThread*()` function, a specific tracking struct +is allocated. + +Prior to this release, on POSIX and WIN32 targets, this +struct would not be `free()`d, resulting in a memory leak. + +This release fixed the leak on POSIX targets. + +See the associated github [issue 241](https://github.com/epics-base/epics-base/issues/241) +for WIN32 status. + ### Fix `CHECK_RELEASE = WARN` This now works again, it was broken in 2019 (7.0.3.1) by an errant commit. diff --git a/modules/libcom/src/osi/os/WIN32/osdMutex.c b/modules/libcom/src/osi/os/WIN32/osdMutex.c index 561093cac..b18e849f4 100644 --- a/modules/libcom/src/osi/os/WIN32/osdMutex.c +++ b/modules/libcom/src/osi/os/WIN32/osdMutex.c @@ -22,25 +22,10 @@ #define VC_EXTRALEAN #define STRICT -/* - * Defining this allows the *much* faster critical - * section mutex primitive to be used. Unfortunately, - * using certain of these functions drops support for W95\W98\WME - * unless we specify "delay loading" when we link with the - * DLL so that DLL entry points are not resolved until they - * are used. The code does have run time switches so - * that the more advanced calls are not called unless - * they are available in the windows OS, but this feature - * isn't going to be very useful unless we specify "delay - * loading" when we link with the DLL. - * - * It appears that the only entry point used here that causes - * portability problems with W95\W98\WME is TryEnterCriticalSection. - */ -#ifndef _WIN32_WINNT -# define _WIN32_WINNT 0x0400 -#endif #include +#if _WIN32_WINNT < 0x0501 +# error Minimum supported is Windows XP +#endif #define EPICS_PRIVATE_API diff --git a/modules/libcom/src/osi/os/WIN32/osdThread.c b/modules/libcom/src/osi/os/WIN32/osdThread.c index e2faff813..1cfeb3767 100644 --- a/modules/libcom/src/osi/os/WIN32/osdThread.c +++ b/modules/libcom/src/osi/os/WIN32/osdThread.c @@ -12,6 +12,11 @@ * Author: Jeff Hill */ +/* pull in _WIN32_WINNT* definitions, if _WIN32_WINNT + * is not already defined it is set to max supported by SDK + */ +#include + #include #include #include @@ -32,14 +37,58 @@ #include "epicsAtomic.h" #include "osdThreadPvt.h" +/* Windows Vista and higher supports fibre functions, but the + * prototypes only appear in the windows SDK 8 and above. + * VS2010 supplies sdk 7, but can be upgraded to later SDK + + * To accomodate this we suuply prototypes on, for XP + * fall back to Tls*() which will build and run + * correctly for epicsThreads, but means that TLS allocations from + * epicsThreadImplicitCreate() will continue to leak (for non-EPICS threads). + * + * Also, WINE circa 5.0.3 provides the FLS storage functions, but doesn't + * actually run the dtor function. + * + * we check for existance of _WIN32_WINNT_WIN8 which will only be defined + * in SDK 8 and above. If Visa is detected and SDK < 8 we will supply + * the missing prototypes + */ + +#if _WIN32_WINNT >= 0x0600 /* VISTA */ +# ifdef _WIN32_WINNT_WIN8 /* Existance means using SDK 8 or higher */ +# include +# else +# include /* for PFLS_CALLBACK_FUNCTION */ +/* the fibers api exists in vista and above, but no header is provided until SDK 8 */ + WINBASEAPI DWORD WINAPI FlsAlloc(PFLS_CALLBACK_FUNCTION); + WINBASEAPI PVOID WINAPI FlsGetValue(DWORD); + WINBASEAPI BOOL WINAPI FlsSetValue(DWORD , PVOID); + WINBASEAPI BOOL WINAPI FlsFree(DWORD); +# endif +#elif _WIN32_WINNT >= 0x0501 /* Windows XP */ + typedef void (WINAPI *xPFLS_CALLBACK_FUNCTION) (void*); + static + DWORD xFlsAlloc(xPFLS_CALLBACK_FUNCTION dtor) { + (void)dtor; + return TlsAlloc(); + } +# define FlsAlloc xFlsAlloc +# define FlsSetValue TlsSetValue +# define FlsGetValue TlsGetValue +# define USE_TLSALLOC_FALLBACK +#else +# error Minimum supported is Windows XP +#endif + LIBCOM_API void osdThreadHooksRun(epicsThreadId id); void setThreadName ( DWORD dwThreadID, LPCSTR szThreadName ); +static void WINAPI epicsParmCleanupWIN32 ( void * praw ); typedef struct win32ThreadGlobal { CRITICAL_SECTION mutex; ELLLIST threadList; - DWORD tlsIndexThreadLibraryEPICS; + DWORD flsIndexThreadLibraryEPICS; } win32ThreadGlobal; typedef struct epicsThreadOSD { @@ -119,7 +168,6 @@ BOOL WINAPI DllMain ( * Don't allow user's explicitly calling FreeLibrary for Com.dll to yank * the carpet out from under EPICS threads that are still using Com.dll */ -#if _WIN32_WINNT >= 0x0501 /* * Only in WXP * That's a shame because this is probably much faster @@ -127,22 +175,7 @@ BOOL WINAPI DllMain ( success = GetModuleHandleEx ( GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, ( LPCTSTR ) DllMain, & dllHandle ); -#else - { - char name[256]; - DWORD nChar = GetModuleFileName ( - hModule, name, sizeof ( name ) ); - if ( nChar && nChar < sizeof ( name ) ) { - dllHandle = LoadLibrary ( name ); - if ( ! dllHandle ) { - success = FALSE; - } - } - else { - success = FALSE; - } - } -#endif + if ( success ) { success = TlsSetValue ( dllHandleIndex, dllHandle ); } @@ -207,8 +240,8 @@ static win32ThreadGlobal * fetchWin32ThreadGlobal ( void ) InitializeCriticalSection ( & pWin32ThreadGlobal->mutex ); ellInit ( & pWin32ThreadGlobal->threadList ); - pWin32ThreadGlobal->tlsIndexThreadLibraryEPICS = TlsAlloc(); - if ( pWin32ThreadGlobal->tlsIndexThreadLibraryEPICS == 0xFFFFFFFF ) { + pWin32ThreadGlobal->flsIndexThreadLibraryEPICS = FlsAlloc(&epicsParmCleanupWIN32); + if ( pWin32ThreadGlobal->flsIndexThreadLibraryEPICS == FLS_OUT_OF_INDEXES ) { DeleteCriticalSection ( & pWin32ThreadGlobal->mutex ); free ( pWin32ThreadGlobal ); pWin32ThreadGlobal = 0; @@ -233,10 +266,14 @@ static void epicsParmCleanupDataWIN32 ( win32ThreadParam * pParm ) } } -static void epicsParmCleanupWIN32 ( win32ThreadParam * pParm ) +static void WINAPI epicsParmCleanupWIN32 ( void * praw ) { - win32ThreadGlobal * pGbl = fetchWin32ThreadGlobal (); - if ( ! pGbl ) { + win32ThreadParam * pParm = praw; + win32ThreadGlobal * pGbl; + if(!praw) + return; + + if ( ! (pGbl = fetchWin32ThreadGlobal ()) ) { fprintf ( stderr, "epicsParmCleanupWIN32: unable to find ctx\n" ); return; } @@ -464,7 +501,7 @@ static unsigned WINAPI epicsWin32ThreadEntry ( LPVOID lpParameter ) if ( pGbl ) { setThreadName ( pParm->id, pParm->pName ); - success = TlsSetValue ( pGbl->tlsIndexThreadLibraryEPICS, pParm ); + success = FlsSetValue ( pGbl->flsIndexThreadLibraryEPICS, pParm ); if ( success ) { osdThreadHooksRun ( ( epicsThreadId ) pParm ); /* printf ( "starting thread %d\n", pParm->id ); */ @@ -482,15 +519,15 @@ static unsigned WINAPI epicsWin32ThreadEntry ( LPVOID lpParameter ) epicsExitCallAtThreadExits (); - /* - * CAUTION: !!!! the thread id might continue to be used after this thread exits !!!! + /* On Windows we could omit this and rely on the callback given to FlsAlloc() to free. + * However < vista doesn't implement FLS at all, and WINE (circa 5.0.3) doesn't + * implement fully (dtor never runs). So for EPICS threads, we explicitly + * free() here. */ - if ( pGbl ) { - TlsSetValue ( pGbl->tlsIndexThreadLibraryEPICS, (void*)0xdeadbeef ); + if(pGbl && FlsSetValue ( pGbl->flsIndexThreadLibraryEPICS, NULL )) { + epicsParmCleanupWIN32 ( pParm ); } - epicsParmCleanupWIN32 ( pParm ); - return retStat; /* this indirectly closes the thread handle */ } @@ -550,7 +587,7 @@ static win32ThreadParam * epicsThreadImplicitCreate ( void ) win32ThreadPriority = GetThreadPriority ( pParm->handle ); assert ( win32ThreadPriority != THREAD_PRIORITY_ERROR_RETURN ); pParm->epicsPriority = epicsThreadGetOsiPriorityValue ( win32ThreadPriority ); - success = TlsSetValue ( pGbl->tlsIndexThreadLibraryEPICS, pParm ); + success = FlsSetValue ( pGbl->flsIndexThreadLibraryEPICS, pParm ); if ( ! success ) { epicsParmCleanupWIN32 ( pParm ); pParm = 0; @@ -638,6 +675,8 @@ epicsThreadId epicsThreadCreateOpt ( return NULL; } + /* after this point, new thread is responsible to free(pParmWIN32) */ + return ( epicsThreadId ) pParmWIN32; } @@ -685,7 +724,7 @@ static void* getMyWin32ThreadParam ( win32ThreadGlobal * pGbl ) } pParm = ( win32ThreadParam * ) - TlsGetValue ( pGbl->tlsIndexThreadLibraryEPICS ); + FlsGetValue ( pGbl->flsIndexThreadLibraryEPICS ); if ( ! pParm ) { pParm = epicsThreadImplicitCreate (); } @@ -1046,6 +1085,11 @@ LIBCOM_API void epicsStdCall epicsThreadShowAll ( unsigned level ) win32ThreadGlobal * pGbl = fetchWin32ThreadGlobal (); win32ThreadParam * pParm; +#ifdef USE_TLSALLOC_FALLBACK + fprintf(epicsGetStdout(), "Warning: For this target, use of epicsThread* from non-EPICS threads\n" + " May leak memory. Recommend to upgrade to >= Window Vista\n"); +#endif + if ( ! pGbl ) { return; } From fe9995c0b50e0aacd43e7aaa7fe6fab1e866db2a Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Fri, 28 Apr 2023 10:38:34 -0500 Subject: [PATCH 22/29] Update recommendation for CONFIGURE_MAXIMUM_FILE_DESCRIPTORS --- modules/libcom/RTEMS/posix/rtems_config.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/modules/libcom/RTEMS/posix/rtems_config.c b/modules/libcom/RTEMS/posix/rtems_config.c index c9fa8a8d7..e4f882355 100644 --- a/modules/libcom/RTEMS/posix/rtems_config.c +++ b/modules/libcom/RTEMS/posix/rtems_config.c @@ -60,7 +60,19 @@ extern void *POSIX_Init(void *argument); #define CONFIGURE_APPLICATION_NEEDS_STUB_DRIVER #define CONFIGURE_APPLICATION_NEEDS_ZERO_DRIVER -/* Max FDs cannot exceed FD_SETSIZE from newlib (64) */ +/* Note: The select() system call can only be used with the first FD_SETSIZE + * File Descriptors (newlib default is 64). Beginning RTEMS 5.1, FDs are + * allocated sequentially. So changing this CONFIGURE parameter such + * that CONFIGURE_MAXIMUM_FILE_DESCRIPTORS >= FD_SETSIZE will likely + * cause applications making select() calls to fault at some point. + * + * IOC core components (libca and RSRV) do not make select() calls. + * + * Applications and driver code using poll() or other socket + * multiplexers do not share this limitation. + * + * cf. https://github.com/epics-base/epics-base/issues/300 + */ #define CONFIGURE_MAXIMUM_FILE_DESCRIPTORS 64 #define CONFIGURE_IMFS_ENABLE_MKFIFO 2 From 8f1243da406aebf5a8cebda65ea84e5dbeeff286 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Thu, 29 Sep 2022 17:51:05 -0700 Subject: [PATCH 23/29] epicsSingleton: eliminate global ctor with >= c++11 --- modules/libcom/src/cxxTemplates/epicsSingleton.h | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/modules/libcom/src/cxxTemplates/epicsSingleton.h b/modules/libcom/src/cxxTemplates/epicsSingleton.h index 074ca16fb..21f15c37e 100644 --- a/modules/libcom/src/cxxTemplates/epicsSingleton.h +++ b/modules/libcom/src/cxxTemplates/epicsSingleton.h @@ -23,6 +23,9 @@ class SingletonUntyped { public: +#if __cplusplus>=201103L + constexpr +#endif SingletonUntyped () :_pInstance ( 0 ), _refCount ( 0 ) {} # if 0 ~SingletonUntyped () { @@ -112,6 +115,9 @@ public: epicsSingleton * _pSingleton; }; friend class reference; +#if __cplusplus>=201103L + constexpr +#endif epicsSingleton () {} // mutex lock/unlock pair overhead incurred // when either of these are called From 0c13e6ba6cbae559cb767f63836ca7409000a54d Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Sun, 12 Mar 2023 09:03:26 +0000 Subject: [PATCH 24/29] iocsh: tab completion handle iocshArgArgv --- modules/libcom/src/iocsh/iocsh.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/modules/libcom/src/iocsh/iocsh.cpp b/modules/libcom/src/iocsh/iocsh.cpp index fc323d39a..31703560f 100644 --- a/modules/libcom/src/iocsh/iocsh.cpp +++ b/modules/libcom/src/iocsh/iocsh.cpp @@ -559,7 +559,17 @@ char** iocsh_attempt_completion(const char* word, int start, int end) break; } } - err = arg-1u >= size_t(def->pFuncDef->nargs); + if(arg-1u >= size_t(def->pFuncDef->nargs)) { + if(def->pFuncDef->arg + && def->pFuncDef->nargs + && def->pFuncDef->arg[def->pFuncDef->nargs-1u]->type == iocshArgArgv) + { + // last argument is variable length + arg = def->pFuncDef->nargs; + } else { + err = true; + } + } } if(!err) { From d4fab0d20e9b45c94859c2085cbd761446d4bd79 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Sun, 12 Mar 2023 09:15:10 +0000 Subject: [PATCH 25/29] iocsh: dbCompleteRecord() missing NULL check --- .../src/ioc/dbStatic/dbCompleteRecord.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/modules/database/src/ioc/dbStatic/dbCompleteRecord.cpp b/modules/database/src/ioc/dbStatic/dbCompleteRecord.cpp index 73ad6164f..ee8a08e74 100644 --- a/modules/database/src/ioc/dbStatic/dbCompleteRecord.cpp +++ b/modules/database/src/ioc/dbStatic/dbCompleteRecord.cpp @@ -140,14 +140,16 @@ char** dbCompleteRecord(const char *cword) if(!prefix.empty() || !suggestions.empty()) { ret = (char**)malloc(sizeof(*ret)*(2u + suggestions.size())); - ret[0] = prefix.dup(); - size_t n=1u; - for(suggestions_t::iterator it(suggestions.begin()), end(suggestions.end()); - it!=end; ++it) - { - ret[n++] = it->dup(); + if(ret) { + ret[0] = prefix.dup(); + size_t n=1u; + for(suggestions_t::iterator it(suggestions.begin()), end(suggestions.end()); + it!=end; ++it) + { + ret[n++] = it->dup(); + } + ret[n] = NULL; } - ret[n] = NULL; } return ret; From cb97d662a74b8de6d7265a0863edbf88d0486f28 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Mon, 8 Aug 2022 17:11:03 -0700 Subject: [PATCH 26/29] doc --- modules/database/src/ioc/db/dbLink.h | 13 +++++++++++++ modules/libcom/src/iocsh/iocsh.h | 1 + 2 files changed, 14 insertions(+) diff --git a/modules/database/src/ioc/db/dbLink.h b/modules/database/src/ioc/db/dbLink.h index d1e454485..9b4e9a9f0 100644 --- a/modules/database/src/ioc/db/dbLink.h +++ b/modules/database/src/ioc/db/dbLink.h @@ -410,8 +410,21 @@ DBCORE_API long dbLoadLinkArray(struct link *, short dbrType, void *pbuffer, DBCORE_API long dbGetNelements(const struct link *plink, long *pnElements); DBCORE_API int dbIsLinkConnected(const struct link *plink); /* 0 or 1 */ DBCORE_API int dbGetLinkDBFtype(const struct link *plink); +/** \brief Fetch current value from link. + * \param dbrType Database DBR code + * \param pbuffer Destination buffer + * \param nRequest If !NULL. Caller initializes with number of elements requested, + * On success, set to number of elements written to pbuffer. + * \return 0 on success + * + * When called with `nRequest==NULL`, treated as a request for one (1) + * element. + * + * see lset::getValue + */ DBCORE_API long dbTryGetLink(struct link *, short dbrType, void *pbuffer, long *nRequest); +/** see dbTryGetLink() */ DBCORE_API long dbGetLink(struct link *, short dbrType, void *pbuffer, long *options, long *nRequest); DBCORE_API long dbGetControlLimits(const struct link *plink, double *low, diff --git a/modules/libcom/src/iocsh/iocsh.h b/modules/libcom/src/iocsh/iocsh.h index ef0d5c50d..2c8da0998 100644 --- a/modules/libcom/src/iocsh/iocsh.h +++ b/modules/libcom/src/iocsh/iocsh.h @@ -287,6 +287,7 @@ LIBCOM_API int epicsStdCall iocshRun(const char *cmd, const char* macros); * * @param err 0 - success (no op), !=0 - error * @return The err argument value. + * @since 7.0.3.1 */ LIBCOM_API int iocshSetError(int err); From 5a1f3ecc8b9d97a5fa4f3a981651b4614fc7217f Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Thu, 18 May 2023 11:57:41 -0700 Subject: [PATCH 27/29] doc: note when some record types were introduced --- modules/database/src/std/rec/aSubRecord.dbd.pod | 2 ++ modules/database/src/std/rec/histogramRecord.dbd.pod | 2 ++ modules/database/src/std/rec/int64inRecord.dbd.pod | 2 ++ modules/database/src/std/rec/int64outRecord.dbd.pod | 2 ++ modules/database/src/std/rec/lsiRecord.dbd.pod | 2 ++ modules/database/src/std/rec/lsoRecord.dbd.pod | 2 ++ modules/database/src/std/rec/printfRecord.dbd.pod | 2 ++ 7 files changed, 14 insertions(+) diff --git a/modules/database/src/std/rec/aSubRecord.dbd.pod b/modules/database/src/std/rec/aSubRecord.dbd.pod index b03073f2c..5eeb30d54 100644 --- a/modules/database/src/std/rec/aSubRecord.dbd.pod +++ b/modules/database/src/std/rec/aSubRecord.dbd.pod @@ -42,6 +42,8 @@ value is non-zero. =back +This record type was included in base.dbd beginning with epics-base 3.14.10 . + =head2 Record-specific Menus =head3 Menu aSubLFLG diff --git a/modules/database/src/std/rec/histogramRecord.dbd.pod b/modules/database/src/std/rec/histogramRecord.dbd.pod index 261023084..1c8a4a556 100644 --- a/modules/database/src/std/rec/histogramRecord.dbd.pod +++ b/modules/database/src/std/rec/histogramRecord.dbd.pod @@ -13,6 +13,8 @@ The histogram record is used to store frequency counts of a signal into an array of arbitrary length. The user can configure the range of the signal value that the array will store. Anything outside this range will be ignored. +This record type was included in base.dbd beginning with epics-base 3.15.0.1 . + =head2 Parameter Fields The record-specific fields are described below. diff --git a/modules/database/src/std/rec/int64inRecord.dbd.pod b/modules/database/src/std/rec/int64inRecord.dbd.pod index 48e976155..a6e793d8d 100644 --- a/modules/database/src/std/rec/int64inRecord.dbd.pod +++ b/modules/database/src/std/rec/int64inRecord.dbd.pod @@ -14,6 +14,8 @@ from a hardware input. The record supports alarm limits, alarm filtering, graphics and control limits. +This record type was included in base.dbd beginning with epics-base 3.16.1 . + =head2 Parameter Fields The record-specific fields are described below. diff --git a/modules/database/src/std/rec/int64outRecord.dbd.pod b/modules/database/src/std/rec/int64outRecord.dbd.pod index 09fd38d6d..07e929a12 100644 --- a/modules/database/src/std/rec/int64outRecord.dbd.pod +++ b/modules/database/src/std/rec/int64outRecord.dbd.pod @@ -13,6 +13,8 @@ This record type is normally used to send an integer value of up to 64 bits to an output device. The record supports alarm, drive, graphics and control limits. +This record type was included in base.dbd beginning with epics-base 3.16.1 . + =head2 Parameter Fields The record-specific fields are described below. diff --git a/modules/database/src/std/rec/lsiRecord.dbd.pod b/modules/database/src/std/rec/lsiRecord.dbd.pod index 035dc0cb4..619a1df1a 100644 --- a/modules/database/src/std/rec/lsiRecord.dbd.pod +++ b/modules/database/src/std/rec/lsiRecord.dbd.pod @@ -11,6 +11,8 @@ The long string input record is used to retrieve an arbitrary ASCII string with a maximum length of 65535 characters. +This record type was included in base.dbd beginning with epics-base 3.15.0.2 . + =head2 Parameter Fields The record-specific fields are described below, grouped by functionality. diff --git a/modules/database/src/std/rec/lsoRecord.dbd.pod b/modules/database/src/std/rec/lsoRecord.dbd.pod index f4f4883ec..0b3bc06e2 100644 --- a/modules/database/src/std/rec/lsoRecord.dbd.pod +++ b/modules/database/src/std/rec/lsoRecord.dbd.pod @@ -11,6 +11,8 @@ The long string output record is used to write an arbitrary ASCII string with a maximum length of 65535 characters. +This record type was included in base.dbd beginning with epics-base 3.15.0.2 . + =head2 Parameter Fields The record-specific fields are described below, grouped by functionality. diff --git a/modules/database/src/std/rec/printfRecord.dbd.pod b/modules/database/src/std/rec/printfRecord.dbd.pod index 04c443471..b649158ee 100644 --- a/modules/database/src/std/rec/printfRecord.dbd.pod +++ b/modules/database/src/std/rec/printfRecord.dbd.pod @@ -11,6 +11,8 @@ The printf record is used to generate and write a string using a format specification and parameters, analogous to the C C function. +This record type was included in base.dbd beginning with epics-base 3.15.0.2 . + =head2 Parameter Fields The record-specific fields are described below, grouped by functionality. From 4ecc0daa793f8b64869eb8e8e922b1f60475b85c Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Tue, 13 Jun 2023 08:18:12 -0700 Subject: [PATCH 28/29] make mapDBFToDBR[] const --- modules/database/src/ioc/db/dbAccess.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/database/src/ioc/db/dbAccess.c b/modules/database/src/ioc/db/dbAccess.c index 8fcdab8a7..7f6a6b6f7 100644 --- a/modules/database/src/ioc/db/dbAccess.c +++ b/modules/database/src/ioc/db/dbAccess.c @@ -73,7 +73,7 @@ epicsExportAddress(int, dbAccessDebugPUTF); DB_LOAD_RECORDS_HOOK_ROUTINE dbLoadRecordsHook = NULL; -static short mapDBFToDBR[DBF_NTYPES] = { +static const short mapDBFToDBR[DBF_NTYPES] = { /* DBF_STRING => */ DBR_STRING, /* DBF_CHAR => */ DBR_CHAR, /* DBF_UCHAR => */ DBR_UCHAR, From 42604fc794b6f26b60db953a230b52c1118d3039 Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Tue, 13 Jun 2023 08:22:17 -0700 Subject: [PATCH 29/29] Allow clang with GCC compilerSpecific.h Makes it easier to run clang derivative analysis tools on builds configured for GCC. --- modules/libcom/src/osi/compiler/gcc/compilerSpecific.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/modules/libcom/src/osi/compiler/gcc/compilerSpecific.h b/modules/libcom/src/osi/compiler/gcc/compilerSpecific.h index 6e98f4d17..d56d32212 100644 --- a/modules/libcom/src/osi/compiler/gcc/compilerSpecific.h +++ b/modules/libcom/src/osi/compiler/gcc/compilerSpecific.h @@ -21,10 +21,6 @@ # error compiler/gcc/compilerSpecific.h is only for use with the gnu compiler #endif -#ifdef __clang__ -# error compiler/gcc/compilerSpecific.h is not for use with the clang compiler -#endif - #define EPICS_ALWAYS_INLINE __inline__ __attribute__((always_inline)) /* Expands to a 'const char*' which describes the name of the current function scope */