45#define WIFCONTINUED(wstatus) 0
50 namespace NewProcessUnixImp
56class G::NewProcessUnixImp::Pipe
64 void dupTo(
int fd_std ) ;
65 void write(
const std::string & ) ;
68 Pipe(
const Pipe & ) = delete ;
69 Pipe( Pipe && ) = delete ;
70 Pipe & operator=(
const Pipe & ) = delete ;
71 Pipe & operator=( Pipe && ) = delete ;
74 std::array<int,2U> m_fds{{-1,-1}} ;
81 using Fd = NewProcess::Fd ;
82 using Pipe = NewProcessUnixImp::Pipe ;
83 NewProcessImp(
const Path & ,
const StringArray & ,
const NewProcess::Config & ) ;
84 int id() const noexcept ;
85 static
std::pair<
bool,pid_t> fork() ;
86 NewProcessWaitable & waitable() noexcept ;
87 int run( const Path & , const
StringArray & , const Environment & ,
bool strict_exe ) ;
88 void kill() noexcept ;
89 static
void printError(
int , const
std::
string & s ) ;
90 std::
string execErrorFormat( const
std::
string & format ,
int errno_ ) ;
91 static
bool duplicate( Fd ,
int ) ;
94 ~NewProcessImp() = default ;
95 NewProcessImp( const NewProcessImp & ) = delete ;
96 NewProcessImp( NewProcessImp && ) = delete ;
97 NewProcessImp & operator=( const NewProcessImp & ) = delete ;
98 NewProcessImp & operator=( NewProcessImp && ) = delete ;
102 NewProcessWaitable m_waitable ;
103 pid_t m_child_pid {-1} ;
104 bool m_killed {
false} ;
110 m_imp(
std::make_unique<NewProcessImp>(exe,args,config))
119 return m_imp->waitable() ;
124 return NewProcessImp::fork() ;
137 G::threading::yield() ;
138 ::close( ::open(
"/dev/null" , O_RDONLY ) ) ;
139 G::threading::yield() ;
148 if( 1 != (config.stdout==Fd::pipe()?1:0) + (config.stderr==Fd::pipe()?1:0) || config.stdin==Fd::pipe() )
149 throw NewProcess::InvalidParameter() ;
151 throw NewProcess::InvalidParameter() ;
155 throw NewProcess::InvalidPath( exe.
str() ) ;
158 throw NewProcess::Insecure() ;
162 std::tie(in_child,m_child_pid) = fork() ;
168 if( !config.cd.
empty() )
177 if( config.stdout == Fd::pipe() )
179 m_pipe.dupTo( STDOUT_FILENO ) ;
180 duplicate( config.stderr , STDERR_FILENO ) ;
184 duplicate( config.stdout , STDOUT_FILENO ) ;
185 m_pipe.dupTo( STDERR_FILENO ) ;
187 duplicate( config.stdin , STDIN_FILENO ) ;
192 ::signal( SIGPIPE , SIG_DFL ) ;
198 if( !config.strict_exe && !config.exec_search_path.empty() )
202 int e = run( exe , args , config.env , config.strict_exe ) ;
205 int fd_pipe = config.stdout == Fd::pipe() ? STDOUT_FILENO : STDERR_FILENO ;
206 if( config.exec_error_format_fn !=
nullptr )
207 printError( fd_pipe , (config.exec_error_format_fn)(config.exec_error_format,e) ) ;
208 else if( !config.exec_error_format.empty() )
209 printError( fd_pipe , execErrorFormat(config.exec_error_format,e) ) ;
214 std::_Exit( config.exec_error_exit ) ;
219 m_waitable.assign( m_child_pid , m_pipe.fd() ) ;
223std::pair<bool,pid_t> G::NewProcessImp::fork()
225 std::cout << std::flush ;
226 std::cerr << std::flush ;
227 pid_t rc = ::fork() ;
228 const bool ok = rc != -1 ;
229 if( !ok )
throw NewProcess::CannotFork() ;
230 bool in_child = rc == 0 ;
231 auto child_pid =
static_cast<pid_t
>(rc) ;
232 return { in_child , child_pid } ;
235void G::NewProcessImp::printError(
int stdxxx ,
const std::string & s )
238 if( stdxxx <= 0 ) return ;
239 GDEF_IGNORE_RETURN ::write( stdxxx , s.c_str() , s.length() ) ;
243 const Environment & env ,
bool strict_exe )
245 char ** argv =
new char* [ args.size() + 2U ] ;
246 argv[0U] =
const_cast<char*
>( exe.
cstr() ) ;
247 unsigned int argc = 1U ;
248 for(
auto arg_p = args.begin() ; arg_p != args.end() ; ++arg_p , argc++ )
249 argv[argc] =
const_cast<char*
>(arg_p->c_str()) ;
250 argv[argc] = nullptr ;
257 ::execv( exe.
cstr() , argv ) ;
262 ::execvp( exe.
cstr() , argv ) ;
270 ::execve( exe.
cstr() , argv , env.v() ) ;
275 ::execvpe( exe.
cstr() , argv , env.v() ) ;
281 G_DEBUG(
"G::NewProcess::run: execve() returned: errno=" << e <<
": " << exe ) ;
285int G::NewProcessImp::id() const noexcept
287 return static_cast<int>(m_child_pid) ;
295void G::NewProcessImp::kill() noexcept
297 if( !m_killed && m_child_pid != -1 )
300 ::kill( -m_child_pid , SIGTERM ) ;
305std::string G::NewProcessImp::execErrorFormat(
const std::string & format ,
int errno_ )
307 std::string result = format ;
313bool G::NewProcessImp::duplicate( Fd fd ,
int fd_std )
315 G_ASSERT( !(fd==Fd::pipe()) ) ;
316 if( fd == Fd::devnull() )
318 int fd_null = ::open(
G::Path::nullDevice().cstr() , fd_std == STDIN_FILENO ? O_RDONLY : O_WRONLY ) ;
319 if( fd_null < 0 )
throw NewProcess::Error(
"failed to open /dev/null" ) ;
320 ::dup2( fd_null , fd_std ) ;
323 else if( fd.m_fd != fd_std )
325 if( ::dup2(fd.m_fd,fd_std) != fd_std )
326 throw NewProcess::Error(
"dup failed" ) ;
338G::NewProcessUnixImp::Pipe::Pipe()
340 if( ::socketpair( AF_UNIX , SOCK_STREAM , 0 , &m_fds[0] ) < 0 )
341 throw NewProcess::PipeError() ;
342 G_DEBUG(
"G::Pipe::ctor: " << m_fds[0] <<
" " << m_fds[1] ) ;
345G::NewProcessUnixImp::Pipe::~Pipe()
347 if( m_fds[0] >= 0 ) ::close( m_fds[0] ) ;
348 if( m_fds[1] >= 0 ) ::close( m_fds[1] ) ;
351void G::NewProcessUnixImp::Pipe::inChild()
353 ::close( m_fds[0] ) ;
358void G::NewProcessUnixImp::Pipe::inParent()
360 ::close( m_fds[1] ) ;
365int G::NewProcessUnixImp::Pipe::fd()
const
370void G::NewProcessUnixImp::Pipe::dupTo(
int fd_std )
372 if( NewProcessImp::duplicate( NewProcess::Fd::fd(m_fd) , fd_std ) )
398 m_buffer.resize( 1024U ) ;
416 p.set_value( std::make_pair(rc,output()) ) ;
420 try { p.set_exception( std::current_exception() ) ; }
catch(...) {}
429 std::array<char,64U> discard {} ;
430 char * p = &m_buffer[0] ;
431 std::size_t space = m_buffer.size() ;
432 std::size_t size = 0U ;
435 ssize_t n = ::read( m_fd , p?p:&discard[0] , p?space:discard.size() ) ;
436 m_read_error = errno ;
437 if( n < 0 && m_read_error == EINTR )
448 m_buffer.resize( std::min(size,m_buffer.size()) ) ;
454 std::size_t nn =
static_cast<std::size_t
>(n) ;
455 nn = std::min( nn , space ) ;
467 m_rc = ::waitpid( m_pid , &m_status , 0 ) ;
469 if( m_rc >= 0 && ( WIFSTOPPED(m_status) || WIFCONTINUED(m_status) ) )
471 else if( m_rc == -1 && m_error == EINTR )
486 if( m_error == ECHILD )
492 else if( m_error || m_read_error )
494 std::ostringstream ss ;
495 ss <<
"errno=" << (m_read_error?m_read_error:m_error) ;
496 throw NewProcess::WaitError( ss.str() ) ;
498 else if( !WIFEXITED(m_status) )
501 std::ostringstream ss ;
502 ss <<
"pid=" << m_pid ;
503 if( WIFSIGNALED(m_status) )
504 ss <<
" signal=" << WTERMSIG(m_status) ;
505 throw NewProcess::ChildError( ss.str() ) ;
509 result = WEXITSTATUS( m_status ) ;
520 if( m_error || m_read_error )
522 else if( !WIFEXITED(m_status) )
523 result = 128 + WTERMSIG(m_status) ;
525 result = WEXITSTATUS( m_status ) ;
532 if( m_fd < 0 || m_read_error != 0 )
535 return { &m_buffer[0] , m_buffer.size() } ;
static void put(const std::string &name, const std::string &value)
Sets the environment variable value.
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.
Holds the parameters and future results of a waitpid() system call.
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...
NewProcessWaitable & waitable() noexcept
Returns a reference to the Waitable sub-object so that the caller can wait for the child process to e...
NewProcess(const Path &exe, const G::StringArray &args, const Config &)
Constructor.
A Path object represents a file system path.
const char * cstr() const noexcept
Returns the path string.
static Path nullDevice()
Returns the path of the "/dev/null" special file, or equivalent.
bool isRelative() const noexcept
Returns true if the path is a relative path or empty().
std::string str() const
Returns the path string.
bool empty() const noexcept
Returns true if size() is zero.
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-id 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 unsigned int replaceAll(std::string &s, string_view from, string_view to)
Does a global replace on string 's', replacing all occurrences of sub-string 'from' with 'to'.
static std::string fromInt(int i)
Converts int 'i' to a string.
A static interface for enabling test features at run-time.
bool enabled() noexcept
Returns true if pop code is built in.
std::vector< std::string > StringArray
A std::vector of std::strings.
Configuration structure for G::NewProcess.