#ifndef _DW_DFORK_C
#define _DW_DFORK_C

/*--------------------------------------------------------------------------
 *  Copyright (c) 2001-2004 Igor Daniloff. 
 *	All rights reserved.
 *
 * By using this file, you agree to the terms and conditions set
 * forth in the LICENSE file which can be found at this directory.
 *------------------------------------------------------------------------*/

#ifndef lint
static const char rcsid[] = "$Id: dw_dfork.c,v 1.18 2006/07/25 15:32:08 andreyc Exp $";
#endif

#if defined(__UNIX_DW)

#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#if !defined(EMX_DW)
#include <sysexits.h>
#else /* EMX */
#include <process.h>
#endif /* EMX */
#include <syslog.h>
#include <fcntl.h>
#include <errno.h>
#include "dw_dfork.h"
#include "dwc_lib.h"
#include "dwc_log.h"

#if defined(DW_SAMBA) || defined(DW_COURIER) || ((defined(Linux_DW) || defined(EMX_DW)) && (defined(DW_CGP) || defined(DW_MIO)))
/* -------------------------------------------------------------------- */
dwc_pid_t dw_dfork( dw_routine_t routine, dw_routine_t fail_routine, dw_routine_arg_t argument )
{
    sigset_t          sigmask;
    struct sigaction        sigact,sigold;
    dwc_pid_t         pid;

    if( !routine )
    {
        printLib( DW_LL_ERROR, "starter: null routine was passed");
        if( fail_routine )
           (*fail_routine)( argument );
        return -1;
    }

    sigact.sa_flags = sigold.sa_flags = 0;
    sigact.sa_handler = SIG_DFL;
    sigemptyset(&sigact.sa_mask);
    sigemptyset(&sigold.sa_mask);
    if( sigaction( SIGCHLD, &sigact, &sigold ) < 0 )
    {
        printLib( DW_LL_ERROR, "dwlib: set signal(%d) handler failed - %s", SIGCHLD, stdlib_error(errno) );
        return FALSE;
    }

    pid = fork();
    if( pid == 0 )
    {
        pid = fork();
        if( pid < 0 )
        {
            printLib( DW_LL_ERROR, "starter: cannot fork twice for new worker - %s", stdlib_error(errno) );
            if( fail_routine )
                (*fail_routine)( argument );
            exit(EX_OSERR);
        }
        else if( pid > 0 )
        {
            exit(0);
        }
        /* child - it's a new worker */
        sigemptyset( &sigmask );
        sigfillset( &sigmask );
        if( sigprocmask( SIG_BLOCK, &sigmask, NULL ) < 0 )
        {
            printLib( DW_LL_ERROR, "starter: cannot block signals for new worker" );
            if( fail_routine )
                (*fail_routine)( argument );
            exit(EX_OSERR);
        }
        printLib( DW_LL_DEBUG, "starter: run new worker" );
        (*routine)( argument );
        exit(0);
    }
    else if( pid != -1 )
    {
        if( dw_wait( pid, TRUE, "runner" ) < 0 )
        {
            printLib( DW_LL_ERROR, "starter: cannot wait new worker(parent)");
            if( fail_routine )
                (*fail_routine)( argument );
            pid=-1;
        }        
    }
    else
    {
        printLib( DW_LL_ERROR, "starter: cannot fork for new worker - %s", stdlib_error(errno) );
        if( fail_routine )
            (*fail_routine)( argument );
    }
    if( sigaction( SIGCHLD, &sigold, NULL ) < 0 )
    {
        printLib( DW_LL_ERROR, "dwlib: set signal(%d) handler failed - %s", SIGCHLD, stdlib_error(errno) );
        pid=-1;
    }
    return pid;
}    
#endif /* SAMBA or COURIER or ((Linux or EMX) and (CGP or MIO)) */

