1 /*---------------------------------------------------------------------\
3 | |__ / \ / / . \ . \ |
8 \---------------------------------------------------------------------*/
9 /** \file zypp/ExternalProgram.cc
12 #define _GNU_SOURCE 1 // for ::getline
19 #include <pty.h> // openpty
20 #include <stdlib.h> // setenv
22 #include <cstring> // strsignal
26 #include "zypp/base/Logger.h"
27 #include "zypp/base/String.h"
28 #include "zypp/base/Gettext.h"
29 #include "zypp/ExternalProgram.h"
33 #undef ZYPP_BASE_LOGGER_LOGGROUP
34 #define ZYPP_BASE_LOGGER_LOGGROUP "zypp::exec"
38 ExternalProgram::ExternalProgram()
44 ExternalProgram::ExternalProgram( std::string commandline,
45 Stderr_Disposition stderr_disp,
49 const Pathname & root )
56 argv[2] = commandline.c_str();
59 start_program( argv, Environment(), stderr_disp, stderr_fd, default_locale, root.c_str() );
63 ExternalProgram::ExternalProgram( const Arguments & argv,
64 Stderr_Disposition stderr_disp,
68 const Pathname & root )
72 const char * argvp[argv.size() + 1];
74 for_( i, argv.begin(), argv.end() )
76 argvp[c] = i->c_str();
81 start_program( argvp, Environment(), stderr_disp, stderr_fd, default_locale, root.c_str() );
85 ExternalProgram::ExternalProgram( const Arguments & argv,
86 const Environment & environment,
87 Stderr_Disposition stderr_disp,
91 const Pathname & root )
95 const char * argvp[argv.size() + 1];
97 for_( i, argv.begin(), argv.end() )
99 argvp[c] = i->c_str();
104 start_program( argvp, environment, stderr_disp, stderr_fd, default_locale, root.c_str() );
109 ExternalProgram::ExternalProgram( const char *const *argv,
110 Stderr_Disposition stderr_disp,
114 const Pathname & root )
118 start_program( argv, Environment(), stderr_disp, stderr_fd, default_locale, root.c_str() );
122 ExternalProgram::ExternalProgram( const char *const * argv,
123 const Environment & environment,
124 Stderr_Disposition stderr_disp,
128 const Pathname & root )
132 start_program( argv, environment, stderr_disp, stderr_fd, default_locale, root.c_str() );
136 ExternalProgram::ExternalProgram( const char *binpath,
137 const char *const *argv_1,
145 const char *argv[i + 1];
147 memcpy( &argv[1], argv_1, (i - 1) * sizeof (char *) );
148 start_program( argv, Environment() );
152 ExternalProgram::ExternalProgram( const char *binpath,
153 const char *const *argv_1,
154 const Environment & environment,
162 const char *argv[i + 1];
164 memcpy( &argv[1], argv_1, (i - 1) * sizeof (char *) );
165 start_program( argv, environment );
169 ExternalProgram::~ExternalProgram()
174 void ExternalProgram::start_program( const char *const *argv,
175 const Environment & environment,
176 Stderr_Disposition stderr_disp,
183 int to_external[2], from_external[2]; // fds for pair of pipes
184 int master_tty, slave_tty; // fds for pair of ttys
186 // retrieve options at beginning of arglist
187 const char * redirectStdin = nullptr; // <[file]
188 const char * redirectStdout = nullptr; // >[file]
189 const char * chdirTo = nullptr; // #/[path]
193 if ( root[0] == '\0' )
195 root = nullptr; // ignore empty root
197 else if ( root[0] == '/' && root[1] == '\0' )
199 // If root is '/' do not chroot, but chdir to '/'
200 // unless arglist defines another dir.
206 for ( bool strip = false; argv[0]; ++argv )
209 switch ( argv[0][0] )
213 redirectStdin = argv[0]+1;
214 if ( *redirectStdin == '\0' )
215 redirectStdin = "/dev/null";
220 redirectStdout = argv[0]+1;
221 if ( *redirectStdout == '\0' )
222 redirectStdout = "/dev/null";
227 if ( argv[0][1] == '/' ) // #/[path]
235 // do not remove the single quotes around every argument, copy&paste of
236 // command to shell will not work otherwise!
239 for (int i = 0; argv[i]; i++)
241 if (i>0) cmdstr << ' ';
247 cmdstr << " < '" << redirectStdin << "'";
248 if ( redirectStdout )
249 cmdstr << " > '" << redirectStdout << "'";
250 _command = cmdstr.str();
252 DBG << "Executing " << _command << endl;
257 // Create pair of ttys
258 DBG << "Using ttys for communication with " << argv[0] << endl;
259 if (openpty (&master_tty, &slave_tty, 0, 0, 0) != 0)
261 _execError = str::form( _("Can't open pty (%s)."), strerror(errno) );
263 ERR << _execError << endl;
269 // Create pair of pipes
270 if (pipe (to_external) != 0 || pipe (from_external) != 0)
272 _execError = str::form( _("Can't open pipe (%s)."), strerror(errno) );
274 ERR << _execError << endl;
279 // Create module process
280 if ((pid = fork()) == 0)
282 //////////////////////////////////////////////////////////////////////
283 // Don't write to the logfile after fork!
284 //////////////////////////////////////////////////////////////////////
289 dup2 (slave_tty, 1); // set new stdout
290 renumber_fd (slave_tty, 0); // set new stdin
291 ::close(master_tty); // Belongs to father process
293 // We currently have no controlling terminal (due to setsid).
294 // The first open call will also set the new ctty (due to historical
295 // unix guru knowledge ;-) )
298 ttyname_r(slave_tty, name, sizeof(name));
299 ::close(open(name, O_RDONLY));
303 renumber_fd (to_external[0], 0); // set new stdin
304 ::close(from_external[0]); // Belongs to father process
306 renumber_fd (from_external[1], 1); // set new stdout
307 ::close(to_external [1]); // Belongs to father process
313 int inp_fd = open( redirectStdin, O_RDONLY );
317 if ( redirectStdout )
320 int inp_fd = open( redirectStdout, O_WRONLY|O_CREAT|O_APPEND, 0600 );
325 if (stderr_disp == Discard_Stderr)
327 int null_fd = open("/dev/null", O_WRONLY);
331 else if (stderr_disp == Stderr_To_Stdout)
335 else if (stderr_disp == Stderr_To_FileDesc)
337 // Note: We don't have to close anything regarding stderr_fd.
338 // Our caller is responsible for that.
342 for ( Environment::const_iterator it = environment.begin(); it != environment.end(); ++it ) {
343 setenv( it->first.c_str(), it->second.c_str(), 1 );
347 setenv("LC_ALL","C",1);
351 if(chroot(root) == -1)
353 _execError = str::form( _("Can't chroot to '%s' (%s)."), root, strerror(errno) );
354 std::cerr << _execError << endl;// After fork log on stderr too
355 _exit (128); // No sense in returning! I am forked away!!
361 if ( chdirTo && chdir( chdirTo ) == -1 )
363 _execError = root ? str::form( _("Can't chdir to '%s' inside chroot '%s' (%s)."), chdirTo, root, strerror(errno) )
364 : str::form( _("Can't chdir to '%s' (%s)."), chdirTo, strerror(errno) );
365 std::cerr << _execError << endl;// After fork log on stderr too
366 _exit (128); // No sense in returning! I am forked away!!
369 // close all filedesctiptors above stderr
370 for ( int i = ::getdtablesize() - 1; i > 2; --i ) {
374 execvp(argv[0], const_cast<char *const *>(argv));
375 // don't want to get here
376 _execError = str::form( _("Can't exec '%s' (%s)."), argv[0], strerror(errno) );
377 std::cerr << _execError << endl;// After fork log on stderr too
378 _exit (129); // No sense in returning! I am forked away!!
379 //////////////////////////////////////////////////////////////////////
382 else if (pid == -1) // Fork failed, close everything.
384 _execError = str::form( _("Can't fork (%s)."), strerror(errno) );
386 ERR << _execError << endl;
393 ::close(to_external[0]);
394 ::close(to_external[1]);
395 ::close(from_external[0]);
396 ::close(from_external[1]);
403 ::close(slave_tty); // belongs to child process
404 inputfile = fdopen(master_tty, "r");
405 outputfile = fdopen(master_tty, "w");
409 ::close(to_external[0]); // belongs to child process
410 ::close(from_external[1]); // belongs to child process
411 inputfile = fdopen(from_external[0], "r");
412 outputfile = fdopen(to_external[1], "w");
415 DBG << "pid " << pid << " launched" << endl;
417 if (!inputfile || !outputfile)
419 ERR << "Cannot create streams to external program " << argv[0] << endl;
427 ExternalProgram::close()
433 // Discard any output instead of closing the pipe,
434 // but watch out for the command exiting while some
435 // subprocess keeps the filedescriptor open.
436 setBlocking( false );
437 FILE * inputfile = inputFile();
438 int inputfileFd = ::fileno( inputfile );
442 /* Watch inputFile to see when it has input. */
445 FD_SET( inputfileFd, &rfds );
447 /* Wait up to 1 seconds. */
449 tv.tv_sec = (delay < 0 ? 1 : 0);
450 tv.tv_usec = (delay < 0 ? 0 : delay*100000);
451 if ( delay >= 0 && ++delay > 9 )
453 int retval = select( inputfileFd+1, &rfds, NULL, NULL, &tv );
457 ERR << "select error: " << strerror(errno) << endl;
458 if ( errno != EINTR )
463 // Data is available now.
464 static size_t linebuffer_size = 0; // static because getline allocs
465 static char * linebuffer = 0; // and reallocs if buffer is too small
466 getline( &linebuffer, &linebuffer_size, inputfile );
467 // ::feof check is important as select returns
468 // positive if the file was closed.
469 if ( ::feof( inputfile ) )
471 clearerr( inputfile );
475 // No data within time.
482 // Wait for child to exit
487 ret = waitpid(pid, &status, 0);
489 while (ret == -1 && errno == EINTR);
493 _exitStatus = checkStatus( status );
502 int ExternalProgram::checkStatus( int status )
504 if (WIFEXITED (status))
506 status = WEXITSTATUS (status);
509 DBG << "Pid " << pid << " exited with status " << status << endl;
510 _execError = str::form( _("Command exited with status %d."), status );
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
520 else if (WIFSIGNALED (status))
522 status = WTERMSIG (status);
523 WAR << "Pid " << pid << " was killed by signal " << status
524 << " (" << strsignal(status);
525 if (WCOREDUMP (status))
527 WAR << ", core dumped";
530 _execError = str::form( _("Command was killed by signal %d (%s)."), status, strsignal(status) );
534 ERR << "Pid " << pid << " exited with unknown error" << endl;
535 _execError = _("Command exited with unknown error.");
542 ExternalProgram::kill()
546 ::kill(pid, SIGKILL);
554 ExternalProgram::running()
556 if ( pid < 0 ) return false;
559 int p = waitpid( pid, &status, WNOHANG );
563 ERR << "waitpid( " << pid << ") returned error '" << strerror(errno) << "'" << endl;
567 return true; // still running
571 // Here: completed...
572 _exitStatus = checkStatus( status );
577 // origfd will be accessible as newfd and closed (unless they were equal)
578 void ExternalProgram::renumber_fd (int origfd, int newfd)
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)
585 dup2 (origfd, newfd);
590 std::ostream & ExternalProgram::operator>>( std::ostream & out_r )
593 for ( std::string line = receiveLine(); line.length(); line = receiveLine() )
598 //////////////////////////////////////////////////////////////////////
600 // class ExternalProgramWithStderr
602 //////////////////////////////////////////////////////////////////////
604 namespace externalprogram
606 EarlyPipe::EarlyPipe()
608 _fds[R] = _fds[W] = -1;
610 ::pipe2( _fds, O_NONBLOCK );
613 ::fcntl(_fds[R], F_SETFD, O_NONBLOCK );
614 ::fcntl(_fds[W], F_SETFD, O_NONBLOCK );
616 _stderr = ::fdopen( _fds[R], "r" );
619 EarlyPipe::~EarlyPipe()
625 } // namespace externalprogram
627 bool ExternalProgramWithStderr::stderrGetUpTo( std::string & retval_r, const char delim_r, bool returnDelim_r )
631 if ( delim_r && ! _buffer.empty() )
633 // check for delim already in buffer
634 std::string::size_type pos( _buffer.find( delim_r ) );
635 if ( pos != std::string::npos )
637 retval_r = _buffer.substr( 0, returnDelim_r ? pos+1 : pos );
638 _buffer.erase( 0, pos+1 );
642 ::clearerr( _stderr );
644 int ch = fgetc( _stderr );
647 if ( ch != delim_r || ! delim_r )
648 _buffer.push_back( ch );
652 _buffer.push_back( delim_r );
656 else if ( ::feof( _stderr ) )
658 if ( _buffer.empty() )
662 else if ( errno != EINTR )
665 // HERE: we left after readig at least one char (\n)
666 retval_r.swap( _buffer );