/*
 *	Copyright 1988 by Rayan S. Zachariassen, all rights reserved.
 *	This will be free software, but only when it is finished.
 */

#include "hostenv.h"
#include <stdio.h>
#include <sys/param.h>
#include "scheduler.h"
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>
#include <sys/file.h>

/*
 * FD_SETSIZE should be set in either <sys/param.h> or
 * <sys/types.h> if we have USE_SELECT
 */
#ifndef FD_SETSIZE
#define	FD_SETSIZE	32
#endif	/* !FD_SETSIZE */

#ifdef	USE_UNIONWAIT
#include <sys/wait.h>
#endif	/* USE_UNIONWAIT */

struct procinfo *cpids = NULL;

#define	PIPESIZ	4096	/* pipe buffer size (max contents before blocking) */

#define	MAXFILESPERTRANSPORT	1000

int	numkids = 0;

/*
 * Collect up all the marked control file names (unmarking them in the process)
 * and send them to the command given by the parameter. Said command must be
 * forked off, and the appropriate structures set up to deal with the stdout
 * of that command.
 */

static int t_n;
static char **t_files;

int
tscan(spl)
	struct spblk *spl;
{
	struct ctlfile *cfp = (struct ctlfile *)spl->data;
	char *dir, path[MAXPATHLEN], buf[MAXPATHLEN];
	extern int subsubdirs;
	extern char *vtx_to_dir();

	/* assert cfp != NULL */
	if (cfp->mark == V_NONE || t_n >= MAXFILESPERTRANSPORT)
		return -1;
	/*
	 * XX: Note that any old filename will do for the control file,
	 * since all names are links to the same file anyway.
	 * However, we need to get the filename right, based on
	 * our favorite filename generation technique.
	 */
	if (subsubdirs)	/* channel/host */
		dir = vtx_to_dir(cfp->head, buf);
	else		/* channel */
		dir = cfp->head->orig[L_CHANNEL]->name;
	(void) sprintf(path, "%s/%lu", dir, cfp->id);
	t_files[t_n++] = strnsave(path, strlen(path));
	cfp->mark = V_NONE;
	return 0;
}

void
transport(vhead, channel, host)
	struct vertex *vhead;
	char *channel, *host;
{
	int i;
	char *files[MAXFILESPERTRANSPORT];	/* XX */
	extern void dotransport();

	/* generate the file names we care about */
	t_n = 0;
	t_files = files;
	sp_scan(tscan, (struct spblk *)NULL, spt_mesh[L_CTLFILE]);
	dotransport(files, t_n, vhead, channel, host);
	for (i = 0; i < t_n; ++i)
		free(files[i]);
}

