/*                 This file is part of the Opie Project             Copyright (C) 2002-2004 Holger Freyther and The Opie Team =. Based on KProcess (C) 1997 Christian Czezatke (e9025461@student.tuwien.ac.at) .=l.          .>+-= _;:,     .>    :=|. This program is free software; you can .> <`_,   >  .   <= redistribute it and/or modify it under :`=1 )Y*s>-.--   : the terms of the GNU Library General Public .="- .-=="i,     .._ License as published by the Free Software - .   .-<_>     .<> Foundation; either version 2 of the License,    ._= =}       : or (at your option) any later version.   .%`+i>       _;_.   .i_,=:_.      -`: PARTICULAR PURPOSE. See the GNU ..}^=.=       =       ; Library General Public License for more ++=   -.     .`     .: details. :     =  ...= . :.=- -.   .:....=;==+<; You should have received a copy of the GNU -_. . .   )=.  = Library General Public License along with   --        :-=` this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "oprocctrl.h" /* OPIE */ #include /* QT */ #include #include #include #include #include /* STD */ #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_SYS_SELECT_H #include #endif #ifdef HAVE_INITGROUPS #include #endif using namespace Opie::Core::Internal; namespace Opie { namespace Core { namespace Internal { class OProcessPrivate { public: OProcessPrivate() : useShell( false ) { } bool useShell; QMap env; QString wd; QCString shell; }; } OProcess::OProcess( QObject *parent, const char *name ) : QObject( parent, name ) { init ( ); } OProcess::OProcess( const QString &arg0, QObject *parent, const char *name ) : QObject( parent, name ) { init ( ); *this << arg0; } OProcess::OProcess( const QStringList &args, QObject *parent, const char *name ) : QObject( parent, name ) { init ( ); *this << args; } void OProcess::init ( ) { run_mode = NotifyOnExit; runs = false; pid_ = 0; status = 0; keepPrivs = false; innot = 0; outnot = 0; errnot = 0; communication = NoCommunication; input_data = 0; input_sent = 0; input_total = 0; d = 0; if ( 0 == OProcessController::theOProcessController ) { ( void ) new OProcessController(); CHECK_PTR( OProcessController::theOProcessController ); } OProcessController::theOProcessController->addOProcess( this ); out[ 0 ] = out[ 1 ] = -1; in[ 0 ] = in[ 1 ] = -1; err[ 0 ] = err[ 1 ] = -1; } void OProcess::setEnvironment( const QString &name, const QString &value ) { if ( !d ) d = new OProcessPrivate; d->env.insert( name, value ); } void OProcess::setWorkingDirectory( const QString &dir ) { if ( !d ) d = new OProcessPrivate; d->wd = dir; } void OProcess::setupEnvironment() { if ( d ) { QMap::Iterator it; for ( it = d->env.begin(); it != d->env.end(); ++it ) setenv( QFile::encodeName( it.key() ).data(), QFile::encodeName( it.data() ).data(), 1 ); if ( !d->wd.isEmpty() ) chdir( QFile::encodeName( d->wd ).data() ); } } void OProcess::setRunPrivileged( bool keepPrivileges ) { keepPrivs = keepPrivileges; } bool OProcess::runPrivileged() const { return keepPrivs; } OProcess::~OProcess() { // destroying the OProcess instance sends a SIGKILL to the // child process (if it is running) after removing it from the // list of valid processes (if the process is not started as // "DontCare") OProcessController::theOProcessController->removeOProcess( this ); // this must happen before we kill the child // TODO: block the signal while removing the current process from the process list if ( runs && ( run_mode != DontCare ) ) kill( SIGKILL ); // Clean up open fd's and socket notifiers. closeStdin(); closeStdout(); closeStderr(); // TODO: restore SIGCHLD and SIGPIPE handler if this is the last OProcess delete d; } void OProcess::detach() { OProcessController::theOProcessController->removeOProcess( this ); runs = false; pid_ = 0; // Clean up open fd's and socket notifiers. closeStdin(); closeStdout(); closeStderr(); } bool OProcess::setExecutable( const QString& proc ) { if ( runs ) return false; if ( proc.isEmpty() ) return false; if ( !arguments.isEmpty() ) arguments.remove( arguments.begin() ); arguments.prepend( QFile::encodeName( proc ) ); return true; } OProcess &OProcess::operator<<( const QStringList& args ) { QStringList::ConstIterator it = args.begin(); for ( ; it != args.end() ; ++it ) arguments.append( QFile::encodeName( *it ) ); return *this; } OProcess &OProcess::operator<<( const QCString& arg ) { return operator<< ( arg.data() ); } OProcess &OProcess::operator<<( const char* arg ) { arguments.append( arg ); return *this; } OProcess &OProcess::operator<<( const QString& arg ) { arguments.append( QFile::encodeName( arg ) ); return *this; } void OProcess::clearArguments() { arguments.clear(); } bool OProcess::start( RunMode runmode, Communication comm ) { uint i; uint n = arguments.count(); char **arglist; if ( runs || ( 0 == n ) ) { return false; // cannot start a process that is already running // or if no executable has been assigned } run_mode = runmode; status = 0; QCString shellCmd; if ( d && d->useShell ) { if ( d->shell.isEmpty() ) { qWarning( "Could not find a valid shell" ); return false; } arglist = static_cast( malloc( ( 4 ) * sizeof( char * ) ) ); for ( i = 0; i < n; i++ ) { shellCmd += arguments[ i ]; shellCmd += " "; // CC: to separate the arguments } arglist[ 0 ] = d->shell.data(); arglist[ 1 ] = ( char * ) "-c"; arglist[ 2 ] = shellCmd.data(); arglist[ 3 ] = 0; } else { arglist = static_cast( malloc( ( n + 1 ) * sizeof( char * ) ) ); for ( i = 0; i < n; i++ ) arglist[ i ] = arguments[ i ].data(); arglist[ n ] = 0; } if ( !setupCommunication( comm ) ) qWarning( "Could not setup Communication!" ); // We do this in the parent because if we do it in the child process // gdb gets confused when the application runs from gdb. uid_t uid = getuid(); gid_t gid = getgid(); #ifdef HAVE_INITGROUPS struct passwd *pw = getpwuid( uid ); #endif int fd[ 2 ]; if ( 0 > pipe( fd ) ) { fd[ 0 ] = fd[ 1 ] = 0; // Pipe failed.. continue } runs = true; QApplication::flushX(); // WABA: Note that we use fork() and not vfork() because // vfork() has unclear semantics and is not standardized. pid_ = fork(); if ( 0 == pid_ ) { if ( fd[ 0 ] ) close( fd[ 0 ] ); if ( !runPrivileged() ) { setgid( gid ); #if defined( HAVE_INITGROUPS) if ( pw ) initgroups( pw->pw_name, pw->pw_gid ); #endif setuid( uid ); } // The child process if ( !commSetupDoneC() ) qWarning( "Could not finish comm setup in child!" ); setupEnvironment(); // Matthias if ( run_mode == DontCare ) setpgid( 0, 0 ); // restore default SIGPIPE handler (Harri) struct sigaction act; sigemptyset( &( act.sa_mask ) ); sigaddset( &( act.sa_mask ), SIGPIPE ); act.sa_handler = SIG_DFL; act.sa_flags = 0; sigaction( SIGPIPE, &act, 0L ); // We set the close on exec flag. // Closing of fd[1] indicates that the execvp succeeded! if ( fd[ 1 ] ) fcntl( fd[ 1 ], F_SETFD, FD_CLOEXEC ); execvp( arglist[ 0 ], arglist ); char resultByte = 1; if ( fd[ 1 ] ) write( fd[ 1 ], &resultByte, 1 ); _exit( -1 ); } else if ( -1 == pid_ ) { // forking failed runs = false; free( arglist ); return false; } else { if ( fd[ 1 ] ) close( fd[ 1 ] ); // the parent continues here // Discard any data for stdin that might still be there input_data = 0; // Check whether client could be started. if ( fd[ 0 ] ) for ( ;; ) { char resultByte; int n = ::read( fd[ 0 ], &resultByte, 1 ); if ( n == 1 ) { // Error runs = false; close( fd[ 0 ] ); free( arglist ); pid_ = 0; return false; } if ( n == -1 ) { if ( ( errno == ECHILD ) || ( errno == EINTR ) ) continue; // Ignore } break; // success } if ( fd[ 0 ] ) close( fd[ 0 ] ); if ( !commSetupDoneP() ) // finish communication socket setup for the parent qWarning( "Could not finish comm setup in parent!" ); if ( run_mode == Block ) { commClose(); // The SIGCHLD handler of the process controller will catch // the exit and set the status while ( runs ) { OProcessController::theOProcessController-> slotDoHousekeeping( 0 ); } runs = FALSE; emit processExited( this ); } } free( arglist ); return true; } bool OProcess::kill( int signo ) { bool rv = false; if ( 0 != pid_ ) rv = ( -1 != ::kill( pid_, signo ) ); // probably store errno somewhere... return rv; } bool OProcess::isRunning() const { return runs; } pid_t OProcess::pid() const { return pid_; } bool OProcess::normalExit() const { int _status = status; return ( pid_ != 0 ) && ( !runs ) && ( WIFEXITED( ( _status ) ) ); } int OProcess::exitStatus() const { int _status = status; return WEXITSTATUS( ( _status ) ); } bool OProcess::writeStdin( const char *buffer, int buflen ) { bool rv; // if there is still data pending, writing new data // to stdout is not allowed (since it could also confuse // kprocess... if ( 0 != input_data ) return false; if ( runs && ( communication & Stdin ) ) { input_data = buffer; input_sent = 0; input_total = buflen; slotSendData( 0 ); innot->setEnabled( true ); rv = true; } else rv = false; return rv; } void OProcess::flushStdin ( ) { if ( !input_data || ( input_sent == input_total ) ) return ; int d1, d2; do { d1 = input_total - input_sent; slotSendData ( 0 ); d2 = input_total - input_sent; } while ( d2 <= d1 ); } void OProcess::suspend() { if ( ( communication & Stdout ) && outnot ) outnot->setEnabled( false ); } void OProcess::resume() { if ( ( communication & Stdout ) && outnot ) outnot->setEnabled( true ); } bool OProcess::closeStdin() { bool rv; if ( communication & Stdin ) { communication = ( Communication ) ( communication & ~Stdin ); delete innot; innot = 0; close( in[ 1 ] ); rv = true; } else rv = false; return rv; } bool OProcess::closeStdout() { bool rv; if ( communication & Stdout ) { communication = ( Communication ) ( communication & ~Stdout ); delete outnot; outnot = 0; close( out[ 0 ] ); rv = true; } else rv = false; return rv; } bool OProcess::closeStderr() { bool rv; if ( communication & Stderr ) { communication = static_cast( communication & ~Stderr ); delete errnot; errnot = 0; close( err[ 0 ] ); rv = true; } else rv = false; return rv; } void OProcess::slotChildOutput( int fdno ) { if ( !childOutput( fdno ) ) closeStdout(); } void OProcess::slotChildError( int fdno ) { if ( !childError( fdno ) ) closeStderr(); } void OProcess::slotSendData( int ) { if ( input_sent == input_total ) { innot->setEnabled( false ); input_data = 0; emit wroteStdin( this ); } else input_sent += ::write( in[ 1 ], input_data + input_sent, input_total - input_sent ); } void OProcess::processHasExited( int state ) { if ( runs ) { runs = false; status = state; commClose(); // cleanup communication sockets // also emit a signal if the process was run Blocking if ( DontCare != run_mode ) { emit processExited( this ); } } } int OProcess::childOutput( int fdno ) { if ( communication & NoRead ) { int len = -1; emit receivedStdout( fdno, len ); errno = 0; // Make sure errno doesn't read "EAGAIN" return len; } else { char buffer[ 1024 ]; int len; len = ::read( fdno, buffer, 1024 ); if ( 0 < len ) { emit receivedStdout( this, buffer, len ); } return len; } } int OProcess::childError( int fdno ) { char buffer[ 1024 ]; int len; len = ::read( fdno, buffer, 1024 ); if ( 0 < len ) emit receivedStderr( this, buffer, len ); return len; } int OProcess::setupCommunication( Communication comm ) { int ok; communication = comm; ok = 1; if ( comm & Stdin ) ok &= socketpair( AF_UNIX, SOCK_STREAM, 0, in ) >= 0; if ( comm & Stdout ) ok &= socketpair( AF_UNIX, SOCK_STREAM, 0, out ) >= 0; if ( comm & Stderr ) ok &= socketpair( AF_UNIX, SOCK_STREAM, 0, err ) >= 0; return ok; } int OProcess::commSetupDoneP() { int ok = 1; if ( communication != NoCommunication ) { if ( communication & Stdin ) close( in[ 0 ] ); if ( communication & Stdout ) close( out[ 1 ] ); if ( communication & Stderr ) close( err[ 1 ] ); // Don't create socket notifiers and set the sockets non-blocking if // blocking is requested. if ( run_mode == Block ) return ok; if ( communication & Stdin ) { // ok &= (-1 != fcntl(in[1], F_SETFL, O_NONBLOCK)); innot = new QSocketNotifier( in[ 1 ], QSocketNotifier::Write, this ); CHECK_PTR( innot ); innot->setEnabled( false ); // will be enabled when data has to be sent QObject::connect( innot, SIGNAL( activated(int) ), this, SLOT( slotSendData(int) ) ); } if ( communication & Stdout ) { // ok &= (-1 != fcntl(out[0], F_SETFL, O_NONBLOCK)); outnot = new QSocketNotifier( out[ 0 ], QSocketNotifier::Read, this ); CHECK_PTR( outnot ); QObject::connect( outnot, SIGNAL( activated(int) ), this, SLOT( slotChildOutput(int) ) ); if ( communication & NoRead ) suspend(); } if ( communication & Stderr ) { // ok &= (-1 != fcntl(err[0], F_SETFL, O_NONBLOCK)); errnot = new QSocketNotifier( err[ 0 ], QSocketNotifier::Read, this ); CHECK_PTR( errnot ); QObject::connect( errnot, SIGNAL( activated(int) ), this, SLOT( slotChildError(int) ) ); } } return ok; } int OProcess::commSetupDoneC() { int ok = 1; struct linger so; memset( &so, 0, sizeof( so ) ); if ( communication & Stdin ) close( in[ 1 ] ); if ( communication & Stdout ) close( out[ 0 ] ); if ( communication & Stderr ) close( err[ 0 ] ); if ( communication & Stdin ) ok &= dup2( in[ 0 ], STDIN_FILENO ) != -1; else { int null_fd = open( "/dev/null", O_RDONLY ); ok &= dup2( null_fd, STDIN_FILENO ) != -1; close( null_fd ); } if ( communication & Stdout ) { ok &= dup2( out[ 1 ], STDOUT_FILENO ) != -1; ok &= !setsockopt( out[ 1 ], SOL_SOCKET, SO_LINGER, ( char* ) & so, sizeof( so ) ); } else { int null_fd = open( "/dev/null", O_WRONLY ); ok &= dup2( null_fd, STDOUT_FILENO ) != -1; close( null_fd ); } if ( communication & Stderr ) { ok &= dup2( err[ 1 ], STDERR_FILENO ) != -1; ok &= !setsockopt( err[ 1 ], SOL_SOCKET, SO_LINGER, reinterpret_cast( &so ), sizeof( so ) ); } else { int null_fd = open( "/dev/null", O_WRONLY ); ok &= dup2( null_fd, STDERR_FILENO ) != -1; close( null_fd ); } return ok; } void OProcess::commClose() { if ( NoCommunication != communication ) { bool b_in = ( communication & Stdin ); bool b_out = ( communication & Stdout ); bool b_err = ( communication & Stderr ); if ( b_in ) delete innot; if ( b_out || b_err ) { // If both channels are being read we need to make sure that one socket buffer // doesn't fill up whilst we are waiting for data on the other (causing a deadlock). // Hence we need to use select. // Once one or other of the channels has reached EOF (or given an error) go back // to the usual mechanism. int fds_ready = 1; fd_set rfds; int max_fd = 0; if ( b_out ) { fcntl( out[ 0 ], F_SETFL, O_NONBLOCK ); if ( out[ 0 ] > max_fd ) max_fd = out[ 0 ]; delete outnot; outnot = 0; } if ( b_err ) { fcntl( err[ 0 ], F_SETFL, O_NONBLOCK ); if ( err[ 0 ] > max_fd ) max_fd = err[ 0 ]; delete errnot; errnot = 0; } while ( b_out || b_err ) { // * If the process is still running we block until we // receive data. (p_timeout = 0, no timeout) // * If the process has already exited, we only check // the available data, we don't wait for more. // (p_timeout = &timeout, timeout immediately) struct timeval timeout; timeout.tv_sec = 0; timeout.tv_usec = 0; struct timeval *p_timeout = runs ? 0 : &timeout; FD_ZERO( &rfds ); if ( b_out ) FD_SET( out[ 0 ], &rfds ); if ( b_err ) FD_SET( err[ 0 ], &rfds ); fds_ready = select( max_fd + 1, &rfds, 0, 0, p_timeout ); if ( fds_ready <= 0 ) break; if ( b_out && FD_ISSET( out[ 0 ], &rfds ) ) { int ret = 1; while ( ret > 0 ) ret = childOutput( out[ 0 ] ); if ( ( ret == -1 && errno != EAGAIN ) || ret == 0 ) b_out = false; } if ( b_err && FD_ISSET( err[ 0 ], &rfds ) ) { int ret = 1; while ( ret > 0 ) ret = childError( err[ 0 ] ); if ( ( ret == -1 && errno != EAGAIN ) || ret == 0 ) b_err = false; } } } if ( b_in ) { communication = ( Communication ) ( communication & ~Stdin ); close( in[ 1 ] ); } if ( b_out ) { communication = ( Communication ) ( communication & ~Stdout ); close( out[ 0 ] ); } if ( b_err ) { communication = ( Communication ) ( communication & ~Stderr ); close( err[ 0 ] ); } } } void OProcess::setUseShell( bool useShell, const char *shell ) { if ( !d ) d = new OProcessPrivate; d->useShell = useShell; d->shell = shell; if ( d->shell.isEmpty() ) d->shell = searchShell(); } QString OProcess::quote( const QString &arg ) { QString res = arg; res.replace( QRegExp( QString::fromLatin1( "\'" ) ), QString::fromLatin1( "'\"'\"'" ) ); res.prepend( '\'' ); res.append( '\'' ); return res; } QCString OProcess::searchShell() { QCString tmpShell = QCString( getenv( "SHELL" ) ).stripWhiteSpace(); if ( !isExecutable( tmpShell ) ) { tmpShell = "/bin/sh"; } return tmpShell; } bool OProcess::isExecutable( const QCString &filename ) { struct stat fileinfo; if ( filename.isEmpty() ) return false; // CC: we've got a valid filename, now let's see whether we can execute that file if ( -1 == stat( filename.data(), &fileinfo ) ) return false; // CC: return false if the file does not exist // CC: anyway, we cannot execute directories, block/character devices, fifos or sockets if ( ( S_ISDIR( fileinfo.st_mode ) ) || ( S_ISCHR( fileinfo.st_mode ) ) || ( S_ISBLK( fileinfo.st_mode ) ) || #ifdef S_ISSOCK // CC: SYSVR4 systems don't have that macro ( S_ISSOCK( fileinfo.st_mode ) ) || #endif ( S_ISFIFO( fileinfo.st_mode ) ) || ( S_ISDIR( fileinfo.st_mode ) ) ) { return false; } // CC: now check for permission to execute the file if ( access( filename.data(), X_OK ) != 0 ) return false; // CC: we've passed all the tests... return true; } int OProcess::processPID( const QString& process ) { QString line; QDir d = QDir( "/proc" ); QStringList dirs = d.entryList( QDir::Dirs ); QStringList::Iterator it; for ( it = dirs.begin(); it != dirs.end(); ++it ) { //qDebug( "next entry: %s", (const char*) *it ); QFile file( "/proc/"+*it+"/cmdline" ); if ( !file.open( IO_ReadOnly ) ) continue; if ( !file.isOpen() ) continue; QTextStream t( &file ); line = t.readLine(); //qDebug( "cmdline = %s", (const char*) line ); if ( line.contains( process ) ) break; //FIXME: That may find also other process, if the name is not long enough ;) } if ( line.contains( process ) ) { //qDebug( "found process id #%d", (*it).toInt() ); return (*it).toInt(); } else { //qDebug( "process '%s' not found", (const char*) process ); return 0; } } } }