Page MenuHome GnuPG

windowsprocessdevice.cpp
No OneTemporary

windowsprocessdevice.cpp

/* 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 <Libkleo/GnuPG>
#include <windows.h>
#include <QDir>
#include <QRegularExpression>
/* 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<arguments.size(); ++i) {
QString tmp = arguments.at(i);
// Quotes are escaped and their preceding backslashes are doubled.
tmp.replace(QRegularExpression(QLatin1String(R"--((\\*)")--")), QLatin1String(R"--(\1\1\")--"));
if (tmp.isEmpty() || tmp.contains(QLatin1Char(' ')) || tmp.contains(QLatin1Char('\t'))) {
// The argument must not end with a \ since this would be interpreted
// as escaping the quote -- rather put the \ behind the quote: e.g.
// rather use "foo"\ than "foo\"
int i = tmp.length();
while (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<const wchar_t *>(args.utf16()));
const wchar_t *proc = reinterpret_cast<const wchar_t *>(mPath.utf16());
const QString nativeWorkingDirectory = QDir::toNativeSeparators(mWorkingDirectory);
const wchar_t *wd = reinterpret_cast<const wchar_t *>(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

File Metadata

Mime Type
text/x-c++
Expires
Mon, Dec 8, 11:02 AM (7 h, 44 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
14/5d/374cac7bd76b4459c97a4cce543e

Event Timeline