Merge ~dirk.zimoch/epics-base:FixShellCommands into ~epics-core/epics-base/+git/epics-base:7.0

Proposed by Dirk Zimoch
Status: Merged
Approved by: Andrew Johnson
Approved revision: 4190f38db0d88264e2f88a7962880a6dfea09db6
Merged at revision: f0bbae1767f56875beb6cad9fe3c9088725c7fa3
Proposed branch: ~dirk.zimoch/epics-base:FixShellCommands
Merge into: ~epics-core/epics-base/+git/epics-base:7.0
Diff against target: 349 lines (+96/-15)
12 files modified
modules/database/src/ioc/db/dbAccess.c (+10/-1)
modules/database/src/ioc/db/dbBkpt.c (+12/-0)
modules/database/src/ioc/db/dbNotify.c (+5/-1)
modules/database/src/ioc/db/dbState.c (+6/-0)
modules/database/src/ioc/db/db_test.c (+12/-0)
modules/database/src/ioc/misc/dlload.c (+8/-3)
modules/libcom/src/iocsh/iocsh.h (+7/-0)
modules/libcom/src/iocsh/libComRegister.c (+28/-10)
modules/libcom/src/osi/os/Darwin/osdEnv.c (+1/-0)
modules/libcom/src/osi/os/default/osdEnv.c (+1/-0)
modules/libcom/src/osi/os/iOS/osdEnv.c (+1/-0)
modules/libcom/src/osi/os/vxWorks/osdEnv.c (+5/-0)
Reviewer Review Type Date Requested Status
Andrew Johnson Approve
Review via email: mp+355761@code.launchpad.net

Commit message

Fix several ioc shell functions.

Description of the change

The functions 'echo', 'dlload', 'epicsParamShow', 'setIocLogDisable' and 'errlog' which are available in iocsh were missing in the vxWorks shell. Calling 'errlog' caused a crash because of a thread with that name. These functions have now been added to the vxWorks shell.

Several functions showed buggy behaviour or printed hard to understand error messages when being called without arguments.
* 'epicsEnvSet' without arguments used to set *NULL to *NULL causing problems later
* 'dbLoadDatabase' and 'dbLoadRecords' printed: "dbRead opening file (null)"
* 'dbap', 'dbb' and 'dbd' printed: "Record (null) not found"
* 'gft', 'pft' and 'tpn' printed: "Channel couldn't be created"
* 'dbtpn' printed: "dbtpn: No such channel" but without newline.
They all now print a "Usage: ..." message.
BTW: I found that all the tests for those functions did not include missing (NULL) mandatory string arguments. I suggest to add that case to the tests for every shell function.

Not fixed:
* 'var' is not available on the vxWorks shell but would be difficult to implement.
* 'epicsThreadSleep' called on the vxWorks shell without arguments sleeps for an unpredictable period. This is a problem of using double type arguments on the vxWorks shell.

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

Hi Dirk,

Good idea, a few comments about your specific changes though.

It's only the VxWorks shell (and I guess the RTEMS CEXP shell) that can pass in NULL pointers to some of these commands. Adding the NULL check to any osdEnv.c file except for libcom/src/osi/os/vxWorks/osdEnv.c doesn't make too much sense to me.

I would prefer that we not print anything in dbStateCreate() which is also an API entry point for the dbState subsystem, just return NULL for a NULL input.

Your VxWorks dlload() routine needs a check for a NULL input argument...

Finally a few comments about your Usage messages: Parentheses are not required around arguments to VxWorks or iocsh commands, they're only needed for an IOC using CEXP on RTEMS, and the people who run such IOCs know about the requirement. Parentheses take longer to type and I'd rather we not imply they're needed when they aren't. The iocsh help output shows command usage without parentheses (which are allowed there too), although I wish it prefixed the full command with "Usage:" when shown with argumemts (like you do):

epics> help dbLoadDatabase
dbLoadDatabase 'file name' path substitutions
epics> help dbLoadRecords
dbLoadRecords 'file name' substitutions
epics> help dbb
dbb 'record name'
epics> help dbtpn
dbtpn 'record name' value
epics> help epicsEnvSet
epicsEnvSet name value

