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