summaryrefslogtreecommitdiff
path: root/contrib/SDL-3.2.8/src/io
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/SDL-3.2.8/src/io')
-rw-r--r--contrib/SDL-3.2.8/src/io/SDL_asyncio.c330
-rw-r--r--contrib/SDL-3.2.8/src/io/SDL_asyncio_c.h30
-rw-r--r--contrib/SDL-3.2.8/src/io/SDL_iostream.c1657
-rw-r--r--contrib/SDL-3.2.8/src/io/SDL_iostream_c.h35
-rw-r--r--contrib/SDL-3.2.8/src/io/SDL_sysasyncio.h144
-rw-r--r--contrib/SDL-3.2.8/src/io/generic/SDL_asyncio_generic.c465
-rw-r--r--contrib/SDL-3.2.8/src/io/io_uring/SDL_asyncio_liburing.c551
-rw-r--r--contrib/SDL-3.2.8/src/io/n3ds/SDL_iostreamromfs.c88
-rw-r--r--contrib/SDL-3.2.8/src/io/n3ds/SDL_iostreamromfs.h28
-rw-r--r--contrib/SDL-3.2.8/src/io/windows/SDL_asyncio_windows_ioring.c550
10 files changed, 3878 insertions, 0 deletions
diff --git a/contrib/SDL-3.2.8/src/io/SDL_asyncio.c b/contrib/SDL-3.2.8/src/io/SDL_asyncio.c
new file mode 100644
index 0000000..aa5ba87
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/io/SDL_asyncio.c
@@ -0,0 +1,330 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21
22#include "SDL_internal.h"
23#include "SDL_sysasyncio.h"
24#include "SDL_asyncio_c.h"
25
26static const char *AsyncFileModeValid(const char *mode)
27{
28 static const struct { const char *valid; const char *with_binary; } mode_map[] = {
29 { "r", "rb" },
30 { "w", "wb" },
31 { "r+","r+b" },
32 { "w+", "w+b" }
33 };
34
35 for (int i = 0; i < SDL_arraysize(mode_map); i++) {
36 if (SDL_strcmp(mode, mode_map[i].valid) == 0) {
37 return mode_map[i].with_binary;
38 }
39 }
40 return NULL;
41}
42
43SDL_AsyncIO *SDL_AsyncIOFromFile(const char *file, const char *mode)
44{
45 if (!file) {
46 SDL_InvalidParamError("file");
47 return NULL;
48 } else if (!mode) {
49 SDL_InvalidParamError("mode");
50 return NULL;
51 }
52
53 const char *binary_mode = AsyncFileModeValid(mode);
54 if (!binary_mode) {
55 SDL_SetError("Unsupported file mode");
56 return NULL;
57 }
58
59 SDL_AsyncIO *asyncio = (SDL_AsyncIO *)SDL_calloc(1, sizeof(*asyncio));
60 if (!asyncio) {
61 return NULL;
62 }
63
64 asyncio->lock = SDL_CreateMutex();
65 if (!asyncio->lock) {
66 SDL_free(asyncio);
67 return NULL;
68 }
69
70 if (!SDL_SYS_AsyncIOFromFile(file, binary_mode, asyncio)) {
71 SDL_DestroyMutex(asyncio->lock);
72 SDL_free(asyncio);
73 return NULL;
74 }
75
76 return asyncio;
77}
78
79Sint64 SDL_GetAsyncIOSize(SDL_AsyncIO *asyncio)
80{
81 if (!asyncio) {
82 SDL_InvalidParamError("asyncio");
83 return -1;
84 }
85 return asyncio->iface.size(asyncio->userdata);
86}
87
88static bool RequestAsyncIO(bool reading, SDL_AsyncIO *asyncio, void *ptr, Uint64 offset, Uint64 size, SDL_AsyncIOQueue *queue, void *userdata)
89{
90 if (!asyncio) {
91 return SDL_InvalidParamError("asyncio");
92 } else if (!ptr) {
93 return SDL_InvalidParamError("ptr");
94 } else if (!queue) {
95 return SDL_InvalidParamError("queue");
96 }
97
98 SDL_AsyncIOTask *task = (SDL_AsyncIOTask *) SDL_calloc(1, sizeof (*task));
99 if (!task) {
100 return false;
101 }
102
103 task->asyncio = asyncio;
104 task->type = reading ? SDL_ASYNCIO_TASK_READ : SDL_ASYNCIO_TASK_WRITE;
105 task->offset = offset;
106 task->buffer = ptr;
107 task->requested_size = size;
108 task->app_userdata = userdata;
109 task->queue = queue;
110
111 SDL_LockMutex(asyncio->lock);
112 if (asyncio->closing) {
113 SDL_free(task);
114 SDL_UnlockMutex(asyncio->lock);
115 return SDL_SetError("SDL_AsyncIO is closing, can't start new tasks");
116 }
117 LINKED_LIST_PREPEND(task, asyncio->tasks, asyncio);
118 SDL_AddAtomicInt(&queue->tasks_inflight, 1);
119 SDL_UnlockMutex(asyncio->lock);
120
121 const bool queued = reading ? asyncio->iface.read(asyncio->userdata, task) : asyncio->iface.write(asyncio->userdata, task);
122 if (!queued) {
123 SDL_AddAtomicInt(&queue->tasks_inflight, -1);
124 SDL_LockMutex(asyncio->lock);
125 LINKED_LIST_UNLINK(task, asyncio);
126 SDL_UnlockMutex(asyncio->lock);
127 SDL_free(task);
128 task = NULL;
129 }
130
131 return (task != NULL);
132}
133
134bool SDL_ReadAsyncIO(SDL_AsyncIO *asyncio, void *ptr, Uint64 offset, Uint64 size, SDL_AsyncIOQueue *queue, void *userdata)
135{
136 return RequestAsyncIO(true, asyncio, ptr, offset, size, queue, userdata);
137}
138
139bool SDL_WriteAsyncIO(SDL_AsyncIO *asyncio, void *ptr, Uint64 offset, Uint64 size, SDL_AsyncIOQueue *queue, void *userdata)
140{
141 return RequestAsyncIO(false, asyncio, ptr, offset, size, queue, userdata);
142}
143
144bool SDL_CloseAsyncIO(SDL_AsyncIO *asyncio, bool flush, SDL_AsyncIOQueue *queue, void *userdata)
145{
146 if (!asyncio) {
147 return SDL_InvalidParamError("asyncio");
148 } else if (!queue) {
149 return SDL_InvalidParamError("queue");
150 }
151
152 SDL_LockMutex(asyncio->lock);
153 if (asyncio->closing) {
154 SDL_UnlockMutex(asyncio->lock);
155 return SDL_SetError("Already closing");
156 }
157
158 SDL_AsyncIOTask *task = (SDL_AsyncIOTask *) SDL_calloc(1, sizeof (*task));
159 if (task) {
160 task->asyncio = asyncio;
161 task->type = SDL_ASYNCIO_TASK_CLOSE;
162 task->app_userdata = userdata;
163 task->queue = queue;
164 task->flush = flush;
165
166 asyncio->closing = task;
167
168 if (LINKED_LIST_START(asyncio->tasks, asyncio) == NULL) { // no tasks? Queue the close task now.
169 LINKED_LIST_PREPEND(task, asyncio->tasks, asyncio);
170 SDL_AddAtomicInt(&queue->tasks_inflight, 1);
171 if (!asyncio->iface.close(asyncio->userdata, task)) {
172 // uhoh, maybe they can try again later...?
173 SDL_AddAtomicInt(&queue->tasks_inflight, -1);
174 LINKED_LIST_UNLINK(task, asyncio);
175 SDL_free(task);
176 task = asyncio->closing = NULL;
177 }
178 }
179 }
180
181 SDL_UnlockMutex(asyncio->lock);
182
183 return (task != NULL);
184}
185
186SDL_AsyncIOQueue *SDL_CreateAsyncIOQueue(void)
187{
188 SDL_AsyncIOQueue *queue = SDL_calloc(1, sizeof (*queue));
189 if (queue) {
190 SDL_SetAtomicInt(&queue->tasks_inflight, 0);
191 if (!SDL_SYS_CreateAsyncIOQueue(queue)) {
192 SDL_free(queue);
193 return NULL;
194 }
195 }
196 return queue;
197}
198
199static bool GetAsyncIOTaskOutcome(SDL_AsyncIOTask *task, SDL_AsyncIOOutcome *outcome)
200{
201 if (!task || !outcome) {
202 return false;
203 }
204
205 SDL_AsyncIO *asyncio = task->asyncio;
206
207 SDL_zerop(outcome);
208 outcome->asyncio = asyncio->oneshot ? NULL : asyncio;
209 outcome->result = task->result;
210 outcome->type = task->type;
211 outcome->buffer = task->buffer;
212 outcome->offset = task->offset;
213 outcome->bytes_requested = task->requested_size;
214 outcome->bytes_transferred = task->result_size;
215 outcome->userdata = task->app_userdata;
216
217 // Take the completed task out of the SDL_AsyncIO that created it.
218 SDL_LockMutex(asyncio->lock);
219 LINKED_LIST_UNLINK(task, asyncio);
220 // see if it's time to queue a pending close request (close requested and no other pending tasks)
221 SDL_AsyncIOTask *closing = asyncio->closing;
222 if (closing && (task != closing) && (LINKED_LIST_START(asyncio->tasks, asyncio) == NULL)) {
223 LINKED_LIST_PREPEND(closing, asyncio->tasks, asyncio);
224 SDL_AddAtomicInt(&closing->queue->tasks_inflight, 1);
225 const bool async_close_task_was_queued = asyncio->iface.close(asyncio->userdata, closing);
226 SDL_assert(async_close_task_was_queued); // !!! FIXME: if this fails to queue the task, we're leaking resources!
227 if (!async_close_task_was_queued) {
228 SDL_AddAtomicInt(&closing->queue->tasks_inflight, -1);
229 }
230 }
231 SDL_UnlockMutex(task->asyncio->lock);
232
233 // was this the result of a closing task? Finally destroy the asyncio.
234 bool retval = true;
235 if (closing && (task == closing)) {
236 if (asyncio->oneshot) {
237 retval = false; // don't send the close task results on to the app, just the read task for these.
238 }
239 asyncio->iface.destroy(asyncio->userdata);
240 SDL_DestroyMutex(asyncio->lock);
241 SDL_free(asyncio);
242 }
243
244 SDL_AddAtomicInt(&task->queue->tasks_inflight, -1);
245 SDL_free(task);
246
247 return retval;
248}
249
250bool SDL_GetAsyncIOResult(SDL_AsyncIOQueue *queue, SDL_AsyncIOOutcome *outcome)
251{
252 if (!queue || !outcome) {
253 return false;
254 }
255 return GetAsyncIOTaskOutcome(queue->iface.get_results(queue->userdata), outcome);
256}
257
258bool SDL_WaitAsyncIOResult(SDL_AsyncIOQueue *queue, SDL_AsyncIOOutcome *outcome, Sint32 timeoutMS)
259{
260 if (!queue || !outcome) {
261 return false;
262 }
263 return GetAsyncIOTaskOutcome(queue->iface.wait_results(queue->userdata, timeoutMS), outcome);
264}
265
266void SDL_SignalAsyncIOQueue(SDL_AsyncIOQueue *queue)
267{
268 if (queue) {
269 queue->iface.signal(queue->userdata);
270 }
271}
272
273void SDL_DestroyAsyncIOQueue(SDL_AsyncIOQueue *queue)
274{
275 if (queue) {
276 // block until any pending tasks complete.
277 while (SDL_GetAtomicInt(&queue->tasks_inflight) > 0) {
278 SDL_AsyncIOTask *task = queue->iface.wait_results(queue->userdata, -1);
279 if (task) {
280 if (task->asyncio->oneshot) {
281 SDL_free(task->buffer); // throw away the buffer from SDL_LoadFileAsync that will never be consumed/freed by app.
282 task->buffer = NULL;
283 }
284 SDL_AsyncIOOutcome outcome;
285 GetAsyncIOTaskOutcome(task, &outcome); // this frees the task, and does other upkeep.
286 }
287 }
288
289 queue->iface.destroy(queue->userdata);
290 SDL_free(queue);
291 }
292}
293
294void SDL_QuitAsyncIO(void)
295{
296 SDL_SYS_QuitAsyncIO();
297}
298
299bool SDL_LoadFileAsync(const char *file, SDL_AsyncIOQueue *queue, void *userdata)
300{
301 if (!file) {
302 return SDL_InvalidParamError("file");
303 } else if (!queue) {
304 return SDL_InvalidParamError("queue");
305 }
306
307 bool retval = false;
308 SDL_AsyncIO *asyncio = SDL_AsyncIOFromFile(file, "r");
309 if (asyncio) {
310 asyncio->oneshot = true;
311
312 void *ptr = NULL;
313 const Sint64 flen = SDL_GetAsyncIOSize(asyncio);
314 if (flen >= 0) {
315 // !!! FIXME: check if flen > address space, since it'll truncate and we'll just end up with an incomplete buffer or a crash.
316 ptr = SDL_malloc((size_t) (flen + 1)); // over-allocate by one so we can add a null-terminator.
317 if (ptr) {
318 retval = SDL_ReadAsyncIO(asyncio, ptr, 0, (Uint64) flen, queue, userdata);
319 if (!retval) {
320 SDL_free(ptr);
321 }
322 }
323 }
324
325 SDL_CloseAsyncIO(asyncio, false, queue, userdata); // if this fails, we'll have a resource leak, but this would already be a dramatic system failure.
326 }
327
328 return retval;
329}
330
diff --git a/contrib/SDL-3.2.8/src/io/SDL_asyncio_c.h b/contrib/SDL-3.2.8/src/io/SDL_asyncio_c.h
new file mode 100644
index 0000000..861ba1a
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/io/SDL_asyncio_c.h
@@ -0,0 +1,30 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "../SDL_internal.h"
22
23#ifndef SDL_asyncio_c_h_
24#define SDL_asyncio_c_h_
25
26// Shutdown any still-existing Async I/O. Note that there is no Init function, as it inits on-demand!
27extern void SDL_QuitAsyncIO(void);
28
29#endif // SDL_asyncio_c_h_
30
diff --git a/contrib/SDL-3.2.8/src/io/SDL_iostream.c b/contrib/SDL-3.2.8/src/io/SDL_iostream.c
new file mode 100644
index 0000000..989f3b9
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/io/SDL_iostream.c
@@ -0,0 +1,1657 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#if defined(SDL_PLATFORM_WINDOWS)
24#include "../core/windows/SDL_windows.h"
25#else
26#include <unistd.h>
27#endif
28
29#ifdef HAVE_STDIO_H
30#include <stdio.h>
31#include <errno.h>
32#include <sys/stat.h>
33#endif
34#ifdef HAVE_LIMITS_H
35#include <limits.h>
36#endif
37
38#ifdef SDL_PLATFORM_APPLE
39#include <fcntl.h>
40#endif
41
42#include "SDL_iostream_c.h"
43
44/* This file provides a general interface for SDL to read and write
45 data sources. It can easily be extended to files, memory, etc.
46*/
47
48struct SDL_IOStream
49{
50 SDL_IOStreamInterface iface;
51 void *userdata;
52 SDL_IOStatus status;
53 SDL_PropertiesID props;
54};
55
56#ifdef SDL_PLATFORM_3DS
57#include "n3ds/SDL_iostreamromfs.h"
58#endif // SDL_PLATFORM_3DS
59
60#ifdef SDL_PLATFORM_ANDROID
61#include <unistd.h>
62#include "../core/android/SDL_android.h"
63#endif
64
65#if defined(SDL_PLATFORM_WINDOWS)
66
67typedef struct IOStreamWindowsData
68{
69 HANDLE h;
70 void *data;
71 size_t size;
72 size_t left;
73 bool append;
74 bool autoclose;
75} IOStreamWindowsData;
76
77
78// Functions to read/write Win32 API file pointers
79#ifndef INVALID_SET_FILE_POINTER
80#define INVALID_SET_FILE_POINTER 0xFFFFFFFF
81#endif
82
83#define READAHEAD_BUFFER_SIZE 1024
84
85static HANDLE SDLCALL windows_file_open(const char *filename, const char *mode)
86{
87#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
88 UINT old_error_mode;
89#endif
90 HANDLE h;
91 DWORD r_right, w_right;
92 DWORD must_exist, truncate;
93 int a_mode;
94
95 // "r" = reading, file must exist
96 // "w" = writing, truncate existing, file may not exist
97 // "r+"= reading or writing, file must exist
98 // "a" = writing, append file may not exist
99 // "a+"= append + read, file may not exist
100 // "w+" = read, write, truncate. file may not exist
101
102 must_exist = (SDL_strchr(mode, 'r') != NULL) ? OPEN_EXISTING : 0;
103 truncate = (SDL_strchr(mode, 'w') != NULL) ? CREATE_ALWAYS : 0;
104 r_right = (SDL_strchr(mode, '+') != NULL || must_exist) ? GENERIC_READ : 0;
105 a_mode = (SDL_strchr(mode, 'a') != NULL) ? OPEN_ALWAYS : 0;
106 w_right = (a_mode || SDL_strchr(mode, '+') || truncate) ? GENERIC_WRITE : 0;
107
108 if (!r_right && !w_right) {
109 return INVALID_HANDLE_VALUE; // inconsistent mode
110 }
111 // failed (invalid call)
112
113#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
114 // Do not open a dialog box if failure
115 old_error_mode = SetErrorMode(SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS);
116#endif
117
118 {
119 LPWSTR str = WIN_UTF8ToStringW(filename);
120 h = CreateFileW(str,
121 (w_right | r_right),
122 (w_right) ? 0 : FILE_SHARE_READ,
123 NULL,
124 (must_exist | truncate | a_mode),
125 FILE_ATTRIBUTE_NORMAL,
126 NULL);
127 SDL_free(str);
128 }
129
130#if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
131 // restore old behavior
132 SetErrorMode(old_error_mode);
133#endif
134
135 if (h == INVALID_HANDLE_VALUE) {
136 char *error;
137 if (SDL_asprintf(&error, "Couldn't open %s", filename) > 0) {
138 WIN_SetError(error);
139 SDL_free(error);
140 } else {
141 SDL_SetError("Couldn't open %s", filename);
142 }
143 }
144 return h;
145}
146
147static Sint64 SDLCALL windows_file_size(void *userdata)
148{
149 IOStreamWindowsData *iodata = (IOStreamWindowsData *) userdata;
150 LARGE_INTEGER size;
151
152 if (!GetFileSizeEx(iodata->h, &size)) {
153 return WIN_SetError("windows_file_size");
154 }
155
156 return size.QuadPart;
157}
158
159static Sint64 SDLCALL windows_file_seek(void *userdata, Sint64 offset, SDL_IOWhence whence)
160{
161 IOStreamWindowsData *iodata = (IOStreamWindowsData *) userdata;
162 DWORD windowswhence;
163 LARGE_INTEGER windowsoffset;
164
165 // FIXME: We may be able to satisfy the seek within buffered data
166 if ((whence == SDL_IO_SEEK_CUR) && (iodata->left)) {
167 offset -= iodata->left;
168 }
169 iodata->left = 0;
170
171 switch (whence) {
172 case SDL_IO_SEEK_SET:
173 windowswhence = FILE_BEGIN;
174 break;
175 case SDL_IO_SEEK_CUR:
176 windowswhence = FILE_CURRENT;
177 break;
178 case SDL_IO_SEEK_END:
179 windowswhence = FILE_END;
180 break;
181 default:
182 SDL_SetError("windows_file_seek: Unknown value for 'whence'");
183 return -1;
184 }
185
186 windowsoffset.QuadPart = offset;
187 if (!SetFilePointerEx(iodata->h, windowsoffset, &windowsoffset, windowswhence)) {
188 return WIN_SetError("Error seeking in datastream");
189 }
190 return windowsoffset.QuadPart;
191}
192
193static size_t SDLCALL windows_file_read(void *userdata, void *ptr, size_t size, SDL_IOStatus *status)
194{
195 IOStreamWindowsData *iodata = (IOStreamWindowsData *) userdata;
196 size_t total_need = size;
197 size_t total_read = 0;
198 size_t read_ahead;
199 DWORD bytes;
200
201 if (iodata->left > 0) {
202 void *data = (char *)iodata->data +
203 iodata->size -
204 iodata->left;
205 read_ahead = SDL_min(total_need, iodata->left);
206 SDL_memcpy(ptr, data, read_ahead);
207 iodata->left -= read_ahead;
208
209 if (read_ahead == total_need) {
210 return size;
211 }
212 ptr = (char *)ptr + read_ahead;
213 total_need -= read_ahead;
214 total_read += read_ahead;
215 }
216
217 if (total_need < READAHEAD_BUFFER_SIZE) {
218 if (!ReadFile(iodata->h, iodata->data, READAHEAD_BUFFER_SIZE, &bytes, NULL)) {
219 DWORD error = GetLastError();
220 switch (error) {
221 case ERROR_BROKEN_PIPE:
222 case ERROR_HANDLE_EOF:
223 break;
224 case ERROR_NO_DATA:
225 *status = SDL_IO_STATUS_NOT_READY;
226 break;
227 default:
228 WIN_SetError("Error reading from datastream");
229 break;
230 }
231 return 0;
232 }
233 read_ahead = SDL_min(total_need, bytes);
234 SDL_memcpy(ptr, iodata->data, read_ahead);
235 iodata->size = bytes;
236 iodata->left = bytes - read_ahead;
237 total_read += read_ahead;
238 } else {
239 if (!ReadFile(iodata->h, ptr, (DWORD)total_need, &bytes, NULL)) {
240 DWORD error = GetLastError();
241 switch (error) {
242 case ERROR_BROKEN_PIPE:
243 case ERROR_HANDLE_EOF:
244 break;
245 case ERROR_NO_DATA:
246 *status = SDL_IO_STATUS_NOT_READY;
247 break;
248 default:
249 WIN_SetError("Error reading from datastream");
250 break;
251 }
252 return 0;
253 }
254 total_read += bytes;
255 }
256 return total_read;
257}
258
259static size_t SDLCALL windows_file_write(void *userdata, const void *ptr, size_t size, SDL_IOStatus *status)
260{
261 IOStreamWindowsData *iodata = (IOStreamWindowsData *) userdata;
262 DWORD bytes;
263
264 if (iodata->left) {
265 if (!SetFilePointer(iodata->h, -(LONG)iodata->left, NULL, FILE_CURRENT)) {
266 WIN_SetError("Error seeking in datastream");
267 return 0;
268 }
269 iodata->left = 0;
270 }
271
272 // if in append mode, we must go to the EOF before write
273 if (iodata->append) {
274 LARGE_INTEGER windowsoffset;
275 windowsoffset.QuadPart = 0;
276 if (!SetFilePointerEx(iodata->h, windowsoffset, &windowsoffset, FILE_END)) {
277 WIN_SetError("Error seeking in datastream");
278 return 0;
279 }
280 }
281
282 if (!WriteFile(iodata->h, ptr, (DWORD)size, &bytes, NULL)) {
283 WIN_SetError("Error writing to datastream");
284 return 0;
285 }
286 if (bytes == 0 && size > 0) {
287 *status = SDL_IO_STATUS_NOT_READY;
288 }
289 return bytes;
290}
291
292static bool SDLCALL windows_file_flush(void *userdata, SDL_IOStatus *status)
293{
294 IOStreamWindowsData *iodata = (IOStreamWindowsData *) userdata;
295 if (!FlushFileBuffers(iodata->h)) {
296 return WIN_SetError("Error flushing datastream");
297 }
298 return true;
299}
300
301static bool SDLCALL windows_file_close(void *userdata)
302{
303 IOStreamWindowsData *iodata = (IOStreamWindowsData *) userdata;
304 if (iodata->h != INVALID_HANDLE_VALUE) {
305 if (iodata->autoclose) {
306 CloseHandle(iodata->h);
307 }
308 iodata->h = INVALID_HANDLE_VALUE; // to be sure
309 }
310 SDL_free(iodata->data);
311 SDL_free(iodata);
312 return true;
313}
314
315SDL_IOStream *SDL_IOFromHandle(HANDLE handle, const char *mode, bool autoclose)
316{
317 IOStreamWindowsData *iodata = (IOStreamWindowsData *) SDL_calloc(1, sizeof (*iodata));
318 if (!iodata) {
319 if (autoclose) {
320 CloseHandle(handle);
321 }
322 return NULL;
323 }
324
325 SDL_IOStreamInterface iface;
326 SDL_INIT_INTERFACE(&iface);
327 if (GetFileType(handle) == FILE_TYPE_DISK) {
328 iface.size = windows_file_size;
329 iface.seek = windows_file_seek;
330 }
331 iface.read = windows_file_read;
332 iface.write = windows_file_write;
333 iface.flush = windows_file_flush;
334 iface.close = windows_file_close;
335
336 iodata->h = handle;
337 iodata->append = (SDL_strchr(mode, 'a') != NULL);
338 iodata->autoclose = autoclose;
339
340 iodata->data = (char *)SDL_malloc(READAHEAD_BUFFER_SIZE);
341 if (!iodata->data) {
342 iface.close(iodata);
343 return NULL;
344 }
345
346 SDL_IOStream *iostr = SDL_OpenIO(&iface, iodata);
347 if (!iostr) {
348 iface.close(iodata);
349 } else {
350 const SDL_PropertiesID props = SDL_GetIOProperties(iostr);
351 if (props) {
352 SDL_SetPointerProperty(props, SDL_PROP_IOSTREAM_WINDOWS_HANDLE_POINTER, iodata->h);
353 }
354 }
355
356 return iostr;
357}
358#endif // defined(SDL_PLATFORM_WINDOWS)
359
360#if !defined(SDL_PLATFORM_WINDOWS)
361
362// Functions to read/write file descriptors. Not used for windows.
363
364typedef struct IOStreamFDData
365{
366 int fd;
367 bool autoclose;
368 bool regular_file;
369} IOStreamFDData;
370
371static int SDL_fdatasync(int fd)
372{
373 int result = 0;
374
375#if defined(SDL_PLATFORM_APPLE) // Apple doesn't have fdatasync (rather, the symbol exists as an incompatible system call).
376 result = fcntl(fd, F_FULLFSYNC);
377#elif defined(SDL_PLATFORM_HAIKU)
378 result = fsync(fd);
379#elif defined(HAVE_FDATASYNC)
380 result = fdatasync(fd);
381#endif
382 return result;
383}
384
385static Sint64 SDLCALL fd_seek(void *userdata, Sint64 offset, SDL_IOWhence whence)
386{
387 IOStreamFDData *iodata = (IOStreamFDData *) userdata;
388 int fdwhence;
389
390 switch (whence) {
391 case SDL_IO_SEEK_SET:
392 fdwhence = SEEK_SET;
393 break;
394 case SDL_IO_SEEK_CUR:
395 fdwhence = SEEK_CUR;
396 break;
397 case SDL_IO_SEEK_END:
398 fdwhence = SEEK_END;
399 break;
400 default:
401 SDL_SetError("Unknown value for 'whence'");
402 return -1;
403 }
404
405 off_t result = lseek(iodata->fd, (off_t)offset, fdwhence);
406 if (result < 0) {
407 SDL_SetError("Couldn't get stream offset: %s", strerror(errno));
408 }
409 return result;
410}
411
412static size_t SDLCALL fd_read(void *userdata, void *ptr, size_t size, SDL_IOStatus *status)
413{
414 IOStreamFDData *iodata = (IOStreamFDData *) userdata;
415 ssize_t bytes;
416 do {
417 bytes = read(iodata->fd, ptr, size);
418 } while (bytes < 0 && errno == EINTR);
419
420 if (bytes < 0) {
421 if (errno == EAGAIN) {
422 *status = SDL_IO_STATUS_NOT_READY;
423 } else {
424 SDL_SetError("Error reading from datastream: %s", strerror(errno));
425 }
426 bytes = 0;
427 }
428 return (size_t)bytes;
429}
430
431static size_t SDLCALL fd_write(void *userdata, const void *ptr, size_t size, SDL_IOStatus *status)
432{
433 IOStreamFDData *iodata = (IOStreamFDData *) userdata;
434 ssize_t bytes;
435 do {
436 bytes = write(iodata->fd, ptr, size);
437 } while (bytes < 0 && errno == EINTR);
438
439 if (bytes < 0) {
440 if (errno == EAGAIN) {
441 *status = SDL_IO_STATUS_NOT_READY;
442 } else {
443 SDL_SetError("Error writing to datastream: %s", strerror(errno));
444 }
445 bytes = 0;
446 }
447 return (size_t)bytes;
448}
449
450static bool SDLCALL fd_flush(void *userdata, SDL_IOStatus *status)
451{
452 IOStreamFDData *iodata = (IOStreamFDData *) userdata;
453 int result;
454 do {
455 result = SDL_fdatasync(iodata->fd);
456 } while (result < 0 && errno == EINTR);
457
458 if (result < 0) {
459 return SDL_SetError("Error flushing datastream: %s", strerror(errno));
460 }
461 return true;
462}
463
464static bool SDLCALL fd_close(void *userdata)
465{
466 IOStreamFDData *iodata = (IOStreamFDData *) userdata;
467 bool status = true;
468 if (iodata->autoclose) {
469 if (close(iodata->fd) < 0) {
470 status = SDL_SetError("Error closing datastream: %s", strerror(errno));
471 }
472 }
473 SDL_free(iodata);
474 return status;
475}
476
477SDL_IOStream *SDL_IOFromFD(int fd, bool autoclose)
478{
479 IOStreamFDData *iodata = (IOStreamFDData *) SDL_calloc(1, sizeof (*iodata));
480 if (!iodata) {
481 if (autoclose) {
482 close(fd);
483 }
484 return NULL;
485 }
486
487 SDL_IOStreamInterface iface;
488 SDL_INIT_INTERFACE(&iface);
489 // There's no fd_size because SDL_GetIOSize emulates it the same way we'd do it for fd anyhow.
490 iface.seek = fd_seek;
491 iface.read = fd_read;
492 iface.write = fd_write;
493 iface.flush = fd_flush;
494 iface.close = fd_close;
495
496 iodata->fd = fd;
497 iodata->autoclose = autoclose;
498
499 struct stat st;
500 iodata->regular_file = ((fstat(fd, &st) == 0) && S_ISREG(st.st_mode));
501
502 SDL_IOStream *iostr = SDL_OpenIO(&iface, iodata);
503 if (!iostr) {
504 iface.close(iodata);
505 } else {
506 const SDL_PropertiesID props = SDL_GetIOProperties(iostr);
507 if (props) {
508 SDL_SetNumberProperty(props, SDL_PROP_IOSTREAM_FILE_DESCRIPTOR_NUMBER, fd);
509 }
510 }
511
512 return iostr;
513}
514#endif // !defined(SDL_PLATFORM_WINDOWS)
515
516#if defined(HAVE_STDIO_H) && !defined(SDL_PLATFORM_WINDOWS)
517
518// Functions to read/write stdio file pointers. Not used for windows.
519
520typedef struct IOStreamStdioData
521{
522 FILE *fp;
523 bool autoclose;
524 bool regular_file;
525} IOStreamStdioData;
526
527#ifdef HAVE_FOPEN64
528#define fopen fopen64
529#endif
530#ifdef HAVE_FSEEKO64
531#define fseek_off_t off64_t
532#define fseek fseeko64
533#define ftell ftello64
534#elif defined(HAVE_FSEEKO)
535#if defined(OFF_MIN) && defined(OFF_MAX)
536#define FSEEK_OFF_MIN OFF_MIN
537#define FSEEK_OFF_MAX OFF_MAX
538#elif defined(HAVE_LIMITS_H)
539/* POSIX doesn't specify the minimum and maximum macros for off_t so
540 * we have to improvise and dance around implementation-defined
541 * behavior. This may fail if the off_t type has padding bits or
542 * is not a two's-complement representation. The compilers will detect
543 * and eliminate the dead code if off_t has 64 bits.
544 */
545#define FSEEK_OFF_MAX (((((off_t)1 << (sizeof(off_t) * CHAR_BIT - 2)) - 1) << 1) + 1)
546#define FSEEK_OFF_MIN (-(FSEEK_OFF_MAX)-1)
547#endif
548#define fseek_off_t off_t
549#define fseek fseeko
550#define ftell ftello
551#elif defined(HAVE__FSEEKI64)
552#define fseek_off_t __int64
553#define fseek _fseeki64
554#define ftell _ftelli64
555#else
556#ifdef HAVE_LIMITS_H
557#define FSEEK_OFF_MIN LONG_MIN
558#define FSEEK_OFF_MAX LONG_MAX
559#endif
560#define fseek_off_t long
561#endif
562
563static Sint64 SDLCALL stdio_seek(void *userdata, Sint64 offset, SDL_IOWhence whence)
564{
565 IOStreamStdioData *iodata = (IOStreamStdioData *) userdata;
566 int stdiowhence;
567
568 switch (whence) {
569 case SDL_IO_SEEK_SET:
570 stdiowhence = SEEK_SET;
571 break;
572 case SDL_IO_SEEK_CUR:
573 stdiowhence = SEEK_CUR;
574 break;
575 case SDL_IO_SEEK_END:
576 stdiowhence = SEEK_END;
577 break;
578 default:
579 SDL_SetError("Unknown value for 'whence'");
580 return -1;
581 }
582
583#if defined(FSEEK_OFF_MIN) && defined(FSEEK_OFF_MAX)
584 if (offset < (Sint64)(FSEEK_OFF_MIN) || offset > (Sint64)(FSEEK_OFF_MAX)) {
585 SDL_SetError("Seek offset out of range");
586 return -1;
587 }
588#endif
589
590 // don't make a possibly-costly API call for the noop seek from SDL_TellIO
591 const bool is_noop = (whence == SDL_IO_SEEK_CUR) && (offset == 0);
592
593 if (is_noop || fseek(iodata->fp, (fseek_off_t)offset, stdiowhence) == 0) {
594 const Sint64 pos = ftell(iodata->fp);
595 if (pos < 0) {
596 SDL_SetError("Couldn't get stream offset: %s", strerror(errno));
597 return -1;
598 }
599 return pos;
600 }
601 SDL_SetError("Error seeking in datastream: %s", strerror(errno));
602 return -1;
603}
604
605static size_t SDLCALL stdio_read(void *userdata, void *ptr, size_t size, SDL_IOStatus *status)
606{
607 IOStreamStdioData *iodata = (IOStreamStdioData *) userdata;
608 const size_t bytes = fread(ptr, 1, size, iodata->fp);
609 if (bytes == 0 && ferror(iodata->fp)) {
610 if (errno == EAGAIN) {
611 *status = SDL_IO_STATUS_NOT_READY;
612 clearerr(iodata->fp);
613 } else {
614 SDL_SetError("Error reading from datastream: %s", strerror(errno));
615 }
616 }
617 return bytes;
618}
619
620static size_t SDLCALL stdio_write(void *userdata, const void *ptr, size_t size, SDL_IOStatus *status)
621{
622 IOStreamStdioData *iodata = (IOStreamStdioData *) userdata;
623 const size_t bytes = fwrite(ptr, 1, size, iodata->fp);
624 if (bytes == 0 && ferror(iodata->fp)) {
625 if (errno == EAGAIN) {
626 *status = SDL_IO_STATUS_NOT_READY;
627 clearerr(iodata->fp);
628 } else {
629 SDL_SetError("Error writing to datastream: %s", strerror(errno));
630 }
631 }
632 return bytes;
633}
634
635static bool SDLCALL stdio_flush(void *userdata, SDL_IOStatus *status)
636{
637 IOStreamStdioData *iodata = (IOStreamStdioData *) userdata;
638 if (fflush(iodata->fp) != 0) {
639 if (errno == EAGAIN) {
640 *status = SDL_IO_STATUS_NOT_READY;
641 return false;
642 } else {
643 return SDL_SetError("Error flushing datastream: %s", strerror(errno));
644 }
645 }
646
647 int result;
648 int fd = fileno(iodata->fp);
649 do {
650 result = SDL_fdatasync(fd);
651 } while (result < 0 && errno == EINTR);
652
653 if (result < 0) {
654 return SDL_SetError("Error flushing datastream: %s", strerror(errno));
655 }
656 return true;
657}
658
659static bool SDLCALL stdio_close(void *userdata)
660{
661 IOStreamStdioData *iodata = (IOStreamStdioData *) userdata;
662 bool status = true;
663 if (iodata->autoclose) {
664 if (fclose(iodata->fp) != 0) {
665 status = SDL_SetError("Error closing datastream: %s", strerror(errno));
666 }
667 }
668 SDL_free(iodata);
669 return status;
670}
671
672SDL_IOStream *SDL_IOFromFP(FILE *fp, bool autoclose)
673{
674 IOStreamStdioData *iodata = (IOStreamStdioData *) SDL_calloc(1, sizeof (*iodata));
675 if (!iodata) {
676 if (autoclose) {
677 fclose(fp);
678 }
679 return NULL;
680 }
681
682 SDL_IOStreamInterface iface;
683 SDL_INIT_INTERFACE(&iface);
684 // There's no stdio_size because SDL_GetIOSize emulates it the same way we'd do it for stdio anyhow.
685 iface.seek = stdio_seek;
686 iface.read = stdio_read;
687 iface.write = stdio_write;
688 iface.flush = stdio_flush;
689 iface.close = stdio_close;
690
691 iodata->fp = fp;
692 iodata->autoclose = autoclose;
693
694 struct stat st;
695 iodata->regular_file = ((fstat(fileno(fp), &st) == 0) && S_ISREG(st.st_mode));
696
697 SDL_IOStream *iostr = SDL_OpenIO(&iface, iodata);
698 if (!iostr) {
699 iface.close(iodata);
700 } else {
701 const SDL_PropertiesID props = SDL_GetIOProperties(iostr);
702 if (props) {
703 SDL_SetPointerProperty(props, SDL_PROP_IOSTREAM_STDIO_FILE_POINTER, fp);
704 SDL_SetNumberProperty(props, SDL_PROP_IOSTREAM_FILE_DESCRIPTOR_NUMBER, fileno(fp));
705 }
706 }
707
708 return iostr;
709}
710#endif // !HAVE_STDIO_H && !defined(SDL_PLATFORM_WINDOWS)
711
712// Functions to read/write memory pointers
713
714typedef struct IOStreamMemData
715{
716 Uint8 *base;
717 Uint8 *here;
718 Uint8 *stop;
719} IOStreamMemData;
720
721static Sint64 SDLCALL mem_size(void *userdata)
722{
723 const IOStreamMemData *iodata = (IOStreamMemData *) userdata;
724 return (iodata->stop - iodata->base);
725}
726
727static Sint64 SDLCALL mem_seek(void *userdata, Sint64 offset, SDL_IOWhence whence)
728{
729 IOStreamMemData *iodata = (IOStreamMemData *) userdata;
730 Uint8 *newpos;
731
732 switch (whence) {
733 case SDL_IO_SEEK_SET:
734 newpos = iodata->base + offset;
735 break;
736 case SDL_IO_SEEK_CUR:
737 newpos = iodata->here + offset;
738 break;
739 case SDL_IO_SEEK_END:
740 newpos = iodata->stop + offset;
741 break;
742 default:
743 SDL_SetError("Unknown value for 'whence'");
744 return -1;
745 }
746 if (newpos < iodata->base) {
747 newpos = iodata->base;
748 }
749 if (newpos > iodata->stop) {
750 newpos = iodata->stop;
751 }
752 iodata->here = newpos;
753 return (Sint64)(iodata->here - iodata->base);
754}
755
756static size_t mem_io(void *userdata, void *dst, const void *src, size_t size)
757{
758 IOStreamMemData *iodata = (IOStreamMemData *) userdata;
759 const size_t mem_available = (iodata->stop - iodata->here);
760 if (size > mem_available) {
761 size = mem_available;
762 }
763 SDL_memcpy(dst, src, size);
764 iodata->here += size;
765 return size;
766}
767
768static size_t SDLCALL mem_read(void *userdata, void *ptr, size_t size, SDL_IOStatus *status)
769{
770 IOStreamMemData *iodata = (IOStreamMemData *) userdata;
771 return mem_io(userdata, ptr, iodata->here, size);
772}
773
774static size_t SDLCALL mem_write(void *userdata, const void *ptr, size_t size, SDL_IOStatus *status)
775{
776 IOStreamMemData *iodata = (IOStreamMemData *) userdata;
777 return mem_io(userdata, iodata->here, ptr, size);
778}
779
780static bool SDLCALL mem_close(void *userdata)
781{
782 SDL_free(userdata);
783 return true;
784}
785
786// Functions to create SDL_IOStream structures from various data sources
787
788#if defined(HAVE_STDIO_H) && !defined(SDL_PLATFORM_WINDOWS)
789static bool IsRegularFileOrPipe(FILE *f)
790{
791#ifndef SDL_PLATFORM_EMSCRIPTEN
792 struct stat st;
793 if (fstat(fileno(f), &st) < 0 || !(S_ISREG(st.st_mode) || S_ISFIFO(st.st_mode))) {
794 return false;
795 }
796#endif // !SDL_PLATFORM_EMSCRIPTEN
797
798 return true;
799}
800#endif
801
802SDL_IOStream *SDL_IOFromFile(const char *file, const char *mode)
803{
804 SDL_IOStream *iostr = NULL;
805
806 if (!file || !*file) {
807 SDL_InvalidParamError("file");
808 return NULL;
809 }
810 if (!mode || !*mode) {
811 SDL_InvalidParamError("mode");
812 return NULL;
813 }
814
815#ifdef SDL_PLATFORM_ANDROID
816#ifdef HAVE_STDIO_H
817 // Try to open the file on the filesystem first
818 if (*file == '/') {
819 FILE *fp = fopen(file, mode);
820 if (fp) {
821 if (!IsRegularFileOrPipe(fp)) {
822 fclose(fp);
823 SDL_SetError("%s is not a regular file or pipe", file);
824 return NULL;
825 }
826 return SDL_IOFromFP(fp, true);
827 }
828 } else if (SDL_strncmp(file, "content://", 10) == 0) {
829 // Try opening content:// URI
830 int fd = Android_JNI_OpenFileDescriptor(file, mode);
831 if (fd == -1) {
832 // SDL error is already set.
833 return NULL;
834 }
835
836 FILE *fp = fdopen(fd, mode);
837 if (!fp) {
838 close(fd);
839 SDL_SetError("Unable to open file descriptor (%d) from URI %s: %s", fd, file, strerror(errno));
840 return NULL;
841 }
842
843 return SDL_IOFromFP(fp, true);
844 } else {
845 // Try opening it from internal storage if it's a relative path
846 char *path = NULL;
847 SDL_asprintf(&path, "%s/%s", SDL_GetAndroidInternalStoragePath(), file);
848 if (path) {
849 FILE *fp = fopen(path, mode);
850 SDL_free(path);
851 if (fp) {
852 if (!IsRegularFileOrPipe(fp)) {
853 fclose(fp);
854 SDL_SetError("%s is not a regular file or pipe", path);
855 return NULL;
856 }
857 return SDL_IOFromFP(fp, true);
858 }
859 }
860 }
861#endif // HAVE_STDIO_H
862
863 // Try to open the file from the asset system
864
865 void *iodata = NULL;
866 if (!Android_JNI_FileOpen(&iodata, file, mode)) {
867 return NULL;
868 }
869
870 SDL_IOStreamInterface iface;
871 SDL_INIT_INTERFACE(&iface);
872 iface.size = Android_JNI_FileSize;
873 iface.seek = Android_JNI_FileSeek;
874 iface.read = Android_JNI_FileRead;
875 iface.write = Android_JNI_FileWrite;
876 iface.close = Android_JNI_FileClose;
877
878 iostr = SDL_OpenIO(&iface, iodata);
879 if (!iostr) {
880 iface.close(iodata);
881 } else {
882 const SDL_PropertiesID props = SDL_GetIOProperties(iostr);
883 if (props) {
884 SDL_SetPointerProperty(props, SDL_PROP_IOSTREAM_ANDROID_AASSET_POINTER, iodata);
885 }
886 }
887
888#elif defined(SDL_PLATFORM_WINDOWS)
889 HANDLE handle = windows_file_open(file, mode);
890 if (handle != INVALID_HANDLE_VALUE) {
891 iostr = SDL_IOFromHandle(handle, mode, true);
892 }
893
894#elif defined(HAVE_STDIO_H)
895 {
896 #if defined(SDL_PLATFORM_3DS)
897 FILE *fp = N3DS_FileOpen(file, mode);
898 #else
899 FILE *fp = fopen(file, mode);
900 #endif
901
902 if (!fp) {
903 SDL_SetError("Couldn't open %s: %s", file, strerror(errno));
904 } else if (!IsRegularFileOrPipe(fp)) {
905 fclose(fp);
906 fp = NULL;
907 SDL_SetError("%s is not a regular file or pipe", file);
908 } else {
909 iostr = SDL_IOFromFP(fp, true);
910 }
911 }
912
913#else
914 SDL_SetError("SDL not compiled with stdio support");
915#endif // !HAVE_STDIO_H
916
917 return iostr;
918}
919
920SDL_IOStream *SDL_IOFromMem(void *mem, size_t size)
921{
922 if (!mem) {
923 SDL_InvalidParamError("mem");
924 return NULL;
925 } else if (!size) {
926 SDL_InvalidParamError("size");
927 return NULL;
928 }
929
930 IOStreamMemData *iodata = (IOStreamMemData *) SDL_calloc(1, sizeof (*iodata));
931 if (!iodata) {
932 return NULL;
933 }
934
935 SDL_IOStreamInterface iface;
936 SDL_INIT_INTERFACE(&iface);
937 iface.size = mem_size;
938 iface.seek = mem_seek;
939 iface.read = mem_read;
940 iface.write = mem_write;
941 iface.close = mem_close;
942
943 iodata->base = (Uint8 *)mem;
944 iodata->here = iodata->base;
945 iodata->stop = iodata->base + size;
946
947 SDL_IOStream *iostr = SDL_OpenIO(&iface, iodata);
948 if (!iostr) {
949 SDL_free(iodata);
950 } else {
951 const SDL_PropertiesID props = SDL_GetIOProperties(iostr);
952 if (props) {
953 SDL_SetPointerProperty(props, SDL_PROP_IOSTREAM_MEMORY_POINTER, mem);
954 SDL_SetNumberProperty(props, SDL_PROP_IOSTREAM_MEMORY_SIZE_NUMBER, size);
955 }
956 }
957 return iostr;
958}
959
960SDL_IOStream *SDL_IOFromConstMem(const void *mem, size_t size)
961{
962 if (!mem) {
963 SDL_InvalidParamError("mem");
964 return NULL;
965 } else if (!size) {
966 SDL_InvalidParamError("size");
967 return NULL;
968 }
969
970 IOStreamMemData *iodata = (IOStreamMemData *) SDL_calloc(1, sizeof (*iodata));
971 if (!iodata) {
972 return NULL;
973 }
974
975 SDL_IOStreamInterface iface;
976 SDL_INIT_INTERFACE(&iface);
977 iface.size = mem_size;
978 iface.seek = mem_seek;
979 iface.read = mem_read;
980 // leave iface.write as NULL.
981 iface.close = mem_close;
982
983 iodata->base = (Uint8 *)mem;
984 iodata->here = iodata->base;
985 iodata->stop = iodata->base + size;
986
987 SDL_IOStream *iostr = SDL_OpenIO(&iface, iodata);
988 if (!iostr) {
989 SDL_free(iodata);
990 } else {
991 const SDL_PropertiesID props = SDL_GetIOProperties(iostr);
992 if (props) {
993 SDL_SetPointerProperty(props, SDL_PROP_IOSTREAM_MEMORY_POINTER, (void *)mem);
994 SDL_SetNumberProperty(props, SDL_PROP_IOSTREAM_MEMORY_SIZE_NUMBER, size);
995 }
996 }
997 return iostr;
998}
999
1000typedef struct IOStreamDynamicMemData
1001{
1002 SDL_IOStream *stream;
1003 IOStreamMemData data;
1004 Uint8 *end;
1005} IOStreamDynamicMemData;
1006
1007static Sint64 SDLCALL dynamic_mem_size(void *userdata)
1008{
1009 IOStreamDynamicMemData *iodata = (IOStreamDynamicMemData *) userdata;
1010 return mem_size(&iodata->data);
1011}
1012
1013static Sint64 SDLCALL dynamic_mem_seek(void *userdata, Sint64 offset, SDL_IOWhence whence)
1014{
1015 IOStreamDynamicMemData *iodata = (IOStreamDynamicMemData *) userdata;
1016 return mem_seek(&iodata->data, offset, whence);
1017}
1018
1019static size_t SDLCALL dynamic_mem_read(void *userdata, void *ptr, size_t size, SDL_IOStatus *status)
1020{
1021 IOStreamDynamicMemData *iodata = (IOStreamDynamicMemData *) userdata;
1022 return mem_io(&iodata->data, ptr, iodata->data.here, size);
1023}
1024
1025static bool dynamic_mem_realloc(IOStreamDynamicMemData *iodata, size_t size)
1026{
1027 size_t chunksize = (size_t)SDL_GetNumberProperty(SDL_GetIOProperties(iodata->stream), SDL_PROP_IOSTREAM_DYNAMIC_CHUNKSIZE_NUMBER, 0);
1028 if (!chunksize) {
1029 chunksize = 1024;
1030 }
1031
1032 // We're intentionally allocating more memory than needed so it can be null terminated
1033 size_t chunks = (((iodata->end - iodata->data.base) + size) / chunksize) + 1;
1034 size_t length = (chunks * chunksize);
1035 Uint8 *base = (Uint8 *)SDL_realloc(iodata->data.base, length);
1036 if (!base) {
1037 return false;
1038 }
1039
1040 size_t here_offset = (iodata->data.here - iodata->data.base);
1041 size_t stop_offset = (iodata->data.stop - iodata->data.base);
1042 iodata->data.base = base;
1043 iodata->data.here = base + here_offset;
1044 iodata->data.stop = base + stop_offset;
1045 iodata->end = base + length;
1046 return SDL_SetPointerProperty(SDL_GetIOProperties(iodata->stream), SDL_PROP_IOSTREAM_DYNAMIC_MEMORY_POINTER, base);
1047}
1048
1049static size_t SDLCALL dynamic_mem_write(void *userdata, const void *ptr, size_t size, SDL_IOStatus *status)
1050{
1051 IOStreamDynamicMemData *iodata = (IOStreamDynamicMemData *) userdata;
1052 if (size > (size_t)(iodata->data.stop - iodata->data.here)) {
1053 if (size > (size_t)(iodata->end - iodata->data.here)) {
1054 if (!dynamic_mem_realloc(iodata, size)) {
1055 return 0;
1056 }
1057 }
1058 iodata->data.stop = iodata->data.here + size;
1059 }
1060 return mem_io(&iodata->data, iodata->data.here, ptr, size);
1061}
1062
1063static bool SDLCALL dynamic_mem_close(void *userdata)
1064{
1065 const IOStreamDynamicMemData *iodata = (IOStreamDynamicMemData *) userdata;
1066 void *mem = SDL_GetPointerProperty(SDL_GetIOProperties(iodata->stream), SDL_PROP_IOSTREAM_DYNAMIC_MEMORY_POINTER, NULL);
1067 if (mem) {
1068 SDL_free(mem);
1069 }
1070 SDL_free(userdata);
1071 return true;
1072}
1073
1074SDL_IOStream *SDL_IOFromDynamicMem(void)
1075{
1076 IOStreamDynamicMemData *iodata = (IOStreamDynamicMemData *) SDL_calloc(1, sizeof (*iodata));
1077 if (!iodata) {
1078 return NULL;
1079 }
1080
1081 SDL_IOStreamInterface iface;
1082 SDL_INIT_INTERFACE(&iface);
1083 iface.size = dynamic_mem_size;
1084 iface.seek = dynamic_mem_seek;
1085 iface.read = dynamic_mem_read;
1086 iface.write = dynamic_mem_write;
1087 iface.close = dynamic_mem_close;
1088
1089 SDL_IOStream *iostr = SDL_OpenIO(&iface, iodata);
1090 if (iostr) {
1091 iodata->stream = iostr;
1092 } else {
1093 SDL_free(iodata);
1094 }
1095 return iostr;
1096}
1097
1098SDL_IOStatus SDL_GetIOStatus(SDL_IOStream *context)
1099{
1100 if (!context) {
1101 SDL_InvalidParamError("context");
1102 return SDL_IO_STATUS_ERROR;
1103 }
1104 return context->status;
1105}
1106
1107SDL_IOStream *SDL_OpenIO(const SDL_IOStreamInterface *iface, void *userdata)
1108{
1109 if (!iface) {
1110 SDL_InvalidParamError("iface");
1111 return NULL;
1112 }
1113 if (iface->version < sizeof(*iface)) {
1114 // Update this to handle older versions of this interface
1115 SDL_SetError("Invalid interface, should be initialized with SDL_INIT_INTERFACE()");
1116 return NULL;
1117 }
1118
1119 SDL_IOStream *iostr = (SDL_IOStream *)SDL_calloc(1, sizeof(*iostr));
1120 if (iostr) {
1121 SDL_copyp(&iostr->iface, iface);
1122 iostr->userdata = userdata;
1123 }
1124 return iostr;
1125}
1126
1127bool SDL_CloseIO(SDL_IOStream *iostr)
1128{
1129 bool result = true;
1130 if (iostr) {
1131 if (iostr->iface.close) {
1132 result = iostr->iface.close(iostr->userdata);
1133 }
1134 SDL_DestroyProperties(iostr->props);
1135 SDL_free(iostr);
1136 }
1137 return result;
1138}
1139
1140// Load all the data from an SDL data stream
1141void *SDL_LoadFile_IO(SDL_IOStream *src, size_t *datasize, bool closeio)
1142{
1143 const int FILE_CHUNK_SIZE = 1024;
1144 Sint64 size, size_total = 0;
1145 size_t size_read;
1146 char *data = NULL, *newdata;
1147 bool loading_chunks = false;
1148
1149 if (!src) {
1150 SDL_InvalidParamError("src");
1151 goto done;
1152 }
1153
1154 size = SDL_GetIOSize(src);
1155 if (size < 0) {
1156 size = FILE_CHUNK_SIZE;
1157 loading_chunks = true;
1158 }
1159 if (size >= SDL_SIZE_MAX - 1) {
1160 goto done;
1161 }
1162 data = (char *)SDL_malloc((size_t)(size + 1));
1163 if (!data) {
1164 goto done;
1165 }
1166
1167 size_total = 0;
1168 for (;;) {
1169 if (loading_chunks) {
1170 if ((size_total + FILE_CHUNK_SIZE) > size) {
1171 size = (size_total + FILE_CHUNK_SIZE);
1172 if (size >= SDL_SIZE_MAX - 1) {
1173 newdata = NULL;
1174 } else {
1175 newdata = (char *)SDL_realloc(data, (size_t)(size + 1));
1176 }
1177 if (!newdata) {
1178 SDL_free(data);
1179 data = NULL;
1180 goto done;
1181 }
1182 data = newdata;
1183 }
1184 }
1185
1186 size_read = SDL_ReadIO(src, data + size_total, (size_t)(size - size_total));
1187 if (size_read > 0) {
1188 size_total += size_read;
1189 continue;
1190 } else if (SDL_GetIOStatus(src) == SDL_IO_STATUS_NOT_READY) {
1191 // Wait for the stream to be ready
1192 SDL_Delay(1);
1193 continue;
1194 }
1195
1196 // The stream status will remain set for the caller to check
1197 break;
1198 }
1199
1200 data[size_total] = '\0';
1201
1202done:
1203 if (datasize) {
1204 *datasize = (size_t)size_total;
1205 }
1206 if (closeio && src) {
1207 SDL_CloseIO(src);
1208 }
1209 return data;
1210}
1211
1212void *SDL_LoadFile(const char *file, size_t *datasize)
1213{
1214 SDL_IOStream *stream = SDL_IOFromFile(file, "rb");
1215 if (!stream) {
1216 if (datasize) {
1217 *datasize = 0;
1218 }
1219 return NULL;
1220 }
1221 return SDL_LoadFile_IO(stream, datasize, true);
1222}
1223
1224bool SDL_SaveFile_IO(SDL_IOStream *src, const void *data, size_t datasize, bool closeio)
1225{
1226 size_t size_written = 0;
1227 size_t size_total = 0;
1228 bool success = true;
1229
1230 if (!src) {
1231 SDL_InvalidParamError("src");
1232 goto done;
1233 }
1234
1235 if (!data && datasize > 0) {
1236 SDL_InvalidParamError("data");
1237 goto done;
1238 }
1239
1240 if (datasize > 0) {
1241 while (size_total < datasize) {
1242 size_written = SDL_WriteIO(src, ((const char *) data) + size_written, datasize - size_written);
1243
1244 if (size_written <= 0) {
1245 if (SDL_GetIOStatus(src) == SDL_IO_STATUS_NOT_READY) {
1246 // Wait for the stream to be ready
1247 SDL_Delay(1);
1248 continue;
1249 } else {
1250 success = false;
1251 goto done;
1252 }
1253 }
1254
1255 size_total += size_written;
1256 }
1257 }
1258
1259done:
1260 if (closeio && src) {
1261 SDL_CloseIO(src);
1262 }
1263
1264 return success;
1265}
1266
1267bool SDL_SaveFile(const char *file, const void *data, size_t datasize)
1268{
1269 SDL_IOStream *stream = SDL_IOFromFile(file, "wb");
1270 if (!stream) {
1271 return false;
1272 }
1273 return SDL_SaveFile_IO(stream, data, datasize, true);
1274}
1275
1276SDL_PropertiesID SDL_GetIOProperties(SDL_IOStream *context)
1277{
1278 if (!context) {
1279 SDL_InvalidParamError("context");
1280 return 0;
1281 }
1282
1283 if (context->props == 0) {
1284 context->props = SDL_CreateProperties();
1285 }
1286 return context->props;
1287}
1288
1289Sint64 SDL_GetIOSize(SDL_IOStream *context)
1290{
1291 if (!context) {
1292 return SDL_InvalidParamError("context");
1293 }
1294 if (!context->iface.size) {
1295 Sint64 pos, size;
1296
1297 pos = SDL_SeekIO(context, 0, SDL_IO_SEEK_CUR);
1298 if (pos < 0) {
1299 return -1;
1300 }
1301 size = SDL_SeekIO(context, 0, SDL_IO_SEEK_END);
1302
1303 SDL_SeekIO(context, pos, SDL_IO_SEEK_SET);
1304 return size;
1305 }
1306 return context->iface.size(context->userdata);
1307}
1308
1309Sint64 SDL_SeekIO(SDL_IOStream *context, Sint64 offset, SDL_IOWhence whence)
1310{
1311 if (!context) {
1312 SDL_InvalidParamError("context");
1313 return -1;
1314 } else if (!context->iface.seek) {
1315 SDL_Unsupported();
1316 return -1;
1317 }
1318 return context->iface.seek(context->userdata, offset, whence);
1319}
1320
1321Sint64 SDL_TellIO(SDL_IOStream *context)
1322{
1323 return SDL_SeekIO(context, 0, SDL_IO_SEEK_CUR);
1324}
1325
1326size_t SDL_ReadIO(SDL_IOStream *context, void *ptr, size_t size)
1327{
1328 size_t bytes;
1329
1330 if (!context) {
1331 SDL_InvalidParamError("context");
1332 return 0;
1333 } else if (!context->iface.read) {
1334 context->status = SDL_IO_STATUS_WRITEONLY;
1335 SDL_Unsupported();
1336 return 0;
1337 }
1338
1339 context->status = SDL_IO_STATUS_READY;
1340 SDL_ClearError();
1341
1342 if (size == 0) {
1343 return 0;
1344 }
1345
1346 bytes = context->iface.read(context->userdata, ptr, size, &context->status);
1347 if (bytes == 0 && context->status == SDL_IO_STATUS_READY) {
1348 if (*SDL_GetError()) {
1349 context->status = SDL_IO_STATUS_ERROR;
1350 } else {
1351 context->status = SDL_IO_STATUS_EOF;
1352 }
1353 }
1354 return bytes;
1355}
1356
1357size_t SDL_WriteIO(SDL_IOStream *context, const void *ptr, size_t size)
1358{
1359 size_t bytes;
1360
1361 if (!context) {
1362 SDL_InvalidParamError("context");
1363 return 0;
1364 } else if (!context->iface.write) {
1365 context->status = SDL_IO_STATUS_READONLY;
1366 SDL_Unsupported();
1367 return 0;
1368 }
1369
1370 context->status = SDL_IO_STATUS_READY;
1371 SDL_ClearError();
1372
1373 if (size == 0) {
1374 return 0;
1375 }
1376
1377 bytes = context->iface.write(context->userdata, ptr, size, &context->status);
1378 if ((bytes == 0) && (context->status == SDL_IO_STATUS_READY)) {
1379 context->status = SDL_IO_STATUS_ERROR;
1380 }
1381 return bytes;
1382}
1383
1384size_t SDL_IOprintf(SDL_IOStream *context, SDL_PRINTF_FORMAT_STRING const char *fmt, ...)
1385{
1386 va_list ap;
1387 int size;
1388 char *string;
1389 size_t bytes;
1390
1391 va_start(ap, fmt);
1392 size = SDL_vasprintf(&string, fmt, ap);
1393 va_end(ap);
1394 if (size < 0) {
1395 return 0;
1396 }
1397
1398 bytes = SDL_WriteIO(context, string, (size_t)size);
1399 SDL_free(string);
1400 return bytes;
1401}
1402
1403size_t SDL_IOvprintf(SDL_IOStream *context, SDL_PRINTF_FORMAT_STRING const char *fmt, va_list ap)
1404{
1405 int size;
1406 char *string;
1407 size_t bytes;
1408
1409 size = SDL_vasprintf(&string, fmt, ap);
1410 if (size < 0) {
1411 return 0;
1412 }
1413
1414 bytes = SDL_WriteIO(context, string, (size_t)size);
1415 SDL_free(string);
1416 return bytes;
1417}
1418
1419bool SDL_FlushIO(SDL_IOStream *context)
1420{
1421 bool result = true;
1422
1423 if (!context) {
1424 return SDL_InvalidParamError("context");
1425 }
1426
1427 context->status = SDL_IO_STATUS_READY;
1428 SDL_ClearError();
1429
1430 if (context->iface.flush) {
1431 result = context->iface.flush(context->userdata, &context->status);
1432 }
1433 if (!result && (context->status == SDL_IO_STATUS_READY)) {
1434 context->status = SDL_IO_STATUS_ERROR;
1435 }
1436 return result;
1437}
1438
1439// Functions for dynamically reading and writing endian-specific values
1440
1441bool SDL_ReadU8(SDL_IOStream *src, Uint8 *value)
1442{
1443 Uint8 data = 0;
1444 bool result = false;
1445
1446 if (SDL_ReadIO(src, &data, sizeof(data)) == sizeof(data)) {
1447 result = true;
1448 }
1449 if (value) {
1450 *value = data;
1451 }
1452 return result;
1453}
1454
1455bool SDL_ReadS8(SDL_IOStream *src, Sint8 *value)
1456{
1457 Sint8 data = 0;
1458 bool result = false;
1459
1460 if (SDL_ReadIO(src, &data, sizeof(data)) == sizeof(data)) {
1461 result = true;
1462 }
1463 if (value) {
1464 *value = data;
1465 }
1466 return result;
1467}
1468
1469bool SDL_ReadU16LE(SDL_IOStream *src, Uint16 *value)
1470{
1471 Uint16 data = 0;
1472 bool result = false;
1473
1474 if (SDL_ReadIO(src, &data, sizeof(data)) == sizeof(data)) {
1475 result = true;
1476 }
1477 if (value) {
1478 *value = SDL_Swap16LE(data);
1479 }
1480 return result;
1481}
1482
1483bool SDL_ReadS16LE(SDL_IOStream *src, Sint16 *value)
1484{
1485 return SDL_ReadU16LE(src, (Uint16 *)value);
1486}
1487
1488bool SDL_ReadU16BE(SDL_IOStream *src, Uint16 *value)
1489{
1490 Uint16 data = 0;
1491 bool result = false;
1492
1493 if (SDL_ReadIO(src, &data, sizeof(data)) == sizeof(data)) {
1494 result = true;
1495 }
1496 if (value) {
1497 *value = SDL_Swap16BE(data);
1498 }
1499 return result;
1500}
1501
1502bool SDL_ReadS16BE(SDL_IOStream *src, Sint16 *value)
1503{
1504 return SDL_ReadU16BE(src, (Uint16 *)value);
1505}
1506
1507bool SDL_ReadU32LE(SDL_IOStream *src, Uint32 *value)
1508{
1509 Uint32 data = 0;
1510 bool result = false;
1511
1512 if (SDL_ReadIO(src, &data, sizeof(data)) == sizeof(data)) {
1513 result = true;
1514 }
1515 if (value) {
1516 *value = SDL_Swap32LE(data);
1517 }
1518 return result;
1519}
1520
1521bool SDL_ReadS32LE(SDL_IOStream *src, Sint32 *value)
1522{
1523 return SDL_ReadU32LE(src, (Uint32 *)value);
1524}
1525
1526bool SDL_ReadU32BE(SDL_IOStream *src, Uint32 *value)
1527{
1528 Uint32 data = 0;
1529 bool result = false;
1530
1531 if (SDL_ReadIO(src, &data, sizeof(data)) == sizeof(data)) {
1532 result = true;
1533 }
1534 if (value) {
1535 *value = SDL_Swap32BE(data);
1536 }
1537 return result;
1538}
1539
1540bool SDL_ReadS32BE(SDL_IOStream *src, Sint32 *value)
1541{
1542 return SDL_ReadU32BE(src, (Uint32 *)value);
1543}
1544
1545bool SDL_ReadU64LE(SDL_IOStream *src, Uint64 *value)
1546{
1547 Uint64 data = 0;
1548 bool result = false;
1549
1550 if (SDL_ReadIO(src, &data, sizeof(data)) == sizeof(data)) {
1551 result = true;
1552 }
1553 if (value) {
1554 *value = SDL_Swap64LE(data);
1555 }
1556 return result;
1557}
1558
1559bool SDL_ReadS64LE(SDL_IOStream *src, Sint64 *value)
1560{
1561 return SDL_ReadU64LE(src, (Uint64 *)value);
1562}
1563
1564bool SDL_ReadU64BE(SDL_IOStream *src, Uint64 *value)
1565{
1566 Uint64 data = 0;
1567 bool result = false;
1568
1569 if (SDL_ReadIO(src, &data, sizeof(data)) == sizeof(data)) {
1570 result = true;
1571 }
1572 if (value) {
1573 *value = SDL_Swap64BE(data);
1574 }
1575 return result;
1576}
1577
1578bool SDL_ReadS64BE(SDL_IOStream *src, Sint64 *value)
1579{
1580 return SDL_ReadU64BE(src, (Uint64 *)value);
1581}
1582
1583bool SDL_WriteU8(SDL_IOStream *dst, Uint8 value)
1584{
1585 return (SDL_WriteIO(dst, &value, sizeof(value)) == sizeof(value));
1586}
1587
1588bool SDL_WriteS8(SDL_IOStream *dst, Sint8 value)
1589{
1590 return (SDL_WriteIO(dst, &value, sizeof(value)) == sizeof(value));
1591}
1592
1593bool SDL_WriteU16LE(SDL_IOStream *dst, Uint16 value)
1594{
1595 const Uint16 swapped = SDL_Swap16LE(value);
1596 return (SDL_WriteIO(dst, &swapped, sizeof(swapped)) == sizeof(swapped));
1597}
1598
1599bool SDL_WriteS16LE(SDL_IOStream *dst, Sint16 value)
1600{
1601 return SDL_WriteU16LE(dst, (Uint16)value);
1602}
1603
1604bool SDL_WriteU16BE(SDL_IOStream *dst, Uint16 value)
1605{
1606 const Uint16 swapped = SDL_Swap16BE(value);
1607 return (SDL_WriteIO(dst, &swapped, sizeof(swapped)) == sizeof(swapped));
1608}
1609
1610bool SDL_WriteS16BE(SDL_IOStream *dst, Sint16 value)
1611{
1612 return SDL_WriteU16BE(dst, (Uint16)value);
1613}
1614
1615bool SDL_WriteU32LE(SDL_IOStream *dst, Uint32 value)
1616{
1617 const Uint32 swapped = SDL_Swap32LE(value);
1618 return (SDL_WriteIO(dst, &swapped, sizeof(swapped)) == sizeof(swapped));
1619}
1620
1621bool SDL_WriteS32LE(SDL_IOStream *dst, Sint32 value)
1622{
1623 return SDL_WriteU32LE(dst, (Uint32)value);
1624}
1625
1626bool SDL_WriteU32BE(SDL_IOStream *dst, Uint32 value)
1627{
1628 const Uint32 swapped = SDL_Swap32BE(value);
1629 return (SDL_WriteIO(dst, &swapped, sizeof(swapped)) == sizeof(swapped));
1630}
1631
1632bool SDL_WriteS32BE(SDL_IOStream *dst, Sint32 value)
1633{
1634 return SDL_WriteU32BE(dst, (Uint32)value);
1635}
1636
1637bool SDL_WriteU64LE(SDL_IOStream *dst, Uint64 value)
1638{
1639 const Uint64 swapped = SDL_Swap64LE(value);
1640 return (SDL_WriteIO(dst, &swapped, sizeof(swapped)) == sizeof(swapped));
1641}
1642
1643bool SDL_WriteS64LE(SDL_IOStream *dst, Sint64 value)
1644{
1645 return SDL_WriteU64LE(dst, (Uint64)value);
1646}
1647
1648bool SDL_WriteU64BE(SDL_IOStream *dst, Uint64 value)
1649{
1650 const Uint64 swapped = SDL_Swap64BE(value);
1651 return (SDL_WriteIO(dst, &swapped, sizeof(swapped)) == sizeof(swapped));
1652}
1653
1654bool SDL_WriteS64BE(SDL_IOStream *dst, Sint64 value)
1655{
1656 return SDL_WriteU64BE(dst, (Uint64)value);
1657}
diff --git a/contrib/SDL-3.2.8/src/io/SDL_iostream_c.h b/contrib/SDL-3.2.8/src/io/SDL_iostream_c.h
new file mode 100644
index 0000000..c04fe20
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/io/SDL_iostream_c.h
@@ -0,0 +1,35 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifndef SDL_iostream_c_h_
24#define SDL_iostream_c_h_
25
26#if defined(SDL_PLATFORM_WINDOWS)
27SDL_IOStream *SDL_IOFromHandle(HANDLE handle, const char *mode, bool autoclose);
28#else
29#if defined(HAVE_STDIO_H)
30extern SDL_IOStream *SDL_IOFromFP(FILE *fp, bool autoclose);
31#endif
32extern SDL_IOStream *SDL_IOFromFD(int fd, bool autoclose);
33#endif
34
35#endif // SDL_iostream_c_h_
diff --git a/contrib/SDL-3.2.8/src/io/SDL_sysasyncio.h b/contrib/SDL-3.2.8/src/io/SDL_sysasyncio.h
new file mode 100644
index 0000000..fbe41d1
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/io/SDL_sysasyncio.h
@@ -0,0 +1,144 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21
22#include "SDL_internal.h"
23
24#ifndef SDL_sysasyncio_h_
25#define SDL_sysasyncio_h_
26
27#if defined(SDL_PLATFORM_WINDOWS) && defined(NTDDI_WIN10_NI)
28#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_APP) && NTDDI_VERSION >= NTDDI_WIN10_NI
29#define HAVE_IORINGAPI_H
30#endif
31#endif
32
33// If your platform has an option other than the "generic" code, make sure this
34// is #defined to 0 instead and implement the SDL_SYS_* functions below in your
35// backend (having them maybe call into the SDL_SYS_*_Generic versions as a
36// fallback if the platform has functionality that isn't always available).
37#if defined(HAVE_LIBURING_H) || defined(HAVE_IORINGAPI_H)
38#define SDL_ASYNCIO_ONLY_HAVE_GENERIC 0
39#else
40#define SDL_ASYNCIO_ONLY_HAVE_GENERIC 1
41#endif
42
43// this entire thing is just juggling doubly-linked lists, so make some helper macros.
44#define LINKED_LIST_DECLARE_FIELDS(type, prefix) \
45 type *prefix##prev; \
46 type *prefix##next
47
48#define LINKED_LIST_PREPEND(item, list, prefix) do { \
49 item->prefix##prev = &list; \
50 item->prefix##next = list.prefix##next; \
51 if (item->prefix##next) { \
52 item->prefix##next->prefix##prev = item; \
53 } \
54 list.prefix##next = item; \
55} while (false)
56
57#define LINKED_LIST_UNLINK(item, prefix) do { \
58 if (item->prefix##next) { \
59 item->prefix##next->prefix##prev = item->prefix##prev; \
60 } \
61 item->prefix##prev->prefix##next = task->prefix##next; \
62 item->prefix##prev = item->prefix##next = NULL; \
63} while (false)
64
65#define LINKED_LIST_START(list, prefix) (list.prefix##next)
66#define LINKED_LIST_NEXT(item, prefix) (item->prefix##next)
67#define LINKED_LIST_PREV(item, prefix) (item->prefix##prev)
68
69typedef struct SDL_AsyncIOTask SDL_AsyncIOTask;
70
71struct SDL_AsyncIOTask
72{
73 SDL_AsyncIO *asyncio;
74 SDL_AsyncIOTaskType type;
75 SDL_AsyncIOQueue *queue;
76 Uint64 offset;
77 bool flush;
78 void *buffer;
79 char *error;
80 SDL_AsyncIOResult result;
81 Uint64 requested_size;
82 Uint64 result_size;
83 void *app_userdata;
84 LINKED_LIST_DECLARE_FIELDS(struct SDL_AsyncIOTask, asyncio);
85 LINKED_LIST_DECLARE_FIELDS(struct SDL_AsyncIOTask, queue); // the generic backend uses this, so I've added it here to avoid the extra allocation.
86 LINKED_LIST_DECLARE_FIELDS(struct SDL_AsyncIOTask, threadpool); // the generic backend uses this, so I've added it here to avoid the extra allocation.
87};
88
89typedef struct SDL_AsyncIOQueueInterface
90{
91 bool (*queue_task)(void *userdata, SDL_AsyncIOTask *task);
92 void (*cancel_task)(void *userdata, SDL_AsyncIOTask *task);
93 SDL_AsyncIOTask * (*get_results)(void *userdata);
94 SDL_AsyncIOTask * (*wait_results)(void *userdata, Sint32 timeoutMS);
95 void (*signal)(void *userdata);
96 void (*destroy)(void *userdata);
97} SDL_AsyncIOQueueInterface;
98
99struct SDL_AsyncIOQueue
100{
101 SDL_AsyncIOQueueInterface iface;
102 void *userdata;
103 SDL_AtomicInt tasks_inflight;
104};
105
106// this interface is kept per-object, even though generally it's going to decide
107// on a single interface that is the same for the entire process, but I've kept
108// the abstraction in case we start exposing more types of async i/o, like
109// sockets, in the future.
110typedef struct SDL_AsyncIOInterface
111{
112 Sint64 (*size)(void *userdata);
113 bool (*read)(void *userdata, SDL_AsyncIOTask *task);
114 bool (*write)(void *userdata, SDL_AsyncIOTask *task);
115 bool (*close)(void *userdata, SDL_AsyncIOTask *task);
116 void (*destroy)(void *userdata);
117} SDL_AsyncIOInterface;
118
119struct SDL_AsyncIO
120{
121 SDL_AsyncIOInterface iface;
122 void *userdata;
123 SDL_Mutex *lock;
124 SDL_AsyncIOTask tasks;
125 SDL_AsyncIOTask *closing; // The close task, which isn't queued until all pending work for this file is done.
126 bool oneshot; // true if this is a SDL_LoadFileAsync open.
127};
128
129// This is implemented for various platforms; param validation is done before calling this. Open file, fill in iface and userdata.
130extern bool SDL_SYS_AsyncIOFromFile(const char *file, const char *mode, SDL_AsyncIO *asyncio);
131
132// This is implemented for various platforms. Call SDL_OpenAsyncIOQueue from in here.
133extern bool SDL_SYS_CreateAsyncIOQueue(SDL_AsyncIOQueue *queue);
134
135// This is called during SDL_QuitAsyncIO, after all tasks have completed and all files are closed, to let the platform clean up global backend details.
136extern void SDL_SYS_QuitAsyncIO(void);
137
138// the "generic" version is always available, since it is almost always needed as a fallback even on platforms that might offer something better.
139extern bool SDL_SYS_AsyncIOFromFile_Generic(const char *file, const char *mode, SDL_AsyncIO *asyncio);
140extern bool SDL_SYS_CreateAsyncIOQueue_Generic(SDL_AsyncIOQueue *queue);
141extern void SDL_SYS_QuitAsyncIO_Generic(void);
142
143#endif
144
diff --git a/contrib/SDL-3.2.8/src/io/generic/SDL_asyncio_generic.c b/contrib/SDL-3.2.8/src/io/generic/SDL_asyncio_generic.c
new file mode 100644
index 0000000..4c2a562
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/io/generic/SDL_asyncio_generic.c
@@ -0,0 +1,465 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21
22// The generic backend uses a threadpool to block on synchronous i/o.
23// This is not ideal, it's meant to be used if there isn't a platform-specific
24// backend that can do something more efficient!
25
26#include "SDL_internal.h"
27#include "../SDL_sysasyncio.h"
28
29// on Emscripten without threads, async i/o is synchronous. Sorry. Almost
30// everything is MEMFS, so it's just a memcpy anyhow, and the Emscripten
31// filesystem APIs don't offer async. In theory, directly accessing
32// persistent storage _does_ offer async APIs at the browser level, but
33// that's not exposed in Emscripten's filesystem abstraction.
34#if defined(SDL_PLATFORM_EMSCRIPTEN) && !defined(__EMSCRIPTEN_PTHREADS__)
35#define SDL_ASYNCIO_USE_THREADPOOL 0
36#else
37#define SDL_ASYNCIO_USE_THREADPOOL 1
38#endif
39
40typedef struct GenericAsyncIOQueueData
41{
42 SDL_Mutex *lock;
43 SDL_Condition *condition;
44 SDL_AsyncIOTask completed_tasks;
45} GenericAsyncIOQueueData;
46
47typedef struct GenericAsyncIOData
48{
49 SDL_Mutex *lock; // !!! FIXME: we can skip this lock if we have an equivalent of pread/pwrite
50 SDL_IOStream *io;
51} GenericAsyncIOData;
52
53static void AsyncIOTaskComplete(SDL_AsyncIOTask *task)
54{
55 SDL_assert(task->queue);
56 GenericAsyncIOQueueData *data = (GenericAsyncIOQueueData *) task->queue->userdata;
57 SDL_LockMutex(data->lock);
58 LINKED_LIST_PREPEND(task, data->completed_tasks, queue);
59 SDL_SignalCondition(data->condition); // wake a thread waiting on the queue.
60 SDL_UnlockMutex(data->lock);
61}
62
63// synchronous i/o is offloaded onto the threadpool. This function does the threaded work.
64// This is called directly, without a threadpool, if !SDL_ASYNCIO_USE_THREADPOOL.
65static void SynchronousIO(SDL_AsyncIOTask *task)
66{
67 SDL_assert(task->result != SDL_ASYNCIO_CANCELED); // shouldn't have gotten in here if canceled!
68
69 GenericAsyncIOData *data = (GenericAsyncIOData *) task->asyncio->userdata;
70 SDL_IOStream *io = data->io;
71 const size_t size = (size_t) task->requested_size;
72 void *ptr = task->buffer;
73
74 // this seek won't work if two tasks are reading from the same file at the same time,
75 // so we lock here. This makes multiple reads from a single file serialize, but different
76 // files will still run in parallel. An app can also open the same file twice to avoid this.
77 SDL_LockMutex(data->lock);
78 if (task->type == SDL_ASYNCIO_TASK_CLOSE) {
79 bool okay = true;
80 if (task->flush) {
81 okay = SDL_FlushIO(data->io);
82 }
83 okay = SDL_CloseIO(data->io) && okay;
84 task->result = okay ? SDL_ASYNCIO_COMPLETE : SDL_ASYNCIO_FAILURE;
85 } else if (SDL_SeekIO(io, (Sint64) task->offset, SDL_IO_SEEK_SET) < 0) {
86 task->result = SDL_ASYNCIO_FAILURE;
87 } else {
88 const bool writing = (task->type == SDL_ASYNCIO_TASK_WRITE);
89 task->result_size = (Uint64) (writing ? SDL_WriteIO(io, ptr, size) : SDL_ReadIO(io, ptr, size));
90 if (task->result_size == task->requested_size) {
91 task->result = SDL_ASYNCIO_COMPLETE;
92 } else {
93 if (writing) {
94 task->result = SDL_ASYNCIO_FAILURE; // it's always a failure on short writes.
95 } else {
96 const SDL_IOStatus status = SDL_GetIOStatus(io);
97 SDL_assert(status != SDL_IO_STATUS_READY); // this should have either failed or been EOF.
98 SDL_assert(status != SDL_IO_STATUS_NOT_READY); // these should not be non-blocking reads!
99 task->result = (status == SDL_IO_STATUS_EOF) ? SDL_ASYNCIO_COMPLETE : SDL_ASYNCIO_FAILURE;
100 }
101 }
102 }
103 SDL_UnlockMutex(data->lock);
104
105 AsyncIOTaskComplete(task);
106}
107
108#if SDL_ASYNCIO_USE_THREADPOOL
109static SDL_InitState threadpool_init;
110static SDL_Mutex *threadpool_lock = NULL;
111static bool stop_threadpool = false;
112static SDL_AsyncIOTask threadpool_tasks;
113static SDL_Condition *threadpool_condition = NULL;
114static int max_threadpool_threads = 0;
115static int running_threadpool_threads = 0;
116static int idle_threadpool_threads = 0;
117static int threadpool_threads_spun = 0;
118
119static int SDLCALL AsyncIOThreadpoolWorker(void *data)
120{
121 SDL_LockMutex(threadpool_lock);
122
123 while (!stop_threadpool) {
124 SDL_AsyncIOTask *task = LINKED_LIST_START(threadpool_tasks, threadpool);
125 if (!task) {
126 // if we go 30 seconds without a new task, terminate unless we're the only thread left.
127 idle_threadpool_threads++;
128 const bool rc = SDL_WaitConditionTimeout(threadpool_condition, threadpool_lock, 30000);
129 idle_threadpool_threads--;
130
131 if (!rc) {
132 // decide if we have too many idle threads, and if so, quit to let thread pool shrink when not busy.
133 if (idle_threadpool_threads) {
134 break;
135 }
136 }
137
138 continue;
139 }
140
141 LINKED_LIST_UNLINK(task, threadpool);
142
143 SDL_UnlockMutex(threadpool_lock);
144
145 // bookkeeping is done, so we drop the mutex and fire the work.
146 SynchronousIO(task);
147
148 SDL_LockMutex(threadpool_lock); // take the lock again and see if there's another task (if not, we'll wait on the Condition).
149 }
150
151 running_threadpool_threads--;
152
153 // this is kind of a hack, but this lets us reuse threadpool_condition to block on shutdown until all threads have exited.
154 if (stop_threadpool) {
155 SDL_BroadcastCondition(threadpool_condition);
156 }
157
158 SDL_UnlockMutex(threadpool_lock);
159
160 return 0;
161}
162
163static bool MaybeSpinNewWorkerThread(void)
164{
165 // if all existing threads are busy and the pool of threads isn't maxed out, make a new one.
166 if ((idle_threadpool_threads == 0) && (running_threadpool_threads < max_threadpool_threads)) {
167 char threadname[32];
168 SDL_snprintf(threadname, sizeof (threadname), "SDLasyncio%d", threadpool_threads_spun);
169 SDL_Thread *thread = SDL_CreateThread(AsyncIOThreadpoolWorker, threadname, NULL);
170 if (thread == NULL) {
171 return false;
172 }
173 SDL_DetachThread(thread); // these terminate themselves when idle too long, so we never WaitThread.
174 running_threadpool_threads++;
175 threadpool_threads_spun++;
176 }
177 return true;
178}
179
180static void QueueAsyncIOTask(SDL_AsyncIOTask *task)
181{
182 SDL_assert(task != NULL);
183
184 SDL_LockMutex(threadpool_lock);
185
186 if (stop_threadpool) { // just in case.
187 task->result = SDL_ASYNCIO_CANCELED;
188 AsyncIOTaskComplete(task);
189 } else {
190 LINKED_LIST_PREPEND(task, threadpool_tasks, threadpool);
191 MaybeSpinNewWorkerThread(); // okay if this fails or the thread pool is maxed out. Something will get there eventually.
192
193 // tell idle threads to get to work.
194 // This is a broadcast because we want someone from the thread pool to wake up, but
195 // also shutdown might also be blocking on this. One of the threads will grab
196 // it, the others will go back to sleep.
197 SDL_BroadcastCondition(threadpool_condition);
198 }
199
200 SDL_UnlockMutex(threadpool_lock);
201}
202
203// We don't initialize async i/o at all until it's used, so
204// JUST IN CASE two things try to start at the same time,
205// this will make sure everything gets the same mutex.
206static bool PrepareThreadpool(void)
207{
208 bool okay = true;
209 if (SDL_ShouldInit(&threadpool_init)) {
210 max_threadpool_threads = (SDL_GetNumLogicalCPUCores() * 2) + 1; // !!! FIXME: this should probably have a hint to override.
211 max_threadpool_threads = SDL_clamp(max_threadpool_threads, 1, 8); // 8 is probably more than enough.
212
213 okay = (okay && ((threadpool_lock = SDL_CreateMutex()) != NULL));
214 okay = (okay && ((threadpool_condition = SDL_CreateCondition()) != NULL));
215 okay = (okay && MaybeSpinNewWorkerThread()); // make sure at least one thread is going, since we'll need it.
216
217 if (!okay) {
218 if (threadpool_condition) {
219 SDL_DestroyCondition(threadpool_condition);
220 threadpool_condition = NULL;
221 }
222 if (threadpool_lock) {
223 SDL_DestroyMutex(threadpool_lock);
224 threadpool_lock = NULL;
225 }
226 }
227
228 SDL_SetInitialized(&threadpool_init, okay);
229 }
230 return okay;
231}
232
233static void ShutdownThreadpool(void)
234{
235 if (SDL_ShouldQuit(&threadpool_init)) {
236 SDL_LockMutex(threadpool_lock);
237
238 // cancel anything that's still pending.
239 SDL_AsyncIOTask *task;
240 while ((task = LINKED_LIST_START(threadpool_tasks, threadpool)) != NULL) {
241 LINKED_LIST_UNLINK(task, threadpool);
242 task->result = SDL_ASYNCIO_CANCELED;
243 AsyncIOTaskComplete(task);
244 }
245
246 stop_threadpool = true;
247 SDL_BroadcastCondition(threadpool_condition); // tell the whole threadpool to wake up and quit.
248
249 while (running_threadpool_threads > 0) {
250 // each threadpool thread will broadcast this condition before it terminates if stop_threadpool is set.
251 // we can't just join the threads because they are detached, so the thread pool can automatically shrink as necessary.
252 SDL_WaitCondition(threadpool_condition, threadpool_lock);
253 }
254
255 SDL_UnlockMutex(threadpool_lock);
256
257 SDL_DestroyMutex(threadpool_lock);
258 threadpool_lock = NULL;
259 SDL_DestroyCondition(threadpool_condition);
260 threadpool_condition = NULL;
261
262 max_threadpool_threads = running_threadpool_threads = idle_threadpool_threads = threadpool_threads_spun = 0;
263
264 stop_threadpool = false;
265 SDL_SetInitialized(&threadpool_init, false);
266 }
267}
268#endif
269
270
271static Sint64 generic_asyncio_size(void *userdata)
272{
273 GenericAsyncIOData *data = (GenericAsyncIOData *) userdata;
274 return SDL_GetIOSize(data->io);
275}
276
277static bool generic_asyncio_io(void *userdata, SDL_AsyncIOTask *task)
278{
279 return task->queue->iface.queue_task(task->queue->userdata, task);
280}
281
282static void generic_asyncio_destroy(void *userdata)
283{
284 GenericAsyncIOData *data = (GenericAsyncIOData *) userdata;
285 SDL_DestroyMutex(data->lock);
286 SDL_free(data);
287}
288
289
290static bool generic_asyncioqueue_queue_task(void *userdata, SDL_AsyncIOTask *task)
291{
292 #if SDL_ASYNCIO_USE_THREADPOOL
293 QueueAsyncIOTask(task);
294 #else
295 SynchronousIO(task); // oh well. Get a better platform.
296 #endif
297 return true;
298}
299
300static void generic_asyncioqueue_cancel_task(void *userdata, SDL_AsyncIOTask *task)
301{
302 #if !SDL_ASYNCIO_USE_THREADPOOL // in theory, this was all synchronous and should never call this, but just in case.
303 task->result = SDL_ASYNCIO_CANCELED;
304 AsyncIOTaskComplete(task);
305 #else
306 // we can't stop i/o that's in-flight, but we _can_ just refuse to start it if the threadpool hadn't picked it up yet.
307 SDL_LockMutex(threadpool_lock);
308 if (LINKED_LIST_PREV(task, threadpool) != NULL) { // still in the queue waiting to be run? Take it out.
309 LINKED_LIST_UNLINK(task, threadpool);
310 task->result = SDL_ASYNCIO_CANCELED;
311 AsyncIOTaskComplete(task);
312 }
313 SDL_UnlockMutex(threadpool_lock);
314 #endif
315}
316
317static SDL_AsyncIOTask *generic_asyncioqueue_get_results(void *userdata)
318{
319 GenericAsyncIOQueueData *data = (GenericAsyncIOQueueData *) userdata;
320 SDL_LockMutex(data->lock);
321 SDL_AsyncIOTask *task = LINKED_LIST_START(data->completed_tasks, queue);
322 if (task) {
323 LINKED_LIST_UNLINK(task, queue);
324 }
325 SDL_UnlockMutex(data->lock);
326 return task;
327}
328
329static SDL_AsyncIOTask *generic_asyncioqueue_wait_results(void *userdata, Sint32 timeoutMS)
330{
331 GenericAsyncIOQueueData *data = (GenericAsyncIOQueueData *) userdata;
332 SDL_LockMutex(data->lock);
333 SDL_AsyncIOTask *task = LINKED_LIST_START(data->completed_tasks, queue);
334 if (!task) {
335 SDL_WaitConditionTimeout(data->condition, data->lock, timeoutMS);
336 task = LINKED_LIST_START(data->completed_tasks, queue);
337 }
338 if (task) {
339 LINKED_LIST_UNLINK(task, queue);
340 }
341 SDL_UnlockMutex(data->lock);
342 return task;
343}
344
345static void generic_asyncioqueue_signal(void *userdata)
346{
347 GenericAsyncIOQueueData *data = (GenericAsyncIOQueueData *) userdata;
348 SDL_LockMutex(data->lock);
349 SDL_BroadcastCondition(data->condition);
350 SDL_UnlockMutex(data->lock);
351}
352
353static void generic_asyncioqueue_destroy(void *userdata)
354{
355 GenericAsyncIOQueueData *data = (GenericAsyncIOQueueData *) userdata;
356 SDL_DestroyMutex(data->lock);
357 SDL_DestroyCondition(data->condition);
358 SDL_free(data);
359}
360
361bool SDL_SYS_CreateAsyncIOQueue_Generic(SDL_AsyncIOQueue *queue)
362{
363 #if SDL_ASYNCIO_USE_THREADPOOL
364 if (!PrepareThreadpool()) {
365 return false;
366 }
367 #endif
368
369 GenericAsyncIOQueueData *data = (GenericAsyncIOQueueData *) SDL_calloc(1, sizeof (*data));
370 if (!data) {
371 return false;
372 }
373
374 data->lock = SDL_CreateMutex();
375 if (!data->lock) {
376 SDL_free(data);
377 return false;
378 }
379
380 data->condition = SDL_CreateCondition();
381 if (!data->condition) {
382 SDL_DestroyMutex(data->lock);
383 SDL_free(data);
384 return false;
385 }
386
387 static const SDL_AsyncIOQueueInterface SDL_AsyncIOQueue_Generic = {
388 generic_asyncioqueue_queue_task,
389 generic_asyncioqueue_cancel_task,
390 generic_asyncioqueue_get_results,
391 generic_asyncioqueue_wait_results,
392 generic_asyncioqueue_signal,
393 generic_asyncioqueue_destroy
394 };
395
396 SDL_copyp(&queue->iface, &SDL_AsyncIOQueue_Generic);
397 queue->userdata = data;
398 return true;
399}
400
401
402bool SDL_SYS_AsyncIOFromFile_Generic(const char *file, const char *mode, SDL_AsyncIO *asyncio)
403{
404 #if SDL_ASYNCIO_USE_THREADPOOL
405 if (!PrepareThreadpool()) {
406 return false;
407 }
408 #endif
409
410 GenericAsyncIOData *data = (GenericAsyncIOData *) SDL_calloc(1, sizeof (*data));
411 if (!data) {
412 return false;
413 }
414
415 data->lock = SDL_CreateMutex();
416 if (!data->lock) {
417 SDL_free(data);
418 return false;
419 }
420
421 data->io = SDL_IOFromFile(file, mode);
422 if (!data->io) {
423 SDL_DestroyMutex(data->lock);
424 SDL_free(data);
425 return false;
426 }
427
428 static const SDL_AsyncIOInterface SDL_AsyncIOFile_Generic = {
429 generic_asyncio_size,
430 generic_asyncio_io,
431 generic_asyncio_io,
432 generic_asyncio_io,
433 generic_asyncio_destroy
434 };
435
436 SDL_copyp(&asyncio->iface, &SDL_AsyncIOFile_Generic);
437 asyncio->userdata = data;
438 return true;
439}
440
441void SDL_SYS_QuitAsyncIO_Generic(void)
442{
443 #if SDL_ASYNCIO_USE_THREADPOOL
444 ShutdownThreadpool();
445 #endif
446}
447
448
449#if SDL_ASYNCIO_ONLY_HAVE_GENERIC
450bool SDL_SYS_AsyncIOFromFile(const char *file, const char *mode, SDL_AsyncIO *asyncio)
451{
452 return SDL_SYS_AsyncIOFromFile_Generic(file, mode, asyncio);
453}
454
455bool SDL_SYS_CreateAsyncIOQueue(SDL_AsyncIOQueue *queue)
456{
457 return SDL_SYS_CreateAsyncIOQueue_Generic(queue);
458}
459
460void SDL_SYS_QuitAsyncIO(void)
461{
462 SDL_SYS_QuitAsyncIO_Generic();
463}
464#endif
465
diff --git a/contrib/SDL-3.2.8/src/io/io_uring/SDL_asyncio_liburing.c b/contrib/SDL-3.2.8/src/io/io_uring/SDL_asyncio_liburing.c
new file mode 100644
index 0000000..07e8c24
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/io/io_uring/SDL_asyncio_liburing.c
@@ -0,0 +1,551 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21
22// The Linux backend uses io_uring for asynchronous i/o, and falls back to
23// the "generic" threadpool implementation if liburing isn't available or
24// fails for some other reason.
25
26#include "SDL_internal.h"
27
28#ifdef HAVE_LIBURING_H
29
30#include "../SDL_sysasyncio.h"
31
32#include <liburing.h>
33#include <errno.h>
34#include <fcntl.h>
35#include <string.h> // for strerror()
36
37static SDL_InitState liburing_init;
38
39// We could add a whole bootstrap thing like the audio/video/etc subsystems use, but let's keep this simple for now.
40static bool (*CreateAsyncIOQueue)(SDL_AsyncIOQueue *queue);
41static void (*QuitAsyncIO)(void);
42static bool (*AsyncIOFromFile)(const char *file, const char *mode, SDL_AsyncIO *asyncio);
43
44// we never link directly to liburing.
45// (this says "-ffi" which sounds like a scripting language binding thing, but the non-ffi version
46// is static-inline code we can't lookup with dlsym. This is by design.)
47static const char *liburing_library = "liburing-ffi.so.2";
48static void *liburing_handle = NULL;
49
50#define SDL_LIBURING_FUNCS \
51 SDL_LIBURING_FUNC(int, io_uring_queue_init, (unsigned entries, struct io_uring *ring, unsigned flags)) \
52 SDL_LIBURING_FUNC(struct io_uring_probe *,io_uring_get_probe,(void)) \
53 SDL_LIBURING_FUNC(void, io_uring_free_probe, (struct io_uring_probe *probe)) \
54 SDL_LIBURING_FUNC(int, io_uring_opcode_supported, (const struct io_uring_probe *p, int op)) \
55 SDL_LIBURING_FUNC(struct io_uring_sqe *, io_uring_get_sqe, (struct io_uring *ring)) \
56 SDL_LIBURING_FUNC(void, io_uring_prep_read,(struct io_uring_sqe *sqe, int fd, void *buf, unsigned nbytes, __u64 offset)) \
57 SDL_LIBURING_FUNC(void, io_uring_prep_write,(struct io_uring_sqe *sqe, int fd, const void *buf, unsigned nbytes, __u64 offset)) \
58 SDL_LIBURING_FUNC(void, io_uring_prep_close, (struct io_uring_sqe *sqe, int fd)) \
59 SDL_LIBURING_FUNC(void, io_uring_prep_fsync, (struct io_uring_sqe *sqe, int fd, unsigned fsync_flags)) \
60 SDL_LIBURING_FUNC(void, io_uring_prep_cancel, (struct io_uring_sqe *sqe, void *user_data, int flags)) \
61 SDL_LIBURING_FUNC(void, io_uring_prep_timeout, (struct io_uring_sqe *sqe, struct __kernel_timespec *ts, unsigned count, unsigned flags)) \
62 SDL_LIBURING_FUNC(void, io_uring_prep_nop, (struct io_uring_sqe *sqe)) \
63 SDL_LIBURING_FUNC(void, io_uring_sqe_set_data, (struct io_uring_sqe *sqe, void *data)) \
64 SDL_LIBURING_FUNC(void, io_uring_sqe_set_flags, (struct io_uring_sqe *sqe, unsigned flags)) \
65 SDL_LIBURING_FUNC(int, io_uring_submit, (struct io_uring *ring)) \
66 SDL_LIBURING_FUNC(int, io_uring_peek_cqe, (struct io_uring *ring, struct io_uring_cqe **cqe_ptr)) \
67 SDL_LIBURING_FUNC(int, io_uring_wait_cqe, (struct io_uring *ring, struct io_uring_cqe **cqe_ptr)) \
68 SDL_LIBURING_FUNC(int, io_uring_wait_cqe_timeout, (struct io_uring *ring, struct io_uring_cqe **cqe_ptr, struct __kernel_timespec *ts)) \
69 SDL_LIBURING_FUNC(void, io_uring_cqe_seen, (struct io_uring *ring, struct io_uring_cqe *cqe)) \
70 SDL_LIBURING_FUNC(void, io_uring_queue_exit, (struct io_uring *ring)) \
71
72
73#define SDL_LIBURING_FUNC(ret, fn, args) typedef ret (*SDL_fntype_##fn) args;
74SDL_LIBURING_FUNCS
75#undef SDL_LIBURING_FUNC
76
77typedef struct SDL_LibUringFunctions
78{
79 #define SDL_LIBURING_FUNC(ret, fn, args) SDL_fntype_##fn fn;
80 SDL_LIBURING_FUNCS
81 #undef SDL_LIBURING_FUNC
82} SDL_LibUringFunctions;
83
84static SDL_LibUringFunctions liburing;
85
86
87typedef struct LibUringAsyncIOQueueData
88{
89 SDL_Mutex *sqe_lock;
90 SDL_Mutex *cqe_lock;
91 struct io_uring ring;
92 SDL_AtomicInt num_waiting;
93} LibUringAsyncIOQueueData;
94
95
96static void UnloadLibUringLibrary(void)
97{
98 if (liburing_library) {
99 SDL_UnloadObject(liburing_handle);
100 liburing_library = NULL;
101 }
102 SDL_zero(liburing);
103}
104
105static bool LoadLibUringSyms(void)
106{
107 #define SDL_LIBURING_FUNC(ret, fn, args) { \
108 liburing.fn = (SDL_fntype_##fn) SDL_LoadFunction(liburing_handle, #fn); \
109 if (!liburing.fn) { \
110 return false; \
111 } \
112 }
113 SDL_LIBURING_FUNCS
114 #undef SDL_LIBURING_FUNC
115 return true;
116}
117
118// we rely on the presence of liburing to handle io_uring for us. The alternative is making
119// direct syscalls into the kernel, which is undesirable. liburing both shields us from this,
120// but also smooths over some kernel version differences, etc.
121static bool LoadLibUring(void)
122{
123 bool result = true;
124
125 if (!liburing_handle) {
126 liburing_handle = SDL_LoadObject(liburing_library);
127 if (!liburing_handle) {
128 result = false;
129 // Don't call SDL_SetError(): SDL_LoadObject already did.
130 } else {
131 result = LoadLibUringSyms();
132 if (result) {
133 static const int needed_ops[] = {
134 IORING_OP_NOP,
135 IORING_OP_FSYNC,
136 IORING_OP_TIMEOUT,
137 IORING_OP_CLOSE,
138 IORING_OP_READ,
139 IORING_OP_WRITE,
140 IORING_OP_ASYNC_CANCEL
141 };
142
143 struct io_uring_probe *probe = liburing.io_uring_get_probe();
144 if (!probe) {
145 result = false;
146 } else {
147 for (int i = 0; i < SDL_arraysize(needed_ops); i++) {
148 if (!io_uring_opcode_supported(probe, needed_ops[i])) {
149 result = false;
150 break;
151 }
152 }
153 liburing.io_uring_free_probe(probe);
154 }
155 }
156
157 if (!result) {
158 UnloadLibUringLibrary();
159 }
160 }
161 }
162 return result;
163}
164
165static bool liburing_SetError(const char *what, int err)
166{
167 SDL_assert(err <= 0);
168 return SDL_SetError("%s failed: %s", what, strerror(-err));
169}
170
171static Sint64 liburing_asyncio_size(void *userdata)
172{
173 const int fd = (int) (intptr_t) userdata;
174 struct stat statbuf;
175 if (fstat(fd, &statbuf) < 0) {
176 SDL_SetError("fstat failed: %s", strerror(errno));
177 return -1;
178 }
179 return ((Sint64) statbuf.st_size);
180}
181
182// you must hold sqe_lock when calling this!
183static bool liburing_asyncioqueue_queue_task(void *userdata, SDL_AsyncIOTask *task)
184{
185 LibUringAsyncIOQueueData *queuedata = (LibUringAsyncIOQueueData *) userdata;
186 const int rc = liburing.io_uring_submit(&queuedata->ring);
187 return (rc < 0) ? liburing_SetError("io_uring_submit", rc) : true;
188}
189
190static void liburing_asyncioqueue_cancel_task(void *userdata, SDL_AsyncIOTask *task)
191{
192 SDL_AsyncIOTask *cancel_task = (SDL_AsyncIOTask *) SDL_calloc(1, sizeof (*cancel_task));
193 if (!cancel_task) {
194 return; // oh well, the task can just finish on its own.
195 }
196
197 LibUringAsyncIOQueueData *queuedata = (LibUringAsyncIOQueueData *) userdata;
198
199 // have to hold a lock because otherwise two threads could get_sqe and submit while one request isn't fully set up.
200 SDL_LockMutex(queuedata->sqe_lock);
201 struct io_uring_sqe *sqe = liburing.io_uring_get_sqe(&queuedata->ring);
202 if (!sqe) {
203 SDL_UnlockMutex(queuedata->sqe_lock);
204 SDL_free(cancel_task); // oh well, the task can just finish on its own.
205 return;
206 }
207
208 cancel_task->app_userdata = task;
209 liburing.io_uring_prep_cancel(sqe, task, 0);
210 liburing.io_uring_sqe_set_data(sqe, cancel_task);
211 liburing_asyncioqueue_queue_task(userdata, task);
212 SDL_UnlockMutex(queuedata->sqe_lock);
213}
214
215static SDL_AsyncIOTask *ProcessCQE(LibUringAsyncIOQueueData *queuedata, struct io_uring_cqe *cqe)
216{
217 if (!cqe) {
218 return NULL;
219 }
220
221 SDL_AsyncIOTask *task = (SDL_AsyncIOTask *) io_uring_cqe_get_data(cqe);
222 if (task) { // can be NULL if this was just a wakeup message, a NOP, etc.
223 if (!task->queue) { // We leave `queue` blank to signify this was a task cancellation.
224 SDL_AsyncIOTask *cancel_task = task;
225 task = (SDL_AsyncIOTask *) cancel_task->app_userdata;
226 SDL_free(cancel_task);
227 if (cqe->res >= 0) { // cancel was successful?
228 task->result = SDL_ASYNCIO_CANCELED;
229 } else {
230 task = NULL; // it already finished or was too far along to cancel, so we'll pick up the actual results later.
231 }
232 } else if (cqe->res < 0) {
233 task->result = SDL_ASYNCIO_FAILURE;
234 // !!! FIXME: fill in task->error.
235 } else {
236 if ((task->type == SDL_ASYNCIO_TASK_WRITE) && (((Uint64) cqe->res) < task->requested_size)) {
237 task->result = SDL_ASYNCIO_FAILURE; // it's always a failure on short writes.
238 }
239
240 // don't explicitly mark it as COMPLETE; that's the default value and a linked task might have failed in an earlier operation and this would overwrite it.
241
242 if ((task->type == SDL_ASYNCIO_TASK_READ) || (task->type == SDL_ASYNCIO_TASK_WRITE)) {
243 task->result_size = (Uint64) cqe->res;
244 }
245 }
246
247 if ((task->type == SDL_ASYNCIO_TASK_CLOSE) && task->flush) {
248 task->flush = false;
249 task = NULL; // don't return this one, it's a linked task, so it'll arrive in a later CQE.
250 }
251 }
252
253 return task;
254}
255
256static SDL_AsyncIOTask *liburing_asyncioqueue_get_results(void *userdata)
257{
258 LibUringAsyncIOQueueData *queuedata = (LibUringAsyncIOQueueData *) userdata;
259
260 // have to hold a lock because otherwise two threads will get the same cqe until we mark it "seen". Copy and mark it right away, then process further.
261 SDL_LockMutex(queuedata->cqe_lock);
262 struct io_uring_cqe *cqe = NULL;
263 const int rc = liburing.io_uring_peek_cqe(&queuedata->ring, &cqe);
264 if (rc != 0) {
265 SDL_assert(rc == -EAGAIN); // should only fail because nothing is available at the moment.
266 SDL_UnlockMutex(queuedata->cqe_lock);
267 return NULL;
268 }
269
270 struct io_uring_cqe cqe_copy;
271 SDL_copyp(&cqe_copy, cqe); // this is only a few bytes.
272 liburing.io_uring_cqe_seen(&queuedata->ring, cqe); // let io_uring use this slot again.
273 SDL_UnlockMutex(queuedata->cqe_lock);
274
275 return ProcessCQE(queuedata, &cqe_copy);
276}
277
278static SDL_AsyncIOTask *liburing_asyncioqueue_wait_results(void *userdata, Sint32 timeoutMS)
279{
280 LibUringAsyncIOQueueData *queuedata = (LibUringAsyncIOQueueData *) userdata;
281 struct io_uring_cqe *cqe = NULL;
282
283 SDL_AddAtomicInt(&queuedata->num_waiting, 1);
284 if (timeoutMS < 0) {
285 liburing.io_uring_wait_cqe(&queuedata->ring, &cqe);
286 } else {
287 struct __kernel_timespec ts = { (Sint64) timeoutMS / SDL_MS_PER_SECOND, (Sint64) SDL_MS_TO_NS(timeoutMS % SDL_MS_PER_SECOND) };
288 liburing.io_uring_wait_cqe_timeout(&queuedata->ring, &cqe, &ts);
289 }
290 SDL_AddAtomicInt(&queuedata->num_waiting, -1);
291
292 // (we don't care if the wait failed for any reason, as the upcoming peek_cqe will report valid information. We just wanted the wait operation to block.)
293
294 // each thing that peeks or waits for a completion _gets the same cqe_ until we mark it as seen. So when we wake up from the wait, lock the mutex and
295 // then use peek to make sure we have a unique cqe, and other competing threads either get their own or nothing.
296 return liburing_asyncioqueue_get_results(userdata); // this just happens to do all those things.
297}
298
299static void liburing_asyncioqueue_signal(void *userdata)
300{
301 LibUringAsyncIOQueueData *queuedata = (LibUringAsyncIOQueueData *) userdata;
302 const int num_waiting = SDL_GetAtomicInt(&queuedata->num_waiting);
303
304 SDL_LockMutex(queuedata->sqe_lock);
305 for (int i = 0; i < num_waiting; i++) { // !!! FIXME: is there a better way to do this than pushing a zero-timeout request for everything waiting?
306 struct io_uring_sqe *sqe = liburing.io_uring_get_sqe(&queuedata->ring);
307 if (sqe) {
308 static struct __kernel_timespec ts; // no wait, just wake a thread as fast as this can land in the completion queue.
309 liburing.io_uring_prep_timeout(sqe, &ts, 0, 0);
310 liburing.io_uring_sqe_set_data(sqe, NULL);
311 }
312 }
313 liburing.io_uring_submit(&queuedata->ring);
314
315 SDL_UnlockMutex(queuedata->sqe_lock);
316}
317
318static void liburing_asyncioqueue_destroy(void *userdata)
319{
320 LibUringAsyncIOQueueData *queuedata = (LibUringAsyncIOQueueData *) userdata;
321 liburing.io_uring_queue_exit(&queuedata->ring);
322 SDL_DestroyMutex(queuedata->sqe_lock);
323 SDL_DestroyMutex(queuedata->cqe_lock);
324 SDL_free(queuedata);
325}
326
327static bool SDL_SYS_CreateAsyncIOQueue_liburing(SDL_AsyncIOQueue *queue)
328{
329 LibUringAsyncIOQueueData *queuedata = (LibUringAsyncIOQueueData *) SDL_calloc(1, sizeof (*queuedata));
330 if (!queuedata) {
331 return false;
332 }
333
334 SDL_SetAtomicInt(&queuedata->num_waiting, 0);
335
336 queuedata->sqe_lock = SDL_CreateMutex();
337 if (!queuedata->sqe_lock) {
338 SDL_free(queuedata);
339 return false;
340 }
341
342 queuedata->cqe_lock = SDL_CreateMutex();
343 if (!queuedata->cqe_lock) {
344 SDL_DestroyMutex(queuedata->sqe_lock);
345 SDL_free(queuedata);
346 return false;
347 }
348
349 // !!! FIXME: no idea how large the queue should be. Is 128 overkill or too small?
350 const int rc = liburing.io_uring_queue_init(128, &queuedata->ring, 0);
351 if (rc != 0) {
352 SDL_DestroyMutex(queuedata->sqe_lock);
353 SDL_DestroyMutex(queuedata->cqe_lock);
354 SDL_free(queuedata);
355 return liburing_SetError("io_uring_queue_init", rc);
356 }
357
358 static const SDL_AsyncIOQueueInterface SDL_AsyncIOQueue_liburing = {
359 liburing_asyncioqueue_queue_task,
360 liburing_asyncioqueue_cancel_task,
361 liburing_asyncioqueue_get_results,
362 liburing_asyncioqueue_wait_results,
363 liburing_asyncioqueue_signal,
364 liburing_asyncioqueue_destroy
365 };
366
367 SDL_copyp(&queue->iface, &SDL_AsyncIOQueue_liburing);
368 queue->userdata = queuedata;
369 return true;
370}
371
372
373static bool liburing_asyncio_read(void *userdata, SDL_AsyncIOTask *task)
374{
375 LibUringAsyncIOQueueData *queuedata = (LibUringAsyncIOQueueData *) task->queue->userdata;
376 const int fd = (int) (intptr_t) userdata;
377
378 // !!! FIXME: `unsigned` is likely smaller than requested_size's Uint64. If we overflow it, we could try submitting multiple SQEs
379 // !!! FIXME: and make a note in the task that there are several in sequence.
380 if (task->requested_size > ((Uint64) ~((unsigned) 0))) {
381 return SDL_SetError("io_uring: i/o task is too large");
382 }
383
384 // have to hold a lock because otherwise two threads could get_sqe and submit while one request isn't fully set up.
385 SDL_LockMutex(queuedata->sqe_lock);
386 bool retval;
387 struct io_uring_sqe *sqe = liburing.io_uring_get_sqe(&queuedata->ring);
388 if (!sqe) {
389 retval = SDL_SetError("io_uring: submission queue is full");
390 } else {
391 liburing.io_uring_prep_read(sqe, fd, task->buffer, (unsigned) task->requested_size, task->offset);
392 liburing.io_uring_sqe_set_data(sqe, task);
393 retval = task->queue->iface.queue_task(task->queue->userdata, task);
394 }
395 SDL_UnlockMutex(queuedata->sqe_lock);
396 return retval;
397}
398
399static bool liburing_asyncio_write(void *userdata, SDL_AsyncIOTask *task)
400{
401 LibUringAsyncIOQueueData *queuedata = (LibUringAsyncIOQueueData *) task->queue->userdata;
402 const int fd = (int) (intptr_t) userdata;
403
404 // !!! FIXME: `unsigned` is likely smaller than requested_size's Uint64. If we overflow it, we could try submitting multiple SQEs
405 // !!! FIXME: and make a note in the task that there are several in sequence.
406 if (task->requested_size > ((Uint64) ~((unsigned) 0))) {
407 return SDL_SetError("io_uring: i/o task is too large");
408 }
409
410 // have to hold a lock because otherwise two threads could get_sqe and submit while one request isn't fully set up.
411 SDL_LockMutex(queuedata->sqe_lock);
412 bool retval;
413 struct io_uring_sqe *sqe = liburing.io_uring_get_sqe(&queuedata->ring);
414 if (!sqe) {
415 retval = SDL_SetError("io_uring: submission queue is full");
416 } else {
417 liburing.io_uring_prep_write(sqe, fd, task->buffer, (unsigned) task->requested_size, task->offset);
418 liburing.io_uring_sqe_set_data(sqe, task);
419 retval = task->queue->iface.queue_task(task->queue->userdata, task);
420 }
421 SDL_UnlockMutex(queuedata->sqe_lock);
422 return retval;
423}
424
425static bool liburing_asyncio_close(void *userdata, SDL_AsyncIOTask *task)
426{
427 LibUringAsyncIOQueueData *queuedata = (LibUringAsyncIOQueueData *) task->queue->userdata;
428 const int fd = (int) (intptr_t) userdata;
429
430 // have to hold a lock because otherwise two threads could get_sqe and submit while one request isn't fully set up.
431 SDL_LockMutex(queuedata->sqe_lock);
432 bool retval;
433 struct io_uring_sqe *sqe = liburing.io_uring_get_sqe(&queuedata->ring);
434 if (!sqe) {
435 retval = SDL_SetError("io_uring: submission queue is full");
436 } else {
437 if (task->flush) {
438 struct io_uring_sqe *flush_sqe = sqe;
439 sqe = liburing.io_uring_get_sqe(&queuedata->ring); // this will be our actual close task.
440 if (!sqe) {
441 liburing.io_uring_prep_nop(flush_sqe); // we already have the first sqe, just make it a NOP.
442 liburing.io_uring_sqe_set_data(flush_sqe, NULL);
443 task->queue->iface.queue_task(task->queue->userdata, task);
444 SDL_UnlockMutex(queuedata->sqe_lock);
445 return SDL_SetError("io_uring: submission queue is full");
446 }
447 liburing.io_uring_prep_fsync(flush_sqe, fd, IORING_FSYNC_DATASYNC);
448 liburing.io_uring_sqe_set_data(flush_sqe, task);
449 liburing.io_uring_sqe_set_flags(flush_sqe, IOSQE_IO_HARDLINK); // must complete before next sqe starts, and next sqe should run even if this fails.
450 }
451
452 liburing.io_uring_prep_close(sqe, fd);
453 liburing.io_uring_sqe_set_data(sqe, task);
454
455 retval = task->queue->iface.queue_task(task->queue->userdata, task);
456 }
457 SDL_UnlockMutex(queuedata->sqe_lock);
458 return retval;
459}
460
461static void liburing_asyncio_destroy(void *userdata)
462{
463 // this is only a Unix file descriptor, should have been closed elsewhere.
464}
465
466static int PosixOpenModeFromString(const char *mode)
467{
468 // this is exactly the set of strings that SDL_AsyncIOFromFile promises will work.
469 static const struct { const char *str; int flags; } mappings[] = {
470 { "rb", O_RDONLY },
471 { "wb", O_WRONLY | O_CREAT | O_TRUNC },
472 { "r+b", O_RDWR },
473 { "w+b", O_RDWR | O_CREAT | O_TRUNC }
474 };
475
476 for (int i = 0; i < SDL_arraysize(mappings); i++) {
477 if (SDL_strcmp(mappings[i].str, mode) == 0) {
478 return mappings[i].flags;
479 }
480 }
481
482 SDL_assert(!"Shouldn't have reached this code");
483 return 0;
484}
485
486static bool SDL_SYS_AsyncIOFromFile_liburing(const char *file, const char *mode, SDL_AsyncIO *asyncio)
487{
488 const int fd = open(file, PosixOpenModeFromString(mode), 0644);
489 if (fd == -1) {
490 return SDL_SetError("open failed: %s", strerror(errno));
491 }
492
493 static const SDL_AsyncIOInterface SDL_AsyncIOFile_liburing = {
494 liburing_asyncio_size,
495 liburing_asyncio_read,
496 liburing_asyncio_write,
497 liburing_asyncio_close,
498 liburing_asyncio_destroy
499 };
500
501 SDL_copyp(&asyncio->iface, &SDL_AsyncIOFile_liburing);
502 asyncio->userdata = (void *) (intptr_t) fd;
503 return true;
504}
505
506static void SDL_SYS_QuitAsyncIO_liburing(void)
507{
508 UnloadLibUringLibrary();
509}
510
511static void MaybeInitializeLibUring(void)
512{
513 if (SDL_ShouldInit(&liburing_init)) {
514 if (LoadLibUring()) {
515 CreateAsyncIOQueue = SDL_SYS_CreateAsyncIOQueue_liburing;
516 QuitAsyncIO = SDL_SYS_QuitAsyncIO_liburing;
517 AsyncIOFromFile = SDL_SYS_AsyncIOFromFile_liburing;
518 } else { // can't use liburing? Use the "generic" threadpool implementation instead.
519 CreateAsyncIOQueue = SDL_SYS_CreateAsyncIOQueue_Generic;
520 QuitAsyncIO = SDL_SYS_QuitAsyncIO_Generic;
521 AsyncIOFromFile = SDL_SYS_AsyncIOFromFile_Generic;
522 }
523 SDL_SetInitialized(&liburing_init, true);
524 }
525}
526
527bool SDL_SYS_CreateAsyncIOQueue(SDL_AsyncIOQueue *queue)
528{
529 MaybeInitializeLibUring();
530 return CreateAsyncIOQueue(queue);
531}
532
533bool SDL_SYS_AsyncIOFromFile(const char *file, const char *mode, SDL_AsyncIO *asyncio)
534{
535 MaybeInitializeLibUring();
536 return AsyncIOFromFile(file, mode, asyncio);
537}
538
539void SDL_SYS_QuitAsyncIO(void)
540{
541 if (SDL_ShouldQuit(&liburing_init)) {
542 QuitAsyncIO();
543 CreateAsyncIOQueue = NULL;
544 QuitAsyncIO = NULL;
545 AsyncIOFromFile = NULL;
546 SDL_SetInitialized(&liburing_init, false);
547 }
548}
549
550#endif // defined HAVE_LIBURING_H
551
diff --git a/contrib/SDL-3.2.8/src/io/n3ds/SDL_iostreamromfs.c b/contrib/SDL-3.2.8/src/io/n3ds/SDL_iostreamromfs.c
new file mode 100644
index 0000000..b42f539
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/io/n3ds/SDL_iostreamromfs.c
@@ -0,0 +1,88 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21
22#include "SDL_iostreamromfs.h"
23
24// Checks if the mode is a kind of reading
25static bool IsReadMode(const char *mode);
26
27// Checks if the file starts with the given prefix
28static bool HasPrefix(const char *file, const char *prefix);
29
30static FILE *TryOpenFile(const char *file, const char *mode);
31static FILE *TryOpenInRomfs(const char *file, const char *mode);
32
33/* Nintendo 3DS applications may embed resources in the executable. The
34 resources are stored in a special read-only partition prefixed with
35 'romfs:/'. As such, when opening a file, we should first try the romfs
36 unless sdmc is specifically mentioned.
37*/
38FILE *N3DS_FileOpen(const char *file, const char *mode)
39{
40 // romfs are read-only
41 if (!IsReadMode(mode)) {
42 return fopen(file, mode);
43 }
44
45 // If the path has an explicit prefix, we skip the guess work
46 if (HasPrefix(file, "romfs:/") || HasPrefix(file, "sdmc:/")) {
47 return fopen(file, mode);
48 }
49
50 return TryOpenFile(file, mode);
51}
52
53static bool IsReadMode(const char *mode)
54{
55 return SDL_strchr(mode, 'r') != NULL;
56}
57
58static bool HasPrefix(const char *file, const char *prefix)
59{
60 return SDL_strncmp(prefix, file, SDL_strlen(prefix)) == 0;
61}
62
63static FILE *TryOpenFile(const char *file, const char *mode)
64{
65 FILE *fp = NULL;
66
67 fp = TryOpenInRomfs(file, mode);
68 if (!fp) {
69 fp = fopen(file, mode);
70 }
71
72 return fp;
73}
74
75FILE *TryOpenInRomfs(const char *file, const char *mode)
76{
77 FILE *fp = NULL;
78 char *prefixed_filepath = NULL;
79
80 if (SDL_asprintf(&prefixed_filepath, "romfs:/%s", file) < 0) {
81 return NULL;
82 }
83
84 fp = fopen(prefixed_filepath, mode);
85
86 SDL_free(prefixed_filepath);
87 return fp;
88}
diff --git a/contrib/SDL-3.2.8/src/io/n3ds/SDL_iostreamromfs.h b/contrib/SDL-3.2.8/src/io/n3ds/SDL_iostreamromfs.h
new file mode 100644
index 0000000..b931390
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/io/n3ds/SDL_iostreamromfs.h
@@ -0,0 +1,28 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifndef SDL_iostreamromfs_h_
24#define SDL_iostreamromfs_h_
25
26FILE *N3DS_FileOpen(const char *file, const char *mode);
27
28#endif // SDL_iostreamromfs_h_
diff --git a/contrib/SDL-3.2.8/src/io/windows/SDL_asyncio_windows_ioring.c b/contrib/SDL-3.2.8/src/io/windows/SDL_asyncio_windows_ioring.c
new file mode 100644
index 0000000..2d38efc
--- /dev/null
+++ b/contrib/SDL-3.2.8/src/io/windows/SDL_asyncio_windows_ioring.c
@@ -0,0 +1,550 @@
1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21
22// The Windows backend uses IoRing for asynchronous i/o, and falls back to
23// the "generic" threadpool implementation if it isn't available or
24// fails for some other reason. IoRing was introduced in Windows 11.
25
26#include "SDL_internal.h"
27#include "../SDL_sysasyncio.h"
28
29#ifdef HAVE_IORINGAPI_H
30
31#include "../../core/windows/SDL_windows.h"
32#include <ioringapi.h>
33
34// Don't know what the lowest usable version is, but this seems safe.
35#define SDL_REQUIRED_IORING_VERSION IORING_VERSION_3
36
37static SDL_InitState ioring_init;
38
39// We could add a whole bootstrap thing like the audio/video/etc subsystems use, but let's keep this simple for now.
40static bool (*CreateAsyncIOQueue)(SDL_AsyncIOQueue *queue);
41static void (*QuitAsyncIO)(void);
42static bool (*AsyncIOFromFile)(const char *file, const char *mode, SDL_AsyncIO *asyncio);
43
44// we never link directly to ioring.
45static const char *ioring_library = "KernelBase.dll";
46static void *ioring_handle = NULL;
47
48#define SDL_IORING_FUNCS \
49 SDL_IORING_FUNC(HRESULT, QueryIoRingCapabilities, (IORING_CAPABILITIES *capabilities)) \
50 SDL_IORING_FUNC(BOOL, IsIoRingOpSupported, (HIORING ioRing, IORING_OP_CODE op)) \
51 SDL_IORING_FUNC(HRESULT, CreateIoRing, (IORING_VERSION ioringVersion, IORING_CREATE_FLAGS flags, UINT32 submissionQueueSize, UINT32 completionQueueSize, HIORING* h)) \
52 SDL_IORING_FUNC(HRESULT, GetIoRingInfo, (HIORING ioRing, IORING_INFO* info)) \
53 SDL_IORING_FUNC(HRESULT, SubmitIoRing, (HIORING ioRing, UINT32 waitOperations, UINT32 milliseconds, UINT32* submittedEntries)) \
54 SDL_IORING_FUNC(HRESULT, CloseIoRing, (HIORING ioRing)) \
55 SDL_IORING_FUNC(HRESULT, PopIoRingCompletion, (HIORING ioRing, IORING_CQE* cqe)) \
56 SDL_IORING_FUNC(HRESULT, SetIoRingCompletionEvent, (HIORING ioRing, HANDLE hEvent)) \
57 SDL_IORING_FUNC(HRESULT, BuildIoRingCancelRequest, (HIORING ioRing, IORING_HANDLE_REF file, UINT_PTR opToCancel, UINT_PTR userData)) \
58 SDL_IORING_FUNC(HRESULT, BuildIoRingReadFile, (HIORING ioRing, IORING_HANDLE_REF fileRef, IORING_BUFFER_REF dataRef, UINT32 numberOfBytesToRead, UINT64 fileOffset, UINT_PTR userData, IORING_SQE_FLAGS sqeFlags)) \
59 SDL_IORING_FUNC(HRESULT, BuildIoRingWriteFile, (HIORING ioRing, IORING_HANDLE_REF fileRef, IORING_BUFFER_REF bufferRef, UINT32 numberOfBytesToWrite, UINT64 fileOffset, FILE_WRITE_FLAGS writeFlags, UINT_PTR userData, IORING_SQE_FLAGS sqeFlags)) \
60 SDL_IORING_FUNC(HRESULT, BuildIoRingFlushFile, (HIORING ioRing, IORING_HANDLE_REF fileRef, FILE_FLUSH_MODE flushMode, UINT_PTR userData, IORING_SQE_FLAGS sqeFlags)) \
61
62#define SDL_IORING_FUNC(ret, fn, args) typedef ret (WINAPI *SDL_fntype_##fn) args;
63SDL_IORING_FUNCS
64#undef SDL_IORING_FUNC
65
66typedef struct SDL_WinIoRingFunctions
67{
68 #define SDL_IORING_FUNC(ret, fn, args) SDL_fntype_##fn fn;
69 SDL_IORING_FUNCS
70 #undef SDL_IORING_FUNC
71} SDL_WinIoRingFunctions;
72
73static SDL_WinIoRingFunctions ioring;
74
75
76typedef struct WinIoRingAsyncIOQueueData
77{
78 SDL_Mutex *sqe_lock;
79 SDL_Mutex *cqe_lock;
80 HANDLE event;
81 HIORING ring;
82 SDL_AtomicInt num_waiting;
83} WinIoRingAsyncIOQueueData;
84
85
86static void UnloadWinIoRingLibrary(void)
87{
88 if (ioring_library) {
89 SDL_UnloadObject(ioring_handle);
90 ioring_library = NULL;
91 }
92 SDL_zero(ioring);
93}
94
95static bool LoadWinIoRingSyms(void)
96{
97 #define SDL_IORING_FUNC(ret, fn, args) { \
98 ioring.fn = (SDL_fntype_##fn) SDL_LoadFunction(ioring_handle, #fn); \
99 if (!ioring.fn) { \
100 return false; \
101 } \
102 }
103 SDL_IORING_FUNCS
104 #undef SDL_IORING_FUNC
105 return true;
106}
107
108static bool LoadWinIoRing(void)
109{
110 bool result = true;
111
112 if (!ioring_handle) {
113 ioring_handle = SDL_LoadObject(ioring_library);
114 if (!ioring_handle) {
115 result = false;
116 // Don't call SDL_SetError(): SDL_LoadObject already did.
117 } else {
118 result = LoadWinIoRingSyms();
119 if (result) {
120 IORING_CAPABILITIES caps;
121 HRESULT hr = ioring.QueryIoRingCapabilities(&caps);
122 if (FAILED(hr)) {
123 result = false;
124 } else if (caps.MaxVersion < SDL_REQUIRED_IORING_VERSION) {
125 result = false;
126 }
127 }
128
129 if (!result) {
130 UnloadWinIoRingLibrary();
131 }
132 }
133 }
134 return result;
135}
136
137static Sint64 ioring_asyncio_size(void *userdata)
138{
139 HANDLE handle = (HANDLE) userdata;
140 LARGE_INTEGER size;
141 if (!GetFileSizeEx(handle, &size)) {
142 WIN_SetError("GetFileSizeEx");
143 return -1;
144 }
145 return (Sint64) size.QuadPart;
146}
147
148// you must hold sqe_lock when calling this!
149static bool ioring_asyncioqueue_queue_task(void *userdata, SDL_AsyncIOTask *task)
150{
151 WinIoRingAsyncIOQueueData *queuedata = (WinIoRingAsyncIOQueueData *) userdata;
152 const HRESULT hr = ioring.SubmitIoRing(queuedata->ring, 0, 0, NULL);
153 return (FAILED(hr) ? WIN_SetErrorFromHRESULT("SubmitIoRing", hr) : true);
154}
155
156static void ioring_asyncioqueue_cancel_task(void *userdata, SDL_AsyncIOTask *task)
157{
158 if (!task->asyncio || !task->asyncio->userdata) {
159 return; // Windows IoRing needs the file handle in question, so we'll just have to let it complete if unknown.
160 }
161
162 SDL_AsyncIOTask *cancel_task = (SDL_AsyncIOTask *) SDL_calloc(1, sizeof (*cancel_task));
163 if (!cancel_task) {
164 return; // oh well, the task can just finish on its own.
165 }
166
167 WinIoRingAsyncIOQueueData *queuedata = (WinIoRingAsyncIOQueueData *) userdata;
168 HANDLE handle = (HANDLE) task->asyncio->userdata;
169 IORING_HANDLE_REF href = IoRingHandleRefFromHandle(handle);
170
171 // have to hold a lock because otherwise two threads could get_sqe and submit while one request isn't fully set up.
172 SDL_LockMutex(queuedata->sqe_lock);
173 const HRESULT hr = ioring.BuildIoRingCancelRequest(queuedata->ring, href, (UINT_PTR) task, (UINT_PTR) cancel_task);
174 if (FAILED(hr)) {
175 SDL_UnlockMutex(queuedata->sqe_lock);
176 SDL_free(cancel_task); // oh well, the task can just finish on its own.
177 return;
178 }
179
180 cancel_task->app_userdata = task;
181 ioring_asyncioqueue_queue_task(userdata, task);
182 SDL_UnlockMutex(queuedata->sqe_lock);
183}
184
185static SDL_AsyncIOTask *ProcessCQE(WinIoRingAsyncIOQueueData *queuedata, IORING_CQE *cqe)
186{
187 if (!cqe) {
188 return NULL;
189 }
190
191 SDL_AsyncIOTask *task = (SDL_AsyncIOTask *) cqe->UserData;
192 if (task) { // can be NULL if this was just a wakeup message, a NOP, etc.
193 if (!task->queue) { // We leave `queue` blank to signify this was a task cancellation.
194 SDL_AsyncIOTask *cancel_task = task;
195 task = (SDL_AsyncIOTask *) cancel_task->app_userdata;
196 SDL_free(cancel_task);
197 if (SUCCEEDED(cqe->ResultCode)) { // cancel was successful?
198 task->result = SDL_ASYNCIO_CANCELED;
199 } else {
200 task = NULL; // it already finished or was too far along to cancel, so we'll pick up the actual results later.
201 }
202 } else if (FAILED(cqe->ResultCode)) {
203 task->result = SDL_ASYNCIO_FAILURE;
204 // !!! FIXME: fill in task->error.
205 } else {
206 if ((task->type == SDL_ASYNCIO_TASK_WRITE) && (((Uint64) cqe->Information) < task->requested_size)) {
207 task->result = SDL_ASYNCIO_FAILURE; // it's always a failure on short writes.
208 }
209
210 // don't explicitly mark it as COMPLETE; that's the default value and a linked task might have failed in an earlier operation and this would overwrite it.
211
212 if ((task->type == SDL_ASYNCIO_TASK_READ) || (task->type == SDL_ASYNCIO_TASK_WRITE)) {
213 task->result_size = (Uint64) cqe->Information;
214 }
215 }
216
217 // we currently send all close operations through as flushes, requested or not, so the actually closing is (in theory) fast. We do that here.
218 // if a later IoRing interface version offers an asynchronous close operation, revisit this to only flush if requested, like we do in the Linux io_uring code.
219 if (task->type == SDL_ASYNCIO_TASK_CLOSE) {
220 SDL_assert(task->asyncio != NULL);
221 SDL_assert(task->asyncio->userdata != NULL);
222 HANDLE handle = (HANDLE) task->asyncio->userdata;
223 if (!CloseHandle(handle)) {
224 task->result = SDL_ASYNCIO_FAILURE; // shrug.
225 }
226 }
227 }
228
229 return task;
230}
231
232static SDL_AsyncIOTask *ioring_asyncioqueue_get_results(void *userdata)
233{
234 WinIoRingAsyncIOQueueData *queuedata = (WinIoRingAsyncIOQueueData *) userdata;
235
236 // unlike liburing's io_uring_peek_cqe(), it's possible PopIoRingCompletion() is thread safe, but for now we wrap it in a mutex just in case.
237 SDL_LockMutex(queuedata->cqe_lock);
238 IORING_CQE cqe;
239 const HRESULT hr = ioring.PopIoRingCompletion(queuedata->ring, &cqe);
240 SDL_UnlockMutex(queuedata->cqe_lock);
241
242 if ((hr == S_FALSE) || FAILED(hr)) {
243 return NULL; // nothing available at the moment.
244 }
245
246 return ProcessCQE(queuedata, &cqe);
247}
248
249static SDL_AsyncIOTask *ioring_asyncioqueue_wait_results(void *userdata, Sint32 timeoutMS)
250{
251 WinIoRingAsyncIOQueueData *queuedata = (WinIoRingAsyncIOQueueData *) userdata;
252
253 // the event only signals when the IoRing moves from empty to non-empty, so you have to try a (non-blocking) get_results first or risk eternal hangs.
254 SDL_AsyncIOTask *task = ioring_asyncioqueue_get_results(userdata);
255 if (!task) {
256 SDL_AddAtomicInt(&queuedata->num_waiting, 1);
257 WaitForSingleObject(queuedata->event, (timeoutMS < 0) ? INFINITE : (DWORD) timeoutMS);
258 SDL_AddAtomicInt(&queuedata->num_waiting, -1);
259
260 // (we don't care if the wait failed for any reason, as the upcoming get_results will report valid information. We just wanted the wait operation to block.)
261 task = ioring_asyncioqueue_get_results(userdata);
262 }
263
264 return task;
265}
266
267static void ioring_asyncioqueue_signal(void *userdata)
268{
269 WinIoRingAsyncIOQueueData *queuedata = (WinIoRingAsyncIOQueueData *) userdata;
270 const int num_waiting = SDL_GetAtomicInt(&queuedata->num_waiting);
271 for (int i = 0; i < num_waiting; i++) {
272 SetEvent(queuedata->event);
273 }
274}
275
276static void ioring_asyncioqueue_destroy(void *userdata)
277{
278 WinIoRingAsyncIOQueueData *queuedata = (WinIoRingAsyncIOQueueData *) userdata;
279 ioring.CloseIoRing(queuedata->ring);
280 CloseHandle(queuedata->event);
281 SDL_DestroyMutex(queuedata->sqe_lock);
282 SDL_DestroyMutex(queuedata->cqe_lock);
283 SDL_free(queuedata);
284}
285
286static bool SDL_SYS_CreateAsyncIOQueue_ioring(SDL_AsyncIOQueue *queue)
287{
288 WinIoRingAsyncIOQueueData *queuedata = (WinIoRingAsyncIOQueueData *) SDL_calloc(1, sizeof (*queuedata));
289 if (!queuedata) {
290 return false;
291 }
292
293 HRESULT hr;
294 IORING_CREATE_FLAGS flags;
295
296 SDL_SetAtomicInt(&queuedata->num_waiting, 0);
297
298 queuedata->sqe_lock = SDL_CreateMutex();
299 if (!queuedata->sqe_lock) {
300 goto failed;
301 }
302
303 queuedata->cqe_lock = SDL_CreateMutex();
304 if (!queuedata->cqe_lock) {
305 goto failed;
306 }
307
308 queuedata->event = CreateEventW(NULL, FALSE, FALSE, NULL);
309 if (!queuedata->event) {
310 WIN_SetError("CreateEventW");
311 goto failed;
312 }
313
314 // !!! FIXME: no idea how large the queue should be. Is 128 overkill or too small?
315 flags.Required = IORING_CREATE_REQUIRED_FLAGS_NONE;
316 flags.Advisory = IORING_CREATE_ADVISORY_FLAGS_NONE;
317 hr = ioring.CreateIoRing(SDL_REQUIRED_IORING_VERSION, flags, 128, 128, &queuedata->ring);
318 if (FAILED(hr)) {
319 WIN_SetErrorFromHRESULT("CreateIoRing", hr);
320 goto failed;
321 }
322
323 hr = ioring.SetIoRingCompletionEvent(queuedata->ring, queuedata->event);
324 if (FAILED(hr)) {
325 WIN_SetErrorFromHRESULT("SetIoRingCompletionEvent", hr);
326 goto failed;
327 }
328
329 static const IORING_OP_CODE needed_ops[] = {
330 IORING_OP_NOP,
331 IORING_OP_FLUSH,
332 IORING_OP_READ,
333 IORING_OP_WRITE,
334 IORING_OP_CANCEL
335 };
336
337 for (int i = 0; i < SDL_arraysize(needed_ops); i++) {
338 if (!ioring.IsIoRingOpSupported(queuedata->ring, needed_ops[i])) {
339 SDL_SetError("Created IoRing doesn't support op %u", (unsigned int) needed_ops[i]);
340 goto failed;
341 }
342 }
343
344 static const SDL_AsyncIOQueueInterface SDL_AsyncIOQueue_ioring = {
345 ioring_asyncioqueue_queue_task,
346 ioring_asyncioqueue_cancel_task,
347 ioring_asyncioqueue_get_results,
348 ioring_asyncioqueue_wait_results,
349 ioring_asyncioqueue_signal,
350 ioring_asyncioqueue_destroy
351 };
352
353 SDL_copyp(&queue->iface, &SDL_AsyncIOQueue_ioring);
354 queue->userdata = queuedata;
355 return true;
356
357failed:
358 if (queuedata->ring) {
359 ioring.CloseIoRing(queuedata->ring);
360 }
361 if (queuedata->event) {
362 CloseHandle(queuedata->event);
363 }
364 if (queuedata->sqe_lock) {
365 SDL_DestroyMutex(queuedata->sqe_lock);
366 }
367 if (queuedata->cqe_lock) {
368 SDL_DestroyMutex(queuedata->cqe_lock);
369 }
370 SDL_free(queuedata);
371 return false;
372}
373
374static bool ioring_asyncio_read(void *userdata, SDL_AsyncIOTask *task)
375{
376 // !!! FIXME: UINT32 smaller than requested_size's Uint64. If we overflow it, we could try submitting multiple SQEs
377 // !!! FIXME: and make a note in the task that there are several in sequence.
378 if (task->requested_size > 0xFFFFFFFF) {
379 return SDL_SetError("ioring: i/o task is too large");
380 }
381
382 HANDLE handle = (HANDLE) userdata;
383 WinIoRingAsyncIOQueueData *queuedata = (WinIoRingAsyncIOQueueData *) task->queue->userdata;
384 IORING_HANDLE_REF href = IoRingHandleRefFromHandle(handle);
385 IORING_BUFFER_REF bref = IoRingBufferRefFromPointer(task->buffer);
386
387 // have to hold a lock because otherwise two threads could get_sqe and submit while one request isn't fully set up.
388 SDL_LockMutex(queuedata->sqe_lock);
389 bool retval;
390 const HRESULT hr = ioring.BuildIoRingReadFile(queuedata->ring, href, bref, (UINT32) task->requested_size, task->offset, (UINT_PTR) task, IOSQE_FLAGS_NONE);
391 if (FAILED(hr)) {
392 retval = WIN_SetErrorFromHRESULT("BuildIoRingReadFile", hr);
393 } else {
394 retval = task->queue->iface.queue_task(task->queue->userdata, task);
395 }
396 SDL_UnlockMutex(queuedata->sqe_lock);
397 return retval;
398}
399
400static bool ioring_asyncio_write(void *userdata, SDL_AsyncIOTask *task)
401{
402 // !!! FIXME: UINT32 smaller than requested_size's Uint64. If we overflow it, we could try submitting multiple SQEs
403 // !!! FIXME: and make a note in the task that there are several in sequence.
404 if (task->requested_size > 0xFFFFFFFF) {
405 return SDL_SetError("ioring: i/o task is too large");
406 }
407
408 HANDLE handle = (HANDLE) userdata;
409 WinIoRingAsyncIOQueueData *queuedata = (WinIoRingAsyncIOQueueData *) task->queue->userdata;
410 IORING_HANDLE_REF href = IoRingHandleRefFromHandle(handle);
411 IORING_BUFFER_REF bref = IoRingBufferRefFromPointer(task->buffer);
412
413 // have to hold a lock because otherwise two threads could get_sqe and submit while one request isn't fully set up.
414 SDL_LockMutex(queuedata->sqe_lock);
415 bool retval;
416 const HRESULT hr = ioring.BuildIoRingWriteFile(queuedata->ring, href, bref, (UINT32) task->requested_size, task->offset, 0 /*FILE_WRITE_FLAGS_NONE*/, (UINT_PTR) task, IOSQE_FLAGS_NONE);
417 if (FAILED(hr)) {
418 retval = WIN_SetErrorFromHRESULT("BuildIoRingWriteFile", hr);
419 } else {
420 retval = task->queue->iface.queue_task(task->queue->userdata, task);
421 }
422 SDL_UnlockMutex(queuedata->sqe_lock);
423 return retval;
424}
425
426static bool ioring_asyncio_close(void *userdata, SDL_AsyncIOTask *task)
427{
428 // current IoRing operations don't offer asynchronous closing, but let's assume most of the potential work is flushing to disk, so just do it for everything, explicit flush or not. We'll close when it finishes.
429 HANDLE handle = (HANDLE) userdata;
430 WinIoRingAsyncIOQueueData *queuedata = (WinIoRingAsyncIOQueueData *)task->queue->userdata;
431 IORING_HANDLE_REF href = IoRingHandleRefFromHandle(handle);
432
433 // have to hold a lock because otherwise two threads could get_sqe and submit while one request isn't fully set up.
434 SDL_LockMutex(queuedata->sqe_lock);
435 bool retval;
436 const HRESULT hr = ioring.BuildIoRingFlushFile(queuedata->ring, href, FILE_FLUSH_DEFAULT, (UINT_PTR) task, IOSQE_FLAGS_NONE);
437 if (FAILED(hr)) {
438 retval = WIN_SetErrorFromHRESULT("BuildIoRingFlushFile", hr);
439 } else {
440 retval = task->queue->iface.queue_task(task->queue->userdata, task);
441 }
442 SDL_UnlockMutex(queuedata->sqe_lock);
443 return retval;
444}
445
446static void ioring_asyncio_destroy(void *userdata)
447{
448 // this is only a Win32 file HANDLE, should have been closed elsewhere.
449}
450
451static bool Win32OpenModeFromString(const char *mode, DWORD *access_mode, DWORD *create_mode)
452{
453 // this is exactly the set of strings that SDL_AsyncIOFromFile promises will work.
454 static const struct { const char *str; DWORD amode; WORD cmode; } mappings[] = {
455 { "rb", GENERIC_READ, OPEN_EXISTING },
456 { "wb", GENERIC_WRITE, CREATE_ALWAYS },
457 { "r+b", GENERIC_READ | GENERIC_WRITE, OPEN_EXISTING },
458 { "w+b", GENERIC_READ | GENERIC_WRITE, CREATE_ALWAYS }
459 };
460
461 for (int i = 0; i < SDL_arraysize(mappings); i++) {
462 if (SDL_strcmp(mappings[i].str, mode) == 0) {
463 *access_mode = mappings[i].amode;
464 *create_mode = mappings[i].cmode;
465 return true;
466 }
467 }
468
469 SDL_assert(!"Shouldn't have reached this code");
470 return SDL_SetError("Invalid file open mode");
471}
472
473static bool SDL_SYS_AsyncIOFromFile_ioring(const char *file, const char *mode, SDL_AsyncIO *asyncio)
474{
475 DWORD access_mode, create_mode;
476 if (!Win32OpenModeFromString(mode, &access_mode, &create_mode)) {
477 return false;
478 }
479
480 LPWSTR wstr = WIN_UTF8ToStringW(file);
481 if (!wstr) {
482 return false;
483 }
484
485 HANDLE handle = CreateFileW(wstr, access_mode, FILE_SHARE_READ, NULL, create_mode, FILE_ATTRIBUTE_NORMAL, NULL);
486 SDL_free(wstr);
487 if (!handle) {
488 return WIN_SetError("CreateFileW");
489 }
490
491 static const SDL_AsyncIOInterface SDL_AsyncIOFile_ioring = {
492 ioring_asyncio_size,
493 ioring_asyncio_read,
494 ioring_asyncio_write,
495 ioring_asyncio_close,
496 ioring_asyncio_destroy
497 };
498
499 SDL_copyp(&asyncio->iface, &SDL_AsyncIOFile_ioring);
500
501 asyncio->userdata = (void *) handle;
502 return true;
503}
504
505static void SDL_SYS_QuitAsyncIO_ioring(void)
506{
507 UnloadWinIoRingLibrary();
508}
509
510static void MaybeInitializeWinIoRing(void)
511{
512 if (SDL_ShouldInit(&ioring_init)) {
513 if (LoadWinIoRing()) {
514 CreateAsyncIOQueue = SDL_SYS_CreateAsyncIOQueue_ioring;
515 QuitAsyncIO = SDL_SYS_QuitAsyncIO_ioring;
516 AsyncIOFromFile = SDL_SYS_AsyncIOFromFile_ioring;
517 } else { // can't use ioring? Use the "generic" threadpool implementation instead.
518 CreateAsyncIOQueue = SDL_SYS_CreateAsyncIOQueue_Generic;
519 QuitAsyncIO = SDL_SYS_QuitAsyncIO_Generic;
520 AsyncIOFromFile = SDL_SYS_AsyncIOFromFile_Generic;
521 }
522 SDL_SetInitialized(&ioring_init, true);
523 }
524}
525
526bool SDL_SYS_CreateAsyncIOQueue(SDL_AsyncIOQueue *queue)
527{
528 MaybeInitializeWinIoRing();
529 return CreateAsyncIOQueue(queue);
530}
531
532bool SDL_SYS_AsyncIOFromFile(const char *file, const char *mode, SDL_AsyncIO *asyncio)
533{
534 MaybeInitializeWinIoRing();
535 return AsyncIOFromFile(file, mode, asyncio);
536}
537
538void SDL_SYS_QuitAsyncIO(void)
539{
540 if (SDL_ShouldQuit(&ioring_init)) {
541 QuitAsyncIO();
542 CreateAsyncIOQueue = NULL;
543 QuitAsyncIO = NULL;
544 AsyncIOFromFile = NULL;
545 SDL_SetInitialized(&ioring_init, false);
546 }
547}
548
549#endif // defined HAVE_IORINGAPI_H
550