libzypp  17.28.8
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 
322  inputfile = fdopen( childStdoutParentFd, "r" );
323  childStdoutParentFd.resetDispose();
324  outputfile = fdopen( childStdinParentFd, "w" );
325  childStdinParentFd.resetDispose();
326 
327  if (!inputfile || !outputfile)
328  {
329  ERR << "Cannot create streams to external program " << argv[0] << endl;
331  }
332  } else {
333  // Fork failed, exit code and status was set by backend
334  return;
335  }
336  }
337 
338  int
340  {
341  if ( !_backend ) {
342  ExternalDataSource::close();
343  return -1;
344  }
345 
346  if ( _backend->isRunning() )
347  {
348  if ( inputFile() )
349  {
350  // Discard any output instead of closing the pipe,
351  // but watch out for the command exiting while some
352  // subprocess keeps the filedescriptor open.
353  setBlocking( false );
354  FILE * inputfile = inputFile();
355  int inputfileFd = ::fileno( inputfile );
356  long delay = 0;
357  do
358  {
359  /* Watch inputFile to see when it has input. */
360  fd_set rfds;
361  FD_ZERO( &rfds );
362  FD_SET( inputfileFd, &rfds );
363 
364  /* Wait up to 1 seconds. */
365  struct timeval tv;
366  tv.tv_sec = (delay < 0 ? 1 : 0);
367  tv.tv_usec = (delay < 0 ? 0 : delay*100000);
368  if ( delay >= 0 && ++delay > 9 )
369  delay = -1;
370  int retval = select( inputfileFd+1, &rfds, NULL, NULL, &tv );
371 
372  if ( retval == -1 )
373  {
374  if ( errno != EINTR ) {
375  ERR << "select error: " << strerror(errno) << endl;
376  break;
377  }
378  }
379  else if ( retval )
380  {
381  // Data is available now.
382  static size_t linebuffer_size = 0; // static because getline allocs
383  static char * linebuffer = 0; // and reallocs if buffer is too small
385  // ::feof check is important as select returns
386  // positive if the file was closed.
387  if ( ::feof( inputfile ) )
388  break;
389  clearerr( inputfile );
390  }
391  else
392  {
393  // No data within time.
394  if ( ! _backend->isRunning() )
395  break;
396  }
397  } while ( true );
398  }
399 
400  // wait for the process to end)
401  _backend->isRunning( true );
402  }
403 
404  ExternalDataSource::close();
405  return _backend->exitStatus();
406  }
407 
408  bool
410  {
411  if ( _backend && _backend->isRunning() )
412  {
413  ::kill( _backend->pid(), SIGKILL);
414  close();
415  }
416  return true;
417  }
418 
419  bool ExternalProgram::kill(int sig)
420  {
421  if ( _backend && _backend->isRunning() )
422  {
423  ::kill( _backend->pid(), sig );
424  }
425  return true;
426  }
427 
428  bool
430  {
431  if ( !_backend ) return false;
432  return _backend->isRunning();
433  }
434 
436  {
437  if ( !running() )
438  return -1;
439  return _backend->pid();
440  }
441 
442  const std::string &ExternalProgram::command() const
443  {
444  if ( !_backend ) {
445  static std::string empty;
446  return empty;
447  }
448  return _backend->executedCommand();
449  }
450 
451  const std::string &ExternalProgram::execError() const
452  {
453  if ( !_backend ) {
454  static std::string empty;
455  return empty;
456  }
457  return _backend->execError();
458  }
459 
460  // origfd will be accessible as newfd and closed (unless they were equal)
461  void ExternalProgram::renumber_fd (int origfd, int newfd)
462  {
463  return zyppng::renumberFd( origfd, newfd );
464  }
465 
466  std::ostream & ExternalProgram::operator>>( std::ostream & out_r )
467  {
468  setBlocking( true );
469  for ( std::string line = receiveLine(); line.length(); line = receiveLine() )
470  out_r << line;
471  return out_r;
472  }
473 
475  //
476  // class ExternalProgramWithStderr
477  //
479 
480  namespace externalprogram
481  {
483  {
484  _fds[R] = _fds[W] = -1;
485 #ifdef HAVE_PIPE2
486  ::pipe2( _fds, O_NONBLOCK );
487 #else
488  ::pipe( _fds );
489  ::fcntl(_fds[R], F_SETFD, O_NONBLOCK );
490  ::fcntl(_fds[W], F_SETFD, O_NONBLOCK );
491 #endif
492  _stderr = ::fdopen( _fds[R], "r" );
493  }
494 
496  {
497  closeW();
498  if ( _stderr )
499  ::fclose( _stderr );
500  }
501  } // namespace externalprogram
502 
503  bool ExternalProgramWithStderr::stderrGetUpTo( std::string & retval_r, const char delim_r, bool returnDelim_r )
504  {
505  if ( ! _stderr )
506  return false;
507  if ( delim_r && ! _buffer.empty() )
508  {
509  // check for delim already in buffer
510  std::string::size_type pos( _buffer.find( delim_r ) );
511  if ( pos != std::string::npos )
512  {
513  retval_r = _buffer.substr( 0, returnDelim_r ? pos+1 : pos );
514  _buffer.erase( 0, pos+1 );
515  return true;
516  }
517  }
518  ::clearerr( _stderr );
519  do {
520  int ch = fgetc( _stderr );
521  if ( ch != EOF )
522  {
523  if ( ch != delim_r || ! delim_r )
524  _buffer.push_back( ch );
525  else
526  {
527  if ( returnDelim_r )
528  _buffer.push_back( delim_r );
529  break;
530  }
531  }
532  else if ( ::feof( _stderr ) )
533  {
534  if ( _buffer.empty() )
535  return false;
536  break;
537  }
538  else if ( errno != EINTR )
539  return false;
540  } while ( true );
541  // HERE: we left after readig at least one char (\n)
542  retval_r.swap( _buffer );
543  _buffer.clear();
544  return true;
545  }
546 
547 
548 } // namespace zypp
void resetDispose()
Set no dispose function.
Definition: AutoDispose.h:164
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:283
#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