From 7a25132c125f6e5e413ad26ea950ae22bee982f5 Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Mon, 21 Mar 2016 13:40:11 -0500 Subject: if our USB device is disconnected while prompting the user for ADBD, cancel the prompt. --- CMakeLists.txt | 1 + debian/control | 1 + src/CMakeLists.txt | 1 + src/adbd-client.cpp | 1 + src/main.cpp | 4 +- src/usb-manager.cpp | 63 +++++++++++++++++--------- src/usb-manager.h | 11 ++++- src/usb-monitor.cpp | 81 ++++++++++++++++++++++++++++++++++ src/usb-monitor.h | 52 ++++++++++++++++++++++ src/usb-snap.cpp | 1 + tests/integration/usb-manager-test.cpp | 44 ++++++++++++++++-- tests/unit/usb-snap-test.cpp | 1 - tests/utils/mock-usb-monitor.h | 32 ++++++++++++++ tests/utils/qdbus-helpers.h | 21 --------- tests/utils/qt-fixture.h | 18 +++++++- 15 files changed, 284 insertions(+), 48 deletions(-) create mode 100644 src/usb-monitor.cpp create mode 100644 src/usb-monitor.h create mode 100644 tests/utils/mock-usb-monitor.h delete mode 100644 tests/utils/qdbus-helpers.h diff --git a/CMakeLists.txt b/CMakeLists.txt index bb7568e..8a1a6aa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,6 +48,7 @@ set(GLIB_MINIMUM 2.36) pkg_check_modules(SERVICE_DEPS REQUIRED gio-unix-2.0>=${GLIB_MINIMUM} glib-2.0>=${GLIB_MINIMUM} + gudev-1.0 ) include_directories (SYSTEM ${SERVICE_DEPS_INCLUDE_DIRS} diff --git a/debian/control b/debian/control index 529fa37..90e2590 100644 --- a/debian/control +++ b/debian/control @@ -7,6 +7,7 @@ Build-Depends: cmake, cmake-extras (>= 0.4), dbus, libglib2.0-dev (>= 2.36), + libgudev-1.0-dev, libproperties-cpp-dev, # for coverage reports lcov, diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d3a021b..cdd2384 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -13,6 +13,7 @@ add_library( indicator.cpp rotation-lock.cpp usb-manager.cpp + usb-monitor.cpp usb-snap.cpp ) diff --git a/src/adbd-client.cpp b/src/adbd-client.cpp index 4f7d28f..937215e 100644 --- a/src/adbd-client.cpp +++ b/src/adbd-client.cpp @@ -45,6 +45,7 @@ public: { // tell the worker thread to stop whatever it's doing and exit. g_cancellable_cancel(m_cancellable); + m_pkresponse_cv.notify_one(); m_sleep_cv.notify_one(); m_worker_thread.join(); g_clear_object(&m_cancellable); diff --git a/src/main.cpp b/src/main.cpp index 7d6eb5f..27e6bcc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include // bindtextdomain() #include @@ -59,7 +60,8 @@ main(int /*argc*/, char** /*argv*/) // even though it doesn't have an indicator component yet static constexpr char const * ADB_SOCKET_PATH {"/dev/socket/adbd"}; static constexpr char const * PUBLIC_KEYS_FILENAME {"/data/misc/adb/adb_keys"}; - UsbManager usb_manager {ADB_SOCKET_PATH, PUBLIC_KEYS_FILENAME}; + auto usb_monitor = std::make_shared(); + UsbManager usb_manager {ADB_SOCKET_PATH, PUBLIC_KEYS_FILENAME, usb_monitor}; // let's go! g_main_loop_run(loop); diff --git a/src/usb-manager.cpp b/src/usb-manager.cpp index 7f43520..840a04b 100644 --- a/src/usb-manager.cpp +++ b/src/usb-manager.cpp @@ -28,39 +28,57 @@ #include #include +#include + class UsbManager::Impl { public: explicit Impl( const std::string& socket_path, - const std::string& public_keys_filename + const std::string& public_keys_filename, + const std::shared_ptr& usb_monitor ): m_adbd_client{std::make_shared(socket_path)}, - m_public_keys_filename{public_keys_filename} + m_public_keys_filename{public_keys_filename}, + m_usb_monitor{usb_monitor} { - m_adbd_client->on_pk_request().connect([this](const AdbdClient::PKRequest& req){ - auto snap = new UsbSnap(req.fingerprint); - snap->on_user_response().connect([this,req,snap](AdbdClient::PKResponse response, bool remember_choice){ - g_debug("%s user responded! response %d, remember %d", G_STRLOC, int(response), int(remember_choice)); - req.respond(response); - if (remember_choice && (response == AdbdClient::PKResponse::ALLOW)) - write_public_key(req.public_key); - // delete_later - g_idle_add([](gpointer gsnap){delete static_cast(gsnap); return G_SOURCE_REMOVE;}, snap); - }); + m_usb_monitor->on_usb_disconnected().connect([this](const std::string& /*usb_name*/) { + m_snap.reset(); }); - } - ~Impl() - { + m_adbd_client->on_pk_request().connect( + [this](const AdbdClient::PKRequest& req){ + + m_snap.reset(new UsbSnap(req.fingerprint), + [this](UsbSnap* snap){ + m_snap_connections.clear(); + delete snap; + } + ); + + m_snap_connections.insert((*m_snap).on_user_response().connect( + [this,req](AdbdClient::PKResponse response, bool remember_choice){ + g_message("%s user responded! response %d, remember %d", G_STRLOC, int(response), int(remember_choice)); + req.respond(response); + g_message("%s", G_STRLOC); + if (remember_choice && (response == AdbdClient::PKResponse::ALLOW)) + write_public_key(req.public_key); + g_idle_add([](gpointer gself){static_cast(gself)->m_snap.reset(); return G_SOURCE_REMOVE;}, this); + } + )); + } + ); + } + ~Impl() =default; + private: void write_public_key(const std::string& public_key) { - g_debug("writing public key '%s' to '%s'", public_key.c_str(), m_public_keys_filename.c_str()); + g_message("%s writing public key '%s' to '%s'", G_STRLOC, public_key.c_str(), m_public_keys_filename.c_str()); // confirm the directory exists auto dirname = g_path_get_dirname(m_public_keys_filename.c_str()); @@ -78,12 +96,12 @@ private: S_IRUSR|S_IWUSR|S_IRGRP ); if (fd == -1) { - g_warning("Error opening ADB datafile '%s': %s", m_public_keys_filename.c_str(), g_strerror(errno)); + g_warning("Error opening ADB datafile: %s", g_strerror(errno)); return; } // write the new public key on its own line - const std::string buf {public_key + '\n'}; + std::string buf {public_key + '\n'}; if (write(fd, buf.c_str(), buf.size()) == -1) g_warning("Error writing ADB datafile: %d %s", errno, g_strerror(errno)); close(fd); @@ -91,6 +109,10 @@ private: std::shared_ptr m_adbd_client; const std::string m_public_keys_filename; + std::shared_ptr m_usb_monitor; + + std::shared_ptr m_snap; + std::set m_snap_connections; }; /*** @@ -99,9 +121,10 @@ private: UsbManager::UsbManager( const std::string& socket_path, - const std::string& public_keys_filename + const std::string& public_keys_filename, + const std::shared_ptr& usb_monitor ): - impl{new Impl{socket_path, public_keys_filename}} + impl{new Impl{socket_path, public_keys_filename, usb_monitor}} { } diff --git a/src/usb-manager.h b/src/usb-manager.h index ec405c0..960d634 100644 --- a/src/usb-manager.h +++ b/src/usb-manager.h @@ -19,6 +19,8 @@ #pragma once +#include + #include #include @@ -28,10 +30,17 @@ class UsbManager { public: - UsbManager(const std::string& socket_path, const std::string& public_key_filename); + + UsbManager( + const std::string& socket_path, + const std::string& public_key_filename, + const std::shared_ptr& + ); + ~UsbManager(); protected: + class Impl; std::unique_ptr impl; }; diff --git a/src/usb-monitor.cpp b/src/usb-monitor.cpp new file mode 100644 index 0000000..5fc5a6d --- /dev/null +++ b/src/usb-monitor.cpp @@ -0,0 +1,81 @@ +/* + * Copyright 2016 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * + * Authors: + * Charles Kerr + */ + +#include + +#include +#include + +class GUDevUsbMonitor::Impl +{ +public: + + Impl() + { + const char* subsystems[] = {"android_usb", nullptr}; + m_udev_client = g_udev_client_new(subsystems); + g_signal_connect(m_udev_client, "uevent", G_CALLBACK(on_android_usb_event), this); + } + + ~Impl() + { + g_signal_handlers_disconnect_by_data(m_udev_client, this); + g_clear_object(&m_udev_client); + } + + core::Signal& on_usb_disconnected() + { + return m_on_usb_disconnected; + } + +private: + + static void on_android_usb_event(GUdevClient*, gchar* action, GUdevDevice* device, gpointer gself) + { + if (!g_strcmp0(action, "change")) + if (!g_strcmp0(g_udev_device_get_property(device, "USB_STATE"), "DISCONNECTED")) + static_cast(gself)->m_on_usb_disconnected(g_udev_device_get_name(device)); + } + + core::Signal m_on_usb_disconnected; + + GUdevClient* m_udev_client = nullptr; +}; + +/*** +**** +***/ + +UsbMonitor::UsbMonitor() =default; + +UsbMonitor::~UsbMonitor() =default; + +GUDevUsbMonitor::GUDevUsbMonitor(): + impl{new Impl{}} +{ +} + +GUDevUsbMonitor::~GUDevUsbMonitor() =default; + +core::Signal& +GUDevUsbMonitor::on_usb_disconnected() +{ + return impl->on_usb_disconnected(); +} + diff --git a/src/usb-monitor.h b/src/usb-monitor.h new file mode 100644 index 0000000..d9be539 --- /dev/null +++ b/src/usb-monitor.h @@ -0,0 +1,52 @@ +/* + * Copyright 2016 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * + * Authors: + * Charles Kerr + */ + +#pragma once + +#include + +#include +#include + +/** + * Simple interface that emits signals on USB device state changes + */ +class UsbMonitor +{ +public: + UsbMonitor(); + virtual ~UsbMonitor(); + virtual core::Signal& on_usb_disconnected() =0; +}; + +/** + * Simple GUDev wrapper that notifies on android_usb device state changes + */ +class GUDevUsbMonitor: public UsbMonitor +{ +public: + GUDevUsbMonitor(); + virtual ~GUDevUsbMonitor(); + core::Signal& on_usb_disconnected() override; + +protected: + class Impl; + std::unique_ptr impl; +}; + diff --git a/src/usb-snap.cpp b/src/usb-snap.cpp index 41c78c6..349d80e 100644 --- a/src/usb-snap.cpp +++ b/src/usb-snap.cpp @@ -148,6 +148,7 @@ private: { GError* error {}; auto reply = g_dbus_connection_call_finish (G_DBUS_CONNECTION(obus), res, &error); +g_message("%s got notify response %s", G_STRLOC, g_variant_print(reply, true)); if (error != nullptr) { if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) g_warning("UsbSnap: Error calling Notify: %s", error->message); diff --git a/tests/integration/usb-manager-test.cpp b/tests/integration/usb-manager-test.cpp index 21fdc97..19c0401 100644 --- a/tests/integration/usb-manager-test.cpp +++ b/tests/integration/usb-manager-test.cpp @@ -17,10 +17,9 @@ * Charles Kerr */ -#define QT_NO_KEYWORDS - #include #include +#include #include #include @@ -64,6 +63,8 @@ protected: { super::SetUp(); + m_usb_monitor.reset(new MockUsbMonitor{}); + char tmpl[] = {"usb-manager-test-XXXXXX"}; m_tmpdir.reset(new std::string{g_mkdtemp(tmpl)}, file_deleter); g_message("using tmpdir '%s'", m_tmpdir->c_str()); @@ -83,6 +84,7 @@ protected: QtDBusTest::DBusTestRunner dbusTestRunner; QtDBusMock::DBusMock dbusMock; std::shared_ptr m_tmpdir; + std::shared_ptr m_usb_monitor; }; TEST_F(UsbManagerFixture, Allow) @@ -102,7 +104,7 @@ TEST_F(UsbManagerFixture, Allow) auto adbd_server = std::make_shared(*socket_path, std::vector{"PK"+public_key}); // set up a UsbManager to process the request - auto usb_manager = std::make_shared(*socket_path, *public_keys_path); + auto usb_manager = std::make_shared(*socket_path, *public_keys_path, m_usb_monitor); // wait for the notification to show up, confirm it looks right wait_for_signals(notificationsSpy, 1); @@ -151,3 +153,39 @@ TEST_F(UsbManagerFixture, Allow) ASSERT_EQ(1, lines.size()); EXPECT_EQ(public_key, lines[0]); } + +TEST_F(UsbManagerFixture, Cancel) +{ + const std::shared_ptr socket_path {new std::string{*m_tmpdir+"/socket"}, file_deleter}; + const std::shared_ptr public_keys_path {new std::string{*m_tmpdir+"/adb_keys"}, file_deleter}; + + // add a signal spy to listen to the notification daemon + QSignalSpy notificationsSpy( + ¬ificationsMockInterface(), + SIGNAL(MethodCalled(const QString &, const QVariantList &)) + ); + + // start a mock AdbdServer ready to submit a request + const std::string public_key {"public_key"}; + auto adbd_server = std::make_shared(*socket_path, std::vector{"PK"+public_key}); + + // set up a UsbManager to process the request + auto usb_manager = std::make_shared(*socket_path, *public_keys_path, m_usb_monitor); + + // wait for a notification to show up + wait_for_signals(notificationsSpy, 1); + EXPECT_EQ("Notify", notificationsSpy.at(0).at(0)); + notificationsSpy.clear(); + + // wait for UsbSnap to receive dbusmock's response to the Notify request. + // there's no event to key off of for this, so just wait for a moment + wait_msec(); + + // disconnect the USB before the user has a chance to allow/deny + m_usb_monitor->m_on_usb_disconnected("android0"); + + // confirm that we requested the notification to be pulled down + wait_for_signals(notificationsSpy, 1); + EXPECT_EQ("CloseNotification", notificationsSpy.at(0).at(0)); + notificationsSpy.clear(); +} diff --git a/tests/unit/usb-snap-test.cpp b/tests/unit/usb-snap-test.cpp index 663f9e6..40de94a 100644 --- a/tests/unit/usb-snap-test.cpp +++ b/tests/unit/usb-snap-test.cpp @@ -17,7 +17,6 @@ * Charles Kerr */ -#define QT_NO_KEYWORDS #include #include diff --git a/tests/utils/mock-usb-monitor.h b/tests/utils/mock-usb-monitor.h new file mode 100644 index 0000000..92b89db --- /dev/null +++ b/tests/utils/mock-usb-monitor.h @@ -0,0 +1,32 @@ +/* + * Copyright 2016 Canonical Ltd. + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + * + * Authors: + * Charles Kerr + */ + +#pragma once + +#include + +class MockUsbMonitor: public UsbMonitor +{ +public: + MockUsbMonitor() =default; + virtual ~MockUsbMonitor() =default; + core::Signal& on_usb_disconnected() override {return m_on_usb_disconnected;} + core::Signal m_on_usb_disconnected; +}; + diff --git a/tests/utils/qdbus-helpers.h b/tests/utils/qdbus-helpers.h deleted file mode 100644 index f873e23..0000000 --- a/tests/utils/qdbus-helpers.h +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#define QT_NO_KEYWORDS -#include -#include - -bool qDBusArgumentToMap(QVariant const& variant, QVariantMap& map) -{ - if (variant.canConvert()) - { - QDBusArgument value(variant.value()); - if (value.currentType() == QDBusArgument::MapType) - { - value >> map; - return true; - } - } - - return false; -} - diff --git a/tests/utils/qt-fixture.h b/tests/utils/qt-fixture.h index 321d56e..0f5722b 100644 --- a/tests/utils/qt-fixture.h +++ b/tests/utils/qt-fixture.h @@ -22,12 +22,13 @@ #define QT_NO_KEYWORDS #include -#include #include #include #include +#include +#include #include class QtFixture: public GlibFixture @@ -54,5 +55,20 @@ protected: ASSERT_EQ(signalsExpected, signalSpy.size()); } + + bool qDBusArgumentToMap(QVariant const& variant, QVariantMap& map) + { + if (variant.canConvert()) + { + QDBusArgument value(variant.value()); + if (value.currentType() == QDBusArgument::MapType) + { + value >> map; + return true; + } + } + + return false; + } }; -- cgit v1.2.3