libzypp  15.28.6
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 
22 #include <cstring> // strsignal
23 #include <iostream>
24 #include <sstream>
25 
26 #include "zypp/base/Logger.h"
27 #include "zypp/base/String.h"
28 #include "zypp/base/Gettext.h"
29 #include "zypp/ExternalProgram.h"
30 
31 using namespace std;
32 
33 namespace zypp {
34 
35  ExternalProgram::ExternalProgram()
36  : use_pty (false)
37  , pid( -1 )
38  {}
39 
40 
41  ExternalProgram::ExternalProgram( std::string commandline,
42  Stderr_Disposition stderr_disp,
43  bool use_pty,
44  int stderr_fd,
45  bool default_locale,
46  const Pathname & root )
47  : use_pty (use_pty)
48  , pid( -1 )
49  {
50  const char *argv[4];
51  argv[0] = "/bin/sh";
52  argv[1] = "-c";
53  argv[2] = commandline.c_str();
54  argv[3] = 0;
55 
56  start_program( argv, Environment(), stderr_disp, stderr_fd, default_locale, root.c_str() );
57  }
58 
59 
61  Stderr_Disposition stderr_disp,
62  bool use_pty,
63  int stderr_fd,
64  bool default_locale,
65  const Pathname & root )
66  : use_pty (use_pty)
67  , pid( -1 )
68  {
69  const char * argvp[argv.size() + 1];
70  unsigned c = 0;
71  for_( i, argv.begin(), argv.end() )
72  {
73  argvp[c] = i->c_str();
74  ++c;
75  }
76  argvp[c] = 0;
77 
78  start_program( argvp, Environment(), stderr_disp, stderr_fd, default_locale, root.c_str() );
79  }
80 
81 
83  const Environment & environment,
84  Stderr_Disposition stderr_disp,
85  bool use_pty,
86  int stderr_fd,
87  bool default_locale,
88  const Pathname & root )
89  : use_pty (use_pty)
90  , pid( -1 )
91  {
92  const char * argvp[argv.size() + 1];
93  unsigned c = 0;
94  for_( i, argv.begin(), argv.end() )
95  {
96  argvp[c] = i->c_str();
97  ++c;
98  }
99  argvp[c] = 0;
100 
101  start_program( argvp, environment, stderr_disp, stderr_fd, default_locale, root.c_str() );
102  }
103 
104 
105 
106  ExternalProgram::ExternalProgram( const char *const *argv,
107  Stderr_Disposition stderr_disp,
108  bool use_pty,
109  int stderr_fd,
110  bool default_locale,
111  const Pathname & root )
112  : use_pty (use_pty)
113  , pid( -1 )
114  {
115  start_program( argv, Environment(), stderr_disp, stderr_fd, default_locale, root.c_str() );
116  }
117 
118 
119  ExternalProgram::ExternalProgram( const char *const * argv,
120  const Environment & environment,
121  Stderr_Disposition stderr_disp,
122  bool use_pty,
123  int stderr_fd,
124  bool default_locale,
125  const Pathname & root )
126  : use_pty (use_pty)
127  , pid( -1 )
128  {
129  start_program( argv, environment, stderr_disp, stderr_fd, default_locale, root.c_str() );
130  }
131 
132 
133  ExternalProgram::ExternalProgram( const char *binpath,
134  const char *const *argv_1,
135  bool use_pty )
136  : use_pty (use_pty)
137  , pid( -1 )
138  {
139  int i = 0;
140  while (argv_1[i++])
141  ;
142  const char *argv[i + 1];
143  argv[0] = binpath;
144  memcpy( &argv[1], argv_1, (i - 1) * sizeof (char *) );
145  start_program( argv, Environment() );
146  }
147 
148 
149  ExternalProgram::ExternalProgram( const char *binpath,
150  const char *const *argv_1,
151  const Environment & environment,
152  bool use_pty )
153  : use_pty (use_pty)
154  , pid( -1 )
155  {
156  int i = 0;
157  while (argv_1[i++])
158  ;
159  const char *argv[i + 1];
160  argv[0] = binpath;
161  memcpy( &argv[1], argv_1, (i - 1) * sizeof (char *) );
162  start_program( argv, environment );
163  }
164 
165 
167  {}
168 
169 
170 
171  void ExternalProgram::start_program( const char *const *argv,
172  const Environment & environment,
173  Stderr_Disposition stderr_disp,
174  int stderr_fd,
175  bool default_locale,
176  const char * root )
177  {
178  pid = -1;
179  _exitStatus = 0;
180  int to_external[2], from_external[2]; // fds for pair of pipes
181  int master_tty, slave_tty; // fds for pair of ttys
182 
183  // retrieve options at beginning of arglist
184  const char * redirectStdin = nullptr; // <[file]
185  const char * redirectStdout = nullptr; // >[file]
186  const char * chdirTo = nullptr; // #/[path]
187 
188  if ( root )
189  {
190  if ( root[0] == '\0' )
191  {
192  root = nullptr; // ignore empty root
193  }
194  else if ( root[0] == '/' && root[1] == '\0' )
195  {
196  // If root is '/' do not chroot, but chdir to '/'
197  // unless arglist defines another dir.
198  chdirTo = "/";
199  root = nullptr;
200  }
201  }
202 
203  for ( bool strip = false; argv[0]; ++argv )
204  {
205  strip = false;
206  switch ( argv[0][0] )
207  {
208  case '<':
209  strip = true;
210  redirectStdin = argv[0]+1;
211  if ( *redirectStdin == '\0' )
212  redirectStdin = "/dev/null";
213  break;
214 
215  case '>':
216  strip = true;
217  redirectStdout = argv[0]+1;
218  if ( *redirectStdout == '\0' )
219  redirectStdout = "/dev/null";
220  break;
221 
222  case '#':
223  strip = true;
224  if ( argv[0][1] == '/' ) // #/[path]
225  chdirTo = argv[0]+1;
226  break;
227  }
228  if ( ! strip )
229  break;
230  }
231 
232  // do not remove the single quotes around every argument, copy&paste of
233  // command to shell will not work otherwise!
234  {
235  stringstream cmdstr;
236  for (int i = 0; argv[i]; i++)
237  {
238  if (i>0) cmdstr << ' ';
239  cmdstr << '\'';
240  cmdstr << argv[i];
241  cmdstr << '\'';
242  }
243  if ( redirectStdin )
244  cmdstr << " < '" << redirectStdin << "'";
245  if ( redirectStdout )
246  cmdstr << " > '" << redirectStdout << "'";
247  _command = cmdstr.str();
248  }
249  DBG << "Executing " << _command << endl;
250 
251 
252  if (use_pty)
253  {
254  // Create pair of ttys
255  DBG << "Using ttys for communication with " << argv[0] << endl;
256  if (openpty (&master_tty, &slave_tty, 0, 0, 0) != 0)
257  {
258  _execError = str::form( _("Can't open pty (%s)."), strerror(errno) );
259  _exitStatus = 126;
260  ERR << _execError << endl;
261  return;
262  }
263  }
264  else
265  {
266  // Create pair of pipes
267  if (pipe (to_external) != 0 || pipe (from_external) != 0)
268  {
269  _execError = str::form( _("Can't open pipe (%s)."), strerror(errno) );
270  _exitStatus = 126;
271  ERR << _execError << endl;
272  return;
273  }
274  }
275 
276  // Create module process
277  if ((pid = fork()) == 0)
278  {
280  // Don't write to the logfile after fork!
282  if (use_pty)
283  {
284  setsid();
285  if(slave_tty != 1)
286  dup2 (slave_tty, 1); // set new stdout
287  renumber_fd (slave_tty, 0); // set new stdin
288  ::close(master_tty); // Belongs to father process
289 
290  // We currently have no controlling terminal (due to setsid).
291  // The first open call will also set the new ctty (due to historical
292  // unix guru knowledge ;-) )
293 
294  char name[512];
295  ttyname_r(slave_tty, name, sizeof(name));
296  ::close(open(name, O_RDONLY));
297  }
298  else
299  {
300  renumber_fd (to_external[0], 0); // set new stdin
301  ::close(from_external[0]); // Belongs to father process
302 
303  renumber_fd (from_external[1], 1); // set new stdout
304  ::close(to_external [1]); // Belongs to father process
305  }
306 
307  if ( redirectStdin )
308  {
309  ::close( 0 );
310  int inp_fd = open( redirectStdin, O_RDONLY );
311  dup2( inp_fd, 0 );
312  }
313 
314  if ( redirectStdout )
315  {
316  ::close( 1 );
317  int inp_fd = open( redirectStdout, O_WRONLY|O_CREAT|O_APPEND, 0600 );
318  dup2( inp_fd, 1 );
319  }
320 
321  // Handle stderr
322  if (stderr_disp == Discard_Stderr)
323  {
324  int null_fd = open("/dev/null", O_WRONLY);
325  dup2(null_fd, 2);
326  ::close(null_fd);
327  }
328  else if (stderr_disp == Stderr_To_Stdout)
329  {
330  dup2(1, 2);
331  }
332  else if (stderr_disp == Stderr_To_FileDesc)
333  {
334  // Note: We don't have to close anything regarding stderr_fd.
335  // Our caller is responsible for that.
336  dup2 (stderr_fd, 2);
337  }
338 
339  for ( Environment::const_iterator it = environment.begin(); it != environment.end(); ++it ) {
340  setenv( it->first.c_str(), it->second.c_str(), 1 );
341  }
342 
343  if(default_locale)
344  setenv("LC_ALL","C",1);
345 
346  if(root)
347  {
348  if(chroot(root) == -1)
349  {
350  _execError = str::form( _("Can't chroot to '%s' (%s)."), root, strerror(errno) );
351  std::cerr << _execError << endl;// After fork log on stderr too
352  _exit (128); // No sense in returning! I am forked away!!
353  }
354  if ( ! chdirTo )
355  chdirTo = "/";
356  }
357 
358  if ( chdirTo && chdir( chdirTo ) == -1 )
359  {
360  _execError = root ? str::form( _("Can't chdir to '%s' inside chroot '%s' (%s)."), chdirTo, root, strerror(errno) )
361  : str::form( _("Can't chdir to '%s' (%s)."), chdirTo, strerror(errno) );
362  std::cerr << _execError << endl;// After fork log on stderr too
363  _exit (128); // No sense in returning! I am forked away!!
364  }
365 
366  // close all filedesctiptors above stderr
367  for ( int i = ::getdtablesize() - 1; i > 2; --i ) {
368  ::close( i );
369  }
370 
371  execvp(argv[0], const_cast<char *const *>(argv));
372  // don't want to get here
373  _execError = str::form( _("Can't exec '%s' (%s)."), argv[0], strerror(errno) );
374  std::cerr << _execError << endl;// After fork log on stderr too
375  _exit (129); // No sense in returning! I am forked away!!
377  }
378 
379  else if (pid == -1) // Fork failed, close everything.
380  {
381  _execError = str::form( _("Can't fork (%s)."), strerror(errno) );
382  _exitStatus = 127;
383  ERR << _execError << endl;
384 
385  if (use_pty) {
386  ::close(master_tty);
387  ::close(slave_tty);
388  }
389  else {
390  ::close(to_external[0]);
391  ::close(to_external[1]);
392  ::close(from_external[0]);
393  ::close(from_external[1]);
394  }
395  }
396 
397  else {
398  if (use_pty)
399  {
400  ::close(slave_tty); // belongs to child process
401  inputfile = fdopen(master_tty, "r");
402  outputfile = fdopen(master_tty, "w");
403  }
404  else
405  {
406  ::close(to_external[0]); // belongs to child process
407  ::close(from_external[1]); // belongs to child process
408  inputfile = fdopen(from_external[0], "r");
409  outputfile = fdopen(to_external[1], "w");
410  }
411 
412  DBG << "pid " << pid << " launched" << endl;
413 
414  if (!inputfile || !outputfile)
415  {
416  ERR << "Cannot create streams to external program " << argv[0] << endl;
417  close();
418  }
419  }
420  }
421 
422 
423  int
425  {
426  if (pid > 0)
427  {
428  if ( inputFile() )
429  {
430  // Discard any output instead of closing the pipe,
431  // but watch out for the command exiting while some
432  // subprocess keeps the filedescriptor open.
433  setBlocking( false );
434  FILE * inputfile = inputFile();
435  int inputfileFd = ::fileno( inputfile );
436  long delay = 0;
437  do
438  {
439  /* Watch inputFile to see when it has input. */
440  fd_set rfds;
441  FD_ZERO( &rfds );
442  FD_SET( inputfileFd, &rfds );
443 
444  /* Wait up to 1 seconds. */
445  struct timeval tv;
446  tv.tv_sec = (delay < 0 ? 1 : 0);
447  tv.tv_usec = (delay < 0 ? 0 : delay*100000);
448  if ( delay >= 0 && ++delay > 9 )
449  delay = -1;
450  int retval = select( inputfileFd+1, &rfds, NULL, NULL, &tv );
451 
452  if ( retval == -1 )
453  {
454  ERR << "select error: " << strerror(errno) << endl;
455  if ( errno != EINTR )
456  break;
457  }
458  else if ( retval )
459  {
460  // Data is available now.
461  static size_t linebuffer_size = 0; // static because getline allocs
462  static char * linebuffer = 0; // and reallocs if buffer is too small
463  getline( &linebuffer, &linebuffer_size, inputfile );
464  // ::feof check is important as select returns
465  // positive if the file was closed.
466  if ( ::feof( inputfile ) )
467  break;
468  clearerr( inputfile );
469  }
470  else
471  {
472  // No data within time.
473  if ( ! running() )
474  break;
475  }
476  } while ( true );
477  }
478 
479  if ( pid > 0 ) // bsc#1109877: must re-check! running() in the loop above may have already waited.
480  {
481  // Wait for child to exit
482  int ret;
483  int status = 0;
484  do
485  {
486  ret = waitpid(pid, &status, 0);
487  }
488  while (ret == -1 && errno == EINTR);
489 
490  if (ret != -1)
491  {
492  _exitStatus = checkStatus( status );
493  }
494  pid = -1;
495  }
496  }
497 
498  return _exitStatus;
499  }
500 
501 
503  {
504  if (WIFEXITED (status))
505  {
506  status = WEXITSTATUS (status);
507  if(status)
508  {
509  DBG << "Pid " << pid << " exited with status " << status << endl;
510  _execError = str::form( _("Command exited with status %d."), status );
511  }
512  else
513  {
514  // if 'launch' is logged, completion should be logged,
515  // even if successfull.
516  DBG << "Pid " << pid << " successfully completed" << endl;
517  _execError.clear(); // empty if running or successfully completed
518  }
519  }
520  else if (WIFSIGNALED (status))
521  {
522  status = WTERMSIG (status);
523  WAR << "Pid " << pid << " was killed by signal " << status
524  << " (" << strsignal(status);
525  if (WCOREDUMP (status))
526  {
527  WAR << ", core dumped";
528  }
529  WAR << ")" << endl;
530  _execError = str::form( _("Command was killed by signal %d (%s)."), status, strsignal(status) );
531  status+=128;
532  }
533  else {
534  ERR << "Pid " << pid << " exited with unknown error" << endl;
535  _execError = _("Command exited with unknown error.");
536  }
537 
538  return status;
539  }
540 
541  bool
543  {
544  if (pid > 0)
545  {
546  ::kill(pid, SIGKILL);
547  close();
548  }
549  return true;
550  }
551 
552 
553  bool
555  {
556  if ( pid < 0 ) return false;
557 
558  int status = 0;
559  int p = waitpid( pid, &status, WNOHANG );
560  switch ( p )
561  {
562  case -1:
563  ERR << "waitpid( " << pid << ") returned error '" << strerror(errno) << "'" << endl;
564  return false;
565  break;
566  case 0:
567  return true; // still running
568  break;
569  }
570 
571  // Here: completed...
572  _exitStatus = checkStatus( status );
573  pid = -1;
574  return false;
575  }
576 
577  // origfd will be accessible as newfd and closed (unless they were equal)
578  void ExternalProgram::renumber_fd (int origfd, int newfd)
579  {
580  // It may happen that origfd is already the one we want
581  // (Although in our circumstances, that would mean somebody has closed
582  // our stdin or stdout... weird but has appened to Cray, #49797)
583  if (origfd != newfd)
584  {
585  dup2 (origfd, newfd);
586  ::close (origfd);
587  }
588  }
589 
590  std::ostream & ExternalProgram::operator>>( std::ostream & out_r )
591  {
592  setBlocking( true );
593  for ( std::string line = receiveLine(); line.length(); line = receiveLine() )
594  out_r << line;
595  return out_r;
596  }
597 
599  //
600  // class ExternalProgramWithStderr
601  //
603 
604  namespace externalprogram
605  {
607  {
608  _fds[R] = _fds[W] = -1;
609 #ifdef HAVE_PIPE2
610  ::pipe2( _fds, O_NONBLOCK );
611 #else
612  ::pipe( _fds );
613  ::fcntl(_fds[R], F_SETFD, O_NONBLOCK );
614  ::fcntl(_fds[W], F_SETFD, O_NONBLOCK );
615 #endif
616  _stderr = ::fdopen( _fds[R], "r" );
617  }
618 
620  {
621  closeW();
622  if ( _stderr )
623  ::fclose( _stderr );
624  }
625  } // namespace externalprogram
626 
627  bool ExternalProgramWithStderr::stderrGetUpTo( std::string & retval_r, const char delim_r, bool returnDelim_r )
628  {
629  if ( ! _stderr )
630  return false;
631  if ( delim_r && ! _buffer.empty() )
632  {
633  // check for delim already in buffer
634  std::string::size_type pos( _buffer.find( delim_r ) );
635  if ( pos != std::string::npos )
636  {
637  retval_r = _buffer.substr( 0, returnDelim_r ? pos+1 : pos );
638  _buffer.erase( 0, pos+1 );
639  return true;
640  }
641  }
642  ::clearerr( _stderr );
643  do {
644  int ch = fgetc( _stderr );
645  if ( ch != EOF )
646  {
647  if ( ch != delim_r || ! delim_r )
648  _buffer.push_back( ch );
649  else
650  {
651  if ( returnDelim_r )
652  _buffer.push_back( delim_r );
653  break;
654  }
655  }
656  else if ( ::feof( _stderr ) )
657  {
658  if ( _buffer.empty() )
659  return false;
660  break;
661  }
662  else if ( errno != EINTR )
663  return false;
664  } while ( true );
665  // HERE: we left after readig at least one char (\n)
666  retval_r.swap( _buffer );
667  _buffer.clear();
668  return true;
669  }
670 
671 
672 } // namespace zypp
Interface to gettext.
ExternalProgram()
Start an external program by giving the arguments as an arry of char *pointers.
bool use_pty
Set to true, if a pair of ttys is used for communication instead of a pair of pipes.
std::ostream & operator>>(std::ostream &out_r)
Redirect all command output to an ostream.
bool kill()
Kill the program.
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)
#define for_(IT, BEG, END)
Convenient for-loops using iterator.
Definition: Easy.h:27
bool running()
Return whether program is running.
std::string form(const char *format,...) __attribute__((format(printf
Printf style construction of std::string.
Definition: String.cc:36
#define ERR
Definition: Logger.h:66
std::vector< std::string > Arguments
std::string getline(std::istream &str)
Read one line from stream.
Definition: IOStream.cc:33
#define WAR
Definition: Logger.h:65
#define _(MSG)
Definition: Gettext.h:29
std::string receiveLine()
Read one line from the input stream.
Stderr_Disposition
Define symbols for different policies on the handling of stderr.
FILE * inputFile() const
Return the input stream.
std::map< std::string, std::string > Environment
For passing additional environment variables to set.
SolvableIdType size_type
Definition: PoolMember.h:152
bool stderrGetUpTo(std::string &retval_r, const char delim_r, bool returnDelim_r=false)
Read data up to delim_r from stderr (nonblocking).
std::string _execError
Remember execution errors like failed fork/exec.
int close()
Wait for the progamm to complete.
std::string _command
Store the command we're executing.
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.
std::string strerror(int errno_r)
Return string describing the error_r code.
Definition: String.cc:53
#define DBG
Definition: Logger.h:63