| #include "../../unity/unity.h" |
| #include <unistd.h> |
| #include <fcntl.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <errno.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #include <stdbool.h> |
| #include <stdio.h> |
|
|
| |
| |
|
|
| |
| static int saved_conversions_mask; |
| static int saved_output_flags; |
| static idx_t saved_output_blocksize; |
| static bool saved_o_nocache; |
| static bool saved_o_nocache_eof; |
| static bool saved_final_op_was_seek; |
| static int saved_status_level; |
|
|
| void setUp(void) { |
| saved_conversions_mask = conversions_mask; |
| saved_output_flags = output_flags; |
| saved_output_blocksize = output_blocksize; |
| saved_o_nocache = o_nocache; |
| saved_o_nocache_eof = o_nocache_eof; |
| saved_final_op_was_seek = final_op_was_seek; |
| saved_status_level = status_level; |
| } |
|
|
| void tearDown(void) { |
| conversions_mask = saved_conversions_mask; |
| output_flags = saved_output_flags; |
| output_blocksize = saved_output_blocksize; |
| o_nocache = saved_o_nocache; |
| o_nocache_eof = saved_o_nocache_eof; |
| final_op_was_seek = saved_final_op_was_seek; |
| status_level = saved_status_level; |
| } |
|
|
| |
| static int helper_mkstemp(char *buf, size_t buflen) { |
| |
| const char *prefix = "/tmp/dd_iwrite_test_"; |
| size_t plen = strlen(prefix); |
| TEST_ASSERT_TRUE_MESSAGE(buflen > plen + 7, "Buffer too small for mkstemp template"); |
| memcpy(buf, prefix, plen); |
| memcpy(buf + plen, "XXXXXX", 7); |
| int fd = mkstemp(buf); |
| TEST_ASSERT_TRUE_MESSAGE(fd >= 0, "mkstemp failed"); |
| return fd; |
| } |
|
|
| |
| void test_iwrite_basic_regular_file(void) { |
| char path[128]; |
| int fd = helper_mkstemp(path, sizeof(path)); |
|
|
| conversions_mask = 0; |
| output_flags = 0; |
| final_op_was_seek = false; |
|
|
| const char *data = "abc123XYZ"; |
| idx_t len = (idx_t)strlen(data); |
|
|
| idx_t ret = iwrite(fd, data, len); |
| TEST_ASSERT_EQUAL_INT((int)len, (int)ret); |
| TEST_ASSERT_FALSE(final_op_was_seek); |
|
|
| |
| ssize_t r; |
| char buf[64] = {0}; |
| lseek(fd, 0, SEEK_SET); |
| r = read(fd, buf, sizeof(buf)); |
| TEST_ASSERT_EQUAL_INT((int)len, (int)r); |
| TEST_ASSERT_EQUAL_INT(0, memcmp(buf, data, (size_t)len)); |
|
|
| close(fd); |
| unlink(path); |
| } |
|
|
| |
| |
| void test_iwrite_sparse_regular_file_seek(void) { |
| char path[128]; |
| int fd = helper_mkstemp(path, sizeof(path)); |
|
|
| conversions_mask = C_SPARSE; |
| output_flags = 0; |
| final_op_was_seek = false; |
|
|
| const idx_t size = 4096; |
| char *zeros = (char *)calloc(1, (size_t)size); |
| TEST_ASSERT_NOT_NULL(zeros); |
|
|
| idx_t ret = iwrite(fd, zeros, size); |
| TEST_ASSERT_EQUAL_INT((int)size, (int)ret); |
| TEST_ASSERT_TRUE(final_op_was_seek); |
|
|
| |
| struct stat st; |
| TEST_ASSERT_EQUAL_INT(0, fstat(fd, &st)); |
| TEST_ASSERT_EQUAL_INT(0, (int)st.st_size); |
|
|
| off_t off = lseek(fd, 0, SEEK_CUR); |
| TEST_ASSERT_EQUAL_INT((int)size, (int)off); |
|
|
| free(zeros); |
| close(fd); |
| unlink(path); |
| } |
|
|
| |
| |
| void test_iwrite_sparse_nonseekable_pipe_falls_back_and_clears_flag(void) { |
| int fds[2]; |
| TEST_ASSERT_EQUAL_INT(0, pipe(fds)); |
|
|
| conversions_mask = C_SPARSE; |
| output_flags = 0; |
| final_op_was_seek = false; |
|
|
| const idx_t size = 1024; |
| char *zeros = (char *)calloc(1, (size_t)size); |
| TEST_ASSERT_NOT_NULL(zeros); |
|
|
| idx_t ret = iwrite(fds[1], zeros, size); |
| TEST_ASSERT_EQUAL_INT((int)size, (int)ret); |
| TEST_ASSERT_FALSE(final_op_was_seek); |
|
|
| |
| TEST_ASSERT_EQUAL_INT(0, (conversions_mask & C_SPARSE)); |
|
|
| |
| idx_t total = 0; |
| char buf[512]; |
| while (total < size) { |
| ssize_t n = read(fds[0], buf, sizeof(buf)); |
| TEST_ASSERT_TRUE_MESSAGE(n >= 0, "read from pipe failed"); |
| if (n == 0) break; |
| |
| for (ssize_t i = 0; i < n; i++) { |
| TEST_ASSERT_EQUAL_UINT8(0, (unsigned char)buf[i]); |
| } |
| total += (idx_t)n; |
| } |
| TEST_ASSERT_EQUAL_INT((int)size, (int)total); |
|
|
| free(zeros); |
| close(fds[0]); |
| close(fds[1]); |
| } |
|
|
| |
| void test_iwrite_zero_size_noop(void) { |
| char path[128]; |
| int fd = helper_mkstemp(path, sizeof(path)); |
|
|
| conversions_mask = 0; |
| output_flags = 0; |
| final_op_was_seek = false; |
|
|
| const char *data = "ignored"; |
| idx_t ret = iwrite(fd, data, 0); |
| TEST_ASSERT_EQUAL_INT(0, (int)ret); |
| TEST_ASSERT_FALSE(final_op_was_seek); |
|
|
| off_t off = lseek(fd, 0, SEEK_CUR); |
| TEST_ASSERT_EQUAL_INT(0, (int)off); |
|
|
| close(fd); |
| unlink(path); |
| } |
|
|
| |
| void test_iwrite_error_on_readonly_fd_returns_0(void) { |
| char path[128]; |
| int wfd = helper_mkstemp(path, sizeof(path)); |
| close(wfd); |
|
|
| int rfd = open(path, O_RDONLY); |
| TEST_ASSERT_TRUE_MESSAGE(rfd >= 0, "open O_RDONLY failed"); |
|
|
| conversions_mask = 0; |
| output_flags = 0; |
| final_op_was_seek = false; |
|
|
| const char *data = "cannot write"; |
| errno = 0; |
| idx_t ret = iwrite(rfd, data, (idx_t)strlen(data)); |
| int err = errno; |
|
|
| TEST_ASSERT_EQUAL_INT(0, (int)ret); |
| TEST_ASSERT_FALSE(final_op_was_seek); |
| TEST_ASSERT_TRUE_MESSAGE(err == EBADF || err == EINVAL || err == EPERM, |
| "Expected EBADF/EINVAL/EPERM for write to read-only fd"); |
|
|
| close(rfd); |
| unlink(path); |
| } |
|
|
| |
| void test_iwrite_with_o_nocache(void) { |
| char path[128]; |
| int fd = helper_mkstemp(path, sizeof(path)); |
|
|
| conversions_mask = 0; |
| output_flags = 0; |
| o_nocache = true; |
| final_op_was_seek = false; |
|
|
| const char payload[] = "cache test payload"; |
| idx_t sz = (idx_t)strlen(payload); |
|
|
| idx_t ret = iwrite(fd, payload, sz); |
| TEST_ASSERT_EQUAL_INT((int)sz, (int)ret); |
| TEST_ASSERT_FALSE(final_op_was_seek); |
|
|
| |
| char buf[64] = {0}; |
| lseek(fd, 0, SEEK_SET); |
| ssize_t r = read(fd, buf, sizeof(buf)); |
| TEST_ASSERT_EQUAL_INT((int)sz, (int)r); |
| TEST_ASSERT_EQUAL_INT(0, memcmp(buf, payload, (size_t)sz)); |
|
|
| close(fd); |
| unlink(path); |
| } |
|
|
| int main(void) { |
| UNITY_BEGIN(); |
| RUN_TEST(test_iwrite_basic_regular_file); |
| RUN_TEST(test_iwrite_sparse_regular_file_seek); |
| RUN_TEST(test_iwrite_sparse_nonseekable_pipe_falls_back_and_clears_flag); |
| RUN_TEST(test_iwrite_zero_size_noop); |
| RUN_TEST(test_iwrite_error_on_readonly_fd_returns_0); |
| RUN_TEST(test_iwrite_with_o_nocache); |
| return UNITY_END(); |
| } |