/********************************************
vio_orig.c - virtual IO: original mawk file/pipe IO implementation

libmawk changes (C) 2009-2013, Tibor 'Igor2' Palinkas;
based on mawk code coming with the below copyright:

copyright 1991-94.  Michael D. Brennan

This is a source file for mawk, an implementation of
the AWK programming language.

Mawk is distributed without warranty under the terms of
the GNU General Public License, version 2, 1991.
********************************************/
#include "conf.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdarg.h>
#include "mawk.h"
#include "files.h"
#include "memory.h"
#include "fin.h"
#include "field.h"
#include "vio.h"
#include "array_environ.h"

/* The original mawk file IO layer uses FILE * for output (because fprintf()
   and fflush() works on them) and int fd for input (probably for short reads
   and manual buffering). Pipes (background processes) also need a pid for
   wait(). In some cases stdin was read by fgets().

   The vio reimplementation follows the same conventions, defining mawk_vio_t
   to contain a FILE *, an int fd and a pid. For output FILE * is required,
   input is done exclusively using int fd.
*/

#ifdef	V7
#include  <sgtty.h>							/* defines FIOCLEX */
#endif
#ifndef	 NO_FCNTL_H

#include <fcntl.h>
#define	 CLOSE_ON_EXEC(fd)    fcntl(fd, F_SETFD, 1)

#else
#define	 CLOSE_ON_EXEC(fd) ioctl(fd, FIOCLEX, (PTR) 0)
#endif

typedef struct mawk_vio_orig_s {
	mawk_vio_t vio_common_head;
	pid_t pid; /* we need to wait() when we close a pipe */
	FILE *f;
	int fd;
	int at_eof;
	int no_close;
} mawk_vio_orig_t;


const mawk_vio_imp_t mawk_vio_orig_imp;

static mawk_vio_orig_t *vio_alloc_(mawk_state_t *MAWK)
{
	mawk_vio_orig_t *vf;
	vf = mawk_zmalloc(MAWK, sizeof(mawk_vio_orig_t));
	vf->vio_common_head.imp = &mawk_vio_orig_imp;
	vf->vio_common_head.refco = 0;
	vf->pid = -1;
	vf->fd = -1;
	vf->f = NULL;
	vf->at_eof = 0;
	vf->no_close = 0;
	return vf;
}

int mawk_vio_orig_close(mawk_state_t *MAWK, mawk_vio_t *vf_)
{
	mawk_vio_orig_t *vf = (mawk_vio_orig_t *)vf_;

	if (vf->f != NULL) {
		if (!vf->no_close) {
			fclose(vf->f);
			vf->f = NULL;
		}
	}
	if (vf->fd >= 0) {
		if (!vf->no_close) {
			close(vf->fd);
			vf->fd = -1;
		}
	}
#ifndef MAWK_NO_FORK
	if (vf->pid > 0) {
		if (!vf->no_close) {
			mawk_wait_for(MAWK, vf->pid);
			vf->pid = -1;
		}
	}
#endif
	mawk_zfree(MAWK, vf, sizeof(mawk_vio_orig_t));
	return 0;
}

int mawk_vio_orig_flush(mawk_state_t *MAWK, mawk_vio_t *vf_)
{
	mawk_vio_orig_t *vf = (mawk_vio_orig_t *)vf_;
	if (vf->f != NULL) {
		if (fflush(vf->f) < 0) {
			mawk_errmsg(MAWK, errno, "unexpected write error");
			mawk_exitval(MAWK, 2, -1);
		}
	}
	return 0;
}

static void bufsetup(FILE *f)
{
	if (isatty(fileno(f)))
		setbuf(f, (char *) 0);
}

/* fopen() but no buffering to ttys */
static FILE *tfopen(const char *name, const char *mode)
{
	FILE *retval = fopen(name, mode);
	if (retval)
		bufsetup(retval);

	return retval;
}

static mawk_vio_t *mawk_vio_orig_fdopen(mawk_state_t *MAWK, int fd, mawk_vio_open_mode_t mode)
{
	mawk_vio_orig_t *vf;
	FILE *f;

	switch(mode) {
		case MAWK_VIO_O_TRUNC:
			f = fdopen(fd, "w");
			if (f == NULL)
				return NULL;
			bufsetup(f);
			break;
		case MAWK_VIO_O_APPEND:
			f = fdopen(fd, "a");
			if (f == NULL)
				return NULL;
			bufsetup(f);
			break;
		case MAWK_VIO_I:
			f = NULL;
			break;
	}

	vf = vio_alloc_(MAWK);
	vf->f = f;
	vf->fd = fd;
	return (mawk_vio_t *)vf;
}




