/*
 *	vmspipe.c
 *		adapted from elvis, in turn from
 *		Chris Janton's (chj) VMS Icon port.
 *
 * $Header: /usr/build/vile/vile/RCS/vmspipe.c,v 1.13 2000/01/15 01:09:01 tom Exp $
 *
 */

#include "estruct.h"

#include <starlet.h>
#include <lib$routines.h>

#include <iodef.h>
#include <ssdef.h>
#include <dvidef.h>
#include <file.h>

#define	OK(f)	((f) & STS$M_SUCCESS)
#define	MAXBFR	256

/*
 *	Here we fudge to help the "elvis" program implement rpipe.  The
 *	routine essentially does an popen using fd as stdin--except that
 *	few VMS utilities use the 'C' library.	So we pass in the standard
 *	input file name and use it if fd is non-zero.
 */

typedef struct _descr {
		int	length;
		char *	ptr;
	} descriptor;

typedef struct _pipe {
	long	pid;		/* process id of child */
	long	status;		/* exit status of child */
	long	flags;		/* lib$spawn flags */
	int	ichan;		/* MBX channel number */
	int	ochan;
	int	efn;
	unsigned running : 1;	/* 1 if child is running */
} Pipe;

Pipe _pipes[_NFILE];		/* one for every open file */

#define NOWAIT		1
#define NOCLISYM	2
#define NOLOGNAM	4
#define NOKEYPAD	8
#define NOTIFY		16
#define NOCONTROL	32
#define SFLAGS	(NOWAIT|NOKEYPAD|NOCONTROL)


/*
 * Taken from pclose - close a pipe
 * Last modified 2-Apr-86/chj
 */
static int
vms_rpclose(int pfile)
{
	Pipe *pd = (pfile >= 0 && pfile < TABLESIZE(_pipes)) ? &_pipes[pfile] : 0;
	int status;
	int fstatus;

	if (pd == NULL)
		return (-1);
	fstatus = close(pfile);
	sys$dassgn(pd->ichan);
	sys$dassgn(pd->ochan);
	lib$free_ef(&pd->efn);
	pd->running = 0;
	return (fstatus);
}

static int
vms_pread (int pfile, char *buffer, int size)
/* Be compatible when we read data in (handle newlines). */
{
	Pipe *pd = (pfile >= 0 && pfile < TABLESIZE(_pipes)) ? &_pipes[pfile] : 0;
	struct {
		short	status,
			count;
		int:16;
	} iosb;

	/*
	 * This is sort of nasty.  The default mailbox size is 256 maxmsg and
	 * 1056 bufquo if your sysgen parameters are standard.  Asking for more
	 * on the CREMBX command (in rpipe) might be a bad idea as that could
	 * cause an "exceeded quota" error.  Since we only return -1 on error,
	 * there's no hope that the poor user would ever know what went wrong.
	 */
	if (pd != 0) {
		int	request = size > MAXBFR ? MAXBFR : size - 1;
		register int	got;

		if (OK(sys$qiow(0, pd->ochan, IO$_READVBLK, &iosb, 0, 0,
				buffer, request, 0, 0, 0, 0))) {

			if (iosb.status == SS$_ENDOFFILE)
				return 0;
			got = iosb.count;
			if (got == 0 || buffer[got-1] != '\n')
				buffer[got++] = '\n';
			buffer[got] = EOS;
			return got;
		}
	}
	return -1;
}

