diff --git a/Makefile b/Makefile index 2f5a07af..b16d3b37 100644 --- a/Makefile +++ b/Makefile @@ -907,7 +907,7 @@ UTARGETS += test_batchsvd MODULES_test_batchsvd = -llowrank # lib misc -UTARGETS += test_pattern test_types test_misc test_memcfl test_tree test_streams +UTARGETS += test_pattern test_types test_misc test_memcfl test_tree test_streams test_io # lib moba UTARGETS += test_moba diff --git a/src/win/vdprintf.h b/src/win/vdprintf.h index 6a52e659..f2ddccff 100644 --- a/src/win/vdprintf.h +++ b/src/win/vdprintf.h @@ -9,21 +9,57 @@ #ifndef _VDPRINTF_WINDOWS #define _VDPRINTF_WINDOWS +#include #include -#include "misc/misc.h" +#include +#include int vdprintf(int, const char*, va_list); +// Formats into memory and writes the bytes to the descriptor, as POSIX +// vdprintf does. A stdio stream opened over the descriptor for each call +// could not be closed without closing the descriptor, and the C runtime +// holds a fixed number of streams per process. int vdprintf(int fd, const char *format, va_list ap) { - FILE* stream = _fdopen(fd, "a"); - if (stream == NULL) - error("Unable to open file.\n"); - int err = vfprintf(stream, format, ap); - if (err == -1) - error("Unable to write to file.\n"); - fflush(stream); - return err; + char small[256]; + char* buf = small; + + va_list aq; + va_copy(aq, ap); + int len = vsnprintf(small, sizeof small, format, aq); + va_end(aq); + + if (len < 0) + return -1; + + if ((size_t)len >= sizeof small) { + + if (NULL == (buf = malloc((size_t)len + 1))) + return -1; + + vsnprintf(buf, (size_t)len + 1, format, ap); + } + + int done = 0; + + while (done < len) { + + int n = _write(fd, buf + done, (unsigned int)(len - done)); + + if (n <= 0) { + + done = -1; + break; + } + + done += n; + } + + if (small != buf) + free(buf); + + return done; } #endif /* _VDPRINTF_WINDOWS */ diff --git a/utests/test_io.c b/utests/test_io.c new file mode 100644 index 00000000..13291de1 --- /dev/null +++ b/utests/test_io.c @@ -0,0 +1,71 @@ +/* Copyright 2026. Pulserver contributors. + * All rights reserved. Use of this source code is governed by + * a BSD-style license which can be found in the LICENSE file. + * + * Headers written through the descriptor-level printf, many of them in one + * process, as a program that runs BART's commands in-process writes them. + */ + +#include +#include +#include +#include + +#include "misc/misc.h" +#include "misc/io.h" + +#ifdef _WIN32 +#include "win/open_patch.h" +#endif + +#include "utest.h" + + +static bool test_cfl_header_length_is_its_bytes(void) +{ + const char* name = "test_io_header.hdr"; + bart_dim_t dims[16] = { 3, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5 }; + + int fd = open(name, O_RDWR|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR); + + UT_RETURN_ON_FAILURE(-1 != fd); + + int written = write_cfl_header(fd, NULL, 16, dims); + + off_t size = lseek(fd, 0, SEEK_END); + + close(fd); + unlink(name); + + UT_RETURN_ASSERT((0 < written) && (size == written)); +} + +UT_REGISTER_TEST(test_cfl_header_length_is_its_bytes); + + +static bool test_many_cfl_headers_in_one_process(void) +{ +#ifdef _WIN32 + const char* null_file = "nul"; +#else + const char* null_file = "/dev/null"; +#endif + bart_dim_t dims[16] = { 3, 4, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 5 }; + + int fd = open(null_file, O_WRONLY); + + UT_RETURN_ON_FAILURE(-1 != fd); + + // Each header is a few dozen formatted writes, so this is more writes + // than a C runtime holds streams. + bool ok = true; + + for (int i = 0; ok && (i < 200); i++) + ok = (0 < write_cfl_header(fd, NULL, 16, dims)); + + close(fd); + + UT_RETURN_ASSERT(ok); +} + +UT_REGISTER_TEST(test_many_cfl_headers_in_one_process);