libzypp  17.30.2
ExternalProgram.cc
Go to the documentation of this file.
1 /*---------------------------------------------------------------------\
2 | ____ _ __ __ ___ |
3 | |__ / \ / / . \ . \ |
4 | / / \ V /| _/ _/ |
5 | / /__ | | | | | | |
6 | /_____||_| |_| |_| |
7 | |
8 \---------------------------------------------------------------------*/
12 #define _GNU_SOURCE 1 // for ::getline
13 
14 #include <signal.h>
15 #include <errno.h>
16 #include <unistd.h>
17 #include <sys/wait.h>
18 #include <fcntl.h>
19 #include <pty.h> // openpty
20 #include <stdlib.h> // setenv
21 #include <sys/prctl.h> // prctl(), PR_SET_PDEATHSIG
22 
23 #include <cstring> // strsignal
24 #include <iostream>
25 #include <sstream>
26 
27 #include <zypp-core/AutoDispose.h>
28 #include <zypp-core/base/Logger.h>
29 #include <zypp-core/base/String.h>
30 #include <zypp-core/base/Gettext.h>
31 #include <zypp-core/ExternalProgram.h>
33 
34 #include <zypp-core/zyppng/base/private/linuxhelpers_p.h>
35 #include <zypp-core/zyppng/io/private/forkspawnengine_p.h>
36 
37 using std::endl;
38 
39 #undef ZYPP_BASE_LOGGER_LOGGROUP
40 #define ZYPP_BASE_LOGGER_LOGGROUP "zypp::exec"
41 
42 namespace zypp {
43 
45  {}
46 
47 
48  ExternalProgram::ExternalProgram( std::string commandline,
49  Stderr_Disposition stderr_disp,
50  bool use_pty,
51  int stderr_fd,
52  bool default_locale,
53  const Pathname & root )
54  {
55  const char *argv[4];
56  argv[0] = "/bin/sh";
57  argv[1] = "-c";
58  argv[2] = commandline.c_str();
59  argv[3] = 0;
60 
61  start_program( argv, Environment(), stderr_disp, stderr_fd, default_locale, root.c_str(), false, false, use_pty );
62  }
63 
65  Stderr_Disposition stderr_disp,
66  bool use_pty,
67  int stderr_fd,
68  bool default_locale,
69  const Pathname & root )
70  {
71  const char * argvp[argv.size() + 1];
72  unsigned c = 0;
73  for_( i, argv.begin(), argv.end() )
74  {
75  argvp[c] = i->c_str();
76  ++c;
77  }
78  argvp[c] = 0;
79 
80  start_program( argvp, Environment(), stderr_disp, stderr_fd, default_locale, root.c_str(), false, false, use_pty );
81  }
82 
84  const Environment & environment,
85  Stderr_Disposition stderr_disp,
86  bool use_pty,
87  int stderr_fd,
88  bool default_locale,
89  const Pathname & root )
90  {
91  const char * argvp[argv.size() + 1];
92  unsigned c = 0;
93  for_( i, argv.begin(), argv.end() )
94  {
95  argvp[c] = i->c_str();
96  ++c;
97  }
98  argvp[c] = 0;
99 
100  start_program( argvp, environment, stderr_disp, stderr_fd, default_locale, root.c_str(), false, false, use_pty );
101  }
102 
103  ExternalProgram::ExternalProgram( const char *const *argv,
104  Stderr_Disposition stderr_disp,
105  bool use_pty,
106  int stderr_fd,
107  bool default_locale,
108  const Pathname & root )
109  {
110  start_program( argv, Environment(), stderr_disp, stderr_fd, default_locale, root.c_str(), false, false, use_pty );
111  }
112 
113  ExternalProgram::ExternalProgram( const char *const * argv,
114  const Environment & environment,
115  Stderr_Disposition stderr_disp,
116  bool use_pty,
117  int stderr_fd,
118  bool default_locale,
119  const Pathname & root )
120  {
121  start_program( argv, environment, stderr_disp, stderr_fd, default_locale, root.c_str(), false, false, use_pty );
122  }
123 
124 
125  ExternalProgram::ExternalProgram( const char *binpath,
126  const char *const *argv_1,
127  bool use_pty )
128  {
129  int i = 0;
130  while (argv_1[i++])
131  ;
132  const char *argv[i + 1];
133  argv[0] = binpath;
134  memcpy( &argv[1], argv_1, (i - 1) * sizeof (char *) );
135  start_program( argv, Environment(), Normal_Stderr, 1, false, NULL, false, false, use_pty );
136  }
137 
138  ExternalProgram::ExternalProgram( const char *binpath,
139  const char *const *argv_1,
140  const Environment & environment,
141  bool use_pty )
142  {
143  int i = 0;
144  while (argv_1[i++])
145  ;
146  const char *argv[i + 1];
147  argv[0] = binpath;
148  memcpy( &argv[1], argv_1, (i - 1) * sizeof (char *) );
149  start_program( argv, environment, Normal_Stderr, 1, false, NULL, false, false, use_pty );
150  }
151 
153  { }
154 
155 
156 
157  void ExternalProgram::start_program( const char *const *argv,
158  const Environment & environment,
159  Stderr_Disposition stderr_disp,
160  int stderr_fd,
161  bool default_locale,
162  const char * root , bool switch_pgid, bool die_with_parent , bool usePty )
163  {
164  if ( _backend )
165  return;
166 
167  // usePty is only supported by the forking backend
168  if ( usePty ) {
169  DBG << "usePty was set, forcing the ForkSpawnEngine to start external processes" << std::endl;
170  _backend = std::make_unique<zyppng::ForkSpawnEngine>();
171  static_cast<zyppng::ForkSpawnEngine&>(*_backend).setUsePty( true );
172  } else {
173  _backend = zyppng::AbstractSpawnEngine::createDefaultEngine();
174  }
175 
176  // retrieve options at beginning of arglist
177  const char * redirectStdin = nullptr; // <[file]
178  const char * redirectStdout = nullptr; // >[file]
179  const char * chdirTo = nullptr; // #/[path]
180 
181  if ( root )
182  {
183  if ( root[0] == '\0' )
184  {
185  root = nullptr; // ignore empty root
186  }
187  else if ( root[0] == '/' && root[1] == '\0' )
188  {
189  // If root is '/' do not chroot, but chdir to '/'
190  // unless arglist defines another dir.
191  chdirTo = "/";
192  root = nullptr;
193  }
194  }
195 
196  for ( bool strip = false; argv[0] != nullptr; ++argv )
197  {
198  strip = false;
199  switch ( argv[0][0] )
200  {
201  case '<':
202  strip = true;
203  redirectStdin = argv[0]+1;
204  if ( *redirectStdin == '\0' )
205  redirectStdin = "/dev/null";
206  break;
207 
208  case '>':
209  strip = true;
210  redirectStdout = argv[0]+1;
211  if ( *redirectStdout == '\0' )
212  redirectStdout = "/dev/null";
213  break;
214 
215  case '#':
216  strip = true;
217  if ( argv[0][1] == '/' ) // #/[path]
218  chdirTo = argv[0]+1;
219  break;
220  }
221  if ( ! strip )
222  break;
223  }
224 
225  // those are the FDs that the new process will receive
226  // AutoFD will take care of closing them on our side
227  zypp::AutoFD stdinFd = -1;
228  zypp::AutoFD stdoutFd = -1;
229  zypp::AutoFD stderrFd = -1;
230 
231  // those are the fds we will keep, we put them into autofds in case
232  // we need to return early without actually spawning the new process
233  zypp::AutoFD childStdinParentFd = -1;
234  zypp::AutoFD childStdoutParentFd = -1;
235 
236  if ( usePty )
237  {
238 
239  int master_tty, slave_tty; // fds for pair of ttys
240 
241  // Create pair of ttys
242  DBG << "Using ttys for communication with " << argv[0] << endl;
243  if (openpty (&master_tty, &slave_tty, 0, 0, 0) != 0)
244  {
245  _backend->setExecError( str::form( _("Can't open pty (%s)."), strerror(errno) ) );
246  _backend->setExitStatus( 126 );
247  ERR << _backend->execError() << endl;
248  return;
249  }
250 
251  stdinFd = slave_tty;
252  stdoutFd = slave_tty;
253  childStdinParentFd = master_tty;
254  childStdoutParentFd = master_tty;
255  }
256  else
257  {
258  if ( redirectStdin ) {
259  stdinFd = open( redirectStdin, O_RDONLY );
260  } else {
261  int to_external[2];
262  if ( pipe (to_external) != 0 )
263  {
264  _backend->setExecError( str::form( _("Can't open pipe (%s)."), strerror(errno) ) );
265  _backend->setExitStatus( 126 );
266  ERR << _backend->execError() << endl;
267  return;
268  }
269  stdinFd = to_external[0];
270  childStdinParentFd = to_external[1];
271  }
272 
273  if ( redirectStdout ) {
274  stdoutFd = open( redirectStdout, O_WRONLY|O_CREAT|O_APPEND, 0600 );
275  } else {
276 
277  int from_external[2];
278  // Create pair of pipes
279  if ( pipe (from_external) != 0 )
280  {
281  _backend->setExecError( str::form( _("Can't open pipe (%s)."), strerror(errno) ) );
282  _backend->setExitStatus( 126 );
283  ERR << _backend->execError() << endl;
284  return;
285  }
286  stdoutFd = from_external[1];
287  childStdoutParentFd = from_external[0];
288  }
289  }
290 
291  // Handle stderr
292  if (stderr_disp == Discard_Stderr)
293  {
294  stderrFd = open("/dev/null", O_WRONLY);
295  }
296  else if (stderr_disp == Stderr_To_Stdout)
297  {
298  stderrFd = *stdoutFd;
299  //no double close
300  stderrFd.resetDispose();
301  }
302  else if (stderr_disp == Stderr_To_FileDesc)
303  {
304  // Note: We don't have to close anything regarding stderr_fd.
305  // Our caller is responsible for that.
306  stderrFd = stderr_fd;
307  stderrFd.resetDispose();
308  }
309 
310  if ( root )
311  _backend->setChroot( root );
312  if ( chdirTo )
313  _backend->setWorkingDirectory( chdirTo );
314 
315  _backend->setDieWithParent( die_with_parent );
316  _backend->setSwitchPgid( switch_pgid );
317  _backend->setEnvironment( environment );
318  _backend->setUseDefaultLocale( default_locale );
319 
320  if ( _backend->start( argv, stdinFd, stdoutFd, stderrFd ) ) {
321  bool connected = true;
322  if ( childStdoutParentFd != -1 ) {
323  inputfile = fdopen( childStdoutParentFd, "r" );
324  if ( inputfile )
325  childStdoutParentFd.resetDispose();
326  else
327  connected = false;
328  }
329  if ( childStdinParentFd != -1 ) {
330  outputfile = fdopen( childStdinParentFd, "w" );
331  if ( outputfile )
332  childStdinParentFd.resetDispose();
333  else
334  connected = false;
335  }
336  if ( not connected )
337  {
338  ERR << "Cannot create streams to external program " << argv[0] << endl;
340  }
341  } else {
342  // Fork failed, exit code and status was set by backend
343  return;
344  }
345  }
346 
347  int
349  {
350  if ( !_backend ) {
351  ExternalDataSource::close();
352  return -1;
353  }
354 
355  if ( _backend->isRunning() )
356  {
357  if ( inputFile() )
358  {
359  // Discard any output instead of closing the pipe,
360  // but watch out for the command exiting while some
361  // subprocess keeps the filedescriptor open.
362  setBlocking( false );
363  FILE * inputfile = inputFile();
364  int inputfileFd = ::fileno( inputfile );
365  long delay = 0;
366  do
367  {
368  /* Watch inputFile to see when it has input. */
369  fd_set rfds;
370  FD_ZERO( &rfds );
371  FD_SET( inputfileFd, &rfds );
372 
373  /* Wait up to 1 seconds. */
374  struct timeval tv;
375  tv.tv_sec = (delay < 0 ? 1 : 0);
376  tv.tv_usec = (delay < 0 ? 0 : delay*100000);
377  if ( delay >= 0 && ++delay > 9 )
378  delay = -1;
379  int retval = select( inputfileFd+1, &rfds, NULL, NULL, &tv );
380 
381  if ( retval == -1 )
382  {
383  if ( errno != EINTR ) {
384  ERR << "select error: " << strerror(errno) << endl;
385  break;
386  }
387  }
388  else if ( retval )
389  {
390  // Data is available now.
391  static size_t linebuffer_size = 0; // static because getline allocs
392  static char * linebuffer = 0; // and reallocs if buffer is too small
394  // ::feof check is important as select returns
395  // positive if the file was closed.
396  if ( ::feof( inputfile ) )
397  break;
398  clearerr( inputfile );
399  }
400  else
401  {
402  // No data within time.
403  if ( ! _backend->isRunning() )
404  break;
405  }
406  } while ( true );
407  }
408 
409  // wait for the process to end)
410  _backend->isRunning( true );
411  }
412 
413  ExternalDataSource::close();
414  return _backend->exitStatus();
415  }
416 
417  bool
419  {
420  if ( _backend && _backend->isRunning() )
421  {
422  ::kill( _backend->pid(), SIGKILL);
423  close();
424  }
425  return true;
426  }
427 
428  bool ExternalProgram::kill(int sig)
429  {
430  if ( _backend && _backend->isRunning() )
431  {
432  ::kill( _backend->pid(), sig );
433  }
434  return true;
435  }
436 
437  bool
439  {
440  if ( !_backend ) return false;
441  return _backend->isRunning();
442  }
443 
445  {
446  if ( !running() )
447  return -1;
448  return _backend->pid();
449  }
450 
451  const std::string &ExternalProgram::command() const
452  {
453  if ( !_backend ) {
454  static std::string empty;
455  return empty;
456  }
457  return _backend->executedCommand();
458  }
459 
460  const std::string &ExternalProgram::execError() const
461  {
462  if ( !_backend ) {
463  static std::string empty;
464  return empty;
465  }
466  return _backend->execError();
467  }
468 
469  // origfd will be accessible as newfd and closed (unless they were equal)
470  void ExternalProgram::renumber_fd (int origfd, int newfd)
471  {
472  return zyppng::renumberFd( origfd, newfd );
473  }
474 
475  std::ostream & ExternalProgram::operator>>( std::ostream & out_r )
476  {
477  setBlocking( true );
478  for ( std::string line = receiveLine(); line.length(); line = receiveLine() )
479  out_r << line;
480  return out_r;
481  }
482 
484  //
485  // class ExternalProgramWithStderr
486  //
488 
489  namespace externalprogram
490  {
492  {
493  _fds[R] = _fds[W] = -1;
494 #ifdef HAVE_PIPE2
495  ::pipe2( _fds, O_NONBLOCK );
496 #else
497  ::pipe( _fds );
498  ::fcntl(_fds[R], F_SETFD, O_NONBLOCK );
499  ::fcntl(_fds[W], F_SETFD, O_NONBLOCK );
500 #endif
501  _stderr = ::fdopen( _fds[R], "r" );
502  }
503 
505  {
506  closeW();
507  if ( _stderr )
508  ::fclose( _stderr );
509  }
510  } // namespace externalprogram
511 
512  bool ExternalProgramWithStderr::stderrGetUpTo( std::string & retval_r, const char delim_r, bool returnDelim_r )
513  {
514  if ( ! _stderr )
515  return false;
516  if ( delim_r && ! _buffer.empty() )
517  {
518  // check for delim already in buffer
519  std::string::size_type pos( _buffer.find( delim_r ) );
520  if ( pos != std::string::npos )
521  {
522  retval_r = _buffer.substr( 0, returnDelim_r ? pos+1 : pos );
523  _buffer.erase( 0, pos+1 );
524  return true;
525  }
526  }
527  ::clearerr( _stderr );
528  do {
529  int ch = fgetc( _stderr );
530  if ( ch != EOF )
531  {
532  if ( ch != delim_r || ! delim_r )
533  _buffer.push_back( ch );
534  else
535  {
536  if ( returnDelim_r )
537  _buffer.push_back( delim_r );
538  break;
539  }
540  }
541  else if ( ::feof( _stderr ) )
542  {
543  if ( _buffer.empty() )
544  return false;
545  break;
546  }
547  else if ( errno != EINTR )
548  return false;
549  } while ( true );
550  // HERE: we left after readig at least one char (\n)
551  retval_r.swap( _buffer );
552  _buffer.clear();
553  return true;
554  }
555 
556 
557 } // namespace zypp
void resetDispose()
Set no dispose function.
Definition: AutoDispose.h:176
bool stderrGetUpTo(std::string &retval_r, const char delim_r, bool returnDelim_r=false)
Read data up to delim_r from stderr (nonblocking).
ExternalProgram()
Start an external program by giving the arguments as an arry of char *pointers.
const std::string & command() const
The command we're executing.
std::ostream & operator>>(std::ostream &out_r)
Redirect all command output to an ostream.
std::map< std::string, std::string > Environment
For passing additional environment variables to set.
void start_program(const char *const *argv, const Environment &environment, Stderr_Disposition stderr_disp=Normal_Stderr, int stderr_fd=-1, bool default_locale=false, const char *root=NULL, bool switch_pgid=false, bool die_with_parent=false, bool usePty=false)
static void renumber_fd(int origfd, int newfd)
origfd will be accessible as newfd and closed (unless they were equal)
std::vector< std::string > Arguments
bool kill()
Kill the program.
pid_t getpid()
return pid
const std::string & execError() const
Some detail telling why the execution failed, if it failed.
bool running()
Return whether program is running.
int close()
Wait for the progamm to complete.
Stderr_Disposition
Define symbols for different policies on the handling of stderr.
std::unique_ptr< zyppng::AbstractSpawnEngine > _backend
FILE * inputFile() const
Return the input stream.
void setBlocking(bool mode)
Set the blocking mode of the input stream.
std::string receiveLine()
Read one line from the input stream.
const char * c_str() const
String representation.
Definition: Pathname.h:110
std::string getline(std::istream &str)
Read one line from stream.
Definition: IOStream.cc:33
SolvableIdType size_type
Definition: PoolMember.h:126
std::string strerror(int errno_r)
Return string describing the error_r code.
Definition: String.cc:53
std::string form(const char *format,...) __attribute__((format(printf
Printf style construction of std::string.
Definition: String.cc:36
Easy-to use interface to the ZYPP dependency resolver.
Definition: CodePitfalls.doc:2
AutoDispose<int> calling ::close
Definition: AutoDispose.h:297
#define for_(IT, BEG, END)
Convenient for-loops using iterator.
Definition: Easy.h:28
#define _(MSG)
Definition: Gettext.h:37
#define DBG
Definition: Logger.h:95
#define ERR
Definition: Logger.h:98