Commit e084c2a2 authored by Ingo Klöcker's avatar Ingo Klöcker
Browse files

Use a more modern approach to watch for smartcard removal

Use the new DEVINFO --watch command to watch for status changes of
smartcard devices instead of using a file system watcher to watch
certain files. As with the old approach we are only notified about
the removal of smartcards unless we/the user explicitly trigger/s a
rescan (with the SERIALNO --all command).

GnuPG-bug-id: 5066
parent 2b145436
......@@ -63,6 +63,14 @@ endif()
ki18n_wrap_ui(_kleopatra_uiserver_SRCS crypto/gui/signingcertificateselectionwidget.ui)
if("${Gpgmepp_VERSION}" VERSION_GREATER_EQUAL "1.14.1")
set(_kleopatra_deviceinfowatcher_files
smartcard/deviceinfowatcher.cpp
)
else()
set(_kleopatra_deviceinfowatcher_files)
endif()
set(_kleopatra_SRCS
utils/gui-helper.cpp
utils/filedialog.cpp
......@@ -250,6 +258,8 @@ set(_kleopatra_SRCS
smartcard/keypairinfo.cpp
smartcard/utils.cpp
${_kleopatra_deviceinfowatcher_files}
aboutdata.cpp
systrayicon.cpp
kleopatraapplication.cpp
......
/* smartcard/deviceinfowatcher.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2020 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "deviceinfowatcher.h"
#include "deviceinfowatcher_p.h"
#include <QGpgME/Debug>
#include <gpgme++/context.h>
#include <gpgme++/engineinfo.h>
#include <gpgme++/statusconsumerassuantransaction.h>
#include "kleopatra_debug.h"
using namespace Kleo;
using namespace GpgME;
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;
}
}
// try to connect to the agent for at most ~12.8 seconds with increasing delay between retries
static const int MaxRetryDelay = 100 * 64;
static const char *command = "SCD DEVINFO --watch";
std::unique_ptr<AssuanTransaction> 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";
QMetaObject::invokeMethod(this, "poll", Qt::QueuedConnection);
return;
} else if (err.code() == GPG_ERR_ASS_CONNECT_FAILED) {
if (mRetryDelay <= MaxRetryDelay) {
qCInfo(KLEOPATRA_LOG) << "DeviceInfoWatcher::Worker::start: Connecting to the agent failed. Retrying in" << mRetryDelay << "ms";
QThread::msleep(mRetryDelay);
mRetryDelay *= 2;
QMetaObject::invokeMethod(this, "start", Qt::QueuedConnection);
return;
}
qCWarning(KLEOPATRA_LOG) << "DeviceInfoWatcher::Worker::start: Connecting to the agent failed too often. Giving up.";
} 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();
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()
{
DeviceInfoWatcher::Worker *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()
{
return engineInfo(GpgEngine).engineVersion() >= "2.3.0";
}
void DeviceInfoWatcher::start()
{
d->start();
}
/* smartcard/deviceinfowatcher.h
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2020 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef __KLEOPATRA_SMARTCARD_DEVICEINFOWATCHER_H__
#define __KLEOPATRA_SMARTCARD_DEVICEINFOWATCHER_H__
#include <QObject>
namespace Kleo
{
class DeviceInfoWatcher : public QObject
{
Q_OBJECT
public:
explicit DeviceInfoWatcher(QObject *parent = nullptr);
~DeviceInfoWatcher();
static bool isSupported();
void start();
Q_SIGNALS:
void statusChanged(const QByteArray &details);
private:
class Worker;
private:
class Private;
Private * const d;
};
}
#endif // __KLEOPATRA_SMARTCARD_DEVICEINFOWATCHER_H__
/* smartcard/deviceinfowatcher_p.h
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2020 g10 Code GmbH
SPDX-FileContributor: Ingo Klöcker <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#ifndef __KLEOPATRA_SMARTCARD_DEVICEINFOWATCHER_P_H__
#define __KLEOPATRA_SMARTCARD_DEVICEINFOWATCHER_P_H__
#include <gpgme++/interfaces/statusconsumer.h>
#include <gpgme++/context.h>
#include <gpgme.h>
#include <QThread>
namespace Kleo
{
class DeviceInfoWatcher::Worker : public QObject, public GpgME::StatusConsumer
{
Q_OBJECT
public:
~Worker();
public Q_SLOTS:
void start();
Q_SIGNALS:
void statusChanged(const QByteArray &details);
private:
Q_INVOKABLE void poll();
void status(const char *status, const char *details) override;
private:
int mRetryDelay = 100;
std::unique_ptr<GpgME::Context> mContext;
};
class DeviceInfoWatcher::Private
{
friend class ::Kleo::DeviceInfoWatcher;
DeviceInfoWatcher *const q;
public:
explicit Private(DeviceInfoWatcher *qq);
~Private();
private:
void start();
private:
QThread workerThread;
};
}
#endif // __KLEOPATRA_SMARTCARD_DEVICEINFOWATCHER_P_H__
......@@ -11,8 +11,17 @@
#include <config-kleopatra.h>
#include <gpgme++/gpgmepp_version.h>
#if GPGMEPP_VERSION >= 0x10E01 // 1.14.1
# define QGPGME_HAS_DEBUG
# define GPGME_SUPPORTS_API_FOR_DEVICEINFOWATCHER
#endif
#include "readerstatus.h"
#ifdef GPGME_SUPPORTS_API_FOR_DEVICEINFOWATCHER
# include "deviceinfowatcher.h"
#endif
#include "keypairinfo.h"
#include <Libkleo/GnuPG>
......@@ -20,6 +29,10 @@
#include <Libkleo/FileSystemWatcher>
#include <Libkleo/Stl_Util>
#ifdef QGPGME_HAS_DEBUG
# include <QGpgME/Debug>
#endif
#include <gpgme++/context.h>
#include <gpgme++/defaultassuantransaction.h>
#include <gpgme++/key.h>
......@@ -83,6 +96,7 @@ static QDebug operator<<(QDebug s, const std::string &string)
return s << QString::fromStdString(string);
}
#ifndef QGPGME_HAS_DEBUG
static QDebug operator<<(QDebug s, const GpgME::Error &err)
{
const bool oldSetting = s.autoInsertSpaces();
......@@ -90,6 +104,7 @@ static QDebug operator<<(QDebug s, const GpgME::Error &err)
s.setAutoInsertSpaces(oldSetting);
return s.maybeSpace();
}
#endif
static QDebug operator<<(QDebug s, const std::vector< std::pair<std::string, std::string> > &v)
{
......@@ -708,6 +723,12 @@ Q_SIGNALS:
void oneTransactionFinished(const GpgME::Error &err);
public Q_SLOTS:
void deviceStatusChanged(const QByteArray &details)
{
qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread[GUI]::deviceStatusChanged(" << details << ")";
addTransaction(updateTransaction);
}
void ping()
{
qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread[GUI]::ping()";
......@@ -880,10 +901,6 @@ public:
qRegisterMetaType<Card::Status>("Kleo::SmartCard::Card::Status");
qRegisterMetaType<GpgME::Error>("GpgME::Error");
watcher.whitelistFiles(QStringList(QStringLiteral("reader_*.status")));
watcher.addPath(Kleo::gnupgHomeDirectory());
watcher.setDelay(100);
connect(this, &::ReaderStatusThread::cardAdded,
q, &ReaderStatus::cardAdded);
connect(this, &::ReaderStatusThread::cardChanged,
......@@ -895,8 +912,21 @@ public:
connect(this, &::ReaderStatusThread::anyCardCanLearnKeysChanged,
q, &ReaderStatus::anyCardCanLearnKeysChanged);
connect(&watcher, &FileSystemWatcher::triggered, this, &::ReaderStatusThread::ping);
#ifdef GPGME_SUPPORTS_API_FOR_DEVICEINFOWATCHER
if (DeviceInfoWatcher::isSupported()) {
qCDebug(KLEOPATRA_LOG) << "ReaderStatus::Private: Using new DeviceInfoWatcher";
connect(&devInfoWatcher, &DeviceInfoWatcher::statusChanged, this, &::ReaderStatusThread::deviceStatusChanged);
} else
#endif
{
qCDebug(KLEOPATRA_LOG) << "ReaderStatus::Private: Using deprecated FileSystemWatcher";
watcher.whitelistFiles(QStringList(QStringLiteral("reader_*.status")));
watcher.addPath(Kleo::gnupgHomeDirectory());
watcher.setDelay(100);
connect(&watcher, &FileSystemWatcher::triggered, this, &::ReaderStatusThread::ping);
}
}
~Private()
{
......@@ -925,6 +955,9 @@ private:
private:
FileSystemWatcher watcher;
#ifdef GPGME_SUPPORTS_API_FOR_DEVICEINFOWATCHER
DeviceInfoWatcher devInfoWatcher;
#endif
};
ReaderStatus::ReaderStatus(QObject *parent)
......@@ -944,6 +977,11 @@ ReaderStatus::~ReaderStatus()
void ReaderStatus::startMonitoring()
{
d->start();
#ifdef GPGME_SUPPORTS_API_FOR_DEVICEINFOWATCHER
if (DeviceInfoWatcher::isSupported()) {
d->devInfoWatcher.start();
}
#endif
}
// static
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment