/**************************************************************************** ** $Id: jprocess_unix.cpp,v 1.13 2004/06/07 07:30:04 justin Exp $ ** ** Implementation of QProcess class for Unix ** ** Created : 20000905 ** ** Copyright (C) 1992-2003 Trolltech AS. All rights reserved. ** ** This file is part of the kernel module of the Qt GUI Toolkit. ** ** This file may be distributed under the terms of the Q Public License ** as defined by Trolltech AS of Norway and appearing in the file ** LICENSE.QPL included in the packaging of this file. ** ** This file may be distributed and/or modified under the terms of the ** GNU General Public License version 2 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. ** ** Licensees holding valid Qt Enterprise Edition or Qt Professional Edition ** licenses may use this file in accordance with the Qt Commercial License ** Agreement provided with the Software. ** ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. ** ** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for ** information about Qt Commercial License Agreements. ** See http://www.trolltech.com/qpl/ for QPL licensing information. ** See http://www.trolltech.com/gpl/ for GPL licensing information. ** ** Contact info@trolltech.com if any conditions of this licensing are ** not clear to you. ** **********************************************************************/ #include "qplatformdefs.h" // Solaris redefines connect -> __xnet_connect with _XOPEN_SOURCE_EXTENDED. #if defined(connect) #undef connect #endif //#include "qprocess.h" #include "jprocess.h" #ifndef QT_NO_PROCESS #include "qapplication.h" #include "qptrqueue.h" #include "qptrlist.h" #include "qsocketnotifier.h" #include "qtimer.h" #include "qcleanuphandler.h" #include "qregexp.h" #include "qguardedptr.h" // table to hold callbacks -- Justin struct JP_CALLBACK { void (*childStarting)(JProcess *); JProcess *p; }; static QPtrList jp_callback_list; static JP_CALLBACK *findCallback(JProcess *p) { QPtrListIterator it(jp_callback_list); for(JP_CALLBACK *cb; (cb = it.current()); ++it) { if(cb->p == p) return cb; } return 0; } static void setCallback(void (*childStarting)(JProcess *), JProcess *p) { JP_CALLBACK *cb = findCallback(p); if(childStarting) { if(!cb) { cb = new JP_CALLBACK; cb->p = p; jp_callback_list.append(cb); } cb->childStarting = childStarting; } else { if(cb) { jp_callback_list.removeRef(cb); delete cb; } } } static void callCallback(JProcess *p) { JP_CALLBACK *cb = findCallback(p); if(cb) cb->childStarting(p); } #include #include #ifdef __MIPSEL__ # ifndef SOCK_DGRAM # define SOCK_DGRAM 1 # endif # ifndef SOCK_STREAM # define SOCK_STREAM 2 # endif #endif //#define QT_QPROCESS_DEBUG // Don't need this -- Justin /* #ifdef Q_C_CALLBACKS extern "C" { #endif // Q_C_CALLBACKS QT_SIGNAL_RETTYPE qt_C_sigchldHnd(QT_SIGNAL_ARGS); #ifdef Q_C_CALLBACKS } #endif // Q_C_CALLBACKS */ class JProc; class JProcessManager; class JProcessPrivate { public: JProcessPrivate(); ~JProcessPrivate(); void closeOpenSocketsForChild(); void newProc( pid_t pid, JProcess *process ); QByteArray bufStdout; QByteArray bufStderr; QPtrQueue stdinBuf; QSocketNotifier *notifierStdin; QSocketNotifier *notifierStdout; QSocketNotifier *notifierStderr; ssize_t stdinBufRead; JProc *proc; bool exitValuesCalculated; bool socketReadCalled; static JProcessManager *procManager; }; /*********************************************************************** * * QProc * **********************************************************************/ /* The class QProcess does not necessarily map exactly to the running child processes: if the process is finished, the QProcess class may still be there; furthermore a user can use QProcess to start more than one process. The helper-class QProc has the semantics that one instance of this class maps directly to a running child process. */ class JProc { public: JProc( pid_t p, JProcess *proc=0 ) : pid(p), process(proc) { #if defined(QT_QPROCESS_DEBUG) qDebug( "QProc: Constructor for pid %d and QProcess %p", pid, process ); #endif socketStdin = 0; socketStdout = 0; socketStderr = 0; } ~JProc() { #if defined(QT_QPROCESS_DEBUG) qDebug( "QProc: Destructor for pid %d and QProcess %p", pid, process ); #endif if ( process != 0 ) { if ( process->d->notifierStdin ) process->d->notifierStdin->setEnabled( FALSE ); if ( process->d->notifierStdout ) process->d->notifierStdout->setEnabled( FALSE ); if ( process->d->notifierStderr ) process->d->notifierStderr->setEnabled( FALSE ); process->d->proc = 0; } if( socketStdin != 0 ) ::close( socketStdin ); // ### close these sockets even on parent exit or is it better only on // sigchld (but what do I have to do with them on exit then)? if( socketStdout != 0 ) ::close( socketStdout ); if( socketStderr != 0 ) ::close( socketStderr ); } pid_t pid; int socketStdin; int socketStdout; int socketStderr; JProcess *process; }; /*********************************************************************** * * QProcessManager * **********************************************************************/ class JProcessManager : public QObject { Q_OBJECT public: JProcessManager(); ~JProcessManager(); void append( JProc *p ); void remove( JProc *p ); void cleanup(); public slots: void removeMe(); void sigchldHnd( int ); public: struct sigaction oldactChld; struct sigaction oldactPipe; QPtrList *procList; int sigchldFd[2]; private slots: void cleanupProcesses(); private: QSocketNotifier *sn; QTimer *pollTimer; }; static QCleanupHandler qprocess_cleanup_procmanager; #ifdef Q_OS_QNX6 #define BAILOUT close(tmpSocket);close(socketFD[1]);return -1; int qnx6SocketPairReplacement (int socketFD[2]) { int tmpSocket; tmpSocket = socket (AF_INET, SOCK_STREAM, 0); if (tmpSocket == -1) return -1; socketFD[1] = socket(AF_INET, SOCK_STREAM, 0); if (socketFD[1] == -1) { BAILOUT }; sockaddr_in ipAddr; memset(&ipAddr, 0, sizeof(ipAddr)); ipAddr.sin_family = AF_INET; ipAddr.sin_addr.s_addr = INADDR_ANY; int socketOptions = 1; setsockopt(tmpSocket, SOL_SOCKET, SO_REUSEADDR, &socketOptions, sizeof(int)); bool found = FALSE; for (int socketIP = 2000; (socketIP < 2500) && !(found); socketIP++) { ipAddr.sin_port = htons(socketIP); if (bind(tmpSocket, (struct sockaddr *)&ipAddr, sizeof(ipAddr))) found = TRUE; } if (listen(tmpSocket, 5)) { BAILOUT }; // Select non-blocking mode int originalFlags = fcntl(socketFD[1], F_GETFL, 0); fcntl(socketFD[1], F_SETFL, originalFlags | O_NONBLOCK); // Request connection if (connect(socketFD[1], (struct sockaddr*)&ipAddr, sizeof(ipAddr))) if (errno != EINPROGRESS) { BAILOUT }; // Accept connection socketFD[0] = accept(tmpSocket, (struct sockaddr *)NULL, (size_t *)NULL); if(socketFD[0] == -1) { BAILOUT }; // We're done close(tmpSocket); // Restore original flags , ie return to blocking fcntl(socketFD[1], F_SETFL, originalFlags); return 0; } #undef BAILOUT #endif JProcessManager::JProcessManager() : sn(0) { procList = new QPtrList; procList->setAutoDelete( TRUE ); // really lame workaround -- Justin pollTimer = new QTimer(this); connect(pollTimer, SIGNAL(timeout()), SLOT(cleanupProcesses())); // The SIGCHLD handler writes to a socket to tell the manager that // something happened. This is done to get the processing in sync with the // event reporting. #ifndef Q_OS_QNX6 if ( ::socketpair( AF_UNIX, SOCK_STREAM, 0, sigchldFd ) ) { #else if ( qnx6SocketPairReplacement (sigchldFd) ) { #endif sigchldFd[0] = 0; sigchldFd[1] = 0; } else { #if defined(QT_QPROCESS_DEBUG) qDebug( "QProcessManager: install socket notifier (%d)", sigchldFd[1] ); #endif sn = new QSocketNotifier( sigchldFd[1], QSocketNotifier::Read, this ); connect( sn, SIGNAL(activated(int)), this, SLOT(sigchldHnd(int)) ); sn->setEnabled( TRUE ); } // install a SIGCHLD handler and ignore SIGPIPE struct sigaction act; // NO SIGCHLD -- Justin /* #if defined(QT_QPROCESS_DEBUG) qDebug( "QProcessManager: install a SIGCHLD handler" ); #endif act.sa_handler = qt_C_sigchldHnd; sigemptyset( &(act.sa_mask) ); sigaddset( &(act.sa_mask), SIGCHLD ); act.sa_flags = SA_NOCLDSTOP; #if defined(SA_RESTART) act.sa_flags |= SA_RESTART; #endif if ( sigaction( SIGCHLD, &act, &oldactChld ) != 0 ) qWarning( "Error installing SIGCHLD handler" ); */ // obtain current handler if ( sigaction( SIGPIPE, NULL, &act ) == 0 ) { // already ignoring? if(act.sa_handler == QT_SIGNAL_IGNORE) { // do nothing } else { #if defined(QT_QPROCESS_DEBUG) qDebug( "QProcessManager: install a SIGPIPE handler (SIG_IGN)" ); #endif act.sa_handler = QT_SIGNAL_IGNORE; sigemptyset( &(act.sa_mask) ); sigaddset( &(act.sa_mask), SIGPIPE ); act.sa_flags = 0; if ( sigaction( SIGPIPE, &act, &oldactPipe ) != 0 ) qWarning( "Error installing SIGPIPE handler" ); } } pollTimer->start(100); } JProcessManager::~JProcessManager() { delete procList; if ( sigchldFd[0] != 0 ) ::close( sigchldFd[0] ); if ( sigchldFd[1] != 0 ) ::close( sigchldFd[1] ); // NO SIGCHLD -- Justin /* // restore SIGCHLD handler #if defined(QT_QPROCESS_DEBUG) qDebug( "QProcessManager: restore old sigchild handler" ); #endif if ( sigaction( SIGCHLD, &oldactChld, 0 ) != 0 ) qWarning( "Error restoring SIGCHLD handler" ); */ // NO SIGPIPE restore (we might trash the one from QProcess) /* #if defined(QT_QPROCESS_DEBUG) qDebug( "QProcessManager: restore old sigpipe handler" ); #endif if ( sigaction( SIGPIPE, &oldactPipe, 0 ) != 0 ) qWarning( "Error restoring SIGPIPE handler" ); */ } void JProcessManager::append( JProc *p ) { procList->append( p ); #if defined(QT_QPROCESS_DEBUG) qDebug( "QProcessManager: append process (procList.count(): %d)", procList->count() ); #endif } void JProcessManager::remove( JProc *p ) { procList->remove( p ); #if defined(QT_QPROCESS_DEBUG) qDebug( "QProcessManager: remove process (procList.count(): %d)", procList->count() ); #endif cleanup(); } void JProcessManager::cleanup() { if ( procList->count() == 0 ) { QTimer::singleShot( 0, this, SLOT(removeMe()) ); } } void JProcessManager::removeMe() { if ( procList->count() == 0 ) { qprocess_cleanup_procmanager.remove( &JProcessPrivate::procManager ); JProcessPrivate::procManager = 0; delete this; } } void JProcessManager::sigchldHnd( int fd ) { // Disable the socket notifier to make sure that this function is not // called recursively -- this can happen, if you enter the event loop in // the slot connected to the processExited() signal (e.g. by showing a // modal dialog) and there are more than one process which exited in the // meantime. if ( sn ) sn->setEnabled( FALSE ); char tmp; ::read( fd, &tmp, sizeof(tmp) ); #if defined(QT_QPROCESS_DEBUG) qDebug( "QProcessManager::sigchldHnd()" ); #endif JProc *proc; JProcess *process; bool removeProc; proc = procList->first(); while ( proc != 0 ) { removeProc = FALSE; process = proc->process; if ( process != 0 ) { if ( !process->isRunning() ) { #if defined(QT_QPROCESS_DEBUG) qDebug( "QProcessManager::sigchldHnd() (PID: %d): process exited (QProcess available)", proc->pid ); #endif /* Apparently, there is not consistency among different operating systems on how to use FIONREAD. FreeBSD, Linux and Solaris all expect the 3rd argument to ioctl() to be an int, which is normally 32-bit even on 64-bit machines. IRIX, on the other hand, expects a size_t, which is 64-bit on 64-bit machines. So, the solution is to use size_t initialized to zero to make sure all bits are set to zero, preventing underflow with the FreeBSD/Linux/Solaris ioctls. */ size_t nbytes = 0; // read pending data if ( ::ioctl(proc->socketStdout, FIONREAD, (char*)&nbytes)==0 && nbytes>0 ) { #if defined(QT_QPROCESS_DEBUG) qDebug( "QProcessManager::sigchldHnd() (PID: %d): reading %d bytes of pending data on stdout", proc->pid, nbytes ); #endif process->socketRead( proc->socketStdout ); } nbytes = 0; if ( ::ioctl(proc->socketStderr, FIONREAD, (char*)&nbytes)==0 && nbytes>0 ) { #if defined(QT_QPROCESS_DEBUG) qDebug( "QProcessManager::sigchldHnd() (PID: %d): reading %d bytes of pending data on stderr", proc->pid, nbytes ); #endif process->socketRead( proc->socketStderr ); } if ( process->notifyOnExit ) emit process->processExited(); removeProc = TRUE; } } else { int status; if ( ::waitpid( proc->pid, &status, WNOHANG ) == proc->pid ) { #if defined(QT_QPROCESS_DEBUG) qDebug( "QProcessManager::sigchldHnd() (PID: %d): process exited (QProcess not available)", proc->pid ); #endif removeProc = TRUE; } } if ( removeProc ) { JProc *oldproc = proc; proc = procList->next(); remove( oldproc ); } else { proc = procList->next(); } } if ( sn ) sn->setEnabled( TRUE ); } void JProcessManager::cleanupProcesses() { if ( JProcessPrivate::procManager == 0 ) return; if ( JProcessPrivate::procManager->sigchldFd[0] == 0 ) return; char a = 1; ::write( JProcessPrivate::procManager->sigchldFd[0], &a, sizeof(a) ); } #include "jprocess_unix.moc" /*********************************************************************** * * QProcessPrivate * **********************************************************************/ JProcessManager *JProcessPrivate::procManager = 0; JProcessPrivate::JProcessPrivate() { #if defined(QT_QPROCESS_DEBUG) qDebug( "QProcessPrivate: Constructor" ); #endif stdinBufRead = 0; notifierStdin = 0; notifierStdout = 0; notifierStderr = 0; exitValuesCalculated = FALSE; socketReadCalled = FALSE; proc = 0; } JProcessPrivate::~JProcessPrivate() { #if defined(QT_QPROCESS_DEBUG) qDebug( "QProcessPrivate: Destructor" ); #endif if ( proc != 0 ) { if ( proc->socketStdin != 0 ) { ::close( proc->socketStdin ); proc->socketStdin = 0; } proc->process = 0; } while ( !stdinBuf.isEmpty() ) { delete stdinBuf.dequeue(); } delete notifierStdin; delete notifierStdout; delete notifierStderr; } /* Closes all open sockets in the child process that are not needed by the child process. Otherwise one child may have an open socket on standard input, etc. of another child. */ void JProcessPrivate::closeOpenSocketsForChild() { if ( procManager != 0 ) { if ( procManager->sigchldFd[0] != 0 ) ::close( procManager->sigchldFd[0] ); if ( procManager->sigchldFd[1] != 0 ) ::close( procManager->sigchldFd[1] ); // close also the sockets from other QProcess instances for ( JProc *p=procManager->procList->first(); p!=0; p=procManager->procList->next() ) { ::close( p->socketStdin ); ::close( p->socketStdout ); ::close( p->socketStderr ); } } } void JProcessPrivate::newProc( pid_t pid, JProcess *process ) { proc = new JProc( pid, process ); if ( procManager == 0 ) { procManager = new JProcessManager; qprocess_cleanup_procmanager.add( &procManager ); } // the QProcessManager takes care of deleting the QProc instances procManager->append( proc ); } /*********************************************************************** * * sigchld handler callback * **********************************************************************/ // Don't need this -- Justin /*QT_SIGNAL_RETTYPE qt_C_sigchldHnd( QT_SIGNAL_ARGS ) { if ( JProcessPrivate::procManager == 0 ) return; if ( JProcessPrivate::procManager->sigchldFd[0] == 0 ) return; char a = 1; ::write( JProcessPrivate::procManager->sigchldFd[0], &a, sizeof(a) ); }*/ /*********************************************************************** * * QProcess * **********************************************************************/ /* This private class does basic initialization. */ void JProcess::init() { d = new JProcessPrivate(); exitStat = 0; exitNormal = FALSE; } /* This private class resets the process variables, etc. so that it can be used for another process to start. */ void JProcess::reset() { delete d; d = new JProcessPrivate(); exitStat = 0; exitNormal = FALSE; d->bufStdout.resize( 0 ); d->bufStderr.resize( 0 ); } QByteArray* JProcess::bufStdout() { if ( d->proc && d->proc->socketStdout ) { /* Apparently, there is not consistency among different operating systems on how to use FIONREAD. FreeBSD, Linux and Solaris all expect the 3rd argument to ioctl() to be an int, which is normally 32-bit even on 64-bit machines. IRIX, on the other hand, expects a size_t, which is 64-bit on 64-bit machines. So, the solution is to use size_t initialized to zero to make sure all bits are set to zero, preventing underflow with the FreeBSD/Linux/Solaris ioctls. */ size_t nbytes = 0; if ( ::ioctl(d->proc->socketStdout, FIONREAD, (char*)&nbytes)==0 && nbytes>0 ) socketRead( d->proc->socketStdout ); } return &d->bufStdout; } QByteArray* JProcess::bufStderr() { if ( d->proc && d->proc->socketStderr ) { /* Apparently, there is not consistency among different operating systems on how to use FIONREAD. FreeBSD, Linux and Solaris all expect the 3rd argument to ioctl() to be an int, which is normally 32-bit even on 64-bit machines. IRIX, on the other hand, expects a size_t, which is 64-bit on 64-bit machines. So, the solution is to use size_t initialized to zero to make sure all bits are set to zero, preventing underflow with the FreeBSD/Linux/Solaris ioctls. */ size_t nbytes = 0; if ( ::ioctl(d->proc->socketStderr, FIONREAD, (char*)&nbytes)==0 && nbytes>0 ) socketRead( d->proc->socketStderr ); } return &d->bufStderr; } void JProcess::consumeBufStdout( int consume ) { uint n = d->bufStdout.size(); if ( consume==-1 || (uint)consume >= n ) { d->bufStdout.resize( 0 ); } else { QByteArray tmp( n - consume ); memcpy( tmp.data(), d->bufStdout.data()+consume, n-consume ); d->bufStdout = tmp; } } void JProcess::consumeBufStderr( int consume ) { uint n = d->bufStderr.size(); if ( consume==-1 || (uint)consume >= n ) { d->bufStderr.resize( 0 ); } else { QByteArray tmp( n - consume ); memcpy( tmp.data(), d->bufStderr.data()+consume, n-consume ); d->bufStderr = tmp; } } /*! Destroys the instance. If the process is running, it is not terminated! The standard input, standard output and standard error of the process are closed. You can connect the destroyed() signal to the kill() slot, if you want the process to be terminated automatically when the instance is destroyed. \sa tryTerminate() kill() */ JProcess::~JProcess() { setCallback(0, this); delete d; } /*! Tries to run a process for the command and arguments that were specified with setArguments(), addArgument() or that were specified in the constructor. The command is searched for in the path for executable programs; you can also use an absolute path in the command itself. If \a env is null, then the process is started with the same environment as the starting process. If \a env is non-null, then the values in the stringlist are interpreted as environment setttings of the form \c {key=value} and the process is started in these environment settings. For convenience, there is a small exception to this rule: under Unix, if \a env does not contain any settings for the environment variable \c LD_LIBRARY_PATH, then this variable is inherited from the starting process; under Windows the same applies for the environment variable \c PATH. Returns TRUE if the process could be started; otherwise returns FALSE. You can write data to the process's standard input with writeToStdin(). You can close standard input with closeStdin() and you can terminate the process with tryTerminate(), or with kill(). You can call this function even if you've used this instance to create a another process which is still running. In such cases, QProcess closes the old process's standard input and deletes pending data, i.e., you lose all control over the old process, but the old process is not terminated. This applies also if the process could not be started. (On operating systems that have zombie processes, Qt will also wait() on the old process.) \sa launch() closeStdin() */ bool JProcess::start( QStringList *env ) { #if defined(QT_QPROCESS_DEBUG) qDebug( "QProcess::start()" ); #endif reset(); int sStdin[2]; int sStdout[2]; int sStderr[2]; // open sockets for piping #ifndef Q_OS_QNX6 if ( (comms & Stdin) && ::socketpair( AF_UNIX, SOCK_STREAM, 0, sStdin ) == -1 ) { #else if ( (comms & Stdin) && qnx6SocketPairReplacement(sStdin) == -1 ) { #endif return FALSE; } #ifndef Q_OS_QNX6 if ( (comms & Stderr) && ::socketpair( AF_UNIX, SOCK_STREAM, 0, sStderr ) == -1 ) { #else if ( (comms & Stderr) && qnx6SocketPairReplacement(sStderr) == -1 ) { #endif return FALSE; } #ifndef Q_OS_QNX6 if ( (comms & Stdout) && ::socketpair( AF_UNIX, SOCK_STREAM, 0, sStdout ) == -1 ) { #else if ( (comms & Stdout) && qnx6SocketPairReplacement(sStdout) == -1 ) { #endif return FALSE; } // the following pipe is only used to determine if the process could be // started int fd[2]; if ( pipe( fd ) < 0 ) { // non critical error, go on fd[0] = 0; fd[1] = 0; } // construct the arguments for exec QCString *arglistQ = new QCString[ _arguments.count() + 1 ]; const char** arglist = new const char*[ _arguments.count() + 1 ]; int i = 0; for ( QStringList::Iterator it = _arguments.begin(); it != _arguments.end(); ++it ) { arglistQ[i] = (*it).local8Bit(); arglist[i] = arglistQ[i]; #if defined(QT_QPROCESS_DEBUG) qDebug( "QProcess::start(): arg %d = %s", i, arglist[i] ); #endif i++; } arglist[i] = 0; // Must make sure signal handlers are installed before exec'ing // in case the process exits quickly. if ( d->procManager == 0 ) { d->procManager = new JProcessManager; qprocess_cleanup_procmanager.add( &d->procManager ); } // fork and exec QApplication::flushX(); pid_t pid = fork(); if ( pid == 0 ) { callCallback(this); // child d->closeOpenSocketsForChild(); if ( comms & Stdin ) { ::close( sStdin[1] ); ::dup2( sStdin[0], STDIN_FILENO ); } if ( comms & Stdout ) { ::close( sStdout[0] ); ::dup2( sStdout[1], STDOUT_FILENO ); } if ( comms & Stderr ) { ::close( sStderr[0] ); ::dup2( sStderr[1], STDERR_FILENO ); } if ( comms & DupStderr ) { ::dup2( STDOUT_FILENO, STDERR_FILENO ); } #ifndef QT_NO_DIR ::chdir( workingDir.absPath().latin1() ); #endif if ( fd[0] ) ::close( fd[0] ); if ( fd[1] ) ::fcntl( fd[1], F_SETFD, FD_CLOEXEC ); // close on exec shows sucess if ( env == 0 ) { // inherit environment and start process QString command = _arguments[0]; #if defined(Q_OS_MACX) //look in a bundle const QString mac_bundle_suffix = ".app/Contents/MacOS/"; if(!QFile::exists(command) && QFile::exists(command + mac_bundle_suffix)) { QString exec = command; int lslash = command.findRev('/'); if(lslash != -1) exec = command.mid(lslash+1); QFileInfo fileInfo( command + mac_bundle_suffix + exec ); if ( fileInfo.isExecutable() ) command = fileInfo.absFilePath().local8Bit(); } #endif #ifndef Q_OS_QNX4 ::execvp( command, (char*const*)arglist ); // ### cast not nice #else ::execvp( command, (char const*const*)arglist ); // ### cast not nice #endif } else { // start process with environment settins as specified in env // construct the environment for exec int numEntries = env->count(); #if defined(Q_OS_MACX) QString ld_library_path("DYLD_LIBRARY_PATH"); #else QString ld_library_path("LD_LIBRARY_PATH"); #endif bool setLibraryPath = env->grep( QRegExp( "^" + ld_library_path + "=" ) ).empty() && getenv( ld_library_path ) != 0; if ( setLibraryPath ) numEntries++; QCString *envlistQ = new QCString[ numEntries + 1 ]; const char** envlist = new const char*[ numEntries + 1 ]; int i = 0; if ( setLibraryPath ) { envlistQ[i] = QString( ld_library_path + "=%1" ).arg( getenv( ld_library_path ) ).local8Bit(); envlist[i] = envlistQ[i]; i++; } for ( QStringList::Iterator it = env->begin(); it != env->end(); ++it ) { envlistQ[i] = (*it).local8Bit(); envlist[i] = envlistQ[i]; i++; } envlist[i] = 0; // look for the executable in the search path if ( _arguments.count()>0 && getenv("PATH")!=0 ) { QString command = _arguments[0]; if ( !command.contains( '/' ) ) { QStringList pathList = QStringList::split( ':', getenv( "PATH" ) ); for (QStringList::Iterator it = pathList.begin(); it != pathList.end(); ++it ) { QString dir = *it; #if defined(Q_OS_MACX) //look in a bundle if(!QFile::exists(dir + "/" + command) && QFile::exists(dir + "/" + command + ".app")) dir += "/" + command + ".app/Contents/MacOS"; #endif #ifndef QT_NO_DIR QFileInfo fileInfo( dir, command ); #else QFileInfo fileInfo( dir + "/" + command ); #endif if ( fileInfo.isExecutable() ) { #if defined(Q_OS_MACX) arglistQ[0] = fileInfo.absFilePath().local8Bit(); #else arglistQ[0] = fileInfo.filePath().local8Bit(); #endif arglist[0] = arglistQ[0]; break; } } } } #if defined(Q_OS_MACX) if(!QFile::exists(arglist[0])) { QString command = arglist[0]; const QString mac_bundle_suffix = ".app/Contents/MacOS/"; if(QFile::exists(command + mac_bundle_suffix)) { QString exec = command; int lslash = command.findRev('/'); if(lslash != -1) exec = command.mid(lslash+1); QFileInfo fileInfo( command + mac_bundle_suffix + exec ); if ( fileInfo.isExecutable() ) { arglistQ[0] = fileInfo.absFilePath().local8Bit(); arglist[0] = arglistQ[0]; } } } #endif #ifndef Q_OS_QNX4 ::execve( arglist[0], (char*const*)arglist, (char*const*)envlist ); // ### casts not nice #else ::execve( arglist[0], (char const*const*)arglist,(char const*const*)envlist ); // ### casts not nice #endif } if ( fd[1] ) { char buf = 0; ::write( fd[1], &buf, 1 ); ::close( fd[1] ); } ::_exit( -1 ); } else if ( pid == -1 ) { // error forking goto error; } // test if exec was successful if ( fd[1] ) ::close( fd[1] ); if ( fd[0] ) { char buf; for ( ;; ) { int n = ::read( fd[0], &buf, 1 ); if ( n==1 ) { // socket was not closed => error ::waitpid( pid, 0, WNOHANG ); d->proc = 0; goto error; } else if ( n==-1 ) { if ( errno==EAGAIN || errno==EINTR ) // try it again continue; } break; } ::close( fd[0] ); } d->newProc( pid, this ); if ( comms & Stdin ) { ::close( sStdin[0] ); d->proc->socketStdin = sStdin[1]; // Select non-blocking mode int originalFlags = fcntl(d->proc->socketStdin, F_GETFL, 0); fcntl(d->proc->socketStdin, F_SETFL, originalFlags | O_NONBLOCK); d->notifierStdin = new QSocketNotifier( sStdin[1], QSocketNotifier::Write ); connect( d->notifierStdin, SIGNAL(activated(int)), this, SLOT(socketWrite(int)) ); // setup notifiers for the sockets if ( !d->stdinBuf.isEmpty() ) { d->notifierStdin->setEnabled( TRUE ); } } if ( comms & Stdout ) { ::close( sStdout[1] ); d->proc->socketStdout = sStdout[0]; d->notifierStdout = new QSocketNotifier( sStdout[0], QSocketNotifier::Read ); connect( d->notifierStdout, SIGNAL(activated(int)), this, SLOT(socketRead(int)) ); if ( ioRedirection ) d->notifierStdout->setEnabled( TRUE ); } if ( comms & Stderr ) { ::close( sStderr[1] ); d->proc->socketStderr = sStderr[0]; d->notifierStderr = new QSocketNotifier( sStderr[0], QSocketNotifier::Read ); connect( d->notifierStderr, SIGNAL(activated(int)), this, SLOT(socketRead(int)) ); if ( ioRedirection ) d->notifierStderr->setEnabled( TRUE ); } // cleanup and return delete[] arglistQ; delete[] arglist; return TRUE; error: #if defined(QT_QPROCESS_DEBUG) qDebug( "QProcess::start(): error starting process" ); #endif if ( d->procManager ) d->procManager->cleanup(); if ( comms & Stdin ) { ::close( sStdin[1] ); ::close( sStdin[0] ); } if ( comms & Stdout ) { ::close( sStdout[0] ); ::close( sStdout[1] ); } if ( comms & Stderr ) { ::close( sStderr[0] ); ::close( sStderr[1] ); } ::close( fd[0] ); ::close( fd[1] ); delete[] arglistQ; delete[] arglist; return FALSE; } /*! Asks the process to terminate. Processes can ignore this if they wish. If you want to be certain that the process really terminates, you can use kill() instead. The slot returns immediately: it does not wait until the process has finished. When the process terminates, the processExited() signal is emitted. \sa kill() processExited() */ void JProcess::tryTerminate() const { if ( d->proc != 0 ) ::kill( d->proc->pid, SIGTERM ); } /*! Terminates the process. This is not a safe way to end a process since the process will not be able to do any cleanup. tryTerminate() is safer, but processes can ignore a tryTerminate(). The nice way to end a process and to be sure that it is finished, is to do something like this: \code process->tryTerminate(); QTimer::singleShot( 5000, process, SLOT( kill() ) ); \endcode This tries to terminate the process the nice way. If the process is still running after 5 seconds, it terminates the process the hard way. The timeout should be chosen depending on the time the process needs to do all its cleanup: use a higher value if the process is likely to do a lot of computation or I/O on cleanup. The slot returns immediately: it does not wait until the process has finished. When the process terminates, the processExited() signal is emitted. \sa tryTerminate() processExited() */ void JProcess::kill() const { if ( d->proc != 0 ) ::kill( d->proc->pid, SIGKILL ); } /*! Returns TRUE if the process is running; otherwise returns FALSE. \sa normalExit() exitStatus() processExited() */ bool JProcess::isRunning() const { if ( d->exitValuesCalculated ) { #if defined(QT_QPROCESS_DEBUG) qDebug( "QProcess::isRunning(): FALSE (already computed)" ); #endif return FALSE; } if ( d->proc == 0 ) return FALSE; int status; if ( ::waitpid( d->proc->pid, &status, WNOHANG ) == d->proc->pid ) { // compute the exit values JProcess *that = (JProcess*)this; // mutable that->exitNormal = WIFEXITED( status ) != 0; if ( exitNormal ) { that->exitStat = (char)WEXITSTATUS( status ); } d->exitValuesCalculated = TRUE; // On heavy processing, the socket notifier for the sigchild might not // have found time to fire yet. if ( d->procManager ) { fd_set fds; struct timeval tv; FD_ZERO( &fds ); FD_SET( d->procManager->sigchldFd[1], &fds ); tv.tv_sec = 0; tv.tv_usec = 0; while ( ::select( d->procManager->sigchldFd[1]+1, &fds, 0, 0, &tv ) > 0 ) d->procManager->sigchldHnd( d->procManager->sigchldFd[1] ); } #if defined(QT_QPROCESS_DEBUG) qDebug( "QProcess::isRunning() (PID: %d): FALSE", d->proc->pid ); #endif return FALSE; } #if defined(QT_QPROCESS_DEBUG) qDebug( "QProcess::isRunning() (PID: %d): TRUE", d->proc->pid ); #endif return TRUE; } /*! Returns TRUE if it's possible to read an entire line of text from standard output at this time; otherwise returns FALSE. \sa readLineStdout() canReadLineStderr() */ bool JProcess::canReadLineStdout() const { if ( !d->proc || !d->proc->socketStdout ) return !d->bufStdout.isEmpty(); JProcess *that = (JProcess*)this; return that->scanNewline( TRUE, 0 ); } /*! Returns TRUE if it's possible to read an entire line of text from standard error at this time; otherwise returns FALSE. \sa readLineStderr() canReadLineStdout() */ bool JProcess::canReadLineStderr() const { if ( !d->proc || !d->proc->socketStderr ) return !d->bufStderr.isEmpty(); JProcess *that = (JProcess*)this; return that->scanNewline( FALSE, 0 ); } /*! Writes the data \a buf to the process's standard input. The process may or may not read this data. This function returns immediately; the QProcess class might write the data at a later point (you must enter the event loop for this to occur). When all the data is written to the process, the signal wroteToStdin() is emitted. This does not mean that the process actually read the data, since this class only detects when it was able to write the data to the operating system. \sa wroteToStdin() closeStdin() readStdout() readStderr() */ void JProcess::writeToStdin( const QByteArray& buf ) { #if defined(QT_QPROCESS_DEBUG) // qDebug( "QProcess::writeToStdin(): write to stdin (%d)", d->socketStdin ); #endif d->stdinBuf.enqueue( new QByteArray(buf) ); if ( d->notifierStdin != 0 ) d->notifierStdin->setEnabled( TRUE ); } /*! Closes the process's standard input. This function also deletes any pending data that has not been written to standard input. \sa wroteToStdin() */ void JProcess::closeStdin() { if ( d->proc == 0 ) return; if ( d->proc->socketStdin !=0 ) { while ( !d->stdinBuf.isEmpty() ) { delete d->stdinBuf.dequeue(); } delete d->notifierStdin; d->notifierStdin = 0; if ( ::close( d->proc->socketStdin ) != 0 ) { qWarning( "Could not close stdin of child process" ); } #if defined(QT_QPROCESS_DEBUG) qDebug( "QProcess::closeStdin(): stdin (%d) closed", d->proc->socketStdin ); #endif d->proc->socketStdin = 0; } } /* This private slot is called when the process has outputted data to either standard output or standard error. */ void JProcess::socketRead( int fd ) { if ( d->socketReadCalled ) { // the slots that are connected to the readyRead...() signals might // trigger a recursive call of socketRead(). Avoid this since you get a // blocking read otherwise. return; } #if defined(QT_QPROCESS_DEBUG) qDebug( "QProcess::socketRead(): %d", fd ); #endif if ( fd == 0 ) return; const int bufsize = 4096; QByteArray *buffer = 0; uint oldSize; int n; if ( fd == d->proc->socketStdout ) { buffer = &d->bufStdout; } else if ( fd == d->proc->socketStderr ) { buffer = &d->bufStderr; } else { // this case should never happen, but just to be safe return; } // try to read data first (if it fails, the filedescriptor was closed) oldSize = buffer->size(); buffer->resize( oldSize + bufsize ); n = ::read( fd, buffer->data()+oldSize, bufsize ); if ( n > 0 ) buffer->resize( oldSize + n ); else buffer->resize( oldSize ); // eof or error? if ( n == 0 || n == -1 ) { if ( fd == d->proc->socketStdout ) { #if defined(QT_QPROCESS_DEBUG) qDebug( "QProcess::socketRead(): stdout (%d) closed", fd ); #endif d->notifierStdout->setEnabled( FALSE ); delete d->notifierStdout; d->notifierStdout = 0; ::close( d->proc->socketStdout ); d->proc->socketStdout = 0; return; } else if ( fd == d->proc->socketStderr ) { #if defined(QT_QPROCESS_DEBUG) qDebug( "QProcess::socketRead(): stderr (%d) closed", fd ); #endif d->notifierStderr->setEnabled( FALSE ); delete d->notifierStderr; d->notifierStderr = 0; ::close( d->proc->socketStderr ); d->proc->socketStderr = 0; return; } } fd_set fds; struct timeval tv; FD_ZERO( &fds ); FD_SET( fd, &fds ); tv.tv_sec = 0; tv.tv_usec = 0; while ( ::select( fd+1, &fds, 0, 0, &tv ) > 0 ) { // prepare for the next round FD_ZERO( &fds ); FD_SET( fd, &fds ); // read data oldSize = buffer->size(); buffer->resize( oldSize + bufsize ); n = ::read( fd, buffer->data()+oldSize, bufsize ); if ( n > 0 ) { buffer->resize( oldSize + n ); } else { buffer->resize( oldSize ); break; } } d->socketReadCalled = TRUE; if ( fd == d->proc->socketStdout ) { #if defined(QT_QPROCESS_DEBUG) qDebug( "QProcess::socketRead(): %d bytes read from stdout (%d)", buffer->size()-oldSize, fd ); #endif emit readyReadStdout(); } else if ( fd == d->proc->socketStderr ) { #if defined(QT_QPROCESS_DEBUG) qDebug( "QProcess::socketRead(): %d bytes read from stderr (%d)", buffer->size()-oldSize, fd ); #endif emit readyReadStderr(); } d->socketReadCalled = FALSE; } /* This private slot is called when the process tries to read data from standard input. */ void JProcess::socketWrite( int fd ) { while ( fd == d->proc->socketStdin && d->proc->socketStdin != 0 ) { if ( d->stdinBuf.isEmpty() ) { d->notifierStdin->setEnabled( FALSE ); return; } ssize_t ret = ::write( fd, d->stdinBuf.head()->data() + d->stdinBufRead, d->stdinBuf.head()->size() - d->stdinBufRead ); #if defined(QT_QPROCESS_DEBUG) qDebug( "QProcess::socketWrite(): wrote %d bytes to stdin (%d)", ret, fd ); #endif if ( ret == -1 ) return; d->stdinBufRead += ret; if ( d->stdinBufRead == (ssize_t)d->stdinBuf.head()->size() ) { d->stdinBufRead = 0; delete d->stdinBuf.dequeue(); if ( wroteToStdinConnected && d->stdinBuf.isEmpty() ) emit wroteToStdin(); } } } /*! \internal Flushes standard input. This is useful if you want to use QProcess in a synchronous manner. This function should probably go into the public API. */ void JProcess::flushStdin() { socketWrite( d->proc->socketStdin ); } /* This private slot is only used under Windows (but moc does not know about #if defined()). */ void JProcess::timeout() { } /* This private function is used by connectNotify() and disconnectNotify() to change the value of ioRedirection (and related behaviour) */ void JProcess::setIoRedirection( bool value ) { ioRedirection = value; if ( ioRedirection ) { if ( d->notifierStdout ) d->notifierStdout->setEnabled( TRUE ); if ( d->notifierStderr ) d->notifierStderr->setEnabled( TRUE ); } else { if ( d->notifierStdout ) d->notifierStdout->setEnabled( FALSE ); if ( d->notifierStderr ) d->notifierStderr->setEnabled( FALSE ); } } /* This private function is used by connectNotify() and disconnectNotify() to change the value of notifyOnExit (and related behaviour) */ void JProcess::setNotifyOnExit( bool value ) { notifyOnExit = value; } /* This private function is used by connectNotify() and disconnectNotify() to change the value of wroteToStdinConnected (and related behaviour) */ void JProcess::setWroteStdinConnected( bool value ) { wroteToStdinConnected = value; } /*! \enum QProcess::PID \internal */ /*! Returns platform dependent information about the process. This can be used together with platform specific system calls. Under Unix the return value is the PID of the process, or -1 if no process is belongs to this object. Under Windows it is a pointer to the \c PROCESS_INFORMATION struct, or 0 if no process is belongs to this object. Use of this function's return value is likely to be non-portable. */ JProcess::PID JProcess::processIdentifier() { if ( d->proc == 0 ) return -1; return d->proc->pid; } void JProcess::setChildStartingHandler(void (*childStarting)(JProcess *)) { setCallback(childStarting, this); } #endif // QT_NO_PROCESS