void
dotransport(files, n, vhead, channel, host)
	char *files[];
	int n;
	struct vertex *vhead;
	char *channel, *host;
{
	struct spblk *spl;
	struct web *chwp, *howp;
	char	*av[30], *s, *os, *cp, *ocp, buf[MAXPATHLEN];
	int	i;
	extern int	verbose;
	extern char	*replhost, *replchannel;
	extern void runcommand();
	extern int cistrcmp(), ranny();

	if (n == 0)
		return;
	chwp = NULL;
	if (channel != NULL) {
		spl = sp_lookup(symbol((u_char *)channel), spt_mesh[L_CHANNEL]);
		if (spl == NULL || (chwp = (struct web *)spl->data) == NULL)
			return;	/* yes, this can happen */
	}
	howp = NULL;
	if (host != NULL) {
		spl = sp_lookup(symbol((u_char *)host), spt_mesh[L_HOST]);
		if (spl == NULL || (howp = (struct web *)spl->data) == NULL)
			return;	/* yes, this can happen */
	}
	if (vhead->ce.argv == NULL) {
		if (verbose) {
			fprintf(stderr, "No command defined for %s/%s!\n",
				channel, host);
		}
		return;
	}
	/*
	 * Replace the $host and $channel strings in the command line.
	 */
	os = buf;
	for (i = 0; vhead->ce.argv[i] != NULL; ++i) {
		if (cistrcmp(vhead->ce.argv[i], replhost) == 0) {
			av[i] = host;
		} else if (cistrcmp(vhead->ce.argv[i], replchannel) == 0) {
			av[i] = channel;
		} else if (strchr(vhead->ce.argv[i], '$') != NULL) {
			s = os;
			for (cp = vhead->ce.argv[i]; *cp != '\0'; ++cp) {
				if (*cp == '$' && *(cp+1) == '{') {
					cp += 2;
					ocp = cp;
					while (*cp != '\0' && *cp != '}')
						++cp;
					if (*cp == '}') {
						*cp = '\0';
						strcpy(s, getzenv(ocp));
						s += strlen(s);
						*cp = '}';
					} else
						--cp;
				} else
					*s++ = *cp;
			}
			*s = '\0';
			av[i] = os;
			os = s + 1;
		} else
			av[i] = vhead->ce.argv[i];
	}
	av[i] = NULL;
	/*
	 * randomize the file names if appropriate, this is a simpleminded
	 * but probably effective way of avoiding a single message blocking
	 * some queue.  XX: This should really be expanded to invoke a general
	 * strategy routine/mechanism.
	 */
	if (n > 1) {
		char *temp;
		u_int ni;

		for (i = 0; i < n; ++i) {
			ni = ranny(n-1);
			temp = files[i];
			files[i] = files[ni];
			files[ni] = temp;
		}
	}
	/* fork off the appropriate command with the appropriate stdin */
	if (verbose) {
		(void) printf("$");
		for (i = 0; av[i] != NULL; ++i)
			(void) printf(" %s", av[i]);
		(void) printf(" <<\\EOF\n");
		for (i = 0; i < n; ++i)
			(void) printf("%s\n", files[i]);
		(void) printf("\n\\EOF\n");
	}
	runcommand(files, n, av, vhead, chwp, howp);
}

void
runcommand(filearr, nelem, argv, vhead, chwp, howp)
	char	*filearr[];
	int	nelem;
	char	*argv[];
	struct vertex *vhead;
	struct web *chwp, *howp;
{
	int	i, pid, position, to[2], from[2], uid, gid;
	char	*cmd;
	FILE	*fp;
	extern int verbose;
	extern int epipe();
	extern void stashprocess();

	uid = vhead->ce.uid;
	gid = vhead->ce.gid;
	cmd = vhead->ce.command;

	if (nelem <= 0)
		return;
	if (epipe(to) < 0)
		return;
	if (epipe(from) < 0) {
		(void) close(to[0]);
		(void) close(to[1]);
		return;
	}
	if (verbose)
		(void) fprintf(stderr, "to %d/%d from %d/%d\n",
			       to[0],to[1],from[0],from[1]);
	if ((pid = fork()) == 0) {	/* child */
		if (to[0] != 0) {
			(void) close(0);
			(void) dup(to[0]);		/* child stdin */
			(void) close(to[0]);
		}
		if (from[1] != 1) {
			(void) close(1);
			(void) dup(from[1]);		/* child stdout */
			(void) close(from[1]);
		}
		/* keep current stderr for child stderr */
		/* close all other open filedescriptors */
#if	0
		/* ... if detach() did its job, there shouldn't be any! */
		for (i = 3; i < getdtablesize(); ++i)
			(void) close(i);
#else
		(void) close(to[1]);
		(void) close(from[0]);
		/* we can ignore the stdouts from other children */
#endif
		(void) setuid(uid);
		(void) setgid(gid);
		(void) execv(cmd, argv);
		(void) fprintf(stderr, "Exec of %s failed!\n", cmd);
		_exit(1);
	} else if (pid < 0) {		/* fork failed - yell and forget it */
		(void) close(to[0]); (void) close(to[1]);
		(void) close(from[0]); (void) close(from[1]);
		(void) fprintf(stderr, "Fork failed!\n");
		return;
	} else {			/* parent */
		(void) close(to[0]);
		(void) close(from[1]);
		++numkids;
		if (chwp != NULL) chwp->kids += 1;
		if (howp != NULL) howp->kids += 1;
		/* save from[0] away as a descriptor to watch */
		stashprocess(pid, from[0], chwp, howp, vhead);
		/* write the list of files into to[1], and close it */
		if ((fp = fdopen(to[1], "w")) == NULL) {
			if (verbose)
				fprintf(stderr, "cannot open fd %d for w.\n",
						to[1]);
			(void) close(to[1]);
			(void) close(from[0]);
			return;
		}
		for (i = position = 0; i < nelem; ++i) {
			position += strlen(filearr[i]) + 1;
			if (position + 1 >= PIPESIZ)
				break;
			(void) fprintf(fp, "%s\n", filearr[i]);
		}
		if (verbose)
			(void) fprintf(stderr, "wrote %d bytes to fd %d\n",
				       position, fileno(fp));
		(void) fputc('\n', fp);
		(void) fclose(fp);
#if 0
		(void) close(to[1]);	/* XX: will this always give error? */
#endif
	}
}

