UdisksStorage.cxx 9.68 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright 2003-2021 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
 * 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"
25
#include "lib/fmt/ExceptionFormatter.hxx"
26 27 28 29 30 31 32 33 34 35 36
#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"
37
#include "event/InjectEvent.hxx"
38
#include "fs/AllocatedPath.hxx"
39
#include "util/Domain.hxx"
40 41 42 43 44 45
#include "util/StringCompare.hxx"
#include "util/RuntimeError.hxx"
#include "Log.hxx"

#include <stdexcept>

46 47
static constexpr Domain udisks_domain("udisks");

48 49 50 51
class UdisksStorage final : public Storage {
	const std::string base_uri;
	const std::string id;

52 53
	const AllocatedPath inside_path;

54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
	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;

69
	InjectEvent defer_mount, defer_unmount;
70 71

public:
72 73 74
	template<typename B, typename I, typename IP>
	UdisksStorage(EventLoop &_event_loop, B &&_base_uri, I &&_id,
		      IP &&_inside_path)
75 76
		:base_uri(std::forward<B>(_base_uri)),
		 id(std::forward<I>(_id)),
77
		 inside_path(std::forward<IP>(_inside_path)),
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
		 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 (...) {
94 95 96
			FmtError(udisks_domain,
				 "Failed to unmount '{}': {}",
				 base_uri, std::current_exception());
97 98 99
		}
	}

100 101 102
	UdisksStorage(const UdisksStorage &) = delete;
	UdisksStorage &operator=(const UdisksStorage &) = delete;

103
	EventLoop &GetEventLoop() const noexcept {
104 105 106 107
		return defer_mount.GetEventLoop();
	}

	/* virtual methods from class Storage */
108
	StorageFileInfo GetInfo(std::string_view uri_utf8, bool follow) override {
109 110 111 112
		MountWait();
		return mounted_storage->GetInfo(uri_utf8, follow);
	}

113
	std::unique_ptr<StorageDirectoryReader> OpenDirectory(std::string_view uri_utf8) override {
114 115 116 117
		MountWait();
		return mounted_storage->OpenDirectory(uri_utf8);
	}

118
	std::string MapUTF8(std::string_view uri_utf8) const noexcept override;
119

120
	AllocatedPath MapFS(std::string_view uri_utf8) const noexcept override {
121 122 123 124 125 126
		try {
			const_cast<UdisksStorage *>(this)->MountWait();
		} catch (...) {
			return nullptr;
		}

127 128 129
		return mounted_storage->MapFS(uri_utf8);
	}

130
	std::string_view MapToRelativeUTF8(std::string_view uri_utf8) const noexcept override;
131 132

private:
133 134
	void SetMountPoint(Path mount_point);
	void LockSetMountPoint(Path mount_point);
135

136 137 138 139 140 141 142 143 144 145 146
	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;
};

147
inline void
148
UdisksStorage::SetMountPoint(Path mount_point)
149
{
150 151 152 153
	mounted_storage = inside_path.IsNull()
		? CreateLocalStorage(mount_point)
		: CreateLocalStorage(mount_point / inside_path);

154 155
	mount_error = {};
	want_mount = false;
156
	cond.notify_all();
157 158 159
}

void
160
UdisksStorage::LockSetMountPoint(Path mount_point)
161
{
162
	const std::scoped_lock<Mutex> lock(mutex);
163 164 165
	SetMountPoint(mount_point);
}

