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