Merge ~bfrk/epics-base:address-modifiers into ~epics-core/epics-base/+git/epics-base:7.0

Proposed by Ben Franksen
Status: Needs review
Proposed branch: ~bfrk/epics-base:address-modifiers
Merge into: ~epics-core/epics-base/+git/epics-base:7.0
Diff against target: 1262 lines (+847/-99)
16 files modified
modules/database/src/ioc/db/Makefile (+3/-0)
modules/database/src/ioc/db/arrayRangeModifier.c (+167/-0)
modules/database/src/ioc/db/arrayRangeModifier.h (+69/-0)
modules/database/src/ioc/db/dbAccess.c (+36/-15)
modules/database/src/ioc/db/dbAccessDefs.h (+10/-0)
modules/database/src/ioc/db/dbAddrModifier.h (+48/-0)
modules/database/src/ioc/db/dbChannel.c (+21/-63)
modules/database/src/ioc/db/dbChannel.h (+3/-0)
modules/database/src/ioc/db/dbDbLink.c (+1/-2)
modules/database/src/ioc/db/dbUnitTest.c (+2/-2)
modules/database/src/std/filters/arr.c (+1/-17)
modules/database/test/std/rec/Makefile (+11/-0)
modules/database/test/std/rec/addrModifierTest.c (+247/-0)
modules/database/test/std/rec/addrModifierTest.db (+24/-0)
modules/database/test/std/rec/arroutRecord.c (+169/-0)
modules/database/test/std/rec/arroutRecord.dbd (+35/-0)
Reviewer Review Type Date Requested Status
EPICS Core Developers Pending
Review via email: mp+400517@code.launchpad.net

This proposal supersedes a proposal from 2020-04-03.

Description of the change

As discussed on core-talk, this enables interpretation of "array address modifiers" a la "PV[s:i:e]" when using dbChannelPut or dbChannelPutField. This means the feature works for all PV links (I changed DB links to call dbChannelPut) as well as for caput.

The work is based on Dirk's dbChannelForDBLink branch (merged into 7.0) and some of my previous work (see branch address-modifiers-prerequisites).

The current implementation is /very/ liberal in that it never fails if the address modifier is out-of-bounds wrt the (target) record field. In this case the put operation is truncated to the overlap between the modifier and the target array, which may be empty (i.e. no change in the target). Similarly, if the source array has too few elements or excess elements, relative to the modifier, the request gets truncated to the smaller of these numbers.

Another design decision was that a put operation with a modifier never changes the current number of elements of the target. So you cannot currently "extend" an array using a modifier by writing beyond its current size.

All of these particular behaviors are, of course, open for debate. I can easily change any aspect as the implementation is quite simple.

To post a comment you must log in.
Revision history for this message
Andrew Johnson (anj) wrote : Posted in a previous version of this proposal

Hi Ben, this looks very interesting; could you take it a little further? See my diff comments, in particular the one in dbAddrModifier.h where I speculate about enabling future modifiers. I don't have any arguments with your design decisions as described, and the code generally looks nice.

Revision history for this message
Ben Franksen (bfrk) wrote : Posted in a previous version of this proposal
Download full text (3.8 KiB)

Hi Andrew, thanks for your comments.

>> +long dbPutModifier(DBADDR *paddr, short dbrType,
>> + const void *pbuffer, long nRequest, dbAddrModifier *pmod)
>> {
>> dbCommon *precord = paddr->precord;
>> short field_type = paddr->field_type;
>> + short field_size = paddr->field_size;
>> long no_elements = paddr->no_elements;
>> long special = paddr->special;
>> void *pfieldsave = paddr->pfield;
>> rset *prset = dbGetRset(paddr);
>> long status = 0;
>> + long available_no_elements = no_elements;
>
> s/available_no_elements/capacity/ maybe? It's a lot shorter...

Ah, but it's used with the opposite meaning: while initialized with the
capacity, it gets overwritten with the /current/ number of elements by
get_array_info which was previously ignored (passing &dummy).

> Please add the license header to all new source files; the Copyright lines aren't essential, but the 2 lines starting "EPICS BASE is distributed..." should be included.

Will do.

> I could see us wanting to add more kinds of modifiers in the future, although I'm not looking to make them pluggable at run-time. (NB: I'm speculating here).

Should that allow to combine multiple address modifiers, like for
filters? This would be very hard to get right. For the moment I will
assume that only one address modifier can be given per channel.

(Run-time pluggability indeed seems excessive, given that we have to
restart IOCs anyway whenever the database gets re-configured i.e.
records added or removed).

> Could you extract the array-specific operations and data and make dbAddrModifier polymorphic, with the implementation collected into a separate source file maybe, so other modifiers could be added too?

Separating out the code I added to dbPut was already on my TODO list.
Especially after adding ad-hoc index lists as another form of modifier
as you had suggested previously, it became clear that this doesn't
scale. So I will start with this move and then see if/how I can make
address modifiers polymorphic.

> I still want to be able to create a put filter that could accept a JSON string as the value, but this implementation currently hard-codes the dbAddrModifier to only support array filters. My filter doesn't have to use JSON in the PV name modifier, but it does need to do different things when the value comes in and I don't want to have to add that processing code to the dbAccess.c file. Maybe you could even extract the code for the '$' modifier into a separate parse-time routine too?
>
> I'm trying to increase modularity here.

I understand your point. Be warned though that this will increase the
overall complexity, not sure yet how much. Also, and unfortunately,
polymorphism in C is always kind of hazardous and an invitation for
stupid mistakes that should be type errors but can't because the type
checker is too dumb (see the error I recently made with &prec->val
versus prec->val).

>> +/** @brief The address modifier that modifies nothing. */
>> +#define defaultAddrModifier (struct dbAddrModifier){0,1,-1}
>
> Not sure that all our supported C compilers can accept this syntax, but this macro isn't actually being used anywhere in you...

Read more...

Revision history for this message
Ben Franksen (bfrk) wrote : Posted in a previous version of this proposal

@Andrew I have addressed most of the issues you raised and made the address modifier API polymorphic.

The parsing is not yet encapsulated. There is a trade-off here: we use the same syntax as a short-cut for filters. Not sure what to do here; perhaps create a separate module just for this parser. I'll have to experiment to see if that results in a better structure.

Revision history for this message
Ben Franksen (bfrk) wrote :

Resubmitted this MR to drop obsolete prerequisites.

Unmerged commits

8ca85b3... by Ben Franksen

fix (justified) warnings in dbUnitTest.c

aaf6163... by Ben Franksen

move array range parsing to arrayRangeModifier module

6aaf531... by Ben Franksen

fix API docs in arrayRangeModifier.h

a30969c... by Ben Franksen

address modifier API is now polymorphic

There is still only one implementation, the array range modifier, but
alternative ones could be added now. The concrete behavior of the array
range modifier is unchanged and merely hidden in a module. However parsing
is still part of the dbChannel module. Disentangling this part from
dbChannel will require a bit more restructuring.

Since the generic dbAddrModifier now has a small constant size (two
pointers), it makes more sense to embed it fully inside dbChannel.

8f4ed7d... by Ben Franksen

factor out dbHandleModifier from dbPutModifier

This is the first step in changing address modifiers to be polymorphic.

f0dbd54... by Ben Franksen

add tests for address modifiers in output links

