1 /*
2 * vfs.c
3 * Copyright 2006-2013 Ariadne Conill, Daniel Barkalow, Ralf Ertzinger,
4 * Yoshiki Yazawa, Matti Hämäläinen, and John Lindgren
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions are met:
8 *
9 * 1. Redistributions of source code must retain the above copyright notice,
10 * this list of conditions, and the following disclaimer.
11 *
12 * 2. Redistributions in binary form must reproduce the above copyright notice,
13 * this list of conditions, and the following disclaimer in the documentation
14 * provided with the distribution.
15 *
16 * This software is provided "as is" and without any warranty, express or
17 * implied. In no event shall the authors be liable for any damages arising from
18 * the use of this software.
19 */
20
21 #include "vfs.h"
22
23 #define __STDC_FORMAT_MACROS
24 #include <inttypes.h>
25 #include <string.h>
26
27 #include "audstrings.h"
28 #include "i18n.h"
29 #include "internal.h"
30 #include "plugin.h"
31 #include "plugins-internal.h"
32 #include "probe-buffer.h"
33 #include "runtime.h"
34 #include "vfs_local.h"
35
36 /* embedded plugins */
37 static LocalTransport local_transport;
38 static StdinTransport stdin_transport;
39
lookup_transport(const char * filename,String & error,bool * custom_input=nullptr)40 static TransportPlugin * lookup_transport(const char * filename, String & error,
41 bool * custom_input = nullptr)
42 {
43 StringBuf scheme = uri_get_scheme(filename);
44
45 if (!scheme || !strcmp(scheme, "file"))
46 return &local_transport;
47 if (!strcmp(scheme, "stdin"))
48 return &stdin_transport;
49
50 for (PluginHandle * plugin : aud_plugin_list(PluginType::Transport))
51 {
52 if (!aud_plugin_get_enabled(plugin))
53 continue;
54
55 if (transport_plugin_has_scheme(plugin, scheme))
56 {
57 auto tp = (TransportPlugin *)aud_plugin_get_header(plugin);
58 if (tp)
59 return tp;
60 }
61 }
62
63 if (custom_input)
64 {
65 for (PluginHandle * plugin : aud_plugin_list(PluginType::Input))
66 {
67 if (!aud_plugin_get_enabled(plugin))
68 continue;
69
70 if (input_plugin_has_key(plugin, InputKey::Scheme, scheme))
71 {
72 *custom_input = true;
73 return nullptr;
74 }
75 }
76 }
77
78 AUDERR("Unknown URI scheme: %s://\n", (const char *)scheme);
79 error = String(_("Unknown URI scheme"));
80 return nullptr;
81 }
82
83 /**
84 * Opens a stream from a VFS transport using one of the registered
85 * #VFSConstructor handlers.
86 *
87 * @param path The path or URI to open.
88 * @param mode The preferred access privileges (not guaranteed).
89 * @return On success, a #VFSFile object representing the stream.
90 */
VFSFile(const char * filename,const char * mode)91 EXPORT VFSFile::VFSFile(const char * filename, const char * mode)
92 {
93 auto tp = lookup_transport(filename, m_error);
94 if (!tp)
95 return;
96
97 VFSImpl * impl = tp->fopen(strip_subtune(filename), mode, m_error);
98 if (!impl)
99 return;
100
101 /* enable buffering for read-only handles */
102 if (mode[0] == 'r' && !strchr(mode, '+'))
103 impl = new ProbeBuffer(filename, impl);
104
105 AUDINFO("<%p> open (mode %s) %s\n", impl, mode, filename);
106 m_filename = String(filename);
107 m_impl.capture(impl);
108 }
109
tmpfile()110 EXPORT VFSFile VFSFile::tmpfile()
111 {
112 VFSFile file;
113 file.m_impl.capture(vfs_tmpfile(file.m_error));
114 return file;
115 }
116
117 /**
118 * Reads from a VFS stream.
119 *
120 * @param ptr A pointer to the destination buffer.
121 * @param size The size of each element to read.
122 * @param nmemb The number of elements to read.
123 * @param file #VFSFile object that represents the VFS stream.
124 * @return The number of elements successfully read.
125 */
fread(void * ptr,int64_t size,int64_t nmemb)126 EXPORT int64_t VFSFile::fread(void * ptr, int64_t size, int64_t nmemb)
127 {
128 int64_t readed = m_impl->fread(ptr, size, nmemb);
129
130 AUDDBG("<%p> read %" PRId64 " elements of size %" PRId64 " = %" PRId64 "\n",
131 m_impl.get(), nmemb, size, readed);
132
133 return readed;
134 }
135
136 /**
137 * Writes to a VFS stream.
138 *
139 * @param ptr A const pointer to the source buffer.
140 * @param size The size of each element to write.
141 * @param nmemb The number of elements to write.
142 * @param file #VFSFile object that represents the VFS stream.
143 * @return The number of elements successfully written.
144 */
fwrite(const void * ptr,int64_t size,int64_t nmemb)145 EXPORT int64_t VFSFile::fwrite(const void * ptr, int64_t size, int64_t nmemb)
146 {
147 int64_t written = m_impl->fwrite(ptr, size, nmemb);
148
149 AUDDBG("<%p> write %" PRId64 " elements of size %" PRId64 " = %" PRId64
150 "\n",
151 m_impl.get(), nmemb, size, written);
152
153 return written;
154 }
155
156 /**
157 * Performs a seek in given VFS stream. Standard C-style values
158 * of whence can be used to indicate desired action.
159 *
160 * - SEEK_CUR seeks relative to current stream position.
161 * - SEEK_SET seeks to given absolute position (relative to stream beginning).
162 * - SEEK_END sets stream position to current file end.
163 *
164 * @param file #VFSFile object that represents the VFS stream.
165 * @param offset The offset to seek to.
166 * @param whence Type of the seek: SEEK_CUR, SEEK_SET or SEEK_END.
167 * @return On success, 0. Otherwise, -1.
168 */
fseek(int64_t offset,VFSSeekType whence)169 EXPORT int VFSFile::fseek(int64_t offset, VFSSeekType whence)
170 {
171 AUDDBG("<%p> seek to %" PRId64 " from %s\n", m_impl.get(), offset,
172 whence == VFS_SEEK_CUR
173 ? "current"
174 : whence == VFS_SEEK_SET
175 ? "beginning"
176 : whence == VFS_SEEK_END ? "end" : "invalid");
177
178 if (m_impl->fseek(offset, whence) == 0)
179 return 0;
180
181 AUDDBG("<%p> seek failed!\n", m_impl.get());
182
183 return -1;
184 }
185
186 /**
187 * Returns the current position in the VFS stream's buffer.
188 *
189 * @param file #VFSFile object that represents the VFS stream.
190 * @return On success, the current position. Otherwise, -1.
191 */
ftell()192 EXPORT int64_t VFSFile::ftell()
193 {
194 int64_t told = m_impl->ftell();
195
196 AUDDBG("<%p> tell = %" PRId64 "\n", m_impl.get(), told);
197
198 return told;
199 }
200
201 /**
202 * Returns whether or not the VFS stream has reached EOF.
203 *
204 * @param file #VFSFile object that represents the VFS stream.
205 * @return On success, whether or not the VFS stream is at EOF. Otherwise,
206 * false.
207 */
feof()208 EXPORT bool VFSFile::feof()
209 {
210 bool eof = m_impl->feof();
211
212 AUDDBG("<%p> eof = %s\n", m_impl.get(), eof ? "yes" : "no");
213
214 return eof;
215 }
216
217 /**
218 * Truncates a VFS stream to a certain size.
219 *
220 * @param file #VFSFile object that represents the VFS stream.
221 * @param length The length to truncate at.
222 * @return On success, 0. Otherwise, -1.
223 */
ftruncate(int64_t length)224 EXPORT int VFSFile::ftruncate(int64_t length)
225 {
226 AUDDBG("<%p> truncate to %" PRId64 "\n", m_impl.get(), length);
227
228 if (m_impl->ftruncate(length) == 0)
229 return 0;
230
231 AUDDBG("<%p> truncate failed!\n", m_impl.get());
232
233 return -1;
234 }
235
fflush()236 EXPORT int VFSFile::fflush()
237 {
238 AUDDBG("<%p> flush\n", m_impl.get());
239
240 if (m_impl->fflush() == 0)
241 return 0;
242
243 AUDDBG("<%p> flush failed!\n", m_impl.get());
244
245 return -1;
246 }
247
248 /**
249 * Returns size of the file.
250 *
251 * @param file #VFSFile object that represents the VFS stream.
252 * @return On success, the size of the file in bytes. Otherwise, -1.
253 */
fsize()254 EXPORT int64_t VFSFile::fsize()
255 {
256 int64_t size = m_impl->fsize();
257
258 AUDDBG("<%p> size = %" PRId64 "\n", m_impl.get(), size);
259
260 return size;
261 }
262
263 /**
264 * Returns metadata about the stream.
265 *
266 * @param file #VFSFile object that represents the VFS stream.
267 * @param field The string constant field name to get.
268 * @return On success, a copy of the value of the field. Otherwise, nullptr.
269 */
get_metadata(const char * field)270 EXPORT String VFSFile::get_metadata(const char * field)
271 {
272 return m_impl->get_metadata(field);
273 }
274
set_limit_to_buffer(bool limit)275 EXPORT void VFSFile::set_limit_to_buffer(bool limit)
276 {
277 auto buffer = dynamic_cast<ProbeBuffer *>(m_impl.get());
278 if (buffer)
279 buffer->set_limit_to_buffer(limit);
280 else
281 AUDERR("<%p> buffering not supported!\n", m_impl.get());
282 }
283
read_all()284 EXPORT Index<char> VFSFile::read_all()
285 {
286 constexpr int maxbuf = 16777216;
287 constexpr int pagesize = 4096;
288
289 Index<char> buf;
290 int64_t size = fsize();
291 int64_t pos = ftell();
292
293 if (size >= 0 && pos >= 0 && pos <= size)
294 {
295 buf.insert(0, aud::min(size - pos, (int64_t)maxbuf));
296 size = fread(buf.begin(), 1, buf.len());
297 }
298 else
299 {
300 size = 0;
301
302 buf.insert(0, pagesize);
303
304 int64_t readsize;
305 while ((readsize = fread(&buf[size], 1, buf.len() - size)))
306 {
307 size += readsize;
308
309 if (size == buf.len())
310 {
311 if (buf.len() > maxbuf - pagesize)
312 break;
313
314 buf.insert(-1, pagesize);
315 }
316 }
317 }
318
319 buf.remove(size, -1);
320
321 return buf;
322 }
323
copy_from(VFSFile & source,int64_t size)324 EXPORT bool VFSFile::copy_from(VFSFile & source, int64_t size)
325 {
326 constexpr int bufsize = 65536;
327
328 Index<char> buf;
329 buf.resize(bufsize);
330
331 while (size < 0 || size > 0)
332 {
333 int64_t to_read = (size > 0 && size < bufsize) ? size : bufsize;
334 int64_t readsize = source.fread(buf.begin(), 1, to_read);
335
336 if (size > 0)
337 size -= readsize;
338
339 if (fwrite(buf.begin(), 1, readsize) != readsize)
340 return false;
341
342 if (readsize < to_read)
343 break;
344 }
345
346 /* if a fixed size was requested, return true only if all the data was read.
347 * otherwise, return true only if the end of the source file was reached. */
348 return size == 0 || (size < 0 && source.feof());
349 }
350
replace_with(VFSFile & source)351 EXPORT bool VFSFile::replace_with(VFSFile & source)
352 {
353 if (source.fseek(0, VFS_SEEK_SET) < 0)
354 return false;
355
356 if (fseek(0, VFS_SEEK_SET) < 0)
357 return false;
358
359 if (ftruncate(0) < 0)
360 return false;
361
362 return copy_from(source, -1);
363 }
364
test_file(const char * filename,VFSFileTest test)365 EXPORT bool VFSFile::test_file(const char * filename, VFSFileTest test)
366 {
367 String error; /* discarded */
368 return test_file(filename, test, error) == test;
369 }
370
test_file(const char * filename,VFSFileTest test,String & error)371 EXPORT VFSFileTest VFSFile::test_file(const char * filename, VFSFileTest test,
372 String & error)
373 {
374 bool custom_input = false;
375 auto tp = lookup_transport(filename, error, &custom_input);
376
377 /* for URI schemes handled by input plugins, return 0, indicating that we
378 * have no way of testing file attributes */
379 if (custom_input)
380 return VFSFileTest(0);
381
382 /* for unsupported URI schemes, return VFS_NO_ACCESS */
383 if (!tp)
384 return VFSFileTest(test & VFS_NO_ACCESS);
385
386 return tp->test_file(strip_subtune(filename), test, error);
387 }
388
read_folder(const char * filename,String & error)389 EXPORT Index<String> VFSFile::read_folder(const char * filename, String & error)
390 {
391 auto tp = lookup_transport(filename, error);
392 return tp ? tp->read_folder(filename, error) : Index<String>();
393 }
394
read_file(const char * filename,VFSReadOptions options)395 EXPORT Index<char> VFSFile::read_file(const char * filename,
396 VFSReadOptions options)
397 {
398 Index<char> text;
399
400 if (!(options & VFS_IGNORE_MISSING) || test_file(filename, VFS_EXISTS))
401 {
402 VFSFile file(filename, "r");
403 if (file)
404 text = file.read_all();
405 else
406 AUDERR("Cannot open %s for reading: %s\n", filename, file.error());
407 }
408
409 if ((options & VFS_APPEND_NULL))
410 text.append(0);
411
412 return text;
413 }
414
write_file(const char * filename,const void * data,int64_t len)415 EXPORT bool VFSFile::write_file(const char * filename, const void * data,
416 int64_t len)
417 {
418 bool written = false;
419
420 VFSFile file(filename, "w");
421 if (file)
422 written = (file.fwrite(data, 1, len) == len && file.fflush() == 0);
423 else
424 AUDERR("Cannot open %s for writing: %s\n", filename, file.error());
425
426 return written;
427 }
428
supported_uri_schemes()429 EXPORT Index<const char *> VFSFile::supported_uri_schemes()
430 {
431 Index<const char *> schemes;
432
433 schemes.append("file");
434 schemes.append("stdin");
435
436 for (PluginHandle * plugin : aud_plugin_list(PluginType::Transport))
437 {
438 if (!aud_plugin_get_enabled(plugin))
439 continue;
440
441 for (auto & s : transport_plugin_get_schemes(plugin))
442 schemes.append((const char *)s);
443 }
444
445 schemes.append(nullptr);
446
447 return schemes;
448 }
449