That help command only puts quotes around arguments that have spaces in their name, which is different than the requirements of the VxWorks shell (but that is how the iocsh parses arguments). Your use of double-quotes around string arguments shows they are required for VxWorks though, which is fine and correct.

Thanks,

- Andrew

Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote :

Hi Andrew,

It all stared when testing EPICS 7 on vxWorks, thus I am a bit focused on vxWorks.

If you do skip a string argument in iocsh, a NULL is passed to the underlying C function if the *Func wrapper does not handle this case separately.

On a Linux IOC I get this:
epics> epicsEnvSet
Missing environment variable name argument.

So this one indeed prints a good error message.

But:
epics> dbLoadDatabase
filename="../../../src/ioc/dbStatic/dbLexRoutines.c" line number=264
dbRead opening file (null)
epics> dbb
   BKPT> Record (null) not found
epics> gft
Channel couldn't be created
epics> dbtpn
dbtpn: No such channelepics>
epics> dbStateCreate
Segmentation fault

I will remove the message from dbStateCreate().

dlload on vxWorks does already print a useful error message:
-> dlload
epicsLoadLibrary failed: S_ioLib_NO_FILENAME
value = 45 = 0x2d = '-'

But I can add a "Usage:" message as well.

You are right about the (). I will remove them.

I will change the usage messages to remove the () but keep the "". The iocsh users probably know that they are not really necessary. What about optional parameters? Put them in []? But that may confuse the inexperienced user who might then type literal [].

Dirk

Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote :

dbStateFind() is not a shell command but should probably check for NULL as well. I'll add it.

Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote :

'gft', 'pft' and 'tpn' used to print a usage message in 3.14:

usage "pv name","value"

But that had been changed to "Channel couldn't be created" in 3.15.

Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote :

Also 'pft' and 'tpn' had not checked for a 'value' argument.
In a Linux IOC:
epics> pft existing_record
Segmentation fault

Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote :

Apropos epicsEnvSet: I have got requests for a method to remove environment variables. At the moment that is not possible, but would be useful for some scripts. Currently one can only set environment variables to an empty string, but that does not trigger the default value is $(variable=default) expansions.
I propose to use epicsEnvSet with a missing value argument to delete the variable, but I have to check if it possible for all architectures. Some have unsetenv(), some support setenv("variable") without "=value". In vxWorks 5 I had to use a hack (destroy the name by setting the first char to 0).

Should I add that feature here or make a new pull request?

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

I think setting an environment variable to an empty string needs to be allowed (without deleting it). Can I request that you add a new command epicsEnvUnset, and since it will affect more than just VxWorks I think a new merge proposal for that from a separate branch would be appropriate. You can tell Launchpad 'this merge proposal depends on that one having already been applied' to save effort and merge conflicts if you need to.

Thanks!

- Andrew

review: Approve
Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote :

Setting a variable to an empty string would not be a problem even without a separate command, because "" is different from NULL:

Empty string:
epicsEnvSet "var",""

Delete:
epicsEnvSet "var"

(Work for both vxWorks shell and iocsh.)

But I will implement epicsEnvUnset because it is the cleaner interface.

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

I really don't like adding "#ifdef vxWorks" blocks like this does. We have mechanisms that avoid having such blocks of OS-specific code by putting OS-specific routines in OS-specific source files or by defining OS-specific macros. As a result I can do a git grep '#ifdef vxWorks' on the 7.0 tree and the only hits it finds are in test programs. Also some of the additions here are duplicating code from other routines in the same file, which is another code smell from this merge request.

Maybe we could add a macro to iocsh.h for routines that should be exported for VxWorks, so that for example the dlload command could be implemented like this:

