44#define WIFCONTINUED(wstatus) 0
49 namespace NewProcessUnixImp
55class G::NewProcessUnixImp::Pipe
63 void dupTo(
int fd_std ) ;
64 void write(
const std::string & ) ;
67 Pipe(
const Pipe & ) = delete ;
68 Pipe( Pipe && ) = delete ;
69 Pipe & operator=(
const Pipe & ) = delete ;
70 Pipe & operator=( Pipe && ) = delete ;
73 std::array<int,2U> m_fds{{-1,-1}} ;
80 using Fd = NewProcess::Fd ;
81 using Pipe = NewProcessUnixImp::Pipe ;
82 NewProcessImp(
const Path & ,
const StringArray & ,
const NewProcess::Config & ) ;
83 int id() const noexcept ;
84 static
std::pair<
bool,pid_t> fork() ;
85 NewProcessWaitable & waitable() noexcept ;
86 static
int run( const Path & , const
StringArray & , const Environment & ,
bool strict_exe ) ;
87 void kill() noexcept ;
88 static
void printError(
int , const
std::
string & s ) ;
89 static
std::
string execErrorFormat( const
std::
string & format ,
int errno_ ) ;
90 static
bool duplicate( Fd ,
int ) ;
93 ~NewProcessImp() = default ;
94 NewProcessImp( const NewProcessImp & ) = delete ;
95 NewProcessImp( NewProcessImp && ) = delete ;
96 NewProcessImp & operator=( const NewProcessImp & ) = delete ;
97 NewProcessImp & operator=( NewProcessImp && ) = delete ;
101 NewProcessWaitable m_waitable ;
102 pid_t m_child_pid {-1} ;
103 bool m_killed {
false} ;
109 m_imp(
std::make_unique<NewProcessImp>(exe,args,config))
118 return m_imp->waitable() ;
123 return NewProcessImp::fork() ;
136 G::threading::yield() ;
137 ::close( ::open(
"/dev/null" , O_RDONLY ) ) ;
138 G::threading::yield() ;
147 if( 1 != (config.stdout==Fd::pipe()?1:0) + (config.stderr==Fd::pipe()?1:0) || config.stdin==Fd::pipe() )
148 throw NewProcess::InvalidParameter() ;
150 throw NewProcess::InvalidParameter() ;
154 throw NewProcess::InvalidPath( exe.
str() ) ;
157 throw NewProcess::Insecure() ;
160 bool in_child = false ;
161 std::tie(in_child,m_child_pid) = fork() ;
167 if( !config.cd.
empty() )
176 if( config.stdout == Fd::pipe() )
178 m_pipe.dupTo( STDOUT_FILENO ) ;
179 duplicate( config.stderr , STDERR_FILENO ) ;
183 duplicate( config.stdout , STDOUT_FILENO ) ;
184 m_pipe.dupTo( STDERR_FILENO ) ;
186 duplicate( config.stdin , STDIN_FILENO ) ;
190 if( config.close_other_fds )
195 ::signal( SIGPIPE , SIG_DFL ) ;
201 if( !config.strict_exe && !config.exec_search_path.empty() )
205 int e = run( exe , args , config.env , config.strict_exe ) ;
208 int fd_pipe = config.stdout == Fd::pipe() ? STDOUT_FILENO : STDERR_FILENO ;
209 if( config.exec_error_format_fn !=
nullptr )
210 printError( fd_pipe , (config.exec_error_format_fn)(config.exec_error_format,e) ) ;
211 else if( !config.exec_error_format.empty() )
212 printError( fd_pipe , execErrorFormat(config.exec_error_format,e) ) ;
217 std::_Exit( config.exec_error_exit ) ;
222 m_waitable.assign( m_child_pid , m_pipe.fd() ) ;
226std::pair<bool,pid_t> G::NewProcessImp::fork()
228 std::cout << std::flush ;
229 std::cerr << std::flush ;
230 pid_t rc = ::fork() ;
231 const bool ok = rc != -1 ;
232 if( !ok )
throw NewProcess::CannotFork() ;
233 bool in_child = rc == 0 ;
234 auto child_pid = rc ;
235 return { in_child , child_pid } ;
238void G::NewProcessImp::printError(
int stdxxx ,
const std::string & s )
241 if( stdxxx <= 0 ) return ;
242 GDEF_IGNORE_RETURN ::write( stdxxx , s.c_str() , s.length() ) ;
246 const Environment & env ,
bool strict_exe )
248 std::vector<char*> argarray ;
249 argarray.reserve( args.size() + 2U ) ;
250 argarray.push_back(
const_cast<char*
>(exe.
cstr()) ) ;
251 for(
const auto & arg : args )
252 argarray.push_back(
const_cast<char*
>(arg.c_str()) ) ;
253 argarray.push_back(
nullptr ) ;
260 ::execv( exe.
cstr() , argarray.data() ) ;
265 ::execvp( exe.
cstr() , argarray.data() ) ;
271 std::string envblock = env.block() ;
275 ::execve( exe.
cstr() , argarray.data() , envarray.data() ) ;
280 ::execvpe( exe.
cstr() , argarray.data() , envarray.data() ) ;
285 G_DEBUG(
"G::NewProcess::run: execve() returned: errno=" << e <<
": " << exe ) ;
289int G::NewProcessImp::id() const noexcept
291 return static_cast<int>(m_child_pid) ;
299void G::NewProcessImp::kill() noexcept
301 if( !m_killed && m_child_pid != -1 )
304 ::kill( -m_child_pid , SIGTERM ) ;
309std::string G::NewProcessImp::execErrorFormat(
const std::string & format ,
int errno_ )
311 std::string result = format ;
317bool G::NewProcessImp::duplicate( Fd fd ,
int fd_std )
319 G_ASSERT( !(fd==Fd::pipe()) ) ;
320 if( fd == Fd::devnull() )
322 auto mode = fd_std == STDIN_FILENO ? G::File::InOutAppend::In : G::File::InOutAppend::OutNoCreate ;
324 if( fd_null < 0 )
throw NewProcess::Error(
"failed to open /dev/null" ) ;
325 ::dup2( fd_null , fd_std ) ;
328 else if( fd.m_fd != fd_std )
330 if( ::dup2(fd.m_fd,fd_std) != fd_std )
331 throw NewProcess::Error(
"dup failed" ) ;
343G::NewProcessUnixImp::Pipe::Pipe()
345 if( ::socketpair( AF_UNIX , SOCK_STREAM , 0 , m_fds.data() ) < 0 )
346 throw NewProcess::PipeError() ;
347 G_DEBUG(
"G::Pipe::ctor: " << m_fds[0] <<
" " << m_fds[1] ) ;
350G::NewProcessUnixImp::Pipe::~Pipe()
352 if( m_fds[0] >= 0 ) ::close( m_fds[0] ) ;
353 if( m_fds[1] >= 0 ) ::close( m_fds[1] ) ;
356void G::NewProcessUnixImp::Pipe::inChild()
358 ::close( m_fds[0] ) ;
363void G::NewProcessUnixImp::Pipe::inParent()
365 ::close( m_fds[1] ) ;
370int G::NewProcessUnixImp::Pipe::fd()
const
375void G::NewProcessUnixImp::Pipe::dupTo(
int fd_std )
377 if( NewProcessImp::duplicate( NewProcess::Fd::fd(m_fd) , fd_std ) )
403 m_buffer.resize( 1024U ) ;
421 p.set_value( std::make_pair(rc,output()) ) ;
425 try { p.set_exception( std::current_exception() ) ; }
catch(...) {}
434 std::array<char,64U> discard {} ;
435 char * p = m_buffer.data() ;
436 std::size_t space = m_buffer.size() ;
437 std::size_t size = 0U ;
440 ssize_t n = ::read( m_fd , p?p:discard.data() , p?space:discard.size() ) ;
441 m_read_error = errno ;
442 if( n < 0 && m_read_error == EINTR )
453 m_buffer.resize( std::min(size,m_buffer.size()) ) ;
459 std::size_t nn =
static_cast<std::size_t
>(n) ;
460 nn = std::min( nn , space ) ;
472 m_rc = ::waitpid( m_pid , &m_status , 0 ) ;
474 if( m_rc >= 0 && ( WIFSTOPPED(m_status) || WIFCONTINUED(m_status) ) )
476 else if( m_rc == -1 && m_error == EINTR )
491 if( m_error == ECHILD )
497 else if( m_error || m_read_error )
499 std::ostringstream ss ;
500 ss <<
"errno=" << (m_read_error?m_read_error:m_error) ;
501 throw NewProcess::WaitError( ss.str() ) ;
503 else if( !WIFEXITED(m_status) )
506 std::ostringstream ss ;
507 ss <<
"pid=" << m_pid ;
508 if( WIFSIGNALED(m_status) )
509 ss <<
" signal=" << WTERMSIG(m_status) ;
510 throw NewProcess::ChildError( ss.str() ) ;
514 result = WEXITSTATUS( m_status ) ;
525 if( m_error || m_read_error )
527 else if( !WIFEXITED(m_status) )
528 result = 128 + WTERMSIG(m_status) ;
530 result = WEXITSTATUS( m_status ) ;
537 if( m_fd < 0 || m_read_error != 0 )
540 return { m_buffer.data() , m_buffer.size() } ;
static void put(const std::string &name, const std::string &value)
Sets the environment variable value.
static std::vector< char * > array(const std::string &block)
Returns a pointer array pointing into the given block(), with const-casts.
static void open(std::ofstream &, const Path &)
Calls open() on the given output file stream.
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 value_type * cstr() const noexcept
Returns the path's c-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 the path is empty.
static void closeOtherFiles(int fd_keep=-1)
Closes all open file descriptors except the three standard ones and optionally 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 void inheritStandardFiles()
Makes sure that the standard file descriptors are inherited.
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, std::string_view from, std::string_view 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.
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.