mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-12 16:19:29 -10:00
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:
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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user