#include #include #include #include #include #include #include #include #include #include #include #include "sics.h" #include "splitter.h" #include "ascon.i" #include "uselect.h" static double lastClose = 0; /* time of last close operation */ static AsconProtocol *protocols = NULL; /* 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; /* the resolution of this function is usec, if the machine supports this and the mantissa of a double is 51 bits or more (31 for sec and 20 for micro) */ gettimeofday(&now, NULL); return now.tv_sec + now.tv_usec / 1e6; } void AsconError(Ascon *a, char *msg, int errorno) { static char *stateText[]={ "not connected", "connect start", "connecting", "connect done", "write start", "writing", "write done", "read start", "reading", "read done", "idle", "failed", "timeout" }; char *state; if (a->state < 0 || a->state >= AsconMaxState) { state = "bad state"; } else { state = stateText[a->state]; } DynStringCopy(a->errmsg, "ASCERR: "); if (errorno == 0) { DynStringConcat(a->errmsg, msg); DynStringConcat(a->errmsg, " ("); DynStringConcat(a->errmsg, state); DynStringConcat(a->errmsg, " state)"); } else { DynStringConcat(a->errmsg, strerror(errorno)); DynStringConcat(a->errmsg, " ("); DynStringConcat(a->errmsg, state); DynStringConcat(a->errmsg, " state, "); DynStringConcat(a->errmsg, msg); DynStringConcat(a->errmsg, ")"); } a->state = AsconFailed; } static void AsconConnect(Ascon * a) { /* input state: AsconConnectStart output state: AsconFailed or AsconConnecting */ int ret; struct sockaddr_in adr; char *colon; int port; int oldopts; /* wait 0.5 sec before connecting again after a close 2 reasons for that: - it seems that connecting immediately to a closed port fails. We will avoid some "Connection refused" error messages. - a bug in the lantronix terminal: if a channel is closed and reopened within short time the connect may be succesful, but a message will be sent on the channel! In principle we need only to wait when connecting to the same address and port, but bookkeeping would be too complicated. */ if (DoubleTime() < lastClose + 0.5) return; if (a->fd < 0) { a->fd = socket(AF_INET, SOCK_STREAM, 0); if (a->fd < 0) { AsconError(a, "ASC1", errno); return; } } if (a->hostport == NULL) { AsconError(a, "no host:port given", 0); 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; } 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) { switch (errno) { case EINPROGRESS: case EALREADY: case EISCONN: a->state = AsconConnecting; break; default: AsconError(a, "ASC2", errno); return; } } a->state = AsconConnecting; return; } 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 = uselect(fd + 1, &rmask, NULL, NULL, &tmo); if (ret > 0) { l = recv(fd, garbage, sizeof garbage - 1, 0); if (l > 0) { /* swallow */ garbage[l] = '\0'; printf("(((%s)))\n", garbage); 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 = uselect(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 = uselect(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 AsconBaseHandler(Ascon * a) { int ret; int l; char chr; double now = DoubleTime(); if (now > lastCall + 0.5) { /* AsconBaseHandler 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 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, "ASC3", errno); } break; case AsconWriteStart: if (a->sendTerminator) { DynStringConcat(a->wrBuffer, a->sendTerminator); } 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, "ASC4", errno); /* sets state to AsconFailed */ } 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); if (ret <= 0) { if (ret < 0) { AsconError(a, "ASC5", errno); return 0; } if (a->timeout > 0) { if (DoubleTime() - a->start > a->timeout) { AsconError(a, "no response", 0); a->state = AsconTimeout; } } return 0; } a->lastChar = chr; a->start = DoubleTime(); if (DynStringConcatChar(a->rdBuffer, chr) == 0) { AsconError(a, "ASC6", errno); return 0; } break; default: break; } return 1; } void AsconInsertProtocol(AsconProtocol *protocol) { protocol->next = protocols; protocols = protocol; } AsconHandler AsconSetHandler(Ascon *a, SConnection *con, int argc, char *argv[]) { AsconProtocol *p; if (argc < 1) return NULL; if (strcasecmp(argv[0], "std") == 0) { if (argc < 2) return NULL; AsconStdInit(a, con, argc, argv); return AsconStdHandler; } for (p = protocols; p!= NULL; p=p->next) { if (strcasecmp(p->name, argv[0]) == 0) { if (p->init(a, con, argc, argv)) { return p->handler; } else { return NULL; } } } return NULL; } /* --- standard handler ---- */ int AsconStdHandler(Ascon * a) { int result; char chr; int ret; switch (a->state) { case AsconWriteStart: if (strstr(GetCharArray(a->wrBuffer), "@@NOSEND@@") != NULL) { a->state = AsconWriteDone; return 1; } break; /* go to the base handler */ case AsconWriting: if (a->readState) { /* last char was CR */ ret = AsconReadChar(a->fd, &chr); if (ret > 0) { if (chr == '\n') { /* swallow LF after CR */ a->readState = 0; } else { /* garbage character found -> swallow */ } } } break; /* go to the base handler */ case AsconReading: result = AsconBaseHandler(a); if (result == 0) return 0; chr = a->lastChar; if (a->replyTerminator != NULL) { if (strchr(a->replyTerminator, chr) != NULL) { DynStringConcatChar(a->rdBuffer, '\0'); a->state = AsconReadDone; } } else { if (chr == '\n') { if (a->readState) { /* last char was CR */ /* swallow LF after CR */ a->readState = 0; } else { DynStringBackspace(a->rdBuffer); /* remove LF */ DynStringConcatChar(a->rdBuffer, '\0'); a->state = AsconReadDone; } } else if (chr == '\r') { a->readState = 1; /* set 'last char was CR' */ DynStringBackspace(a->rdBuffer); /* remove CR */ DynStringConcatChar(a->rdBuffer, '\0'); a->state = AsconReadDone; } } return 1; /* base handler was already called */ default: break; } return AsconBaseHandler(a); } int AsconStdInit(Ascon *a, SConnection *con, int argc, char *argv[]) { a->hostport = strdup(argv[1]); if (argc > 2) { a->sendTerminator = strdup(argv[2]); } else { a->sendTerminator = strdup("\n"); } if (argc > 3) { a->timeout = atof(argv[3]); } else { a->timeout = 2.0; /* sec */ } a->replyTerminator = NULL; if (argc > 4 && argv[4][0] != '\0') { a->replyTerminator = strdup(argv[4]); } return 1; } /* --- implementation of higher level interface ---- */ char *ConcatArgs(int argc, char *argv[]) { return Arg2Tcl(argc, argv, NULL, -1); } Ascon *AsconMake(SConnection * con, int argc, char *argv[]) { Ascon *a; char *args; a = calloc(1, sizeof(*a)); if (a == NULL) { SCWrite(con, "ERROR: no memory", eError); return NULL; } a->fd = -1; a->state = AsconConnectStart; a->timeout = 2.0; /* 2 sec default */ a->reconnectInterval = 10; /* 10 sec default */ a->replyTerminator = NULL; a->sendTerminator = NULL; a->hostport = NULL; a->responseValid = 0; a->handler = AsconSetHandler(a, con, 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->errmsg = CreateDynString(60, 63); return a; } void AsconKill(Ascon * a) { if (a->fd > 0) { close(a->fd); lastClose = DoubleTime(); } DeleteDynString(a->rdBuffer); DeleteDynString(a->wrBuffer); DeleteDynString(a->errmsg); if (a->hostport) { free(a->hostport); } if(a->sendTerminator){ free(a->sendTerminator); } if(a->replyTerminator){ free(a->replyTerminator); } if (a->private != NULL && a->killPrivate != NULL) { a->killPrivate(a->private); } free(a); } void AsconDisconnect(Ascon * a) { if (a->fd > 0) { close(a->fd); } a->fd = -1; a->state = AsconConnectStart; } AsconStatus AsconTask(Ascon * a) { int result; double now; while (1) { result = a->handler(a); switch (a->state) { case AsconNotConnected: return AsconOffline; case AsconConnecting: return AsconUnconnected; case AsconConnectDone: a->state = AsconIdle; return AsconReady; case AsconWriteDone: if (a->noResponse) { a->state = AsconIdle; return AsconReady; } a->state = AsconReadStart; break; case AsconReading: if (result != 0) { break; /* char received: try again, probably more characters pending */ } return AsconPending; case AsconReadDone: a->state = AsconIdle; a->responseValid = 1; return AsconReady; case AsconIdle: return AsconReady; case AsconTimeout: a->state = AsconIdle; return AsconFailure; case AsconFailed: now = DoubleTime(); if (a->fd > 0) { close(a->fd); lastClose = now; a->fd = -1; } if (now > a->lastReconnect + a->reconnectInterval) { a->lastReconnect = now; a->state = AsconConnectStart; } return AsconFailure; default: return AsconPending; } } } int AsconWrite(Ascon * a, char *command, int noResponse) { if (a->state != AsconIdle) { /* this might happen if a script is sending after an error */ return 0; } DynStringCopy(a->wrBuffer, command); a->noResponse = noResponse; a->state = AsconWriteStart; a->responseValid = 0; AsconTask(a); return 1; } char *AsconRead(Ascon * a) { if (a->noResponse) { a->noResponse = 0; return ""; } if (a->state != AsconIdle) { a->state = AsconIdle; return "programming error in devser.c/ascon.c"; } if (a->responseValid) { a->responseValid = 0; return GetCharArray(a->rdBuffer); } return NULL; } char *AsconGetError(Ascon *a) { return GetCharArray(a->errmsg); }