-rw-r--r-- | microkde/kapplication.cpp | 5 | ||||
-rw-r--r-- | microkde/kapplication.h | 2 | ||||
-rw-r--r-- | microkde/microkdeE.pro | 5 | ||||
-rw-r--r-- | microkde/oprocctrl.cpp | 285 | ||||
-rw-r--r-- | microkde/oprocctrl.h | 129 | ||||
-rw-r--r-- | microkde/oprocess.cpp | 951 | ||||
-rw-r--r-- | microkde/oprocess.h | 761 |
7 files changed, 2138 insertions, 0 deletions
diff --git a/microkde/kapplication.cpp b/microkde/kapplication.cpp index a1d00a8..77ceac5 100644 --- a/microkde/kapplication.cpp +++ b/microkde/kapplication.cpp @@ -15,12 +15,17 @@ QString KApplication::randomString(int length) QString str; while (length--) { int r=random() % 62; r+=48; if (r>57) r+=7; if (r>90) r+=6; str += char(r); // so what if I work backwards? } return str; } + int KApplication::execDialog( QDialog* d ) +{ + d->showMaximized(); + return d->exec(); +} diff --git a/microkde/kapplication.h b/microkde/kapplication.h index 47e64e7..77206f5 100644 --- a/microkde/kapplication.h +++ b/microkde/kapplication.h @@ -1,21 +1,23 @@ #ifndef MINIKDE_KAPPLICATION_H #define MINIKDE_KAPPLICATION_H #include "qstring.h" +#include <qdialog.h> class KApplication { public: static int random(); //US /** * Generates a random string. It operates in the range [A-Za-z0-9] * @param length Generate a string of this length. * @return the random string */ static QString randomString(int length); + static int execDialog( QDialog* ); }; #endif diff --git a/microkde/microkdeE.pro b/microkde/microkdeE.pro index 8d04123..4ee4dd7 100644 --- a/microkde/microkdeE.pro +++ b/microkde/microkdeE.pro @@ -46,24 +46,27 @@ KDGanttMinimizeSplitter.h \ kprocess.h \ krestrictedline.h \ krun.h \ ksimpleconfig.h \ kstaticdeleter.h \ ksystemtray.h \ ktempfile.h \ ktextedit.h \ kunload.h \ kurl.h \ ofileselector_p.h \ ofontselector.h \ +oprocctrl.h \ +oprocess.h \ +osmartpointer.h \ kdeui/kguiitem.h \ kdeui/kaction.h \ kdeui/kactionclasses.h \ kdeui/kactioncollection.h \ kdeui/kcmodule.h \ kdeui/kstdaction.h \ kdeui/kbuttonbox.h \ kdeui/klistbox.h \ kdeui/klistview.h \ kdeui/kjanuswidget.h \ kdeui/kseparator.h \ kdeui/kmainwindow.h \ @@ -118,24 +121,26 @@ KDGanttMinimizeSplitter.cpp \ kglobal.cpp \ kglobalsettings.cpp \ kiconloader.cpp \ kmessagebox.cpp \ kprocess.cpp \ krun.cpp \ ksystemtray.cpp \ ktempfile.cpp \ kurl.cpp \ ktextedit.cpp \ ofileselector_p.cpp \ ofontselector.cpp \ +oprocctrl.cpp \ +oprocess.cpp \ kdecore/kcatalogue.cpp \ kdecore/klibloader.cpp \ kdecore/klocale.cpp \ kdecore/kmdcodec.cpp \ kdecore/kshell.cpp \ kdecore/kstandarddirs.cpp \ kdecore/kstringhandler.cpp \ kdeui/kaction.cpp \ kdeui/kactionclasses.cpp \ kdeui/kactioncollection.cpp \ kdeui/kbuttonbox.cpp \ kdeui/kcmodule.cpp \ diff --git a/microkde/oprocctrl.cpp b/microkde/oprocctrl.cpp new file mode 100644 index 0000000..404e0b3 --- a/dev/null +++ b/microkde/oprocctrl.cpp @@ -0,0 +1,285 @@ +/* This file is part of the KDE libraries + Copyright (C) 1997 Christian Czezakte (e9025461@student.tuwien.ac.at) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A 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. +*/ +// +// KPROCESSCONTROLLER -- A helper class for KProcess +// +// version 0.3.1, Jan, 8th 1997 +// +// (C) Christian Czezatke +// e9025461@student.tuwien.ac.at +// Ported by Holger Freyther +// + +//#include <config.h> + +#include <sys/types.h> +#include <sys/socket.h> + +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <assert.h> + +#include <qsocketnotifier.h> +#include "oprocctrl.h" + +using namespace Opie::Core::Internal; + +OProcessController *OProcessController::theOProcessController = 0; + +struct sigaction OProcessController::oldChildHandlerData; +bool OProcessController::handlerSet = false; + +OProcessController::OProcessController() +{ + assert( theOProcessController == 0 ); + + if (0 > pipe(fd)) + printf(strerror(errno)); + + notifier = new QSocketNotifier(fd[0], QSocketNotifier::Read); + notifier->setEnabled(true); + QObject::connect(notifier, SIGNAL(activated(int)), + this, SLOT(slotDoHousekeeping(int))); + connect( &delayedChildrenCleanupTimer, SIGNAL( timeout()), + SLOT( delayedChildrenCleanup())); + + theOProcessController = this; + + setupHandlers(); +} + + +void OProcessController::setupHandlers() +{ + if( handlerSet ) + return; + struct sigaction act; + act.sa_handler=theSigCHLDHandler; + sigemptyset(&(act.sa_mask)); + sigaddset(&(act.sa_mask), SIGCHLD); + // Make sure we don't block this signal. gdb tends to do that :-( + sigprocmask(SIG_UNBLOCK, &(act.sa_mask), 0); + + act.sa_flags = SA_NOCLDSTOP; + + // CC: take care of SunOS which automatically restarts interrupted system + // calls (and thus does not have SA_RESTART) + +#ifdef SA_RESTART + act.sa_flags |= SA_RESTART; +#endif + + sigaction( SIGCHLD, &act, &oldChildHandlerData ); + + act.sa_handler=SIG_IGN; + sigemptyset(&(act.sa_mask)); + sigaddset(&(act.sa_mask), SIGPIPE); + act.sa_flags = 0; + sigaction( SIGPIPE, &act, 0L); + handlerSet = true; +} + +void OProcessController::resetHandlers() +{ + if( !handlerSet ) + return; + sigaction( SIGCHLD, &oldChildHandlerData, 0 ); + // there should be no problem with SIGPIPE staying SIG_IGN + handlerSet = false; +} + +// block SIGCHLD handler, because it accesses processList +void OProcessController::addOProcess( OProcess* p ) +{ + sigset_t newset, oldset; + sigemptyset( &newset ); + sigaddset( &newset, SIGCHLD ); + sigprocmask( SIG_BLOCK, &newset, &oldset ); + processList.append( p ); + sigprocmask( SIG_SETMASK, &oldset, 0 ); +} + +void OProcessController::removeOProcess( OProcess* p ) +{ + sigset_t newset, oldset; + sigemptyset( &newset ); + sigaddset( &newset, SIGCHLD ); + sigprocmask( SIG_BLOCK, &newset, &oldset ); + processList.remove( p ); + sigprocmask( SIG_SETMASK, &oldset, 0 ); +} + +//using a struct which contains both the pid and the status makes it easier to write +//and read the data into the pipe +//especially this solves a problem which appeared on my box where slotDoHouseKeeping() received +//only 4 bytes (with some debug output around the write()'s it received all 8 bytes) +//don't know why this happened, but when writing all 8 bytes at once it works here, aleXXX +struct waitdata +{ + pid_t pid; + int status; +}; + +void OProcessController::theSigCHLDHandler(int arg) +{ + struct waitdata wd; + // int status; + // pid_t this_pid; + int saved_errno; + + saved_errno = errno; + // since waitpid and write change errno, we have to save it and restore it + // (Richard Stevens, Advanced programming in the Unix Environment) + + bool found = false; + if( theOProcessController != 0 ) + { + // iterating the list doesn't perform any system call + for( QValueList<OProcess*>::ConstIterator it = theOProcessController->processList.begin(); + it != theOProcessController->processList.end(); + ++it ) + { + if( !(*it)->isRunning()) + continue; + wd.pid = waitpid( (*it)->pid(), &wd.status, WNOHANG ); + if ( wd.pid > 0 ) + { + ::write(theOProcessController->fd[1], &wd, sizeof(wd)); + found = true; + } + } + } + if( !found && oldChildHandlerData.sa_handler != SIG_IGN + && oldChildHandlerData.sa_handler != SIG_DFL ) + oldChildHandlerData.sa_handler( arg ); // call the old handler + // handle the rest + if( theOProcessController != 0 ) + { + static const struct waitdata dwd = { 0, 0 } + ; // delayed waitpid() + ::write(theOProcessController->fd[1], &dwd, sizeof(dwd)); + } + else + { + int dummy; + while( waitpid( -1, &dummy, WNOHANG ) > 0 ) + ; + } + + errno = saved_errno; +} + + + +void OProcessController::slotDoHousekeeping(int ) +{ + unsigned int bytes_read = 0; + unsigned int errcnt=0; + // read pid and status from the pipe. + struct waitdata wd; + while ((bytes_read < sizeof(wd)) && (errcnt < 50)) + { + int r = ::read(fd[0], ((char *)&wd) + bytes_read, sizeof(wd) - bytes_read); + if (r > 0) bytes_read += r; + else if (r < 0) errcnt++; + } + if (errcnt >= 50) + { + fprintf(stderr, + "Error: Max. error count for pipe read " + "exceeded in OProcessController::slotDoHousekeeping\n"); + return; // it makes no sense to continue here! + } + if (bytes_read != sizeof(wd)) + { + fprintf(stderr, + "Error: Could not read info from signal handler %d <> %d!\n", + bytes_read, sizeof(wd)); + return; // it makes no sense to continue here! + } + if (wd.pid==0) + { // special case, see delayedChildrenCleanup() + delayedChildrenCleanupTimer.start( 1000, true ); + return; + } + + for( QValueList<OProcess*>::ConstIterator it = processList.begin(); + it != processList.end(); + ++it ) + { + OProcess* proc = *it; + if (proc->pid() == wd.pid) + { + // process has exited, so do emit the respective events + if (proc->run_mode == OProcess::Block) + { + // If the reads are done blocking then set the status in proc + // but do nothing else because OProcess will perform the other + // actions of processHasExited. + proc->status = wd.status; + proc->runs = false; + } + else + { + proc->processHasExited(wd.status); + } + return; + } + } +} + +// this is needed e.g. for popen(), which calls waitpid() checking +// for its forked child, if we did waitpid() directly in the SIGCHLD +// handler, popen()'s waitpid() call would fail +void OProcessController::delayedChildrenCleanup() +{ + struct waitdata wd; + while(( wd.pid = waitpid( -1, &wd.status, WNOHANG ) ) > 0 ) + { + for( QValueList<OProcess*>::ConstIterator it = processList.begin(); + it != processList.end(); + ++it ) + { + if( !(*it)->isRunning() || (*it)->pid() != wd.pid ) + continue; + // it's OProcess, handle it + ::write(fd[1], &wd, sizeof(wd)); + break; + } + } +} + +OProcessController::~OProcessController() +{ + assert( theOProcessController == this ); + resetHandlers(); + + notifier->setEnabled(false); + + close(fd[0]); + close(fd[1]); + + delete notifier; + theOProcessController = 0; +} + +//#include "kprocctrl.moc" diff --git a/microkde/oprocctrl.h b/microkde/oprocctrl.h new file mode 100644 index 0000000..ea00859 --- a/dev/null +++ b/microkde/oprocctrl.h @@ -0,0 +1,129 @@ +/* This file is part of the KDE libraries + Copyright (C) 1997 Christian Czezakte (e9025461@student.tuwien.ac.at) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A 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. +*/ +// +// KPROCESSCONTROLLER -- A helper class for KProcess +// +// version 0.3.1, Jan 8th 1997 +// +// (C) Christian Czezatke +// e9025461@student.tuwien.ac.at +// Ported by Holger Freyther +// + +#ifndef __KPROCCTRL_H__ +#define __KPROCCTRL_H__ + +#include <qvaluelist.h> +#include <qtimer.h> + +#include "oprocess.h" + +class QSocketNotifier; + + +namespace Opie { +namespace Core { +namespace Internal { +class OProcessControllerPrivate; + +/** + * @short Used internally by @ref OProcess + * @internal + * @author Christian Czezakte <e9025461@student.tuwien.ac.at> + * + * A class for internal use by OProcess only. -- Exactly one instance + * of this class is generated by the first instance of OProcess that is + * created (a pointer to it gets stored in @ref theOProcessController ). + * + * This class takes care of the actual (UN*X) signal handling. +*/ +class OProcessController : public QObject +{ + Q_OBJECT + +public: + OProcessController(); + ~OProcessController(); + //CC: WARNING! Destructor Not virtual (but you don't derive classes from this anyhow...) + +public: + + /** + * Only a single instance of this class is allowed at a time, + * and this static variable is used to track the one instance. + */ + static OProcessController *theOProcessController; + + /** + * Automatically called upon SIGCHLD. + * + * Normally you do not need to do anything with this function but + * if your application needs to disable SIGCHLD for some time for + * reasons beyond your control, you should call this function afterwards + * to make sure that no SIGCHLDs where missed. + */ + static void theSigCHLDHandler(int signal); + // handler for sigchld + + /** + * @internal + */ + static void setupHandlers(); + /** + * @internal + */ + static void resetHandlers(); + /** + * @internal + */ + void addOProcess( OProcess* ); + /** + * @internal + */ + void removeOProcess( OProcess* ); +public slots: + /** + * @internal + */ + void slotDoHousekeeping(int socket); + +private slots: + void delayedChildrenCleanup(); +private: + int fd[2]; + QSocketNotifier *notifier; + static struct sigaction oldChildHandlerData; + static bool handlerSet; + QValueList<OProcess*> processList; + QTimer delayedChildrenCleanupTimer; + + // Disallow assignment and copy-construction + OProcessController( const OProcessController& ); + OProcessController& operator= ( const OProcessController& ); + + OProcessControllerPrivate *d; +}; + +} +} +} + + +#endif + diff --git a/microkde/oprocess.cpp b/microkde/oprocess.cpp new file mode 100644 index 0000000..95e3e4b --- a/dev/null +++ b/microkde/oprocess.cpp @@ -0,0 +1,951 @@ +/* + This file is part of the Opie Project + Copyright (C) 2002-2004 Holger Freyther <zecke@handhelds.org> + and The Opie Team <opie-devel@handhelds.org> + =. 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_,=:_. -<s. This program is distributed in the hope that + + . -:. = it will be useful, but WITHOUT ANY WARRANTY; + : .. .:, . . . without even the implied warranty of + =_ + =;=|` MERCHANTABILITY or FITNESS FOR A + _.=:. : :=>`: 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 <oprocess.h> + +/* QT */ + +#include <qapplication.h> +#include <qdir.h> +#include <qmap.h> +#include <qsocketnotifier.h> +#include <qtextstream.h> + +/* STD */ +#include <errno.h> +#include <fcntl.h> +#include <pwd.h> +#include <stdlib.h> +#include <signal.h> +#include <stdio.h> +#include <string.h> +#include <sys/time.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <unistd.h> +#ifdef HAVE_SYS_SELECT_H +#include <sys/select.h> +#endif +#ifdef HAVE_INITGROUPS +#include <grp.h> +#endif + +using namespace Opie::Core::Internal; + +namespace Opie { +namespace Core { +namespace Internal { +class OProcessPrivate +{ +public: + OProcessPrivate() : useShell( false ) + { } + + bool useShell; + QMap<QString, QString> 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<QString, QString>::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<char **>( 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<char **>( 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>( 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<char *>( &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" ); + file.open( IO_ReadOnly ); + 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; + } +} + +} +} diff --git a/microkde/oprocess.h b/microkde/oprocess.h new file mode 100644 index 0000000..be1436c --- a/dev/null +++ b/microkde/oprocess.h @@ -0,0 +1,761 @@ +/* + This file is part of the Opie Project + Copyright (C) 2003-2004 Holger Freyther <zecke@handhelds.org> + Copyright (C) The Opie Team <opie-devel@handhelds.org> + =. 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_,=:_. -<s. This program is distributed in the hope that + + . -:. = it will be useful, but WITHOUT ANY WARRANTY; + : .. .:, . . . without even the implied warranty of + =_ + =;=|` MERCHANTABILITY or FITNESS FOR A + _.=:. : :=>`: 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. +*/ + +#ifndef OPROCESS_H +#define OPROCESS_H + +/* QT */ +#include <qcstring.h> +#include <qobject.h> +#include <qvaluelist.h> + +/* STD */ +#include <sys/types.h> // for pid_t +#include <sys/wait.h> +#include <signal.h> +#include <unistd.h> + +class QSocketNotifier; + +namespace Opie { +namespace Core { +namespace Internal { +class OProcessController; +class OProcessPrivate; +} + +/** + * Child process invocation, monitoring and control. + * + * @sect General usage and features + * + *This class allows a KDE and OPIE application to start child processes without having + *to worry about UN*X signal handling issues and zombie process reaping. + * + *@see KProcIO + * + *Basically, this class distinguishes three different ways of running + *child processes: + * + *@li OProcess::DontCare -- The child process is invoked and both the child + *process and the parent process continue concurrently. + * + *Starting a DontCare child process means that the application is + *not interested in any notification to determine whether the + *child process has already exited or not. + * + *@li OProcess::NotifyOnExit -- The child process is invoked and both the + *child and the parent process run concurrently. + * + *When the child process exits, the OProcess instance + *corresponding to it emits the Qt signal @ref processExited(). + * + *Since this signal is @em not emitted from within a UN*X + *signal handler, arbitrary function calls can be made. + * + *Be aware: When the OProcess objects gets destructed, the child + *process will be killed if it is still running! + *This means in particular, that you cannot use a OProcess on the stack + *with OProcess::NotifyOnExit. + * + *@li OProcess::Block -- The child process starts and the parent process + *is suspended until the child process exits. (@em Really not recommended + *for programs with a GUI.) + * + *OProcess also provides several functions for determining the exit status + *and the pid of the child process it represents. + * + *Furthermore it is possible to supply command-line arguments to the process + *in a clean fashion (no null -- terminated stringlists and such...) + * + *A small usage example: + *<pre> + *OProcess *proc = new OProcess; + * + **proc << "my_executable"; + **proc << "These" << "are" << "the" << "command" << "line" << "args"; + *QApplication::connect(proc, SIGNAL(processExited(Opie::Core::OProcess *)), + * pointer_to_my_object, SLOT(my_objects_slot(Opie::Core::OProcess *))); + *proc->start(); + *</pre> + * + *This will start "my_executable" with the commandline arguments "These"... + * + *When the child process exits, the respective Qt signal will be emitted. + * + *@sect Communication with the child process + * + *OProcess supports communication with the child process through + *stdin/stdout/stderr. + * + *The following functions are provided for getting data from the child + *process or sending data to the child's stdin (For more information, + *have a look at the documentation of each function): + * + *@li bool @ref writeStdin(char *buffer, int buflen); + *@li -- Transmit data to the child process's stdin. + * + *@li bool @ref closeStdin(); + *@li -- Closes the child process's stdin (which causes it to see an feof(stdin)). + *Returns false if you try to close stdin for a process that has been started + *without a communication channel to stdin. + * + *@li bool @ref closeStdout(); + *@li -- Closes the child process's stdout. + *Returns false if you try to close stdout for a process that has been started + *without a communication channel to stdout. + * + *@li bool @ref closeStderr(); + *@li -- Closes the child process's stderr. + *Returns false if you try to close stderr for a process that has been started + *without a communication channel to stderr. + * + * + *@sect QT signals: + * + *@li void @ref receivedStdout(OProcess *proc, char *buffer, int buflen); + *@li void @ref receivedStderr(OProcess *proc, char *buffer, int buflen); + *@li -- Indicates that new data has arrived from either the + *child process's stdout or stderr. + * + *@li void @ref wroteStdin(OProcess *proc); + *@li -- Indicates that all data that has been sent to the child process + *by a prior call to @ref writeStdin() has actually been transmitted to the + *client . + * + *@author Christian Czezakte e9025461@student.tuwien.ac.at + *@author Holger Freyther (Opie Port) + * + **/ +class OProcess : public QObject +{ + Q_OBJECT + +public: + + /** + * Modes in which the communication channel can be opened. + * + * If communication for more than one channel is required, + * the values have to be or'ed together, for example to get + * communication with stdout as well as with stdin, you would + * specify @p Stdin @p | @p Stdout + * + * If @p NoRead is specified in conjunction with @p Stdout, + * no data is actually read from @p Stdout but only + * the signal @ref childOutput(int fd) is emitted. + */ + enum Communication { NoCommunication = 0, Stdin = 1, Stdout = 2, Stderr = 4, + AllOutput = 6, All = 7, + NoRead }; + + /** + * Run-modes for a child process. + */ + enum RunMode { + /** + * The application does not receive notifications from the subprocess when + * it is finished or aborted. + */ + DontCare, + /** + * The application is notified when the subprocess dies. + */ + NotifyOnExit, + /** + * The application is suspended until the started process is finished. + */ + Block }; + + /** + * Constructor + */ + OProcess( QObject *parent = 0, const char *name = 0 ); + /** + * Constructor + */ + OProcess( const QString &arg0, QObject *parent = 0, const char *name = 0 ); + /** + * Constructor + */ + OProcess( const QStringList &args, QObject *parent = 0, const char *name = 0 ); + + /** + *Destructor: + * + * If the process is running when the destructor for this class + * is called, the child process is killed with a SIGKILL, but + * only if the run mode is not of type @p DontCare. + * Processes started as @p DontCare keep running anyway. + */ + virtual ~OProcess(); + + /** + @deprecated + + The use of this function is now deprecated. -- Please use the + "operator<<" instead of "setExecutable". + + Sets the executable to be started with this OProcess object. + Returns false if the process is currently running (in that + case the executable remains unchanged.) + + @see operator<< + + */ + bool setExecutable( const QString& proc ); + + + /** + * Sets the executable and the command line argument list for this process. + * + * For example, doing an "ls -l /usr/local/bin" can be achieved by: + * <pre> + * OProcess p; + * ... + * p << "ls" << "-l" << "/usr/local/bin" + * </pre> + * + **/ + OProcess &operator<<( const QString& arg ); + /** + * Similar to previous method, takes a char *, supposed to be in locale 8 bit already. + */ + OProcess &operator<<( const char * arg ); + /** + * Similar to previous method, takes a QCString, supposed to be in locale 8 bit already. + */ + OProcess &operator<<( const QCString & arg ); + + /** + * Sets the executable and the command line argument list for this process, + * in a single method call, or add a list of arguments. + **/ + OProcess &operator<<( const QStringList& args ); + + /** + * Clear a command line argument list that has been set by using + * the "operator<<". + */ + void clearArguments(); + + /** + * Starts the process. + * For a detailed description of the + * various run modes and communication semantics, have a look at the + * general description of the OProcess class. + * + * The following problems could cause this function to + * return false: + * + * @li The process is already running. + * @li The command line argument list is empty. + * @li The starting of the process failed (could not fork). + * @li The executable was not found. + * + * @param comm Specifies which communication links should be + * established to the child process (stdin/stdout/stderr). By default, + * no communication takes place and the respective communication + * signals will never get emitted. + * + * @return true on success, false on error + * (see above for error conditions) + **/ + virtual bool start( RunMode runmode = NotifyOnExit, + Communication comm = NoCommunication ); + + /** + * Stop the process (by sending it a signal). + * + * @param signo The signal to send. The default is SIGTERM. + * @return @p true if the signal was delivered successfully. + */ + virtual bool kill( int signo = SIGTERM ); + + /** + @return @p true if the process is (still) considered to be running + */ + bool isRunning() const; + + /** Returns the process id of the process. + * + * If it is called after + * the process has exited, it returns the process id of the last + * child process that was created by this instance of OProcess. + * + * Calling it before any child process has been started by this + * OProcess instance causes pid() to return 0. + **/ + pid_t pid() const; + + /** + * Suspend processing of data from stdout of the child process. + */ + void suspend(); + + /** + * Resume processing of data from stdout of the child process. + */ + void resume(); + + /** + * @return @p true if the process has already finished and has exited + * "voluntarily", ie: it has not been killed by a signal. + * + * Note that you should check @ref OProcess::exitStatus() to determine + * whether the process completed its task successful or not. + */ + bool normalExit() const; + + /** + * Returns the exit status of the process. + * + * Please use + * @ref OProcess::normalExit() to check whether the process has exited + * cleanly (i.e., @ref OProcess::normalExit() returns @p true) before calling + * this function because if the process did not exit normally, + * it does not have a valid exit status. + */ + int exitStatus() const; + + + /** + * Transmit data to the child process's stdin. + * + * OProcess::writeStdin may return false in the following cases: + * + * @li The process is not currently running. + * + * @li Communication to stdin has not been requested in the @ref start() call. + * + * @li Transmission of data to the child process by a previous call to + * @ref writeStdin() is still in progress. + * + * Please note that the data is sent to the client asynchronously, + * so when this function returns, the data might not have been + * processed by the child process. + * + * If all the data has been sent to the client, the signal + * @ref wroteStdin() will be emitted. + * + * Please note that you must not free "buffer" or call @ref writeStdin() + * again until either a @ref wroteStdin() signal indicates that the + * data has been sent or a @ref processHasExited() signal shows that + * the child process is no longer alive... + **/ + bool writeStdin( const char *buffer, int buflen ); + + void flushStdin(); + + /** + * This causes the stdin file descriptor of the child process to be + * closed indicating an "EOF" to the child. + * + * @return @p false if no communication to the process's stdin + * had been specified in the call to @ref start(). + */ + bool closeStdin(); + + /** + * This causes the stdout file descriptor of the child process to be + * closed. + * + * @return @p false if no communication to the process's stdout + * had been specified in the call to @ref start(). + */ + bool closeStdout(); + + /** + * This causes the stderr file descriptor of the child process to be + * closed. + * + * @return @p false if no communication to the process's stderr + * had been specified in the call to @ref start(). + */ + bool closeStderr(); + + /** + * Lets you see what your arguments are for debugging. + * \todo make const + */ + + const QValueList<QCString> &args() + { + return arguments; + } + + /** + * Controls whether the started process should drop any + * setuid/segid privileges or whether it should keep them + * + * The default is @p false : drop privileges + */ + void setRunPrivileged( bool keepPrivileges ); + + /** + * Returns whether the started process will drop any + * setuid/segid privileges or whether it will keep them + */ + bool runPrivileged() const; + + /** + * Modifies the environment of the process to be started. + * This function must be called before starting the process. + */ + void setEnvironment( const QString &name, const QString &value ); + + /** + * Changes the current working directory (CWD) of the process + * to be started. + * This function must be called before starting the process. + */ + void setWorkingDirectory( const QString &dir ); + + /** + * Specify whether to start the command via a shell or directly. + * The default is to start the command directly. + * If @p useShell is true @p shell will be used as shell, or + * if shell is empty, the standard shell is used. + * @p quote A flag indicating whether to quote the arguments. + * + * When using a shell, the caller should make sure that all filenames etc. + * are properly quoted when passed as argument. + * @see quote() + */ + void setUseShell( bool useShell, const char *shell = 0 ); + + /** + * This function can be used to quote an argument string such that + * the shell processes it properly. This is e. g. necessary for + * user-provided file names which may contain spaces or quotes. + * It also prevents expansion of wild cards and environment variables. + */ + static QString quote( const QString &arg ); + + /** + * Detaches OProcess from child process. All communication is closed. + * No exit notification is emitted any more for the child process. + * Deleting the OProcess will no longer kill the child process. + * Note that the current process remains the parent process of the + * child process. + */ + void detach(); + + /** + * @return the PID of @a process, or -1 if the process is not running + */ + static int processPID( const QString& process ); + +signals: + + /** + * Emitted after the process has terminated when + * the process was run in the @p NotifyOnExit (==default option to + * @ref start()) or the @ref Block mode. + **/ + void processExited( Opie::Core::OProcess *proc ); + + + /** + * Emitted, when output from the child process has + * been received on stdout. + * + * To actually get + * these signals, the respective communication link (stdout/stderr) + * has to be turned on in @ref start(). + * + * @param buffer The data received. + * @param buflen The number of bytes that are available. + * + * You should copy the information contained in @p buffer to your private + * data structures before returning from this slot. + **/ + void receivedStdout( Opie::Core::OProcess *proc, char *buffer, int buflen ); + + /** + * Emitted when output from the child process has + * been received on stdout. + * + * To actually get these signals, the respective communications link + * (stdout/stderr) has to be turned on in @ref start() and the + * @p NoRead flag should have been passed. + * + * You will need to explicitly call resume() after your call to start() + * to begin processing data from the child process's stdout. This is + * to ensure that this signal is not emitted when no one is connected + * to it, otherwise this signal will not be emitted. + * + * The data still has to be read from file descriptor @p fd. + **/ + void receivedStdout( int fd, int &len ); + + + /** + * Emitted, when output from the child process has + * been received on stderr. + * To actually get + * these signals, the respective communication link (stdout/stderr) + * has to be turned on in @ref start(). + * + * @param buffer The data received. + * @param buflen The number of bytes that are available. + * + * You should copy the information contained in @p buffer to your private + * data structures before returning from this slot. + */ + void receivedStderr( Opie::Core::OProcess *proc, char *buffer, int buflen ); + + /** + * Emitted after all the data that has been + * specified by a prior call to @ref writeStdin() has actually been + * written to the child process. + **/ + void wroteStdin( Opie::Core::OProcess *proc ); + +protected slots: + + /** + * This slot gets activated when data from the child's stdout arrives. + * It usually calls "childOutput" + */ + void slotChildOutput( int fdno ); + + /** + * This slot gets activated when data from the child's stderr arrives. + * It usually calls "childError" + */ + void slotChildError( int fdno ); + /* + Slot functions for capturing stdout and stderr of the child + */ + + /** + * Called when another bulk of data can be sent to the child's + * stdin. If there is no more data to be sent to stdin currently + * available, this function must disable the QSocketNotifier "innot". + */ + void slotSendData( int dummy ); + +protected: + + /** + * Sets up the environment according to the data passed via + * setEnvironment(...) + */ + void setupEnvironment(); + + /** + * The list of the process' command line arguments. The first entry + * in this list is the executable itself. + */ + QValueList<QCString> arguments; + /** + * How to run the process (Block, NotifyOnExit, DontCare). You should + * not modify this data member directly from derived classes. + */ + RunMode run_mode; + /** + * true if the process is currently running. You should not + * modify this data member directly from derived classes. For + * reading the value of this data member, please use "isRunning()" + * since "runs" will probably be made private in later versions + * of OProcess. + */ + bool runs; + + /** + * The PID of the currently running process (see "getPid()"). + * You should not modify this data member in derived classes. + * Please use "getPid()" instead of directly accessing this + * member function since it will probably be made private in + * later versions of OProcess. + */ + pid_t pid_; + + /** + * The process' exit status as returned by "waitpid". You should not + * modify the value of this data member from derived classes. You should + * rather use @ref exitStatus than accessing this data member directly + * since it will probably be made private in further versions of + * OProcess. + */ + int status; + + + /** + * See setRunPrivileged() + */ + bool keepPrivs; + + /* + Functions for setting up the sockets for communication. + setupCommunication + -- is called from "start" before "fork"ing. + commSetupDoneP + -- completes communication socket setup in the parent + commSetupDoneC + -- completes communication setup in the child process + commClose + -- frees all allocated communication resources in the parent + after the process has exited + */ + + /** + * This function is called from "OProcess::start" right before a "fork" takes + * place. According to + * the "comm" parameter this function has to initialize the "in", "out" and + * "err" data member of OProcess. + * + * This function should return 0 if setting the needed communication channels + * was successful. + * + * The default implementation is to create UNIX STREAM sockets for the communication, + * but you could overload this function and establish a TCP/IP communication for + * network communication, for example. + */ + virtual int setupCommunication( Communication comm ); + + /** + * Called right after a (successful) fork on the parent side. This function + * will usually do some communications cleanup, like closing the reading end + * of the "stdin" communication channel. + * + * Furthermore, it must also create the QSocketNotifiers "innot", "outnot" and + * "errnot" and connect their Qt slots to the respective OProcess member functions. + * + * For a more detailed explanation, it is best to have a look at the default + * implementation of "setupCommunication" in kprocess.cpp. + */ + virtual int commSetupDoneP(); + + /** + * Called right after a (successful) fork, but before an "exec" on the child + * process' side. It usually just closes the unused communication ends of + * "in", "out" and "err" (like the writing end of the "in" communication + * channel. + */ + virtual int commSetupDoneC(); + + + /** + * Immediately called after a process has exited. This function normally + * calls commClose to close all open communication channels to this + * process and emits the "processExited" signal (if the process was + * not running in the "DontCare" mode). + */ + virtual void processHasExited( int state ); + + /** + * Should clean up the communication links to the child after it has + * exited. Should be called from "processHasExited". + */ + virtual void commClose(); + + + /** + * the socket descriptors for stdin/stdout/stderr. + */ + int out[ 2 ]; + int in[ 2 ]; + int err[ 2 ]; + + /** + * The socket notifiers for the above socket descriptors. + */ + QSocketNotifier *innot; + QSocketNotifier *outnot; + QSocketNotifier *errnot; + + /** + * Lists the communication links that are activated for the child + * process. Should not be modified from derived classes. + */ + Communication communication; + + /** + * Called by "slotChildOutput" this function copies data arriving from the + * child process's stdout to the respective buffer and emits the signal + * "@ref receivedStderr". + */ + int childOutput( int fdno ); + + /** + * Called by "slotChildOutput" this function copies data arriving from the + * child process's stdout to the respective buffer and emits the signal + * "@ref receivedStderr" + */ + int childError( int fdno ); + + // information about the data that has to be sent to the child: + + const char *input_data; // the buffer holding the data + int input_sent; // # of bytes already transmitted + int input_total; // total length of input_data + + /** + * @ref OProcessController is a friend of OProcess because it has to have + * access to various data members. + */ + friend class Internal::OProcessController; + +private: + /** + * Searches for a valid shell. + * Here is the algorithm used for finding an executable shell: + * + * @li Try the executable pointed to by the "SHELL" environment + * variable with white spaces stripped off + * + * @li If your process runs with uid != euid or gid != egid, a shell + * not listed in /etc/shells will not used. + * + * @li If no valid shell could be found, "/bin/sh" is used as a last resort. + */ + QCString searchShell(); + + /** + * Used by @ref searchShell in order to find out whether the shell found + * is actually executable at all. + */ + bool isExecutable( const QCString &filename ); + + // Disallow assignment and copy-construction + OProcess( const OProcess& ); + OProcess& operator= ( const OProcess& ); + +private: + void init ( ); + Internal::OProcessPrivate *d; +}; +} +} + +#endif + |