From ba8040d0687aeb5327948d97641be9d7608f1027 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Sun, 3 Jun 2018 11:03:49 +0200 Subject: [PATCH] storage/udisks: new plugin Documentation will follow soon. --- Makefile.am | 9 + src/lib/dbus/UDisks2.hxx | 11 + src/storage/Registry.cxx | 4 + src/storage/plugins/UdisksStorage.cxx | 330 ++++++++++++++++++++++++++ src/storage/plugins/UdisksStorage.hxx | 29 +++ 5 files changed, 383 insertions(+) create mode 100644 src/storage/plugins/UdisksStorage.cxx create mode 100644 src/storage/plugins/UdisksStorage.hxx diff --git a/Makefile.am b/Makefile.am index 1550576ec..29e1b9dd6 100644 --- a/Makefile.am +++ b/Makefile.am @@ -792,6 +792,7 @@ libstorage_a_SOURCES = \ src/storage/FileInfo.hxx libstorage_a_CPPFLAGS = $(AM_CPPFLAGS) \ + $(DBUS_CFLAGS) \ $(NFS_CFLAGS) \ $(SMBCLIENT_CFLAGS) @@ -802,6 +803,14 @@ STORAGE_LIBS = \ $(NFS_LIBS) \ $(SMBCLIENT_LIBS) +if ENABLE_UDISKS +libstorage_a_SOURCES += \ + src/storage/plugins/UdisksStorage.cxx src/storage/plugins/UdisksStorage.hxx +STORAGE_LIBS += \ + $(DBUS_LIBS) \ + libodbus.a +endif + if ENABLE_SMBCLIENT libstorage_a_SOURCES += \ $(SMBCLIENT_SOURCES) \ diff --git a/src/lib/dbus/UDisks2.hxx b/src/lib/dbus/UDisks2.hxx index a3675ad62..ed4126cdd 100644 --- a/src/lib/dbus/UDisks2.hxx +++ b/src/lib/dbus/UDisks2.hxx @@ -25,6 +25,7 @@ #define UDISKS2_PATH "/org/freedesktop/UDisks2" #define UDISKS2_INTERFACE "org.freedesktop.UDisks2" +#define UDISKS2_FILESYSTEM_INTERFACE "org.freedesktop.UDisks2.Filesystem" namespace ODBus { class Message; @@ -48,6 +49,16 @@ struct Object { (!drive_id.empty() || !block_id.empty()); } + template + bool IsId(I &&other) const noexcept { + if (!drive_id.empty()) + return drive_id == std::forward(other); + else if (!block_id.empty()) + return block_id == std::forward(other); + else + return false; + } + std::string GetUri() const noexcept { if (!drive_id.empty()) return "udisks://" + drive_id; diff --git a/src/storage/Registry.cxx b/src/storage/Registry.cxx index 85374886e..7df8dda04 100644 --- a/src/storage/Registry.cxx +++ b/src/storage/Registry.cxx @@ -22,6 +22,7 @@ #include "StoragePlugin.hxx" #include "StorageInterface.hxx" #include "plugins/LocalStorage.hxx" +#include "plugins/UdisksStorage.hxx" #include "plugins/SmbclientStorage.hxx" #include "plugins/NfsStorage.hxx" #include "plugins/CurlStorage.hxx" @@ -34,6 +35,9 @@ const StoragePlugin *const storage_plugins[] = { #ifdef ENABLE_SMBCLIENT &smbclient_storage_plugin, #endif +#ifdef ENABLE_UDISKS + &udisks_storage_plugin, +#endif #ifdef ENABLE_NFS &nfs_storage_plugin, #endif diff --git a/src/storage/plugins/UdisksStorage.cxx b/src/storage/plugins/UdisksStorage.cxx new file mode 100644 index 000000000..2bd294e9b --- /dev/null +++ b/src/storage/plugins/UdisksStorage.cxx @@ -0,0 +1,330 @@ +/* + * Copyright 2003-2018 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "UdisksStorage.hxx" +#include "LocalStorage.hxx" +#include "storage/StoragePlugin.hxx" +#include "storage/StorageInterface.hxx" +#include "storage/FileInfo.hxx" +#include "lib/dbus/Glue.hxx" +#include "lib/dbus/AsyncRequest.hxx" +#include "lib/dbus/Message.hxx" +#include "lib/dbus/PendingCall.hxx" +#include "lib/dbus/AppendIter.hxx" +#include "lib/dbus/ReadIter.hxx" +#include "lib/dbus/ObjectManager.hxx" +#include "lib/dbus/UDisks2.hxx" +#include "thread/Mutex.hxx" +#include "thread/Cond.hxx" +#include "thread/SafeSingleton.hxx" +#include "event/Call.hxx" +#include "event/DeferEvent.hxx" +#include "fs/AllocatedPath.hxx" +#include "util/StringCompare.hxx" +#include "util/RuntimeError.hxx" +#include "Log.hxx" + +#include + +class UdisksStorage final : public Storage { + const std::string base_uri; + const std::string id; + + std::string dbus_path; + + SafeSingleton dbus_glue; + ODBus::AsyncRequest list_request; + ODBus::AsyncRequest mount_request; + + mutable Mutex mutex; + Cond cond; + + bool want_mount = false; + + std::unique_ptr mounted_storage; + + std::exception_ptr mount_error; + + DeferEvent defer_mount, defer_unmount; + +public: + template + UdisksStorage(EventLoop &_event_loop, B &&_base_uri, I &&_id) + :base_uri(std::forward(_base_uri)), + id(std::forward(_id)), + dbus_glue(_event_loop), + defer_mount(_event_loop, BIND_THIS_METHOD(DeferredMount)), + defer_unmount(_event_loop, BIND_THIS_METHOD(DeferredUnmount)) {} + + ~UdisksStorage() noexcept override { + if (list_request || mount_request) + BlockingCall(GetEventLoop(), [this](){ + if (list_request) + list_request.Cancel(); + if (mount_request) + mount_request.Cancel(); + }); + + try { + UnmountWait(); + } catch (...) { + FormatError(std::current_exception(), + "Failed to unmount '%s'", + base_uri.c_str()); + } + } + + EventLoop &GetEventLoop() noexcept { + return defer_mount.GetEventLoop(); + } + + /* virtual methods from class Storage */ + StorageFileInfo GetInfo(const char *uri_utf8, bool follow) override { + MountWait(); + return mounted_storage->GetInfo(uri_utf8, follow); + } + + std::unique_ptr OpenDirectory(const char *uri_utf8) override { + MountWait(); + return mounted_storage->OpenDirectory(uri_utf8); + } + + std::string MapUTF8(const char *uri_utf8) const noexcept override; + + const char *MapToRelativeUTF8(const char *uri_utf8) const noexcept override; + +private: + void OnListReply(ODBus::Message reply) noexcept; + + void MountWait(); + void DeferredMount() noexcept; + void OnMountNotify(ODBus::Message reply) noexcept; + + void UnmountWait(); + void DeferredUnmount() noexcept; + void OnUnmountNotify(ODBus::Message reply) noexcept; +}; + +void +UdisksStorage::OnListReply(ODBus::Message reply) noexcept +{ + using namespace UDisks2; + + try { + ParseObjects(reply, [this](Object &&o) { + if (o.IsId(id)) + dbus_path = std::move(o.path); + }); + + if (dbus_path.empty()) + throw FormatRuntimeError("No such UDisks2 object: %s", + id.c_str()); + } catch (...) { + const std::lock_guard lock(mutex); + mount_error = std::current_exception(); + want_mount = false; + cond.broadcast(); + return; + } + + DeferredMount(); +} + +void +UdisksStorage::MountWait() +{ + const std::lock_guard lock(mutex); + + if (mounted_storage) + /* already mounted */ + return; + + if (!want_mount) { + want_mount = true; + defer_mount.Schedule(); + } + + while (want_mount) + cond.wait(mutex); + + if (mount_error) + std::rethrow_exception(mount_error); +} + +void +UdisksStorage::DeferredMount() noexcept +try { + using namespace ODBus; + + auto &connection = dbus_glue->GetConnection(); + + if (dbus_path.empty()) { + auto msg = Message::NewMethodCall(UDISKS2_INTERFACE, + UDISKS2_PATH, + DBUS_OM_INTERFACE, + "GetManagedObjects"); + list_request.Send(connection, *msg.Get(), + std::bind(&UdisksStorage::OnListReply, + this, std::placeholders::_1)); + return; + } + + auto msg = Message::NewMethodCall(UDISKS2_INTERFACE, + dbus_path.c_str(), + UDISKS2_FILESYSTEM_INTERFACE, + "Mount"); + AppendMessageIter(*msg.Get()).AppendEmptyArray>(); + + mount_request.Send(connection, *msg.Get(), + std::bind(&UdisksStorage::OnMountNotify, + this, std::placeholders::_1)); +} catch (...) { + const std::lock_guard lock(mutex); + mount_error = std::current_exception(); + want_mount = false; + cond.broadcast(); +} + +void +UdisksStorage::OnMountNotify(ODBus::Message reply) noexcept +try { + using namespace ODBus; + reply.CheckThrowError(); + + ReadMessageIter i(*reply.Get()); + if (i.GetArgType() != DBUS_TYPE_STRING) + throw std::runtime_error("Malformed 'Mount' response"); + + const char *mount_path = i.GetString(); + + const std::lock_guard lock(mutex); + mounted_storage = CreateLocalStorage(Path::FromFS(mount_path)); + mount_error = {}; + want_mount = false; + cond.broadcast(); +} catch (...) { + const std::lock_guard lock(mutex); + mount_error = std::current_exception(); + want_mount = false; + cond.broadcast(); +} + +void +UdisksStorage::UnmountWait() +{ + const std::lock_guard lock(mutex); + + if (!mounted_storage) + /* not mounted */ + return; + + defer_unmount.Schedule(); + + while (mounted_storage) + cond.wait(mutex); + + if (mount_error) + std::rethrow_exception(mount_error); +} + +void +UdisksStorage::DeferredUnmount() noexcept +try { + using namespace ODBus; + + auto &connection = dbus_glue->GetConnection(); + auto msg = Message::NewMethodCall(UDISKS2_INTERFACE, + dbus_path.c_str(), + UDISKS2_FILESYSTEM_INTERFACE, + "Unmount"); + AppendMessageIter(*msg.Get()).AppendEmptyArray>(); + + mount_request.Send(connection, *msg.Get(), + std::bind(&UdisksStorage::OnUnmountNotify, + this, std::placeholders::_1)); +} catch (...) { + const std::lock_guard lock(mutex); + mount_error = std::current_exception(); + mounted_storage.reset(); + cond.broadcast(); +} + +void +UdisksStorage::OnUnmountNotify(ODBus::Message reply) noexcept +try { + using namespace ODBus; + reply.CheckThrowError(); + + const std::lock_guard lock(mutex); + mount_error = {}; + mounted_storage.reset(); + cond.broadcast(); +} catch (...) { + const std::lock_guard lock(mutex); + mount_error = std::current_exception(); + mounted_storage.reset(); + cond.broadcast(); +} + +std::string +UdisksStorage::MapUTF8(const char *uri_utf8) const noexcept +{ + assert(uri_utf8 != nullptr); + + if (StringIsEmpty(uri_utf8)) + return base_uri; + + return PathTraitsUTF8::Build(base_uri.c_str(), uri_utf8); +} + +const char * +UdisksStorage::MapToRelativeUTF8(const char *uri_utf8) const noexcept +{ + return PathTraitsUTF8::Relative(base_uri.c_str(), uri_utf8); +} + +static std::unique_ptr +CreateUdisksStorageURI(EventLoop &event_loop, const char *base_uri) +{ + const char *id_begin = StringAfterPrefix(base_uri, "udisks://"); + if (id_begin == nullptr) + return nullptr; + + std::string id; + + const char *relative_path = strchr(id_begin, '/'); + if (relative_path == nullptr) { + id = id_begin; + relative_path = ""; + } else { + id = {id_begin, relative_path}; + ++relative_path; + } + + // TODO: use relative_path + + return std::make_unique(event_loop, base_uri, + std::move(id)); +} + +const StoragePlugin udisks_storage_plugin = { + "udisks", + CreateUdisksStorageURI, +}; diff --git a/src/storage/plugins/UdisksStorage.hxx b/src/storage/plugins/UdisksStorage.hxx new file mode 100644 index 000000000..3e3562f4c --- /dev/null +++ b/src/storage/plugins/UdisksStorage.hxx @@ -0,0 +1,29 @@ +/* + * Copyright 2003-2018 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_STORAGE_UDISKS_HXX +#define MPD_STORAGE_UDISKS_HXX + +#include "check.h" + +struct StoragePlugin; + +extern const StoragePlugin udisks_storage_plugin; + +#endif