/* fork.c - fork and exec a process, connecting stdin/out w/pipes */
/* $OpenLDAP: pkg/ldap/servers/slapd/back-shell/fork.c,v 1.9.2.2 2002/07/28 19:14:00 kurt Exp $ */
/*
 * Copyright 1998-2002 The OpenLDAP Foundation, All Rights Reserved.
 * COPYING RESTRICTIONS APPLY, see COPYRIGHT file
 */

#include "portable.h"

#include <stdio.h>

#include <ac/errno.h>
#include <ac/string.h>
#include <ac/socket.h>
#include <ac/unistd.h>

#include "slap.h"
#include "shell.h"

#ifdef SHELL_SURROGATE_PARENT

#include <sys/uio.h>

/* Use several socketpairs to the surrogate parent, because   *
 * a single communication channel to it could be a bottleneck */
ldap_pvt_thread_mutex_t shell_surrogate_fd_mutex[2];
int                     shell_surrogate_fd[2] = { -1, -1 };
/* Index to shell_surrogate_fd, and its mutex */
ldap_pvt_thread_mutex_t shell_surrogate_index_mutex;
static int              shell_surrogate_index = 1;

pid_t                   shell_surrogate_pid = -1;

#define  nread( fd, buf, len ) n_rw( 0, fd, buf, len )
#define nwrite( fd, buf, len ) n_rw( 1, fd, buf, len )

static int
n_rw(
	int do_write,
	int fd,
	void *buf,
	int len
)
{
	int ret = 0, i;
	while( len ) {
		for(;;) {
			i = (do_write
			     ? write( fd, buf, len )
			     :  read( fd, buf, len ));
			if( i < 0 ) {
				if( errno == EINTR )
					continue;
				if( ret == 0 )
					ret = -1;
			}
			break;
		}
		if( i <= 0 )
			break;
		ret += i;
		buf = (char *)buf + i;
		len -= i;
	}
	return ret;
}

