From 68ec926a8b67511879e09197f60a2bf8b1b06944 Mon Sep 17 00:00:00 2001 From: zolliker Date: Fri, 18 Jan 2008 07:55:58 +0000 Subject: [PATCH] - introduce script context --- ascon.c | 493 ++++++++++++++++ ascon.h | 81 +++ ascon.i | 143 +++++ errormsg.c | 63 ++ errormsg.h | 30 + make_gen | 1 + mclist.c | 116 ++++ mclist.h | 177 ++++++ ofac.c | 1 + scriptcontext.c | 1458 +++++++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 2563 insertions(+) create mode 100644 ascon.c create mode 100644 ascon.h create mode 100644 ascon.i create mode 100644 errormsg.c create mode 100644 errormsg.h create mode 100644 mclist.c create mode 100644 mclist.h create mode 100644 scriptcontext.c diff --git a/ascon.c b/ascon.c new file mode 100644 index 00000000..4bc2eb2d --- /dev/null +++ b/ascon.c @@ -0,0 +1,493 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "sics.h" +#include "splitter.h" +#include "ascon.i" + +/* + CreateSocketAdress stolen from Tcl. Thanks to John Ousterhout +*/ + +static int CreateSocketAdress( + struct sockaddr_in *sockaddrPtr, /* Socket address */ + char *host, /* Host. NULL implies INADDR_ANY */ + int port) /* Port number */ +{ + struct hostent *hostent; /* Host database entry */ + struct in_addr addr; /* For 64/32 bit madness */ + + (void) memset((char *) sockaddrPtr, '\0', sizeof(struct sockaddr_in)); + sockaddrPtr->sin_family = AF_INET; + sockaddrPtr->sin_port = htons((unsigned short) (port & 0xFFFF)); + if (host == NULL) { + addr.s_addr = INADDR_ANY; + } else { + hostent = gethostbyname(host); + if (hostent != NULL) { + memcpy((char *) &addr, + (char *) hostent->h_addr_list[0], (size_t) hostent->h_length); + } else { + addr.s_addr = inet_addr(host); + if (addr.s_addr == (unsigned long)-1) { + return 0; /* error */ + } + } + } + /* + * There is a rumor that this assignment may require care on + * some 64 bit machines. + */ + sockaddrPtr->sin_addr.s_addr = addr.s_addr; + return 1; +} + +double DoubleTime(void) { + struct timeval now; + + gettimeofday(&now, NULL); + return now.tv_sec + now.tv_usec / 1e6; +} + +static void AsconError(AsconPtr a, char *msg, int errorno) { + static char *stateText[]={ + "state 0", "kill", "state 2", "notConnected", + "connect", "start connect", "connect finished", "connect failed", + "write", "start write", "write finished", "write failed", + "read", "start read", "read finished", "read failed", + "state 16", "state 17", "state 18", "idle" + }; + char *state; + + if (a->state < 0 || a->state > 19) { + state = "bad state"; + } else { + state = stateText[a->state]; + } + if (errorno != 0) { + ErrPutMsg(&a->errList, NULL, "%s %s (during %s)", msg, strerror(errorno), state); + } else { + ErrPutMsg(&a->errList, NULL, "%s (during %s)", msg, state); + } + a->state |= AsconFailed; +} + +static void AsconConnect(AsconPtr a) { + /* input state: AsconConnectStart + output state: AsconFailed or AsconConnecting */ + int ret; + struct sockaddr_in adr; + char *colon; + int port; + int oldopts; + + if (a->fd < 0) { + a->fd = socket(AF_INET,SOCK_STREAM,0); + if (a->fd < 0) { + AsconError(a, "socket failed:", errno); + return; + } + } + colon = strchr(a->hostport, ':'); + if (colon == NULL) return; + port = atoi(colon+1); + if (port <= 0) { + AsconError(a, "bad port number", 0); + return; + } + *colon = '\0'; + ret = CreateSocketAdress(&adr, a->hostport, port); + *colon = ':'; + if (ret == 0) { + AsconError(a, "bad host specification", 0); + return; + } + /* should we insert the workaround for lantronix server ? see network.c */ + oldopts = fcntl(a->fd, F_GETFL, 0); + fcntl(a->fd, F_SETFL, oldopts | O_NONBLOCK); + ret = connect(a->fd, (struct sockaddr *)&adr, sizeof(struct sockaddr_in)); + if (ret < 0) { + if (errno != EINPROGRESS) { + AsconError(a, "connect failed:", errno); + return; + } + } + a->state = AsconConnecting; + return; +} + +void AsconStdInit(AsconPtr a, char *hostport) { + a->fd = -1; + a->state = AsconConnectStart; + a->timeout = 2.0; /* sec */ + a->hostport = strdup(hostport); +} + +int AsconReadGarbage(int fd) { + fd_set rmask; + struct timeval tmo = {0,0}; + int l, ret, result; + char garbage[100]; + + FD_ZERO(&rmask); + result = 0; + do { + FD_SET(fd, &rmask); + ret = select(fd + 1, &rmask, NULL, NULL, &tmo); + if (ret > 0) { + l = recv(fd, garbage, sizeof garbage, 0); + if (l > 0) { + /* swallow */ + result += l; + } else if (l == 0) { + errno = ECONNRESET; + return -2; + } else if (l < 0) { + return -2; + } + } + } while (ret > 0); + return result; +} + +void PrintChar(char chr) { + if (chr <= 32 || chr >= 127) { + printf("%2.2x ", chr); + } else { + printf(" %c ", chr); + } +} + +int AsconConnectSuccess(int fd) { + fd_set wmask, rmask; + struct timeval tmo = {0,0}; + int oldopts; + int ret; + + oldopts = fcntl(fd, F_GETFL, 0); + assert(oldopts | O_NONBLOCK); /* fd must be in non-blocking mode */ + + FD_ZERO(&wmask); + FD_ZERO(&rmask); + FD_SET(fd, &wmask); + FD_SET(fd, &rmask); + ret = select(fd + 1, &rmask, &wmask, NULL, &tmo); + if (ret > 0) { + assert(FD_ISSET(fd, &wmask)); + if (FD_ISSET(fd, &rmask)) { /* there may already be data for read */ + if (recv(fd, NULL, 0, 0) < 0) { /* zero length, check only return value */ + ret = ASCON_RECV_ERROR; /* first recv failed */ + } + } else { + if (send(fd, NULL, 0, 0) < 0) { /* zero length, check only return value */ + ret = ASCON_SEND_ERROR; /* first send failed */ + } + } + } + fcntl(fd, F_SETFL, oldopts & ~ O_NONBLOCK); /* reset to blocking mode */ + return ret; +} + +int AsconReadChar(int fd, char *chr) { + fd_set rmask; + struct timeval tmo = {0,0}; + int ret; + + FD_ZERO(&rmask); + FD_SET(fd, &rmask); + ret = select(fd + 1, &rmask, NULL, NULL, &tmo); + if (ret <= 0) return ret; + ret = recv(fd, chr, 1, 0); + /* PrintChar(*chr); */ + fflush(stdout); + if (ret > 0) return 1; + if (ret == 0) { + errno = ECONNRESET; + return ASCON_DISCONNECTED; + } + return ASCON_RECV_ERROR; +} + +int AsconWriteChars(int fd, char *data, int length) { + fd_set wmask; + struct timeval tmo = {0,0}; + int ret; + + if (length <= 0) return 0; + /* + { int i; + for (i=0; i 0) return ret; + if (ret == 0) { + errno = ECONNRESET; + return ASCON_DISCONNECTED; + } + return ASCON_SEND_ERROR; +} + +static double lastCall = 0; + +int AsconStdHandler(AsconPtr a) { + int ret; + int l; + char chr; + double now = DoubleTime(); + + if (now > lastCall + 0.5) { /* AsconStdHandler was not called since a long time (0.5 sec) */ + if (lastCall != 0) { /* extend timeout time (for debugging purposes) */ + a->start += now - lastCall - 0.5; + } + } + lastCall = now; + switch (a->state) { + case AsconKillMe: return 0; + case AsconConnectStart: + AsconConnect(a); + break; + case AsconConnecting: + ret = AsconConnectSuccess(a->fd); + if (ret == 0) { + /* in progress */ + } else if (ret > 0) { + a->state = AsconConnectDone; /* success */ + } else if (ret < 0) { + AsconError(a, "AsconConnectSuccess failed:", errno); + } + break; + case AsconWriteStart: + DynStringConcatChar(a->wrBuffer, '\r'); + a->wrPos = 0; + a->state = AsconWriting; + break; + case AsconWriting: + AsconReadGarbage(a->fd); + l = GetDynStringLength(a->wrBuffer) - a->wrPos; + ret = AsconWriteChars(a->fd, GetCharArray(a->wrBuffer) + a->wrPos, l); + if (ret < 0) { + AsconError(a, "send failed:", errno); + } else { + a->wrPos += ret; + if (a->wrPos >= GetDynStringLength(a->wrBuffer)) { + a->state = AsconWriteDone; + } + } + break; + case AsconReadStart: + DynStringClear(a->rdBuffer); + a->start = DoubleTime(); + a->state = AsconReading; + break; + case AsconReading: + ret = AsconReadChar(a->fd, &chr); + while (ret > 0) { + a->start = DoubleTime(); + + if (chr == '\n') { + if (a->readState) { + /* swallow LF after CR */ + DynStringClear(a->rdBuffer); + a->readState = 0; + } else { + DynStringConcatChar(a->rdBuffer, '\0'); + a->state = AsconReadDone; + break; + } + } else if (chr == '\r') { + a->readState = 1; + DynStringConcatChar(a->rdBuffer, '\0'); + a->state = AsconReadDone; + break; + } else { + if (DynStringConcatChar(a->rdBuffer, chr) == 0) { + AsconError(a, "DynStringConcatChar failed:", ENOMEM); + break; + } + a->readState = 0; + } + ret = AsconReadChar(a->fd, &chr); + } + if (ret < 0) { + AsconError(a, "AsconReadChar failed:", errno); + return 1; + } + if (a->state == AsconReadDone) { + DynStringConcatChar(a->rdBuffer, '\0'); + } else { + if (a->timeout > 0) { + if (DoubleTime() - a->start > a->timeout) { + AsconError(a, "read timeout", 0); + } + } + } + break; + default: + return 1; + } + return 1; +} + +/* define type AsconProtocolList and functions AsconProtocolAdd etc. */ +#define MC_NAME(T) AsconProtocol##T +#include "mclist.c" + +static AsconProtocolList protocols={0}; + +void AsconInsertProtocol(AsconProtocol *protocol) { + AsconProtocolAdd(&protocols, protocol); +} + +AsconHandler AsconSetHandler(AsconPtr a, int argc, char *argv[]) { + AsconProtocol *p; + + if (argc < 1) return NULL; + if (strcasecmp(argv[0], "std") == 0) { + if (argc != 2) return NULL; + AsconStdInit(a, argv[1]); + return AsconStdHandler; + } + for (p = protocols.head; p!= NULL; p=p->next) { + if (strcasecmp(p->name, argv[0]) == 0) { + p->init(a, argc, argv); + return p->handler; + } + } + return NULL; +} + +/* --- implementation of higher level interface ---- */ + +char *ConcatArgs(int argc, char *argv[]) { + return Arg2Tcl(argc, argv, NULL, -1); +} + +AsconPtr AsconMake(SConnection *con, int argc, char *argv[]) { + AsconPtr a; + char *args; + + a = calloc(1, sizeof(*a)); + if (a == NULL) { + SCWrite(con, "ERROR: no memory", eError); + return NULL; + } + a->handler = AsconSetHandler(a, argc, argv); + if (a->handler == NULL) { + args = ConcatArgs(argc, argv); + if (!args) return NULL; + SCPrintf(con, eError, "ERROR: illegal protocol: %s", args); + free(args); + return NULL; + } + a->rdBuffer = CreateDynString(60, 63); + a->wrBuffer = CreateDynString(60, 63); + a->errList.head = NULL; + a->responseValid = 0; + return a; +} + +void AsconKill(AsconPtr a) { + a->state = AsconKillMe; + a->handler(a); + if (a->fd > 0) { + close(a->fd); + } + DeleteDynString(a->rdBuffer); + DeleteDynString(a->wrBuffer); + if (a->hostport) { + free(a->hostport); + } + free(a); +} + +AsconState AsconTask(AsconPtr a) { + a->handler(a); + while (1) { + switch (a->state) { + case AsconReading: + case AsconWriting: + return AsconPending; + case AsconNotConnected: + return AsconOffline; + break; + case AsconConnectDone: + a->state = AsconIdle; + return AsconReady; + case AsconWriteDone: + if (a->noResponse) { + return AsconReady; + } + a->state = AsconReadStart; + break; + case AsconReadDone: + a->state = AsconIdle; + a->responseValid = 1; + return AsconReady; + case AsconConnecting: + return AsconUnconnected; + default: + switch (a->state % 4) { + case AsconOnTheWay: + case AsconStart: + return AsconPending; + case AsconFailed: + if (a->state <= AsconConnectFailed) { + return AsconUnconnected; + } + return AsconFailure; + case AsconFinished: + if (a->state < AsconConnectFailed) { + return AsconUnconnected; + } + return AsconReady; + } + } + a->handler(a); + } +} + +int AsconWrite(AsconPtr a, char *command, int noResponse) { + if (a->state <= AsconConnectFailed || a->state % 4 < AsconFinished) return 0; + DynStringCopy(a->wrBuffer, command); + a->noResponse = noResponse; + a->state = AsconWriteStart; + a->responseValid = 0; + AsconTask(a); + return 1; +} + +char *AsconRead(AsconPtr a) { + if (a->noResponse) { + a->noResponse=0; + return ""; + } + if (a->state % 4 == AsconFailed) { + a->state = AsconIdle; + return ""; + } + if (a->responseValid) { + a->responseValid = 0; + return GetCharArray(a->rdBuffer); + } + return NULL; +} + +ErrMsgList *AsconGetErrList(AsconPtr a) { + return &a->errList; +} diff --git a/ascon.h b/ascon.h new file mode 100644 index 00000000..55ae33ae --- /dev/null +++ b/ascon.h @@ -0,0 +1,81 @@ +#ifndef ASCON_H +#define ASCON_H + +#include "errormsg.h" + +/** \file + * \brief Asynchronous connection handling for devices controlled over tcp-ip + * connections. Interface for higher level modules. + */ + +/** \brief the asynchronous connection + */ +typedef struct Ascon *AsconPtr; + +/** \brief the possible results of AsconTask + */ +typedef enum { + AsconOffline, + AsconUnconnected, + AsconPending, + AsconReady, + AsconFailure +} AsconStatus; + +/** \brief make a new asynchronous connection + * \param con the SICS connection + * \param argc number of arguments + * \param argv the arguments. argv[0] must be the protocol name, the other arguments + * are protocol specific, but argv[1] is usually host::port + * \return the created connection or NULL on failure + */ +AsconPtr AsconMake(SConnection *con, int argc, char *argv[]); + +/** \brief kill function + * \param a the connection to be killed + */ +void AsconKill(AsconPtr a); + +/** \brief the task handler. To be called repeatedly. + * \param a the connection + * \return the state of the connection + */ +AsconStatus AsconTask(AsconPtr a); + +/** \brief write to the connection. allowed only when the state is ascon_ready + * \param a the connection + * \param command the command to be sent + * \param noResponse 0 normally, 1 if no reponse is expected + * \return 1 on success, 0 when not ready + */ +int AsconWrite(AsconPtr a, char *command, int noResponse); + +/** \brief read from the connection. allowed only when a response is available + * \param a the connection + * \return the response when a response is ready + * NULL when the command has not completed and the response is not yet finished + * "" when the command has completed, but no response was expected. + * The result is only valid until the next call to other AsconXxx functions + * and has to be duplicated if needed later. + */ +char *AsconRead(AsconPtr a); + +/** \brief get the connections error list + * \return the error list + */ +ErrMsgList *AsconGetErrList(AsconPtr a); + +/** \brief a helper function + * \param argc the number of args + * \param argv the args to be concatenated + * \result a allocated string containing the concatenated arguments + * the args are properly quoted to be used as tcl proc arguments + */ +char *ConcatArgs(int argc, char *argv[]); + +/** \brief function for dealing with times with musec resolution + * \return absolute time as double value + */ +double DoubleTime(void); + +#endif diff --git a/ascon.i b/ascon.i new file mode 100644 index 00000000..c941b184 --- /dev/null +++ b/ascon.i @@ -0,0 +1,143 @@ +#ifndef ASCON_I +#define ASCON_I + +#include +#include "ascon.h" +#include "dynstring.h" + +/** \file + * \brief Asynchronous connection handling for devices controlled over tcp-ip + * connections. Interface for the implementation of custom protocols. + * + * For the implmentation of a custom protocol, hou have to implement + * the handler function and the init function, declare the protocol + * of type AsconProtocol and call AsconInsertProtocol on startup. + * The handler and init functions are normally be a wrapper around AsconStdHandler + * and AsconStdInit + * + * The functions with fd as the first argument are utility functions with + * may be used in handler wrapper functions. + * On error, the return value may be one of the defined macros ASCON_xxx, + * and errno will give more details about the error. + */ + +/** + * A sub-state of the connection. Only states with sub-state AsconStart may + * be set by the caller, and only when the sub-state is not AsconOnTheWay + */ +typedef enum { AsconOnTheWay=0, AsconStart=1, AsconFinished=2, AsconFailed=3 } AsconMode; + +/** + * The state of the connection. The sub-state is state % 4. + */ +typedef enum { + AsconNotConnected=0+AsconFinished, + AsconKillMe=0+AsconStart, + AsconConnecting=4+AsconOnTheWay, + AsconConnectStart=AsconConnecting+AsconStart, + AsconConnectDone=AsconConnecting+AsconFinished, + AsconConnectFailed=AsconConnecting+AsconFailed, + AsconWriting=8+AsconOnTheWay, + AsconWriteStart=AsconWriting+AsconStart, + AsconWriteDone=AsconWriting+AsconFinished, + AsconReading=12+AsconOnTheWay, + AsconReadStart=AsconReading+AsconStart, + AsconReadDone=AsconReading+AsconFinished, + AsconIdle=16+AsconFinished +} AsconState; + +/** \brief the task handler function prototype + * + * custom handlers must have this prototype + */ +typedef int (* AsconHandler)(AsconPtr connection); + +/** Ascon struct + * all members are public, allowing access by handler wrappers + */ +typedef struct Ascon { + AsconState state; /**< the current state */ + int fd; /**< socket */ + int readState; /**< default implementation: 'was cr' */ + pDynString rdBuffer;/**< read buffer */ + pDynString wrBuffer;/**< write buffer */ + int wrPos; /**< write buffer position */ + float timeout; /**< read timeout (sec) */ + char *hostport; /**< host:port to connect */ + ErrMsgList errList; /**< error message list */ + double start; /**< unix time when read was started */ + void *private; /**< private data of protocol */ + int noResponse; /**< no response expected */ + int responseValid; /**< a valid response is ready */ + AsconHandler handler; /**< handler function */ +} Ascon; + +#define ASCON_SELECT_ERROR -1 +#define ASCON_RECV_ERROR -2 +#define ASCON_SEND_ERROR -3 +#define ASCON_DISCONNECTED -4 + +/** \brief the standard handler routine. + * \param a the connection + * \return 0 when task has finished (connection to be closed), 1 when active + * + * In most cases a custom handler may be a wrapper around AsconStdHandler + */ +int AsconStdHandler(AsconPtr a); + +/** \brief initialize a standard connection + * \param a the connection + * \param hostport the tcp/ip address (syntax: host:port) + * + * In most cases a custom init function may be a wrapper around AsconStdInit + */ +void AsconStdInit(AsconPtr a, char *hostport); + +/** The Ascon Protocol + */ +typedef struct AsconProtocol { + struct AsconProtocol *next; + char *name; + AsconHandler handler; + void (*init)(Ascon *s, int argc, char *argv[]); +} AsconProtocol; + +/** \brief Insert a new protocol into the protocol list + * protocol the protocol (must be allocated by the caller, may be statically) + */ +void AsconInsertProtocol(AsconProtocol *protocol); + +/** \brief close the connection and free internal used memory + * \param a the connection to be closed + * remark: the connection struct itself has to be freed manually + */ +void AsconClose(AsconPtr a); + +/** \brief swallow garbage (utility function) + * \param fd the socket + * \return >=0: number of chars swallowed, else error + */ +int AsconReadGarbage(int fd); + +/** \brief check if a connection has succeded (utility function) + * \param fd the socket + * \return 1: connection succesful, 0: connection in progress, <0: error + */ +int AsconConnectSuccess(int fd); + +/** \brief read one character, if available (utility function) + * \param fd the socket + * \param chr the result + * \return 1: succes, 0: no data available, <0: error + */ +int AsconReadChar(int fd, char *chr); + +/** \brief non blocking write (utility function) + * \param fd the socket + * \param data the data (not nul-terminated, may contain nul) + * \param length the length of the data + * \return >0: number of written chars,0: write not yet possible, <0: error + */ +int AsconWriteChars(int fd, char *data, int length); + +#endif diff --git a/errormsg.c b/errormsg.c new file mode 100644 index 00000000..488f26d8 --- /dev/null +++ b/errormsg.c @@ -0,0 +1,63 @@ +#include +#include +#include +#include +#include "errormsg.h" + +/* ErrMsgList implementation */ +#define MC_NAME(T) ErrMsg##T +#define MC_IMPLEMENTATION +#include "mclist.c" + +ErrMsg *ErrPutMsg(ErrMsgList *dump, char *data, char *fmt, ...) { + ErrMsg *m, *p; + va_list ap; + char buf[256]; + char *text; + int l; + + va_start(ap, fmt); + l = vsnprintf(buf, sizeof buf, fmt, ap); + va_end(ap); + if (l < sizeof buf) { + text = buf; + } else { + /* assuming we have a C99 conforming snprintf and need a larger buffer */ + text = calloc(l, 1); + va_start(ap, fmt); + vsnprintf(text, l, fmt, ap); + va_end(ap); + } + + m = NULL; + for (p = ErrMsgFirst(dump); p!= NULL; p = ErrMsgNext(dump)) { + if (strcmp(text, p->text) == 0) { + m = ErrMsgTake(dump); + break; + } + } + if (m == NULL) { + m = calloc(1, sizeof(*m)); + if (text == buf) { + m->text = strdup(text); + } else { + m->text = text; + } + m->data = NULL; + m->cnt = 1; + } else { + if (text != buf) free(text); + m->cnt++; + } + if (m->data) { + free(m->data); + m->data = NULL; + } + if (data) { + m->data = strdup(data); + } + ErrMsgFirst(dump); + ErrMsgInsert(dump, m); + time(&m->last); + return m; +} diff --git a/errormsg.h b/errormsg.h new file mode 100644 index 00000000..70efc753 --- /dev/null +++ b/errormsg.h @@ -0,0 +1,30 @@ +#ifndef ERRORMSG_H +#define ERRORMSG_H + +#include + +/** \file + * \brief Error message collection + */ +/** \brief Error message item + */ +typedef struct ErrMsg { + struct ErrMsg *next; + char *text; /**< the message text */ + char *data; /**< additional text which may be different for the same message */ + int cnt; /**< count */ + time_t last; /**< time of last message */ +} ErrMsg; + +/* define type ErrMsgList and functions ErrMsgAdd etc. */ +#define MC_NAME(T) ErrMsg##T +#include "mclist.h" + +/** \brief Put a formatted message to the error message list + * \param dump the error message list + * \param data some additional text (may be NULL) + * \param fmt the format for the message + */ +ErrMsg *ErrPutMsg(ErrMsgList *dump, char *data, char *fmt, ...); + +#endif diff --git a/make_gen b/make_gen index 66180e68..9e87e018 100644 --- a/make_gen +++ b/make_gen @@ -31,6 +31,7 @@ SOBJ = network.o ifile.o conman.o SCinter.o splitter.o passwd.o \ hmdata.o nxscript.o tclintimpl.o sicsdata.o mcstascounter.o \ mcstashm.o initializer.o remob.o tclmotdriv.o protocol.o \ sinfox.o sicslist.o cone.o hipadaba.o sicshipadaba.o statistics.o \ + ascon.o errormsg.o scriptcontext.o \ moregress.o hdbcommand.o multicounter.o regresscter.o histregress.o \ sicshdbadapter.o polldriv.o sicspoll.o statemon.o hmslave.o \ nwatch.o asyncqueue.o asyncprotocol.o sicsobj.o hdbqueue.o\ diff --git a/mclist.c b/mclist.c new file mode 100644 index 00000000..f67badf0 --- /dev/null +++ b/mclist.c @@ -0,0 +1,116 @@ +#ifndef MC_List_TYPE +#define MC_List_TYPE MC_NAME(List) +#define MC_First_FUN MC_NAME(First) +#define MC_This_FUN MC_NAME(This) +#define MC_Next_FUN MC_NAME(Next) +#define MC_End_FUN MC_NAME(End) +#define MC_Insert_FUN MC_NAME(Insert) +#define MC_Add_FUN MC_NAME(Add) +#define MC_Take_FUN MC_NAME(Take) +#endif + +#ifdef MC_IMPLEMENTATION + +#undef MC_IMPLEMENTATION +#ifndef MC_TYPE +#define MC_TYPE MC_NAME()* +#endif + +#else + +#define MC_DO_NOT_UNDEF +#include "mclist.h" +#undef MC_DO_NOT_UNDEF + +#endif + +#ifndef MC_NEXT +#define MC_NEXT next +#endif + +MC_TYPE MC_First_FUN(MC_List_TYPE *list) { + list->ptr = &list->head; + return list->head; +} + +MC_TYPE MC_This_FUN(MC_List_TYPE *list) { + if (list->head == NULL) { + list->ptr = &list->head; + return NULL; + } + return *list->ptr; + } + +MC_TYPE MC_Next_FUN(MC_List_TYPE *list) { + MC_TYPE node; + if (list->head == NULL) { + list->ptr = &list->head; + return NULL; + } + node = *list->ptr; + if (node) { + list->ptr = &node->MC_NEXT; + } + return *list->ptr; +} + +void MC_End_FUN(MC_List_TYPE *list) { + MC_TYPE node; + if (list->head == NULL) { + list->ptr = &list->head; + } + node = *list->ptr; + if (node) { + while (node->MC_NEXT != NULL) { + node = node->MC_NEXT; + } + list->ptr = &node->MC_NEXT; + } +} + +void MC_Insert_FUN(MC_List_TYPE *list, MC_TYPE node) { + if (list->head == NULL) { + list->ptr = &list->head; + } + node->MC_NEXT = *list->ptr; + *list->ptr = node; +} + +void MC_Add_FUN(MC_List_TYPE *list, MC_TYPE node) { + node->MC_NEXT = NULL; + if (list->head == NULL) { + list->head = node; + list->ptr = &list->head; + } else { + if (*list->ptr != NULL) { + MC_End_FUN(list); + } + *list->ptr = node; + } +} + +MC_TYPE MC_Take_FUN(MC_List_TYPE *list) { + MC_TYPE node; + node = *list->ptr; + if (node != NULL) { + *list->ptr = node->MC_NEXT; + } + return node; +} + +void MC_Delete_FUN(MC_List_TYPE *list, void (*deleteFunc)(MC_TYPE n)) { + MC_TYPE node; + MC_TYPE victim; + node = list->head; + while (node != NULL) { + victim = node; + node = node->next; + deleteFunc(victim); + } + list->head = NULL; + list->ptr = &list->head; +} + +#undef MC_NAME +#undef MC_TYPE +#undef MC_NEXT diff --git a/mclist.h b/mclist.h new file mode 100644 index 00000000..889a6737 --- /dev/null +++ b/mclist.h @@ -0,0 +1,177 @@ +/** \file + * \brief Type safe list handling + * + * The definition and implementation make use of macros extensively to create + * a list type and related functions for any node type. + * However, accessing the list does not use macros. + * The list is implemented as a singly linked list. Sequential appending + * to the tail is fast, because the list structure contains + * a pointer to the anchor of the last accessed node. + * + * For a local list, mclist.c must be included after + * the declaration of the node type and after defining the macro MC_NAME + * and optionally MC_TYPE and MC_NEXT. + * + * For a public list, in the header file mclist.h must be included after + * the declaration of the node type and after defining the macro MC_NAME + * and optionally MC_TYPE. In the implementation mclist.c + * must be included after defining the macro MC_NAME and MC_IMPLEMENTATION + * and optionally MC_TYPE and MC_NEXT. + * + * MC_NAME has one parameter and describes how to combine the list name + * with the function names. + * + * MC_TYPE defines the node type. If undeclared it defaults to a pointer + * to the name. + * + * MC_NEXT indicates the name of the link. It defaults to 'next'. + * + * MC_IMPLEMENTATION has no value and must be defined when the list type + * was already declared. Typically this is done in a header file including mclist.h. + * + * The macros MC_NAME, MC_TYPE, MC_NEXT and MC_IMPLEMENTATION are undefined + * within mclist.c and mclist.h and must be redefined for every list. + * + * \par Usage example + * \code + * // declare the Node type + * typedef struct Node { + * struct Node *next; + * char *name; + * } Node; + * + * // this declaration leads to a list type 'NodeList' and fucntions names 'Node' + * #define MC_NAME(T) Node##T + * // the following line is not needed as 'next' is the default for the link + * #define MC_NEXT next + * // the following line is not needed as 'Node *' is the default for the type in this case + * #define MC_TYPE Node * + * // inside mclist.c, the list type is declared and the related functions are implemented + * #include "mclist.c" + * + * int main(void) { + * // declare and init the list + * NodeList list={NULL}; + * + * // create a node + * Node *node; + * node = malloc(sizeof(*node)); + * node->name = "First"; + * + * // add node at the end of the list + * NodeAdd(&list, node); + * + * // print the names of all list nodes + * for (node = NodeFirst(&list); node != NULL; node = NodeNext(&list)) { + * printf("%s\n", node->name); + * } + * + * // alternative form not touching the list position + * // only for the case, where no insert or take function is used inside the loop + * for (node = list.head; node != NULL; node = node->next) { + * printf("%s\n", node->name); + * } + * + * // remove the node with the name "First" + * for (node = NodeFirst(&list); node != NULL; node = NodeNext(&list)) { + * if (strcmp(node->name, "First") == 0) { + * free(NodeTake(&list)); + * } + * } + * } + * \endcode + */ +#ifndef MC_TYPE +/** \brief default node type + */ +#define MC_TYPE MC_NAME()* +#endif + +#ifndef MC_List_TYPE +#define MC_List_TYPE MC_NAME(List) +#define MC_First_FUN MC_NAME(First) +#define MC_This_FUN MC_NAME(This) +#define MC_Next_FUN MC_NAME(Next) +#define MC_End_FUN MC_NAME(End) +#define MC_Insert_FUN MC_NAME(Insert) +#define MC_Add_FUN MC_NAME(Add) +#define MC_Take_FUN MC_NAME(Take) +#define MC_Delete_FUN MC_NAME(Delete) +#endif + +typedef struct MC_List_TYPE { + MC_TYPE head; + MC_TYPE *ptr; +} MC_List_TYPE; + +/** \brief move to first node and get it + * \param list the list + * \return the node or NULL when the list is empty + * + * Actual position on return: at the first node + */ +MC_TYPE MC_First_FUN(MC_List_TYPE *list); + +/** \brief get the node at the current position + * \param list the list + * \return the node or NULL when the list is empty or the position is at end + * + * Actual position on return: not changed (= at the returned node) + */ +MC_TYPE MC_This_FUN(MC_List_TYPE *list); + +/** \brief get the node after the current node + * \param list the list + * \return the node or NULL when the list is empty or the position is at end + * + * Actual position on return: incremented (= at the returned node or at end) + */ +MC_TYPE MC_Next_FUN(MC_List_TYPE *list); + +/** \brief move the position to the end + * \param list the list + * + * Actual position on return: at end + */ +void MC_End_FUN(MC_List_TYPE *list); + +/** \brief insert at the current position, i.e. before the current node + * \param list the list + * \param node the node to be inserted + * + * Actual position on return: at the inserted node + */ +void MC_Insert_FUN(MC_List_TYPE *list, MC_TYPE node); + +/** \brief add at the end of the list + * \param list the list + * \param node the node to be added + * + * Actual position on return: at the inserted node (before the last node, not at end!) + */ +void MC_Add_FUN(MC_List_TYPE *list, MC_TYPE node); + +/** \brief remove the node at the current position + * \param list the list + * \return the removed node or NULL when the list is empty or the position is at end + * + * Actual position on return: after the taken node + * + * Note: it is the responsibility of the caller to free the node if it is not used + * anymore + */ +MC_TYPE MC_Take_FUN(MC_List_TYPE *list); + +/** \brief remove and delete all nodes + * \param list the list + * \param deleteFunc the kill function of the node + * + * Calls the kill function for every node. The list is + * empty on return. + */ +void MC_Delete_FUN(MC_List_TYPE *list, void (*deleteFunc)(MC_TYPE node)); + +#ifndef MC_DO_NOT_UNDEF +#undef MC_NAME +#undef MC_TYPE +#endif diff --git a/ofac.c b/ofac.c index 9c48f7d8..68ee73e7 100644 --- a/ofac.c +++ b/ofac.c @@ -437,6 +437,7 @@ INIT(SiteInit); /* site specific initializations */ INIT(StatisticsInit); + INIT(SctStartup); } /*--------------------------------------------------------------------------*/ diff --git a/scriptcontext.c b/scriptcontext.c new file mode 100644 index 00000000..8f544afc --- /dev/null +++ b/scriptcontext.c @@ -0,0 +1,1458 @@ +#include +#include +#include +#include +#include "sics.h" +#include "sicsobj.h" +#include "initializer.h" +#include "commandlog.h" +#include "hipadaba.h" +#include "sicshipadaba.h" +#include "dynstring.h" +#include "ascon.h" +#include "errormsg.h" + +/** \file + * \brief script context implementation (Sct) + */ + +/** \brief the maximum hdb path length */ +#define MAX_HDB_PATH 1024 + +typedef enum { + sct_send_keyword, + sct_result_keyword, + sct_command_keyword, + sct_path_keyword, + sct_update_keyword, + sct_take_keyword, + sct_complete_keyword, + sct_chain_keyword, + sct_next_keyword, + sct_steplist_keyword, + sct_no_keyword +} SctKeyword; + +/* keywords must have the same order as SctKeyword */ +static char *sctKeywords[]={ + "send", + "result", + "command", + "path", + "update", + "take", + "complete", + "chain", + "next", + "steplist", + NULL +}; + +static char *reset_type=""; + +typedef struct SctList SctList; + +typedef struct SctChain { + struct SctChain *next; + char *command; + SctList *scriptList; + pHdb relatedNode; + ErrMsg *msg; + int doit; + char *dynamic; +} SctChain; + +/* define SctChainList and their functions */ +#define MC_NAME(T) SctChain##T +#include "mclist.c" + +typedef enum {set_type, timed_type, manual_type, no_type} SctScriptListType; +static char *slTypes[]={"set", "timed", "manual", NULL}; + +struct SctList { + struct SctList *next; + char *name; + SctChain *beginScript; + SctChain *endScript; + SctScriptListType type; + int dirty; + double interval; + double lastPeriod; + SctChain *lastScript; + SctChainList chains; +}; + +/* define SctListList and their functions */ +#define MC_NAME(T) SctList##T +#include "mclist.c" + +typedef struct Sct Sct; + +typedef struct SctParData { + struct SctParData *next; + Sct *sct; + SctChain *scriptChain; + int pending, inprogress; + int refCnt; + hdbValue target; + SCStore conCtx; +} SctParData; + +typedef struct SctObjectName { + struct SctObjectName *next; + char *name; +} SctObjectName; + +/* define SctObjectNameList and their functions */ +#define MC_NAME(T) SctObjectName##T +#include "mclist.c" + +struct Sct { + pObjectDescriptor desc; + char *name; + SctListList scriptLists; + SctObjectNameList names; /* created with the "object" command, needed for kill only */ + AsconPtr ascon; + pHdb currentNode; + SctParData *foundData; + char *cmd; + char *result; + char *nextScript; + SctChain *runningChain; + SCStore debugConn; + char *debugCommand; + int sent; + SctKeyword operation; + int killTask; + int killObj; + int verbose; + int taskSteps; +}; + +static Sct *sct = NULL; + +static void SctFreeParData(void *d) { + SctParData *data=d; + data->refCnt--; + if (data->refCnt == 0) { + if (data->conCtx) SCStoreFree(data->conCtx); + free(data); + } +} + +pHdb SctFindObjNode(char *name) { + CommandList *cmd; + pDummy obj; + + cmd = FindCommand(pServ->pSics, name); + if (cmd == NULL) return NULL; + obj = (void *)cmd->pData; + if (obj == NULL || obj->pDescriptor->parNode==NULL) return NULL; + return obj->pDescriptor->parNode; +} + +/** \brief find the node with the relative path relpath + * \param node the root node (where to start) + * \param relpath an absolute or relative path + * \param parentPtr a pointer to the found parent (may be NULL if not used) + * \param nodePtr a pointer to the found node + * \return the name of the node + * + * An abolute path starts with a slash, a relative path equal to "." + * means that the input node is returned. A syntax like "..", "../down" + * is also allowed, but the double dots must appear at the start of the path. + * + * This routine may also be used in order to find the parent node of + * a node not yet existing. In this case the nodePtr will point + * to a NULL pointer on return, and a pointer to the last element + * in the path is returned. + * + * Nodes anchored in the sics object list are also found when + * the path starts with "/sics/" + */ +char *SctFindNode(pHdb node, char *relpath, pHdb *parentPtr, pHdb *nodePtr) { + char *path; + char buffer[MAX_HDB_PATH]; + pHdb root = NULL; + char *name; + char *slash; + + if (strlen(relpath) >= sizeof(buffer)) { + *nodePtr = NULL; + return NULL; /* relpath too long */ + } + strcpy(buffer, relpath); + path = buffer; + if (path[0] == '/') { + if (strncmp(path, "/sics/", 6) == 0) { + /* sics object case */ + slash = strchr(path+6, '/'); + if (slash != NULL) { + *slash = '\0'; + } + node = SctFindObjNode(path); + if (node == NULL) goto notFound; + if (slash == NULL) goto nodeFound; + path = slash+1; + /* root node is sics object, path is relative to it */ + } else { + /* absolute path */ + node = GetHipadabaRoot(); + /* strip off first slash, root node is sics root */ + path++; + } + } else if (path[0] == '.') { + if (path[1] == '\0') { + /* this path */ + goto nodeFound; + } + /* upper node */ + while (strncmp(path, "..", 2) == 0 && node != NULL) { + node = node->mama; + if (path[2] == '\0') goto nodeFound; + path += 3; + } + if (node == NULL) goto notFound; + } + + /* now go down in path */ + while (node != NULL) { + root = node; + slash = strchr(path, '/'); + if (slash != NULL) *slash='\0'; + for (node = root->child; node != NULL; node = node->next) { + if (strcasecmp(node->name, path) == 0) { + if (slash == NULL) goto nodeFound; + path = slash + 1; + break; + } + } + } + + if (slash == NULL) { + if (parentPtr != NULL) { + *parentPtr = root; + } + *nodePtr = NULL; + /* the returned name must be taken from the end of relpath, as path is no longer valid */ + name = relpath + (path - buffer); + goto finish; + } +notFound: + node = NULL; +nodeFound: + *nodePtr = node; + if (node) { + if (parentPtr != NULL) { + *parentPtr = node->mama; + } + name = node->name; + } else { + if (parentPtr != NULL) { + *parentPtr = NULL; + } + name = NULL; + } +finish: + return name; +} + +/** \brief get the absolute path of a node anchored in the + * Hipadaba root or in a sics object + * \param nodeArg the input node + * \param path the result + * \param pathlen length of the result + * \return 1 on success, 0 on failure + */ +int SctGetPath(pHdb nodeArg, char *path, size_t pathlen) { + pHdb node, root; + int len, pos, l; + static char *sics="/sics"; + + /* determine path length and root node */ + root = nodeArg; + len = 0; + for (node = nodeArg; node != NULL; node = node->mama) { + len += strlen(node->name) + 1; + if (len >= pathlen) return 0; /* buffer overflow (may be recursize path?) */ + root = node; + } + + /* check root and add prefix */ + path[0]='\0'; + if (root != GetHipadabaRoot()) { /* not anchored in root */ + if (!FindCommand(pServ->pSics, root->name)) { + /* not a sicsobject, give up */ + path[0]='\0'; + return 0; + } + /* sics object case */ + l = strlen(sics); + len += l; + if (len >= pathlen) return 0; + strncpy(path, sics, l); + } + + /* build the path backwards */ + path[len]='\0'; + pos = len; + for (node = nodeArg; node != NULL; node = node->mama) { + len = strlen(node->name); + pos -= len; + assert(pos>0); + strncpy(path+pos, node->name, len); + pos--; + path[pos]='/'; + } + return 1; +} + +static int UpdateNode(SConnection *con, ErrMsgList *e, pHdb node, int argc, char *argv[]) { + hdbValue newValue; + static char errtxt[512]; + char *str; + int status; + + if (!cloneHdbValue(&node->value, &newValue)) { + ErrPutMsg(e, NULL, "no memory"); + return 0; + } + str = ConcatArgs(argc, argv); + if (str == NULL) { + ErrPutMsg(e, NULL, "no memory"); + return 0; + } + if (!readHdbValue(&newValue, str, errtxt, sizeof errtxt)) { + ErrPutMsg(e, errtxt, "conversion failure"); + return 0; + } + free(str); + status = UpdateHipadabaPar(node, newValue, con); + ReleaseHdbValue(&newValue); + return status; +} + +static int SctFindKeyword(char *name, char *keywords[]) { + int i; + for (i=0; keywords[i]!=NULL; i++) { + if (strcasecmp(name, keywords[i]) == 0) return i; + } + return i; +} + +static SctChain *SctMakeChain(char *command) { + SctChain *sc; + + sc = calloc(1, sizeof(*sc)); + if (sc == NULL) return NULL; + sc->doit = 0; + sc->dynamic = NULL; + sc->command = command; + sc->scriptList = NULL; + sc->msg = NULL; + return sc; +} + +static void SctSetListDirty(SctList *sl) { + if (!sl->dirty) { + sl->dirty = 1; + if (sl->beginScript) { + sl->beginScript->doit = 1; + } + } +} + +static void SctSetDirty(SctChain *sc) { + SctSetListDirty(sc->scriptList); + sc->doit = 1; +} + +static int SctUpdateCallback(void *user, void *conn, pHdb node, hdbValue value) { + SctParData *data = user; + SConnection *con; + char path[MAX_HDB_PATH]; + pDynString str; + + if (data->inprogress) { + if (data->sct->operation == sct_complete_keyword) { + data->inprogress = 0; + SctGetPath(node, path, sizeof path); + con = SCStorePush(data->conCtx); + str = formatValue(value, node); + SCPrintf(con, eStatus, "%s = %s", path, + GetCharArray(str)); + DeleteDynString(str); + SCStorePop(data->conCtx); + } + } + return 1; +} + +static int SctSetCallback(void *user, void *conn, pHdb node, hdbValue value) { + SctParData *data = user; + SConnection *oldCon; + char path[MAX_HDB_PATH]; + pDynString str; + + if (data->scriptChain) { + if (data->pending) { + SctGetPath(node, path, sizeof path); + oldCon = SCStorePush(data->conCtx); + str = formatValue(value, node); + SCPrintf(oldCon, eStatus, "target of %s changed to %s", path, + GetCharArray(str)); + DeleteDynString(str); + SCStorePop(data->conCtx); + } + data->conCtx = SCSave(conn, data->conCtx); + data->pending = 1; + SctSetDirty(data->scriptChain); + copyHdbValue(&value, &data->target); + return 0; + } + return 1; +} + +static int SctReadCallback(void *user, void *conn, pHdb node, hdbValue value) { + SctParData *data = user; + + data->sct->foundData = data; + if (data->sct->operation == sct_take_keyword && data->pending) { + data->pending = 0; + data->inprogress = 1; + return 0; + } + return 1; +} + +static int SctExec(SConnection *con, SicsInterp *sics, void *object, int argc, char *argv[]) { + SctKeyword keyword; + SctList *sl; + SctChain *sc; + pHdb node; + hdbValue newValue; + pDynString result; + char *args; + char path[MAX_HDB_PATH]; + SConnection *dcon; + + if (sct == NULL) { + SCPrintf(con, eError, "ERROR: %s may be called only in proper context", argv[0]); + return 0; + }; + if (argc < 2) { + SCPrintf(con, eInError, "ERROR: missing %s subcommand", argv[0]); + goto quit; + }; + if (sct->verbose != 0) { + args = ConcatArgs(argc, argv); + dcon = SCStorePush(sct->debugConn); + SCPrintf(dcon, eWarning, "commnd: %s", args); + SCStorePop(sct->debugConn); + free(args); + args = NULL; + } + keyword = SctFindKeyword(argv[1], sctKeywords); + sct->operation = keyword; + switch (keyword) { + case sct_send_keyword: + if (argc < 4) { + SCPrintf(con, eInError, "ERROR: should be: %s send