| #include "../../unity/unity.h" |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <stdint.h> |
| #include <string.h> |
| #include <fcntl.h> |
| #include <unistd.h> |
| #include <errno.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
|
|
| |
| static int create_temp_file_with_data(const void *data, size_t len, char *path_buf, size_t path_buf_sz) |
| { |
| |
| const char *tmpl = "./test_copyfd_src_XXXXXX"; |
| if (path_buf_sz < strlen(tmpl) + 1) |
| return -1; |
| strcpy(path_buf, tmpl); |
| int fd = mkstemp(path_buf); |
| if (fd < 0) |
| return -1; |
|
|
| if (len > 0) { |
| ssize_t wr = write(fd, data, len); |
| if (wr < 0 || (size_t)wr != len) { |
| int saved = errno; |
| close(fd); |
| unlink(path_buf); |
| errno = saved; |
| return -1; |
| } |
| } |
| |
| if (lseek(fd, 0, SEEK_SET) < 0) { |
| int saved = errno; |
| close(fd); |
| unlink(path_buf); |
| errno = saved; |
| return -1; |
| } |
| return fd; |
| } |
|
|
| |
| static char *run_and_check_copy_fd(int src_fd, |
| uintmax_t n_bytes, |
| enum Copy_fd_status expected_status, |
| const void *expected_output, |
| size_t expected_output_len) |
| { |
| |
| fflush(stdout); |
|
|
| |
| int saved_stdout = dup(STDOUT_FILENO); |
| if (saved_stdout < 0) { |
| char *msg = (char*)malloc(256); |
| snprintf(msg, 256, "dup(STDOUT) failed: errno=%d", errno); |
| return msg; |
| } |
|
|
| |
| char out_tmpl[] = "./test_copyfd_out_XXXXXX"; |
| int out_fd = mkstemp(out_tmpl); |
| if (out_fd < 0) { |
| close(saved_stdout); |
| char *msg = (char*)malloc(256); |
| snprintf(msg, 256, "mkstemp for stdout capture failed: errno=%d", errno); |
| return msg; |
| } |
|
|
| |
| if (dup2(out_fd, STDOUT_FILENO) < 0) { |
| int saved = errno; |
| close(out_fd); |
| close(saved_stdout); |
| char *msg = (char*)malloc(256); |
| snprintf(msg, 256, "dup2 to redirect stdout failed: errno=%d", saved); |
| return msg; |
| } |
| close(out_fd); |
|
|
| |
| enum Copy_fd_status st = copy_fd(src_fd, n_bytes); |
|
|
| |
| fflush(stdout); |
|
|
| |
| if (dup2(saved_stdout, STDOUT_FILENO) < 0) { |
| int saved = errno; |
| close(saved_stdout); |
| char *msg = (char*)malloc(256); |
| snprintf(msg, 256, "dup2 to restore stdout failed: errno=%d", saved); |
| return msg; |
| } |
| close(saved_stdout); |
|
|
| |
| int cap_fd = open(out_tmpl, O_RDONLY); |
| if (cap_fd < 0) { |
| int saved = errno; |
| unlink(out_tmpl); |
| char *msg = (char*)malloc(256); |
| snprintf(msg, 256, "open captured file failed: errno=%d", saved); |
| return msg; |
| } |
|
|
| struct stat stbuf; |
| if (fstat(cap_fd, &stbuf) < 0) { |
| int saved = errno; |
| close(cap_fd); |
| unlink(out_tmpl); |
| char *msg = (char*)malloc(256); |
| snprintf(msg, 256, "fstat captured file failed: errno=%d", saved); |
| return msg; |
| } |
|
|
| size_t cap_sz = (size_t)stbuf.st_size; |
| char *cap = NULL; |
| if (cap_sz > 0) { |
| cap = (char*)malloc(cap_sz); |
| if (!cap) { |
| close(cap_fd); |
| unlink(out_tmpl); |
| char *msg = (char*)malloc(256); |
| snprintf(msg, 256, "malloc(%zu) failed for capture buffer", cap_sz); |
| return msg; |
| } |
|
|
| size_t off = 0; |
| while (off < cap_sz) { |
| ssize_t r = read(cap_fd, cap + off, cap_sz - off); |
| if (r < 0) { |
| int saved = errno; |
| free(cap); |
| close(cap_fd); |
| unlink(out_tmpl); |
| char *msg = (char*)malloc(256); |
| snprintf(msg, 256, "read captured file failed: errno=%d", saved); |
| return msg; |
| } |
| if (r == 0) break; |
| off += (size_t)r; |
| } |
| |
| } |
| close(cap_fd); |
| unlink(out_tmpl); |
|
|
| |
| if (st != expected_status) { |
| char *msg = (char*)malloc(256); |
| snprintf(msg, 256, "Status mismatch: got %d, expected %d", (int)st, (int)expected_status); |
| free(cap); |
| return msg; |
| } |
|
|
| |
| if (cap_sz != expected_output_len) { |
| char *msg = (char*)malloc(256); |
| snprintf(msg, 256, "Output length mismatch: got %zu, expected %zu", cap_sz, expected_output_len); |
| free(cap); |
| return msg; |
| } |
|
|
| |
| if (cap_sz > 0 && memcmp(cap, expected_output, cap_sz) != 0) { |
| |
| size_t idx = 0; |
| while (idx < cap_sz && ((unsigned char*)cap)[idx] == ((const unsigned char*)expected_output)[idx]) |
| idx++; |
| char *msg = (char*)malloc(256); |
| snprintf(msg, 256, "Output content mismatch at byte %zu", idx); |
| free(cap); |
| return msg; |
| } |
|
|
| free(cap); |
| return NULL; |
| } |
|
|
| void setUp(void) { |
| |
| } |
|
|
| void tearDown(void) { |
| |
| } |
|
|
| static void fill_pattern(char *buf, size_t len) |
| { |
| for (size_t i = 0; i < len; i++) |
| buf[i] = (char)('A' + (i % 26)); |
| } |
|
|
| |
|
|
| void test_copy_fd_zero_bytes_no_output(void) |
| { |
| const char data[] = "ignored"; |
| char path[64]; |
| int fd = create_temp_file_with_data(data, sizeof(data) - 1, path, sizeof(path)); |
| TEST_ASSERT_MESSAGE(fd >= 0, "Failed to create temp source file"); |
|
|
| |
| char *err = run_and_check_copy_fd(fd, 0, COPY_FD_OK, "", 0); |
| close(fd); |
| unlink(path); |
|
|
| TEST_ASSERT_NULL_MESSAGE(err, err ? err : ""); |
| free(err); |
| } |
|
|
| void test_copy_fd_partial_copy(void) |
| { |
| const char data[] = "abcdef"; |
| const size_t req = 3; |
| char path[64]; |
| int fd = create_temp_file_with_data(data, sizeof(data) - 1, path, sizeof(path)); |
| TEST_ASSERT_MESSAGE(fd >= 0, "Failed to create temp source file"); |
|
|
| char *err = run_and_check_copy_fd(fd, req, COPY_FD_OK, data, req); |
| close(fd); |
| unlink(path); |
|
|
| TEST_ASSERT_NULL_MESSAGE(err, err ? err : ""); |
| free(err); |
| } |
|
|
| void test_copy_fd_exact_copy(void) |
| { |
| const char data[] = "Hello, world!"; |
| const size_t len = sizeof(data) - 1; |
| char path[64]; |
| int fd = create_temp_file_with_data(data, len, path, sizeof(path)); |
| TEST_ASSERT_MESSAGE(fd >= 0, "Failed to create temp source file"); |
|
|
| char *err = run_and_check_copy_fd(fd, len, COPY_FD_OK, data, len); |
| close(fd); |
| unlink(path); |
|
|
| TEST_ASSERT_NULL_MESSAGE(err, err ? err : ""); |
| free(err); |
| } |
|
|
| void test_copy_fd_oversized_request_unexpected_eof(void) |
| { |
| const char data[] = "abcd"; |
| const size_t len = sizeof(data) - 1; |
| const uintmax_t req = 10; |
| char path[64]; |
| int fd = create_temp_file_with_data(data, len, path, sizeof(path)); |
| TEST_ASSERT_MESSAGE(fd >= 0, "Failed to create temp source file"); |
|
|
| |
| char *err = run_and_check_copy_fd(fd, req, COPY_FD_UNEXPECTED_EOF, data, len); |
| close(fd); |
| unlink(path); |
|
|
| TEST_ASSERT_NULL_MESSAGE(err, err ? err : ""); |
| free(err); |
| } |
|
|
| void test_copy_fd_read_error_returns_status(void) |
| { |
| |
| const char data[] = "some data"; |
| char path[64]; |
| int tmpfd = create_temp_file_with_data(data, sizeof(data) - 1, path, sizeof(path)); |
| TEST_ASSERT_MESSAGE(tmpfd >= 0, "Failed to create temp source file"); |
| close(tmpfd); |
|
|
| int wfd = open(path, O_WRONLY); |
| TEST_ASSERT_MESSAGE(wfd >= 0, "Failed to open file write-only to induce read error"); |
|
|
| |
| char *err = run_and_check_copy_fd(wfd, 5, COPY_FD_READ_ERROR, "", 0); |
| close(wfd); |
| unlink(path); |
|
|
| TEST_ASSERT_NULL_MESSAGE(err, err ? err : ""); |
| free(err); |
| } |
|
|
| void test_copy_fd_large_transfer_multiple_buffers(void) |
| { |
| size_t len = (size_t)BUFSIZ * 3 + 123; |
| char *data = (char*)malloc(len); |
| TEST_ASSERT_NOT_NULL(data); |
| fill_pattern(data, len); |
|
|
| char path[64]; |
| int fd = create_temp_file_with_data(data, len, path, sizeof(path)); |
| TEST_ASSERT_MESSAGE(fd >= 0, "Failed to create temp source file"); |
|
|
| char *err = run_and_check_copy_fd(fd, len, COPY_FD_OK, data, len); |
| close(fd); |
| unlink(path); |
| free(data); |
|
|
| TEST_ASSERT_NULL_MESSAGE(err, err ? err : ""); |
| free(err); |
| } |
|
|
| int main(void) |
| { |
| UNITY_BEGIN(); |
| RUN_TEST(test_copy_fd_zero_bytes_no_output); |
| RUN_TEST(test_copy_fd_partial_copy); |
| RUN_TEST(test_copy_fd_exact_copy); |
| RUN_TEST(test_copy_fd_oversized_request_unexpected_eof); |
| RUN_TEST(test_copy_fd_read_error_returns_status); |
| RUN_TEST(test_copy_fd_large_transfer_multiple_buffers); |
| return UNITY_END(); |
| } |