166 167 168 169 170 171
void
UdisksStorage::OnListReply(ODBus::Message reply) noexcept
{
	using namespace UDisks2;

	try {
172 173 174
		std::string mount_point;

		ParseObjects(reply, [this, &mount_point](Object &&o) {
175 176 177 178
			if (!o.IsId(id))
				return;

			dbus_path = std::move(o.path);
179
			mount_point = std::move(o.mount_point);
180
		});
181 182 183 184

		if (dbus_path.empty())
			throw FormatRuntimeError("No such UDisks2 object: %s",
						 id.c_str());
185 186 187 188 189

		if (!mount_point.empty()) {
			/* already mounted: don't attempt to mount
			   again, because this would result in
			   org.freedesktop.UDisks2.Error.AlreadyMounted */
190
			LockSetMountPoint(Path::FromFS(mount_point.c_str()));
191 192
			return;
		}
193
	} catch (...) {
194
		const std::scoped_lock<Mutex> lock(mutex);
195 196
		mount_error = std::current_exception();
		want_mount = false;
197
		cond.notify_all();
198 199 200 201 202 203 204 205 206
		return;
	}

	DeferredMount();
}

void
UdisksStorage::MountWait()
{
207
	std::unique_lock<Mutex> lock(mutex);
208 209 210 211 212 213 214 215 216 217

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

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

218
	cond.wait(lock, [this]{ return !want_mount; });
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236

	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(),
237
				  [this](auto o) { return OnListReply(std::move(o)); });
238 239 240 241 242 243 244 245 246 247
		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(),
248
			   [this](auto o) { return OnMountNotify(std::move(o)); });
249
} catch (...) {
250
	const std::scoped_lock<Mutex> lock(mutex);
251 252
	mount_error = std::current_exception();
	want_mount = false;
253
	cond.notify_all();
254 255 256 257 258 259 260 261 262 263 264 265 266
}

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();
267
	LockSetMountPoint(Path::FromFS(mount_path));
268
} catch (...) {
269
	const std::scoped_lock<Mutex> lock(mutex);
270 271
	mount_error = std::current_exception();
	want_mount = false;
272
	cond.notify_all();
273 274 275 276 277
}

void
UdisksStorage::UnmountWait()
{
278
	std::unique_lock<Mutex> lock(mutex);
279 280 281 282 283 284 285

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

	defer_unmount.Schedule();

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

	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(),
305
			   [this](auto u) { return OnUnmountNotify(std::move(u)); });
306
} catch (...) {
307
	const std::scoped_lock<Mutex> lock(mutex);
308 309
	mount_error = std::current_exception();
	mounted_storage.reset();
310
	cond.notify_all();
311 312 313 314 315 316 317 318
}

void
UdisksStorage::OnUnmountNotify(ODBus::Message reply) noexcept
try {
	using namespace ODBus;
	reply.CheckThrowError();

319
	const std::scoped_lock<Mutex> lock(mutex);
320 321
	mount_error = {};
	mounted_storage.reset();
322
	cond.notify_all();
323
} catch (...) {
324
	const std::scoped_lock<Mutex> lock(mutex);
325 326
	mount_error = std::current_exception();
	mounted_storage.reset();
327
	cond.notify_all();
328 329 330
}

std::string
331
UdisksStorage::MapUTF8(std::string_view uri_utf8) const noexcept
332
{
333
	if (uri_utf8.empty())
334 335 336 337 338
		/* 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;

339 340 341 342 343 344
	try {
		const_cast<UdisksStorage *>(this)->MountWait();

		return mounted_storage->MapUTF8(uri_utf8);
	} catch (...) {
		/* fallback - not usable but the best we can do */
345
		return PathTraitsUTF8::Build(base_uri, uri_utf8);
346
	}
347 348
}

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

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;

Rosen Penev's avatar
Rosen Penev committed
364
	const char *relative_path = std::strchr(id_begin, '/');
365 366 367 368 369 370
	if (relative_path == nullptr) {
		id = id_begin;
		relative_path = "";
	} else {
		id = {id_begin, relative_path};
		++relative_path;
371 372
		while (*relative_path == '/')
			++relative_path;
373 374
	}

375 376 377
	auto inside_path = *relative_path != 0
		? AllocatedPath::FromUTF8Throw(relative_path)
		: nullptr;
378 379

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

384 385
static constexpr const char *udisks_prefixes[] = { "udisks://", nullptr };

386 387
const StoragePlugin udisks_storage_plugin = {
	"udisks",
388
	udisks_prefixes,
389 390
	CreateUdisksStorageURI,
};