#include #include #include #include #include "sics.h" #include "sicsobj.h" #include "splitter.h" #include "initializer.h" #include "commandlog.h" #include "hipadaba.h" #include "sicshipadaba.h" #include "dynstring.h" #include "devser.h" #include "ascon.h" #include "macro.h" #include "syncedprot.h" #include "scriptcontext.h" typedef struct ContextItem { struct ContextItem *next; Hdb *node; SctController *controller; } ContextItem; typedef struct ScriptContext { ObjectDescriptor *desc; ContextItem *nodes; ContextItem *trash; Hdb *sendNode; int sendCalled; } ScriptContext; struct SctController { DevSer *devser; Hdb *node; /* the controller node */ SConnection *conn; int verbose; FILE *fd; }; /* action data and write callback data */ typedef struct SctData { char *name; SctController *controller; SConnection *conCtx; int answered; int inMacro; Hdb *node; long syncid; } SctData; /* data for updatescript */ typedef struct SctUpdatescript { char *name; SctController *controller; } SctUpdatescript; static ScriptContext *sct = NULL; static SctData *queueData = NULL; static struct { char *name; } actionCallback; static struct SctUpdatescript updatescriptCallback; void PushContext(Hdb * node, SctController * controller) { ContextItem *new; if (sct->trash == NULL) { new = calloc(1, sizeof(*new)); } else { new = sct->trash; sct->trash = sct->trash->next; } new->next = sct->nodes; sct->nodes = new; new->node = node; new->controller = controller; } void PopContext(void) { ContextItem *c; c = sct->nodes; assert(c); sct->nodes = c->next; c->next = sct->trash; sct->trash = c; } void CleanStack(Hdb * node) { ContextItem *s; /* clean context from killed nodes */ for (s = sct->nodes; s != NULL; s = s->next) { if (s->node == node) { s->node = NULL; } if (s->controller->node == node) { s->controller = NULL; } } } static char *GetPropAndNode(Hdb * node, Hdb * cNode, char *key, Hdb **foundNode) { char *val; Hdb *mama; if (key[0] == '@') { /* for keys starting with @ look also at the ancestors */ for (mama = node; mama != NULL; mama = mama->mama) { val = GetHdbProp(mama, key); if (val != NULL) { *foundNode = mama; return val; } } } if (node != NULL) { val = GetHdbProp(node, key); if (val != NULL) { *foundNode = node; return val; } } if (cNode != NULL) { val = GetHdbProp(cNode, key); if (val != NULL) { *foundNode = cNode; return val; } } *foundNode = node; return NULL; } static void SetProp(Hdb * node, Hdb * cNode, char *key, char *value) { Hdb *propNode; char *val; val = GetPropAndNode(node, cNode, key, &propNode); if (value == NULL) { if (val != NULL) { SetHdbProperty(propNode, key, ""); } } else { SetHdbProperty(propNode, key, value); } } static char *GetProp(Hdb * node, Hdb * cNode, char *key) { Hdb *propNode; return GetPropAndNode(node, cNode, key, &propNode); } /* * This is the actual sct command available in scripts. */ int SctCommand(SConnection * con, SicsInterp * sics, void *object, int argc, char *argv[]) { static char value[1024]; char *val; char error[512]; Hdb *node = NULL; Hdb *cNode = NULL; hdbValue v; SctController *controller; assert(sct == object); if (sct->nodes != NULL) { node = sct->nodes->node; controller = sct->nodes->controller; } if (node == NULL || controller == NULL || controller->node == NULL) { SCPrintf(con, eError, "ERROR: %s may be called only in proper context", argv[0]); return 0; } /* * get path (pure sct command) */ if (argc <= 1) { GetHdbPath(node, value, sizeof value); SCWrite(con, value, eValue); return 1; } /* * update command */ if (strcmp(argv[1], "update") == 0) { cloneHdbValue(&node->value, &v); Arg2Text(argc - 2, argv + 2, value, sizeof value); if (!readHdbValue(&v, value, error, 512)) { SCWrite(con, error, eError); return 0; } UpdateHipadabaPar(node, v, con); ReleaseHdbValue(&v); SetHdbProperty(node, "geterror", NULL); return 1; } /* * print */ if (strcmp(argv[1], "print") == 0) { if (queueData != NULL) { queueData->answered = 1; SCsetMacro(con, queueData->inMacro); /* take macro flag stored at set callback */ } Arg2Text(argc - 2, argv + 2, value, sizeof value); SCWrite(con, value, eLog); SCsetMacro(con, 1); /* we are always in Macro */ return 1; } /** * controller */ if (strcmp(argv[1], "controller") == 0) { SCWrite(con, controller->node->name, eValue); return 1; } /* * time stamping */ if (strcmp(argv[1], "utime") == 0) { snprintf(value, 1024, "%.3f", DoubleTime()); if (argc > 2) { /* if no property is given, use only return value */ SetHdbProperty(node, argv[2], value); } /* write time as return value */ SCWrite(con, value, eValue); return 1; } /* * with command (execute a script in the context of an other node) */ if (strcmp(argv[1], "with") == 0) { if (argc < 3) { SCPrintf(con, eError, "ERROR: syntax must be: %s %s node script", argv[0], argv[1]); return 0; } GetHdbPath(node, value, sizeof value); node = FindHdbNode(value, argv[2], con); /* get a relative path */ if (node == NULL) { SCPrintf(con, eError, "ERROR: %s %s: node %s not found", argv[0], argv[1], argv[2]); return 0; } if (SctCallInContext(con, argv[3], node, controller, &val) == 0) { SCPrintf(con, eError, "ERROR: in %s %s %s {%s}: %s", argv[0], argv[1], argv[2], argv[3], val); return 0; } return 1; } /* * parent command (return path of parent node) */ if (strcmp(argv[1], "parent") == 0) { if (argc > 3) { SCPrintf(con, eError, "ERROR: syntax must be: %s %s [path]", argv[0], argv[1]); return 0; } if (argc == 3) { node = FindHdbNode(NULL, argv[2], con); } GetHdbPath(node->mama, value, sizeof value); /* returns an empty string on error */ SCWrite(con, value, eValue); return 1; } /* * send command */ if (strcmp(argv[1], "send") == 0 && argc > 2) { if (sct->sendNode != node) { SCPrintf(con, eError, "ERROR: %s may be called only in action scripts", argv[0]); return 0; } sct->sendCalled = 1; /* continue with property handling */ } /* * property handling */ if (argc == 2) { /* get case */ val = GetProp(node, controller->node, argv[1]); if (val == NULL) { SCPrintf(con, eError, "ERROR: %s %s not found", argv[0], argv[1]); return 0; } SCWrite(con, val, eValue); } else { /* set case */ if (argc == 3) { SetProp(node, controller->node, argv[1], argv[2]); } else { val = Arg2Tcl(argc - 2, argv + 2, value, sizeof value); SetProp(node, controller->node, argv[1], val); if (val != NULL && val != value) free(val); } } return 1; } double secondsOfMinute() { double now=DoubleTime(); return (now/60-floor(now/60))*60; } int SctCallInContext(SConnection * con, char *script, Hdb * node, SctController * controller, char **resPtr) { Tcl_Interp *pTcl = InterpGetTcl(pServ->pSics); int ret, l; char *result = NULL; int iRet = 1; int verbose = controller->verbose; PushContext(node, controller); if (verbose) { SCPrintf(con, eLog, "%6.3f script: %s", secondsOfMinute(), script); } if (controller->fd != NULL) { fprintf(controller->fd,"%6.3f script: %s\n", secondsOfMinute(), script); } MacroPush(con); l = strlen(script); ret = Tcl_EvalEx(pTcl, script, l, 0); result = (char *) Tcl_GetStringResult(pTcl); if (ret != TCL_OK && result[0] != '\0') { if (strncmp(result, "ERROR:", 6) == 0) { /* remove "ERROR:", as it will be added later */ result += 6; if (result[0] == ' ') { result++; } } if (verbose) { SCPrintf(con, eLog, "%6.3f error: %s", secondsOfMinute(), result); } if(controller->fd != NULL){ fprintf(controller->fd, "%6.3f error: %s\n", secondsOfMinute(), result); } iRet = 0; } *resPtr = result; MacroPop(); PopContext(); return iRet; } static int SctMatch(void *data1, void *data2) { SctData *a = data1; SctData *b = data2; return a->node == b->node && strcasecmp(a->name, b->name) == 0; } /* * This routine is running the script chain. It is called repeatedly * with response data from the device serializer (devser). This function * basically: * - Figures out which script to run * - Runs the script * - Based on the result either returns the next string to send to the * device or NULL when the script chain is done with */ static char *SctActionHandler(void *actionData, char *lastReply, int commError) { SctData *data = actionData; Hdb *node = data->node; SctController *controller = data->controller; char *state; char *result; char *script = NULL; char *errorScript = NULL; char *send = NULL; int i; int j; SConnection *con; char eprop[80]; char msg[1024]; char path[MAX_HDB_PATH]; char origScript[80]; char *blank; char *emsg; size_t l; int cnt; int ret; char timeKey[50], timeVal[50]; int iMacro; assert(data->name); if (queueData != NULL && queueData->conCtx != NULL) { con = queueData->conCtx; } else { con = controller->conn; } /* * If this is a followup call, the I/O system will have set the * property result to the data from the device. Read this now and * print it if diagnostics is required. */ script = NULL; if (lastReply != NULL) { SetProp(node, controller->node, "result", lastReply); if (*lastReply != '\0') { if (!commError && controller->verbose) { SCPrintf(con, eLog, "%6.3f reply : %s\n", secondsOfMinute(), lastReply); } if(!commError && controller->fd != NULL) { fprintf(controller->fd, "%6.3f reply : %s\n", secondsOfMinute(), lastReply); } if(data != NULL && data->controller != NULL) { traceIO(data->controller->node->name, "reply:%s", lastReply); } else { traceIO("sctunknown", "reply:%s", lastReply); } } } /* * When this is a followup, we use the content of the * state field as the property storing the next script to * run. If this is the start of a chain this is set to the * data->name which is mostly either read or write */ state = GetProp(node, controller->node, "state"); if (state == NULL || strcasecmp(state, "idle") == 0) { state = data->name; SetProp(node, controller->node, "state", state); } /* * Sometimes one wishes to call multiple scripts in succession * before returning into I/O. Such scripts then do not set the * send property. The loop is taking care of this. Not more * then 10 scripts can be chained in this way. */ for (i = 0; i < 10; i++) { /* * read the script to invoke from the property living * in state */ script = GetProp(node, controller->node, state); if (script == NULL) script = state; snprintf(origScript, sizeof origScript, "%s", script); if (commError) { errorScript = GetProp(node, controller->node, "commerror"); if (errorScript != NULL) script = errorScript; commError = 0; } /* * Set the context and invoke the script */ script = strdup(script); sct->sendNode = node; sct->sendCalled = 0; SyncedBegin(data->syncid); ret = SctCallInContext(con, script, node, controller, &result); SyncedEnd(data->syncid); sct->sendNode = NULL; if (ret == 0) { /* * an error occurred in the script: store error message in * a property, and write the error message the first time it * occurs. * * Replaced <> by - because it messed up XML for Gumtree * Mark Koennecke * replaced by {} MZ */ snprintf(eprop, sizeof eprop, "error_during_%s", data->name); emsg = GetHdbProp(node, eprop); if (emsg == NULL || con != controller->conn) { GetHdbPath(node, path, sizeof path); SCPrintf(con, eLogError, "ERROR: action {%s} in {%s} node %s:\nERROR: %s", data->name, origScript, path, result); if(data != NULL && data->controller != NULL){ traceIO(data->controller->node->name, "ERROR: action {%s} in {%s} node %s:\nERROR: %s", data->name, origScript, path, result); } else { traceIO("sctunknown", "reply:%s", "ERROR: action {%s} in {%s} node %s:\nERROR: %s", data->name, origScript, path, result); } } snprintf(msg, sizeof msg, "{%s} %s", origScript, result); if (strcasecmp(data->name, "read") == 0) { SetHdbProperty(node, "geterror", result); } /* Sanitize the text to reduce TCL problems with unbalanced and substituted items */ for (j = 0; msg[j]; ++j) { switch (msg[j]) { case '{': case '}': case '[': case ']': case '<': case '>': case '\'': case '"': case '$': msg[j] = '_'; } } SetHdbProperty(node, eprop, msg); blank = strchr(origScript, ' '); if (blank != NULL) { l = blank - origScript; } else { l = strlen(origScript); } snprintf(eprop, sizeof eprop, "error_in_%.*s", l, origScript); emsg = GetHdbProp(node, eprop); cnt = 0; if (emsg != NULL) { sscanf(emsg, "%d", &cnt); } cnt++; snprintf(msg, sizeof msg, "%dx {%s}: %s", cnt, origScript, result); /* Sanitize the text to reduce TCL problems with unbalanced and substituted items */ for (j = 0; msg[j]; ++j) { switch (msg[j]) { case '{': case '}': case '[': case ']': case '<': case '>': case '\'': case '"': case '$': msg[j] = '_'; } } SetHdbProperty(node, eprop, msg); send = NULL; free(script); goto finish; } /* * The script executed OK. * The next state is the result */ state = result; /* * if the new state is idle, clean everything up * and terminate the script chain */ if (strcasecmp(state, "idle") == 0 || strcasecmp(state, "unpoll") == 0) { if (queueData == data) { /* it was a write action */ SetHdbProperty(node, "requested", NULL); if (!data->answered) { if (queueData->inMacro == 0) { /* * send an O.k. if there was no other reply on write's */ iMacro = SCinMacro(con); con->iMacro = 0; SCWrite(con, "o.k.", eWarning); SCsetMacro(con, iMacro); } } } snprintf(eprop, sizeof eprop, "error_during_%s", data->name); emsg = GetHdbProp(node, eprop); if (emsg != NULL) { GetHdbPath(node, path, sizeof path); SCPrintf(con, eError, "action {%s}: success after error message (%s)\n %s", data->name, path, emsg); SetHdbProperty(node, eprop, NULL); } snprintf(timeKey, sizeof timeKey, "%s_time", data->name); snprintf(timeVal, sizeof timeVal, "%.3f", DoubleTime()); SetHdbProperty(node, timeKey, timeVal); send = NULL; free(script); goto finish; } /* * set the state property to the next state for * the next invocation of this routine */ SetProp(node, controller->node, "state", state); free(script); script = NULL; /* * If there is data to send, check it and do so */ if (sct->sendCalled) { send = GetProp(node, controller->node, "send"); if (send == NULL) send = ""; if (controller->verbose) { SCPrintf(con, eLog, "%6.3f send : %s", secondsOfMinute(), send); } if (controller->fd != NULL) { fprintf(controller->fd, "%6.3f send : %s\n", secondsOfMinute(), send); } if(data != NULL && data->controller != NULL){ traceIO(data->controller->node->name, "send:%s", send); } else { traceIO("sctunknown", "send:%s", send); } return send; } } SCPrintf(con, eLogError, "ERROR: too many quick scripts chained"); finish: if (strcmp(data->name, "write") == 0) { if (GetHdbProp(node, "writestatus") != NULL) { SetHdbProperty(node, "writestatus", "commandsent"); } } if (strcasecmp(state, "unpoll") == 0) { DevUnschedule(controller->devser, data, SctActionHandler, SctMatch); send = NULL; } SetProp(node, controller->node, "state", "idle"); if (data->conCtx != NULL) { SCDeleteConnection(data->conCtx); data->conCtx = NULL; } return send; } static char *SctWriteHandler(void *actionData, char *lastReply, int commError) { SctData *data = actionData; SctData *old; char *result; old = queueData; queueData = data; result = SctActionHandler(data, lastReply, commError); queueData = old; return result; } static int SctMatchNode(void *vNode, void *vData) { Hdb *node = vNode; SctData *d = vData; return node == d->node; } static char * SctDataInfo(void *d) { SctData *data = d; char text[256]; int l; snprintf(text, sizeof text, "%s ", data->name); l = strlen(text); GetHdbPath(data->node, text + l, sizeof text - l); return strdup(text); } static void SctEndData(void *d) { /* no kill, only decrement sync counter */ SctData *data = d; if (data->syncid > 0) { SyncedDecr(data->syncid); data->syncid = SYNCED_NO_ID; } } /* * This is the callback for all nodes participating in the * scriptcontext system */ static hdbCallbackReturn SctMainCallback(Hdb * node, void *userData, hdbMessage * msg) { SctController *controller = userData; hdbDataSearch *dsm; hdbDataMessage *mm; hdbPtrMessage *pm; hdbMessage *km; SConnection *con; char *geterror; char error[256]; if (controller->devser == NULL) { /* defunct controller */ return hdbContinue; } pm = GetKillPtrMessage(msg); if (pm != NULL) { if (controller == pm->pPtr) { return hdbKill; } return hdbContinue; } dsm = GetHdbDataSearchMessage(msg); if (dsm != NULL) { if (dsm->testPtr == controller) { dsm->result = controller; return hdbAbort; } return hdbContinue; } km = GetHdbKillNodeMessage(msg); if (km != NULL) { /* unschedule all actions related to this node */ DevUnschedule(controller->devser, node, SctActionHandler, SctMatchNode); CleanStack(node); return hdbContinue; } mm = GetHdbGetMessage(msg); if (mm != NULL) { con = mm->callData; geterror = GetHdbProp(node, "geterror"); if (geterror != NULL) { snprintf(error,255,"ERROR: %s", geterror); SCWrite(con, error, eError); if (mm->v->dataType == HIPTEXT) { if (mm->v->v.text != NULL) { free(mm->v->v.text); } mm->v->v.text = strdup(error); } return hdbAbort; } return hdbContinue; } return hdbContinue; } /* * This is the callback registered for nodes which * are written too. */ static hdbCallbackReturn SctActionCallback(Hdb * node, void *userData, hdbMessage * msg) { hdbDataSearch *dsm; hdbDataMessage *mm; hdbPtrMessage *pm; SctData *data = userData; Hdb *target; char *script; int l; int iRet; pDynString text; DevPrio prio; char *writeprio; char *error; SConnection *con; char path[MAX_HDB_PATH]; char *sicsCommand; int iMacro; pm = GetKillPtrMessage(msg); if (pm != NULL) { if (data->controller == pm->pPtr) { return hdbKill; } return hdbContinue; } dsm = GetHdbDataSearchMessage(msg); if (dsm != NULL) { if (dsm->testPtr == &actionCallback) { if (strcasecmp(data->name, actionCallback.name) == 0) { dsm->result = data; } return hdbAbort; } return hdbContinue; } mm = GetHdbSetMessage(msg); if (mm != NULL) { con = mm->callData; /* set target value */ text = formatValue(*(mm->v), node); SetHdbProperty(node, "target", GetCharArray(text)); SetHdbProperty(node, "requested", GetCharArray(text)); /* call check script, if available */ script = GetProp(node, data->controller->node, "check"); if (script != NULL) { if (SctCallInContext(con, script, node, data->controller, &error) == 0) { /* SCPrintf(con, eError, "ERROR: in {%s}: %s", script, error); */ SCPrintf(con, eError, "ERROR: %s", error); /* nicer for the user */ SetHdbProperty(node, "target", NULL); SetHdbProperty(node, "requested", NULL); return hdbAbort; } } /* enqueue write action */ writeprio = GetProp(node, data->controller->node, "writeprio"); if (writeprio != NULL) { prio = DevText2Prio(writeprio); if (prio == NullPRIO) { SCPrintf(con, eError, "ERROR: unknown priority: %s", writeprio); prio = WritePRIO; } } else { prio = WritePRIO; } /* * check for duplicate sets on a node. */ if (data->conCtx != NULL) { if (data->inMacro == 0) { GetHdbPath(node, path, sizeof path); SCsetMacro(data->conCtx, 0); SCPrintf(data->conCtx, eWarning, "%s target changed to %s before completion", path, GetCharArray(text)); } /* * the node has already been queued for execution. But as we never * know if the script chain is already running, the only clean * solution is to requeue the node. */ SCDeleteConnection(data->conCtx); } data->conCtx = SCCopyConnection(con); data->answered = 0; data->inMacro = SCinMacro(con); tracePar(node->name,"Queued %s to %s",node->name, GetCharArray(text)); data->syncid = SyncedIncr(0); DevQueue(data->controller->devser, data, prio, SctWriteHandler, SctMatch, SctEndData, SctDataInfo); /* kill function SctEndData does not kill, data is owned by the node (callback list) */ DeleteDynString(text); return hdbContinue; } mm = GetHdbUpdateMessage(msg); if (mm != NULL) { if (queueData != NULL && queueData->conCtx != NULL && queueData->answered != 1) { /* update called from a write action */ text = formatValue(*(mm->v), node); if (queueData->inMacro == 0 && queueData->node == node) { queueData->answered = 1; iMacro = SCinMacro(queueData->conCtx); SCsetMacro(queueData->conCtx, 0); sicsCommand = GetHdbProp(node, "sicscommand"); if (sicsCommand) { SCPrintf(queueData->conCtx, eWarning, "OK: %s %s", sicsCommand, GetCharArray(text)); } else { GetHdbPath(node, path, sizeof path); SCPrintf(queueData->conCtx, eWarning, "OK: hset %s %s", path, GetCharArray(text)); } SCsetMacro(queueData->conCtx, iMacro); } DeleteDynString(text); } return hdbContinue; } return hdbContinue; } static char *ParText(Hdb * cmdNode, char *name, int nPar, char *defaultValue) { Hdb *par; for (par = cmdNode->child; nPar > 0 && par != NULL; par = par->next, nPar--) { if (strcasecmp(par->name, name) == 0) { if (par->value.dataType == HIPTEXT) { return par->value.v.text; } } } return defaultValue; } static double ParValue(Hdb * cmdNode, char *name, int nPar, double defaultValue) { Hdb *par; for (par = cmdNode->child; nPar > 0 && par != NULL; par = par->next, nPar--) { if (strcasecmp(par->name, name) == 0) { switch (par->value.dataType) { case HIPINT: return par->value.v.intValue; case HIPFLOAT: return par->value.v.doubleValue; } } } return defaultValue; } static void SctKillData(void *d) { SctData *data = d; if (data->syncid > 0) { SyncedDecr(data->syncid); data->syncid = SYNCED_NO_ID; } if (data->name) { free(data->name); data->name = NULL; } if (data->conCtx) SCDeleteConnection(data->conCtx); free(data); } static void SctKillCBData(void *d) { SctData *data = d; if (data->controller->devser != NULL) { DevRemoveAction(data->controller->devser, data); } SctKillData(d); } int SctAddPollNode(SctController * controller, Hdb * node, double interval, DevPrio prio, char *action) { SctData *data; hdbCallback *cb; char nodePath[512], info[1024]; if (!FindHdbCallbackData(node, controller)) { cb = MakeHipadabaCallback(SctMainCallback, controller, NULL); assert(cb); AppendHipadabaCallback(node, cb); GetHdbPath(node, nodePath, sizeof nodePath); snprintf(info, 1023, "%s: Not read yet", nodePath); SetHdbProperty(node, "geterror", info); } data = calloc(1, sizeof(*data)); assert(data); data->controller = controller; data->node = node; data->conCtx = NULL; /* we might need a dummy connection here */ data->name = strdup(action); data->syncid = SyncedIncr(0); if (DevSchedule(controller->devser, data, prio, interval, SctActionHandler, SctMatch, SctKillData, SctDataInfo)) { return 1; } else { return 0; } } static int SctPollCmd(pSICSOBJ ccmd, SConnection * con, Hdb * cmdNode, Hdb * par[], int nPar) { Hdb *node; SctController *controller; double interval; char *path; DevPrio prio; char *action; char *prioText; if (nPar < 1) { SCPrintf(con, eError, "ERROR: should be: %s poll ( )", ccmd->objectNode->name); return 0; } controller = ccmd->pPrivate; path = ParText(cmdNode, "node", nPar, ""); node = FindHdbNode(NULL, path, con); if (node == NULL) { SCPrintf(con, eError, "ERROR: %s not found", path); return 0; } interval = ParValue(cmdNode, "interval", nPar, 5); prioText = ParText(cmdNode, "prio", nPar, "read"); prio = DevText2Prio(prioText); if (prio == NullPRIO) { SCPrintf(con, eError, "ERROR: unknown priority: %s", prioText); return 0; } action = ParText(cmdNode, "action", nPar, "read"); if (SctAddPollNode(controller, node, interval, prio, action)) { SCPrintf(con, eValue, "%s poll registered on %s (%g sec, %s prio)", action, path, interval, prioText); } else { SCPrintf(con, eValue, "%s poll on %s changed to %g sec, %s prio", action, path, interval, prioText); } return 1; } static int SctUnpollCmd(pSICSOBJ ccmd, SConnection * con, Hdb * cmdNode, Hdb * par[], int nPar) { SctController *controller; double interval; char *path; DevPrio prio; char *prioText; SctData data; if (nPar < 1) { SCPrintf(con, eError, "ERROR: should be: %s unpoll ()", ccmd->objectNode->name); return 0; } controller = ccmd->pPrivate; path = ParText(cmdNode, "node", nPar, ""); data.node = FindHdbNode(NULL, path, con); if (data.node == NULL) { SCPrintf(con, eError, "ERROR: %s not found", path); return 0; } data.name = ParText(cmdNode, "action", nPar, "read"); if (DevUnschedule(controller->devser, &data, SctActionHandler, SctMatch) == 0) { SCPrintf(con, eValue, "%s poll not found on %s", data.name, path); } else { SCSendOK(con); } return 1; } static int SctConnectCmd(pSICSOBJ ccmd, SConnection * con, Hdb * cmdNode, Hdb * par[], int nPar) { Hdb *node; SctController *controller; char *path; hdbCallback *cb; if (nPar < 1) { SCPrintf(con, eError, "ERROR: should be: %s connect ", ccmd->objectNode->name); return 0; } controller = ccmd->pPrivate; path = ParText(cmdNode, "node", nPar, ""); node = FindHdbNode(NULL, path, con); if (node == NULL) { SCPrintf(con, eError, "ERROR: %s not found", path); return 0; } if (!FindHdbCallbackData(node, controller)) { cb = MakeHipadabaCallback(SctMainCallback, controller, NULL); assert(cb); AppendHipadabaCallback(node, cb); } SCSendOK(con); return 1; } int SctAddWriteNode(SctController * controller, Hdb * node) { hdbCallback *cb; SctData *data; if (!FindHdbCallbackData(node, controller)) { cb = MakeHipadabaCallback(SctMainCallback, controller, NULL); assert(cb); AppendHipadabaCallback(node, cb); } actionCallback.name = "write"; /* local, so no strdup here */ data = FindHdbCallbackData(node, &actionCallback); if (data != NULL) { return 0; } data = calloc(1, sizeof(*data)); assert(data); data->controller = controller; data->node = node; data->conCtx = NULL; data->name = strdup("write"); cb = MakeHipadabaCallback(SctActionCallback, data, SctKillCBData); assert(cb); data->node = node; AppendHipadabaCallback(node, cb); RemoveSetUpdateCallback(node); return 1; } static int SctWriteCmd(pSICSOBJ ccmd, SConnection * con, Hdb * cmdNode, Hdb * par[], int nPar) { Hdb *node; SctController *controller; double interval; char *path; if (nPar < 1) { SCPrintf(con, eError, "ERROR: should be: %s write ", ccmd->objectNode->name); return 0; } controller = ccmd->pPrivate; path = ParText(cmdNode, "node", nPar, ""); node = FindHdbNode(NULL, path, con); if (node == NULL) { SCPrintf(con, eError, "ERROR: %s not found", path); return 0; } if (SctAddWriteNode(controller, node) == 0) { SCPrintf(con, eError, "ERROR: %s has already a write action", path); return 0; } SCSendOK(con); return 1; } void SctQueueNode(SctController * controller, Hdb * node, DevPrio prio, char *action, SConnection * con) { SctData *data; hdbCallback *cb; if (!FindHdbCallbackData(node, controller)) { cb = MakeHipadabaCallback(SctMainCallback, controller, NULL); assert(cb); AppendHipadabaCallback(node, cb); } data = calloc(1, sizeof(*data)); assert(data); data->controller = controller; data->node = node; data->name = strdup(action); data->conCtx = NULL; data->answered = 1; data->syncid = SyncedIncr(0); if (DevQueue(data->controller->devser, data, prio, SctWriteHandler, SctMatch, SctKillData, SctDataInfo)) { if (con != NULL) { data->conCtx = SCCopyConnection(con); } } return; } static int SctQueueCmd(pSICSOBJ ccmd, SConnection * con, Hdb * cmdNode, Hdb * par[], int nPar) { Hdb *node; SctController *controller; double interval; char *path; char *action; DevPrio prio; SctData *data; char *prioText; if (nPar < 2) { SCPrintf(con, eError, "ERROR: should be: %s queue ", ccmd->objectNode->name); return 0; } controller = ccmd->pPrivate; path = ParText(cmdNode, "node", nPar, ""); node = FindHdbNode(NULL, path, con); if (node == NULL) { SCPrintf(con, eError, "ERROR: %s not found", path); return 0; } prioText = ParText(cmdNode, "prio", nPar, "write"); prio = DevText2Prio(prioText); if (prio == NullPRIO) { SCPrintf(con, eError, "ERROR: unknown priority: %s", prioText); return 0; } action = ParText(cmdNode, "action", nPar, "write"); SctQueueNode(controller, node, prio, action, con); return 1; } static void SctKillUpdatescript(void *d) { SctUpdatescript *us = d; if (us->name) { free(us->name); } free(us); } /* * This is the callback for update scripts */ static hdbCallbackReturn SctUpdatescriptCallback(Hdb * node, void *userData, hdbMessage * msg) { SctUpdatescript *us = userData; hdbDataSearch *dsm; hdbDataMessage *mm; hdbPtrMessage *pm; SConnection *con; char *result; char *script; char path[MAX_HDB_PATH]; pDynString text; char *arg; pm = GetKillPtrMessage(msg); if (pm != NULL) { if (us->controller == pm->pPtr) { return hdbKill; } if (pm->pPtr == &updatescriptCallback && strcmp(us->name, updatescriptCallback.name) == 0 && us->controller == updatescriptCallback.controller) { return hdbKill; } return hdbContinue; } dsm = GetHdbDataSearchMessage(msg); if (dsm != NULL) { if (dsm->testPtr == &updatescriptCallback) { if (strcasecmp(us->name, updatescriptCallback.name) == 0 && us->controller == updatescriptCallback.controller) { dsm->result = us; } return hdbAbort; } return hdbContinue; } mm = GetHdbUpdateMessage(msg); if (mm != NULL) { con = mm->callData; script = GetProp(node, us->controller->node, us->name); if (script == NULL) script = us->name; text = formatValue(*(mm->v), node); arg = GetCharArray(text); arg = Arg2Tcl(1, &arg, NULL, 0); DynStringCopy(text, script); DynStringConcat(text, " "); DynStringConcat(text, arg); free(arg); script = GetCharArray(text); if (!SctCallInContext(con, script, node, us->controller, &result)) { GetHdbPath(node, path, sizeof path); SCPrintf(con, eError, "ERROR: in updatescript {%s} node %s: %s", script, path, result); } DeleteDynString(text); return hdbContinue; } return hdbContinue; } int SctUpdatescriptNode(SctController * controller, Hdb * node, char *action) { SctUpdatescript *us; hdbCallback *cb; updatescriptCallback.name = action; updatescriptCallback.controller = controller; us = FindHdbCallbackData(node, &updatescriptCallback); if (us != NULL) { return 0; } us = calloc(1, sizeof(*us)); us->name = strdup(action); us->controller = controller; cb = MakeHipadabaCallback(SctUpdatescriptCallback, us, SctKillUpdatescript); assert(cb); AppendHipadabaCallback(node, cb); return 1; } int SctUpdatescriptKill(SctController * controller, Hdb * node, char *action) { updatescriptCallback.name = action; updatescriptCallback.controller = controller; RemoveSICSInternalCallbackFrom(node, &updatescriptCallback); return 1; } static int SctUpdatescriptCmd(pSICSOBJ ccmd, SConnection * con, Hdb * cmdNode, Hdb * par[], int nPar) { Hdb *node; SctController *controller; double interval; char *path; char *action; SctData *data; char *prioText; if (nPar < 2) { SCPrintf(con, eError, "ERROR: should be: %s updatescript ", ccmd->objectNode->name); return 0; } controller = ccmd->pPrivate; path = ParText(cmdNode, "node", nPar, ""); node = FindHdbNode(NULL, path, con); if (node == NULL) { SCPrintf(con, eError, "ERROR: %s not found", path); return 0; } action = ParText(cmdNode, "action", nPar, "updatescript"); if (SctUpdatescriptNode(controller, node, action)) { SCPrintf(con, eValue, "%s registered for update on %s", action, path); } else { SCPrintf(con, eValue, "%s already registered for update on %s", action, path); } return 1; } static int SctKillUpdatescriptCmd(pSICSOBJ ccmd, SConnection * con, Hdb * cmdNode, Hdb * par[], int nPar) { Hdb *node; SctController *controller; double interval; char *path; char *action; SctData *data; char *prioText; if (nPar < 2) { SCPrintf(con, eError, "ERROR: should be: %s killupdatescript ", ccmd->objectNode->name); return 0; } controller = ccmd->pPrivate; path = ParText(cmdNode, "node", nPar, ""); node = FindHdbNode(NULL, path, con); if (node == NULL) { SCPrintf(con, eError, "ERROR: %s not found", path); return 0; } action = ParText(cmdNode, "action", nPar, "updatescript"); SctUpdatescriptKill(controller, node, action); SCPrintf(con, eValue, "kill %s updatescript on %s", action, path); return 1; } typedef struct SctTransact { char *command; char *reply; int sent; SConnection *con; SctController *controller; } SctTransact, *pSctTransact; static void KillSctTransact(void *data) { pSctTransact self = (pSctTransact) data; if (self == NULL) { return; } if (self->command) { free(self->command); } if (self->reply) { free(self->reply); } if (self->con) { SCDeleteConnection(self->con); } free(self); } static char *TransactionHandler(void *actionData, char *lastReply, int commError) { pSctTransact st = (pSctTransact) actionData; if (st->sent == 0) { st->sent = 1; if (st->controller->verbose) { SCPrintf(st->con, eLog, "%6.3f send : %s", secondsOfMinute(), st->command); } if (st->controller->fd != NULL) { fprintf(st->controller->fd, "%6.3f send : %s\n", secondsOfMinute(), st->command); } if(st->controller != NULL){ traceIO(st->controller->node->name, "transsend:%s", st->command); } else { traceIO("sctunknown", "transsend:%s", st->command); } return st->command; } else { st->sent = 2; /* if (st->controller->verbose) { SCPrintf(st->con, eLog, "%6.3f reply : %s", secondsOfMinute(), lastReply); } if (st->controller->fd != NULL) { fprintf(st->controller->fd,"%6.3f reply : %s\n", secondsOfMinute(), lastReply); } */ /* printf("Transact: %s got %s\n", st->command, lastReply); */ if (lastReply == NULL) { lastReply = ""; } if(st->controller != NULL){ traceIO(st->controller->node->name, "transreply:%s", lastReply); } else { traceIO("sctunknown", "transreply:%s", lastReply); } if(lastReply != NULL){ st->reply = strdup(lastReply); } return NULL; } } static char *SendHandler(void *actionData, char *lastReply, int commError) { pSctTransact st = (pSctTransact) actionData; char *result = TransactionHandler(actionData, lastReply, commError); if (st->sent == 2) { SetHdbProperty(st->controller->node, "reply", lastReply); if (st->controller->verbose) { SCPrintf(st->con, eLog, "%6.3f reply : %s", secondsOfMinute(), lastReply); } if (st->controller->fd != NULL) { fprintf(st->controller->fd, "%6.3f reply : %s\n", secondsOfMinute(), lastReply); } } return result; } static int SctTransactMatch(void *d1, void *d2) { return d1 == d2; } static int SctTransactCmd(pSICSOBJ ccmd, SConnection * con, Hdb * cmdNode, Hdb * par[], int nPar) { pSctTransact st = NULL; SctController *c; c = (SctController *) ccmd->pPrivate; if(nPar < 1 || par[0] == NULL){ SCWrite(con,"ERROR: no data to transact found", eError); return 0; } st = calloc(sizeof(SctTransact), 1); if (st == NULL) { SCWrite(con, "ERROR: out of memory in SctTransactCommand", eError); return 0; } st->con = SCCopyConnection(con); st->command = strdup(par[0]->value.v.text); st->controller = c; st->sent = 0; st->reply = NULL; DevQueue(c->devser, st, WritePRIO, TransactionHandler, SctTransactMatch, NULL, NULL); while (st->sent != 2) { TaskYield(pServ->pTasker); /* not yet tested: if (SCGetInterrupt(con) != eContinue) { DevUnschedule(c->devser, st, TransactionHandler, SctTransactMatch); break; } */ } if (st->reply != NULL) { SCWrite(con,st->reply,eValue); } else { SCWrite(con,"ERROR: no reply!",eError); } KillSctTransact(st); return 1; } static int SctSendCmd(pSICSOBJ ccmd, SConnection * con, Hdb * cmdNode, Hdb * par[], int nPar) { pSctTransact st = NULL; SctController *c; char *prioText = NULL; int prio; c = (SctController *) ccmd->pPrivate; st = calloc(sizeof(SctTransact), 1); if (st == NULL) { SCWrite(con, "ERROR: out of memory in SctSendCmd", eError); return 0; } st->con = SCCopyConnection(con); st->command = strdup(par[0]->value.v.text); st->controller = c; prioText = ParText(cmdNode, "prio", nPar, "write"); prio = DevText2Prio(prioText); if (prio == NullPRIO) { prio = WritePRIO; } SetHdbProperty(c->node, "reply", ""); DevQueue(c->devser, st, prio, SendHandler, SctTransactMatch, KillSctTransact, NULL); return 1; } static int SctDisconnect(pSICSOBJ ccmd, SConnection * con, Hdb * cmdNode, Hdb * par[], int nPar) { SctController *c; c = (SctController *) ccmd->pPrivate; DevDisconnect(c->devser); return 1; } static int SctReconnect(pSICSOBJ ccmd, SConnection * con, Hdb * cmdNode, Hdb * par[], int nPar) { SctController *c; char *reconnectScript, *result; c = (SctController *) ccmd->pPrivate; DevReconnect(c->devser, ParText(cmdNode, "hostport", nPar, "")); reconnectScript = GetProp(c->node, c->node, "reconnect_script"); if (reconnectScript && reconnectScript[0] != '\0') { if (SctCallInContext(con, reconnectScript, c->node, c, &result) == 0) { SCPrintf(con, eError, "ERROR: %s", result); return 0; } } return 1; } static int SctListActions(pSICSOBJ ccmd, SConnection * con, Hdb * cmdNode, Hdb * par[], int nPar) { SctController *c; char *result; c = (SctController *) ccmd->pPrivate; result = DevList(c->devser); SCWrite(con, result, eValue); return 1; } static int SctStatistics(pSICSOBJ ccmd, SConnection * con, Hdb * cmdNode, Hdb * par[], int nPar) { SctController *c; double avg, max; long errCount; int errState, maxState, maxCount; char state[16]; c = (SctController *) ccmd->pPrivate; DevStatistics(c->devser,&avg, &max, &maxCount, &errCount, &errState); if(errState == 1){ strcpy(state,"in error"); } else { strcpy(state,"good"); } SCPrintf(con,eValue,"Average roundtrip time: %lf, maximum roundtrip time %lf, count max roundtrip/2 %d, com error count: %ld, com state: %s", avg, max, maxCount, errCount, state); DevAsconStatistics(c->devser,&avg, &max, &maxState, &maxCount); SCPrintf(con,eValue,"Average time in AsconTask: %lf, maximum time spent in AsconTask %lf, state of Ascon on max %d, count > max/2 %d", avg, max, maxState, maxCount); return 1; } static int SctHostport(pSICSOBJ ccmd, SConnection * con, Hdb * cmdNode, Hdb * par[], int nPar) { SctController *c; char *result; c = (SctController *) ccmd->pPrivate; result = DevHostport(c->devser); SCWrite(con, result, eValue); return 1; } static int SctTimeout(pSICSOBJ ccmd, SConnection * con, Hdb * cmdNode, Hdb * par[], int nPar) { SctController *c; char *result; hdbValue *v = &cmdNode->child->value; c = (SctController *) ccmd->pPrivate; v->v.doubleValue = DevGetSetTimeout(c->devser, v->v.doubleValue, nPar); SCPrintf(con, eValue, "%.6g", v->v.doubleValue); return 1; } static int SctStatus(pSICSOBJ ccmd, SConnection * con, Hdb * cmdNode, Hdb * par[], int nPar) { SctController *c; char *result; c = (SctController *) ccmd->pPrivate; result = DevStatus(c->devser); SCWrite(con, result, eValue); return 1; } static int SctLog(pSICSOBJ ccmd, SConnection * con, Hdb * cmdNode, Hdb * par[], int nPar) { SctController *c; char *filename = NULL; c = (SctController *) ccmd->pPrivate; filename = par[0]->value.v.text; if(strcmp(filename,"close") == 0 ){ if(c->fd != NULL){ fclose(c->fd); c->fd = NULL; } } else { if(c->fd != NULL){ fclose(c->fd); } c->fd = fopen(filename,"w"); if(c->fd == NULL){ SCPrintf(con,eError,"ERROR: failed to open %s for logging", filename); return 0; } else { SCPrintf(con,eValue,"Logging to %s", filename); } } return 1; } static hdbCallbackReturn SctDebugCallback(Hdb * node, void *userData, hdbMessage * msg) { hdbDataMessage *mm; SctController *controller = userData; SConnection *con; int i; mm = GetHdbSetMessage(msg); if (mm != NULL) { i = mm->v->v.intValue; if (i < 0) { controller->verbose = 0; DevDebugMode(controller->devser, -1); } else if (i == 0) { controller->verbose = 1; DevDebugMode(controller->devser, -1); } else { controller->verbose = 1; DevDebugMode(controller->devser, i); } } return hdbContinue; } static void SctDeferredFree(void *data) { free(data); return; } static int SctDeferredTask(void *data) { return 0; } static void SctKillController(void *c) { SctController *controller = c; SConnection *con; hdbPtrMessage m; CleanStack(controller->node); RemoveSICSInternalCallback(controller); RemoveSICSInternalCallbackFrom(controller->node, controller); if (controller->conn) { SCDeleteConnection(controller->conn); } DevKill(controller->devser); controller->devser = NULL; if(controller->fd != NULL){ fclose(controller->fd); controller->fd = NULL; } if (pServ->pTasker) { TaskRegister(pServ->pTasker, SctDeferredTask, NULL, SctDeferredFree, controller, 0); } else { free(controller); } } static int SctMakeController(SConnection * con, SicsInterp * sics, void *object, int argc, char *argv[]) { SICSOBJ *ccmd; Hdb *parent, *par, *cmd; char *objName; hdbCallback *cb; SctController *controller; if (argc < 2) { SCPrintf(con, eError, "ERROR: should be %s ...", argv[0]); return 0; } if (strchr(argv[1], '/') == NULL) { /* object name only -> do not anchor in tree */ parent = NULL; objName = argv[1]; } else { /* full path given -> install into the hipadaba */ parent = FindHdbParent(NULL, argv[1], &objName, con); if (parent == NULL) return 0; /* error message already written */ } controller = calloc(1, sizeof(*controller)); assert(controller); controller->verbose = 0; ccmd = MakeSICSOBJv(objName, "SctController", HIPNONE, usSpy); controller->node = ccmd->objectNode; controller->conn = SCCreateDummyConnection(pServ->pSics); assert(ccmd); assert(controller->node->mama == NULL); ccmd->pPrivate = controller; ccmd->KillPrivate = SctKillController; if (parent != NULL) { AddHipadabaChild(parent, controller->node, con); } controller->devser = DevMake(con, argc - 2, argv + 2); if (!controller->devser) return 0; SetHdbProperty(controller->node, "controllerName", objName); SetHdbProperty(controller->node, "sicsdev", objName); AddCommand(pServ->pSics, objName, InterInvokeSICSOBJ, KillSICSOBJ, ccmd); RegisterSICSOBJKillCmd(ccmd, objName); SetDescriptorKey(ccmd->pDes, "creationCommand", "0"); cmd = AddSICSHdbPar(controller->node, "poll", usMugger, MakeSICSFunc(SctPollCmd)); AddSICSHdbPar(cmd, "node", usMugger, MakeHdbText("")); AddSICSHdbPar(cmd, "interval", usMugger, MakeHdbFloat(5.0)); AddSICSHdbPar(cmd, "prio", usMugger, MakeHdbText("read")); AddSICSHdbPar(cmd, "action", usMugger, MakeHdbText("read")); cmd = AddSICSHdbPar(controller->node, "unpoll", usMugger, MakeSICSFunc(SctUnpollCmd)); AddSICSHdbPar(cmd, "node", usMugger, MakeHdbText("")); AddSICSHdbPar(cmd, "action", usMugger, MakeHdbText("read")); cmd = AddSICSHdbPar(controller->node, "connect", usMugger, MakeSICSFunc(SctConnectCmd)); AddSICSHdbPar(cmd, "node", usMugger, MakeHdbText("")); cmd = AddSICSHdbPar(controller->node, "write", usMugger, MakeSICSFunc(SctWriteCmd)); AddSICSHdbPar(cmd, "node", usMugger, MakeHdbText("")); cmd = AddSICSHdbPar(controller->node, "queue", usMugger, MakeSICSFunc(SctQueueCmd)); AddSICSHdbPar(cmd, "node", usMugger, MakeHdbText("")); AddSICSHdbPar(cmd, "prio", usMugger, MakeHdbText("write")); AddSICSHdbPar(cmd, "action", usMugger, MakeHdbText("write")); cmd = AddSICSHdbPar(controller->node, "updatescript", usMugger, MakeSICSFunc(SctUpdatescriptCmd)); AddSICSHdbPar(cmd, "node", usMugger, MakeHdbText("")); AddSICSHdbPar(cmd, "action", usMugger, MakeHdbText("updatescript")); cmd = AddSICSHdbPar(controller->node, "killupdatescript", usMugger, MakeSICSFunc(SctKillUpdatescriptCmd)); AddSICSHdbPar(cmd, "node", usMugger, MakeHdbText("")); AddSICSHdbPar(cmd, "action", usMugger, MakeHdbText("updatescript")); cmd = AddSICSHdbPar(controller->node, "log", usMugger, MakeSICSFunc(SctLog)); AddSICSHdbPar(cmd, "filename", usMugger, MakeHdbText("")); cmd = AddSICSHdbPar(controller->node, "transact", usMugger, MakeSICSFunc(SctTransactCmd)); AddSICSHdbPar(cmd, "data", usMugger, MakeHdbText("")); cmd = AddSICSHdbPar(controller->node, "send", usMugger, MakeSICSFunc(SctSendCmd)); AddSICSHdbPar(cmd, "data", usMugger, MakeHdbText("")); AddSICSHdbPar(cmd, "prio", usMugger, MakeHdbText("")); cmd = AddSICSHdbPar(controller->node, "disconnect", usMugger, MakeSICSFunc(SctDisconnect)); cmd = AddSICSHdbPar(controller->node, "reconnect", usMugger, MakeSICSFunc(SctReconnect)); AddSICSHdbPar(cmd, "hostport", usMugger, MakeHdbText("")); par = AddSICSHdbPar(controller->node, "debug", usUser, MakeHdbInt(-1)); cmd = AddSICSHdbPar(controller->node, "actions", usUser, MakeSICSFunc(SctListActions)); cmd = AddSICSHdbPar(controller->node, "statistics", usSpy, MakeSICSFunc(SctStatistics)); cmd = AddSICSHdbPar(controller->node, "hostport", usSpy, MakeSICSFunc(SctHostport)); cmd = AddSICSHdbPar(controller->node, "status", usSpy, MakeSICSFunc(SctStatus)); cmd = AddSICSHdbPar(controller->node, "timeout", usSpy, MakeSICSFunc(SctTimeout)); AddSICSHdbPar(cmd, "value", usUser, MakeHdbFloat(-1.0)); /* get the actual timeout value */ SctTimeout(ccmd, con, cmd, &par, 0); cb = MakeHipadabaCallback(SctDebugCallback, controller, NULL); if (cb) AppendHipadabaCallback(par, cb); return 1; } void SctKill(void *object) { assert(sct == object); ContextItem *p, *q; for (p = sct->nodes; p != NULL; p = q) { q = p->next; free(p); } for (p = sct->trash; p != NULL; p = q) { q = p->next; free(p); } if (sct->desc != NULL) { DeleteDescriptor(sct->desc); } free(sct); sct = NULL; } void SctInit(void) { if (sct) return; sct = calloc(1, sizeof(*sct)); assert(sct); sct->desc = CreateDescriptor("ScriptContext"); AddCommand(pServ->pSics, "sct", SctCommand, SctKill, sct); AddCmd("makesctcontroller", SctMakeController); } int SctVerbose(SctController * c) { return c->verbose; }