E-MailRelay
gnewprocess.h
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2021 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 "gstrings.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 struct NewProcessConfig ;
43 class Pipe ;
44}
45
46//| \class G::NewProcess
47/// A class for creating new processes.
48///
49/// Eg:
50/// \code
51/// {
52/// NewProcess task( "foo" , args ) ;
53/// auto& waitable = task.waitable() ;
54/// waitable.wait() ;
55/// int rc = waitable.get() ;
56/// std::string s = waitable.output() ;
57/// ...
58/// }
59/// \endcode
60///
61/// \see G::Daemon, G::NewProcessWaitable
62///
64{
65public:
66 G_EXCEPTION( Error , "cannot spawn new process" ) ;
67 G_EXCEPTION( CannotFork , "cannot fork" ) ;
68 G_EXCEPTION( WaitError , "failed waiting for child process" ) ;
69 G_EXCEPTION( ChildError , "child process terminated abnormally" ) ;
70 G_EXCEPTION( Insecure , "refusing to exec while the user-id is zero" ) ;
71 G_EXCEPTION( PipeError , "pipe error" ) ;
72 G_EXCEPTION( InvalidPath , "invalid executable path -- must be absolute" ) ;
73 G_EXCEPTION( InvalidParameter , "invalid parameter" ) ;
74 G_EXCEPTION( CreateProcessError , "CreateProcess error" ) ; // windows
75
76 struct Fd /// Wraps up a file descriptor for passing to G::NewProcess.
77 {
78 bool m_null ;
79 bool m_pipe ;
80 int m_fd ;
81 Fd( bool null_ , bool pipe_ , int fd_ ) : m_null(null_) , m_pipe(pipe_) , m_fd(fd_) {}
82 static Fd pipe() { return {false,true,-1} ; }
83 static Fd devnull() { return {true,false,-1} ; }
84 static Fd fd(int fd_) { return fd_ < 0 ? devnull() : Fd(false,false,fd_) ; }
85 bool operator==( const Fd & other ) const { return m_null == other.m_null && m_pipe == other.m_pipe && m_fd == other.m_fd ; }
86 bool operator!=( const Fd & other ) const { return !(*this == other) ; }
87 } ;
88
89 NewProcess( const Path & exe , const StringArray & args , const Environment & env = Environment::minimal() ,
90 Fd fd_stdin = Fd::devnull() , Fd fd_stdout = Fd::pipe() , Fd fd_stderr = Fd::devnull() ,
91 const G::Path & cd = G::Path() , bool strict_path = true ,
92 Identity run_as_id = Identity::invalid() , bool strict_id = true ,
93 int exec_error_exit = 127 ,
94 const std::string & exec_error_format = std::string() ,
95 std::string (*exec_error_format_fn)(std::string,int) = nullptr ) ;
96 ///< Constructor. Spawns the given program to run independently in a
97 ///< child process.
98 ///<
99 ///< The child process's stdin, stdout and stderr are connected
100 ///< as directed, but exactly one of stdout and stderr must be the
101 ///< internal pipe since it is used to detect process termination.
102 ///< To inherit the existing file descriptors use Fd(STDIN_FILENO)
103 ///< etc. Using Fd::fd(-1) is equivalent to Fd::devnull().
104 ///<
105 ///< The child process is given the new environment, unless the
106 ///< environment given is empty() in which case the environment is
107 ///< inherited from the calling process (see G::Environment::inherit()).
108 ///<
109 ///< If 'strict_path' then the program must be given as an absolute path.
110 ///< Otherwise it can be relative and the calling process's PATH is used
111 ///< to find it.
112 ///<
113 ///< If a valid identity is supplied then the child process runs as
114 ///< that identity. If 'strict_id' is also true then the id is not
115 ///< allowed to be root. See G::Process::beOrdinaryForExec().
116 ///<
117 ///< If the exec() fails then the 'exec_error_exit' argument is used as
118 ///< the child process exit code.
119 ///<
120 ///< The internal pipe can be used for error messages in the situation
121 ///< where the exec() in the forked child process fails. This requires
122 ///< that one of the 'exec_error_format' parameters is given; by default
123 ///< nothing is sent over the pipe when the exec() fails.
124 ///<
125 ///< The exec error message is assembled by the given callback function,
126 ///< with the 'exec_error_format' argument passed as its first parameter.
127 ///< The second parameter is the exec() errno. The default callback
128 ///< function does text substitution for "__errno__" and "__strerror__"
129 ///< substrings that appear within the error format string.
130
131 explicit NewProcess( const NewProcessConfig & ) ;
132 ///< Constructor overload with parameters packaged into a structure.
133
135 ///< Destructor. Kills the spawned process if the Waitable has
136 ///< not been resolved.
137
138 int id() const noexcept ;
139 ///< Returns the process id.
140
141 NewProcessWaitable & waitable() noexcept ;
142 ///< Returns a reference to the Waitable sub-object so that the caller
143 ///< can wait for the child process to exit.
144
145 void kill( bool yield = false ) noexcept ;
146 ///< Tries to kill the spawned process and optionally yield
147 ///< to a thread that might be waiting on it.
148
149 static std::pair<bool,pid_t> fork() ;
150 ///< A utility function that forks the calling process and returns
151 ///< twice; once in the parent and once in the child. Returns
152 ///< an "is-in-child/child-pid" pair.
153 /// \see G::Daemon
154
155public:
156 NewProcess( const NewProcess & ) = delete ;
157 NewProcess( NewProcess && ) = delete ;
158 void operator=( const NewProcess & ) = delete ;
159 void operator=( NewProcess && ) = delete ;
160
161private:
162 static std::string execErrorFormat( const std::string & , int ) ;
163
164private:
165 std::unique_ptr<NewProcessImp> m_imp ;
166} ;
167
168//| \class G::NewProcessWaitable
169/// Holds the parameters and future results of a waitpid() system call,
170/// as performed by the wait() method.
171///
172/// The wait() method can be called from a worker thread and the results
173/// collected by the main thread using get() once the worker thread has
174/// signalled that it has finished. The signalling mechanism is outside
175/// the scope of this class (see std::promise, GNet::FutureEvent).
176///
178{
179public:
181 ///< Default constructor for an object where wait() does nothing
182 ///< and get() returns zero.
183
184 explicit NewProcessWaitable( pid_t pid , int fd = -1 ) ;
185 ///< Constructor taking a posix process-id and optional
186 ///< readable file descriptor. Only used by the unix
187 ///< implementation of G::NewProcess.
188
189 NewProcessWaitable( HANDLE hprocess , HANDLE hpipe , int ) ;
190 ///< Constructor taking process and pipe handles. Only
191 ///< used by the windows implementation of G::NewProcess.
192
193 void assign( pid_t pid , int fd ) ;
194 ///< Reinitialises as if constructed with the given proces-id
195 ///< and file descriptor.
196
197 void assign( HANDLE hprocess , HANDLE hpipe , int ) ;
198 ///< Reinitialises as if constructed with the given proces
199 ///< handle and pipe handle.
200
201 NewProcessWaitable & wait() ;
202 ///< Waits for the process identified by the constructor
203 ///< parameter to exit. Returns *this. This method can be
204 ///< called from a separate worker thread.
205
206 void waitp( std::promise<std::pair<int,std::string>> ) noexcept ;
207 ///< Calls wait() and then sets the given promise with the get() and
208 ///< output() values or an exception:
209 ///< \code
210 ///< std::promise<int,std::string> p ;
211 ///< std::future<int,std::string> f = p.get_future() ;
212 ///< std::thread t( std::bind(&NewProcessWaitable::waitp,waitable,_1) , std::move(p) ) ;
213 ///< f.wait() ;
214 ///< t.join() ;
215 ///< int e = f.get() ;
216 ///< \endcode
217
218 int get() const ;
219 ///< Returns the result of the wait() as either the process
220 ///< exit code or as a thrown exception. Typically called
221 ///< by the main thread after the wait() worker thread has
222 ///< signalled its completion. Returns zero if there is no
223 ///< process to wait for.
224
225 int get( std::nothrow_t , int exit_code_on_error = 127 ) const ;
226 ///< Non-throwing overload.
227
228 std::string output() const ;
229 ///< Returns the first bit of child-process output.
230 ///< Used after get().
231
232public:
233 ~NewProcessWaitable() = default ;
234 NewProcessWaitable( const NewProcessWaitable & ) = delete ;
236 void operator=( const NewProcessWaitable & ) = delete ;
237 void operator=( NewProcessWaitable && ) = delete ;
238
239private:
240 std::vector<char> m_buffer ;
241 std::size_t m_data_size{0U} ;
242 HANDLE m_hprocess{0} ;
243 HANDLE m_hpipe{0} ;
244 pid_t m_pid{0} ;
245 int m_fd{-1} ;
246 int m_rc{0} ;
247 int m_status{0} ;
248 int m_error{0} ;
249 int m_read_error{0} ;
250 bool m_test_mode{false} ;
251} ;
252
253//| \class G::NewProcessConfig
254/// Provides syntactic sugar for the G::NewProcess constructor.
255///
257{
258 explicit NewProcessConfig( const Path & exe ) ;
259 ///< Constructor.
260
261 explicit NewProcessConfig( const ExecutableCommand & cmd ) ;
262 ///< Constructor.
263
264 NewProcessConfig( const Path & exe , const std::string & argv1 ) ;
265 ///< Constructor.
266
267 NewProcessConfig( const Path & exe , const std::string & argv1 , const std::string & argv2 ) ;
268 ///< Constructor.
269
270 NewProcessConfig( const Path & exe , const StringArray & args ) ;
271 ///< Constructor.
272
273 NewProcessConfig & set_args( const StringArray & args ) ;
274 ///< Sets the command-line arguments.
275
276 NewProcessConfig & set_env( const Environment & env ) ;
277 ///< Sets the environment.
278
279 NewProcessConfig & set_fd_stdin( NewProcess::Fd ) ;
280 ///< Sets the standard-input file descriptor.
281
282 NewProcessConfig & set_fd_stdout( NewProcess::Fd ) ;
283 ///< Sets the standard-output file descriptor.
284
285 NewProcessConfig & set_fd_stderr( NewProcess::Fd ) ;
286 ///< Sets the standard-error file descriptor.
287
288 NewProcessConfig & set_cd( const G::Path & ) ;
289 ///< Sets the working directory.
290
291 NewProcessConfig & set_strict_path( bool = true ) ;
292 ///< Sets the 'strict_path' value.
293
294 NewProcessConfig & set_run_as_id( const G::Identity & ) ;
295 ///< Sets the run-as id.
296
297 NewProcessConfig & set_strict_id( bool = true ) ;
298 ///< Sets the 'strict_id' value.
299
300 NewProcessConfig & set_exec_error_exit( int ) ;
301 ///< Sets the 'exec_error_exit' value.
302
303 NewProcessConfig & set_exec_error_format( const std::string & ) ;
304 ///< Sets the 'exec_error_format' value.
305
306 NewProcessConfig & set_exec_error_format_fn( std::string (*)(std::string,int) ) ;
307 ///< Sets the 'exec_error_format_fn' value.
308
309 Path m_path ;
310 StringArray m_args ;
312 NewProcess::Fd m_stdin{NewProcess::Fd::devnull()} ;
313 NewProcess::Fd m_stdout{NewProcess::Fd::pipe()} ;
314 NewProcess::Fd m_stderr{NewProcess::Fd::devnull()} ;
315 Path m_cd ;
316 bool m_strict_path{true} ;
317 Identity m_run_as{Identity::invalid()} ;
318 bool m_strict_id{true} ;
319 int m_exec_error_exit{127} ;
320 std::string m_exec_error_format ;
321 std::string (*m_exec_error_format_fn)( std::string , int ){nullptr} ;
322} ;
323
324inline
326 m_path(exe)
327{
328}
329
330inline
332 m_path(exe) ,
333 m_args(args)
334{
335}
336
337inline
339 m_path(cmd.exe()) ,
340 m_args(cmd.args())
341{
342}
343
344inline
345G::NewProcessConfig::NewProcessConfig( const Path & exe , const std::string & argv1 ) :
346 m_path(exe)
347{
348 m_args.push_back( argv1 ) ;
349}
350
351inline
352G::NewProcessConfig::NewProcessConfig( const Path & exe , const std::string & argv1 , const std::string & argv2 ) :
353 m_path(exe)
354{
355 m_args.push_back( argv1 ) ;
356 m_args.push_back( argv2 ) ;
357}
358
359inline G::NewProcessConfig & G::NewProcessConfig::set_args( const StringArray & args ) { m_args = args ; return *this ; }
360inline G::NewProcessConfig & G::NewProcessConfig::set_env( const Environment & env ) { m_env = env ; return *this ; }
361inline G::NewProcessConfig & G::NewProcessConfig::set_fd_stdin( NewProcess::Fd fd ) { m_stdin = fd ; return *this ; }
362inline G::NewProcessConfig & G::NewProcessConfig::set_fd_stdout( NewProcess::Fd fd ) { m_stdout = fd ; return *this ; }
363inline G::NewProcessConfig & G::NewProcessConfig::set_fd_stderr( NewProcess::Fd fd ) { m_stderr = fd ; return *this ; }
364inline G::NewProcessConfig & G::NewProcessConfig::set_cd( const G::Path & cd ) { m_cd = cd ; return *this ; }
365inline G::NewProcessConfig & G::NewProcessConfig::set_strict_path( bool strict_path ) { m_strict_path = strict_path ; return *this ; }
366inline G::NewProcessConfig & G::NewProcessConfig::set_run_as_id( const G::Identity & run_as ) { m_run_as = run_as ; return *this ; }
367inline G::NewProcessConfig & G::NewProcessConfig::set_strict_id( bool strict_id ) { m_strict_id = strict_id ; return *this ; }
368inline G::NewProcessConfig & G::NewProcessConfig::set_exec_error_exit( int exec_error_exit ) { m_exec_error_exit = exec_error_exit ; return *this ; }
369inline G::NewProcessConfig & G::NewProcessConfig::set_exec_error_format( const std::string & exec_error_format ) { m_exec_error_format = exec_error_format ; return *this ; }
370inline G::NewProcessConfig & G::NewProcessConfig::set_exec_error_format_fn( std::string (*exec_error_format_fn)(std::string,int) ) { m_exec_error_format_fn = exec_error_format_fn ; return *this ; }
371
372inline
375 config.m_path ,
376 config.m_args ,
377 config.m_env ,
378 config.m_stdin ,
379 config.m_stdout ,
380 config.m_stderr ,
381 config.m_cd ,
382 config.m_strict_path ,
383 config.m_run_as ,
384 config.m_strict_id ,
385 config.m_exec_error_exit ,
386 config.m_exec_error_format ,
387 config.m_exec_error_format_fn )
388{
389}
390
391#endif
Holds a set of environment variables and also provides static methods to wrap getenv() and putenv().
Definition: genvironment.h:39
static Environment minimal()
Returns a minimal, safe set of environment variables.
A structure representing an external program, holding a path and a set of arguments.
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:43
static Identity invalid() noexcept
Returns an invalid 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...
Definition: gnewprocess.h:178
NewProcessWaitable(HANDLE hprocess, HANDLE hpipe, int)
Constructor taking process and pipe handles.
void assign(HANDLE hprocess, HANDLE hpipe, int)
Reinitialises as if constructed with the given proces handle and pipe handle.
A class for creating new processes.
Definition: gnewprocess.h:64
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.
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.
Definition: gpath.h:72
Low-level classes.
Definition: galign.h:28
std::vector< std::string > StringArray
A std::vector of std::strings.
Definition: gstrings.h:31
Provides syntactic sugar for the G::NewProcess constructor.
Definition: gnewprocess.h:257
NewProcessConfig & set_exec_error_format_fn(std::string(*)(std::string, int))
Sets the 'exec_error_format_fn' value.
Definition: gnewprocess.h:370
NewProcessConfig & set_args(const StringArray &args)
Sets the command-line arguments.
Definition: gnewprocess.h:359
NewProcessConfig & set_fd_stdin(NewProcess::Fd)
Sets the standard-input file descriptor.
Definition: gnewprocess.h:361
NewProcessConfig & set_cd(const G::Path &)
Sets the working directory.
Definition: gnewprocess.h:364
NewProcessConfig & set_strict_path(bool=true)
Sets the 'strict_path' value.
Definition: gnewprocess.h:365
NewProcessConfig & set_strict_id(bool=true)
Sets the 'strict_id' value.
Definition: gnewprocess.h:367
NewProcessConfig(const Path &exe)
Constructor.
Definition: gnewprocess.h:325
NewProcessConfig & set_fd_stdout(NewProcess::Fd)
Sets the standard-output file descriptor.
Definition: gnewprocess.h:362
NewProcessConfig & set_exec_error_format(const std::string &)
Sets the 'exec_error_format' value.
Definition: gnewprocess.h:369
NewProcessConfig & set_run_as_id(const G::Identity &)
Sets the run-as id.
Definition: gnewprocess.h:366
NewProcessConfig & set_exec_error_exit(int)
Sets the 'exec_error_exit' value.
Definition: gnewprocess.h:368
NewProcessConfig & set_fd_stderr(NewProcess::Fd)
Sets the standard-error file descriptor.
Definition: gnewprocess.h:363
NewProcessConfig & set_env(const Environment &env)
Sets the environment.
Definition: gnewprocess.h:360
Wraps up a file descriptor for passing to G::NewProcess.
Definition: gnewprocess.h:77