45#define WIFCONTINUED(wstatus) 0
60 void dupTo(
int fd_std ) ;
61 void write(
const std::string & ) ;
66 void operator=(
const Pipe & ) = delete ;
67 void operator=(
Pipe && ) = delete ;
70 std::array<int,2U> m_fds{{-1,-1}} ;
82 Fd fd_stdin ,
Fd fd_stdout ,
Fd fd_stderr ,
const G::Path & cd ,
83 bool strict_path ,
Identity run_as_id ,
bool strict_id ,
84 int exec_error_exit ,
const std::string & exec_error_format ,
85 std::string (*exec_error_format_fn)(std::string,
int) ) ;
86 int id()
const noexcept ;
87 static std::pair<bool,pid_t> fork() ;
90 void kill() noexcept ;
91 static void printError(
int ,
const std::string & s ) ;
92 std::string execErrorFormat(
const std::string &
format ,
int errno_ ) ;
93 static bool duplicate(
Fd ,
int ) ;
105 pid_t m_child_pid{-1} ;
106 bool m_killed{
false} ;
112 Fd fd_stdin ,
Fd fd_stdout ,
Fd fd_stderr ,
const G::Path & cd ,
113 bool strict_path ,
Identity run_as_id ,
bool strict_id ,
114 int exec_error_exit ,
const std::string & exec_error_format ,
115 std::string (*exec_error_format_fn)(std::string,
int) ) :
116 m_imp(std::make_unique<
NewProcessImp>(exe,args,env,fd_stdin,fd_stdout,fd_stderr,cd,strict_path,
117 run_as_id,strict_id,exec_error_exit,exec_error_format,exec_error_format_fn))
126 return m_imp->waitable() ;
131 return NewProcessImp::fork() ;
144 G::threading::yield() ;
145 ::close( ::open(
"/dev/null" , O_RDONLY ) ) ;
146 G::threading::yield() ;
153 Fd fd_stdin , Fd fd_stdout , Fd fd_stderr ,
const G::Path & cd ,
154 bool strict_path ,
Identity run_as_id ,
bool strict_id ,
155 int exec_error_exit ,
const std::string & exec_error_format ,
156 std::string (*exec_error_format_fn)(std::string,
int) )
159 if( 1 != (fd_stdout==Fd::pipe()?1:0) + (fd_stderr==Fd::pipe()?1:0) || fd_stdin==Fd::pipe() )
160 throw NewProcess::InvalidParameter() ;
162 throw NewProcess::InvalidParameter() ;
166 throw NewProcess::InvalidPath( exe.
str() ) ;
169 throw NewProcess::Insecure() ;
173 std::tie(in_child,m_child_pid) = fork() ;
188 if( fd_stdout == Fd::pipe() )
190 m_pipe.dupTo( STDOUT_FILENO ) ;
191 duplicate( fd_stderr , STDERR_FILENO ) ;
195 duplicate( fd_stdout , STDOUT_FILENO ) ;
196 m_pipe.dupTo( STDERR_FILENO ) ;
198 duplicate( fd_stdin , STDIN_FILENO ) ;
203 ::signal( SIGPIPE , SIG_DFL ) ;
209 int e = run( exe , args , env , strict_path ) ;
212 int fd_pipe = fd_stdout == Fd::pipe() ? STDOUT_FILENO : STDERR_FILENO ;
213 if( exec_error_format_fn !=
nullptr )
214 printError( fd_pipe , (*exec_error_format_fn)(exec_error_format,e) ) ;
215 else if( !exec_error_format.empty() )
216 printError( fd_pipe , execErrorFormat(exec_error_format,e) ) ;
221 std::_Exit( exec_error_exit ) ;
226 m_waitable.assign( m_child_pid , m_pipe.fd() ) ;
230std::pair<bool,pid_t> G::NewProcessImp::fork()
232 std::cout << std::flush ;
233 std::cerr << std::flush ;
234 pid_t rc = ::fork() ;
235 const bool ok = rc != -1 ;
236 if( !ok )
throw NewProcess::CannotFork() ;
237 bool in_child = rc == 0 ;
238 auto child_pid =
static_cast<pid_t
>(rc) ;
239 return std::make_pair( in_child , child_pid ) ;
242void G::NewProcessImp::printError(
int stdxxx ,
const std::string & s )
245 if( stdxxx <= 0 ) return ;
246 GDEF_IGNORE_RETURN ::write( stdxxx , s.c_str() , s.length() ) ;
250 const Environment & env ,
bool strict_path )
252 char ** argv =
new char* [ args.size() + 2U ] ;
253 argv[0U] =
const_cast<char*
>( exe.
cstr() ) ;
254 unsigned int argc = 1U ;
255 for(
auto arg_p = args.begin() ; arg_p != args.end() ; ++arg_p , argc++ )
256 argv[argc] =
const_cast<char*
>(arg_p->c_str()) ;
257 argv[argc] = nullptr ;
264 ::execv( exe.
cstr() , argv ) ;
269 ::execvp( exe.
cstr() , argv ) ;
277 ::execve( exe.
cstr() , argv , env.v() ) ;
282 ::execvpe( exe.
cstr() , argv , env.v() ) ;
288 G_DEBUG(
"G::NewProcess::run: execve() returned: errno=" << e <<
": " << exe ) ;
292int G::NewProcessImp::id() const noexcept
294 return static_cast<int>(m_child_pid) ;
302void G::NewProcessImp::kill() noexcept
304 if( !m_killed && m_child_pid != -1 )
307 ::kill( -m_child_pid , SIGTERM ) ;
312std::string G::NewProcessImp::execErrorFormat(
const std::string & format ,
int errno_ )
314 std::string result = format ;
320bool G::NewProcessImp::duplicate( Fd fd ,
int fd_std )
322 G_ASSERT( !(fd==Fd::pipe()) ) ;
323 if( fd == Fd::devnull() )
325 int fd_null = ::open(
G::Path::nullDevice().cstr() , fd_std == STDIN_FILENO ? O_RDONLY : O_WRONLY ) ;
326 if( fd_null < 0 )
throw NewProcess::Error(
"failed to open /dev/null" ) ;
327 ::dup2( fd_null , fd_std ) ;
330 else if( fd.m_fd != fd_std )
332 if( ::dup2(fd.m_fd,fd_std) != fd_std )
333 throw NewProcess::Error(
"dup failed" ) ;
347 if( ::socketpair( AF_UNIX , SOCK_STREAM , 0 , &m_fds[0] ) < 0 )
348 throw NewProcess::PipeError() ;
349 G_DEBUG(
"G::Pipe::ctor: " << m_fds[0] <<
" " << m_fds[1] ) ;
354 if( m_fds[0] >= 0 ) ::close( m_fds[0] ) ;
355 if( m_fds[1] >= 0 ) ::close( m_fds[1] ) ;
358void G::Pipe::inChild()
360 ::close( m_fds[0] ) ;
365void G::Pipe::inParent()
367 ::close( m_fds[1] ) ;
372int G::Pipe::fd()
const
377void G::Pipe::dupTo(
int fd_std )
379 if( NewProcessImp::duplicate( NewProcess::Fd::fd(m_fd) , fd_std ) )
389 m_test_mode(
G::
Test::enabled(
"waitpid-slow"))
397 m_test_mode(
G::
Test::enabled(
"waitpid-slow"))
403 m_buffer.resize( 1024U ) ;
420 p.set_value( std::make_pair(rc,output()) ) ;
424 try { p.set_exception( std::current_exception() ) ; }
catch(...) {}
432 std::array<char,64U> discard {} ;
433 char * p = &m_buffer[0] ;
434 std::size_t space = m_buffer.size() ;
435 std::size_t size = 0U ;
438 ssize_t n = ::read( m_fd , p?p:&discard[0] , p?space:discard.size() ) ;
439 m_read_error = errno ;
440 if( n < 0 && m_read_error == EINTR )
451 m_buffer.resize( std::min(size,m_buffer.size()) ) ;
457 std::size_t nn =
static_cast<std::size_t
>(n) ;
458 nn = std::min( nn , space ) ;
470 m_rc = ::waitpid( m_pid , &m_status , 0 ) ;
472 if( m_rc >= 0 && ( WIFSTOPPED(m_status) || WIFCONTINUED(m_status) ) )
474 else if( m_rc == -1 && m_error == EINTR )
489 if( m_error == ECHILD )
495 else if( m_error || m_read_error )
497 std::ostringstream ss ;
498 ss <<
"errno=" << (m_read_error?m_read_error:m_error) ;
499 throw NewProcess::WaitError( ss.str() ) ;
501 else if( !WIFEXITED(m_status) )
504 std::ostringstream ss ;
505 ss <<
"pid=" << m_pid ;
506 if( WIFSIGNALED(m_status) )
507 ss <<
" signal=" << WTERMSIG(m_status) ;
508 throw NewProcess::ChildError( ss.str() ) ;
512 result = WEXITSTATUS( m_status ) ;
523 if( m_error || m_read_error )
525 else if( !WIFEXITED(m_status) )
526 result = 128 + WTERMSIG(m_status) ;
528 result = WEXITSTATUS( m_status ) ;
535 if( m_fd < 0 || m_read_error != 0 )
536 return std::string() ;
538 return std::string( &m_buffer[0] , m_buffer.size() ) ;
Holds a set of environment variables and also provides static methods to wrap getenv() and putenv().
A combination of user-id and group-id, with a very low-level interface to the get/set/e/uid/gid funct...
bool isRoot() const noexcept
Returns true if the userid is zero.
static Identity invalid() noexcept
Returns an invalid identity.
static Identity effective() noexcept
Returns the current effective identity.
A pimple-pattern implementation class used by G::NewProcess.
Holds the parameters and future results of a waitpid() system call, as performed by the wait() method...
int get() const
Returns the result of the wait() as either the process exit code or as a thrown exception.
void waitp(std::promise< std::pair< int, std::string > >) noexcept
Calls wait() and then sets the given promise with the get() and output() values or an exception:
NewProcessWaitable()
Default constructor for an object where wait() does nothing and get() returns zero.
std::string output() const
Returns the first bit of child-process output.
NewProcessWaitable & wait()
Waits for the process identified by the constructor parameter to exit.
void assign(pid_t pid, int fd)
Reinitialises as if constructed with the given proces-id and file descriptor.
int id() const noexcept
Returns the process id.
void kill(bool yield=false) noexcept
Tries to kill the spawned process and optionally yield to a thread that might be waiting on it.
static std::pair< bool, pid_t > fork()
A utility function that forks the calling process and returns twice; once in the parent and once in t...
NewProcess(const Path &exe, const StringArray &args, const Environment &env=Environment::minimal(), Fd fd_stdin=Fd::devnull(), Fd fd_stdout=Fd::pipe(), Fd fd_stderr=Fd::devnull(), const G::Path &cd=G::Path(), bool strict_path=true, Identity run_as_id=Identity::invalid(), bool strict_id=true, int exec_error_exit=127, const std::string &exec_error_format=std::string(), std::string(*exec_error_format_fn)(std::string, int)=nullptr)
Constructor.
NewProcessWaitable & waitable() noexcept
Returns a reference to the Waitable sub-object so that the caller can wait for the child process to e...
A Path object represents a file system path.
bool isRelative() const
Returns true if the path is a relative path or empty().
const char * cstr() const noexcept
Returns the path string.
static Path nullDevice()
Returns the path of the "/dev/null" special file, or equivalent.
std::string str() const
Returns the path string.
bool empty() const noexcept
Returns true if size() is zero.
A private implementation class used by G::NewProcess that wraps a unix pipe.
static void closeOtherFiles(int fd_keep=-1)
Closes all open file descriptors except the three standard ones and possibly one other.
static std::string strerror(int errno_)
Translates an 'errno' value into a meaningful diagnostic string.
static void beOrdinaryForExec(Identity run_as_id) noexcept
Sets the real and effective user and group ids to those given, on a best-effort basis.
static int errno_(const SignalSafe &=G::SignalSafe()) noexcept
Returns the process's current 'errno' value.
static void cd(const Path &dir)
Changes directory.
static std::string fromInt(int i)
Converts int 'i' to a string.
static unsigned int replaceAll(std::string &s, const std::string &from, const std::string &to)
Does a global replace on string 's', replacing all occurrences of sub-string 'from' with 'to'.
A static interface for enabling test features at run-time.
std::vector< std::string > StringArray
A std::vector of std::strings.
Wraps up a file descriptor for passing to G::NewProcess.