void
stashprocess(pid, fd, chwp, howp, vhead)
	int pid, fd;
	struct web *chwp, *howp;
	struct vertex *vhead;
{
	int i;
	extern int verbose;

	if (verbose)
		(void) fprintf(stderr, "stashprocess(%d, %d, %s, %s)\n",
			       pid, fd, chwp ? chwp->name : "nil",
			       howp ? howp->name : "nil");
	if (cpids == NULL) {
		i = FD_SETSIZE;
		cpids = (struct procinfo *)
			emalloc((unsigned)(i * sizeof (struct procinfo)));
		while (--i >= 0)
			cpids[i].pid = 0;
	}
	cpids[fd].pid = pid;
	cpids[fd].ch = chwp;
	cpids[fd].ho = howp;
	cpids[fd].vertices = vhead;
	vhead->proc = &cpids[fd];
	cpids[fd].carryover = NULL;
#ifdef	USE_BSDNODELAY
	/* set up non-blocking I/O */
	i = fcntl(fd, F_GETFL, 0);
#ifdef	FNONBLOCK
	i |= FNONBLOCK;
#else
	i |= FNDELAY;
#endif
	i = fcntl(fd, F_SETFL, i);
#endif	/* USE_BSDNODELAY */
}

void
reclaim(fd)
	int fd;
{
	struct vertex *vp, *nvp;
	extern char *progname;
	extern void unweb(), reschedule(), fifoschedule();

	cpids[fd].pid = 0;
	if (cpids[fd].carryover != NULL) {
		(void) fprintf(stderr, "%s: HELP! Lost %d bytes: '%s'\n",
				progname, strlen(cpids[fd].carryover),
				cpids[fd].carryover);
		free(cpids[fd].carryover);
		cpids[fd].carryover = NULL;
	}
	if (cpids[fd].ch != NULL) {
		--cpids[fd].ch->kids;
		if (cpids[fd].ch->fifohead)
			fifoschedule(cpids[fd].ch);
		else if (cpids[fd].ch->kids == 0 && cpids[fd].ch->link == NULL)
			unweb(L_CHANNEL, cpids[fd].ch);
	}
	if (cpids[fd].ho != NULL) {
		--cpids[fd].ho->kids;
		if (cpids[fd].ho->fifohead)
			fifoschedule(cpids[fd].ho);
		else if (cpids[fd].ho->kids == 0 && cpids[fd].ho->link == NULL)
			unweb(L_HOST, cpids[fd].ho);
	}
	(void) close(fd);

	/* Reschedule the vertices that are left (that were not reported on). */
	nvp = cpids[fd].vertices;
	cpids[fd].vertices = NULL;
	for (vp = nvp; vp != NULL ; vp = nvp) {
		nvp = vp->nextitem;
		if (vp->ngroup == 0)
			abort();
		reschedule(vp, 0, -1);
	}
}

