#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 "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()
- : use_pty (false)
- {
- }
+ : use_pty (false)
+ , pid( -1 )
+ {}
ExternalProgram::ExternalProgram( std::string commandline,
int stderr_fd,
bool default_locale,
const Pathname & root )
- : use_pty (use_pty)
+ : use_pty (use_pty)
+ , pid( -1 )
{
const char *argv[4];
argv[0] = "/bin/sh";
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 Arguments &argv,
+ ExternalProgram::ExternalProgram( const Arguments & argv,
Stderr_Disposition stderr_disp,
- bool use_pty, int stderr_fd,
+ bool use_pty,
+ int stderr_fd,
bool default_locale,
- const Pathname& root)
- : use_pty (use_pty)
+ const Pathname & root )
+ : use_pty (use_pty)
+ , pid( -1 )
{
- const char * argvp[argv.size() + 1];
- unsigned c = 0;
- for_( i, argv.begin(), argv.end() )
- {
- argvp[c] = i->c_str();
- ++c;
- }
- argvp[c] = 0;
+ const char * argvp[argv.size() + 1];
+ unsigned c = 0;
+ for_( i, argv.begin(), argv.end() )
+ {
+ argvp[c] = i->c_str();
+ ++c;
+ }
+ argvp[c] = 0;
- Environment environment;
- const char* rootdir = NULL;
- if(!root.empty() && root != "/")
- {
- rootdir = root.asString().c_str();
- }
- start_program (argvp, environment, stderr_disp, stderr_fd, default_locale, rootdir);
+ start_program( argvp, Environment(), stderr_disp, stderr_fd, default_locale, root.c_str() );
}
- ExternalProgram::ExternalProgram (const Arguments &argv,
+ ExternalProgram::ExternalProgram( const Arguments & argv,
const Environment & environment,
Stderr_Disposition stderr_disp,
- bool use_pty, int stderr_fd,
+ bool use_pty,
+ int stderr_fd,
bool default_locale,
- const Pathname& root)
- : use_pty (use_pty)
+ const Pathname & root )
+ : use_pty (use_pty)
+ , pid( -1 )
{
- const char * argvp[argv.size() + 1];
- unsigned c = 0;
- for_( i, argv.begin(), argv.end() )
- {
- argvp[c] = i->c_str();
- ++c;
- }
- argvp[c] = 0;
-
- const char* rootdir = NULL;
- if(!root.empty() && root != "/")
- {
- rootdir = root.asString().c_str();
- }
- start_program (argvp, environment, stderr_disp, stderr_fd, default_locale, rootdir);
+ const char * argvp[argv.size() + 1];
+ unsigned c = 0;
+ for_( i, argv.begin(), argv.end() )
+ {
+ argvp[c] = i->c_str();
+ ++c;
+ }
+ argvp[c] = 0;
+ start_program( argvp, 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 )
- : use_pty (use_pty)
+ : use_pty (use_pty)
+ , pid( -1 )
{
- 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, const Environment & environment,
- Stderr_Disposition stderr_disp, bool use_pty,
- int stderr_fd, bool default_locale,
- const Pathname& root)
+ 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 )
{
- const char* rootdir = NULL;
- if(!root.empty() && root != "/")
- {
- rootdir = root.asString().c_str();
- }
- 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 *binpath, const char *const *argv_1,
- bool use_pty)
+ 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!
cmdstr << argv[i];
cmdstr << '\'';
}
+ if ( redirectStdin )
+ cmdstr << " < '" << redirectStdin << "'";
+ if ( redirectStdout )
+ cmdstr << " > '" << redirectStdout << "'";
_command = cmdstr.str();
}
- DBG << "Executing " << _command << endl;
+ DBG << "Executing" << (default_locale?"[C] ":" ") << _command << endl;
if (use_pty)
}
}
+ 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();
}
else
{
+ if ( switch_pgid )
+ setpgid( 0, 0);
renumber_fd (to_external[0], 0); // set new stdin
::close(from_external[0]); // Belongs to father process
::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)
{
if(chroot(root) == -1)
{
_execError = str::form( _("Can't chroot to '%s' (%s)."), root, strerror(errno) );
- ERR << _execError << endl;
- std::cerr << _execError << endl;// After fork log on stderr too
- _exit (128); // No sense in returning! I am forked away!!
- }
- if(chdir("/") == -1)
- {
- _execError = str::form( _("Can't chdir to '/' inside chroot (%s)."), strerror(errno) );
- ERR << _execError << endl;
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));
// don't want to get here
_execError = str::form( _("Can't exec '%s' (%s)."), argv[0], strerror(errno) );
- ERR << _execError << endl;
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 (pid > 0)
{
- setBlocking( true );
- while ( receiveLine().length() )
- ; // discard any output instead of closing the pipe
- //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;
}
// if 'launch' is logged, completion should be logged,
// even if successfull.
DBG << "Pid " << pid << " successfully completed" << endl;
- //_execError = _("Command successfully completed.");
+ _execError.clear(); // empty if running or successfully completed
}
}
else if (WIFSIGNALED (status))
return true;
}
+ bool ExternalProgram::kill(int sig)
+ {
+ if (pid > 0)
+ {
+ ::kill(pid, sig);
+ }
+ return true;
+ }
bool
ExternalProgram::running()
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