#if !defined(DW_SAMBA)
#if !defined(EMX_DW)
/* ---------------------------------------------------------------------- */
dwc_syserr_t dw_popen( dwc_path_t path, dwc_string_t const pipe_argv[], dwc_fd_t fd[] )
{
    int pid;
    int stdin_pipe[2] = { -1, -1 };
#if defined(DW_QMAIL)    
    int stdout_pipe[2] = { -1, -1 };
#endif /* QMAIL */    

    EXIT_IF_2PARAM( !path, !pipe_argv, "starter:", return -1 );
    if( pipe( stdin_pipe ) < 0 )
    {
        printLib( DW_LL_ERROR,"starter: cannot create pipe for submitter(%s) - %s", path, stdlib_error(errno) );
        return -1;
    }
    fd[0] = stdin_pipe[1];
#if defined(DW_QMAIL)
    if( pipe( stdout_pipe ) < 0 )
    {
        printLib( DW_LL_ERROR,"starter: cannot create pipe for submitter - %s", stdlib_error(errno) );
        goto error_exit;
    }
    fd[1] = stdout_pipe[1];
#else /* not QMAIL */
    fd[1] = -1;
#endif /* QMAIL */    
    pid = fork();
    if( pid == 0 )
    {
#if defined(DW_SENDMAIL)
        pid = fork();
        if( pid < 0 )
        {
            printLib( DW_LL_ERROR,"starter: cannot fork for submitting");
            exit(EX_OSERR);
        }
        else if( pid > 0 )
        {
            /* now INIT parent for real submitter */
            exit( 0 );
        }
#endif /* SENDMAIL */    
        /* asv: have a problem in Linux with next line:
           all submitter wait on signal in __pthread_lock() in vsyslog()
        printLib( DW_LL_DEBUG, "starter: submitter run with pid = %d", getpid() );
         */
        close( stdin_pipe[1] );
#if defined(DW_QMAIL)
        close( stdout_pipe[1] );
#endif /* QMAIL */    
        close( STDIN_FILENO ); 
        if( dup2( stdin_pipe[0], STDIN_FILENO ) < 0 )
        {
            printLib( DW_LL_ERROR,"starter: cannot bind pipe with stdin");
            exit(EX_OSERR);
        }
#if defined(DW_QMAIL)
        if( dup2( stdout_pipe[0], STDOUT_FILENO ) < 0 )
        {
            printLib( DW_LL_ERROR,"starter: cannot bind pipe with stdout");
            exit(EX_OSERR);
        }
#endif /* QMAIL */        
        execv(path, pipe_argv);        
        printLib( DW_LL_ERROR, "starter: cannot execv() for %s - (%d) %s", path, errno, stdlib_error(errno) );
        exit(EX_OSERR);
    }
    else if( pid != -1 )
    {
        close( stdin_pipe[0] );
#if defined(DW_QMAIL)
        close( stdout_pipe[0] );
#endif /* QMAIL */    
#if defined(DW_SENDMAIL)
        if( dw_wait( pid, TRUE, "submitter" ) < 0 )
        {
            printLib( DW_LL_ERROR,"starter: cannot wait for submitter" );
            return -1;
        }        
#endif /* SENDMAIL */
        return pid;
    }
    else
    {
        printLib( DW_LL_ERROR,"starter: cannot fork for execute %s", path );
    }
#if defined(DW_QMAIL)
  error_exit:
#endif /* QMAIL */    
    if( stdin_pipe[0] >= 0 )
        close( stdin_pipe[0] );
    if( stdin_pipe[1] >= 0 )
        close( stdin_pipe[1] );
#if defined(DW_QMAIL)
    if( stdout_pipe[0] >= 0 )
        close( stdout_pipe[0] );
    if( stdout_pipe[1] >= 0 )
        close( stdout_pipe[1] );
#endif /* QMAIL */    
    return -1;
}

#else /* EMX */

/* ---------------------------------------------------------------------- */
dwc_syserr_t dw_popen( dwc_path_t path, dwc_string_t const pipe_argv[], dwc_fd_t fd[] )
{
    int pid;
    int stdin_pipe[2] = { -1, -1 };

    EXIT_IF_2PARAM( !path, !pipe_argv, "starter:", return -1 );
    if( pipe( stdin_pipe ) < 0 )
    {
        printLib( DW_LL_ERROR,"starter: cannot create pipe for submitter(%s) - %s", path, stdlib_error(errno) );
        return -1;
    }
    fd[0] = stdin_pipe[1];
    fd[1] = -1;
    close( STDIN_FILENO ); 
    if( dup2( stdin_pipe[0], STDIN_FILENO ) < 0 )
    {
        printLib( DW_LL_ERROR,"starter: cannot bind pipe with stdin");
        exit(EX_OSERR);
    }
#ifndef P_NOWAIT
#define P_NOWAIT  1
#endif /* P_NOWAIT */
    close(stdin_pipe[0]);
    fcntl(stdin_pipe[1], F_SETFD, FD_CLOEXEC);
    pid = spawnv(P_NOWAIT, path, pipe_argv);
    if( pid == -1 )
    {
        printLib( DW_LL_ERROR,"starter: cannot spawnv() for %s - (%d) %s", path, errno, stdlib_error(errno));
        exit(EX_OSERR);
    }
    printLib( DW_LL_DEBUG, "starter: submitter run with pid = %d", pid );
    return pid;
}
#endif /* not EMX */
#endif /* DW_SAMBA */

/* ---------------------------------------------------------------------- */
dwc_syserr_t dw_wait( dwc_pid_t pid, dwc_bool_t waiting, dwc_string_t name )
{
    int status, opt;

    EXIT_IF_1PARAM( !name, "starter: wait ", return -1 );
    if( !waiting )
    {
        opt = WNOHANG;
    }
    else
    {
        opt = 0;
    }
    do
    {
        if( waitpid(pid, &status, opt) == -1 )
        {
            if( errno != EINTR )
            {
                printLib( DW_LL_ERROR, "starter: wait for %s[%d] failed - %s", name, pid, stdlib_error(errno) );
                return -1;
            }
            printLib( DW_LL_DEBUG, "starter: wait for %s[%d] interrupted", name, pid );
        }
        else
        { 
            if( WIFSIGNALED(status) )
            {
                printLib( DW_LL_ERROR, "starter: %s[%d] exited on signal %d", name, pid, WTERMSIG(status) );
            }
            if( WIFSTOPPED(status) )
            {
                printLib( DW_LL_ERROR, "starter: %s[%d] stopped by signal %d", name, pid, WSTOPSIG(status) );
            }
            if( WIFEXITED(status) == 0 )
            {
                printLib( DW_LL_ERROR, "starter: %s[%d] terminates abnormally with status %d", name, pid, WEXITSTATUS(status) );
                return -1;
            }
            else if( WEXITSTATUS(status) == 0 )
            {
                printLib( DW_LL_DEBUG, "starter: %s[%d] exited normally", name, pid );
                return 0;
            }
            else 
            {
                printLib( DW_LL_ERROR, "starter: %s[%d] with error code %d", name, pid, WEXITSTATUS(status) );
                return -1;
            }
        }
    } while(1);
    /* never execute */
    return -1;
}

#endif /* UNIX */

#endif /* _DW_DFORK_C */
