BLI_fileops: Add 'BLI_read' wrapper to ensure the requested data is read #113474

Manually merged
Campbell Barton merged 3 commits from ideasman42/blender:pr-read-lfs into main 2023-10-10 13:53:48 +02:00
3 changed files with 54 additions and 1 deletions

View File

@ -290,6 +290,14 @@ void *BLI_gzopen(const char *filepath, const char *mode) ATTR_WARN_UNUSED_RESULT
int BLI_open(const char *filepath, int oflag, int pmode) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
int BLI_access(const char *filepath, int mode) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* A version of `read` with the following differences:
* - continues reading until failure or the requested size is met.
* - Reads `size_t` bytes instead of `int` on WIN32.
* \return the number of bytes read.
*/
ssize_t BLI_read(int fd, void *buf, size_t nbytes);
/**
* Returns true if the file with the specified name can be written.
* This implementation uses access(2), which makes the check according

View File

@ -92,6 +92,51 @@ static char *windows_operation_string(FileExternalOperation operation)
}
#endif
ssize_t BLI_read(int fd, void *buf, size_t nbytes)
{
/* Define our own read as `read` is not guaranteed to read the number of bytes requested.
* This happens rarely but was observed with larger than 2GB files on Linux, see: #113473.
*
* Even though this is a loop, the most common code-path will exit with "Success" case.
* In the case where read more data than the file contains, it will loop twice,
* exiting on EOF with the second iteration. */
ssize_t nbytes_read_total = 0;
while (true) {
ssize_t nbytes_read = read(fd,
buf,
#ifdef WIN32
ideasman42 marked this conversation as resolved Outdated

ifndef -> ifdef

`ifndef` -> `ifdef`
/* Read must not exceed INT_MAX on WIN32, clamp. */
MIN2(nbytes, INT_MAX)
#else
nbytes
#endif
);
if (nbytes_read == nbytes) {
/* Success (common case). */
return nbytes_read_total + nbytes_read;
}
if (nbytes_read == 0) {
/* EOF (common case for the second iteration when reading more data than `fd` contains). */
return nbytes_read_total;
}
if (nbytes_read < 0) {
/* Error. */
return nbytes_read;
}
if (UNLIKELY(nbytes_read > nbytes)) {
/* Badly behaving C-API - this should never happen.
* Possibly an invalid internal state/corruption, only check to prevent an eternal loop. */
BLI_assert_unreachable();
return -1;
}
/* If this is reached, fewer bytes were read than were requested. */
buf = (void *)(((char *)buf) + nbytes_read);
nbytes_read_total += nbytes_read;
nbytes -= nbytes_read;
}
}
bool BLI_file_external_operation_supported(const char *filepath, FileExternalOperation operation)
{
#ifdef WIN32

View File

@ -124,7 +124,7 @@ static bool buffer_from_filepath(const char *filepath, void **r_mem, size_t *r_s
else if (r_mem && UNLIKELY(!(mem = static_cast<uchar *>(MEM_mallocN(size, __func__))))) {
CLOG_WARN(&LOG, "error allocating buffer for '%s'", filepath);
}
else if (r_mem && UNLIKELY((size_read = read(file, mem, size)) != size)) {
else if (r_mem && UNLIKELY((size_read = BLI_read(file, mem, size)) != size)) {
CLOG_WARN(&LOG,
"error '%s' while reading '%s' (expected %" PRIu64 ", was %" PRId64 ")",
strerror(errno),