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

Proposed by Ben Franksen
Status: Superseded
Proposed branch: ~bfrk/epics-base:address-modifiers
Merge into: ~epics-core/epics-base/+git/epics-base:7.0
Prerequisite: ~bfrk/epics-base:address-modifiers-prerequisites
Diff against target: 1769 lines (+857/-306)
21 files modified
documentation/RELEASE_NOTES.md (+0/-9)
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 (+39/-57)
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 (+25/-119)
modules/database/src/ioc/db/dbChannel.h (+3/-23)
modules/database/src/ioc/db/dbDbLink.c (+3/-26)
modules/database/src/ioc/db/dbEvent.h (+0/-1)
modules/database/src/ioc/db/dbUnitTest.c (+2/-2)
modules/database/src/ioc/db/db_field_log.h (+0/-3)
modules/database/src/ioc/dbStatic/link.h (+1/-7)
modules/database/src/std/filters/arr.c (+1/-24)
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)
modules/database/test/std/rec/linkFilterTest.c (+0/-35)
Reviewer Review Type Date Requested Status
EPICS Core Developers Pending
Review via email: mp+381628@code.launchpad.net

This proposal has been superseded by a proposal from 2021-04-01.

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 :

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 :
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...

~bfrk/epics-base:address-modifiers updated
8f4ed7d... by Ben Franksen

factor out dbHandleModifier from dbPutModifier

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

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.

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

@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.

~bfrk/epics-base:address-modifiers updated
6aaf531... by Ben Franksen

fix API docs in arrayRangeModifier.h

aaf6163... by Ben Franksen

move array range parsing to arrayRangeModifier module

8ca85b3... by Ben Franksen

