openldap/servers/slapd/back-shell/fork.c
2002-07-27 07:37:04 +00:00

370 lines
8.2 KiB
C

/* fork.c - fork and exec a process, connecting stdin/out w/pipes */
/* $OpenLDAP$ */
/*
* 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 );
}