author | tille <tille> | 2002-06-11 08:34:39 (UTC) |
---|---|---|
committer | tille <tille> | 2002-06-11 08:34:39 (UTC) |
commit | b9b967bca768625cba2019981954c8b336020fb2 (patch) (side-by-side diff) | |
tree | 8472192d843ac8b173c88d65aa4eeceb5324104f /libopie/oprocess.cpp | |
parent | 4ca2d71bbbe91c01e398f6aadd69975fa8894c29 (diff) | |
download | opie-b9b967bca768625cba2019981954c8b336020fb2.zip opie-b9b967bca768625cba2019981954c8b336020fb2.tar.gz opie-b9b967bca768625cba2019981954c8b336020fb2.tar.bz2 |
add the cpp files ;)
-rw-r--r-- | libopie/oprocess.cpp | 922 |
1 files changed, 922 insertions, 0 deletions
diff --git a/libopie/oprocess.cpp b/libopie/oprocess.cpp new file mode 100644 index 0000000..f3e52bd --- a/dev/null +++ b/libopie/oprocess.cpp @@ -0,0 +1,922 @@ +/* + + $Id$ + + This file is part of the KDE libraries + Copyright (C) 1997 Christian Czezatke (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. + +*/ + + +// +// KPROCESS -- A class for handling child processes in KDE without +// having to take care of Un*x specific implementation details +// +// version 0.3.1, Jan 8th 1998 +// +// (C) Christian Czezatke +// e9025461@student.tuwien.ac.at +// +// Changes: +// +// March 2nd, 1998: Changed parameter list for KShellProcess: +// Arguments are now placed in a single string so that +// <shell> -c <commandstring> is passed to the shell +// to make the use of "operator<<" consistent with KProcess +// +// +// Ported by Holger Freyther +// <zekce> Harlekin: oprocess and say it was ported to Qt by the Opie developers an Qt 2 + + + +#include "oprocess.h" +#define _MAY_INCLUDE_KPROCESSCONTROLLER_ +#include "oprocctrl.h" + +//#include <config.h> + +#include <qfile.h> +#include <qsocketnotifier.h> +#include <qregexp.h> + +#include <sys/time.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/socket.h> + +#include <errno.h> +#include <fcntl.h> +#include <stdlib.h> +#include <signal.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#ifdef HAVE_SYS_SELECT_H +#include <sys/select.h> +#endif +#ifdef HAVE_INITGROUPS +#include <grp.h> +#endif +#include <pwd.h> + +#include <qapplication.h> +#include <qmap.h> +//#include <kdebug.h> + +///////////////////////////// +// public member functions // +///////////////////////////// + +class OProcessPrivate { +public: + OProcessPrivate() : useShell(false) { } + + bool useShell; + QMap<QString,QString> env; + QString wd; + QCString shell; +}; + + +OProcess::OProcess() + : QObject(), + 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::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; +} + + +///////////////////////////// +// protected slots // +///////////////////////////// + + + +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); +} + + + +////////////////////////////// +// private member functions // +////////////////////////////// + + + +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; +} + +void OProcess::virtual_hook( int, void* ) +{ /*BASE::virtual_hook( id, data );*/ } + + +/////////////////////////// +// CC: Class KShellProcess +/////////////////////////// + +KShellProcess::KShellProcess(const char *shellname): + OProcess() +{ + setUseShell(true, shellname); +} + + +KShellProcess::~KShellProcess() { +} + +QString KShellProcess::quote(const QString &arg) +{ + return OProcess::quote(arg); +} + +bool KShellProcess::start(RunMode runmode, Communication comm) +{ + return OProcess::start(runmode, comm); +} + +void KShellProcess::virtual_hook( int id, void* data ) +{ OProcess::virtual_hook( id, data ); } + +//#include "kprocess.moc" |