1 /* Copyright  (C) 2010-2018 The RetroArch team
2  *
3  * ---------------------------------------------------------------------------------------
4  * The following license statement only applies to this file (file_stream.c).
5  * ---------------------------------------------------------------------------------------
6  *
7  * Permission is hereby granted, free of charge,
8  * to any person obtaining a copy of this software and associated documentation files (the "Software"),
9  * to deal in the Software without restriction, including without limitation the rights to
10  * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
11  * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
16  * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
19  * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21  */
22 
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <stdarg.h>
27 #include <ctype.h>
28 #include <errno.h>
29 
30 #ifdef HAVE_CONFIG_H
31 #include "config.h"
32 #endif
33 
34 #include <streams/file_stream.h>
35 #define VFS_FRONTEND
36 #include <vfs/vfs_implementation.h>
37 
38 static const int64_t vfs_error_return_value      = -1;
39 
40 static retro_vfs_get_path_t filestream_get_path_cb = NULL;
41 static retro_vfs_open_t filestream_open_cb         = NULL;
42 static retro_vfs_close_t filestream_close_cb       = NULL;
43 static retro_vfs_size_t filestream_size_cb         = NULL;
44 static retro_vfs_truncate_t filestream_truncate_cb = NULL;
45 static retro_vfs_tell_t filestream_tell_cb         = NULL;
46 static retro_vfs_seek_t filestream_seek_cb         = NULL;
47 static retro_vfs_read_t filestream_read_cb         = NULL;
48 static retro_vfs_write_t filestream_write_cb       = NULL;
49 static retro_vfs_flush_t filestream_flush_cb       = NULL;
50 static retro_vfs_remove_t filestream_remove_cb     = NULL;
51 static retro_vfs_rename_t filestream_rename_cb     = NULL;
52 
53 struct RFILE
54 {
55    struct retro_vfs_file_handle *hfile;
56 	bool error_flag;
57 	bool eof_flag;
58 };
59 
60 /* VFS Initialization */
61 
filestream_vfs_init(const struct retro_vfs_interface_info * vfs_info)62 void filestream_vfs_init(const struct retro_vfs_interface_info* vfs_info)
63 {
64    const struct retro_vfs_interface* vfs_iface;
65 
66    filestream_get_path_cb = NULL;
67    filestream_open_cb     = NULL;
68    filestream_close_cb    = NULL;
69    filestream_tell_cb     = NULL;
70    filestream_size_cb     = NULL;
71    filestream_truncate_cb = NULL;
72    filestream_seek_cb     = NULL;
73    filestream_read_cb     = NULL;
74    filestream_write_cb    = NULL;
75    filestream_flush_cb    = NULL;
76    filestream_remove_cb   = NULL;
77    filestream_rename_cb   = NULL;
78 
79    vfs_iface              = vfs_info->iface;
80 
81    if (vfs_info->required_interface_version < FILESTREAM_REQUIRED_VFS_VERSION
82          || !vfs_iface)
83       return;
84 
85    filestream_get_path_cb = vfs_iface->get_path;
86    filestream_open_cb     = vfs_iface->open;
87    filestream_close_cb    = vfs_iface->close;
88    filestream_size_cb     = vfs_iface->size;
89    filestream_truncate_cb = vfs_iface->truncate;
90    filestream_tell_cb     = vfs_iface->tell;
91    filestream_seek_cb     = vfs_iface->seek;
92    filestream_read_cb     = vfs_iface->read;
93    filestream_write_cb    = vfs_iface->write;
94    filestream_flush_cb    = vfs_iface->flush;
95    filestream_remove_cb   = vfs_iface->remove;
96    filestream_rename_cb   = vfs_iface->rename;
97 }
98 
99 /* Callback wrappers */
filestream_exists(const char * path)100 bool filestream_exists(const char *path)
101 {
102    RFILE *dummy              = NULL;
103 
104    if (!path || !*path)
105       return false;
106 
107    dummy = filestream_open(path,
108          RETRO_VFS_FILE_ACCESS_READ,
109          RETRO_VFS_FILE_ACCESS_HINT_NONE);
110 
111    if (!dummy)
112       return false;
113 
114    filestream_close(dummy);
115    return true;
116 }
117 
filestream_get_size(RFILE * stream)118 int64_t filestream_get_size(RFILE *stream)
119 {
120    int64_t output;
121 
122    if (filestream_size_cb != NULL)
123       output = filestream_size_cb(stream->hfile);
124    else
125       output = retro_vfs_file_size_impl((libretro_vfs_implementation_file*)stream->hfile);
126 
127    if (output == vfs_error_return_value)
128       stream->error_flag = true;
129 
130    return output;
131 }
132 
filestream_truncate(RFILE * stream,int64_t length)133 int64_t filestream_truncate(RFILE *stream, int64_t length)
134 {
135    int64_t output;
136 
137    if (filestream_truncate_cb != NULL)
138       output = filestream_truncate_cb(stream->hfile, length);
139    else
140       output = retro_vfs_file_truncate_impl((libretro_vfs_implementation_file*)stream->hfile, length);
141 
142    if (output == vfs_error_return_value)
143       stream->error_flag = true;
144 
145    return output;
146 }
147 
148 /**
149  * filestream_open:
150  * @path               : path to file
151  * @mode               : file mode to use when opening (read/write)
152  * @hints              :
153  *
154  * Opens a file for reading or writing, depending on the requested mode.
155  * Returns a pointer to an RFILE if opened successfully, otherwise NULL.
156  **/
filestream_open(const char * path,unsigned mode,unsigned hints)157 RFILE* filestream_open(const char *path, unsigned mode, unsigned hints)
158 {
159    struct retro_vfs_file_handle  *fp = NULL;
160    RFILE* output                     = NULL;
161 
162    if (filestream_open_cb != NULL)
163       fp = (struct retro_vfs_file_handle*)
164          filestream_open_cb(path, mode, hints);
165    else
166       fp = (struct retro_vfs_file_handle*)
167          retro_vfs_file_open_impl(path, mode, hints);
168 
169    if (!fp)
170       return NULL;
171 
172    output             = (RFILE*)malloc(sizeof(RFILE));
173    output->error_flag = false;
174    output->eof_flag   = false;
175    output->hfile      = fp;
176    return output;
177 }
178 
filestream_gets(RFILE * stream,char * s,size_t len)179 char* filestream_gets(RFILE *stream, char *s, size_t len)
180 {
181    int c   = 0;
182    char *p = s;
183    if (!stream)
184       return NULL;
185 
186    /* get max bytes or up to a newline */
187 
188    for (len--; len > 0; len--)
189    {
190       if ((c = filestream_getc(stream)) == EOF)
191          break;
192       *p++ = c;
193       if (c == '\n')
194          break;
195    }
196    *p = 0;
197 
198    if (p == s && c == EOF)
199       return NULL;
200    return (s);
201 }
202 
filestream_getc(RFILE * stream)203 int filestream_getc(RFILE *stream)
204 {
205    char c = 0;
206    if (!stream)
207       return EOF;
208    if (filestream_read(stream, &c, 1) == 1)
209       return (int)(unsigned char)c;
210    return EOF;
211 }
212 
filestream_scanf(RFILE * stream,const char * format,...)213 int filestream_scanf(RFILE *stream, const char* format, ...)
214 {
215    char buf[4096];
216    char subfmt[64];
217    va_list args;
218 
219    const char * bufiter = buf;
220    int64_t startpos     = filestream_tell(stream);
221    int        ret       = 0;
222    int64_t maxlen       = filestream_read(stream, buf, sizeof(buf)-1);
223 
224    if (maxlen <= 0)
225       return EOF;
226 
227    buf[maxlen] = '\0';
228 
229    va_start(args, format);
230 
231    while (*format)
232    {
233       if (*format == '%')
234       {
235          int sublen;
236 
237          char* subfmtiter = subfmt;
238          bool asterisk    = false;
239 
240          *subfmtiter++    = *format++; /* '%' */
241 
242          /* %[*][width][length]specifier */
243 
244          if (*format == '*')
245          {
246             asterisk = true;
247             *subfmtiter++ = *format++;
248          }
249 
250          while (isdigit(*format)) *subfmtiter++ = *format++; /* width */
251 
252          /* length */
253          if (*format == 'h' || *format == 'l')
254          {
255             if (format[1] == format[0]) *subfmtiter++ = *format++;
256             *subfmtiter++ = *format++;
257          }
258          else if (*format == 'j' || *format == 'z' || *format == 't' || *format == 'L')
259          {
260             *subfmtiter++ = *format++;
261          }
262 
263          /* specifier - always a single character (except ]) */
264          if (*format == '[')
265          {
266             while (*format != ']') *subfmtiter++ = *format++;
267             *subfmtiter++ = *format++;
268          }
269          else *subfmtiter++ = *format++;
270 
271          *subfmtiter++ = '%';
272          *subfmtiter++ = 'n';
273          *subfmtiter++ = '\0';
274 
275          if (sizeof(void*) != sizeof(long*)) abort(); /* all pointers must have the same size */
276          if (asterisk)
277          {
278             if (sscanf(bufiter, subfmt, &sublen) != 0) break;
279          }
280          else
281          {
282             if (sscanf(bufiter, subfmt, va_arg(args, void*), &sublen) != 1) break;
283          }
284 
285          ret++;
286          bufiter += sublen;
287       }
288       else if (isspace(*format))
289       {
290          while (isspace(*bufiter)) bufiter++;
291          format++;
292       }
293       else
294       {
295          if (*bufiter != *format)
296             break;
297          bufiter++;
298          format++;
299       }
300    }
301 
302    va_end(args);
303    filestream_seek(stream, startpos+(bufiter-buf), RETRO_VFS_SEEK_POSITION_START);
304 
305    return ret;
306 }
307 
filestream_seek(RFILE * stream,int64_t offset,int seek_position)308 int64_t filestream_seek(RFILE *stream, int64_t offset, int seek_position)
309 {
310    int64_t output;
311 
312    if (filestream_seek_cb != NULL)
313       output = filestream_seek_cb(stream->hfile, offset, seek_position);
314    else
315       output = retro_vfs_file_seek_impl((libretro_vfs_implementation_file*)stream->hfile, offset, seek_position);
316 
317    if (output == vfs_error_return_value)
318       stream->error_flag = true;
319    stream->eof_flag = false;
320 
321    return output;
322 }
323 
filestream_eof(RFILE * stream)324 int filestream_eof(RFILE *stream)
325 {
326    return stream->eof_flag;
327 }
328 
filestream_tell(RFILE * stream)329 int64_t filestream_tell(RFILE *stream)
330 {
331    int64_t output;
332 
333    if (filestream_size_cb != NULL)
334       output = filestream_tell_cb(stream->hfile);
335    else
336       output = retro_vfs_file_tell_impl((libretro_vfs_implementation_file*)stream->hfile);
337 
338    if (output == vfs_error_return_value)
339       stream->error_flag = true;
340 
341    return output;
342 }
343 
filestream_rewind(RFILE * stream)344 void filestream_rewind(RFILE *stream)
345 {
346    if (!stream)
347       return;
348    filestream_seek(stream, 0L, RETRO_VFS_SEEK_POSITION_START);
349    stream->error_flag = false;
350    stream->eof_flag = false;
351 }
352 
filestream_read(RFILE * stream,void * s,int64_t len)353 int64_t filestream_read(RFILE *stream, void *s, int64_t len)
354 {
355    int64_t output;
356 
357    if (filestream_read_cb != NULL)
358       output = filestream_read_cb(stream->hfile, s, len);
359    else
360       output = retro_vfs_file_read_impl(
361             (libretro_vfs_implementation_file*)stream->hfile, s, len);
362 
363    if (output == vfs_error_return_value)
364       stream->error_flag = true;
365    if (output < len)
366       stream->eof_flag = true;
367 
368    return output;
369 }
370 
filestream_flush(RFILE * stream)371 int filestream_flush(RFILE *stream)
372 {
373    int output;
374 
375    if (filestream_flush_cb != NULL)
376       output = filestream_flush_cb(stream->hfile);
377    else
378       output = retro_vfs_file_flush_impl((libretro_vfs_implementation_file*)stream->hfile);
379 
380    if (output == vfs_error_return_value)
381       stream->error_flag = true;
382 
383    return output;
384 }
385 
filestream_delete(const char * path)386 int filestream_delete(const char *path)
387 {
388    if (filestream_remove_cb != NULL)
389       return filestream_remove_cb(path);
390 
391    return retro_vfs_file_remove_impl(path);
392 }
393 
filestream_rename(const char * old_path,const char * new_path)394 int filestream_rename(const char *old_path, const char *new_path)
395 {
396    if (filestream_rename_cb != NULL)
397       return filestream_rename_cb(old_path, new_path);
398 
399    return retro_vfs_file_rename_impl(old_path, new_path);
400 }
401 
filestream_get_path(RFILE * stream)402 const char* filestream_get_path(RFILE *stream)
403 {
404    if (filestream_get_path_cb != NULL)
405       return filestream_get_path_cb(stream->hfile);
406 
407    return retro_vfs_file_get_path_impl((libretro_vfs_implementation_file*)stream->hfile);
408 }
409 
filestream_write(RFILE * stream,const void * s,int64_t len)410 int64_t filestream_write(RFILE *stream, const void *s, int64_t len)
411 {
412    int64_t output;
413 
414    if (filestream_write_cb != NULL)
415       output = filestream_write_cb(stream->hfile, s, len);
416    else
417       output = retro_vfs_file_write_impl((libretro_vfs_implementation_file*)stream->hfile, s, len);
418 
419    if (output == vfs_error_return_value)
420       stream->error_flag = true;
421 
422    return output;
423 }
424 
filestream_putc(RFILE * stream,int c)425 int filestream_putc(RFILE *stream, int c)
426 {
427    char c_char = (char)c;
428    if (!stream)
429       return EOF;
430    return filestream_write(stream, &c_char, 1)==1 ? (int)(unsigned char)c : EOF;
431 }
432 
filestream_vprintf(RFILE * stream,const char * format,va_list args)433 int filestream_vprintf(RFILE *stream, const char* format, va_list args)
434 {
435    static char buffer[8 * 1024];
436    int64_t num_chars = vsprintf(buffer, format, args);
437 
438    if (num_chars < 0)
439       return -1;
440    else if (num_chars == 0)
441       return 0;
442 
443    return (int)filestream_write(stream, buffer, num_chars);
444 }
445 
filestream_printf(RFILE * stream,const char * format,...)446 int filestream_printf(RFILE *stream, const char* format, ...)
447 {
448    va_list vl;
449    int result;
450    va_start(vl, format);
451    result = filestream_vprintf(stream, format, vl);
452    va_end(vl);
453    return result;
454 }
455 
filestream_error(RFILE * stream)456 int filestream_error(RFILE *stream)
457 {
458    if (stream && stream->error_flag)
459       return 1;
460    return 0;
461 }
462 
filestream_close(RFILE * stream)463 int filestream_close(RFILE *stream)
464 {
465    int output;
466    struct retro_vfs_file_handle* fp = stream->hfile;
467 
468    if (filestream_close_cb != NULL)
469       output = filestream_close_cb(fp);
470    else
471       output = retro_vfs_file_close_impl((libretro_vfs_implementation_file*)fp);
472 
473    if (output == 0)
474       free(stream);
475 
476    return output;
477 }
478 
479 /**
480  * filestream_read_file:
481  * @path             : path to file.
482  * @buf              : buffer to allocate and read the contents of the
483  *                     file into. Needs to be freed manually.
484  *
485  * Read the contents of a file into @buf.
486  *
487  * Returns: number of items read, -1 on error.
488  */
filestream_read_file(const char * path,void ** buf,int64_t * len)489 int64_t filestream_read_file(const char *path, void **buf, int64_t *len)
490 {
491    int64_t ret              = 0;
492    int64_t content_buf_size = 0;
493    void *content_buf        = NULL;
494    RFILE *file              = filestream_open(path,
495          RETRO_VFS_FILE_ACCESS_READ,
496          RETRO_VFS_FILE_ACCESS_HINT_NONE);
497 
498    if (!file)
499    {
500       fprintf(stderr, "Failed to open %s: %s\n", path, strerror(errno));
501       goto error;
502    }
503 
504    content_buf_size = filestream_get_size(file);
505 
506    if (content_buf_size < 0)
507       goto error;
508 
509    content_buf      = malloc((size_t)(content_buf_size + 1));
510 
511    if (!content_buf)
512       goto error;
513    if ((int64_t)(uint64_t)(content_buf_size + 1) != (content_buf_size + 1))
514       goto error;
515 
516    ret = filestream_read(file, content_buf, (int64_t)content_buf_size);
517    if (ret < 0)
518    {
519       fprintf(stderr, "Failed to read %s: %s\n", path, strerror(errno));
520       goto error;
521    }
522 
523    filestream_close(file);
524 
525    *buf    = content_buf;
526 
527    /* Allow for easy reading of strings to be safe.
528     * Will only work with sane character formatting (Unix). */
529    ((char*)content_buf)[ret] = '\0';
530 
531    if (len)
532       *len = ret;
533 
534    return 1;
535 
536 error:
537    if (file)
538       filestream_close(file);
539    if (content_buf)
540       free(content_buf);
541    if (len)
542       *len = -1;
543    *buf = NULL;
544    return 0;
545 }
546 
547 /**
548  * filestream_write_file:
549  * @path             : path to file.
550  * @data             : contents to write to the file.
551  * @size             : size of the contents.
552  *
553  * Writes data to a file.
554  *
555  * Returns: true (1) on success, false (0) otherwise.
556  */
filestream_write_file(const char * path,const void * data,int64_t size)557 bool filestream_write_file(const char *path, const void *data, int64_t size)
558 {
559    int64_t ret   = 0;
560    RFILE *file   = filestream_open(path,
561          RETRO_VFS_FILE_ACCESS_WRITE,
562          RETRO_VFS_FILE_ACCESS_HINT_NONE);
563    if (!file)
564       return false;
565 
566    ret = filestream_write(file, data, size);
567    filestream_close(file);
568 
569    if (ret != size)
570       return false;
571 
572    return true;
573 }
574 
575 /* Returned pointer must be freed by the caller. */
filestream_getline(RFILE * stream)576 char* filestream_getline(RFILE *stream)
577 {
578    char *newline_tmp  = NULL;
579    size_t cur_size    = 8;
580    size_t idx         = 0;
581    int in             = 0;
582    char *newline      = (char*)malloc(9);
583 
584    if (!stream || !newline)
585    {
586       if (newline)
587          free(newline);
588       return NULL;
589    }
590 
591    in                 = filestream_getc(stream);
592 
593    while (in != EOF && in != '\n')
594    {
595       if (idx == cur_size)
596       {
597          cur_size    *= 2;
598          newline_tmp  = (char*)realloc(newline, cur_size + 1);
599 
600          if (!newline_tmp)
601          {
602             free(newline);
603             return NULL;
604          }
605 
606          newline     = newline_tmp;
607       }
608 
609       newline[idx++] = in;
610       in             = filestream_getc(stream);
611    }
612 
613    newline[idx]      = '\0';
614    return newline;
615 }
616 
filestream_get_vfs_handle(RFILE * stream)617 libretro_vfs_implementation_file* filestream_get_vfs_handle(RFILE *stream)
618 {
619    return (libretro_vfs_implementation_file*)stream->hfile;
620 }
621