home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Magazyn Amiga Shareware Floppies
/
ma64.dms
/
ma64.adf
/
FTPMount-1.0
/
Source
/
tcp.c
< prev
next >
Wrap
C/C++ Source or Header
|
1995-09-06
|
37KB
|
1,723 lines
/*
* This source file is Copyright 1995 by Evan Scott.
* All rights reserved.
* Permission is granted to distribute this file provided no
* fees beyond distribution costs are levied.
*/
/*
* a message passing API for amitcp
*/
#include <exec/types.h>
#include <exec/ports.h>
#include <dos/dos.h>
#include <dos/dostags.h>
#include <proto/exec.h>
#include <proto/dos.h>
#define INLINES_AS_MACROS 1 /* SAS doesn't seem to do inlines properly */
#include <proto/socket.h>
/* these particularly need to be the amitcp includes */
#include <sys/errno.h>
#include <sys/ioctl.h>
#include <netdb.h>
#include <string.h>
#include "evtypes.h"
#include "verify.h"
#include "tcp.h"
tcpmessage *new_tcpmessage(struct MsgPort *reply_port)
{
tcpmessage *z;
z = (tcpmessage *)allocate(sizeof(*z), V_tcpmessage);
if (!z) return nil;
ensure(z, V_tcpmessage);
z->header.mn_Node.ln_Type = NT_MESSAGE;
z->header.mn_Node.ln_Pri = 0;
z->header.mn_Node.ln_Name = "TCPMessage";
z->header.mn_ReplyPort = reply_port;
z->header.mn_Length = sizeof(*z);
z->command = TCP_NOOP;
z->ident = nil;
z->address.l = 0;
z->port.w = 0;
z->data = nil;
z->interrupt = nil;
z->length = 0;
z->result = 0;
z->error = NO_ERROR;
z->flags = 0;
return z;
}
void free_tcpmessage(tcpmessage *tm)
{
verify(tm, V_tcpmessage);
ensure(tm, 0);
deallocate(tm, V_tcpmessage);
return;
}
tcpident *new_tcpident(sb32 fd)
{
tcpident *ti;
ti = (tcpident *)allocate(sizeof(*ti), V_tcpident);
if (!ti) return nil;
ensure(ti, V_tcpident);
ti->fd = fd;
ti->eof = false;
return ti;
}
void free_tcpident(tcpident *ti)
{
verify(ti, V_tcpident);
ensure(ti, 0);
deallocate(ti, V_tcpident);
return;
}
void fix_read_set(struct List *wait_list, fd_set *reads, sb32 *max_fd)
{
tcpmessage *tm;
tcpident *ti;
FD_ZERO(reads);
*max_fd = -1;
for (tm = (tcpmessage *)wait_list->lh_Head;
tm->header.mn_Node.ln_Succ;
tm = (tcpmessage *)tm->header.mn_Node.ln_Succ) {
ti = tm->ident;
verify(ti, V_tcpident);
if (ti->fd > *max_fd) *max_fd = ti->fd;
if (tm->command == TCP_LISTEN || tm->command == TCP_READ) {
FD_SET(ti->fd, reads);
break;
}
}
}
void fix_write_set(struct List *wait_list, fd_set *writes, sb32 *max_fd)
{
tcpmessage *tm;
tcpident *ti;
FD_ZERO(writes);
*max_fd = -1;
for (tm = (tcpmessage *)wait_list->lh_Head;
tm->header.mn_Node.ln_Succ;
tm = (tcpmessage *)tm->header.mn_Node.ln_Succ) {
ti = tm->ident;
verify(ti, V_tcpident);
if (ti->fd > *max_fd) *max_fd = ti->fd;
if (tm->command == TCP_WRITE) {
FD_SET(ti->fd, writes);
break;
}
}
}
void non_blocking(struct Library *SocketBase, sb32 fd)
{
long one;
one = 1;
IoctlSocket(fd, FIONBIO, (void *)&one);
}
void unique_name(void *tp, b8 *s, b8 *buffer)
{
b32 task;
task = (b32)tp;
buffer[0] = (task >> 28) & 0xf;
buffer[1] = (task >> 24) & 0xf;
buffer[2] = (task >> 20) & 0xf;
buffer[3] = (task >> 16) & 0xf;
buffer[4] = (task >> 12) & 0xf;
buffer[5] = (task >> 8) & 0xf;
buffer[6] = (task >> 4) & 0xf;
buffer[7] = task & 0xf;
if (buffer[0] > 9) buffer[0] += 'a' - 10; else buffer[0] += '0';
if (buffer[1] > 9) buffer[1] += 'a' - 10; else buffer[1] += '0';
if (buffer[2] > 9) buffer[2] += 'a' - 10; else buffer[2] += '0';
if (buffer[3] > 9) buffer[3] += 'a' - 10; else buffer[3] += '0';
if (buffer[4] > 9) buffer[4] += 'a' - 10; else buffer[4] += '0';
if (buffer[5] > 9) buffer[5] += 'a' - 10; else buffer[5] += '0';
if (buffer[6] > 9) buffer[6] += 'a' - 10; else buffer[6] += '0';
if (buffer[7] > 9) buffer[7] += 'a' - 10; else buffer[7] += '0';
strcpy(&buffer[8], s);
}
void tcp_read(struct Library *SocketBase, tcpmessage *tm, struct List *wait_list, fd_set *reads, sb32 *max_fd)
{
tcpident *ti;
sb32 result;
b8 *s;
truth(SocketBase != nil);
truth(max_fd != nil);
truth(reads != nil);
truth(wait_list != nil);
verify(tm, V_tcpmessage);
ti = tm->ident;
if (!ti) {
tm->result = 0;
tm->error = ERROR_NO_CONNECTION;
ReplyMsg(&tm->header);
return;
}
verify(ti, V_tcpident);
if (tm->length == 0) {
tm->result = 0;
if (ti->eof)
tm->error = ERROR_EOF;
else
tm->error = NO_ERROR;
ReplyMsg(&tm->header);
return;
}
/* socket has got to be set to non-blocking */
if (tm->flags & FLAG_READLINE) {
s = tm->data;
tm->result = 0;
while (1) {
result = recv(ti->fd, s, 1, 0);
if (result == 1) {
tm->result++;
if (*s == '\r' || *s == '\n') {
if (tm->result == 1) { /* blank line ... skip it */
tm->result--;
continue;
}
tm->error = NO_ERROR;
ReplyMsg(&tm->header);
return;
}
s++;
if (tm->result == tm->length) {
tm->error = NO_ERROR;
ReplyMsg(&tm->header);
return;
}
continue;
} else if (result == 0) { /* got to be EOF */
ti->eof = true;
tm->error = ERROR_EOF;
ReplyMsg(&tm->header);
return;
} else {
switch (Errno()) {
case EINTR:
case EWOULDBLOCK: /* nothing there to read yet */
AddTail(wait_list, (struct Node *)tm);
FD_SET(ti->fd, reads);
if (ti->fd > *max_fd) *max_fd = ti->fd;
return;
default: /* something went wrong */
tm->error = ERROR_LOST_CONNECTION;
ReplyMsg(&tm->header);
}
return;
}
}
} else {
result = recv(ti->fd, tm->data, tm->length, 0);
if (result == tm->length) { /* satisfied immediately */
tm->result = tm->length;
tm->error = NO_ERROR;
ReplyMsg(&tm->header);
return;
}
}
if (result == 0) { /* got to be EOF */
ti->eof = true;
tm->result = 0;
tm->error = ERROR_EOF;
ReplyMsg(&tm->header);
return;
}
/* from here we have the stuff we have to wait for */
if (result < 0) {
switch (Errno()) {
case EINTR:
case EWOULDBLOCK: /* nothing there to read yet */
tm->result = 0;
AddTail(wait_list, (struct Node *)tm);
FD_SET(ti->fd, reads);
if (ti->fd > *max_fd) *max_fd = ti->fd;
return;
default: /* something went wrong */
tm->result = 0;
tm->error = ERROR_LOST_CONNECTION;
ReplyMsg(&tm->header);
return;
}
}
truth(result < tm->length);
tm->result = result;
AddTail(wait_list, (struct Node *)tm);
FD_SET(ti->fd, reads);
if (ti->fd > *max_fd) *max_fd = ti->fd;
return;
}
void tcp_write(struct Library *SocketBase, tcpmessage *tm, struct List *wait_list, fd_set *writes, sb32 *max_fd)
{
tcpident *ti;
sb32 result;
truth(SocketBase != nil);
truth(max_fd != nil);
truth(writes != nil);
truth(wait_list != nil);
verify(tm, V_tcpmessage);
ti = tm->ident;
if (!ti) {
tm->result = 0;
tm->error = ERROR_NO_CONNECTION;
ReplyMsg(&tm->header);
return;
}
verify(ti, V_tcpident);
if (tm->length == 0) {
tm->result = 0;
tm->error = NO_ERROR;
ReplyMsg(&tm->header);
return;
}
/* socket has got to be set to non-blocking */
result = send(ti->fd, tm->data, tm->length, 0);
if (result == tm->length) { /* satisfied immediately */
tm->result = tm->length;
tm->error = NO_ERROR;
ReplyMsg(&tm->header);
return;
}
/* from here we have the stuff we have to wait for */
if (result < 0) {
switch (Errno()) {
case EWOULDBLOCK:
case EINTR: /* write couldn't get through immediately */
tm->result = 0;
AddTail(wait_list, (struct Node *)tm);
FD_SET(ti->fd, writes);
if (ti->fd > *max_fd) *max_fd = ti->fd;
return;
default: /* something has gone wrong */
tm->result = 0;
tm->error = ERROR_LOST_CONNECTION;
ReplyMsg(&tm->header);
return;
}
}
truth(result < tm->length);
tm->result = result;
AddTail(wait_list, (struct Node *)tm);
FD_SET(ti->fd, writes);
if (ti->fd > *max_fd) *max_fd = ti->fd;
return;
}
void tcp_read_more(struct Library *SocketBase, tcpmessage *tm, struct List *wait_list, fd_set *reads, sb32 *max_fd)
{
tcpident *ti;
sb32 result;
b8 *s;
truth(SocketBase != nil);
truth(max_fd != nil);
truth(reads != nil);
truth(wait_list != nil);
verify(tm, V_tcpmessage);
ti = tm->ident;
verify(ti, V_tcpident);
if (tm->flags & FLAG_READLINE) {
s = (b8 *)tm->data + tm->result;
while (1) {
result = recv(ti->fd, s, 1, 0);
if (result == 1) {
tm->result++;
if (*s == '\r' || *s == '\n') {
if (tm->result == 1) { /* blank line ... skip it */
tm->result--;
continue;
}
tm->error = NO_ERROR;
Remove((struct Node *)tm);
ReplyMsg(&tm->header);
fix_read_set(wait_list, reads, max_fd);
return;
}
s++;
if (tm->result == tm->length) {
tm->error = NO_ERROR;
Remove((struct Node *)tm);
ReplyMsg(&tm->header);
fix_read_set(wait_list, reads, max_fd);
return;
}
continue;
} else if (result == 0) { /* got to be EOF */
ti->eof = true;
tm->error = ERROR_EOF;
Remove((struct Node *)tm);
ReplyMsg(&tm->header);
fix_read_set(wait_list, reads, max_fd);
return;
} else {
switch (Errno()) {
case EINTR:
case EWOULDBLOCK: /* nothing there to read yet */
return;
default: /* something went wrong */
tm->error = ERROR_LOST_CONNECTION;
Remove((struct Node *)tm);
ReplyMsg(&tm->header);
fix_read_set(wait_list, reads, max_fd);
return;
}
return;
}
}
} else {
result = recv(ti->fd, (b8 *)tm->data + tm->result, tm->length - tm->result, 0);
if (result == tm->length - tm->result) { /* satisfied! */
tm->result = tm->length;
tm->error = NO_ERROR;
Remove((struct Node *)tm); /* remove from wait_list */
ReplyMsg(&tm->header); /* send it back completed */
fix_read_set(wait_list, reads, max_fd);
return;
}
if (result == 0) { /* got to be EOF */
ti->eof = true;
tm->error = ERROR_EOF;
Remove((struct Node *)tm); /* as above */
ReplyMsg(&tm->header);
fix_read_set(wait_list, reads, max_fd);
return;
}
/* have to wait some more */
if (result < 0) {
switch (Errno()) {
case EINTR:
case EWOULDBLOCK: /* nothing more to read yet */
return;
default: /* something went wrong */
tm->error = ERROR_LOST_CONNECTION;
Remove((struct Node *)tm);
ReplyMsg(&tm->header);
fix_read_set(wait_list, reads, max_fd);
return;
}
}
truth(result < tm->length - tm->result);
tm->result += result;
return; /* keep waiting */
}
}
void tcp_write_more(struct Library *SocketBase, tcpmessage *tm, struct List *wait_list, fd_set *writes, sb32 *max_fd)
{
tcpident *ti;
sb32 result;
truth(SocketBase != nil);
truth(max_fd != nil);
truth(writes != nil);
truth(wait_list != nil);
verify(tm, V_tcpmessage);
ti = tm->ident;
verify(ti, V_tcpident);
result = send(ti->fd, (b8 *)tm->data + tm->result, tm->length - tm->result, 0);
if (result == tm->length - tm->result) { /* satisfied! */
tm->result = tm->length;
tm->error = NO_ERROR;
Remove((struct Node *)tm);
ReplyMsg(&tm->header);
fix_write_set(wait_list, writes, max_fd);
return;
}
/* from here we have the stuff we have to wait some more for */
if (result < 0) {
switch (Errno()) {
case EWOULDBLOCK:
case EINTR: /* write blocked again */
return;
default: /* something has gone wrong */
tm->error = ERROR_LOST_CONNECTION;
Remove((struct Node *)tm);
ReplyMsg(&tm->header);
fix_write_set(wait_list, writes, max_fd);
return;
}
}
truth(result < tm->length - tm->result);
tm->result += result;
return;
}
void tcp_listen(struct Library *SocketBase, tcpmessage *tm, struct List *wait_list, fd_set *reads, sb32 *max_fd)
{
tcpident *ti;
sb32 result, socklen;
struct sockaddr_in sin;
struct hostent *he;
tcpmessage *wait_tm;
truth(SocketBase != nil);
truth(max_fd != nil);
truth(reads != nil);
truth(wait_list != nil);
verify(tm, V_tcpmessage);
if (tm->ident) {
tm->result = false;
tm->error = ERROR_ALREADY_CONNECTED;
ReplyMsg(&tm->header);
return;
}
memset(&sin, 0, sizeof(sin));
he = gethostbyname("localhost");
if (!he) {
tm->result = false;
tm->error = ERROR_UNKNOWN_HOST;
ReplyMsg(&tm->header);
return;
}
sin.sin_family = he->h_addrtype;
sin.sin_port = tm->port.w;
memcpy(&sin.sin_addr.s_addr, he->h_addr_list[0], he->h_length);
ti = new_tcpident(0); /* filled in later */
if (ti) {
wait_tm = new_tcpmessage(nil);
if (wait_tm) {
result = socket(AF_INET, SOCK_STREAM, 0);
if (result >= 0) {
ti->fd = result;
result = bind(ti->fd, (struct sockaddr *)&sin, sizeof(sin));
if (result == 0) {
result = listen(ti->fd, 5); /* random msq queue length */
if (result == 0) {
/*
* strictly, we shouldn't need this
* ... but, we might (better safe etc)
*/
non_blocking(SocketBase, ti->fd);
socklen = sizeof(sin);
result = getsockname(ti->fd, (struct sockaddr *)&sin, &socklen);
/* we are listening! */
tm->ident = ti;
tm->port.w = sin.sin_port;
wait_tm->ident = ti;
wait_tm->command = TCP_LISTEN;
wait_tm->port.w = sin.sin_port; /* just for curiosity */
/* note the reply port so we can send future accepts
to the same place */
wait_tm->header.mn_ReplyPort = tm->header.mn_ReplyPort;
if (ti->fd > *max_fd) *max_fd = ti->fd;
FD_SET(ti->fd, reads);
AddTail(wait_list, (struct Node *)wait_tm);
tm->result = true;
tm->error = NO_ERROR;
ReplyMsg(&tm->header);
return;
} else {
tm->error = ERROR_ACCESS_DENIED;
}
} else {
tm->error = ERROR_ACCESS_DENIED;
}
CloseSocket(ti->fd);
} else tm->error = ERROR_OOM;
free_tcpmessage(wait_tm);
} else tm->error = ERROR_OOM;
free_tcpident(ti);
} else tm->error = ERROR_OOM;
tm->result = false;
ReplyMsg(&tm->header);
return;
}
void tcp_accept(struct Library *SocketBase, tcpmessage *tm, struct List *wait_list, struct MsgPort *replies)
{
tcpident *ti, *accept_ti;
tcpmessage *accept_tm, *wait_tm;
struct sockaddr_in sin;
sb32 sin_len, result;
truth(SocketBase != nil);
truth(wait_list != nil);
truth(replies != nil);
verify(tm, V_tcpmessage);
ti = tm->ident;
verify(ti, V_tcpident);
sin_len = sizeof(sin);
result = accept(ti->fd, (struct sockaddr *)&sin, &sin_len);
if (result < 0) { /* this should never really happen I don't think, but ... */
/* nothing need be done */
return;
}
non_blocking(SocketBase, result);
accept_tm = new_tcpmessage(tm->header.mn_ReplyPort);
if (accept_tm) {
wait_tm = new_tcpmessage(nil);
if (wait_tm) {
accept_ti = new_tcpident(result);
if (accept_ti) {
/* all systems go! */
/* tell the parent that we have a new connection ... */
accept_tm->command = TCP_ACCEPTED;
accept_tm->address.l = sin.sin_addr.s_addr;
accept_tm->port.w = sin.sin_port;
accept_tm->ident = accept_ti;
accept_tm->result = true;
accept_tm->error = NO_ERROR;
PutMsg(tm->header.mn_ReplyPort, &accept_tm->header);
/* keep a copy on our files for book keeping purposes */
wait_tm->command = TCP_CONNECTED;
wait_tm->address.l = sin.sin_addr.s_addr;
wait_tm->port.w = sin.sin_port;
wait_tm->ident = accept_ti;
/* this is a bit nasty ... it has to be on the head because
we are traversing the list forward */
AddHead(wait_list, (struct Node *)wait_tm);
return;
}
free_tcpmessage(wait_tm);
}
free_tcpmessage(accept_tm);
}
/* this is a bit sad ... it accepts and then it just closes for no
apparent reason ... I don't see any alternative however */
CloseSocket(result);
return;
}
void tcp_close(struct Library *SocketBase, tcpmessage *tm, struct List *wait_list, fd_set *reads, fd_set *writes, sb32 *max_fd)
{
tcpident *ti, *iti;
tcpmessage *itm, *nitm;
int ncons = 0, nlis = 0;
truth(SocketBase != nil);
truth(max_fd != nil);
truth(reads != nil);
truth(wait_list != nil);
verify(tm, V_tcpmessage);
ti = tm->ident;
if (!ti || ti->connecting_port) {
tm->result = false;
tm->error = ERROR_NO_CONNECTION;
ReplyMsg(&tm->header);
return;
}
verify(ti, V_tcpident);
/*
* search through the waiting list and:
* abort reads and writes that match
* close TCP_CONNECTEDs
* close TCP_LISTENs
*/
for (itm = (tcpmessage *)wait_list->lh_Head;
nitm = (tcpmessage *)itm->header.mn_Node.ln_Succ;
itm = nitm) {
verify(itm, V_tcpmessage);
switch (itm->command) {
case TCP_READ:
case TCP_WRITE:
iti = itm->ident;
verify(iti, V_tcpident);
if (iti == ti) {
itm->error = ERROR_INTERRUPTED;
itm->ident = nil;
Remove((struct Node *)itm);
ReplyMsg(&itm->header);
}
break;
case TCP_LISTEN:
iti = itm->ident;
verify(iti, V_tcpident);
if (iti == ti) {
nlis++;
Remove((struct Node *)itm);
free_tcpmessage(itm);
}
break;
case TCP_CONNECTED:
iti = itm->ident;
verify(iti, V_tcpident);
if (iti == ti) {
ncons++;
Remove((struct Node *)itm);
free_tcpmessage(itm);
}
break;
}
}
truth(ncons + nlis == 1); /* one, and only one connection!!! */
if (ti->fd >= 0)
CloseSocket(ti->fd);
free_tcpident(ti);
tm->ident = nil;
tm->result = true;
tm->error = NO_ERROR;
ReplyMsg(&tm->header);
fix_read_set(wait_list, reads, max_fd);
fix_write_set(wait_list, writes, max_fd);
return;
}
void do_connect(struct Library *SocketBase, tcpmessage *mess, struct MsgPort *pport, struct MsgPort *myport)
{
/* bits of the following inspired by the source to NcFTP ... thanks go to Mike Gleason */
struct sockaddr_in sin;
struct hostent *he;
sb32 result, s;
truth(SocketBase != nil);
truth(pport != nil);
truth(myport != nil);
verify(mess, V_tcpmessage);
memset((void *)&sin, 0, sizeof(&sin)); /* not sure this is necessary ... ncftp does this. JIC */
sin.sin_port = mess->port.w;
sin.sin_family = AF_INET;
if (mess->data) { /* if they don't send a string, their address must be right */
sin.sin_addr.s_addr = inet_addr(mess->data);
if (sin.sin_addr.s_addr == -1) { /* not a direct dotted IP number */
he = gethostbyname(mess->data);
if (!he) { /* oh well ... */
mess->result = 0;
mess->error = ERROR_UNKNOWN_HOST;
PutMsg(pport, &mess->header);
WaitPort(myport);
GetMsg(myport);
return;
}
sin.sin_family = he->h_addrtype;
memcpy(&sin.sin_addr, he->h_addr_list[0], he->h_length);
}
} else {
sin.sin_addr.s_addr = mess->address.l;
}
/* we have the address, now try the connect */
s = socket(sin.sin_family, SOCK_STREAM, 0); /* 0 for protocol legal? ncftp does it */
if (s < 0) {
mess->result = 0;
mess->error = ERROR_OOM;
PutMsg(pport, &mess->header);
WaitPort(myport);
GetMsg(myport);
return;
}
result = connect(s, (struct sockaddr *)&sin, sizeof(sin));
if (result < 0) {
/* perhaps we should try the backup addresses, but nahhhh */
switch (Errno()) {
case ENETDOWN:
case ENETUNREACH:
mess->error = ERROR_UNREACHABLE;
break;
case ECONNREFUSED:
mess->error = ERROR_CONNECT_REFUSED;
break;
default:
mess->error = ERROR_CANT_CONNECT;
break;
}
CloseSocket(s);
mess->result = 0;
PutMsg(pport, &mess->header);
WaitPort(myport);
GetMsg(myport);
return;
}
/* hurrah */
/* now we have to transfer our socket to our parents "library domain" */
result = ReleaseSocket(s, UNIQUE_ID);
if (result < 0) { /* BUGGER! just as everything was going so well too :( */
mess->result = 0;
mess->error = ERROR_OOM;
CloseSocket(s);
} else {
mess->result = result;
mess->error = NO_ERROR;
}
PutMsg(pport, &mess->header);
WaitPort(myport);
GetMsg(myport);
return;
}
void __saveds __asm connect_child(register __a0 b8 *parent_port)
{
struct Library *SocketBase;
struct MsgPort *pport, *myport;
tcpmessage *mess;
pport = FindPort(parent_port);
if (!pport) {
/* not a fuck of a lot we can do */
return;
}
myport = CreatePort(0, 0);
if (myport) {
mess = new_tcpmessage(myport);
if (mess) {
SocketBase = OpenLibrary("bsdsocket.library", 0);
if (SocketBase) {
/* tell them we are going */
mess->command = TCP_STARTUP;
mess->result = true;
mess->error = NO_ERROR;
PutMsg(pport, &mess->header);
WaitPort(myport);
GetMsg(myport); /* should be guaranteed to be mess back */
pport = mess->header.mn_ReplyPort; /* may be a different port */
mess->command = TCP_CONNECTED;
mess->header.mn_ReplyPort = myport; /* GOT TO BE THE SAME !!! (for ident purposes) */
do_connect(SocketBase, mess, pport, myport);
CloseLibrary(SocketBase);
} else {
/* tell them we are NOT going */
mess->command = TCP_STARTUP;
mess->result = false;
mess->error = ERROR_NO_CONNECTION;
PutMsg(pport, &mess->header);
WaitPort(myport);
GetMsg(myport);
}
free_tcpmessage(mess);
DeletePort(myport);
return;
}
DeletePort(myport);
}
/* our half-hearted way of telling the parent something is wrong */
Signal(pport->mp_SigTask, 1 << pport->mp_SigBit);
return;
}
void tcp_connect(struct Library *SocketBase, tcpmessage *tm, struct List *wait_list, struct MsgPort *replies)
{
struct Process *child;
b8 buffer[30];
struct MsgPort *tmp_port;
tcpmessage *child_tm;
unique_name(FindTask(0), ": Evans TCP Handler", buffer);
if (tm->ident) {
tm->result = false;
tm->error = ERROR_ALREADY_CONNECTED;
ReplyMsg(&tm->header);
return;
}
tm->ident = new_tcpident(-1);
if (!tm->ident) {
tm->result = false;
tm->error = ERROR_OOM;
ReplyMsg(&tm->header);
return;
}
tmp_port = CreatePort(buffer, 0);
if (!tmp_port) {
free_tcpident(tm->ident);
tm->ident = nil;
tm->result = false;
tm->error = ERROR_OOM;
ReplyMsg(&tm->header);
return;
}
/* start child */
child = CreateNewProcTags(
NP_Entry, connect_child,
NP_Name, "TCP Connect Handler",
NP_Arguments, buffer,
TAG_END, 0
);
if (!child) {
tm->result = false;
tm->error = ERROR_OOM;
DeletePort(tmp_port);
free_tcpident(tm->ident);
tm->ident = nil;
ReplyMsg(&tm->header);
return;
}
/* the child should get back to us immediately ... so
synchronously wait for the startup message */
Wait(1 << tmp_port->mp_SigBit);
child_tm = (tcpmessage *)GetMsg(tmp_port);
if (!child_tm) { /* they had a problem, and failed */
tm->result = false;
tm->error = ERROR_OOM;
DeletePort(tmp_port);
free_tcpident(tm->ident);
tm->ident = nil;
ReplyMsg(&tm->header);
return;
}
if (!child_tm->result) { /* ditto */
tm->error = child_tm->error;
ReplyMsg((struct Message *)child_tm);
tm->result = false;
DeletePort(tmp_port);
free_tcpident(tm->ident);
tm->ident = nil;
ReplyMsg(&tm->header);
return;
}
/* well, all should be ok now */
DeletePort(tmp_port);
tmp_port = child_tm->header.mn_ReplyPort;
child_tm->header.mn_ReplyPort = replies;
child_tm->data = tm->data;
child_tm->flags = tm->flags;
child_tm->port = tm->port;
child_tm->address = tm->address;
PutMsg(tmp_port, &child_tm->header);
/* that will come back when the child has resolved the connect */
((tcpident *)tm->ident)->connecting_port = tmp_port; /* note: this is a special case for TCP_CONNECT ONLY */
AddTail(wait_list, (struct Node *)tm);
return;
}
void tcp_connected(struct Library *SocketBase, tcpmessage *tm, struct List *wait_list)
{
tcpident *ti;
tcpmessage *itm, *nitm, *wait_tm;
struct MsgPort *their_port; /* to identify which child */
sb32 s;
truth(SocketBase != nil);
truth(wait_list != nil);
verify(tm, V_tcpmessage);
their_port = tm->header.mn_ReplyPort;
if (tm->error == NO_ERROR) {
s = ObtainSocket(tm->result, AF_INET, SOCK_STREAM, 0); /* shudder */
} else {
s = -1;
}
/* it may have failed ... but we have to check after we have found the TCP_CONNECT */
/* see which TCP_CONNECT in the wait list it corresponds to ... */
for (itm = (tcpmessage *)wait_list->lh_Head;
nitm = (tcpmessage *)itm->header.mn_Node.ln_Succ;
itm = nitm) {
if (itm->command == TCP_CONNECT) {
ti = itm->ident;
verify(ti, V_tcpident);
if (ti->connecting_port == (void *)their_port) {
Remove((struct Node *)itm);
if ((tm->error != NO_ERROR) || (s < 0)) {
if (tm->error != NO_ERROR) {
itm->result = false;
itm->error = tm->error;
} else { /* this case is our Obtain failing */
itm->result = false;
/* bizarre error for bizarre case */
itm->error = ERROR_LOST_CONNECTION;
}
ReplyMsg(&tm->header);
/* NB: reusing tm */
tm = itm->interrupt;
itm->interrupt = nil;
free_tcpident(ti);
itm->ident = nil;
ReplyMsg(&itm->header);
if (tm) {
verify(tm, V_tcpmessage);
ReplyMsg(&tm->header);
}
return;
}
non_blocking(SocketBase, s);
/* ok, have to make a tcpmessage to wait here */
wait_tm = new_tcpmessage(nil);
if (wait_tm) {
ti->connecting_port = nil;
ti->fd = s;
wait_tm->ident = ti;
wait_tm->command = TCP_CONNECTED;
wait_tm->address = tm->address;
wait_tm->port = tm->port;
itm->address = tm->address;
itm->port = tm->port;
itm->result = true;
itm->error = NO_ERROR;
ReplyMsg(&tm->header);
tm = itm->interrupt;
itm->interrupt = nil;
ReplyMsg(&itm->header);
if (tm) {
verify(tm, V_tcpmessage);
ReplyMsg(&tm->header);
}
AddTail(wait_list, (struct Node *)wait_tm);
return;
} else itm->error = ERROR_OOM;
free_tcpident(ti);
itm->ident = nil;
CloseSocket(s);
itm->result = false;
ReplyMsg(&tm->header);
tm = itm->interrupt;
itm->interrupt = nil;
ReplyMsg(&itm->header);
if (tm) {
verify(tm, V_tcpmessage);
ReplyMsg(&tm->header);
}
return;
}
}
}
/* hmmm, strange things are happening */
if (s >= 0) CloseSocket(s);
ReplyMsg(&tm->header);
return;
}
void tcp_interrupt(tcpmessage *tm, struct List *wait_list, fd_set *reads, fd_set *writes, sb32 *max_fd)
{
tcpident *ti;
tcpmessage *itm, *nitm;
struct MsgPort *port;
verify(tm, V_tcpmessage);
for (itm = (tcpmessage *)wait_list->lh_Head;
nitm = (tcpmessage *)itm->header.mn_Node.ln_Succ;
itm = nitm) {
verify(itm, V_tcpmessage);
if (itm == tm->interrupt) {
switch (itm->command) {
case TCP_READ:
Remove((struct Node *)itm);
itm->error = ERROR_INTERRUPTED;
ReplyMsg(&itm->header);
tm->result = true;
tm->error = NO_ERROR;
ReplyMsg(&tm->header);
fix_read_set(wait_list, reads, max_fd);
return;
case TCP_WRITE:
Remove((struct Node *)itm);
itm->error = ERROR_INTERRUPTED;
ReplyMsg(&itm->header);
tm->result = true;
tm->error = NO_ERROR;
ReplyMsg(&tm->header);
fix_write_set(wait_list, writes, max_fd);
return;
case TCP_CONNECT:
/* this is the fun one */
ti = itm->ident;
verify(ti, V_tcpident);
port = ti->connecting_port;
itm->interrupt = tm;
Signal(port->mp_SigTask, SIGBREAKF_CTRL_C);
return;
}
}
}
tm->result = false;
tm->error = ERROR_NO_CONNECTION;
ReplyMsg(&tm->header);
return;
}
void tcp_peername(struct Library *SocketBase, tcpmessage *tm)
/*
* this one is a strange one ... it may have the potential to block
* I don't really want to write a bloody child process thingy just for
* this trivial bloody function
*/
{
tcpident *ti;
struct sockaddr_in sin;
struct hostent *he;
sb32 sin_len, len;
truth(SocketBase != nil);
verify(tm, V_tcpmessage);
ti = tm->ident;
if (!ti) {
tm->result = false;
tm->error = ERROR_NO_CONNECTION;
ReplyMsg(&tm->header);
return;
}
verify(ti, V_tcpident);
sin_len = sizeof(sin);
if (getpeername(ti->fd, (struct sockaddr *)&sin, &sin_len) < 0) {
if (Errno() == ENOTCONN) {
tm->error = ERROR_NO_CONNECTION;
} else {
tm->error = ERROR_OOM;
}
tm->result = false;
ReplyMsg(&tm->header);
return;
}
he = gethostbyaddr((void *)&sin.sin_addr.s_addr, 4, AF_INET);
if (!he) {
tm->error = ERROR_UNKNOWN_HOST;
tm->result = false;
ReplyMsg(&tm->header);
return;
}
len = strlen(he->h_name);
if (len >= tm->length) {
len = tm->length - 1;
}
memcpy(tm->data, he->h_name, len);
((b8 *)tm->data)[len] = 0;
tm->error = NO_ERROR;
tm->result = true;
ReplyMsg(&tm->header);
return;
}
void tcp_service(struct Library *SocketBase, tcpmessage *tm)
{
struct servent *se;
truth(SocketBase != nil);
verify(tm, V_tcpmessage);
se = getservbyname(tm->data, "tcp");
if (!se) {
tm->result = false;
tm->error = ERROR_UNKNOWN_COMMAND;
} else {
tm->result = true;
tm->error = NO_ERROR;
tm->port.w = se->s_port;
}
ReplyMsg(&tm->header);
return;
}
struct MsgPort *running_running(tcpmessage *emergency, struct MsgPort *commands)
{
b32 tcp_signal, signal_tmp;
tcpmessage *tm, *tm2;
tcpident *ti;
struct Library *SocketBase = nil;
struct List waiting;
fd_set read_set, write_set, read_tmp, write_tmp;
sb32 max_fd, n;
struct MsgPort *death_port;
NewList(&waiting); /* list of waiting requests */
FD_ZERO(&read_set);
FD_ZERO(&write_set);
max_fd = -1;
tcp_signal = (1 << commands->mp_SigBit);
while (1) {
if (SocketBase) {
read_tmp = read_set;
write_tmp = write_set;
signal_tmp = tcp_signal;
n = WaitSelect(max_fd + 1, &read_tmp, &write_tmp, nil, nil, &signal_tmp);
} else {
n = 0;
Wait(tcp_signal);
}
while (tm = (tcpmessage *)GetMsg(commands)) {
/* a message from our parent (sponsor?) */
verify(tm, V_tcpmessage);
if (tm->header.mn_ReplyPort == commands) {
/* its been ReplyMsg'd to us */
free_tcpmessage(tm);
continue;
}
switch (tm->command) {
case TCP_NOOP:
tm->result = true;
ReplyMsg(&tm->header);
break;
case TCP_CONNECT:
if (!SocketBase) {
SocketBase = OpenLibrary("bsdsocket.library", 0);
if (!SocketBase) {
tm->result = false;
tm->error = ERROR_NO_CONNECTION;
ReplyMsg(&tm->header);
break;
}
}
tcp_connect(SocketBase, tm, &waiting, commands);
break;
case TCP_LISTEN:
if (!SocketBase) {
SocketBase = OpenLibrary("bsdsocket.library", 0);
if (!SocketBase) {
tm->result = false;
tm->error = ERROR_NO_CONNECTION;
ReplyMsg(&tm->header);
break;
}
}
tcp_listen(SocketBase, tm, &waiting, &read_set, &max_fd);
break;
case TCP_READ:
if (!SocketBase) { /* someone is frigging us around */
tm->result = 0;
tm->error = ERROR_NO_CONNECTION;
ReplyMsg(&tm->header);
break;
}
tcp_read(SocketBase, tm, &waiting, &read_set, &max_fd);
break;
case TCP_WRITE:
if (!SocketBase) { /* someone is frigging us around */
tm->result = 0;
tm->error = ERROR_NO_CONNECTION;
ReplyMsg(&tm->header);
break;
}
tcp_write(SocketBase, tm, &waiting, &write_set, &max_fd);
break;
case TCP_CLOSE:
if (!SocketBase) { /* someone is frigging us around */
tm->result = false;
tm->error = ERROR_NO_CONNECTION;
ReplyMsg(&tm->header);
break;
}
tcp_close(SocketBase, tm, &waiting, &read_set, &write_set, &max_fd);
if (IsListEmpty(&waiting)) {
/* absolutely everything has closed. We can close
the socket library now in safety */
CloseLibrary(SocketBase);
SocketBase = nil;
}
break;
case TCP_CONNECTED:
/* ahhh, a child is reporting back! */
if (!SocketBase) { /* someone is frigging us around */
tm->result = false;
tm->error = ERROR_NO_CONNECTION;
ReplyMsg(&tm->header);
break;
}
tcp_connected(SocketBase, tm, &waiting);
if (IsListEmpty(&waiting)) {
/* absolutely everything has closed. We can close
the socket library now in safety */
CloseLibrary(SocketBase);
SocketBase = nil;
}
break;
case TCP_INTERRUPT:
tcp_interrupt(tm, &waiting, &read_set, &write_set, &max_fd);
break;
case TCP_NEWMESSAGE:
tm2 = new_tcpmessage(tm->header.mn_ReplyPort);
if (!tm2) {
tm->result = false;
tm->error = ERROR_OOM;
ReplyMsg(&tm->header);
break;
}
tm2->ident = tm->ident;
tm->data = tm2;
tm->result = true;
tm->error = NO_ERROR;
ReplyMsg(&tm->header);
break;
case TCP_DISPOSE:
free_tcpmessage(tm);
break;
case TCP_DIE:
/* we have to assume they've done the right thing and
disposed of all messages (closing all connections) */
/* all we have to do is to free whatever is left over
and exit */
death_port = tm->header.mn_ReplyPort;
free_tcpmessage(tm);
if (SocketBase) {
CloseLibrary(SocketBase);
SocketBase = nil;
}
while (tm = (tcpmessage *)RemHead(&waiting)) {
if (tm->ident && tm->command != TCP_CONNECT)
free_tcpident(tm->ident);
free_tcpmessage(tm);
}
/* perhaps should do a little more ... */
return death_port;
case TCP_PEERNAME:
if (!SocketBase) { /* someone is yankin' our chain */
tm->result = false;
tm->error = ERROR_NO_CONNECTION;
ReplyMsg(&tm->header);
break;
}
tcp_peername(SocketBase, tm);
break;
case TCP_SERVICE:
if (!SocketBase) {
SocketBase = OpenLibrary("bsdsocket.library", 0);
if (!SocketBase) {
tm->result = false;
tm->error = ERROR_NO_CONNECTION;
ReplyMsg(&tm->header);
break;
}
}
tcp_service(SocketBase, tm);
if (IsListEmpty(&waiting)) {
/* absolutely everything has closed. We can close
the socket library now in safety */
CloseLibrary(SocketBase);
SocketBase = nil;
}
break;
default:
tm->result = false;
tm->error = ERROR_UNKNOWN_COMMAND;
ReplyMsg(&tm->header);
break;
}
}
if (n < 0) { /* hmmm ... this seems to happen when they close the library underneath us */
/* should send back any waiting reads/writes/listens */
CloseLibrary(SocketBase);
SocketBase = nil;
continue;
}
#ifdef SDLFKJ
truth (n >= 0); /* I don't _think_ we should ever get SIGINTR ... */
#endif
if (n <= 0) continue; /* no fds are ready, so don't look through list */
for (tm = (tcpmessage *)waiting.lh_Head;
tm2 = (tcpmessage *)tm->header.mn_Node.ln_Succ;
tm = tm2) {
switch (tm->command) {
case TCP_LISTEN:
ti = tm->ident;
verify(ti, V_tcpident);
if (FD_ISSET(ti->fd, &read_tmp)) { /* ready to accept */
tcp_accept(SocketBase, tm, &waiting, commands);
}
break;
case TCP_READ:
ti = tm->ident;
verify(ti, V_tcpident);
if (FD_ISSET(ti->fd, &read_tmp)) { /* ready to continue reading */
tcp_read_more(SocketBase, tm, &waiting, &read_set, &max_fd);
}
break;
case TCP_WRITE:
ti = tm->ident;
verify(ti, V_tcpident);
if (FD_ISSET(ti->fd, &write_tmp)) { /* ready to continue writing */
tcp_write_more(SocketBase, tm, &waiting, &write_set, &max_fd);
}
break;
}
}
}
}
void __saveds __asm tcp_handler(register __a0 b8 *parent_port)
{
struct MsgPort *mp, *tcp_commands;
tcpmessage *tm, *new_tm;
mem_tracking_on();
mp = FindPort(parent_port);
if (!mp) {
truth(FindPort(parent_port) != nil); /* will display an alert */
return; /* OOOPS - not overly much we can do here */
}
tcp_commands = CreatePort(0, 0);
if (!tcp_commands) {
/* this is a signal jobbie because we can't send a message
so when they wait for the signal, and then can't GetMsg
they should guess that something has gone wrong */
Signal(mp->mp_SigTask, 1 << mp->mp_SigBit);
return;
}
tm = new_tcpmessage(tcp_commands);
if (!tm) {
/* again ... our message doesn't exist, so just signal */
Signal(mp->mp_SigTask, 1 << mp->mp_SigBit);
DeletePort(tcp_commands);
return;
}
new_tm = new_tcpmessage(tcp_commands);
if (!new_tm) {
Signal(mp->mp_SigTask, 1 << mp->mp_SigBit);
DeletePort(tcp_commands);
free_tcpmessage(tm);
return;
}
new_tm->command = TCP_NEWMESSAGE;
new_tm->header.mn_ReplyPort = mp;
/* just a guess ... if they want something different, they'se gonna haveta do it themselves */
/* ok, we have enough to communicate ... tell them we started ok */
tm->command = TCP_STARTUP;
tm->data = tcp_commands; /* this tells them where to send commands */
tm->result = true; /* not necessary but wth */
PutMsg(mp, &tm->header);
/* wait for it to come back */
Wait(1 << tcp_commands->mp_SigBit);
GetMsg(tcp_commands); /* should be guaranteed to be tm back ;) */
/* ok, time to carry on */
/* send them a message to bootstrap their message system */
PutMsg(mp, &new_tm->header);
/* Reusing mp here for the death port */
mp = running_running(tm, tcp_commands);
DeletePort(tcp_commands);
free_tcpmessage(tm);
check_memory();
Forbid(); /* once we are RemTask'd the Forbid will be lifted */
Signal(mp->mp_SigTask, 1 << mp->mp_SigBit); /* signal parent we are dead */
return;
}