2010-09-23 20:10:02 +04:00
/*
fork on steroids to avoid SIGCHLD and waitpid
Copyright ( C ) Stefan Metzmacher 2010
Copyright ( C ) Ralph Boehme 2017
This program is free software ; you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation ; either version 3 of the License , or
( at your option ) any later version .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with this program . If not , see < http : //www.gnu.org/licenses/>.
*/
# include "replace.h"
# include "system/wait.h"
# include "system/filesys.h"
2017-04-26 01:48:39 +03:00
# include "system/network.h"
2010-09-23 20:10:02 +04:00
# include "lib/util/samba_util.h"
# include "lib/util/sys_rw.h"
# include "lib/util/tfork.h"
# include "lib/util/debug.h"
2020-02-21 23:45:39 +03:00
# include "lib/util/util_process.h"
2010-09-23 20:10:02 +04:00
2017-04-26 01:48:39 +03:00
# ifdef HAVE_PTHREAD
# include <pthread.h>
# endif
# ifdef NDEBUG
# undef NDEBUG
# endif
# include <assert.h>
/*
* This is how the process hierarchy looks like :
*
* + - - - - - - - - - - +
* | caller |
* + - - - - - - - - - - +
* |
* fork
* |
* v
* + - - - - - - - - - - +
* | waiter |
* + - - - - - - - - - - +
* |
* fork
* |
* v
* + - - - - - - - - - - +
* | worker |
* + - - - - - - - - - - +
*/
2018-11-20 01:07:55 +03:00
# ifdef HAVE_VALGRIND_HELGRIND_H
# include <valgrind/helgrind.h>
# endif
# ifndef ANNOTATE_BENIGN_RACE_SIZED
# define ANNOTATE_BENIGN_RACE_SIZED(obj, size, description)
# endif
# define TFORK_ANNOTATE_BENIGN_RACE(obj) \
ANNOTATE_BENIGN_RACE_SIZED ( \
( obj ) , sizeof ( * ( obj ) ) , \
" no race, serialized by tfork_[un]install_sigchld_handler " ) ;
2017-04-26 01:48:39 +03:00
/*
* The resulting ( private ) state per tfork_create ( ) call , returned as a opaque
* handle to the caller .
*/
struct tfork {
/*
* This is returned to the caller with tfork_event_fd ( )
*/
int event_fd ;
2010-09-23 20:10:02 +04:00
2017-04-26 01:48:39 +03:00
/*
* This is used in the caller by tfork_status ( ) to read the worker exit
* status and to tell the waiter to exit by closing the fd .
*/
int status_fd ;
2010-09-23 20:10:02 +04:00
2017-04-26 01:48:39 +03:00
pid_t waiter_pid ;
pid_t worker_pid ;
} ;
2010-09-23 20:10:02 +04:00
2017-04-26 01:48:39 +03:00
/*
* Internal per - thread state maintained while inside tfork .
*/
struct tfork_state {
pid_t waiter_pid ;
int waiter_errno ;
2010-09-23 20:10:02 +04:00
2017-04-26 01:48:39 +03:00
pid_t worker_pid ;
2010-09-23 20:10:02 +04:00
} ;
/*
2017-04-26 01:48:39 +03:00
* A global state that synchronizes access to handling SIGCHLD and waiting for
2023-04-13 14:29:32 +03:00
* children .
2010-09-23 20:10:02 +04:00
*/
2017-04-26 01:48:39 +03:00
struct tfork_signal_state {
bool available ;
2010-09-23 20:10:02 +04:00
2017-04-26 01:48:39 +03:00
# ifdef HAVE_PTHREAD
pthread_cond_t cond ;
pthread_mutex_t mutex ;
# endif
/*
* pid of the waiter child . This points at waiter_pid in either struct
* tfork or struct tfork_state , depending on who called
* tfork_install_sigchld_handler ( ) .
*
* When tfork_install_sigchld_handler ( ) is called the waiter_pid is
* still - 1 and only set later after fork ( ) , that ' s why this is must be
* a pointer . The signal handler checks this .
*/
pid_t * pid ;
struct sigaction oldact ;
sigset_t oldset ;
} ;
static struct tfork_signal_state signal_state ;
# ifdef HAVE_PTHREAD
static pthread_once_t tfork_global_is_initialized = PTHREAD_ONCE_INIT ;
static pthread_key_t tfork_global_key ;
# else
static struct tfork_state * global_state ;
# endif
static void tfork_sigchld_handler ( int signum , siginfo_t * si , void * p ) ;
# ifdef HAVE_PTHREAD
static void tfork_global_destructor ( void * state )
2010-09-23 20:10:02 +04:00
{
2017-04-26 01:48:39 +03:00
anonymous_shared_free ( state ) ;
}
# endif
static int tfork_acquire_sighandling ( void )
{
int ret = 0 ;
# ifdef HAVE_PTHREAD
ret = pthread_mutex_lock ( & signal_state . mutex ) ;
if ( ret ! = 0 ) {
return ret ;
}
while ( ! signal_state . available ) {
ret = pthread_cond_wait ( & signal_state . cond ,
& signal_state . mutex ) ;
if ( ret ! = 0 ) {
return ret ;
2010-09-23 20:10:02 +04:00
}
}
2017-04-26 01:48:39 +03:00
signal_state . available = false ;
2010-09-23 20:10:02 +04:00
2017-04-26 01:48:39 +03:00
ret = pthread_mutex_unlock ( & signal_state . mutex ) ;
if ( ret ! = 0 ) {
return ret ;
2010-09-23 20:10:02 +04:00
}
2017-04-26 01:48:39 +03:00
# endif
2010-09-23 20:10:02 +04:00
2017-04-26 01:48:39 +03:00
return ret ;
}
static int tfork_release_sighandling ( void )
{
int ret = 0 ;
# ifdef HAVE_PTHREAD
ret = pthread_mutex_lock ( & signal_state . mutex ) ;
if ( ret ! = 0 ) {
return ret ;
}
signal_state . available = true ;
ret = pthread_cond_signal ( & signal_state . cond ) ;
if ( ret ! = 0 ) {
pthread_mutex_unlock ( & signal_state . mutex ) ;
return ret ;
}
ret = pthread_mutex_unlock ( & signal_state . mutex ) ;
if ( ret ! = 0 ) {
return ret ;
2010-09-23 20:10:02 +04:00
}
2017-04-26 01:48:39 +03:00
# endif
2010-09-23 20:10:02 +04:00
2017-04-26 01:48:39 +03:00
return ret ;
2010-09-23 20:10:02 +04:00
}
2017-04-26 01:48:39 +03:00
# ifdef HAVE_PTHREAD
static void tfork_atfork_prepare ( void )
2010-09-23 20:10:02 +04:00
{
2017-04-26 01:48:39 +03:00
int ret ;
ret = pthread_mutex_lock ( & signal_state . mutex ) ;
assert ( ret = = 0 ) ;
}
static void tfork_atfork_parent ( void )
{
int ret ;
ret = pthread_mutex_unlock ( & signal_state . mutex ) ;
assert ( ret = = 0 ) ;
}
# endif
static void tfork_atfork_child ( void )
{
int ret ;
# ifdef HAVE_PTHREAD
ret = pthread_mutex_unlock ( & signal_state . mutex ) ;
assert ( ret = = 0 ) ;
ret = pthread_key_delete ( tfork_global_key ) ;
assert ( ret = = 0 ) ;
ret = pthread_key_create ( & tfork_global_key , tfork_global_destructor ) ;
assert ( ret = = 0 ) ;
2010-09-23 20:10:02 +04:00
2018-11-20 01:07:55 +03:00
/*
* There ' s no data race on the cond variable from the signal state , we
* are writing here , but there are no readers yet . Some data race
* detection tools report a race , but the readers are in the parent
* process .
*/
TFORK_ANNOTATE_BENIGN_RACE ( & signal_state . cond ) ;
2010-09-23 20:10:02 +04:00
/*
2017-04-26 01:48:39 +03:00
* There ' s no way to destroy a condition variable if there are waiters ,
* pthread_cond_destroy ( ) will return EBUSY . Just zero out memory and
* then initialize again . This is not backed by POSIX but should be ok .
2010-09-23 20:10:02 +04:00
*/
2017-04-26 01:48:39 +03:00
ZERO_STRUCT ( signal_state . cond ) ;
ret = pthread_cond_init ( & signal_state . cond , NULL ) ;
assert ( ret = = 0 ) ;
# endif
2010-09-23 20:10:02 +04:00
2017-04-26 01:48:39 +03:00
if ( signal_state . pid ! = NULL ) {
2010-09-23 20:10:02 +04:00
2017-04-26 01:48:39 +03:00
ret = sigaction ( SIGCHLD , & signal_state . oldact , NULL ) ;
assert ( ret = = 0 ) ;
2010-09-23 20:10:02 +04:00
2017-04-26 01:48:39 +03:00
# ifdef HAVE_PTHREAD
ret = pthread_sigmask ( SIG_SETMASK , & signal_state . oldset , NULL ) ;
# else
ret = sigprocmask ( SIG_SETMASK , & signal_state . oldset , NULL ) ;
# endif
2019-06-17 18:23:42 +03:00
assert ( ret = = 0 ) ;
2017-04-26 01:48:39 +03:00
signal_state . pid = NULL ;
2010-09-23 20:10:02 +04:00
}
2017-04-26 01:48:39 +03:00
signal_state . available = true ;
}
static void tfork_global_initialize ( void )
{
# ifdef HAVE_PTHREAD
int ret ;
pthread_atfork ( tfork_atfork_prepare ,
tfork_atfork_parent ,
tfork_atfork_child ) ;
ret = pthread_key_create ( & tfork_global_key , tfork_global_destructor ) ;
assert ( ret = = 0 ) ;
ret = pthread_mutex_init ( & signal_state . mutex , NULL ) ;
assert ( ret = = 0 ) ;
ret = pthread_cond_init ( & signal_state . cond , NULL ) ;
assert ( ret = = 0 ) ;
2018-11-20 01:07:55 +03:00
/*
* In a threaded process there ' s no data race on t - > waiter_pid as
* we ' re serializing globally via tfork_acquire_sighandling ( ) and
* tfork_release_sighandling ( ) .
*/
TFORK_ANNOTATE_BENIGN_RACE ( & signal_state . pid ) ;
2017-04-26 01:48:39 +03:00
# endif
signal_state . available = true ;
}
static struct tfork_state * tfork_global_get ( void )
{
struct tfork_state * state = NULL ;
# ifdef HAVE_PTHREAD
int ret ;
# endif
# ifdef HAVE_PTHREAD
state = ( struct tfork_state * ) pthread_getspecific ( tfork_global_key ) ;
# else
state = global_state ;
# endif
if ( state ! = NULL ) {
return state ;
2010-09-23 20:10:02 +04:00
}
2017-04-26 01:48:39 +03:00
state = ( struct tfork_state * ) anonymous_shared_allocate (
sizeof ( struct tfork_state ) ) ;
if ( state = = NULL ) {
return NULL ;
}
2010-09-23 20:10:02 +04:00
2017-04-26 01:48:39 +03:00
# ifdef HAVE_PTHREAD
ret = pthread_setspecific ( tfork_global_key , state ) ;
if ( ret ! = 0 ) {
anonymous_shared_free ( state ) ;
return NULL ;
2010-09-23 20:10:02 +04:00
}
2017-04-26 01:48:39 +03:00
# endif
return state ;
}
2010-09-23 20:10:02 +04:00
2017-04-26 01:48:39 +03:00
static void tfork_global_free ( void )
{
struct tfork_state * state = NULL ;
# ifdef HAVE_PTHREAD
int ret ;
# endif
# ifdef HAVE_PTHREAD
state = ( struct tfork_state * ) pthread_getspecific ( tfork_global_key ) ;
# else
state = global_state ;
# endif
if ( state = = NULL ) {
return ;
}
2017-04-25 18:47:57 +03:00
2017-04-26 01:48:39 +03:00
# ifdef HAVE_PTHREAD
ret = pthread_setspecific ( tfork_global_key , NULL ) ;
if ( ret ! = 0 ) {
return ;
}
# endif
anonymous_shared_free ( state ) ;
}
2017-04-25 18:47:57 +03:00
2017-04-26 01:48:39 +03:00
/**
* Only one thread at a time is allowed to handle SIGCHLD signals
* */
static int tfork_install_sigchld_handler ( pid_t * pid )
{
int ret ;
struct sigaction act ;
sigset_t set ;
ret = tfork_acquire_sighandling ( ) ;
if ( ret ! = 0 ) {
return - 1 ;
2010-09-23 20:10:02 +04:00
}
2017-04-26 01:48:39 +03:00
assert ( signal_state . pid = = NULL ) ;
signal_state . pid = pid ;
act = ( struct sigaction ) {
. sa_sigaction = tfork_sigchld_handler ,
. sa_flags = SA_SIGINFO ,
} ;
ret = sigaction ( SIGCHLD , & act , & signal_state . oldact ) ;
if ( ret ! = 0 ) {
return - 1 ;
2010-09-23 20:10:02 +04:00
}
2017-04-26 01:48:39 +03:00
sigemptyset ( & set ) ;
sigaddset ( & set , SIGCHLD ) ;
# ifdef HAVE_PTHREAD
ret = pthread_sigmask ( SIG_UNBLOCK , & set , & signal_state . oldset ) ;
# else
ret = sigprocmask ( SIG_UNBLOCK , & set , & signal_state . oldset ) ;
# endif
if ( ret ! = 0 ) {
return - 1 ;
2010-09-23 20:10:02 +04:00
}
2017-04-26 01:48:39 +03:00
return 0 ;
2010-09-23 20:10:02 +04:00
}
2017-04-26 01:48:39 +03:00
static int tfork_uninstall_sigchld_handler ( void )
2010-09-23 20:10:02 +04:00
{
int ret ;
2017-04-26 01:48:39 +03:00
signal_state . pid = NULL ;
ret = sigaction ( SIGCHLD , & signal_state . oldact , NULL ) ;
if ( ret ! = 0 ) {
return - 1 ;
}
# ifdef HAVE_PTHREAD
ret = pthread_sigmask ( SIG_SETMASK , & signal_state . oldset , NULL ) ;
# else
ret = sigprocmask ( SIG_SETMASK , & signal_state . oldset , NULL ) ;
# endif
if ( ret ! = 0 ) {
return - 1 ;
}
ret = tfork_release_sighandling ( ) ;
if ( ret ! = 0 ) {
return - 1 ;
}
return 0 ;
}
static void tfork_sigchld_handler ( int signum , siginfo_t * si , void * p )
{
if ( ( signal_state . pid ! = NULL ) & &
( * signal_state . pid ! = - 1 ) & &
( si - > si_pid = = * signal_state . pid ) )
{
return ;
}
/*
* Not our child , forward to old handler
*/
if ( signal_state . oldact . sa_flags & SA_SIGINFO ) {
signal_state . oldact . sa_sigaction ( signum , si , p ) ;
return ;
}
if ( signal_state . oldact . sa_handler = = SIG_IGN ) {
return ;
}
if ( signal_state . oldact . sa_handler = = SIG_DFL ) {
return ;
}
signal_state . oldact . sa_handler ( signum ) ;
}
static pid_t tfork_start_waiter_and_worker ( struct tfork_state * state ,
int * _event_fd ,
int * _status_fd )
{
int p [ 2 ] ;
int status_sp_caller_fd = - 1 ;
int status_sp_waiter_fd = - 1 ;
int event_pipe_caller_fd = - 1 ;
int event_pipe_waiter_fd = - 1 ;
int ready_pipe_caller_fd = - 1 ;
int ready_pipe_worker_fd = - 1 ;
ssize_t nwritten ;
ssize_t nread ;
2010-09-23 20:10:02 +04:00
pid_t pid ;
2017-04-26 01:48:39 +03:00
int status ;
int fd ;
char c ;
int ret ;
* _event_fd = - 1 ;
* _status_fd = - 1 ;
2010-09-23 20:10:02 +04:00
2017-04-26 01:48:39 +03:00
if ( state = = NULL ) {
2010-09-23 20:10:02 +04:00
return - 1 ;
}
2017-04-26 01:48:39 +03:00
ret = socketpair ( AF_UNIX , SOCK_STREAM , 0 , p ) ;
if ( ret ! = 0 ) {
return - 1 ;
}
set_close_on_exec ( p [ 0 ] ) ;
set_close_on_exec ( p [ 1 ] ) ;
status_sp_caller_fd = p [ 0 ] ;
status_sp_waiter_fd = p [ 1 ] ;
ret = pipe ( p ) ;
if ( ret ! = 0 ) {
close ( status_sp_caller_fd ) ;
close ( status_sp_waiter_fd ) ;
return - 1 ;
}
set_close_on_exec ( p [ 0 ] ) ;
set_close_on_exec ( p [ 1 ] ) ;
event_pipe_caller_fd = p [ 0 ] ;
event_pipe_waiter_fd = p [ 1 ] ;
ret = pipe ( p ) ;
if ( ret ! = 0 ) {
close ( status_sp_caller_fd ) ;
close ( status_sp_waiter_fd ) ;
close ( event_pipe_caller_fd ) ;
close ( event_pipe_waiter_fd ) ;
return - 1 ;
}
set_close_on_exec ( p [ 0 ] ) ;
set_close_on_exec ( p [ 1 ] ) ;
ready_pipe_worker_fd = p [ 0 ] ;
ready_pipe_caller_fd = p [ 1 ] ;
2010-09-23 20:10:02 +04:00
2017-04-26 01:48:39 +03:00
pid = fork ( ) ;
if ( pid = = - 1 ) {
close ( status_sp_caller_fd ) ;
close ( status_sp_waiter_fd ) ;
close ( event_pipe_caller_fd ) ;
close ( event_pipe_waiter_fd ) ;
close ( ready_pipe_caller_fd ) ;
close ( ready_pipe_worker_fd ) ;
return - 1 ;
}
if ( pid ! = 0 ) {
/* The caller */
2010-09-23 20:10:02 +04:00
2018-11-20 01:07:55 +03:00
/*
* In a threaded process there ' s no data race on
* state - > waiter_pid as we ' re serializing globally via
* tfork_acquire_sighandling ( ) and tfork_release_sighandling ( ) .
*/
TFORK_ANNOTATE_BENIGN_RACE ( & state - > waiter_pid ) ;
2017-04-26 01:48:39 +03:00
state - > waiter_pid = pid ;
close ( status_sp_waiter_fd ) ;
close ( event_pipe_waiter_fd ) ;
close ( ready_pipe_worker_fd ) ;
set_blocking ( event_pipe_caller_fd , false ) ;
2010-09-23 20:10:02 +04:00
2017-04-26 01:48:39 +03:00
/*
* wait for the waiter to get ready .
*/
nread = sys_read ( status_sp_caller_fd , & c , sizeof ( char ) ) ;
if ( nread ! = sizeof ( char ) ) {
2010-09-23 20:10:02 +04:00
return - 1 ;
}
2017-04-26 01:48:39 +03:00
/*
* Notify the worker to start .
*/
nwritten = sys_write ( ready_pipe_caller_fd ,
& ( char ) { 0 } , sizeof ( char ) ) ;
if ( nwritten ! = sizeof ( char ) ) {
close ( ready_pipe_caller_fd ) ;
return - 1 ;
}
close ( ready_pipe_caller_fd ) ;
* _event_fd = event_pipe_caller_fd ;
* _status_fd = status_sp_caller_fd ;
return pid ;
2010-09-23 20:10:02 +04:00
}
2017-04-26 01:48:39 +03:00
# ifndef HAVE_PTHREAD
/* cleanup sigchld_handler */
tfork_atfork_child ( ) ;
# endif
2010-09-23 20:10:02 +04:00
/*
2017-04-26 01:48:39 +03:00
* The " waiter " child .
2010-09-23 20:10:02 +04:00
*/
2023-01-20 21:12:54 +03:00
process_set_title ( " tfork waiter " , " tfork waiter process " ) ;
2022-12-02 17:38:44 +03:00
2017-04-26 01:48:39 +03:00
CatchSignal ( SIGCHLD , SIG_DFL ) ;
close ( status_sp_caller_fd ) ;
close ( event_pipe_caller_fd ) ;
close ( ready_pipe_caller_fd ) ;
2010-09-23 20:10:02 +04:00
pid = fork ( ) ;
2017-04-26 01:48:39 +03:00
if ( pid = = - 1 ) {
state - > waiter_errno = errno ;
_exit ( 0 ) ;
}
2010-09-23 20:10:02 +04:00
if ( pid = = 0 ) {
/*
2017-04-26 01:48:39 +03:00
* The worker child .
2010-09-23 20:10:02 +04:00
*/
2017-04-26 01:48:39 +03:00
close ( status_sp_waiter_fd ) ;
close ( event_pipe_waiter_fd ) ;
2010-09-23 20:10:02 +04:00
/*
2017-04-26 01:48:39 +03:00
* Wait for the caller to give us a go !
2010-09-23 20:10:02 +04:00
*/
2017-04-26 01:48:39 +03:00
nread = sys_read ( ready_pipe_worker_fd , & c , sizeof ( char ) ) ;
if ( nread ! = sizeof ( char ) ) {
_exit ( 1 ) ;
2010-09-23 20:10:02 +04:00
}
2017-04-26 01:48:39 +03:00
close ( ready_pipe_worker_fd ) ;
2010-09-23 20:10:02 +04:00
2017-04-26 01:48:39 +03:00
return 0 ;
}
state - > worker_pid = pid ;
2022-12-02 17:38:44 +03:00
process_set_title ( " tfork(%d) " , " tfork waiter process(%d) " , pid ) ;
2017-04-26 01:48:39 +03:00
close ( ready_pipe_worker_fd ) ;
/*
* We ' re going to stay around until child2 exits , so lets close all fds
2021-04-12 12:43:06 +03:00
* other than the pipe fd we may have inherited from the caller .
2017-04-26 01:48:39 +03:00
*
* Dup event_sp_waiter_fd and status_sp_waiter_fd onto fds 0 and 1 so we
* can then call closefrom ( 2 ) .
*/
if ( event_pipe_waiter_fd > 0 ) {
int dup_fd = 0 ;
2010-09-23 20:10:02 +04:00
2017-04-26 01:48:39 +03:00
if ( status_sp_waiter_fd = = 0 ) {
dup_fd = 1 ;
}
2010-09-23 20:10:02 +04:00
2017-04-26 01:48:39 +03:00
do {
fd = dup2 ( event_pipe_waiter_fd , dup_fd ) ;
} while ( ( fd = = - 1 ) & & ( errno = = EINTR ) ) ;
if ( fd = = - 1 ) {
state - > waiter_errno = errno ;
kill ( state - > worker_pid , SIGKILL ) ;
state - > worker_pid = - 1 ;
_exit ( 1 ) ;
2010-09-23 20:10:02 +04:00
}
2017-04-26 01:48:39 +03:00
event_pipe_waiter_fd = fd ;
}
2010-09-23 20:10:02 +04:00
2017-04-26 01:48:39 +03:00
if ( status_sp_waiter_fd > 1 ) {
do {
fd = dup2 ( status_sp_waiter_fd , 1 ) ;
} while ( ( fd = = - 1 ) & & ( errno = = EINTR ) ) ;
if ( fd = = - 1 ) {
state - > waiter_errno = errno ;
kill ( state - > worker_pid , SIGKILL ) ;
state - > worker_pid = - 1 ;
_exit ( 1 ) ;
2010-09-23 20:10:02 +04:00
}
2017-04-26 01:48:39 +03:00
status_sp_waiter_fd = fd ;
}
2010-09-23 20:10:02 +04:00
2017-04-26 01:48:39 +03:00
closefrom ( 2 ) ;
2010-09-23 20:10:02 +04:00
2017-04-26 01:48:39 +03:00
/* Tell the caller we're ready */
nwritten = sys_write ( status_sp_waiter_fd , & ( char ) { 0 } , sizeof ( char ) ) ;
if ( nwritten ! = sizeof ( char ) ) {
_exit ( 1 ) ;
}
tfork_global_free ( ) ;
state = NULL ;
do {
ret = waitpid ( pid , & status , 0 ) ;
} while ( ( ret = = - 1 ) & & ( errno = = EINTR ) ) ;
if ( ret = = - 1 ) {
status = errno ;
kill ( pid , SIGKILL ) ;
}
/*
* This writes the worker child exit status via our internal socketpair
* so the tfork_status ( ) implementation can read it from its end .
*/
nwritten = sys_write ( status_sp_waiter_fd , & status , sizeof ( status ) ) ;
if ( nwritten = = - 1 ) {
if ( errno ! = EPIPE & & errno ! = ECONNRESET ) {
_exit ( errno ) ;
2010-09-23 20:10:02 +04:00
}
2017-04-26 01:48:39 +03:00
/*
2023-04-13 14:29:32 +03:00
* The caller exited and didn ' t call tfork_status ( ) .
2017-04-26 01:48:39 +03:00
*/
2010-09-23 20:10:02 +04:00
_exit ( 0 ) ;
}
2017-04-26 01:48:39 +03:00
if ( nwritten ! = sizeof ( status ) ) {
_exit ( 1 ) ;
}
2010-09-23 20:10:02 +04:00
2017-04-26 01:48:39 +03:00
/*
* This write to the event_fd returned by tfork_event_fd ( ) and notifies
* the caller that the worker child is done and he may now call
* tfork_status ( ) .
*/
nwritten = sys_write ( event_pipe_waiter_fd , & ( char ) { 0 } , sizeof ( char ) ) ;
if ( nwritten ! = sizeof ( char ) ) {
_exit ( 1 ) ;
2010-09-23 20:10:02 +04:00
}
/*
2017-04-26 01:48:39 +03:00
* Wait for our parent ( the process that called tfork_create ( ) ) to
* close ( ) the socketpair fd in tfork_status ( ) .
2010-09-23 20:10:02 +04:00
*
2023-04-13 14:29:32 +03:00
* Again , the caller might have exited without calling tfork_status ( ) .
2010-09-23 20:10:02 +04:00
*/
2017-04-26 01:48:39 +03:00
nread = sys_read ( status_sp_waiter_fd , & c , 1 ) ;
if ( nread = = - 1 ) {
if ( errno = = EPIPE | | errno = = ECONNRESET ) {
_exit ( 0 ) ;
2010-09-23 20:10:02 +04:00
}
2017-04-26 01:48:39 +03:00
_exit ( errno ) ;
}
2017-09-11 01:25:49 +03:00
if ( nread ! = 1 ) {
2017-04-26 01:48:39 +03:00
_exit ( 255 ) ;
}
_exit ( 0 ) ;
}
2010-09-23 20:10:02 +04:00
2017-04-26 01:48:39 +03:00
static int tfork_create_reap_waiter ( pid_t waiter_pid )
{
pid_t pid ;
int waiter_status ;
if ( waiter_pid = = - 1 ) {
return 0 ;
2010-09-23 20:10:02 +04:00
}
2017-04-26 01:48:39 +03:00
kill ( waiter_pid , SIGKILL ) ;
2010-09-23 20:10:02 +04:00
2017-04-26 01:48:39 +03:00
do {
pid = waitpid ( waiter_pid , & waiter_status , 0 ) ;
} while ( ( pid = = - 1 ) & & ( errno = = EINTR ) ) ;
assert ( pid = = waiter_pid ) ;
return 0 ;
}
struct tfork * tfork_create ( void )
{
struct tfork_state * state = NULL ;
struct tfork * t = NULL ;
pid_t pid ;
2023-01-10 03:06:25 +03:00
int saved_errno = 0 ;
2017-04-26 01:48:39 +03:00
int ret = 0 ;
2023-01-10 03:06:25 +03:00
int ret2 ;
2017-04-26 01:48:39 +03:00
# ifdef HAVE_PTHREAD
ret = pthread_once ( & tfork_global_is_initialized ,
tfork_global_initialize ) ;
if ( ret ! = 0 ) {
return NULL ;
}
# else
tfork_global_initialize ( ) ;
# endif
state = tfork_global_get ( ) ;
if ( state = = NULL ) {
return NULL ;
}
* state = ( struct tfork_state ) {
. waiter_pid = - 1 ,
. waiter_errno = ECANCELED ,
. worker_pid = - 1 ,
} ;
t = malloc ( sizeof ( struct tfork ) ) ;
if ( t = = NULL ) {
ret = - 1 ;
goto cleanup ;
2010-09-23 20:10:02 +04:00
}
2017-04-26 01:48:39 +03:00
* t = ( struct tfork ) {
. event_fd = - 1 ,
. status_fd = - 1 ,
. waiter_pid = - 1 ,
. worker_pid = - 1 ,
} ;
ret = tfork_install_sigchld_handler ( & state - > waiter_pid ) ;
if ( ret ! = 0 ) {
goto cleanup ;
}
pid = tfork_start_waiter_and_worker ( state ,
& t - > event_fd ,
& t - > status_fd ) ;
if ( pid = = - 1 ) {
ret = - 1 ;
goto cleanup ;
}
if ( pid = = 0 ) {
/* In the worker */
tfork_global_free ( ) ;
t - > worker_pid = 0 ;
return t ;
}
2018-11-20 01:07:55 +03:00
/*
* In a threaded process there ' s no data race on t - > waiter_pid as
* we ' re serializing globally via tfork_acquire_sighandling ( ) and
* tfork_release_sighandling ( ) .
*/
TFORK_ANNOTATE_BENIGN_RACE ( & t - > waiter_pid ) ;
2017-04-26 01:48:39 +03:00
t - > waiter_pid = pid ;
t - > worker_pid = state - > worker_pid ;
cleanup :
if ( ret = = - 1 ) {
saved_errno = errno ;
if ( t ! = NULL ) {
if ( t - > status_fd ! = - 1 ) {
close ( t - > status_fd ) ;
}
if ( t - > event_fd ! = - 1 ) {
close ( t - > event_fd ) ;
}
2023-01-10 03:06:25 +03:00
ret2 = tfork_create_reap_waiter ( state - > waiter_pid ) ;
assert ( ret2 = = 0 ) ;
2017-04-26 01:48:39 +03:00
free ( t ) ;
t = NULL ;
}
}
2023-01-10 03:06:25 +03:00
ret2 = tfork_uninstall_sigchld_handler ( ) ;
assert ( ret2 = = 0 ) ;
2017-04-26 01:48:39 +03:00
tfork_global_free ( ) ;
2010-09-23 20:10:02 +04:00
2017-04-26 01:48:39 +03:00
if ( ret = = - 1 ) {
2010-09-23 20:10:02 +04:00
errno = saved_errno ;
2017-04-26 01:48:39 +03:00
}
return t ;
}
pid_t tfork_child_pid ( const struct tfork * t )
{
return t - > worker_pid ;
}
2017-09-16 11:22:31 +03:00
int tfork_event_fd ( struct tfork * t )
2017-04-26 01:48:39 +03:00
{
2017-09-16 11:22:31 +03:00
int fd = t - > event_fd ;
assert ( t - > event_fd ! = - 1 ) ;
t - > event_fd = - 1 ;
return fd ;
2017-04-26 01:48:39 +03:00
}
int tfork_status ( struct tfork * * _t , bool wait )
{
struct tfork * t = * _t ;
int status ;
ssize_t nread ;
int waiter_status ;
pid_t pid ;
int ret ;
if ( t = = NULL ) {
2010-09-23 20:10:02 +04:00
return - 1 ;
}
2017-04-26 01:48:39 +03:00
if ( wait ) {
set_blocking ( t - > status_fd , true ) ;
2010-09-23 20:10:02 +04:00
2017-04-26 01:48:39 +03:00
nread = sys_read ( t - > status_fd , & status , sizeof ( int ) ) ;
} else {
set_blocking ( t - > status_fd , false ) ;
nread = read ( t - > status_fd , & status , sizeof ( int ) ) ;
if ( ( nread = = - 1 ) & &
( ( errno = = EAGAIN ) | | ( errno = = EWOULDBLOCK ) | | errno = = EINTR ) ) {
errno = EAGAIN ;
return - 1 ;
}
}
if ( nread ! = sizeof ( int ) ) {
2010-09-23 20:10:02 +04:00
return - 1 ;
}
2017-04-26 01:48:39 +03:00
ret = tfork_install_sigchld_handler ( & t - > waiter_pid ) ;
if ( ret ! = 0 ) {
return - 1 ;
}
/*
* This triggers process exit in the waiter .
2017-09-11 01:25:49 +03:00
* We write to the fd as well as closing it , as any tforked sibling
* processes will also have the writable end of this socket open .
*
2017-04-26 01:48:39 +03:00
*/
2017-09-11 01:25:49 +03:00
{
size_t nwritten ;
nwritten = sys_write ( t - > status_fd , & ( char ) { 0 } , sizeof ( char ) ) ;
if ( nwritten ! = sizeof ( char ) ) {
close ( t - > status_fd ) ;
return - 1 ;
}
}
2017-04-26 01:48:39 +03:00
close ( t - > status_fd ) ;
do {
pid = waitpid ( t - > waiter_pid , & waiter_status , 0 ) ;
} while ( ( pid = = - 1 ) & & ( errno = = EINTR ) ) ;
assert ( pid = = t - > waiter_pid ) ;
2017-09-16 11:22:31 +03:00
if ( t - > event_fd ! = - 1 ) {
close ( t - > event_fd ) ;
t - > event_fd = - 1 ;
}
2017-04-26 01:48:39 +03:00
free ( t ) ;
t = NULL ;
* _t = NULL ;
ret = tfork_uninstall_sigchld_handler ( ) ;
assert ( ret = = 0 ) ;
return status ;
}
int tfork_destroy ( struct tfork * * _t )
{
struct tfork * t = * _t ;
int ret ;
if ( t = = NULL ) {
errno = EINVAL ;
return - 1 ;
}
kill ( t - > worker_pid , SIGKILL ) ;
ret = tfork_status ( _t , true ) ;
if ( ret = = - 1 ) {
return - 1 ;
}
2010-09-23 20:10:02 +04:00
2017-04-26 01:48:39 +03:00
return 0 ;
2010-09-23 20:10:02 +04:00
}