diff options
author | Andrew Christian <andrew.christian@nokia.com> | 2012-02-23 10:00:28 -0500 |
---|---|---|
committer | Chris Craig <ext-chris.craig@nokia.com> | 2012-02-27 12:14:59 +0100 |
commit | d996e4d26539a0a80a63d03fc75ea37d916733d2 (patch) | |
tree | 1d9fdcb1ca41c93147f1b5beef3565a20340ae86 | |
parent | 3668383c67e6fab36ce377835ea160a868061b24 (diff) |
Added forklauncher code and test cases.
Change-Id: I2e21b7635169464e229a36d02cc728a5f5bab443
Reviewed-by: Chris Craig <ext-chris.craig@nokia.com>
-rw-r--r-- | src/core/core-lib.pri | 6 | ||||
-rw-r--r-- | src/core/forklauncher.cpp | 671 | ||||
-rw-r--r-- | src/core/forklauncher.h | 52 | ||||
-rw-r--r-- | src/core/pipeprocessbackendfactory.cpp | 3 | ||||
-rw-r--r-- | src/core/remoteprocessbackendfactory.cpp | 3 | ||||
-rw-r--r-- | src/core/remoteprotocol.h | 2 | ||||
-rw-r--r-- | src/declarative/declarativesocketlauncher.h | 3 | ||||
-rw-r--r-- | tests/auto/processmanager/processmanager.pro | 2 | ||||
-rw-r--r-- | tests/auto/processmanager/testClient/main.cpp | 19 | ||||
-rw-r--r-- | tests/auto/processmanager/testForkLauncher/.gitignore | 1 | ||||
-rw-r--r-- | tests/auto/processmanager/testForkLauncher/main.cpp | 142 | ||||
-rw-r--r-- | tests/auto/processmanager/testForkLauncher/testForkLauncher.pro | 11 | ||||
-rw-r--r-- | tests/auto/processmanager/testPrelaunch/main.cpp | 21 | ||||
-rw-r--r-- | tests/auto/processmanager/tst_processmanager.cpp | 140 |
14 files changed, 1040 insertions, 36 deletions
diff --git a/src/core/core-lib.pri b/src/core/core-lib.pri index 77ecb38..26a9423 100644 --- a/src/core/core-lib.pri +++ b/src/core/core-lib.pri @@ -29,7 +29,8 @@ PUBLIC_HEADERS += \ $$PWD/pipelauncher.h \ $$PWD/socketlauncher.h \ $$PWD/procutils.h \ - $$PWD/remoteprotocol.h + $$PWD/remoteprotocol.h \ + $$PWD/forklauncher.h HEADERS += \ $$PUBLIC_HEADERS \ @@ -61,4 +62,5 @@ SOURCES += \ $$PWD/launcherclient.cpp \ $$PWD/pipelauncher.cpp \ $$PWD/socketlauncher.cpp \ - $$PWD/procutils.cpp + $$PWD/procutils.cpp \ + $$PWD/forklauncher.cpp diff --git a/src/core/forklauncher.cpp b/src/core/forklauncher.cpp new file mode 100644 index 0000000..46894a5 --- /dev/null +++ b/src/core/forklauncher.cpp @@ -0,0 +1,671 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QDebug> +#include <QJsonDocument> +#include <QJsonObject> +#include <QByteArray> +#include <QMap> +#include <QtEndian> +#include <QFile> +#include <QElapsedTimer> +#include <QProcess> + +#include <signal.h> +#include <sys/select.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <pwd.h> +#include <errno.h> +#include <sys/resource.h> +#include <fcntl.h> + +// Linux only? +#include <sys/wait.h> +#include <grp.h> + +#include "forklauncher.h" +#include "remoteprotocol.h" +#include "processinfo.h" +#include "procutils.h" + +#if defined(Q_OS_MAC) && !defined(QT_NO_CORESERVICES) +// Shared libraries don't have direct access to environ until runtime +# include <crt_externs.h> +# define environ (*_NSGetEnviron()) +#else + extern char **environ; +#endif + + +QT_BEGIN_NAMESPACE_PROCESSMANAGER + +static int sig_child_pipe[2]; +static struct sigaction old_sig_child_handler; + +static void sig_child_handler(int sig) +{ + ::write(sig_child_pipe[1], "@", 1); + + // Complicated way of calling the old child handler + void (*oldAction)(int) = ((volatile struct sigaction *)&old_sig_child_handler)->sa_handler; + if (oldAction && oldAction != SIG_IGN) + oldAction(sig); +} + +static void readToBuffer(int fd, QByteArray& buffer) +{ + const int bufsize = 1024; + uint oldSize = buffer.size(); + buffer.resize(oldSize + bufsize); + int n = ::read(fd, buffer.data()+oldSize, bufsize); + if (n > 0) + buffer.resize(oldSize+n); + else + buffer.resize(oldSize); +} + +static void writeFromBuffer(int fd, QByteArray& buffer) +{ + int n = ::write(fd, buffer.data(), buffer.size()); + if (n == -1) { + qDebug() << "Failed to write to " << fd; + exit(-1); + } + if (n < buffer.size()) + buffer = buffer.mid(n); + else + buffer.clear(); +} + +static void copyToOutgoing(QByteArray& outgoing, const QString& channel, QByteArray& buf, int id) +{ + QJsonObject message; + message.insert(RemoteProtocol::event(), RemoteProtocol::output()); + message.insert(RemoteProtocol::id(), id); + message.insert(channel, QString::fromLocal8Bit(buf.data(), buf.size())); + + outgoing.append(QJsonDocument(message).toBinaryData()); + buf.clear(); +} + +/* + Update the current process to match the information in info + */ + +static void fixProcessState(const ProcessInfo& info, int *argc_ptr, char ***argv_ptr) +{ + // Fix the UID & GID values + ::setpgid(0,0); + if (info.contains(ProcessInfoConstants::Gid)) + ::setgid(info.gid()); + if (info.contains(ProcessInfoConstants::Uid)) + ::setuid(info.uid()); + ::umask(S_IWGRP | S_IWOTH); + struct passwd *pw = getpwent(); + if (pw) + ::initgroups(pw->pw_name, pw->pw_gid); + else + ::setgroups(0,0); + + if (info.contains(ProcessInfoConstants::Priority)) { + int priority = info.priority(); + if (::setpriority(PRIO_PROCESS, ::getpid(), priority) == -1) + qWarning("Unable to set priority of pid=%d to %d", ::getpid(), priority); + } + + if (info.contains(ProcessInfoConstants::OomAdjustment)) { + int adj = info.oomAdjustment(); + if (!ProcUtils::setOomAdjustment(::getpid(), adj)) + qWarning("Unable to set oom adjustment of pid=%d to %d", ::getpid(), adj); + } + + if (info.contains(ProcessInfoConstants::WorkingDirectory)) { + QByteArray wd = QFile::encodeName(info.workingDirectory()); + if (!::chdir(wd.constData())) + qWarning("Unable to chdir to %s", wd.constData()); + } + + // Fix the environment + if (info.contains(ProcessInfoConstants::Environment)) { + const char *entry; + QList<QByteArray> envlist; + for (int count = 0 ; (entry = environ[count]) ; ++count) { + const char *equal = strchr(entry, '='); + if (!equal) + envlist.append(QByteArray(entry)); + else + envlist.append(QByteArray(entry, equal - entry)); + } + + QVariantMap env = info.environment(); + foreach (const QByteArray& ba, envlist) { + QString key = QString::fromLocal8Bit(ba.constData(), ba.size()); + if (!env.contains(key)) // Remove only keys that don't exist in the planned environment + ::unsetenv(ba.constData()); + } + + QMapIterator<QString, QVariant> iter(env); + while (iter.hasNext()) { + iter.next(); + QByteArray key = iter.key().toLocal8Bit(); + QByteArray value = iter.value().toByteArray(); + ::setenv(key.constData(), value.constData(), 1); + } + } + + // Fix up the argument list + if (info.contains(ProcessInfoConstants::Arguments)) { + const int argc = info.arguments().size() + 1; + char **argv = new char *[argc + 1]; + if (info.contains(ProcessInfoConstants::Program)) + argv[0] = strdup(info.program().toLocal8Bit().constData()); + else + argv[0] = (*argv_ptr)[0]; + for (int i = 0 ; i < argc ; i++ ) + argv[i+1] = strdup(info.arguments().at(0).toLocal8Bit().constData()); + argv[argc] = 0; + *argc_ptr = argc; + *argv_ptr = argv; + } + else if (info.contains(ProcessInfoConstants::Program)) { + // No new arguments; just copy in the new program name + (*argv_ptr)[0] = strdup(info.program().toLocal8Bit().constData()); + } +} + + +/**************************************************************************/ + +// ### TODO: Should we watch for 'startOutputPattern'??? + +class ChildProcess { +public: + ChildProcess(int id); + ~ChildProcess(); + + int updateFdSet(int n, fd_set& rfds, fd_set& wfds); + void processFdSet(QByteArray& outgoing, fd_set& rfds, fd_set& wfds); + void stop(int timeout); + void setPriority(int priority); + void setOomAdjustment(int oomAdjustment); + bool doFork(); + + void write(const QByteArray& buf) { m_inbuf.append(buf); } + pid_t pid() const { return m_pid; } + int id() const { return m_id; } + bool needTimeout() const { return m_state == SentSigTerm; } + + void sendStateChanged(QByteArray& outgoing, QProcess::ProcessState state); + void sendStarted(QByteArray& outgoing); + void sendFinished(QByteArray& outgoing, int exitCode, QProcess::ExitStatus); + void sendError(QByteArray& outgoing, QProcess::ProcessError err, const QString& errString); + + enum ProcessState { + NotRunning, + Running, + SentSigTerm, + SentSigKill, + Finished + }; +private: + ProcessState m_state; + pid_t m_pid; + int m_id; // Unique id for this process + QElapsedTimer m_timer; + int m_timeout; + int m_stdin, m_stdout, m_stderr; + QByteArray m_inbuf; // Data being written + QByteArray m_outbuf; // Data being read + QByteArray m_errbuf; // Data being read +}; + +ChildProcess::ChildProcess(int id) + : m_state(NotRunning) + , m_pid(-1) + , m_id(id) + , m_stdin(-1) + , m_stdout(-1) + , m_stderr(-1) +{ +} + +ChildProcess::~ChildProcess() +{ + if (m_stdin > 0) close(m_stdin); + if (m_stdout > 0) close(m_stdout); + if (m_stderr > 0) close(m_stderr); +} + +int ChildProcess::updateFdSet(int n, fd_set& rfds, fd_set& wfds) +{ + FD_SET(m_stdout, &rfds); + int n2 = qMax(n, m_stdout); + FD_SET(m_stderr, &rfds); + n2 = qMax(n2, m_stderr); + if (m_inbuf.size()) { + FD_SET(m_stdin, &wfds); + n2 = qMax(n2, m_stdin); + } + return n2; +} + +void ChildProcess::processFdSet(QByteArray& outgoing, fd_set& rfds, fd_set& wfds) +{ + if (FD_ISSET(m_stdin, &wfds)) { // Data to write + writeFromBuffer(m_stdin, m_inbuf); + } + if (FD_ISSET(m_stdout, &rfds)) { // Data to read + readToBuffer(m_stdout, m_outbuf); + copyToOutgoing(outgoing, RemoteProtocol::stdout(), m_outbuf, m_id); + } + if (FD_ISSET(m_stderr, &rfds)) { // Data to read + readToBuffer(m_stderr, m_errbuf); + copyToOutgoing(outgoing, RemoteProtocol::stderr(), m_errbuf, m_id); + } + if (m_state == SentSigTerm && m_timer.hasExpired(m_timeout)) { + m_state = SentSigKill; + ::kill(m_pid, SIGKILL); + } +} + +/* + Stop the child process from running. Pass in a timeout value in milliseconds. + */ + +void ChildProcess::stop(int timeout) +{ + if (m_state == Running) { + // ### TODO: Kill by progress group... + if (timeout > 0) { + m_state = SentSigTerm; + m_timeout = timeout; + m_timer.start(); + ::kill(m_pid, SIGTERM); + } + else { + m_state = SentSigKill; + ::kill(m_pid, SIGKILL); + } + } +} + +void ChildProcess::setPriority(int priority) +{ + if (::setpriority(PRIO_PROCESS, m_pid, priority) == -1) + qWarning("Unable to set priority of pid=%d to %d", m_pid, priority); +} + +void ChildProcess::setOomAdjustment(int oomAdjustment) +{ + if (!ProcUtils::setOomAdjustment(m_pid, oomAdjustment)) + qWarning("Unable to set oom adjustment of pid=%d to %d", m_pid, oomAdjustment); +} + +static void makePipe(int fd[]) +{ + if (::pipe(fd) == -1) + qFatal("Unable to create pipe: %s", strerror(errno)); + if (::fcntl(fd[0], F_SETFL, O_NONBLOCK) == -1) // Set non-block on read end + qFatal("Unable to set nonblocking: %s", strerror(errno)); +} + +bool ChildProcess::doFork() +{ + m_state = Running; + + int fd1[2]; // Stdin of the child + int fd2[2]; // Stdout of the child + int fd3[2]; // Stderr of the child + makePipe(fd1); + makePipe(fd2); + makePipe(fd3); + + m_pid = fork(); + if (m_pid < 0) // failed to fork + qFatal("Failed to fork: %s", strerror(errno)); + + if (m_pid == 0) { // child + dup2(fd1[0], STDIN_FILENO); // Duplicate input side of pipe to stdin + dup2(fd2[1], STDOUT_FILENO); // Duplicate output side of the pipe to stdout + dup2(fd3[1], STDERR_FILENO); // Duplicate output side of the pipe to stderr + // Close all of the original pipes + close(fd1[0]); + close(fd1[1]); + close(fd2[0]); + close(fd2[1]); + close(fd3[0]); + close(fd3[1]); + return true; + } + + // Execute parent code here.... + m_stdin = fd1[1]; + m_stdout = fd2[0]; + m_stderr = fd3[0]; + close(fd1[0]); + close(fd2[1]); + close(fd3[1]); + return false; // Parent returns false +} + +void ChildProcess::sendStateChanged(QByteArray& outgoing, QProcess::ProcessState state) +{ + QJsonObject msg; + msg.insert(RemoteProtocol::event(), RemoteProtocol::stateChanged()); + msg.insert(RemoteProtocol::id(), m_id); + msg.insert(RemoteProtocol::stateChanged(), state); + outgoing.append(QJsonDocument(msg).toBinaryData()); +} + +void ChildProcess::sendStarted(QByteArray& outgoing) +{ + QJsonObject msg; + msg.insert(RemoteProtocol::event(), RemoteProtocol::started()); + msg.insert(RemoteProtocol::id(), m_id); + msg.insert(RemoteProtocol::pid(), m_pid); + outgoing.append(QJsonDocument(msg).toBinaryData()); +} + +void ChildProcess::sendFinished(QByteArray& outgoing, int exitCode, QProcess::ExitStatus exitStatus) +{ + QJsonObject msg; + msg.insert(RemoteProtocol::event(), RemoteProtocol::finished()); + msg.insert(RemoteProtocol::id(), m_id); + msg.insert(RemoteProtocol::exitCode(), exitCode); + msg.insert(RemoteProtocol::exitStatus(), exitStatus); + outgoing.append(QJsonDocument(msg).toBinaryData()); +} + +void ChildProcess::sendError(QByteArray& outgoing, QProcess::ProcessError err, const QString& errString) +{ + QJsonObject msg; + msg.insert(RemoteProtocol::event(), RemoteProtocol::error()); + msg.insert(RemoteProtocol::id(), m_id); + msg.insert(RemoteProtocol::error(), err); + msg.insert(RemoteProtocol::errorString(), errString); + outgoing.append(QJsonDocument(msg).toBinaryData()); +} + + +/**************************************************************************/ + +class ParentProcess { +public: + ParentProcess(int *argc, char ***argv); + ~ParentProcess(); + + ChildProcess *childFromPid(pid_t pid); + + int updateFdSet(fd_set& rfds, fd_set& wfds); + bool processFdSet(fd_set& rfds, fd_set& wfds); + void waitForChildren(); + bool handleMessage(QJsonObject& message); + bool needTimeout() const; + +private: + int *m_argc_ptr; + char ***m_argv_ptr; + QMap<int, ChildProcess *> m_children; + QByteArray m_sendbuf; + QByteArray m_recvbuf; +}; + + +ParentProcess::ParentProcess(int *argc, char ***argv) + : m_argc_ptr(argc), m_argv_ptr(argv) +{ + // Set up a signal handler for child events + makePipe(sig_child_pipe); + + struct sigaction action; + memset(&action, 0, sizeof(action)); + action.sa_handler = sig_child_handler; + action.sa_flags = SA_NOCLDSTOP; + ::sigaction(SIGCHLD, &action, &old_sig_child_handler); +} + +ParentProcess::~ParentProcess() +{ + // The normal destructor is only executed by a child process, so all + // we do is close down the extra file descriptors + foreach (ChildProcess *child, m_children) + delete child; // This closes the file descriptors included in the child processes + + ::sigaction(SIGCHLD, &old_sig_child_handler, 0); + ::close(sig_child_pipe[0]); + ::close(sig_child_pipe[1]); +} + +int ParentProcess::updateFdSet(fd_set& rfds, fd_set& wfds) +{ + FD_SET(0, &rfds); // Always read from stdin + FD_SET(sig_child_pipe[0], &rfds); // Watch for signals + if (m_sendbuf.size() > 0) + FD_SET(1, &wfds); + + int n = sig_child_pipe[0]; // We're pretty sure this is the largest so far + foreach (ChildProcess *child, m_children) + n = child->updateFdSet(n, rfds, wfds); + return n; +} + +ChildProcess *ParentProcess::childFromPid(pid_t pid) +{ + foreach (ChildProcess *child, m_children) + if (child->pid() == pid) + return child; + return NULL; +} + +void ParentProcess::waitForChildren() +{ + int status; + while (1) { + pid_t pid = ::waitpid(-1, &status, WNOHANG); + if (pid == -1 && errno == ECHILD) + return; + if (pid < 0) + qFatal("Error in wait %s", strerror(errno)); + bool crashed = !WIFEXITED(status); + int exitCode = WEXITSTATUS(status); + ChildProcess *child = childFromPid(pid); + if (child) { + m_children.take(child->id()); + if (crashed) + child->sendError(m_sendbuf, QProcess::Crashed, QStringLiteral("Process crashed")); + child->sendStateChanged(m_sendbuf, QProcess::NotRunning); + child->sendFinished(m_sendbuf, exitCode, + (crashed ? QProcess::CrashExit : QProcess::NormalExit)); + delete child; + } + } +} + +// Return 'true' if we're a new child process +bool ParentProcess::processFdSet(fd_set& rfds, fd_set& wfds) +{ + // Handle the children first because other messages may change the child list + foreach (ChildProcess *child, m_children) + child->processFdSet(m_sendbuf, rfds, wfds); + + if (FD_ISSET(sig_child_pipe[0], &rfds)) { // A child process died + char c; + if (::read(sig_child_pipe[0], &c, 1) == 1) + waitForChildren(); + else + qDebug() << "############################ READ SIG PROBLEM"; + } + if (FD_ISSET(0, &rfds)) { // Data available on stdin + readToBuffer(0, m_recvbuf); + // Process messages here + while (m_recvbuf.size() >= 12) { + qint32 message_size = qFromLittleEndian(((qint32 *)m_recvbuf.data())[2]) + 8; + if (m_recvbuf.size() < message_size) + break; + QByteArray msg = m_recvbuf.left(message_size); + m_recvbuf = m_recvbuf.mid(message_size); + QJsonObject object = QJsonDocument::fromBinaryData(msg).object(); + if (handleMessage(object)) + return true; + } + } + if (FD_ISSET(1, &wfds)) // Write to stdout + writeFromBuffer(1, m_sendbuf); + return false; +} + +// Return 'true' if this is a child process +bool ParentProcess::handleMessage(QJsonObject& message) +{ + if (message.value(RemoteProtocol::remote()).toString() == RemoteProtocol::stop()) { + // Force all children to stop + foreach (ChildProcess *child, m_children) + child->stop(0); + exit(0); + } + else { + QString command = message.value(RemoteProtocol::command()).toString(); + int id = message.value(RemoteProtocol::id()).toDouble(); + if (command == RemoteProtocol::stop()) { + ChildProcess *child = m_children.value(id); + if (child) { + int timeout = message.value(RemoteProtocol::timeout()).toDouble(); + child->stop(timeout); + } + } else if (command == RemoteProtocol::set()) { + ChildProcess *child = m_children.value(id); + if (child) { + QString key = message.value(RemoteProtocol::key()).toString(); + int value = message.value(RemoteProtocol::value()).toDouble(); + if (key == RemoteProtocol::priority()) + child->setPriority(value); + else if (key == RemoteProtocol::oomAdjustment()) + child->setOomAdjustment(value); + } + } else if (command == RemoteProtocol::start()) { + ProcessInfo info(message.value(RemoteProtocol::info()).toObject().toVariantMap()); + ChildProcess *child = new ChildProcess(id); + if (child->doFork()) { + delete child; + fixProcessState(info, m_argc_ptr, m_argv_ptr); + return true; + } + else { + m_children.insert(id, child); + child->sendStateChanged(m_sendbuf, QProcess::Starting); + child->sendStateChanged(m_sendbuf, QProcess::Running); + child->sendStarted(m_sendbuf); + } + } else if (command == RemoteProtocol::write()) { + ChildProcess *child = m_children.value(id); + if (child) + child->write(message.value(RemoteProtocol::data()).toString().toLocal8Bit()); + } + } + return false; +} + +/*! + Return true if some child is in a "needs a timeout" phase + */ + +bool ParentProcess::needTimeout() const +{ + foreach (ChildProcess *child, m_children) + if (child->needTimeout()) + return true; + return false; +} + +/**************************************************************************/ + +void forklauncher(int *argc, char ***argv ) +{ + ParentProcess parent(argc, argv); + + while (1) { + fd_set rfds, wfds; + FD_ZERO(&rfds); + FD_ZERO(&wfds); + int n = parent.updateFdSet(rfds, wfds); + + struct timeval timeout; + timeout.tv_sec = 0; + timeout.tv_usec = 100000; + struct timeval *tptr = (parent.needTimeout() ? &timeout : NULL); + + // Select on the inputs + int retval = ::select(n+1, &rfds, &wfds, NULL, tptr); + if (retval == -1 && errno == EINTR) + continue; + if (retval < 0) + qFatal("select: %s", strerror(errno)); + + if (parent.processFdSet(rfds, wfds)) + return; + } +} + +void displayFileDescriptors(int argc, char **argv) +{ + QList<QByteArray> arglist; + for (int i = 0 ; i < argc ; i++) + arglist << argv[i]; + + QList<QByteArray> envlist; + const char *entry; + for (int count = 0 ; (entry = environ[count]) ; count++) + envlist.append(entry); + + struct rlimit data; + if (::getrlimit(RLIMIT_NOFILE, &data) < 0) + qFatal("Unable to read rlimit"); + QList<int> fdlist; + for (unsigned int i=0 ; i < data.rlim_cur ; i++) { + if (::fcntl(i, F_GETFD, 0) != -1) + fdlist << i; + } + + qDebug() << "##### " << arglist; + qDebug() << "##### " << envlist; + qDebug() << "##### FD " << fdlist; +} + +QT_END_NAMESPACE_PROCESSMANAGER diff --git a/src/core/forklauncher.h b/src/core/forklauncher.h new file mode 100644 index 0000000..8cb74ea --- /dev/null +++ b/src/core/forklauncher.h @@ -0,0 +1,52 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef FORK_LAUNCHER_H +#define FORK_LAUNCHER_H + +#include "processmanager-global.h" + +QT_BEGIN_NAMESPACE_PROCESSMANAGER + +Q_CORE_EXPORT void forklauncher(int *argc, char ***argv); +Q_CORE_EXPORT void displayFileDescriptors(int argc, char **argv); + +QT_END_NAMESPACE_PROCESSMANAGER + +#endif // FORK_LAUNCHER_H diff --git a/src/core/pipeprocessbackendfactory.cpp b/src/core/pipeprocessbackendfactory.cpp index da867f5..d69fd46 100644 --- a/src/core/pipeprocessbackendfactory.cpp +++ b/src/core/pipeprocessbackendfactory.cpp @@ -129,7 +129,6 @@ QList<Q_PID> PipeProcessBackendFactory::internalProcesses() */ bool PipeProcessBackendFactory::send(const QJsonObject& message) { - // qDebug() << Q_FUNC_INFO << message; if (m_process->state() != QProcess::Running) { qCritical("Pipe process not running"); return false; @@ -169,7 +168,6 @@ void PipeProcessBackendFactory::pipeReadyReadStandardError() void PipeProcessBackendFactory::pipeStarted() { -// qDebug() << "Pipe process started"; } void PipeProcessBackendFactory::pipeError(QProcess::ProcessError error) @@ -187,7 +185,6 @@ void PipeProcessBackendFactory::pipeFinished(int exitCode, QProcess::ExitStatus void PipeProcessBackendFactory::pipeStateChanged(QProcess::ProcessState state) { Q_UNUSED(state); -// qDebug() << "Pipe process state change" << state; } #include "moc_pipeprocessbackendfactory.cpp" diff --git a/src/core/remoteprocessbackendfactory.cpp b/src/core/remoteprocessbackendfactory.cpp index 41ec4af..7c1f252 100644 --- a/src/core/remoteprocessbackendfactory.cpp +++ b/src/core/remoteprocessbackendfactory.cpp @@ -40,8 +40,6 @@ #include "remoteprocessbackendfactory.h" #include "remoteprocessbackend.h" -#include <QDebug> - QT_BEGIN_NAMESPACE_PROCESSMANAGER const int kRemoteTimerInterval = 1000; @@ -146,7 +144,6 @@ ProcessBackend * RemoteProcessBackendFactory::create(const ProcessInfo& info, QO void RemoteProcessBackendFactory::receive(const QJsonObject& message) { - // qDebug() << Q_FUNC_INFO << message; int id = message.value(QLatin1String("id")).toDouble(); if (m_backendMap.contains(id)) m_backendMap.value(id)->receive(message); diff --git a/src/core/remoteprotocol.h b/src/core/remoteprotocol.h index 3a31613..99f7131 100644 --- a/src/core/remoteprotocol.h +++ b/src/core/remoteprotocol.h @@ -40,6 +40,8 @@ #ifndef _REMOTE_PROTOCOL_H #define _REMOTE_PROTOCOL_H +#include <QString> + #include "processmanager-global.h" QT_BEGIN_NAMESPACE_PROCESSMANAGER diff --git a/src/declarative/declarativesocketlauncher.h b/src/declarative/declarativesocketlauncher.h index 494b4bc..64036bd 100644 --- a/src/declarative/declarativesocketlauncher.h +++ b/src/declarative/declarativesocketlauncher.h @@ -43,9 +43,6 @@ #include <QtDeclarative> #include "socketlauncher.h" -class JsonAuthority; -class QtAddOn::JsonStream::JsonAuthority; - QT_BEGIN_NAMESPACE_PROCESSMANAGER class Q_ADDON_PROCESSMANAGER_EXPORT DeclarativeSocketLauncher : public SocketLauncher, diff --git a/tests/auto/processmanager/processmanager.pro b/tests/auto/processmanager/processmanager.pro index 864fbf9..2049057 100644 --- a/tests/auto/processmanager/processmanager.pro +++ b/tests/auto/processmanager/processmanager.pro @@ -1,3 +1,3 @@ TEMPLATE = subdirs -SUBDIRS = testClient testPrelaunch testPipeLauncher testSocketLauncher test +SUBDIRS = testClient testPrelaunch testPipeLauncher testSocketLauncher testForkLauncher test diff --git a/tests/auto/processmanager/testClient/main.cpp b/tests/auto/processmanager/testClient/main.cpp index bb5875c..17cde87 100644 --- a/tests/auto/processmanager/testClient/main.cpp +++ b/tests/auto/processmanager/testClient/main.cpp @@ -41,6 +41,8 @@ #include <sys/uio.h> #include <unistd.h> #include <string.h> +#include <signal.h> +#include <stdio.h> const int kBufSize = 100; @@ -72,9 +74,24 @@ ssize_t readline(char *buffer, int max_len) return len; } +static char tough[] = "tough\n"; + int -main(int /*argc*/, char ** /*argv*/) +main(int argc, char **argv) { + for (int i = 1 ; i < argc ; i++) { + if (!strcmp(argv[i], "-noterm")) { + struct sigaction action; + memset(&action, 0, sizeof(action)); + action.sa_handler=SIG_IGN; + if (sigaction(SIGTERM, &action, NULL) < 0) { + perror("Unable ignore SIGTERM"); + return 1; + } + if (writeline(tough, strlen(tough)) < 0) + return 3; + } + } char buffer[kBufSize+1]; while (1) { diff --git a/tests/auto/processmanager/testForkLauncher/.gitignore b/tests/auto/processmanager/testForkLauncher/.gitignore new file mode 100644 index 0000000..65b56dd --- /dev/null +++ b/tests/auto/processmanager/testForkLauncher/.gitignore @@ -0,0 +1 @@ +testForkLauncher diff --git a/tests/auto/processmanager/testForkLauncher/main.cpp b/tests/auto/processmanager/testForkLauncher/main.cpp new file mode 100644 index 0000000..ca25645 --- /dev/null +++ b/tests/auto/processmanager/testForkLauncher/main.cpp @@ -0,0 +1,142 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QCoreApplication> +#include <QSocketNotifier> +#include <QTimer> +#include <QDebug> +#include <QtEndian> +#include <QJsonDocument> +#include <QJsonObject> + +#include "forklauncher.h" + +#if defined(Q_OS_LINUX) +#include <sys/types.h> +#include <unistd.h> +#include <grp.h> +#endif +#include <pwd.h> + +QT_USE_NAMESPACE_PROCESSMANAGER + +class Container : public QObject +{ + Q_OBJECT + +public: + Container() { + m_in = new QSocketNotifier( STDIN_FILENO, QSocketNotifier::Read, this ); + connect(m_in, SIGNAL(activated(int)), SLOT(inReady(int))); + m_in->setEnabled(true); + m_out = new QSocketNotifier( STDOUT_FILENO, QSocketNotifier::Write, this ); + connect(m_out, SIGNAL(activated(int)), SLOT(outReady(int))); + m_out->setEnabled(false); + } + + void handleMessage(const QString& cmd) { + if (cmd == QLatin1String("stop")) { + qDebug() << "Stopping"; + exit(0); + } + else if (cmd == QLatin1String("crash")) { + qDebug() << "Crashing"; + exit(2); + } + else { + m_outbuf.append(cmd.toLatin1()); + m_outbuf.append('\n'); + m_out->setEnabled(true); + } + } + +public slots: + void inReady(int fd) { + m_in->setEnabled(false); + const int bufsize = 1024; + uint oldSize = m_inbuf.size(); + m_inbuf.resize(oldSize + bufsize); + int n = ::read(fd, m_inbuf.data()+oldSize, bufsize); + if (n > 0) + m_inbuf.resize(oldSize+n); + else + m_inbuf.resize(oldSize); + + int offset; + while ((offset=m_inbuf.indexOf('\n')) != -1) { + QByteArray msg = m_inbuf.left(offset); + m_inbuf = m_inbuf.mid(offset + 1); + if (msg.size() > 0) + handleMessage(QString::fromLocal8Bit(msg)); + } + m_in->setEnabled(true); + } + + void outReady(int fd) { + m_out->setEnabled(false); + if (m_outbuf.size()) { + int n = ::write(fd, m_outbuf.data(), m_outbuf.size()); + if (n == -1) { + qDebug() << "Failed to write to stdout"; + exit(-1); + } + if (n < m_outbuf.size()) + m_outbuf = m_outbuf.mid(n); + else + m_outbuf.clear(); + } + if (m_outbuf.size()) + m_out->setEnabled(true); + } + +private: + QSocketNotifier *m_in, *m_out; + QByteArray m_inbuf, m_outbuf; +}; + +int +main(int argc, char **argv) +{ + forklauncher(&argc, &argv); + QCoreApplication app(argc, argv); + Container c; + return app.exec(); +} + +#include "main.moc" diff --git a/tests/auto/processmanager/testForkLauncher/testForkLauncher.pro b/tests/auto/processmanager/testForkLauncher/testForkLauncher.pro new file mode 100644 index 0000000..bb5184a --- /dev/null +++ b/tests/auto/processmanager/testForkLauncher/testForkLauncher.pro @@ -0,0 +1,11 @@ +CONFIG -= app_bundle +QT += processmanager + +include(../processmanager.pri) + +DESTDIR = ./ +SOURCES += main.cpp +TARGET = testForkLauncher + +target.path = $$[QT_INSTALL_TESTS]/$$TESTCASE_NAME/testForkLauncher +INSTALLS += target diff --git a/tests/auto/processmanager/testPrelaunch/main.cpp b/tests/auto/processmanager/testPrelaunch/main.cpp index de83b1a..c53a258 100644 --- a/tests/auto/processmanager/testPrelaunch/main.cpp +++ b/tests/auto/processmanager/testPrelaunch/main.cpp @@ -45,6 +45,8 @@ #include <QJsonDocument> #include <QJsonObject> #include "processinfo.h" +#include <signal.h> +#include <stdio.h> #if defined(Q_OS_LINUX) #include <sys/types.h> @@ -158,6 +160,25 @@ int main(int argc, char **argv) { QCoreApplication app(argc, argv); + QStringList args = QCoreApplication::arguments(); + QString progname = args.takeFirst(); + while (args.size()) { + QString arg = args.at(0); + if (!arg.startsWith('-')) + break; + args.removeFirst(); + if (arg == QStringLiteral("-noterm")) { + struct sigaction action; + memset(&action, 0, sizeof(action)); + action.sa_handler=SIG_IGN; + if (sigaction(SIGTERM, &action, NULL) < 0) { + perror("Unable ignore SIGTERM"); + return 1; + } + qDebug() << "tough"; + } + } + Container c; qDebug() << "testPrelaunch running"; return app.exec(); diff --git a/tests/auto/processmanager/tst_processmanager.cpp b/tests/auto/processmanager/tst_processmanager.cpp index f47d87c..6b7db25 100644 --- a/tests/auto/processmanager/tst_processmanager.cpp +++ b/tests/auto/processmanager/tst_processmanager.cpp @@ -219,10 +219,10 @@ public: , stderrSpy(process, SIGNAL(standardError(const QByteArray&))) {} void check( int startCount, int errorCount, int finishedCount, int stateCount ) { - QVERIFY(startSpy.count() == startCount); - QVERIFY(errorSpy.count() == errorCount); - QVERIFY(finishedSpy.count() == finishedCount); - QVERIFY(stateSpy.count() == stateCount); + QCOMPARE(startSpy.count(), startCount); + QCOMPARE(errorSpy.count(), errorCount); + QCOMPARE(finishedSpy.count(), finishedCount); + QCOMPARE(stateSpy.count(), stateCount); bool failedToStart = false; for (int i = 0 ; i < errorSpy.count() ; i++) if (errorSpy.at(i) == QProcess::FailedToStart) @@ -400,6 +400,47 @@ static void startAndStopClient(ProcessBackendManager *manager, ProcessInfo info, cleanupProcess(process); } +const int kProcessCount = 20; + +static void startAndStopMultiple(ProcessBackendManager *manager, ProcessInfo info, CommandFunc func) +{ + ProcessBackend *plist[kProcessCount]; + Spy *slist[kProcessCount]; + + for (int i = 0 ; i < kProcessCount ; i++) { + ProcessBackend *process = manager->create(info); + QVERIFY(process); + QVERIFY(process->state() == QProcess::NotRunning); + Spy *spy = new Spy(process); + plist[i] = process; + slist[i] = spy; + } + + for (int i = 0 ; i < kProcessCount ; i++ ) + plist[i]->start(); + + for (int i = 0 ; i < kProcessCount ; i++ ) { + slist[i]->waitStart(); + verifyRunning(plist[i]); + slist[i]->check(1,0,0,2); + } + + for (int i = 0 ; i < kProcessCount ; i++ ) + func(plist[i], "stop"); + + for (int i = 0 ; i < kProcessCount ; i++ ) { + slist[i]->waitFinished(); + slist[i]->check(1,0,1,3); + slist[i]->checkExitCode(0); + slist[i]->checkExitStatus(QProcess::NormalExit); + } + + for (int i = 0 ; i < kProcessCount ; i++) { + cleanupProcess(plist[i]); + delete slist[i]; + } +} + static void startAndKillClient(ProcessBackendManager *manager, ProcessInfo info, CommandFunc func) { Q_UNUSED(func); @@ -424,6 +465,15 @@ static void startAndKillClient(ProcessBackendManager *manager, ProcessInfo info, cleanupProcess(process); } +static void startAndKillTough(ProcessBackendManager *manager, ProcessInfo info, CommandFunc func) +{ + Q_UNUSED(func); + QStringList args = info.arguments(); + args << "-noterm"; + info.setArguments(args); + startAndKillClient(manager, info, func); +} + static void startAndCrashClient(ProcessBackendManager *manager, ProcessInfo info, CommandFunc func) { ProcessBackend *process = manager->create(info); @@ -608,7 +658,7 @@ static void fixUidGid(ProcessInfo& info) info.setGid(gidString.toLongLong()); } -static void standardFactoryTest( clientFunc func ) +static void standardTest( clientFunc func ) { ProcessBackendManager *manager = new ProcessBackendManager; @@ -621,7 +671,7 @@ static void standardFactoryTest( clientFunc func ) delete manager; } -static void prelaunchFactoryTest( clientFunc func ) +static void prelaunchTest( clientFunc func ) { ProcessBackendManager *manager = new ProcessBackendManager; @@ -699,6 +749,25 @@ static void socketLauncherTest( clientFunc func, QStringList args=QStringList() delete remote; } +#if defined(Q_OS_LINUX) +static void forkLauncherTest( clientFunc func ) +{ + ProcessBackendManager *manager = new ProcessBackendManager; + ProcessInfo info; + info.setValue("program", "testForkLauncher/testForkLauncher"); + manager->addFactory(new PipeProcessBackendFactory(info)); + + // Wait for the factory to have launched a pipe + waitForInternalProcess(manager); + QVERIFY(manager->internalProcesses().count() == 1); + + ProcessInfo info2; + fixUidGid(info2); + func(manager, info2, writeLine); + delete manager; +} +#endif + static void socketSchemaTest( clientFunc func ) { QStringList args; @@ -718,31 +787,37 @@ class tst_ProcessManager : public QObject private slots: void initTestCase(); - void standardStartAndStop() { standardFactoryTest(startAndStopClient); } - void standardStartAndKill() { standardFactoryTest(startAndKillClient); } - void standardStartAndCrash() { standardFactoryTest(startAndCrashClient); } - void standardFailToStart() { standardFactoryTest(failToStartClient); } - void standardEcho() { standardFactoryTest(echoClient); } - void standardPriorityChangeBefore() { standardFactoryTest(priorityChangeBeforeClient); } - void standardPriorityChangeAfter() { standardFactoryTest(priorityChangeAfterClient); } + void standardStartAndStop() { standardTest(startAndStopClient); } + void standardStartAndStopMultiple() { standardTest(startAndStopMultiple); } + void standardStartAndKill() { standardTest(startAndKillClient); } + void standardStartAndKillTough() { standardTest(startAndKillTough); } + void standardStartAndCrash() { standardTest(startAndCrashClient); } + void standardFailToStart() { standardTest(failToStartClient); } + void standardEcho() { standardTest(echoClient); } + void standardPriorityChangeBefore() { standardTest(priorityChangeBeforeClient); } + void standardPriorityChangeAfter() { standardTest(priorityChangeAfterClient); } #if defined(Q_OS_LINUX) - void standardOomChangeBefore() { standardFactoryTest(oomChangeBeforeClient); } - void standardOomChangeAfter() { standardFactoryTest(oomChangeAfterClient); } + void standardOomChangeBefore() { standardTest(oomChangeBeforeClient); } + void standardOomChangeAfter() { standardTest(oomChangeAfterClient); } #endif - void prelaunchStartAndStop() { prelaunchFactoryTest(startAndStopClient); } - void prelaunchStartAndKill() { prelaunchFactoryTest(startAndKillClient); } - void prelaunchStartAndCrash() { prelaunchFactoryTest(startAndCrashClient); } - void prelaunchEcho() { prelaunchFactoryTest(echoClient); } - void prelaunchPriorityChangeBefore() { prelaunchFactoryTest(priorityChangeBeforeClient); } - void prelaunchPriorityChangeAfter() { prelaunchFactoryTest(priorityChangeAfterClient); } + void prelaunchStartAndStop() { prelaunchTest(startAndStopClient); } + void prelaunchStartAndStopMultiple() { prelaunchTest(startAndStopMultiple); } + void prelaunchStartAndKill() { prelaunchTest(startAndKillClient); } + void prelaunchStartAndKillTough() { prelaunchTest(startAndKillTough); } + void prelaunchStartAndCrash() { prelaunchTest(startAndCrashClient); } + void prelaunchEcho() { prelaunchTest(echoClient); } + void prelaunchPriorityChangeBefore() { prelaunchTest(priorityChangeBeforeClient); } + void prelaunchPriorityChangeAfter() { prelaunchTest(priorityChangeAfterClient); } #if defined(Q_OS_LINUX) - void prelaunchOomChangeBefore() { prelaunchFactoryTest(oomChangeBeforeClient); } - void prelaunchOomChangeAfter() { prelaunchFactoryTest(oomChangeAfterClient); } + void prelaunchOomChangeBefore() { prelaunchTest(oomChangeBeforeClient); } + void prelaunchOomChangeAfter() { prelaunchTest(oomChangeAfterClient); } #endif void prelaunchRestrictedStartAndStop() { prelaunchRestrictedTest(startAndStopClient); } + void prelaunchRestrictedStartAndStopMultiple() { prelaunchRestrictedTest(startAndStopMultiple); } void prelaunchRestrictedStartAndKill() { prelaunchRestrictedTest(startAndKillClient); } + void prelaunchRestrictedStartAndKillTough() { prelaunchRestrictedTest(startAndKillTough); } void prelaunchRestrictedStartAndCrash() { prelaunchRestrictedTest(startAndCrashClient); } void prelaunchRestrictedEcho() { prelaunchRestrictedTest(echoClient); } void prelaunchRestrictedPriorityChangeBefore() { prelaunchRestrictedTest(priorityChangeBeforeClient); } @@ -753,7 +828,9 @@ private slots: #endif void pipeLauncherStartAndStop() { pipeLauncherTest(startAndStopClient); } + void pipeLauncherStartAndStopMultiple() { pipeLauncherTest(startAndStopMultiple); } void pipeLauncherStartAndKill() { pipeLauncherTest(startAndKillClient); } + void pipeLauncherStartAndKillTough() { pipeLauncherTest(startAndKillTough); } void pipeLauncherStartAndCrash() { pipeLauncherTest(startAndCrashClient); } void pipeLauncherEcho() { pipeLauncherTest(echoClient); } void pipeLauncherPriorityChangeBefore() { pipeLauncherTest(priorityChangeBeforeClient); } @@ -764,7 +841,9 @@ private slots: #endif void socketLauncherStartAndStop() { socketLauncherTest(startAndStopClient); } + void socketLauncherStartAndStopMultiple() { socketLauncherTest(startAndStopMultiple); } void socketLauncherStartAndKill() { socketLauncherTest(startAndKillClient); } + void socketLauncherStartAndKillTough() { socketLauncherTest(startAndKillTough); } void socketLauncherStartAndCrash() { socketLauncherTest(startAndCrashClient); } void socketLauncherEcho() { socketLauncherTest(echoClient); } void socketLauncherPriorityChangeBefore() { socketLauncherTest(priorityChangeBeforeClient); } @@ -775,7 +854,9 @@ private slots: #endif void socketSchemaStartAndStop() { socketSchemaTest(startAndStopClient); } + void socketSchemaStartAndStopMultiple() { socketSchemaTest(startAndStopMultiple); } void socketSchemaStartAndKill() { socketSchemaTest(startAndKillClient); } + void socketSchemaStartAndKillTough() { socketSchemaTest(startAndKillTough); } void socketSchemaStartAndCrash() { socketSchemaTest(startAndCrashClient); } void socketSchemaEcho() { socketSchemaTest(echoClient); } void socketSchemaPriorityChangeBefore() { socketSchemaTest(priorityChangeBeforeClient); } @@ -785,6 +866,19 @@ private slots: void socketSchemaOomChangeAfter() { socketSchemaTest(oomChangeAfterClient); } #endif +#if defined(Q_OS_LINUX) + void forkLauncherStartAndStop() { forkLauncherTest(startAndStopClient); } + void forkLauncherStartAndStopMultiple() { forkLauncherTest(startAndStopMultiple); } + void forkLauncherStartAndKill() { forkLauncherTest(startAndKillClient); } + void forkLauncherStartAndKillTough() { forkLauncherTest(startAndKillTough); } + void forkLauncherStartAndCrash() { forkLauncherTest(startAndCrashClient); } + void forkLauncherEcho() { forkLauncherTest(echoClient); } + void forkLauncherPriorityChangeBefore() { forkLauncherTest(priorityChangeBeforeClient); } + void forkLauncherPriorityChangeAfter() { forkLauncherTest(priorityChangeAfterClient); } + void forkLauncherOomChangeBefore() { forkLauncherTest(oomChangeBeforeClient); } + void forkLauncherOomChangeAfter() { forkLauncherTest(oomChangeAfterClient); } +#endif + void prelaunchChildAbort(); void frontend(); |