void
make_surrogate_parent( void )
{
	int pair[2][2], io[2], i, j, p, argc;
	ber_len_t len, buflen, offset;
	char *buf, **argv;
	pid_t pid;

	if( socketpair( AF_LOCAL, SOCK_STREAM, 0, pair[0] ) < 0 ||
	    socketpair( AF_LOCAL, SOCK_STREAM, 0, pair[1] ) < 0   ) {
		Debug( LDAP_DEBUG_ANY, "socketpair failed\n", 0, 0, 0 );
		exit( EXIT_FAILURE );
	}
	fflush( NULL );
	switch( fork() ) {
	case -1:
		Debug( LDAP_DEBUG_ANY, "fork failed\n", 0, 0, 0 );
		exit( EXIT_FAILURE );
	case 0:
		break;
	default:
		shell_surrogate_fd[0] = pair[0][0];
		shell_surrogate_fd[1] = pair[1][0];
		close( pair[0][1] );
		close( pair[1][1] );
		return;
	}

	/* Close unused file descriptors */
	for( i = 3, j = 32; j && i < 1024; i++ )
		if( i != pair[0][1] && i != pair[1][1] && close( i ) < 0 )
			--j;
		else if( j < 32 )
			j = 32;

	/* Surrogate parent running */

	buflen = 0;
	buf = NULL;
	argc = 0;
	argv = NULL;
	p = 0;

	for(;;) {
		/* Read file descriptors io[] from socket */ 
		static char dummy;
		static struct iovec iov = { &dummy, 1 };
		struct msghdr msg;
# ifdef CMSG_SPACE
		union {
			struct cmsghdr cm;
			char control[CMSG_SPACE(sizeof(io))];
		} control_un;
		struct cmsghdr *cmptr;
# endif

		/* clear msghdr */
		memset( &msg, 0, sizeof msg );

# ifdef CMSG_SPACE
		msg.msg_control = control_un.control;
		msg.msg_controllen = sizeof(control_un.control);
# else
		msg.msg_accrights = (caddr_t) io;
		msg.msg_accrightslen = sizeof(io);
# endif

		msg.msg_name = NULL;
		msg.msg_namelen = 0;
		msg.msg_iov = &iov;
		msg.msg_iovlen = 1;

		switch( recvmsg( pair[p][1], &msg, MSG_WAITALL ) ) {
		case -1:
			if( errno == EINTR )
				continue;
			_exit( EXIT_FAILURE );
		case 0:
			_exit( EXIT_SUCCESS );
		}
# ifdef CMSG_SPACE
		if( (cmptr = CMSG_FIRSTHDR(&msg)) == NULL   ||
		    cmptr->cmsg_len != CMSG_LEN(sizeof(io)) ||
		    cmptr->cmsg_level != SOL_SOCKET         ||
		    cmptr->cmsg_type != SCM_RIGHTS            ) {
			fputs( "bad descriptor message received\n", stderr );
			exit( EXIT_FAILURE );
		}
		memcpy( io, CMSG_DATA( cmptr ), sizeof(io) );
# else
		if( msg.msg_accrightslen != sizeof(io) ) {
			fputs( "bad descriptor message received\n", stderr );
			exit( EXIT_FAILURE );
		}
# endif

		/* Read length of arguments and then arguments from socket */
		if( nread( pair[p][1], &len, sizeof(len) ) != sizeof(len) ) {
			fputs( "bad descriptor message received\n", stderr );
			exit( EXIT_FAILURE );
		}
		if( buflen < len ) {
			buf = realloc( buf, buflen = len );
			if( buf == NULL ) {
				fputs( "realloc failed\n", stderr );
				exit( EXIT_FAILURE );
			}
		}
		if( nread( pair[p][1], buf, len ) != len ) {
			fputs( "bad descriptor message received\n", stderr );
			exit( EXIT_FAILURE );
		}
		i = 0;
		offset = 0;
		while( offset < len ) {
			if( i >= argc-1 ) {
				argc += i + 10;
				argv = realloc( argv, argc * sizeof(*argv) );
				if( argv == NULL ) {
					fputs( "realloc failed\n", stderr );
					exit( EXIT_FAILURE );
				}
			}
			argv[i++] = buf + offset;
			offset += strlen( buf + offset ) + 1;
		}
		argv[i] = NULL;

		/* Run program */
		pid = fork();
		switch( pid )
		{
		case 0:		/* child */
			if( dup2( io[0], 0 ) == -1 || dup2( io[1], 1 ) == -1 ) {
				fputs( "dup2 failed\n", stderr );
				exit( EXIT_FAILURE );
			}
			close( io[0] );
			close( io[1] );
			close( pair[0][1] );
			close( pair[1][1] );
			execv( argv[0], argv );

			fputs( "execv failed\n", stderr );
			exit( EXIT_FAILURE );

		case -1:	/* trouble */
			fputs( "fork failed\n", stderr );
			break;

		default:	/* parent */
			close( io[0] );
			close( io[1] );
			break;
		}
		if( nwrite( pair[p][1], &pid,
		            sizeof(pid_t) ) != sizeof(pid_t) )  {
			fputs( "could not send pid\n", stderr );
			exit( EXIT_FAILURE );
		}
		p ^= 1;
	}
}
#endif /* SHELL_SURROGATE_PARENT */

