aboutsummaryrefslogtreecommitdiffhomepage
path: root/postfork.cpp
diff options
context:
space:
mode:
authorGravatar ridiculousfish <corydoras@ridiculousfish.com>2012-02-28 15:11:46 -0800
committerGravatar ridiculousfish <corydoras@ridiculousfish.com>2012-02-29 16:14:51 -0800
commit909d24cde6acf87587370355d7a9cbc7dc18435c (patch)
treee859cfa7d3d39998affe6ce42bb44d47977a804b /postfork.cpp
parent4e912ef83df234d34fff4156cd2bfb165e113674 (diff)
More work on improving interaction between fork and pthreads. Added null_terminated_array_t class.
Diffstat (limited to 'postfork.cpp')
-rw-r--r--postfork.cpp314
1 files changed, 314 insertions, 0 deletions
diff --git a/postfork.cpp b/postfork.cpp
index 2ea88b23..0be31562 100644
--- a/postfork.cpp
+++ b/postfork.cpp
@@ -3,7 +3,34 @@
Functions that we may safely call after fork().
*/
+#include <fcntl.h>
+#include "signal.h"
#include "postfork.h"
+#include "iothread.h"
+#include "exec.h"
+
+
+/** The number of times to try to call fork() before giving up */
+#define FORK_LAPS 5
+
+/** The number of nanoseconds to sleep between attempts to call fork() */
+#define FORK_SLEEP_TIME 1000000
+
+/** Base open mode to pass to calls to open */
+#define OPEN_MASK 0666
+
+/** fork error message */
+#define FORK_ERROR _( L"Could not create child process - exiting" )
+
+/** file redirection clobbering error message */
+#define NOCLOB_ERROR _( L"The file '%ls' already exists" )
+
+/** file redirection error message */
+#define FILE_ERROR _( L"An error occurred while redirecting file '%ls'" )
+
+/** file descriptor redirection error message */
+#define FD_ERROR _( L"An error occurred while redirecting file descriptor %d" )
+
/**
This function should be called by both the parent process and the
@@ -17,6 +44,8 @@
Returns 0 on sucess, -1 on failiure.
*/
+
+// PCA These calls to debug are rather sketchy because they may allocate memory. Fortunately they only occur if an error occurs.
int set_child_group( job_t *j, process_t *p, int print_errors )
{
int res = 0;
@@ -66,3 +95,288 @@ int set_child_group( job_t *j, process_t *p, int print_errors )
return res;
}
+/** Make sure the fd used by this redirection is not used by i.e. a pipe. */
+static void free_fd( io_data_t *io, int fd )
+{
+ if( !io )
+ return;
+
+ if( ( io->io_mode == IO_PIPE ) || ( io->io_mode == IO_BUFFER ) )
+ {
+ int i;
+ for( i=0; i<2; i++ )
+ {
+ if(io->param1.pipe_fd[i] == fd )
+ {
+ while(1)
+ {
+ if( (io->param1.pipe_fd[i] = dup(fd)) == -1)
+ {
+ if( errno != EINTR )
+ {
+ debug( 1,
+ FD_ERROR,
+ fd );
+ wperror( L"dup" );
+ FATAL_EXIT();
+ }
+ }
+ else
+ {
+ break;
+ }
+ }
+ }
+ }
+ }
+ free_fd( io->next, fd );
+}
+
+
+/**
+ Set up a childs io redirections. Should only be called by
+ setup_child_process(). Does the following: First it closes any open
+ file descriptors not related to the child by calling
+ close_unused_internal_pipes() and closing the universal variable
+ server file descriptor. It then goes on to perform all the
+ redirections described by \c io.
+
+ \param io the list of IO redirections for the child
+
+ \return 0 on sucess, -1 on failiure
+*/
+static int handle_child_io( io_data_t *io )
+{
+
+ close_unused_internal_pipes( io );
+
+ for( ; io; io=io->next )
+ {
+ int tmp;
+
+ if( io->io_mode == IO_FD && io->fd == io->param1.old_fd )
+ {
+ continue;
+ }
+
+ if( io->fd > 2 )
+ {
+ /*
+ Make sure the fd used by this redirection is not used by e.g. a pipe.
+ */
+ free_fd( io, io->fd );
+ }
+
+ switch( io->io_mode )
+ {
+ case IO_CLOSE:
+ {
+ if( close(io->fd) )
+ {
+ debug( 0, _(L"Failed to close file descriptor %d"), io->fd );
+ wperror( L"close" );
+ }
+ break;
+ }
+
+ case IO_FILE:
+ {
+ if( (tmp=wopen( io->filename,
+ io->param2.flags, OPEN_MASK ) )==-1 )
+ {
+ if( ( io->param2.flags & O_EXCL ) &&
+ ( errno ==EEXIST ) )
+ {
+ debug( 1,
+ NOCLOB_ERROR,
+ io->filename.c_str() );
+ }
+ else
+ {
+ debug( 1,
+ FILE_ERROR,
+ io->filename.c_str() );
+
+ wperror( L"open" );
+ }
+
+ return -1;
+ }
+ else if( tmp != io->fd)
+ {
+ /*
+ This call will sometimes fail, but that is ok,
+ this is just a precausion.
+ */
+ close(io->fd);
+
+ if(dup2( tmp, io->fd ) == -1 )
+ {
+ debug( 1,
+ FD_ERROR,
+ io->fd );
+ wperror( L"dup2" );
+ return -1;
+ }
+ exec_close( tmp );
+ }
+ break;
+ }
+
+ case IO_FD:
+ {
+ /*
+ This call will sometimes fail, but that is ok,
+ this is just a precausion.
+ */
+ close(io->fd);
+
+ if( dup2( io->param1.old_fd, io->fd ) == -1 )
+ {
+ debug( 1,
+ FD_ERROR,
+ io->fd );
+ wperror( L"dup2" );
+ return -1;
+ }
+ break;
+ }
+
+ case IO_BUFFER:
+ case IO_PIPE:
+ {
+ int write_pipe;
+
+ write_pipe = !io->is_input;
+/*
+ debug( 0,
+ L"%ls %ls on fd %d (%d %d)",
+ write_pipe?L"write":L"read",
+ (io->io_mode == IO_BUFFER)?L"buffer":L"pipe",
+ io->fd,
+ io->param1.pipe_fd[0],
+ io->param1.pipe_fd[1]);
+*/
+ if( dup2( io->param1.pipe_fd[write_pipe], io->fd ) != io->fd )
+ {
+ debug( 1, PIPE_ERROR );
+ wperror( L"dup2" );
+ return -1;
+ }
+
+ if( write_pipe )
+ {
+ exec_close( io->param1.pipe_fd[0]);
+ exec_close( io->param1.pipe_fd[1]);
+ }
+ else
+ {
+ exec_close( io->param1.pipe_fd[0] );
+ }
+ break;
+ }
+
+ }
+ }
+
+ return 0;
+
+}
+
+
+/**
+ Initialize a new child process. This should be called right away
+ after forking in the child process. If job control is enabled for
+ this job, the process is put in the process group of the job, all
+ signal handlers are reset, signals are unblocked (this function may
+ only be called inside the exec function, which blocks all signals),
+ and all IO redirections and other file descriptor actions are
+ performed.
+
+ \param j the job to set up the IO for
+ \param p the child process to set up
+
+ \return 0 on sucess, -1 on failiure. When this function returns,
+ signals are always unblocked. On failiure, signal handlers, io
+ redirections and process group of the process is undefined.
+*/
+int setup_child_process( job_t *j, process_t *p )
+{
+ int res=0;
+
+ if( p )
+ {
+ res = set_child_group( j, p, 1 );
+ }
+
+ if( !res )
+ {
+ res = handle_child_io( j->io );
+ if( p != 0 && res )
+ {
+ exit_without_destructors( 1 );
+ }
+ }
+
+ /* Set the handling for job control signals back to the default. */
+ if( !res )
+ {
+ signal_reset_handlers();
+ }
+
+ /* Remove all signal blocks */
+ signal_unblock();
+
+ return res;
+
+}
+
+/**
+ This function is a wrapper around fork. If the fork calls fails
+ with EAGAIN, it is retried FORK_LAPS times, with a very slight
+ delay between each lap. If fork fails even then, the process will
+ exit with an error message.
+*/
+pid_t execute_fork(bool wait_for_threads_to_die)
+{
+ ASSERT_IS_MAIN_THREAD();
+
+ if (wait_for_threads_to_die) {
+ /* Make sure we have no outstanding threads before we fork. This is a pretty sketchy thing to do here, both because exec.cpp shouldn't have to know about iothreads, and because the completion handlers may do unexpected things. */
+ iothread_drain_all();
+ }
+
+ pid_t pid;
+ struct timespec pollint;
+ int i;
+
+ for( i=0; i<FORK_LAPS; i++ )
+ {
+ pid = fork();
+ if( pid >= 0)
+ {
+ return pid;
+ }
+
+ if( errno != EAGAIN )
+ {
+ break;
+ }
+
+ pollint.tv_sec = 0;
+ pollint.tv_nsec = FORK_SLEEP_TIME;
+
+ /*
+ Don't sleep on the final lap - sleeping might change the
+ value of errno, which will break the error reporting below.
+ */
+ if( i != FORK_LAPS-1 )
+ {
+ nanosleep( &pollint, NULL );
+ }
+ }
+
+ debug( 0, FORK_ERROR );
+ wperror (L"fork");
+ FATAL_EXIT();
+}