archie/tcl-dp/api/dpApi.c
2024-05-27 16:13:40 +02:00

434 lines
9.9 KiB
C

/*
* dpAPI.c - contains a basic C API for accessing Tcl-DP RPC servers
* This is a standalone file and does not require DP or Tcl.
*
* Note that unlike the Tcl and DP sources, this file
* requires an ANSI C compiler.
*
* This API is extremely multithread unsafe.
*
*/
#include <stdio.h>
#include <string.h>
#include "dpApi.h"
/*------------------ DP RPC protocol defines -----------------*/
#define TOK_RPC 'e'
#define TOK_RDO 'd'
#define TOK_RET 'r'
#define TOK_ERR 'x'
/*
* The following strings are used to provide callback and/or error
* catching for RDOs. c = callback, e = onerror, ce = both
*/
/*
* sprintf template when both callback and onerror are specified.
* Params are cmd, onerror, callback
*/
static char *ceCmdTemplate =
"if [catch {%s} dp_rv] {\
dp_RDO $dp_rpcFile set errorInfo \"$errorInfo\n while remotely executing\n%s\"; \
dp_RDO $dp_rpcFile eval \"%s \\{$dp_rv\\}\"\
} else {\
dp_RDO $dp_rpcFile eval \"%s \\{$dp_rv\\}\"\
}";
/*
* sprintf template when just onerror is specified.
* Params are cmd, onerror
*/
static char *eCmdTemplate =
"if [catch {%s} dp_rv] {\
dp_RDO $dp_rpcFile set errorInfo \"$errorInfo\n while remotely executing\n%s\"; \
dp_RDO $dp_rpcFile eval \"%s \\{$dp_rv\\}\"\
}";
/*
* sprintf template when just callback is specified.
* Params are cmd, callback
*/
static char *cCmdTemplate =
"set dp_rv [%s]; dp_RDO $dp_rpcFile eval \"%s \\{$dp_rv\\}\"";
/*----------------------- Globals --------------------------*/
/*
* Holds the latest message received.
* bufPtr points to the free space
*/
#define DP_BUFFER_SIZE 8192
char retStr[DP_BUFFER_SIZE];
static char *bufPtr;
/*-------------------- Internal Routines -------------------*/
static int SendRPCMessage (DPServer server, char token,
int id, char *msgStr);
/*
*--------------------------------------------------------------
*
* Dp_RPC --
*
* Send an RPC message to the given server.
*
* If tv is NULL, there is no timeout, otherwise
* the RPC will timeout once tv's amount of time
* has passed.
*
* Results:
* errorPtr is non-zero if there was an error.
* Returns the Tcl result in a static string.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
char *
Dp_RPC(server, mesgStr, tv, errorPtr)
DPServer server; /* in: TCP socket to send on */
char *mesgStr; /* in: RPC string to send */
struct timeval *tv; /* in: Timeout value for select() */
int *errorPtr; /* out: POSIX errorcode */
{
int rc, len;
int amtRecv = -1, totalAmt = 0;
*errorPtr = 0;
/*
* Send the RPC to the remote server. Note the 12 is
* just a random ID since we don't use IDs.
*/
rc = SendRPCMessage(server, TOK_RPC, 12, mesgStr);
if (rc <= 0) {
*errorPtr = errno;
strcpy(retStr, "Error writing on socket");
return retStr;
}
/*
* Now we want to recv the reply.
* We spin in a loop waiting for the entire message
* to arrive. This gets a bit messy.
*/
bufPtr = retStr;
while (amtRecv < totalAmt) {
char lengthStr[7];
rc = Dp_WaitForSocket(server, tv);
if (rc <= 0) {
if (rc == 0) {
*errorPtr = -1;
strcpy(retStr, "RPC timed out");
return retStr;
} else {
*errorPtr = errno;
strcpy(retStr, "Select error");
return retStr;
}
}
amtRecv = recv(server, bufPtr, (retStr + DP_BUFFER_SIZE) - bufPtr, 0);
if ((amtRecv >= 6) && (totalAmt == 0)) {
/*
* Extract the length field from the incoming message
* so we know when we have recv'd the entire message.
*/
strncpy(lengthStr, retStr, 6);
lengthStr[6] = '\0';
totalAmt = atoi(lengthStr);
} else if (amtRecv == 0) {
/*
* EOF
*/
break;
}
bufPtr += amtRecv;
}
if (retStr[7] == 'x') {
*errorPtr = -1;
}
len = totalAmt - 16;
memcpy(retStr, &retStr[16], len);
retStr[len] = '\0';
return retStr;
}
/*
*--------------------------------------------------------------
*
* Dp_RDOSend --
*
* Send an RDO message to the given server.
* It is the caller's responsibilty to call
* Dp_RDORead if we need a return code or error.
* The size of the RDO is limited to BUFFER_SIZE - 1.
*
* Results:
* Amount sent.
*
* Side effects:
* Destroys previous value of retStr.
*
*--------------------------------------------------------------
*/
int
Dp_RDOSend(server, mesgStr, flags)
DPServer server;
char *mesgStr;
int flags;
{
char bigBuf[8096];
/*
* This is our fake callback/error Tcl script.
* This is necessary for compatibility with DP 4.0.
* Note since we will never evaluate the return
* script, this can be anything.
*/
char *dummy = "mperham";
int len, amt;
switch (flags) {
case DP_REPORT_ERROR:
sprintf(bigBuf, eCmdTemplate, mesgStr, mesgStr, dummy);
break;
case DP_RETURN_VALUE:
sprintf(bigBuf, cCmdTemplate, mesgStr, mesgStr, dummy);
break;
case DP_RETURN_VALUE | DP_REPORT_ERROR:
sprintf(bigBuf, ceCmdTemplate, mesgStr, mesgStr, dummy, dummy);
break;
default:
strcpy(bigBuf, mesgStr);
break;
}
amt = SendRPCMessage(server, TOK_RDO, 0, bigBuf);
/*
* Now we need to hack the amount sent because
* the caller knows nothing about the templates
* used above and thus will think an error
* has happened because amtSent != strlen(mesgStr)
*/
len = strlen(mesgStr);
if (amt >= len) {
return len;
} else {
return amt;
}
}
/*
*--------------------------------------------------------------
*
* Dp_RDORead --
*
* Recv's an RDO response from the given server.
*
* Results:
* Pointer to the string from the DP server or
* NULL with errorPtr set to a POSIX error.
*
* Side effects:
* Destroys previous value of retStr.
*
*--------------------------------------------------------------
*/
char *
Dp_RDORead(server, errorPtr)
DPServer server;
int *errorPtr;
{
int amount, len;
amount = recv(server, retStr, DP_BUFFER_SIZE, 0);
if (amount <= 0) {
if (amount == 0) {
/*
* EOF on socket
*/
*errorPtr = ECONNRESET;
return NULL;
}
*errorPtr = errno;
return NULL;
}
len = amount - 16;
memcpy(retStr, &retStr[16], len);
retStr[len] = '\0';
return retStr;
}
/*
*--------------------------------------------------------------
*
* Dp_WaitForServer --
*
* Waits until the given socket is readable.
*
* Results:
*
* 0 if we timeout before the socket is readable
* < 0 if there is an error.
* > 0 if the socket is now readable.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
int
Dp_WaitForServer(server, tv)
DPServer server;
struct timeval *tv;
{
fd_set readFD;
FD_ZERO(&readFD);
FD_SET(server, &readFD);
return select(1, &readFD, NULL, NULL, tv);
}
/*
*--------------------------------------------------------------
*
* Dp_ConnectToServer --
*
* Creates a TCP connection to a given IP addr and port.
*
* Results:
* returns the socket or -1 on error.
*
* Side effects:
* Creates a new socket. This function can block forever
* at the Dp_WaitForServer() or recv() calls. Be careful.
*
*--------------------------------------------------------------
*/
DPServer
Dp_ConnectToServer(int inetAddr, int port)
{
DPServer sock;
struct sockaddr_in myAddr;
struct sockaddr_in destAddr;
int rc;
char EOL;
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
return -1;
}
myAddr.sin_addr.s_addr = INADDR_ANY;
myAddr.sin_family = AF_INET;
myAddr.sin_port = 0;
rc = bind(sock, (struct sockaddr *) &myAddr, sizeof(myAddr));
if (rc < 0) {
return -1;
}
destAddr.sin_addr.s_addr = htonl(inetAddr);
destAddr.sin_family = AF_INET;
destAddr.sin_port = htons((unsigned short)port);
rc = connect(sock, (struct sockaddr *) &destAddr,
sizeof(destAddr));
if (rc < 0) {
return -1;
}
/*
* The Tcl-DP RPC library sends "Connection accepted"
* upon successful linkup with a DP RPC server. We
* need to strip that off now so that future reads
* don't get confused.
*
* There is a slight problem here in that we don't know
* what the EOL indictator is. We'll read in 20, which
* is 19 + 1 character for EOL. If the last char is
* \r, we'll read in another byte since the server
* sent \r\n. This completely overlooks the fact
* that Macs send \r as their EOL, but since DP
* isn't suppose to run on Macs, this is an acceptable
* hack.
*/
Dp_WaitForSocket(sock, NULL);
rc = recv(sock, retStr, 20, 0);
while (rc < 20) {
rc += recv(sock, &retStr[rc], 20 - rc, 0);
}
EOL = retStr[19];
retStr[19] = '\0';
if (strcmp("Connection accepted", retStr)) {
return -1;
}
if (EOL == '\r') {
if (recv(sock, &EOL, 1, 0) != 1) {
return -1;
}
}
return sock;
}
/* ============================================================= *
* =================== Internal Routines ======================= *
* ============================================================= */
/*
*--------------------------------------------------------------
*
* SendRPCMessage --
*
* Send an RPC message on the given socket.
*
* Results:
* Number of bytes sent on the socket.
*
* Side effects:
* None.
*
*--------------------------------------------------------------
*/
static int
SendRPCMessage(server, token, id, msgStr)
DPServer server;
char token;
int id;
char *msgStr;
{
char *bufStr;
int result, totalLength;
totalLength = strlen(msgStr) + 16;
bufStr = malloc(totalLength + 1);
sprintf(bufStr, "%6d %c %6d %s", totalLength, token, id++%1000000,
msgStr);
result = send(server, bufStr, totalLength, 0);
free(bufStr);
if (result >= 16) {
result -= 16;
}
return result;
}