434 lines
9.9 KiB
C
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;
|
|
}
|