diff --git a/src/smartcard/deviceinfowatcher.cpp b/src/smartcard/deviceinfowatcher.cpp index e0d93bced..04fa22c46 100644 --- a/src/smartcard/deviceinfowatcher.cpp +++ b/src/smartcard/deviceinfowatcher.cpp @@ -1,143 +1,147 @@ /* smartcard/deviceinfowatcher.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2020 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include "deviceinfowatcher.h" #include "deviceinfowatcher_p.h" #include #include #include #include #include "kleopatra_debug.h" using namespace Kleo; using namespace GpgME; using namespace std::chrono_literals; static const auto initialRetryDelay = 125ms; static const auto maxRetryDelay = 1000ms; static const auto maxConnectionAttempts = 10; DeviceInfoWatcher::Worker::Worker() : mRetryDelay{initialRetryDelay} { } DeviceInfoWatcher::Worker::~Worker() { if (mContext) { mContext->cancelPendingOperationImmediately(); } } void DeviceInfoWatcher::Worker::start() { if (!mContext) { Error err; mContext = Context::createForEngine(AssuanEngine, &err); if (err) { qCWarning(KLEOPATRA_LOG) << "DeviceInfoWatcher::Worker::start: Creating context failed:" << err; return; } } static const char *command = "SCD DEVINFO --watch"; std::unique_ptr t(new StatusConsumerAssuanTransaction(this)); const Error err = mContext->startAssuanTransaction(command, std::move(t)); if (!err) { qCDebug(KLEOPATRA_LOG) << "DeviceInfoWatcher::Worker::start: Assuan transaction for" << command << "started"; mRetryDelay = initialRetryDelay; mFailedConnectionAttempts = 0; QMetaObject::invokeMethod(this, "poll", Qt::QueuedConnection); return; } else if (err.code() == GPG_ERR_ASS_CONNECT_FAILED) { mFailedConnectionAttempts++; if (mFailedConnectionAttempts < maxConnectionAttempts) { qCInfo(KLEOPATRA_LOG) << "DeviceInfoWatcher::Worker::start: Connecting to the agent failed. Retrying in" << mRetryDelay.count() << "ms"; QThread::msleep(mRetryDelay.count()); mRetryDelay = std::min(mRetryDelay * 2, maxRetryDelay); QMetaObject::invokeMethod(this, "start", Qt::QueuedConnection); return; } qCWarning(KLEOPATRA_LOG) << "DeviceInfoWatcher::Worker::start: Connecting to the agent failed too often. Giving up."; + } else if (err.code() == GPG_ERR_EPIPE) { + qCDebug(KLEOPATRA_LOG) << "DeviceInfoWatcher::Worker::start: Assuan transaction failed with broken pipe. The agent seems to have died. Resetting context."; + mContext.reset(); + QMetaObject::invokeMethod(this, "start", Qt::QueuedConnection); } else { qCWarning(KLEOPATRA_LOG) << "DeviceInfoWatcher::Worker::start: Starting Assuan transaction for" << command << "failed:" << err; } } void DeviceInfoWatcher::Worker::poll() { const bool finished = mContext->poll(); if (finished) { qCDebug(KLEOPATRA_LOG) << "DeviceInfoWatcher::Worker::poll: context finished with" << mContext->lastError(); QThread::msleep(1000); QMetaObject::invokeMethod(this, "start", Qt::QueuedConnection); } else { QMetaObject::invokeMethod(this, "poll", Qt::QueuedConnection); } } void DeviceInfoWatcher::Worker::status(const char* status, const char* details) { qCDebug(KLEOPATRA_LOG) << "DeviceInfoWatcher::Worker::status:" << status << details; if (status && std::strcmp(status, "DEVINFO_STATUS") == 0) { Q_EMIT statusChanged(QByteArray(details)); } } DeviceInfoWatcher::Private::Private(DeviceInfoWatcher *qq) : q(qq) { } DeviceInfoWatcher::Private::~Private() { workerThread.quit(); workerThread.wait(); } void DeviceInfoWatcher::Private::start() { auto worker = new DeviceInfoWatcher::Worker; worker->moveToThread(&workerThread); connect(&workerThread, &QThread::started, worker, &DeviceInfoWatcher::Worker::start); connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater); connect(worker, &DeviceInfoWatcher::Worker::statusChanged, q, &DeviceInfoWatcher::statusChanged); workerThread.start(); } DeviceInfoWatcher::DeviceInfoWatcher(QObject *parent) : QObject(parent) , d(new Private(this)) { } DeviceInfoWatcher::~DeviceInfoWatcher() { delete d; } // static bool DeviceInfoWatcher::isSupported() { #ifdef Q_OS_WIN // "SCD DEVINFO --watch" does not seem to work on Windows; see https://dev.gnupg.org/T5359 return false; #else return engineInfo(GpgEngine).engineVersion() >= "2.3.0"; #endif } void DeviceInfoWatcher::start() { d->start(); }