| |
\---------------------------------------------------------------------*/
/** \file zypp/ExternalProgram.cc
- *
- * \todo replace by Blocxx
- *
*/
#define _GNU_SOURCE 1 // for ::getline
#include <fcntl.h>
#include <pty.h> // openpty
#include <stdlib.h> // setenv
+#include <sys/prctl.h> // prctl(), PR_SET_PDEATHSIG
#include <cstring> // strsignal
#include <iostream>
+#include <sstream>
#include "zypp/base/Logger.h"
+#include "zypp/base/String.h"
+#include "zypp/base/Gettext.h"
#include "zypp/ExternalProgram.h"
+#include "zypp/base/CleanerThread_p.h"
using namespace std;
+#undef ZYPP_BASE_LOGGER_LOGGROUP
+#define ZYPP_BASE_LOGGER_LOGGROUP "zypp::exec"
+
namespace zypp {
- ExternalProgram::ExternalProgram (string commandline,
- Stderr_Disposition stderr_disp, bool use_pty,
- int stderr_fd, bool default_locale,
- const Pathname& root)
+ ExternalProgram::ExternalProgram()
+ : use_pty (false)
+ , pid( -1 )
+ {}
+
+
+ ExternalProgram::ExternalProgram( std::string commandline,
+ Stderr_Disposition stderr_disp,
+ bool use_pty,
+ int stderr_fd,
+ bool default_locale,
+ const Pathname & root )
: use_pty (use_pty)
+ , pid( -1 )
{
const char *argv[4];
argv[0] = "/bin/sh";
argv[1] = "-c";
argv[2] = commandline.c_str();
argv[3] = 0;
-
- const char* rootdir = NULL;
- if(!root.empty() && root != "/")
- {
- rootdir = root.asString().c_str();
- }
- Environment environment;
- start_program (argv, environment, stderr_disp, stderr_fd, default_locale, rootdir);
+
+ start_program( argv, Environment(), stderr_disp, stderr_fd, default_locale, root.c_str() );
}
-
-
- ExternalProgram::ExternalProgram (const char *const *argv,
- Stderr_Disposition stderr_disp, bool use_pty,
- int stderr_fd, bool default_locale,
- const Pathname& root)
+
+
+ ExternalProgram::ExternalProgram( const Arguments & argv,
+ Stderr_Disposition stderr_disp,
+ bool use_pty,
+ int stderr_fd,
+ bool default_locale,
+ const Pathname & root )
: use_pty (use_pty)
+ , pid( -1 )
{
- const char* rootdir = NULL;
- if(!root.empty() && root != "/")
+ const char * argvp[argv.size() + 1];
+ unsigned c = 0;
+ for_( i, argv.begin(), argv.end() )
{
- rootdir = root.asString().c_str();
+ argvp[c] = i->c_str();
+ ++c;
}
- Environment environment;
- start_program (argv, environment, stderr_disp, stderr_fd, default_locale, rootdir);
+ argvp[c] = 0;
+
+ start_program( argvp, Environment(), stderr_disp, stderr_fd, default_locale, root.c_str() );
}
-
-
- ExternalProgram::ExternalProgram (const char *const *argv, const Environment & environment,
- Stderr_Disposition stderr_disp, bool use_pty,
- int stderr_fd, bool default_locale,
- const Pathname& root)
+
+
+ ExternalProgram::ExternalProgram( const Arguments & argv,
+ const Environment & environment,
+ Stderr_Disposition stderr_disp,
+ bool use_pty,
+ int stderr_fd,
+ bool default_locale,
+ const Pathname & root )
: use_pty (use_pty)
+ , pid( -1 )
{
- const char* rootdir = NULL;
- if(!root.empty() && root != "/")
+ const char * argvp[argv.size() + 1];
+ unsigned c = 0;
+ for_( i, argv.begin(), argv.end() )
{
- rootdir = root.asString().c_str();
+ argvp[c] = i->c_str();
+ ++c;
}
- start_program (argv, environment, stderr_disp, stderr_fd, default_locale, rootdir);
+ argvp[c] = 0;
+
+ start_program( argvp, environment, stderr_disp, stderr_fd, default_locale, root.c_str() );
}
-
-
- ExternalProgram::ExternalProgram (const char *binpath, const char *const *argv_1,
- bool use_pty)
+
+
+
+ ExternalProgram::ExternalProgram( const char *const *argv,
+ Stderr_Disposition stderr_disp,
+ bool use_pty,
+ int stderr_fd,
+ bool default_locale,
+ const Pathname & root )
: use_pty (use_pty)
+ , pid( -1 )
+ {
+ start_program( argv, Environment(), stderr_disp, stderr_fd, default_locale, root.c_str() );
+ }
+
+
+ ExternalProgram::ExternalProgram( const char *const * argv,
+ const Environment & environment,
+ Stderr_Disposition stderr_disp,
+ bool use_pty,
+ int stderr_fd,
+ bool default_locale,
+ const Pathname & root )
+ : use_pty (use_pty)
+ , pid( -1 )
+ {
+ start_program( argv, environment, stderr_disp, stderr_fd, default_locale, root.c_str() );
+ }
+
+
+ ExternalProgram::ExternalProgram( const char *binpath,
+ const char *const *argv_1,
+ bool use_pty )
+ : use_pty (use_pty)
+ , pid( -1 )
{
int i = 0;
while (argv_1[i++])
;
const char *argv[i + 1];
argv[0] = binpath;
- memcpy (&argv[1], argv_1, (i - 1) * sizeof (char *));
- Environment environment;
- start_program (argv, environment);
+ memcpy( &argv[1], argv_1, (i - 1) * sizeof (char *) );
+ start_program( argv, Environment() );
}
-
-
- ExternalProgram::ExternalProgram (const char *binpath, const char *const *argv_1, const Environment & environment,
- bool use_pty)
+
+
+ ExternalProgram::ExternalProgram( const char *binpath,
+ const char *const *argv_1,
+ const Environment & environment,
+ bool use_pty )
: use_pty (use_pty)
+ , pid( -1 )
{
int i = 0;
while (argv_1[i++])
;
const char *argv[i + 1];
argv[0] = binpath;
- memcpy (&argv[1], argv_1, (i - 1) * sizeof (char *));
- start_program (argv, environment);
+ memcpy( &argv[1], argv_1, (i - 1) * sizeof (char *) );
+ start_program( argv, environment );
}
-
-
+
+
ExternalProgram::~ExternalProgram()
{
+ if ( running() ) {
+ // we got destructed while the external process is still alive
+ // make sure the zombie is cleaned up once it exits
+ CleanerThread::watchPID( pid );
+ }
}
-
-
- void
- ExternalProgram::start_program (const char *const *argv, const Environment & environment,
- Stderr_Disposition stderr_disp,
- int stderr_fd, bool default_locale, const char* root)
+
+
+
+ void ExternalProgram::start_program(const char *const *argv,
+ const Environment & environment,
+ Stderr_Disposition stderr_disp,
+ int stderr_fd,
+ bool default_locale,
+ const char * root , bool switch_pgid, bool die_with_parent )
{
pid = -1;
_exitStatus = 0;
- int to_external[2], from_external[2]; // fds for pair of pipes
- int master_tty, slave_tty; // fds for pair of ttys
-
+ int to_external[2], from_external[2]; // fds for pair of pipes
+ int master_tty, slave_tty; // fds for pair of ttys
+
+ // retrieve options at beginning of arglist
+ const char * redirectStdin = nullptr; // <[file]
+ const char * redirectStdout = nullptr; // >[file]
+ const char * chdirTo = nullptr; // #/[path]
+
+ if ( root )
+ {
+ if ( root[0] == '\0' )
+ {
+ root = nullptr; // ignore empty root
+ }
+ else if ( root[0] == '/' && root[1] == '\0' )
+ {
+ // If root is '/' do not chroot, but chdir to '/'
+ // unless arglist defines another dir.
+ chdirTo = "/";
+ root = nullptr;
+ }
+ }
+
+ for ( bool strip = false; argv[0]; ++argv )
+ {
+ strip = false;
+ switch ( argv[0][0] )
+ {
+ case '<':
+ strip = true;
+ redirectStdin = argv[0]+1;
+ if ( *redirectStdin == '\0' )
+ redirectStdin = "/dev/null";
+ break;
+
+ case '>':
+ strip = true;
+ redirectStdout = argv[0]+1;
+ if ( *redirectStdout == '\0' )
+ redirectStdout = "/dev/null";
+ break;
+
+ case '#':
+ strip = true;
+ if ( argv[0][1] == '/' ) // #/[path]
+ chdirTo = argv[0]+1;
+ break;
+ }
+ if ( ! strip )
+ break;
+ }
+
+ // do not remove the single quotes around every argument, copy&paste of
+ // command to shell will not work otherwise!
+ {
+ stringstream cmdstr;
+ for (int i = 0; argv[i]; i++)
+ {
+ if (i>0) cmdstr << ' ';
+ cmdstr << '\'';
+ cmdstr << argv[i];
+ cmdstr << '\'';
+ }
+ if ( redirectStdin )
+ cmdstr << " < '" << redirectStdin << "'";
+ if ( redirectStdout )
+ cmdstr << " > '" << redirectStdout << "'";
+ _command = cmdstr.str();
+ }
+ DBG << "Executing" << (default_locale?"[C] ":" ") << _command << endl;
+
+
if (use_pty)
{
// Create pair of ttys
- DBG << "Using ttys for communication with " << argv[0] << endl;
+ DBG << "Using ttys for communication with " << argv[0] << endl;
if (openpty (&master_tty, &slave_tty, 0, 0, 0) != 0)
{
- ERR << "openpty failed" << endl;
- return;
+ _execError = str::form( _("Can't open pty (%s)."), strerror(errno) );
+ _exitStatus = 126;
+ ERR << _execError << endl;
+ return;
}
}
else
// Create pair of pipes
if (pipe (to_external) != 0 || pipe (from_external) != 0)
{
- ERR << "pipe failed" << endl;
- return;
+ _execError = str::form( _("Can't open pipe (%s)."), strerror(errno) );
+ _exitStatus = 126;
+ ERR << _execError << endl;
+ return;
}
}
-
- // do not remove the single quotes around every argument, copy&paste of
- // command to shell will not work otherwise!
- DBG << "Executing ";
- for (int i = 0; argv[i]; i++)
- {
- if (i>0) DBG << ' ';
- DBG << '\'';
- DBG << argv[i];
- DBG << '\'';
- }
- DBG << endl;
-
+
+ pid_t ppid_before_fork = ::getpid();
+
// Create module process
if ((pid = fork()) == 0)
{
+ //////////////////////////////////////////////////////////////////////
+ // Don't write to the logfile after fork!
+ //////////////////////////////////////////////////////////////////////
if (use_pty)
{
setsid();
dup2 (slave_tty, 1); // set new stdout
renumber_fd (slave_tty, 0); // set new stdin
::close(master_tty); // Belongs to father process
-
+
// We currently have no controlling terminal (due to setsid).
// The first open call will also set the new ctty (due to historical
// unix guru knowledge ;-) )
-
+
char name[512];
ttyname_r(slave_tty, name, sizeof(name));
::close(open(name, O_RDONLY));
}
else
{
+ if ( switch_pgid )
+ setpgid( 0, 0);
renumber_fd (to_external[0], 0); // set new stdin
::close(from_external[0]); // Belongs to father process
-
+
renumber_fd (from_external[1], 1); // set new stdout
::close(to_external [1]); // Belongs to father process
}
-
+
+ if ( redirectStdin )
+ {
+ ::close( 0 );
+ int inp_fd = open( redirectStdin, O_RDONLY );
+ dup2( inp_fd, 0 );
+ }
+
+ if ( redirectStdout )
+ {
+ ::close( 1 );
+ int inp_fd = open( redirectStdout, O_WRONLY|O_CREAT|O_APPEND, 0600 );
+ dup2( inp_fd, 1 );
+ }
+
// Handle stderr
if (stderr_disp == Discard_Stderr)
{
// Our caller is responsible for that.
dup2 (stderr_fd, 2);
}
-
+
for ( Environment::const_iterator it = environment.begin(); it != environment.end(); ++it ) {
setenv( it->first.c_str(), it->second.c_str(), 1 );
}
-
+
if(default_locale)
setenv("LC_ALL","C",1);
-
+
if(root)
{
if(chroot(root) == -1)
{
- ERR << "chroot to " << root << " failed: " << strerror(errno) << endl;
- _exit (3); // No sense in returning! I am forked away!!
- }
- if(chdir("/") == -1)
- {
- ERR << "chdir to / inside chroot failed: " << strerror(errno) << endl;
- _exit (4); // No sense in returning! I am forked away!!
+ _execError = str::form( _("Can't chroot to '%s' (%s)."), root, strerror(errno) );
+ std::cerr << _execError << endl;// After fork log on stderr too
+ _exit (128); // No sense in returning! I am forked away!!
}
+ if ( ! chdirTo )
+ chdirTo = "/";
}
-
+
+ if ( chdirTo && chdir( chdirTo ) == -1 )
+ {
+ _execError = root ? str::form( _("Can't chdir to '%s' inside chroot '%s' (%s)."), chdirTo, root, strerror(errno) )
+ : str::form( _("Can't chdir to '%s' (%s)."), chdirTo, strerror(errno) );
+ std::cerr << _execError << endl;// After fork log on stderr too
+ _exit (128); // No sense in returning! I am forked away!!
+ }
+
// close all filedesctiptors above stderr
for ( int i = ::getdtablesize() - 1; i > 2; --i ) {
::close( i );
}
-
+
+ if ( die_with_parent ) {
+ // process dies with us
+ int r = prctl(PR_SET_PDEATHSIG, SIGTERM);
+ if (r == -1) {
+ //ignore if it did not work, worst case the process lives on after the parent dies
+ std::cerr << "Failed to set PR_SET_PDEATHSIG" << endl;// After fork log on stderr too
+ }
+
+ // test in case the original parent exited just
+ // before the prctl() call
+ pid_t ppidNow = getppid();
+ if (ppidNow != ppid_before_fork) {
+ std::cerr << "PPID changed from "<<ppid_before_fork<<" to "<< ppidNow << endl;// After fork log on stderr too
+ _exit(128);
+ }
+ }
+
execvp(argv[0], const_cast<char *const *>(argv));
- ERR << "Cannot execute external program "
- << argv[0] << ":" << strerror(errno) << endl;
- _exit (5); // No sense in returning! I am forked away!!
+ // don't want to get here
+ _execError = str::form( _("Can't exec '%s' (%s)."), argv[0], strerror(errno) );
+ std::cerr << _execError << endl;// After fork log on stderr too
+ _exit (129); // No sense in returning! I am forked away!!
+ //////////////////////////////////////////////////////////////////////
}
-
+
else if (pid == -1) // Fork failed, close everything.
{
- if (use_pty) {
+ _execError = str::form( _("Can't fork (%s)."), strerror(errno) );
+ _exitStatus = 127;
+ ERR << _execError << endl;
+
+ if (use_pty) {
::close(master_tty);
::close(slave_tty);
}
::close(from_external[0]);
::close(from_external[1]);
}
- ERR << "Cannot fork " << strerror(errno) << endl;
}
-
+
else {
if (use_pty)
{
inputfile = fdopen(from_external[0], "r");
outputfile = fdopen(to_external[1], "w");
}
-
+
DBG << "pid " << pid << " launched" << endl;
-
+
if (!inputfile || !outputfile)
{
ERR << "Cannot create streams to external program " << argv[0] << endl;
}
}
}
-
-
+
+
int
ExternalProgram::close()
{
if (pid > 0)
{
- ExternalDataSource::close();
- // Wait for child to exit
- int ret;
- int status = 0;
- do
- {
- ret = waitpid(pid, &status, 0);
- }
- while (ret == -1 && errno == EINTR);
-
- if (ret != -1)
- {
- status = checkStatus( status );
- }
- pid = -1;
- return status;
- }
- else
- {
- return _exitStatus;
+ if ( inputFile() )
+ {
+ // Discard any output instead of closing the pipe,
+ // but watch out for the command exiting while some
+ // subprocess keeps the filedescriptor open.
+ setBlocking( false );
+ FILE * inputfile = inputFile();
+ int inputfileFd = ::fileno( inputfile );
+ long delay = 0;
+ do
+ {
+ /* Watch inputFile to see when it has input. */
+ fd_set rfds;
+ FD_ZERO( &rfds );
+ FD_SET( inputfileFd, &rfds );
+
+ /* Wait up to 1 seconds. */
+ struct timeval tv;
+ tv.tv_sec = (delay < 0 ? 1 : 0);
+ tv.tv_usec = (delay < 0 ? 0 : delay*100000);
+ if ( delay >= 0 && ++delay > 9 )
+ delay = -1;
+ int retval = select( inputfileFd+1, &rfds, NULL, NULL, &tv );
+
+ if ( retval == -1 )
+ {
+ ERR << "select error: " << strerror(errno) << endl;
+ if ( errno != EINTR )
+ break;
+ }
+ else if ( retval )
+ {
+ // Data is available now.
+ static size_t linebuffer_size = 0; // static because getline allocs
+ static char * linebuffer = 0; // and reallocs if buffer is too small
+ getline( &linebuffer, &linebuffer_size, inputfile );
+ // ::feof check is important as select returns
+ // positive if the file was closed.
+ if ( ::feof( inputfile ) )
+ break;
+ clearerr( inputfile );
+ }
+ else
+ {
+ // No data within time.
+ if ( ! running() )
+ break;
+ }
+ } while ( true );
+ }
+
+ if ( pid > 0 ) // bsc#1109877: must re-check! running() in the loop above may have already waited.
+ {
+ // Wait for child to exit
+ int ret;
+ int status = 0;
+ do
+ {
+ ret = waitpid(pid, &status, 0);
+ }
+ while (ret == -1 && errno == EINTR);
+
+ if (ret != -1)
+ {
+ _exitStatus = checkStatus( status );
+ }
+ pid = -1;
+ }
}
+
+ return _exitStatus;
}
-
-
+
+
int ExternalProgram::checkStatus( int status )
{
if (WIFEXITED (status))
status = WEXITSTATUS (status);
if(status)
{
- DBG << "pid " << pid << " exited with status " << status << endl;
+ DBG << "Pid " << pid << " exited with status " << status << endl;
+ _execError = str::form( _("Command exited with status %d."), status );
}
else
{
// if 'launch' is logged, completion should be logged,
// even if successfull.
- DBG << "pid " << pid << " successfully completed" << endl;
+ DBG << "Pid " << pid << " successfully completed" << endl;
+ _execError.clear(); // empty if running or successfully completed
}
}
else if (WIFSIGNALED (status))
{
status = WTERMSIG (status);
- WAR << "pid " << pid << " was killed by signal " << status
+ WAR << "Pid " << pid << " was killed by signal " << status
<< " (" << strsignal(status);
if (WCOREDUMP (status))
{
WAR << ", core dumped";
}
WAR << ")" << endl;
+ _execError = str::form( _("Command was killed by signal %d (%s)."), status, strsignal(status) );
status+=128;
}
else {
- ERR << "pid " << pid << " exited with unknown error" << endl;
+ ERR << "Pid " << pid << " exited with unknown error" << endl;
+ _execError = _("Command exited with unknown error.");
}
-
+
return status;
}
-
+
bool
ExternalProgram::kill()
{
}
return true;
}
-
-
+
+ bool ExternalProgram::kill(int sig)
+ {
+ if (pid > 0)
+ {
+ ::kill(pid, sig);
+ }
+ return true;
+ }
+
bool
ExternalProgram::running()
{
if ( pid < 0 ) return false;
-
+
int status = 0;
int p = waitpid( pid, &status, WNOHANG );
- if ( p < 0 ) return false;
-
- status = checkStatus( status );
-
- if ( status == 0 )
- {
- return true;
- }
- else
- {
- _exitStatus = status;
- pid = -1;
+ switch ( p )
+ {
+ case -1:
+ ERR << "waitpid( " << pid << ") returned error '" << strerror(errno) << "'" << endl;
return false;
- }
+ break;
+ case 0:
+ return true; // still running
+ break;
+ }
+
+ // Here: completed...
+ _exitStatus = checkStatus( status );
+ pid = -1;
+ return false;
}
-
+
// origfd will be accessible as newfd and closed (unless they were equal)
void ExternalProgram::renumber_fd (int origfd, int newfd)
{
::close (origfd);
}
}
-
+
+ std::ostream & ExternalProgram::operator>>( std::ostream & out_r )
+ {
+ setBlocking( true );
+ for ( std::string line = receiveLine(); line.length(); line = receiveLine() )
+ out_r << line;
+ return out_r;
+ }
+
+ //////////////////////////////////////////////////////////////////////
+ //
+ // class ExternalProgramWithStderr
+ //
+ //////////////////////////////////////////////////////////////////////
+
+ namespace externalprogram
+ {
+ EarlyPipe::EarlyPipe()
+ {
+ _fds[R] = _fds[W] = -1;
+#ifdef HAVE_PIPE2
+ ::pipe2( _fds, O_NONBLOCK );
+#else
+ ::pipe( _fds );
+ ::fcntl(_fds[R], F_SETFD, O_NONBLOCK );
+ ::fcntl(_fds[W], F_SETFD, O_NONBLOCK );
+#endif
+ _stderr = ::fdopen( _fds[R], "r" );
+ }
+
+ EarlyPipe::~EarlyPipe()
+ {
+ closeW();
+ if ( _stderr )
+ ::fclose( _stderr );
+ }
+ } // namespace externalprogram
+
+ bool ExternalProgramWithStderr::stderrGetUpTo( std::string & retval_r, const char delim_r, bool returnDelim_r )
+ {
+ if ( ! _stderr )
+ return false;
+ if ( delim_r && ! _buffer.empty() )
+ {
+ // check for delim already in buffer
+ std::string::size_type pos( _buffer.find( delim_r ) );
+ if ( pos != std::string::npos )
+ {
+ retval_r = _buffer.substr( 0, returnDelim_r ? pos+1 : pos );
+ _buffer.erase( 0, pos+1 );
+ return true;
+ }
+ }
+ ::clearerr( _stderr );
+ do {
+ int ch = fgetc( _stderr );
+ if ( ch != EOF )
+ {
+ if ( ch != delim_r || ! delim_r )
+ _buffer.push_back( ch );
+ else
+ {
+ if ( returnDelim_r )
+ _buffer.push_back( delim_r );
+ break;
+ }
+ }
+ else if ( ::feof( _stderr ) )
+ {
+ if ( _buffer.empty() )
+ return false;
+ break;
+ }
+ else if ( errno != EINTR )
+ return false;
+ } while ( true );
+ // HERE: we left after readig at least one char (\n)
+ retval_r.swap( _buffer );
+ _buffer.clear();
+ return true;
+ }
+
+
} // namespace zypp