libzypp  17.31.22
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  bool ExternalProgram::waitForExit(std::optional<uint64_t> timeout)
348  {
349  if ( !_backend ) {
350  // no backend means no running progress, return true
351  return true;
352  }
353  return _backend->waitForExit( timeout );
354  }
355 
356  int
358  {
359  if ( !_backend ) {
360  ExternalDataSource::close();
361  return -1;
362  }
363 
364  if ( _backend->isRunning() )
365  {
366  if ( inputFile() )
367  {
368  // Discard any output instead of closing the pipe,
369  // but watch out for the command exiting while some
370  // subprocess keeps the filedescriptor open.
371  setBlocking( false );
372  FILE * inputfile = inputFile();
373  int inputfileFd = ::fileno( inputfile );
374  long delay = 0;
375  do
376  {
377  /* Watch inputFile to see when it has input. */
378  fd_set rfds;
379  FD_ZERO( &rfds );
380  FD_SET( inputfileFd, &rfds );
381 
382  /* Wait up to 1 seconds. */
383  struct timeval tv;
384  tv.tv_sec = (delay < 0 ? 1 : 0);
385  tv.tv_usec = (delay < 0 ? 0 : delay*100000);
386  if ( delay >= 0 && ++delay > 9 )
387  delay = -1;
388  int retval = select( inputfileFd+1, &rfds, NULL, NULL, &tv );
389 
390  if ( retval == -1 )
391  {
392  if ( errno != EINTR ) {
393  ERR << "select error: " << strerror(errno) << endl;
394  break;
395  }
396  }
397  else if ( retval )
398  {
399  // Data is available now.
400  static size_t linebuffer_size = 0; // static because getline allocs
401  static char * linebuffer = 0; // and reallocs if buffer is too small
403  // ::feof check is important as select returns
404  // positive if the file was closed.
405  if ( ::feof( inputfile ) )
406  break;
407  clearerr( inputfile );
408  }
409  else
410  {
411  // No data within time.
412  if ( ! _backend->isRunning() )
413  break;
414  }
415  } while ( true );
416  }
417 
418  // wait for the process to end)
419  _backend->isRunning( true );
420  }
421 
422  ExternalDataSource::close();
423  return _backend->exitStatus();
424  }
425 
426  bool
428  {
429  if ( _backend && _backend->isRunning() )
430  {
431  if ( ::kill( _backend->pid(), SIGKILL) == -1 ) {
432  ERR << "Failed to kill PID " << _backend->pid() << " with error: " << Errno() << std::endl;
433  return false;
434  }
435  close();
436  }
437  return true;
438  }
439 
440  bool ExternalProgram::kill(int sig)
441  {
442  if ( _backend && _backend->isRunning() )
443  {
444  if ( ::kill( _backend->pid(), sig ) == -1 ) {
445  ERR << "Failed to kill PID " << _backend->pid() << " with error: " << Errno() << std::endl;
446  return false;
447  }
448  }
449  return true;
450  }
451 
452  bool
454  {
455  if ( !_backend ) return false;
456  return _backend->isRunning();
457  }
458 
460  {
461  if ( !running() )
462  return -1;
463  return _backend->pid();
464  }
465 
466  const std::string &ExternalProgram::command() const
467  {
468  if ( !_backend ) {
469  static std::string empty;
470  return empty;
471  }
472  return _backend->executedCommand();
473  }
474 
475  const std::string &ExternalProgram::execError() const
476  {
477  if ( !_backend ) {
478  static std::string empty;
479  return empty;
480  }
481  return _backend->execError();
482  }
483 
484  // origfd will be accessible as newfd and closed (unless they were equal)
485  void ExternalProgram::renumber_fd (int origfd, int newfd)
486  {
487  return zyppng::renumberFd( origfd, newfd );
488  }
489 
490  std::ostream & ExternalProgram::operator>>( std::ostream & out_r )
491  {
492  setBlocking( true );
493  for ( std::string line = receiveLine(); line.length(); line = receiveLine() )
494  out_r << line;
495  return out_r;
496  }
497 
499  //
500  // class ExternalProgramWithStderr
501  //
503 
504  namespace externalprogram
505  {
507  {
508  _fds[R] = _fds[W] = -1;
509 #ifdef HAVE_PIPE2
510  ::pipe2( _fds, O_NONBLOCK );
511 #else
512  ::pipe( _fds );
513  ::fcntl(_fds[R], F_SETFD, O_NONBLOCK );
514  ::fcntl(_fds[W], F_SETFD, O_NONBLOCK );
515 #endif
516  _stderr = ::fdopen( _fds[R], "r" );
517  }
518 
520  {
521  closeW();
522  if ( _stderr )
523  ::fclose( _stderr );
524  }
525  } // namespace externalprogram
526 
527  bool ExternalProgramWithStderr::stderrGetUpTo( std::string & retval_r, const char delim_r, bool returnDelim_r )
528  {
529  if ( ! _stderr )
530  return false;
531  if ( delim_r && ! _buffer.empty() )
532  {
533  // check for delim already in buffer
534  std::string::size_type pos( _buffer.find( delim_r ) );
535  if ( pos != std::string::npos )
536  {
537  retval_r = _buffer.substr( 0, returnDelim_r ? pos+1 : pos );
538  _buffer.erase( 0, pos+1 );
539  return true;
540  }
541  }
542  ::clearerr( _stderr );
543  do {
544  int ch = fgetc( _stderr );
545  if ( ch != EOF )
546  {
547  if ( ch != delim_r || ! delim_r )
548  _buffer.push_back( ch );
549  else
550  {
551  if ( returnDelim_r )
552  _buffer.push_back( delim_r );
553  break;
554  }
555  }
556  else if ( ::feof( _stderr ) )
557  {
558  if ( _buffer.empty() )
559  return false;
560  break;
561  }
562  else if ( errno != EINTR )
563  return false;
564  } while ( true );
565  // HERE: we left after readig at least one char (\n)
566  retval_r.swap( _buffer );
567  _buffer.clear();
568  return true;
569  }
570 
571 
572 } // namespace zypp
ExternalProgram()
Start an external program by giving the arguments as an arry of char *pointers.
#define _(MSG)
Definition: Gettext.h:37
std::ostream & operator>>(std::ostream &out_r)
Redirect all command output to an ostream.
bool kill()
Kill the program.
Convenience errno wrapper.
Definition: Errno.h:25
const std::string & execError() const
Some detail telling why the execution failed, if it failed.
std::unique_ptr< zyppng::AbstractSpawnEngine > _backend
#define for_(IT, BEG, END)
Convenient for-loops using iterator.
Definition: Easy.h:28
const char * c_str() const
String representation.
Definition: Pathname.h:110
bool running()
Return whether program is running.
std::string receiveLine()
Read one line from the input stream.
std::string form(const char *format,...) __attribute__((format(printf
Printf style construction of std::string.
Definition: String.cc:36
AutoDispose<int> calling ::close
Definition: AutoDispose.h:301
#define ERR
Definition: Logger.h:98
std::vector< std::string > Arguments
std::string getline(std::istream &str)
Read one line from stream.
Definition: IOStream.cc:33
Stderr_Disposition
Define symbols for different policies on the handling of stderr.
std::map< std::string, std::string > Environment
For passing additional environment variables to set.
void resetDispose()
Set no dispose function.
Definition: AutoDispose.h:180
SolvableIdType size_type
Definition: PoolMember.h:126
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)
bool stderrGetUpTo(std::string &retval_r, const char delim_r, bool returnDelim_r=false)
Read data up to delim_r from stderr (nonblocking).
int close()
Wait for the progamm to complete.
const std::string & command() const
The command we&#39;re executing.
bool waitForExit(std::optional< uint64_t > timeout={})
static void renumber_fd(int origfd, int newfd)
origfd will be accessible as newfd and closed (unless they were equal)
void setBlocking(bool mode)
Set the blocking mode of the input stream.
pid_t getpid()
return pid
FILE * inputFile() const
Return the input stream.
std::string strerror(int errno_r)
Return string describing the error_r code.
Definition: String.cc:53
Easy-to use interface to the ZYPP dependency resolver.
Definition: CodePitfalls.doc:1
#define DBG
Definition: Logger.h:95