#include <sstream>
#include "zypp/base/Logger.h"
+#include "zypp/base/String.h"
+#include "zypp/base/Gettext.h"
#include "zypp/ExternalProgram.h"
using namespace std;
namespace zypp {
ExternalProgram::ExternalProgram()
- : use_pty (false)
- {
- }
+ : use_pty (false)
+ , pid( -1 )
+ {}
+
ExternalProgram::ExternalProgram( std::string commandline,
Stderr_Disposition stderr_disp,
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 char *const *argv,
+ ExternalProgram::ExternalProgram( const Arguments & argv,
Stderr_Disposition stderr_disp,
bool use_pty,
- int stderr_fd,
+ 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 != "/")
+ 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()
- {
- }
+ {}
- 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 )
{
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 " << _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!
-
- stringstream cmdstr;
-
- cmdstr << "Executing ";
- for (int i = 0; argv[i]; i++)
- {
- if (i>0) cmdstr << ' ';
- cmdstr << '\'';
- cmdstr << argv[i];
- cmdstr << '\'';
- }
- DBG << cmdstr.str() << endl;
-
// Create module process
if ((pid = fork()) == 0)
{
+ //////////////////////////////////////////////////////////////////////
+ // Don't write to the logfile after fork!
+ //////////////////////////////////////////////////////////////////////
if (use_pty)
{
setsid();
::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)
{
- 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 );
}
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 (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 );
+ }
+
+ // 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;
}
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;
}
}
+ 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 );
+ }
+ }
+
+ 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