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