E-MailRelay
gnewprocess.h
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2024 Graeme Walker <graeme_walker@users.sourceforge.net>
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16// ===
17///
18/// \file gnewprocess.h
19///
20
21#ifndef G_NEW_PROCESS_H
22#define G_NEW_PROCESS_H
23
24#include "gdef.h"
25#include "gexception.h"
26#include "genvironment.h"
27#include "gexecutablecommand.h"
28#include "gidentity.h"
29#include "gprocess.h"
30#include "gpath.h"
31#include "gstringarray.h"
32#include <memory>
33#include <new>
34#include <string>
35#include <future>
36
37namespace G
38{
39 class NewProcess ;
40 class NewProcessImp ;
41 class NewProcessWaitable ;
42}
43
44//| \class G::NewProcess
45/// A class for creating new processes.
46///
47/// Eg:
48/// \code
49/// {
50/// NewProcess task( "exe" , args , {} ) ;
51/// auto& waitable = task.waitable() ;
52/// waitable.wait() ;
53/// int rc = waitable.get() ;
54/// std::string s = waitable.output() ;
55/// ...
56/// }
57/// \endcode
58///
59/// \see G::Daemon, G::NewProcessWaitable
60///
62{
63public:
64 G_EXCEPTION( Error , tx("cannot spawn new process") )
65 G_EXCEPTION( CannotFork , tx("cannot fork") )
66 G_EXCEPTION( WaitError , tx("failed waiting for child process") )
67 G_EXCEPTION( ChildError , tx("child process terminated abnormally") )
68 G_EXCEPTION( Insecure , tx("refusing to exec while the user-id is zero") )
69 G_EXCEPTION( PipeError , tx("pipe error") )
70 G_EXCEPTION( InvalidPath , tx("invalid executable path -- must be absolute") )
71 G_EXCEPTION( InvalidParameter , tx("invalid parameter") )
72 G_EXCEPTION( CreateProcessError , tx("CreateProcess error") ) ; // windows
73 G_EXCEPTION( SystemError , tx("system error") ) ; // windows
74
75 struct Fd /// Wraps up a file descriptor for passing to G::NewProcess.
76 {
77 bool m_null ;
78 bool m_pipe ;
79 int m_fd ;
80 Fd( bool null_ , bool pipe_ , int fd_ ) : m_null(null_) , m_pipe(pipe_) , m_fd(fd_) {}
81 static Fd pipe() { return {false,true,-1} ; }
82 static Fd devnull() { return {true,false,-1} ; }
83 static Fd fd(int fd_) { return fd_ < 0 ? devnull() : Fd(false,false,fd_) ; }
84 bool operator==( const Fd & other ) const { return m_null == other.m_null && m_pipe == other.m_pipe && m_fd == other.m_fd ; }
85 bool operator!=( const Fd & other ) const { return !(*this == other) ; }
86 } ;
87
88 struct Config /// Configuration structure for G::NewProcess.
89 {
90 using Fd = NewProcess::Fd ;
91 using FormatFn = std::string (*)(std::string,int) ;
92 Environment env {Environment::minimal()} ; // execve() envp parameter
93 NewProcess::Fd stdin {Fd::devnull()} ;
94 NewProcess::Fd stdout {Fd::pipe()} ;
95 NewProcess::Fd stderr {Fd::devnull()} ;
96 Path cd ; // change directory in child process before exec
97 bool strict_exe {true} ; // require 'exe' is absolute
98 std::string exec_search_path ; // PATH in child process before execvpe() -- not windows
99 Identity run_as {Identity::invalid()} ; // see Process::beOrdinaryForExec() -- not windows
100 bool strict_id {true} ; // dont allow run_as root -- not windows
101 bool close_other_fds {true} ; // close non-standard file descriptors -- not windows
102 int exec_error_exit {127} ; // exec failure error code -- not windows
103 std::string exec_error_format ; // exec failure error message with substitution of strerror and errno
104 FormatFn exec_error_format_fn {nullptr} ; // exec failure error message function passed exec_error_format and errno
105 HANDLE keep_handle_1 {HNULL} ; // extra handle to keep -- windows only
106 HANDLE keep_handle_2 {HNULL} ; // extra handle to keep -- windows only
107
108 Config & set_env( const Environment & ) ;
109 Config & set_stdin( Fd ) noexcept ;
110 Config & set_stdout( Fd ) noexcept ;
111 Config & set_stderr( Fd ) noexcept ;
112 Config & set_cd( const Path & ) ;
113 Config & set_strict_exe( bool = true ) noexcept ;
114 Config & set_exec_search_path( const std::string & ) ;
115 Config & set_run_as( Identity ) ;
116 Config & set_strict_id( bool = true ) noexcept ;
117 Config & set_close_other_fds( bool = true ) noexcept ;
118 Config & set_exec_error_exit( int ) noexcept ;
119 Config & set_exec_error_format( const std::string & ) ;
120 Config & set_exec_error_format_fn( FormatFn ) ;
121 } ;
122
123 NewProcess( const Path & exe , const G::StringArray & args , const Config & ) ;
124 ///< Constructor. Spawns the given program to run independently in a
125 ///< child process.
126 ///<
127 ///< The child process's stdin, stdout and stderr are connected
128 ///< as directed, but exactly one of stdout and stderr must be the
129 ///< internal pipe since it is used to detect process termination.
130 ///< To inherit the existing file descriptors use Fd(STDIN_FILENO)
131 ///< etc. Using Fd::fd(-1) is equivalent to Fd::devnull().
132 ///<
133 ///< The child process is given the new environment, unless the
134 ///< environment given is empty() in which case the environment is
135 ///< inherited from the calling process (see G::Environment::inherit()).
136 ///<
137 ///< If 'strict_exe' then the program must be given as an absolute path.
138 ///< Otherwise it can be a relative path and the calling process's PATH
139 ///< variable or the 'exec_search_path' is used to find it.
140 ///<
141 ///< If a valid identity is supplied then the child process runs as
142 ///< that identity. If 'strict_id' is also true then the id is not
143 ///< allowed to be root. See G::Process::beOrdinaryForExec().
144 ///<
145 ///< If the exec() fails on Unix it is reported asynchronously with
146 ///< the 'exec_error_exit' argument used as the child process exit
147 ///< value; on Windows a CreateProcess() failure throws an exception.
148 ///<
149 ///< The internal pipe can be used for error messages in the situation
150 ///< where the exec() in the forked child process fails. This requires
151 ///< 'exec_error_format' or 'exec_error_format_fn' to be given; by
152 ///< default nothing is sent over the pipe when the exec() fails.
153 ///<
154 ///< If 'exec_error_format_fn' is given then the error message is
155 ///< assembled by passing it 'exec_error_format' as its first parameter
156 ///< and the errno as the second parameter. If the 'exec_error_format'
157 ///< string is given without 'exec_error_format_fn' then it is used
158 ///< as the error message after substitution of any "__errno__" and
159 ///< "__strerror__" sub-strings.
160
162 ///< Destructor. Kills the spawned process if the Waitable has
163 ///< not been resolved.
164
165 int id() const noexcept ;
166 ///< Returns the process id.
167
168 NewProcessWaitable & waitable() noexcept ;
169 ///< Returns a reference to the Waitable sub-object so that the caller
170 ///< can wait for the child process to exit.
171
172 void kill( bool yield = false ) noexcept ;
173 ///< Tries to kill the spawned process and optionally yield
174 ///< to a thread that might be waiting on it.
175
176 static std::pair<bool,pid_t> fork() ;
177 ///< A utility function that forks the calling process and returns
178 ///< twice; once in the parent and once in the child. Not implemented
179 ///< on windows. Returns an "is-in-child/child-pid" pair.
180 /// \see G::Daemon
181
182public:
183 NewProcess( const NewProcess & ) = delete ;
184 NewProcess( NewProcess && ) = delete ;
185 NewProcess & operator=( const NewProcess & ) = delete ;
186 NewProcess & operator=( NewProcess && ) = delete ;
187
188private:
189 static std::string execErrorFormat( const std::string & , int ) ;
190
191private:
192 std::unique_ptr<NewProcessImp> m_imp ;
193} ;
194
195//| \class G::NewProcessWaitable
196/// Holds the parameters and future results of a waitpid() system call.
197///
198/// The wait() method can be called from a worker thread and the results
199/// collected by the main thread using get() once the worker thread has
200/// signalled that it has finished. The signalling mechanism is outside
201/// the scope of this class (see std::promise, GNet::FutureEvent).
202///
204{
205public:
207 ///< Default constructor for an object where wait() does nothing
208 ///< and get() returns zero.
209
210 explicit NewProcessWaitable( pid_t pid , int fd = -1 ) ;
211 ///< Constructor taking a posix process-id and optional
212 ///< readable file descriptor. Only used by the unix
213 ///< implementation of G::NewProcess.
214
215 NewProcessWaitable( HANDLE hprocess , HANDLE hpipe , int ) ;
216 ///< Constructor taking process and pipe handles. Only
217 ///< used by the windows implementation of G::NewProcess.
218
219 void assign( pid_t pid , int fd ) ;
220 ///< Reinitialises as if constructed with the given proces-id
221 ///< and file descriptor.
222
223 void assign( HANDLE hprocess , HANDLE hpipe , int ) ;
224 ///< Reinitialises as if constructed with the given proces
225 ///< handle and pipe handle.
226
227 NewProcessWaitable & wait() ;
228 ///< Waits for the process identified by the constructor
229 ///< parameter to exit. Returns *this. This method can be
230 ///< called from a separate worker thread.
231
232 void waitp( std::promise<std::pair<int,std::string>> ) noexcept ;
233 ///< Calls wait() and then sets the given promise with the get() and
234 ///< output() values or an exception:
235 ///< \code
236 ///< std::promise<int,std::string> p ;
237 ///< std::future<int,std::string> f = p.get_future() ;
238 ///< std::thread t( std::bind(&NewProcessWaitable::waitp,waitable,_1) , std::move(p) ) ;
239 ///< f.wait() ;
240 ///< t.join() ;
241 ///< int e = f.get().first ;
242 ///< \endcode
243
244 int get() const ;
245 ///< Returns the result of the wait() as either the process
246 ///< exit code or as a thrown exception. Typically called
247 ///< by the main thread after the wait() worker thread has
248 ///< signalled its completion. Returns zero if there is no
249 ///< process to wait for.
250
251 int get( std::nothrow_t , int exit_code_on_error = 127 ) const noexcept ;
252 ///< Non-throwing overload.
253
254 std::string output() const ;
255 ///< Returns the first bit of child-process output.
256 ///< Used after get().
257
258public:
259 ~NewProcessWaitable() = default ;
260 NewProcessWaitable( const NewProcessWaitable & ) = delete ;
262 NewProcessWaitable & operator=( const NewProcessWaitable & ) = delete ;
263 NewProcessWaitable & operator=( NewProcessWaitable && ) = delete ;
264
265private:
266 std::vector<char> m_buffer ;
267 std::size_t m_data_size {0U} ;
268 HANDLE m_hprocess {0} ;
269 HANDLE m_hpipe {0} ;
270 pid_t m_pid {0} ;
271 int m_fd {-1} ;
272 int m_rc {0} ;
273 int m_status {0} ;
274 int m_error {0} ;
275 int m_read_error {0} ;
276 bool m_test_mode {false} ;
277} ;
278
279inline G::NewProcess::Config & G::NewProcess::Config::set_env( const Environment & e ) { env = e ; return *this ; }
280inline G::NewProcess::Config & G::NewProcess::Config::set_stdin( Fd fd ) noexcept { stdin = fd ; return *this ; }
281inline G::NewProcess::Config & G::NewProcess::Config::set_stdout( Fd fd ) noexcept { stdout = fd ; return *this ; }
282inline G::NewProcess::Config & G::NewProcess::Config::set_stderr( Fd fd ) noexcept { stderr = fd ; return *this ; }
283inline G::NewProcess::Config & G::NewProcess::Config::set_cd( const Path & p ) { cd = p ; return *this ; }
284inline G::NewProcess::Config & G::NewProcess::Config::set_strict_exe( bool b ) noexcept { strict_exe = b ; return *this ; }
285inline G::NewProcess::Config & G::NewProcess::Config::set_exec_search_path( const std::string & s ) { exec_search_path = s ; return *this ; }
286inline G::NewProcess::Config & G::NewProcess::Config::set_run_as( Identity i ) { run_as = i ; return *this ; }
287inline G::NewProcess::Config & G::NewProcess::Config::set_strict_id( bool b ) noexcept { strict_id = b ; return *this ; }
288inline G::NewProcess::Config & G::NewProcess::Config::set_close_other_fds( bool b ) noexcept { close_other_fds = b ; return *this ; }
289inline G::NewProcess::Config & G::NewProcess::Config::set_exec_error_exit( int n ) noexcept { exec_error_exit = n ; return *this ; }
290inline G::NewProcess::Config & G::NewProcess::Config::set_exec_error_format( const std::string & s ) { exec_error_format = s ; return *this ; }
291inline G::NewProcess::Config & G::NewProcess::Config::set_exec_error_format_fn( FormatFn f ) { exec_error_format_fn = f ; return *this ; }
292
293#endif
Holds a set of environment variables and also provides static methods to wrap getenv() and putenv().
Definition: genvironment.h:42
static Environment minimal(bool sbin=false)
Returns a minimal, safe set of environment variables.
A combination of user-id and group-id, with a very low-level interface to the get/set/e/uid/gid funct...
Definition: gidentity.h:45
static Identity invalid() noexcept
Returns an invalid identity.
Holds the parameters and future results of a waitpid() system call.
Definition: gnewprocess.h:204
A class for creating new processes.
Definition: gnewprocess.h:62
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()
Destructor.
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.
Definition: gpath.h:82
Low-level classes.
Definition: garg.h:36
std::vector< std::string > StringArray
A std::vector of std::strings.
Definition: gstringarray.h:30
constexpr const char * tx(const char *p) noexcept
A briefer alternative to G::gettext_noop().
Definition: ggettext.h:84
STL namespace.
Configuration structure for G::NewProcess.
Definition: gnewprocess.h:89
Wraps up a file descriptor for passing to G::NewProcess.
Definition: gnewprocess.h:76