mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-17 00:10:31 +02:00
The libretro-common submodule pinned the standalone repository at 76a3d54. It is replaced by an in-tree copy of RetroArch master (b655a20) holding only what the core uses: the VFS, file, string, encoding, compat and time sources it already built, the headers they and the core include, the threading set - rthreads, retro_atomic, retro_spsc and retro_waitable_spsc, mpsc_stack, retro_triple_buffer, retro_eventcount, retro_asym_eventcount, retro_procbarrier and tpool, with features_cpu and rstrtod, which they need - and the codecs that replace the bundled zlib, zstd, libchdr, libzip and libpng: rinflate and rdeflate, rzstd, rchd with its LZMA and FLAC decoders, rzip_archive, rpng and crc32. stdstring.c, which nothing calls, is no longer built. The threading sources are always built, by both the static Makefile and CMake; the VFS set stays behind HAVE_LIBRETRO_VFS as before. The codecs are built by the change that switches the core over to them.
4158 lines
130 KiB
C
4158 lines
130 KiB
C
/* Copyright (C) 2010-2020 The RetroArch team
|
|
*
|
|
* ---------------------------------------------------------------------------------------
|
|
* The following license statement only applies to this file (vfs_implementation.c).
|
|
* ---------------------------------------------------------------------------------------
|
|
*
|
|
* Permission is hereby granted, free of charge,
|
|
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
|
|
* to deal in the Software without restriction, including without limitation the rights to
|
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
|
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
|
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
#include <retro_posix_source.h>
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <limits.h> /* INT_MAX, LONG_MAX -- both C89 */
|
|
#include <sys/types.h>
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#if defined(_WIN32)
|
|
# ifdef _MSC_VER
|
|
# define setmode _setmode
|
|
# endif
|
|
#include <sys/stat.h>
|
|
# ifdef _XBOX
|
|
# include <xtl.h>
|
|
# define INVALID_FILE_ATTRIBUTES -1
|
|
# else
|
|
|
|
# include <fcntl.h>
|
|
# include <direct.h>
|
|
# include <windows.h>
|
|
# endif
|
|
# include <io.h>
|
|
#else
|
|
# if defined(PSP)
|
|
# include <pspiofilemgr.h>
|
|
# endif
|
|
# include <sys/types.h>
|
|
# include <sys/stat.h>
|
|
# if !defined(VITA)
|
|
# include <dirent.h>
|
|
# endif
|
|
# include <unistd.h>
|
|
# if defined(WIIU)
|
|
# include <malloc.h>
|
|
# endif
|
|
#endif
|
|
|
|
#include <fcntl.h>
|
|
#if defined(__linux__)
|
|
#include <linux/falloc.h>
|
|
#endif
|
|
|
|
/* TODO: Some things are duplicated but I'm really afraid of breaking other platforms by touching this */
|
|
#if defined(VITA)
|
|
# include <psp2/io/fcntl.h>
|
|
# include <psp2/io/dirent.h>
|
|
# include <psp2/io/stat.h>
|
|
# include <psp2/rtc.h>
|
|
#elif !defined(_WIN32)
|
|
# if defined(PSP)
|
|
# include <pspiofilemgr.h>
|
|
# endif
|
|
# include <sys/types.h>
|
|
# include <sys/stat.h>
|
|
# include <dirent.h>
|
|
# include <unistd.h>
|
|
#endif
|
|
|
|
#if defined(__QNX__) || defined(PSP)
|
|
#include <unistd.h> /* stat() is defined here */
|
|
#endif
|
|
|
|
#ifdef __APPLE__
|
|
#include <CoreFoundation/CoreFoundation.h>
|
|
#ifdef __MACH__
|
|
#include <TargetConditionals.h>
|
|
#endif
|
|
#endif
|
|
#ifdef __HAIKU__
|
|
#include <kernel/image.h>
|
|
#endif
|
|
#ifndef __MACH__
|
|
#include <compat/strl.h>
|
|
#include <compat/posix_string.h>
|
|
#endif
|
|
#include <compat/strcasestr.h>
|
|
#include <retro_miscellaneous.h>
|
|
#include <encodings/utf.h>
|
|
|
|
#if defined(_WIN32)
|
|
#ifndef _XBOX
|
|
#if defined(_MSC_VER) && _MSC_VER <= 1200
|
|
#define INVALID_FILE_ATTRIBUTES ((DWORD)-1)
|
|
#endif
|
|
#endif
|
|
#elif defined(VITA)
|
|
#define SCE_ERROR_ERRNO_EEXIST 0x80010011
|
|
#include <psp2/io/fcntl.h>
|
|
#include <psp2/io/dirent.h>
|
|
#include <psp2/io/stat.h>
|
|
#else
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
|
|
#if defined(PSP)
|
|
#include <pspkernel.h>
|
|
#endif
|
|
|
|
#if defined(__PS3__) || defined(__PSL1GHT__)
|
|
#define FS_SUCCEEDED 0
|
|
#define FS_TYPE_DIR 1
|
|
#ifdef __PSL1GHT__
|
|
#include <lv2/sysfs.h>
|
|
#ifndef O_RDONLY
|
|
#define O_RDONLY SYS_O_RDONLY
|
|
#endif
|
|
#ifndef O_WRONLY
|
|
#define O_WRONLY SYS_O_WRONLY
|
|
#endif
|
|
#ifndef O_CREAT
|
|
#define O_CREAT SYS_O_CREAT
|
|
#endif
|
|
#ifndef O_TRUNC
|
|
#define O_TRUNC SYS_O_TRUNC
|
|
#endif
|
|
#ifndef O_RDWR
|
|
#define O_RDWR SYS_O_RDWR
|
|
#endif
|
|
#else
|
|
#include <cell/cell_fs.h>
|
|
#ifndef O_RDONLY
|
|
#define O_RDONLY CELL_FS_O_RDONLY
|
|
#endif
|
|
#ifndef O_WRONLY
|
|
#define O_WRONLY CELL_FS_O_WRONLY
|
|
#endif
|
|
#ifndef O_CREAT
|
|
#define O_CREAT CELL_FS_O_CREAT
|
|
#endif
|
|
#ifndef O_TRUNC
|
|
#define O_TRUNC CELL_FS_O_TRUNC
|
|
#endif
|
|
#ifndef O_RDWR
|
|
#define O_RDWR CELL_FS_O_RDWR
|
|
#endif
|
|
#ifndef sysFsStat
|
|
#define sysFsStat cellFsStat
|
|
#endif
|
|
#ifndef sysFSDirent
|
|
#define sysFSDirent CellFsDirent
|
|
#endif
|
|
#ifndef sysFsOpendir
|
|
#define sysFsOpendir cellFsOpendir
|
|
#endif
|
|
#ifndef sysFsReaddir
|
|
#define sysFsReaddir cellFsReaddir
|
|
#endif
|
|
#ifndef sysFSDirent
|
|
#define sysFSDirent CellFsDirent
|
|
#endif
|
|
#ifndef sysFsClosedir
|
|
#define sysFsClosedir cellFsClosedir
|
|
#endif
|
|
#endif
|
|
#endif
|
|
|
|
#if defined(VITA)
|
|
#define FIO_S_ISDIR SCE_S_ISDIR
|
|
#endif
|
|
|
|
#if defined(__QNX__) || defined(PSP)
|
|
#include <unistd.h> /* stat() is defined here */
|
|
#endif
|
|
|
|
#if defined(_WIN32)
|
|
#if defined(_MSC_VER) && _MSC_VER >= 1400
|
|
#define ATLEAST_VC2005
|
|
#endif
|
|
#endif
|
|
|
|
#include <vfs/vfs_implementation.h>
|
|
#include <libretro.h>
|
|
#if defined(HAVE_MMAP)
|
|
#include <memmap.h>
|
|
#include <sys/mman.h>
|
|
#endif
|
|
#include <encodings/utf.h>
|
|
#include <compat/fopen_utf8.h>
|
|
#include <file/file_path.h>
|
|
#include <string/stdstring.h>
|
|
|
|
/* VFS API v5 metadata operations (read-only state, modification time)
|
|
* are implemented on the platforms whose libc exposes chmod()/utimes()
|
|
* with a permission model behind them, and on Win32 via attributes.
|
|
* Everywhere else they report failure rather than pretend. */
|
|
#if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) \
|
|
|| defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) \
|
|
|| defined(__HAIKU__) || defined(__QNX__) || defined(ANDROID) \
|
|
|| defined(__sun__)
|
|
#define VFS_HAVE_POSIX_METADATA 1
|
|
#include <sys/time.h>
|
|
#endif
|
|
|
|
/* fstatat(): POSIX.1-2008. glibc, musl, bionic, the BSDs and Haiku have
|
|
* it; QNX 6.5 does not, ORBIS's FreeBSD-derived libc does not, and on
|
|
* Apple it arrived with the 10.10 SDK, which the PowerPC cross SDK
|
|
* predates. Everyone else takes the join+stat path in dirent_stat. */
|
|
#if defined(VFS_HAVE_POSIX_METADATA) && !defined(__QNX__) && !defined(ORBIS)
|
|
#if defined(__APPLE__)
|
|
#include <AvailabilityMacros.h>
|
|
#if defined(MAC_OS_X_VERSION_MIN_REQUIRED) && MAC_OS_X_VERSION_MIN_REQUIRED >= 101000
|
|
#define VFS_HAVE_FSTATAT 1
|
|
#elif !defined(MAC_OS_X_VERSION_MIN_REQUIRED) && defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE
|
|
#define VFS_HAVE_FSTATAT 1
|
|
#endif
|
|
#else
|
|
#define VFS_HAVE_FSTATAT 1
|
|
#endif
|
|
#endif
|
|
/* clonefile(): APFS constant-time copy. Needs the 10.12+ SDK and a
|
|
* deployment target that has the symbol; the PowerPC and other legacy
|
|
* SDKs have neither, and __has_include keeps them from even looking. */
|
|
#if defined(__APPLE__) && !defined(VFS_COPY_NO_FASTPATH)
|
|
#include <AvailabilityMacros.h>
|
|
#if defined(__has_include)
|
|
#if __has_include(<sys/clonefile.h>) \
|
|
&& defined(MAC_OS_X_VERSION_MIN_REQUIRED) \
|
|
&& MAC_OS_X_VERSION_MIN_REQUIRED >= 101200
|
|
#include <sys/clonefile.h>
|
|
#define VFS_HAVE_CLONEFILE 1
|
|
#endif
|
|
#endif
|
|
#endif
|
|
/* copy_file_range() through syscall(): the glibc wrapper is only
|
|
* declared under _GNU_SOURCE, which standalone consumers of this file
|
|
* need not define, and the raw syscall number has been in the kernel
|
|
* headers since 4.5. Define VFS_COPY_NO_FASTPATH to force the
|
|
* portable loop (used by the samples to test that path on a host
|
|
* that would otherwise never take it). */
|
|
#if defined(__linux__) && !defined(VFS_COPY_NO_FASTPATH)
|
|
#include <sys/syscall.h>
|
|
#if defined(SYS_copy_file_range)
|
|
#define VFS_HAVE_COPY_FILE_RANGE 1
|
|
#endif
|
|
#endif
|
|
|
|
/* Windows FILETIME is 100 ns ticks since 1601-01-01; Unix time is
|
|
* seconds since 1970-01-01. Both conversions run in uint64_t so the
|
|
* epoch offset can never be a signed-overflow UB site. */
|
|
#if defined(_WIN32)
|
|
#define VFS_FILETIME_EPOCH_DIFF 116444736000000000ULL
|
|
#define VFS_FILETIME_TICKS_PER_S 10000000ULL
|
|
static int64_t vfs_filetime_to_unix(const FILETIME *ft)
|
|
{
|
|
uint64_t t = ((uint64_t)ft->dwHighDateTime << 32) | (uint64_t)ft->dwLowDateTime;
|
|
if (t < VFS_FILETIME_EPOCH_DIFF)
|
|
return -(int64_t)((VFS_FILETIME_EPOCH_DIFF - t) / VFS_FILETIME_TICKS_PER_S);
|
|
return (int64_t)((t - VFS_FILETIME_EPOCH_DIFF) / VFS_FILETIME_TICKS_PER_S);
|
|
}
|
|
|
|
/* FILETIME covers 1601-01-01 .. ~30828 AD. Anything outside is clamped
|
|
* to the nearest end rather than wrapped; -unix_s is never formed, so
|
|
* INT64_MIN is safe. */
|
|
#define VFS_FILETIME_MIN_UNIX (-(int64_t)(VFS_FILETIME_EPOCH_DIFF / VFS_FILETIME_TICKS_PER_S))
|
|
#define VFS_FILETIME_MAX_UNIX ((int64_t)((0x7fffffffffffffffULL - VFS_FILETIME_EPOCH_DIFF) / VFS_FILETIME_TICKS_PER_S))
|
|
static void vfs_unix_to_filetime(int64_t unix_s, FILETIME *ft)
|
|
{
|
|
uint64_t t;
|
|
if (unix_s <= VFS_FILETIME_MIN_UNIX)
|
|
t = 0;
|
|
else if (unix_s >= VFS_FILETIME_MAX_UNIX)
|
|
t = 0x7fffffffffffffffULL;
|
|
else if (unix_s < 0)
|
|
t = VFS_FILETIME_EPOCH_DIFF - ((uint64_t)0 - (uint64_t)unix_s) * VFS_FILETIME_TICKS_PER_S;
|
|
else
|
|
t = VFS_FILETIME_EPOCH_DIFF + (uint64_t)unix_s * VFS_FILETIME_TICKS_PER_S;
|
|
ft->dwLowDateTime = (DWORD)(t & 0xffffffffULL);
|
|
ft->dwHighDateTime = (DWORD)(t >> 32);
|
|
}
|
|
#endif
|
|
|
|
#ifdef HAVE_CDROM
|
|
#include <vfs/vfs_implementation_cdrom.h>
|
|
#endif
|
|
|
|
#if defined(ANDROID) && defined(HAVE_SAF)
|
|
#include <vfs/vfs_implementation_saf.h>
|
|
#endif
|
|
|
|
#ifdef HAVE_SMBCLIENT
|
|
#include "vfs_implementation_smb.h"
|
|
#endif
|
|
#ifdef HAVE_NFSCLIENT
|
|
#include "vfs_implementation_nfs.h"
|
|
#endif
|
|
|
|
#if (defined(_POSIX_C_SOURCE) && (_POSIX_C_SOURCE - 0) >= 200112) || (defined(__POSIX_VISIBLE) && __POSIX_VISIBLE >= 200112) || (defined(_POSIX_VERSION) && _POSIX_VERSION >= 200112) || (defined(__USE_LARGEFILE) && __USE_LARGEFILE) || (defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64)
|
|
#ifndef HAVE_64BIT_OFFSETS
|
|
#define HAVE_64BIT_OFFSETS
|
|
#endif
|
|
#endif
|
|
|
|
#define RFILE_HINT_UNBUFFERED (1 << 8)
|
|
|
|
/* Largest count handed to one fread/fwrite/read/write call. bionic's
|
|
* stdio routes counts through an int-typed callback (aborting via
|
|
* FORTIFY above INT_MAX) and one Linux read()/write() syscall moves at
|
|
* most MAX_RW_COUNT; both fit comfortably under 1 GiB. */
|
|
#define VFS_STDIO_IO_CHUNK_MAX (1024 * 1024 * 1024)
|
|
|
|
/* 64-bit seek on the descriptor path.
|
|
*
|
|
* The buffered path seeks with _fseeki64/fseeko. The descriptor path
|
|
* - the one RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS selects, and
|
|
* the only one a memory-mapped file ever uses - seeked with lseek()
|
|
* and off_t. off_t is 32 bits on every Windows build, 64-bit ones
|
|
* included (long is 32-bit there), and on any Unix built without
|
|
* large-file support. A file of 2 GiB or more therefore could not be
|
|
* seeked, and because retro_vfs_file_open_impl() sizes a new handle by
|
|
* seeking to its end, the failure landed on stream->size and
|
|
* stream->mapsize at open: the whole file came back as size 0 or a
|
|
* negative one, and the mapping was skipped. Disc images are exactly
|
|
* the files that cross that line.
|
|
*
|
|
* The width is settled at compile time, in this order:
|
|
*
|
|
* Windows SetFilePointer with the high-order dword, on the
|
|
* handle behind the descriptor. Deliberately not the
|
|
* CRT's _lseeki64: that arrived with Visual C++ 4 and is
|
|
* exported by the msvcrt.dll of that era onwards, but
|
|
* Windows 95 shipped without an msvcrt.dll at all and
|
|
* which one a 9x box ended up with came down to what an
|
|
* installer happened to drop there. SetFilePointer has
|
|
* no such history - it is the original Win32 seek, in
|
|
* kernel32 since NT 3.1 and Windows 95 RTM, and it is
|
|
* what the CRT's own _lseeki64 calls underneath. (Not
|
|
* SetFilePointerEx, which is XP and up and would be the
|
|
* one call here that 9x could not make.) The Xbox XDKs
|
|
* have it too, alongside the _get_osfhandle this file
|
|
* already uses for the Win32 mapping path, so no console
|
|
* needs a case of its own. Note that on the original
|
|
* Xbox the buffered path is stuck at 32 bits regardless:
|
|
* that CRT has fseek(long) and no _fseeki64, so a large
|
|
* file there is reachable only through this path.
|
|
*
|
|
* INVALID_SET_FILE_POINTER is also a legitimate low
|
|
* dword for a file this size, hence the SetLastError
|
|
* dance rather than a bare comparison.
|
|
*
|
|
* Store/UWP targets, where SetFilePointer is outside the
|
|
* app API partition, take _lseeki64 instead - every CRT
|
|
* new enough to build for that target has it.
|
|
*
|
|
* Android lseek64. Bionic declares it in <unistd.h> at every
|
|
* API level with no feature macro required, which is the
|
|
* point: _FILE_OFFSET_BITS=64 is not usable on Android
|
|
* before API 24, where asking for it removes mmap() from
|
|
* the ABI rather than widening it.
|
|
*
|
|
* otherwise lseek with off_t, whose width is the build's business
|
|
* and not this file's. An earlier version of this tried
|
|
* to widen it here by defining _FILE_OFFSET_BITS and
|
|
* _LARGEFILE64_SOURCE above the includes, which was wrong
|
|
* twice: this file is #included into griffin.c as the
|
|
* 424th member of that translation unit, so "before every
|
|
* header" is not a promise it can keep, and the macros it
|
|
* set were read further down as capability tests -
|
|
* _LARGEFILE64_SOURCE selecting struct stat64/stat64(),
|
|
* which 3DS, PSP, DJGPP and Darwin do not have, and
|
|
* _FILE_OFFSET_BITS forcing HAVE_64BIT_OFFSETS and its
|
|
* fseeko/ftello onto every non-Windows target. A build
|
|
* wanting a wider off_t passes -D_FILE_OFFSET_BITS=64
|
|
* itself, which is the only place it can be set
|
|
* consistently for a whole program anyway. Where off_t
|
|
* is narrow, an offset it cannot represent is refused
|
|
* here rather than silently truncated.
|
|
*/
|
|
/* 64-bit seek on the buffered path.
|
|
*
|
|
* The existing ladder asked for _fseeki64 only under ATLEAST_VC2005
|
|
* (an _MSC_VER test, so never true for MinGW) and otherwise for
|
|
* fseeko under HAVE_64BIT_OFFSETS, whose POSIX feature macros no
|
|
* Windows toolchain defines by itself. A MinGW build that did not
|
|
* happen to pass -D_FILE_OFFSET_BITS=64 therefore fell all the way
|
|
* through to fseek(fp, (long)offset, whence) and could not seek a
|
|
* stream past 2 GiB - the same failure as the descriptor path, in the
|
|
* path callers reach without asking for any hint at all. Measured on
|
|
* an unfixed tree, both Windows targets: size -1073741824, seeks
|
|
* failing, tell reporting the truncation.
|
|
*
|
|
* Windows tiers, widest provenance last:
|
|
*
|
|
* _fseeki64 MSVC 2005 and up, and MinGW/MinGW-w64, which
|
|
* declare it and import it from msvcrt.
|
|
* fgetpos/ C89, in every CRT that has ever existed, including
|
|
* fsetpos the 2002 Xbox one - and on Windows fpos_t is a
|
|
* 64-bit scalar (__int64) rather than the struct the
|
|
* standard permits, so it carries the whole range.
|
|
* This is the tier VC6 and the Xbox XDK land on.
|
|
* SEEK_END needs a length, taken from the handle
|
|
* with GetFileSize rather than from a CRT call, for
|
|
* the same reason SetFilePointer was chosen below.
|
|
* fseek Anything else: unchanged behaviour, with an
|
|
* unrepresentable offset refused rather than
|
|
* silently truncated.
|
|
*/
|
|
#if defined(_WIN32)
|
|
#if defined(ATLEAST_VC2005) || defined(__MINGW32__)
|
|
#define RETRO_VFS_HAVE_FSEEKI64 1
|
|
#elif !defined(__STDC__) && defined(_INTEGRAL_MAX_BITS) && _INTEGRAL_MAX_BITS >= 64
|
|
#define RETRO_VFS_HAVE_FPOS64 1
|
|
#endif
|
|
#endif
|
|
|
|
#if defined(_WIN32) && !defined(RETRO_VFS_HAVE_FSEEKI64) && defined(RETRO_VFS_HAVE_FPOS64)
|
|
static int64_t retro_vfs_fp_length64(FILE *fp)
|
|
{
|
|
HANDLE h;
|
|
DWORD lo, hi = 0;
|
|
|
|
fflush(fp);
|
|
h = (HANDLE)_get_osfhandle(_fileno(fp));
|
|
if (h == INVALID_HANDLE_VALUE)
|
|
return -1;
|
|
|
|
SetLastError(NO_ERROR);
|
|
lo = GetFileSize(h, &hi);
|
|
if (lo == INVALID_FILE_SIZE && GetLastError() != NO_ERROR)
|
|
return -1;
|
|
|
|
return (int64_t)(((uint64_t)hi << 32) | (uint64_t)lo);
|
|
}
|
|
#endif
|
|
|
|
static int retro_vfs_fp_seek64(FILE *fp, int64_t offset, int whence)
|
|
{
|
|
#if defined(RETRO_VFS_HAVE_FSEEKI64)
|
|
return _fseeki64(fp, offset, whence);
|
|
#elif defined(RETRO_VFS_HAVE_FPOS64)
|
|
fpos_t pos;
|
|
|
|
switch (whence)
|
|
{
|
|
case SEEK_SET:
|
|
break;
|
|
case SEEK_CUR:
|
|
if (fgetpos(fp, &pos) != 0)
|
|
return -1;
|
|
offset += (int64_t)pos;
|
|
break;
|
|
case SEEK_END:
|
|
{
|
|
const int64_t len = retro_vfs_fp_length64(fp);
|
|
if (len < 0)
|
|
return -1;
|
|
offset += len;
|
|
}
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
|
|
if (offset < 0)
|
|
return -1;
|
|
pos = (fpos_t)offset;
|
|
return fsetpos(fp, &pos) == 0 ? 0 : -1;
|
|
#elif defined(HAVE_64BIT_OFFSETS)
|
|
return fseeko(fp, (off_t)offset, whence);
|
|
#else
|
|
if (sizeof(long) < sizeof(int64_t))
|
|
{
|
|
if ( offset > (int64_t)LONG_MAX
|
|
|| offset < -(int64_t)LONG_MAX - 1)
|
|
return -1;
|
|
}
|
|
return fseek(fp, (long)offset, whence) != 0 ? -1 : 0;
|
|
#endif
|
|
}
|
|
|
|
static int64_t retro_vfs_fp_tell64(FILE *fp)
|
|
{
|
|
#if defined(RETRO_VFS_HAVE_FSEEKI64)
|
|
return _ftelli64(fp);
|
|
#elif defined(RETRO_VFS_HAVE_FPOS64)
|
|
fpos_t pos;
|
|
if (fgetpos(fp, &pos) != 0)
|
|
return -1;
|
|
return (int64_t)pos;
|
|
#elif defined(HAVE_64BIT_OFFSETS)
|
|
return ftello(fp);
|
|
#else
|
|
return ftell(fp);
|
|
#endif
|
|
}
|
|
|
|
static int64_t retro_vfs_fd_seek64(int fd, int64_t offset, int whence)
|
|
{
|
|
#if defined(_WIN32) && (!defined(WINAPI_FAMILY) || defined(_CRT_USE_WINAPI_FAMILY_DESKTOP_APP))
|
|
HANDLE h = (HANDLE)_get_osfhandle(fd);
|
|
LONG hi;
|
|
DWORD lo;
|
|
DWORD method;
|
|
|
|
if (h == INVALID_HANDLE_VALUE)
|
|
return -1;
|
|
|
|
switch (whence)
|
|
{
|
|
case SEEK_SET:
|
|
method = FILE_BEGIN;
|
|
break;
|
|
case SEEK_CUR:
|
|
method = FILE_CURRENT;
|
|
break;
|
|
case SEEK_END:
|
|
method = FILE_END;
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
|
|
/* Two's complement split: a negative offset relative to CURRENT or
|
|
* END arrives as a sign-extended high dword, which is what
|
|
* SetFilePointer's signed PLONG wants. */
|
|
hi = (LONG)(offset >> 32);
|
|
SetLastError(NO_ERROR);
|
|
lo = SetFilePointer(h, (LONG)(DWORD)(offset & 0xFFFFFFFFu), &hi, method);
|
|
if (lo == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR)
|
|
return -1;
|
|
|
|
return (int64_t)(((uint64_t)(DWORD)hi << 32) | (uint64_t)lo);
|
|
#elif defined(_WIN32)
|
|
return (int64_t)_lseeki64(fd, (__int64)offset, whence);
|
|
#elif defined(__ANDROID__)
|
|
return (int64_t)lseek64(fd, (off64_t)offset, whence);
|
|
#elif defined(VITA)
|
|
/* SCE_SEEK_SET/CUR/END are 0/1/2, the same values as SEEK_*. */
|
|
{
|
|
SceOff pos = sceIoLseek(fd, (SceOff)offset, whence);
|
|
return (pos < 0) ? -1 : (int64_t)pos;
|
|
}
|
|
#else
|
|
/* Compile-time: the cast is lossless exactly when off_t is wide
|
|
* enough, and the guard costs nothing when it is (the comparison
|
|
* folds away). */
|
|
if (sizeof(off_t) < sizeof(int64_t))
|
|
{
|
|
if ( offset > (int64_t)LONG_MAX
|
|
|| offset < -(int64_t)LONG_MAX - 1)
|
|
return -1;
|
|
}
|
|
return (int64_t)lseek(fd, (off_t)offset, whence);
|
|
#endif
|
|
}
|
|
|
|
/* Keeps a cold path that needs a PATH_MAX_LENGTH scratch buffer out of
|
|
* a caller that would otherwise not need a frame at all. Under -Os the
|
|
* compiler is already optimizing for size and the forced outlining only
|
|
* buys a call, so it is disabled there. */
|
|
#if defined(__OPTIMIZE_SIZE__)
|
|
#define VFS_NOINLINE
|
|
#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3))
|
|
#define VFS_NOINLINE __attribute__((noinline))
|
|
#elif defined(_MSC_VER)
|
|
#define VFS_NOINLINE __declspec(noinline)
|
|
#else
|
|
#define VFS_NOINLINE
|
|
#endif
|
|
|
|
#ifdef HAVE_CDROM
|
|
static int path_is_cdrom(const char *p)
|
|
{
|
|
return (p
|
|
&& p[0] == 'c' && p[1] == 'd' && p[2] == 'r'
|
|
&& p[3] == 'o' && p[4] == 'm' && p[5] == ':'
|
|
&& p[6] == '/' && p[7] == '/' && p[8] != '\0');
|
|
}
|
|
#endif
|
|
|
|
#ifdef HAVE_SMBCLIENT
|
|
static int path_is_smb(const char *p)
|
|
{
|
|
return (p
|
|
&& p[0] == 's' && p[1] == 'm' && p[2] == 'b'
|
|
&& p[3] == ':' && p[4] == '/' && p[5] == '/'
|
|
&& p[6] != '\0');
|
|
}
|
|
#endif
|
|
#ifdef HAVE_NFSCLIENT
|
|
static int path_is_nfs(const char *p)
|
|
{
|
|
return (p
|
|
&& p[0] == 'n' && p[1] == 'f' && p[2] == 's'
|
|
&& p[3] == ':' && p[4] == '/' && p[5] == '/'
|
|
&& p[6] != '\0');
|
|
}
|
|
#endif
|
|
|
|
#if defined(ANDROID) && defined(HAVE_SAF)
|
|
static int path_is_saf(const char *p)
|
|
{
|
|
return (p
|
|
&& p[0] == 's' && p[1] == 'a' && p[2] == 'f'
|
|
&& p[3] == ':' && p[4] == '/' && p[5] == '/'
|
|
&& p[6] != '\0');
|
|
}
|
|
#endif
|
|
|
|
int64_t retro_vfs_file_seek_internal(
|
|
libretro_vfs_implementation_file *stream,
|
|
int64_t offset, int whence)
|
|
{
|
|
if (!stream)
|
|
return -1;
|
|
|
|
if ((stream->hints & RFILE_HINT_UNBUFFERED) == 0)
|
|
{
|
|
#ifdef HAVE_CDROM
|
|
if (stream->scheme == VFS_SCHEME_CDROM)
|
|
return retro_vfs_file_seek_cdrom(stream, offset, whence);
|
|
#endif
|
|
#ifdef HAVE_SMBCLIENT
|
|
if (stream->scheme == VFS_SCHEME_SMB)
|
|
return retro_vfs_file_seek_smb(stream, offset, whence);
|
|
#endif
|
|
#ifdef HAVE_NFSCLIENT
|
|
if (stream->scheme == VFS_SCHEME_NFS)
|
|
return retro_vfs_file_seek_nfs(stream, offset, whence);
|
|
#endif
|
|
return retro_vfs_fp_seek64(stream->fp, offset, whence);
|
|
}
|
|
#ifdef VFS_HAVE_FILE_MAPPING
|
|
/* Need to check stream->mapped because this function is
|
|
* called in filestream_open() */
|
|
if (stream->mapped && (stream->hints &
|
|
RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS))
|
|
{
|
|
/* fseek() returns error on under/overflow but
|
|
* allows cursor > EOF for
|
|
read-only file descriptors. */
|
|
/* The file position must never be negative. */
|
|
switch (whence)
|
|
{
|
|
case SEEK_SET:
|
|
if (offset < 0)
|
|
return -1;
|
|
|
|
stream->mappos = offset;
|
|
break;
|
|
|
|
case SEEK_CUR:
|
|
if ((int64_t)stream->mappos + offset < 0)
|
|
return -1;
|
|
|
|
stream->mappos += offset;
|
|
break;
|
|
|
|
case SEEK_END:
|
|
/* RETRO_VFS_SEEK_POSITION_END states offset should be negative.
|
|
* However, this is impractical because we would be forcing the
|
|
* end of file to always be off by one.
|
|
*/
|
|
if (offset > 0 || (int64_t)stream->mapsize + offset < 0)
|
|
return -1;
|
|
|
|
stream->mappos = stream->mapsize + offset;
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
return retro_vfs_fd_seek64(stream->fd, offset, whence) == -1 ? -1 : 0;
|
|
}
|
|
|
|
/**
|
|
* retro_vfs_file_open_impl:
|
|
* @path : path to file
|
|
* @mode : file mode to use when opening (read/write)
|
|
* @hints :
|
|
*
|
|
* Opens a file for reading or writing, depending on the requested mode.
|
|
* Returns a pointer to an RFILE if opened successfully, otherwise NULL.
|
|
**/
|
|
|
|
libretro_vfs_implementation_file *retro_vfs_file_open_impl(
|
|
const char *path, unsigned mode, unsigned hints)
|
|
{
|
|
int flags = 0;
|
|
const char *mode_str = NULL;
|
|
libretro_vfs_implementation_file *stream =
|
|
(libretro_vfs_implementation_file*)
|
|
calloc(1, sizeof(*stream));
|
|
|
|
if (!stream)
|
|
return NULL;
|
|
|
|
stream->fd = -1;
|
|
stream->hints = hints;
|
|
stream->scheme = VFS_SCHEME_NONE;
|
|
|
|
#ifdef VFS_FRONTEND
|
|
if ( path
|
|
&& path[0] == 'v'
|
|
&& path[1] == 'f'
|
|
&& path[2] == 's'
|
|
&& path[3] == 'o'
|
|
&& path[4] == 'n'
|
|
&& path[5] == 'l'
|
|
&& path[6] == 'y'
|
|
&& path[7] == ':'
|
|
&& path[8] == '/'
|
|
&& path[9] == '/')
|
|
path += sizeof("vfsonly://")-1;
|
|
#endif
|
|
|
|
#ifdef HAVE_CDROM
|
|
if (path_is_cdrom(path))
|
|
{
|
|
/* The sector cache and cue sheet state only exist for cdrom://
|
|
* handles, so they are allocated here rather than carried by
|
|
* every open file. Zeroed, because the old inline member was
|
|
* zeroed by the calloc() above and the cdrom paths rely on
|
|
* that (cue_buf NULL, last_frame_valid false, cur_track 0). */
|
|
if (!(stream->cdrom = (vfs_cdrom_t*)calloc(1, sizeof(*stream->cdrom))))
|
|
{
|
|
free(stream);
|
|
return NULL;
|
|
}
|
|
path += sizeof("cdrom://")-1;
|
|
stream->scheme = VFS_SCHEME_CDROM;
|
|
}
|
|
#endif
|
|
|
|
#ifdef HAVE_SMBCLIENT
|
|
if (path_is_smb(path))
|
|
{
|
|
stream->scheme = VFS_SCHEME_SMB;
|
|
}
|
|
#endif
|
|
#ifdef HAVE_NFSCLIENT
|
|
if (path_is_nfs(path))
|
|
stream->scheme = VFS_SCHEME_NFS;
|
|
#endif
|
|
|
|
#if defined(ANDROID) && defined(HAVE_SAF)
|
|
if (path_is_saf(path))
|
|
{
|
|
stream->scheme = VFS_SCHEME_SAF;
|
|
}
|
|
#endif
|
|
|
|
if (path)
|
|
stream->orig_path = strdup(path);
|
|
|
|
#ifdef VFS_HAVE_FILE_MAPPING
|
|
if (stream->hints & RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS && mode == RETRO_VFS_FILE_ACCESS_READ)
|
|
stream->hints |= RFILE_HINT_UNBUFFERED;
|
|
else
|
|
#endif
|
|
stream->hints &= ~RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS;
|
|
|
|
#ifdef VFS_HAVE_DESCRIPTOR_IO
|
|
/* A read-once-and-close stream wants the descriptor path for the
|
|
* opposite reason a mapped one does: not to keep the bytes around,
|
|
* but to avoid a buffer it will never reuse. Deliberately after
|
|
* the block above and never touching the mapping hint, so a
|
|
* stream asking for both still maps and behaves exactly as before.
|
|
*
|
|
* Restricted to VFS_SCHEME_NONE: the descriptor branch below knows
|
|
* about plain files and (on Android) SAF, and has no case for
|
|
* cdrom:// or smb://, which have their own read/seek entry points
|
|
* that only the buffered branch dispatches to. */
|
|
if ( (stream->hints & RETRO_VFS_FILE_ACCESS_HINT_SEQUENTIAL_BULK)
|
|
&& mode == RETRO_VFS_FILE_ACCESS_READ
|
|
&& stream->scheme == VFS_SCHEME_NONE)
|
|
stream->hints |= RFILE_HINT_UNBUFFERED;
|
|
#ifdef VFS_HAVE_DESCRIPTOR_WRITE
|
|
/* A write-once stream is the same shape: every byte it will ever
|
|
* write arrives in one call, so a stdio buffer only adds a copy
|
|
* and a flush. */
|
|
if ( (stream->hints & RETRO_VFS_FILE_ACCESS_HINT_SEQUENTIAL_BULK)
|
|
&& ( mode == RETRO_VFS_FILE_ACCESS_WRITE
|
|
|| mode == RETRO_VFS_FILE_ACCESS_READ_WRITE)
|
|
&& stream->scheme == VFS_SCHEME_NONE)
|
|
stream->hints |= RFILE_HINT_UNBUFFERED;
|
|
#endif
|
|
#endif
|
|
|
|
switch (mode)
|
|
{
|
|
case RETRO_VFS_FILE_ACCESS_READ:
|
|
mode_str = "rb";
|
|
|
|
flags = O_RDONLY;
|
|
#ifdef _WIN32
|
|
flags |= O_BINARY;
|
|
#endif
|
|
break;
|
|
|
|
case RETRO_VFS_FILE_ACCESS_WRITE:
|
|
mode_str = "wb";
|
|
|
|
flags = O_WRONLY | O_CREAT | O_TRUNC;
|
|
#if defined(_WIN32)
|
|
flags |= O_BINARY;
|
|
#endif
|
|
break;
|
|
|
|
case RETRO_VFS_FILE_ACCESS_READ_WRITE:
|
|
mode_str = "w+b";
|
|
flags = O_RDWR | O_CREAT | O_TRUNC;
|
|
#if defined(_WIN32)
|
|
flags |= O_BINARY;
|
|
#endif
|
|
break;
|
|
|
|
case RETRO_VFS_FILE_ACCESS_WRITE | RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING:
|
|
case RETRO_VFS_FILE_ACCESS_READ_WRITE | RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING:
|
|
mode_str = "r+b";
|
|
|
|
flags = O_RDWR;
|
|
#if defined(_WIN32)
|
|
flags |= O_BINARY;
|
|
#endif
|
|
break;
|
|
|
|
default:
|
|
goto error;
|
|
}
|
|
|
|
if ((stream->hints & RFILE_HINT_UNBUFFERED) == 0)
|
|
{
|
|
FILE *fp;
|
|
switch (stream->scheme)
|
|
{
|
|
#ifdef HAVE_CDROM
|
|
case VFS_SCHEME_CDROM:
|
|
retro_vfs_file_open_cdrom(stream, path, mode, hints);
|
|
#if defined(_WIN32) && !defined(_XBOX)
|
|
if (!stream->fh)
|
|
goto error;
|
|
#elif defined(__APPLE__)
|
|
if (!stream->iokit_mmc)
|
|
goto error;
|
|
#else
|
|
if (!stream->fp)
|
|
goto error;
|
|
#endif
|
|
break;
|
|
#endif
|
|
|
|
#ifdef HAVE_SMBCLIENT
|
|
case VFS_SCHEME_SMB:
|
|
if (!retro_vfs_file_open_smb(stream, path, mode, hints))
|
|
goto error;
|
|
break;
|
|
#endif
|
|
#ifdef HAVE_NFSCLIENT
|
|
case VFS_SCHEME_NFS:
|
|
if (!retro_vfs_file_open_nfs(stream, path, mode, hints))
|
|
goto error;
|
|
break;
|
|
#endif
|
|
|
|
#if defined(ANDROID) && defined(HAVE_SAF)
|
|
case VFS_SCHEME_SAF:
|
|
{
|
|
struct libretro_vfs_implementation_saf_path_split_result saf_split_result;
|
|
int fd;
|
|
if (!retro_vfs_path_split_saf(&saf_split_result, path))
|
|
goto error;
|
|
fd = retro_vfs_file_open_saf(saf_split_result.tree, saf_split_result.path, mode);
|
|
free(saf_split_result.path);
|
|
free(saf_split_result.tree);
|
|
if (fd == -1)
|
|
goto error;
|
|
stream->fp = fdopen(fd, mode_str);
|
|
if (!stream->fp)
|
|
{
|
|
close(fd);
|
|
goto error;
|
|
}
|
|
}
|
|
break;
|
|
#endif
|
|
|
|
default:
|
|
if (!(fp = (FILE*)fopen_utf8(path, mode_str)))
|
|
{
|
|
#if TARGET_OS_IPHONE
|
|
if (errno == EEXIST)
|
|
{
|
|
retro_vfs_file_remove_impl(path);
|
|
fp = (FILE*)fopen_utf8(path, mode_str);
|
|
}
|
|
if (!fp)
|
|
#endif
|
|
goto error;
|
|
}
|
|
stream->fp = fp;
|
|
break;
|
|
}
|
|
|
|
/* Regarding setvbuf:
|
|
*
|
|
* https://www.freebsd.org/cgi/man.cgi?query=setvbuf&apropos=0&sektion=0&manpath=FreeBSD+11.1-RELEASE&arch=default&format=html
|
|
*
|
|
* If the size argument is not zero but buf is NULL,
|
|
* a buffer of the given size will be allocated immediately, and
|
|
* released on close. This is an extension to ANSI C.
|
|
*
|
|
* Since C89 does not support specifying a NULL buffer
|
|
* with a non-zero size, we create and track our own buffer for it.
|
|
*/
|
|
/* stdio sizes a file stream's buffer from st_blksize, which the
|
|
* filesystem reports and which is 4 KiB on every platform
|
|
* checked. Callers here write in much smaller pieces than that
|
|
* - a JSON writer flushing a kilobyte at a time, for instance -
|
|
* so that default decides how many syscalls a file costs.
|
|
*
|
|
* Measured writing 8 MiB in 1 KiB pieces, default against
|
|
* 64 KiB:
|
|
*
|
|
* desktop Linux 14.02 ms -> 3.05 ms reads 1.68 -> 0.75 ms
|
|
* macOS 9.35 ms -> 1.58 ms reads 1.59 -> 0.80 ms
|
|
*
|
|
* Sixteen times fewer syscalls for the same bytes, and the
|
|
* dearer a syscall is the more that is worth - which is why the
|
|
* platforms with the slowest storage are the ones that already
|
|
* did this, and with the largest buffers.
|
|
*
|
|
* This is deliberately not gated on a platform list. Such a
|
|
* list has to be maintained, is wrong the moment a target is
|
|
* added, and would leave that target silently on 4 KiB forever -
|
|
* which is exactly the state everything but the two consoles
|
|
* below was in. The rest of this codebase does not gate it
|
|
* either: config_file.c and verbosity.c both buffer
|
|
* unconditionally.
|
|
*
|
|
* The memory is one allocation per open file, released when the
|
|
* file closes, and a handful of files are open at once. Where
|
|
* even that is too much the allocation simply fails and the C
|
|
* library default is used, so a platform under real pressure
|
|
* degrades rather than breaks. A platform wanting a different
|
|
* size says so, as the two below do.
|
|
*
|
|
* It is malloc rather than calloc because nothing ever reads
|
|
* these bytes before stdio writes them - the buffer is handed
|
|
* straight to setvbuf and otherwise only freed, and the WiiU
|
|
* path below has always used non-zeroing memalign. Zeroing it
|
|
* was the single dearest part of opening a small file: 3000
|
|
* opens of 256-byte files measured 2.9-3.1 us each with the
|
|
* zeroing and 1.8 us without, so scan-shaped and thumbnail-
|
|
* shaped workloads - thousands of opens, few bytes each - spent
|
|
* more time clearing buffers than reading files. */
|
|
#if defined(_3DS)
|
|
if (stream->scheme != VFS_SCHEME_CDROM)
|
|
{
|
|
stream->buf = (char*)malloc(0x10000);
|
|
if (stream->fp)
|
|
setvbuf(stream->fp, stream->buf, _IOFBF, 0x10000);
|
|
}
|
|
#elif defined(WIIU)
|
|
if (stream->scheme != VFS_SCHEME_CDROM)
|
|
{
|
|
const int bufsize = 128 * 1024;
|
|
stream->buf = (char*)memalign(0x40, bufsize);
|
|
if (stream->fp)
|
|
setvbuf(stream->fp, stream->buf, _IOFBF, bufsize);
|
|
}
|
|
#else
|
|
if (stream->scheme != VFS_SCHEME_CDROM)
|
|
{
|
|
const int bufsize = 64 * 1024;
|
|
/* NULL rather than a buffer of our own, so the C library
|
|
* allocates and owns it.
|
|
*
|
|
* Who owns the buffer is not a detail on every library.
|
|
* Apple's fread() has a fast path that reads a large request
|
|
* straight into the caller's buffer instead of copying it
|
|
* through the stream, and gates it on __SMBF - "this buffer
|
|
* is mine" - which setvbuf() clears when handed a buffer
|
|
* from outside (Libc stdio/FreeBSD/setvbuf.c sets __SMBF
|
|
* only on the branch that allocates, and fread.c tests it;
|
|
* see also radars 5980080 and 6180417). So the 64 KiB
|
|
* buffer added here for write throughput was disqualifying
|
|
* the read fast path on every Apple target, and measured at
|
|
* roughly half the whole-file read speed on macOS.
|
|
*
|
|
* The size argument still applies, so writes keep the buffer
|
|
* they were given. A library that will not allocate for a
|
|
* NULL buffer leaves the stream on its own default, which is
|
|
* slower for small writes but correct - the same outcome the
|
|
* malloc failing used to produce. */
|
|
if (stream->fp)
|
|
setvbuf(stream->fp, NULL, _IOFBF, bufsize);
|
|
}
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
switch (stream->scheme)
|
|
{
|
|
#if defined(ANDROID) && defined(HAVE_SAF)
|
|
case VFS_SCHEME_SAF:
|
|
{
|
|
struct libretro_vfs_implementation_saf_path_split_result saf_split_result;
|
|
if (!retro_vfs_path_split_saf(&saf_split_result, path))
|
|
goto error;
|
|
stream->fd = retro_vfs_file_open_saf(saf_split_result.tree, saf_split_result.path, mode);
|
|
free(saf_split_result.path);
|
|
free(saf_split_result.tree);
|
|
}
|
|
break;
|
|
#endif
|
|
|
|
default:
|
|
{
|
|
#if defined(_WIN32) && !defined(_XBOX)
|
|
#if defined(LEGACY_WIN32_RUNTIME)
|
|
if (win32_needs_local_encoding())
|
|
{
|
|
char *path_local = utf8_to_local_string_alloc(path);
|
|
stream->fd = open(path_local, flags, 0);
|
|
if (path_local)
|
|
free(path_local);
|
|
}
|
|
else
|
|
{
|
|
wchar_t * path_wide = utf8_to_utf16_string_alloc(path);
|
|
stream->fd = _wopen(path_wide, flags, 0);
|
|
if (path_wide)
|
|
free(path_wide);
|
|
}
|
|
#elif defined(LEGACY_WIN32)
|
|
char *path_local = utf8_to_local_string_alloc(path);
|
|
stream->fd = open(path_local, flags, 0);
|
|
if (path_local)
|
|
free(path_local);
|
|
#else
|
|
wchar_t * path_wide = utf8_to_utf16_string_alloc(path);
|
|
stream->fd = _wopen(path_wide, flags, 0);
|
|
if (path_wide)
|
|
free(path_wide);
|
|
#endif
|
|
#elif defined(VITA)
|
|
{
|
|
int sce_flags = 0;
|
|
SceUID uid;
|
|
|
|
/* The POSIX flags above are for the open() targets. */
|
|
(void)flags;
|
|
|
|
switch (mode)
|
|
{
|
|
case RETRO_VFS_FILE_ACCESS_READ:
|
|
sce_flags = SCE_O_RDONLY;
|
|
break;
|
|
case RETRO_VFS_FILE_ACCESS_WRITE:
|
|
sce_flags = SCE_O_WRONLY | SCE_O_CREAT | SCE_O_TRUNC;
|
|
break;
|
|
case RETRO_VFS_FILE_ACCESS_READ_WRITE:
|
|
sce_flags = SCE_O_RDWR | SCE_O_CREAT | SCE_O_TRUNC;
|
|
break;
|
|
default:
|
|
sce_flags = SCE_O_RDWR;
|
|
break;
|
|
}
|
|
|
|
/* SceUID error codes are negative; the descriptor
|
|
* path only ever tests for -1. */
|
|
uid = sceIoOpen(path, sce_flags, 0777);
|
|
stream->fd = (uid < 0) ? -1 : (int)uid;
|
|
}
|
|
#else
|
|
stream->fd = open(path, flags, S_IRUSR | S_IWUSR);
|
|
#endif
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (stream->fd == -1)
|
|
goto error;
|
|
|
|
#ifdef VFS_HAVE_FILE_MAPPING
|
|
if (stream->hints & RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS)
|
|
{
|
|
stream->mappos = 0;
|
|
stream->mapped = NULL;
|
|
|
|
retro_vfs_file_seek_internal(stream, 0, SEEK_END);
|
|
|
|
stream->mapsize = retro_vfs_file_tell_impl(stream);
|
|
if (stream->mapsize == (uint64_t)-1)
|
|
goto error;
|
|
|
|
retro_vfs_file_seek_internal(stream, 0, SEEK_SET);
|
|
|
|
#if defined(HAVE_MMAP)
|
|
/* mmap() takes a size_t. On a 32-bit host a file larger than
|
|
* the address space would truncate to its low bits on the way
|
|
* in - and a truncated length can still map successfully,
|
|
* leaving a short mapping while mapsize goes on claiming the
|
|
* whole file, so every read past the cut would fault instead
|
|
* of coming up short. Refuse the mapping and let the
|
|
* descriptor path below serve the file, exactly as a failed
|
|
* mmap() does. Folds away where size_t is 64 bits. */
|
|
if (stream->mapsize > (uint64_t)(size_t)-1)
|
|
{
|
|
stream->mapped = NULL;
|
|
stream->hints &= ~RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS;
|
|
}
|
|
else if ((stream->mapped = (uint8_t*)mmap((void*)0,
|
|
(size_t)stream->mapsize, PROT_READ, MAP_SHARED, stream->fd, 0)) == MAP_FAILED)
|
|
{
|
|
stream->mapped = NULL;
|
|
stream->hints &= ~RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS;
|
|
}
|
|
#else
|
|
{
|
|
/* Win32 file mapping. A zero-length file cannot be mapped
|
|
* (CreateFileMapping rejects it), and a failed mapping of
|
|
* any kind - including a >4GB file on a 32-bit process,
|
|
* where MapViewOfFile cannot find address space - degrades
|
|
* to the ordinary descriptor path by dropping the hint,
|
|
* exactly as the POSIX branch does. */
|
|
HANDLE fh = (HANDLE)_get_osfhandle(stream->fd);
|
|
stream->map_handle = NULL;
|
|
if (fh != INVALID_HANDLE_VALUE && stream->mapsize > 0)
|
|
stream->map_handle = CreateFileMapping(fh, NULL,
|
|
PAGE_READONLY, 0, 0, NULL);
|
|
if (stream->map_handle)
|
|
{
|
|
stream->mapped = (uint8_t*)MapViewOfFile(stream->map_handle,
|
|
FILE_MAP_READ, 0, 0, 0);
|
|
if (!stream->mapped)
|
|
{
|
|
CloseHandle(stream->map_handle);
|
|
stream->map_handle = NULL;
|
|
}
|
|
}
|
|
if (!stream->mapped)
|
|
stream->hints &= ~RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS;
|
|
}
|
|
#endif
|
|
}
|
|
#endif
|
|
}
|
|
#ifdef HAVE_CDROM
|
|
if (stream->scheme == VFS_SCHEME_CDROM)
|
|
{
|
|
retro_vfs_file_seek_cdrom(stream, 0, SEEK_END);
|
|
|
|
stream->size = retro_vfs_file_tell_impl(stream);
|
|
|
|
retro_vfs_file_seek_cdrom(stream, 0, SEEK_SET);
|
|
}
|
|
else
|
|
#endif
|
|
/* A truncating open leaves a zero-length file positioned at 0,
|
|
* so the probe below - a seek to the end, a tell and a seek back,
|
|
* each a system call - would only ever confirm that. */
|
|
if ( mode != RETRO_VFS_FILE_ACCESS_WRITE
|
|
&& mode != RETRO_VFS_FILE_ACCESS_READ_WRITE)
|
|
{
|
|
retro_vfs_file_seek_internal(stream, 0, SEEK_END);
|
|
|
|
stream->size = retro_vfs_file_tell_impl(stream);
|
|
|
|
retro_vfs_file_seek_internal(stream, 0, SEEK_SET);
|
|
}
|
|
return stream;
|
|
|
|
error:
|
|
retro_vfs_file_close_impl(stream);
|
|
return NULL;
|
|
}
|
|
|
|
int retro_vfs_file_close_impl(libretro_vfs_implementation_file *stream)
|
|
{
|
|
if (!stream)
|
|
return -1;
|
|
|
|
#ifdef HAVE_CDROM
|
|
if (stream->scheme == VFS_SCHEME_CDROM)
|
|
{
|
|
retro_vfs_file_close_cdrom(stream);
|
|
goto end;
|
|
}
|
|
#endif
|
|
|
|
#ifdef HAVE_SMBCLIENT
|
|
if (stream->scheme == VFS_SCHEME_SMB)
|
|
{
|
|
retro_vfs_file_close_smb(stream);
|
|
goto netend;
|
|
}
|
|
#endif
|
|
#ifdef HAVE_NFSCLIENT
|
|
if (stream->scheme == VFS_SCHEME_NFS)
|
|
{
|
|
retro_vfs_file_close_nfs(stream);
|
|
goto netend;
|
|
}
|
|
#endif
|
|
|
|
if ((stream->hints & RFILE_HINT_UNBUFFERED) == 0)
|
|
{
|
|
if (stream->fp)
|
|
fclose(stream->fp);
|
|
}
|
|
else
|
|
{
|
|
#ifdef VFS_HAVE_FILE_MAPPING
|
|
if (stream->mapped && (stream->hints & RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS))
|
|
{
|
|
#if defined(HAVE_MMAP)
|
|
munmap(stream->mapped, stream->mapsize);
|
|
#else
|
|
UnmapViewOfFile(stream->mapped);
|
|
if (stream->map_handle)
|
|
{
|
|
CloseHandle(stream->map_handle);
|
|
stream->map_handle = NULL;
|
|
}
|
|
#endif
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if (stream->fd >= 0)
|
|
{
|
|
#if defined(VITA)
|
|
sceIoClose((SceUID)stream->fd);
|
|
#else
|
|
close(stream->fd);
|
|
#endif
|
|
}
|
|
#ifdef HAVE_CDROM
|
|
end:
|
|
/* Reached both by the goto above and by fall-through from the
|
|
* non-cdrom path, where stream->cdrom is NULL. */
|
|
if (stream->cdrom)
|
|
{
|
|
if (stream->cdrom->cue_buf)
|
|
free(stream->cdrom->cue_buf);
|
|
free(stream->cdrom);
|
|
stream->cdrom = NULL;
|
|
}
|
|
#endif
|
|
#if defined(HAVE_SMBCLIENT) || defined(HAVE_NFSCLIENT)
|
|
netend:
|
|
#endif
|
|
|
|
if (stream->buf)
|
|
free(stream->buf);
|
|
|
|
if (stream->orig_path)
|
|
free(stream->orig_path);
|
|
|
|
free(stream);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int retro_vfs_file_error_impl(libretro_vfs_implementation_file *stream)
|
|
{
|
|
if (!stream)
|
|
return -1;
|
|
#ifdef HAVE_CDROM
|
|
if (stream->scheme == VFS_SCHEME_CDROM)
|
|
return retro_vfs_file_error_cdrom(stream);
|
|
#endif
|
|
#ifdef HAVE_SMBCLIENT
|
|
if (stream->scheme == VFS_SCHEME_SMB)
|
|
return retro_vfs_file_error_smb(stream);
|
|
#endif
|
|
#ifdef HAVE_NFSCLIENT
|
|
if (stream->scheme == VFS_SCHEME_NFS)
|
|
return retro_vfs_file_error_nfs(stream);
|
|
#endif
|
|
if (!stream->fp)
|
|
return -1;
|
|
return ferror(stream->fp);
|
|
}
|
|
|
|
int64_t retro_vfs_file_size_impl(libretro_vfs_implementation_file *stream)
|
|
{
|
|
if (stream)
|
|
return stream->size;
|
|
return 0;
|
|
}
|
|
|
|
/* Deallocate a range without changing the file's length. See
|
|
* filestream_punch_hole for why this is a capability rather than a
|
|
* guarantee: most backends cannot do it, and callers fall back to writing
|
|
* zeroes. Returns 0 on success, -1 otherwise. */
|
|
#if defined(_WIN32) && !defined(_XBOX)
|
|
/* The pieces of winioctl.h that retro_vfs_file_punch_hole_impl needs,
|
|
* declared here rather than by including that header.
|
|
*
|
|
* winioctl.h defines the storage class GUIDs, and in a unity build --
|
|
* griffin.c, where this file is compiled alongside the D3D headers that
|
|
* set INITGUID -- those definitions collide with the ones already emitted
|
|
* in the same translation unit, which is twenty-odd C2374 "redefinition;
|
|
* multiple initialization" errors on MSVC. Nothing here needs a GUID.
|
|
*
|
|
* The two control codes are the documented CTL_CODE expansions:
|
|
* FSCTL_SET_SPARSE = CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 49, METHOD_BUFFERED, FILE_SPECIAL_ACCESS)
|
|
* FSCTL_SET_ZERO_DATA = CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 50, METHOD_BUFFERED, FILE_WRITE_DATA)
|
|
* They carry their own names so a translation unit that also sees
|
|
* winioctl.h holds one definition of each. */
|
|
#define RA_FSCTL_SET_SPARSE 0x000900c4
|
|
#define RA_FSCTL_SET_ZERO_DATA 0x000980c8
|
|
#ifndef FILE_ZERO_DATA_INFORMATION_DEFINED
|
|
#define FILE_ZERO_DATA_INFORMATION_DEFINED
|
|
typedef struct _RETRO_FILE_ZERO_DATA_INFORMATION
|
|
{
|
|
LARGE_INTEGER FileOffset;
|
|
LARGE_INTEGER BeyondFinalZero;
|
|
} RETRO_FILE_ZERO_DATA_INFORMATION;
|
|
#endif
|
|
#endif
|
|
|
|
#if defined(_WIN32) && !defined(_XBOX)
|
|
/* FILE_NAME_NORMALIZED came in with the Vista SDK, the same generation as
|
|
* GetFinalPathNameByHandleW itself. The function is resolved by name at
|
|
* run time below precisely so that older toolchains still build this
|
|
* file; the flag has to clear the same bar, so supply the documented
|
|
* value when the SDK does not. */
|
|
#ifndef FILE_NAME_NORMALIZED
|
|
#define FILE_NAME_NORMALIZED 0x0
|
|
#endif
|
|
#endif
|
|
|
|
/* Allocation unit of the filesystem holding this file: the smallest span
|
|
* retro_vfs_file_punch_hole_impl can actually free. 0 when unknown.
|
|
*
|
|
* This exists so a caller never has to reach for the descriptor itself.
|
|
* On NTFS this is the compression unit rather than the cluster, because
|
|
* that is what the filesystem actually deallocates in.
|
|
*
|
|
* PCSX2's ATA backend derived both the cluster size and the filesystem
|
|
* name by calling
|
|
* GetFinalPathNameByHandle on a HANDLE it pulled out of a FILE*, which is
|
|
* the one thing that kept it off the VFS -- the question is about the
|
|
* filesystem, not the file, and the backend that owns the handle is the
|
|
* right place to answer it. */
|
|
int64_t retro_vfs_file_get_sparse_granularity_impl(
|
|
libretro_vfs_implementation_file *stream)
|
|
{
|
|
if (!stream)
|
|
return 0;
|
|
|
|
/* Excludes __WINRT__ for the same reason features_cpu.c does: the probe
|
|
* below reaches kernel32 through GetModuleHandle, and that entry point is
|
|
* not in the Windows Store API partition -- dylib_proc() says as much and
|
|
* refuses to call it there. A UWP build reports "unknown" and skips
|
|
* sparse handling, which is already the documented behaviour for any
|
|
* system that cannot resolve these names. */
|
|
#if defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)
|
|
{
|
|
/* GetFinalPathNameByHandleW and GetVolumeInformationByHandleW are
|
|
* Vista and later. Importing them statically costs more than the
|
|
* feature is worth: the loader resolves imports before main, so a
|
|
* binary that names them will not start at all on 9x or XP, and
|
|
* mingw does not even declare them below _WIN32_WINNT 0x0600, which
|
|
* is what the i686 MXE build targets. Resolved by name instead, so
|
|
* older systems simply report "unknown" and skip sparse handling. */
|
|
typedef DWORD (WINAPI *final_path_t)(HANDLE, LPWSTR, DWORD, DWORD);
|
|
typedef BOOL (WINAPI *vol_info_t)(HANDLE, LPWSTR, DWORD, LPDWORD,
|
|
LPDWORD, LPDWORD, LPWSTR, DWORD);
|
|
typedef BOOL (WINAPI *vol_path_t)(LPCWSTR, LPWSTR, DWORD);
|
|
typedef BOOL (WINAPI *disk_free_t)(LPCWSTR, LPDWORD, LPDWORD,
|
|
LPDWORD, LPDWORD);
|
|
|
|
static final_path_t p_final_path = NULL;
|
|
static vol_info_t p_vol_info = NULL;
|
|
static vol_path_t p_vol_path = NULL;
|
|
static disk_free_t p_disk_free = NULL;
|
|
static int resolved = 0;
|
|
|
|
DWORD sectors_per_cluster = 0;
|
|
DWORD bytes_per_sector = 0;
|
|
DWORD tmp1 = 0;
|
|
DWORD tmp2 = 0;
|
|
DWORD len;
|
|
HANDLE handle = stream->fh;
|
|
wchar_t *final_path;
|
|
wchar_t root[MAX_PATH + 1];
|
|
wchar_t fs_name[MAX_PATH + 1];
|
|
int64_t cluster;
|
|
|
|
if (!resolved)
|
|
{
|
|
HMODULE k32 = GetModuleHandleA("kernel32.dll");
|
|
|
|
resolved = 1;
|
|
if (k32)
|
|
{
|
|
p_final_path = (final_path_t)GetProcAddress(k32,
|
|
"GetFinalPathNameByHandleW");
|
|
p_vol_info = (vol_info_t)GetProcAddress(k32,
|
|
"GetVolumeInformationByHandleW");
|
|
/* Wide GetVolumePathName is 2000 and later, and 9x does not
|
|
* export it at all -- a static import would stop the binary
|
|
* loading there, so every one of these is resolved by name. */
|
|
p_vol_path = (vol_path_t)GetProcAddress(k32,
|
|
"GetVolumePathNameW");
|
|
p_disk_free = (disk_free_t)GetProcAddress(k32,
|
|
"GetDiskFreeSpaceW");
|
|
}
|
|
}
|
|
|
|
if (!p_final_path || !p_vol_path || !p_disk_free)
|
|
return 0;
|
|
|
|
if (!handle || handle == INVALID_HANDLE_VALUE)
|
|
{
|
|
if (!stream->fp)
|
|
return 0;
|
|
handle = (HANDLE)_get_osfhandle(_fileno(stream->fp));
|
|
if (handle == INVALID_HANDLE_VALUE)
|
|
return 0;
|
|
}
|
|
|
|
/* The volume that matters is the one the file really lives on, so
|
|
* the path has to be resolved through any junctions first. */
|
|
len = p_final_path(handle, NULL, 0, FILE_NAME_NORMALIZED);
|
|
if (len == 0)
|
|
return 0;
|
|
|
|
final_path = (wchar_t*)calloc(len + 1, sizeof(wchar_t));
|
|
if (!final_path)
|
|
return 0;
|
|
|
|
if (p_final_path(handle, final_path, len, FILE_NAME_NORMALIZED) == 0)
|
|
{
|
|
free(final_path);
|
|
return 0;
|
|
}
|
|
|
|
/* GetVolumePathNameW gives the mount point, which is what
|
|
* GetDiskFreeSpaceW wants, and unlike splitting the string by hand
|
|
* it copes with a path mounted on a folder rather than a letter. */
|
|
if (!p_vol_path(final_path, root, MAX_PATH))
|
|
{
|
|
free(final_path);
|
|
return 0;
|
|
}
|
|
free(final_path);
|
|
|
|
if (!p_disk_free(root, §ors_per_cluster, &bytes_per_sector,
|
|
&tmp1, &tmp2))
|
|
return 0;
|
|
|
|
cluster = (int64_t)sectors_per_cluster * (int64_t)bytes_per_sector;
|
|
|
|
/* On NTFS the cluster size is not the answer. A sparse file is
|
|
* deallocated in compression units, which are 16 clusters, capped
|
|
* at 64K -- so a 4K-cluster volume, the common case, frees in 64K
|
|
* chunks and punching a single 4K cluster frees nothing at all.
|
|
* Other filesystems deallocate by cluster, so the cluster size
|
|
* stands. Without the volume query, assume the conservative case. */
|
|
if (!p_vol_info)
|
|
return cluster;
|
|
|
|
if (p_vol_info(handle, NULL, 0, NULL, NULL, NULL, fs_name, MAX_PATH)
|
|
&& !wcscmp(fs_name, L"NTFS"))
|
|
{
|
|
const int64_t unit = cluster * 16;
|
|
return (unit > 65536) ? 65536 : unit;
|
|
}
|
|
|
|
return cluster;
|
|
}
|
|
#elif !defined(_WIN32) && !defined(VITA) && !defined(PSP) && !defined(PS2) && !defined(ORBIS) && !defined(GEKKO) && !defined(_3DS)
|
|
{
|
|
struct stat st;
|
|
int fd = stream->fd;
|
|
|
|
if (fd < 0)
|
|
{
|
|
if (!stream->fp)
|
|
return 0;
|
|
fd = fileno(stream->fp);
|
|
if (fd < 0)
|
|
return 0;
|
|
}
|
|
|
|
if (fstat(fd, &st) != 0)
|
|
return 0;
|
|
if (st.st_blksize > 0)
|
|
return (int64_t)st.st_blksize;
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
int retro_vfs_file_punch_hole_impl(libretro_vfs_implementation_file *stream,
|
|
int64_t offset, int64_t len)
|
|
{
|
|
if (!stream || offset < 0 || len <= 0)
|
|
return -1;
|
|
|
|
#if defined(_WIN32) && !defined(_XBOX)
|
|
{
|
|
RETRO_FILE_ZERO_DATA_INFORMATION zero_info;
|
|
DWORD returned = 0;
|
|
HANDLE handle = stream->fh;
|
|
|
|
if (!handle || handle == INVALID_HANDLE_VALUE)
|
|
{
|
|
if (!stream->fp)
|
|
return -1;
|
|
handle = (HANDLE)_get_osfhandle(_fileno(stream->fp));
|
|
if (handle == INVALID_HANDLE_VALUE)
|
|
return -1;
|
|
}
|
|
|
|
/* The file has to be marked sparse before a zero-data range does
|
|
* anything; on a non-sparse file the call succeeds and writes real
|
|
* zeroes, which is correct but saves nothing. Marking is idempotent. */
|
|
{
|
|
DWORD tmp = 0;
|
|
DeviceIoControl(handle, RA_FSCTL_SET_SPARSE, NULL, 0, NULL, 0, &tmp, NULL);
|
|
}
|
|
|
|
zero_info.FileOffset.QuadPart = offset;
|
|
zero_info.BeyondFinalZero.QuadPart = offset + len;
|
|
|
|
if (!DeviceIoControl(handle, RA_FSCTL_SET_ZERO_DATA, &zero_info,
|
|
sizeof(zero_info), NULL, 0, &returned, NULL))
|
|
return -1;
|
|
return 0;
|
|
}
|
|
/* fallocate() sits behind _GNU_SOURCE in glibc (it is gated on __USE_GNU),
|
|
* so whether it is *declared* depends on the consumer's feature-test
|
|
* macros rather than on anything this file controls. <linux/falloc.h>
|
|
* supplies FALLOC_FL_PUNCH_HOLE either way, so testing the flag alone
|
|
* leaves a consumer that does not define _GNU_SOURCE calling an
|
|
* undeclared function -- a warning today, an error on stricter
|
|
* compilers, and wrong argument passing for the off_t pair on 32-bit.
|
|
* Require the declaration too, and fall through to the unsupported
|
|
* path when it is absent. uClibc declares it only from uClibc-ng 1.0
|
|
* on, and only with its Linux-specific API enabled. */
|
|
#elif defined(__linux__) && defined(FALLOC_FL_PUNCH_HOLE) \
|
|
&& (defined(_GNU_SOURCE) || defined(__USE_GNU)) \
|
|
&& (!defined(__UCLIBC__) || (defined(__UCLIBC_LINUX_SPECIFIC__) \
|
|
&& defined(__UCLIBC_MAJOR__) && __UCLIBC_MAJOR__ >= 1))
|
|
{
|
|
int fd = stream->fd;
|
|
|
|
if (fd < 0)
|
|
{
|
|
if (!stream->fp)
|
|
return -1;
|
|
fd = fileno(stream->fp);
|
|
if (fd < 0)
|
|
return -1;
|
|
}
|
|
|
|
if (fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
|
|
(off_t)offset, (off_t)len) != 0)
|
|
return -1;
|
|
return 0;
|
|
}
|
|
#else
|
|
/* No hole punching on this platform. */
|
|
(void)offset;
|
|
(void)len;
|
|
return -1;
|
|
#endif
|
|
}
|
|
|
|
int64_t retro_vfs_file_truncate_impl(libretro_vfs_implementation_file *stream, int64_t len)
|
|
{
|
|
#ifdef _WIN32
|
|
/* _chsize takes a long and silently truncates lengths > LONG_MAX
|
|
* (2 GiB on Windows) -- present on all Windows CRTs including
|
|
* VC6. _chsize_s takes __int64 and was added in the Secure CRT
|
|
* (VS 2005, _MSC_VER 1400). Prefer the 64-bit variant when
|
|
* available, and on older MSVC / MinGW with legacy msvcrt fall
|
|
* back to _chsize only for lengths that fit in long -- return
|
|
* an error for larger lengths rather than silently truncating
|
|
* the file. */
|
|
#if defined(_MSC_VER) && _MSC_VER >= 1400
|
|
if (stream && stream->fp && _chsize_s(_fileno(stream->fp), len) == 0)
|
|
{
|
|
stream->size = len;
|
|
return 0;
|
|
}
|
|
#else
|
|
if (stream && stream->fp && len >= 0 && len <= (int64_t)LONG_MAX
|
|
&& _chsize(_fileno(stream->fp), (long)len) == 0)
|
|
{
|
|
stream->size = len;
|
|
return 0;
|
|
}
|
|
#endif
|
|
#elif !defined(VITA) && !defined(PSP) && !defined(PS2) && !defined(ORBIS) && (!defined(SWITCH) || defined(HAVE_LIBNX))
|
|
if (stream && stream->fp && ftruncate(fileno(stream->fp), (off_t)len) == 0)
|
|
{
|
|
stream->size = len;
|
|
return 0;
|
|
}
|
|
#endif
|
|
return -1;
|
|
}
|
|
|
|
int64_t retro_vfs_file_tell_impl(libretro_vfs_implementation_file *stream)
|
|
{
|
|
if (!stream)
|
|
return -1;
|
|
|
|
if ((stream->hints & RFILE_HINT_UNBUFFERED) == 0)
|
|
{
|
|
#ifdef HAVE_CDROM
|
|
if (stream->scheme == VFS_SCHEME_CDROM)
|
|
return retro_vfs_file_tell_cdrom(stream);
|
|
#endif
|
|
#ifdef HAVE_SMBCLIENT
|
|
if (stream->scheme == VFS_SCHEME_SMB)
|
|
return retro_vfs_file_tell_smb(stream);
|
|
#endif
|
|
#ifdef HAVE_NFSCLIENT
|
|
if (stream->scheme == VFS_SCHEME_NFS)
|
|
return retro_vfs_file_tell_nfs(stream);
|
|
#endif
|
|
return retro_vfs_fp_tell64(stream->fp);
|
|
}
|
|
#ifdef VFS_HAVE_FILE_MAPPING
|
|
/* Need to check stream->mapped because this function
|
|
* is called in filestream_open() */
|
|
if (stream->mapped && (stream->hints &
|
|
RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS))
|
|
return stream->mappos;
|
|
#endif
|
|
return retro_vfs_fd_seek64(stream->fd, 0, SEEK_CUR);
|
|
}
|
|
|
|
int64_t retro_vfs_file_seek_impl(libretro_vfs_implementation_file *stream,
|
|
int64_t offset, int seek_position)
|
|
{
|
|
return retro_vfs_file_seek_internal(stream, offset, seek_position);
|
|
}
|
|
|
|
int64_t retro_vfs_file_read_impl(libretro_vfs_implementation_file *stream,
|
|
void *s, uint64_t len)
|
|
{
|
|
if (!stream || !s)
|
|
return -1;
|
|
|
|
/* A length with the top bit set is a negative int64 that arrived
|
|
* through the unsigned parameter - no legitimate caller reads 8EiB.
|
|
* The stdio path below hands len to fread() unclamped, where bionic
|
|
* FORTIFY turns it into a process abort (observed in the field);
|
|
* fail the read instead, like the mapped path's clamp below already
|
|
* does for its overflow case. */
|
|
if (len > (uint64_t)INT64_MAX)
|
|
return -1;
|
|
|
|
if ((stream->hints & RFILE_HINT_UNBUFFERED) == 0)
|
|
{
|
|
#ifdef HAVE_CDROM
|
|
if (stream->scheme == VFS_SCHEME_CDROM)
|
|
return retro_vfs_file_read_cdrom(stream, s, len);
|
|
#endif
|
|
#ifdef HAVE_SMBCLIENT
|
|
if (stream->scheme == VFS_SCHEME_SMB)
|
|
return retro_vfs_file_read_smb(stream, s, len);
|
|
#endif
|
|
#ifdef HAVE_NFSCLIENT
|
|
if (stream->scheme == VFS_SCHEME_NFS)
|
|
return retro_vfs_file_read_nfs(stream, s, len);
|
|
#endif
|
|
/* bionic's stdio dispatches fread through the FILE's int-typed
|
|
* read callback: a single count above INT_MAX truncates to a
|
|
* negative int inside libc, __sread sign-extends it back into
|
|
* read(), and FORTIFY aborts the process ("read: count ... >
|
|
* SSIZE_MAX"). Chunk the transfer so no single libc call sees
|
|
* a count any stdio cannot take. */
|
|
{
|
|
uint8_t *p = (uint8_t*)s;
|
|
uint64_t total = 0;
|
|
while (len != 0)
|
|
{
|
|
size_t _chunk = (len > VFS_STDIO_IO_CHUNK_MAX)
|
|
? (size_t)VFS_STDIO_IO_CHUNK_MAX : (size_t)len;
|
|
size_t _got = fread(p, 1, _chunk, stream->fp);
|
|
total += _got;
|
|
if (_got < _chunk)
|
|
break;
|
|
p += _got;
|
|
len -= _got;
|
|
}
|
|
return (int64_t)total;
|
|
}
|
|
}
|
|
#ifdef VFS_HAVE_FILE_MAPPING
|
|
if (stream->hints & RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS)
|
|
{
|
|
if (stream->mappos >= stream->mapsize)
|
|
{
|
|
/* At or past EOF: 0 bytes is the correct return for
|
|
* fread-style semantics on a legitimate read that reached
|
|
* EOF, -1 if we were already past EOF (corrupt state). */
|
|
if (stream->mappos == stream->mapsize)
|
|
return 0;
|
|
return -1;
|
|
}
|
|
|
|
/* Clamp len against the remaining mapped bytes. Done as an
|
|
* unsigned subtraction *before* computing mappos+len to avoid
|
|
* integer overflow: mappos+len can wrap past mapsize when
|
|
* both operands are large uint64_t values, defeating the
|
|
* naive "mappos + len > mapsize" bound check. */
|
|
{
|
|
uint64_t remaining = stream->mapsize - stream->mappos;
|
|
if (len > remaining)
|
|
len = remaining;
|
|
}
|
|
|
|
memcpy(s, &stream->mapped[stream->mappos], (size_t)len);
|
|
stream->mappos += len;
|
|
|
|
return (int64_t)len;
|
|
}
|
|
#endif
|
|
|
|
/* One read() moves at most MAX_RW_COUNT (INT_MAX & PAGE_MASK) on
|
|
* Linux and returns short past it, so a large request needs the
|
|
* same chunk loop as the stdio path to honor the full count. */
|
|
{
|
|
uint8_t *p = (uint8_t*)s;
|
|
uint64_t total = 0;
|
|
while (len != 0)
|
|
{
|
|
size_t _chunk = (len > VFS_STDIO_IO_CHUNK_MAX)
|
|
? (size_t)VFS_STDIO_IO_CHUNK_MAX : (size_t)len;
|
|
#if defined(VITA)
|
|
ssize_t _got = (ssize_t)sceIoRead((SceUID)stream->fd, p, (SceSize)_chunk);
|
|
#else
|
|
ssize_t _got = read(stream->fd, p, _chunk);
|
|
#endif
|
|
if (_got < 0)
|
|
return (total != 0) ? (int64_t)total : -1;
|
|
total += (uint64_t)_got;
|
|
if ((size_t)_got < _chunk)
|
|
break;
|
|
p += _got;
|
|
len -= (uint64_t)_got;
|
|
}
|
|
return (int64_t)total;
|
|
}
|
|
}
|
|
|
|
int64_t retro_vfs_file_write_impl(libretro_vfs_implementation_file *stream, const void *s, uint64_t len)
|
|
{
|
|
int64_t pos = 0;
|
|
ssize_t ret = -1;
|
|
|
|
if (!stream)
|
|
return -1;
|
|
|
|
if ((stream->hints & RFILE_HINT_UNBUFFERED) == 0)
|
|
{
|
|
#ifdef HAVE_NFSCLIENT
|
|
if (stream->scheme == VFS_SCHEME_NFS)
|
|
{
|
|
pos = retro_vfs_file_tell_nfs(stream);
|
|
ret = retro_vfs_file_write_nfs(stream, s, len);
|
|
if (ret != -1 && pos + ret > stream->size)
|
|
stream->size = pos + ret;
|
|
return ret;
|
|
}
|
|
#endif
|
|
#ifdef HAVE_SMBCLIENT
|
|
if (stream->scheme == VFS_SCHEME_SMB)
|
|
{
|
|
pos = retro_vfs_file_tell_smb(stream);
|
|
ret = retro_vfs_file_write_smb(stream, s, len);
|
|
if (ret != -1 && pos + ret > stream->size)
|
|
stream->size = pos + ret;
|
|
return ret;
|
|
}
|
|
#endif
|
|
pos = retro_vfs_file_tell_impl(stream);
|
|
/* Same int-typed stdio callback contract as the read side:
|
|
* chunk so no single fwrite count exceeds what bionic takes. */
|
|
{
|
|
const uint8_t *p = (const uint8_t*)s;
|
|
uint64_t total = 0;
|
|
while (len != 0)
|
|
{
|
|
size_t _chunk = (len > VFS_STDIO_IO_CHUNK_MAX)
|
|
? (size_t)VFS_STDIO_IO_CHUNK_MAX : (size_t)len;
|
|
size_t _put = fwrite(p, 1, _chunk, stream->fp);
|
|
total += _put;
|
|
if (_put < _chunk)
|
|
break;
|
|
p += _put;
|
|
len -= _put;
|
|
}
|
|
ret = (ssize_t)total;
|
|
}
|
|
|
|
if (ret > 0 && pos + ret > stream->size)
|
|
stream->size = pos + ret;
|
|
|
|
return ret;
|
|
}
|
|
#ifdef VFS_HAVE_FILE_MAPPING
|
|
if (stream->hints & RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS)
|
|
return -1;
|
|
#endif
|
|
|
|
pos = retro_vfs_file_tell_impl(stream);
|
|
/* write() shares read()'s MAX_RW_COUNT clamp; loop for the full
|
|
* count. */
|
|
{
|
|
const uint8_t *p = (const uint8_t*)s;
|
|
uint64_t total = 0;
|
|
while (len != 0)
|
|
{
|
|
size_t _chunk = (len > VFS_STDIO_IO_CHUNK_MAX)
|
|
? (size_t)VFS_STDIO_IO_CHUNK_MAX : (size_t)len;
|
|
#if defined(VITA)
|
|
ssize_t _put = (ssize_t)sceIoWrite((SceUID)stream->fd, p, (SceSize)_chunk);
|
|
#else
|
|
ssize_t _put = write(stream->fd, p, _chunk);
|
|
#endif
|
|
if (_put < 0)
|
|
{
|
|
if (total == 0)
|
|
return -1;
|
|
break;
|
|
}
|
|
total += (uint64_t)_put;
|
|
if ((size_t)_put < _chunk)
|
|
break;
|
|
p += _put;
|
|
len -= (uint64_t)_put;
|
|
}
|
|
ret = (ssize_t)total;
|
|
}
|
|
|
|
if (ret != -1 && pos + ret > stream->size)
|
|
stream->size = pos + ret;
|
|
|
|
return ret;
|
|
}
|
|
|
|
int retro_vfs_file_flush_impl(libretro_vfs_implementation_file *stream)
|
|
{
|
|
if (!stream)
|
|
return -1;
|
|
#ifdef HAVE_CDROM
|
|
if (stream->scheme == VFS_SCHEME_CDROM)
|
|
return 0;
|
|
#endif
|
|
#ifdef HAVE_SMBCLIENT
|
|
if (stream->scheme == VFS_SCHEME_SMB)
|
|
return 0;
|
|
#endif
|
|
#ifdef HAVE_NFSCLIENT
|
|
if (stream->scheme == VFS_SCHEME_NFS)
|
|
return 0;
|
|
#endif
|
|
if (stream->fp && fflush(stream->fp) == 0)
|
|
return 0;
|
|
return -1;
|
|
}
|
|
|
|
int retro_vfs_file_remove_impl(const char *path)
|
|
{
|
|
if (path && *path)
|
|
{
|
|
int ret = -1;
|
|
|
|
#if defined(ANDROID) && defined(HAVE_SAF)
|
|
if (path_is_saf(path))
|
|
{
|
|
struct libretro_vfs_implementation_saf_path_split_result saf_split_result;
|
|
if (!retro_vfs_path_split_saf(&saf_split_result, path))
|
|
return -1;
|
|
ret = retro_vfs_file_remove_saf(saf_split_result.tree, saf_split_result.path);
|
|
free(saf_split_result.path);
|
|
free(saf_split_result.tree);
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
#if defined(_WIN32) && !defined(_XBOX)
|
|
/* Win32 (no Xbox) */
|
|
#if defined(LEGACY_WIN32_RUNTIME)
|
|
if (win32_needs_local_encoding())
|
|
{
|
|
char *path_local = NULL;
|
|
if ((path_local = utf8_to_local_string_alloc(path)))
|
|
{
|
|
/* We need to check if path is a directory */
|
|
if ((retro_vfs_stat_impl(path, NULL) & RETRO_VFS_STAT_IS_DIRECTORY) != 0)
|
|
ret = _rmdir(path_local);
|
|
else
|
|
ret = remove(path_local);
|
|
free(path_local);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
wchar_t *path_wide = NULL;
|
|
if ((path_wide = utf8_to_utf16_string_alloc(path)))
|
|
{
|
|
/* We need to check if path is a directory */
|
|
if ((retro_vfs_stat_impl(path, NULL) & RETRO_VFS_STAT_IS_DIRECTORY) != 0)
|
|
ret = _wrmdir(path_wide);
|
|
else
|
|
ret = _wremove(path_wide);
|
|
free(path_wide);
|
|
}
|
|
}
|
|
#elif defined(LEGACY_WIN32)
|
|
char *path_local = NULL;
|
|
if ((path_local = utf8_to_local_string_alloc(path)))
|
|
{
|
|
/* We need to check if path is a directory */
|
|
if ((retro_vfs_stat_impl(path, NULL) & RETRO_VFS_STAT_IS_DIRECTORY) != 0)
|
|
ret = _rmdir(path_local);
|
|
else
|
|
ret = remove(path_local);
|
|
free(path_local);
|
|
}
|
|
#else
|
|
wchar_t *path_wide = NULL;
|
|
if ((path_wide = utf8_to_utf16_string_alloc(path)))
|
|
{
|
|
/* We need to check if path is a directory */
|
|
if ((retro_vfs_stat_impl(path, NULL) & RETRO_VFS_STAT_IS_DIRECTORY) != 0)
|
|
ret = _wrmdir(path_wide);
|
|
else
|
|
ret = _wremove(path_wide);
|
|
free(path_wide);
|
|
}
|
|
#endif
|
|
#else
|
|
ret = remove(path);
|
|
#endif
|
|
if (ret == 0)
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
#if defined(_WIN32) && !defined(_XBOX) && (defined(LEGACY_WIN32_RUNTIME) || defined(LEGACY_WIN32))
|
|
/* Replace @new_local with @old_local where MoveFileEx() is not
|
|
* available (Windows 9x): a destination in the way is moved aside,
|
|
* the source renamed into place, and the moved-aside copy removed
|
|
* only then - or put back if the second rename fails. Both paths
|
|
* are in the local code page. */
|
|
static int win32_rename_replace_local(const char *old_local,
|
|
const char *new_local)
|
|
{
|
|
size_t _len;
|
|
char *aside;
|
|
int ret;
|
|
|
|
if (rename(old_local, new_local) == 0)
|
|
return 0;
|
|
|
|
_len = strlen(new_local);
|
|
if (!(aside = (char*)malloc(_len + sizeof(".old"))))
|
|
return -1;
|
|
memcpy(aside, new_local, _len);
|
|
memcpy(aside + _len, ".old", sizeof(".old"));
|
|
|
|
ret = -1;
|
|
remove(aside); /* a leftover from an earlier run */
|
|
if (rename(new_local, aside) == 0)
|
|
{
|
|
if (rename(old_local, new_local) == 0)
|
|
{
|
|
remove(aside);
|
|
ret = 0;
|
|
}
|
|
else
|
|
rename(aside, new_local);
|
|
}
|
|
|
|
free(aside);
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
int retro_vfs_file_rename_impl(const char *old_path, const char *new_path)
|
|
{
|
|
#if defined(ANDROID) && defined(HAVE_SAF)
|
|
if (path_is_saf(old_path) && path_is_saf(new_path))
|
|
{
|
|
int ret;
|
|
struct libretro_vfs_implementation_saf_path_split_result saf_split_result_old, saf_split_result_new;
|
|
if (!retro_vfs_path_split_saf(&saf_split_result_old, old_path))
|
|
return -1;
|
|
if (!retro_vfs_path_split_saf(&saf_split_result_new, new_path))
|
|
{
|
|
free(saf_split_result_old.path);
|
|
free(saf_split_result_old.tree);
|
|
return -1;
|
|
}
|
|
ret = retro_vfs_file_rename_saf(saf_split_result_old.tree, saf_split_result_old.path, saf_split_result_new.tree, saf_split_result_new.path);
|
|
free(saf_split_result_new.path);
|
|
free(saf_split_result_new.tree);
|
|
free(saf_split_result_old.path);
|
|
free(saf_split_result_old.tree);
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
#if defined(_WIN32) && !defined(_XBOX)
|
|
/* Win32 (no Xbox) */
|
|
int ret = -1;
|
|
|
|
if (!old_path || !*old_path || !new_path || !*new_path)
|
|
return -1;
|
|
|
|
/* Neither rename() nor _wrename() can replace an existing
|
|
* destination on Windows, which breaks writing to a temporary and
|
|
* renaming it over the original. MoveFileExW with
|
|
* MOVEFILE_REPLACE_EXISTING gives the POSIX overwrite behaviour.
|
|
* It is unsupported on 9x, so the local-encoding path moves the
|
|
* destination aside and renames into its place. */
|
|
#if defined(LEGACY_WIN32_RUNTIME)
|
|
if (win32_needs_local_encoding())
|
|
{
|
|
char *old_path_local = utf8_to_local_string_alloc(old_path);
|
|
|
|
if (old_path_local)
|
|
{
|
|
char *new_path_local = utf8_to_local_string_alloc(new_path);
|
|
|
|
if (new_path_local)
|
|
{
|
|
ret = win32_rename_replace_local(old_path_local, new_path_local);
|
|
free(new_path_local);
|
|
}
|
|
|
|
free(old_path_local);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
wchar_t *old_path_wide = utf8_to_utf16_string_alloc(old_path);
|
|
|
|
if (old_path_wide)
|
|
{
|
|
wchar_t *new_path_wide = utf8_to_utf16_string_alloc(new_path);
|
|
|
|
if (new_path_wide)
|
|
{
|
|
if (MoveFileExW(old_path_wide, new_path_wide,
|
|
MOVEFILE_REPLACE_EXISTING))
|
|
ret = 0;
|
|
free(new_path_wide);
|
|
}
|
|
|
|
free(old_path_wide);
|
|
}
|
|
}
|
|
#elif defined(LEGACY_WIN32)
|
|
{
|
|
char *old_path_local = utf8_to_local_string_alloc(old_path);
|
|
|
|
if (old_path_local)
|
|
{
|
|
char *new_path_local = utf8_to_local_string_alloc(new_path);
|
|
|
|
if (new_path_local)
|
|
{
|
|
ret = win32_rename_replace_local(old_path_local, new_path_local);
|
|
free(new_path_local);
|
|
}
|
|
|
|
free(old_path_local);
|
|
}
|
|
}
|
|
#else
|
|
{
|
|
wchar_t *old_path_wide = utf8_to_utf16_string_alloc(old_path);
|
|
|
|
if (old_path_wide)
|
|
{
|
|
wchar_t *new_path_wide = utf8_to_utf16_string_alloc(new_path);
|
|
|
|
if (new_path_wide)
|
|
{
|
|
if (MoveFileExW(old_path_wide, new_path_wide,
|
|
MOVEFILE_REPLACE_EXISTING))
|
|
ret = 0;
|
|
free(new_path_wide);
|
|
}
|
|
|
|
free(old_path_wide);
|
|
}
|
|
}
|
|
#endif
|
|
return ret;
|
|
|
|
#elif defined(_XBOX)
|
|
/* MoveFileEx() is not in the XDK and the CRT rename() refuses an
|
|
* existing destination, so a destination in the way is moved
|
|
* aside with MoveFile(), the source moved into place, and the
|
|
* moved-aside copy deleted only then - or put back if the second
|
|
* move fails. */
|
|
{
|
|
size_t _len;
|
|
char *aside;
|
|
int ret;
|
|
|
|
if (!old_path || !*old_path || !new_path || !*new_path)
|
|
return -1;
|
|
|
|
if (GetFileAttributes(new_path) == INVALID_FILE_ATTRIBUTES)
|
|
return MoveFile(old_path, new_path) ? 0 : -1;
|
|
|
|
_len = strlen(new_path);
|
|
if (!(aside = (char*)malloc(_len + sizeof(".old"))))
|
|
return -1;
|
|
memcpy(aside, new_path, _len);
|
|
memcpy(aside + _len, ".old", sizeof(".old"));
|
|
|
|
ret = -1;
|
|
DeleteFile(aside); /* a leftover from an earlier run */
|
|
if (MoveFile(new_path, aside))
|
|
{
|
|
if (MoveFile(old_path, new_path))
|
|
{
|
|
DeleteFile(aside);
|
|
ret = 0;
|
|
}
|
|
else
|
|
MoveFile(aside, new_path);
|
|
}
|
|
|
|
free(aside);
|
|
return ret;
|
|
}
|
|
#elif defined(VITA) || defined(PSP)
|
|
/* rename() here means "replace": the Win32 branch above says so
|
|
* with MOVEFILE_REPLACE_EXISTING and POSIX says so by definition,
|
|
* and the write-to-temporary-then-rename pattern - playlists, the
|
|
* config file, core info - is built on it. The kernel's own
|
|
* sceIoRename() refuses an existing destination on both PSP and
|
|
* Vita. pspsdk's rename() passes that refusal through; Vita's
|
|
* newlib bridges the gap by sceIoRemove()ing the destination
|
|
* first and renaming after. Between those two calls nothing is
|
|
* on disk, and if the rename then fails the caller's temporary
|
|
* is discarded on top: the file that was being replaced is
|
|
* simply gone.
|
|
*
|
|
* So call the kernel directly and, when the destination is in the
|
|
* way, move it aside rather than delete it. The old file is only
|
|
* removed once its replacement is in place, and is put back if
|
|
* the replacement cannot be. */
|
|
{
|
|
SceIoStat st;
|
|
size_t _len;
|
|
char *aside;
|
|
int ret;
|
|
|
|
if (!old_path || !*old_path || !new_path || !*new_path)
|
|
return -1;
|
|
|
|
if (sceIoRename(old_path, new_path) >= 0)
|
|
return 0;
|
|
|
|
/* Only worth trying when there is a destination to move aside. */
|
|
if (sceIoGetstat(new_path, &st) < 0)
|
|
return -1;
|
|
|
|
_len = strlen(new_path);
|
|
if (!(aside = (char*)malloc(_len + sizeof(".old"))))
|
|
return -1;
|
|
memcpy(aside, new_path, _len);
|
|
memcpy(aside + _len, ".old", sizeof(".old"));
|
|
|
|
ret = -1;
|
|
/* A leftover aside from an earlier run is removed only when it
|
|
* is in the way: a lookup of a missing name scans the directory. */
|
|
if ( sceIoRename(new_path, aside) >= 0
|
|
|| ( sceIoRemove(aside) >= 0
|
|
&& sceIoRename(new_path, aside) >= 0))
|
|
{
|
|
if (sceIoRename(old_path, new_path) >= 0)
|
|
{
|
|
sceIoRemove(aside);
|
|
ret = 0;
|
|
}
|
|
else
|
|
sceIoRename(aside, new_path);
|
|
}
|
|
|
|
free(aside);
|
|
return ret;
|
|
}
|
|
#elif defined(_3DS) || defined(PS2) || defined(WIIU) || defined(GEKKO) || defined(__PSL1GHT__) || defined(__PS3__) || (defined(SWITCH) && defined(HAVE_LIBNX))
|
|
/* The filesystems here refuse to rename onto an existing entry -
|
|
* FS on 3DS and Switch, FatFs behind bdmfs_fatfs on PS2, libfat
|
|
* on Wii and GameCube, LV2's sys_fs_rename on PS3 - and libnx,
|
|
* ps2sdk, libfat and PSL1GHT pass that refusal through, while libctru
|
|
* clears the way by deleting the destination first, leaving
|
|
* nothing on disk until the rename lands. Wii U's FSRename()
|
|
* is taken to behave the same way. So when a destination is in
|
|
* the way, move it aside before calling rename() at all: it is
|
|
* removed only once its replacement is in place, and put back
|
|
* if the replacement cannot be. */
|
|
{
|
|
struct stat st;
|
|
size_t _len;
|
|
char *aside;
|
|
int ret;
|
|
|
|
if (!old_path || !*old_path || !new_path || !*new_path)
|
|
return -1;
|
|
|
|
if (stat(new_path, &st) != 0)
|
|
return rename(old_path, new_path) == 0 ? 0 : -1;
|
|
|
|
_len = strlen(new_path);
|
|
if (!(aside = (char*)malloc(_len + sizeof(".old"))))
|
|
return -1;
|
|
memcpy(aside, new_path, _len);
|
|
memcpy(aside + _len, ".old", sizeof(".old"));
|
|
|
|
ret = -1;
|
|
/* A leftover aside from an earlier run is removed only when it
|
|
* is in the way: a lookup of a missing name scans the directory. */
|
|
if ( rename(new_path, aside) == 0
|
|
|| ( remove(aside) == 0
|
|
&& rename(new_path, aside) == 0))
|
|
{
|
|
if (rename(old_path, new_path) == 0)
|
|
{
|
|
remove(aside);
|
|
ret = 0;
|
|
}
|
|
else
|
|
rename(aside, new_path);
|
|
}
|
|
|
|
free(aside);
|
|
return ret;
|
|
}
|
|
#else
|
|
/* Every other platform */
|
|
if (!old_path || !*old_path || !new_path || !*new_path)
|
|
return -1;
|
|
return rename(old_path, new_path) == 0 ? 0 : -1;
|
|
#endif
|
|
}
|
|
|
|
const char *retro_vfs_file_get_path_impl(
|
|
libretro_vfs_implementation_file *stream)
|
|
{
|
|
if (!stream)
|
|
return NULL;
|
|
return stream->orig_path;
|
|
}
|
|
|
|
#if defined(_WIN32) && !defined(_XBOX) && !defined(HAVE_MMAP)
|
|
/* PrefetchVirtualMemory is Windows 8 and later; resolved by name so the
|
|
* binary still loads where it is absent. */
|
|
typedef struct
|
|
{
|
|
PVOID VirtualAddress;
|
|
SIZE_T NumberOfBytes;
|
|
} vfs_win32_memory_range_t;
|
|
typedef BOOL (WINAPI *vfs_prefetch_t)(HANDLE, ULONG_PTR,
|
|
vfs_win32_memory_range_t*, ULONG);
|
|
|
|
static vfs_prefetch_t vfs_win32_prefetch(void)
|
|
{
|
|
static vfs_prefetch_t p_prefetch;
|
|
static int resolved;
|
|
|
|
if (!resolved)
|
|
{
|
|
HMODULE k32 = GetModuleHandleA("kernel32.dll");
|
|
|
|
resolved = 1;
|
|
if (k32)
|
|
p_prefetch = (vfs_prefetch_t)GetProcAddress(k32,
|
|
"PrefetchVirtualMemory");
|
|
}
|
|
return p_prefetch;
|
|
}
|
|
#endif
|
|
|
|
void retro_vfs_file_prefetch_impl(
|
|
libretro_vfs_implementation_file *stream, uint64_t offset,
|
|
uint64_t len)
|
|
{
|
|
if (!stream || !len)
|
|
return;
|
|
if (stream->scheme != VFS_SCHEME_NONE)
|
|
return;
|
|
|
|
#ifdef VFS_HAVE_FILE_MAPPING
|
|
if (stream->mapped
|
|
&& (stream->hints & RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS))
|
|
{
|
|
size_t page;
|
|
size_t lo, hi;
|
|
|
|
if (offset >= stream->mapsize)
|
|
return;
|
|
if (len > stream->mapsize - offset)
|
|
len = stream->mapsize - offset;
|
|
#if defined(HAVE_MMAP)
|
|
{
|
|
long sz = sysconf(_SC_PAGESIZE);
|
|
page = sz > 0 ? (size_t)sz : 0;
|
|
}
|
|
#else
|
|
{
|
|
SYSTEM_INFO si;
|
|
GetSystemInfo(&si);
|
|
page = si.dwPageSize;
|
|
}
|
|
#endif
|
|
if (!page)
|
|
return;
|
|
lo = (size_t)offset & ~(page - 1);
|
|
hi = (size_t)(offset + len);
|
|
#if defined(HAVE_MMAP)
|
|
madvise(stream->mapped + lo, hi - lo, MADV_WILLNEED);
|
|
#else
|
|
{
|
|
vfs_prefetch_t prefetch = vfs_win32_prefetch();
|
|
vfs_win32_memory_range_t range;
|
|
|
|
if (!prefetch)
|
|
return;
|
|
range.VirtualAddress = stream->mapped + lo;
|
|
range.NumberOfBytes = hi - lo;
|
|
prefetch(GetCurrentProcess(), 1, &range, 0);
|
|
}
|
|
#endif
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
#if defined(POSIX_FADV_WILLNEED) && !defined(_WIN32)
|
|
if (stream->fd >= 0)
|
|
posix_fadvise(stream->fd, (off_t)offset, (off_t)len,
|
|
POSIX_FADV_WILLNEED);
|
|
#endif
|
|
}
|
|
|
|
const uint8_t *retro_vfs_file_get_mapped_ptr_impl(
|
|
libretro_vfs_implementation_file *stream, int64_t *len)
|
|
{
|
|
if (len)
|
|
*len = 0;
|
|
#ifdef VFS_HAVE_FILE_MAPPING
|
|
/* Gate on the hint as well as the pointer, matching the read and
|
|
* seek paths: those consult 'mapped' only under the hint, so the
|
|
* map is authoritative for the file contents only when the hint
|
|
* put it there. */
|
|
if ( stream
|
|
&& stream->mapped
|
|
&& (stream->hints & RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS))
|
|
{
|
|
if (len)
|
|
*len = (int64_t)stream->mapsize;
|
|
return stream->mapped;
|
|
}
|
|
#else
|
|
(void)stream;
|
|
#endif
|
|
return NULL;
|
|
}
|
|
|
|
#if defined(_WIN32) && !defined(VITA) && !defined(__PSL1GHT__) && !defined(__PS3__)
|
|
#if defined(LEGACY_WIN32) || defined(LEGACY_WIN32_RUNTIME)
|
|
/* Use _stat64 explicitly to match the struct _stat64 buffer the caller
|
|
* declares. The bare _stat is a macro that expands to _stat64i32 on
|
|
* VS2005+ (or _stat32 with _USE_32BIT_TIME_T), neither of which match
|
|
* struct _stat64 -- passing the wrong struct silently truncates
|
|
* st_size. _stat64 has been in MSVC since VS2003 (_MSC_VER >= 1300)
|
|
* and is provided by mingw-w64. VC6 has no 64-bit time_t at all;
|
|
* _stati64 is the only match. */
|
|
/* GetFileAttributesEx rather than GetFileAttributes: same availability
|
|
* (Win98/NT4+), and it also hands back the last-write FILETIME, which
|
|
* unlike the CRT's st_mtime can represent dates before 1970. */
|
|
static int vfs_stat_win32_ansi(const char *path,
|
|
struct _stat64 *stat_buf, DWORD *file_info, FILETIME *mtime_ft)
|
|
{
|
|
WIN32_FILE_ATTRIBUTE_DATA ad;
|
|
char *path_local = utf8_to_local_string_alloc(path);
|
|
|
|
if (!path_local)
|
|
return 0;
|
|
|
|
if (!GetFileAttributesExA(path_local, GetFileExInfoStandard, &ad))
|
|
{
|
|
free(path_local);
|
|
return 0;
|
|
}
|
|
*file_info = ad.dwFileAttributes;
|
|
*mtime_ft = ad.ftLastWriteTime;
|
|
|
|
#if defined(_MSC_VER) && _MSC_VER < 1300
|
|
if (_stati64(path_local, (struct _stati64*)stat_buf) != 0)
|
|
#else
|
|
if (_stat64(path_local, stat_buf) != 0)
|
|
#endif
|
|
{
|
|
free(path_local);
|
|
return 0;
|
|
}
|
|
|
|
free(path_local);
|
|
return 1;
|
|
}
|
|
#endif
|
|
|
|
#if !defined(LEGACY_WIN32) || defined(LEGACY_WIN32_RUNTIME)
|
|
static int vfs_stat_win32_wide(const char *path,
|
|
struct _stat64 *stat_buf, DWORD *file_info, FILETIME *mtime_ft)
|
|
{
|
|
WIN32_FILE_ATTRIBUTE_DATA ad;
|
|
wchar_t *path_wide = utf8_to_utf16_string_alloc(path);
|
|
|
|
if (!path_wide)
|
|
return 0;
|
|
|
|
if (!GetFileAttributesExW(path_wide, GetFileExInfoStandard, &ad))
|
|
{
|
|
free(path_wide);
|
|
return 0;
|
|
}
|
|
*file_info = ad.dwFileAttributes;
|
|
*mtime_ft = ad.ftLastWriteTime;
|
|
|
|
if (_wstat64(path_wide, stat_buf) != 0)
|
|
{
|
|
free(path_wide);
|
|
return 0;
|
|
}
|
|
|
|
free(path_wide);
|
|
return 1;
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
/* One platform ladder serves stat, stat_64, get_mtime and the POSIX
|
|
* fallback of dirent_stat. @mtime is filled (seconds since the Unix
|
|
* epoch) where the platform reports one; the SMB and SAF backends do
|
|
* not carry it through their stat helpers yet, so it is left untouched
|
|
* there and callers treat -1 from get_mtime as "unavailable". */
|
|
static int retro_vfs_stat_full(const char *path, int64_t *size, int64_t *mtime)
|
|
{
|
|
int ret = RETRO_VFS_STAT_IS_VALID;
|
|
|
|
if (!path || !*path)
|
|
return 0;
|
|
|
|
#ifdef HAVE_SMBCLIENT
|
|
if (path_is_smb(path))
|
|
return retro_vfs_stat_smb(path, size);
|
|
#endif
|
|
#ifdef HAVE_NFSCLIENT
|
|
if (path_is_nfs(path))
|
|
return retro_vfs_stat_nfs(path, size);
|
|
#endif
|
|
|
|
#if defined(ANDROID) && defined(HAVE_SAF)
|
|
if (path_is_saf(path))
|
|
{
|
|
struct libretro_vfs_implementation_saf_path_split_result saf_split_result;
|
|
if (!retro_vfs_path_split_saf(&saf_split_result, path))
|
|
return 0;
|
|
ret = retro_vfs_stat_saf(saf_split_result.tree, saf_split_result.path, size);
|
|
free(saf_split_result.path);
|
|
free(saf_split_result.tree);
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
{
|
|
#if defined(VITA)
|
|
/* Vita / PSP */
|
|
SceIoStat stat_buf;
|
|
int dir_ret;
|
|
char path_buf[PATH_MAX_LENGTH];
|
|
size_t _len = strlcpy(path_buf, path, sizeof(path_buf));
|
|
if (_len > 0 && path_buf[_len-1] == '/')
|
|
path_buf[_len-1] = '\0';
|
|
|
|
dir_ret = sceIoGetstat(path_buf, &stat_buf);
|
|
if (dir_ret < 0)
|
|
return 0;
|
|
|
|
if (size)
|
|
*size = (int64_t)stat_buf.st_size;
|
|
if (mtime)
|
|
{
|
|
time_t t = 0;
|
|
sceRtcGetTime_t(&stat_buf.st_mtime, &t);
|
|
*mtime = (int64_t)t;
|
|
}
|
|
|
|
if (FIO_S_ISDIR(stat_buf.st_mode))
|
|
ret |= RETRO_VFS_STAT_IS_DIRECTORY;
|
|
if (!(stat_buf.st_mode & SCE_S_IWUSR))
|
|
ret |= RETRO_VFS_STAT_IS_READONLY;
|
|
#elif defined(__PSL1GHT__) || defined(__PS3__)
|
|
/* Lowlevel Lv2 */
|
|
sysFSStat stat_buf;
|
|
|
|
if (sysFsStat(path, &stat_buf) < 0)
|
|
return 0;
|
|
|
|
if (size)
|
|
*size = (int64_t)stat_buf.st_size;
|
|
if (mtime)
|
|
*mtime = (int64_t)stat_buf.st_mtime;
|
|
|
|
if ((stat_buf.st_mode & S_IFMT) == S_IFDIR)
|
|
ret |= RETRO_VFS_STAT_IS_DIRECTORY;
|
|
if (!(stat_buf.st_mode & S_IWUSR))
|
|
ret |= RETRO_VFS_STAT_IS_READONLY;
|
|
#elif defined(_WIN32)
|
|
/* Windows
|
|
* Older MSVC _stat may fail on directory paths
|
|
* with a trailing backslash */
|
|
struct _stat64 stat_buf;
|
|
FILETIME mtime_ft;
|
|
char path_buf[PATH_MAX_LENGTH];
|
|
const char *stat_path = path;
|
|
DWORD file_info;
|
|
size_t _len = strlcpy(path_buf, path, sizeof(path_buf));
|
|
|
|
if (_len > 0 && _len < sizeof(path_buf))
|
|
{
|
|
while (_len > 0 &&
|
|
(path_buf[_len - 1] == '\\' || path_buf[_len - 1] == '/'))
|
|
{
|
|
/* Keep drive roots like "C:\" intact */
|
|
if (_len == 3 &&
|
|
((((path_buf[0] >= 'A') && (path_buf[0] <= 'Z')) ||
|
|
((path_buf[0] >= 'a') && (path_buf[0] <= 'z'))) &&
|
|
path_buf[1] == ':' && path_buf[2] == '\\'))
|
|
break;
|
|
|
|
path_buf[--_len] = '\0';
|
|
}
|
|
|
|
stat_path = path_buf;
|
|
}
|
|
#if defined(LEGACY_WIN32_RUNTIME)
|
|
if (win32_needs_local_encoding())
|
|
{
|
|
if (!vfs_stat_win32_ansi(stat_path, &stat_buf, &file_info, &mtime_ft))
|
|
return 0;
|
|
}
|
|
else if (!vfs_stat_win32_wide(stat_path, &stat_buf, &file_info, &mtime_ft))
|
|
return 0;
|
|
#elif defined(LEGACY_WIN32)
|
|
if (!vfs_stat_win32_ansi(stat_path, &stat_buf, &file_info, &mtime_ft))
|
|
return 0;
|
|
#else
|
|
if (!vfs_stat_win32_wide(stat_path, &stat_buf, &file_info, &mtime_ft))
|
|
return 0;
|
|
#endif
|
|
|
|
if (size)
|
|
*size = (int64_t)stat_buf.st_size;
|
|
if (mtime)
|
|
*mtime = vfs_filetime_to_unix(&mtime_ft);
|
|
|
|
if (file_info & FILE_ATTRIBUTE_DIRECTORY)
|
|
ret |= RETRO_VFS_STAT_IS_DIRECTORY;
|
|
if (file_info & FILE_ATTRIBUTE_READONLY)
|
|
ret |= RETRO_VFS_STAT_IS_READONLY;
|
|
#elif defined(GEKKO)
|
|
/* On GEKKO platforms, paths cannot have
|
|
* trailing slashes - we must therefore
|
|
* remove them */
|
|
size_t _len;
|
|
char path_buf[PATH_MAX_LENGTH];
|
|
struct stat stat_buf;
|
|
|
|
_len = strlcpy(path_buf, path, sizeof(path_buf));
|
|
if (_len > 0 && path_buf[_len - 1] == '/')
|
|
path_buf[_len - 1] = '\0';
|
|
|
|
if (stat(path_buf, &stat_buf) < 0)
|
|
return 0;
|
|
|
|
if (size)
|
|
*size = (int64_t)stat_buf.st_size;
|
|
if (mtime)
|
|
*mtime = (int64_t)stat_buf.st_mtime;
|
|
|
|
if (S_ISDIR(stat_buf.st_mode))
|
|
ret |= RETRO_VFS_STAT_IS_DIRECTORY;
|
|
if (S_ISCHR(stat_buf.st_mode))
|
|
ret |= RETRO_VFS_STAT_IS_CHARACTER_SPECIAL;
|
|
if (!(stat_buf.st_mode & S_IWUSR))
|
|
ret |= RETRO_VFS_STAT_IS_READONLY;
|
|
#else
|
|
/* Every other platform */
|
|
/* _LARGEFILE64_SOURCE is a request for the LFS64 API, not evidence
|
|
* that it exists: Darwin, the 3DS and PSP toolchains, DJGPP and musl
|
|
* from 1.2.4 have no stat64 to offer whoever asks. Paired with a libc
|
|
* known to carry it, so that a build defining the macro for its own
|
|
* reasons cannot turn this into a compile error. */
|
|
#if defined(_LARGEFILE64_SOURCE) \
|
|
&& (defined(__GLIBC__) || defined(__ANDROID__) || defined(__UCLIBC__))
|
|
struct stat64 stat_buf;
|
|
if (stat64(path, &stat_buf) < 0)
|
|
return 0;
|
|
#else
|
|
struct stat stat_buf;
|
|
|
|
if (stat(path, &stat_buf) < 0)
|
|
return 0;
|
|
#endif
|
|
|
|
if (size)
|
|
*size = (int64_t)stat_buf.st_size;
|
|
if (mtime)
|
|
*mtime = (int64_t)stat_buf.st_mtime;
|
|
|
|
if (S_ISDIR(stat_buf.st_mode))
|
|
ret |= RETRO_VFS_STAT_IS_DIRECTORY;
|
|
if (S_ISCHR(stat_buf.st_mode))
|
|
ret |= RETRO_VFS_STAT_IS_CHARACTER_SPECIAL;
|
|
if (!(stat_buf.st_mode & S_IWUSR))
|
|
ret |= RETRO_VFS_STAT_IS_READONLY;
|
|
#endif
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
int retro_vfs_stat_64_impl(const char *path, int64_t *size)
|
|
{
|
|
return retro_vfs_stat_full(path, size, NULL);
|
|
}
|
|
|
|
int retro_vfs_get_mtime_impl(const char *path, int64_t *mtime)
|
|
{
|
|
int64_t t = 0;
|
|
int flags;
|
|
bool got_it;
|
|
|
|
if (!mtime)
|
|
return -1;
|
|
|
|
/* The SMB/SAF stat helpers leave @mtime untouched; a sentinel that
|
|
* no real file system reports tells those cases apart from a genuine
|
|
* timestamp. */
|
|
t = INT64_MIN;
|
|
flags = retro_vfs_stat_full(path, NULL, &t);
|
|
got_it = (flags != 0) && (t != INT64_MIN);
|
|
if (!got_it)
|
|
return -1;
|
|
*mtime = t;
|
|
return 0;
|
|
}
|
|
|
|
int retro_vfs_stat_impl(const char *path, int32_t *size)
|
|
{
|
|
int64_t size64 = 0;
|
|
int ret = retro_vfs_stat_64_impl(path, size ? &size64 : NULL);
|
|
|
|
/* If a file is larger than 2 GiB, size64 holds the correct value
|
|
* but a naked (int32_t) cast would truncate -- worse, on files in
|
|
* (INT32_MAX, UINT32_MAX] the high bit wraps and callers see a
|
|
* negative size that they may interpret as an error. Saturate to
|
|
* INT_MAX so a caller using the legacy API gets a clamped-large
|
|
* value rather than a corrupted one, and migrate to
|
|
* retro_vfs_stat_64_impl for files that need the real size.
|
|
* INT_MAX is used instead of INT32_MAX for C89 / VC6 portability
|
|
* (stdint.h's INT32_MAX is a C99 addition; INT_MAX is C89). int
|
|
* is 32-bit on all MSVC targets including VC6, so INT_MAX ==
|
|
* INT32_MAX everywhere this code runs. */
|
|
if (size)
|
|
*size = (size64 > (int64_t)INT_MAX) ? INT_MAX : (int32_t)size64;
|
|
|
|
return ret;
|
|
}
|
|
|
|
#if defined(VITA)
|
|
#define path_mkdir_err(ret) (((ret) == SCE_ERROR_ERRNO_EEXIST))
|
|
#elif defined(PSP) || defined(PS2) || defined(_3DS) || defined(WIIU) || defined(SWITCH)
|
|
#define path_mkdir_err(ret) ((ret) == -1)
|
|
#else
|
|
#define path_mkdir_err(ret) ((ret) < 0 && errno == EEXIST)
|
|
#endif
|
|
|
|
int retro_vfs_mkdir_impl(const char *dir)
|
|
{
|
|
#if defined(ANDROID) && defined(HAVE_SAF)
|
|
if (path_is_saf(dir))
|
|
{
|
|
int ret;
|
|
struct libretro_vfs_implementation_saf_path_split_result saf_split_result;
|
|
if (!retro_vfs_path_split_saf(&saf_split_result, dir))
|
|
return -1;
|
|
ret = retro_vfs_mkdir_saf(saf_split_result.tree, saf_split_result.path);
|
|
free(saf_split_result.path);
|
|
free(saf_split_result.tree);
|
|
return ret;
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
#if defined(_WIN32)
|
|
#if defined(LEGACY_WIN32_RUNTIME)
|
|
int ret = -1;
|
|
|
|
if (win32_needs_local_encoding())
|
|
ret = _mkdir(dir);
|
|
else
|
|
{
|
|
wchar_t *dir_w = utf8_to_utf16_string_alloc(dir);
|
|
|
|
if (dir_w)
|
|
{
|
|
ret = _wmkdir(dir_w);
|
|
free(dir_w);
|
|
}
|
|
}
|
|
#elif defined(LEGACY_WIN32)
|
|
int ret = _mkdir(dir);
|
|
#else
|
|
wchar_t *dir_w = utf8_to_utf16_string_alloc(dir);
|
|
int ret = -1;
|
|
|
|
if (dir_w)
|
|
{
|
|
ret = _wmkdir(dir_w);
|
|
free(dir_w);
|
|
}
|
|
#endif
|
|
#elif TARGET_OS_IPHONE
|
|
int ret = mkdir(dir, 0755);
|
|
#elif defined(VITA)
|
|
int ret = sceIoMkdir(dir, 0777);
|
|
#elif defined(__QNX__)
|
|
int ret = mkdir(dir, 0777);
|
|
#elif defined(GEKKO) || defined(WIIU)
|
|
/* On GEKKO platforms, mkdir() fails if
|
|
* the path has a trailing slash. We must
|
|
* therefore remove it. */
|
|
int ret = -1;
|
|
char *dir_buf = strdup(dir);
|
|
|
|
if (dir_buf)
|
|
{
|
|
size_t _len = strlen(dir_buf);
|
|
|
|
if (_len > 0)
|
|
if (dir_buf[_len - 1] == '/')
|
|
dir_buf[_len - 1] = '\0';
|
|
|
|
ret = mkdir(dir_buf, 0750);
|
|
|
|
free(dir_buf);
|
|
}
|
|
#else
|
|
int ret = mkdir(dir, 0750);
|
|
#endif
|
|
|
|
if (path_mkdir_err(ret))
|
|
return -2;
|
|
return ret < 0 ? -1 : 0;
|
|
}
|
|
}
|
|
|
|
/* Removes the empty directory @dir. 0 on success, -1 otherwise -
|
|
* including a directory that is not empty, and platforms with no
|
|
* directory removal here, where an empty directory is simply left. */
|
|
int retro_vfs_rmdir_impl(const char *dir)
|
|
{
|
|
if (!dir || !*dir)
|
|
return -1;
|
|
#if defined(ANDROID) && defined(HAVE_SAF)
|
|
if (path_is_saf(dir))
|
|
{
|
|
int ret;
|
|
struct libretro_vfs_implementation_saf_path_split_result saf_split_result;
|
|
if (!retro_vfs_path_split_saf(&saf_split_result, dir))
|
|
return -1;
|
|
ret = retro_vfs_file_remove_saf(saf_split_result.tree,
|
|
saf_split_result.path);
|
|
free(saf_split_result.path);
|
|
free(saf_split_result.tree);
|
|
return ret == 0 ? 0 : -1;
|
|
}
|
|
#endif
|
|
{
|
|
#if defined(_WIN32) && !defined(_XBOX)
|
|
#if defined(LEGACY_WIN32_RUNTIME)
|
|
int ret = -1;
|
|
|
|
if (win32_needs_local_encoding())
|
|
ret = _rmdir(dir);
|
|
else
|
|
{
|
|
wchar_t *dir_w = utf8_to_utf16_string_alloc(dir);
|
|
if (dir_w)
|
|
{
|
|
ret = _wrmdir(dir_w);
|
|
free(dir_w);
|
|
}
|
|
}
|
|
#elif defined(LEGACY_WIN32)
|
|
int ret = _rmdir(dir);
|
|
#else
|
|
int ret = -1;
|
|
wchar_t *dir_w = utf8_to_utf16_string_alloc(dir);
|
|
|
|
if (dir_w)
|
|
{
|
|
ret = _wrmdir(dir_w);
|
|
free(dir_w);
|
|
}
|
|
#endif
|
|
#elif defined(VITA)
|
|
int ret = sceIoRmdir(dir);
|
|
#elif defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) \
|
|
|| defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) \
|
|
|| defined(__HAIKU__) || defined(__QNX__) || defined(__EMSCRIPTEN__)
|
|
int ret = rmdir(dir);
|
|
#else
|
|
/* No directory removal wired up for this platform yet */
|
|
int ret = -1;
|
|
#endif
|
|
return ret < 0 ? -1 : 0;
|
|
}
|
|
}
|
|
|
|
#if defined(_WIN32)
|
|
/* Worst-case UTF-8 for a find-data name, terminator included.
|
|
*
|
|
* W: cFileName is WCHAR[MAX_PATH]. A BMP unit is at most 3 UTF-8 bytes;
|
|
* a surrogate pair is 2 units for 4 bytes, so 3 per unit bounds both.
|
|
*
|
|
* A: cFileName is CHAR[MAX_PATH] in the local codepage. One SBCS byte
|
|
* is one BMP character, so it is bounded by the same 3; DBCS spends two
|
|
* bytes per character and is cheaper still.
|
|
*
|
|
* The old code decoded back over cFileName itself, which is 520 bytes on
|
|
* the W path and 260 on the A path - short of this bound whenever the
|
|
* name is not ASCII, so a 174-character CJK name (255 is legal on NTFS)
|
|
* was silently strlcpy()-truncated. It also destroyed the source
|
|
* encoding in place, making the function return garbage if ever called
|
|
* twice on one entry. */
|
|
#define VFS_WIN32_NAME_UTF8_MAX (MAX_PATH * 3 + 1)
|
|
#endif
|
|
|
|
#ifdef VFS_FRONTEND
|
|
struct retro_vfs_dir_handle
|
|
#else
|
|
struct libretro_vfs_implementation_dir
|
|
#endif
|
|
{
|
|
char* orig_path;
|
|
#if defined(_WIN32)
|
|
#if defined(LEGACY_WIN32_RUNTIME)
|
|
/* Unlike every other site, the difference here is a type, not a
|
|
* call: the handle carries the find-data across retro_readdir()
|
|
* calls. A union costs the larger of the two (the W form, by the
|
|
* width of cFileName) and lets both live in one handle. */
|
|
union
|
|
{
|
|
WIN32_FIND_DATA a;
|
|
WIN32_FIND_DATAW w;
|
|
} entry;
|
|
#elif defined(LEGACY_WIN32)
|
|
WIN32_FIND_DATA entry;
|
|
#else
|
|
WIN32_FIND_DATAW entry;
|
|
#endif
|
|
HANDLE directory;
|
|
bool next;
|
|
char path[PATH_MAX_LENGTH];
|
|
char name_utf8[VFS_WIN32_NAME_UTF8_MAX];
|
|
#elif defined(VITA)
|
|
SceUID directory;
|
|
SceIoDirent entry;
|
|
#elif defined(__PSL1GHT__) || defined(__PS3__)
|
|
int error;
|
|
int directory;
|
|
sysFSDirent entry;
|
|
#else
|
|
DIR *directory;
|
|
const struct dirent *entry;
|
|
#endif
|
|
#if defined(ANDROID) && defined(HAVE_SAF)
|
|
libretro_vfs_implementation_saf_dir *saf_directory;
|
|
#endif
|
|
#ifdef HAVE_SMBCLIENT
|
|
smb_dir_handle* smb_handle;
|
|
char smb_path[PATH_MAX_LENGTH];
|
|
bool smb_is_dir;
|
|
#endif
|
|
#ifdef HAVE_NFSCLIENT
|
|
nfs_dir_handle* nfs_handle;
|
|
const char *nfs_name;
|
|
bool nfs_is_dir;
|
|
#endif
|
|
};
|
|
|
|
static bool dirent_check_err(libretro_vfs_implementation_dir *rdir)
|
|
{
|
|
#if defined(_WIN32)
|
|
return (rdir->directory == INVALID_HANDLE_VALUE);
|
|
#elif defined(VITA) || defined(ORBIS)
|
|
return (rdir->directory < 0);
|
|
#elif defined(__PSL1GHT__) || defined(__PS3__)
|
|
return (rdir->error != FS_SUCCEEDED);
|
|
#else
|
|
return !(rdir->directory);
|
|
#endif
|
|
}
|
|
|
|
int retro_vfs_restrict_permissions_impl(const char *path)
|
|
{
|
|
#if defined(_WIN32) || defined(VITA) || defined(PSP) || defined(PS2) \
|
|
|| defined(__PS3__) || defined(GEKKO) || defined(WIIU) \
|
|
|| defined(_3DS) || defined(HAVE_LIBNX) || defined(__CELLOS_LV2__) \
|
|
|| defined(ORBIS) || defined(DJGPP) || defined(EMSCRIPTEN) \
|
|
|| defined(ANDROID) || TARGET_OS_IPHONE || TARGET_OS_TV
|
|
/* No per-user permission model the frontend controls: Windows
|
|
* profile directories and mobile app sandboxes are already
|
|
* private, consoles are single-user. */
|
|
(void)path;
|
|
return 0;
|
|
#else
|
|
return chmod(path, S_IRUSR | S_IWUSR) == 0 ? 0 : -1;
|
|
#endif
|
|
}
|
|
|
|
int retro_vfs_set_readonly_impl(const char *path, int readonly)
|
|
{
|
|
if (!path || !*path)
|
|
return -1;
|
|
|
|
#if defined(_WIN32) && !defined(_XBOX)
|
|
{
|
|
DWORD attrs;
|
|
int ret = -1;
|
|
#if defined(LEGACY_WIN32_RUNTIME)
|
|
if (win32_needs_local_encoding())
|
|
{
|
|
#endif
|
|
#if defined(LEGACY_WIN32) || defined(LEGACY_WIN32_RUNTIME)
|
|
{
|
|
char *path_local = utf8_to_local_string_alloc(path);
|
|
if (!path_local)
|
|
return -1;
|
|
attrs = GetFileAttributes(path_local);
|
|
if (attrs != INVALID_FILE_ATTRIBUTES)
|
|
{
|
|
if (readonly)
|
|
attrs |= FILE_ATTRIBUTE_READONLY;
|
|
else
|
|
attrs &= ~FILE_ATTRIBUTE_READONLY;
|
|
ret = SetFileAttributes(path_local, attrs) ? 0 : -1;
|
|
}
|
|
free(path_local);
|
|
}
|
|
#endif
|
|
#if defined(LEGACY_WIN32_RUNTIME)
|
|
}
|
|
else
|
|
#endif
|
|
#if !defined(LEGACY_WIN32) || defined(LEGACY_WIN32_RUNTIME)
|
|
{
|
|
wchar_t *path_wide = utf8_to_utf16_string_alloc(path);
|
|
if (!path_wide)
|
|
return -1;
|
|
attrs = GetFileAttributesW(path_wide);
|
|
if (attrs != INVALID_FILE_ATTRIBUTES)
|
|
{
|
|
if (readonly)
|
|
attrs |= FILE_ATTRIBUTE_READONLY;
|
|
else
|
|
attrs &= ~FILE_ATTRIBUTE_READONLY;
|
|
ret = SetFileAttributesW(path_wide, attrs) ? 0 : -1;
|
|
}
|
|
free(path_wide);
|
|
}
|
|
#endif
|
|
return ret;
|
|
}
|
|
#elif defined(VITA)
|
|
{
|
|
SceIoStat st;
|
|
if (sceIoGetstat(path, &st) < 0)
|
|
return -1;
|
|
/* Owner write bit only: SCE_S_IWOTH is deprecated in the SDK and
|
|
* the contract is "the current user cannot write". */
|
|
if (readonly)
|
|
st.st_mode &= ~SCE_S_IWUSR;
|
|
else
|
|
st.st_mode |= SCE_S_IWUSR;
|
|
return sceIoChstat(path, &st, SCE_CST_MODE) < 0 ? -1 : 0;
|
|
}
|
|
#elif defined(VFS_HAVE_POSIX_METADATA)
|
|
{
|
|
struct stat st;
|
|
mode_t mode;
|
|
if (stat(path, &st) < 0)
|
|
return -1;
|
|
mode = st.st_mode & 07777;
|
|
if (readonly)
|
|
mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH);
|
|
else
|
|
mode |= S_IWUSR;
|
|
return chmod(path, mode) == 0 ? 0 : -1;
|
|
}
|
|
#else
|
|
/* No permission model reachable from here. */
|
|
(void)readonly;
|
|
return -1;
|
|
#endif
|
|
}
|
|
|
|
int retro_vfs_set_mtime_impl(const char *path, int64_t mtime)
|
|
{
|
|
if (!path || !*path)
|
|
return -1;
|
|
|
|
#if defined(_WIN32) && !defined(_XBOX)
|
|
{
|
|
HANDLE h = INVALID_HANDLE_VALUE;
|
|
FILETIME ft;
|
|
BOOL ok;
|
|
|
|
vfs_unix_to_filetime(mtime, &ft);
|
|
#if defined(LEGACY_WIN32_RUNTIME)
|
|
if (win32_needs_local_encoding())
|
|
{
|
|
#endif
|
|
#if defined(LEGACY_WIN32) || defined(LEGACY_WIN32_RUNTIME)
|
|
{
|
|
char *path_local = utf8_to_local_string_alloc(path);
|
|
if (!path_local)
|
|
return -1;
|
|
h = CreateFile(path_local, FILE_WRITE_ATTRIBUTES,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
|
|
FILE_FLAG_BACKUP_SEMANTICS, NULL);
|
|
free(path_local);
|
|
}
|
|
#endif
|
|
#if defined(LEGACY_WIN32_RUNTIME)
|
|
}
|
|
else
|
|
#endif
|
|
#if !defined(LEGACY_WIN32) || defined(LEGACY_WIN32_RUNTIME)
|
|
{
|
|
wchar_t *path_wide = utf8_to_utf16_string_alloc(path);
|
|
if (!path_wide)
|
|
return -1;
|
|
h = CreateFileW(path_wide, FILE_WRITE_ATTRIBUTES,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
|
|
FILE_FLAG_BACKUP_SEMANTICS, NULL);
|
|
free(path_wide);
|
|
}
|
|
#endif
|
|
if (h == INVALID_HANDLE_VALUE)
|
|
return -1;
|
|
/* Creation and access times are left alone. */
|
|
ok = SetFileTime(h, NULL, NULL, &ft);
|
|
CloseHandle(h);
|
|
return ok ? 0 : -1;
|
|
}
|
|
#elif defined(VITA)
|
|
{
|
|
SceIoStat st;
|
|
time_t t = (time_t)mtime;
|
|
if (sceIoGetstat(path, &st) < 0)
|
|
return -1;
|
|
sceRtcSetTime_t(&st.st_mtime, t);
|
|
return sceIoChstat(path, &st, SCE_CST_MT) < 0 ? -1 : 0;
|
|
}
|
|
#elif defined(VFS_HAVE_POSIX_METADATA)
|
|
{
|
|
struct stat st;
|
|
struct timeval tv[2];
|
|
if (stat(path, &st) < 0)
|
|
return -1;
|
|
/* Keep the access time; only the modification time changes. */
|
|
tv[0].tv_sec = st.st_atime;
|
|
tv[0].tv_usec = 0;
|
|
tv[1].tv_sec = (time_t)mtime;
|
|
tv[1].tv_usec = 0;
|
|
return utimes(path, tv) == 0 ? 0 : -1;
|
|
}
|
|
#else
|
|
(void)mtime;
|
|
return -1;
|
|
#endif
|
|
}
|
|
|
|
/* Create @dir and every missing directory above it. Same shape as
|
|
* path_mkdir(), kept local so this file does not grow a link-time
|
|
* dependency on file_path_io.c for standalone consumers. Works from
|
|
* the bottom up: try @dir, on failure make its parent and retry, so a
|
|
* drive root ("C:"), a UNC share prefix or a doubled separator is just
|
|
* a rung that fails harmlessly instead of a component we try to
|
|
* create. @dir is modified in place and restored before returning.
|
|
* Returns 0 when @dir exists afterwards. */
|
|
static int vfs_copy_mkdir_parents(char *dir)
|
|
{
|
|
char *sep;
|
|
int ret = retro_vfs_mkdir_impl(dir);
|
|
if (ret != -1)
|
|
return 0; /* 0 created, -2 already there */
|
|
/* Strip the last component (ignoring a trailing separator) and
|
|
* recurse; stop at the top of the string. */
|
|
sep = dir + strlen(dir);
|
|
while (sep > dir && (sep[-1] == '/' || sep[-1] == '\\'))
|
|
sep--;
|
|
while (sep > dir && sep[-1] != '/' && sep[-1] != '\\')
|
|
sep--;
|
|
while (sep > dir && (sep[-1] == '/' || sep[-1] == '\\'))
|
|
sep--;
|
|
if (sep == dir)
|
|
return -1;
|
|
{
|
|
char c = *sep;
|
|
*sep = '\0';
|
|
vfs_copy_mkdir_parents(dir);
|
|
*sep = c;
|
|
}
|
|
return retro_vfs_mkdir_impl(dir) != -1 ? 0 : -1;
|
|
}
|
|
|
|
/* ---- copy: begin / step / close --------------------------------------
|
|
*
|
|
* A copy is a resumable state machine that the caller advances. There
|
|
* is no thread in here and no lock: begin() does the up-front checks
|
|
* and opens both ends, step() moves at most the bytes it was asked to
|
|
* and returns, close() releases everything and removes a partial dst.
|
|
* Whoever wants the transfer off their own thread (RetroArch's task
|
|
* queue, a core's worker) makes that decision, not the VFS.
|
|
*
|
|
* Memory: the handle, its two path strings, the two open files, and on
|
|
* the portable path one transfer buffer (1 MiB, 64 KiB if that
|
|
* allocation fails). The Linux path moves bytes with copy_file_range
|
|
* at explicit offsets, so it needs no buffer at all and resumes exactly
|
|
* where the last step stopped. On APFS a same-volume copy is a
|
|
* clonefile() in begin(): O(1), no bytes move, step() reports DONE.
|
|
*
|
|
* Speed: with a large budget a step is the same kernel primitive a
|
|
* blocking copy would issue, so throughput is that of the primitive;
|
|
* with a small budget it is bounded latency. The caller picks. */
|
|
|
|
/* The samples build with VFS_COPY_DEBUG so a CI lane shows which path
|
|
* each step took and what the platform answered; never set in a
|
|
* frontend build. */
|
|
#if defined(VFS_COPY_DEBUG)
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
#define VFS_COPY_DBG(...) fprintf(stderr, "[vfs-copy] " __VA_ARGS__)
|
|
#else
|
|
#define VFS_COPY_DBG(...) do { } while (0)
|
|
#endif
|
|
|
|
/* Transfer buffer for the portable path, which is the one consoles,
|
|
* SAF, SMB and CDROM take -- the places with the least memory to
|
|
* spare. Measured on a 512 MiB copy: 16 KiB and 64 KiB are clearly
|
|
* slower, and everything from 128 KiB to 1 MiB is the same within
|
|
* run-to-run noise, so 128 KiB is the ceiling and small files get
|
|
* only what they need. The kernel fast paths allocate nothing. */
|
|
#define VFS_COPY_BUF_MAX (128 * 1024)
|
|
#define VFS_COPY_BUF_MIN (16 * 1024)
|
|
/* Largest single kernel request per step; the loop inside a step
|
|
* issues as many as the budget allows. */
|
|
#define VFS_COPY_KERNEL_REQ ((size_t)16 * 1024 * 1024)
|
|
/* Default step when the caller passes 0: bounded enough for a frame
|
|
* loop on fast media, large enough that a poll-per-frame caller still
|
|
* moves hundreds of MB/s. */
|
|
#define VFS_COPY_DEFAULT_STEP ((int64_t)4 * 1024 * 1024)
|
|
|
|
struct retro_vfs_copy_handle
|
|
{
|
|
char *src;
|
|
char *dst;
|
|
int64_t total;
|
|
int64_t done;
|
|
int status; /* RETRO_VFS_COPY_RUNNING / DONE / FAILED */
|
|
#if defined(VFS_HAVE_COPY_FILE_RANGE)
|
|
int in_fd; /* -1 when the kernel path is not in use */
|
|
int out_fd;
|
|
#endif
|
|
/* Portable path: both ends through the VFS so either may be any
|
|
* backend (SAF, SMB, CDROM, native). */
|
|
libretro_vfs_implementation_file *in;
|
|
libretro_vfs_implementation_file *out;
|
|
char *buf;
|
|
size_t buf_len;
|
|
};
|
|
|
|
static void vfs_copy_handle_free(struct retro_vfs_copy_handle *h)
|
|
{
|
|
if (!h)
|
|
return;
|
|
#if defined(VFS_HAVE_COPY_FILE_RANGE)
|
|
if (h->in_fd >= 0) close(h->in_fd);
|
|
if (h->out_fd >= 0) close(h->out_fd);
|
|
#endif
|
|
if (h->in) retro_vfs_file_close_impl(h->in);
|
|
if (h->out) retro_vfs_file_close_impl(h->out);
|
|
free(h->buf);
|
|
free(h->src);
|
|
free(h->dst);
|
|
free(h);
|
|
}
|
|
|
|
/* Opens the portable path's two ends and buffer. With @resume the
|
|
* destination is opened in place (no truncate) and both ends are
|
|
* positioned at h->done, for taking over a copy another path started.
|
|
* Returns 0 or -1. */
|
|
static int vfs_copy_open_portable(struct retro_vfs_copy_handle *h, bool resume)
|
|
{
|
|
/* No point in a buffer larger than what is left to copy. */
|
|
{
|
|
int64_t left = h->total - h->done;
|
|
h->buf_len = VFS_COPY_BUF_MAX;
|
|
if (left > 0 && left < (int64_t)h->buf_len)
|
|
h->buf_len = (size_t)left;
|
|
if (h->buf_len < VFS_COPY_BUF_MIN)
|
|
h->buf_len = VFS_COPY_BUF_MIN;
|
|
}
|
|
if (!(h->buf = (char*)malloc(h->buf_len)))
|
|
{
|
|
h->buf_len = VFS_COPY_BUF_MIN;
|
|
if (!(h->buf = (char*)malloc(h->buf_len)))
|
|
return -1;
|
|
}
|
|
h->in = retro_vfs_file_open_impl(h->src, RETRO_VFS_FILE_ACCESS_READ,
|
|
RETRO_VFS_FILE_ACCESS_HINT_SEQUENTIAL_BULK);
|
|
if (!h->in)
|
|
return -1;
|
|
h->out = retro_vfs_file_open_impl(h->dst,
|
|
resume ? (RETRO_VFS_FILE_ACCESS_READ_WRITE | RETRO_VFS_FILE_ACCESS_UPDATE_EXISTING)
|
|
: RETRO_VFS_FILE_ACCESS_WRITE,
|
|
RETRO_VFS_FILE_ACCESS_HINT_NONE);
|
|
if (!h->out)
|
|
return -1;
|
|
if (resume && h->done > 0)
|
|
{
|
|
if ( retro_vfs_file_seek_impl(h->in, h->done, RETRO_VFS_SEEK_POSITION_START) < 0
|
|
|| retro_vfs_file_seek_impl(h->out, h->done, RETRO_VFS_SEEK_POSITION_START) < 0)
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* One portable step: up to @budget bytes through the buffer.
|
|
* Returns the new status. */
|
|
static int vfs_copy_step_portable(struct retro_vfs_copy_handle *h, int64_t budget)
|
|
{
|
|
while (budget > 0)
|
|
{
|
|
size_t want = h->buf_len;
|
|
int64_t n;
|
|
if ((int64_t)want > budget)
|
|
want = (size_t)budget;
|
|
n = retro_vfs_file_read_impl(h->in, h->buf, want);
|
|
VFS_COPY_DBG("portable: want %lu -> read %lld (done %lld budget %lld)\n",
|
|
(unsigned long)want, (long long)n, (long long)h->done, (long long)budget);
|
|
if (n < 0)
|
|
return RETRO_VFS_COPY_FAILED;
|
|
if (n == 0)
|
|
{
|
|
/* EOF: close dst so a DONE report means "complete and closed". */
|
|
libretro_vfs_implementation_file *out = h->out;
|
|
h->out = NULL;
|
|
if (retro_vfs_file_close_impl(out) != 0)
|
|
return RETRO_VFS_COPY_FAILED;
|
|
return RETRO_VFS_COPY_DONE;
|
|
}
|
|
if (retro_vfs_file_write_impl(h->out, h->buf, (uint64_t)n) != n)
|
|
return RETRO_VFS_COPY_FAILED;
|
|
h->done += n;
|
|
budget -= n;
|
|
}
|
|
return RETRO_VFS_COPY_RUNNING;
|
|
}
|
|
|
|
#if defined(VFS_HAVE_COPY_FILE_RANGE)
|
|
/* A kernel that hands back more bytes than it was asked for cannot be
|
|
* held to a step budget. Real Linux never does; sandboxed kernels
|
|
* (gVisor was observed copying to EOF regardless of len) do. Once
|
|
* seen, this process stops offering it work: every later copy takes
|
|
* the portable path, which is bounded by construction. */
|
|
static bool vfs_cfr_untrusted = false;
|
|
|
|
/* Kernel path: copy_file_range at explicit offsets, resumable from
|
|
* h->done with no state in the kernel between steps. Returns 1 if it
|
|
* is in use after this call, 0 if the kernel declined before any byte
|
|
* moved (caller falls back to the portable path), -1 on error. */
|
|
static int vfs_copy_open_linux(struct retro_vfs_copy_handle *h)
|
|
{
|
|
if (vfs_cfr_untrusted)
|
|
{
|
|
VFS_COPY_DBG("open: kernel path retired, portable\n");
|
|
return 0;
|
|
}
|
|
h->in_fd = open(h->src, O_RDONLY | O_CLOEXEC);
|
|
if (h->in_fd < 0)
|
|
return 0;
|
|
h->out_fd = open(h->dst, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0666);
|
|
if (h->out_fd < 0)
|
|
{
|
|
close(h->in_fd);
|
|
h->in_fd = -1;
|
|
return 0;
|
|
}
|
|
posix_fadvise(h->in_fd, 0, 0, POSIX_FADV_SEQUENTIAL);
|
|
/* Reserve the extent so the copy lands contiguously; harmless if
|
|
* the file system cannot (FAT, tmpfs). */
|
|
if (h->total > 0)
|
|
posix_fallocate(h->out_fd, 0, (off_t)h->total);
|
|
return 1;
|
|
}
|
|
|
|
static int vfs_copy_step_linux(struct retro_vfs_copy_handle *h, int64_t budget)
|
|
{
|
|
while (budget > 0 && h->done < h->total)
|
|
{
|
|
int64_t left = h->total - h->done;
|
|
size_t want = (size_t)(left < (int64_t)VFS_COPY_KERNEL_REQ ? left : (int64_t)VFS_COPY_KERNEL_REQ);
|
|
long long off_in = (long long)h->done;
|
|
long long off_out = (long long)h->done;
|
|
ssize_t n;
|
|
if ((int64_t)want > budget)
|
|
want = (size_t)budget;
|
|
/* Every argument widened to long: syscall() reads its varargs
|
|
* as longs, and an int or unsigned in a register may carry
|
|
* whatever was in its upper half. */
|
|
#if defined(VFS_COPY_TEST_FAKE_CFR)
|
|
/* Test-only stand-in for a kernel that ignores len and copies to
|
|
* EOF (what gVisor was seen doing): the samples build with this
|
|
* to exercise the distrust-and-resume path on a real kernel. */
|
|
{
|
|
char tmp[65536];
|
|
ssize_t got, total_fake = 0;
|
|
/* 1: copy to EOF. 2: copy three times what was asked, so the
|
|
* resume-in-place path (kernel left the copy part-way) runs. */
|
|
while ((VFS_COPY_TEST_FAKE_CFR == 1 || (size_t)total_fake < 3 * want)
|
|
&& (got = pread(h->in_fd, tmp, sizeof(tmp), (off_t)(off_in + total_fake))) > 0)
|
|
{
|
|
if (pwrite(h->out_fd, tmp, (size_t)got, (off_t)(off_out + total_fake)) != got)
|
|
{ got = -1; break; }
|
|
total_fake += got;
|
|
}
|
|
n = got < 0 ? -1 : total_fake;
|
|
}
|
|
#else
|
|
n = (ssize_t)syscall(SYS_copy_file_range, (long)h->in_fd, (long)&off_in,
|
|
(long)h->out_fd, (long)&off_out, (long)want, 0L);
|
|
#endif
|
|
VFS_COPY_DBG("copy_file_range: want %lu at %lld -> %ld errno %d (budget %lld total %lld)\n",
|
|
(unsigned long)want, (long long)h->done, (long)n, n < 0 ? errno : 0,
|
|
(long long)budget, (long long)h->total);
|
|
if (n < 0)
|
|
{
|
|
if (h->done == 0 && (errno == EXDEV || errno == ENOSYS
|
|
|| errno == EINVAL || errno == EOPNOTSUPP || errno == EPERM))
|
|
{
|
|
/* Kernel cannot do this pair: hand over to the portable
|
|
* path from offset 0. The fds go; the VFS opens its own. */
|
|
close(h->in_fd);
|
|
close(h->out_fd);
|
|
h->in_fd = h->out_fd = -1;
|
|
if (vfs_copy_open_portable(h, false) != 0)
|
|
return RETRO_VFS_COPY_FAILED;
|
|
return vfs_copy_step_portable(h, budget);
|
|
}
|
|
return RETRO_VFS_COPY_FAILED;
|
|
}
|
|
if (n == 0)
|
|
break; /* src shrank under us; what we have is the file */
|
|
if ((size_t)n > want)
|
|
{
|
|
/* Bytes are on disk, so account for them, but this kernel
|
|
* does not honour len: no further kernel steps, here or in
|
|
* any later copy. */
|
|
vfs_cfr_untrusted = true;
|
|
h->done += n;
|
|
if (h->done > h->total)
|
|
h->done = h->total;
|
|
break;
|
|
}
|
|
h->done += n;
|
|
budget -= n;
|
|
}
|
|
if (vfs_cfr_untrusted && h->done < h->total)
|
|
{
|
|
/* Continue this copy on the portable path from where the
|
|
* kernel left it. */
|
|
close(h->in_fd);
|
|
close(h->out_fd);
|
|
h->in_fd = h->out_fd = -1;
|
|
if (vfs_copy_open_portable(h, true) != 0)
|
|
return RETRO_VFS_COPY_FAILED;
|
|
return RETRO_VFS_COPY_RUNNING;
|
|
}
|
|
if (h->done >= h->total || budget > 0)
|
|
{
|
|
int out_fd = h->out_fd;
|
|
h->out_fd = -1;
|
|
/* fallocate may have reserved more than src turned out to hold */
|
|
if (ftruncate(out_fd, (off_t)h->done) != 0 || close(out_fd) != 0)
|
|
return RETRO_VFS_COPY_FAILED;
|
|
return RETRO_VFS_COPY_DONE;
|
|
}
|
|
return RETRO_VFS_COPY_RUNNING;
|
|
}
|
|
#endif
|
|
|
|
struct retro_vfs_copy_handle *retro_vfs_copy_begin_impl(
|
|
const char *src, const char *dst, unsigned flags)
|
|
{
|
|
struct retro_vfs_copy_handle *h = NULL;
|
|
int64_t src_size = 0;
|
|
int sflags, dflags;
|
|
bool native = true;
|
|
char dst_buf[PATH_MAX_LENGTH];
|
|
|
|
if (!src || !*src || !dst || !*dst)
|
|
return NULL;
|
|
#if defined(_WIN32)
|
|
if (string_is_equal_case_insensitive(src, dst))
|
|
return NULL;
|
|
#else
|
|
if (string_is_equal(src, dst))
|
|
return NULL;
|
|
#endif
|
|
|
|
sflags = retro_vfs_stat_full(src, &src_size, NULL);
|
|
if ( !(sflags & RETRO_VFS_STAT_IS_VALID)
|
|
|| (sflags & RETRO_VFS_STAT_IS_DIRECTORY)
|
|
|| (sflags & RETRO_VFS_STAT_IS_CHARACTER_SPECIAL))
|
|
return NULL;
|
|
|
|
dflags = retro_vfs_stat_full(dst, NULL, NULL);
|
|
if (dflags & RETRO_VFS_STAT_IS_VALID)
|
|
{
|
|
if (dflags & RETRO_VFS_STAT_IS_DIRECTORY)
|
|
return NULL;
|
|
if (!(flags & RETRO_VFS_COPY_OVERWRITE))
|
|
return NULL;
|
|
/* cp -f semantics: a stale read-only dst must not defeat an
|
|
* explicit overwrite, and a fresh inode is what the kernel
|
|
* paths want anyway. Windows refuses to delete a read-only
|
|
* file, so clear the attribute and try once more. */
|
|
if (retro_vfs_file_remove_impl(dst) != 0)
|
|
{
|
|
if ( !(dflags & RETRO_VFS_STAT_IS_READONLY)
|
|
|| retro_vfs_set_readonly_impl(dst, 0) != 0
|
|
|| retro_vfs_file_remove_impl(dst) != 0)
|
|
return NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Parent directory of dst, if dst has one. */
|
|
const char *last = strrchr(dst, '/');
|
|
const char *bs = strrchr(dst, '\\');
|
|
if (bs > last)
|
|
last = bs;
|
|
if (last && last > dst)
|
|
{
|
|
size_t n = (size_t)(last - dst);
|
|
if (n >= sizeof(dst_buf))
|
|
return NULL;
|
|
memcpy(dst_buf, dst, n);
|
|
dst_buf[n] = '\0';
|
|
/* mkdir reports "exists" for a file of that name too, so
|
|
* confirm the parent really is a directory. */
|
|
if ( vfs_copy_mkdir_parents(dst_buf) != 0
|
|
|| !(retro_vfs_stat_full(dst_buf, NULL, NULL) & RETRO_VFS_STAT_IS_DIRECTORY))
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if (!(h = (struct retro_vfs_copy_handle*)calloc(1, sizeof(*h))))
|
|
return NULL;
|
|
#if defined(VFS_HAVE_COPY_FILE_RANGE)
|
|
h->in_fd = h->out_fd = -1;
|
|
#endif
|
|
h->src = strdup(src);
|
|
h->dst = strdup(dst);
|
|
h->total = src_size;
|
|
h->status = RETRO_VFS_COPY_RUNNING;
|
|
if (!h->src || !h->dst)
|
|
goto fail;
|
|
|
|
/* Fast paths only when both ends are native. */
|
|
#if defined(HAVE_SMBCLIENT)
|
|
if (path_is_smb(src) || path_is_smb(dst))
|
|
native = false;
|
|
#endif
|
|
#if defined(HAVE_NFSCLIENT)
|
|
if (path_is_nfs(src) || path_is_nfs(dst))
|
|
native = false;
|
|
#endif
|
|
#if defined(ANDROID) && defined(HAVE_SAF)
|
|
if (path_is_saf(src) || path_is_saf(dst))
|
|
native = false;
|
|
#endif
|
|
|
|
#if defined(VFS_HAVE_CLONEFILE)
|
|
/* APFS same-volume: a clone. Constant time, no bytes move, and
|
|
* the result is a complete independent file, so the copy is DONE
|
|
* before the first step. Any failure (other volume, HFS+, network)
|
|
* just means we copy bytes like everyone else. */
|
|
if (native && clonefile(src, dst, 0) == 0)
|
|
{
|
|
h->done = h->total;
|
|
h->status = RETRO_VFS_COPY_DONE;
|
|
return h;
|
|
}
|
|
#endif
|
|
#if defined(VFS_HAVE_COPY_FILE_RANGE)
|
|
if (native)
|
|
{
|
|
int r = vfs_copy_open_linux(h);
|
|
if (r < 0)
|
|
goto fail;
|
|
if (r == 1)
|
|
return h;
|
|
/* r == 0: could not even open natively; portable path. */
|
|
}
|
|
#endif
|
|
(void)native;
|
|
if (vfs_copy_open_portable(h, false) != 0)
|
|
goto fail;
|
|
return h;
|
|
|
|
fail:
|
|
vfs_copy_handle_free(h);
|
|
retro_vfs_file_remove_impl(dst);
|
|
return NULL;
|
|
}
|
|
|
|
int retro_vfs_copy_step_impl(struct retro_vfs_copy_handle *h,
|
|
int64_t max_bytes, int64_t *bytes_done, int64_t *bytes_total)
|
|
{
|
|
if (!h)
|
|
return RETRO_VFS_COPY_FAILED;
|
|
if (h->status == RETRO_VFS_COPY_RUNNING)
|
|
{
|
|
int64_t budget = max_bytes > 0 ? max_bytes : VFS_COPY_DEFAULT_STEP;
|
|
#if defined(VFS_HAVE_COPY_FILE_RANGE)
|
|
if (h->in_fd >= 0)
|
|
h->status = vfs_copy_step_linux(h, budget);
|
|
else
|
|
#endif
|
|
h->status = vfs_copy_step_portable(h, budget);
|
|
if (h->status == RETRO_VFS_COPY_FAILED)
|
|
{
|
|
/* Release the ends now so the partial dst can go; the
|
|
* handle itself lives until close(). */
|
|
#if defined(VFS_HAVE_COPY_FILE_RANGE)
|
|
if (h->in_fd >= 0) { close(h->in_fd); h->in_fd = -1; }
|
|
if (h->out_fd >= 0) { close(h->out_fd); h->out_fd = -1; }
|
|
#endif
|
|
if (h->in) { retro_vfs_file_close_impl(h->in); h->in = NULL; }
|
|
if (h->out) { retro_vfs_file_close_impl(h->out); h->out = NULL; }
|
|
retro_vfs_file_remove_impl(h->dst);
|
|
}
|
|
}
|
|
if (bytes_done)
|
|
*bytes_done = h->done;
|
|
if (bytes_total)
|
|
*bytes_total = h->total;
|
|
return h->status;
|
|
}
|
|
|
|
int retro_vfs_copy_close_impl(struct retro_vfs_copy_handle *h)
|
|
{
|
|
int status;
|
|
if (!h)
|
|
return -1;
|
|
status = h->status;
|
|
if (status == RETRO_VFS_COPY_RUNNING)
|
|
{
|
|
/* Cancelled: drop both ends first (a Win32 dst cannot be removed
|
|
* while open), then the partial file. */
|
|
#if defined(VFS_HAVE_COPY_FILE_RANGE)
|
|
if (h->in_fd >= 0) { close(h->in_fd); h->in_fd = -1; }
|
|
if (h->out_fd >= 0) { close(h->out_fd); h->out_fd = -1; }
|
|
#endif
|
|
if (h->in) { retro_vfs_file_close_impl(h->in); h->in = NULL; }
|
|
if (h->out) { retro_vfs_file_close_impl(h->out); h->out = NULL; }
|
|
retro_vfs_file_remove_impl(h->dst);
|
|
}
|
|
vfs_copy_handle_free(h);
|
|
return status == RETRO_VFS_COPY_DONE ? 0 : -1;
|
|
}
|
|
|
|
libretro_vfs_implementation_dir *retro_vfs_opendir_impl(
|
|
const char *name, bool include_hidden)
|
|
{
|
|
#if defined(_WIN32)
|
|
char path_buf[1024];
|
|
size_t _len;
|
|
#if defined(LEGACY_WIN32) || defined(LEGACY_WIN32_RUNTIME)
|
|
char *path_local = NULL;
|
|
#endif
|
|
#if !defined(LEGACY_WIN32) || defined(LEGACY_WIN32_RUNTIME)
|
|
wchar_t *path_wide = NULL;
|
|
#endif
|
|
#endif
|
|
libretro_vfs_implementation_dir *rdir;
|
|
|
|
/* Reject NULL or empty string paths*/
|
|
if (!name || (*name == 0))
|
|
return NULL;
|
|
|
|
/*Allocate RDIR struct. Tidied later with retro_closedir*/
|
|
if (!(rdir = (libretro_vfs_implementation_dir*)
|
|
calloc(1, sizeof(*rdir))))
|
|
return NULL;
|
|
|
|
rdir->orig_path = strdup(name);
|
|
if (rdir->orig_path == NULL)
|
|
{
|
|
free(rdir);
|
|
return NULL;
|
|
}
|
|
|
|
#ifdef HAVE_SMBCLIENT
|
|
if (path_is_smb(name))
|
|
{
|
|
smb_dir_handle *dh = retro_vfs_opendir_smb(name, include_hidden);
|
|
if (!dh || !dh->dir)
|
|
{
|
|
free(rdir->orig_path);
|
|
free(rdir);
|
|
return NULL;
|
|
}
|
|
rdir->smb_handle = dh;
|
|
rdir->smb_path[0] = '\0';
|
|
rdir->smb_is_dir = false;
|
|
return rdir;
|
|
}
|
|
#endif
|
|
#ifdef HAVE_NFSCLIENT
|
|
rdir->nfs_handle = NULL;
|
|
if (path_is_nfs(name))
|
|
{
|
|
nfs_dir_handle *dh = retro_vfs_opendir_nfs(name, include_hidden);
|
|
if (!dh)
|
|
{
|
|
free(rdir->orig_path);
|
|
free(rdir);
|
|
return NULL;
|
|
}
|
|
rdir->nfs_handle = dh;
|
|
rdir->nfs_name = "";
|
|
rdir->nfs_is_dir = false;
|
|
return rdir;
|
|
}
|
|
#endif
|
|
|
|
#if defined(ANDROID) && defined(HAVE_SAF)
|
|
rdir->saf_directory = NULL;
|
|
|
|
if (path_is_saf(name))
|
|
{
|
|
struct libretro_vfs_implementation_saf_path_split_result saf_split_result;
|
|
if (!retro_vfs_path_split_saf(&saf_split_result, name))
|
|
{
|
|
free(rdir->orig_path);
|
|
free(rdir);
|
|
return NULL;
|
|
}
|
|
rdir->saf_directory = retro_vfs_opendir_saf(saf_split_result.tree, saf_split_result.path, include_hidden);
|
|
free(saf_split_result.path);
|
|
free(saf_split_result.tree);
|
|
if (rdir->saf_directory == NULL)
|
|
{
|
|
free(rdir->orig_path);
|
|
free(rdir);
|
|
return NULL;
|
|
}
|
|
return rdir;
|
|
}
|
|
#endif
|
|
|
|
#if defined(_WIN32)
|
|
_len = strlcpy(path_buf, name, sizeof(path_buf));
|
|
/* Non-NT platforms don't like extra slashes in the path */
|
|
if (path_buf[_len - 1] != '\\')
|
|
path_buf [_len++] = '\\';
|
|
|
|
path_buf[_len ] = '*';
|
|
path_buf[_len + 1] = '\0';
|
|
#if defined(LEGACY_WIN32_RUNTIME)
|
|
if (win32_needs_local_encoding())
|
|
{
|
|
path_local = utf8_to_local_string_alloc(path_buf);
|
|
rdir->directory = FindFirstFileA(path_local, &rdir->entry.a);
|
|
if (path_local)
|
|
free(path_local);
|
|
}
|
|
else
|
|
{
|
|
path_wide = utf8_to_utf16_string_alloc(path_buf);
|
|
rdir->directory = FindFirstFileW(path_wide, &rdir->entry.w);
|
|
if (path_wide)
|
|
free(path_wide);
|
|
}
|
|
#elif defined(LEGACY_WIN32)
|
|
path_local = utf8_to_local_string_alloc(path_buf);
|
|
rdir->directory = FindFirstFile(path_local, &rdir->entry);
|
|
if (path_local)
|
|
free(path_local);
|
|
#else
|
|
path_wide = utf8_to_utf16_string_alloc(path_buf);
|
|
rdir->directory = FindFirstFileW(path_wide, &rdir->entry);
|
|
if (path_wide)
|
|
free(path_wide);
|
|
#endif
|
|
|
|
#elif defined(VITA)
|
|
rdir->directory = sceIoDopen(name);
|
|
#elif defined(_3DS)
|
|
rdir->directory = opendir(name);
|
|
rdir->entry = NULL;
|
|
#elif defined(__PSL1GHT__) || defined(__PS3__)
|
|
rdir->error = sysFsOpendir(name, &rdir->directory);
|
|
#else
|
|
rdir->directory = opendir(name);
|
|
rdir->entry = NULL;
|
|
#endif
|
|
|
|
#ifdef _WIN32
|
|
#if defined(LEGACY_WIN32_RUNTIME)
|
|
/* Same field, same offset in both arms of the union - but C wants
|
|
* one of them named, so follow whichever the handle is using. */
|
|
if (win32_needs_local_encoding())
|
|
{
|
|
if (include_hidden)
|
|
rdir->entry.a.dwFileAttributes |= FILE_ATTRIBUTE_HIDDEN;
|
|
else
|
|
rdir->entry.a.dwFileAttributes &= ~FILE_ATTRIBUTE_HIDDEN;
|
|
}
|
|
else
|
|
{
|
|
if (include_hidden)
|
|
rdir->entry.w.dwFileAttributes |= FILE_ATTRIBUTE_HIDDEN;
|
|
else
|
|
rdir->entry.w.dwFileAttributes &= ~FILE_ATTRIBUTE_HIDDEN;
|
|
}
|
|
#else
|
|
if (include_hidden)
|
|
rdir->entry.dwFileAttributes |= FILE_ATTRIBUTE_HIDDEN;
|
|
else
|
|
rdir->entry.dwFileAttributes &= ~FILE_ATTRIBUTE_HIDDEN;
|
|
#endif
|
|
#else
|
|
(void)include_hidden;
|
|
#endif
|
|
|
|
if (rdir->directory && !dirent_check_err(rdir))
|
|
return rdir;
|
|
|
|
retro_vfs_closedir_impl(rdir);
|
|
return NULL;
|
|
}
|
|
|
|
bool retro_vfs_readdir_impl(libretro_vfs_implementation_dir *rdir)
|
|
{
|
|
#ifdef HAVE_SMBCLIENT
|
|
if (rdir->smb_handle)
|
|
{
|
|
struct smbc_dirent *de = retro_vfs_readdir_smb(rdir->smb_handle);
|
|
if (!de)
|
|
return false;
|
|
strlcpy(rdir->smb_path, de->name, sizeof(rdir->smb_path));
|
|
rdir->smb_is_dir = (de->type == RETRO_SMB_DIRENT_DIR);
|
|
return true;
|
|
}
|
|
/* If we opened an SMB path but failed, do not fall through to native readdir */
|
|
if (path_is_smb(rdir->orig_path))
|
|
return false;
|
|
#endif
|
|
#ifdef HAVE_NFSCLIENT
|
|
if (rdir->nfs_handle)
|
|
{
|
|
struct nfs_dirent *de = retro_vfs_readdir_nfs(rdir->nfs_handle);
|
|
if (!de)
|
|
return false;
|
|
rdir->nfs_name = de->name;
|
|
rdir->nfs_is_dir = (de->type == RETRO_NFS_DIRENT_DIR);
|
|
return true;
|
|
}
|
|
if (path_is_nfs(rdir->orig_path))
|
|
return false;
|
|
#endif
|
|
#if defined(ANDROID) && defined(HAVE_SAF)
|
|
if (rdir->saf_directory != NULL)
|
|
return retro_vfs_readdir_saf(rdir->saf_directory);
|
|
#endif
|
|
|
|
#if defined(_WIN32)
|
|
if (rdir->next)
|
|
#if defined(LEGACY_WIN32_RUNTIME)
|
|
{
|
|
if (win32_needs_local_encoding())
|
|
return (FindNextFileA(rdir->directory, &rdir->entry.a) != 0);
|
|
return (FindNextFileW(rdir->directory, &rdir->entry.w) != 0);
|
|
}
|
|
#elif defined(LEGACY_WIN32)
|
|
return (FindNextFile(rdir->directory, &rdir->entry) != 0);
|
|
#else
|
|
return (FindNextFileW(rdir->directory, &rdir->entry) != 0);
|
|
#endif
|
|
|
|
rdir->next = true;
|
|
return (rdir->directory != INVALID_HANDLE_VALUE);
|
|
#elif defined(VITA)
|
|
return (sceIoDread(rdir->directory, &rdir->entry) > 0);
|
|
#elif defined(__PSL1GHT__) || defined(__PS3__)
|
|
uint64_t nread;
|
|
rdir->error = sysFsReaddir(rdir->directory, &rdir->entry, &nread);
|
|
return (nread != 0);
|
|
#else
|
|
return ((rdir->entry = readdir(rdir->directory)) != NULL);
|
|
#endif
|
|
}
|
|
|
|
#if defined(_WIN32)
|
|
#if defined(LEGACY_WIN32) || defined(LEGACY_WIN32_RUNTIME)
|
|
/* Which codepages actually need converting is a question for
|
|
* encodings/, so ask it there rather than restate it here; on targets
|
|
* where the answer is "none" this allocates nothing. */
|
|
static const char *vfs_win32_name_local(
|
|
libretro_vfs_implementation_dir *rdir, const char *src)
|
|
{
|
|
if (!local_to_utf8_string(src, rdir->name_utf8, sizeof(rdir->name_utf8)))
|
|
return NULL;
|
|
return rdir->name_utf8;
|
|
}
|
|
#endif
|
|
|
|
#if !defined(LEGACY_WIN32) || defined(LEGACY_WIN32_RUNTIME)
|
|
/* utf16_conv_utf8() writes without an output bound and does not
|
|
* terminate; VFS_WIN32_NAME_UTF8_MAX is what makes the first safe and
|
|
* the store below covers the second. Taking it directly is what drops
|
|
* the per-entry allocation - utf16_to_utf8_string_alloc() and
|
|
* utf16_to_char_string() both malloc internally. */
|
|
static const char *vfs_win32_name_utf16(
|
|
libretro_vfs_implementation_dir *rdir, const wchar_t *src)
|
|
{
|
|
char *name;
|
|
size_t out_len = 0;
|
|
size_t in_len = 0;
|
|
|
|
while (src[in_len])
|
|
in_len++;
|
|
|
|
if (utf16_conv_utf8((uint8_t*)rdir->name_utf8, &out_len,
|
|
(const uint16_t*)src, in_len))
|
|
{
|
|
rdir->name_utf8[out_len] = '\0';
|
|
return rdir->name_utf8;
|
|
}
|
|
|
|
/* Unpaired surrogate: utf16_conv_utf8() stops at one, and NTFS
|
|
* permits them in names. WideCharToMultiByte() substitutes U+FFFD
|
|
* and carries on, so fall back to it rather than hand back a name
|
|
* truncated at the bad unit. Rare enough that the allocation this
|
|
* costs does not matter. */
|
|
if (!(name = utf16_to_utf8_string_alloc(src)))
|
|
return NULL;
|
|
strlcpy(rdir->name_utf8, name, sizeof(rdir->name_utf8));
|
|
free(name);
|
|
return rdir->name_utf8;
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
const char *retro_vfs_dirent_get_name_impl(libretro_vfs_implementation_dir *rdir)
|
|
{
|
|
#ifdef HAVE_SMBCLIENT
|
|
if (rdir->smb_handle)
|
|
return rdir->smb_path;
|
|
#endif
|
|
#ifdef HAVE_NFSCLIENT
|
|
if (rdir->nfs_handle)
|
|
return rdir->nfs_name;
|
|
#endif
|
|
#if defined(ANDROID) && defined(HAVE_SAF)
|
|
if (rdir->saf_directory != NULL)
|
|
return retro_vfs_dirent_get_name_saf(rdir->saf_directory);
|
|
else
|
|
#endif
|
|
{
|
|
#if defined(_WIN32)
|
|
#if defined(LEGACY_WIN32_RUNTIME)
|
|
if (win32_needs_local_encoding())
|
|
return vfs_win32_name_local(rdir, rdir->entry.a.cFileName);
|
|
return vfs_win32_name_utf16(rdir, rdir->entry.w.cFileName);
|
|
#elif defined(LEGACY_WIN32)
|
|
return vfs_win32_name_local(rdir, rdir->entry.cFileName);
|
|
#else
|
|
return vfs_win32_name_utf16(rdir, rdir->entry.cFileName);
|
|
#endif
|
|
#elif defined(VITA) || defined(__PSL1GHT__) || defined(__PS3__)
|
|
return rdir->entry.d_name;
|
|
#else
|
|
if (!rdir || !rdir->entry)
|
|
return NULL;
|
|
return rdir->entry->d_name;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
#if !defined(_WIN32) && !defined(VITA) && !defined(__PSL1GHT__) && !defined(__PS3__)
|
|
/* Split out of retro_vfs_dirent_is_dir_impl() so that the d_type test
|
|
* there does not have to carry this scratch buffer. Filesystems that
|
|
* populate d_type - ext4, APFS, NTFS - answer from the dirent alone and
|
|
* never reach this, but the array and the struct stat were declared in
|
|
* the same scope as the test, so every entry paid a 2224-byte frame
|
|
* plus, under -fstack-protector-strong, a canary written and re-read at
|
|
* offset 2200 of a frame the fast path otherwise never touches. */
|
|
static VFS_NOINLINE bool retro_vfs_dirent_is_dir_stat(
|
|
libretro_vfs_implementation_dir *rdir)
|
|
{
|
|
struct stat buf;
|
|
char path[PATH_MAX_LENGTH];
|
|
|
|
fill_pathname_join_special(path, rdir->orig_path,
|
|
retro_vfs_dirent_get_name_impl(rdir), sizeof(path));
|
|
if (stat(path, &buf) < 0)
|
|
return false;
|
|
return S_ISDIR(buf.st_mode);
|
|
}
|
|
#endif
|
|
|
|
bool retro_vfs_dirent_is_dir_impl(libretro_vfs_implementation_dir *rdir)
|
|
{
|
|
#ifdef HAVE_SMBCLIENT
|
|
if (rdir->smb_handle)
|
|
return rdir->smb_is_dir;
|
|
#endif
|
|
#ifdef HAVE_NFSCLIENT
|
|
if (rdir->nfs_handle)
|
|
return rdir->nfs_is_dir;
|
|
#endif
|
|
#if defined(ANDROID) && defined(HAVE_SAF)
|
|
if (rdir->saf_directory != NULL)
|
|
return retro_vfs_dirent_is_dir_saf(rdir->saf_directory);
|
|
else
|
|
#endif
|
|
{
|
|
#if defined(_WIN32)
|
|
const WIN32_FIND_DATA *entry = (const WIN32_FIND_DATA*)&rdir->entry;
|
|
return entry->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
|
|
#elif defined(VITA)
|
|
const SceIoDirent *entry = (const SceIoDirent*)&rdir->entry;
|
|
return SCE_S_ISDIR(entry->d_stat.st_mode);
|
|
#elif defined(__PSL1GHT__) || defined(__PS3__)
|
|
sysFSDirent *entry = (sysFSDirent*)&rdir->entry;
|
|
return (entry->d_type == FS_TYPE_DIR);
|
|
#else
|
|
#if defined(DT_DIR)
|
|
const struct dirent *entry = (const struct dirent*)rdir->entry;
|
|
if (entry->d_type == DT_DIR)
|
|
return true;
|
|
/* This can happen on certain file systems. */
|
|
if (!(entry->d_type == DT_UNKNOWN || entry->d_type == DT_LNK))
|
|
return false;
|
|
#endif
|
|
/* dirent struct doesn't have d_type, do it the slow way ... */
|
|
return retro_vfs_dirent_is_dir_stat(rdir);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/* The join-and-stat fallback: one full-path stat per entry, which is
|
|
* exactly what a caller without dirent_stat would do itself, so it is
|
|
* never worse than today. Split out so the PATH_MAX_LENGTH local
|
|
* stays off the fast paths' stack. Only compiled where some branch
|
|
* of dirent_stat reaches it. */
|
|
#if defined(HAVE_SMBCLIENT) || defined(HAVE_NFSCLIENT) || (defined(ANDROID) && defined(HAVE_SAF)) \
|
|
|| !(defined(_WIN32) || defined(VITA) \
|
|
|| defined(VFS_HAVE_FSTATAT))
|
|
static VFS_NOINLINE int retro_vfs_dirent_stat_slow(
|
|
libretro_vfs_implementation_dir *rdir, int64_t *size, int64_t *mtime)
|
|
{
|
|
char path[PATH_MAX_LENGTH];
|
|
const char *name = retro_vfs_dirent_get_name_impl(rdir);
|
|
if (!name || !rdir->orig_path)
|
|
return 0;
|
|
fill_pathname_join_special(path, rdir->orig_path, name, sizeof(path));
|
|
return retro_vfs_stat_full(path, size, mtime);
|
|
}
|
|
#endif
|
|
|
|
int retro_vfs_dirent_stat_impl(libretro_vfs_implementation_dir *rdir,
|
|
int64_t *size, int64_t *mtime)
|
|
{
|
|
if (!rdir)
|
|
return 0;
|
|
#ifdef HAVE_SMBCLIENT
|
|
if (rdir->smb_handle)
|
|
return retro_vfs_dirent_stat_slow(rdir, size, mtime);
|
|
#endif
|
|
#ifdef HAVE_NFSCLIENT
|
|
if (rdir->nfs_handle)
|
|
return retro_vfs_dirent_stat_slow(rdir, size, mtime);
|
|
#endif
|
|
#if defined(ANDROID) && defined(HAVE_SAF)
|
|
if (rdir->saf_directory != NULL)
|
|
return retro_vfs_dirent_stat_slow(rdir, size, mtime);
|
|
else
|
|
#endif
|
|
{
|
|
#if defined(_WIN32)
|
|
/* Everything is already in the find data; no I/O at all. */
|
|
const WIN32_FIND_DATA *entry = (const WIN32_FIND_DATA*)&rdir->entry;
|
|
int ret = RETRO_VFS_STAT_IS_VALID;
|
|
if (size)
|
|
*size = (int64_t)(((uint64_t)entry->nFileSizeHigh << 32)
|
|
| (uint64_t)entry->nFileSizeLow);
|
|
if (mtime)
|
|
*mtime = vfs_filetime_to_unix(&entry->ftLastWriteTime);
|
|
if (entry->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
|
ret |= RETRO_VFS_STAT_IS_DIRECTORY;
|
|
if (entry->dwFileAttributes & FILE_ATTRIBUTE_READONLY)
|
|
ret |= RETRO_VFS_STAT_IS_READONLY;
|
|
return ret;
|
|
#elif defined(VITA)
|
|
const SceIoDirent *entry = (const SceIoDirent*)&rdir->entry;
|
|
int ret = RETRO_VFS_STAT_IS_VALID;
|
|
if (size)
|
|
*size = (int64_t)entry->d_stat.st_size;
|
|
if (mtime)
|
|
{
|
|
time_t t = 0;
|
|
sceRtcGetTime_t(&entry->d_stat.st_mtime, &t);
|
|
*mtime = (int64_t)t;
|
|
}
|
|
if (SCE_S_ISDIR(entry->d_stat.st_mode))
|
|
ret |= RETRO_VFS_STAT_IS_DIRECTORY;
|
|
if (!(entry->d_stat.st_mode & SCE_S_IWUSR))
|
|
ret |= RETRO_VFS_STAT_IS_READONLY;
|
|
return ret;
|
|
#elif defined(VFS_HAVE_FSTATAT)
|
|
/* fstatat on the open directory: no path join, no lookup from
|
|
* the root, one inode read. */
|
|
const struct dirent *entry = (const struct dirent*)rdir->entry;
|
|
struct stat st;
|
|
int ret = RETRO_VFS_STAT_IS_VALID;
|
|
if (!entry || fstatat(dirfd(rdir->directory), entry->d_name, &st, 0) < 0)
|
|
return 0;
|
|
if (size)
|
|
*size = (int64_t)st.st_size;
|
|
if (mtime)
|
|
*mtime = (int64_t)st.st_mtime;
|
|
if (S_ISDIR(st.st_mode))
|
|
ret |= RETRO_VFS_STAT_IS_DIRECTORY;
|
|
if (S_ISCHR(st.st_mode))
|
|
ret |= RETRO_VFS_STAT_IS_CHARACTER_SPECIAL;
|
|
if (!(st.st_mode & S_IWUSR))
|
|
ret |= RETRO_VFS_STAT_IS_READONLY;
|
|
return ret;
|
|
#else
|
|
return retro_vfs_dirent_stat_slow(rdir, size, mtime);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
int retro_vfs_closedir_impl(libretro_vfs_implementation_dir *rdir)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (!rdir)
|
|
return -1;
|
|
|
|
#ifdef HAVE_SMBCLIENT
|
|
if (rdir->smb_handle)
|
|
{
|
|
retro_vfs_closedir_smb(rdir->smb_handle);
|
|
rdir->smb_handle = NULL;
|
|
}
|
|
#endif
|
|
#ifdef HAVE_NFSCLIENT
|
|
if (rdir->nfs_handle)
|
|
{
|
|
retro_vfs_closedir_nfs(rdir->nfs_handle);
|
|
rdir->nfs_handle = NULL;
|
|
}
|
|
#endif
|
|
|
|
#if defined(ANDROID) && defined(HAVE_SAF)
|
|
if (rdir->saf_directory != NULL)
|
|
ret = retro_vfs_closedir_saf(rdir->saf_directory);
|
|
else
|
|
#endif
|
|
{
|
|
#if defined(_WIN32)
|
|
if (rdir->directory != INVALID_HANDLE_VALUE)
|
|
FindClose(rdir->directory);
|
|
#elif defined(VITA)
|
|
sceIoDclose(rdir->directory);
|
|
#elif defined(__PSL1GHT__) || defined(__PS3__)
|
|
rdir->error = sysFsClosedir(rdir->directory);
|
|
#else
|
|
if (rdir->directory)
|
|
closedir(rdir->directory);
|
|
#endif
|
|
}
|
|
|
|
if (rdir->orig_path)
|
|
free(rdir->orig_path);
|
|
free(rdir);
|
|
return ret;
|
|
}
|