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