/** @file
$ Id $
$ Date $
$ Author $
@ copy & copy ; Marc W & auml ; ckerlin
@ license LGPL , see file < a href = " license.html " > COPYING < / a >
$ Log $
Revision 1.5 2004 / 12 / 14 20 : 30 : 09 marc
added possibility to pass string to stdin of child process
Revision 1.4 2004 / 08 / 28 16 : 21 : 25 marc
mrw - c + + - 0.92 ( mrw )
- new file : version . cpp
- new file header for all sources
- work around warning in mrw : : auto < T >
- possibility to compile without log4cxx
- work around bugs in demangle . h and libiberty . h
- corrections in documentation
- added simple tracing mechanism
- more warnings
- small corrections in Auto < > : : Free and a new test for it
- possibility to compile without stack trace
*/
# include <mrw/exec.hpp>
# include <mrw/unistd.hpp>
# include <mrw/exception.hpp>
# include <mrw/stdext.hpp> // max
# include <sys/wait.h> // waitpid
# include <unistd.h> // fork, exec
# include <string.h> // memcpy
# include <assert.h> // assert
mrw : : ExecutionFailedExc : : ExecutionFailedExc ( const std : : string & w ,
const std : : string & c )
throw ( std : : bad_exception ) :
_what ( std : : string ( " mrw::Exec: command execution failed \n " ) +
std : : string ( " failed command was: \" " + c + " \" \n " ) +
std : : string ( " error was: \" " ) + w + ' " ' ) {
/**
@ c what looks like :
@ verbatim
mrw : : Exec : command execution failed
failed command was : " /bin/OOOOPS -v -q --crash "
error was : " execution failed "
@ endverbatim
*/
}
mrw : : Cmd : : Cmd ( const std : : string & c ) throw ( std : : bad_exception ) {
_cmd . push_back ( c ) ;
}
mrw : : Cmd & mrw : : Cmd : : operator , ( const std : : string & arg )
throw ( std : : bad_exception ) {
_cmd . push_back ( arg ) ;
return * this ;
}
mrw : : Cmd & mrw : : Cmd : : operator < < ( const std : : string & arg )
throw ( std : : bad_exception ) {
_cmd . push_back ( arg ) ;
return * this ;
}
mrw : : Cmd : : operator std : : string ( ) const throw ( std : : bad_exception ) {
ArgList : : const_iterator it ( _cmd . begin ( ) ) ;
std : : string c ( * it ) ;
while ( + + it ! = _cmd . end ( ) ) c + = ' ' + * it ;
return c ;
}
mrw : : Cmd : : operator mrw : : Exec ( ) const throw ( std : : bad_exception ) {
return mrw : : Exec ( * this ) ;
}
mrw : : Exec mrw : : Cmd : : execute ( bool exc ) const throw ( std : : exception ) {
return mrw : : Exec ( * this ) . execute ( exc ) ;
}
mrw : : Exec mrw : : Cmd : : execute ( const std : : string & input , bool exc ) const
throw ( std : : exception ) {
return mrw : : Exec ( * this ) . execute ( input , exc ) ;
}
const char * mrw : : Cmd : : path ( ) const throw ( std : : bad_exception ) {
return _cmd . front ( ) . c_str ( ) ;
}
char * * mrw : : Cmd : : args ( ) const throw ( std : : bad_exception ) {
if ( _cmd . size ( ) = = 0 ) return 0 ;
char * * array = new char * [ _cmd . size ( ) + 1 ] ;
int i ( 0 ) ;
for ( ArgList : : const_iterator it ( _cmd . begin ( ) ) ; it ! = _cmd . end ( ) ; + + it )
memcpy ( array [ i + + ] = new char [ it - > size ( ) + 1 ] , it - > c_str ( ) , it - > size ( ) + 1 ) ;
array [ i ] = 0 ;
return array ;
}
mrw : : Exec : : Exec ( const mrw : : Cmd & c ) throw ( std : : bad_exception ) :
_cmd ( new mrw : : Cmd ( c ) ) , _success ( false ) {
}
mrw : : Exec : : Exec ( const mrw : : Exec & e ) throw ( std : : bad_exception ) :
_cmd ( new mrw : : Cmd ( * e . _cmd ) ) ,
_res ( e . _res ) , _err ( e . _err ) , _success ( e . _success ) {
}
mrw : : Exec : : ~ Exec ( ) throw ( ) {
delete _cmd ;
}
mrw : : Exec & mrw : : Exec : : operator = ( const mrw : : Exec & e ) throw ( std : : bad_exception ) {
if ( this = = & e ) return * this ;
* _cmd = * e . _cmd ; _res = e . _res ; _err = e . _err ; _success = e . _success ;
return * this ;
}
mrw : : Exec & mrw : : Exec : : execute ( bool exc ) throw ( std : : exception ) {
/** This method calls @c fork, sets up a pipe connection to pass @c
stdout and @ c stderr from the child process to the parent
process using mrw : : Pipe and calls @ c execvp to execute the
program . */
_success = false ;
_res = _err = " " ;
mrw : : Pipe stdOut , stdErr ;
if ( ! stdOut | | ! stdErr )
throw mrw : : ExecutionFailedExc ( " cannot create pipe " , * _cmd ) ;
pid_t pid ( fork ( ) ) ;
if ( pid < 0 )
throw ExecutionFailedExc ( " cannot fork " , * _cmd ) ;
if ( pid ) { // parent
stdOut . close_out ( ) ;
stdErr . close_out ( ) ;
if ( ! stdOut | | ! stdErr )
throw ExecutionFailedExc ( " cannot close pipe " , * _cmd ) ;
int num1 ( 0 ) , num2 ( 0 ) ;
for ( char buf1 [ 4096 ] , buf2 [ 4096 ] ;
( num1 = read ( stdOut . istream ( ) , buf1 , sizeof ( buf1 ) ) ) > 0
| | num1 = = - 1 & & ( errno = = EINTR | | errno = = EAGAIN )
| | ( num2 = read ( stdErr . istream ( ) , buf2 , sizeof ( buf2 ) ) ) > 0
| | num2 = = - 1 & & ( errno = = EINTR | | errno = = EAGAIN ) ; ) {
if ( num1 > 0 ) _res + = std : : string ( buf1 , num1 ) ;
if ( num2 > 0 ) _err + = std : : string ( buf2 , num2 ) ;
}
// wait for child to get return code
int s ( 0 ) ;
if ( waitpid ( pid , & s , 0 ) ! = pid | | WIFEXITED ( s ) ! = 0 & & WEXITSTATUS ( s ) ! = 0
| | num1 = = - 1 | | num2 = = - 1 ) {
if ( exc ) {
throw ExecutionFailedExc ( " execution failed " , * _cmd ) ;
} else {
_success = false ;
return * this ;
}
}
} else { // child
stdOut . close_in ( ) ;
stdErr . close_in ( ) ;
stdOut . connect_cout ( ) ;
stdErr . connect_cerr ( ) ;
execvp ( _cmd - > path ( ) , _cmd - > args ( ) ) ;
exit ( 1 ) ; // execute failed
}
_success = true ;
return * this ;
}
mrw : : Exec & mrw : : Exec : : execute ( const std : : string & input , bool exc )
throw ( std : : exception ) {
/// @c input length must be smaller than @c SSIZE_MAX.
/// I'll only add support for longer strings upon request.
assert ( input . size ( ) < = SSIZE_MAX & &
" sdin input exeeds C library limit in mrw::Exec "
" please contact the author of the library " ) ;
/** This method calls @c fork, sets up a pipe connection to pass @c
stdin , @ c stdout and @ c stderr from the child process to the
parent process using mrw : : Pipe and calls @ c execvp to execute
the program . */
_success = false ;
_res = _err = " " ;
mrw : : Pipe stdIn ( true ) , // child terminates if stdin is non-blocking
stdOut , stdErr ;
if ( ! stdIn | | ! stdOut | | ! stdErr )
throw mrw : : ExecutionFailedExc ( " cannot create pipe " , * _cmd ) ;
pid_t pid ( fork ( ) ) ;
if ( pid < 0 ) throw ExecutionFailedExc ( " cannot fork " , * _cmd ) ;
if ( pid ) { // parent
stdIn . close_in ( ) ;
stdOut . close_out ( ) ;
stdErr . close_out ( ) ;
if ( ! stdIn | | ! stdOut | | ! stdErr )
throw ExecutionFailedExc ( " cannot close pipes " , * _cmd ) ;
ssize_t num0 ( - 1 ) , num1 ( - 1 ) , num2 ( - 1 ) ;
std : : string in ( input ) ;
fd_set writefds , readfds ;
char buf [ 4096 ] ;
while ( num0 | | num1 | | num2 ) try { // no end of file in stdin, -out, -err
int res ( 0 ) , s ( 0 ) ;
if ( ( res = waitpid ( pid , & s , WNOHANG ) ) ) {
if ( res ! = pid | | WIFEXITED ( s ) ! = 0 & & WEXITSTATUS ( s ) ! = 0 )
throw ExecutionFailedExc ( " execution failed " , * _cmd ) ;
_success = true ;
return * this ;
}
// select...
FD_ZERO ( & writefds ) ;
FD_ZERO ( & readfds ) ;
if ( num0 ) FD_SET ( stdIn . ostream ( ) , & writefds ) ;
if ( num1 ) FD_SET ( stdOut . istream ( ) , & readfds ) ;
if ( num2 ) FD_SET ( stdErr . istream ( ) , & readfds ) ;
timeval tm = { 1 , 0 } ;
int n ( mrw : : max ( ( num0 ? stdIn . ostream ( ) : 0 ) ,
mrw : : max ( ( num1 ? stdOut . istream ( ) : 0 ) ,
( num2 ? stdErr . istream ( ) : 0 ) ) ) ) ;
int num = select ( n , & readfds , & writefds , 0 , & tm ) ;
if ( num < 0 ) throw ExecutionFailedExc ( " select failed " , * _cmd ) ;
// check and handle stdin
if ( num0 & & num > 0
& & FD_ISSET ( stdIn . ostream ( ) , & writefds ) )
if ( ( num0 = write ( stdIn . ostream ( ) , in . c_str ( ) , in . size ( ) ) ) > 0 ) {
if ( ( unsigned int ) num0 < in . size ( ) )
in = in . substr ( num0 ) ;
else if ( ( unsigned int ) num0 = = in . size ( ) )
num0 = 0 , stdIn . close_out ( ) ;
} else if ( num0 = = - 1 & & ( errno ! = EINTR & & errno ! = EAGAIN ) )
throw ExecutionFailedExc ( " writing stdin " , * _cmd ) ;
// check and handle stdout
if ( num > 0 & & FD_ISSET ( stdOut . istream ( ) , & readfds ) )
if ( ( num1 = read ( stdOut . istream ( ) , buf , sizeof ( buf ) ) ) > 0 )
_res + = std : : string ( buf , num1 ) ;
else if ( num1 = = - 1 & & ( errno ! = EINTR & & errno ! = EAGAIN ) )
throw ExecutionFailedExc ( " readin stdout " , * _cmd ) ;
// check and handle stderr
if ( num > 0 & & FD_ISSET ( stdErr . istream ( ) , & readfds ) )
if ( ( num2 = read ( stdErr . istream ( ) , buf , sizeof ( buf ) ) ) > 0 )
_err + = std : : string ( buf , num2 ) ;
else if ( num2 = = - 1 & & ( errno ! = EINTR & & errno ! = EAGAIN ) )
throw ExecutionFailedExc ( " readin stderr " , * _cmd ) ;
} catch ( . . . ) {
if ( exc ) throw ;
_success = false ;
return * this ;
}
// wait for child to get return code
int s ( 0 ) ;
if ( waitpid ( pid , & s , 0 ) ! = pid | | WIFEXITED ( s ) ! = 0 & & WEXITSTATUS ( s ) ! = 0 ) {
if ( exc ) {
throw ExecutionFailedExc ( " execution failed " , * _cmd ) ;
} else {
_success = false ;
return * this ;
}
}
} else { // child
stdIn . close_out ( ) ;
stdOut . close_in ( ) ;
stdErr . close_in ( ) ;
stdIn . connect_cin ( ) ;
stdOut . connect_cout ( ) ; // if cin is non blocking, child terminates here?!?
stdErr . connect_cerr ( ) ;
execvp ( _cmd - > path ( ) , _cmd - > args ( ) ) ;
exit ( 1 ) ; // execute failed
}
_success = true ;
return * this ;
}
mrw : : Exec & mrw : : Exec : : operator > > ( std : : string & res ) throw ( std : : exception ) {
execute ( ) ;
res + = _res ;
return * this ;
}
mrw : : Exec : : operator std : : string & ( ) throw ( std : : exception ) {
if ( ! _success ) execute ( ) ;
return _res ;
}
mrw : : Exec : : operator bool ( ) throw ( std : : bad_exception ) {
return _success ;
}
std : : string & mrw : : Exec : : result ( ) throw ( std : : exception ) {
if ( ! _success ) execute ( ) ;
return _res ;
}
std : : string & mrw : : Exec : : error ( ) throw ( std : : exception ) {
if ( ! _success ) execute ( ) ;
return _err ;
}
bool mrw : : Exec : : success ( ) throw ( std : : bad_exception ) {
return _success ;
}