#ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include #include #include #include #include #include #include #define EPICSVER EPICS_VERSION*10000+EPICS_REVISION*100+EPICS_MODIFICATION #ifdef vxWorks #include "asprintf.h" #ifdef _WRS_VXWORKS_MAJOR /* vxWorks 6+ */ #include #else /* vxWorks 5 */ #include #include "strdup.h" #endif #endif #if defined (_WIN32) #include "asprintf.h" #endif #if (EPICSVER<31400) extern char** ppGlobalEnviron; #define OSI_PATH_LIST_SEPARATOR ":" extern volatile int interruptAccept; #else #include #include #ifndef EPICS_VERSION_INT epicsShareFunc int epicsShareAPI iocshCmd(const char *cmd); #endif #include #endif #include "expr.h" #include "require.h" #define SAVEENV(var) do { old_##var = getenv(#var); if (old_##var) old_##var=strdup(old_##var); } while(0) #define RESTOREENV(var) do { if(old_##var) { putenvprintf("%s=%s", #var, old_##var); free(old_##var); }} while(0) int runScriptDebug=0; int isAbsPath(const char* filename) { #ifdef _WIN32 /* network path, e.g. "\\x03ma\gac-x03ma\Data1" */ if (strncmp(filename, "\\\\", 2) == 0) return 1; /* driver letter, e.g. "C:\Temp" */ else if (strlen(filename) > 3 && isalpha(filename[0]) && filename[1] == ':') return 1; else return 0; #else return filename[0] == '/' ? 1 : 0; #endif } int runScript(const char* filename, const char* args) { MAC_HANDLE *mac = NULL; FILE* file = NULL; char* line_raw = NULL; char* line_exp = NULL; long line_raw_size = 256; long line_exp_size = line_raw_size; long len; char** pairs; int status = 0; char* old_MODULE = NULL; char* old_MODULE_DIR = NULL; if (!filename) { fprintf(stderr, "Usage: runScript filename [macro=value,...]\n"); return -1; } if (macCreateHandle(&mac,( #if (EPICSVER>=31501) const #endif char*[]){ "", "environ", NULL, NULL }) != 0) goto error; macSuppressWarning(mac, 1); #if (EPICSVER<31403) /* Have no environment macro substitution, thus load envionment explicitly */ #ifndef vxWorks /* In 3.14 before 3.14.3 we may have non-vxWorks without environment macro substitution */ /* non-vxWorks systems have environ instead of ppGlobalEnviron */ #define ppGlobalEnviron environ #endif #ifdef _WRS_VXWORKS_MAJOR /* VxWorks 6 bug: environment is not NULL terminated ! */ /* There is a non-public counter 8 bytes after ppGlobalEnviron */ char** endEnviron = ppGlobalEnviron+((unsigned int*)&ppGlobalEnviron)[2]; if (runScriptDebug) printf("runScript: %u environment variables\n", ((unsigned int*)&ppGlobalEnviron)[2]); for (pairs = ppGlobalEnviron; pairs < endEnviron; pairs++) #else for (pairs = ppGlobalEnviron; *pairs; pairs++) #endif { char* var, *eq; if (runScriptDebug) printf("runScript: environ %s\n", *pairs); /* take a copy to replace '=' with null byte */ if ((var = strdup(*pairs)) == NULL) goto error; eq = strchr(var, '='); if (eq) { *eq = 0; macPutValue(mac, var, eq+1); } free(var); } #endif if ((line_exp = malloc(line_exp_size)) == NULL) goto error; if ((line_raw = malloc(line_raw_size)) == NULL) goto error; #ifdef vxWorks /* expand macros (environment variables) in file name because vxWorks shell can't do it */ #if (EPICSVER<31400) /* 3.13 version of macExpandString is broken and may write more than allowed */ while ((len = labs(macExpandString(mac, (char*)filename, line_exp, line_exp_size/2))) >= line_exp_size/2) #else while ((len = labs(macExpandString(mac, filename, line_exp, line_exp_size-1))) >= line_exp_size-2) #endif { if (runScriptDebug) printf("runScript: grow expand buffer: len=%ld size=%ld\n", len, line_exp_size); free(line_exp); if ((line_exp = malloc(line_exp_size *= 2)) == NULL) goto error; } filename = line_exp; #endif /* add args to macro definitions */ if (args) { if (runScriptDebug) printf("runScript: macParseDefns \"%s\"\n", args); macParseDefns(mac, (char*)args, &pairs); macInstallMacros(mac, pairs); free(pairs); } if (isAbsPath(filename)) { file = fopen(filename, "r"); } else { const char* dirname; const char* end; char* fullname; const char* path = getenv("SCRIPT_PATH"); int dirlen; for (dirname = path; dirname != NULL; dirname = end) { end = strchr(dirname, OSI_PATH_LIST_SEPARATOR[0]); if (end && end[1] == '/' && end[2] == '/') /* "http://..." and friends */ end = strchr(end+2, OSI_PATH_LIST_SEPARATOR[0]); if (end) dirlen = (int)(end++ - dirname); else dirlen = (int)strlen(dirname); if (dirlen == 0) continue; /* ignore empty path elements */ if (dirname[dirlen-1] == '/') dirlen--; asprintf(&fullname, "%.*s/%s", dirlen, dirname, filename); if (runScriptDebug) printf("runScript: trying %s\n", fullname); file = fopen(fullname, "r"); if (!file && (errno & 0xffff) != ENOENT) perror(fullname); free(fullname); if (file) break; } } if (file == NULL) { perror(filename); return errno; } /* save some environments variables */ SAVEENV(MODULE); SAVEENV(MODULE_DIR); /* execute script line by line after expanding macros with arguments or environment */ while (fgets(line_raw, line_raw_size, file)) { char* line_start, *x; int fail_ok = 0; /* check if we have a line longer than the buffer size */ while (line_raw[(len = (long)strlen(line_raw))-1] != '\n' && !feof(file)) { if (runScriptDebug) printf("runScript partial line: \"%s\"\n", line_raw); if ((line_raw = realloc(line_raw, line_raw_size *= 2)) == NULL) goto error; if (fgets(line_raw + len, line_raw_size - len, file) == NULL) break; } while (len > 0 && isspace((unsigned char)line_raw[len-1])) line_raw[--len] = 0; /* get rid of '\n' and friends */ /* Remember state of macros in case environment variable gets expanded */ /* This would otherwise "freeze" environment macros to the state of their first expansion */ macPushScope(mac); if (runScriptDebug) printf("runScript raw line (%ld chars): '%s'\n", len, line_raw); /* expand and check the buffer size (different epics versions write different may number of bytes)*/ while ((len = labs(macExpandString(mac, line_raw, line_exp, #if (EPICSVER<31400) /* 3.13 version of macExpandString is broken and may write more than allowed */ line_exp_size/2))) >= line_exp_size/2) #else line_exp_size-1))) >= line_exp_size-2) #endif { if (runScriptDebug) printf("runScript: grow expand buffer: len=%ld size=%ld\n", len, line_exp_size); free(line_exp); if ((line_exp = malloc(line_exp_size *= 2)) == NULL) goto error; } if (runScriptDebug) printf("runScript expanded line (%ld chars): '%s'\n", len, line_exp); macPopScope(mac); line_start = line_exp; while (isspace((unsigned char)*line_start)) line_start++; if (line_start[0] != '#' || line_start[1] != '-') printf("%s\n", line_exp); if (line_start[0] == 0 || line_start[0] == '#') continue; /* find local variable assignments */ if ((x = strpbrk(line_start, "=(, \t\n\r")) != NULL && *x=='=') { *x++ = 0; replaceExpressions(x, line_raw, line_raw_size); if (runScriptDebug) printf("runScript: assign %s=%s\n", line_start, line_raw); macPutValue(mac, line_start, line_raw); continue; } if (*line_start == '?') { fail_ok = 1; line_start++; while (isspace((unsigned char)*line_start)) line_start++; } #ifdef _WRS_VXWORKS_MAJOR if (strlen(line_start) >= 255) { fprintf(stderr, "runScript: Line too long (>=255):\n%s\n", line_start); return -1; } else { SHELL_EVAL_VALUE result; status = shellInterpEvaluate(line_start, "C", &result); } #elif defined(vxWorks) if (strlen(line_start) >= 120) { fprintf(stderr, "runScript: Line too long (>=120):\n%s\n", line_start); return -1; } status = execute(line_start); #else status = iocshCmd(line_start); #endif if (status != 0) { if (fail_ok) fprintf(stderr, "runScript: Error ignored\n"); else { fprintf(stderr, "runScript: Error terminated script %s\n", filename); break; } } } goto end; error: if (errno) { status = errno; perror("runScript"); } end: free(line_raw); free(line_exp); if (mac) macDeleteHandle(mac); if (file) fclose(file); /* restore environment */ RESTOREENV(MODULE); RESTOREENV(MODULE_DIR); return status; } #if (EPICSVER>=31400) epicsExportAddress(int, runScriptDebug); epicsExportAddress(int, exprDebug); static const iocshFuncDef runScriptDef = { "runScript", 2, (const iocshArg *[]) { &(iocshArg) { "filename", iocshArgString }, &(iocshArg) { "substitutions", iocshArgString }, }}; static void runScriptFunc(const iocshArgBuf *args) { runScript(args[0].sval, args[1].sval); } static void runScriptRegister(void) { static int firstTime = 1; if (firstTime) { firstTime = 0; iocshRegister (&runScriptDef, runScriptFunc); } } epicsExportRegistrar(runScriptRegister); #endif