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