pid_t
forkandexec(
    Cmd_info  args,
    FILE	**rfp,
    FILE	**wfp
)
{
	int	p2c[2] = { -1, -1 }, c2p[2];
	pid_t	pid;

	if ( pipe( p2c ) != 0 || pipe( c2p ) != 0 ) {
		Debug( LDAP_DEBUG_ANY, "pipe failed\n", 0, 0, 0 );
		close( p2c[0] );
		close( p2c[1] );
		return( -1 );
	}

	/*
	 * what we're trying to set up looks like this:
	 *	parent *wfp -> p2c[1] | p2c[0] -> stdin child
	 *	parent *rfp <- c2p[0] | c2p[1] <- stdout child
	 */

#ifdef SHELL_SURROGATE_PARENT

	{
		int io[2] = { p2c[0], c2p[1] }, i, c;
		static char dummy = '\0';
		static struct iovec iov = { &dummy, 1 };
		struct msghdr msg;
# ifdef CMSG_SPACE
		union {
			struct cmsghdr cm;
			char control[CMSG_SPACE(sizeof(io))];
		} control_un;
		struct cmsghdr *cmptr;
# endif

		/* clear msghdr */
		memset( &msg, 0, sizeof msg );

# ifdef CMSG_SPACE
		msg.msg_control = control_un.control;
		msg.msg_controllen = sizeof(control_un.control);
		cmptr = CMSG_FIRSTHDR(&msg);
		cmptr->cmsg_len = CMSG_LEN(sizeof(io));
		cmptr->cmsg_level = SOL_SOCKET;
		cmptr->cmsg_type = SCM_RIGHTS;
		memcpy( CMSG_DATA(cmptr), io, sizeof(io) );
# else
		msg.msg_accrights = (caddr_t) io;
		msg.msg_accrightslen = sizeof(io);
# endif

		msg.msg_name = NULL;
		msg.msg_namelen = 0;
		msg.msg_iov = &iov;
		msg.msg_iovlen = 1;

		ldap_pvt_thread_mutex_lock( &shell_surrogate_index_mutex );
		i = shell_surrogate_index ^= 1;
		ldap_pvt_thread_mutex_unlock( &shell_surrogate_index_mutex );
		ldap_pvt_thread_mutex_lock( &shell_surrogate_fd_mutex[i] );
		c = (sendmsg( shell_surrogate_fd[i], &msg, 0 ) == 1 &&
			 nwrite( shell_surrogate_fd[i], &args.bv_len,
			         sizeof(args.bv_len) ) == sizeof(args.bv_len) &&
			 nwrite( shell_surrogate_fd[i], args.bv_val,
			         args.bv_len ) == args.bv_len &&
			 nread( shell_surrogate_fd[i], &pid,
			        sizeof(pid) ) == sizeof(pid));
		ldap_pvt_thread_mutex_unlock( &shell_surrogate_fd_mutex[i] );
		close( p2c[0] );
		close( c2p[1] );
		if ( !c ) {
			Debug( LDAP_DEBUG_ANY, "process creation failed\n", 0, 0, 0 );
			close( p2c[1] );
			close( c2p[0] );
			close( shell_surrogate_fd[0] );
			close( shell_surrogate_fd[1] );
			shell_surrogate_fd[0] =
				shell_surrogate_fd[1] = -1;
			return( -1 );
		}
	}

#else /* !SHELL_SURROGATE_PARENT */

	fflush( NULL );
# ifdef HAVE_THR
	pid = fork1();
# else
	pid = fork();
# endif
	if ( pid == 0 ) {		/* child */
		/*
		 * child could deadlock here due to resources locked
		 * by our parent
		 *
		 * If so, configure --without-threads.
		 */
		if ( dup2( p2c[0], 0 ) == -1 || dup2( c2p[1], 1 ) == -1 ) {
			Debug( LDAP_DEBUG_ANY, "dup2 failed\n", 0, 0, 0 );
			exit( EXIT_FAILURE );
		}
	}
	close( p2c[0] );
	close( c2p[1] );
	if ( pid <= 0 ) {
		close( p2c[1] );
		close( c2p[0] );
	}
	switch ( pid ) {
	case 0:
		execv( args[0], args );

		Debug( LDAP_DEBUG_ANY, "execv failed\n", 0, 0, 0 );
		exit( EXIT_FAILURE );

	case -1:	/* trouble */
		Debug( LDAP_DEBUG_ANY, "fork failed\n", 0, 0, 0 );
		return( -1 );
	}

#endif /* SHELL_SURROGATE_PARENT */

	/* parent */
	if ( (*rfp = fdopen( c2p[0], "r" )) == NULL || (*wfp = fdopen( p2c[1],
	    "w" )) == NULL ) {
		Debug( LDAP_DEBUG_ANY, "fdopen failed\n", 0, 0, 0 );
		close( c2p[0] );
		close( p2c[1] );

		return( -1 );
	}

	return( pid );
}
