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