diff --git a/src/utils/windowsprocessdevice.cpp b/src/utils/windowsprocessdevice.cpp index 39f577b73..98a05e01d 100644 --- a/src/utils/windowsprocessdevice.cpp +++ b/src/utils/windowsprocessdevice.cpp @@ -1,408 +1,409 @@ /* utils/windowsprocessdevice.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2019 g 10code GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #ifdef WIN32 #include "windowsprocessdevice.h" #include "kleopatra_debug.h" #include #include #include +#include /* This is the amount of data GPGME reads at once */ #define PIPEBUF_SIZE 16384 using namespace Kleo; static void CloseHandleX(HANDLE &h) { if (h && h != INVALID_HANDLE_VALUE) { if (!CloseHandle(h)) { qCWarning(KLEOPATRA_LOG) << "CloseHandle failed!"; } h = nullptr; } } class WindowsProcessDevice::Private { public: ~Private(); Private(const QString &path, const QStringList &args, const QString &wd): mPath(path), mArgs(args), mWorkingDirectory(wd), mStdInRd(nullptr), mStdInWr(nullptr), mStdOutRd(nullptr), mStdOutWr(nullptr), mStdErrRd(nullptr), mStdErrWr(nullptr), mProc(nullptr), mThread(nullptr), mEnded(false) { } bool start (QIODevice::OpenMode mode); qint64 write(const char *data, qint64 size) { if (size < 0 || (size >> 32)) { qCDebug (KLEOPATRA_LOG) << "Invalid write"; return -1; } if (!mStdInWr) { qCDebug (KLEOPATRA_LOG) << "Write to closed or read only device"; return -1; } DWORD dwWritten; if (!WriteFile(mStdInWr, data, (DWORD) size, &dwWritten, nullptr)) { qCDebug(KLEOPATRA_LOG) << "Failed to write"; return -1; } if (dwWritten != size) { qCDebug(KLEOPATRA_LOG) << "Failed to write everything"; return -1; } return size; } qint64 read(char *data, qint64 maxSize) { if (!mStdOutRd) { qCDebug (KLEOPATRA_LOG) << "Read of closed or write only device"; return -1; } if (!maxSize) { return 0; } DWORD exitCode = 0; if (GetExitCodeProcess (mProc, &exitCode)) { if (exitCode != STILL_ACTIVE) { if (exitCode) { qCDebug(KLEOPATRA_LOG) << "Non zero exit code"; mError = readAllStdErr(); return -1; } mEnded = true; qCDebug(KLEOPATRA_LOG) << "Process finished with code " << exitCode; } } else { qCDebug(KLEOPATRA_LOG) << "GetExitCodeProcess Failed"; } if (mEnded) { DWORD avail = 0; if (!PeekNamedPipe(mStdOutRd, nullptr, 0, nullptr, &avail, nullptr)) { qCDebug(KLEOPATRA_LOG) << "Failed to peek pipe"; return -1; } if (!avail) { qCDebug(KLEOPATRA_LOG) << "Process ended and nothing more in pipe"; return 0; } } DWORD dwRead; if (!ReadFile(mStdOutRd, data, (DWORD) maxSize, &dwRead, nullptr)) { qCDebug(KLEOPATRA_LOG) << "Failed to read"; return -1; } return dwRead; } QString readAllStdErr() { QString ret; if (!mStdErrRd) { qCDebug (KLEOPATRA_LOG) << "Read of closed stderr"; } DWORD dwRead = 0; do { char buf[4096]; DWORD avail; if (!PeekNamedPipe(mStdErrRd, nullptr, 0, nullptr, &avail, nullptr)) { qCDebug(KLEOPATRA_LOG) << "Failed to peek pipe"; return ret; } if (!avail) { return ret; } ReadFile(mStdErrRd, buf, 4096, &dwRead, nullptr); if (dwRead) { QByteArray ba (buf, dwRead); ret += QString::fromLocal8Bit (ba); } } while (dwRead); return ret; } void close() { if (mProc && mProc != INVALID_HANDLE_VALUE) { TerminateProcess(mProc, 0xf291); CloseHandleX(mProc); } } QString errorString() { return mError; } void closeWriteChannel() { CloseHandleX(mStdInWr); } private: QString mPath; QStringList mArgs; QString mWorkingDirectory; QString mError; HANDLE mStdInRd; HANDLE mStdInWr; HANDLE mStdOutRd; HANDLE mStdOutWr; HANDLE mStdErrRd; HANDLE mStdErrWr; HANDLE mProc; HANDLE mThread; bool mEnded; }; WindowsProcessDevice::WindowsProcessDevice(const QString &path, const QStringList &args, const QString &wd): d(new Private(path, args, wd)) { } bool WindowsProcessDevice::open(QIODevice::OpenMode mode) { bool ret = d->start(mode); if (ret) { setOpenMode(mode); } return ret; } qint64 WindowsProcessDevice::readData(char *data, qint64 maxSize) { return d->read(data, maxSize); } qint64 WindowsProcessDevice::writeData(const char *data, qint64 maxSize) { return d->write(data, maxSize); } bool WindowsProcessDevice::isSequential() const { return true; } void WindowsProcessDevice::closeWriteChannel() { d->closeWriteChannel(); } void WindowsProcessDevice::close() { d->close(); QIODevice::close(); } QString getLastErrorString() { wchar_t *lpMsgBuf = nullptr; DWORD dw = GetLastError(); FormatMessageW( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (wchar_t *) &lpMsgBuf, 0, NULL); QString ret = QString::fromWCharArray(lpMsgBuf); LocalFree(lpMsgBuf); return ret; } WindowsProcessDevice::Private::~Private() { if (mProc && mProc != INVALID_HANDLE_VALUE) { close(); } CloseHandleX (mThread); CloseHandleX (mStdInRd); CloseHandleX (mStdInWr); CloseHandleX (mStdOutRd); CloseHandleX (mStdOutWr); CloseHandleX (mStdErrRd); CloseHandleX (mStdErrWr); } static QString qt_create_commandline(const QString &program, const QStringList &arguments, const QString &nativeArguments) { QString args; if (!program.isEmpty()) { QString programName = program; if (!programName.startsWith(QLatin1Char('\"')) && !programName.endsWith(QLatin1Char('\"')) && programName.contains(QLatin1Char(' '))) programName = QLatin1Char('\"') + programName + QLatin1Char('\"'); programName.replace(QLatin1Char('/'), QLatin1Char('\\')); // add the prgram as the first arg ... it works better args = programName + QLatin1Char(' '); } for (int i=0; i 0 && tmp.at(i - 1) == QLatin1Char('\\')) --i; tmp.insert(i, QLatin1Char('"')); tmp.prepend(QLatin1Char('"')); } args += QLatin1Char(' ') + tmp; } if (!nativeArguments.isEmpty()) { if (!args.isEmpty()) args += QLatin1Char(' '); args += nativeArguments; } return args; } bool WindowsProcessDevice::Private::start(QIODevice::OpenMode mode) { if (mode != QIODevice::ReadOnly && mode != QIODevice::WriteOnly && mode != QIODevice::ReadWrite) { qCDebug(KLEOPATRA_LOG) << "Unsupported open mode " << mode; return false; } SECURITY_ATTRIBUTES saAttr; ZeroMemory (&saAttr, sizeof (SECURITY_ATTRIBUTES)); saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); saAttr.bInheritHandle = TRUE; saAttr.lpSecurityDescriptor = NULL; // Create the pipes if (!CreatePipe(&mStdOutRd, &mStdOutWr, &saAttr, PIPEBUF_SIZE) || !CreatePipe(&mStdErrRd, &mStdErrWr, &saAttr, 0) || !CreatePipe(&mStdInRd, &mStdInWr, &saAttr, PIPEBUF_SIZE)) { qCDebug(KLEOPATRA_LOG) << "Failed to create pipes"; mError = getLastErrorString(); return false; } // Ensure only the proper handles are inherited if (!SetHandleInformation(mStdOutRd, HANDLE_FLAG_INHERIT, 0) || !SetHandleInformation(mStdErrRd, HANDLE_FLAG_INHERIT, 0) || !SetHandleInformation(mStdInWr, HANDLE_FLAG_INHERIT, 0)) { qCDebug(KLEOPATRA_LOG) << "Failed to set inherit flag"; mError = getLastErrorString(); return false; } PROCESS_INFORMATION piProcInfo; ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION)); STARTUPINFO siStartInfo; ZeroMemory(&siStartInfo, sizeof(STARTUPINFO)); siStartInfo.cb = sizeof(STARTUPINFO); siStartInfo.hStdError = mStdErrWr; siStartInfo.hStdOutput = mStdOutWr; siStartInfo.hStdInput = mStdInRd; siStartInfo.dwFlags |= STARTF_USESTDHANDLES; const auto args = qt_create_commandline(mPath, mArgs, QString()); wchar_t *cmdLine = wcsdup (reinterpret_cast(args.utf16())); const wchar_t *proc = reinterpret_cast(mPath.utf16()); const QString nativeWorkingDirectory = QDir::toNativeSeparators(mWorkingDirectory); const wchar_t *wd = reinterpret_cast(nativeWorkingDirectory.utf16()); qCDebug(KLEOPATRA_LOG) << "Spawning:" << args; // Now lets start bool suc = CreateProcessW(proc, cmdLine, // command line NULL, // process security attributes NULL, // primary thread security attributes TRUE, // handles are inherited CREATE_NO_WINDOW,// creation flags NULL, // use parent's environment wd, // use parent's current directory &siStartInfo, // STARTUPINFO pointer &piProcInfo); // receives PROCESS_INFORMATION free(cmdLine); if (!suc) { qCDebug(KLEOPATRA_LOG) << "Failed to create process"; mError = getLastErrorString(); return false; } mProc = piProcInfo.hProcess; mThread = piProcInfo.hThread; if (mode == QIODevice::WriteOnly) { CloseHandleX (mStdInRd); } if (mode == QIODevice::ReadOnly) { CloseHandleX (mStdInWr); } return true; } QString WindowsProcessDevice::errorString() { return d->errorString(); } #include "windowsprocessdevice.moc" #endif