Files
sics/ascon.c
zolliker 6c4f57ec6f - introduced <controller> actions function for listing actions
- introduced <controller> reconnect function
2010-04-13 14:17:58 +00:00

699 lines
16 KiB
C

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#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;
}
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<length; i++) {
PrintChar(data[i]);
}
}
printf("<written\n");
*/
FD_ZERO(&wmask);
FD_SET(fd, &wmask);
ret = uselect(fd + 1, NULL, &wmask, NULL, &tmo);
if (ret <= 0)
return ASCON_SELECT_ERROR;
ret = send(fd, data, length, 0);
if (ret > 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, l;
char *cmd, *opt;
switch (a->state) {
case AsconWriteStart:
cmd = GetCharArray(a->wrBuffer);
a->lineCount = 1;
if (a->separator != NULL) { /* multiline mode enabled */
l = strlen(cmd);
if (l> 0 && cmd[l-1] == '}') {
opt = strrchr(cmd, '{');
if (opt != NULL) {
if (sscanf(opt, "{%d}", &a->lineCount) == 1) {
/* remove option */
for (l = strlen(opt); l > 0; l--) {
DynStringBackspace(a->wrBuffer);
}
}
}
}
} else if (strstr(GetCharArray(a->wrBuffer), "@@NOSEND@@") != NULL) {
a->state = AsconWriteDone;
return 1;
}
if(a->lineCount == 0){
a->noResponse = 1;
} else {
a->noResponse = 0;
}
break; /* go to the base handler */
case AsconReading:
if (a->lineCount == 0) {
/* no response expected */
a->state = AsconReadDone;
return 1;
}
result = AsconBaseHandler(a);
if (result == 0)
return 0;
chr = a->lastChar;
if (chr == '\0') { /* skip NUL characters */
DynStringBackspace(a->rdBuffer); /* remove NUL */
return 1;
}
if (a->replyTerminator != NULL) {
if (strchr(a->replyTerminator, chr) != NULL) {
a->state = AsconReadDone;
if (chr == '\n' || chr == '\r') {
DynStringBackspace(a->rdBuffer); /* remove LF or CR */
}
}
} else {
if (chr == '\n') { /* LF */
DynStringBackspace(a->rdBuffer); /* remove LF */
if (a->readState) { /* last char was CR */
/* LF after CR is not a terminator */
a->readState = 0;
} else {
a->state = AsconReadDone;
}
} else if (chr == '\r') { /* CR */
DynStringBackspace(a->rdBuffer); /* remove CR */
a->readState = 1; /* set 'last char was CR' */
a->state = AsconReadDone;
}
}
if (a->state == AsconReadDone && a->lineCount > 1) {
if (a->separator != NULL) {
DynStringConcat(a->rdBuffer, a->separator);
}
a->lineCount--;
a->state = AsconReading;
}
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]);
}
a->separator = NULL;
if (argc > 5 && argv[5][0] != '\0') {
a->separator = strdup(argv[5]);
}
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->readState = 0;
a->lineCount = 1;
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->separator) {
free(a->separator);
}
if (a->private != NULL && a->killPrivate != NULL) {
a->killPrivate(a->private);
}
free(a);
}
void AsconDisconnect(Ascon * a)
{
if (a->fd > 0) {
close(a->fd);
lastClose = DoubleTime();
}
a->fd = -1;
a->state = AsconNotConnected;
}
void AsconReconnect(Ascon * a, char * hostport)
{
if (a->fd > 0) {
close(a->fd);
lastClose = DoubleTime();
}
if (hostport != NULL && hostport[0] != '\0') {
if (a->hostport) {
free(a->hostport);
}
a->hostport = strdup(hostport);
}
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);
}