TODO("TODO print runtime warnings")
mawk_vio_t *mawk_vio_orig_open(mawk_state_t *MAWK, const char *name, mawk_vio_open_mode_t mode)
{
	mawk_vio_orig_t *vf;
	FILE *f;
	int fd = -1;

	if (((name[0] == '-') && (name[1] == 0)) || (strcmp(name, "/dev/stdin") == 0))
		return mawk_vio_orig_fdopen(MAWK, 0, mode);

	/* /dev/fd/xxx should be fdopen */
	if (strncmp(name, "/dev/fd/", 8) == 0) {
		int fd;
		char *end;
		fd = strtol(name+8, &end, 10);
		if (*end == '\0')
			return mawk_vio_orig_fdopen(MAWK, fd, mode);
	}

	switch(mode) {
		case MAWK_VIO_O_TRUNC:
			f = tfopen(name, "w");
			if (f == NULL)
				return NULL;
			break;
		case MAWK_VIO_O_APPEND:
			f = tfopen(name, "a");
			if (f == NULL)
				return NULL;
			break;
		case MAWK_VIO_I:
			f = NULL;
			fd = open(name, O_RDONLY);
			if (fd < 0)
				return NULL;
	}

	vf = vio_alloc_(MAWK);
	vf->f = f;
	vf->fd = fd;
	return (mawk_vio_t *)vf;
}

#ifdef MAWK_NO_FORK
mawk_vio_t *mawk_vio_orig_open_pipe(mawk_state_t *MAWK, const char *name, int type)
{
	if (!MAWK->do_exit)
		mawk_rt_error(MAWK, "process execution not supported");
	return NULL;
}

void mawk_vio_orig_exec_shell(mawk_state_t *MAWK, const char *cmd)
{
	if (!MAWK->do_exit)
		mawk_rt_error(MAWK, "process execution not supported");
}
#else

void mawk_vio_orig_exec_shell(mawk_state_t *MAWK, const char *cmd)
{
	char **envp = mawk_environ_extract(MAWK);

	if (envp == NULL) {
		mawk_errmsg(MAWK, errno, "failed to exec %s -c %s: can't set up environ[]", MAWK->shell, cmd);
		fflush(stderr);
		_exit(128);
	}
	execle(MAWK->shell, MAWK->shell, "-c", cmd, (char *) 0, envp);
	mawk_errmsg(MAWK, errno, "failed to exec %s -c %s", MAWK->shell, cmd);
	fflush(stderr);
	_exit(128);
}

#ifdef MAWK_NO_PIPE
mawk_vio_t *mawk_vio_orig_open_pipe(mawk_state_t *MAWK, const char *name, int type)
{
	if (!MAWK->do_exit)
		mawk_rt_error(MAWK, "pipes (and background processes) not supported");
	return NULL;
}
#else

mawk_vio_t *mawk_vio_orig_open_pipe(mawk_state_t *MAWK, const char *name, int type)
{
	int the_pipe[2], local_fd, remote_fd;
	mawk_vio_orig_t *vf;
	pid_t pid;
	FILE *f;

	if (pipe(the_pipe) < 0)
		return NULL;

	local_fd = the_pipe[type == PIPE_OUT];
	remote_fd = the_pipe[type == PIPE_IN];

	/* output files shall have a FILE * */
	if (type == PIPE_OUT) {
		f = fdopen(local_fd, "w");
		if (f == NULL) {
			close(local_fd);
			close(remote_fd);
			return NULL;
		}
	}

	/* to keep output ordered correctly */
	fflush(stdout);
	fflush(stderr);

	pid = fork();
	switch (pid) {
		case -1:
			close(local_fd);
			close(remote_fd);
			return NULL;
		case 0:
			close(local_fd);
			close(type == PIPE_IN);
			dup(remote_fd);
			close(remote_fd);
			mawk_vio_exec_shell(MAWK, name);
		default:
			close(remote_fd);
			/* we could deadlock if future child inherit the local fd ,
			   set close on exec flag */
TODO(": better do this by hand - close on exec is not really portable")
			CLOSE_ON_EXEC(local_fd);
			break;
	}

	vf = vio_alloc_(MAWK);
	vf->pid = pid;

	/* for an output pipe store FILE * for consistent writes */
	if (type == PIPE_OUT)
		vf->f = f;
	else
		vf->fd = local_fd;

	return (mawk_vio_t *)vf;
}
#endif
#endif

int mawk_vio_orig_putc(mawk_state_t *MAWK, mawk_vio_t *vf_, char c)
{
	mawk_vio_orig_t *vf = (mawk_vio_orig_t *)vf_;
	if (vf->f == NULL)
		return -1;
	if (fputc(c, vf->f) != c)
		return -1;
	return 0;
}