2ca474f... by Ben Franksen

add support for array address modifiers on put

This is independent from server-side filters. It only works with the bracket
notation "PV[s:i:e]", and not with the JSON filter syntax. However, we
re-use the function wrapArrayIndices from the array filters, moving it to
the ioc/db layer.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/modules/database/src/ioc/db/Makefile b/modules/database/src/ioc/db/Makefile
2index ebb78e2..e590cc6 100644
3--- a/modules/database/src/ioc/db/Makefile
4+++ b/modules/database/src/ioc/db/Makefile
5@@ -11,10 +11,12 @@
6
7 SRC_DIRS += $(IOCDIR)/db
8
9+INC += arrayRangeModifier.h
10 INC += callback.h
11 INC += dbAccess.h
12 INC += dbAccessDefs.h
13 INC += dbAddr.h
14+INC += dbAddrModifier.h
15 INC += dbBkpt.h
16 INC += dbCa.h
17 INC += dbChannel.h
18@@ -66,6 +68,7 @@ HTMLS += dbCommonRecord.html
19 HTMLS += dbCommonInput.html
20 HTMLS += dbCommonOutput.html
21
22+dbCore_SRCS += arrayRangeModifier.c
23 dbCore_SRCS += dbLock.c
24 dbCore_SRCS += dbAccess.c
25 dbCore_SRCS += dbBkpt.c
26diff --git a/modules/database/src/ioc/db/arrayRangeModifier.c b/modules/database/src/ioc/db/arrayRangeModifier.c
27new file mode 100644
28index 0000000..1feb7bc
29--- /dev/null
30+++ b/modules/database/src/ioc/db/arrayRangeModifier.c
31@@ -0,0 +1,167 @@
32+/*************************************************************************\
33+* EPICS BASE is distributed subject to a Software License Agreement found
34+* in file LICENSE that is included with this distribution.
35+\*************************************************************************/
36+
37+#include <assert.h>
38+#include <stdlib.h>
39+#include <stdio.h>
40+
41+#include "arrayRangeModifier.h"
42+#include "dbAccessDefs.h"
43+#include "dbConvertFast.h"
44+#include "dbConvert.h"
45+
46+typedef struct {
47+ long start;
48+ long incr;
49+ long end;
50+} arrayRangeModifier;
51+
52+long wrapArrayIndices(long *start, const long increment,
53+ long *end, const long no_elements)
54+{
55+ if (*start < 0) *start = no_elements + *start;
56+ if (*start < 0) *start = 0;
57+ if (*start > no_elements) *start = no_elements;
58+
59+ if (*end < 0) *end = no_elements + *end;
60+ if (*end < 0) *end = 0;
61+ if (*end >= no_elements) *end = no_elements - 1;
62+
63+ if (*end - *start >= 0)
64+ return 1 + (*end - *start) / increment;
65+ else
66+ return 0;
67+}
68+
69+static long handleArrayRangeModifier(DBADDR *paddr, short dbrType, const void
70+ *pbuffer, long nRequest, void *pvt, long offset, long available)
71+{
72+ arrayRangeModifier *pmod = (arrayRangeModifier *)pvt;
73+ long status = 0;
74+ long start = pmod->start;
75+ long end = pmod->end;
76+ /* Note that this limits the return value to be <= available */
77+ long n = wrapArrayIndices(&start, pmod->incr, &end, available);
78+
79+ assert(pmod->incr > 0);
80+ if (pmod->incr > 1) {
81+ long i, j;
82+ long (*putCvt) (const void *from, void *to, const dbAddr * paddr) =
83+ dbFastPutConvertRoutine[dbrType][paddr->field_type];
84+ short dbr_size = dbValueSize(dbrType);
85+ if (nRequest > n)
86+ nRequest = n;
87+ for (i = 0, j = (offset + start) % paddr->no_elements; i < nRequest;
88+ i++, j = (j + pmod->incr) % paddr->no_elements) {
89+ status = putCvt(pbuffer + (i * dbr_size),
90+ paddr->pfield + (j * paddr->field_size), paddr);
91+ }
92+ } else {
93+ offset = (offset + start) % paddr->no_elements;
94+ if (nRequest > n)
95+ nRequest = n;
96+ if (paddr->no_elements <= 1) {
97+ status = dbFastPutConvertRoutine[dbrType][paddr->field_type] (pbuffer,
98+ paddr->pfield, paddr);
99+ } else {
100+ if (paddr->no_elements < nRequest)
101+ nRequest = paddr->no_elements;
102+ status = dbPutConvertRoutine[dbrType][paddr->field_type] (paddr,
103+ pbuffer, nRequest, paddr->no_elements, offset);
104+ }
105+ }
106+ return status;
107+}
108+
109+long createArrayRangeModifier(dbAddrModifier *pmod, long start, long incr, long end)
110+{
111+ arrayRangeModifier *pvt =
112+ (arrayRangeModifier *) malloc(sizeof(arrayRangeModifier));
113+ if (incr <= 0) {
114+ return S_db_errArg;
115+ }
116+ if (!pvt) {
117+ return S_db_noMemory;
118+ }
119+ pvt->start = start;
120+ pvt->incr = incr;
121+ pvt->end = end;
122+ pmod->pvt = pvt;
123+ pmod->handle = handleArrayRangeModifier;
124+ return 0;
125+}
126+
127+void deleteArrayRangeModifier(dbAddrModifier *pmod)
128+{
129+ if (pmod->pvt)
130+ free(pmod->pvt);
131+}
132+
133+long parseArrayRange(dbAddrModifier *pmod, const char **pstring)
134+{
135+ long start = 0;
136+ long end = -1;
137+ long incr = 1;
138+ long tmp;
139+ const char *pcurrent = *pstring;
140+ char *pnext;
141+ ptrdiff_t advance;
142+ long status = 0;
143+
144+ if (*pcurrent != '[') {
145+ return -1;
146+ }
147+ pcurrent++;
148+ /* If no number is present, strtol() returns 0 and sets pnext=pcurrent,
149+ else pnext points to the first char after the number */
150+ tmp = strtol(pcurrent, &pnext, 0);
151+ advance = pnext - pcurrent;
152+ if (advance) start = tmp;
153+ pcurrent = pnext;
154+ if (*pcurrent == ']' && advance) {
155+ end = start;
156+ goto done;
157+ }
158+ if (*pcurrent != ':') {
159+ return -1;
160+ }
161+ pcurrent++;
162+ tmp = strtol(pcurrent, &pnext, 0);
163+ advance = pnext - pcurrent;
164+ pcurrent = pnext;
165+ if (*pcurrent == ']') {
166+ if (advance) end = tmp;
167+ goto done;
168+ }
169+ if (advance) incr = tmp;
170+ if (*pcurrent != ':') {
171+ return -1;
172+ }
173+ pcurrent++;
174+ tmp = strtol(pcurrent, &pnext, 0);
175+ advance = pnext - pcurrent;
176+ if (advance) end = tmp;
177+ pcurrent = pnext;
178+ if (*pcurrent != ']') {
179+ return -1;
180+ }
181+
182+done:
183+ status = createArrayRangeModifier(pmod, start, incr, end);
184+ if (status) {
185+ return status;
186+ }
187+ pcurrent++;
188+ *pstring = pcurrent;
189+ return 0;
190+}
191+
192+void getArrayRange(dbAddrModifier *pmod, long *pstart, long *pincr, long *pend)
193+{
194+ arrayRangeModifier *pvt = (arrayRangeModifier *)pmod->pvt;
195+ *pstart = pvt->start;
196+ *pincr = pvt->incr;
197+ *pend = pvt->end;
198+}
199diff --git a/modules/database/src/ioc/db/arrayRangeModifier.h b/modules/database/src/ioc/db/arrayRangeModifier.h
200new file mode 100644
201index 0000000..8e0cd2f
202--- /dev/null
203+++ b/modules/database/src/ioc/db/arrayRangeModifier.h
204@@ -0,0 +1,69 @@
205+/*************************************************************************\
206+* EPICS BASE is distributed subject to a Software License Agreement found
207+* in file LICENSE that is included with this distribution.
208+\*************************************************************************/
209+#ifndef ARRAYRANGEMODIFIER_H
210+#define ARRAYRANGEMODIFIER_H
211+
212+#include "dbAddrModifier.h"
213+#include "shareLib.h"
214+
215+/** @brief The array range address modifier. */
216+
217+/** @brief Given a number of elements, convert negative start and end indices
218+ * to non-negative ones by counting from the end of the array.
219+ *
220+ * @param start Pointer to possibly negative start index
221+ * @param increment Increment
222+ * @param end Pointer to possibly negative end index
223+ * @param no_elements Number of array elements
224+ * @return Final number of elements
225+ */
226+epicsShareFunc
227+long wrapArrayIndices(long *start, long increment, long *end, long no_elements);
228+
229+/** @brief Create an array range modifier from start index, increment, and end index
230+ *
231+ * @param pmod Pointer to address modifier (user allocated)
232+ * @param start Start index (possibly negative)
233+ * @param incr Increment (must be positive)
234+ * @param end End index (possibly negative)
235+ * @return Status
236+ */
237+epicsShareFunc
238+long createArrayRangeModifier(dbAddrModifier *pmod, long start, long incr, long end);
239+
240+/** @brief Alternative creation function that parses the data from a string.
241+ *
242+ * If successful, advances 'pstring' to point after the recognized address modifier.
243+ *
244+ * @param pmod Pointer to uninitialized address modifier (user allocated)
245+ * @param pstring Pointer to string to parse (in/out)
246+ * @return 0 (success) or -1 (failure)
247+ */
248+epicsShareFunc
249+long parseArrayRange(dbAddrModifier *pmod, const char **pstring);
250+
251+/** @brief Extract the private data
252+ *
253+ * The 'pmod' argument must point to a valid array range address modifier.
254+ * The other aruments must point to appropriately sized storage.
255+ *
256+ * @param pmod Pointer to uninitialized address modifier (user allocated)
257+ * @param pstart Start index (out)
258+ * @param pincr Increment (out)
259+ * @param pend End index (out)
260+ */
261+epicsShareFunc
262+void getArrayRange(dbAddrModifier *pmod, long *pstart, long *pincr, long *pend);
263+
264+/** @brief Free private memory associated with an array range modifier
265+ *
266+ * Note this does not free 'pmod' which is always user allocated.
267+ *
268+ * @param pmod Pointer to address modifier (user allocated)
269+ */
270+epicsShareFunc
271+void deleteArrayRangeModifier(dbAddrModifier *pmod);
272+
273+#endif /* ARRAYRANGEMODIFIER_H */
274diff --git a/modules/database/src/ioc/db/dbAccess.c b/modules/database/src/ioc/db/dbAccess.c
275index bac208f..52169c0 100644
276--- a/modules/database/src/ioc/db/dbAccess.c
277+++ b/modules/database/src/ioc/db/dbAccess.c
278@@ -40,6 +40,7 @@
279 #include "callback.h"
280 #include "dbAccessDefs.h"
281 #include "dbAddr.h"
282+#include "dbAddrModifier.h"
283 #include "dbBase.h"
284 #include "dbBkpt.h"
285 #include "dbCommonPvt.h"
286@@ -1225,6 +1226,12 @@ cleanup:
287 long dbPutField(DBADDR *paddr, short dbrType,
288 const void *pbuffer, long nRequest)
289 {
290+ return dbPutFieldModifier(paddr, dbrType, pbuffer, nRequest, NULL);
291+}
292+
293+long dbPutFieldModifier(DBADDR *paddr, short dbrType,
294+ const void *pbuffer, long nRequest, dbAddrModifier *pmod)
295+{
296 long status = 0;
297 long special = paddr->special;
298 dbFldDes *pfldDes = paddr->pfldDes;
299@@ -1242,7 +1249,7 @@ long dbPutField(DBADDR *paddr, short dbrType,
300 return dbPutFieldLink(paddr, dbrType, pbuffer, nRequest);
301
302 dbScanLock(precord);
303- status = dbPut(paddr, dbrType, pbuffer, nRequest);
304+ status = dbPutModifier(paddr, dbrType, pbuffer, nRequest, pmod);
305 if (status == 0) {
306 if (paddr->pfield == &precord->proc ||
307 (pfldDes->process_passive &&
308@@ -1296,8 +1303,13 @@ static long putAcks(DBADDR *paddr, const void *pbuffer, long nRequest,
309 return 0;
310 }
311
312-long dbPut(DBADDR *paddr, short dbrType,
313- const void *pbuffer, long nRequest)
314+long dbPut(DBADDR *paddr, short dbrType, const void *pbuffer, long nRequest)
315+{
316+ return dbPutModifier(paddr, dbrType, pbuffer, nRequest, NULL);
317+}
318+
319+long dbPutModifier(DBADDR *paddr, short dbrType,
320+ const void *pbuffer, long nRequest, dbAddrModifier *pmod)
321 {
322 dbCommon *precord = paddr->precord;
323 short field_type = paddr->field_type;
324@@ -1306,6 +1318,7 @@ long dbPut(DBADDR *paddr, short dbrType,
325 void *pfieldsave = paddr->pfield;
326 rset *prset = dbGetRset(paddr);
327 long status = 0;
328+ long available_no_elements = no_elements;
329 long offset;
330 dbFldDes *pfldDes;
331 int isValueField;
332@@ -1332,25 +1345,33 @@ long dbPut(DBADDR *paddr, short dbrType,
333
334 if (paddr->pfldDes->special == SPC_DBADDR &&
335 prset && prset->get_array_info) {
336- long dummy;
337
338- status = prset->get_array_info(paddr, &dummy, &offset);
339+ status = prset->get_array_info(paddr, &available_no_elements, &offset);
340 /* paddr->pfield may be modified */
341 if (status) goto done;
342- if (no_elements < nRequest)
343- nRequest = no_elements;
344- status = dbPutConvertRoutine[dbrType][field_type](paddr, pbuffer,
345- nRequest, no_elements, offset);
346- /* update array info */
347- if (!status && prset->put_array_info)
348- status = prset->put_array_info(paddr, nRequest);
349+ } else
350+ offset = 0;
351+
352+ if (pmod && pmod->handle && pmod->pvt) {
353+ status = pmod->handle(paddr, dbrType, pbuffer, nRequest,
354+ pmod->pvt, offset, available_no_elements);
355 } else {
356- if (nRequest < 1) {
357- recGblSetSevr(precord, LINK_ALARM, INVALID_ALARM);
358- } else {
359+ if (no_elements <= 1) {
360 status = dbFastPutConvertRoutine[dbrType][field_type](pbuffer,
361 paddr->pfield, paddr);
362 nRequest = 1;
363+ } else {
364+ if (no_elements < nRequest)
365+ nRequest = no_elements;
366+ status = dbPutConvertRoutine[dbrType][field_type](paddr, pbuffer,
367+ nRequest, no_elements, offset);
368+ }
369+
370+ /* update array info unless writing failed */
371+ if (!status &&
372+ paddr->pfldDes->special == SPC_DBADDR &&
373+ prset && prset->put_array_info) {
374+ status = prset->put_array_info(paddr, nRequest);
375 }
376 }
377
378diff --git a/modules/database/src/ioc/db/dbAccessDefs.h b/modules/database/src/ioc/db/dbAccessDefs.h
379index f2cf091..45e9f6e 100644
380--- a/modules/database/src/ioc/db/dbAccessDefs.h
381+++ b/modules/database/src/ioc/db/dbAccessDefs.h
382@@ -27,7 +27,9 @@
383
384 #include "dbBase.h"
385 #include "dbAddr.h"
386+#include "dbAddrModifier.h"
387 #include "recSup.h"
388+#include "db_field_log.h"
389
390 #ifdef __cplusplus
391 extern "C" {
392@@ -247,11 +249,19 @@ epicsShareFunc long dbGetField(
393 epicsShareFunc long dbGet(
394 struct dbAddr *,short dbrType,void *pbuffer,long *options,
395 long *nRequest,void *pfl);
396+
397 epicsShareFunc long dbPutField(
398 struct dbAddr *,short dbrType,const void *pbuffer,long nRequest);
399 epicsShareFunc long dbPut(
400 struct dbAddr *,short dbrType,const void *pbuffer,long nRequest);
401
402+epicsShareFunc long dbPutFieldModifier(
403+ struct dbAddr *,short dbrType,const void *pbuffer,long nRequest,
404+ dbAddrModifier *pmod);
405+epicsShareFunc long dbPutModifier(
406+ struct dbAddr *,short dbrType,const void *pbuffer,long nRequest,
407+ dbAddrModifier *pmod);
408+
409 typedef void(*SPC_ASCALLBACK)(struct dbCommon *);
410 /*dbSpcAsRegisterCallback called by access security */
411 epicsShareFunc void dbSpcAsRegisterCallback(SPC_ASCALLBACK func);
412diff --git a/modules/database/src/ioc/db/dbAddrModifier.h b/modules/database/src/ioc/db/dbAddrModifier.h
413new file mode 100644
414index 0000000..dcc3c86
415--- /dev/null
416+++ b/modules/database/src/ioc/db/dbAddrModifier.h
417@@ -0,0 +1,48 @@
418+/*************************************************************************\
419+* EPICS BASE is distributed subject to a Software License Agreement found
420+* in file LICENSE that is included with this distribution.
421+\*************************************************************************/
422+#ifndef DBADDRMODIFIER_H
423+#define DBADDRMODIFIER_H
424+
425+#include "dbAddr.h"
426+
427+/** @brief Generic API for address modifiers. */
428+
429+/** @brief Type of functions that handle an address modifier.
430+ *
431+ * This function should write the value in 'pbuffer' to the target 'pAddr',
432+ * according to the number of requested elements 'nRequest', the requested type
433+ * 'dbrType', and the address modifier private data 'pvt'. It may also take
434+ * into account the actual number of elements 'available' in the target, and
435+ * the 'offset', both of which usually result from a previous call to
436+ * 'get_array_info'.
437+ * The targeted record must be locked. Furthermore, the lock must not be given
438+ * up between the call to 'get_array_info' and call to this function, otherwise
439+ * 'offset' and 'available' may be out of sync.
440+ *
441+ * @param paddr Target (field) address
442+ * @param dbrType Requested (element) type
443+ * @param pbuffer Data requested to be written
444+ * @param nRequest Number of elements in pbuffer
445+ * @param pvt Private modifier data
446+ * @param offset Current offset in the target field
447+ * @param available Current number of elements in the target field
448+ * @return Status
449+ */
450+typedef long dbHandleAddrModifier(DBADDR *paddr, short dbrType,
451+ const void *pbuffer, long nRequest, void *pvt, long offset,
452+ long available);
453+
454+/** @brief Structure of an address modifier.
455+ *
456+ * This will normally be allocated by user code. An address modifier
457+ * implementation should supply a function to create the private data 'pvt'
458+ * and initialize 'handle' with a function to handle the modifier.
459+ */
460+typedef struct {
461+ void *pvt; /** @brief Private modifier data */
462+ dbHandleAddrModifier *handle; /** @brief Function to handle the modifier */
463+} dbAddrModifier;
464+
465+#endif /* DBADDRMODIFIER_H */
466diff --git a/modules/database/src/ioc/db/dbChannel.c b/modules/database/src/ioc/db/dbChannel.c
467index c71d842..a9eb4e2 100644
468--- a/modules/database/src/ioc/db/dbChannel.c
469+++ b/modules/database/src/ioc/db/dbChannel.c
470@@ -30,6 +30,7 @@
471 #include "yajl_parse.h"
472
473 #define epicsExportSharedSymbols
474+#include "arrayRangeModifier.h"
475 #include "dbAccessDefs.h"
476 #include "dbBase.h"
477 #include "dbChannel.h"
478@@ -349,75 +350,27 @@ if (Func) { \
479 if (result != parse_continue) goto failure; \
480 }
481
482-static long parseArrayRange(dbChannel* chan, const char *pname, const char **ppnext) {
483- epicsInt32 start = 0;
484- epicsInt32 end = -1;
485- epicsInt32 incr = 1;
486- epicsInt32 l;
487- char *pnext;
488- ptrdiff_t exist;
489+static long createArrayRangeFilter(dbChannel* chan) {
490+ long start, incr, end;
491 chFilter *filter;
492 const chFilterPlugin *plug;
493 parse_result result;
494- long status = 0;
495-
496- /* If no number is present, strtol() returns 0 and sets pnext=pname,
497- else pnext points to the first char after the number */
498- pname++;
499- l = strtol(pname, &pnext, 0);
500- exist = pnext - pname;
501- if (exist) start = l;
502- pname = pnext;
503- if (*pname == ']' && exist) {
504- end = start;
505- goto insertplug;
506- }
507- if (*pname != ':') {
508- status = S_dbLib_fieldNotFound;
509- goto finish;
510- }
511- pname++;
512- l = strtol(pname, &pnext, 0);
513- exist = pnext - pname;
514- pname = pnext;
515- if (*pname == ']') {
516- if (exist) end = l;
517- goto insertplug;
518- }
519- if (exist) incr = l;
520- if (*pname != ':') {
521- status = S_dbLib_fieldNotFound;
522- goto finish;
523- }
524- pname++;
525- l = strtol(pname, &pnext, 0);
526- exist = pnext - pname;
527- if (exist) end = l;
528- pname = pnext;
529- if (*pname != ']') {
530- status = S_dbLib_fieldNotFound;
531- goto finish;
532- }
533-
534- insertplug:
535- pname++;
536- *ppnext = pname;
537
538 plug = dbFindFilter("arr", 3);
539 if (!plug) {
540- status = S_dbLib_fieldNotFound;
541- goto finish;
542+ /* "arr" plugin not found, this is not an error! */
543+ return 0;
544 }
545
546 filter = freeListCalloc(chFilterFreeList);
547 if (!filter) {
548- status = S_db_noMemory;
549- goto finish;
550+ return S_db_noMemory;
551 }
552 filter->chan = chan;
553 filter->plug = plug;
554 filter->puser = NULL;
555
556+ getArrayRange(&chan->addrModifier, &start, &incr, &end);
557 TRY(filter->plug->fif->parse_start, (filter));
558 TRY(filter->plug->fif->parse_start_map, (filter));
559 if (start != 0) {
560@@ -438,12 +391,9 @@ static long parseArrayRange(dbChannel* chan, const char *pname, const char **ppn
561 ellAdd(&chan->filters, &filter->list_node);
562 return 0;
563
564- failure:
565+failure:
566 freeListFree(chFilterFreeList, filter);
567- status = S_dbLib_fieldNotFound;
568-
569- finish:
570- return status;
571+ return S_dbLib_fieldNotFound;
572 }
573
574 dbChannel * dbChannelCreate(const char *name)
575@@ -481,6 +431,8 @@ dbChannel * dbChannelCreate(const char *name)
576 goto finish;
577
578 /* Handle field modifiers */
579+ chan->addrModifier.pvt = NULL;
580+ chan->addrModifier.handle = NULL;
581 if (*pname) {
582 short dbfType = paddr->field_type;
583
584@@ -505,12 +457,15 @@ dbChannel * dbChannelCreate(const char *name)
585 pname++;
586 }
587
588- if (*pname == '[') {
589- status = parseArrayRange(chan, pname, &pname);
590+ /* Try to recognize an array range expression */
591+ if (parseArrayRange(&chan->addrModifier, &pname)==0) {
592+ status = createArrayRangeFilter(chan);
593 if (status) goto finish;
594 }
595
596 /* JSON may follow */
597+ /* Note that chf_parse issues error messages if it fails,
598+ which is why we have to check for the opening brace here. */
599 if (*pname == '{') {
600 status = chf_parse(chan, &pname);
601 if (status) goto finish;
602@@ -655,13 +610,15 @@ long dbChannelGetField(dbChannel *chan, short dbrType, void *pbuffer,
603 long dbChannelPut(dbChannel *chan, short type, const void *pbuffer,
604 long nRequest)
605 {
606- return dbPut(&chan->addr, type, pbuffer, nRequest);
607+ return dbPutModifier(&chan->addr, type, pbuffer, nRequest,
608+ &chan->addrModifier);
609 }
610
611 long dbChannelPutField(dbChannel *chan, short type, const void *pbuffer,
612 long nRequest)
613 {
614- return dbPutField(&chan->addr, type, pbuffer, nRequest);
615+ return dbPutFieldModifier(&chan->addr, type, pbuffer, nRequest,
616+ &chan->addrModifier);
617 }
618
619 void dbChannelShow(dbChannel *chan, int level, const unsigned short indent)
620@@ -715,6 +672,7 @@ void dbChannelDelete(dbChannel *chan)
621 freeListFree(chFilterFreeList, filter);
622 }
623 free((char *) chan->name);
624+ deleteArrayRangeModifier(&chan->addrModifier);
625 freeListFree(dbChannelFreeList, chan);
626 }
627
628diff --git a/modules/database/src/ioc/db/dbChannel.h b/modules/database/src/ioc/db/dbChannel.h
629index ec86e9e..60fbb9d 100644
630--- a/modules/database/src/ioc/db/dbChannel.h
631+++ b/modules/database/src/ioc/db/dbChannel.h
632@@ -23,6 +23,7 @@
633 #include "epicsTypes.h"
634 #include "errMdef.h"
635 #include "db_field_log.h"
636+#include "dbAddrModifier.h"
637 #include "dbEvent.h"
638 #include "dbCoreAPI.h"
639
640@@ -54,6 +55,8 @@ typedef struct chFilter chFilter;
641 typedef struct dbChannel {
642 const char *name;
643 dbAddr addr; /* address structure for record/field */
644+ dbAddrModifier addrModifier;
645+ /* optional: which indices are targeted */
646 long final_no_elements; /* final number of elements (arrays) */
647 short final_field_size; /* final size of element */
648 short final_type; /* final type of database field */
649diff --git a/modules/database/src/ioc/db/dbDbLink.c b/modules/database/src/ioc/db/dbDbLink.c
650index b281314..fe7c372 100644
651--- a/modules/database/src/ioc/db/dbDbLink.c
652+++ b/modules/database/src/ioc/db/dbDbLink.c
653@@ -364,9 +364,8 @@ static long dbDbPutValue(struct link *plink, short dbrType,
654 struct pv_link *ppv_link = &plink->value.pv_link;
655 dbChannel *chan = linkChannel(plink);
656 struct dbCommon *psrce = plink->precord;
657- DBADDR *paddr = &chan->addr;
658 dbCommon *pdest = dbChannelRecord(chan);
659- long status = dbPut(paddr, dbrType, pbuffer, nRequest);
660+ long status = dbChannelPut(chan, dbrType, pbuffer, nRequest);
661
662 recGblInheritSevr(ppv_link->pvlMask & pvlOptMsMode, pdest, psrce->nsta,
663 psrce->nsev);
664diff --git a/modules/database/src/ioc/db/dbUnitTest.c b/modules/database/src/ioc/db/dbUnitTest.c
665index 0b40d92..1dd4909 100644
666--- a/modules/database/src/ioc/db/dbUnitTest.c
667+++ b/modules/database/src/ioc/db/dbUnitTest.c
668@@ -267,7 +267,7 @@ done:
669 void testdbPutArrFieldOk(const char* pv, short dbrType, unsigned long count, const void *pbuf)
670 {
671 dbChannel *chan = dbChannelCreate(pv);
672- long status;
673+ long status = 0;
674
675 if(!chan || (status=dbChannelOpen(chan))) {
676 testFail("Channel error (%p, %ld) : %s", chan, status, pv);
677@@ -290,7 +290,7 @@ void testdbGetArrFieldEqual(const char* pv, short dbfType, long nRequest, unsign
678 const long vSize = dbValueSize(dbfType);
679 const long nStore = vSize * nRequest;
680 long status = S_dbLib_recNotFound;
681- char *gbuf, *gstore;
682+ char *gbuf, *gstore = NULL;
683 const char *pbuf = pbufraw;
684
685 if(!chan || (status=dbChannelOpen(chan))) {
686diff --git a/modules/database/src/std/filters/arr.c b/modules/database/src/std/filters/arr.c
687index ffe3fce..ed42c67 100644
688--- a/modules/database/src/std/filters/arr.c
689+++ b/modules/database/src/std/filters/arr.c
690@@ -13,6 +13,7 @@
691
692 #include <stdio.h>
693
694+#include "arrayRangeModifier.h"
695 #include "chfPlugin.h"
696 #include "dbAccessDefs.h"
697 #include "dbExtractArray.h"
698@@ -74,23 +75,6 @@ static void freeArray(db_field_log *pfl)
699 }
700 }
701
702-static long wrapArrayIndices(long *start, const long increment, long *end,
703- const long no_elements)
704-{
705- if (*start < 0) *start = no_elements + *start;
706- if (*start < 0) *start = 0;
707- if (*start > no_elements) *start = no_elements;
708-
709- if (*end < 0) *end = no_elements + *end;
710- if (*end < 0) *end = 0;
711- if (*end >= no_elements) *end = no_elements - 1;
712-
713- if (*end - *start >= 0)
714- return 1 + (*end - *start) / increment;
715- else
716- return 0;
717-}
718-
719 static db_field_log* filter(void* pvt, dbChannel *chan, db_field_log *pfl)
720 {
721 myStruct *my = (myStruct*) pvt;
722diff --git a/modules/database/test/std/rec/Makefile b/modules/database/test/std/rec/Makefile
723index e8c5464..71884b4 100644
724--- a/modules/database/test/std/rec/Makefile
725+++ b/modules/database/test/std/rec/Makefile
726@@ -15,6 +15,7 @@ USR_CPPFLAGS += -DUSE_TYPED_DSET
727
728 TESTLIBRARY = dbRecStdTest
729
730+dbRecStdTest_SRCS += arroutRecord.c
731 dbRecStdTest_SRCS += asTestLib.c
732 dbRecStdTest_LIBS += dbRecStd dbCore ca Com
733
734@@ -23,6 +24,7 @@ PROD_LIBS = dbRecStdTest dbRecStd dbCore ca Com
735 TARGETS += $(COMMON_DIR)/recTestIoc.dbd
736 DBDDEPENDS_FILES += recTestIoc.dbd$(DEP)
737 recTestIoc_DBD = base.dbd
738+recTestIoc_DBD += arroutRecord.dbd
739 TESTFILES += $(COMMON_DIR)/recTestIoc.dbd
740
741 testHarness_SRCS += recTestIoc_registerRecordDeviceDriver.cpp
742@@ -166,6 +168,13 @@ testHarness_SRCS += linkFilterTest.c
743 TESTFILES += ../linkFilterTest.db
744 TESTS += linkFilterTest
745
746+TESTPROD_HOST += addrModifierTest
747+addrModifierTest_SRCS += addrModifierTest.c
748+addrModifierTest_SRCS += recTestIoc_registerRecordDeviceDriver.cpp
749+testHarness_SRCS += addrModifierTest.c
750+TESTFILES += ../addrModifierTest.db
751+TESTS += addrModifierTest
752+
753 # dbHeader* is only a compile test
754 # no need to actually run
755 TESTPROD += dbHeaderTest
756@@ -199,5 +208,7 @@ endif
757
758 include $(TOP)/configure/RULES
759
760+arroutRecord$(DEP): $(COMMON_DIR)/arroutRecord.h
761+
762 rtemsTestData.c : $(TESTFILES) $(TOOLS)/epicsMakeMemFs.pl
763 $(PERL) $(TOOLS)/epicsMakeMemFs.pl $@ epicsRtemsFSImage $(TESTFILES)
764diff --git a/modules/database/test/std/rec/addrModifierTest.c b/modules/database/test/std/rec/addrModifierTest.c
765new file mode 100644
766index 0000000..d555b91
767--- /dev/null
768+++ b/modules/database/test/std/rec/addrModifierTest.c
769@@ -0,0 +1,247 @@
770+/*************************************************************************\
771+* Copyright (c) 2020 Dirk Zimoch
772+* EPICS BASE is distributed subject to a Software License Agreement found
773+* in file LICENSE that is included with this distribution.
774+\*************************************************************************/
775+
776+#include <string.h>
777+
778+#include "dbAccess.h"
779+#include "devSup.h"
780+#include "alarm.h"
781+#include "dbUnitTest.h"
782+#include "errlog.h"
783+#include "epicsThread.h"
784+
785+#include "longinRecord.h"
786+
787+#include "testMain.h"
788+
789+void recTestIoc_registerRecordDeviceDriver(struct dbBase *);
790+
791+static void startTestIoc(const char *dbfile)
792+{
793+ testdbPrepare();
794+ testdbReadDatabase("recTestIoc.dbd", NULL, NULL);
795+ recTestIoc_registerRecordDeviceDriver(pdbbase);
796+ testdbReadDatabase(dbfile, NULL, NULL);
797+
798+ eltc(0);
799+ testIocInitOk();
800+ eltc(1);
801+}
802+
803+struct pv {
804+ const char *name;
805+ long count;
806+ double values[4];
807+};
808+
809+static void expectProcSuccess(struct pv *pv)
810+{
811+ char fieldname[20];
812+ testdbPutFieldOk("reset.PROC", DBF_LONG, 1);
813+ testDiag("expecting success from %s", pv->name);
814+ sprintf(fieldname, "%s.PROC", pv->name);
815+ testdbPutFieldOk(fieldname, DBF_LONG, 1);
816+ sprintf(fieldname, "%s.SEVR", pv->name);
817+ testdbGetFieldEqual(fieldname, DBF_LONG, NO_ALARM);
818+ sprintf(fieldname, "%s.STAT", pv->name);
819+ testdbGetFieldEqual(fieldname, DBF_LONG, NO_ALARM);
820+}
821+
822+#if 0
823+static void expectProcFailure(struct pv *pv)
824+{
825+ char fieldname[20];
826+ testdbPutFieldOk("reset.PROC", DBF_LONG, 1);
827+ testDiag("expecting failure S_db_badField %#x from %s", S_db_badField, pv->name);
828+ sprintf(fieldname, "%s.PROC", pv->name);
829+ testdbPutFieldFail(S_db_badField, fieldname, DBF_LONG, 1);
830+ sprintf(fieldname, "%s.SEVR", pv->name);
831+ testdbGetFieldEqual(fieldname, DBF_LONG, INVALID_ALARM);
832+ sprintf(fieldname, "%s.STAT", pv->name);
833+ testdbGetFieldEqual(fieldname, DBF_LONG, LINK_ALARM);
834+}
835+#endif
836+
837+static double initial[] = {0,1,2,3,4,5,6,7,8,9};
838+static double buf[10];
839+
840+static void changeRange(struct pv *pv, long start, long incr, long end)
841+{
842+ char linkstring[60];
843+ char pvstring[10];
844+
845+ if (incr)
846+ sprintf(linkstring, "tgt.[%ld:%ld:%ld]", start, incr, end);
847+ else if (end)
848+ sprintf(linkstring, "tgt.[%ld:%ld]", start, end);
849+ else
850+ sprintf(linkstring, "tgt.[%ld]", start);
851+ testDiag("modifying %s.OUT link: %s", pv->name, linkstring);
852+ sprintf(pvstring, "%s.OUT", pv->name);
853+ testdbPutFieldOk(pvstring, DBF_STRING, linkstring);
854+}
855+
856+static void expectRange(double *values, long start, long incr, long end)
857+{
858+ int i,j;
859+ if (!incr)
860+ incr = 1;
861+ for (i=0; i<10; i++)
862+ buf[i] = initial[i];
863+ for (i=0, j=start; j<=end; i++, j+=incr)
864+ buf[j] = values[i];
865+ testdbGetFieldEqual("tgt.NORD", DBF_LONG, 8);
866+ testdbGetFieldEqual("tgt.SEVR", DBF_LONG, NO_ALARM);
867+ testdbGetFieldEqual("tgt.STAT", DBF_LONG, NO_ALARM);
868+ testdbGetArrFieldEqual("tgt.VAL", DBF_DOUBLE, 10, 8, buf);
869+}
870+
871+#if 0
872+static void expectEmptyArray(void)
873+{
874+ /* empty arrays are now allowed at the moment */
875+ testDiag("expecting empty array");
876+ testdbGetFieldEqual("tgt.NORD", DBF_LONG, 0);
877+}
878+#endif
879+
880+struct pv ao_pv = {"ao",1,{20,0,0,0}};
881+struct pv src_pv = {"src",4,{30,40,50,60}};
882+struct pv *ao = &ao_pv;
883+struct pv *src = &src_pv;
884+
885+static double pini_values[] = {20,30,40,50};
886+
887+#define expectEmptyRange() expectRange(0,0,0,-1)
888+
889+MAIN(addrModifierTest)
890+{
891+ testPlan(205);
892+ startTestIoc("addrModifierTest.db");
893+
894+ testdbGetFieldEqual("src.NORD", DBF_LONG, 4);
895+ testDiag("PINI");
896+ expectRange(pini_values,2,1,5);
897+
898+ testDiag("after processing");
899+ testdbPutFieldOk("ao.PROC", DBF_LONG, 1);
900+ testdbPutFieldOk("src.PROC", DBF_LONG, 1);
901+ expectRange(pini_values,2,1,5);
902+
903+ testDiag("after processing target record");
904+ testdbPutFieldOk("tgt.PROC", DBF_LONG, 1);
905+ expectRange(pini_values,2,1,5);
906+
907+ testDiag("modify range");
908+
909+ changeRange(ao,3,0,0);
910+ expectProcSuccess(ao);
911+ expectRange(ao->values,3,1,3);
912+
913+ changeRange(src,4,0,7);
914+ expectProcSuccess(src);
915+ expectRange(src->values,4,1,7);
916+
917+ testDiag("put more than available");
918+
919+ changeRange(ao,3,0,6);
920+ expectProcSuccess(ao);
921+ expectRange(ao->values,3,1,3); /* clipped range */
922+
923+ changeRange(src,3,0,9);
924+ expectProcSuccess(src);
925+ expectRange(src->values,3,1,6); /* clipped range */
926+
927+ testDiag("backward range");
928+
929+ changeRange(ao,5,0,3);
930+ expectProcSuccess(ao);
931+ expectEmptyRange();
932+
933+ changeRange(src,5,0,3);
934+ expectProcSuccess(src);
935+ expectEmptyRange();
936+
937+ testDiag("step 2");
938+
939+ changeRange(ao,1,2,6);
940+ expectProcSuccess(ao);
941+ expectRange(ao->values,1,1,1); /* clipped range */
942+
943+ changeRange(src,1,2,6);
944+ expectProcSuccess(src);
945+ expectRange(src->values,1,2,5);
946+
947+ testDiag("range start beyond tgt.NORD");
948+
949+ changeRange(ao,8,0,0);
950+ expectProcSuccess(ao);
951+ expectEmptyRange();
952+
953+ changeRange(src,8,0,9);
954+ expectProcSuccess(src);
955+ expectEmptyRange();
956+
957+ testDiag("range end beyond tgt.NORD");
958+
959+ changeRange(ao,3,0,9);
960+ expectProcSuccess(ao);
961+ expectRange(ao->values,3,1,3); /* clipped range */
962+
963+ changeRange(src,3,0,9);
964+ expectProcSuccess(src);
965+ expectRange(src->values,3,1,6); /* clipped range */
966+
967+ testDiag("range start beyond tgt.NELM");
968+
969+ changeRange(ao,11,0,12);
970+ expectProcSuccess(ao);
971+ expectEmptyRange();
972+
973+ changeRange(src,11,0,12);
974+ expectProcSuccess(src);
975+ expectEmptyRange();
976+
977+ testDiag("range end beyond tgt.NELM");
978+
979+ changeRange(src,4,0,12);
980+ expectProcSuccess(src);
981+ expectRange(src->values,4,1,7); /* clipped range */
982+
983+ testDiag("single value beyond tgt.NORD");
984+
985+ changeRange(ao,8,0,0);
986+ expectProcSuccess(ao);
987+ expectEmptyRange();
988+
989+ changeRange(src,8,0,0);
990+ expectProcSuccess(src);
991+ expectEmptyRange();
992+
993+ testDiag("single value");
994+
995+ changeRange(ao,5,0,0);
996+ expectProcSuccess(ao);
997+ expectRange(ao->values,5,1,5);
998+
999+ changeRange(src,5,0,0);
1000+ expectProcSuccess(src);
1001+ expectRange(src->values,5,1,5);
1002+
1003+ testDiag("single values beyond tgt.NELM");
1004+
1005+ changeRange(ao,12,0,0);
1006+ expectProcSuccess(ao);
1007+ expectEmptyRange();
1008+
1009+ changeRange(src,12,0,0);
1010+ expectProcSuccess(src);
1011+ expectEmptyRange();
1012+
1013+ testIocShutdownOk();
1014+ testdbCleanup();
1015+ return testDone();
1016+}
1017diff --git a/modules/database/test/std/rec/addrModifierTest.db b/modules/database/test/std/rec/addrModifierTest.db
1018new file mode 100644
1019index 0000000..ed28a80
1020--- /dev/null
1021+++ b/modules/database/test/std/rec/addrModifierTest.db
1022@@ -0,0 +1,24 @@
1023+record(arrout, "tgt") {
1024+ field(NELM, "10")
1025+ field(FTVL, "SHORT")
1026+ field(DOL, [0, 1, 2, 3, 4, 5, 6, 7])
1027+ field(PINI, "YES")
1028+}
1029+record(ao, "ao") {
1030+ field(OUT, "tgt.[2]")
1031+ field(DOL, 20)
1032+ field(PINI, "YES")
1033+}
1034+record(arrout, "src") {
1035+ field(NELM, "5")
1036+ field(FTVL, "DOUBLE")
1037+ field(DOL, [30.0, 40.0, 50.0, 60.0])
1038+ field(OUT, "tgt.[3:5]")
1039+ field(PINI, "YES")
1040+}
1041+record(arrout, "reset") {
1042+ field(NELM, "10")
1043+ field(FTVL, "SHORT")
1044+ field(DOL, [0, 1, 2, 3, 4, 5, 6, 7])
1045+ field(OUT, "tgt PP")
1046+}
1047diff --git a/modules/database/test/std/rec/arroutRecord.c b/modules/database/test/std/rec/arroutRecord.c
1048new file mode 100644
1049index 0000000..61c7680
1050--- /dev/null
1051+++ b/modules/database/test/std/rec/arroutRecord.c
1052@@ -0,0 +1,169 @@
1053+/*************************************************************************\
1054+* Copyright (c) 2010 Brookhaven National Laboratory.
1055+* Copyright (c) 2010 Helmholtz-Zentrum Berlin
1056+* fuer Materialien und Energie GmbH.
1057+* Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne
1058+* National Laboratory.
1059+* Copyright (c) 2002 The Regents of the University of California, as
1060+* Operator of Los Alamos National Laboratory.
1061+* EPICS BASE is distributed subject to a Software License Agreement found
1062+* in file LICENSE that is included with this distribution.
1063+\*************************************************************************/
1064+
1065+/* arroutRecord.c - minimal array output record for test purposes: no processing */
1066+
1067+/* adapted from: arrRecord.c
1068+ *
1069+ * Author: Ralph Lange <Ralph.Lange@bessy.de>
1070+ *
1071+ * vaguely implemented like parts of recWaveform.c by Bob Dalesio
1072+ *
1073+ */
1074+
1075+#include <stdio.h>
1076+
1077+#include "alarm.h"
1078+#include "epicsPrint.h"
1079+#include "dbAccess.h"
1080+#include "dbEvent.h"
1081+#include "dbFldTypes.h"
1082+#include "recSup.h"
1083+#include "recGbl.h"
1084+#include "cantProceed.h"
1085+#define GEN_SIZE_OFFSET
1086+#include "arroutRecord.h"
1087+#undef GEN_SIZE_OFFSET
1088+#include "epicsExport.h"
1089+
1090+/* Create RSET - Record Support Entry Table*/
1091+#define report NULL
1092+#define initialize NULL
1093+static long init_record(struct dbCommon *, int);
1094+static long process(struct dbCommon *);
1095+#define special NULL
1096+#define get_value NULL
1097+static long cvt_dbaddr(DBADDR *);
1098+static long get_array_info(DBADDR *, long *, long *);
1099+static long put_array_info(DBADDR *, long);
1100+#define get_units NULL
1101+#define get_precision NULL
1102+#define get_enum_str NULL
1103+#define get_enum_strs NULL
1104+#define put_enum_str NULL
1105+#define get_graphic_double NULL
1106+#define get_control_double NULL
1107+#define get_alarm_double NULL
1108+
1109+rset arroutRSET = {
1110+ RSETNUMBER,
1111+ report,
1112+ initialize,
1113+ init_record,
1114+ process,
1115+ special,
1116+ get_value,
1117+ cvt_dbaddr,
1118+ get_array_info,
1119+ put_array_info,
1120+ get_units,
1121+ get_precision,
1122+ get_enum_str,
1123+ get_enum_strs,
1124+ put_enum_str,
1125+ get_graphic_double,
1126+ get_control_double,
1127+ get_alarm_double
1128+};
1129+epicsExportAddress(rset, arroutRSET);
1130+
1131+static long init_record(struct dbCommon *pcommon, int pass)
1132+{
1133+ struct arroutRecord *prec = (struct arroutRecord *)pcommon;
1134+
1135+ if (pass == 0) {
1136+ if (prec->nelm <= 0)
1137+ prec->nelm = 1;
1138+ if (prec->ftvl > DBF_ENUM)
1139+ prec->ftvl = DBF_UCHAR;
1140+ prec->val = callocMustSucceed(prec->nelm, dbValueSize(prec->ftvl),
1141+ "arr calloc failed");
1142+ return 0;
1143+ } else {
1144+ /* copied from devWfSoft.c */
1145+ long nelm = prec->nelm;
1146+ long status = dbLoadLinkArray(&prec->dol, prec->ftvl, prec->val, &nelm);
1147+
1148+ if (!status && nelm > 0) {
1149+ prec->nord = nelm;
1150+ prec->udf = FALSE;
1151+ }
1152+ else
1153+ prec->nord = 0;
1154+ return status;
1155+ }
1156+}
1157+
1158+static long process(struct dbCommon *pcommon)
1159+{
1160+ struct arroutRecord *prec = (struct arroutRecord *)pcommon;
1161+ long status = 0;
1162+
1163+ prec->pact = TRUE;
1164+ /* read DOL */
1165+ if (!dbLinkIsConstant(&prec->dol)) {
1166+ long nReq = prec->nelm;
1167+
1168+ status = dbGetLink(&prec->dol, prec->ftvl, prec->val, 0, &nReq);
1169+ if (status) {
1170+ recGblSetSevr(prec, LINK_ALARM, INVALID_ALARM);
1171+ } else {
1172+ prec->nord = nReq;
1173+ }
1174+ }
1175+ /* soft "device support": write OUT */
1176+ status = dbPutLink(&prec->out, prec->ftvl, prec->val, prec->nord);
1177+ if (status) {
1178+ recGblSetSevr(prec, LINK_ALARM, INVALID_ALARM);
1179+ }
1180+ recGblGetTimeStamp(prec);
1181+ recGblResetAlarms(prec);
1182+ recGblFwdLink(prec);
1183+ prec->pact = FALSE;
1184+ return status;
1185+}
1186+
1187+static long cvt_dbaddr(DBADDR *paddr)
1188+{
1189+ arroutRecord *prec = (arroutRecord *) paddr->precord;
1190+ int fieldIndex = dbGetFieldIndex(paddr);
1191+
1192+ if (fieldIndex == arroutRecordVAL) {
1193+ paddr->pfield = prec->val;
1194+ paddr->no_elements = prec->nelm;
1195+ paddr->field_type = prec->ftvl;
1196+ paddr->field_size = dbValueSize(prec->ftvl);
1197+ paddr->dbr_field_type = prec->ftvl;
1198+ }
1199+ return 0;
1200+}
1201+
1202+static long get_array_info(DBADDR *paddr, long *no_elements, long *offset)
1203+{
1204+ arroutRecord *prec = (arroutRecord *) paddr->precord;
1205+
1206+ *no_elements = prec->nord;
1207+ *offset = prec->off;
1208+
1209+ return 0;
1210+}
1211+
1212+static long put_array_info(DBADDR *paddr, long nNew)
1213+{
1214+ arroutRecord *prec = (arroutRecord *) paddr->precord;
1215+
1216+ prec->nord = nNew;
1217+ if (prec->nord > prec->nelm)
1218+ prec->nord = prec->nelm;
1219+
1220+ return 0;
1221+}
1222diff --git a/modules/database/test/std/rec/arroutRecord.dbd b/modules/database/test/std/rec/arroutRecord.dbd
1223new file mode 100644
1224index 0000000..2c99e64
1225--- /dev/null
1226+++ b/modules/database/test/std/rec/arroutRecord.dbd
1227@@ -0,0 +1,35 @@
1228+include "menuGlobal.dbd"
1229+include "menuConvert.dbd"
1230+include "menuScan.dbd"
1231+recordtype(arrout) {
1232+ include "dbCommon.dbd"
1233+ field(VAL, DBF_NOACCESS) {
1234+ prompt("Value")
1235+ special(SPC_DBADDR)
1236+ pp(TRUE)
1237+ extra("void *val")
1238+ }
1239+ field(OUT, DBF_OUTLINK) {
1240+ prompt("Output Link")
1241+ }
1242+ field(NELM, DBF_ULONG) {
1243+ prompt("Number of Elements")
1244+ special(SPC_NOMOD)
1245+ initial("1")
1246+ }
1247+ field(FTVL, DBF_MENU) {
1248+ prompt("Field Type of Value")
1249+ special(SPC_NOMOD)
1250+ menu(menuFtype)
1251+ }
1252+ field(NORD, DBF_ULONG) {
1253+ prompt("Number elements read")
1254+ special(SPC_NOMOD)
1255+ }
1256+ field(OFF, DBF_ULONG) {
1257+ prompt("Offset into array")
1258+ }
1259+ field(DOL, DBF_INLINK) {
1260+ prompt("Desired Output Link")
1261+ }
1262+}

Subscribers

People subscribed via source and target branches