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