diff --git a/include/SDL3/SDL_iostream.h b/include/SDL3/SDL_iostream.h index 8bdfc1dbd3..677702e2a6 100644 --- a/include/SDL3/SDL_iostream.h +++ b/include/SDL3/SDL_iostream.h @@ -111,7 +111,7 @@ typedef struct SDL_IOStreamInterface /** * Read up to `size` bytes from the data stream to the area pointed - * at by `ptr`. + * at by `ptr`. `size` will always be > 0. * * On an incomplete read, you should set `*status` to a value from the * SDL_IOStatus enum. You do not have to explicitly set this on @@ -123,7 +123,7 @@ typedef struct SDL_IOStreamInterface /** * Write exactly `size` bytes from the area pointed at by `ptr` - * to data stream. + * to data stream. `size` will always be > 0. * * On an incomplete write, you should set `*status` to a value from the * SDL_IOStatus enum. You do not have to explicitly set this on @@ -580,6 +580,10 @@ extern SDL_DECLSPEC Sint64 SDLCALL SDL_TellIO(SDL_IOStream *context); * the stream is not at EOF, SDL_GetIOStatus() will return a different error * value and SDL_GetError() will offer a human-readable message. * + * A request for zero bytes on a valid stream will return zero immediately + * without accessing the stream, so the stream status (EOF, err, etc) will + * not change. + * * \param context a pointer to an SDL_IOStream structure. * \param ptr a pointer to a buffer to read data into. * \param size the number of bytes to read from the data source. @@ -609,6 +613,10 @@ extern SDL_DECLSPEC size_t SDLCALL SDL_ReadIO(SDL_IOStream *context, void *ptr, * recoverable, such as a non-blocking write that can simply be retried later, * or a fatal error. * + * A request for zero bytes on a valid stream will return zero immediately + * without accessing the stream, so the stream status (EOF, err, etc) will + * not change. + * * \param context a pointer to an SDL_IOStream structure. * \param ptr a pointer to a buffer containing data to write. * \param size the number of bytes to write. diff --git a/src/core/android/SDL_android.c b/src/core/android/SDL_android.c index e0745a0a57..6e9d2e7389 100644 --- a/src/core/android/SDL_android.c +++ b/src/core/android/SDL_android.c @@ -1827,7 +1827,10 @@ size_t Android_JNI_FileRead(void *userdata, void *buffer, size_t size, SDL_IOSta const int bytes = AAsset_read((AAsset *)userdata, buffer, size); if (bytes < 0) { SDL_SetError("AAsset_read() failed"); + *status = SDL_IO_STATUS_ERROR; return 0; + } else if (bytes < size) { + *status = SDL_IO_STATUS_EOF; } return (size_t)bytes; } @@ -1835,6 +1838,7 @@ size_t Android_JNI_FileRead(void *userdata, void *buffer, size_t size, SDL_IOSta size_t Android_JNI_FileWrite(void *userdata, const void *buffer, size_t size, SDL_IOStatus *status) { SDL_SetError("Cannot write to Android package filesystem"); + *status = SDL_IO_STATUS_ERROR; return 0; } diff --git a/src/io/SDL_iostream.c b/src/io/SDL_iostream.c index 7c956baf5f..2c1544144f 100644 --- a/src/io/SDL_iostream.c +++ b/src/io/SDL_iostream.c @@ -220,15 +220,17 @@ static size_t SDLCALL windows_file_read(void *userdata, void *ptr, size_t size, switch (error) { case ERROR_BROKEN_PIPE: case ERROR_HANDLE_EOF: + *status = SDL_IO_STATUS_EOF; break; case ERROR_NO_DATA: *status = SDL_IO_STATUS_NOT_READY; break; default: + *status = SDL_IO_STATUS_ERROR; WIN_SetError("Error reading from datastream"); break; } - return 0; + return 0; // !!! FIXME: this should return the bytes read from any readahead we finished out before this (the `iodata->left > 0` code above). In that case, fail on the next read. } read_ahead = SDL_min(total_need, bytes); SDL_memcpy(ptr, iodata->data, read_ahead); @@ -241,15 +243,17 @@ static size_t SDLCALL windows_file_read(void *userdata, void *ptr, size_t size, switch (error) { case ERROR_BROKEN_PIPE: case ERROR_HANDLE_EOF: + *status = SDL_IO_STATUS_EOF; break; case ERROR_NO_DATA: *status = SDL_IO_STATUS_NOT_READY; break; default: + *status = SDL_IO_STATUS_ERROR; WIN_SetError("Error reading from datastream"); break; } - return 0; + return 0; // !!! FIXME: this should return the bytes read from any readahead we finished out before this (the `iodata->left > 0` code above). In that case, fail on the next read. } total_read += bytes; } @@ -263,6 +267,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)) { + *status = SDL_IO_STATUS_ERROR; WIN_SetError("Error seeking in datastream"); return 0; } @@ -274,16 +279,17 @@ 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)) { + *status = SDL_IO_STATUS_ERROR; WIN_SetError("Error seeking in datastream"); return 0; } } if (!WriteFile(iodata->h, ptr, (DWORD)size, &bytes, NULL)) { + *status = SDL_IO_STATUS_ERROR; WIN_SetError("Error writing to datastream"); return 0; - } - if (bytes == 0 && size > 0) { + } else if (bytes == 0 && size > 0) { *status = SDL_IO_STATUS_NOT_READY; } return bytes; @@ -421,9 +427,12 @@ static size_t SDLCALL fd_read(void *userdata, void *ptr, size_t size, SDL_IOStat if (errno == EAGAIN) { *status = SDL_IO_STATUS_NOT_READY; } else { + *status = SDL_IO_STATUS_ERROR; SDL_SetError("Error reading from datastream: %s", strerror(errno)); } bytes = 0; + } else if (bytes < size) { + *status = SDL_IO_STATUS_EOF; } return (size_t)bytes; } @@ -440,6 +449,7 @@ static size_t SDLCALL fd_write(void *userdata, const void *ptr, size_t size, SDL if (errno == EAGAIN) { *status = SDL_IO_STATUS_NOT_READY; } else { + *status = SDL_IO_STATUS_ERROR; SDL_SetError("Error writing to datastream: %s", strerror(errno)); } bytes = 0; @@ -606,12 +616,18 @@ 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)) { - if (errno == EAGAIN) { - *status = SDL_IO_STATUS_NOT_READY; - clearerr(iodata->fp); + if (bytes < size) { + if (ferror(iodata->fp)) { + if (errno == EAGAIN) { + *status = SDL_IO_STATUS_NOT_READY; + clearerr(iodata->fp); + } else { + *status = SDL_IO_STATUS_ERROR; + SDL_SetError("Error reading from datastream: %s", strerror(errno)); + } } else { - SDL_SetError("Error reading from datastream: %s", strerror(errno)); + SDL_assert(feof(iodata->fp)); + *status = SDL_IO_STATUS_EOF; } } return bytes; @@ -626,6 +642,7 @@ static size_t SDLCALL stdio_write(void *userdata, const void *ptr, size_t size, *status = SDL_IO_STATUS_NOT_READY; clearerr(iodata->fp); } else { + *status = SDL_IO_STATUS_ERROR; SDL_SetError("Error writing to datastream: %s", strerror(errno)); } } @@ -769,13 +786,22 @@ static size_t mem_io(void *userdata, void *dst, const void *src, size_t size) static size_t SDLCALL mem_read(void *userdata, void *ptr, size_t size, SDL_IOStatus *status) { IOStreamMemData *iodata = (IOStreamMemData *) userdata; - return mem_io(userdata, ptr, iodata->here, size); + const size_t retval = mem_io(userdata, ptr, iodata->here, size); + if ((retval < size) && (iodata->stop == iodata->here)) { + *status = SDL_IO_STATUS_EOF; + } + return retval; } static size_t SDLCALL mem_write(void *userdata, const void *ptr, size_t size, SDL_IOStatus *status) { IOStreamMemData *iodata = (IOStreamMemData *) userdata; - return mem_io(userdata, iodata->here, ptr, size); + const size_t retval = mem_io(userdata, iodata->here, ptr, size); + if ((retval < size) && (iodata->stop == iodata->here)) { + SDL_SetError("Memory buffer is full"); + *status = SDL_IO_STATUS_ERROR; + } + return retval; } static bool SDLCALL mem_close(void *userdata) @@ -1027,7 +1053,11 @@ static Sint64 SDLCALL dynamic_mem_seek(void *userdata, Sint64 offset, SDL_IOWhen static size_t SDLCALL dynamic_mem_read(void *userdata, void *ptr, size_t size, SDL_IOStatus *status) { IOStreamDynamicMemData *iodata = (IOStreamDynamicMemData *) userdata; - return mem_io(&iodata->data, ptr, iodata->data.here, size); + const size_t retval = mem_io(&iodata->data, ptr, iodata->data.here, size); + if ((retval < size) && (iodata->data.stop == iodata->data.here)) { + *status = SDL_IO_STATUS_EOF; + } + return retval; } static bool dynamic_mem_realloc(IOStreamDynamicMemData *iodata, size_t size) @@ -1060,12 +1090,15 @@ static size_t SDLCALL dynamic_mem_write(void *userdata, const void *ptr, size_t if (size > (size_t)(iodata->data.stop - iodata->data.here)) { if (size > (size_t)(iodata->end - iodata->data.here)) { if (!dynamic_mem_realloc(iodata, size)) { + *status = SDL_IO_STATUS_ERROR; return 0; } } iodata->data.stop = iodata->data.here + size; } - return mem_io(&iodata->data, iodata->data.here, ptr, size); + const size_t retval = mem_io(&iodata->data, iodata->data.here, ptr, size); + SDL_assert(retval == size); // we should have allocated enough to cover this! + return retval; } static bool SDLCALL dynamic_mem_close(void *userdata) @@ -1333,8 +1366,6 @@ Sint64 SDL_TellIO(SDL_IOStream *context) size_t SDL_ReadIO(SDL_IOStream *context, void *ptr, size_t size) { - size_t bytes; - if (!context) { SDL_InvalidParamError("context"); return 0; @@ -1342,30 +1373,18 @@ size_t SDL_ReadIO(SDL_IOStream *context, void *ptr, size_t size) context->status = SDL_IO_STATUS_WRITEONLY; SDL_Unsupported(); return 0; + } else if (size == 0) { + return 0; // context->status doesn't change for this. } context->status = SDL_IO_STATUS_READY; SDL_ClearError(); - if (size == 0) { - return 0; - } - - bytes = context->iface.read(context->userdata, ptr, size, &context->status); - if (bytes == 0 && context->status == SDL_IO_STATUS_READY) { - if (*SDL_GetError()) { - context->status = SDL_IO_STATUS_ERROR; - } else { - context->status = SDL_IO_STATUS_EOF; - } - } - return bytes; + return context->iface.read(context->userdata, ptr, size, &context->status); } size_t SDL_WriteIO(SDL_IOStream *context, const void *ptr, size_t size) { - size_t bytes; - if (!context) { SDL_InvalidParamError("context"); return 0; @@ -1373,20 +1392,14 @@ size_t SDL_WriteIO(SDL_IOStream *context, const void *ptr, size_t size) context->status = SDL_IO_STATUS_READONLY; SDL_Unsupported(); return 0; + } else if (size == 0) { + return 0; // context->status doesn't change for this. } context->status = SDL_IO_STATUS_READY; SDL_ClearError(); - if (size == 0) { - return 0; - } - - bytes = context->iface.write(context->userdata, ptr, size, &context->status); - if ((bytes == 0) && (context->status == SDL_IO_STATUS_READY)) { - context->status = SDL_IO_STATUS_ERROR; - } - return bytes; + return context->iface.write(context->userdata, ptr, size, &context->status); } size_t SDL_IOprintf(SDL_IOStream *context, SDL_PRINTF_FORMAT_STRING const char *fmt, ...)