E-MailRelay
gnewprocess.h
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2023 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 ; // cd 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()
99 Identity run_as {Identity::invalid()} ; // see Process::beOrdinaryForExec()
100 bool strict_id {true} ; // dont allow run_as root
101 int exec_error_exit {127} ; // exec failure error code
102 std::string exec_error_format ; // exec failure error message with substitution of strerror and errno
103 FormatFn exec_error_format_fn {nullptr} ; // exec failure error message function passed exec_error_format and errno
104
105 Config & set_env( const Environment & ) ;
106 Config & set_stdin( Fd ) ;
107 Config & set_stdout( Fd ) ;
108 Config & set_stderr( Fd ) ;
109 Config & set_cd( const Path & ) ;
110 Config & set_strict_exe( bool = true ) ;
111 Config & set_exec_search_path( const std::string & ) ;
112 Config & set_run_as( Identity ) ;
113 Config & set_strict_id( bool = true ) ;
114 Config & set_exec_error_exit( int ) ;
115 Config & set_exec_error_format( const std::string & ) ;
116 Config & set_exec_error_format_fn( FormatFn ) ;
117 } ;
118
119 NewProcess( const Path & exe , const G::StringArray & args , const Config & ) ;
120 ///< Constructor. Spawns the given program to run independently in a
121 ///< child process.
122 ///<
123 ///< The child process's stdin, stdout and stderr are connected
124 ///< as directed, but exactly one of stdout and stderr must be the
125 ///< internal pipe since it is used to detect process termination.
126 ///< To inherit the existing file descriptors use Fd(STDIN_FILENO)
127 ///< etc. Using Fd::fd(-1) is equivalent to Fd::devnull().
128 ///<
129 ///< The child process is given the new environment, unless the
130 ///< environment given is empty() in which case the environment is
131 ///< inherited from the calling process (see G::Environment::inherit()).
132 ///<
133 ///< If 'strict_exe' then the program must be given as an absolute path.
134 ///< Otherwise it can be a relative path and the calling process's PATH
135 ///< variable or the 'exec_search_path' is used to find it.
136 ///<
137 ///< If a valid identity is supplied then the child process runs as
138 ///< that identity. If 'strict_id' is also true then the id is not
139 ///< allowed to be root. See G::Process::beOrdinaryForExec().
140 ///<
141 ///< If the exec() fails then the 'exec_error_exit' argument is used as
142 ///< the child process exit code.
143 ///<
144 ///< The internal pipe can be used for error messages in the situation
145 ///< where the exec() in the forked child process fails. This requires
146 ///< that one of the 'exec_error_format' parameters is given; by default
147 ///< nothing is sent over the pipe when the exec() fails.
148 ///<
149 ///< The exec error message is assembled by the given callback function,
150 ///< with the 'exec_error_format' argument passed as its first parameter.
151 ///< The second parameter is the exec() errno. The default callback
152 ///< function does text substitution for "__errno__" and "__strerror__"
153 ///< substrings that appear within the error format string.
154
156 ///< Destructor. Kills the spawned process if the Waitable has
157 ///< not been resolved.
158
159 int id() const noexcept ;
160 ///< Returns the process id.
161
162 NewProcessWaitable & waitable() noexcept ;
163 ///< Returns a reference to the Waitable sub-object so that the caller
164 ///< can wait for the child process to exit.
165
166 void kill( bool yield = false ) noexcept ;
167 ///< Tries to kill the spawned process and optionally yield
168 ///< to a thread that might be waiting on it.
169
170 static std::pair<bool,pid_t> fork() ;
171 ///< A utility function that forks the calling process and returns
172 ///< twice; once in the parent and once in the child. Not implemented
173 ///< on windows. Returns an "is-in-child/child-pid" pair.
174 /// \see G::Daemon
175
176public:
177 NewProcess( const NewProcess & ) = delete ;
178 NewProcess( NewProcess && ) = delete ;
179 NewProcess & operator=( const NewProcess & ) = delete ;
180 NewProcess & operator=( NewProcess && ) = delete ;
181
182private:
183 static std::string execErrorFormat( const std::string & , int ) ;
184
185private:
186 std::unique_ptr<NewProcessImp> m_imp ;
187} ;
188
189//| \class G::NewProcessWaitable
190/// Holds the parameters and future results of a waitpid() system call.
191///
192/// The wait() method can be called from a worker thread and the results
193/// collected by the main thread using get() once the worker thread has
194/// signalled that it has finished. The signalling mechanism is outside
195/// the scope of this class (see std::promise, GNet::FutureEvent).
196///
198{
199public:
201 ///< Default constructor for an object where wait() does nothing
202 ///< and get() returns zero.
203
204 explicit NewProcessWaitable( pid_t pid , int fd = -1 ) ;
205 ///< Constructor taking a posix process-id and optional
206 ///< readable file descriptor. Only used by the unix
207 ///< implementation of G::NewProcess.
208
209 NewProcessWaitable( HANDLE hprocess , HANDLE hpipe , int ) ;
210 ///< Constructor taking process and pipe handles. Only
211 ///< used by the windows implementation of G::NewProcess.
212
213 void assign( pid_t pid , int fd ) ;
214 ///< Reinitialises as if constructed with the given proces-id
215 ///< and file descriptor.
216
217 void assign( HANDLE hprocess , HANDLE hpipe , int ) ;
218 ///< Reinitialises as if constructed with the given proces
219 ///< handle and pipe handle.
220
221 NewProcessWaitable & wait() ;
222 ///< Waits for the process identified by the constructor
223 ///< parameter to exit. Returns *this. This method can be
224 ///< called from a separate worker thread.
225
226 void waitp( std::promise<std::pair<int,std::string>> ) noexcept ;
227 ///< Calls wait() and then sets the given promise with the get() and
228 ///< output() values or an exception:
229 ///< \code
230 ///< std::promise<int,std::string> p ;
231 ///< std::future<int,std::string> f = p.get_future() ;
232 ///< std::thread t( std::bind(&NewProcessWaitable::waitp,waitable,_1) , std::move(p) ) ;
233 ///< f.wait() ;
234 ///< t.join() ;
235 ///< int e = f.get().first ;
236 ///< \endcode
237
238 int get() const ;
239 ///< Returns the result of the wait() as either the process
240 ///< exit code or as a thrown exception. Typically called
241 ///< by the main thread after the wait() worker thread has
242 ///< signalled its completion. Returns zero if there is no
243 ///< process to wait for.
244
245 int get( std::nothrow_t , int exit_code_on_error = 127 ) const noexcept ;
246 ///< Non-throwing overload.
247
248 std::string output() const ;
249 ///< Returns the first bit of child-process output.
250 ///< Used after get().
251
252public:
253 ~NewProcessWaitable() = default ;
254 NewProcessWaitable( const NewProcessWaitable & ) = delete ;
256 NewProcessWaitable & operator=( const NewProcessWaitable & ) = delete ;
257 NewProcessWaitable & operator=( NewProcessWaitable && ) = delete ;
258
259private:
260 std::vector<char> m_buffer ;
261 std::size_t m_data_size {0U} ;
262 HANDLE m_hprocess {0} ;
263 HANDLE m_hpipe {0} ;
264 pid_t m_pid {0} ;
265 int m_fd {-1} ;
266 int m_rc {0} ;
267 int m_status {0} ;
268 int m_error {0} ;
269 int m_read_error {0} ;
270 bool m_test_mode {false} ;
271} ;
272
273inline G::NewProcess::Config & G::NewProcess::Config::set_env( const Environment & e ) { env = e ; return *this ; }
274inline G::NewProcess::Config & G::NewProcess::Config::set_stdin( Fd fd ) { stdin = fd ; return *this ; }
275inline G::NewProcess::Config & G::NewProcess::Config::set_stdout( Fd fd ) { stdout = fd ; return *this ; }
276inline G::NewProcess::Config & G::NewProcess::Config::set_stderr( Fd fd ) { stderr = fd ; return *this ; }
277inline G::NewProcess::Config & G::NewProcess::Config::set_cd( const Path & p ) { cd = p ; return *this ; }
278inline G::NewProcess::Config & G::NewProcess::Config::set_strict_exe( bool b ) { strict_exe = b ; return *this ; }
279inline G::NewProcess::Config & G::NewProcess::Config::set_exec_search_path( const std::string & s ) { exec_search_path = s ; return *this ; }
280inline G::NewProcess::Config & G::NewProcess::Config::set_run_as( Identity i ) { run_as = i ; return *this ; }
281inline G::NewProcess::Config & G::NewProcess::Config::set_strict_id( bool b ) { strict_id = b ; return *this ; }
282inline G::NewProcess::Config & G::NewProcess::Config::set_exec_error_exit( int n ) { exec_error_exit = n ; return *this ; }
283inline G::NewProcess::Config & G::NewProcess::Config::set_exec_error_format( const std::string & s ) { exec_error_format = s ; return *this ; }
284inline G::NewProcess::Config & G::NewProcess::Config::set_exec_error_format_fn( FormatFn f ) { exec_error_format_fn = f ; return *this ; }
285
286#endif
Holds a set of environment variables and also provides static methods to wrap getenv() and putenv().
Definition: genvironment.h:40
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:44
static Identity invalid() noexcept
Returns an invalid identity.
Holds the parameters and future results of a waitpid() system call.
Definition: gnewprocess.h:198
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:73
Low-level classes.
Definition: garg.h:30
std::vector< std::string > StringArray
A std::vector of std::strings.
Definition: gstringarray.h:30
constexpr const char * tx(const char *p)
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