summaryrefslogtreecommitdiff
path: root/src/storage/plugins/UdisksStorage.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'src/storage/plugins/UdisksStorage.cxx')
-rw-r--r--src/storage/plugins/UdisksStorage.cxx330
1 files changed, 330 insertions, 0 deletions
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 <stdexcept>
+
+class UdisksStorage final : public Storage {
+ const std::string base_uri;
+ const std::string id;
+
+ std::string dbus_path;
+
+ SafeSingleton<ODBus::Glue> dbus_glue;
+ ODBus::AsyncRequest list_request;
+ ODBus::AsyncRequest mount_request;
+
+ mutable Mutex mutex;
+ Cond cond;
+
+ bool want_mount = false;
+
+ std::unique_ptr<Storage> mounted_storage;
+
+ std::exception_ptr mount_error;
+
+ DeferEvent defer_mount, defer_unmount;
+
+public:
+ template<typename B, typename I>
+ UdisksStorage(EventLoop &_event_loop, B &&_base_uri, I &&_id)
+ :base_uri(std::forward<B>(_base_uri)),
+ id(std::forward<I>(_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<StorageDirectoryReader> 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<Mutex> lock(mutex);
+ mount_error = std::current_exception();
+ want_mount = false;
+ cond.broadcast();
+ return;
+ }
+
+ DeferredMount();
+}
+
+void
+UdisksStorage::MountWait()
+{
+ const std::lock_guard<Mutex> 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<DictEntryTypeTraits<StringTypeTraits, VariantTypeTraits>>();
+
+ mount_request.Send(connection, *msg.Get(),
+ std::bind(&UdisksStorage::OnMountNotify,
+ this, std::placeholders::_1));
+} catch (...) {
+ const std::lock_guard<Mutex> 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<Mutex> lock(mutex);
+ mounted_storage = CreateLocalStorage(Path::FromFS(mount_path));
+ mount_error = {};
+ want_mount = false;
+ cond.broadcast();
+} catch (...) {
+ const std::lock_guard<Mutex> lock(mutex);
+ mount_error = std::current_exception();
+ want_mount = false;
+ cond.broadcast();
+}
+
+void
+UdisksStorage::UnmountWait()
+{
+ const std::lock_guard<Mutex> 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<DictEntryTypeTraits<StringTypeTraits, VariantTypeTraits>>();
+
+ mount_request.Send(connection, *msg.Get(),
+ std::bind(&UdisksStorage::OnUnmountNotify,
+ this, std::placeholders::_1));
+} catch (...) {
+ const std::lock_guard<Mutex> 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<Mutex> lock(mutex);
+ mount_error = {};
+ mounted_storage.reset();
+ cond.broadcast();
+} catch (...) {
+ const std::lock_guard<Mutex> 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<Storage>
+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<UdisksStorage>(event_loop, base_uri,
+ std::move(id));
+}
+
+const StoragePlugin udisks_storage_plugin = {
+ "udisks",
+ CreateUdisksStorageURI,
+};