Merge ~info-martin-konrad/epics-base:clean-up-msi into ~epics-core/epics-base/+git/epics-base:3.15

Proposed by Martin Konrad
Status: Merged
Approved by: mdavidsaver
Approved revision: a9606dbf6ef07f444c2149afd441d0b62dc0c482
Merged at revision: 59cb5ba6a0f66461e1b0efea28699046d44a7a8b
Proposed branch: ~info-martin-konrad/epics-base:clean-up-msi
Merge into: ~epics-core/epics-base/+git/epics-base:3.15
Prerequisite: ~info-martin-konrad/epics-base:fix-substitution-file-expansion
Diff against target: 791 lines (+129/-164)
5 files modified
src/ioc/dbtemplate/msi.cpp (+107/-162)
src/ioc/dbtemplate/test/msi.plt (+15/-2)
src/ioc/dbtemplate/test/t12-result.txt (+2/-0)
src/ioc/dbtemplate/test/t12-substitute.txt (+3/-0)
src/ioc/dbtemplate/test/t12-template.txt (+2/-0)
Reviewer Review Type Date Requested Status
mdavidsaver Approve
Review via email: mp+361502@code.launchpad.net

Description of the change

Further clean up to simplify code and replace manual memory management by leveraging C++ features (e.g. std::string, std::list instead of potentially unsafe fiddling with raw pointers).

To post a comment you must log in.
Revision history for this message
mdavidsaver (mdavidsaver) wrote :

The MSI test still passes: https://travis-ci.org/mdavidsaver/epics-base/builds/479219104

There is nothing here which I don't like, so I'm inclined to merge this.

There is a more which could be done to eliminate the remaining explicit deletes.

1. The inputData struct is only ever instantiated in main(). So it could be made a local, and the inputCloseAllFiles() call moved to ~inputData.

2. Similarly for subInfo, which is also only instantiated in main().

3. subInfo::psubFile cries out for auto_ptr.

review: Approve
Revision history for this message
Ralph Lange (ralph-lange) wrote :

Looks good to me, too.

Re 3. It would feel somewhat weird to introduce a concept that immediately creates deprecation warnings. I know it's a catch 22, just saying.

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

On 1/14/19 12:57 AM, Ralph Lange wrote:
> Looks good to me, too.
>
> Re 3. It would feel somewhat weird to introduce a concept that immediately creates deprecation warnings. I know it's a catch 22, just saying.

It feels weird that in 2019 we're still at c++98 with no timetable to moving forward.

Revision history for this message
Andrew Johnson (anj) wrote :

I'm very happy to see this work being done, thanks Martin. I haven't looked at the code yet and I don't know if/when I'll have time to do so, but since both Ralph and Michael have approved this at least in principle I don't think I need to.

Since msi gets built for host architectures only, its code doesn't have to meet the compiler limitations of our embedded targets. Of course the MSVC C++ compilers have their own limitations so I would like to see this code built against various versions of Visual C++ before merging, but I wouldn't be averse to removing the older versions of that compiler from what we support.

We should probably update and better document our set of pre-requisites anyway. The list in documentation/README.html is out of date in places, and while there are some versions listed on the APS website it would be better to have a definitive list in the source tree.

- Andrew

Revision history for this message
Martin Konrad (info-martin-konrad) wrote :

While making the modifications on this branch I was also wondering if we could relax the C++98 requirement for host-only applications like MSI. Some newer C++ features would in fact come quite handy. Initializer lists, range-based for loops, std::forward_list, const iterators, and the auto keyword are all C++03/11 features - not to mention move semantics and std::regex which might allow us to simplify the code for parsing significantly without adding any new dependencies.

Which C++ version do you think would be realistic?

BTW: I successfully built this branch with MSVC++ 14.16 (a.k.a. Visual Studio 2017 v15.9) - all MSI tests passed.

Revision history for this message
Andrew Johnson (anj) wrote :

Any kind of 'what should we be supporting' question should really go to tech-talk, explaining what the issues are and what software and targets would be affected. I'm not saying that *anyone* using EPICS has veto power over this kind of decision, but we should let the community know and allow them some input before we do something that might have effects we don't know about.