void
waitandclose(fd)
	int	fd;
{
	int	n, pid;
	extern int verbose;

#if 0
	/* This is a quick and dirty hack (for A/UX I think). BEWARE */
	if (cpids[fd].pid <= 0) {
		fprintf(stderr,"fd %d: pid is %d ??\n",fd,cpids[fd].pid);
		return;
	}
	n = waitpid(cpids[fd].pid, (int *)NULL, 0);
	if(n != cpids[fd].pid && (n != -1 || errno != 10)) {
		fprintf(stderr,"fd %d, pid %d: waitpid says %d, errno %d\n",fd,cpids[fd].pid, n, errno);
		return;
	}
	--numkids;
	reclaim(fd);
#else
	if (cpids[fd].pid > 0) {
#ifdef	USE_UNIONWAIT
		while ((pid = wait((union wait *)0)) > 0)
#else	/* !USE_UNIONWAIT */
		while ((pid = wait((int *)0)) > 0)
#endif	/* USE_UNIONWAIT */
		{
			--numkids;
			if (verbose)
				(void) printf("reaped %d\n", pid);
			if (pid == cpids[fd].pid) {
				reclaim(fd);
				break;
			} else {
				for (n = 0; n < FD_SETSIZE; ++n) {
					if (cpids[n].pid == pid) {
						cpids[n].pid = -pid;
						++numkids;
						break;
					}
				}
			}
		}
	} else if (cpids[fd].pid < 0) {
		--numkids;
		reclaim(fd);
	}
#endif
}

#ifdef	USE_SELECT

#if	defined(BSD4_3) || defined(sun)
#include <sys/file.h>
#endif
#include <errno.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/time.h>

#ifndef	NFDBITS
/*
 * This stuff taken from the 4.3bsd /usr/include/sys/types.h, but on the
 * assumption we are dealing with pre-4.3bsd select().
 */

typedef long	fd_mask;

#ifndef	NBBY
#define	NBBY	8
#endif	/* NBBY */
#define	NFDBITS		((sizeof fd_mask) * NBBY)

/* SunOS 3.x and 4.x>2 BSD already defines this in /usr/include/sys/types.h */
#ifdef	notdef
typedef	struct fd_set { fd_mask	fds_bits[1]; } fd_set;
#endif	/* notdef */

#ifndef	FD_SET
#define	FD_SET(n, p)	((p)->fds_bits[0] |= (1 << (n)))
#define	FD_CLR(n, p)	((p)->fds_bits[0] &= ~(1 << (n)))
#define	FD_ISSET(n, p)	((p)->fds_bits[0] & (1 << (n)))
#define FD_ZERO(p)	bzero((char *)(p), sizeof(*(p)))
#endif	/* !FD_SET */
#endif	/* !NFDBITS */

void
mux()
{
	int	i, n, maxf;
	fd_set	mask;
	struct sockaddr_in raddr;
	int	raddrlen;
	extern int errno, querysocket;
	extern void qprint(), readfrom();

	maxf = -1;
	FD_ZERO(&mask);
	for (i = 0; i < FD_SETSIZE ; ++i)
		if (cpids[i].pid != 0) {
			FD_SET(i, &mask);
			maxf = i;
		}
	if (querysocket >= 0) {
		FD_SET(querysocket, &mask);
		if (maxf < querysocket)
			maxf = querysocket;
	}
	if (maxf < 0)
		return;
	++maxf;
	/*fprintf(stderr, "about to select on %x [%d]\n",
			  mask.fds_bits[0], maxf); */
	if ((n = select(maxf, &mask,
	    (int *)NULL, (int *)NULL, (struct timeval *)NULL)) < 0) {
		/* fprintf(stderr, "got an interrupt (%d)\n", errno); */
		if (errno == EINTR)
			return;
	} else if (n == 0) {
		(void) fprintf(stderr, "abnormal 0 return from select!\n");
	} else {
		/*fprintf(stderr, "got %d ready (%x)\n", n, mask.fds_bits[0]);*/
		while (n > 0) {
			if (querysocket >= 0 && FD_ISSET(querysocket, &mask)) {
				--n;
				FD_CLR(querysocket, &mask);
				raddrlen = sizeof raddr;
				i = accept(querysocket,
					   (struct sockaddr *)&raddr,&raddrlen);
				if (i < 0) {
					perror("accept");
				} else
					qprint(i);
				continue;
			}
			for (i = 0; i < maxf; ++i) {
				if (cpids[i].pid != 0 && FD_ISSET(i, &mask)) {
					--n;
					FD_CLR(i, &mask);
					/*fprintf(stderr,"that is fd %d\n",i);*/
					break;
				}
			}
			if (i >= maxf)
				break;
			/* do non-blocking reads from this fd */
			readfrom(i);
		}
	}
	/* fprintf(stderr, "return from mux\n"); */
}

