1 /*
2  * Copyright 2003-2021 The Music Player Daemon Project
3  * http://www.musicpd.org
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18  */
19 
20 #include "UdisksStorage.hxx"
21 #include "LocalStorage.hxx"
22 #include "storage/StoragePlugin.hxx"
23 #include "storage/StorageInterface.hxx"
24 #include "storage/FileInfo.hxx"
25 #include "lib/fmt/ExceptionFormatter.hxx"
26 #include "lib/dbus/Glue.hxx"
27 #include "lib/dbus/AsyncRequest.hxx"
28 #include "lib/dbus/Message.hxx"
29 #include "lib/dbus/AppendIter.hxx"
30 #include "lib/dbus/ReadIter.hxx"
31 #include "lib/dbus/ObjectManager.hxx"
32 #include "lib/dbus/UDisks2.hxx"
33 #include "thread/Mutex.hxx"
34 #include "thread/Cond.hxx"
35 #include "thread/SafeSingleton.hxx"
36 #include "event/Call.hxx"
37 #include "event/InjectEvent.hxx"
38 #include "fs/AllocatedPath.hxx"
39 #include "util/Domain.hxx"
40 #include "util/StringCompare.hxx"
41 #include "util/RuntimeError.hxx"
42 #include "Log.hxx"
43 
44 #include <stdexcept>
45 
46 static constexpr Domain udisks_domain("udisks");
47 
48 class UdisksStorage final : public Storage {
49 	const std::string base_uri;
50 	const std::string id;
51 
52 	const AllocatedPath inside_path;
53 
54 	std::string dbus_path;
55 
56 	SafeSingleton<ODBus::Glue> dbus_glue;
57 	ODBus::AsyncRequest list_request;
58 	ODBus::AsyncRequest mount_request;
59 
60 	mutable Mutex mutex;
61 	Cond cond;
62 
63 	bool want_mount = false;
64 
65 	std::unique_ptr<Storage> mounted_storage;
66 
67 	std::exception_ptr mount_error;
68 
69 	InjectEvent defer_mount, defer_unmount;
70 
71 public:
72 	template<typename B, typename I, typename IP>
UdisksStorage(EventLoop & _event_loop,B && _base_uri,I && _id,IP && _inside_path)73 	UdisksStorage(EventLoop &_event_loop, B &&_base_uri, I &&_id,
74 		      IP &&_inside_path)
75 		:base_uri(std::forward<B>(_base_uri)),
76 		 id(std::forward<I>(_id)),
77 		 inside_path(std::forward<IP>(_inside_path)),
78 		 dbus_glue(_event_loop),
79 		 defer_mount(_event_loop, BIND_THIS_METHOD(DeferredMount)),
80 		 defer_unmount(_event_loop, BIND_THIS_METHOD(DeferredUnmount)) {}
81 
~UdisksStorage()82 	~UdisksStorage() noexcept override {
83 		if (list_request || mount_request)
84 			BlockingCall(GetEventLoop(), [this](){
85 					if (list_request)
86 						list_request.Cancel();
87 					if (mount_request)
88 						mount_request.Cancel();
89 				});
90 
91 		try {
92 			UnmountWait();
93 		} catch (...) {
94 			FmtError(udisks_domain,
95 				 "Failed to unmount '{}': {}",
96 				 base_uri, std::current_exception());
97 		}
98 	}
99 
100 	UdisksStorage(const UdisksStorage &) = delete;
101 	UdisksStorage &operator=(const UdisksStorage &) = delete;
102 
GetEventLoop() const103 	EventLoop &GetEventLoop() const noexcept {
104 		return defer_mount.GetEventLoop();
105 	}
106 
107 	/* virtual methods from class Storage */
GetInfo(std::string_view uri_utf8,bool follow)108 	StorageFileInfo GetInfo(std::string_view uri_utf8, bool follow) override {
109 		MountWait();
110 		return mounted_storage->GetInfo(uri_utf8, follow);
111 	}
112 
OpenDirectory(std::string_view uri_utf8)113 	std::unique_ptr<StorageDirectoryReader> OpenDirectory(std::string_view uri_utf8) override {
114 		MountWait();
115 		return mounted_storage->OpenDirectory(uri_utf8);
116 	}
117 
118 	std::string MapUTF8(std::string_view uri_utf8) const noexcept override;
119 
MapFS(std::string_view uri_utf8) const120 	AllocatedPath MapFS(std::string_view uri_utf8) const noexcept override {
121 		try {
122 			const_cast<UdisksStorage *>(this)->MountWait();
123 		} catch (...) {
124 			return nullptr;
125 		}
126 
127 		return mounted_storage->MapFS(uri_utf8);
128 	}
129 
130 	std::string_view MapToRelativeUTF8(std::string_view uri_utf8) const noexcept override;
131 
132 private:
133 	void SetMountPoint(Path mount_point);
134 	void LockSetMountPoint(Path mount_point);
135 
136 	void OnListReply(ODBus::Message reply) noexcept;
137 
138 	void MountWait();
139 	void DeferredMount() noexcept;
140 	void OnMountNotify(ODBus::Message reply) noexcept;
141 
142 	void UnmountWait();
143 	void DeferredUnmount() noexcept;
144 	void OnUnmountNotify(ODBus::Message reply) noexcept;
145 };
146 
147 inline void
SetMountPoint(Path mount_point)148 UdisksStorage::SetMountPoint(Path mount_point)
149 {
150 	mounted_storage = inside_path.IsNull()
151 		? CreateLocalStorage(mount_point)
152 		: CreateLocalStorage(mount_point / inside_path);
153 
154 	mount_error = {};
155 	want_mount = false;
156 	cond.notify_all();
157 }
158 
159 void
LockSetMountPoint(Path mount_point)160 UdisksStorage::LockSetMountPoint(Path mount_point)
161 {
162 	const std::scoped_lock<Mutex> lock(mutex);
163 	SetMountPoint(mount_point);
164 }
165 
166 void
OnListReply(ODBus::Message reply)167 UdisksStorage::OnListReply(ODBus::Message reply) noexcept
168 {
169 	using namespace UDisks2;
170 
171 	try {
172 		std::string mount_point;
173 
174 		ParseObjects(reply, [this, &mount_point](Object &&o) {
175 			if (!o.IsId(id))
176 				return;
177 
178 			dbus_path = std::move(o.path);
179 			mount_point = std::move(o.mount_point);
180 		});
181 
182 		if (dbus_path.empty())
183 			throw FormatRuntimeError("No such UDisks2 object: %s",
184 						 id.c_str());
185 
186 		if (!mount_point.empty()) {
187 			/* already mounted: don't attempt to mount
188 			   again, because this would result in
189 			   org.freedesktop.UDisks2.Error.AlreadyMounted */
190 			LockSetMountPoint(Path::FromFS(mount_point.c_str()));
191 			return;
192 		}
193 	} catch (...) {
194 		const std::scoped_lock<Mutex> lock(mutex);
195 		mount_error = std::current_exception();
196 		want_mount = false;
197 		cond.notify_all();
198 		return;
199 	}
200 
201 	DeferredMount();
202 }
203 
204 void
MountWait()205 UdisksStorage::MountWait()
206 {
207 	std::unique_lock<Mutex> lock(mutex);
208 
209 	if (mounted_storage)
210 		/* already mounted */
211 		return;
212 
213 	if (!want_mount) {
214 		want_mount = true;
215 		defer_mount.Schedule();
216 	}
217 
218 	cond.wait(lock, [this]{ return !want_mount; });
219 
220 	if (mount_error)
221 		std::rethrow_exception(mount_error);
222 }
223 
224 void
DeferredMount()225 UdisksStorage::DeferredMount() noexcept
226 try {
227 	using namespace ODBus;
228 
229 	auto &connection = dbus_glue->GetConnection();
230 
231 	if (dbus_path.empty()) {
232 		auto msg = Message::NewMethodCall(UDISKS2_INTERFACE,
233 						  UDISKS2_PATH,
234 						  DBUS_OM_INTERFACE,
235 						  "GetManagedObjects");
236 		list_request.Send(connection, *msg.Get(),
237 				  [this](auto o) { return OnListReply(std::move(o)); });
238 		return;
239 	}
240 
241 	auto msg = Message::NewMethodCall(UDISKS2_INTERFACE,
242 					  dbus_path.c_str(),
243 					  UDISKS2_FILESYSTEM_INTERFACE,
244 					  "Mount");
245 	AppendMessageIter(*msg.Get()).AppendEmptyArray<DictEntryTypeTraits<StringTypeTraits, VariantTypeTraits>>();
246 
247 	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 	mount_error = std::current_exception();
252 	want_mount = false;
253 	cond.notify_all();
254 }
255 
256 void
OnMountNotify(ODBus::Message reply)257 UdisksStorage::OnMountNotify(ODBus::Message reply) noexcept
258 try {
259 	using namespace ODBus;
260 	reply.CheckThrowError();
261 
262 	ReadMessageIter i(*reply.Get());
263 	if (i.GetArgType() != DBUS_TYPE_STRING)
264 		throw std::runtime_error("Malformed 'Mount' response");
265 
266 	const char *mount_path = i.GetString();
267 	LockSetMountPoint(Path::FromFS(mount_path));
268 } catch (...) {
269 	const std::scoped_lock<Mutex> lock(mutex);
270 	mount_error = std::current_exception();
271 	want_mount = false;
272 	cond.notify_all();
273 }
274 
275 void
UnmountWait()276 UdisksStorage::UnmountWait()
277 {
278 	std::unique_lock<Mutex> lock(mutex);
279 
280 	if (!mounted_storage)
281 		/* not mounted */
282 		return;
283 
284 	defer_unmount.Schedule();
285 
286 	cond.wait(lock, [this]{ return !mounted_storage; });
287 
288 	if (mount_error)
289 		std::rethrow_exception(mount_error);
290 }
291 
292 void
DeferredUnmount()293 UdisksStorage::DeferredUnmount() noexcept
294 try {
295 	using namespace ODBus;
296 
297 	auto &connection = dbus_glue->GetConnection();
298 	auto msg = Message::NewMethodCall(UDISKS2_INTERFACE,
299 					  dbus_path.c_str(),
300 					  UDISKS2_FILESYSTEM_INTERFACE,
301 					  "Unmount");
302 	AppendMessageIter(*msg.Get()).AppendEmptyArray<DictEntryTypeTraits<StringTypeTraits, VariantTypeTraits>>();
303 
304 	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 	mount_error = std::current_exception();
309 	mounted_storage.reset();
310 	cond.notify_all();
311 }
312 
313 void
OnUnmountNotify(ODBus::Message reply)314 UdisksStorage::OnUnmountNotify(ODBus::Message reply) noexcept
315 try {
316 	using namespace ODBus;
317 	reply.CheckThrowError();
318 
319 	const std::scoped_lock<Mutex> lock(mutex);
320 	mount_error = {};
321 	mounted_storage.reset();
322 	cond.notify_all();
323 } catch (...) {
324 	const std::scoped_lock<Mutex> lock(mutex);
325 	mount_error = std::current_exception();
326 	mounted_storage.reset();
327 	cond.notify_all();
328 }
329 
330 std::string
MapUTF8(std::string_view uri_utf8) const331 UdisksStorage::MapUTF8(std::string_view uri_utf8) const noexcept
332 {
333 	if (uri_utf8.empty())
334 		/* kludge for a special case: return the "udisks://"
335 		   URI if the parameter is an empty string to fix the
336 		   mount URIs in the state file */
337 		return base_uri;
338 
339 	try {
340 		const_cast<UdisksStorage *>(this)->MountWait();
341 
342 		return mounted_storage->MapUTF8(uri_utf8);
343 	} catch (...) {
344 		/* fallback - not usable but the best we can do */
345 		return PathTraitsUTF8::Build(base_uri, uri_utf8);
346 	}
347 }
348 
349 std::string_view
MapToRelativeUTF8(std::string_view uri_utf8) const350 UdisksStorage::MapToRelativeUTF8(std::string_view uri_utf8) const noexcept
351 {
352 	return PathTraitsUTF8::Relative(base_uri, uri_utf8);
353 }
354 
355 static std::unique_ptr<Storage>
CreateUdisksStorageURI(EventLoop & event_loop,const char * base_uri)356 CreateUdisksStorageURI(EventLoop &event_loop, const char *base_uri)
357 {
358 	const char *id_begin = StringAfterPrefix(base_uri, "udisks://");
359 	if (id_begin == nullptr)
360 		return nullptr;
361 
362 	std::string id;
363 
364 	const char *relative_path = std::strchr(id_begin, '/');
365 	if (relative_path == nullptr) {
366 		id = id_begin;
367 		relative_path = "";
368 	} else {
369 		id = {id_begin, relative_path};
370 		++relative_path;
371 		while (*relative_path == '/')
372 			++relative_path;
373 	}
374 
375 	auto inside_path = *relative_path != 0
376 		? AllocatedPath::FromUTF8Throw(relative_path)
377 		: nullptr;
378 
379 	return std::make_unique<UdisksStorage>(event_loop, base_uri,
380 					       std::move(id),
381 					       std::move(inside_path));
382 }
383 
384 static constexpr const char *udisks_prefixes[] = { "udisks://", nullptr };
385 
386 const StoragePlugin udisks_storage_plugin = {
387 	"udisks",
388 	udisks_prefixes,
389 	CreateUdisksStorageURI,
390 };
391