E-MailRelay

- Main Page
- Namespaces
- Classes
- Files

- File List
- File Members

gnewprocess_unix.cpp
1 //
2 // Copyright (C) 2001-2018 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 // gnewprocess_unix.cpp
19 //
20
21 #include "gdef.h"
22 #include "glimits.h"
23 #include "gnewprocess.h"
24 #include "gprocess.h"
25 #include "genvironment.h"
26 #include "gfile.h"
27 #include "gidentity.h"
28 #include "gassert.h"
29 #include "gstr.h"
30 #include "glog.h"
31 #include <errno.h>
32 #include <sys/types.h>
33 #include <sys/wait.h>
34 #include <sys/stat.h>
35 #include <fcntl.h> // open()
36 #include <unistd.h> // setuid() etc
37 #include <algorithm> // std::swap()
38 #include <utility> // std::swap()
39 #include <iostream>
40 #include <signal.h> // kill()
41
42 /// \class G::Pipe
43 /// A private implementation class used by G::NewProcess that wraps
44 /// a unix pipe.
45 ///
46 class G::Pipe
47 {
48 public:
49 Pipe() ;
50 ~Pipe() ;
51 void inChild() ; // writer
52 void inParent() ; // reader
53 int fd() const ;
54 void dupTo( int stdxxx ) ; // onto stdout/stderr
55 void write( const std::string & ) ;
56
57 private:
58 int m_fds[2] ;
59 int m_fd ;
60 } ;
61
62 /// \class G::NewProcessImp
63 /// A pimple-pattern implementation class used by G::NewProcess.
64 ///
65 class G::NewProcessImp
66 {
67 public:
68 NewProcessImp( const Path & exe , const StringArray & args ,
69 int stdxxx , bool clean_env , bool strict_path ,
70 Identity run_as_id , bool strict_id ,
71 int exec_error_exit , const std::string & exec_error_format ,
72 std::string (*exec_error_format_fn)(std::string,int) ) ;
73
74 int id() const ;
75 static std::pair<bool,pid_t> fork() ;
76 NewProcessWaitFuture & wait() ;
77 int run( const G::Path & , const StringArray & , bool clean_env , bool strict ) ;
78 void kill() ;
79 static void printError( int , const std::string & s ) ;
80 std::string execErrorFormat( const std::string & format , int errno_ ) ;
81
82 private:
83 Pipe m_pipe ;
84 NewProcessWaitFuture m_wait_future ;
85 pid_t m_child_pid ;
86 bool m_killed ;
87
88 private:
89 NewProcessImp( const NewProcessImp & ) ;
90 void operator=( const NewProcessImp & ) ;
91 } ;
92
93 // ==
94
95 G::NewProcess::NewProcess( const Path & exe , const StringArray & args ,
96 int stdxxx , bool clean_env , bool strict_path ,
97 Identity run_as_id , bool strict_id ,
98 int exec_error_exit , const std::string & exec_error_format ,
99 std::string (*exec_error_format_fn)(std::string,int) ) :
100 m_imp(new NewProcessImp(exe,args,stdxxx,clean_env,strict_path,
101 run_as_id,strict_id,exec_error_exit,exec_error_format,exec_error_format_fn) )
102 {
103 }
104
105 G::NewProcess::~NewProcess()
106 {
107 delete m_imp ;
108 }
109
110 G::NewProcessWaitFuture & G::NewProcess::wait()
111 {
112 return m_imp->wait() ;
113 }
114
115 std::pair<bool,pid_t> G::NewProcess::fork()
116 {
117 return NewProcessImp::fork() ;
118 }
119
120 int G::NewProcess::id() const
121 {
122 return m_imp->id() ;
123 }
124
125 void G::NewProcess::kill()
126 {
127 m_imp->kill() ;
128 }
129
130 // ==
131
132 G::NewProcessImp::NewProcessImp( const Path & exe , const StringArray & args ,
133 int stdxxx , bool clean_env , bool strict_path ,
134 Identity run_as_id , bool strict_id ,
135 int exec_error_exit , const std::string & exec_error_format ,
136 std::string (*exec_error_format_fn)(std::string,int) ) :
137 m_wait_future(0) ,
138 m_child_pid(-1) ,
139 m_killed(false)
140 {
141 // impose sanity
142 G_ASSERT( stdxxx == -1 || stdxxx == 1 || stdxxx == 2 ) ;
143 if( stdxxx == 1 ) stdxxx = STDOUT_FILENO ;
144 else if( stdxxx == 2 ) stdxxx = STDERR_FILENO ;
145
146 // safety checks
147 if( strict_path && exe.isRelative() )
148 throw NewProcess::InvalidPath( exe.str() ) ;
149 if( strict_id && run_as_id != Identity::invalid() &&
150 ( Identity::effective().isRoot() || run_as_id.isRoot() ) )
151 throw NewProcess::Insecure() ;
152
153 // fork
154 std::pair<bool,pid_t> f = fork() ;
155 bool in_child = f.first ;
156 m_child_pid = f.second ;
157
158 if( in_child )
159 {
160 try
161 {
162 // set real id
163 if( run_as_id != Identity::invalid() )
164 Process::beOrdinaryForExec( run_as_id ) ;
165
166 // dup() so writing to stdxxx goes down the pipe
167 m_pipe.inChild() ;
168 Process::closeFilesExcept( m_pipe.fd() ) ;
169 m_pipe.dupTo( stdxxx ) ;
170
171 // restore SIGPIPE handling so that writing to
172 // the closed pipe should terminate the child
173 ::signal( SIGPIPE , SIG_DFL ) ;
174
175 // start a new process group
176 ::setpgrp() ; // feature-tested -- see gdef.h
177
178 // exec -- doesnt normally return from run()
179 int e = run( exe , args , clean_env , strict_path ) ;
180
181 // execve() failed -- write an error message to stdxxx
182 if( exec_error_format_fn != 0 )
183 printError( stdxxx , (*exec_error_format_fn)(exec_error_format,e) ) ;
184 else if( !exec_error_format.empty() )
185 printError( stdxxx , execErrorFormat(exec_error_format,e) ) ;
186 }
187 catch(...)
188 {
189 }
190 ::_exit( exec_error_exit ) ;
191 }
192 else
193 {
194 m_pipe.inParent() ;
195 m_wait_future = NewProcessWaitFuture( m_child_pid , m_pipe.fd() ) ;
196 }
197 }
198
199 std::pair<bool,pid_t> G::NewProcessImp::fork()
200 {
201 std::cout << std::flush ;
202 std::cerr << std::flush ;
203 pid_t rc = ::fork() ;
204 const bool ok = rc != -1 ;
205 if( !ok ) throw NewProcess::CannotFork() ;
206 bool in_child = rc == 0 ;
207 pid_t child_pid = static_cast<pid_t>(rc) ;
208 return std::make_pair( in_child , child_pid ) ;
209 }
210
211 void G::NewProcessImp::printError( int stdxxx , const std::string & s )
212 {
213 // write an exec-failure message back down the pipe
214 G_IGNORE_RETURN( int , ::write( stdxxx , s.c_str() , s.length() ) ) ;
215 }
216
217 int G::NewProcessImp::run( const G::Path & exe , const StringArray & args , bool clean_env , bool strict )
218 {
219 char * env[3U] ;
220 std::string path( "PATH=/usr/bin:/bin" ) ; // no "."
221 std::string ifs( "IFS= \t\n" ) ;
222 env[0U] = const_cast<char*>( path.c_str() ) ;
223 env[1U] = const_cast<char*>( ifs.c_str() ) ;
224 env[2U] = nullptr ;
225
226 char ** argv = new char* [ args.size() + 2U ] ;
227 std::string str_exe = exe.str() ;
228 argv[0U] = const_cast<char*>( str_exe.c_str() ) ;
229 unsigned int argc = 1U ;
230 for( StringArray::const_iterator arg_p = args.begin() ; arg_p != args.end() ; ++arg_p , argc++ )
231 argv[argc] = const_cast<char*>(arg_p->c_str()) ;
232 argv[argc] = nullptr ;
233
234 const std::string exe_str = exe.str() ;
235 const char * exe_p = exe_str.c_str() ;
236
237 int e = 0 ;
238 if( clean_env )
239 {
240 ::execve( exe_p , argv , env ) ;
241 e = Process::errno_() ;
242 }
243 else if( strict )
244 {
245 ::execv( exe_p , argv ) ;
246 e = Process::errno_() ;
247 }
248 else
249 {
250 ::execvp( exe_p , argv ) ;
251 e = Process::errno_() ;
252 }
253
254 delete [] argv ;
255 G_DEBUG( "G::NewProcess::run: execve() returned: errno=" << e << ": " << exe ) ;
256 return e ;
257 }
258
259 int G::NewProcessImp::id() const
260 {
261 return static_cast<int>(m_child_pid) ;
262 }
263
264 G::NewProcessWaitFuture & G::NewProcessImp::wait()
265 {
266 return m_wait_future ;
267 }
268
269 void G::NewProcessImp::kill()
270 {
271 G_DEBUG( "G::NewProcessImp::kill: killing process group " << m_child_pid ) ;
272 if( !m_killed && m_child_pid != -1 )
273 {
274 // kill the group so the pipe is closed in all processes and the read returns zero
275 ::kill( -m_child_pid , SIGTERM ) ;
276 m_killed = true ;
277 }
278 }
279
280 std::string G::NewProcessImp::execErrorFormat( const std::string & format , int errno_ )
281 {
282 std::string result = format ;
283 Str::replaceAll( result , "__errno__" , Str::fromInt(errno_) ) ;
284 Str::replaceAll( result , "__strerror__" , Process::strerror(errno_) ) ;
285 return result ;
286 }
287
288 // ==
289
290 G::Pipe::Pipe() :
291 m_fd(-1)
292 {
293 m_fds[0] = m_fds[1] = -1 ;
294 if( ::socketpair( AF_UNIX , SOCK_STREAM , 0 , m_fds ) < 0 ) // must be a stream to dup() onto stdout
295 throw NewProcess::PipeError() ;
296 G_DEBUG( "G::Pipe::ctor: " << m_fds[0] << " " << m_fds[1] ) ;
297 }
298
299 G::Pipe::~Pipe()
300 {
301 if( m_fds[0] >= 0 ) ::close( m_fds[0] ) ;
302 if( m_fds[1] >= 0 ) ::close( m_fds[1] ) ;
303 }
304
305 void G::Pipe::inChild()
306 {
307 ::close( m_fds[0] ) ; // close read end
308 m_fds[0] = -1 ;
309 m_fd = m_fds[1] ; // writer
310 }
311
312 void G::Pipe::inParent()
313 {
314 ::close( m_fds[1] ) ; // close write end
315 m_fds[1] = -1 ;
316 m_fd = m_fds[0] ; // reader
317 }
318
319 int G::Pipe::fd() const
320 {
321 return m_fd ;
322 }
323
324 void G::Pipe::dupTo( int stdxxx )
325 {
326 if( m_fd != -1 && stdxxx != -1 && m_fd != stdxxx )
327 {
328 if( ::dup2(m_fd,stdxxx) != stdxxx )
329 throw NewProcess::PipeError() ;
330 ::close( m_fd ) ;
331 m_fd = -1 ;
332 m_fds[1] = -1 ;
333
334 int flags = ::fcntl( stdxxx , F_GETFD ) ;
335 flags &= ~FD_CLOEXEC ;
336 ::fcntl( stdxxx , F_SETFD , flags ) ; // no close on exec
337 }
338 }
339
340 // ==
341
342 G::NewProcessWaitFuture::NewProcessWaitFuture() :
343 m_hprocess(0) ,
344 m_pid(0) ,
345 m_fd(-1) ,
346 m_rc(0) ,
347 m_status(0) ,
348 m_error(0) ,
349 m_read_error(0)
350 {
351 }
352
353 G::NewProcessWaitFuture::NewProcessWaitFuture( pid_t pid , int fd ) :
354 m_buffer(1024U) ,
355 m_hprocess(0) ,
356 m_pid(pid) ,
357 m_fd(fd) ,
358 m_rc(0) ,
359 m_status(0) ,
360 m_error(0) ,
361 m_read_error(0)
362 {
363 }
364
365 G::NewProcessWaitFuture & G::NewProcessWaitFuture::run()
366 {
367 // (worker thread - keep it simple - read then wait)
368 {
369 char more[64] ;
370 char * p = &m_buffer[0] ;
371 size_t space = m_buffer.size() ;
372 size_t size = 0U ;
373 while( m_fd >= 0 )
374 {
375 ssize_t n = ::read( m_fd , p?p:more , p?space:sizeof(more) ) ;
376 m_read_error = errno ;
377 if( n < 0 && m_error == EINTR )
378 {
379 ; // keep reading
380 }
381 else if( n < 0 )
382 {
383 m_buffer.clear() ;
384 break ;
385 }
386 else if( n == 0 )
387 {
388 m_buffer.resize( size ) ;
389 m_read_error = 0 ;
390 break ;
391 }
392 else if( p )
393 {
394 p += n ;
395 size += n ;
396 space -= n ;
397 if( space == 0U )
398 p = nullptr ;
399 }
400 }
401 }
402 while( m_pid != 0 )
403 {
404 errno = 0 ;
405 m_rc = ::waitpid( m_pid , &m_status , 0 ) ;
406 m_error = errno ;
407 if( m_rc == -1 && m_error == EINTR )
408 ; // signal in parent -- keep waiting
409 else
410 break ;
411 }
412 return *this ;
413 }
414
415 int G::NewProcessWaitFuture::get()
416 {
417 int result = 0 ;
418 if( m_pid != 0 )
419 {
420 if( m_error || m_read_error )
421 {
422 std::ostringstream ss ;
423 ss << "errno=" << (m_read_error?m_read_error:m_error) ;
424 throw NewProcess::WaitError( ss.str() ) ;
425 }
426 if( ! WIFEXITED(m_status) )
427 {
428 // uncaught signal or stopped
429 std::ostringstream ss ;
430 ss << "status=" << m_status ;
431 throw NewProcess::ChildError( ss.str() ) ;
432 }
433 result = WEXITSTATUS(m_status) ;
434 }
435 return result ;
436 }
437
438 std::string G::NewProcessWaitFuture::output()
439 {
440 if( m_fd < 0 || m_read_error != 0 )
441 return std::string() ;
442 else
443 return std::string( &m_buffer[0] , m_buffer.size() ) ;
444 }
445
G::NewProcessWaitFuture::get
int get()
Returns the result of the run() as either the process exit code or as a thrown exception.
Definition: gnewprocess_unix.cpp:415
G::NewProcess::kill
void kill()
Tries to kill the spawned process.
Definition: gnewprocess_unix.cpp:125
G::Path::str
std::string str() const
Returns the path string.
Definition: gpath.cpp:290
G::Identity::isRoot
bool isRoot() const
Returns true if the userid is zero.
Definition: gidentity_unix.cpp:132
glog.h
G::Identity::invalid
static Identity invalid()
Returns an invalid identity.
Definition: gidentity_unix.cpp:107
gidentity.h
G::NewProcessWaitFuture
A class that holds the parameters and the results of a waitpid() system call.
Definition: gnewprocess.h:144
G::Identity
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:42
G::StringArray
std::vector< std::string > StringArray
A std::vector of std::strings.
Definition: gstrings.h:33
G::Identity::effective
static Identity effective()
Returns the current effective identity.
Definition: gidentity_unix.cpp:91
G::NewProcess::id
int id() const
Returns the process id.
Definition: gnewprocess_unix.cpp:120
G::Str::fromInt
static std::string fromInt(int i)
Converts int 'i' to a string.
Definition: gstr.cpp:332
G::Path::isRelative
bool isRelative() const
Returns true if the path is a relative path.
Definition: gpath.cpp:305
G::NewProcess::fork
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...
Definition: gnewprocess_unix.cpp:115
G::NewProcess::~NewProcess
~NewProcess()
Destructor.
Definition: gnewprocess_unix.cpp:105
G::NewProcessWaitFuture::output
std::string output()
Returns the first bit of child-process output.
Definition: gnewprocess_unix.cpp:438
glimits.h
G::NewProcessWaitFuture::run
NewProcessWaitFuture & run()
Waits for the process identified by the constructor parameter to exit.
Definition: gnewprocess_unix.cpp:365
gstr.h
gassert.h
G::Str::replaceAll
static unsigned int replaceAll(std::string &s, const std::string &from, const std::string &to)
Does a global replace on string 's', replacing all occurances of sub-string 'from' with 'to'...
Definition: gstr.cpp:195
G::NewProcess::wait
NewProcessWaitFuture & wait()
Returns a reference to the WaitFuture sub-object so that the caller can wait for the child process to...
Definition: gnewprocess_unix.cpp:110
G::NewProcessWaitFuture::NewProcessWaitFuture
NewProcessWaitFuture()
Default constructor for an object where run() does nothing and get() returns zero.
Definition: gnewprocess_unix.cpp:342
gfile.h
gprocess.h
G::NewProcessImp
A pimple-pattern implementation class used by G::NewProcess.
Definition: gnewprocess_unix.cpp:65
gnewprocess.h
genvironment.h
G::NewProcess::NewProcess
NewProcess(const Path &exe, const StringArray &args, int capture_stdxxx=1, bool clean_environment=true, 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)=0)
Constructor.
Definition: gnewprocess_unix.cpp:95
G::Path
A Path object represents a file system path.
Definition: gpath.h:72
G::Pipe
A private implementation class used by G::NewProcess that wraps a unix pipe.
Definition: gnewprocess_unix.cpp:46