SDL: Added SDL_FlushIO()

From 6c83491116e0a4db4dd8667367f3a86f929ef1b2 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Fri, 13 Sep 2024 14:13:53 -0700
Subject: [PATCH] Added SDL_FlushIO()

Also added SDL_PROP_IOSTREAM_FILE_DESCRIPTOR_NUMBER and refactored the internal API to be able to create SDL_IOStream objects from native file handles.
---
 include/SDL3/SDL_iostream.h       |  41 +++++-
 src/dynapi/SDL_dynapi.sym         |   1 +
 src/dynapi/SDL_dynapi_overrides.h |   1 +
 src/dynapi/SDL_dynapi_procs.h     |   1 +
 src/file/SDL_iostream.c           | 205 +++++++++++++++++++-----------
 src/file/SDL_iostream_c.h         |  32 +++++
 6 files changed, 205 insertions(+), 76 deletions(-)
 create mode 100644 src/file/SDL_iostream_c.h

diff --git a/include/SDL3/SDL_iostream.h b/include/SDL3/SDL_iostream.h
index b5cb2f03bdf9d..745e35aae1123 100644
--- a/include/SDL3/SDL_iostream.h
+++ b/include/SDL3/SDL_iostream.h
@@ -133,6 +133,17 @@ typedef struct SDL_IOStreamInterface
      */
     size_t (SDLCALL *write)(void *userdata, const void *ptr, size_t size, SDL_IOStatus *status);
 
+    /**
+     *  If the stream is buffering, make sure the data is written out.
+     *
+     *  On failure, you should set `*status` to a value from the
+     *  SDL_IOStatus enum. You do not have to explicitly set this on
+     *  a successful flush.
+     *
+     *  \return SDL_TRUE if successful or SDL_FALSE on write error when flushing data.
+     */
+    SDL_bool (SDLCALL *flush)(void *userdata, SDL_IOStatus *status);
+
     /**
      *  Close and free any allocated resources.
      *
@@ -152,8 +163,8 @@ typedef struct SDL_IOStreamInterface
  * the code using this interface should be updated to handle the old version.
  */
 SDL_COMPILE_TIME_ASSERT(SDL_IOStreamInterface_SIZE,
-    (sizeof(void *) == 4 && sizeof(SDL_IOStreamInterface) == 24) ||
-    (sizeof(void *) == 8 && sizeof(SDL_IOStreamInterface) == 48));
+    (sizeof(void *) == 4 && sizeof(SDL_IOStreamInterface) == 28) ||
+    (sizeof(void *) == 8 && sizeof(SDL_IOStreamInterface) == 56));
 
 /**
  * The read/write operation structure.
@@ -233,6 +244,7 @@ typedef struct SDL_IOStream SDL_IOStream;
  *   than your app, trying to use this pointer will almost certainly result in
  *   a crash! This is mostly a problem on Windows; make sure you build SDL and
  *   your app with the same compiler and settings to avoid it.
+ * - `SDL_PROP_IOSTREAM_FILE_DESCRIPTOR_NUMBER`: a file descriptor that this SDL_IOStream is using to access the filesystem.
  * - `SDL_PROP_IOSTREAM_ANDROID_AASSET_POINTER`: a pointer, that can be cast
  *   to an Android NDK `AAsset *`, that this SDL_IOStream is using to access
  *   the filesystem. If SDL used some other method to access the filesystem,
@@ -247,6 +259,7 @@ typedef struct SDL_IOStream SDL_IOStream;
  * \since This function is available since SDL 3.0.0.
  *
  * \sa SDL_CloseIO
+ * \sa SDL_FlushIO
  * \sa SDL_ReadIO
  * \sa SDL_SeekIO
  * \sa SDL_TellIO
@@ -256,6 +269,7 @@ extern SDL_DECLSPEC SDL_IOStream * SDLCALL SDL_IOFromFile(const char *file, cons
 
 #define SDL_PROP_IOSTREAM_WINDOWS_HANDLE_POINTER    "SDL.iostream.windows.handle"
 #define SDL_PROP_IOSTREAM_STDIO_FILE_POINTER        "SDL.iostream.stdio.file"
+#define SDL_PROP_IOSTREAM_FILE_DESCRIPTOR_NUMBER    "SDL.iostream.file_descriptor"
 #define SDL_PROP_IOSTREAM_ANDROID_AASSET_POINTER    "SDL.iostream.android.aasset"
 
 /**
@@ -282,6 +296,7 @@ extern SDL_DECLSPEC SDL_IOStream * SDLCALL SDL_IOFromFile(const char *file, cons
  *
  * \sa SDL_IOFromConstMem
  * \sa SDL_CloseIO
+ * \sa SDL_FlushIO
  * \sa SDL_ReadIO
  * \sa SDL_SeekIO
  * \sa SDL_TellIO
@@ -465,8 +480,7 @@ extern SDL_DECLSPEC Sint64 SDLCALL SDL_GetIOSize(SDL_IOStream *context);
  *               negative.
  * \param whence any of `SDL_IO_SEEK_SET`, `SDL_IO_SEEK_CUR`,
  *               `SDL_IO_SEEK_END`.
- * \returns the final offset in the data stream after the seek or a negative
- *          error code on failure; call SDL_GetError() for more information.
+ * \returns the final offset in the data stream after the seek or -1 on failure; call SDL_GetError() for more information.
  *
  * \since This function is available since SDL 3.0.0.
  *
@@ -539,6 +553,7 @@ extern SDL_DECLSPEC size_t SDLCALL SDL_ReadIO(SDL_IOStream *context, void *ptr,
  * \sa SDL_IOprintf
  * \sa SDL_ReadIO
  * \sa SDL_SeekIO
+ * \sa SDL_FlushIO
  * \sa SDL_GetIOStatus
  */
 extern SDL_DECLSPEC size_t SDLCALL SDL_WriteIO(SDL_IOStream *context, const void *ptr, size_t size);
