/*
** Copyright 1998 - 2023 S. Varshavchik.
** See COPYING for distribution information.
*/

#include	"funcs.h"
#include	"message.h"
#include	"messageinfo.h"
#include	"alarm.h"
#include	"rfc822/rfc822.h"
#include	"pipefds.h"
#include	"formatmbox.h"
#include	"xconfig.h"
#include	"varlist.h"
#include	"maildrop.h"
#include	"config.h"
#include	<sys/types.h>
#if HAVE_SYS_STAT_H
#include	<sys/stat.h>
#endif
#include	"mywait.h"
#include	"mytime.h"
#include	<signal.h>
#include	<errno.h>
#if	HAVE_STRINGS_H
#include	<strings.h>
#endif
#include	<iostream>

///////////////////////////////////////////////////////////////////////////
//
//  Filter message through an external command.
//
///////////////////////////////////////////////////////////////////////////

int xfilter(const char *, int);

int filter(const char *filtercmd)
{
	try
	{
	int	rc=xfilter(filtercmd, 0);

		if (rc == 0)
		{
		Message	*ptr=maildrop.savemsgptr;

			maildrop.savemsgptr=maildrop.msgptr;
			maildrop.msgptr=ptr;
			maildrop.msgptr->setmsgsize();
			maildrop.msginfo.filtered();
		}
		maildrop.savemsgptr->Init();
		return(rc);
	}
	catch (...)
	{
		maildrop.savemsgptr->Init();
		throw;
	}
}

