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 "CompositeStorage.hxx"
21 #include "FileInfo.hxx"
22 #include "fs/AllocatedPath.hxx"
23 #include "util/IterableSplitString.hxx"
24 #include "util/StringCompare.hxx"
25
26 #include <set>
27 #include <stdexcept>
28
29 #include <string.h>
30
31 /**
32 * Combines the directory entries of another #StorageDirectoryReader
33 * instance and the virtual directory entries.
34 */
35 class CompositeDirectoryReader final : public StorageDirectoryReader {
36 std::unique_ptr<StorageDirectoryReader> other;
37
38 std::set<std::string> names;
39 std::set<std::string>::const_iterator current, next;
40
41 public:
42 template<typename O, typename M>
CompositeDirectoryReader(O && _other,const M & map)43 CompositeDirectoryReader(O &&_other, const M &map)
44 :other(std::forward<O>(_other)) {
45 for (const auto &i : map)
46 names.insert(i.first);
47 next = names.begin();
48 }
49
50 /* virtual methods from class StorageDirectoryReader */
51 const char *Read() noexcept override;
52 StorageFileInfo GetInfo(bool follow) override;
53 };
54
55 const char *
Read()56 CompositeDirectoryReader::Read() noexcept
57 {
58 if (other != nullptr) {
59 const char *name = other->Read();
60 if (name != nullptr) {
61 names.erase(name);
62 return name;
63 }
64
65 other.reset();
66 }
67
68 if (next == names.end())
69 return nullptr;
70
71 current = next++;
72 return current->c_str();
73 }
74
75 StorageFileInfo
GetInfo(bool follow)76 CompositeDirectoryReader::GetInfo(bool follow)
77 {
78 if (other != nullptr)
79 return other->GetInfo(follow);
80
81 assert(current != names.end());
82
83 return StorageFileInfo(StorageFileInfo::Type::DIRECTORY);
84 }
85
86 static std::string_view
NextSegment(std::string_view & uri_r)87 NextSegment(std::string_view &uri_r) noexcept
88 {
89 StringView uri(uri_r);
90 auto s = uri.Split('/');
91 uri_r = s.second;
92 return s.first;
93 }
94
95 const CompositeStorage::Directory *
Find(std::string_view uri) const96 CompositeStorage::Directory::Find(std::string_view uri) const noexcept
97 {
98 const Directory *directory = this;
99
100 for (std::string_view name : IterableSplitString(uri, '/')) {
101 if (name.empty())
102 continue;
103
104 auto i = directory->children.find(name);
105 if (i == directory->children.end())
106 return nullptr;
107
108 directory = &i->second;
109 }
110
111 return directory;
112 }
113
114 CompositeStorage::Directory &
Make(std::string_view uri)115 CompositeStorage::Directory::Make(std::string_view uri)
116 {
117 Directory *directory = this;
118
119 for (std::string_view name : IterableSplitString(uri, '/')) {
120 if (name.empty())
121 continue;
122
123 auto i = directory->children.emplace(name, Directory());
124 directory = &i.first->second;
125 }
126
127 return *directory;
128 }
129
130 bool
Unmount()131 CompositeStorage::Directory::Unmount() noexcept
132 {
133 if (storage == nullptr)
134 return false;
135
136 storage.reset();
137 return true;
138 }
139
140 bool
Unmount(std::string_view uri)141 CompositeStorage::Directory::Unmount(std::string_view uri) noexcept
142 {
143 if (uri.empty())
144 return Unmount();
145
146 const auto name = NextSegment(uri);
147
148 auto i = children.find(name);
149 if (i == children.end() || !i->second.Unmount(uri))
150 return false;
151
152 if (i->second.IsEmpty())
153 children.erase(i);
154
155 return true;
156
157 }
158
159 bool
MapToRelativeUTF8(std::string & buffer,std::string_view uri) const160 CompositeStorage::Directory::MapToRelativeUTF8(std::string &buffer,
161 std::string_view uri) const noexcept
162 {
163 if (storage != nullptr) {
164 auto result = storage->MapToRelativeUTF8(uri);
165 if (result.data() != nullptr) {
166 buffer = result;
167 return true;
168 }
169 }
170
171 for (const auto &i : children) {
172 if (i.second.MapToRelativeUTF8(buffer, uri)) {
173 buffer.insert(buffer.begin(), '/');
174 buffer.insert(buffer.begin(),
175 i.first.begin(), i.first.end());
176 return true;
177 }
178 }
179
180 return false;
181 }
182
CompositeStorage()183 CompositeStorage::CompositeStorage() noexcept
184 {
185 /* note: no "=default" here because members of this class are
186 allowed to throw during construction according to the C++
187 standard (e.g. std::map), but we choose to ignore these
188 exceptions; if construction of std::map goes wrong, MPD has
189 no chance to work at all, so it's ok to std::terminate() */
190 }
191
192 CompositeStorage::~CompositeStorage() = default;
193
194 Storage *
GetMount(std::string_view uri)195 CompositeStorage::GetMount(std::string_view uri) noexcept
196 {
197 const std::scoped_lock<Mutex> protect(mutex);
198
199 auto result = FindStorage(uri);
200 if (!result.uri.empty())
201 /* not a mount point */
202 return nullptr;
203
204 return result.directory->storage.get();
205 }
206
207 void
Mount(const char * uri,std::unique_ptr<Storage> storage)208 CompositeStorage::Mount(const char *uri, std::unique_ptr<Storage> storage)
209 {
210 const std::scoped_lock<Mutex> protect(mutex);
211
212 Directory &directory = root.Make(uri);
213 assert(!directory.storage);
214 directory.storage = std::move(storage);
215 }
216
217 bool
Unmount(const char * uri)218 CompositeStorage::Unmount(const char *uri)
219 {
220 const std::scoped_lock<Mutex> protect(mutex);
221
222 return root.Unmount(uri);
223 }
224
225 CompositeStorage::FindResult
FindStorage(std::string_view uri) const226 CompositeStorage::FindStorage(std::string_view uri) const noexcept
227 {
228 FindResult result{&root, uri};
229
230 const Directory *directory = &root;
231 while (!uri.empty()) {
232 const auto name = NextSegment(uri);
233
234 auto i = directory->children.find(name);
235 if (i == directory->children.end())
236 break;
237
238 directory = &i->second;
239 if (directory->storage != nullptr)
240 result = FindResult{directory, uri};
241 }
242
243 return result;
244 }
245
246 StorageFileInfo
GetInfo(std::string_view uri,bool follow)247 CompositeStorage::GetInfo(std::string_view uri, bool follow)
248 {
249 const std::scoped_lock<Mutex> protect(mutex);
250
251 std::exception_ptr error;
252
253 auto f = FindStorage(uri);
254 if (f.directory->storage != nullptr) {
255 try {
256 return f.directory->storage->GetInfo(f.uri, follow);
257 } catch (...) {
258 error = std::current_exception();
259 }
260 }
261
262 const Directory *directory = f.directory->Find(f.uri);
263 if (directory != nullptr)
264 return StorageFileInfo(StorageFileInfo::Type::DIRECTORY);
265
266 if (error)
267 std::rethrow_exception(error);
268 else
269 throw std::runtime_error("No such file or directory");
270 }
271
272 std::unique_ptr<StorageDirectoryReader>
OpenDirectory(std::string_view uri)273 CompositeStorage::OpenDirectory(std::string_view uri)
274 {
275 const std::scoped_lock<Mutex> protect(mutex);
276
277 auto f = FindStorage(uri);
278 const Directory *directory = f.directory->Find(f.uri);
279 if (directory == nullptr || directory->children.empty()) {
280 /* no virtual directories here */
281
282 if (f.directory->storage == nullptr)
283 throw std::runtime_error("No such directory");
284
285 return f.directory->storage->OpenDirectory(f.uri);
286 }
287
288 std::unique_ptr<StorageDirectoryReader> other;
289
290 try {
291 other = f.directory->storage->OpenDirectory(f.uri);
292 } catch (...) {
293 }
294
295 return std::make_unique<CompositeDirectoryReader>(std::move(other),
296 directory->children);
297 }
298
299 std::string
MapUTF8(std::string_view uri) const300 CompositeStorage::MapUTF8(std::string_view uri) const noexcept
301 {
302 const std::scoped_lock<Mutex> protect(mutex);
303
304 auto f = FindStorage(uri);
305 if (f.directory->storage == nullptr)
306 return {};
307
308 return f.directory->storage->MapUTF8(f.uri);
309 }
310
311 AllocatedPath
MapFS(std::string_view uri) const312 CompositeStorage::MapFS(std::string_view uri) const noexcept
313 {
314 const std::scoped_lock<Mutex> protect(mutex);
315
316 auto f = FindStorage(uri);
317 if (f.directory->storage == nullptr)
318 return nullptr;
319
320 return f.directory->storage->MapFS(f.uri);
321 }
322
323 std::string_view
MapToRelativeUTF8(std::string_view uri) const324 CompositeStorage::MapToRelativeUTF8(std::string_view uri) const noexcept
325 {
326 const std::scoped_lock<Mutex> protect(mutex);
327
328 if (root.storage != nullptr) {
329 auto result = root.storage->MapToRelativeUTF8(uri);
330 if (result.data() != nullptr)
331 return result;
332 }
333
334 if (!root.MapToRelativeUTF8(relative_buffer, uri))
335 return {};
336
337 return relative_buffer;
338 }
339