libretro: VFS v6 - reads and writes at an offset, with no lock

read_at and write_at take the offset with the call and leave the
stream's own position alone, so any number of threads can use one
handle at once and a core no longer needs a lock of its own around
seek-then-read. write_at with RETRO_VFS_WRITE_AT_APPEND reserves its
place at the end with one atomic add, so appends from several threads
neither overlap nor tear.

pread/pwrite where the platform has them, a second handle with its
own position (ReOpenFile, OVERLAPPED) on Windows, a memcpy for a
mapped stream. -1 where there is neither (consoles, UWP, the other
schemes); the caller then does what it did before.

filestream_read_at / filestream_write_at reach it from libretro-common.

vfs_positional_test: four threads read at offsets on one handle while
a fifth reads it in sequence, for buffered, descriptor and mapped
streams, then four threads append records. With the positional read
swapped for seek-then-read it reports tens of thousands of wrong reads.
Runs in the libretro-common samples workflow; also built with MinGW
and run under wine.
This commit is contained in:
LibretroAdmin authored and libretroadmin committed 2026-11-12 08:15:00 +01:00
1 parent 2a688d8f43
commit 34fd7248c0
11 files changed
+679 -6

No files matched your search

@@ -288,6 +288,9 @@ jobs:
# A read-write stream on the descriptor path, as an emulated
# file system uses it: in-place writes, truncate, reopen.
vfs_descriptor_rw_test
# Positional I/O (VFS v6): reads at offsets and appends from
# several threads on one stream, with no lock.
vfs_positional_test
cdrom_cuesheet_overflow_test
rdvd_h262_test
rdvd_lpcm_test
+51
View File
@@ -4345,6 +4345,50 @@ typedef int (RETRO_CALLCONV *retro_vfs_dirent_stat_t)(struct retro_vfs_dir_handl
*/
typedef int (RETRO_CALLCONV *retro_vfs_closedir_t)(struct retro_vfs_dir_handle *dirstream);
/**
* Reads from a file at a given offset, without moving the position that
* \c retro_vfs_read_t, \c retro_vfs_seek_t and \c retro_vfs_tell_t share.
* Any number of threads may call it on one handle at once, with no lock
* of the core's: the frontend reads at the offset itself. Introduced in
* VFS API v6.
*
* It reads the file as it is on disk: bytes written with
* \c retro_vfs_write_t and not flushed yet may not be seen.
*
* @param stream The file to read from.
* @param s Where the bytes go.
* @param len How many to read.
* @param offset Where in the file, from its start; not negative.
* @return The number of bytes read, 0 at or past the end of the file, or
* -1 on an error or when the frontend has no positional I/O for this
* file (a cdrom:// or network path, a platform without it): the core
* then seeks and reads, under a lock of its own if it shares the handle.
*/
typedef int64_t (RETRO_CALLCONV *retro_vfs_read_at_t)(struct retro_vfs_file_handle *stream, void *s, uint64_t len, int64_t offset);
/** \c offset of \c retro_vfs_write_at_t for an append. */
#define RETRO_VFS_WRITE_AT_APPEND (-1)
/**
* Writes to a file at a given offset, without moving the handle's
* position; callable from several threads at once as
* \c retro_vfs_read_at_t is. Introduced in VFS API v6.
*
* With \c RETRO_VFS_WRITE_AT_APPEND the bytes are appended: each append
* goes in whole, after the one before it, whichever thread it came from
* - a log or a cache several threads add records to needs no lock. The
* appends start at the end of the file as it was opened, so a handle
* that is appended to is written with this call only.
*
* @param stream The file to write to.
* @param s The bytes to write.
* @param len How many.
* @param offset Where in the file, or \c RETRO_VFS_WRITE_AT_APPEND.
* @return The number of bytes written, or -1 on an error or when the
* frontend has no positional I/O for this file.
*/
typedef int64_t (RETRO_CALLCONV *retro_vfs_write_at_t)(struct retro_vfs_file_handle *stream, const void *s, uint64_t len, int64_t offset);
/**
* File system interface exposed by the frontend.
*
@@ -4440,6 +4484,13 @@ struct retro_vfs_interface
/** @copydoc retro_vfs_dirent_stat_t */
retro_vfs_dirent_stat_t dirent_stat;
/* VFS API v6 */
/** @copydoc retro_vfs_read_at_t */
retro_vfs_read_at_t read_at;
/** @copydoc retro_vfs_write_at_t */
retro_vfs_write_at_t write_at;
};
/**
@@ -159,6 +159,33 @@ int64_t filestream_read(RFILE *stream, void *data, int64_t len);
*/
int64_t filestream_write(RFILE *stream, const void *data, int64_t len);
/**
* Reads \c len bytes at \c offset without moving the stream's position
* or touching its read-ahead: any number of threads may call it on one
* stream at once, with no lock. It reads the file as it is on disk;
* bytes written with \c filestream_write and not flushed are not seen.
*
* @return The number of bytes read, 0 at or past the end, or -1 on an
* error or where there is no positional I/O for this stream (the VFS in
* use is older than v6, a cdrom:// or network path, a platform without
* it): the caller then seeks and reads, under a lock of its own if it
* shares the stream.
* @see retro_vfs_read_at_t
*/
int64_t filestream_read_at(RFILE *stream, void *data, int64_t len,
int64_t offset);
/**
* Writes \c len bytes at \c offset the same way. With
* \c RETRO_VFS_WRITE_AT_APPEND for \c offset each call appends, whole
* and after the one before it, whichever thread it came from.
*
* @return The number of bytes written, or -1 as \c filestream_read_at.
* @see retro_vfs_write_at_t
*/
int64_t filestream_write_at(RFILE *stream, const void *data, int64_t len,
int64_t offset);
/**
* Writes @count pieces of memory to the given file, in order, as if
* each were passed to \c filestream_write in turn. A stream opened
+10
View File
@@ -27,6 +27,7 @@
#include <retro_common_api.h>
#include <boolean.h>
#include <stdint.h>
#include <retro_atomic.h>
#ifdef RARCH_INTERNAL
#ifndef VFS_FRONTEND
@@ -230,6 +231,15 @@ struct libretro_vfs_implementation_file
#if defined(HAVE_CDROM) && defined(__APPLE__)
void *iokit_plugin; /* IOCFPlugInInterface ** */
void *iokit_mmc; /* MMCDeviceInterface ** */
#endif
/* Positional I/O (retro_vfs_file_read_at_impl, _write_at_impl), from
* any number of threads at once. append_end: where the next append
* goes, plus one; 0 until the first. at_handle (Windows): a handle of
* its own on the file, with a position of its own, made at the first
* positional call; NULL until then. */
retro_atomic_size_t append_end;
#ifdef _WIN32
retro_atomic_ptr_t at_handle;
#endif
};
#endif
@@ -67,6 +67,21 @@ int64_t retro_vfs_file_write_impl(libretro_vfs_implementation_file *stream, cons
int64_t retro_vfs_file_writev_impl(libretro_vfs_implementation_file *stream,
const struct retro_vfs_span *spans, size_t count);
/* Reads @len bytes at @offset without moving the stream's position;
* any number of threads may call it on one stream at once. Returns the
* bytes read, 0 at or past the end, or -1 on an error or where the
* platform or the stream's scheme has no positional I/O (the caller then
* seeks and reads, under a lock of its own if it shares the stream). */
int64_t retro_vfs_file_read_at_impl(libretro_vfs_implementation_file *stream,
void *s, uint64_t len, int64_t offset);
/* Writes @len bytes at @offset the same way; with a negative @offset it
* appends, each append whole and after the one before it whichever
* thread it came from (after the file's end as it was opened, so a
* stream appended to is written with this call only). -1 as above. */
int64_t retro_vfs_file_write_at_impl(libretro_vfs_implementation_file *stream,
const void *s, uint64_t len, int64_t offset);
int retro_vfs_file_flush_impl(libretro_vfs_implementation_file *stream);
int retro_vfs_file_remove_impl(const char *path);
+8 -4
View File
@@ -14,6 +14,7 @@ TARGET_TEST10 := vfs_view_test
TARGET_TEST11 := vfs_prefetch_offthread_test
TARGET_TEST12 := vfs_close_result_test
TARGET_TEST13 := vfs_descriptor_rw_test
TARGET_TEST14 := vfs_positional_test
LIBRETRO_COMM_DIR := ../../..
@@ -36,7 +37,7 @@ OBJS := vfs_read_overflow_test.o vfs_mapped_ptr_test.o \
vfs_v5_metadata_test.o vfs_rename_replace_test.o \
vfs_prefetch_test.o vfs_readahead_test.o vfs_view_test.o \
vfs_prefetch_offthread_test.o vfs_close_result_test.o \
vfs_descriptor_rw_test.o $(COMMON_OBJS)
vfs_descriptor_rw_test.o vfs_positional_test.o $(COMMON_OBJS)
# vfs_view_test also hashes through sha1_calculate(), whose dispatch
# asks features_cpu.c what the CPU has.
@@ -159,7 +160,7 @@ endif
all: $(TARGET) $(TARGET_TEST) $(TARGET_TEST2) $(TARGET_TEST3) $(TARGET_TEST4) \
$(TARGET_TEST5) $(TARGET_TEST6) $(TARGET_TEST8) $(TARGET_TEST9) \
$(TARGET_TEST10) $(TARGET_TEST11) $(TARGET_TEST12) $(TARGET_TEST13) \
$(RENAME_TARGET)
$(TARGET_TEST14) $(RENAME_TARGET)
%.o: %.c
$(CC) -c -o $@ $< $(CFLAGS)
@@ -180,6 +181,9 @@ $(TARGET_TEST8): vfs_prefetch_test.o $(COMMON_OBJS)
$(TARGET_TEST13): vfs_descriptor_rw_test.o $(COMMON_OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
$(TARGET_TEST14): vfs_positional_test.o $(COMMON_OBJS)
$(CC) -o $@ $^ $(LDFLAGS) -lpthread
$(TARGET_TEST10): vfs_view_test.o $(VIEW_OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
@@ -248,7 +252,7 @@ $(TARGET_TEST7): vfs_rename_replace_test.o $(RENAME_OBJS)
$(CLOSE_VFS_OBJ): $(LIBRETRO_COMM_DIR)/vfs/vfs_implementation.c close_hook.h
$(CC) -c -o $@ $< $(filter-out -DHAVE_MMAP,$(CFLAGS)) -DPS2 \
-I. -include close_hook.h
-DRETRO_ATOMIC_FORCE_VOLATILE -I. -include close_hook.h
$(TARGET_TEST12): vfs_close_result_test.o $(CLOSE_OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
@@ -258,7 +262,7 @@ clean:
rm -f $(TARGET) $(TARGET_TEST) $(TARGET_TEST2) $(TARGET_TEST3) \
$(TARGET_TEST4) $(TARGET_TEST5) $(TARGET_TEST6) \
$(TARGET_TEST7) $(TARGET_TEST8) $(TARGET_TEST9) $(TARGET_TEST10) \
$(TARGET_TEST11) $(TARGET_TEST12) $(TARGET_TEST13) $(CLOSE_VFS_OBJ) \
$(TARGET_TEST11) $(TARGET_TEST12) $(TARGET_TEST13) $(TARGET_TEST14) $(CLOSE_VFS_OBJ) \
$(OBJS) $(FAULT_FS_OBJ) $(RENAME_VFS_OBJ) \
$(RA_VFS_OBJ) $(HOOK_VFS_OBJ) rthreads_ra.o retro_eventcount_ra.o
rm -rf v5_meta_dir
@@ -0,0 +1,279 @@
/* Positional I/O on a VFS stream (VFS API v6: retro_vfs_file_read_at_impl,
* retro_vfs_file_write_at_impl, filestream_read_at, filestream_write_at):
* a read or write at an offset given with the call, which leaves the
* stream's own position alone, so several threads share one stream with
* no lock.
*
* - four threads read at offsets all over one stream at once, while the
* main thread reads it front to back with the ordinary read: every byte
* read either way is the file's byte at that place - no read lands
* where another thread's seek left the position, which is what a seek
* and a read per thread does without a lock;
* - the same on a buffered stream, a descriptor stream
* (SEQUENTIAL_BULK) and a mapped one (FREQUENT_ACCESS);
* - a read at or past the end is 0 bytes, one across it is short;
* - four threads append records to one stream at once: the file holds
* every record, whole, each thread's in the order it wrote them;
* - a write at an offset lands there and nowhere else;
* - a stream without positional I/O says -1 (a NULL one here).
*
* Where the platform has no positional I/O at all the calls answer -1
* and the test says so and passes: the caller's fallback is the seek
* and read it had before. */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <libretro.h>
#include <streams/file_stream.h>
#include <vfs/vfs_implementation.h>
#include <retro_atomic.h>
#ifdef _WIN32
#include <windows.h>
typedef HANDLE test_thread_t;
#define THREAD_RET DWORD WINAPI
#define THREAD_ARG LPVOID
static void thread_start(test_thread_t *t, LPTHREAD_START_ROUTINE fn, void *arg)
{
*t = CreateThread(NULL, 0, fn, arg, 0, NULL);
}
static void thread_join(test_thread_t t)
{
WaitForSingleObject(t, INFINITE);
CloseHandle(t);
}
#else
#include <pthread.h>
typedef pthread_t test_thread_t;
#define THREAD_RET void *
#define THREAD_ARG void *
static void thread_start(test_thread_t *t, void *(*fn)(void*), void *arg)
{
pthread_create(t, NULL, fn, arg);
}
static void thread_join(test_thread_t t)
{
pthread_join(t, NULL);
}
#endif
#define FILE_SIZE (4u << 20)
#define THREADS 4
#define READS 20000
#define RECORDS 5000
#define RECORD_LEN 64
static int failures;
#define CHECK(c) do { if (!(c)) { fprintf(stderr, "%s:%d: check failed: %s\n", __FILE__, __LINE__, #c); failures++; } } while (0)
/* The file's byte at @at */
static uint8_t pattern(uint32_t at)
{
return (uint8_t)((at * 2654435761u) >> 24);
}
static libretro_vfs_implementation_file *shared;
static retro_atomic_int_t wrong_reads;
static THREAD_RET reader(THREAD_ARG arg)
{
uint32_t x = (uint32_t)(uintptr_t)arg * 2246822519u + 1;
uint8_t buf[512];
int i;
for (i = 0; i < READS; i++)
{
uint32_t at, len, k;
int64_t got;
x = x * 1103515245u + 12345u;
at = (x >> 8) % (FILE_SIZE - sizeof(buf));
len = 1 + (x >> 4) % sizeof(buf);
got = retro_vfs_file_read_at_impl(shared, buf, len, at);
if (got != (int64_t)len)
{
retro_atomic_fetch_add_int(&wrong_reads, 1);
continue;
}
for (k = 0; k < len; k++)
if (buf[k] != pattern(at + k))
{
retro_atomic_fetch_add_int(&wrong_reads, 1);
break;
}
}
return 0;
}
static THREAD_RET appender(THREAD_ARG arg)
{
uint8_t rec[RECORD_LEN];
uint32_t id = (uint32_t)(uintptr_t)arg;
uint32_t i;
for (i = 0; i < RECORDS; i++)
{
memset(rec, (int)(id * 16 + (i & 15)), sizeof(rec));
memcpy(rec, &id, 4);
memcpy(rec + 4, &i, 4);
if (retro_vfs_file_write_at_impl(shared, rec, sizeof(rec),
RETRO_VFS_WRITE_AT_APPEND) != (int64_t)sizeof(rec))
retro_atomic_fetch_add_int(&wrong_reads, 1);
}
return 0;
}
static int read_shared(const char *path, unsigned hints, const char *what)
{
test_thread_t t[THREADS];
uint8_t buf[4096];
uint8_t one;
uint32_t at = 0, wrong_seq = 0;
int i;
shared = retro_vfs_file_open_impl(path, RETRO_VFS_FILE_ACCESS_READ, hints);
CHECK(shared != NULL);
if (!shared)
return 0;
if (retro_vfs_file_read_at_impl(shared, &one, 1, 0) < 0)
{
printf("vfs positional: no positional I/O for a %s stream here, skipped\n", what);
retro_vfs_file_close_impl(shared);
return 0;
}
retro_atomic_int_init(&wrong_reads, 0);
for (i = 0; i < THREADS; i++)
thread_start(&t[i], reader, (void*)(uintptr_t)(i + 1));
/* Front to back with the ordinary read, the others reading meanwhile */
while (at < FILE_SIZE)
{
int64_t got = retro_vfs_file_read_impl(shared, buf, sizeof(buf));
int64_t k;
if (got <= 0)
break;
for (k = 0; k < got; k++)
if (buf[k] != pattern(at + (uint32_t)k))
{
wrong_seq++;
break;
}
at += (uint32_t)got;
}
for (i = 0; i < THREADS; i++)
thread_join(t[i]);
CHECK(at == FILE_SIZE);
CHECK(wrong_seq == 0);
CHECK(retro_atomic_load_acquire_int(&wrong_reads) == 0);
/* The end */
CHECK(retro_vfs_file_read_at_impl(shared, buf, 16, FILE_SIZE) == 0);
CHECK(retro_vfs_file_read_at_impl(shared, buf, 16, FILE_SIZE + 100) == 0);
CHECK(retro_vfs_file_read_at_impl(shared, buf, 16, FILE_SIZE - 5) == 5);
CHECK(retro_vfs_file_read_at_impl(shared, buf, 16, -1) == -1);
printf("vfs positional: %s stream: %d reads at offsets on %d threads, %d wrong; %u sequential blocks wrong\n",
what, READS * THREADS, THREADS, retro_atomic_load_acquire_int(&wrong_reads), wrong_seq);
retro_vfs_file_close_impl(shared);
return 1;
}
int main(void)
{
static const char path[] = "vfs_positional.bin";
static const char path_w[] = "vfs_positional_append.bin";
uint8_t *whole = (uint8_t*)malloc(FILE_SIZE);
uint8_t one;
uint32_t i;
int had;
RFILE *f;
if (!whole)
return 1;
for (i = 0; i < FILE_SIZE; i++)
whole[i] = pattern(i);
CHECK(filestream_write_file(path, whole, FILE_SIZE));
had = read_shared(path, RETRO_VFS_FILE_ACCESS_HINT_NONE, "buffered");
had |= read_shared(path, RETRO_VFS_FILE_ACCESS_HINT_SEQUENTIAL_BULK, "descriptor");
had |= read_shared(path, RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS, "mapped");
CHECK(retro_vfs_file_read_at_impl(NULL, &one, 1, 0) == -1);
CHECK(retro_vfs_file_write_at_impl(NULL, &one, 1, 0) == -1);
if (had)
{
/* Appends from four threads */
test_thread_t t[THREADS];
uint32_t next[THREADS + 1];
uint32_t torn = 0, out_of_order = 0;
int64_t size;
int k;
shared = retro_vfs_file_open_impl(path_w, RETRO_VFS_FILE_ACCESS_READ_WRITE,
RETRO_VFS_FILE_ACCESS_HINT_NONE);
CHECK(shared != NULL);
retro_atomic_int_init(&wrong_reads, 0);
for (k = 0; k < THREADS; k++)
thread_start(&t[k], appender, (void*)(uintptr_t)(k + 1));
for (k = 0; k < THREADS; k++)
thread_join(t[k]);
CHECK(retro_atomic_load_acquire_int(&wrong_reads) == 0);
/* A write at an offset: there and nowhere else */
memset(whole, 0xAB, 16);
CHECK(retro_vfs_file_write_at_impl(shared, whole, 16, 8) == 16);
retro_vfs_file_close_impl(shared);
f = filestream_open(path_w, RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE);
size = f ? filestream_get_size(f) : -1;
CHECK(size == (int64_t)THREADS * RECORDS * RECORD_LEN);
memset(next, 0, sizeof(next));
for (i = 0; f && i < THREADS * RECORDS; i++)
{
uint8_t rec[RECORD_LEN];
uint32_t id, seq, b;
if (filestream_read_at(f, rec, sizeof(rec), (int64_t)i * RECORD_LEN) != (int64_t)sizeof(rec))
{
torn++;
continue;
}
if (i == 0)
{
/* The sixteen bytes written over the first record */
for (b = 8; b < 24; b++)
if (rec[b] != 0xAB)
torn++;
continue;
}
memcpy(&id, rec, 4);
memcpy(&seq, rec + 4, 4);
if (id < 1 || id > THREADS)
{
torn++;
continue;
}
for (b = 8; b < RECORD_LEN; b++)
if (rec[b] != (uint8_t)(id * 16 + (seq & 15)))
{
torn++;
break;
}
if (seq < next[id])
out_of_order++;
next[id] = seq + 1;
}
if (f)
filestream_close(f);
CHECK(torn == 0);
CHECK(out_of_order == 0);
printf("vfs positional: %d records appended by %d threads: %u torn, %u out of a thread's order\n",
THREADS * RECORDS, THREADS, torn, out_of_order);
}
remove(path);
remove(path_w);
free(whole);
printf("vfs positional: %s\n", failures ? "FAIL" : "ok");
return failures ? 1 : 0;
}
+42
View File
@@ -197,6 +197,8 @@ static retro_vfs_rename_t filestream_rename_cb = NULL;
static retro_vfs_copy_begin_t filestream_copy_begin_cb = NULL;
static retro_vfs_copy_step_t filestream_copy_step_cb = NULL;
static retro_vfs_copy_close_t filestream_copy_close_cb = NULL;
static retro_vfs_read_at_t filestream_read_at_cb = NULL;
static retro_vfs_write_at_t filestream_write_at_cb = NULL;
static bool filestream_copy_unavailable = false;
/* VFS Initialization */
@@ -221,6 +223,8 @@ void filestream_vfs_init(const struct retro_vfs_interface_info* vfs_info)
filestream_copy_begin_cb = NULL;
filestream_copy_step_cb = NULL;
filestream_copy_close_cb = NULL;
filestream_read_at_cb = NULL;
filestream_write_at_cb = NULL;
filestream_copy_unavailable = false;
if (
@@ -251,6 +255,13 @@ void filestream_vfs_init(const struct retro_vfs_interface_info* vfs_info)
}
else
filestream_copy_unavailable = true;
/* v6: positional I/O; a frontend before it has none to offer */
if (vfs_info->required_interface_version >= 6)
{
filestream_read_at_cb = vfs_iface->read_at;
filestream_write_at_cb = vfs_iface->write_at;
}
}
/* Callback wrappers */
@@ -2018,6 +2029,37 @@ int64_t filestream_write(RFILE *stream, const void *s, int64_t len)
return output;
}
int64_t filestream_read_at(RFILE *stream, void *data, int64_t len,
int64_t offset)
{
if (!stream || len < 0)
return -1;
/* The file itself, at the offset: the stream's position, its
* read-ahead and its last operation are not touched, which is what
* lets several threads call this at once */
if (filestream_read_cb)
return filestream_read_at_cb
? filestream_read_at_cb(stream->hfile, data, (uint64_t)len, offset)
: -1;
return retro_vfs_file_read_at_impl(
(libretro_vfs_implementation_file*)stream->hfile,
data, (uint64_t)len, offset);
}
int64_t filestream_write_at(RFILE *stream, const void *data, int64_t len,
int64_t offset)
{
if (!stream || len < 0)
return -1;
if (filestream_write_cb)
return filestream_write_at_cb
? filestream_write_at_cb(stream->hfile, data, (uint64_t)len, offset)
: -1;
return retro_vfs_file_write_at_impl(
(libretro_vfs_implementation_file*)stream->hfile,
data, (uint64_t)len, offset);
}
int64_t filestream_writev(RFILE *stream,
const struct retro_vfs_span *spans, size_t count)
{
+217
View File
@@ -720,6 +720,14 @@ int64_t retro_vfs_file_seek_internal(
* Returns a pointer to an RFILE if opened successfully, otherwise NULL.
**/
/* Where positional I/O is had: see retro_vfs_file_read_at_impl */
#if defined(VFS_HAVE_FILE_MAPPING) && defined(HAVE_MMAP)
#define VFS_POSITIONAL_POSIX 1
#elif defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__) \
&& defined(_WIN32_WINNT) && _WIN32_WINNT >= 0x0600
#define VFS_POSITIONAL_WIN32 1
#endif
libretro_vfs_implementation_file *retro_vfs_file_open_impl(
const char *path, unsigned mode, unsigned hints)
{
@@ -1229,6 +1237,12 @@ libretro_vfs_implementation_file *retro_vfs_file_open_impl(
retro_vfs_file_seek_internal(stream, 0, SEEK_SET);
}
/* Appends (retro_vfs_file_write_at_impl) start at the end as opened */
retro_atomic_size_init(&stream->append_end,
stream->size > 0 ? (size_t)stream->size : 0);
#ifdef _WIN32
retro_atomic_ptr_init(&stream->at_handle, NULL);
#endif
return stream;
error:
@@ -1265,6 +1279,14 @@ int retro_vfs_file_close_impl(libretro_vfs_implementation_file *stream)
}
#endif
#ifdef VFS_POSITIONAL_WIN32
{
HANDLE at = (HANDLE)retro_atomic_load_acquire_ptr(&stream->at_handle);
if (at)
CloseHandle(at);
}
#endif
if ((stream->hints & RFILE_HINT_UNBUFFERED) == 0)
{
if (stream->fp)
@@ -2068,6 +2090,201 @@ int64_t retro_vfs_file_writev_impl(libretro_vfs_implementation_file *stream,
return total;
}
/* Positional I/O: at an offset given with the call, leaving the stream's
* position alone, from any number of threads at once on one stream with
* no lock - pread()/pwrite() where there is one, a handle of the stream's
* own with OVERLAPPED offsets on Windows, the mapping where the stream is
* mapped. -1 where the platform or the stream's scheme (cdrom://, smb://,
* nfs://, SAF) has none: the caller seeks and reads under a lock of its
* own, as before. They act on the file itself: bytes written with
* retro_vfs_file_write_impl and not flushed yet are not seen. */
#ifdef VFS_POSITIONAL_WIN32
/* The stream's second handle, made by whichever thread gets there first */
static HANDLE vfs_positional_handle(libretro_vfs_implementation_file *stream)
{
HANDLE base, made;
HANDLE at = (HANDLE)retro_atomic_load_acquire_ptr(&stream->at_handle);
int fd;
if (at)
return at;
fd = (stream->hints & RFILE_HINT_UNBUFFERED) ? stream->fd
: (stream->fp ? _fileno(stream->fp) : -1);
if (fd < 0)
return NULL;
base = (HANDLE)_get_osfhandle(fd);
if (base == INVALID_HANDLE_VALUE)
return NULL;
made = ReOpenFile(base, GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0);
if (made == INVALID_HANDLE_VALUE)
made = ReOpenFile(base, GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, 0);
if (made == INVALID_HANDLE_VALUE)
return NULL;
if (retro_atomic_cas_ptr(&stream->at_handle, NULL, made))
return made;
/* Another thread's is in: this one is not needed */
CloseHandle(made);
return (HANDLE)retro_atomic_load_acquire_ptr(&stream->at_handle);
}
#endif
int64_t retro_vfs_file_read_at_impl(libretro_vfs_implementation_file *stream,
void *s, uint64_t len, int64_t offset)
{
if (!stream || !s || offset < 0 || len > (uint64_t)INT64_MAX)
return -1;
if (stream->scheme != VFS_SCHEME_NONE)
return -1;
#ifdef VFS_HAVE_FILE_MAPPING
if ( (stream->hints & RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS)
&& stream->mapped)
{
if ((uint64_t)offset >= stream->mapsize)
return 0;
if (len > stream->mapsize - (uint64_t)offset)
len = stream->mapsize - (uint64_t)offset;
memcpy(s, &stream->mapped[offset], (size_t)len);
return (int64_t)len;
}
#endif
#if defined(VFS_POSITIONAL_POSIX)
{
uint8_t *p = (uint8_t*)s;
uint64_t total = 0;
int fd = (stream->hints & RFILE_HINT_UNBUFFERED) ? stream->fd
: (stream->fp ? fileno(stream->fp) : -1);
if (fd < 0)
return -1;
while (len != 0)
{
size_t _chunk = (len > VFS_STDIO_IO_CHUNK_MAX)
? (size_t)VFS_STDIO_IO_CHUNK_MAX : (size_t)len;
ssize_t _got = pread(fd, p, _chunk, (off_t)(offset + (int64_t)total));
if (_got < 0)
return (total != 0) ? (int64_t)total : -1;
if (_got == 0)
break;
total += (uint64_t)_got;
p += _got;
len -= (uint64_t)_got;
}
return (int64_t)total;
}
#elif defined(VFS_POSITIONAL_WIN32)
{
uint8_t *p = (uint8_t*)s;
uint64_t total = 0;
HANDLE at = vfs_positional_handle(stream);
if (!at)
return -1;
while (len != 0)
{
OVERLAPPED ov;
DWORD _got = 0;
DWORD _chunk = (len > 0x40000000u) ? 0x40000000u : (DWORD)len;
uint64_t where = (uint64_t)offset + total;
memset(&ov, 0, sizeof(ov));
ov.Offset = (DWORD)(where & 0xffffffffu);
ov.OffsetHigh = (DWORD)(where >> 32);
if (!ReadFile(at, p, _chunk, &_got, &ov))
{
if (GetLastError() == ERROR_HANDLE_EOF)
break;
return (total != 0) ? (int64_t)total : -1;
}
if (_got == 0)
break;
total += _got;
p += _got;
len -= _got;
}
return (int64_t)total;
}
#else
return -1;
#endif
}
int64_t retro_vfs_file_write_at_impl(libretro_vfs_implementation_file *stream,
const void *s, uint64_t len, int64_t offset)
{
if (!stream || !s || len > (uint64_t)INT64_MAX)
return -1;
if (stream->scheme != VFS_SCHEME_NONE)
return -1;
#ifdef VFS_HAVE_FILE_MAPPING
/* A mapped stream is read-only */
if ( (stream->hints & RETRO_VFS_FILE_ACCESS_HINT_FREQUENT_ACCESS)
&& stream->mapped)
return -1;
#endif
#if defined(VFS_POSITIONAL_POSIX) || defined(VFS_POSITIONAL_WIN32)
if (offset < 0)
{
/* An append: its place taken with one atomic add, so appends from
* several threads land one after another, whole, in some order */
if (len > (uint64_t)((size_t)-1))
return -1;
offset = (int64_t)retro_atomic_fetch_add_size(&stream->append_end,
(size_t)len);
}
#endif
#if defined(VFS_POSITIONAL_POSIX)
{
const uint8_t *p = (const uint8_t*)s;
uint64_t total = 0;
int fd = (stream->hints & RFILE_HINT_UNBUFFERED) ? stream->fd
: (stream->fp ? fileno(stream->fp) : -1);
if (fd < 0)
return -1;
while (len != 0)
{
size_t _chunk = (len > VFS_STDIO_IO_CHUNK_MAX)
? (size_t)VFS_STDIO_IO_CHUNK_MAX : (size_t)len;
ssize_t _put = pwrite(fd, p, _chunk, (off_t)(offset + (int64_t)total));
if (_put <= 0)
return (total != 0) ? (int64_t)total : -1;
total += (uint64_t)_put;
p += _put;
len -= (uint64_t)_put;
}
return (int64_t)total;
}
#elif defined(VFS_POSITIONAL_WIN32)
{
const uint8_t *p = (const uint8_t*)s;
uint64_t total = 0;
HANDLE at = vfs_positional_handle(stream);
if (!at)
return -1;
while (len != 0)
{
OVERLAPPED ov;
DWORD _put = 0;
DWORD _chunk = (len > 0x40000000u) ? 0x40000000u : (DWORD)len;
uint64_t where = (uint64_t)offset + total;
memset(&ov, 0, sizeof(ov));
ov.Offset = (DWORD)(where & 0xffffffffu);
ov.OffsetHigh = (DWORD)(where >> 32);
if (!WriteFile(at, p, _chunk, &_put, &ov) || _put == 0)
return (total != 0) ? (int64_t)total : -1;
total += _put;
p += _put;
len -= _put;
}
return (int64_t)total;
}
#else
(void)offset;
return -1;
#endif
}
int retro_vfs_file_flush_impl(libretro_vfs_implementation_file *stream)
{
if (!stream)
@@ -270,6 +270,28 @@ int64_t retro_vfs_file_writev_impl(libretro_vfs_implementation_file *stream,
return total;
}
/* No positional I/O here: one handle, one position. The caller seeks and
* reads, as the C backend's callers do where it answers -1. */
int64_t retro_vfs_file_read_at_impl(libretro_vfs_implementation_file *stream,
void *s, uint64_t len, int64_t offset)
{
(void)stream;
(void)s;
(void)len;
(void)offset;
return -1;
}
int64_t retro_vfs_file_write_at_impl(libretro_vfs_implementation_file *stream,
const void *s, uint64_t len, int64_t offset)
{
(void)stream;
(void)s;
(void)len;
(void)offset;
return -1;
}
int retro_vfs_file_flush_impl(libretro_vfs_implementation_file* stream)
{
if (stream && fflush(stream->fp) == 0)
+5 -2
View File
@@ -3920,7 +3920,7 @@ bool runloop_environment_cb(unsigned cmd, void *data)
case RETRO_ENVIRONMENT_GET_VFS_INTERFACE:
{
const uint32_t supported_vfs_version = 5;
const uint32_t supported_vfs_version = 6;
static struct retro_vfs_interface vfs_iface =
{
/* VFS API v1 */
@@ -3954,7 +3954,10 @@ bool runloop_environment_cb(unsigned cmd, void *data)
retro_vfs_copy_begin_impl,
retro_vfs_copy_step_impl,
retro_vfs_copy_close_impl,
retro_vfs_dirent_stat_impl
retro_vfs_dirent_stat_impl,
/* VFS API v6 */
retro_vfs_file_read_at_impl,
retro_vfs_file_write_at_impl
};
struct retro_vfs_interface_info *vfs_iface_info = (struct retro_vfs_interface_info *) data;