libzypp  14.29.1
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 * chdirTo = nullptr; // #/[path]
186 
187  if ( root )
188  {
189  if ( root[0] == '\0' )
190  {
191  root = nullptr; // ignore empty root
192  }
193  else if ( root[0] == '/' && root[1] == '\0' )
194  {
195  // If root is '/' do not chroot, but chdir to '/'
196  // unless arglist defines another dir.
197  chdirTo = "/";
198  root = nullptr;
199  }
200  }
201 
202  for ( bool strip = false; argv[0]; ++argv )
203  {
204  strip = false;
205  switch ( argv[0][0] )
206  {
207  case '<':
208  strip = true;
209  redirectStdin = argv[0]+1;
210  if ( *redirectStdin == '\0' )
211  redirectStdin = "/dev/null";
212  break;
213 
214  case '#':
215  strip = true;
216  if ( argv[0][1] == '/' ) // #/[path]
217  chdirTo = argv[0]+1;
218  break;
219  }
220  if ( ! strip )
221  break;
222  }
223 
224  // do not remove the single quotes around every argument, copy&paste of
225  // command to shell will not work otherwise!
226  {
227  stringstream cmdstr;
228  for (int i = 0; argv[i]; i++)
229  {
230  if (i>0) cmdstr << ' ';
231  cmdstr << '\'';
232  cmdstr << argv[i];
233  cmdstr << '\'';
234  }
235  if ( redirectStdin )
236  cmdstr << " < '" << redirectStdin << "'";
237  _command = cmdstr.str();
238  }
239  DBG << "Executing " << _command << endl;
240 
241 
242  if (use_pty)
243  {
244  // Create pair of ttys
245  DBG << "Using ttys for communication with " << argv[0] << endl;
246  if (openpty (&master_tty, &slave_tty, 0, 0, 0) != 0)
247  {
248  _execError = str::form( _("Can't open pty (%s)."), strerror(errno) );
249  _exitStatus = 126;
250  ERR << _execError << endl;
251  return;
252  }
253  }
254  else
255  {
256  // Create pair of pipes
257  if (pipe (to_external) != 0 || pipe (from_external) != 0)
258  {
259  _execError = str::form( _("Can't open pipe (%s)."), strerror(errno) );
260  _exitStatus = 126;
261  ERR << _execError << endl;
262  return;
263  }
264  }
265 
266  // Create module process
267  if ((pid = fork()) == 0)
268  {
270  // Don't write to the logfile after fork!
272  if (use_pty)
273  {
274  setsid();
275  if(slave_tty != 1)
276  dup2 (slave_tty, 1); // set new stdout
277  renumber_fd (slave_tty, 0); // set new stdin
278  ::close(master_tty); // Belongs to father process
279 
280  // We currently have no controlling terminal (due to setsid).
281  // The first open call will also set the new ctty (due to historical
282  // unix guru knowledge ;-) )
283 
284  char name[512];
285  ttyname_r(slave_tty, name, sizeof(name));
286  ::close(open(name, O_RDONLY));
287  }
288  else
289  {
290  renumber_fd (to_external[0], 0); // set new stdin
291  ::close(from_external[0]); // Belongs to father process
292 
293  renumber_fd (from_external[1], 1); // set new stdout
294  ::close(to_external [1]); // Belongs to father process
295  }
296 
297  if ( redirectStdin )
298  {
299  ::close( 0 );
300  int inp_fd = open( redirectStdin, O_RDONLY );
301  dup2( inp_fd, 0 );
302  }
303 
304  // Handle stderr
305  if (stderr_disp == Discard_Stderr)
306  {
307  int null_fd = open("/dev/null", O_WRONLY);
308  dup2(null_fd, 2);
309  ::close(null_fd);
310  }
311  else if (stderr_disp == Stderr_To_Stdout)
312  {
313  dup2(1, 2);
314  }
315  else if (stderr_disp == Stderr_To_FileDesc)
316  {
317  // Note: We don't have to close anything regarding stderr_fd.
318  // Our caller is responsible for that.
319  dup2 (stderr_fd, 2);
320  }
321 
322  for ( Environment::const_iterator it = environment.begin(); it != environment.end(); ++it ) {
323  setenv( it->first.c_str(), it->second.c_str(), 1 );
324  }
325 
326  if(default_locale)
327  setenv("LC_ALL","C",1);
328 
329  if(root)
330  {
331  if(chroot(root) == -1)
332  {
333  _execError = str::form( _("Can't chroot to '%s' (%s)."), root, strerror(errno) );
334  std::cerr << _execError << endl;// After fork log on stderr too
335  _exit (128); // No sense in returning! I am forked away!!
336  }
337  if ( ! chdirTo )
338  chdirTo = "/";
339  }
340 
341  if ( chdirTo && chdir( chdirTo ) == -1 )
342  {
343  _execError = root ? str::form( _("Can't chdir to '%s' inside chroot '%s' (%s)."), chdirTo, root, strerror(errno) )
344  : str::form( _("Can't chdir to '%s' (%s)."), chdirTo, strerror(errno) );
345  std::cerr << _execError << endl;// After fork log on stderr too
346  _exit (128); // No sense in returning! I am forked away!!
347  }
348 
349  // close all filedesctiptors above stderr
350  for ( int i = ::getdtablesize() - 1; i > 2; --i ) {
351  ::close( i );
352  }
353 
354  execvp(argv[0], const_cast<char *const *>(argv));
355  // don't want to get here
356  _execError = str::form( _("Can't exec '%s' (%s)."), argv[0], strerror(errno) );
357  std::cerr << _execError << endl;// After fork log on stderr too
358  _exit (129); // No sense in returning! I am forked away!!
360  }
361 
362  else if (pid == -1) // Fork failed, close everything.
363  {
364  _execError = str::form( _("Can't fork (%s)."), strerror(errno) );
365  _exitStatus = 127;
366  ERR << _execError << endl;
367 
368  if (use_pty) {
369  ::close(master_tty);
370  ::close(slave_tty);
371  }
372  else {
373  ::close(to_external[0]);
374  ::close(to_external[1]);
375  ::close(from_external[0]);
376  ::close(from_external[1]);
377  }
378  }
379 
380  else {
381  if (use_pty)
382  {
383  ::close(slave_tty); // belongs to child process
384  inputfile = fdopen(master_tty, "r");
385  outputfile = fdopen(master_tty, "w");
386  }
387  else
388  {
389  ::close(to_external[0]); // belongs to child process
390  ::close(from_external[1]); // belongs to child process
391  inputfile = fdopen(from_external[0], "r");
392  outputfile = fdopen(to_external[1], "w");
393  }
394 
395  DBG << "pid " << pid << " launched" << endl;
396 
397  if (!inputfile || !outputfile)
398  {
399  ERR << "Cannot create streams to external program " << argv[0] << endl;
400  close();
401  }
402  }
403  }
404 
405 
406  int
408  {
409  if (pid > 0)
410  {
411  if ( inputFile() )
412  {
413  // Discard any output instead of closing the pipe,
414  // but watch out for the command exiting while some
415  // subprocess keeps the filedescriptor open.
416  setBlocking( false );
417  FILE * inputfile = inputFile();
418  int inputfileFd = ::fileno( inputfile );
419  long delay = 0;
420  do
421  {
422  /* Watch inputFile to see when it has input. */
423  fd_set rfds;
424  FD_ZERO( &rfds );
425  FD_SET( inputfileFd, &rfds );
426 
427  /* Wait up to 1 seconds. */
428  struct timeval tv;
429  tv.tv_sec = (delay < 0 ? 1 : 0);
430  tv.tv_usec = (delay < 0 ? 0 : delay*100000);
431  if ( delay >= 0 && ++delay > 9 )
432  delay = -1;
433  int retval = select( inputfileFd+1, &rfds, NULL, NULL, &tv );
434 
435  if ( retval == -1 )
436  {
437  ERR << "select error: " << strerror(errno) << endl;
438  if ( errno != EINTR )
439  break;
440  }
441  else if ( retval )
442  {
443  // Data is available now.
444  static size_t linebuffer_size = 0; // static because getline allocs
445  static char * linebuffer = 0; // and reallocs if buffer is too small
446  getline( &linebuffer, &linebuffer_size, inputfile );
447  // ::feof check is important as select returns
448  // positive if the file was closed.
449  if ( ::feof( inputfile ) )
450  break;
451  clearerr( inputfile );
452  }
453  else
454  {
455  // No data within time.
456  if ( ! running() )
457  break;
458  }
459  } while ( true );
460  }
461 
462  // Wait for child to exit
463  int ret;
464  int status = 0;
465  do
466  {
467  ret = waitpid(pid, &status, 0);
468  }
469  while (ret == -1 && errno == EINTR);
470 
471  if (ret != -1)
472  {
473  _exitStatus = checkStatus( status );
474  }
475  pid = -1;
476  }
477 
478  return _exitStatus;
479  }
480 
481 
483  {
484  if (WIFEXITED (status))
485  {
486  status = WEXITSTATUS (status);
487  if(status)
488  {
489  DBG << "Pid " << pid << " exited with status " << status << endl;
490  _execError = str::form( _("Command exited with status %d."), status );
491  }
492  else
493  {
494  // if 'launch' is logged, completion should be logged,
495  // even if successfull.
496  DBG << "Pid " << pid << " successfully completed" << endl;
497  _execError.clear(); // empty if running or successfully completed
498  }
499  }
500  else if (WIFSIGNALED (status))
501  {
502  status = WTERMSIG (status);
503  WAR << "Pid " << pid << " was killed by signal " << status
504  << " (" << strsignal(status);
505  if (WCOREDUMP (status))
506  {
507  WAR << ", core dumped";
508  }
509  WAR << ")" << endl;
510  _execError = str::form( _("Command was killed by signal %d (%s)."), status, strsignal(status) );
511  status+=128;
512  }
513  else {
514  ERR << "Pid " << pid << " exited with unknown error" << endl;
515  _execError = _("Command exited with unknown error.");
516  }
517 
518  return status;
519  }
520 
521  bool
523  {
524  if (pid > 0)
525  {
526  ::kill(pid, SIGKILL);
527  close();
528  }
529  return true;
530  }
531 
532 
533  bool
535  {
536  if ( pid < 0 ) return false;
537 
538  int status = 0;
539  int p = waitpid( pid, &status, WNOHANG );
540  switch ( p )
541  {
542  case -1:
543  ERR << "waitpid( " << pid << ") returned error '" << strerror(errno) << "'" << endl;
544  return false;
545  break;
546  case 0:
547  return true; // still running
548  break;
549  }
550 
551  // Here: completed...
552  _exitStatus = checkStatus( status );
553  pid = -1;
554  return false;
555  }
556 
557  // origfd will be accessible as newfd and closed (unless they were equal)
558  void ExternalProgram::renumber_fd (int origfd, int newfd)
559  {
560  // It may happen that origfd is already the one we want
561  // (Although in our circumstances, that would mean somebody has closed
562  // our stdin or stdout... weird but has appened to Cray, #49797)
563  if (origfd != newfd)
564  {
565  dup2 (origfd, newfd);
566  ::close (origfd);
567  }
568  }
569 
570  std::ostream & ExternalProgram::operator>>( std::ostream & out_r )
571  {
572  setBlocking( true );
573  for ( std::string line = receiveLine(); line.length(); line = receiveLine() )
574  out_r << line;
575  return out_r;
576  }
577 
579  //
580  // class ExternalProgramWithStderr
581  //
583 
584  namespace _ExternalProgram
585  {
587  {
588  _fds[R] = _fds[W] = -1;
589 #ifdef HAVE_PIPE2
590  ::pipe2( _fds, O_NONBLOCK );
591 #else
592  ::pipe( _fds );
593  ::fcntl(_fds[R], F_SETFD, O_NONBLOCK );
594  ::fcntl(_fds[W], F_SETFD, O_NONBLOCK );
595 #endif
596  _stderr = ::fdopen( _fds[R], "r" );
597  }
598 
600  {
601  closeW();
602  if ( _stderr )
603  ::fclose( _stderr );
604  }
605  }
606 
607  bool ExternalProgramWithStderr::stderrGetUpTo( std::string & retval_r, const char delim_r, bool returnDelim_r )
608  {
609  if ( ! _stderr )
610  return false;
611  if ( delim_r && ! _buffer.empty() )
612  {
613  // check for delim already in buffer
614  std::string::size_type pos( _buffer.find( delim_r ) );
615  if ( pos != std::string::npos )
616  {
617  retval_r = _buffer.substr( 0, returnDelim_r ? pos+1 : pos );
618  _buffer.erase( 0, pos+1 );
619  return true;
620  }
621  }
622  ::clearerr( _stderr );
623  do {
624  int ch = fgetc( _stderr );
625  if ( ch != EOF )
626  {
627  if ( ch != delim_r || ! delim_r )
628  _buffer.push_back( ch );
629  else
630  {
631  if ( returnDelim_r )
632  _buffer.push_back( delim_r );
633  break;
634  }
635  }
636  else if ( ::feof( _stderr ) )
637  {
638  if ( _buffer.empty() )
639  return false;
640  break;
641  }
642  else if ( errno != EINTR )
643  return false;
644  } while ( true );
645  // HERE: we left after readig at least one char (\n)
646  retval_r.swap( _buffer );
647  _buffer.clear();
648  return true;
649  }
650 
651 
652 } // 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)
Definition: Arch.h:330
#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:34
#define ERR
Definition: Logger.h:49
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:48
#define _(MSG)
Return translated text.
Definition: Gettext.h:21
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:99
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:51
Easy-to use interface to the ZYPP dependency resolver.
Definition: CodePitfalls.doc:1
#define DBG
Definition: Logger.h:46