fix (justified) warnings in dbUnitTest.c

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/documentation/RELEASE_NOTES.md b/documentation/RELEASE_NOTES.md
2index 6129a4c..49cdd74 100644
3--- a/documentation/RELEASE_NOTES.md
4+++ b/documentation/RELEASE_NOTES.md
5@@ -553,15 +553,6 @@ As long as all support modules and IOCs are rebuilt from source after updating
6 them to use this release of EPICS Base, these changes should not have any
7 affect.
8
9-### Filters in database links
10-
11-Input links can now use filters, most importantly array element and sub array
12-access, even if they are not channel access links.
13-
14-### ai Soft Channel support
15-
16-The Soft Channel device support for ai records now returns failure when
17-fetching the INP link fails.
18
19 ### logClient reliability
20
21diff --git a/modules/database/src/ioc/db/Makefile b/modules/database/src/ioc/db/Makefile
22index ebb78e2..e590cc6 100644
23--- a/modules/database/src/ioc/db/Makefile
24+++ b/modules/database/src/ioc/db/Makefile
25@@ -11,10 +11,12 @@
26
27 SRC_DIRS += $(IOCDIR)/db
28
29+INC += arrayRangeModifier.h
30 INC += callback.h
31 INC += dbAccess.h
32 INC += dbAccessDefs.h
33 INC += dbAddr.h
34+INC += dbAddrModifier.h
35 INC += dbBkpt.h
36 INC += dbCa.h
37 INC += dbChannel.h
38@@ -66,6 +68,7 @@ HTMLS += dbCommonRecord.html
39 HTMLS += dbCommonInput.html
40 HTMLS += dbCommonOutput.html
41
42+dbCore_SRCS += arrayRangeModifier.c
43 dbCore_SRCS += dbLock.c
44 dbCore_SRCS += dbAccess.c
45 dbCore_SRCS += dbBkpt.c
46diff --git a/modules/database/src/ioc/db/arrayRangeModifier.c b/modules/database/src/ioc/db/arrayRangeModifier.c
47new file mode 100644
48index 0000000..1feb7bc
49--- /dev/null
50+++ b/modules/database/src/ioc/db/arrayRangeModifier.c
51@@ -0,0 +1,167 @@
52+/*************************************************************************\
53+* EPICS BASE is distributed subject to a Software License Agreement found
54+* in file LICENSE that is included with this distribution.
55+\*************************************************************************/
56+
57+#include <assert.h>
58+#include <stdlib.h>
59+#include <stdio.h>
60+
61+#include "arrayRangeModifier.h"
62+#include "dbAccessDefs.h"
63+#include "dbConvertFast.h"
64+#include "dbConvert.h"
65+
66+typedef struct {
67+ long start;
68+ long incr;
69+ long end;
70+} arrayRangeModifier;
71+
72+long wrapArrayIndices(long *start, const long increment,
73+ long *end, const long no_elements)
74+{
75+ if (*start < 0) *start = no_elements + *start;
76+ if (*start < 0) *start = 0;
77+ if (*start > no_elements) *start = no_elements;
78+
79+ if (*end < 0) *end = no_elements + *end;
80+ if (*end < 0) *end = 0;
81+ if (*end >= no_elements) *end = no_elements - 1;
82+
83+ if (*end - *start >= 0)
84+ return 1 + (*end - *start) / increment;
85+ else
86+ return 0;
87+}
88+
89+static long handleArrayRangeModifier(DBADDR *paddr, short dbrType, const void
90+ *pbuffer, long nRequest, void *pvt, long offset, long available)
91+{
92+ arrayRangeModifier *pmod = (arrayRangeModifier *)pvt;
93+ long status = 0;
94+ long start = pmod->start;
95+ long end = pmod->end;
96+ /* Note that this limits the return value to be <= available */
97+ long n = wrapArrayIndices(&start, pmod->incr, &end, available);
98+
99+ assert(pmod->incr > 0);
100+ if (pmod->incr > 1) {
101+ long i, j;
102+ long (*putCvt) (const void *from, void *to, const dbAddr * paddr) =
103+ dbFastPutConvertRoutine[dbrType][paddr->field_type];
104+ short dbr_size = dbValueSize(dbrType);
105+ if (nRequest > n)
106+ nRequest = n;
107+ for (i = 0, j = (offset + start) % paddr->no_elements; i < nRequest;
108+ i++, j = (j + pmod->incr) % paddr->no_elements) {
109+ status = putCvt(pbuffer + (i * dbr_size),
110+ paddr->pfield + (j * paddr->field_size), paddr);
111+ }
112+ } else {
113+ offset = (offset + start) % paddr->no_elements;
114+ if (nRequest > n)
115+ nRequest = n;
116+ if (paddr->no_elements <= 1) {
117+ status = dbFastPutConvertRoutine[dbrType][paddr->field_type] (pbuffer,
118+ paddr->pfield, paddr);
119+ } else {
120+ if (paddr->no_elements < nRequest)
121+ nRequest = paddr->no_elements;
122+ status = dbPutConvertRoutine[dbrType][paddr->field_type] (paddr,
123+ pbuffer, nRequest, paddr->no_elements, offset);
124+ }
125+ }
126+ return status;
127+}
128+
129+long createArrayRangeModifier(dbAddrModifier *pmod, long start, long incr, long end)
130+{
131+ arrayRangeModifier *pvt =
132+ (arrayRangeModifier *) malloc(sizeof(arrayRangeModifier));
133+ if (incr <= 0) {
134+ return S_db_errArg;
135+ }
136+ if (!pvt) {
137+ return S_db_noMemory;
138+ }
139+ pvt->start = start;
140+ pvt->incr = incr;
141+ pvt->end = end;
142+ pmod->pvt = pvt;
143+ pmod->handle = handleArrayRangeModifier;
144+ return 0;
145+}
146+
147+void deleteArrayRangeModifier(dbAddrModifier *pmod)
148+{
149+ if (pmod->pvt)
150+ free(pmod->pvt);
151+}
152+
153+long parseArrayRange(dbAddrModifier *pmod, const char **pstring)
154+{
155+ long start = 0;
156+ long end = -1;
157+ long incr = 1;
158+ long tmp;
159+ const char *pcurrent = *pstring;
160+ char *pnext;
161+ ptrdiff_t advance;
162+ long status = 0;
163+
164+ if (*pcurrent != '[') {
165+ return -1;
166+ }
167+ pcurrent++;
168+ /* If no number is present, strtol() returns 0 and sets pnext=pcurrent,
169+ else pnext points to the first char after the number */
170+ tmp = strtol(pcurrent, &pnext, 0);
171+ advance = pnext - pcurrent;
172+ if (advance) start = tmp;
173+ pcurrent = pnext;
174+ if (*pcurrent == ']' && advance) {
175+ end = start;
176+ goto done;
177+ }
178+ if (*pcurrent != ':') {
179+ return -1;
180+ }
181+ pcurrent++;
182+ tmp = strtol(pcurrent, &pnext, 0);
183+ advance = pnext - pcurrent;
184+ pcurrent = pnext;
185+ if (*pcurrent == ']') {
186+ if (advance) end = tmp;
187+ goto done;
188+ }
189+ if (advance) incr = tmp;
190+ if (*pcurrent != ':') {
191+ return -1;
192+ }
193+ pcurrent++;
194+ tmp = strtol(pcurrent, &pnext, 0);
195+ advance = pnext - pcurrent;
196+ if (advance) end = tmp;
197+ pcurrent = pnext;
198+ if (*pcurrent != ']') {
199+ return -1;
200+ }
201+
202+done:
203+ status = createArrayRangeModifier(pmod, start, incr, end);
204+ if (status) {
205+ return status;
206+ }
207+ pcurrent++;
208+ *pstring = pcurrent;
209+ return 0;
210+}
211+
212+void getArrayRange(dbAddrModifier *pmod, long *pstart, long *pincr, long *pend)
213+{
214+ arrayRangeModifier *pvt = (arrayRangeModifier *)pmod->pvt;
215+ *pstart = pvt->start;
216+ *pincr = pvt->incr;
217+ *pend = pvt->end;
218+}
219diff --git a/modules/database/src/ioc/db/arrayRangeModifier.h b/modules/database/src/ioc/db/arrayRangeModifier.h
220new file mode 100644
221index 0000000..8e0cd2f
222--- /dev/null
223+++ b/modules/database/src/ioc/db/arrayRangeModifier.h
224@@ -0,0 +1,69 @@
225+/*************************************************************************\
226+* EPICS BASE is distributed subject to a Software License Agreement found
227+* in file LICENSE that is included with this distribution.
228+\*************************************************************************/
229+#ifndef ARRAYRANGEMODIFIER_H
230+#define ARRAYRANGEMODIFIER_H
231+
232+#include "dbAddrModifier.h"
233+#include "shareLib.h"
234+
235+/** @brief The array range address modifier. */
236+
237+/** @brief Given a number of elements, convert negative start and end indices
238+ * to non-negative ones by counting from the end of the array.
239+ *
240+ * @param start Pointer to possibly negative start index
241+ * @param increment Increment
242+ * @param end Pointer to possibly negative end index
243+ * @param no_elements Number of array elements
244+ * @return Final number of elements
245+ */
246+epicsShareFunc
247+long wrapArrayIndices(long *start, long increment, long *end, long no_elements);
248+
249+/** @brief Create an array range modifier from start index, increment, and end index
250+ *
251+ * @param pmod Pointer to address modifier (user allocated)
252+ * @param start Start index (possibly negative)
253+ * @param incr Increment (must be positive)
254+ * @param end End index (possibly negative)
255+ * @return Status
256+ */
257+epicsShareFunc
258+long createArrayRangeModifier(dbAddrModifier *pmod, long start, long incr, long end);
259+
260+/** @brief Alternative creation function that parses the data from a string.
261+ *
262+ * If successful, advances 'pstring' to point after the recognized address modifier.
263+ *
264+ * @param pmod Pointer to uninitialized address modifier (user allocated)
265+ * @param pstring Pointer to string to parse (in/out)
266+ * @return 0 (success) or -1 (failure)
267+ */
268+epicsShareFunc
269+long parseArrayRange(dbAddrModifier *pmod, const char **pstring);
270+
271+/** @brief Extract the private data
272+ *
273+ * The 'pmod' argument must point to a valid array range address modifier.
274+ * The other aruments must point to appropriately sized storage.
275+ *
276+ * @param pmod Pointer to uninitialized address modifier (user allocated)
277+ * @param pstart Start index (out)
278+ * @param pincr Increment (out)
279+ * @param pend End index (out)
280+ */
281+epicsShareFunc
282+void getArrayRange(dbAddrModifier *pmod, long *pstart, long *pincr, long *pend);
283+
284+/** @brief Free private memory associated with an array range modifier
285+ *
286+ * Note this does not free 'pmod' which is always user allocated.
287+ *
288+ * @param pmod Pointer to address modifier (user allocated)
289+ */
290+epicsShareFunc
291+void deleteArrayRangeModifier(dbAddrModifier *pmod);
292+
293+#endif /* ARRAYRANGEMODIFIER_H */
294diff --git a/modules/database/src/ioc/db/dbAccess.c b/modules/database/src/ioc/db/dbAccess.c
295index 539e35f..52169c0 100644
296--- a/modules/database/src/ioc/db/dbAccess.c
297+++ b/modules/database/src/ioc/db/dbAccess.c
298@@ -40,6 +40,7 @@
299 #include "callback.h"
300 #include "dbAccessDefs.h"
301 #include "dbAddr.h"
302+#include "dbAddrModifier.h"
303 #include "dbBase.h"
304 #include "dbBkpt.h"
305 #include "dbCommonPvt.h"
306@@ -375,7 +376,6 @@ static void getOptions(DBADDR *paddr, char **poriginal, long *options,
307 if((field_type==DBF_FLOAT || field_type==DBF_DOUBLE)
308 && prset && prset->get_precision ){
309 (*prset->get_precision)(paddr,(long *)pbuffer);
310-<<<<<<< modules/database/src/ioc/db/dbAccess.c
311 } else {
312 *options ^= DBR_PRECISION; /*Turn off DBR_PRECISION*/
313 }
314@@ -383,15 +383,6 @@ static void getOptions(DBADDR *paddr, char **poriginal, long *options,
315 }
316 if( (*options) & DBR_TIME ) {
317 epicsUInt32 *ptime = (epicsUInt32 *)pbuffer;
318-=======
319- } else {
320- *options ^= DBR_PRECISION; /*Turn off DBR_PRECISION*/
321- }
322- pbuffer += dbr_precision_size;
323- }
324- if( (*options) & DBR_TIME ) {
325- epicsUInt32 *ptime = (epicsUInt32 *)pbuffer;
326->>>>>>> modules/database/src/ioc/db/dbAccess.c
327
328 if (!pfl) {
329 *ptime++ = pcommon->time.secPastEpoch;
330@@ -906,20 +897,13 @@ long dbGet(DBADDR *paddr, short dbrType,
331 db_field_log *pfl = (db_field_log *)pflin;
332 short field_type;
333 long capacity, no_elements, offset;
334- int is_scalar_request = !nRequest;
335- long n_scalar_request = 1;
336 rset *prset;
337 long status = 0;
338
339 if (options && *options)
340 getOptions(paddr, &pbuf, options, pflin);
341-
342- if (is_scalar_request)
343- nRequest = &n_scalar_request;
344- else if (*nRequest == 0)
345- /* trivially succeed for zero length array request */
346+ if (nRequest && *nRequest == 0)
347 return 0;
348- assert(nRequest && *nRequest > 0);
349
350 if (!pfl) {
351 field_type = paddr->field_type;
352@@ -961,7 +945,6 @@ long dbGet(DBADDR *paddr, short dbrType,
353 goto done;
354 }
355
356-<<<<<<< modules/database/src/ioc/db/dbAccess.c
357 if (offset == 0 && (!nRequest || no_elements == 1)) {
358 if (nRequest)
359 *nRequest = 1;
360@@ -971,17 +954,6 @@ long dbGet(DBADDR *paddr, short dbrType,
361 }
362
363 if (!dbfl_has_copy(pfl)) {
364-=======
365- /* A scalar request that cannot be met is an error. */
366- if (is_scalar_request && no_elements < 1) {
367- status = S_db_badField;
368- goto done;
369- }
370-
371- if (offset == 0 && (is_scalar_request || no_elements == 1)) {
372- *nRequest = 1;
373- if (!pfl) {
374->>>>>>> modules/database/src/ioc/db/dbAccess.c
375 status = dbFastGetConvertRoutine[field_type][dbrType]
376 (paddr->pfield, pbuf, paddr);
377 } else {
378@@ -1004,7 +976,6 @@ long dbGet(DBADDR *paddr, short dbrType,
379 long n;
380 GETCONVERTFUNC convert;
381
382-<<<<<<< modules/database/src/ioc/db/dbAccess.c
383 if (nRequest) {
384 if (no_elements < *nRequest)
385 *nRequest = no_elements;
386@@ -1014,11 +985,6 @@ long dbGet(DBADDR *paddr, short dbrType,
387 } else {
388 n = 1;
389 }
390-=======
391- if (no_elements < *nRequest)
392- *nRequest = no_elements;
393- n = *nRequest;
394->>>>>>> modules/database/src/ioc/db/dbAccess.c
395 convert = dbGetConvertRoutine[field_type][dbrType];
396 if (!convert) {
397 char message[80];
398@@ -1032,11 +998,7 @@ long dbGet(DBADDR *paddr, short dbrType,
399 /* convert data into the caller's buffer */
400 if (n <= 0) {
401 ; /*do nothing */
402-<<<<<<< modules/database/src/ioc/db/dbAccess.c
403 } else if (!dbfl_has_copy(pfl)) {
404-=======
405- } else if (!pfl) {
406->>>>>>> modules/database/src/ioc/db/dbAccess.c
407 status = convert(paddr, pbuf, n, capacity, offset);
408 } else {
409 DBADDR localAddr = *paddr; /* Structure copy */
410@@ -1054,10 +1016,10 @@ long dbGet(DBADDR *paddr, short dbrType,
411 status = convert(&localAddr, pbuf, n, capacity, offset);
412 }
413
414- if(!status && dbrType==DBF_CHAR && !is_scalar_request &&
415+ if(!status && dbrType==DBF_CHAR && nRequest &&
416 paddr->pfldDes && paddr->pfldDes->field_type==DBF_STRING)
417 {
418- /* long string: ensure termination and truncate to actual length */
419+ /* long string ensure nil and truncate to actual length */
420 long nReq = *nRequest;
421 pbuf[nReq-1] = '\0';
422 *nRequest = strlen(pbuf)+1;
423@@ -1264,6 +1226,12 @@ cleanup:
424 long dbPutField(DBADDR *paddr, short dbrType,
425 const void *pbuffer, long nRequest)
426 {
427+ return dbPutFieldModifier(paddr, dbrType, pbuffer, nRequest, NULL);
428+}
429+
430+long dbPutFieldModifier(DBADDR *paddr, short dbrType,
431+ const void *pbuffer, long nRequest, dbAddrModifier *pmod)
432+{
433 long status = 0;
434 long special = paddr->special;
435 dbFldDes *pfldDes = paddr->pfldDes;
436@@ -1281,7 +1249,7 @@ long dbPutField(DBADDR *paddr, short dbrType,
437 return dbPutFieldLink(paddr, dbrType, pbuffer, nRequest);
438
439 dbScanLock(precord);
440- status = dbPut(paddr, dbrType, pbuffer, nRequest);
441+ status = dbPutModifier(paddr, dbrType, pbuffer, nRequest, pmod);
442 if (status == 0) {
443 if (paddr->pfield == &precord->proc ||
444 (pfldDes->process_passive &&
445@@ -1335,8 +1303,13 @@ static long putAcks(DBADDR *paddr, const void *pbuffer, long nRequest,
446 return 0;
447 }
448
449-long dbPut(DBADDR *paddr, short dbrType,
450- const void *pbuffer, long nRequest)
451+long dbPut(DBADDR *paddr, short dbrType, const void *pbuffer, long nRequest)
452+{
453+ return dbPutModifier(paddr, dbrType, pbuffer, nRequest, NULL);
454+}
455+
456+long dbPutModifier(DBADDR *paddr, short dbrType,
457+ const void *pbuffer, long nRequest, dbAddrModifier *pmod)
458 {
459 dbCommon *precord = paddr->precord;
460 short field_type = paddr->field_type;
461@@ -1345,6 +1318,7 @@ long dbPut(DBADDR *paddr, short dbrType,
462 void *pfieldsave = paddr->pfield;
463 rset *prset = dbGetRset(paddr);
464 long status = 0;
465+ long available_no_elements = no_elements;
466 long offset;
467 dbFldDes *pfldDes;
468 int isValueField;
469@@ -1371,25 +1345,33 @@ long dbPut(DBADDR *paddr, short dbrType,
470
471 if (paddr->pfldDes->special == SPC_DBADDR &&
472 prset && prset->get_array_info) {
473- long dummy;
474
475- status = prset->get_array_info(paddr, &dummy, &offset);
476+ status = prset->get_array_info(paddr, &available_no_elements, &offset);
477 /* paddr->pfield may be modified */
478 if (status) goto done;
479- if (no_elements < nRequest)
480- nRequest = no_elements;
481- status = dbPutConvertRoutine[dbrType][field_type](paddr, pbuffer,
482- nRequest, no_elements, offset);
483- /* update array info */
484- if (!status && prset->put_array_info)
485- status = prset->put_array_info(paddr, nRequest);
486+ } else
487+ offset = 0;
488+
489+ if (pmod && pmod->handle && pmod->pvt) {
490+ status = pmod->handle(paddr, dbrType, pbuffer, nRequest,
491+ pmod->pvt, offset, available_no_elements);
492 } else {
493- if (nRequest < 1) {
494- recGblSetSevr(precord, LINK_ALARM, INVALID_ALARM);
495- } else {
496+ if (no_elements <= 1) {
497 status = dbFastPutConvertRoutine[dbrType][field_type](pbuffer,
498 paddr->pfield, paddr);
499 nRequest = 1;
500+ } else {
501+ if (no_elements < nRequest)
502+ nRequest = no_elements;
503+ status = dbPutConvertRoutine[dbrType][field_type](paddr, pbuffer,
504+ nRequest, no_elements, offset);
505+ }
506+
507+ /* update array info unless writing failed */
508+ if (!status &&
509+ paddr->pfldDes->special == SPC_DBADDR &&
510+ prset && prset->put_array_info) {
511+ status = prset->put_array_info(paddr, nRequest);
512 }
513 }
514
515diff --git a/modules/database/src/ioc/db/dbAccessDefs.h b/modules/database/src/ioc/db/dbAccessDefs.h
516index f2cf091..45e9f6e 100644
517--- a/modules/database/src/ioc/db/dbAccessDefs.h
518+++ b/modules/database/src/ioc/db/dbAccessDefs.h
519@@ -27,7 +27,9 @@
520
521 #include "dbBase.h"
522 #include "dbAddr.h"
523+#include "dbAddrModifier.h"
524 #include "recSup.h"
525+#include "db_field_log.h"
526
527 #ifdef __cplusplus
528 extern "C" {
529@@ -247,11 +249,19 @@ epicsShareFunc long dbGetField(
530 epicsShareFunc long dbGet(
531 struct dbAddr *,short dbrType,void *pbuffer,long *options,
532 long *nRequest,void *pfl);
533+
534 epicsShareFunc long dbPutField(
535 struct dbAddr *,short dbrType,const void *pbuffer,long nRequest);
536 epicsShareFunc long dbPut(
537 struct dbAddr *,short dbrType,const void *pbuffer,long nRequest);
538
539+epicsShareFunc long dbPutFieldModifier(
540+ struct dbAddr *,short dbrType,const void *pbuffer,long nRequest,
541+ dbAddrModifier *pmod);
542+epicsShareFunc long dbPutModifier(
543+ struct dbAddr *,short dbrType,const void *pbuffer,long nRequest,
544+ dbAddrModifier *pmod);
545+
546 typedef void(*SPC_ASCALLBACK)(struct dbCommon *);
547 /*dbSpcAsRegisterCallback called by access security */
548 epicsShareFunc void dbSpcAsRegisterCallback(SPC_ASCALLBACK func);
549diff --git a/modules/database/src/ioc/db/dbAddrModifier.h b/modules/database/src/ioc/db/dbAddrModifier.h
550new file mode 100644
551index 0000000..dcc3c86
552--- /dev/null
553+++ b/modules/database/src/ioc/db/dbAddrModifier.h
554@@ -0,0 +1,48 @@
555+/*************************************************************************\
556+* EPICS BASE is distributed subject to a Software License Agreement found
557+* in file LICENSE that is included with this distribution.
558+\*************************************************************************/
559+#ifndef DBADDRMODIFIER_H
560+#define DBADDRMODIFIER_H
561+
562+#include "dbAddr.h"
563+
564+/** @brief Generic API for address modifiers. */
565+
566+/** @brief Type of functions that handle an address modifier.
567+ *
568+ * This function should write the value in 'pbuffer' to the target 'pAddr',
569+ * according to the number of requested elements 'nRequest', the requested type
570+ * 'dbrType', and the address modifier private data 'pvt'. It may also take
571+ * into account the actual number of elements 'available' in the target, and
572+ * the 'offset', both of which usually result from a previous call to
573+ * 'get_array_info'.
574+ * The targeted record must be locked. Furthermore, the lock must not be given
575+ * up between the call to 'get_array_info' and call to this function, otherwise
576+ * 'offset' and 'available' may be out of sync.
577+ *
578+ * @param paddr Target (field) address
579+ * @param dbrType Requested (element) type
580+ * @param pbuffer Data requested to be written
581+ * @param nRequest Number of elements in pbuffer
582+ * @param pvt Private modifier data
583+ * @param offset Current offset in the target field
584+ * @param available Current number of elements in the target field
585+ * @return Status
586+ */
587+typedef long dbHandleAddrModifier(DBADDR *paddr, short dbrType,
588+ const void *pbuffer, long nRequest, void *pvt, long offset,
589+ long available);
590+
591+/** @brief Structure of an address modifier.
592+ *
593+ * This will normally be allocated by user code. An address modifier
594+ * implementation should supply a function to create the private data 'pvt'
595+ * and initialize 'handle' with a function to handle the modifier.
596+ */
597+typedef struct {
598+ void *pvt; /** @brief Private modifier data */
599+ dbHandleAddrModifier *handle; /** @brief Function to handle the modifier */
600+} dbAddrModifier;
601+
602+#endif /* DBADDRMODIFIER_H */
603diff --git a/modules/database/src/ioc/db/dbChannel.c b/modules/database/src/ioc/db/dbChannel.c
604index 33c44f8..a9eb4e2 100644
605--- a/modules/database/src/ioc/db/dbChannel.c
606+++ b/modules/database/src/ioc/db/dbChannel.c
607@@ -30,11 +30,11 @@
608 #include "yajl_parse.h"
609
610 #define epicsExportSharedSymbols
611+#include "arrayRangeModifier.h"
612 #include "dbAccessDefs.h"
613 #include "dbBase.h"
614 #include "dbChannel.h"
615 #include "dbCommon.h"
616-#include "dbConvertFast.h"
617 #include "dbEvent.h"
618 #include "dbLock.h"
619 #include "dbStaticLib.h"
620@@ -350,75 +350,27 @@ if (Func) { \
621 if (result != parse_continue) goto failure; \
622 }
623
624-static long parseArrayRange(dbChannel* chan, const char *pname, const char **ppnext) {
625- epicsInt32 start = 0;
626- epicsInt32 end = -1;
627- epicsInt32 incr = 1;
628- epicsInt32 l;
629- char *pnext;
630- ptrdiff_t exist;
631+static long createArrayRangeFilter(dbChannel* chan) {
632+ long start, incr, end;
633 chFilter *filter;
634 const chFilterPlugin *plug;
635 parse_result result;
636- long status = 0;
637-
638- /* If no number is present, strtol() returns 0 and sets pnext=pname,
639- else pnext points to the first char after the number */
640- pname++;
641- l = strtol(pname, &pnext, 0);
642- exist = pnext - pname;
643- if (exist) start = l;
644- pname = pnext;
645- if (*pname == ']' && exist) {
646- end = start;
647- goto insertplug;
648- }
649- if (*pname != ':') {
650- status = S_dbLib_fieldNotFound;
651- goto finish;
652- }
653- pname++;
654- l = strtol(pname, &pnext, 0);
655- exist = pnext - pname;
656- pname = pnext;
657- if (*pname == ']') {
658- if (exist) end = l;
659- goto insertplug;
660- }
661- if (exist) incr = l;
662- if (*pname != ':') {
663- status = S_dbLib_fieldNotFound;
664- goto finish;
665- }
666- pname++;
667- l = strtol(pname, &pnext, 0);
668- exist = pnext - pname;
669- if (exist) end = l;
670- pname = pnext;
671- if (*pname != ']') {
672- status = S_dbLib_fieldNotFound;
673- goto finish;
674- }
675-
676- insertplug:
677- pname++;
678- *ppnext = pname;
679
680 plug = dbFindFilter("arr", 3);
681 if (!plug) {
682- status = S_dbLib_fieldNotFound;
683- goto finish;
684+ /* "arr" plugin not found, this is not an error! */
685+ return 0;
686 }
687
688 filter = freeListCalloc(chFilterFreeList);
689 if (!filter) {
690- status = S_db_noMemory;
691- goto finish;
692+ return S_db_noMemory;
693 }
694 filter->chan = chan;
695 filter->plug = plug;
696 filter->puser = NULL;
697
698+ getArrayRange(&chan->addrModifier, &start, &incr, &end);
699 TRY(filter->plug->fif->parse_start, (filter));
700 TRY(filter->plug->fif->parse_start_map, (filter));
701 if (start != 0) {
702@@ -439,12 +391,9 @@ static long parseArrayRange(dbChannel* chan, const char *pname, const char **ppn
703 ellAdd(&chan->filters, &filter->list_node);
704 return 0;
705
706- failure:
707+failure:
708 freeListFree(chFilterFreeList, filter);
709- status = S_dbLib_fieldNotFound;
710-
711- finish:
712- return status;
713+ return S_dbLib_fieldNotFound;
714 }
715
716 dbChannel * dbChannelCreate(const char *name)
717@@ -482,6 +431,8 @@ dbChannel * dbChannelCreate(const char *name)
718 goto finish;
719
720 /* Handle field modifiers */
721+ chan->addrModifier.pvt = NULL;
722+ chan->addrModifier.handle = NULL;
723 if (*pname) {
724 short dbfType = paddr->field_type;
725
726@@ -506,12 +457,15 @@ dbChannel * dbChannelCreate(const char *name)
727 pname++;
728 }
729
730- if (*pname == '[') {
731- status = parseArrayRange(chan, pname, &pname);
732+ /* Try to recognize an array range expression */
733+ if (parseArrayRange(&chan->addrModifier, &pname)==0) {
734+ status = createArrayRangeFilter(chan);
735 if (status) goto finish;
736 }
737
738 /* JSON may follow */
739+ /* Note that chf_parse issues error messages if it fails,
740+ which is why we have to check for the opening brace here. */
741 if (*pname == '{') {
742 status = chf_parse(chan, &pname);
743 if (status) goto finish;
744@@ -631,65 +585,14 @@ long dbChannelOpen(dbChannel *chan)
745 }
746
747 /* Only use dbChannelGet() if the record is already locked. */
748-long dbChannelGet(dbChannel *chan, short dbrType, void *pbuffer,
749- long *options, long *nRequest, db_field_log *pfl)
750+long dbChannelGet(dbChannel *chan, short type, void *pbuffer,
751+ long *options, long *nRequest, void *pfl)
752 {
753- dbAddr addr = chan->addr; /* structure copy */
754- int pfl_has_copy = pfl && (pfl->type != dbfl_type_ref || pfl->u.r.dtor);
755-
756- if (pfl) {
757- addr.field_size = pfl->field_size;
758- addr.field_type = pfl->field_type;
759- addr.no_elements = pfl->no_elements;
760- switch ((enum dbfl_type)pfl->type) {
761- case dbfl_type_val: addr.pfield = &pfl->u.v.field; break;
762- case dbfl_type_ref: addr.pfield = pfl->u.r.field; break;
763- }
764- }
765-
766- /*
767- * Try to optimize scalar requests by caching (just) the conversion
768- * routine. This is possible only if we have no options, the field and the
769- * request have exactly one element, and we have no special processing to
770- * do. Note that if the db_field_log has already copied the data, dbGet
771- * does not call get_array_info.
772- */
773- if ((options && *options)
774- || addr.no_elements > 1
775- || (nRequest && *nRequest > 1)
776- || (!pfl_has_copy && addr.special == SPC_DBADDR)
777- || addr.special == SPC_ATTRIBUTE)
778- {
779- chan->getCvt = NULL;
780- return dbGet(&addr, dbrType, pbuffer, options, nRequest, pfl);
781- }
782-
783- /* If this is a scalar request, fail if it cannot be met */
784- if (!nRequest && addr.no_elements < 1)
785- return S_db_badField;
786-
787- /*
788- * Optimization for scalar requests with no options,
789- * no special processing, and where the channel has only one element.
790- */
791- if (chan->getCvt && chan->lastGetdbrType == dbrType) {
792- return chan->getCvt(addr.pfield, pbuffer, &addr);
793- } else {
794- unsigned short dbfType = addr.field_type;
795-
796- if (dbrType < 0 || dbrType > DBR_ENUM || dbfType > DBF_DEVICE)
797- return S_db_badDbrtype;
798-
799- chan->getCvt = dbFastGetConvertRoutine[dbfType][dbrType];
800- chan->lastGetdbrType = dbrType;
801- if (nRequest)
802- *nRequest = addr.no_elements;
803- return chan->getCvt(addr.pfield, pbuffer, &addr);
804- }
805+ return dbGet(&chan->addr, type, pbuffer, options, nRequest, pfl);
806 }
807
808 long dbChannelGetField(dbChannel *chan, short dbrType, void *pbuffer,
809- long *options, long *nRequest, db_field_log *pfl)
810+ long *options, long *nRequest, void *pfl)
811 {
812 dbCommon *precord = chan->addr.precord;
813 long status = 0;
814@@ -707,13 +610,15 @@ long dbChannelGetField(dbChannel *chan, short dbrType, void *pbuffer,
815 long dbChannelPut(dbChannel *chan, short type, const void *pbuffer,
816 long nRequest)
817 {
818- return dbPut(&chan->addr, type, pbuffer, nRequest);
819+ return dbPutModifier(&chan->addr, type, pbuffer, nRequest,
820+ &chan->addrModifier);
821 }
822
823 long dbChannelPutField(dbChannel *chan, short type, const void *pbuffer,
824 long nRequest)
825 {
826- return dbPutField(&chan->addr, type, pbuffer, nRequest);
827+ return dbPutFieldModifier(&chan->addr, type, pbuffer, nRequest,
828+ &chan->addrModifier);
829 }
830
831 void dbChannelShow(dbChannel *chan, int level, const unsigned short indent)
832@@ -767,6 +672,7 @@ void dbChannelDelete(dbChannel *chan)
833 freeListFree(chFilterFreeList, filter);
834 }
835 free((char *) chan->name);
836+ deleteArrayRangeModifier(&chan->addrModifier);
837 freeListFree(dbChannelFreeList, chan);
838 }
839
840diff --git a/modules/database/src/ioc/db/dbChannel.h b/modules/database/src/ioc/db/dbChannel.h
841index 1825cb3..60fbb9d 100644
842--- a/modules/database/src/ioc/db/dbChannel.h
843+++ b/modules/database/src/ioc/db/dbChannel.h
844@@ -23,6 +23,7 @@
845 #include "epicsTypes.h"
846 #include "errMdef.h"
847 #include "db_field_log.h"
848+#include "dbAddrModifier.h"
849 #include "dbEvent.h"
850 #include "dbCoreAPI.h"
851
852@@ -50,21 +51,18 @@ typedef struct evSubscrip {
853
854 typedef struct chFilter chFilter;
855
856-typedef long fastConvert(void *from, void *to, const dbAddr *paddr);
857-
858 /* A dbChannel points to a record field, and can have multiple filters */
859 typedef struct dbChannel {
860 const char *name;
861 dbAddr addr; /* address structure for record/field */
862+ dbAddrModifier addrModifier;
863+ /* optional: which indices are targeted */
864 long final_no_elements; /* final number of elements (arrays) */
865 short final_field_size; /* final size of element */
866 short final_type; /* final type of database field */
867 ELLLIST filters; /* list of filters as created from JSON */
868 ELLLIST pre_chain; /* list of filters to be called pre-event-queue */
869 ELLLIST post_chain; /* list of filters to be called post-event-queue */
870- /* Support for optimizing scalar requests */
871- fastConvert *getCvt; /* fast get conversion function */
872- short lastGetdbrType; /* last dbChannelGet dbrType */
873 } dbChannel;
874
875 /* Prototype for the channel event function that is called in filter stacks
876@@ -212,19 +210,11 @@ DBCORE_API extern unsigned short dbDBRnewToDBRold[];
877 #define dbChannelField(pChan) ((pChan)->addr.pfield)
878
879
880-<<<<<<< modules/database/src/ioc/db/dbChannel.h
881 DBCORE_API long dbChannelGet(dbChannel *chan, short type,
882 void *pbuffer, long *options, long *nRequest, void *pfl);
883 DBCORE_API long dbChannelGetField(dbChannel *chan, short type,
884 void *pbuffer, long *options, long *nRequest, void *pfl);
885 DBCORE_API long dbChannelPut(dbChannel *chan, short type,
886-=======
887-epicsShareFunc long dbChannelGet(dbChannel *chan, short type,
888- void *pbuffer, long *options, long *nRequest, db_field_log *pfl);
889-epicsShareFunc long dbChannelGetField(dbChannel *chan, short type,
890- void *pbuffer, long *options, long *nRequest, db_field_log *pfl);
891-epicsShareFunc long dbChannelPut(dbChannel *chan, short type,
892->>>>>>> modules/database/src/ioc/db/dbChannel.h
893 const void *pbuffer, long nRequest);
894 DBCORE_API long dbChannelPutField(dbChannel *chan, short type,
895 const void *pbuffer, long nRequest);
896@@ -232,7 +222,6 @@ DBCORE_API void dbChannelShow(dbChannel *chan, int level,
897 const unsigned short indent);
898 DBCORE_API void dbChannelFilterShow(dbChannel *chan, int level,
899 const unsigned short indent);
900-<<<<<<< modules/database/src/ioc/db/dbChannel.h
901 DBCORE_API void dbChannelDelete(dbChannel *chan);
902
903 DBCORE_API void dbRegisterFilter(const char *key, const chFilterIf *fif, void *puser);
904@@ -240,15 +229,6 @@ DBCORE_API db_field_log* dbChannelRunPreChain(dbChannel *chan, db_field_log *pLo
905 DBCORE_API db_field_log* dbChannelRunPostChain(dbChannel *chan, db_field_log *pLogIn);
906 DBCORE_API const chFilterPlugin * dbFindFilter(const char *key, size_t len);
907 DBCORE_API void dbChannelGetArrayInfo(dbChannel *chan,
908-=======
909-epicsShareFunc void dbChannelDelete(dbChannel *chan);
910-
911-epicsShareFunc void dbRegisterFilter(const char *key, const chFilterIf *fif, void *puser);
912-epicsShareFunc db_field_log* dbChannelRunPreChain(dbChannel *chan, db_field_log *pLogIn);
913-epicsShareFunc db_field_log* dbChannelRunPostChain(dbChannel *chan, db_field_log *pLogIn);
914-epicsShareFunc const chFilterPlugin * dbFindFilter(const char *key, size_t len);
915-epicsShareFunc void dbChannelGetArrayInfo(dbChannel *chan,
916->>>>>>> modules/database/src/ioc/db/dbChannel.h
917 void **pfield, long *no_elements, long *offset);
918
919 #ifdef __cplusplus
920diff --git a/modules/database/src/ioc/db/dbDbLink.c b/modules/database/src/ioc/db/dbDbLink.c
921index 0c9aa8f..fe7c372 100644
922--- a/modules/database/src/ioc/db/dbDbLink.c
923+++ b/modules/database/src/ioc/db/dbDbLink.c
924@@ -58,6 +58,7 @@
925 #include "dbBase.h"
926 #include "dbBkpt.h"
927 #include "dbCommonPvt.h"
928+#include "dbConvertFast.h"
929 #include "dbConvert.h"
930 #include "db_field_log.h"
931 #include "db_access_routines.h"
932@@ -134,11 +135,9 @@ static void dbDbRemoveLink(struct dbLocker *locker, struct link *plink)
933 /* locker is NULL when an isolated IOC is closing its links */
934 if (locker) {
935 plink->value.pv_link.pvt = 0;
936+ plink->value.pv_link.getCvt = 0;
937 plink->value.pv_link.pvlMask = 0;
938-<<<<<<< modules/database/src/ioc/db/dbDbLink.c
939 plink->value.pv_link.lastGetdbrType = 0;
940-=======
941->>>>>>> modules/database/src/ioc/db/dbDbLink.c
942 ellDelete(&precord->bklnk, &plink->value.pv_link.backlinknode);
943 dbLockSetSplit(locker, plink->precord, precord);
944 }
945@@ -168,10 +167,7 @@ static long dbDbGetValue(struct link *plink, short dbrType, void *pbuffer,
946 {
947 struct pv_link *ppv_link = &plink->value.pv_link;
948 dbChannel *chan = linkChannel(plink);
949-<<<<<<< modules/database/src/ioc/db/dbDbLink.c
950 DBADDR *paddr = &chan->addr;
951-=======
952->>>>>>> modules/database/src/ioc/db/dbDbLink.c
953 dbCommon *precord = plink->precord;
954 db_field_log *pfl = NULL;
955 long status;
956@@ -183,7 +179,6 @@ static long dbDbGetValue(struct link *plink, short dbrType, void *pbuffer,
957 return status;
958 }
959
960-<<<<<<< modules/database/src/ioc/db/dbDbLink.c
961 if (ppv_link->getCvt && ppv_link->lastGetdbrType == dbrType)
962 {
963 /* shortcut: scalar with known conversion, no filter */
964@@ -229,23 +224,6 @@ static long dbDbGetValue(struct link *plink, short dbrType, void *pbuffer,
965 }
966
967 if (!status && precord != dbChannelRecord(chan))
968-=======
969- /* If filters are involved in a read, create field log and run filters */
970- if (ellCount(&chan->filters)) {
971- pfl = db_create_read_log(chan);
972- if (!pfl)
973- return S_db_noMemory;
974- pfl = dbChannelRunPreChain(chan, pfl);
975- pfl = dbChannelRunPostChain(chan, pfl);
976- }
977- status = dbChannelGet(chan, dbrType, pbuffer, NULL, pnRequest, pfl);
978- if (pfl)
979- db_delete_field_log(pfl);
980- if (status)
981- return status;
982-
983- if (precord != dbChannelRecord(chan))
984->>>>>>> modules/database/src/ioc/db/dbDbLink.c
985 recGblInheritSevr(plink->value.pv_link.pvlMask & pvlOptMsMode,
986 plink->precord,
987 dbChannelRecord(chan)->stat, dbChannelRecord(chan)->sevr);
988@@ -386,9 +364,8 @@ static long dbDbPutValue(struct link *plink, short dbrType,
989 struct pv_link *ppv_link = &plink->value.pv_link;
990 dbChannel *chan = linkChannel(plink);
991 struct dbCommon *psrce = plink->precord;
992- DBADDR *paddr = &chan->addr;
993 dbCommon *pdest = dbChannelRecord(chan);
994- long status = dbPut(paddr, dbrType, pbuffer, nRequest);
995+ long status = dbChannelPut(chan, dbrType, pbuffer, nRequest);
996
997 recGblInheritSevr(ppv_link->pvlMask & pvlOptMsMode, pdest, psrce->nsta,
998 psrce->nsev);
999diff --git a/modules/database/src/ioc/db/dbEvent.h b/modules/database/src/ioc/db/dbEvent.h
1000index 0cefcab..2c6feca 100644
1001--- a/modules/database/src/ioc/db/dbEvent.h
1002+++ b/modules/database/src/ioc/db/dbEvent.h
1003@@ -51,7 +51,6 @@ epicsShareFunc int db_post_events (
1004 typedef void * dbEventCtx;
1005
1006 typedef void EXTRALABORFUNC (void *extralabor_arg);
1007-void db_init_event_freelists (void);
1008 epicsShareFunc dbEventCtx db_init_events (void);
1009 epicsShareFunc int db_start_events (
1010 dbEventCtx ctx, const char *taskname, void (*init_func)(void *),
1011diff --git a/modules/database/src/ioc/db/dbUnitTest.c b/modules/database/src/ioc/db/dbUnitTest.c
1012index 0b40d92..1dd4909 100644
1013--- a/modules/database/src/ioc/db/dbUnitTest.c
1014+++ b/modules/database/src/ioc/db/dbUnitTest.c
1015@@ -267,7 +267,7 @@ done:
1016 void testdbPutArrFieldOk(const char* pv, short dbrType, unsigned long count, const void *pbuf)
1017 {
1018 dbChannel *chan = dbChannelCreate(pv);
1019- long status;
1020+ long status = 0;
1021
1022 if(!chan || (status=dbChannelOpen(chan))) {
1023 testFail("Channel error (%p, %ld) : %s", chan, status, pv);
1024@@ -290,7 +290,7 @@ void testdbGetArrFieldEqual(const char* pv, short dbfType, long nRequest, unsign
1025 const long vSize = dbValueSize(dbfType);
1026 const long nStore = vSize * nRequest;
1027 long status = S_dbLib_recNotFound;
1028- char *gbuf, *gstore;
1029+ char *gbuf, *gstore = NULL;
1030 const char *pbuf = pbufraw;
1031
1032 if(!chan || (status=dbChannelOpen(chan))) {
1033diff --git a/modules/database/src/ioc/db/db_field_log.h b/modules/database/src/ioc/db/db_field_log.h
1034index 5059fb2..e517d52 100644
1035--- a/modules/database/src/ioc/db/db_field_log.h
1036+++ b/modules/database/src/ioc/db/db_field_log.h
1037@@ -133,12 +133,9 @@ typedef struct db_field_log {
1038 */
1039 #define dbfl_has_copy(p)\
1040 ((p) && ((p)->type==dbfl_type_val || (p)->u.r.dtor || (p)->no_elements==0))
1041-<<<<<<< modules/database/src/ioc/db/db_field_log.h
1042
1043 #define dbfl_pfield(p)\
1044 ((p)->type==dbfl_type_val ? &p->u.v.field : p->u.r.field)
1045-=======
1046->>>>>>> modules/database/src/ioc/db/db_field_log.h
1047
1048 #ifdef __cplusplus
1049 }
1050diff --git a/modules/database/src/ioc/dbStatic/link.h b/modules/database/src/ioc/dbStatic/link.h
1051index b98181f..c3c0045 100644
1052--- a/modules/database/src/ioc/dbStatic/link.h
1053+++ b/modules/database/src/ioc/dbStatic/link.h
1054@@ -78,21 +78,15 @@ struct macro_link {
1055 };
1056
1057 struct dbCommon;
1058+typedef long (*LINKCVT)();
1059
1060 struct pv_link {
1061-<<<<<<< modules/database/src/ioc/dbStatic/link.h
1062 ELLNODE backlinknode;
1063 char *pvname; /* pvname link points to */
1064 void *pvt; /* CA or DB private */
1065 LINKCVT getCvt; /* input conversion function */
1066 short pvlMask; /* Options mask */
1067 short lastGetdbrType; /* last dbrType for DB or CA get */
1068-=======
1069- ELLNODE backlinknode;
1070- char *pvname; /* pvname link points to */
1071- void *pvt; /* CA or DB private */
1072- short pvlMask; /* Options mask */
1073->>>>>>> modules/database/src/ioc/dbStatic/link.h
1074 };
1075
1076 struct jlink;
1077diff --git a/modules/database/src/std/filters/arr.c b/modules/database/src/std/filters/arr.c
1078index b35bdf5..ed42c67 100644
1079--- a/modules/database/src/std/filters/arr.c
1080+++ b/modules/database/src/std/filters/arr.c
1081@@ -13,6 +13,7 @@
1082
1083 #include <stdio.h>
1084
1085+#include "arrayRangeModifier.h"
1086 #include "chfPlugin.h"
1087 #include "dbAccessDefs.h"
1088 #include "dbExtractArray.h"
1089@@ -74,23 +75,6 @@ static void freeArray(db_field_log *pfl)
1090 }
1091 }
1092
1093-static long wrapArrayIndices(long *start, const long increment, long *end,
1094- const long no_elements)
1095-{
1096- if (*start < 0) *start = no_elements + *start;
1097- if (*start < 0) *start = 0;
1098- if (*start > no_elements) *start = no_elements;
1099-
1100- if (*end < 0) *end = no_elements + *end;
1101- if (*end < 0) *end = 0;
1102- if (*end >= no_elements) *end = no_elements - 1;
1103-
1104- if (*end - *start >= 0)
1105- return 1 + (*end - *start) / increment;
1106- else
1107- return 0;
1108-}
1109-
1110 static db_field_log* filter(void* pvt, dbChannel *chan, db_field_log *pfl)
1111 {
1112 myStruct *my = (myStruct*) pvt;
1113@@ -128,14 +112,7 @@ static db_field_log* filter(void* pvt, dbChannel *chan, db_field_log *pfl)
1114 pfl->u.r.dtor = freeArray;
1115 pfl->u.r.pvt = my->arrayFreeList;
1116 }
1117-<<<<<<< modules/database/src/std/filters/arr.c
1118 /* adjust no_elements (even if zero elements remain) */
1119-=======
1120- /*
1121- * Adjust offset and no_elements to refer to the new pTarget.
1122- * (If zero elements remain, they need not refer to anything.)
1123- */
1124->>>>>>> modules/database/src/std/filters/arr.c
1125 pfl->no_elements = nTarget;
1126 if (must_lock)
1127 dbScanUnlock(dbChannelRecord(chan));
1128diff --git a/modules/database/test/std/rec/Makefile b/modules/database/test/std/rec/Makefile
1129index e8c5464..71884b4 100644
1130--- a/modules/database/test/std/rec/Makefile
1131+++ b/modules/database/test/std/rec/Makefile
1132@@ -15,6 +15,7 @@ USR_CPPFLAGS += -DUSE_TYPED_DSET
1133
1134 TESTLIBRARY = dbRecStdTest
1135
1136+dbRecStdTest_SRCS += arroutRecord.c
1137 dbRecStdTest_SRCS += asTestLib.c
1138 dbRecStdTest_LIBS += dbRecStd dbCore ca Com
1139
1140@@ -23,6 +24,7 @@ PROD_LIBS = dbRecStdTest dbRecStd dbCore ca Com
1141 TARGETS += $(COMMON_DIR)/recTestIoc.dbd
1142 DBDDEPENDS_FILES += recTestIoc.dbd$(DEP)
1143 recTestIoc_DBD = base.dbd
1144+recTestIoc_DBD += arroutRecord.dbd
1145 TESTFILES += $(COMMON_DIR)/recTestIoc.dbd
1146
1147 testHarness_SRCS += recTestIoc_registerRecordDeviceDriver.cpp
1148@@ -166,6 +168,13 @@ testHarness_SRCS += linkFilterTest.c
1149 TESTFILES += ../linkFilterTest.db
1150 TESTS += linkFilterTest
1151
1152+TESTPROD_HOST += addrModifierTest
1153+addrModifierTest_SRCS += addrModifierTest.c
1154+addrModifierTest_SRCS += recTestIoc_registerRecordDeviceDriver.cpp
1155+testHarness_SRCS += addrModifierTest.c
1156+TESTFILES += ../addrModifierTest.db
1157+TESTS += addrModifierTest
1158+
1159 # dbHeader* is only a compile test
1160 # no need to actually run
1161 TESTPROD += dbHeaderTest
1162@@ -199,5 +208,7 @@ endif
1163
1164 include $(TOP)/configure/RULES
1165
1166+arroutRecord$(DEP): $(COMMON_DIR)/arroutRecord.h
1167+
1168 rtemsTestData.c : $(TESTFILES) $(TOOLS)/epicsMakeMemFs.pl
1169 $(PERL) $(TOOLS)/epicsMakeMemFs.pl $@ epicsRtemsFSImage $(TESTFILES)
1170diff --git a/modules/database/test/std/rec/addrModifierTest.c b/modules/database/test/std/rec/addrModifierTest.c
1171new file mode 100644
1172index 0000000..d555b91
1173--- /dev/null
1174+++ b/modules/database/test/std/rec/addrModifierTest.c
1175@@ -0,0 +1,247 @@
1176+/*************************************************************************\
1177+* Copyright (c) 2020 Dirk Zimoch
1178+* EPICS BASE is distributed subject to a Software License Agreement found
1179+* in file LICENSE that is included with this distribution.
1180+\*************************************************************************/
1181+
1182+#include <string.h>
1183+
1184+#include "dbAccess.h"
1185+#include "devSup.h"
1186+#include "alarm.h"
1187+#include "dbUnitTest.h"
1188+#include "errlog.h"
1189+#include "epicsThread.h"
1190+
1191+#include "longinRecord.h"
1192+
1193+#include "testMain.h"
1194+
1195+void recTestIoc_registerRecordDeviceDriver(struct dbBase *);
1196+
1197+static void startTestIoc(const char *dbfile)
1198+{
1199+ testdbPrepare();
1200+ testdbReadDatabase("recTestIoc.dbd", NULL, NULL);
1201+ recTestIoc_registerRecordDeviceDriver(pdbbase);
1202+ testdbReadDatabase(dbfile, NULL, NULL);
1203+
1204+ eltc(0);
1205+ testIocInitOk();
1206+ eltc(1);
1207+}
1208+
1209+struct pv {
1210+ const char *name;
1211+ long count;
1212+ double values[4];
1213+};
1214+
1215+static void expectProcSuccess(struct pv *pv)
1216+{
1217+ char fieldname[20];
1218+ testdbPutFieldOk("reset.PROC", DBF_LONG, 1);
1219+ testDiag("expecting success from %s", pv->name);
1220+ sprintf(fieldname, "%s.PROC", pv->name);
1221+ testdbPutFieldOk(fieldname, DBF_LONG, 1);
1222+ sprintf(fieldname, "%s.SEVR", pv->name);
1223+ testdbGetFieldEqual(fieldname, DBF_LONG, NO_ALARM);
1224+ sprintf(fieldname, "%s.STAT", pv->name);
1225+ testdbGetFieldEqual(fieldname, DBF_LONG, NO_ALARM);
1226+}
1227+
1228+#if 0
1229+static void expectProcFailure(struct pv *pv)
1230+{
1231+ char fieldname[20];
1232+ testdbPutFieldOk("reset.PROC", DBF_LONG, 1);
1233+ testDiag("expecting failure S_db_badField %#x from %s", S_db_badField, pv->name);
1234+ sprintf(fieldname, "%s.PROC", pv->name);
1235+ testdbPutFieldFail(S_db_badField, fieldname, DBF_LONG, 1);
1236+ sprintf(fieldname, "%s.SEVR", pv->name);
1237+ testdbGetFieldEqual(fieldname, DBF_LONG, INVALID_ALARM);
1238+ sprintf(fieldname, "%s.STAT", pv->name);
1239+ testdbGetFieldEqual(fieldname, DBF_LONG, LINK_ALARM);
1240+}
1241+#endif
1242+
1243+static double initial[] = {0,1,2,3,4,5,6,7,8,9};
1244+static double buf[10];
1245+
1246+static void changeRange(struct pv *pv, long start, long incr, long end)
1247+{
1248+ char linkstring[60];
1249+ char pvstring[10];
1250+
1251+ if (incr)
1252+ sprintf(linkstring, "tgt.[%ld:%ld:%ld]", start, incr, end);
1253+ else if (end)
1254+ sprintf(linkstring, "tgt.[%ld:%ld]", start, end);
1255+ else
1256+ sprintf(linkstring, "tgt.[%ld]", start);
1257+ testDiag("modifying %s.OUT link: %s", pv->name, linkstring);
1258+ sprintf(pvstring, "%s.OUT", pv->name);
1259+ testdbPutFieldOk(pvstring, DBF_STRING, linkstring);
1260+}
1261+
1262+static void expectRange(double *values, long start, long incr, long end)
1263+{
1264+ int i,j;
1265+ if (!incr)
1266+ incr = 1;
1267+ for (i=0; i<10; i++)
1268+ buf[i] = initial[i];
1269+ for (i=0, j=start; j<=end; i++, j+=incr)
1270+ buf[j] = values[i];
1271+ testdbGetFieldEqual("tgt.NORD", DBF_LONG, 8);
1272+ testdbGetFieldEqual("tgt.SEVR", DBF_LONG, NO_ALARM);
1273+ testdbGetFieldEqual("tgt.STAT", DBF_LONG, NO_ALARM);
1274+ testdbGetArrFieldEqual("tgt.VAL", DBF_DOUBLE, 10, 8, buf);
1275+}
1276+
1277+#if 0
1278+static void expectEmptyArray(void)
1279+{
1280+ /* empty arrays are now allowed at the moment */
1281+ testDiag("expecting empty array");
1282+ testdbGetFieldEqual("tgt.NORD", DBF_LONG, 0);
1283+}
1284+#endif
1285+
1286+struct pv ao_pv = {"ao",1,{20,0,0,0}};
1287+struct pv src_pv = {"src",4,{30,40,50,60}};
1288+struct pv *ao = &ao_pv;
1289+struct pv *src = &src_pv;
1290+
1291+static double pini_values[] = {20,30,40,50};
1292+
1293+#define expectEmptyRange() expectRange(0,0,0,-1)
1294+
1295+MAIN(addrModifierTest)
1296+{
1297+ testPlan(205);
1298+ startTestIoc("addrModifierTest.db");
1299+
1300+ testdbGetFieldEqual("src.NORD", DBF_LONG, 4);
1301+ testDiag("PINI");
1302+ expectRange(pini_values,2,1,5);
1303+
1304+ testDiag("after processing");
1305+ testdbPutFieldOk("ao.PROC", DBF_LONG, 1);
1306+ testdbPutFieldOk("src.PROC", DBF_LONG, 1);
1307+ expectRange(pini_values,2,1,5);
1308+
1309+ testDiag("after processing target record");
1310+ testdbPutFieldOk("tgt.PROC", DBF_LONG, 1);
1311+ expectRange(pini_values,2,1,5);
1312+
1313+ testDiag("modify range");
1314+
1315+ changeRange(ao,3,0,0);
1316+ expectProcSuccess(ao);
1317+ expectRange(ao->values,3,1,3);
1318+
1319+ changeRange(src,4,0,7);
1320+ expectProcSuccess(src);
1321+ expectRange(src->values,4,1,7);
1322+
1323+ testDiag("put more than available");
1324+
1325+ changeRange(ao,3,0,6);
1326+ expectProcSuccess(ao);
1327+ expectRange(ao->values,3,1,3); /* clipped range */
1328+
1329+ changeRange(src,3,0,9);
1330+ expectProcSuccess(src);
1331+ expectRange(src->values,3,1,6); /* clipped range */
1332+
1333+ testDiag("backward range");
1334+
1335+ changeRange(ao,5,0,3);
1336+ expectProcSuccess(ao);
1337+ expectEmptyRange();
1338+
1339+ changeRange(src,5,0,3);
1340+ expectProcSuccess(src);
1341+ expectEmptyRange();
1342+
1343+ testDiag("step 2");
1344+
1345+ changeRange(ao,1,2,6);
1346+ expectProcSuccess(ao);
1347+ expectRange(ao->values,1,1,1); /* clipped range */
1348+
1349+ changeRange(src,1,2,6);
1350+ expectProcSuccess(src);
1351+ expectRange(src->values,1,2,5);
1352+
1353+ testDiag("range start beyond tgt.NORD");
1354+
1355+ changeRange(ao,8,0,0);
1356+ expectProcSuccess(ao);
1357+ expectEmptyRange();
1358+
1359+ changeRange(src,8,0,9);
1360+ expectProcSuccess(src);
1361+ expectEmptyRange();
1362+
1363+ testDiag("range end beyond tgt.NORD");
1364+
1365+ changeRange(ao,3,0,9);
1366+ expectProcSuccess(ao);
1367+ expectRange(ao->values,3,1,3); /* clipped range */
1368+
1369+ changeRange(src,3,0,9);
1370+ expectProcSuccess(src);
1371+ expectRange(src->values,3,1,6); /* clipped range */
1372+
1373+ testDiag("range start beyond tgt.NELM");
1374+
1375+ changeRange(ao,11,0,12);
1376+ expectProcSuccess(ao);
1377+ expectEmptyRange();
1378+
1379+ changeRange(src,11,0,12);
1380+ expectProcSuccess(src);
1381+ expectEmptyRange();
1382+
1383+ testDiag("range end beyond tgt.NELM");
1384+
1385+ changeRange(src,4,0,12);
1386+ expectProcSuccess(src);
1387+ expectRange(src->values,4,1,7); /* clipped range */
1388+
1389+ testDiag("single value beyond tgt.NORD");
1390+
1391+ changeRange(ao,8,0,0);
1392+ expectProcSuccess(ao);
1393+ expectEmptyRange();
1394+
1395+ changeRange(src,8,0,0);
1396+ expectProcSuccess(src);
1397+ expectEmptyRange();
1398+
1399+ testDiag("single value");
1400+
1401+ changeRange(ao,5,0,0);
1402+ expectProcSuccess(ao);
1403+ expectRange(ao->values,5,1,5);
1404+
1405+ changeRange(src,5,0,0);
1406+ expectProcSuccess(src);
1407+ expectRange(src->values,5,1,5);
1408+
1409+ testDiag("single values beyond tgt.NELM");
1410+
1411+ changeRange(ao,12,0,0);
1412+ expectProcSuccess(ao);
1413+ expectEmptyRange();
1414+
1415+ changeRange(src,12,0,0);
1416+ expectProcSuccess(src);
1417+ expectEmptyRange();
1418+
1419+ testIocShutdownOk();
1420+ testdbCleanup();
1421+ return testDone();
1422+}
1423diff --git a/modules/database/test/std/rec/addrModifierTest.db b/modules/database/test/std/rec/addrModifierTest.db
1424new file mode 100644
1425index 0000000..ed28a80
1426--- /dev/null
1427+++ b/modules/database/test/std/rec/addrModifierTest.db
1428@@ -0,0 +1,24 @@
1429+record(arrout, "tgt") {
1430+ field(NELM, "10")
1431+ field(FTVL, "SHORT")
1432+ field(DOL, [0, 1, 2, 3, 4, 5, 6, 7])
1433+ field(PINI, "YES")
1434+}
1435+record(ao, "ao") {
1436+ field(OUT, "tgt.[2]")
1437+ field(DOL, 20)
1438+ field(PINI, "YES")
1439+}
1440+record(arrout, "src") {
1441+ field(NELM, "5")
1442+ field(FTVL, "DOUBLE")
1443+ field(DOL, [30.0, 40.0, 50.0, 60.0])
1444+ field(OUT, "tgt.[3:5]")
1445+ field(PINI, "YES")
1446+}
1447+record(arrout, "reset") {
1448+ field(NELM, "10")
1449+ field(FTVL, "SHORT")
1450+ field(DOL, [0, 1, 2, 3, 4, 5, 6, 7])
1451+ field(OUT, "tgt PP")
1452+}
1453diff --git a/modules/database/test/std/rec/arroutRecord.c b/modules/database/test/std/rec/arroutRecord.c
1454new file mode 100644
1455index 0000000..61c7680
1456--- /dev/null
1457+++ b/modules/database/test/std/rec/arroutRecord.c
1458@@ -0,0 +1,169 @@
1459+/*************************************************************************\
1460+* Copyright (c) 2010 Brookhaven National Laboratory.
1461+* Copyright (c) 2010 Helmholtz-Zentrum Berlin
1462+* fuer Materialien und Energie GmbH.
1463+* Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne
1464+* National Laboratory.
1465+* Copyright (c) 2002 The Regents of the University of California, as
1466+* Operator of Los Alamos National Laboratory.
1467+* EPICS BASE is distributed subject to a Software License Agreement found
1468+* in file LICENSE that is included with this distribution.
1469+\*************************************************************************/
1470+
1471+/* arroutRecord.c - minimal array output record for test purposes: no processing */
1472+
1473+/* adapted from: arrRecord.c
1474+ *
1475+ * Author: Ralph Lange <Ralph.Lange@bessy.de>
1476+ *
1477+ * vaguely implemented like parts of recWaveform.c by Bob Dalesio
1478+ *
1479+ */
1480+
1481+#include <stdio.h>
1482+
1483+#include "alarm.h"
1484+#include "epicsPrint.h"
1485+#include "dbAccess.h"
1486+#include "dbEvent.h"
1487+#include "dbFldTypes.h"
1488+#include "recSup.h"
1489+#include "recGbl.h"
1490+#include "cantProceed.h"
1491+#define GEN_SIZE_OFFSET
1492+#include "arroutRecord.h"
1493+#undef GEN_SIZE_OFFSET
1494+#include "epicsExport.h"
1495+
1496+/* Create RSET - Record Support Entry Table*/
1497+#define report NULL
1498+#define initialize NULL
1499+static long init_record(struct dbCommon *, int);
1500+static long process(struct dbCommon *);
1501+#define special NULL
1502+#define get_value NULL
1503+static long cvt_dbaddr(DBADDR *);
1504+static long get_array_info(DBADDR *, long *, long *);
1505+static long put_array_info(DBADDR *, long);
1506+#define get_units NULL
1507+#define get_precision NULL
1508+#define get_enum_str NULL
1509+#define get_enum_strs NULL
1510+#define put_enum_str NULL
1511+#define get_graphic_double NULL
1512+#define get_control_double NULL
1513+#define get_alarm_double NULL
1514+
1515+rset arroutRSET = {
1516+ RSETNUMBER,
1517+ report,
1518+ initialize,
1519+ init_record,
1520+ process,
1521+ special,
1522+ get_value,
1523+ cvt_dbaddr,
1524+ get_array_info,
1525+ put_array_info,
1526+ get_units,
1527+ get_precision,
1528+ get_enum_str,
1529+ get_enum_strs,
1530+ put_enum_str,
1531+ get_graphic_double,
1532+ get_control_double,
1533+ get_alarm_double
1534+};
1535+epicsExportAddress(rset, arroutRSET);
1536+
1537+static long init_record(struct dbCommon *pcommon, int pass)
1538+{
1539+ struct arroutRecord *prec = (struct arroutRecord *)pcommon;
1540+
1541+ if (pass == 0) {
1542+ if (prec->nelm <= 0)
1543+ prec->nelm = 1;
1544+ if (prec->ftvl > DBF_ENUM)
1545+ prec->ftvl = DBF_UCHAR;
1546+ prec->val = callocMustSucceed(prec->nelm, dbValueSize(prec->ftvl),
1547+ "arr calloc failed");
1548+ return 0;
1549+ } else {
1550+ /* copied from devWfSoft.c */
1551+ long nelm = prec->nelm;
1552+ long status = dbLoadLinkArray(&prec->dol, prec->ftvl, prec->val, &nelm);
1553+
1554+ if (!status && nelm > 0) {
1555+ prec->nord = nelm;
1556+ prec->udf = FALSE;
1557+ }
1558+ else
1559+ prec->nord = 0;
1560+ return status;
1561+ }
1562+}
1563+
1564+static long process(struct dbCommon *pcommon)
1565+{
1566+ struct arroutRecord *prec = (struct arroutRecord *)pcommon;
1567+ long status = 0;
1568+
1569+ prec->pact = TRUE;
1570+ /* read DOL */
1571+ if (!dbLinkIsConstant(&prec->dol)) {
1572+ long nReq = prec->nelm;
1573+
1574+ status = dbGetLink(&prec->dol, prec->ftvl, prec->val, 0, &nReq);
1575+ if (status) {
1576+ recGblSetSevr(prec, LINK_ALARM, INVALID_ALARM);
1577+ } else {
1578+ prec->nord = nReq;
1579+ }
1580+ }
1581+ /* soft "device support": write OUT */
1582+ status = dbPutLink(&prec->out, prec->ftvl, prec->val, prec->nord);
1583+ if (status) {
1584+ recGblSetSevr(prec, LINK_ALARM, INVALID_ALARM);
1585+ }
1586+ recGblGetTimeStamp(prec);
1587+ recGblResetAlarms(prec);
1588+ recGblFwdLink(prec);
1589+ prec->pact = FALSE;
1590+ return status;
1591+}
1592+
1593+static long cvt_dbaddr(DBADDR *paddr)
1594+{
1595+ arroutRecord *prec = (arroutRecord *) paddr->precord;
1596+ int fieldIndex = dbGetFieldIndex(paddr);
1597+
1598+ if (fieldIndex == arroutRecordVAL) {
1599+ paddr->pfield = prec->val;
1600+ paddr->no_elements = prec->nelm;
1601+ paddr->field_type = prec->ftvl;
1602+ paddr->field_size = dbValueSize(prec->ftvl);
1603+ paddr->dbr_field_type = prec->ftvl;
1604+ }
1605+ return 0;
1606+}
1607+
1608+static long get_array_info(DBADDR *paddr, long *no_elements, long *offset)
1609+{
1610+ arroutRecord *prec = (arroutRecord *) paddr->precord;
1611+
1612+ *no_elements = prec->nord;
1613+ *offset = prec->off;
1614+
1615+ return 0;
1616+}
1617+
1618+static long put_array_info(DBADDR *paddr, long nNew)
1619+{
1620+ arroutRecord *prec = (arroutRecord *) paddr->precord;
1621+
1622+ prec->nord = nNew;
1623+ if (prec->nord > prec->nelm)
1624+ prec->nord = prec->nelm;
1625+
1626+ return 0;
1627+}
1628diff --git a/modules/database/test/std/rec/arroutRecord.dbd b/modules/database/test/std/rec/arroutRecord.dbd
1629new file mode 100644
1630index 0000000..2c99e64
1631--- /dev/null
1632+++ b/modules/database/test/std/rec/arroutRecord.dbd
1633@@ -0,0 +1,35 @@
1634+include "menuGlobal.dbd"
1635+include "menuConvert.dbd"
1636+include "menuScan.dbd"
1637+recordtype(arrout) {
1638+ include "dbCommon.dbd"
1639+ field(VAL, DBF_NOACCESS) {
1640+ prompt("Value")
1641+ special(SPC_DBADDR)
1642+ pp(TRUE)
1643+ extra("void *val")
1644+ }
1645+ field(OUT, DBF_OUTLINK) {
1646+ prompt("Output Link")
1647+ }
1648+ field(NELM, DBF_ULONG) {
1649+ prompt("Number of Elements")
1650+ special(SPC_NOMOD)
1651+ initial("1")
1652+ }
1653+ field(FTVL, DBF_MENU) {
1654+ prompt("Field Type of Value")
1655+ special(SPC_NOMOD)
1656+ menu(menuFtype)
1657+ }
1658+ field(NORD, DBF_ULONG) {
1659+ prompt("Number elements read")
1660+ special(SPC_NOMOD)
1661+ }
1662+ field(OFF, DBF_ULONG) {
1663+ prompt("Offset into array")
1664+ }
1665+ field(DOL, DBF_INLINK) {
1666+ prompt("Desired Output Link")
1667+ }
1668+}
1669diff --git a/modules/database/test/std/rec/linkFilterTest.c b/modules/database/test/std/rec/linkFilterTest.c
1670index 75c3421..1996c8c 100644
1671--- a/modules/database/test/std/rec/linkFilterTest.c
1672+++ b/modules/database/test/std/rec/linkFilterTest.c
1673@@ -48,11 +48,7 @@ static void expectProcFailure(const char *rec)
1674 char fieldname[20];
1675 testDiag("expecting failure S_db_badField %#x from %s", S_db_badField, rec);
1676 sprintf(fieldname, "%s.PROC", rec);
1677-<<<<<<< modules/database/test/std/rec/linkFilterTest.c
1678 testdbPutFieldFail(S_db_onlyOne, fieldname, DBF_LONG, 1);
1679-=======
1680- testdbPutFieldFail(S_db_badField, fieldname, DBF_LONG, 1);
1681->>>>>>> modules/database/test/std/rec/linkFilterTest.c
1682 sprintf(fieldname, "%s.SEVR", rec);
1683 testdbGetFieldEqual(fieldname, DBF_LONG, INVALID_ALARM);
1684 sprintf(fieldname, "%s.STAT", rec);
1685@@ -73,11 +69,7 @@ static void changeRange(long start, long stop, long step)
1686 testdbPutFieldOk("wf.INP", DBF_STRING, linkstring);
1687 }
1688
1689-<<<<<<< modules/database/test/std/rec/linkFilterTest.c
1690 static const double buf[] = {1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 2, 4, 6};
1691-=======
1692-static double buf[] = {1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 2, 4, 6};
1693->>>>>>> modules/database/test/std/rec/linkFilterTest.c
1694
1695 static void expectRange(long start, long end)
1696 {
1697@@ -87,7 +79,6 @@ static void expectRange(long start, long end)
1698 testdbGetArrFieldEqual("wf.VAL", DBF_DOUBLE, n+2, n, buf+start);
1699 }
1700
1701-<<<<<<< modules/database/test/std/rec/linkFilterTest.c
1702 static void expectEmptyArray(void)
1703 {
1704 testDiag("expecting empty array");
1705@@ -97,20 +88,6 @@ static void expectEmptyArray(void)
1706 MAIN(linkFilterTest)
1707 {
1708 testPlan(102);
1709-=======
1710-#if 0
1711-static void expectEmptyArray(void)
1712-{
1713- /* empty arrays are now allowed at the moment */
1714- testDiag("expecting empty array");
1715- testdbGetFieldEqual("wf.NORD", DBF_LONG, 0);
1716-}
1717-#endif
1718-
1719-MAIN(linkFilterTest)
1720-{
1721- testPlan(98);
1722->>>>>>> modules/database/test/std/rec/linkFilterTest.c
1723 startTestIoc("linkFilterTest.db");
1724
1725 testDiag("PINI");
1726@@ -126,10 +103,7 @@ MAIN(linkFilterTest)
1727 changeRange(5,3,0);
1728 expectProcFailure("ai");
1729 expectProcSuccess("wf");
1730-<<<<<<< modules/database/test/std/rec/linkFilterTest.c
1731 expectEmptyArray();
1732-=======
1733->>>>>>> modules/database/test/std/rec/linkFilterTest.c
1734
1735 testDiag("step 2");
1736 changeRange(1,6,2);
1737@@ -141,10 +115,7 @@ MAIN(linkFilterTest)
1738 changeRange(8,9,0);
1739 expectProcFailure("ai");
1740 expectProcSuccess("wf");
1741-<<<<<<< modules/database/test/std/rec/linkFilterTest.c
1742 expectEmptyArray();
1743-=======
1744->>>>>>> modules/database/test/std/rec/linkFilterTest.c
1745
1746 testDiag("range end beyond src.NORD");
1747 changeRange(3,9,0);
1748@@ -167,10 +138,7 @@ MAIN(linkFilterTest)
1749 changeRange(8,0,0);
1750 expectProcFailure("ai");
1751 expectProcSuccess("wf");
1752-<<<<<<< modules/database/test/std/rec/linkFilterTest.c
1753 expectEmptyArray();
1754-=======
1755->>>>>>> modules/database/test/std/rec/linkFilterTest.c
1756
1757 testDiag("single value");
1758 changeRange(5,0,0);
1759@@ -182,10 +150,7 @@ MAIN(linkFilterTest)
1760 changeRange(12,0,0);
1761 expectProcFailure("ai");
1762 expectProcSuccess("wf");
1763-<<<<<<< modules/database/test/std/rec/linkFilterTest.c
1764 expectEmptyArray();
1765-=======
1766->>>>>>> modules/database/test/std/rec/linkFilterTest.c
1767
1768 testIocShutdownOk();
1769 testdbCleanup();

Subscribers

People subscribed via source and target branches