From the perspective of the APS Controls group I would want to be able to use the standard version of G++ that comes with RHEL-7.4 (or maybe 7.5 but I'd have to check with IT to see what the oldest RHEL version is that we have installed on our systems). Other groups may have other criteria, such as they need PVA and the latest AreaDetector to run IOCs on a Linux or Windows CPU that comes built into some commercial detector box whose manufacturer only provides <whatever>.

Revision history for this message
Andrew Johnson (anj) wrote :

Core group: Plans for C++11 support will be announced soon.

Back to MSI: We're considering replacing msi with a Perl version, Benjamin Franksen has offered, which would save having to maintain this complex code. However we don't expect it soon, and it might never happen in which case we will revisit this merge request (please don't delete this MR yet, we may add your test case as a TODO to msi.plt).

Revision history for this message
Martin Konrad (info-martin-konrad) wrote :

Any news on the Perl version? If we can't retire the C++ version, yet, I would appreciate if we could get this or the smaller version (https://code.launchpad.net/~info-martin-konrad/epics-base/+git/epics-base/+merge/361500) merged.

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

> Any news on the Perl version?

I may have been a bit hasty with my offer to re-write it in Perl. It is less a matter of time and effort and more one of motivation. Perl is fun for quick and dirty data mining, but doing serious reliable parsing with it is not (IME). C/C++ may not be ideal for this task but at least it is statically typed. Anyway, a rewrite in Perl will need serious real-life testing over an extended period before it can replace the existing code, so I see no good reason to hold back on merging your fixes.

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

Andrew/Ralph, share we reconsider merging this work? (maybe against 7.0 if you have concerns wrt. 3.15)

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

Having heard no objections, I'm going to merge this into 3.15.

Revision history for this message
Andrew Johnson (anj) wrote :

While merging 3.15 up to the 7.0 branch I noticed that Martin removed the inclusion of errlog.h and the call to errlogFlush() at the end of the main() routine. While there are no calls to errlog routines in the msi.cpp file, the macLib routines *do* call errlogPrintf() in the event that errors are detected, so the errlogFlush() is still necessary. I will restore it.

Revision history for this message
Martin Konrad (info-martin-konrad) wrote :

Good catch. Some day I will write a proper C++ wrapper around the EPICS logger that flushes automatically when it gets destroyed. I'm actually using something like that in some of my support modules: https://gist.github.com/mark0n/d36292ac2640b96c433892720bacd032. But I guess as long as we still have code using the old C API I guess I have to be careful...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/src/ioc/dbtemplate/msi.cpp b/src/ioc/dbtemplate/msi.cpp
2index 9a370e8..49d8611 100644
3--- a/src/ioc/dbtemplate/msi.cpp
4+++ b/src/ioc/dbtemplate/msi.cpp
5@@ -10,6 +10,7 @@
6 /* msi - macro substitutions and include */
7
8 #include <string>
9+#include <list>
10
11 #include <stdlib.h>
12 #include <stddef.h>
13@@ -20,8 +21,6 @@
14
15 #include <dbDefs.h>
16 #include <macLib.h>
17-#include <ellLib.h>
18-#include <errlog.h>
19 #include <epicsString.h>
20 #include <osiFileName.h>
21 #include <osiUnistd.h>
22@@ -68,10 +67,10 @@ static void inputErrPrint(const inputData * const pvt);
23 /* Module to read the substitution file */
24 typedef struct subInfo subInfo;
25
26-static void substituteOpen(subInfo **ppvt, char * const substitutionName);
27+static void substituteOpen(subInfo **ppvt, const std::string& substitutionName);
28 static void substituteDestruct(subInfo * const pvt);
29-static int substituteGetNextSet(subInfo * const pvt, char **filename);
30-static int substituteGetGlobalSet(subInfo * const pvt);
31+static bool substituteGetNextSet(subInfo * const pvt, char **filename);
32+static bool substituteGetGlobalSet(subInfo * const pvt);
33 static const char *substituteGetReplacements(subInfo * const pvt);
34 static const char *substituteGetGlobalReplacements(subInfo * const pvt);
35
36@@ -86,7 +85,7 @@ static void makeSubstitutions(inputData * const inputPvt,
37
38 /*Global variables */
39 static int opt_V = 0;
40-static int opt_D = 0;
41+static bool opt_D = false;
42
43 static char *outFile = 0;
44 static int numDeps = 0, depHashes[MAX_DEPS];
45@@ -97,23 +96,21 @@ int main(int argc,char **argv)
46 inputData *inputPvt;
47 MAC_HANDLE *macPvt;
48 char *pval;
49- int narg;
50- char *substitutionName = 0;
51+ std::string substitutionName;
52 char *templateName = 0;
53- int i;
54- int localScope = 1;
55+ bool localScope = true;
56
57 inputConstruct(&inputPvt);
58 macCreateHandle(&macPvt, 0);
59 while ((argc > 1) && (argv[1][0] == '-')) {
60- narg = (strlen(argv[1]) == 2) ? 2 : 1;
61+ int narg = (strlen(argv[1]) == 2) ? 2 : 1;
62 pval = (narg == 1) ? (argv[1] + 2) : argv[2];
63
64 if (strncmp(argv[1], "-I", 2) == 0) {
65 inputAddPath(inputPvt, pval);
66 }
67 else if (strcmp(argv[1], "-D") == 0) {
68- opt_D = 1;
69+ opt_D = true;
70 narg = 1; /* no argument for this option */
71 }
72 else if(strncmp(argv[1], "-o", 2) == 0) {
73@@ -123,14 +120,14 @@ int main(int argc,char **argv)
74 addMacroReplacements(macPvt, pval);
75 }
76 else if(strncmp(argv[1], "-S", 2) == 0) {
77- substitutionName = epicsStrDup(pval);
78+ substitutionName = pval;
79 }
80 else if (strcmp(argv[1], "-V") == 0) {
81 opt_V = 1;
82 narg = 1; /* no argument for this option */
83 }
84 else if (strcmp(argv[1], "-g") == 0) {
85- localScope = 0;
86+ localScope = false;
87 narg = 1; /* no argument for this option */
88 }
89 else if (strcmp(argv[1], "-h") == 0) {
90@@ -142,7 +139,7 @@ int main(int argc,char **argv)
91 }
92
93 argc -= narg;
94- for (i = 1; i < argc; i++)
95+ for (int i = 1; i < argc; i++)
96 argv[i] = argv[i + narg];
97 }
98
99@@ -170,16 +167,16 @@ int main(int argc,char **argv)
100 if (argc == 2)
101 templateName = epicsStrDup(argv[1]);
102
103- if (!substitutionName) {
104+ if (substitutionName.empty()) {
105 STEP("Single template+substitutions file");
106 makeSubstitutions(inputPvt, macPvt, templateName);
107 }
108 else {
109 subInfo *substitutePvt;
110 char *filename = 0;
111- int isGlobal, isFile;
112+ bool isGlobal, isFile;
113
114- STEPS("Substitutions from file", substitutionName);
115+ STEPS("Substitutions from file", substitutionName.c_str());
116 substituteOpen(&substitutePvt, substitutionName);
117 do {
118 isGlobal = substituteGetGlobalSet(substitutePvt);
119@@ -213,14 +210,12 @@ int main(int argc,char **argv)
120 } while (isGlobal || isFile);
121 substituteDestruct(substitutePvt);
122 }
123- errlogFlush();
124 macDeleteHandle(macPvt);
125 inputDestruct(inputPvt);
126 if (opt_D) {
127 printf("\n");
128 }
129 free(templateName);
130- free(substitutionName);
131 return opt_V & 2;
132 }
133
134
135@@ -301,7 +296,6 @@ static void makeSubstitutions(inputData * const inputPvt,
136 if (command) {
137 char *pstart;
138 char *pend;
139- char *copy;
140 int cmdind=-1;
141 int i;
142
143@@ -334,16 +328,15 @@ static void makeSubstitutions(inputData * const inputPvt,
144 /*skip quote and any trailing blanks*/
145 while (*++p == ' ') ;
146 if (*p != '\n' && *p != 0) goto endcmd;
147- copy = static_cast<char *>(calloc(pend-pstart + 1, sizeof(char)));
148- strncpy(copy, pstart, pend-pstart);
149+ std::string copy = std::string(pstart, pend);
150
151 switch(cmdind) {
152 case cmdInclude:
153- inputNewIncludeFile(inputPvt,copy);
154+ inputNewIncludeFile(inputPvt, copy.c_str());
155 break;
156
157 case cmdSubstitute:
158- addMacroReplacements(macPvt,copy);
159+ addMacroReplacements(macPvt, copy.c_str());
160 break;
161
162 default:
163@@ -351,7 +344,6 @@ static void makeSubstitutions(inputData * const inputPvt,
164 inputErrPrint(inputPvt);
165 abortExit(1);
166 }
167- free(copy);
168 expand = 0;
169 }
170
171@@ -370,21 +362,16 @@ endcmd:
172 }
173
174
175 typedef struct inputFile {
176- ELLNODE node;
177- char *filename;
178+ std::string filename;
179 FILE *fp;
180 int lineNum;
181 } inputFile;
182
183-typedef struct pathNode {
184- ELLNODE node;
185- char *directory;
186-} pathNode;
187-
188 struct inputData {
189- ELLLIST inputFileList;
190- ELLLIST pathList;
191+ std::list<inputFile> inputFileList;
192+ std::list<std::string> pathList;
193 char inputBuffer[MAX_BUFFER_SIZE];
194+ inputData() { memset(inputBuffer, 0, sizeof(inputBuffer) * sizeof(inputBuffer[0])); };
195 };
196
197 static void inputOpenFile(inputData *pinputData, const char * const filename);
198@@ -393,66 +380,49 @@ static void inputCloseAllFiles(inputData *pinputData);
199
200 static void inputConstruct(inputData **ppvt)
201 {
202- inputData *pinputData;
203-
204- pinputData = static_cast<inputData *>(calloc(1, sizeof(inputData)));
205- ellInit(&pinputData->inputFileList);
206- ellInit(&pinputData->pathList);
207- *ppvt = pinputData;
208+ *ppvt = new inputData;
209 }
210
211 static void inputDestruct(inputData * const pinputData)
212 {
213- pathNode *ppathNode;
214-
215 inputCloseAllFiles(pinputData);
216- while ((ppathNode = (pathNode *) ellFirst(&pinputData->pathList))) {
217- ellDelete(&pinputData->pathList, &ppathNode->node);
218- free(ppathNode->directory);
219- free(ppathNode);
220- }
221- free(pinputData);
222+ delete(pinputData);
223 }
224
225
226 static void inputAddPath(inputData * const pinputData, const char * const path)
227 {
228- ELLLIST *ppathList = &pinputData->pathList;
229- pathNode *ppathNode;
230 const char *pcolon;
231 const char *pdir;
232 size_t len;
233- int emptyName;
234 const char sep = *OSI_PATH_LIST_SEPARATOR;
235
236 ENTER;
237 pdir = path;
238 /*an empty name at beginning, middle, or end means current directory*/
239 while (pdir && *pdir) {
240- emptyName = ((*pdir == sep) ? 1 : 0);
241+ bool emptyName = (*pdir == sep);
242 if (emptyName) ++pdir;
243
244- ppathNode = (pathNode *) calloc(1, sizeof(pathNode));
245- ellAdd(ppathList, &ppathNode->node);
246-
247+ std::string directory;
248 if (!emptyName) {
249 pcolon = strchr(pdir, sep);
250 len = (pcolon ? (pcolon - pdir) : strlen(pdir));
251 if (len > 0) {
252- ppathNode->directory = (char *) calloc(len + 1, sizeof(char));
253- strncpy(ppathNode->directory, pdir, len);
254+ directory = std::string(pdir, len);
255 pdir = pcolon;
256 /*unless at end skip past first colon*/
257 if (pdir && *(pdir + 1) != 0) ++pdir;
258 }
259 else { /*must have been trailing : */
260- emptyName = 1;
261+ emptyName = true;
262 }
263 }
264
265 if (emptyName) {
266- ppathNode->directory = (char *) calloc(2, sizeof(char));
267- strcpy(ppathNode->directory, ".");
268+ directory = ".";
269 }
270+
271+ pinputData->pathList.push_back(directory);
272 }
273 EXIT;
274 }
275@@ -467,14 +437,14 @@ static void inputBegin(inputData * const pinputData, const char * const fileName
276
277 static char *inputNextLine(inputData * const pinputData)
278 {
279- inputFile *pinputFile;
280- char *pline;
281+ std::list<inputFile>& inFileList = pinputData->inputFileList;
282
283 ENTER;
284- while ((pinputFile = (inputFile *) ellFirst(&pinputData->inputFileList))) {
285- pline = fgets(pinputData->inputBuffer, MAX_BUFFER_SIZE, pinputFile->fp);
286+ while (!inFileList.empty()) {
287+ inputFile& inFile = inFileList.front();
288+ char *pline = fgets(pinputData->inputBuffer, MAX_BUFFER_SIZE, inFile.fp);
289 if (pline) {
290- ++pinputFile->lineNum;
291+ ++inFile.lineNum;
292 EXITS(pline);
293 return pline;
294 }
295@@ -494,23 +464,21 @@ static void inputNewIncludeFile(inputData * const pinputData,
296
297 static void inputErrPrint(const inputData *const pinputData)
298 {
299- inputFile *pinputFile;
300-
301 ENTER;
302 fprintf(stderr, "input: '%s' at ", pinputData->inputBuffer);
303- pinputFile = (inputFile *) ellFirst(&pinputData->inputFileList);
304- while (pinputFile) {
305- fprintf(stderr, "line %d of ", pinputFile->lineNum);
306+ const std::list<inputFile>& inFileList = pinputData->inputFileList;
307+ std::list<inputFile>::const_iterator inFileIt = inFileList.begin();
308+ while (inFileIt != inFileList.end()) {
309+ fprintf(stderr, "line %d of ", inFileIt->lineNum);
310
311- if (pinputFile->filename) {
312- fprintf(stderr, " file %s\n", pinputFile->filename);
313+ if (!inFileIt->filename.empty()) {
314+ fprintf(stderr, " file %s\n", inFileIt->filename.c_str());
315 }
316 else {
317 fprintf(stderr, "stdin:\n");
318 }
319
320- pinputFile = (inputFile *) ellNext(&pinputFile->node);
321- if (pinputFile) {
322+ if (++inFileIt != inFileList.end()) {
323 fprintf(stderr, " included from ");
324 }
325 else {
326@@ -523,10 +491,9 @@ static void inputErrPrint(const inputData *const pinputData)
327
328
329 static void inputOpenFile(inputData *pinputData, const char * const filename)
330 {
331- ELLLIST *ppathList = &pinputData->pathList;
332- pathNode *ppathNode = 0;
333- inputFile *pinputFile;
334- char *fullname = 0;
335+ std::list<std::string>& pathList = pinputData->pathList;
336+ std::list<std::string>::iterator pathIt = pathList.end();
337+ std::string fullname;
338 FILE *fp = 0;
339
340 ENTER;
341@@ -534,24 +501,19 @@ static void inputOpenFile(inputData *pinputData, const char * const filename)
342 STEP("Using stdin");
343 fp = stdin;
344 }
345- else if ((ellCount(ppathList) == 0) || strchr(filename, '/')){
346+ else if (pathList.empty() || strchr(filename, '/')){
347 STEPS("Opening ", filename);
348 fp = fopen(filename, "r");
349 }
350 else {
351- ppathNode = (pathNode *) ellFirst(ppathList);
352- while (ppathNode) {
353- fullname = static_cast<char *>(calloc(strlen(filename) + strlen(ppathNode->directory) + 2,
354- sizeof(char)));
355- strcpy(fullname, ppathNode->directory);
356- strcat(fullname, "/");
357- strcat(fullname, filename);
358+ pathIt = pathList.begin();
359+ while(pathIt != pathList.end()) {
360+ fullname = *pathIt + "/" + filename;
361 STEPS("Trying", filename);
362- fp = fopen(fullname, "r");
363+ fp = fopen(fullname.c_str(), "r");
364 if (fp)
365 break;
366- free(fullname);
367- ppathNode = (pathNode *) ellNext(&ppathNode->node);
368+ ++pathIt;
369 }
370 }
371
372@@ -562,20 +524,20 @@ static void inputOpenFile(inputData *pinputData, const char * const filename)
373 }
374
375 STEP("File opened");
376- pinputFile = static_cast<inputFile *>(calloc(1, sizeof(inputFile)));
377+ inputFile inFile = inputFile();
378
379- if (ppathNode) {
380- pinputFile->filename = fullname;
381+ if (pathIt != pathList.end()) {
382+ inFile.filename = fullname;
383 }
384 else if (filename) {
385- pinputFile->filename = epicsStrDup(filename);
386+ inFile.filename = filename;
387 }
388 else {
389- pinputFile->filename = epicsStrDup("stdin");
390+ inFile.filename = "stdin";
391 }
392
393 if (opt_D) {
394- int hash = epicsStrHash(pinputFile->filename, 12345);
395+ int hash = epicsStrHash(inFile.filename.c_str(), 12345);
396 int i = 0;
397 int match = 0;
398
399@@ -588,7 +550,7 @@ static void inputOpenFile(inputData *pinputData, const char * const filename)
400 if (!match) {
401 const char *wrap = numDeps ? " \\\n" : "";
402
403- printf("%s %s", wrap, pinputFile->filename);
404+ printf("%s %s", wrap, inFile.filename.c_str());
405 if (numDeps < MAX_DEPS) {
406 depHashes[numDeps++] = hash;
407 }
408@@ -599,33 +561,29 @@ static void inputOpenFile(inputData *pinputData, const char * const filename)
409 }
410 }
411
412- pinputFile->fp = fp;
413- ellInsert(&pinputData->inputFileList, 0, &pinputFile->node);
414+ inFile.fp = fp;
415+ pinputData->inputFileList.push_front(inFile);
416 EXIT;
417 }
418
419 static void inputCloseFile(inputData *pinputData)
420 {
421- inputFile *pinputFile;
422-
423+ std::list<inputFile>& inFileList = pinputData->inputFileList;
424 ENTER;
425- pinputFile = (inputFile *) ellFirst(&pinputData->inputFileList);
426- if (pinputFile) {
427- ellDelete(&pinputData->inputFileList, &pinputFile->node);
428- if (fclose(pinputFile->fp))
429- fprintf(stderr, "msi: Can't close input file '%s'\n", pinputFile->filename);
430- free(pinputFile->filename);
431- free(pinputFile);
432+ if(!inFileList.empty()) {
433+ inputFile& inFile = inFileList.front();
434+ if (fclose(inFile.fp))
435+ fprintf(stderr, "msi: Can't close input file '%s'\n", inFile.filename.c_str());
436+ inFileList.erase(inFileList.begin());
437 }
438 EXIT;
439 }
440
441 static void inputCloseAllFiles(inputData *pinputData)
442 {
443- inputFile *pinputFile;
444-
445 ENTER;
446- while ((pinputFile = (inputFile *) ellFirst(&pinputData->inputFileList))) {
447+ const std::list<inputFile>& inFileList = pinputData->inputFileList;
448+ while(!inFileList.empty()) {
449 inputCloseFile(pinputData);
450 }
451 EXIT;
452@@ -637,7 +595,7 @@ typedef enum {
453 } tokenType;
454
455 typedef struct subFile {
456- char *substitutionName;
457+ std::string substitutionName;
458 FILE *fp;
459 int lineNum;
460 char inputBuffer[MAX_BUFFER_SIZE];
461@@ -646,19 +604,15 @@ typedef struct subFile {
462 char string[MAX_BUFFER_SIZE];
463 } subFile;
464
465-typedef struct patternNode {
466- ELLNODE node;
467- char *var;
468-} patternNode;
469-
470 struct subInfo {
471 subFile *psubFile;
472- int isFile;
473+ bool isFile;
474 char *filename;
475- int isPattern;
476- ELLLIST patternList;
477+ bool isPattern;
478+ std::list<std::string> patternList;
479 std::string macroReplacements;
480- subInfo() : psubFile(NULL), isFile(0), filename(NULL), isPattern(0) {};
481+ subInfo() : psubFile(NULL), isFile(false), filename(NULL),
482+ isPattern(false) {};
483 };
484
485 static char *subGetNextLine(subFile *psubFile);
486@@ -677,7 +631,7 @@ void freeSubFile(subInfo *psubInfo)
487 if (fclose(psubFile->fp))
488 fprintf(stderr, "msi: Can't close substitution file\n");
489 }
490- free(psubFile);
491+ delete(psubFile);
492 free(psubInfo->filename);
493 psubInfo->psubFile = 0;
494 EXIT;
495@@ -685,15 +639,9 @@ void freeSubFile(subInfo *psubInfo)
496
497 void freePattern(subInfo *psubInfo)
498 {
499- patternNode *ppatternNode;
500-
501 ENTER;
502- while ((ppatternNode = (patternNode *) ellFirst(&psubInfo->patternList))) {
503- ellDelete(&psubInfo->patternList, &ppatternNode->node);
504- free(ppatternNode->var);
505- free(ppatternNode);
506- }
507- psubInfo->isPattern = 0;
508+ psubInfo->patternList.clear();
509+ psubInfo->isPattern = false;
510 EXIT;
511 }
512
513
514@@ -706,7 +654,7 @@ static void substituteDestruct(subInfo * const psubInfo)
515 EXIT;
516 }
517
518-static void substituteOpen(subInfo **ppvt, char * const substitutionName)
519+static void substituteOpen(subInfo **ppvt, const std::string& substitutionName)
520 {
521 subInfo *psubInfo;
522 subFile *psubFile;
523@@ -715,13 +663,12 @@ static void substituteOpen(subInfo **ppvt, char * const substitutionName)
524 ENTER;
525 psubInfo = new subInfo;
526 *ppvt = psubInfo;
527- psubFile = static_cast<subFile *>(calloc(1, sizeof(subFile)));
528+ psubFile = new subFile;
529 psubInfo->psubFile = psubFile;
530- ellInit(&psubInfo->patternList);
531
532- fp = fopen(substitutionName, "r");
533+ fp = fopen(substitutionName.c_str(), "r");
534 if (!fp) {
535- fprintf(stderr, "msi: Can't open file '%s'\n", substitutionName);
536+ fprintf(stderr, "msi: Can't open file '%s'\n", substitutionName.c_str());
537 abortExit(1);
538 }
539
540@@ -734,7 +681,7 @@ static void substituteOpen(subInfo **ppvt, char * const substitutionName)
541 EXIT;
542 }
543
544-static int substituteGetGlobalSet(subInfo * const psubInfo)
545+static bool substituteGetGlobalSet(subInfo * const psubInfo)
546 {
547 subFile *psubFile = psubInfo->psubFile;
548
549@@ -746,17 +693,16 @@ static int substituteGetGlobalSet(subInfo * const psubInfo)
550 strcmp(psubFile->string, "global") == 0) {
551 subGetNextToken(psubFile);
552 EXITD(1);
553- return 1;
554+ return true;
555 }
556
557 EXITD(0);
558- return 0;
559+ return false;
560 }
561
562-static int substituteGetNextSet(subInfo * const psubInfo,char **filename)
563+static bool substituteGetNextSet(subInfo * const psubInfo,char **filename)
564 {
565 subFile *psubFile = psubInfo->psubFile;
566- patternNode *ppatternNode;
567
568 ENTER;
569 *filename = 0;
570@@ -765,7 +711,7 @@ static int substituteGetNextSet(subInfo * const psubInfo,char **filename)
571
572 if (psubFile->token == tokenEOF) {
573 EXITD(0);
574- return 0;
575+ return false;
576 }
577
578 if (psubFile->token == tokenString &&
579@@ -773,7 +719,7 @@ static int substituteGetNextSet(subInfo * const psubInfo,char **filename)
580 size_t len;
581
582 STEP("Parsed 'file'");
583- psubInfo->isFile = 1;
584+ psubInfo->isFile = true;
585 if (subGetNextToken(psubFile) != tokenString) {
586 subFileErrPrint(psubFile, "Parse error, expecting a filename");
587 abortExit(1);
588@@ -808,7 +754,7 @@ static int substituteGetNextSet(subInfo * const psubInfo,char **filename)
589
590 if (psubFile->token == tokenLBrace) {
591 EXITD(1);
592- return 1;
593+ return true;
594 }
595
596 if (psubFile->token == tokenRBrace) {
597@@ -824,7 +770,7 @@ static int substituteGetNextSet(subInfo * const psubInfo,char **filename)
598
599 STEP("Parsed 'pattern'");
600 freePattern(psubInfo);
601- psubInfo->isPattern = 1;
602+ psubInfo->isPattern = true;
603
604 while (subGetNextToken(psubFile) == tokenSeparator);
605
606@@ -834,15 +780,13 @@ static int substituteGetNextSet(subInfo * const psubInfo,char **filename)
607 }
608 STEP("Parsed '{'");
609
610- while (1) {
611+ while (true) {
612 while (subGetNextToken(psubFile) == tokenSeparator);
613
614 if (psubFile->token != tokenString)
615 break;
616
617- ppatternNode = static_cast<patternNode *>(calloc(1, sizeof(patternNode)));
618- ellAdd(&psubInfo->patternList, &ppatternNode->node);
619- ppatternNode->var = epicsStrDup(psubFile->string);
620+ psubInfo->patternList.push_back(psubFile->string);
621 }
622
623 if (psubFile->token != tokenRBrace) {
624@@ -852,7 +796,7 @@ static int substituteGetNextSet(subInfo * const psubInfo,char **filename)
625
626 subGetNextToken(psubFile);
627 EXITD(1);
628- return 1;
629+ return true;
630 }
631
632
633 static const char *substituteGetGlobalReplacements(subInfo * const psubInfo)
634@@ -866,7 +810,7 @@ static const char *substituteGetGlobalReplacements(subInfo * const psubInfo)
635 subGetNextToken(psubFile);
636
637 if (psubFile->token == tokenRBrace && psubInfo->isFile) {
638- psubInfo->isFile = 0;
639+ psubInfo->isFile = false;
640 free(psubInfo->filename);
641 psubInfo->filename = 0;
642 freePattern(psubInfo);
643@@ -884,7 +828,7 @@ static const char *substituteGetGlobalReplacements(subInfo * const psubInfo)
644 return 0;
645 }
646
647- while (1) {
648+ while (true) {
649 switch(subGetNextToken(psubFile)) {
650 case tokenRBrace:
651 subGetNextToken(psubFile);
652@@ -912,7 +856,6 @@ static const char *substituteGetGlobalReplacements(subInfo * const psubInfo)
653 static const char *substituteGetReplacements(subInfo * const psubInfo)
654 {
655 subFile *psubFile = psubInfo->psubFile;
656- patternNode *ppatternNode;
657
658 ENTER;
659 psubInfo->macroReplacements.clear();
660@@ -921,7 +864,7 @@ static const char *substituteGetReplacements(subInfo * const psubInfo)
661 subGetNextToken(psubFile);
662
663 if (psubFile->token==tokenRBrace && psubInfo->isFile) {
664- psubInfo->isFile = 0;
665+ psubInfo->isFile = false;
666 free(psubInfo->filename);
667 psubInfo->filename = 0;
668 freePattern(psubInfo);
669@@ -941,11 +884,12 @@ static const char *substituteGetReplacements(subInfo * const psubInfo)
670 }
671
672 if (psubInfo->isPattern) {
673- int gotFirstPattern = 0;
674+ bool gotFirstPattern = false;
675
676 while (subGetNextToken(psubFile) == tokenSeparator);
677- ppatternNode = (patternNode *) ellFirst(&psubInfo->patternList);
678- while (1) {
679+ std::list<std::string>& patternList = psubInfo->patternList;
680+ std::list<std::string>::iterator patternIt = patternList.begin();
681+ while (true) {
682 if (psubFile->token == tokenRBrace) {
683 subGetNextToken(psubFile);
684 EXITS(psubInfo->macroReplacements.c_str());
685@@ -959,13 +903,13 @@ static const char *substituteGetReplacements(subInfo * const psubInfo)
686
687 if (gotFirstPattern)
688 catMacroReplacements(psubInfo, ",");
689- gotFirstPattern = 1;
690+ gotFirstPattern = true;
691
692- if (ppatternNode) {
693- catMacroReplacements(psubInfo, ppatternNode->var);
694+ if (patternIt != patternList.end()) {
695+ catMacroReplacements(psubInfo, patternIt->c_str());
696 catMacroReplacements(psubInfo, "=");
697 catMacroReplacements(psubInfo, psubFile->string);
698- ppatternNode = (patternNode *) ellNext(&ppatternNode->node);
699+ ++patternIt;
700 }
701 else {
702 subFileErrPrint(psubFile, "Warning, too many values given");
703@@ -974,7 +918,7 @@ static const char *substituteGetReplacements(subInfo * const psubInfo)
704 while (subGetNextToken(psubFile) == tokenSeparator);
705 }
706 }
707- else while(1) {
708+ else while(true) {
709 switch(subGetNextToken(psubFile)) {
710 case tokenRBrace:
711 subGetNextToken(psubFile);
712@@ -1026,7 +970,8 @@ static void subFileErrPrint(subFile *psubFile, const char * message)
713 {
714 fprintf(stderr, "msi: %s\n",message);
715 fprintf(stderr, " in substitution file '%s' at line %d:\n %s",
716- psubFile->substitutionName, psubFile->lineNum, psubFile->inputBuffer);
717+ psubFile->substitutionName.c_str(), psubFile->lineNum,
718+ psubFile->inputBuffer);
719 }
720
721
722
723diff --git a/src/ioc/dbtemplate/test/msi.plt b/src/ioc/dbtemplate/test/msi.plt
724index 332defb..ca87ca1 100644
725--- a/src/ioc/dbtemplate/test/msi.plt
726+++ b/src/ioc/dbtemplate/test/msi.plt
727@@ -11,7 +11,7 @@
728 use strict;
729 use Test;
730
731-BEGIN {plan tests => 11}
732+BEGIN {plan tests => 12}
733
734 # Check include/substitute command model
735 ok(msi('-I .. ../t1-template.txt'), slurp('../t1-result.txt'));
736@@ -56,6 +56,8 @@ ok(msi('-I. -I.. -S ../t10-substitute.txt'), slurp('../t10-result.txt'));
737 # Substitution file, pattern format, with 0 pattern definitions
738 ok(msi('-I. -I.. -S ../t11-substitute.txt'), slurp('../t11-result.txt'));
739
740+# Macros in template-file name populated from environment variable
741+ok(msi('-I. -I.. -S ../t12-substitute.txt', 'TEST_NO=12,PREFIX=t'), slurp('../t12-result.txt'));
742
743 # Test support routines
744
745@@ -68,10 +70,16 @@ sub slurp {
746 }
747
748 sub msi {
749- my ($args) = @_;
750+ my ($args, $envstr) = @_;
751 my $exe = ($^O eq 'MSWin32') || ($^O eq 'cygwin') ? '.exe' : '';
752 my $msi = "./msi-copy$exe";
753 my $result;
754+ my @envs = split(/,/, $envstr);
755+ foreach (@envs)
756+ {
757+ my ($var, $value) = split /=/, $_;
758+ $ENV{$var} = $value;
759+ }
760 if ($args =~ m/-o / && $args !~ m/-D/) {
761 # An empty result is expected
762 $result = `$msi $args`;
763@@ -85,5 +93,10 @@ sub msi {
764 if $result eq '';
765 } while ($result eq '') && (--$count > 0);
766 }
767+ foreach (@envs)
768+ {
769+ my ($var, $value) = split /=/, $_;
770+ delete $ENV{$var};
771+ }
772 return $result;
773 }
774diff --git a/src/ioc/dbtemplate/test/t12-result.txt b/src/ioc/dbtemplate/test/t12-result.txt
775new file mode 100644
776index 0000000..6cfcc57
777--- /dev/null
778+++ b/src/ioc/dbtemplate/test/t12-result.txt
779@@ -0,0 +1,2 @@
780+# comment line
781+a=foo
782diff --git a/src/ioc/dbtemplate/test/t12-substitute.txt b/src/ioc/dbtemplate/test/t12-substitute.txt
783new file mode 100644
784index 0000000..7a26b6f
785--- /dev/null
786+++ b/src/ioc/dbtemplate/test/t12-substitute.txt
787@@ -0,0 +1,3 @@
788+file $(PREFIX)$(TEST_NO)-template.txt {
789+ { a=foo }
790+}
791diff --git a/src/ioc/dbtemplate/test/t12-template.txt b/src/ioc/dbtemplate/test/t12-template.txt
792new file mode 100644
793index 0000000..7958885
794--- /dev/null
795+++ b/src/ioc/dbtemplate/test/t12-template.txt
796@@ -0,0 +1,2 @@
797+# comment line
798+a=$(a)

Subscribers

People subscribed via source and target branches