/*
**  server3.c
**
**  Written by Dale Larson, Software Engineer, Commodore-Amiga, Inc.
**  Copyright 1991, Commodore-Amiga, Inc.
**  Permission to use granted provided this notice remains intact.
**
**  This is a version of the ncopy server which processes multiple requests
**  simultaneously.  It utilizes selectwait() to do so (a server could also
**  spawn multiple processes to do so).  It is more complex than the simple
**  server, but is nicer under frequent and/or heavy use, and allows users to
**  copy a file from one location on a remote machine to another location on
**  the same remote machine (i.e.  "ncopy sprite!ram:x sprite!work:y").  Using
**  selectwait() (or multiple processes) also makes it reasonable to implement
**  exit-on-break-signal. A system requester will not pop up if the server
**  recieves a request to read/write to/from a non-existent device.
**
**  Note that this server really does not properly handle copying from one
**  locations on a remote machine to another location on the same remote
**  machine.  It works fine for files not more than about 8k, but will hang
**  for files larger than that.  The reason is that it sends all of a file
**  at once.  If the target is another machine, the other machine's ncopy or
**  server will read the data as this server writes it.  But if this server
**  is also supposed to read the data, 8 TCP packets will be buffered, then
**  send() will block until some of them are read and acknowledged.  This
**  problem could be solved in the server by including a select() in the
**  send loop, by not allowing ncopy to transfer to and from the same machine,
**  or by redesigning the ncopy protocol.  Any of these solutions would unduly
**  complicate and excede the scope of this example.  Note that the number '8'
**  is an implementation-dependent number.
**
**  You should be familiar with the ncopy client (ncopy.c and transfer.c) and
**  the simple ncopy server (server1.c) before studying this server.
**
*/


#include "ncopy.h"

struct Library *SockBase;
#define MAXSOCKS 10

char vers[] = "\0$VER: server3 1.0 (28.02.91)";

int process(int s, int state, fd_set *mask, int highSock);
int nhs(int s, fd_set *mask);
int init(void);

/*  state constants  */
#define SCLOSE 		0  /*  Need to close the socket  */
#define SINIT		1  /*  Need to initialize connection  */
#define SDATA		2  /*  Need to recieve data and write to disk  */

struct timeval NoTime = {0L, 0L};

main(int argc, char **argv)
{
int s,			/*  socket to accept connections on, new socket  */
    ns,			/*  new socket  	*/
    i,			/*  index in for loops  */
    state[FD_SETSIZE],	/*  where we are in communication on all socket  */
    highSock;		/*  the highest open socket  */
fd_set sockmask, mask; 	/*  mask of all open sockets, return val  */
long umask;

	highSock = s = init();
	FD_ZERO(&sockmask);
	FD_SET(s, &sockmask);
	while(1)
	{
		mask = sockmask;
		umask = SIGBREAKF_CTRL_C;

		if(selectwait(highSock+1, &mask, NULL, NULL, NULL,
			      &umask) == -1)
		{
			perror("select");
			goto cleanup;
		}

		for(i=0; i <= highSock; i++)
			if(FD_ISSET(i, &mask))
				if (i == s)
				{
					ns = accept(s, NULL, NULL);
					if(ns != -1)
					{
						state[ns] = SINIT;
						FD_SET(ns, &sockmask);
						if(ns > highSock)
							highSock = ns;
					}
				} else
				{
					state[i] = process(i, state[i], &sockmask, highSock);
					if(state[i] == SCLOSE)
					{
						if(i == highSock)
							highSock = nhs(i,&sockmask);
						s_close(i);
						FD_CLR(i, &sockmask);
					}
				}
	}

cleanup:
	for(i=0; i<=highSock; i++)
		if( FD_ISSET(i, &sockmask) )
		{
			s_close(i);
		}
	cleanup_sockets();
	CloseLibrary(SockBase);
}


int nhs(int s, fd_set *mask)
{
	while(s--)
	{
		if(FD_ISSET(s, mask))
			return s;
	}
	return FD_SETSIZE;
}


int init(void)
{
struct sockaddr_in sin;	/*  socket address (internet) to establish  */
int s, 			/*  socket to accept connections on  */
    len = sizeof(sin);

	/*  Open Shared Socket library:
	*/
        if(SockBase = OpenLibrary( "inet:libs/socket.library", 0L ))
        {
                setup_sockets( MAXSOCKS, &errno );
	}else
	{
		puts("Can't open socket.library.");
		exit(RETURN_ERROR);
	}

	if( (s = socket(AF_INET, SOCK_STREAM, 0)) == -1)
	{
		perror("socket");
		goto cleanup;;
	}

	bzero(&sin, len);
	sin.sin_family = AF_INET;
	/*
	**  While prototyping a program, pick an unused port number above
	**  1024 like I did here.  For a real program, assign a name and a
	**  port number above 1024 and stick it in inet:db/servers on all
	**  machines which will use the application.  That way the user can
	**  reconfigure port numbers used by new applications if necessary.
	**  You can modify the server to get the port number from the
	**  database by using code like this (instead of "sin.sin_port=
	**  htons(6666)" below):
	**
	**  struct servent *servptr;
	**  char serv[] = "servicename";
	**
	**  if( (servptr = getservbyname(serv, "tcp")) == NULL)
	**  {
	**  	printf("Unknown service %s.  Check inet:db/services.\n",serv);
	**	return RETURN_ERROR;
	**  }
	**  sin.sin_port = servptr->s_port;
	*/
	sin.sin_port = htons(6666);
	sin.sin_addr.s_addr = INADDR_ANY;
	if(bind(s, (struct sockaddr *)&sin, len) < 0)
	{
		perror("bind");
		s_close(s);
		goto cleanup;
	}

	listen(s,5);
	return s;

cleanup:
	cleanup_sockets();
	CloseLibrary(SockBase);
	exit(RETURN_ERROR);
}


int process(int s, int state, fd_set *mask, int highSock)
{
char buffer[LENGTH];
static int mode[FD_SETSIZE];	/*  AmigaDOS file open modes  */
static BPTR file[FD_SETSIZE];	/*  File handles  */
int n;				/*  number of bytes read/written  */

	switch(state)
	{
	case SINIT:
		recv(s, (char *)&(mode[s]), sizeof(int), 0);
		recv(s, buffer, LENGTH, 0);
		if( !(file[s] = Open(buffer, mode[s])) )
		{
		int len;

			len = strlen(buffer);
			strncat(buffer, "': ", LENGTH-4);  /*  4 not 3 'cause of nul  */
			Fault(IoErr(), NULL, buffer+len+3, LENGTH-len-4);
			send(s, buffer, strlen(buffer)+1, 0);
			return SCLOSE;
		}
		send(s, "\0", 1, 0);
		if(mode[s] == MODE_NEWFILE)
		{
			return SDATA;
		}
/*  fall through if MODE_OLDFILE  */
	case SDATA:
		if(mode[s] == MODE_OLDFILE)
		{
			while(1)
			{
				n = Read(file[s], (void *)buffer, LENGTH);
		       		if(n <= 0)
					goto close;
				if(send(s, buffer, n, 0) < 0)
					goto close;
			}
		}else
		{
			n = recv(s, buffer, LENGTH, 0);
			if(n <= 0)
				goto close;
			if(Write(file[s], (void *)buffer, n) < 0)
				goto close;
			return SDATA;
		}
	default:
		;
	/*  in a real program, should do something more intellegent here  */
	}
close:
	Close(file[s]);
	return SCLOSE;
}