@@ -580,6 +595,22 @@ extern SDL_DECLSPEC size_t SDLCALL SDL_IOprintf(SDL_IOStream *context, SDL_PRINT
  */
 extern SDL_DECLSPEC size_t SDLCALL SDL_IOvprintf(SDL_IOStream *context, SDL_PRINTF_FORMAT_STRING const char *fmt, va_list ap) SDL_PRINTF_VARARG_FUNCV(2);
 
+/**
+ * Flush any buffered data in the stream.
+ *
+ * This function makes sure that any buffered data is written to the stream. Normally this isn't necessary but if the stream is a pipe or socket it guarantees that any pending data is sent.
+ *
+ * \param context SDL_IOStream structure to flush.
+ * \returns SDL_TRUE on success or SDL_FALSE on failure; call SDL_GetError()
+ *          for more information.
+ *
+ * \since This function is available since SDL 3.0.0.
+ *
+ * \sa SDL_OpenIO
+ * \sa SDL_WriteIO
+ */
+extern SDL_DECLSPEC SDL_bool SDLCALL SDL_FlushIO(SDL_IOStream *context);
+
 /**
  * Load all the data from an SDL data stream.
  *
@@ -590,7 +621,7 @@ extern SDL_DECLSPEC size_t SDLCALL SDL_IOvprintf(SDL_IOStream *context, SDL_PRIN
  * The data should be freed with SDL_free().
  *
  * \param src the SDL_IOStream to read all available data from.
- * \param datasize if not NULL, will store the number of bytes read.
+ * \param datasize a pointer filled in with the number of bytes read, may be NULL.
  * \param closeio if SDL_TRUE, calls SDL_CloseIO() on `src` before returning,
  *                even in the case of an error.
  * \returns the data or NULL on failure; call SDL_GetError() for more
diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym
index 1b5f1a40a3bf5..2b906b96d1161 100644
--- a/src/dynapi/SDL_dynapi.sym
+++ b/src/dynapi/SDL_dynapi.sym
@@ -169,6 +169,7 @@ SDL3_0.0.0 {
     SDL_FlushAudioStream;
     SDL_FlushEvent;
     SDL_FlushEvents;
+    SDL_FlushIO;
     SDL_FlushRenderer;
     SDL_GDKResumeGPU;
     SDL_GDKSuspendComplete;
diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h
index 8bad28dbdee6c..212ccf4de0d29 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -194,6 +194,7 @@
 #define SDL_FlushAudioStream SDL_FlushAudioStream_REAL
 #define SDL_FlushEvent SDL_FlushEvent_REAL
 #define SDL_FlushEvents SDL_FlushEvents_REAL
+#define SDL_FlushIO SDL_FlushIO_REAL
 #define SDL_FlushRenderer SDL_FlushRenderer_REAL
 #define SDL_GDKResumeGPU SDL_GDKResumeGPU_REAL
 #define SDL_GDKSuspendComplete SDL_GDKSuspendComplete_REAL
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index 250089ab4c216..fad787ac34c32 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -214,6 +214,7 @@ SDL_DYNAPI_PROC(SDL_bool,SDL_FlipSurface,(SDL_Surface *a, SDL_FlipMode b),(a,b),
 SDL_DYNAPI_PROC(SDL_bool,SDL_FlushAudioStream,(SDL_AudioStream *a),(a),return)
 SDL_DYNAPI_PROC(void,SDL_FlushEvent,(Uint32 a),(a),)
 SDL_DYNAPI_PROC(void,SDL_FlushEvents,(Uint32 a, Uint32 b),(a,b),)
+SDL_DYNAPI_PROC(SDL_bool,SDL_FlushIO,(SDL_IOStream *a),(a),return)
 SDL_DYNAPI_PROC(SDL_bool,SDL_FlushRenderer,(SDL_Renderer *a),(a),return)
 SDL_DYNAPI_PROC(void,SDL_GDKResumeGPU,(SDL_GPUDevice *a),(a),)
 SDL_DYNAPI_PROC(void,SDL_GDKSuspendComplete,(void),(),)
diff --git a/src/file/SDL_iostream.c b/src/file/SDL_iostream.c
index e00438bde4e1a..0d9f39fbb055a 100644
--- a/src/file/SDL_iostream.c
+++ b/src/file/SDL_iostream.c
@@ -58,11 +58,12 @@ struct SDL_IOStream
 
 typedef struct IOStreamWindowsData
 {
-    bool append;
     HANDLE h;
     void *data;
     size_t size;
     size_t left;
+    bool append;
+    bool autoclose;
 } IOStreamWindowsData;
 
 
@@ -73,7 +74,7 @@ typedef struct IOStreamWindowsData
 
 #define READAHEAD_BUFFER_SIZE 1024
 
-static bool SDLCALL windows_file_open(IOStreamWindowsData *iodata, const char *filename, const char *mode)
+static HANDLE SDLCALL windows_file_open(const char *filename, const char *mode)
 {
 #if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
     UINT old_error_mode;
@@ -83,9 +84,6 @@ static bool SDLCALL windows_file_open(IOStreamWindowsData *iodata, const char *f
     DWORD must_exist, truncate;
     int a_mode;
 
-    SDL_zerop(iodata);
-    iodata->h = INVALID_HANDLE_VALUE; // mark this as unusable
-
     // "r" = reading, file must exist
     // "w" = writing, truncate existing, file may not exist
     // "r+"= reading or writing, file must exist
@@ -100,18 +98,13 @@ static bool SDLCALL windows_file_open(IOStreamWindowsData *iodata, const char *f
     w_right = (a_mode || SDL_strchr(mode, '+') || truncate) ? GENERIC_WRITE : 0;
 
     if (!r_right && !w_right) {
-        return false; // inconsistent mode
+        return INVALID_HANDLE_VALUE; // inconsistent mode
     }
     // failed (invalid call)
 
-    iodata->data = (char *)SDL_malloc(READAHEAD_BUFFER_SIZE);
-    if (!iodata->data) {
-        return false;
-    }
 #if !defined(SDL_PLATFORM_XBOXONE) && !defined(SDL_PLATFORM_XBOXSERIES)
     // Do not open a dialog box if failure
-    old_error_mode =
-        SetErrorMode(SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS);
+    old_error_mode = SetErrorMode(SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS);
 #endif
 
     {
@@ -132,15 +125,9 @@ static bool SDLCALL windows_file_open(IOStreamWindowsData *iodata, const char *f
 #endif
 
     if (h == INVALID_HANDLE_VALUE) {
-        SDL_free(iodata->data);
-        iodata->data = NULL;
         SDL_SetError("Couldn't open %s", filename);
-        return false; // failed (CreateFile)
     }
-    iodata->h = h;
-    iodata->append = a_mode ? true : false;
-
-    return true; // ok
+    return h;
 }
 
 static Sint64 SDLCALL windows_file_size(void *userdata)
@@ -184,7 +171,7 @@ static Sint64 SDLCALL windows_file_seek(void *userdata, Sint64 offset, SDL_IOWhe
 
     windowsoffset.QuadPart = offset;
     if (!SetFilePointerEx(iodata->h, windowsoffset, &windowsoffset, windowswhence)) {
-        return WIN_SetError("windows_file_seek");
+        return WIN_SetError("Error seeking in datastream");
     }
     return windowsoffset.QuadPart;
 }
@@ -215,7 +202,9 @@ static size_t SDLCALL windows_file_read(void *userdata, void *ptr, size_t size,
 
     if (total_need < READAHEAD_BUFFER_SIZE) {
         if (!ReadFile(iodata->h, iodata->data, READAHEAD_BUFFER_SIZE, &bytes, NULL)) {
-            SDL_SetError("Error reading from datastream");
+            if (GetLastError() != ERROR_HANDLE_EOF && GetLastError() != ERROR_BROKEN_PIPE) {
+                WIN_SetError("Error reading from datastream");
+            }
             return 0;
         }
         read_ahead = SDL_min(total_need, bytes);
@@ -225,7 +214,9 @@ static size_t SDLCALL windows_file_read(void *userdata, void *ptr, size_t size,
         total_read += read_ahead;
     } else {
         if (!ReadFile(iodata->h, ptr, (DWORD)total_need, &bytes, NULL)) {
-            SDL_SetError("Error reading from datastream");
+            if (GetLastError() != ERROR_HANDLE_EOF && GetLastError() != ERROR_BROKEN_PIPE) {
+                WIN_SetError("Error reading from datastream");
+            }
             return 0;
         }
         total_read += bytes;
@@ -241,7 +232,7 @@ static size_t SDLCALL windows_file_write(void *userdata, const void *ptr, size_t
 
     if (iodata->left) {
         if (!SetFilePointer(iodata->h, -(LONG)iodata->left, NULL, FILE_CURRENT)) {
-            SDL_SetError("Error seeking in datastream");
+            WIN_SetError("Error seeking in datastream");
             return 0;
         }
         iodata->left = 0;
@@ -252,13 +243,13 @@ static size_t SDLCALL windows_file_write(void *userdata, const void *ptr, size_t
         LARGE_INTEGER windowsoffset;
         windowsoffset.QuadPart = 0;
         if (!SetFilePointerEx(iodata->h, windowsoffset, &windowsoffset, FILE_END)) {
-            SDL_SetError("Error seeking in datastream");
+            WIN_SetError("Error seeking in datastream");
             return 0;
         }
     }
 
     if (!WriteFile(iodata->h, ptr, (DWORD)total_bytes, &bytes, NULL)) {
-        SDL_SetError("Error writing to datastream");
+        WIN_SetError("Error writing to datastream");
         return 0;
     }
 
@@ -269,13 +260,58 @@ static SDL_bool SDLCALL windows_file_close(void *userdata)
 {
     IOStreamWindowsData *iodata = (IOStreamWindowsData *) userdata;
     if (iodata->h != INVALID_HANDLE_VALUE) {
-        CloseHandle(iodata->h);
+        if (iodata->autoclose) {
+            CloseHandle(iodata->h);
+        }
         iodata->h = INVALID_HANDLE_VALUE; // to be sure
     }
     SDL_free(iodata->data);
     SDL_free(iodata);
     return true;
 }
+
+SDL_IOStream *SDL_IOFromHandle(HANDLE handle, const char *mode, bool autoclose)
+{
+    IOStreamWindowsData *iodata = (IOStreamWindowsData *) SDL_calloc(1, sizeof (*iodata));
+    if (!iodata) {
+        if (autoclose) {
+            CloseHandle(handle);
+        }
+        return NULL;
+    }
+
+    SDL_IOStreamInterface iface;
+    SDL_INIT_INTERFACE(&iface);
+    if (GetFileType(handle) == FILE_TYPE_DISK) {
+        iface.size = windows_file_size;
+        iface.seek = windows_file_seek;
+    }
+    iface.read = windows_file_read;
+    iface.write = windows_file_write;
+    iface.close = windows_file_close;
+
+    iodata->h = handle;
+    iodata->append = (SDL_strchr(mode, 'a') != NULL);
+    iodata->autoclose = autoclose;
+
+    iodata->data = (char *)SDL_malloc(READAHEAD_BUFFER_SIZE);
+    if (!iodata->data) {
+        iface.close(iodata);
+        return NULL;
+    }
+
+    SDL_IOStream *iostr = SDL_OpenIO(&iface, iodata);
+    if (!iostr) {
+        iface.close(iodata);
+    } else {
+        const SDL_PropertiesID props = SDL_GetIOProperties(iostr);
+        if (props) {
+            SDL_SetPointerProperty(props, SDL_PROP_IOSTREAM_WINDOWS_HANDLE_POINTER, iodata->h);
+        }
+    }
+
+    return iostr;
+}
 #endif // defined(SDL_PLATFORM_WINDOWS)
 
 #if defined(HAVE_STDIO_H) && !defined(SDL_PLATFORM_WINDOWS)
@@ -357,12 +393,12 @@ static Sint64 SDLCALL stdio_seek(void *userdata, Sint64 offset, SDL_IOWhence whe
     if (is_noop || fseek(iodata->fp, (fseek_off_t)offset, stdiowhence) == 0) {
         const Sint64 pos = ftell(iodata->fp);
         if (pos < 0) {
-            SDL_SetError("Couldn't get stream offset");
+            SDL_SetError("Couldn't get stream offset: %s", strerror(errno));
             return -1;
         }
         return pos;
     }
-    SDL_SetError("Error seeking in datastream");
+    SDL_SetError("Error seeking in datastream: %s", strerror(errno));
     return -1;
 }
 
@@ -371,7 +407,12 @@ static size_t SDLCALL stdio_read(void *userdata, void *ptr, size_t size, SDL_IOS
     IOStreamStdioData *iodata = (IOStreamStdioData *) userdata;
     const size_t bytes = fread(ptr, 1, size, iodata->fp);
     if (bytes == 0 && ferror(iodata->fp)) {
-        SDL_SetError("Error reading from datastream");
+        if (errno == EAGAIN) {
+            *status = SDL_IO_STATUS_NOT_READY;
+            clearerr(iodata->fp);
+        } else {
+            SDL_SetError("Error reading from datastream: %s", strerror(errno));
+        }
     }
     return bytes;
 }
@@ -381,28 +422,50 @@ static size_t SDLCALL stdio_write(void *userdata, const void *ptr, size_t size,
     IOStreamStdioData *iodata = (IOStreamStdioData *) userdata;
     const size_t bytes = fwrite(ptr, 1, size, iodata->fp);
     if (bytes == 0 && ferror(iodata->fp)) {
-        SDL_SetError("Error writing to datastream");
+        if (errno == EAGAIN) {
+            *status = SDL_IO_STATUS_NOT_READY;
+            clearerr(iodata->fp);
+        } else {
+            SDL_SetError("Error writing to datastream: %s", strerror(errno));
+        }
     }
     return bytes;
 }
 
+static SDL_bool SDLCALL stdio_flush(void *userdata, SDL_IOStatus *status)
+{
+    IOStreamStdioData *iodata = (IOStreamStdioData *) userdata;
+    if (fflush(iodata->fp) != 0) {
+        if (errno == EAGAIN) {
+            *status = SDL_IO_STATUS_NOT_READY;
+            return false;
+        } else {
+            return SDL_SetError("Error flushing datastream: %s", strerror(errno));
+        }
+    }
+    return true;
+}
+
 static SDL_bool SDLCALL stdio_close(void *userdata)
 {
     IOStreamStdioData *iodata = (IOStreamStdioData *) userdata;
     bool status = true;
     if (iodata->autoclose) {
         if (fclose(iodata->fp) != 0) {
-            status = SDL_SetError("Error writing to datastream");
+            status = SDL_SetError("Error writing to datastream: %s", strerror(errno));
         }
     }
     SDL_free(iodata);
     return status;
 }
 
-static SDL_IOStream *SDL_IOFromFP(FILE *fp, bool autoclose)
+SDL_IOStream *SDL_IOFromFP(FILE *fp, bool autoclose)
 {
-    IOStreamStdioData *iodata = (IOStreamStdioData *) SDL_malloc(sizeof (*iodata));
+    IOStreamStdioData *iodata = (IOStreamStdioData *) SDL_calloc(1, sizeof (*iodata));
     if (!iodata) {
+        if (autoclose) {
+           fclose(fp);
+        }
         return NULL;
     }
 
@@ -412,6 +475,7 @@ static SDL_IOStream *SDL_IOFromFP(FILE *fp, bool autoclose)
     iface.seek = stdio_seek;
     iface.read = stdio_read;
     iface.write = stdio_write;
+    iface.flush = stdio_flush;
     iface.close = stdio_close;
 
     iodata->fp = fp;
@@ -424,6 +488,7 @@ static SDL_IOStream *SDL_IOFromFP(FILE *fp, bool autoclose)
         const SDL_PropertiesID props = SDL_GetIOProperties(iostr);
         if (props) {
             SDL_SetPointerProperty(props, SDL_PROP_IOSTREAM_STDIO_FILE_POINTER, fp);
+            SDL_SetNumberProperty(props, SDL_PROP_IOSTREAM_FILE_DESCRIPTOR_NUMBER, fileno(fp));
         }
     }
 
@@ -542,7 +607,7 @@ SDL_IOStream *SDL_IOFromFile(const char *file, const char *mode)
                 SDL_SetError("%s is not a regular file or pipe", file);
                 return NULL;
             }
-            return SDL_IOFromFP(fp, 1);
+            return SDL_IOFromFP(fp, true);
         }
     } else if (SDL_strncmp(file, "content://", 10) == 0) {
         // Try opening content:// URI
@@ -573,7 +638,7 @@ SDL_IOStream *SDL_IOFromFile(const char *file, const char *mode)
                     SDL_SetError("%s is not a regular file or pipe", path);
                     return NULL;
                 }
-                return SDL_IOFromFP(fp, 1);
+                return SDL_IOFromFP(fp, true);
             }
         }
     }
@@ -605,32 +670,9 @@ SDL_IOStream *SDL_IOFromFile(const char *file, const char *mode)
     }
 
 #elif defined(SDL_PLATFORM_WINDOWS)
-    IOStreamWindowsData *iodata = (IOStreamWindowsData *) SDL_malloc(sizeof (*iodata));
-    if (!iodata) {
-        return NULL;
-    }
-
-    if (!windows_file_open(iodata, file, mode)) {
-        windows_file_close(iodata);
-        return NULL;
-    }
-
-    SDL_IOStreamInterface iface;
-    SDL_INIT_INTERFACE(&iface);
-    iface.size = windows_file_size;
-    iface.seek = windows_file_seek;
-    iface.read = windows_file_read;
-    iface.write = windows_file_write;
-    iface.close = windows_file_close;
-
-    iostr = SDL_OpenIO(&iface, iodata);
-    if (!iostr) {
-        windows_file_close(iodata);
-    } else {
-        const SDL_PropertiesID props = SDL_GetIOProperties(iostr);
-        if (props) {
-            SDL_SetPointerProperty(props, SDL_PROP_IOSTREAM_WINDOWS_HANDLE_POINTER, iodata->h);
-        }
+    HANDLE handle = windows_file_open(file, mode);
+    if (handle != INVALID_HANDLE_VALUE) {
+        iostr = SDL_IOFromHandle(handle, mode, true);
     }
 
 #elif defined(HAVE_STDIO_H)
@@ -669,7 +711,7 @@ SDL_IOStream *SDL_IOFromMem(void *mem, size_t size)
         return NULL;
     }
 
-    IOStreamMemData *iodata = (IOStreamMemData *) SDL_malloc(sizeof (*iodata));
+    IOStreamMemData *iodata = (IOStreamMemData *) SDL_calloc(1, sizeof (*iodata));
     if (!iodata) {
         return NULL;
     }
@@ -703,7 +745,7 @@ SDL_IOStream *SDL_IOFromConstMem(const void *mem, size_t size)
         return NULL;
     }
 
-    IOStreamMemData *iodata = (IOStreamMemData *) SDL_malloc(sizeof (*iodata));
+    IOStreamMemData *iodata = (IOStreamMemData *) SDL_calloc(1, sizeof (*iodata));
     if (!iodata) {
         return NULL;
     }
@@ -803,7 +845,7 @@ static SDL_bool SDLCALL dynamic_mem_close(void *userdata)
 
 SDL_IOStream *SDL_IOFromDynamicMem(void)
 {
-    IOStreamDynamicMemData *iodata = (IOStreamDynamicMemData *) SDL_malloc(sizeof (*iodata));
+    IOStreamDynamicMemData *iodata = (IOStreamDynamicMemData *) SDL_calloc(1, sizeof (*iodata));
     if (!iodata) {
         return NULL;
     }
@@ -816,11 +858,6 @@ SDL_IOStream *SDL_IOFromDynamicMem(void)
     iface.write = dynamic_mem_write;
     iface.close = dynamic_mem_close;
 
-    iodata->data.base = NULL;
-    iodata->data.here = NULL;
-    iodata->data.stop = NULL;
-    iodata->end = NULL;
-
     SDL_IOStream *iostr = SDL_OpenIO(&iface, iodata);
     if (iostr) {
         iodata->stream = iostr;
@@ -922,6 +959,10 @@ void *SDL_LoadFile_IO(SDL_IOStream *src, size_t *datasize, SDL_bool closeio)
         if (size_read > 0) {
             size_total += size_read;
             continue;
+        } else if (SDL_GetIOStatus(src) == SDL_IO_STATUS_NOT_READY) {
+            // Wait for the stream to be ready
+            SDL_Delay(1);
+            continue;
         }
 
         // The stream status will remain set for the caller to check
@@ -981,9 +1022,11 @@ Sint64 SDL_GetIOSize(SDL_IOStream *context)
 Sint64 SDL_SeekIO(SDL_IOStream *context, Sint64 offset, SDL_IOWhence whence)
 {
     if (!context) {
-        return SDL_InvalidParamError("context");
+        SDL_InvalidParamError("context");
+        return -1;
     } else if (!context->iface.seek) {
-        return SDL_Unsupported();
+        SDL_Unsupported();
+        return -1;
     }
     return context->iface.seek(context->userdata, offset, whence);
 }
@@ -1086,6 +1129,26 @@ size_t SDL_IOvprintf(SDL_IOStream *context, SDL_PRINTF_FORMAT_STRING const char
     return bytes;
 }
 
+SDL_bool SDL_FlushIO(SDL_IOStream *context)
+{
+    bool result = true;
+
+    if (!context) {
+        return SDL_InvalidParamError("context");
+    }
+
+    context->status = SDL_IO_STATUS_READY;
+    SDL_ClearError();
+
+    if (context->iface.flush) {
+        result = context->iface.flush(context->userdata, &context->status);
+    }
+    if (!result && (context->status == SDL_IO_STATUS_READY)) {
+        context->status = SDL_IO_STATUS_ERROR;
+    }
+    return result;
+}
+
 // Functions for dynamically reading and writing endian-specific values
 
 SDL_bool SDL_ReadU8(SDL_IOStream *src, Uint8 *value)
diff --git a/src/file/SDL_iostream_c.h b/src/file/SDL_iostream_c.h
new file mode 100644
index 0000000000000..005eb72bcc7ac
--- /dev/null
+++ b/src/file/SDL_iostream_c.h
@@ -0,0 +1,32 @@
+/*
+  Simple DirectMedia Layer
+  Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+*/
+#include "SDL_internal.h"
+
+#ifndef SDL_iostream_c_h_
+#define SDL_iostream_c_h_
+
+#if defined(SDL_PLATFORM_WINDOWS)
+SDL_IOStream *SDL_IOFromHandle(HANDLE handle, const char *mode, bool autoclose);
+#elif defined(HAVE_STDIO_H)
+extern SDL_IOStream *SDL_IOFromFP(FILE *fp, bool autoclose);
+#endif
+
+#endif // SDL_iostream_c_h_