void
ipcpause()
{
#ifdef	AF_INET
	int	i, n;
	fd_set	mask;
	struct sockaddr_in raddr;
	int	raddrlen;
	extern int querysocket;
	extern void qprint();

	if (querysocket < 0) {
		pause();
		return;
	}
	FD_ZERO(&mask);
	FD_SET(querysocket, &mask);
	if ((n = select(querysocket+1, &mask,
		    (int *)NULL, (int *)NULL, (struct timeval *)NULL)) < 0) {
		if (errno == EINTR)
			return;
	} else if (n == 0) {
		(void) fprintf(stderr, "abnormal 0 return from select!\n");
	} else {
		raddrlen = sizeof raddr;
		i = accept(querysocket, (struct sockaddr *)&raddr, &raddrlen);
		if (i < 0) {
			perror("accept");
		} else
			qprint(i);
	}
#else	/* !AF_INET */
	pause();
#endif	/* AF_INET */
}

void
queryipcinit()
{
#ifdef	AF_INET
	time_t now;
	struct servent *serv;
	struct sockaddr_in sad;
	extern int querysocket;
	extern time_t qipcretry, time();

	if (querysocket >= 0)
		return;
	now = time((time_t *)0);
	if ((serv = getservbyname("mailq", "tcp")) == NULL) {
		(void) fprintf(stderr, "No 'mailq' tcp service defined!\n");
		/* try again in 5 minutes or so */
		qipcretry = now + 300;
		return;
	}
	qipcretry = now + 5;
	sad.sin_port = serv->s_port;
	sad.sin_family = AF_INET;
	sad.sin_addr.s_addr = INADDR_ANY;
	if ((querysocket = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
		perror("socket");
		return;
	}
	if (bind(querysocket, (struct sockaddr *)&sad, sizeof sad) < 0) {
		perror("bind");
		(void) close(querysocket);
		querysocket = -1;
		return;
	}
	if (listen(querysocket, 5) < 0) {
		perror("listen");
		(void) close(querysocket);
		querysocket = -1;
		return;
	}
	qipcretry = 0;
#endif	/* AF_INET */
}
#else	/* !HAVE_SELECT */
void
mux()
{
	int	fd;

	/*
	 * Nice'n easy and simpleminded: grab a random file descriptor,
	 * and sit and read off it until something happens.
	 * Some very complicated mux'ing schemes (with shared pipes'n stuff)
	 * are possible in the absence of async i/o like select() or the
	 * simulation USG supplies, but it ain't worth the hassle.
	 */
	for (fd = 0; fd < FD_SETSIZE ; ++fd)
		if (cpids[fd].pid != 0)
			break;
	if (fd >= FD_SETSIZE)
		return;
	readfrom(fd);
}

ipcpause()
{
	pause();
}

void
queryipcinit()
{
}
#endif	/* HAVE_SELECT */

void
readfrom(fd)
	int fd;
{
	int	n, dontbreak;
	char	*cp, *pcp, *eobuf, buf[4096];
	extern void update();
	extern int errno, verbose;

	if (cpids[fd].carryover != NULL) {
		(void) strcpy(buf, cpids[fd].carryover);
		cp = buf+strlen(buf);
		pcp = buf;
		(void) free(cpids[fd].carryover);
		cpids[fd].carryover = NULL;
		dontbreak = 1;
	} else {
		dontbreak = 0;
		cp = pcp = NULL;
	}
	/* Note that if we get an alarm() call, the read will return -1, TG */
	while ((n = read(fd, dontbreak ? cp : buf,
			     sizeof buf - (dontbreak ? (cp - buf) : 0))) > 0) {
		if (verbose)
			(void) fprintf(stderr, "read from %d returns %d\n", fd, n);
		eobuf = (dontbreak ? cp : buf) + n;
		for (cp = buf, pcp = buf; cp < eobuf; ++cp) {
			if (*cp == '\n') {
				*cp = '\0';
				if (verbose)
					fprintf(stderr, "%d processed: %s\n",
							fd, pcp);
				update(pcp);
				pcp = cp + 1;
				dontbreak = 0;
			} else
				dontbreak = 1;
		}
		if (dontbreak && cp == buf + sizeof buf) {
			if (pcp == buf) {
			/* XX:
			 * can't happen, this would mean a status report line 
			 * that is rather long...
			 * (oh no! it did happen, it did, it did!...)
			 */
			} else {
				bcopy(pcp, buf, cp-pcp);
				cp = buf + (cp-pcp);
				*cp = '\0';
				pcp = buf;	/* may be used below */
			}
		}
#ifndef	USE_BSDNODELAY
		if (!dontbreak)
			break;
#endif	/* !USE_BSDNODELAY */
	}
	if (verbose) {
		fprintf(stderr, "read from %d returns %d\n", fd, n);
		if (n < 0) fprintf(stderr, "errno is %d\n", errno);
	}
	if (n == 0 || (n < 0 && errno != EINTR && errno != EWOULDBLOCK
			     && errno != EAGAIN))
		waitandclose(fd);
	/* fprintf(stderr, "n = %d, errno = %d\n", n, errno); */
	/*
	 * if n < 0, then either we got an interrupt or the read would
	 * block (EINTR or EWOULDBLOCK). In both cases we basically just
	 * want to get back to whatever we were doing. We just need to
	 * make darned sure that a newline was the last character we saw,
	 * or else some report may get lost somewhere.
	 */
	if (dontbreak) {
		if (cpids[fd].pid != 0) {
			cpids[fd].carryover = emalloc(cp-pcp+1);
			bcopy(pcp, cpids[fd].carryover, cp-pcp);
			*(cpids[fd].carryover+(cp-pcp)) = '\0';
		} else
			fprintf(stderr,
				"HELP! Lost %d bytes (n=%d/%d, off=%d): '%s'\n",
				cp - pcp, n, errno, pcp-buf, pcp);
	}
}

#if 0
close(fd)
	int fd;
{
	fprintf(stderr, "closing fd %d\n", fd);
	if (fd == querysocket && getppid() == 1)
		abort();
	xclose(fd);
}
#endif


#if defined(USE_BINMKDIR) || defined(USE_BINRMDIR)

/*
 * From Ross Ridge's Xenix port:
 * - A nasty problem occurs with scheduler if rmdir (and mkdir also I think),
 *   is implented as system("/bin/rmdir ...").  When system() calls wait()
 *   it can reap the scheduler's children without it knowing.  I fixed this
 *   problem by writing a replacement system() function for scheduler.
 *
 */

int
system(name)
	char *name;
{
        char *sh;
        int st, r;
        int pid;
        int i;

        pid = fork();
        switch(pid) {
        case -1:
                return -1;
        case 0:
                sh = getenv("SHELL");
                if (sh == NULL) {
                        sh = "/bin/sh";
                }
                execl(sh, sh, "-c", name, NULL);
                _exit(1);
        default:
                while(1) {
                        r = wait(&st);
                        if (r == -1) {
                                if (errno != EINTR) {
                                        return -1;
                                if (errno != EINTR) {
                                        return -1;
                                }
                        } else if (r == pid) {
                                break;
                        }
                        for(i = 0; i < FD_SETSIZE; i++) {
                                if (cpids[i].pid == r) {
                                        cpids[i].pid = -r;
                                        break;
                                }
                        }
                }

                if ((st & 0x00ff) == 0) {
                        return st >> 8;
                }

                return 1;
        }
}

#endif