int mawk_vio_orig_write(mawk_state_t *MAWK, mawk_vio_t *vf_, const char *data, int len)
{
	mawk_vio_orig_t *vf = (mawk_vio_orig_t *)vf_;
	if (vf->f == NULL)
		return -1;
	if (fwrite(data, 1, len, vf->f) == 1)
		return 0;
	return  -1;
}

int mawk_vio_orig_write_str(mawk_state_t *MAWK, mawk_vio_t *vf_, const char *str)
{
	mawk_vio_orig_t *vf = (mawk_vio_orig_t *)vf_;
	if (vf->f == NULL)
		return -1;
	return fprintf(vf->f, "%s", str);
}

int mawk_vio_orig_printf(mawk_state_t *MAWK, mawk_vio_t *vf_, const char *fmt, ...)
{
	mawk_vio_orig_t *vf = (mawk_vio_orig_t *)vf_;
	int len;
	va_list ap;

	if (vf->f == NULL)
		return -1;

	va_start(ap, fmt);
	len = vfprintf(vf->f, fmt, ap);
	va_end(ap);
	return len;
}

int mawk_vio_orig_error(mawk_state_t *MAWK, mawk_vio_t *vf_)
{
	mawk_vio_orig_t *vf = (mawk_vio_orig_t *)vf_;
	if (vf->f == NULL)
		return -1;
	return ferror(vf->f);
}

int mawk_vio_orig_setbuf(mawk_state_t *MAWK, mawk_vio_t *vf_, int buf_enable)
{
	mawk_vio_orig_t *vf = (mawk_vio_orig_t *)vf_;
	if (vf->f == NULL)
		return -1;

	if (buf_enable)
		setbuf(vf->f, "");
	else
		setbuf(vf->f, NULL);

	return 0;
}

int mawk_vio_orig_read(mawk_state_t *MAWK, mawk_vio_t *vf_, char *dst, long int size)
{
	mawk_vio_orig_t *vf = (mawk_vio_orig_t *)vf_;
	int len;

	/* input file must not have a FILE * */
	if (vf->f != NULL)
		return -1;

	/* already at eof, don't attempt to read any more */
	if (vf->at_eof)
		return 0;

	/* invalid fd, how did we get here? */
	if (vf->fd < 0)
		return -1;

	len = read(vf->fd, dst, size);
	if (len <= 0)
		vf->at_eof = 1;
	return len;
}

void mawk_vio_orig_mark_no_close(mawk_state_t *MAWK, mawk_vio_t *vf_)
{
	mawk_vio_orig_t *vf = (mawk_vio_orig_t *)vf_;
	vf->no_close = 1;
}

const mawk_vio_imp_t mawk_vio_orig_imp = {
	mawk_vio_orig_putc,
	mawk_vio_orig_write_str,
	mawk_vio_orig_write,
	mawk_vio_orig_printf,
	mawk_vio_orig_read,
	mawk_vio_orig_close,
	mawk_vio_orig_flush,
	mawk_vio_orig_error,
	mawk_vio_orig_mark_no_close
};

const mawk_vio_init_t mawk_vio_orig_init = {
	mawk_vio_orig_open,
	mawk_vio_orig_open_pipe,
	mawk_vio_orig_exec_shell,
	1
};


void mawk_vio_orig_setup_stdio(mawk_state_t * MAWK, int enable_stdin, int enable_stdout, int enable_stderr)
{
	mawk_vio_orig_t *vf;

	if (enable_stdin) {
		vf = (mawk_vio_orig_t *)mawk_vio_orig_open(MAWK, "/dev/stdin", MAWK_VIO_I);
		mawk_file_register(MAWK, "/dev/stdin", F_IN, (mawk_vio_t *)vf);
		mawk_vio_orig_mark_no_close(MAWK, (mawk_vio_t *)vf);
	}

	if (enable_stdout) {
		vf = vio_alloc_(MAWK);
		vf->f = stdout;
		mawk_file_register_nofin(MAWK, "/dev/stdout", F_TRUNC, (mawk_vio_t *)vf);
		mawk_vio_orig_mark_no_close(MAWK, (mawk_vio_t *)vf);
		/* don't buffer stdout in interactive mode */
		if (MAWK->interactive_flag)
				mawk_vio_orig_setbuf(MAWK, (mawk_vio_t *)vf, 0);
	}

	if (enable_stderr) {
		vf = vio_alloc_(MAWK);
		vf->f = stderr;
		mawk_file_register_nofin(MAWK, "/dev/stderr", F_TRUNC, (mawk_vio_t *)vf);
		mawk_vio_orig_mark_no_close(MAWK, (mawk_vio_t *)vf);
		/* never buffer stderr */
		mawk_vio_orig_setbuf(MAWK, (mawk_vio_t *)vf, 0);
	}
}
