diff --git a/libutils/buffer.c b/libutils/buffer.c index 861bf1cd..e86d0cf7 100644 --- a/libutils/buffer.c +++ b/libutils/buffer.c @@ -24,6 +24,7 @@ #include #include #include +#include /* FullRead() */ #include #include #ifdef WITH_PCRE2 @@ -304,6 +305,29 @@ void BufferAppend(Buffer *buffer, const char *bytes, size_t length) } } +bool BufferAppendFileContent(Buffer *buffer, int fd) +{ + assert(buffer != NULL); + + char bytes[4096]; + ssize_t n_read; + do + { + n_read = FullRead(fd, bytes, sizeof(bytes)); + if (n_read < 0) + { + return false; + } + + BufferAppend(buffer, bytes, (size_t) n_read); + + /* FullRead() reads until the requested amount or end of file, so a + * short read means we have it all. */ + } while ((size_t) n_read == sizeof(bytes)); + + return true; +} + void BufferAppendChar(Buffer *buffer, char byte) { assert(buffer != NULL); diff --git a/libutils/buffer.h b/libutils/buffer.h index 752a3269..8fdf6f9e 100644 --- a/libutils/buffer.h +++ b/libutils/buffer.h @@ -147,6 +147,15 @@ void BufferAppendChar(Buffer *buffer, char byte); void BufferAppendF(Buffer *buffer, const char *format, ...); void BufferAppendString(Buffer *buffer, const char *str); +/** + @brief Appends everything that can be read from a file descriptor. + @param buffer Structure to operate on. + @param fd File descriptor to read from, until end of file. + @return Whether everything was read successfully. On failure, errno is set by + read(2) and the buffer contains whatever was read before the failure. + */ +bool BufferAppendFileContent(Buffer *buffer, int fd); + /** @brief Stores complex data on the buffer. diff --git a/tests/unit/buffer_test.c b/tests/unit/buffer_test.c index d153de73..70f86bb1 100644 --- a/tests/unit/buffer_test.c +++ b/tests/unit/buffer_test.c @@ -5,6 +5,7 @@ #include #include #include +#include static void test_createBuffer(void) { @@ -599,6 +600,54 @@ static void test_vprintf(void) free(char0int0char1double0); } +static void test_appendFileContent(void) +{ + /* Sizes around the 4096 byte chunk BufferAppendFileContent() reads in, so + * that both the short-read and the exact-multiple cases are covered. */ + const size_t sizes[] = { 0, 1, 4095, 4096, 4097, 8192, 100000 }; + + for (size_t i = 0; i < sizeof(sizes) / sizeof(*sizes); i++) + { + const size_t size = sizes[i]; + char *expected = xmalloc(size + 1); + for (size_t j = 0; j < size; j++) + { + expected[j] = 'a' + (j % 26); + } + expected[size] = '\0'; + + char filename[] = "/tmp/buffer_test_XXXXXX"; + int fd = mkstemp(filename); + assert_true(fd >= 0); + assert_int_equal(size, FullWrite(fd, expected, size)); + assert_int_equal(0, lseek(fd, 0, SEEK_SET)); + + Buffer *buffer = BufferNew(); + BufferAppendString(buffer, "prefix:"); + assert_true(BufferAppendFileContent(buffer, fd)); + assert_int_equal(strlen("prefix:") + size, BufferSize(buffer)); + assert_int_equal(0, memcmp(BufferData(buffer) + strlen("prefix:"), expected, size)); + + BufferDestroy(buffer); + close(fd); + unlink(filename); + free(expected); + } + + /* Reading from a descriptor which is not open for reading fails. */ + char filename[] = "/tmp/buffer_test_XXXXXX"; + int fd = mkstemp(filename); + assert_true(fd >= 0); + close(fd); + int wronly_fd = open(filename, O_WRONLY); + assert_true(wronly_fd >= 0); + Buffer *buffer = BufferNew(); + assert_false(BufferAppendFileContent(buffer, wronly_fd)); + BufferDestroy(buffer); + close(wronly_fd); + unlink(filename); +} + int main() { PRINT_TEST_BANNER(); @@ -611,6 +660,7 @@ int main() unit_test(test_copyCompareBuffer), unit_test(test_setBuffer), unit_test(test_appendBuffer), + unit_test(test_appendFileContent), unit_test(test_append_boundaries), unit_test(test_printf), unit_test(test_vprintf)