int xfilter(const char *filtercmd, int ignorewerr)
{
FormatMbox	format_mbox;
char	buffer[1024];

	maildrop.savemsgptr->Init();

//	if (format_mbox.HasMsg())	return (0);	// Empty
	(void)format_mbox.HasMsg();

std::string	cmdbuf;

	cmdbuf= filtercmd;

PipeFds	pipe0, pipe1;

	if (pipe0.Pipe() < 0 || pipe1.Pipe() < 0)
		throw "Cannot create pipe.";

pid_t	pid=fork();

	if (pid < 0)
		throw "Cannot fork.";

	if (pid == 0)
	{
		try
		{
			pipe0.close1();
			pipe1.close0();
			dup2(pipe0.fds[0], 0);
			pipe0.close0();
			dup2(pipe1.fds[1], 1);
			pipe1.close1();
			subshell(filtercmd);
		}
		catch (const char *p)
		{
			if (write(2, p, strlen(p)) < 0 ||
			    write(2, "\n", 1) < 0)
				; /* ignore */
			_exit(100);
		}
		catch (char *p)
		{
			if (write(2, p, strlen(p)) < 0 ||
			    write(2, "\n", 1) < 0)
				; /* ignore */
			_exit(100);
		}
		catch (...)
		{
			_exit(101);
		}
	}

	pipe0.close0();
	pipe1.close1();

	format_mbox.Init(0);

//////////////////////////////////////////////////////////////////////////
//
// Write message contents to the subprocess.  Simultaneously read process's
// output, and save it.  This is done simultaneously, via select() call.
//
//////////////////////////////////////////////////////////////////////////

const	char	*writebufptr=0;
int		writebuflen=0;

fd_set	readfd, writefd;

	FD_ZERO(&readfd);
	FD_ZERO(&writefd);

int	errflag=0;
int	maxfd=pipe1.fds[0];

	if (pipe0.fds[1] > maxfd)
		maxfd=pipe0.fds[1];
	++maxfd;

	fcntl(pipe1.fds[0], F_SETFL, O_NDELAY);
	fcntl(pipe0.fds[1], F_SETFL, O_NDELAY);

	rfc2045::entity_parser<false> parser;
	Message::init_partial partial{*maildrop.savemsgptr, parser};

	for (;;)
	{
		FD_SET(pipe1.fds[0], &readfd);
		if (pipe0.fds[1] >= 0)
			FD_SET(pipe0.fds[1], &writefd);

		if (!writebuflen && pipe0.fds[1] >= 0)
		{		// Need more to write.
		std::string	*p=format_mbox.NextLine();

			if (!p)
			{
				FD_CLR(pipe0.fds[1], &writefd);
				pipe0.close1();	// End of msg.
			}
			else
			{
				writebufptr= p->c_str();
				writebuflen= p->size();
			}
		}

	int	n=select(maxfd, &readfd, &writefd, NULL, NULL);

		if (n < 0)
		{
			if (errno != EINTR)
				throw "maildrop: select() error.";
			continue;
		}

		if (pipe0.fds[1] >= 0 && FD_ISSET(pipe0.fds[1], &writefd))
		{
		int	n= ::write(pipe0.fds[1], writebufptr, writebuflen);

			if (n < 0)
			{
				if (errno != EINTR
#ifdef EAGAIN
				    && errno != EAGAIN
#endif
#ifdef EWOULDBLOCK
				    && errno != EWOULDBLOCK
#endif

				    )	// Perfectly OK
				{
					FD_CLR(pipe0.fds[1], &writefd);
					pipe0.close1();
					writebuflen=0;
					if (!ignorewerr)
					{
						std::cerr << "maildrop: error writing to filter.\n"
							  << std::flush;
						errflag=1;
						break;
					}
				}
			}
			else
			{
				writebufptr += n;
				writebuflen -= n;
			}
		}

		if (FD_ISSET(pipe1.fds[0], &readfd))
		{
		int	readbuflen=::read(pipe1.fds[0], buffer, sizeof(buffer));

			if (readbuflen < 0)
			{
				if (errno != EINTR
#ifdef EAGAIN
				    && errno != EAGAIN
#endif
#ifdef EWOULDBLOCK
				    && errno != EWOULDBLOCK
#endif
				    )
				{
					std::cerr << "maildrop: error reading from filter.\n"
						  << std::flush;
					errflag=1;
					break;
				}
				continue;
			}
			if (readbuflen == 0)	// FILTERED
			{
				if ( (pipe0.fds[1] >= 0) && (!ignorewerr) )
				{
					std::cerr << "maildrop: filter terminated prematurely.\n"
						  << std::flush;
					errflag=1;	// Not everything
					break;		// was written, though.
				}
				break;
			}

			partial(buffer, readbuflen);
		}
	}

	pipe0.close1();
	pipe1.close0();
	partial.done();

	int	wait_stat;

	while (Alarm::wait_child(&wait_stat) != pid)
		;
	wait_stat= WIFEXITED(wait_stat) ? WEXITSTATUS(wait_stat):-1;

	if (!wait_stat && errflag)
		wait_stat= -1;

	{
		std::string	val;

		add_integer(val, wait_stat);
		SetVar("RETURNCODE", val);
	}

	if (wait_stat)
		return (-1);
	return (0);
}

void executesystem(const char *cmd)
{
	int devnull=open("/dev/null", O_RDONLY);
	pid_t	pid;

	if (devnull < 0)
		throw "Cannot open /dev/null";

	pid=fork();
	if (pid < 0)
	{
		close(devnull);
		throw "Cannot fork.";
	}

	if (pid == 0)
	{
		try
		{
			dup2(devnull, 0);
			close(devnull);
			subshell(cmd);
		}
		catch (const char *p)
		{
			if (write(2, p, strlen(p)) < 0 ||
			    write(2, "\n", 1) < 0)
				; /* ignore */
			_exit(100);
		}
		catch (char *p)
		{
			if (write(2, p, strlen(p)) < 0 ||
			    write(2, "\n", 1) < 0)
				; /* ignore */
			_exit(100);
		}
		catch (...)
		{
			_exit(101);
		}
	}
	close(devnull);

	int	wait_stat;

	while (Alarm::wait_child(&wait_stat) != pid)
		;
	wait_stat= WIFEXITED(wait_stat) ? WEXITSTATUS(wait_stat):-1;

	{
		std::string	val;

		add_integer(val, wait_stat);
		SetVar("RETURNCODE", val);
	}
}
