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