
/*
 *  DNET.C
 *
 *	DNET (c)Copyright 1988, Matthew Dillon, All Rights Reserved
 *
 *	Handles action on all active file descriptors and dispatches
 *	to the proper function in FILES.C
 *
 */

#include "dnet.h"
#include <sys/wait.h>
#include <sys/resource.h>

handle_child()
{
    union wait stat;
    struct rusage rus;
    while (wait3(&stat, WNOHANG, &rus) > 0);
}

char *
showselect(ptr)
fd_set *ptr;
{
    static char buf[FD_SETSIZE+32];
    short i;

    for (i = 0; i < FD_SETSIZE; ++i) {
	buf[i] = (FD_ISSET(i, ptr)) ? '1' : '0';
    }
    buf[i] = 0;
    return(buf);
}


loganddie()
{
    fflush(stderr);
    fprintf(stderr, "\nHUPSIGNAL\n");
    perror("HUP, last error:");
    fprintf(stderr, "Last select return:\n");
    fprintf(stderr, "  %s\n", showselect(&Fdread));
    fprintf(stderr, "  %s\n", showselect(&Fdwrite));
    fprintf(stderr, "  %s\n", showselect(&Fdexcept));
    fprintf(stderr, "RcvData = %ld\n", RcvData);
    fprintf(stderr, "RChan/WChan = %ld/%ld\n", RChan, WChan);
    fprintf(stderr, "RPStart = %ld\n", RPStart);
    fprintf(stderr, "WPStart = %ld\n", WPStart);
    fprintf(stderr, "WPUsed = %ld\n", WPUsed);
    fprintf(stderr, "RState = %ld\n", RState);
    fflush(stderr);
    kill(0, SIGILL);
    exit(1);
}

#define SASIZE(sa)      (sizeof(sa)-sizeof((sa).sa_data)+strlen((sa).sa_data))

main(ac,av)
char *av[];
{
    long sink_mask, dnet_mask;
    long mark = 0;
    ubyte notdone;
    char local_dir[MAXPATHLEN];
    struct passwd pw_info;
    extern void RcvInt();

    if (getenv("DNETDIR")) {
	strcpy(local_dir, getenv("DNETDIR"));
	if (chdir(local_dir)) {
	    fprintf(stderr, "Unable to chdir to DNETDIR: %s\n", local_dir);
	    exit(1);
	}
	freopen("DNET.LOG", "w", stderr);
    } else {
	pw_info = *getpwuid(getuid());
	strcpy(local_dir, pw_info.pw_dir);
	strcat(local_dir, "/.dnet");
	if (chdir(local_dir)) {
	    mkdir(local_dir, 0700);
	    if (chdir(local_dir)) {
		fprintf(stderr, "Unable to create dir %s\n", local_dir);
		exit(1);
	    }
	}
	freopen("DNET.LOG", "w", stderr);
    }
    fprintf(stderr, "DNet startup\n");
    fprintf(stderr, "Log file placed in %s\n", local_dir);
    fflush(stderr);

    signal(SIGINT, SIG_IGN);
    signal(SIGPIPE, SIG_IGN);
    signal(SIGQUIT, SIG_IGN);
    signal(SIGCHLD, handle_child);
    signal(SIGHUP, loganddie);

    bzero(Pkts,sizeof(Pkts));
    setlistenport("");

    {
	register short i;
	for (i = 1; i < ac; ++i) {
	    register char *ptr = av[i];
	    if (*ptr != '-') {
		DDebug = 1;
		fprintf(stderr, "Debug mode on\n");
		continue;
	    }
	    switch(*ptr) {
	    case 'B':
		USecPerByte = atoi(ptr+1) / 10;     /*  bytes per second */
		USecPerByte = 1000000 / USecPerByte;
		fprintf(stderr, "Assuming about %ld uS/byte for timeout calc\n",
		    USecPerByte
		);
		break;
	    default:
		fprintf(stderr, "Unknown option: %c\n", *ptr);
		printf("Unknown option: %c\n", *ptr);
		exit(1);
	    }
	}
    }

    NewList(&TxList);

    Fdperm[0] = 1;
    Fdstate[0] = RcvInt;
    FD_SET(0, &Fdread);
    FD_SET(0, &Fdexcept);

    fprintf(stderr, "DNET RUNNING, Listenfd=%ld\n", DNet_fd);
    NetOpen();          /* initialize network and interrupt driven read */
    TimerOpen();        /* initialize timers                            */

    do_netreset();
    do_restart();

    notdone = 1;
    while (notdone) {
	/*
	 *    MAIN LOOP.  select() on all the file descriptors.  Set the
	 *    timeout to infinity (NULL) normally.  However, if there is
	 *    a pending read or write timeout, set the select timeout
	 *    to 2 seconds in case they timeout before we call select().
	 *    (i.e. a timing window).  OR, if we are in the middle of a
	 *    read, don't use descriptor 0 and timeout according to
	 *    the expected read length, then set the descriptor as ready.
	 */

	fd_set fd_rd;
	fd_set fd_wr;
	fd_set fd_ex;
	struct timeval tv, *ptv;
	int err;

	fd_rd = Fdread;
	fd_wr = Fdwrite;
	fd_ex = Fdexcept;

	tv.tv_sec = 0;		/* normally wait forever for an event */
	tv.tv_usec= 0;
	ptv = NULL;
	if ((Rto_act || Wto_act)) {     /* unless timeout pending */
	    ptv = &tv;
	    tv.tv_sec = 2;
	}

	/*   ... or expecting data (don't just wait for one byte).
	 *
	 *   This is an attempt to reduce the CPU usage for the process.
	 *   If we are expecting data over the serial line, then don't
	 *   return from the select() even if data is available, but
	 *   wait for the timeout period indicated before reading the
	 *   data.  Don't wait more than 64 byte times or we may loose
	 *   some data (the silo's are only so big.. like 128 bytes).
	 *
	 *   Currently, USecPerByte is not set properly (set for 9600 baud)
	 */

	if (RExpect) {
	    ptv = &tv;
	    tv.tv_usec= USecPerByte * ((RExpect < 64) ? RExpect : 64);
	    tv.tv_sec = 0;
	    FD_CLR(0, &fd_rd);
	}
	err = select(FD_SETSIZE, &fd_rd, &fd_wr, &fd_ex, ptv);
	if (RExpect) {
	    FD_SET(0, &fd_rd);   /* pretend data ready */
	}
	if (DDebug)
	    fprintf(stderr, "SERR %ld %ld %08lx %08lx\n",
		err, errno, RExpect, ptv
	    );

	if (RTimedout) {
	    RTimedout = 0;
	    do_rto();
	    mark = 0;
	}
	if (WTimedout) {
	    WTimedout = 0;
	    do_wto();
	}
	if (err < 0) {
	    if (errno == EBADF) {
		perror("select");
		dneterror(NULL);
	    }
	} else {
	    register short i;
	    register short j;
	    register long mask;

	    for (i = 0; i < FD_SETSIZE/NFDBITS; ++i) {
		if (mask = fd_ex.fds_bits[i]) {
		    for (j = i * NFDBITS; mask; (mask >>= 1),(++j)) {
			if (mask & 1)
			    (*Fdstate[j])(2,j);
		    }
		}
		if (mask = fd_wr.fds_bits[i]) {
		    for (j = i * NFDBITS; mask; (mask >>= 1),(++j)) {
			if (mask & 1)
			    (*Fdstate[j])(1,j);
		    }
		}
		if (mask = fd_rd.fds_bits[i]) {
		    for (j = i * NFDBITS; mask; (mask >>= 1),(++j)) {
			if (mask & 1)
			    (*Fdstate[j])(0,j);
		    }
		}
	    }
	}
	if (RcvData != mark)
	    mark = do_rnet();
	do_wupdate();
	do_wnet();
    }
    dneterror(NULL);
}