FILE *
vms_rpipe (const char *cmd, int fd, const char *input_file)
{
	int	pfile;		/* the Pfile */
	Pipe	*pd;		/* _pipe database */
	descriptor inmbxname;	/* name of input mailbox */
	descriptor outmbxname;	/* name of mailbox */
	char	inmname[65];
	char	outmname[65];	/* mailbox name string */
	int	ochan;		/* mailbox channel number */
	int	ichan;		/* Input mailbox channel number */
	int	efn;

	struct {
		short	len;
		short	code;
		void *	address;
		void *	retlen;
		int	last;
	} itmlst;

	if (!cmd || cmd[0] == EOS)
		return (0);
	lib$get_ef(&efn);
	if (efn == -1)
		return (0);

	/* create and open the input mailbox */
	if (!OK(sys$crembx(0, &ichan, 0, 0, 0, 0, 0))) {
		lib$free_ef(&efn);
		return (0);
	}

	itmlst.last = inmbxname.length = 0;
	itmlst.address = inmbxname.ptr = inmname;
	itmlst.retlen = &inmbxname.length;
	itmlst.code = DVI$_DEVNAM;
	itmlst.len = 64;
	if (!OK(sys$getdviw(0, ichan, 0, &itmlst, 0, 0, 0, 0))) {
		lib$free_ef(&efn);
		return (0);
	}
	inmname[inmbxname.length] = EOS;

	/* create and open the output mailbox */
	if (!OK(sys$crembx(0, &ochan, 0, 0, 0, 0, 0))) {
		lib$free_ef(&efn);
		return (0);
	}

	itmlst.last = outmbxname.length = 0;
	itmlst.address = outmbxname.ptr = outmname;
	itmlst.retlen = &outmbxname.length;
	if (!OK(sys$getdviw(0, ochan, 0, &itmlst, 0, 0, 0, 0))) {
		lib$free_ef(&efn);
		return (0);
	}

	outmname[outmbxname.length] = EOS;
	pfile = open(outmname, O_RDONLY, S_IREAD);
	if (pfile < 0) {
		lib$free_ef(&efn);
		sys$dassgn(ichan);
		sys$dassgn(ochan);
		return (0);
	}

	/* Save file information now */
	pd = &_pipes[pfile]; /* get Pipe pointer */
	pd->pid   = pd->status = pd->running = 0;
	pd->flags = SFLAGS;
	pd->ichan = ichan;
	pd->ochan = ochan;
	pd->efn   = efn;

	/* Initiate the command by writing down the input mailbox (SYS$INPUT). */

	if (fd > 0) {
		char	pre_command[132+12];

		(void)strcpy (pre_command, "DEFINE/USER SYS$INPUT ");
		(void)strcat (pre_command, input_file);
		if (!OK(
			sys$qiow(0,
				ichan,
				IO$_WRITEVBLK | IO$M_NOW,
				0, 0, 0,
				pre_command, strlen(pre_command),
				0, 0, 0, 0))) {
			lib$free_ef(&efn);
			sys$dassgn(ichan);
			sys$dassgn(ochan);
			return (0);
		}
	}

	if (!OK(
		sys$qiow(0,
		ichan,
		IO$_WRITEVBLK | IO$M_NOW,
		0, 0, 0, cmd, strlen(cmd), 0, 0, 0, 0))) {
		;
	} else if (!OK(
		sys$qiow(0,
			ichan,
			IO$_WRITEOF | IO$M_NOW,
			0, 0, 0, 0, 0, 0, 0, 0, 0))) {
		;
	} else if (!OK(
		lib$spawn(0,
			&inmbxname,		/* input file */
			&outmbxname,		/* output file */
			&pd->flags,
			0,
			&pd->pid,
			&pd->status,
			&pd->efn,
			0, 0, 0, 0))) {
		;
	} else {
		FILE	*pp;
		int	len;
		int	count	= 0;
		char	buffer[MAXBFR];

		pd->running = 1;

		/*
		 * For our application, we need a file-pointer.  Empty the
		 * pipe into a temporary file.  Unfortunately, this means that
		 * the application will use 'slowreadf()' to write the data.
		 */
		if ((pp = tmpfile()) != 0) {
			for_ever {
				len = vms_pread(pfile, buffer, sizeof(buffer));
				if (len <= 0)
					break;
				fwrite(buffer, sizeof(*buffer), len, pp);
				count++;
			}
			mlforce("[Read %d lines from %s ]", count, cmd);
			vms_rpclose(pfile);
			(void)fflush(pp);
			rewind(pp);
			return pp;
		}
		return (0);
	}

	lib$free_ef(&efn);
	sys$dassgn(ichan);
	sys$dassgn(ochan);
	return (0);
}
