UdisksStorage.cxx 9.42 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright 2003-2020 The Music Player Daemon Project
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
 * 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 "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/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;

48 49
	const AllocatedPath inside_path;

50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
	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:
68 69 70
	template<typename B, typename I, typename IP>
	UdisksStorage(EventLoop &_event_loop, B &&_base_uri, I &&_id,
		      IP &&_inside_path)
71 72
		:base_uri(std::forward<B>(_base_uri)),
		 id(std::forward<I>(_id)),
73
		 inside_path(std::forward<IP>(_inside_path)),
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
		 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());
		}
	}

96
	EventLoop &GetEventLoop() const noexcept {
97 98 99 100
		return defer_mount.GetEventLoop();
	}

	/* virtual methods from class Storage */
101
	StorageFileInfo GetInfo(std::string_view uri_utf8, bool follow) override {
102 103 104 105
		MountWait();
		return mounted_storage->GetInfo(uri_utf8, follow);
	}

106
	std::unique_ptr<StorageDirectoryReader> OpenDirectory(std::string_view uri_utf8) override {
107 108 109 110
		MountWait();
		return mounted_storage->OpenDirectory(uri_utf8);
	}

111
	std::string MapUTF8(std::string_view uri_utf8) const noexcept override;
112

113
	AllocatedPath MapFS(std::string_view uri_utf8) const noexcept override {
114 115 116 117 118 119
		try {
			const_cast<UdisksStorage *>(this)->MountWait();
		} catch (...) {
			return nullptr;
		}

120 121 122
		return mounted_storage->MapFS(uri_utf8);
	}

123
	std::string_view MapToRelativeUTF8(std::string_view uri_utf8) const noexcept override;
124 125

private:
126 127
	void SetMountPoint(Path mount_point);
	void LockSetMountPoint(Path mount_point);
128

129 130 131 132 133 134 135 136 137 138 139
	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;
};

140
inline void
141
UdisksStorage::SetMountPoint(Path mount_point)
142
{
143 144 145 146
	mounted_storage = inside_path.IsNull()
		? CreateLocalStorage(mount_point)
		: CreateLocalStorage(mount_point / inside_path);

147 148
	mount_error = {};
	want_mount = false;
149
	cond.notify_all();
150 151 152
}

void
153
UdisksStorage::LockSetMountPoint(Path mount_point)
154 155 156 157 158
{
	const std::lock_guard<Mutex> lock(mutex);
	SetMountPoint(mount_point);
}

159 160 161 162 163 164
void
UdisksStorage::OnListReply(ODBus::Message reply) noexcept
{
	using namespace UDisks2;

	try {
165 166 167
		std::string mount_point;

		ParseObjects(reply, [this, &mount_point](Object &&o) {
168 169 170 171
			if (!o.IsId(id))
				return;

			dbus_path = std::move(o.path);
172
			mount_point = std::move(o.mount_point);
173
		});
174 175 176 177

		if (dbus_path.empty())
			throw FormatRuntimeError("No such UDisks2 object: %s",
						 id.c_str());
178 179 180 181 182

		if (!mount_point.empty()) {
			/* already mounted: don't attempt to mount
			   again, because this would result in
			   org.freedesktop.UDisks2.Error.AlreadyMounted */
183
			LockSetMountPoint(Path::FromFS(mount_point.c_str()));
184 185
			return;
		}
186 187 188 189
	} catch (...) {
		const std::lock_guard<Mutex> lock(mutex);
		mount_error = std::current_exception();
		want_mount = false;
190
		cond.notify_all();
191 192 193 194 195 196 197 198 199
		return;
	}

	DeferredMount();
}

void
UdisksStorage::MountWait()
{
200
	std::unique_lock<Mutex> lock(mutex);
201 202 203 204 205 206 207 208 209 210

	if (mounted_storage)
		/* already mounted */
		return;

	if (!want_mount) {
		want_mount = true;
		defer_mount.Schedule();
	}

211
	cond.wait(lock, [this]{ return !want_mount; });
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247

	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;
248
	cond.notify_all();
249 250 251 252 253 254 255 256 257 258 259 260 261
}

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();
262
	LockSetMountPoint(Path::FromFS(mount_path));
263 264 265 266
} catch (...) {
	const std::lock_guard<Mutex> lock(mutex);
	mount_error = std::current_exception();
	want_mount = false;
267
	cond.notify_all();
268 269 270 271 272
}

void
UdisksStorage::UnmountWait()
{
273
	std::unique_lock<Mutex> lock(mutex);
274 275 276 277 278 279 280

	if (!mounted_storage)
		/* not mounted */
		return;

	defer_unmount.Schedule();

281
	cond.wait(lock, [this]{ return !mounted_storage; });
282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305

	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();
306
	cond.notify_all();
307 308 309 310 311 312 313 314 315 316 317
}

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();
318
	cond.notify_all();
319 320 321 322
} catch (...) {
	const std::lock_guard<Mutex> lock(mutex);
	mount_error = std::current_exception();
	mounted_storage.reset();
323
	cond.notify_all();
324 325 326
}

std::string
327
UdisksStorage::MapUTF8(std::string_view uri_utf8) const noexcept
328
{
329
	if (uri_utf8.empty())
330 331 332 333 334
		/* kludge for a special case: return the "udisks://"
		   URI if the parameter is an empty string to fix the
		   mount URIs in the state file */
		return base_uri;

335 336 337 338 339 340
	try {
		const_cast<UdisksStorage *>(this)->MountWait();

		return mounted_storage->MapUTF8(uri_utf8);
	} catch (...) {
		/* fallback - not usable but the best we can do */
341
		return PathTraitsUTF8::Build(base_uri, uri_utf8);
342
	}
343 344
}

345 346
std::string_view
UdisksStorage::MapToRelativeUTF8(std::string_view uri_utf8) const noexcept
347
{
348
	return PathTraitsUTF8::Relative(base_uri, uri_utf8);
349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366
}

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;
367 368
		while (*relative_path == '/')
			++relative_path;
369 370
	}

371 372 373
	auto inside_path = *relative_path != 0
		? AllocatedPath::FromUTF8Throw(relative_path)
		: nullptr;
374 375

	return std::make_unique<UdisksStorage>(event_loop, base_uri,
376 377
					       std::move(id),
					       std::move(inside_path));
378 379 380 381 382 383
}

const StoragePlugin udisks_storage_plugin = {
	"udisks",
	CreateUdisksStorageURI,
};