void
nop()
{
}

do_netreset()
{
    register short i;
    register CHAN *ch;
    for (i = 0; i < FD_SETSIZE; ++i) {
	if (!Fdperm[i])
	    Fdstate[i] = nop;
    }
    for (i = 0, ch = Chan; i < MAXCHAN; ++i, ++ch) {
	switch(ch->state) {
	case CHAN_OPEN:
	case CHAN_LOPEN:	/*  pending on network	    */
	case CHAN_CLOSE:
	    if (ch->fd >= 0) {
		FD_CLR(ch->fd, &Fdread);
		FD_CLR(ch->fd, &Fdexcept);
		Fdstate[ch->fd] = nop;
		close(ch->fd);
		ch->fd = -1;
		ch->state = CHAN_FREE;
		ch->flags = 0;
		--NumCon;
	    }
	    ClearChan(&TxList, i, 1);
	    break;
	}
    }
    RPStart = 0;
    WPStart = 0;
    WPUsed  = 0;
    RState  = 0;
    RChan = 0;
    WChan = 0;
}

do_restart()
{
    static ubyte buf[3] = { SYNC, PKCMD_RESTART, (SYNC<<1)^PKCMD_RESTART };
    Restart = 1;
    NetWrite(buf, sizeof(buf), 1);
    WTimeout(WTIME);
}

setlistenport(remotehost)
char *remotehost;
{
    static struct sockaddr sa[2];
    int s;
    extern void do_localopen();

    if (DNet_fd >= 0) {
	unlink(sa[0].sa_data);
	Fdstate[DNet_fd] = nop;
	Fdperm[DNet_fd] = 0;
	FD_CLR(DNet_fd, &Fdread);
	FD_CLR(DNet_fd, &Fdexcept);
	close(DNet_fd);
    }
    setenv("DNETHOST=", remotehost);
    sprintf(sa[0].sa_data, "DNET.%s", remotehost);
    unlink(sa[0].sa_data);
    sa[0].sa_family = AF_UNIX;

    s = socket(PF_UNIX, SOCK_STREAM, 0);
    fcntl(s, F_SETOWN, getpid());
    fcntl(s, F_SETFL,  FNDELAY);
    if (bind(s, &sa[0], SASIZE(sa[0])) < 0) {
	perror("bind");
	exit(1);
    }
    if (listen(s, 5) < 0) {
	unlink(sa[0].sa_data);
	perror("listen");
	exit(1);
    }
    DNet_fd = s;
    Fdstate[DNet_fd] = do_localopen;
    Fdperm[DNet_fd] = 1;
    FD_SET(DNet_fd, &Fdread);
    FD_SET(DNet_fd, &Fdexcept);
}

