From 0d8d4edde65f94ef5a49df6def21f988c8e6b0ad Mon Sep 17 00:00:00 2001 From: NeikiDev <67499407+NeikiDev@users.noreply.github.com> Date: Sat, 25 Jul 2026 21:36:09 +0200 Subject: [PATCH] Fix nob_read_entire_file for procfs files and pipes --- nob.c | 1 + nob.h | 46 +++++++++++++---------- tests/read_entire_file.c | 50 +++++++++++++++++++++++++ tests/read_entire_file.stdout.txt | 7 ++++ tests/read_entire_file.win32.stdout.txt | 5 +++ 5 files changed, 89 insertions(+), 20 deletions(-) create mode 100644 tests/read_entire_file.c create mode 100644 tests/read_entire_file.stdout.txt create mode 100644 tests/read_entire_file.win32.stdout.txt diff --git a/nob.c b/nob.c index acc56b7..9706439 100644 --- a/nob.c +++ b/nob.c @@ -5,6 +5,7 @@ const char *test_names[] = { "cmd_redirect", "cmd_args_passing", "read_entire_dir", + "read_entire_file", "da_resize", "da_last", "da_remove_unordered", diff --git a/nob.h b/nob.h index 2bdb16b..80c1486 100644 --- a/nob.h +++ b/nob.h @@ -2564,36 +2564,42 @@ NOBDEF bool nob_rename(const char *old_path, const char *new_path) NOBDEF bool nob_read_entire_file(const char *path, Nob_String_Builder *sb) { bool result = true; + size_t saved_count = sb->count; + long long m = 0; FILE *f = fopen(path, "rb"); - size_t new_count = 0; - long long m = 0; - if (f == NULL) nob_return_defer(false); - if (fseek(f, 0, SEEK_END) < 0) nob_return_defer(false); + if (f == NULL) nob_return_defer(false); + + if (fseek(f, 0, SEEK_END) == 0) { #ifndef _WIN32 - m = ftell(f); + m = ftell(f); #else - m = _telli64(_fileno(f)); + m = _telli64(_fileno(f)); #endif - if (m < 0) nob_return_defer(false); - if (fseek(f, 0, SEEK_SET) < 0) nob_return_defer(false); - - new_count = sb->count + m; - if (new_count > sb->capacity) { - sb->items = NOB_DECLTYPE_CAST(sb->items)NOB_REALLOC(sb->items, new_count); - NOB_ASSERT(sb->items != NULL && "Buy more RAM lool!!"); - sb->capacity = new_count; + if (fseek(f, 0, SEEK_SET) < 0) nob_return_defer(false); + if (m > 0) nob_da_reserve(sb, sb->count + (size_t)m); + } else { + clearerr(f); } - fread(sb->items + sb->count, m, 1, f); - if (ferror(f)) { - // TODO: Afaik, ferror does not set errno. So the error reporting in defer is not correct in this case. - nob_return_defer(false); + for (;;) { + nob_da_reserve(sb, sb->count + 1); + size_t n = fread(sb->items + sb->count, 1, sb->capacity - sb->count, f); + sb->count += n; + if (n == 0) { + if (ferror(f)) { + // TODO: Afaik, ferror does not set errno. So the error reporting in defer is not correct in this case. + nob_return_defer(false); + } + break; + } } - sb->count = new_count; defer: - if (!result) nob_log(NOB_ERROR, "Could not read file %s: %s", path, strerror(errno)); + if (!result) { + sb->count = saved_count; + nob_log(NOB_ERROR, "Could not read file %s: %s", path, strerror(errno)); + } if (f) fclose(f); return result; } diff --git a/tests/read_entire_file.c b/tests/read_entire_file.c new file mode 100644 index 0000000..a0ebdeb --- /dev/null +++ b/tests/read_entire_file.c @@ -0,0 +1,50 @@ +#include "shared.h" + +size_t error_counter = 0; + +void error_counting_log_handler(Nob_Log_Level level, const char *fmt, va_list args) +{ + UNUSED(fmt); + UNUSED(args); + if (level == ERROR) error_counter++; +} + +int main(void) +{ + String_Builder sb = {0}; + + const char *payload = "first line\nsecond line\n"; + if (!write_entire_file("file.txt", payload, strlen(payload))) return 1; + if (!read_entire_file("file.txt", &sb)) return 1; + printf("regular file: read %zu bytes\n", sb.count); + printf("%.*s", (int)sb.count, sb.items); + + if (!read_entire_file("file.txt", &sb)) return 1; + printf("read again: %zu bytes total\n", sb.count); + + Nob_Log_Handler *saved_log_handler = get_log_handler(); + set_log_handler(error_counting_log_handler); + bool ok = read_entire_file("does-not-exist.txt", &sb); + set_log_handler(saved_log_handler); + printf("missing file: ok=%s, error_counter=%zu, count=%zu\n", ok ? "true" : "false", error_counter, sb.count); + +#ifndef _WIN32 + if (mkfifo("fifo", 0644) < 0) return 1; + pid_t pid = fork(); + if (pid < 0) return 1; + if (pid == 0) { + FILE *f = fopen("fifo", "wb"); + if (f == NULL) _exit(1); + fputs("hello through a fifo\n", f); + fclose(f); + _exit(0); + } + sb.count = 0; + if (!read_entire_file("fifo", &sb)) return 1; + if (waitpid(pid, NULL, 0) < 0) return 1; + printf("fifo: read %zu bytes\n", sb.count); + printf("%.*s", (int)sb.count, sb.items); +#endif + + return 0; +} diff --git a/tests/read_entire_file.stdout.txt b/tests/read_entire_file.stdout.txt new file mode 100644 index 0000000..8e0e0b8 --- /dev/null +++ b/tests/read_entire_file.stdout.txt @@ -0,0 +1,7 @@ +regular file: read 23 bytes +first line +second line +read again: 46 bytes total +missing file: ok=false, error_counter=1, count=46 +fifo: read 21 bytes +hello through a fifo diff --git a/tests/read_entire_file.win32.stdout.txt b/tests/read_entire_file.win32.stdout.txt new file mode 100644 index 0000000..ceed233 --- /dev/null +++ b/tests/read_entire_file.win32.stdout.txt @@ -0,0 +1,5 @@ +regular file: read 23 bytes +first line +second line +read again: 46 bytes total +missing file: ok=false, error_counter=1, count=46