| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Copyright (C) 2016 Intel Corporation. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the QtCore module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| //#define QPROCESS_DEBUG |
| #include "qdebug.h" |
| |
| #if QT_CONFIG(process) && defined(QPROCESS_DEBUG) |
| #include "private/qtools_p.h" |
| #include <ctype.h> |
| |
| /* |
| Returns a human readable representation of the first \a len |
| characters in \a data. |
| */ |
| QT_BEGIN_NAMESPACE |
| static QByteArray qt_prettyDebug(const char *data, int len, int maxSize) |
| { |
| if (!data) return "(null)"; |
| QByteArray out; |
| for (int i = 0; i < len; ++i) { |
| char c = data[i]; |
| if (isprint(c)) { |
| out += c; |
| } else switch (c) { |
| case '\n': out += "\\n"; break; |
| case '\r': out += "\\r"; break; |
| case '\t': out += "\\t"; break; |
| default: { |
| const char buf[] = { |
| '\\', |
| QtMiscUtils::toOct(uchar(c) / 64), |
| QtMiscUtils::toOct(uchar(c) % 64 / 8), |
| QtMiscUtils::toOct(uchar(c) % 8), |
| 0 |
| }; |
| out += buf; |
| } |
| } |
| } |
| |
| if (len < maxSize) |
| out += "..."; |
| |
| return out; |
| } |
| QT_END_NAMESPACE |
| #endif |
| |
| #include "qplatformdefs.h" |
| |
| #include "qprocess.h" |
| #include "qprocess_p.h" |
| #include "qstandardpaths.h" |
| #include "private/qcore_unix_p.h" |
| #include "private/qlocking_p.h" |
| |
| #ifdef Q_OS_MAC |
| #include <private/qcore_mac_p.h> |
| #endif |
| |
| #include <private/qcoreapplication_p.h> |
| #include <private/qthread_p.h> |
| #include <qfile.h> |
| #include <qfileinfo.h> |
| #include <qdir.h> |
| #include <qlist.h> |
| #include <qmutex.h> |
| #include <qsocketnotifier.h> |
| #include <qthread.h> |
| #include <qelapsedtimer.h> |
| |
| #ifdef Q_OS_QNX |
| # include <sys/neutrino.h> |
| #endif |
| |
| #include <errno.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #if QT_CONFIG(process) |
| #include <forkfd.h> |
| #endif |
| |
| QT_BEGIN_NAMESPACE |
| |
| #if !defined(Q_OS_DARWIN) |
| |
| QT_BEGIN_INCLUDE_NAMESPACE |
| extern char **environ; |
| QT_END_INCLUDE_NAMESPACE |
| |
| QProcessEnvironment QProcessEnvironment::systemEnvironment() |
| { |
| QProcessEnvironment env; |
| const char *entry; |
| for (int count = 0; (entry = environ[count]); ++count) { |
| const char *equal = strchr(entry, '='); |
| if (!equal) |
| continue; |
| |
| QByteArray name(entry, equal - entry); |
| QByteArray value(equal + 1); |
| env.d->vars.insert(QProcessEnvironmentPrivate::Key(name), |
| QProcessEnvironmentPrivate::Value(value)); |
| } |
| return env; |
| } |
| |
| #endif // !defined(Q_OS_DARWIN) |
| |
| #if QT_CONFIG(process) |
| |
| namespace { |
| struct QProcessPoller |
| { |
| QProcessPoller(const QProcessPrivate &proc); |
| |
| int poll(int timeout); |
| |
| pollfd &stdinPipe() { return pfds[0]; } |
| pollfd &stdoutPipe() { return pfds[1]; } |
| pollfd &stderrPipe() { return pfds[2]; } |
| pollfd &forkfd() { return pfds[3]; } |
| pollfd &childStartedPipe() { return pfds[4]; } |
| |
| enum { n_pfds = 5 }; |
| pollfd pfds[n_pfds]; |
| }; |
| |
| QProcessPoller::QProcessPoller(const QProcessPrivate &proc) |
| { |
| for (int i = 0; i < n_pfds; i++) |
| pfds[i] = qt_make_pollfd(-1, POLLIN); |
| |
| stdoutPipe().fd = proc.stdoutChannel.pipe[0]; |
| stderrPipe().fd = proc.stderrChannel.pipe[0]; |
| |
| if (!proc.writeBuffer.isEmpty()) { |
| stdinPipe().fd = proc.stdinChannel.pipe[1]; |
| stdinPipe().events = POLLOUT; |
| } |
| |
| forkfd().fd = proc.forkfd; |
| |
| if (proc.processState == QProcess::Starting) |
| childStartedPipe().fd = proc.childStartedPipe[0]; |
| } |
| |
| int QProcessPoller::poll(int timeout) |
| { |
| const nfds_t nfds = (childStartedPipe().fd == -1) ? 4 : 5; |
| return qt_poll_msecs(pfds, nfds, timeout); |
| } |
| } // anonymous namespace |
| |
| static bool qt_pollfd_check(const pollfd &pfd, short revents) |
| { |
| return pfd.fd >= 0 && (pfd.revents & (revents | POLLHUP | POLLERR | POLLNVAL)) != 0; |
| } |
| |
| static int qt_create_pipe(int *pipe) |
| { |
| if (pipe[0] != -1) |
| qt_safe_close(pipe[0]); |
| if (pipe[1] != -1) |
| qt_safe_close(pipe[1]); |
| int pipe_ret = qt_safe_pipe(pipe); |
| if (pipe_ret != 0) { |
| qErrnoWarning("QProcessPrivate::createPipe: Cannot create pipe %p", pipe); |
| } |
| return pipe_ret; |
| } |
| |
| void QProcessPrivate::destroyPipe(int *pipe) |
| { |
| if (pipe[1] != -1) { |
| qt_safe_close(pipe[1]); |
| pipe[1] = -1; |
| } |
| if (pipe[0] != -1) { |
| qt_safe_close(pipe[0]); |
| pipe[0] = -1; |
| } |
| } |
| |
| void QProcessPrivate::closeChannel(Channel *channel) |
| { |
| destroyPipe(channel->pipe); |
| } |
| |
| /* |
| Create the pipes to a QProcessPrivate::Channel. |
| |
| This function must be called in order: stdin, stdout, stderr |
| */ |
| bool QProcessPrivate::openChannel(Channel &channel) |
| { |
| Q_Q(QProcess); |
| |
| if (&channel == &stderrChannel && processChannelMode == QProcess::MergedChannels) { |
| channel.pipe[0] = -1; |
| channel.pipe[1] = -1; |
| return true; |
| } |
| |
| if (channel.type == Channel::Normal) { |
| // we're piping this channel to our own process |
| if (qt_create_pipe(channel.pipe) != 0) |
| return false; |
| |
| // create the socket notifiers |
| if (threadData->hasEventDispatcher()) { |
| if (&channel == &stdinChannel) { |
| channel.notifier = new QSocketNotifier(channel.pipe[1], |
| QSocketNotifier::Write, q); |
| channel.notifier->setEnabled(false); |
| QObject::connect(channel.notifier, SIGNAL(activated(int)), |
| q, SLOT(_q_canWrite())); |
| } else { |
| channel.notifier = new QSocketNotifier(channel.pipe[0], |
| QSocketNotifier::Read, q); |
| const char *receiver; |
| if (&channel == &stdoutChannel) |
| receiver = SLOT(_q_canReadStandardOutput()); |
| else |
| receiver = SLOT(_q_canReadStandardError()); |
| QObject::connect(channel.notifier, SIGNAL(activated(int)), |
| q, receiver); |
| } |
| } |
| |
| return true; |
| } else if (channel.type == Channel::Redirect) { |
| // we're redirecting the channel to/from a file |
| QByteArray fname = QFile::encodeName(channel.file); |
| |
| if (&channel == &stdinChannel) { |
| // try to open in read-only mode |
| channel.pipe[1] = -1; |
| if ( (channel.pipe[0] = qt_safe_open(fname, O_RDONLY)) != -1) |
| return true; // success |
| setErrorAndEmit(QProcess::FailedToStart, |
| QProcess::tr("Could not open input redirection for reading")); |
| } else { |
| int mode = O_WRONLY | O_CREAT; |
| if (channel.append) |
| mode |= O_APPEND; |
| else |
| mode |= O_TRUNC; |
| |
| channel.pipe[0] = -1; |
| if ( (channel.pipe[1] = qt_safe_open(fname, mode, 0666)) != -1) |
| return true; // success |
| |
| setErrorAndEmit(QProcess::FailedToStart, |
| QProcess::tr("Could not open input redirection for reading")); |
| } |
| cleanup(); |
| return false; |
| } else { |
| Q_ASSERT_X(channel.process, "QProcess::start", "Internal error"); |
| |
| Channel *source; |
| Channel *sink; |
| |
| if (channel.type == Channel::PipeSource) { |
| // we are the source |
| source = &channel; |
| sink = &channel.process->stdinChannel; |
| |
| Q_ASSERT(source == &stdoutChannel); |
| Q_ASSERT(sink->process == this && sink->type == Channel::PipeSink); |
| } else { |
| // we are the sink; |
| source = &channel.process->stdoutChannel; |
| sink = &channel; |
| |
| Q_ASSERT(sink == &stdinChannel); |
| Q_ASSERT(source->process == this && source->type == Channel::PipeSource); |
| } |
| |
| if (source->pipe[1] != INVALID_Q_PIPE || sink->pipe[0] != INVALID_Q_PIPE) { |
| // already created, do nothing |
| return true; |
| } else { |
| Q_ASSERT(source->pipe[0] == INVALID_Q_PIPE && source->pipe[1] == INVALID_Q_PIPE); |
| Q_ASSERT(sink->pipe[0] == INVALID_Q_PIPE && sink->pipe[1] == INVALID_Q_PIPE); |
| |
| Q_PIPE pipe[2] = { -1, -1 }; |
| if (qt_create_pipe(pipe) != 0) |
| return false; |
| sink->pipe[0] = pipe[0]; |
| source->pipe[1] = pipe[1]; |
| |
| return true; |
| } |
| } |
| } |
| |
| static char **_q_dupEnvironment(const QProcessEnvironmentPrivate::Map &environment, int *envc) |
| { |
| *envc = 0; |
| if (environment.isEmpty()) |
| return 0; |
| |
| char **envp = new char *[environment.count() + 2]; |
| envp[environment.count()] = 0; |
| envp[environment.count() + 1] = 0; |
| |
| auto it = environment.constBegin(); |
| const auto end = environment.constEnd(); |
| for ( ; it != end; ++it) { |
| QByteArray key = it.key(); |
| QByteArray value = it.value().bytes(); |
| key.reserve(key.length() + 1 + value.length()); |
| key.append('='); |
| key.append(value); |
| |
| envp[(*envc)++] = ::strdup(key.constData()); |
| } |
| |
| return envp; |
| } |
| |
| void QProcessPrivate::startProcess() |
| { |
| Q_Q(QProcess); |
| |
| #if defined (QPROCESS_DEBUG) |
| qDebug("QProcessPrivate::startProcess()"); |
| #endif |
| |
| // Initialize pipes |
| if (!openChannel(stdinChannel) || |
| !openChannel(stdoutChannel) || |
| !openChannel(stderrChannel) || |
| qt_create_pipe(childStartedPipe) != 0) { |
| setErrorAndEmit(QProcess::FailedToStart, qt_error_string(errno)); |
| cleanup(); |
| return; |
| } |
| |
| if (threadData->hasEventDispatcher()) { |
| startupSocketNotifier = new QSocketNotifier(childStartedPipe[0], |
| QSocketNotifier::Read, q); |
| QObject::connect(startupSocketNotifier, SIGNAL(activated(int)), |
| q, SLOT(_q_startupNotification())); |
| } |
| |
| // Start the process (platform dependent) |
| q->setProcessState(QProcess::Starting); |
| |
| // Create argument list with right number of elements, and set the final |
| // one to 0. |
| char **argv = new char *[arguments.count() + 2]; |
| argv[arguments.count() + 1] = 0; |
| |
| // Encode the program name. |
| QByteArray encodedProgramName = QFile::encodeName(program); |
| #ifdef Q_OS_MAC |
| // allow invoking of .app bundles on the Mac. |
| QFileInfo fileInfo(program); |
| if (encodedProgramName.endsWith(".app") && fileInfo.isDir()) { |
| QCFType<CFURLRef> url = CFURLCreateWithFileSystemPath(0, |
| QCFString(fileInfo.absoluteFilePath()), |
| kCFURLPOSIXPathStyle, true); |
| { |
| // CFBundle is not reentrant, since CFBundleCreate might return a reference |
| // to a cached bundle object. Protect the bundle calls with a mutex lock. |
| static QBasicMutex cfbundleMutex; |
| const auto locker = qt_scoped_lock(cfbundleMutex); |
| QCFType<CFBundleRef> bundle = CFBundleCreate(0, url); |
| // 'executableURL' can be either relative or absolute ... |
| QCFType<CFURLRef> executableURL = CFBundleCopyExecutableURL(bundle); |
| // not to depend on caching - make sure it's always absolute. |
| url = CFURLCopyAbsoluteURL(executableURL); |
| } |
| if (url) { |
| const QCFString str = CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle); |
| encodedProgramName += (QDir::separator() + QDir(program).relativeFilePath(QString::fromCFString(str))).toUtf8(); |
| } |
| } |
| #endif |
| |
| // Add the program name to the argument list. |
| argv[0] = nullptr; |
| if (!program.contains(QLatin1Char('/'))) { |
| const QString &exeFilePath = QStandardPaths::findExecutable(program); |
| if (!exeFilePath.isEmpty()) { |
| const QByteArray &tmp = QFile::encodeName(exeFilePath); |
| argv[0] = ::strdup(tmp.constData()); |
| } |
| } |
| if (!argv[0]) |
| argv[0] = ::strdup(encodedProgramName.constData()); |
| |
| // Add every argument to the list |
| for (int i = 0; i < arguments.count(); ++i) |
| argv[i + 1] = ::strdup(QFile::encodeName(arguments.at(i)).constData()); |
| |
| // Duplicate the environment. |
| int envc = 0; |
| char **envp = 0; |
| if (environment.d.constData()) { |
| envp = _q_dupEnvironment(environment.d.constData()->vars, &envc); |
| } |
| |
| // Encode the working directory if it's non-empty, otherwise just pass 0. |
| const char *workingDirPtr = 0; |
| QByteArray encodedWorkingDirectory; |
| if (!workingDirectory.isEmpty()) { |
| encodedWorkingDirectory = QFile::encodeName(workingDirectory); |
| workingDirPtr = encodedWorkingDirectory.constData(); |
| } |
| |
| // Start the process manager, and fork off the child process. |
| pid_t childPid; |
| forkfd = ::forkfd(FFD_CLOEXEC, &childPid); |
| int lastForkErrno = errno; |
| if (forkfd != FFD_CHILD_PROCESS) { |
| // Parent process. |
| // Clean up duplicated memory. |
| for (int i = 0; i <= arguments.count(); ++i) |
| free(argv[i]); |
| for (int i = 0; i < envc; ++i) |
| free(envp[i]); |
| delete [] argv; |
| delete [] envp; |
| } |
| |
| // On QNX, if spawnChild failed, childPid will be -1 but forkfd is still 0. |
| // This is intentional because we only want to handle failure to fork() |
| // here, which is a rare occurrence. Handling of the failure to start is |
| // done elsewhere. |
| if (forkfd == -1) { |
| // Cleanup, report error and return |
| #if defined (QPROCESS_DEBUG) |
| qDebug("fork failed: %ls", qUtf16Printable(qt_error_string(lastForkErrno))); |
| #endif |
| q->setProcessState(QProcess::NotRunning); |
| setErrorAndEmit(QProcess::FailedToStart, |
| QProcess::tr("Resource error (fork failure): %1").arg(qt_error_string(lastForkErrno))); |
| cleanup(); |
| return; |
| } |
| |
| // Start the child. |
| if (forkfd == FFD_CHILD_PROCESS) { |
| execChild(workingDirPtr, argv, envp); |
| ::_exit(-1); |
| } |
| |
| pid = Q_PID(childPid); |
| |
| // parent |
| // close the ends we don't use and make all pipes non-blocking |
| qt_safe_close(childStartedPipe[1]); |
| childStartedPipe[1] = -1; |
| |
| if (stdinChannel.pipe[0] != -1) { |
| qt_safe_close(stdinChannel.pipe[0]); |
| stdinChannel.pipe[0] = -1; |
| } |
| |
| if (stdinChannel.pipe[1] != -1) |
| ::fcntl(stdinChannel.pipe[1], F_SETFL, ::fcntl(stdinChannel.pipe[1], F_GETFL) | O_NONBLOCK); |
| |
| if (stdoutChannel.pipe[1] != -1) { |
| qt_safe_close(stdoutChannel.pipe[1]); |
| stdoutChannel.pipe[1] = -1; |
| } |
| |
| if (stdoutChannel.pipe[0] != -1) |
| ::fcntl(stdoutChannel.pipe[0], F_SETFL, ::fcntl(stdoutChannel.pipe[0], F_GETFL) | O_NONBLOCK); |
| |
| if (stderrChannel.pipe[1] != -1) { |
| qt_safe_close(stderrChannel.pipe[1]); |
| stderrChannel.pipe[1] = -1; |
| } |
| if (stderrChannel.pipe[0] != -1) |
| ::fcntl(stderrChannel.pipe[0], F_SETFL, ::fcntl(stderrChannel.pipe[0], F_GETFL) | O_NONBLOCK); |
| |
| if (threadData->eventDispatcher.loadAcquire()) { |
| deathNotifier = new QSocketNotifier(forkfd, QSocketNotifier::Read, q); |
| QObject::connect(deathNotifier, SIGNAL(activated(int)), |
| q, SLOT(_q_processDied())); |
| } |
| } |
| |
| struct ChildError |
| { |
| int code; |
| char function[8]; |
| }; |
| |
| void QProcessPrivate::execChild(const char *workingDir, char **argv, char **envp) |
| { |
| ::signal(SIGPIPE, SIG_DFL); // reset the signal that we ignored |
| |
| Q_Q(QProcess); |
| ChildError error = { 0, {} }; // force zeroing of function[8] |
| |
| // copy the stdin socket if asked to (without closing on exec) |
| if (inputChannelMode != QProcess::ForwardedInputChannel) |
| qt_safe_dup2(stdinChannel.pipe[0], STDIN_FILENO, 0); |
| |
| // copy the stdout and stderr if asked to |
| if (processChannelMode != QProcess::ForwardedChannels) { |
| if (processChannelMode != QProcess::ForwardedOutputChannel) |
| qt_safe_dup2(stdoutChannel.pipe[1], STDOUT_FILENO, 0); |
| |
| // merge stdout and stderr if asked to |
| if (processChannelMode == QProcess::MergedChannels) { |
| qt_safe_dup2(STDOUT_FILENO, STDERR_FILENO, 0); |
| } else if (processChannelMode != QProcess::ForwardedErrorChannel) { |
| qt_safe_dup2(stderrChannel.pipe[1], STDERR_FILENO, 0); |
| } |
| } |
| |
| // make sure this fd is closed if execv() succeeds |
| qt_safe_close(childStartedPipe[0]); |
| |
| // enter the working directory |
| if (workingDir && QT_CHDIR(workingDir) == -1) { |
| // failed, stop the process |
| strcpy(error.function, "chdir"); |
| goto report_errno; |
| } |
| |
| // this is a virtual call, and it base behavior is to do nothing. |
| q->setupChildProcess(); |
| |
| // execute the process |
| if (!envp) { |
| qt_safe_execv(argv[0], argv); |
| strcpy(error.function, "execvp"); |
| } else { |
| #if defined (QPROCESS_DEBUG) |
| fprintf(stderr, "QProcessPrivate::execChild() starting %s\n", argv[0]); |
| #endif |
| qt_safe_execve(argv[0], argv, envp); |
| strcpy(error.function, "execve"); |
| } |
| |
| // notify failure |
| // don't use strerror or any other routines that may allocate memory, since |
| // some buggy libc versions can deadlock on locked mutexes. |
| report_errno: |
| error.code = errno; |
| qt_safe_write(childStartedPipe[1], &error, sizeof(error)); |
| childStartedPipe[1] = -1; |
| } |
| |
| bool QProcessPrivate::processStarted(QString *errorMessage) |
| { |
| ChildError buf; |
| int ret = qt_safe_read(childStartedPipe[0], &buf, sizeof(buf)); |
| |
| if (startupSocketNotifier) { |
| startupSocketNotifier->setEnabled(false); |
| startupSocketNotifier->deleteLater(); |
| startupSocketNotifier = 0; |
| } |
| qt_safe_close(childStartedPipe[0]); |
| childStartedPipe[0] = -1; |
| |
| #if defined (QPROCESS_DEBUG) |
| qDebug("QProcessPrivate::processStarted() == %s", i <= 0 ? "true" : "false"); |
| #endif |
| |
| // did we read an error message? |
| if (ret > 0 && errorMessage) |
| *errorMessage = QLatin1String(buf.function) + QLatin1String(": ") + qt_error_string(buf.code); |
| |
| return ret <= 0; |
| } |
| |
| qint64 QProcessPrivate::bytesAvailableInChannel(const Channel *channel) const |
| { |
| Q_ASSERT(channel->pipe[0] != INVALID_Q_PIPE); |
| int nbytes = 0; |
| qint64 available = 0; |
| if (::ioctl(channel->pipe[0], FIONREAD, (char *) &nbytes) >= 0) |
| available = (qint64) nbytes; |
| #if defined (QPROCESS_DEBUG) |
| qDebug("QProcessPrivate::bytesAvailableInChannel(%d) == %lld", int(channel - &stdinChannel), available); |
| #endif |
| return available; |
| } |
| |
| qint64 QProcessPrivate::readFromChannel(const Channel *channel, char *data, qint64 maxlen) |
| { |
| Q_ASSERT(channel->pipe[0] != INVALID_Q_PIPE); |
| qint64 bytesRead = qt_safe_read(channel->pipe[0], data, maxlen); |
| #if defined QPROCESS_DEBUG |
| int save_errno = errno; |
| qDebug("QProcessPrivate::readFromChannel(%d, %p \"%s\", %lld) == %lld", |
| int(channel - &stdinChannel), |
| data, qt_prettyDebug(data, bytesRead, 16).constData(), maxlen, bytesRead); |
| errno = save_errno; |
| #endif |
| if (bytesRead == -1 && errno == EWOULDBLOCK) |
| return -2; |
| return bytesRead; |
| } |
| |
| bool QProcessPrivate::writeToStdin() |
| { |
| const char *data = writeBuffer.readPointer(); |
| const qint64 bytesToWrite = writeBuffer.nextDataBlockSize(); |
| |
| qint64 written = qt_safe_write_nosignal(stdinChannel.pipe[1], data, bytesToWrite); |
| #if defined QPROCESS_DEBUG |
| qDebug("QProcessPrivate::writeToStdin(), write(%p \"%s\", %lld) == %lld", |
| data, qt_prettyDebug(data, bytesToWrite, 16).constData(), bytesToWrite, written); |
| if (written == -1) |
| qDebug("QProcessPrivate::writeToStdin(), failed to write (%ls)", qUtf16Printable(qt_error_string(errno))); |
| #endif |
| if (written == -1) { |
| // If the O_NONBLOCK flag is set and If some data can be written without blocking |
| // the process, write() will transfer what it can and return the number of bytes written. |
| // Otherwise, it will return -1 and set errno to EAGAIN |
| if (errno == EAGAIN) |
| return true; |
| |
| closeChannel(&stdinChannel); |
| setErrorAndEmit(QProcess::WriteError); |
| return false; |
| } |
| writeBuffer.free(written); |
| if (!emittedBytesWritten && written != 0) { |
| emittedBytesWritten = true; |
| emit q_func()->bytesWritten(written); |
| emittedBytesWritten = false; |
| } |
| return true; |
| } |
| |
| void QProcessPrivate::terminateProcess() |
| { |
| #if defined (QPROCESS_DEBUG) |
| qDebug("QProcessPrivate::terminateProcess()"); |
| #endif |
| if (pid) |
| ::kill(pid_t(pid), SIGTERM); |
| } |
| |
| void QProcessPrivate::killProcess() |
| { |
| #if defined (QPROCESS_DEBUG) |
| qDebug("QProcessPrivate::killProcess()"); |
| #endif |
| if (pid) |
| ::kill(pid_t(pid), SIGKILL); |
| } |
| |
| bool QProcessPrivate::waitForStarted(int msecs) |
| { |
| #if defined (QPROCESS_DEBUG) |
| qDebug("QProcessPrivate::waitForStarted(%d) waiting for child to start (fd = %d)", msecs, |
| childStartedPipe[0]); |
| #endif |
| |
| pollfd pfd = qt_make_pollfd(childStartedPipe[0], POLLIN); |
| |
| if (qt_poll_msecs(&pfd, 1, msecs) == 0) { |
| setError(QProcess::Timedout); |
| #if defined (QPROCESS_DEBUG) |
| qDebug("QProcessPrivate::waitForStarted(%d) == false (timed out)", msecs); |
| #endif |
| return false; |
| } |
| |
| bool startedEmitted = _q_startupNotification(); |
| #if defined (QPROCESS_DEBUG) |
| qDebug("QProcessPrivate::waitForStarted() == %s", startedEmitted ? "true" : "false"); |
| #endif |
| return startedEmitted; |
| } |
| |
| bool QProcessPrivate::waitForReadyRead(int msecs) |
| { |
| #if defined (QPROCESS_DEBUG) |
| qDebug("QProcessPrivate::waitForReadyRead(%d)", msecs); |
| #endif |
| |
| QElapsedTimer stopWatch; |
| stopWatch.start(); |
| |
| forever { |
| QProcessPoller poller(*this); |
| |
| int timeout = qt_subtract_from_timeout(msecs, stopWatch.elapsed()); |
| int ret = poller.poll(timeout); |
| |
| if (ret < 0) { |
| break; |
| } |
| if (ret == 0) { |
| setError(QProcess::Timedout); |
| return false; |
| } |
| |
| if (qt_pollfd_check(poller.childStartedPipe(), POLLIN)) { |
| if (!_q_startupNotification()) |
| return false; |
| } |
| |
| bool readyReadEmitted = false; |
| if (qt_pollfd_check(poller.stdoutPipe(), POLLIN)) { |
| bool canRead = _q_canReadStandardOutput(); |
| if (currentReadChannel == QProcess::StandardOutput && canRead) |
| readyReadEmitted = true; |
| } |
| if (qt_pollfd_check(poller.stderrPipe(), POLLIN)) { |
| bool canRead = _q_canReadStandardError(); |
| if (currentReadChannel == QProcess::StandardError && canRead) |
| readyReadEmitted = true; |
| } |
| if (readyReadEmitted) |
| return true; |
| |
| if (qt_pollfd_check(poller.stdinPipe(), POLLOUT)) |
| _q_canWrite(); |
| |
| // Signals triggered by I/O may have stopped this process: |
| if (processState == QProcess::NotRunning) |
| return false; |
| |
| if (qt_pollfd_check(poller.forkfd(), POLLIN)) { |
| if (_q_processDied()) |
| return false; |
| } |
| } |
| return false; |
| } |
| |
| bool QProcessPrivate::waitForBytesWritten(int msecs) |
| { |
| #if defined (QPROCESS_DEBUG) |
| qDebug("QProcessPrivate::waitForBytesWritten(%d)", msecs); |
| #endif |
| |
| QElapsedTimer stopWatch; |
| stopWatch.start(); |
| |
| while (!writeBuffer.isEmpty()) { |
| QProcessPoller poller(*this); |
| |
| int timeout = qt_subtract_from_timeout(msecs, stopWatch.elapsed()); |
| int ret = poller.poll(timeout); |
| |
| if (ret < 0) { |
| break; |
| } |
| |
| if (ret == 0) { |
| setError(QProcess::Timedout); |
| return false; |
| } |
| |
| if (qt_pollfd_check(poller.childStartedPipe(), POLLIN)) { |
| if (!_q_startupNotification()) |
| return false; |
| } |
| |
| if (qt_pollfd_check(poller.stdinPipe(), POLLOUT)) |
| return _q_canWrite(); |
| |
| if (qt_pollfd_check(poller.stdoutPipe(), POLLIN)) |
| _q_canReadStandardOutput(); |
| |
| if (qt_pollfd_check(poller.stderrPipe(), POLLIN)) |
| _q_canReadStandardError(); |
| |
| // Signals triggered by I/O may have stopped this process: |
| if (processState == QProcess::NotRunning) |
| return false; |
| |
| if (qt_pollfd_check(poller.forkfd(), POLLIN)) { |
| if (_q_processDied()) |
| return false; |
| } |
| } |
| |
| return false; |
| } |
| |
| bool QProcessPrivate::waitForFinished(int msecs) |
| { |
| #if defined (QPROCESS_DEBUG) |
| qDebug("QProcessPrivate::waitForFinished(%d)", msecs); |
| #endif |
| |
| QElapsedTimer stopWatch; |
| stopWatch.start(); |
| |
| forever { |
| QProcessPoller poller(*this); |
| |
| int timeout = qt_subtract_from_timeout(msecs, stopWatch.elapsed()); |
| int ret = poller.poll(timeout); |
| |
| if (ret < 0) { |
| break; |
| } |
| if (ret == 0) { |
| setError(QProcess::Timedout); |
| return false; |
| } |
| |
| if (qt_pollfd_check(poller.childStartedPipe(), POLLIN)) { |
| if (!_q_startupNotification()) |
| return false; |
| } |
| if (qt_pollfd_check(poller.stdinPipe(), POLLOUT)) |
| _q_canWrite(); |
| |
| if (qt_pollfd_check(poller.stdoutPipe(), POLLIN)) |
| _q_canReadStandardOutput(); |
| |
| if (qt_pollfd_check(poller.stderrPipe(), POLLIN)) |
| _q_canReadStandardError(); |
| |
| // Signals triggered by I/O may have stopped this process: |
| if (processState == QProcess::NotRunning) |
| return true; |
| |
| if (qt_pollfd_check(poller.forkfd(), POLLIN)) { |
| if (_q_processDied()) |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| void QProcessPrivate::findExitCode() |
| { |
| } |
| |
| bool QProcessPrivate::waitForDeadChild() |
| { |
| if (forkfd == -1) |
| return true; // child has already exited |
| |
| // read the process information from our fd |
| forkfd_info info; |
| int ret; |
| EINTR_LOOP(ret, forkfd_wait(forkfd, &info, nullptr)); |
| |
| exitCode = info.status; |
| crashed = info.code != CLD_EXITED; |
| |
| delete deathNotifier; |
| deathNotifier = 0; |
| |
| EINTR_LOOP(ret, forkfd_close(forkfd)); |
| forkfd = -1; // Child is dead, don't try to kill it anymore |
| |
| #if defined QPROCESS_DEBUG |
| qDebug() << "QProcessPrivate::waitForDeadChild() dead with exitCode" |
| << exitCode << ", crashed?" << crashed; |
| #endif |
| return true; |
| } |
| |
| bool QProcessPrivate::startDetached(qint64 *pid) |
| { |
| QByteArray encodedWorkingDirectory = QFile::encodeName(workingDirectory); |
| |
| // To catch the startup of the child |
| int startedPipe[2]; |
| if (qt_safe_pipe(startedPipe) != 0) |
| return false; |
| // To communicate the pid of the child |
| int pidPipe[2]; |
| if (qt_safe_pipe(pidPipe) != 0) { |
| qt_safe_close(startedPipe[0]); |
| qt_safe_close(startedPipe[1]); |
| return false; |
| } |
| |
| if ((stdinChannel.type == Channel::Redirect && !openChannel(stdinChannel)) |
| || (stdoutChannel.type == Channel::Redirect && !openChannel(stdoutChannel)) |
| || (stderrChannel.type == Channel::Redirect && !openChannel(stderrChannel))) { |
| closeChannel(&stdinChannel); |
| closeChannel(&stdoutChannel); |
| closeChannel(&stderrChannel); |
| qt_safe_close(pidPipe[0]); |
| qt_safe_close(pidPipe[1]); |
| qt_safe_close(startedPipe[0]); |
| qt_safe_close(startedPipe[1]); |
| return false; |
| } |
| |
| pid_t childPid = fork(); |
| if (childPid == 0) { |
| struct sigaction noaction; |
| memset(&noaction, 0, sizeof(noaction)); |
| noaction.sa_handler = SIG_IGN; |
| ::sigaction(SIGPIPE, &noaction, 0); |
| |
| ::setsid(); |
| |
| qt_safe_close(startedPipe[0]); |
| qt_safe_close(pidPipe[0]); |
| |
| pid_t doubleForkPid = fork(); |
| if (doubleForkPid == 0) { |
| qt_safe_close(pidPipe[1]); |
| |
| // copy the stdin socket if asked to (without closing on exec) |
| if (stdinChannel.type == Channel::Redirect) |
| qt_safe_dup2(stdinChannel.pipe[0], STDIN_FILENO, 0); |
| |
| // copy the stdout and stderr if asked to |
| if (stdoutChannel.type == Channel::Redirect) |
| qt_safe_dup2(stdoutChannel.pipe[1], STDOUT_FILENO, 0); |
| if (stderrChannel.type == Channel::Redirect) |
| qt_safe_dup2(stderrChannel.pipe[1], STDERR_FILENO, 0); |
| |
| if (!encodedWorkingDirectory.isEmpty()) { |
| if (QT_CHDIR(encodedWorkingDirectory.constData()) == -1) |
| qWarning("QProcessPrivate::startDetached: failed to chdir to %s", encodedWorkingDirectory.constData()); |
| } |
| |
| char **argv = new char *[arguments.size() + 2]; |
| for (int i = 0; i < arguments.size(); ++i) |
| argv[i + 1] = ::strdup(QFile::encodeName(arguments.at(i)).constData()); |
| argv[arguments.size() + 1] = 0; |
| |
| // Duplicate the environment. |
| int envc = 0; |
| char **envp = nullptr; |
| if (environment.d.constData()) { |
| envp = _q_dupEnvironment(environment.d.constData()->vars, &envc); |
| } |
| |
| QByteArray tmp; |
| if (!program.contains(QLatin1Char('/'))) { |
| const QString &exeFilePath = QStandardPaths::findExecutable(program); |
| if (!exeFilePath.isEmpty()) |
| tmp = QFile::encodeName(exeFilePath); |
| } |
| if (tmp.isEmpty()) |
| tmp = QFile::encodeName(program); |
| argv[0] = tmp.data(); |
| |
| if (envp) |
| qt_safe_execve(argv[0], argv, envp); |
| else |
| qt_safe_execv(argv[0], argv); |
| |
| struct sigaction noaction; |
| memset(&noaction, 0, sizeof(noaction)); |
| noaction.sa_handler = SIG_IGN; |
| ::sigaction(SIGPIPE, &noaction, 0); |
| |
| // '\1' means execv failed |
| char c = '\1'; |
| qt_safe_write(startedPipe[1], &c, 1); |
| qt_safe_close(startedPipe[1]); |
| ::_exit(1); |
| } else if (doubleForkPid == -1) { |
| struct sigaction noaction; |
| memset(&noaction, 0, sizeof(noaction)); |
| noaction.sa_handler = SIG_IGN; |
| ::sigaction(SIGPIPE, &noaction, 0); |
| |
| // '\2' means internal error |
| char c = '\2'; |
| qt_safe_write(startedPipe[1], &c, 1); |
| } |
| |
| qt_safe_close(startedPipe[1]); |
| qt_safe_write(pidPipe[1], (const char *)&doubleForkPid, sizeof(pid_t)); |
| if (QT_CHDIR("/") == -1) |
| qWarning("QProcessPrivate::startDetached: failed to chdir to /"); |
| ::_exit(1); |
| } |
| |
| closeChannel(&stdinChannel); |
| closeChannel(&stdoutChannel); |
| closeChannel(&stderrChannel); |
| qt_safe_close(startedPipe[1]); |
| qt_safe_close(pidPipe[1]); |
| |
| if (childPid == -1) { |
| qt_safe_close(startedPipe[0]); |
| qt_safe_close(pidPipe[0]); |
| return false; |
| } |
| |
| char reply = '\0'; |
| int startResult = qt_safe_read(startedPipe[0], &reply, 1); |
| int result; |
| qt_safe_close(startedPipe[0]); |
| qt_safe_waitpid(childPid, &result, 0); |
| bool success = (startResult != -1 && reply == '\0'); |
| if (success && pid) { |
| pid_t actualPid = 0; |
| if (qt_safe_read(pidPipe[0], (char *)&actualPid, sizeof(pid_t)) == sizeof(pid_t)) { |
| *pid = actualPid; |
| } else { |
| *pid = 0; |
| } |
| } |
| qt_safe_close(pidPipe[0]); |
| return success; |
| } |
| |
| #endif // QT_CONFIG(process) |
| |
| QT_END_NAMESPACE |