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