1 /* Copyright  (C) 2010-2020 The RetroArch team
2 *
3 * ---------------------------------------------------------------------------------------
4 * The following license statement only applies to this file (vfs_implementation.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 <errno.h>
27 #include <sys/types.h>
28 
29 #include <string/stdstring.h>
30 
31 #ifdef HAVE_CONFIG_H
32 #include "config.h"
33 #endif
34 
35 #if defined(_WIN32)
36 #  ifdef _MSC_VER
37 #    define setmode _setmode
38 #  endif
39 #include <sys/stat.h>
40 #  ifdef _XBOX
41 #    include <xtl.h>
42 #    define INVALID_FILE_ATTRIBUTES -1
43 #  else
44 
45 #    include <fcntl.h>
46 #    include <direct.h>
47 #    include <windows.h>
48 #  endif
49 #    include <io.h>
50 #else
51 #  if defined(PSP)
52 #    include <pspiofilemgr.h>
53 #  endif
54 #  include <sys/types.h>
55 #  include <sys/stat.h>
56 #  if !defined(VITA)
57 #  include <dirent.h>
58 #  endif
59 #  include <unistd.h>
60 #  if defined(ORBIS)
61 #  include <sys/fcntl.h>
62 #  include <sys/dirent.h>
63 #  include <orbisFile.h>
64 #  endif
65 #endif
66 
67 #include <fcntl.h>
68 
69 /* TODO: Some things are duplicated but I'm really afraid of breaking other platforms by touching this */
70 #if defined(VITA)
71 #  include <psp2/io/fcntl.h>
72 #  include <psp2/io/dirent.h>
73 #  include <psp2/io/stat.h>
74 #elif defined(ORBIS)
75 #  include <orbisFile.h>
76 #  include <ps4link.h>
77 #  include <sys/dirent.h>
78 #  include <sys/fcntl.h>
79 #elif !defined(_WIN32)
80 #  if defined(PSP)
81 #    include <pspiofilemgr.h>
82 #  endif
83 #  include <sys/types.h>
84 #  include <sys/stat.h>
85 #  include <dirent.h>
86 #  include <unistd.h>
87 #endif
88 
89 #if defined(__QNX__) || defined(PSP)
90 #include <unistd.h> /* stat() is defined here */
91 #endif
92 
93 #ifdef __APPLE__
94 #include <CoreFoundation/CoreFoundation.h>
95 #endif
96 #ifdef __HAIKU__
97 #include <kernel/image.h>
98 #endif
99 #ifndef __MACH__
100 #include <compat/strl.h>
101 #include <compat/posix_string.h>
102 #endif
103 #include <compat/strcasestr.h>
104 #include <retro_miscellaneous.h>
105 #include <encodings/utf.h>
106 
107 #if defined(_WIN32)
108 #ifndef _XBOX
109 #if defined(_MSC_VER) && _MSC_VER <= 1200
110 #define INVALID_FILE_ATTRIBUTES ((DWORD)-1)
111 #endif
112 #endif
113 #elif defined(VITA)
114 #define SCE_ERROR_ERRNO_EEXIST 0x80010011
115 #include <psp2/io/fcntl.h>
116 #include <psp2/io/dirent.h>
117 #include <psp2/io/stat.h>
118 #else
119 #include <sys/types.h>
120 #include <sys/stat.h>
121 #include <unistd.h>
122 #endif
123 
124 #if defined(ORBIS)
125 #include <orbisFile.h>
126 #include <sys/fcntl.h>
127 #include <sys/dirent.h>
128 #endif
129 #if defined(PSP)
130 #include <pspkernel.h>
131 #endif
132 
133 #if defined(VITA)
134 #define FIO_S_ISDIR SCE_S_ISDIR
135 #endif
136 
137 #if defined(__QNX__) || defined(PSP)
138 #include <unistd.h> /* stat() is defined here */
139 #endif
140 
141 /* Assume W-functions do not work below Win2K and Xbox platforms */
142 #if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0500 || defined(_XBOX)
143 
144 #ifndef LEGACY_WIN32
145 #define LEGACY_WIN32
146 #endif
147 
148 #endif
149 
150 #if defined(_WIN32)
151 #if !defined(_MSC_VER) || (defined(_MSC_VER) && _MSC_VER >= 1400)
152 #define ATLEAST_VC2005
153 #endif
154 #endif
155 
156 #include <vfs/vfs_implementation.h>
157 #include <libretro.h>
158 #include <memmap.h>
159 #include <encodings/utf.h>
160 #include <compat/fopen_utf8.h>
161 #include <file/file_path.h>
162 
163 #ifdef HAVE_CDROM
164 #include <vfs/vfs_implementation_cdrom.h>
165 #endif
166 
167 #if (defined(_POSIX_C_SOURCE) && (_POSIX_C_SOURCE - 0) >= 200112) || (defined(__POSIX_VISIBLE) && __POSIX_VISIBLE >= 200112) || (defined(_POSIX_VERSION) && _POSIX_VERSION >= 200112) || __USE_LARGEFILE
168 #ifndef HAVE_64BIT_OFFSETS
169 #define HAVE_64BIT_OFFSETS
170 #endif
171 #endif
172 
173 #define RFILE_HINT_UNBUFFERED (1 << 8)
174 
retro_vfs_file_seek_internal(libretro_vfs_implementation_file * stream,int64_t offset,int whence)175 int64_t retro_vfs_file_seek_internal(
176       libretro_vfs_implementation_file *stream,
177       int64_t offset, int whence)
178 {
179    if (!stream)
180       return -1;
181 
182    if ((stream->hints & RFILE_HINT_UNBUFFERED) == 0)
183    {
184 #ifdef HAVE_CDROM
185       if (stream->scheme == VFS_SCHEME_CDROM)
186          return retro_vfs_file_seek_cdrom(stream, offset, whence);
187 #endif
188 #ifdef ATLEAST_VC2005
189       /* VC2005 and up have a special 64-bit fseek */
190       return _fseeki64(stream->fp, offset, whence);
191 #elif defined(ORBIS)
192       {
193          int ret = orbisLseek(stream->fd, offset, whence);
194          if (ret < 0)
195             return -1;
196          return 0;
197       }
198 #elif defined(HAVE_64BIT_OFFSETS)
199       return fseeko(stream->fp, (off_t)offset, whence);
200 #else
201       return fseek(stream->fp, (long)offset, whence);
202 #endif
203    }
204 #ifdef HAVE_MMAP
205    /* Need to check stream->mapped because this function is
206     * called in filestream_open() */
207    if (stream->mapped && stream->hints &
208          RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS)
209    {
210       /* fseek() returns error on under/overflow but
211        * allows cursor > EOF for
212        read-only file descriptors. */
213       switch (whence)
214       {
215          case SEEK_SET:
216             if (offset < 0)
217                return -1;
218 
219             stream->mappos = offset;
220             break;
221 
222          case SEEK_CUR:
223             if (  (offset < 0 && stream->mappos + offset > stream->mappos) ||
224                   (offset > 0 && stream->mappos + offset < stream->mappos))
225                return -1;
226 
227             stream->mappos += offset;
228             break;
229 
230          case SEEK_END:
231             if (stream->mapsize + offset < stream->mapsize)
232                return -1;
233 
234             stream->mappos = stream->mapsize + offset;
235             break;
236       }
237       return stream->mappos;
238    }
239 #endif
240 
241    if (lseek(stream->fd, offset, whence) < 0)
242       return -1;
243 
244    return 0;
245 }
246 
247 /**
248  * retro_vfs_file_open_impl:
249  * @path               : path to file
250  * @mode               : file mode to use when opening (read/write)
251  * @hints              :
252  *
253  * Opens a file for reading or writing, depending on the requested mode.
254  * Returns a pointer to an RFILE if opened successfully, otherwise NULL.
255  **/
256 
retro_vfs_file_open_impl(const char * path,unsigned mode,unsigned hints)257 libretro_vfs_implementation_file *retro_vfs_file_open_impl(
258       const char *path, unsigned mode, unsigned hints)
259 {
260 #if defined(VFS_FRONTEND) || defined(HAVE_CDROM)
261    int                             path_len = (int)strlen(path);
262 #endif
263 #ifdef VFS_FRONTEND
264    const char                 *dumb_prefix  = "vfsonly://";
265    size_t                   dumb_prefix_siz = STRLEN_CONST("vfsonly://");
266    int                      dumb_prefix_len = (int)dumb_prefix_siz;
267 #endif
268 #ifdef HAVE_CDROM
269    const char *cdrom_prefix                 = "cdrom://";
270    size_t cdrom_prefix_siz                  = STRLEN_CONST("cdrom://");
271    int cdrom_prefix_len                     = (int)cdrom_prefix_siz;
272 #endif
273    int                                flags = 0;
274    const char                     *mode_str = NULL;
275    libretro_vfs_implementation_file *stream =
276       (libretro_vfs_implementation_file*)
277       malloc(sizeof(*stream));
278 
279    if (!stream)
280       return NULL;
281 
282    stream->fd                     = 0;
283    stream->hints                  = hints;
284    stream->size                   = 0;
285    stream->buf                    = NULL;
286    stream->fp                     = NULL;
287 #ifdef _WIN32
288    stream->fh                     = 0;
289 #endif
290    stream->orig_path              = NULL;
291    stream->mappos                 = 0;
292    stream->mapsize                = 0;
293    stream->mapped                 = NULL;
294    stream->scheme                 = VFS_SCHEME_NONE;
295 
296 #ifdef VFS_FRONTEND
297    if (path_len >= dumb_prefix_len)
298       if (!memcmp(path, dumb_prefix, dumb_prefix_len))
299          path             += dumb_prefix_siz;
300 #endif
301 
302 #ifdef HAVE_CDROM
303    stream->cdrom.cue_buf          = NULL;
304    stream->cdrom.cue_len          = 0;
305    stream->cdrom.byte_pos         = 0;
306    stream->cdrom.drive            = 0;
307    stream->cdrom.cur_min          = 0;
308    stream->cdrom.cur_sec          = 0;
309    stream->cdrom.cur_frame        = 0;
310    stream->cdrom.cur_track        = 0;
311    stream->cdrom.cur_lba          = 0;
312    stream->cdrom.last_frame_lba   = 0;
313    stream->cdrom.last_frame[0]    = '\0';
314    stream->cdrom.last_frame_valid = false;
315 
316    if (path_len > cdrom_prefix_len)
317    {
318       if (!memcmp(path, cdrom_prefix, cdrom_prefix_len))
319       {
320          path             += cdrom_prefix_siz;
321          stream->scheme    = VFS_SCHEME_CDROM;
322       }
323    }
324 #endif
325 
326    stream->orig_path       = strdup(path);
327 
328 #ifdef HAVE_MMAP
329    if (stream->hints & RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS && mode == RETRO_VFS_FILE_ACCESS_READ)
330       stream->hints |= RFILE_HINT_UNBUFFERED;
331    else
332 #endif
333       stream->hints &= ~RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS;
334 
335    switch (mode)
336    {
337       case RETRO_VFS_FILE_ACCESS_READ:
338          mode_str = "rb";
339 
340          flags    = O_RDONLY;
341 #ifdef _WIN32
342          flags   |= O_BINARY;
343 #endif
344          break;
345 
346       case RETRO_VFS_FILE_ACCESS_WRITE:
347          mode_str = "wb";
348 
349          flags    = O_WRONLY | O_CREAT | O_TRUNC;
350 #if !defined(ORBIS)
351 #if !defined(_WIN32)
352          flags   |= S_IRUSR | S_IWUSR;
353 #else
354          flags   |= O_BINARY;
355 #endif
356 #endif
357          break;
358 
359       case RETRO_VFS_FILE_ACCESS_READ_WRITE:
360          mode_str = "w+b";
361          flags    = O_RDWR | O_CREAT | O_TRUNC;
362 #if !defined(ORBIS)
363 #if !defined(_WIN32)
364          flags   |= S_IRUSR | S_IWUSR;
365 #else
366          flags   |= O_BINARY;
367 #endif
368 #endif
369          break;
370 
371       case RETRO_VFS_FILE_ACCESS_WRITE | RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING:
372       case RETRO_VFS_FILE_ACCESS_READ_WRITE | RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING:
373          mode_str = "r+b";
374 
375          flags    = O_RDWR;
376 #if !defined(ORBIS)
377 #if !defined(_WIN32)
378          flags   |= S_IRUSR | S_IWUSR;
379 #else
380          flags   |= O_BINARY;
381 #endif
382 #endif
383          break;
384 
385       default:
386          goto error;
387    }
388 
389    if ((stream->hints & RFILE_HINT_UNBUFFERED) == 0)
390    {
391 #ifdef ORBIS
392       int fd = orbisOpen(path, flags, 0644);
393       if (fd < 0)
394       {
395          stream->fd = -1;
396          goto error;
397       }
398       stream->fd    = fd;
399 #else
400       FILE *fp;
401 #ifdef HAVE_CDROM
402       if (stream->scheme == VFS_SCHEME_CDROM)
403       {
404          retro_vfs_file_open_cdrom(stream, path, mode, hints);
405 #if defined(_WIN32) && !defined(_XBOX)
406          if (!stream->fh)
407             goto error;
408 #else
409          if (!stream->fp)
410             goto error;
411 #endif
412       }
413       else
414 #endif
415       {
416          fp = (FILE*)fopen_utf8(path, mode_str);
417 
418          if (!fp)
419             goto error;
420 
421          stream->fp  = fp;
422       }
423       /* Regarding setvbuf:
424        *
425        * https://www.freebsd.org/cgi/man.cgi?query=setvbuf&apropos=0&sektion=0&manpath=FreeBSD+11.1-RELEASE&arch=default&format=html
426        *
427        * If the size argument is not zero but buf is NULL,
428        * a buffer of the given size will be allocated immediately, and
429        * released on close. This is an extension to ANSI C.
430        *
431        * Since C89 does not support specifying a NULL buffer
432        * with a non-zero size, we create and track our own buffer for it.
433        */
434       /* TODO: this is only useful for a few platforms,
435        * find which and add ifdef */
436 #if !defined(PSP)
437       if (stream->scheme != VFS_SCHEME_CDROM)
438       {
439          stream->buf = (char*)calloc(1, 0x4000);
440          if (stream->fp)
441             setvbuf(stream->fp, stream->buf, _IOFBF, 0x4000);
442       }
443 #endif
444 #endif
445    }
446    else
447    {
448 #if defined(_WIN32) && !defined(_XBOX)
449 #if defined(LEGACY_WIN32)
450       char *path_local    = utf8_to_local_string_alloc(path);
451       stream->fd          = open(path_local, flags, 0);
452       if (path_local)
453          free(path_local);
454 #else
455       wchar_t * path_wide = utf8_to_utf16_string_alloc(path);
456       stream->fd          = _wopen(path_wide, flags, 0);
457       if (path_wide)
458          free(path_wide);
459 #endif
460 #else
461       stream->fd          = open(path, flags, 0);
462 #endif
463 
464       if (stream->fd == -1)
465          goto error;
466 
467 #ifdef HAVE_MMAP
468       if (stream->hints & RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS)
469       {
470          stream->mappos  = 0;
471          stream->mapped  = NULL;
472          stream->mapsize = retro_vfs_file_seek_internal(stream, 0, SEEK_END);
473 
474          if (stream->mapsize == (uint64_t)-1)
475             goto error;
476 
477          retro_vfs_file_seek_internal(stream, 0, SEEK_SET);
478 
479          stream->mapped = (uint8_t*)mmap((void*)0,
480                stream->mapsize, PROT_READ,  MAP_SHARED, stream->fd, 0);
481 
482          if (stream->mapped == MAP_FAILED)
483             stream->hints &= ~RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS;
484       }
485 #endif
486    }
487 #ifdef ORBIS
488    stream->size = orbisLseek(stream->fd, 0, SEEK_END);
489    orbisLseek(stream->fd, 0, SEEK_SET);
490 #else
491 #ifdef HAVE_CDROM
492    if (stream->scheme == VFS_SCHEME_CDROM)
493    {
494       retro_vfs_file_seek_cdrom(stream, 0, SEEK_SET);
495       retro_vfs_file_seek_cdrom(stream, 0, SEEK_END);
496 
497       stream->size = retro_vfs_file_tell_impl(stream);
498 
499       retro_vfs_file_seek_cdrom(stream, 0, SEEK_SET);
500    }
501    else
502 #endif
503    {
504       retro_vfs_file_seek_internal(stream, 0, SEEK_SET);
505       retro_vfs_file_seek_internal(stream, 0, SEEK_END);
506 
507       stream->size = retro_vfs_file_tell_impl(stream);
508 
509       retro_vfs_file_seek_internal(stream, 0, SEEK_SET);
510    }
511 #endif
512    return stream;
513 
514 error:
515    retro_vfs_file_close_impl(stream);
516    return NULL;
517 }
518 
retro_vfs_file_close_impl(libretro_vfs_implementation_file * stream)519 int retro_vfs_file_close_impl(libretro_vfs_implementation_file *stream)
520 {
521    if (!stream)
522       return -1;
523 
524 #ifdef HAVE_CDROM
525    if (stream->scheme == VFS_SCHEME_CDROM)
526    {
527       retro_vfs_file_close_cdrom(stream);
528       goto end;
529    }
530 #endif
531 
532    if ((stream->hints & RFILE_HINT_UNBUFFERED) == 0)
533    {
534       if (stream->fp)
535          fclose(stream->fp);
536    }
537    else
538    {
539 #ifdef HAVE_MMAP
540       if (stream->hints & RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS)
541          munmap(stream->mapped, stream->mapsize);
542 #endif
543    }
544 
545    if (stream->fd > 0)
546    {
547 #ifdef ORBIS
548       orbisClose(stream->fd);
549       stream->fd = -1;
550 #else
551       close(stream->fd);
552 #endif
553    }
554 #ifdef HAVE_CDROM
555 end:
556    if (stream->cdrom.cue_buf)
557       free(stream->cdrom.cue_buf);
558 #endif
559    if (stream->buf)
560       free(stream->buf);
561 
562    if (stream->orig_path)
563       free(stream->orig_path);
564 
565    free(stream);
566 
567    return 0;
568 }
569 
retro_vfs_file_error_impl(libretro_vfs_implementation_file * stream)570 int retro_vfs_file_error_impl(libretro_vfs_implementation_file *stream)
571 {
572 #ifdef HAVE_CDROM
573    if (stream->scheme == VFS_SCHEME_CDROM)
574       return retro_vfs_file_error_cdrom(stream);
575 #endif
576 #ifdef ORBIS
577    /* TODO/FIXME - implement this? */
578    return 0;
579 #else
580    return ferror(stream->fp);
581 #endif
582 }
583 
retro_vfs_file_size_impl(libretro_vfs_implementation_file * stream)584 int64_t retro_vfs_file_size_impl(libretro_vfs_implementation_file *stream)
585 {
586    if (stream)
587       return stream->size;
588    return 0;
589 }
590 
retro_vfs_file_truncate_impl(libretro_vfs_implementation_file * stream,int64_t length)591 int64_t retro_vfs_file_truncate_impl(libretro_vfs_implementation_file *stream, int64_t length)
592 {
593    if (!stream)
594       return -1;
595 
596 #ifdef _WIN32
597    if (_chsize(_fileno(stream->fp), length) != 0)
598       return -1;
599 #elif !defined(VITA) && !defined(PSP) && !defined(PS2) && !defined(ORBIS) && (!defined(SWITCH) || defined(HAVE_LIBNX))
600    if (ftruncate(fileno(stream->fp), length) != 0)
601       return -1;
602 #endif
603 
604    return 0;
605 }
606 
retro_vfs_file_tell_impl(libretro_vfs_implementation_file * stream)607 int64_t retro_vfs_file_tell_impl(libretro_vfs_implementation_file *stream)
608 {
609    if (!stream)
610       return -1;
611 
612    if ((stream->hints & RFILE_HINT_UNBUFFERED) == 0)
613    {
614 #ifdef HAVE_CDROM
615       if (stream->scheme == VFS_SCHEME_CDROM)
616          return retro_vfs_file_tell_cdrom(stream);
617 #endif
618 #ifdef ORBIS
619       {
620          int64_t ret = orbisLseek(stream->fd, 0, SEEK_CUR);
621          if (ret < 0)
622             return -1;
623          return ret;
624       }
625 #else
626 #ifdef ATLEAST_VC2005
627       /* VC2005 and up have a special 64-bit ftell */
628       return _ftelli64(stream->fp);
629 #elif defined(HAVE_64BIT_OFFSETS)
630       return ftello(stream->fp);
631 #else
632       return ftell(stream->fp);
633 #endif
634 #endif
635    }
636 #ifdef HAVE_MMAP
637    /* Need to check stream->mapped because this function
638     * is called in filestream_open() */
639    if (stream->mapped && stream->hints &
640          RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS)
641       return stream->mappos;
642 #endif
643    if (lseek(stream->fd, 0, SEEK_CUR) < 0)
644       return -1;
645 
646    return 0;
647 }
648 
retro_vfs_file_seek_impl(libretro_vfs_implementation_file * stream,int64_t offset,int seek_position)649 int64_t retro_vfs_file_seek_impl(libretro_vfs_implementation_file *stream,
650       int64_t offset, int seek_position)
651 {
652    int whence = -1;
653    switch (seek_position)
654    {
655       case RETRO_VFS_SEEK_POSITION_START:
656          whence = SEEK_SET;
657          break;
658       case RETRO_VFS_SEEK_POSITION_CURRENT:
659          whence = SEEK_CUR;
660          break;
661       case RETRO_VFS_SEEK_POSITION_END:
662          whence = SEEK_END;
663          break;
664    }
665 
666    return retro_vfs_file_seek_internal(stream, offset, whence);
667 }
668 
retro_vfs_file_read_impl(libretro_vfs_implementation_file * stream,void * s,uint64_t len)669 int64_t retro_vfs_file_read_impl(libretro_vfs_implementation_file *stream,
670       void *s, uint64_t len)
671 {
672    if (!stream || !s)
673       return -1;
674 
675    if ((stream->hints & RFILE_HINT_UNBUFFERED) == 0)
676    {
677 #ifdef HAVE_CDROM
678       if (stream->scheme == VFS_SCHEME_CDROM)
679          return retro_vfs_file_read_cdrom(stream, s, len);
680 #endif
681 #ifdef ORBIS
682       if (orbisRead(stream->fd, s, (size_t)len) < 0)
683          return -1;
684       return 0;
685 #else
686       return fread(s, 1, (size_t)len, stream->fp);
687 #endif
688    }
689 #ifdef HAVE_MMAP
690    if (stream->hints & RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS)
691    {
692       if (stream->mappos > stream->mapsize)
693          return -1;
694 
695       if (stream->mappos + len > stream->mapsize)
696          len = stream->mapsize - stream->mappos;
697 
698       memcpy(s, &stream->mapped[stream->mappos], len);
699       stream->mappos += len;
700 
701       return len;
702    }
703 #endif
704 
705    return read(stream->fd, s, (size_t)len);
706 }
707 
retro_vfs_file_write_impl(libretro_vfs_implementation_file * stream,const void * s,uint64_t len)708 int64_t retro_vfs_file_write_impl(libretro_vfs_implementation_file *stream, const void *s, uint64_t len)
709 {
710    if (!stream)
711       return -1;
712 
713    if ((stream->hints & RFILE_HINT_UNBUFFERED) == 0)
714    {
715 #ifdef ORBIS
716       if (orbisWrite(stream->fd, s, (size_t)len) < 0)
717          return -1;
718       return 0;
719 #else
720       return fwrite(s, 1, (size_t)len, stream->fp);
721 #endif
722    }
723 
724 #ifdef HAVE_MMAP
725    if (stream->hints & RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS)
726       return -1;
727 #endif
728    return write(stream->fd, s, (size_t)len);
729 }
730 
retro_vfs_file_flush_impl(libretro_vfs_implementation_file * stream)731 int retro_vfs_file_flush_impl(libretro_vfs_implementation_file *stream)
732 {
733    if (!stream)
734       return -1;
735 #ifdef ORBIS
736    return 0;
737 #else
738    return fflush(stream->fp) == 0 ? 0 : -1;
739 #endif
740 }
741 
retro_vfs_file_remove_impl(const char * path)742 int retro_vfs_file_remove_impl(const char *path)
743 {
744 #if defined(_WIN32) && !defined(_XBOX)
745    /* Win32 (no Xbox) */
746 
747 #if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0500
748    char *path_local    = NULL;
749 #else
750    wchar_t *path_wide  = NULL;
751 #endif
752    if (!path || !*path)
753       return -1;
754 #if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0500
755    path_local = utf8_to_local_string_alloc(path);
756 
757    if (path_local)
758    {
759       int ret = remove(path_local);
760       free(path_local);
761 
762       if (ret == 0)
763          return 0;
764    }
765 #else
766    path_wide = utf8_to_utf16_string_alloc(path);
767 
768    if (path_wide)
769    {
770       int ret = _wremove(path_wide);
771       free(path_wide);
772 
773       if (ret == 0)
774          return 0;
775    }
776 #endif
777    return -1;
778 #elif defined(ORBIS)
779    /* Orbis
780     * TODO/FIXME - stub for now */
781    return 0;
782 #else
783    if (remove(path) == 0)
784       return 0;
785    return -1;
786 #endif
787 }
788 
retro_vfs_file_rename_impl(const char * old_path,const char * new_path)789 int retro_vfs_file_rename_impl(const char *old_path, const char *new_path)
790 {
791 #if defined(_WIN32) && !defined(_XBOX)
792    /* Win32 (no Xbox) */
793    int ret                 = -1;
794 #if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0500
795    char *old_path_local    = NULL;
796 #else
797    wchar_t *old_path_wide  = NULL;
798 #endif
799 
800    if (!old_path || !*old_path || !new_path || !*new_path)
801       return -1;
802 
803 #if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0500
804    old_path_local = utf8_to_local_string_alloc(old_path);
805 
806    if (old_path_local)
807    {
808       char *new_path_local = utf8_to_local_string_alloc(new_path);
809 
810       if (new_path_local)
811       {
812          if (rename(old_path_local, new_path_local) == 0)
813             ret = 0;
814          free(new_path_local);
815       }
816 
817       free(old_path_local);
818    }
819 #else
820    old_path_wide = utf8_to_utf16_string_alloc(old_path);
821 
822    if (old_path_wide)
823    {
824       wchar_t *new_path_wide = utf8_to_utf16_string_alloc(new_path);
825 
826       if (new_path_wide)
827       {
828          if (_wrename(old_path_wide, new_path_wide) == 0)
829             ret = 0;
830          free(new_path_wide);
831       }
832 
833       free(old_path_wide);
834    }
835 #endif
836    return ret;
837 
838 #elif defined(ORBIS)
839    /* Orbis */
840    /* TODO/FIXME - Stub for now */
841    if (!old_path || !*old_path || !new_path || !*new_path)
842       return -1;
843    return 0;
844 
845 #else
846    /* Every other platform */
847    if (!old_path || !*old_path || !new_path || !*new_path)
848       return -1;
849    return rename(old_path, new_path) == 0 ? 0 : -1;
850 #endif
851 }
852 
retro_vfs_file_get_path_impl(libretro_vfs_implementation_file * stream)853 const char *retro_vfs_file_get_path_impl(
854       libretro_vfs_implementation_file *stream)
855 {
856    /* should never happen, do something noisy so caller can be fixed */
857    if (!stream)
858       abort();
859    return stream->orig_path;
860 }
861 
retro_vfs_stat_impl(const char * path,int32_t * size)862 int retro_vfs_stat_impl(const char *path, int32_t *size)
863 {
864    bool is_dir               = false;
865    bool is_character_special = false;
866 #if defined(VITA) || defined(PSP)
867    /* Vita / PSP */
868    SceIoStat buf;
869    int dir_ret;
870    char *tmp                 = NULL;
871    size_t len                = 0;
872 
873    if (!path || !*path)
874       return 0;
875 
876    tmp                       = strdup(path);
877    len                       = strlen(tmp);
878    if (tmp[len-1] == '/')
879       tmp[len-1] = '\0';
880 
881    dir_ret                   = sceIoGetstat(tmp, &buf);
882    free(tmp);
883    if (dir_ret < 0)
884       return 0;
885 
886    if (size)
887       *size                  = (int32_t)buf.st_size;
888 
889    is_dir                    = FIO_S_ISDIR(buf.st_mode);
890 #elif defined(ORBIS)
891    /* Orbis */
892    int dir_ret               = 0;
893 
894    if (!path || !*path)
895       return 0;
896 
897    if (size)
898       *size                  = (int32_t)buf.st_size;
899 
900    dir_ret                   = orbisDopen(path);
901    is_dir                    = dir_ret > 0;
902    orbisDclose(dir_ret);
903 
904    is_character_special      = S_ISCHR(buf.st_mode);
905 #elif defined(_WIN32)
906    /* Windows */
907    DWORD file_info;
908    struct _stat buf;
909 #if defined(LEGACY_WIN32)
910    char *path_local          = NULL;
911 #else
912    wchar_t *path_wide        = NULL;
913 #endif
914 
915    if (!path || !*path)
916       return 0;
917 
918 #if defined(LEGACY_WIN32)
919    path_local                = utf8_to_local_string_alloc(path);
920    file_info                 = GetFileAttributes(path_local);
921 
922    if (!string_is_empty(path_local))
923       _stat(path_local, &buf);
924 
925    if (path_local)
926       free(path_local);
927 #else
928    path_wide                 = utf8_to_utf16_string_alloc(path);
929    file_info                 = GetFileAttributesW(path_wide);
930 
931    _wstat(path_wide, &buf);
932 
933    if (path_wide)
934       free(path_wide);
935 #endif
936 
937    if (file_info == INVALID_FILE_ATTRIBUTES)
938       return 0;
939 
940    if (size)
941       *size = (int32_t)buf.st_size;
942 
943    is_dir = (file_info & FILE_ATTRIBUTE_DIRECTORY);
944 #else
945    /* Every other platform */
946    struct stat buf;
947 
948    if (!path || !*path)
949       return 0;
950    if (stat(path, &buf) < 0)
951       return 0;
952 
953    if (size)
954       *size             = (int32_t)buf.st_size;
955 
956    is_dir               = S_ISDIR(buf.st_mode);
957    is_character_special = S_ISCHR(buf.st_mode);
958 #endif
959    return RETRO_VFS_STAT_IS_VALID | (is_dir ? RETRO_VFS_STAT_IS_DIRECTORY : 0) | (is_character_special ? RETRO_VFS_STAT_IS_CHARACTER_SPECIAL : 0);
960 }
961 
962 #if defined(VITA)
963 #define path_mkdir_error(ret) (((ret) == SCE_ERROR_ERRNO_EEXIST))
964 #elif defined(PSP) || defined(PS2) || defined(_3DS) || defined(WIIU) || defined(SWITCH) || defined(ORBIS)
965 #define path_mkdir_error(ret) ((ret) == -1)
966 #else
967 #define path_mkdir_error(ret) ((ret) < 0 && errno == EEXIST)
968 #endif
969 
retro_vfs_mkdir_impl(const char * dir)970 int retro_vfs_mkdir_impl(const char *dir)
971 {
972 #if defined(_WIN32)
973 #ifdef LEGACY_WIN32
974    int ret        = _mkdir(dir);
975 #else
976    wchar_t *dir_w = utf8_to_utf16_string_alloc(dir);
977    int       ret  = -1;
978 
979    if (dir_w)
980    {
981       ret = _wmkdir(dir_w);
982       free(dir_w);
983    }
984 #endif
985 #elif defined(IOS)
986    int ret = mkdir(dir, 0755);
987 #elif defined(VITA) || defined(PSP)
988    int ret = sceIoMkdir(dir, 0777);
989 #elif defined(ORBIS)
990    int ret = orbisMkdir(dir, 0755);
991 #elif defined(__QNX__)
992    int ret = mkdir(dir, 0777);
993 #else
994    int ret = mkdir(dir, 0750);
995 #endif
996 
997    if (path_mkdir_error(ret))
998       return -2;
999    return ret < 0 ? -1 : 0;
1000 }
1001 
1002 #ifdef VFS_FRONTEND
1003 struct retro_vfs_dir_handle
1004 #else
1005 struct libretro_vfs_implementation_dir
1006 #endif
1007 {
1008    char* orig_path;
1009 #if defined(_WIN32)
1010 #if defined(LEGACY_WIN32)
1011    WIN32_FIND_DATA entry;
1012 #else
1013    WIN32_FIND_DATAW entry;
1014 #endif
1015    HANDLE directory;
1016    bool next;
1017    char path[PATH_MAX_LENGTH];
1018 #elif defined(VITA) || defined(PSP)
1019    SceUID directory;
1020    SceIoDirent entry;
1021 #elif defined(ORBIS)
1022    int directory;
1023    struct dirent entry;
1024 #else
1025    DIR *directory;
1026    const struct dirent *entry;
1027 #endif
1028 };
1029 
dirent_check_error(libretro_vfs_implementation_dir * rdir)1030 static bool dirent_check_error(libretro_vfs_implementation_dir *rdir)
1031 {
1032 #if defined(_WIN32)
1033    return (rdir->directory == INVALID_HANDLE_VALUE);
1034 #elif defined(VITA) || defined(PSP) || defined(ORBIS)
1035    return (rdir->directory < 0);
1036 #else
1037    return !(rdir->directory);
1038 #endif
1039 }
1040 
retro_vfs_opendir_impl(const char * name,bool include_hidden)1041 libretro_vfs_implementation_dir *retro_vfs_opendir_impl(
1042       const char *name, bool include_hidden)
1043 {
1044 #if defined(_WIN32)
1045    unsigned path_len;
1046    char path_buf[1024];
1047    size_t copied      = 0;
1048 #if defined(LEGACY_WIN32)
1049    char *path_local   = NULL;
1050 #else
1051    wchar_t *path_wide = NULL;
1052 #endif
1053 #endif
1054    libretro_vfs_implementation_dir *rdir;
1055 
1056    /*Reject null or empty string paths*/
1057    if (!name || (*name == 0))
1058       return NULL;
1059 
1060    /*Allocate RDIR struct. Tidied later with retro_closedir*/
1061    rdir = (libretro_vfs_implementation_dir*)calloc(1, sizeof(*rdir));
1062    if (!rdir)
1063       return NULL;
1064 
1065    rdir->orig_path       = strdup(name);
1066 
1067 #if defined(_WIN32)
1068    path_buf[0]           = '\0';
1069    path_len              = strlen(name);
1070 
1071    copied                = strlcpy(path_buf, name, sizeof(path_buf));
1072 
1073    /* Non-NT platforms don't like extra slashes in the path */
1074    if (name[path_len - 1] != '\\')
1075       path_buf[copied++]   = '\\';
1076 
1077    path_buf[copied]        = '*';
1078    path_buf[copied+1]      = '\0';
1079 
1080 #if defined(LEGACY_WIN32)
1081    path_local              = utf8_to_local_string_alloc(path_buf);
1082    rdir->directory         = FindFirstFile(path_local, &rdir->entry);
1083 
1084    if (path_local)
1085       free(path_local);
1086 #else
1087    path_wide               = utf8_to_utf16_string_alloc(path_buf);
1088    rdir->directory         = FindFirstFileW(path_wide, &rdir->entry);
1089 
1090    if (path_wide)
1091       free(path_wide);
1092 #endif
1093 
1094 #elif defined(VITA) || defined(PSP)
1095    rdir->directory       = sceIoDopen(name);
1096 #elif defined(_3DS)
1097    rdir->directory       = !string_is_empty(name) ? opendir(name) : NULL;
1098    rdir->entry           = NULL;
1099 #elif defined(ORBIS)
1100    rdir->directory       = orbisDopen(name);
1101 #else
1102    rdir->directory       = opendir(name);
1103    rdir->entry           = NULL;
1104 #endif
1105 
1106 #ifdef _WIN32
1107    if (include_hidden)
1108       rdir->entry.dwFileAttributes |= FILE_ATTRIBUTE_HIDDEN;
1109    else
1110       rdir->entry.dwFileAttributes &= ~FILE_ATTRIBUTE_HIDDEN;
1111 #endif
1112 
1113    if (rdir->directory && !dirent_check_error(rdir))
1114       return rdir;
1115 
1116    retro_vfs_closedir_impl(rdir);
1117    return NULL;
1118 }
1119 
retro_vfs_readdir_impl(libretro_vfs_implementation_dir * rdir)1120 bool retro_vfs_readdir_impl(libretro_vfs_implementation_dir *rdir)
1121 {
1122 #if defined(_WIN32)
1123    if (rdir->next)
1124 #if defined(LEGACY_WIN32)
1125       return (FindNextFile(rdir->directory, &rdir->entry) != 0);
1126 #else
1127       return (FindNextFileW(rdir->directory, &rdir->entry) != 0);
1128 #endif
1129 
1130    rdir->next = true;
1131    return (rdir->directory != INVALID_HANDLE_VALUE);
1132 #elif defined(VITA) || defined(PSP)
1133    return (sceIoDread(rdir->directory, &rdir->entry) > 0);
1134 #elif defined(ORBIS)
1135    return (orbisDread(rdir->directory, &rdir->entry) > 0);
1136 #else
1137    return ((rdir->entry = readdir(rdir->directory)) != NULL);
1138 #endif
1139 }
1140 
retro_vfs_dirent_get_name_impl(libretro_vfs_implementation_dir * rdir)1141 const char *retro_vfs_dirent_get_name_impl(libretro_vfs_implementation_dir *rdir)
1142 {
1143 #if defined(_WIN32)
1144 #if defined(LEGACY_WIN32)
1145    char *name       = local_to_utf8_string_alloc(rdir->entry.cFileName);
1146 #else
1147    char *name       = utf16_to_utf8_string_alloc(rdir->entry.cFileName);
1148 #endif
1149    memset(rdir->entry.cFileName, 0, sizeof(rdir->entry.cFileName));
1150    strlcpy((char*)rdir->entry.cFileName, name, sizeof(rdir->entry.cFileName));
1151    if (name)
1152       free(name);
1153    return (char*)rdir->entry.cFileName;
1154 #elif defined(VITA) || defined(PSP) || defined(ORBIS)
1155    return rdir->entry.d_name;
1156 #else
1157    if (!rdir || !rdir->entry)
1158       return NULL;
1159    return rdir->entry->d_name;
1160 #endif
1161 }
1162 
retro_vfs_dirent_is_dir_impl(libretro_vfs_implementation_dir * rdir)1163 bool retro_vfs_dirent_is_dir_impl(libretro_vfs_implementation_dir *rdir)
1164 {
1165 #if defined(_WIN32)
1166    const WIN32_FIND_DATA *entry = (const WIN32_FIND_DATA*)&rdir->entry;
1167    return entry->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
1168 #elif defined(PSP) || defined(VITA)
1169    const SceIoDirent *entry     = (const SceIoDirent*)&rdir->entry;
1170 #if defined(PSP)
1171    return (entry->d_stat.st_attr & FIO_SO_IFDIR) == FIO_SO_IFDIR;
1172 #elif defined(VITA)
1173    return SCE_S_ISDIR(entry->d_stat.st_mode);
1174 #endif
1175 #elif defined(ORBIS)
1176    const struct dirent *entry   = &rdir->entry;
1177    if (entry->d_type == DT_DIR)
1178       return true;
1179    if (!(entry->d_type == DT_UNKNOWN || entry->d_type == DT_LNK))
1180       return false;
1181 #else
1182    struct stat buf;
1183    char path[PATH_MAX_LENGTH];
1184 #if defined(DT_DIR)
1185    const struct dirent *entry = (const struct dirent*)rdir->entry;
1186    if (entry->d_type == DT_DIR)
1187       return true;
1188    /* This can happen on certain file systems. */
1189    if (!(entry->d_type == DT_UNKNOWN || entry->d_type == DT_LNK))
1190       return false;
1191 #endif
1192    /* dirent struct doesn't have d_type, do it the slow way ... */
1193    path[0] = '\0';
1194    fill_pathname_join(path, rdir->orig_path, retro_vfs_dirent_get_name_impl(rdir), sizeof(path));
1195    if (stat(path, &buf) < 0)
1196       return false;
1197    return S_ISDIR(buf.st_mode);
1198 #endif
1199 }
1200 
retro_vfs_closedir_impl(libretro_vfs_implementation_dir * rdir)1201 int retro_vfs_closedir_impl(libretro_vfs_implementation_dir *rdir)
1202 {
1203    if (!rdir)
1204       return -1;
1205 
1206 #if defined(_WIN32)
1207    if (rdir->directory != INVALID_HANDLE_VALUE)
1208       FindClose(rdir->directory);
1209 #elif defined(VITA) || defined(PSP)
1210    sceIoDclose(rdir->directory);
1211 #elif defined(ORBIS)
1212    orbisDclose(rdir->directory);
1213 #else
1214    if (rdir->directory)
1215       closedir(rdir->directory);
1216 #endif
1217 
1218    if (rdir->orig_path)
1219       free(rdir->orig_path);
1220    free(rdir);
1221    return 0;
1222 }
1223