summaryrefslogtreecommitdiff
Side-by-side diff
Diffstat (more/less context) (show whitespace changes)
-rw-r--r--core/launcher/qprocess_unix.cpp2
1 files changed, 1 insertions, 1 deletions
diff --git a/core/launcher/qprocess_unix.cpp b/core/launcher/qprocess_unix.cpp
index d62e4e6..56e1b1d 100644
--- a/core/launcher/qprocess_unix.cpp
+++ b/core/launcher/qprocess_unix.cpp
@@ -1,823 +1,823 @@
/**********************************************************************
** Copyright (C) 2000-2002 Trolltech AS. All rights reserved.
**
** This file is part of the Qtopia Environment.
**
** 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.
**
** 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/gpl/ for GPL licensing information.
**
** Contact info@trolltech.com if any conditions of this licensing are
** not clear to you.
**
**********************************************************************/
// Solaris redefines connect -> __xnet_connect with _XOPEN_SOURCE_EXTENDED.
#if defined(connect)
#undef connect
#endif
#include "qprocess.h"
/* OPIE */
#include <opie2/odebug.h>
using namespace Opie::Core;
/* QT */
#ifndef QT_NO_PROCESS
#include <qapplication.h>
#include <qqueue.h>
#include <qlist.h>
#include <qsocketnotifier.h>
#include <qtimer.h>
#include <qregexp.h>
#include "qcleanuphandler_p.h"
/* STD */
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <sys/fcntl.h>
-#include <sys/resource.h>
#include <errno.h>
#ifdef Q_OS_MACX
#include <sys/time.h>
#endif
+#include <sys/resource.h>
#ifdef __MIPSEL__
# ifndef SOCK_DGRAM
# define SOCK_DGRAM 1
# endif
# ifndef SOCK_STREAM
# define SOCK_STREAM 2
# endif
#endif
//#define QT_QPROCESS_DEBUG
#ifdef Q_C_CALLBACKS
extern "C" {
#endif // Q_C_CALLBACKS
#define QT_SIGNAL_RETTYPE void
#define QT_SIGNAL_ARGS int
#define QT_SIGNAL_IGNORE SIG_IGN
QT_SIGNAL_RETTYPE qt_C_sigchldHnd(QT_SIGNAL_ARGS);
QT_SIGNAL_RETTYPE qt_C_sigpipeHnd(QT_SIGNAL_ARGS);
#ifdef Q_C_CALLBACKS
}
#endif // Q_C_CALLBACKS
class QProc;
class QProcessManager;
class QProcessPrivate
{
public:
QProcessPrivate();
~QProcessPrivate();
void closeOpenSocketsForChild();
void newProc( pid_t pid, QProcess *process );
QByteArray bufStdout;
QByteArray bufStderr;
QQueue<QByteArray> stdinBuf;
QSocketNotifier *notifierStdin;
QSocketNotifier *notifierStdout;
QSocketNotifier *notifierStderr;
ssize_t stdinBufRead;
QProc *proc;
bool exitValuesCalculated;
bool socketReadCalled;
static QProcessManager *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 QProc
{
public:
QProc( pid_t p, QProcess *proc=0 ) : pid(p), process(proc)
{
#if defined(QT_QPROCESS_DEBUG)
odebug << "QProc: Constructor for pid " << pid << " and QProcess " << process << "" << oendl;
#endif
socketStdin = 0;
socketStdout = 0;
socketStderr = 0;
}
~QProc()
{
#if defined(QT_QPROCESS_DEBUG)
odebug << "QProc: Destructor for pid " << pid << " and QProcess " << process << "" << oendl;
#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;
QProcess *process;
};
/***********************************************************************
*
* QProcessManager
*
**********************************************************************/
class QProcessManager : public QObject
{
Q_OBJECT
public:
QProcessManager();
~QProcessManager();
void append( QProc *p );
void remove( QProc *p );
void cleanup();
public slots:
void removeMe();
void sigchldHnd( int );
public:
struct sigaction oldactChld;
struct sigaction oldactPipe;
QList<QProc> *procList;
int sigchldFd[2];
};
QCleanupHandler<QProcessManager> qprocess_cleanup_procmanager;
QProcessManager::QProcessManager()
{
procList = new QList<QProc>;
procList->setAutoDelete( TRUE );
// 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.
if ( ::socketpair( AF_UNIX, SOCK_STREAM, 0, sigchldFd ) ) {
sigchldFd[0] = 0;
sigchldFd[1] = 0;
} else {
#if defined(QT_QPROCESS_DEBUG)
odebug << "QProcessManager: install socket notifier (" << sigchldFd[1] << ")" << oendl;
#endif
QSocketNotifier *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;
#if defined(QT_QPROCESS_DEBUG)
odebug << "QProcessManager: install a SIGCHLD handler" << oendl;
#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 )
owarn << "Error installing SIGCHLD handler" << oendl;
#if defined(QT_QPROCESS_DEBUG)
odebug << "QProcessManager: install a SIGPIPE handler (SIG_IGN)" << oendl;
#endif
/*
Using qt_C_sigpipeHnd rather than SIG_IGN is a workaround
for a strange problem where GNU tar (called by backuprestore)
would hang on filesystem-full. Strangely, the qt_C_sigpipeHnd
is never even called, yet this avoids the hang.
*/
act.sa_handler = qt_C_sigpipeHnd;
sigemptyset( &(act.sa_mask) );
sigaddset( &(act.sa_mask), SIGPIPE );
act.sa_flags = 0;
if ( sigaction( SIGPIPE, &act, &oldactPipe ) != 0 )
owarn << "Error installing SIGPIPE handler" << oendl;
}
QProcessManager::~QProcessManager()
{
delete procList;
if ( sigchldFd[0] != 0 )
::close( sigchldFd[0] );
if ( sigchldFd[1] != 0 )
::close( sigchldFd[1] );
// restore SIGCHLD handler
#if defined(QT_QPROCESS_DEBUG)
odebug << "QProcessManager: restore old sigchild handler" << oendl;
#endif
if ( sigaction( SIGCHLD, &oldactChld, 0 ) != 0 )
owarn << "Error restoring SIGCHLD handler" << oendl;
#if defined(QT_QPROCESS_DEBUG)
odebug << "QProcessManager: restore old sigpipe handler" << oendl;
#endif
if ( sigaction( SIGPIPE, &oldactPipe, 0 ) != 0 )
owarn << "Error restoring SIGPIPE handler" << oendl;
}
void QProcessManager::append( QProc *p )
{
procList->append( p );
#if defined(QT_QPROCESS_DEBUG)
odebug << "QProcessManager: append process (procList.count(): " << procList->count() << ")" << oendl;
#endif
}
void QProcessManager::remove( QProc *p )
{
procList->remove( p );
#if defined(QT_QPROCESS_DEBUG)
odebug << "QProcessManager: remove process (procList.count(): " << procList->count() << ")" << oendl;
#endif
cleanup();
}
void QProcessManager::cleanup()
{
if ( procList->count() == 0 ) {
QTimer::singleShot( 0, this, SLOT(removeMe()) );
}
}
void QProcessManager::removeMe()
{
if ( procList->count() == 0 ) {
qprocess_cleanup_procmanager.remove( &QProcessPrivate::procManager );
QProcessPrivate::procManager = 0;
delete this;
}
}
void QProcessManager::sigchldHnd( int fd )
{
char tmp;
::read( fd, &tmp, sizeof(tmp) );
#if defined(QT_QPROCESS_DEBUG)
odebug << "QProcessManager::sigchldHnd()" << oendl;
#endif
QProc *proc;
QProcess *process;
bool removeProc;
proc = procList->first();
while ( proc != 0 ) {
removeProc = FALSE;
process = proc->process;
QProcess *process_exit_notify=0;
if ( process != 0 ) {
if ( !process->isRunning() ) {
#if defined(QT_QPROCESS_DEBUG)
odebug << "QProcessManager::sigchldHnd() (PID: " << proc->pid << "): process exited (QProcess available)" << oendl;
#endif
// read pending data
int nbytes = 0;
if ( ::ioctl(proc->socketStdout, FIONREAD, (char*)&nbytes)==0 && nbytes>0 ) {
#if defined(QT_QPROCESS_DEBUG)
odebug << "QProcessManager::sigchldHnd() (PID: " << proc->pid << "): reading " << nbytes << " bytes of pending data on stdout" << oendl;
#endif
process->socketRead( proc->socketStdout );
}
nbytes = 0;
if ( ::ioctl(proc->socketStderr, FIONREAD, (char*)&nbytes)==0 && nbytes>0 ) {
#if defined(QT_QPROCESS_DEBUG)
odebug << "QProcessManager::sigchldHnd() (PID: " << proc->pid << "): reading " << nbytes << " bytes of pending data on stderr" << oendl;
#endif
process->socketRead( proc->socketStderr );
}
if ( process->notifyOnExit )
process_exit_notify = process;
removeProc = TRUE;
}
} else {
int status;
if ( ::waitpid( proc->pid, &status, WNOHANG ) == proc->pid ) {
#if defined(QT_QPROCESS_DEBUG)
odebug << "QProcessManager::sigchldHnd() (PID: " << proc->pid << "): process exited (QProcess not available)" << oendl;
#endif
removeProc = TRUE;
}
}
if ( removeProc ) {
QProc *oldproc = proc;
proc = procList->next();
remove( oldproc );
} else {
proc = procList->next();
}
if ( process_exit_notify )
emit process_exit_notify->processExited();
}
}
#include "qprocess_unix.moc"
/***********************************************************************
*
* QProcessPrivate
*
**********************************************************************/
QProcessManager *QProcessPrivate::procManager = 0;
QProcessPrivate::QProcessPrivate()
{
#if defined(QT_QPROCESS_DEBUG)
odebug << "QProcessPrivate: Constructor" << oendl;
#endif
stdinBufRead = 0;
notifierStdin = 0;
notifierStdout = 0;
notifierStderr = 0;
exitValuesCalculated = FALSE;
socketReadCalled = FALSE;
proc = 0;
}
QProcessPrivate::~QProcessPrivate()
{
#if defined(QT_QPROCESS_DEBUG)
odebug << "QProcessPrivate: Destructor" << oendl;
#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 QProcessPrivate::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
QProc *proc;
for ( proc=procManager->procList->first(); proc!=0; proc=procManager->procList->next() ) {
::close( proc->socketStdin );
::close( proc->socketStdout );
::close( proc->socketStderr );
}
}
}
void QProcessPrivate::newProc( pid_t pid, QProcess *process )
{
proc = new QProc( pid, process );
if ( procManager == 0 ) {
procManager = new QProcessManager;
qprocess_cleanup_procmanager.add( &procManager );
}
// the QProcessManager takes care of deleting the QProc instances
procManager->append( proc );
}
/***********************************************************************
*
* sigchld handler callback
*
**********************************************************************/
QT_SIGNAL_RETTYPE qt_C_sigchldHnd( QT_SIGNAL_ARGS )
{
if ( QProcessPrivate::procManager == 0 )
return;
if ( QProcessPrivate::procManager->sigchldFd[0] == 0 )
return;
char a = 1;
::write( QProcessPrivate::procManager->sigchldFd[0], &a, sizeof(a) );
}
QT_SIGNAL_RETTYPE qt_C_sigpipeHnd( QT_SIGNAL_ARGS )
{
// Ignore (but in a way somehow different to SIG_IGN).
}
/***********************************************************************
*
* QProcess
*
**********************************************************************/
/*!
This private class does basic initialization.
*/
void QProcess::init()
{
d = new QProcessPrivate();
exitStat = 0;
exitNormal = FALSE;
}
/*!
This private class resets the process variables, etc. so that it can be used
for another process to start.
*/
void QProcess::reset()
{
delete d;
d = new QProcessPrivate();
exitStat = 0;
exitNormal = FALSE;
d->bufStdout.resize( 0 );
d->bufStderr.resize( 0 );
}
QByteArray* QProcess::bufStdout()
{
if ( d->proc && d->proc->socketStdout ) {
// ### can this cause a blocking behaviour (maybe do a ioctl() to see
// if data is available)?
socketRead( d->proc->socketStdout );
}
return &d->bufStdout;
}
QByteArray* QProcess::bufStderr()
{
if ( d->proc && d->proc->socketStderr ) {
// ### can this cause a blocking behaviour (maybe do a ioctl() to see
// if data is available)?
socketRead( d->proc->socketStderr );
}
return &d->bufStderr;
}
void QProcess::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 QProcess::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 class.
If the process is running, it is NOT terminated! 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 class is destroyed.
\sa tryTerminate() kill()
*/
QProcess::~QProcess()
{
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 in the path for executable programs; you can also use an
absolute path to the command.
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 enverionment varialbe \c PATH.
Returns TRUE if the process could be started, otherwise FALSE.
You can write data to standard input of the process with
writeToStdin(), you can close standard input with closeStdin() and you can
terminate the process tryTerminate() resp. kill().
You can call this function even when there already is a running
process in this object. In this case, QProcess closes standard input
of the old process and deletes pending data, i.e., you loose all
control over that process, but the 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 QProcess::start( QStringList *env )
{
#if defined(QT_QPROCESS_DEBUG)
odebug << "QProcess::start()" << oendl;
#endif
reset();
int sStdin[2];
int sStdout[2];
int sStderr[2];
// open sockets for piping
if ( (comms & Stdin) && ::socketpair( AF_UNIX, SOCK_STREAM, 0, sStdin ) == -1 ) {
return FALSE;
}
if ( (comms & Stderr) && ::socketpair( AF_UNIX, SOCK_STREAM, 0, sStderr ) == -1 ) {
return FALSE;
}
if ( (comms & Stdout) && ::socketpair( AF_UNIX, SOCK_STREAM, 0, sStdout ) == -1 ) {
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)
odebug << "QProcess::start(): arg " << i << " = " << arglist[i] << "" << oendl;
#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 QProcessManager;
qprocess_cleanup_procmanager.add( &d->procManager );
}
// fork and exec
QApplication::flushX();
pid_t pid = fork();
if ( pid == 0 ) {
// 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
::execvp( arglist[0], (char*const*)arglist ); // ### cast not nice
} else { // start process with environment settins as specified in env
// construct the environment for exec
int numEntries = env->count();
bool setLibraryPath =
env->grep( QRegExp( "^LD_LIBRARY_PATH=" ) ).isEmpty() &&
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;
#ifdef Q_OS_MACX
if(QFile::exists(dir + "/" + command + ".app")) //look in a bundle
dir += "/" + command + ".app/Contents/MacOS";
#endif
#ifndef QT_NO_DIR
QFileInfo fileInfo( dir, command );
#else
QFileInfo fileInfo( dir + "/" + command );
#endif
if ( fileInfo.isExecutable() ) {
arglistQ[0] = fileInfo.filePath().local8Bit();
arglist[0] = arglistQ[0];
break;
}
}
}
}
::execve( arglist[0], (char*const*)arglist, (char*const*)envlist ); // ### casts not nice
}
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
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];
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)
odebug << "QProcess::start(): error starting process" << oendl;
#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;
}