static const iocshFuncDef dlloadFuncDef = {"dlload", 1, dlloadArgs};
IOCSH_STATIC_FUNC void dlload(const char* name)
{
    if (!name || !*name) {
        printf("usage: dlload \"file\"\n");
    }
    else if (!epicsLoadLibrary(name)) {
        printf("epicsLoadLibrary failed: %s\n", epicsLoadError());
    }
}
static void dlloadCallFunc(const iocshArgBuf *args)
{
    dlload(args[0].sval);
}
static void dlloadRegistar(void) {

The IOCSH_STATIC_FUNC macro would expand to static EPICS_ALWAYS_INLINE on everything except VxWorks where it would be empty (Hmm, should GeSys users get these routines as well?). The body of the function is no longer duplicated, there's nothing here which is VxWorks-specific, and if GeSys does need these functions we only have to edit the iocsh.h header to make them available there too.

I'm *not* suggesting that we should have a modules/database/src/ioc/misc/os/vxWorks/dlload.c file, that would be taking this too far and I think the macro solution should work fine.

Sorry to go back on my earlier approval...

review: Needs Fixing
Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote :

OK. I didn't like the code duplication either, but I wanted to keep the change minimal.
Concerning #ifdef <ARCH>, I actually like it more than separate files. Why? To avoid code duplication.

I will try to implement some macro magic...

Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote :

Are RTEMS/GeSys users the only ones using Cexp? If not, the functions should probably be global for everyone. How to find out if someone uses GeSys/Cexp? Should I assume this for any RTEMS and RTEMS only, thus making the functions global for vxWorks and RTEMS and static for every other OS?

Can there be a conflict with existing system functions, in particular for 'dlload' and 'echo'? Originally it was this concern why I implemented the functions for vxWorks only.

Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote :

Now I have
#if defined(vxWorks) || defined(__rtems__)
centrally in iocsh.h.
If preferred, I can create separate header files for vxWorks and RTEMS in osi/os/xx instead.

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

Cexp only exists for RTEMS, Till wrote it to replicate the functionality of the VxWorks target shell.

I'm okay with that OS-conditional being in iocsh.h, so this looks good now and I will merge it sometime in the next few days — thanks.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/modules/database/src/ioc/db/dbAccess.c b/modules/database/src/ioc/db/dbAccess.c
2index cd75351..f52b888 100644
3--- a/modules/database/src/ioc/db/dbAccess.c
4+++ b/modules/database/src/ioc/db/dbAccess.c
5@@ -754,13 +754,22 @@ long dbBufferSize(short dbr_type, long options, long no_elements)
6 }
7 int dbLoadDatabase(const char *file, const char *path, const char *subs)
8 {
9+ if (!file) {
10+ printf("Usage: dbLoadDatabase \"file\", \"path\", \"subs\"\n");
11+ return -1;
12+ }
13 return dbReadDatabase(&pdbbase, file, path, subs);
14 }
15
16 int dbLoadRecords(const char* file, const char* subs)
17 {
18- int status = dbReadDatabase(&pdbbase, file, 0, subs);
19+ int status;
20
21+ if (!file) {
22+ printf("Usage: dbLoadRecords \"file\", \"subs\"\n");
23+ return -1;
24+ }
25+ status = dbReadDatabase(&pdbbase, file, 0, subs);
26 if (!status && dbLoadRecordsHook)
27 dbLoadRecordsHook(file, subs);
28 return status;
29diff --git a/modules/database/src/ioc/db/dbBkpt.c b/modules/database/src/ioc/db/dbBkpt.c
30index 8e7bf3a..b3fdd2d 100644
31--- a/modules/database/src/ioc/db/dbBkpt.c
32+++ b/modules/database/src/ioc/db/dbBkpt.c
33@@ -283,6 +283,10 @@ long dbb(const char *record_name)
34 /*
35 * Convert name to address
36 */
37+ if (!record_name) {
38+ printf("Usage: dbb \"record_name\"\n");
39+ return -1;
40+ }
41 status = dbNameToAddr(record_name, &addr);
42 if (status == S_db_notFound)
43 printf(" BKPT> Record %s not found\n", record_name);
44@@ -403,6 +407,10 @@ long dbd(const char *record_name)
45 /*
46 * Convert name to address
47 */
48+ if (!record_name) {
49+ printf("Usage: dbd \"record_name\"\n");
50+ return -1;
51+ }
52 status = dbNameToAddr(record_name, &addr);
53 if (status == S_db_notFound)
54 printf(" BKPT> Record %s not found\n", record_name);
55@@ -846,6 +854,10 @@ long dbap(const char *record_name)
56 /*
57 * Convert name to address
58 */
59+ if (!record_name) {
60+ printf("Usage: dbap \"record_name\"\n");
61+ return -1;
62+ }
63 status = dbNameToAddr(record_name, &addr);
64 if (status == S_db_notFound)
65 printf(" BKPT> Record %s not found\n", record_name);
66diff --git a/modules/database/src/ioc/db/dbNotify.c b/modules/database/src/ioc/db/dbNotify.c
67index c718d37..c2420af 100644
68--- a/modules/database/src/ioc/db/dbNotify.c
69+++ b/modules/database/src/ioc/db/dbNotify.c
70@@ -596,9 +596,13 @@ long dbtpn(char *pname, char *pvalue)
71 tpnInfo *ptpnInfo;
72 processNotify *ppn=NULL;
73
74+ if (!pname) {
75+ printf("Usage: dbtpn \"name\", \"value\"\n");
76+ return -1;
77+ }
78 chan = dbChannelCreate(pname);
79 if (!chan) {
80- printf("dbtpn: No such channel");
81+ printf("dbtpn: No such channel\n");
82 return -1;
83 }
84
85diff --git a/modules/database/src/ioc/db/dbState.c b/modules/database/src/ioc/db/dbState.c
86index 6de7735..60819a0 100644
87--- a/modules/database/src/ioc/db/dbState.c
88+++ b/modules/database/src/ioc/db/dbState.c
89@@ -37,6 +37,9 @@ dbStateId dbStateFind(const char *name)
90 ELLNODE *node;
91 dbStateId id;
92
93+ if (!name)
94+ return NULL;
95+
96 for (node = ellFirst(&states); node; node = ellNext(node)) {
97 id = CONTAINER(node, dbState, node);
98 if (strcmp(id->name, name) == 0)
99@@ -49,6 +52,9 @@ dbStateId dbStateCreate(const char *name)
100 {
101 dbStateId id;
102
103+ if (!name)
104+ return NULL;
105+
106 if ((id = dbStateFind(name)))
107 return id;
108
109diff --git a/modules/database/src/ioc/db/db_test.c b/modules/database/src/ioc/db/db_test.c
110index 8d7ad31..3d536f0 100644
111--- a/modules/database/src/ioc/db/db_test.c
112+++ b/modules/database/src/ioc/db/db_test.c
113@@ -40,6 +40,10 @@ int gft(const char *pname)
114 short type;
115 int i;
116
117+ if (!pname) {
118+ printf("Usage: gft \"pv_name\"\n");
119+ return -1;
120+ }
121 chan = dbChannel_create(pname);
122 if (!chan) {
123 printf("Channel couldn't be created\n");
124@@ -94,6 +98,10 @@ int pft(const char *pname, const char *pvalue)
125 unsigned char charvalue;
126 double doublevalue;
127
128+ if (!pname || !pvalue) {
129+ printf("Usage: pft \"pv_name\", \"value\"\n");
130+ return -1;
131+ }
132 chan = dbChannel_create(pname);
133 if (!chan) {
134 printf("Channel couldn't be created\n");
135@@ -223,6 +231,10 @@ int tpn(const char *pname, const char *pvalue)
136 tpnInfo *ptpnInfo;
137 processNotify *ppn = NULL;
138
139+ if (!pname || !pvalue) {
140+ printf("Usage: tpn \"pv_name\", \"value\"\n");
141+ return -1;
142+ }
143 chan = dbChannel_create(pname);
144 if (!chan) {
145 printf("Channel couldn't be created\n");
146diff --git a/modules/database/src/ioc/misc/dlload.c b/modules/database/src/ioc/misc/dlload.c
147index 5b0591d..ddf04f4 100644
148--- a/modules/database/src/ioc/misc/dlload.c
149+++ b/modules/database/src/ioc/misc/dlload.c
150@@ -9,14 +9,19 @@
151 #include "iocsh.h"
152 #include "epicsExport.h"
153
154+IOCSH_STATIC_FUNC void dlload(const char* name)
155+{
156+ if (!epicsLoadLibrary(name)) {
157+ printf("epicsLoadLibrary failed: %s\n", epicsLoadError());
158+ }
159+}
160+
161 static const iocshArg dlloadArg0 = { "path/library.so", iocshArgString};
162 static const iocshArg * const dlloadArgs[] = {&dlloadArg0};
163 static const iocshFuncDef dlloadFuncDef = {"dlload", 1, dlloadArgs};
164 static void dlloadCallFunc(const iocshArgBuf *args)
165 {
166- if (!epicsLoadLibrary(args[0].sval)) {
167- printf("epicsLoadLibrary failed: %s\n", epicsLoadError());
168- }
169+ dlload(args[0].sval);
170 }
171
172 static void dlloadRegistar(void) {
173diff --git a/modules/libcom/src/iocsh/iocsh.h b/modules/libcom/src/iocsh/iocsh.h
174index 3ef3d95..84b38f2 100644
175--- a/modules/libcom/src/iocsh/iocsh.h
176+++ b/modules/libcom/src/iocsh/iocsh.h
177@@ -14,8 +14,15 @@
178 #define INCiocshH
179
180 #include <stdio.h>
181+#include "compilerDependencies.h"
182 #include "shareLib.h"
183
184+#if defined(vxWorks) || defined(__rtems__)
185+#define IOCSH_STATIC_FUNC
186+#else
187+#define IOCSH_STATIC_FUNC static EPICS_ALWAYS_INLINE
188+#endif
189+
190 #ifdef __cplusplus
191 extern "C" {
192 #endif
193diff --git a/modules/libcom/src/iocsh/libComRegister.c b/modules/libcom/src/iocsh/libComRegister.c
194index d3a5cfa..88abdc6 100644
195--- a/modules/libcom/src/iocsh/libComRegister.c
196+++ b/modules/libcom/src/iocsh/libComRegister.c
197@@ -27,6 +27,7 @@
198 #include "libComRegister.h"
199
200
201+/* date */
202 void date(const char *format)
203 {
204 epicsTimeStamp now;
205@@ -42,7 +43,6 @@ void date(const char *format)
206 puts(nowText);
207 }
208
209-/* date */
210 static const iocshArg dateArg0 = { "format",iocshArgString};
211 static const iocshArg * const dateArgs[] = {&dateArg0};
212 static const iocshFuncDef dateFuncDef = {"date", 1, dateArgs};
213@@ -52,13 +52,8 @@ static void dateCallFunc (const iocshArgBuf *args)
214 }
215
216 /* echo */
217-static const iocshArg echoArg0 = { "string",iocshArgString};
218-static const iocshArg * const echoArgs[1] = {&echoArg0};
219-static const iocshFuncDef echoFuncDef = {"echo",1,echoArgs};
220-static void echoCallFunc(const iocshArgBuf *args)
221+IOCSH_STATIC_FUNC void echo(char* str)
222 {
223- char *str = args[0].sval;
224-
225 if (str)
226 dbTranslateEscape(str, str); /* in-place is safe */
227 else
228@@ -66,6 +61,14 @@ static void echoCallFunc(const iocshArgBuf *args)
229 printf("%s\n", str);
230 }
231
232+static const iocshArg echoArg0 = { "string",iocshArgString};
233+static const iocshArg * const echoArgs[1] = {&echoArg0};
234+static const iocshFuncDef echoFuncDef = {"echo",1,echoArgs};
235+static void echoCallFunc(const iocshArgBuf *args)
236+{
237+ echo(args[0].sval);
238+}
239+
240 /* chdir */
241 static const iocshArg chdirArg0 = { "directory name",iocshArgString};
242 static const iocshArg * const chdirArgs[1] = {&chdirArg0};
243@@ -111,10 +114,15 @@ static void epicsEnvSetCallFunc(const iocshArgBuf *args)
244 }
245
246 /* epicsParamShow */
247+IOCSH_STATIC_FUNC void epicsParamShow()
248+{
249+ epicsPrtEnvParams ();
250+}
251+
252 static const iocshFuncDef epicsParamShowFuncDef = {"epicsParamShow",0,NULL};
253 static void epicsParamShowCallFunc(const iocshArgBuf *args)
254 {
255- epicsPrtEnvParams ();
256+ epicsParamShow ();
257 }
258
259 /* epicsPrtEnvParams */
260@@ -148,12 +156,17 @@ static void iocLogInitCallFunc(const iocshArgBuf *args)
261 }
262
263 /* iocLogDisable */
264+IOCSH_STATIC_FUNC void setIocLogDisable(int val)
265+{
266+ iocLogDisable = val;
267+}
268+
269 static const iocshArg iocLogDisableArg0 = {"(0,1)=>(false,true)",iocshArgInt};
270 static const iocshArg * const iocLogDisableArgs[1] = {&iocLogDisableArg0};
271 static const iocshFuncDef iocLogDisableFuncDef = {"setIocLogDisable",1,iocLogDisableArgs};
272 static void iocLogDisableCallFunc(const iocshArgBuf *args)
273 {
274- iocLogDisable = args[0].ival;
275+ setIocLogDisable(args[0].ival);
276 }
277
278 /* iocLogShow */
279@@ -197,12 +210,17 @@ static void errlogInit2CallFunc(const iocshArgBuf *args)
280 }
281
282 /* errlog */
283+IOCSH_STATIC_FUNC void errlog(const char *message)
284+{
285+ errlogPrintfNoConsole("%s\n", message);
286+}
287+
288 static const iocshArg errlogArg0 = { "message",iocshArgString};
289 static const iocshArg * const errlogArgs[1] = {&errlogArg0};
290 static const iocshFuncDef errlogFuncDef = {"errlog",1,errlogArgs};
291 static void errlogCallFunc(const iocshArgBuf *args)
292 {
293- errlogPrintfNoConsole("%s\n", args[0].sval);
294+ errlog(args[0].sval);
295 }
296
297 /* iocLogPrefix */
298diff --git a/modules/libcom/src/osi/os/Darwin/osdEnv.c b/modules/libcom/src/osi/os/Darwin/osdEnv.c
299index ab3f936..e6206c3 100644
300--- a/modules/libcom/src/osi/os/Darwin/osdEnv.c
301+++ b/modules/libcom/src/osi/os/Darwin/osdEnv.c
302@@ -35,6 +35,7 @@
303 */
304 epicsShareFunc void epicsShareAPI epicsEnvSet (const char *name, const char *value)
305 {
306+ if (!name) return;
307 iocshEnvClear(name);
308 setenv(name, value, 1);
309 }
310diff --git a/modules/libcom/src/osi/os/default/osdEnv.c b/modules/libcom/src/osi/os/default/osdEnv.c
311index 682bcc9..b284a86 100644
312--- a/modules/libcom/src/osi/os/default/osdEnv.c
313+++ b/modules/libcom/src/osi/os/default/osdEnv.c
314@@ -36,6 +36,7 @@ epicsShareFunc void epicsShareAPI epicsEnvSet (const char *name, const char *val
315 {
316 char *cp;
317
318+ if (!name) return;
319 iocshEnvClear(name);
320
321 cp = mallocMustSucceed (strlen (name) + strlen (value) + 2, "epicsEnvSet");
322diff --git a/modules/libcom/src/osi/os/iOS/osdEnv.c b/modules/libcom/src/osi/os/iOS/osdEnv.c
323index a32cce5..fdc4570 100644
324--- a/modules/libcom/src/osi/os/iOS/osdEnv.c
325+++ b/modules/libcom/src/osi/os/iOS/osdEnv.c
326@@ -32,6 +32,7 @@
327 */
328 epicsShareFunc void epicsShareAPI epicsEnvSet (const char *name, const char *value)
329 {
330+ if (!name) return;
331 iocshEnvClear(name);
332 setenv(name, value, 1);
333 }
334diff --git a/modules/libcom/src/osi/os/vxWorks/osdEnv.c b/modules/libcom/src/osi/os/vxWorks/osdEnv.c
335index c81f493..9245ae7 100644
336--- a/modules/libcom/src/osi/os/vxWorks/osdEnv.c
337+++ b/modules/libcom/src/osi/os/vxWorks/osdEnv.c
338@@ -37,6 +37,11 @@ epicsShareFunc void epicsShareAPI epicsEnvSet (const char *name, const char *val
339 {
340 char *cp;
341
342+ if (!name) {
343+ printf ("Usage: epicsEnvSet \"name\", \"value\"\n");
344+ return;
345+ }
346+
347 iocshEnvClear(name);
348
349 cp = mallocMustSucceed (strlen (name) + strlen (value) + 2, "epicsEnvSet");

Subscribers

People subscribed via source and target branches