#include "../../unity/unity.h" #include #include #include #include #include #include #include #include /* Access to dd.c internals since this test file is included into dd.c */ extern intmax_t skip (int fdesc, char const *file, intmax_t records, idx_t blocksize, idx_t *bytes); /* The following variables/functions are defined in dd.c and are accessible here */ extern ssize_t iread (int fd, char *buf, idx_t size); extern int ifd_reopen (int desired_fd, char const *file, int flag, mode_t mode); /* Globals from dd.c (file-scope static there, but visible here via inclusion) */ extern char const *input_file; extern char const *output_file; extern idx_t page_size; extern idx_t input_blocksize; extern idx_t output_blocksize; extern char *ibuf; extern char *obuf; extern off_t input_offset; extern ssize_t (*iread_fnc) (int fd, char *buf, idx_t size); /* Helpers */ static long get_pagesize_fallback(void) { long ps = sysconf(_SC_PAGESIZE); if (ps <= 0) ps = 4096; return ps; } static int save_fd(int fd) { int dupfd = dup(fd); return dupfd; } static void restore_fd(int dstfd, int saved) { if (saved >= 0) { (void)dup2(saved, dstfd); close(saved); } } static void make_temp_file(char *tmpl_buf, size_t bufsz) { /* Caller should provide a buffer like "/tmp/ddtest-XXXXXX" */ int fd = mkstemp(tmpl_buf); TEST_ASSERT_MESSAGE(fd >= 0, "mkstemp failed"); close(fd); } static void set_file_size(const char *path, off_t size) { int fd = open(path, O_RDWR); TEST_ASSERT_MESSAGE(fd >= 0, "open temp file failed"); int r = ftruncate(fd, size); TEST_ASSERT_MESSAGE(r == 0, "ftruncate failed"); close(fd); } static void reopen_stdin_to(const char *path) { int r = ifd_reopen(STDIN_FILENO, path, O_RDONLY, 0); TEST_ASSERT_MESSAGE(r == 0, "ifd_reopen(stdin) failed"); } static void reopen_stdout_to(const char *path) { int r = ifd_reopen(STDOUT_FILENO, path, O_WRONLY | O_CREAT | O_TRUNC, 0600); TEST_ASSERT_MESSAGE(r == 0, "ifd_reopen(stdout) failed"); } void setUp(void) { /* Ensure environment */ page_size = (idx_t)get_pagesize_fallback(); /* Set a consistent input/output blocksize before any allocation occurs. */ input_blocksize = 4; output_blocksize = 4; /* Ensure buffers are not yet allocated by our tests unless needed. */ /* ibuf/obuf are static in dd.c; if already allocated by previous tests, we'll keep using the same blocksize (4) to avoid mismatch. */ /* Set default function pointer for reading. */ iread_fnc = iread; input_offset = 0; input_file = NULL; output_file = NULL; } void tearDown(void) { /* Nothing special to clean here; each test restores fds and unlinks files. */ } /* Test 1: seekable STDIN, lseek succeeds within file (no EOF overrun). */ void test_skip_stdin_seek_within_file(void) { int saved_stdin = save_fd(STDIN_FILENO); char path[64] = "/tmp/ddtest1-XXXXXX"; make_temp_file(path, sizeof(path)); set_file_size(path, 100); input_file = path; input_offset = 0; reopen_stdin_to(path); /* Target: records=3, blocksize=10, bytes=5 => offset 35 (within 100). */ idx_t bytes = 5; intmax_t rem = skip(STDIN_FILENO, input_file, 3, (idx_t)10, &bytes); TEST_ASSERT_EQUAL_INT64(0, rem); /* bytes is not specified to change for STDIN lseek path; ensure it's unchanged */ TEST_ASSERT_EQUAL_UINT64(5, (unsigned long long)bytes); off_t pos = lseek(STDIN_FILENO, 0, SEEK_CUR); TEST_ASSERT_EQUAL_INT64(35, (long long)pos); /* input_offset should have advanced by 35 */ TEST_ASSERT_EQUAL_INT64(35, (long long)input_offset); restore_fd(STDIN_FILENO, saved_stdin); unlink(path); } /* Test 2: seekable STDIN, lseek succeeds but skipping past EOF. Expect returned remaining full records equal to overrun/blocksize, and input_offset advanced only up to EOF. */ void test_skip_stdin_seek_past_eof(void) { int saved_stdin = save_fd(STDIN_FILENO); char path[64] = "/tmp/ddtest2-XXXXXX"; make_temp_file(path, sizeof(path)); set_file_size(path, 10); /* 10 bytes */ input_file = path; input_offset = 0; reopen_stdin_to(path); /* records=5, blocksize=4 => 20 bytes; past EOF by 10; so remaining records = (20-10)/4 = 2 */ idx_t bytes = 0; intmax_t rem = skip(STDIN_FILENO, input_file, 5, (idx_t)4, &bytes); TEST_ASSERT_EQUAL_INT64(2, rem); /* File position was lseek'd to requested offset (20) */ off_t pos = lseek(STDIN_FILENO, 0, SEEK_CUR); TEST_ASSERT_EQUAL_INT64(20, (long long)pos); /* input_offset advanced only by available data (10) */ TEST_ASSERT_EQUAL_INT64(10, (long long)input_offset); restore_fd(STDIN_FILENO, saved_stdin); unlink(path); } /* Test 3: non-seekable STDIN (pipe), fallback reading. Provide less data than requested so that some extra bytes remain unskipped. Expect returned remaining records 0, but bytes remain nonzero. */ void test_skip_stdin_fallback_pipe_partial_blocks_and_bytes(void) { int saved_stdin = save_fd(STDIN_FILENO); int p[2]; TEST_ASSERT_MESSAGE(pipe(p) == 0, "pipe failed"); /* Write 10 bytes total to the pipe */ const char payload[10] = {0}; ssize_t wr = write(p[1], payload, sizeof(payload)); TEST_ASSERT_EQUAL_INT64(10, wr); close(p[1]); /* EOF after 10 bytes */ /* Make read end be STDIN */ (void)dup2(p[0], STDIN_FILENO); close(p[0]); input_file = "pipe"; input_offset = 0; /* Request: records=3, blocksize=4 (12 bytes), then bytes=2 => total 14; only 10 available */ idx_t bytes = 2; intmax_t rem = skip(STDIN_FILENO, input_file, 3, (idx_t)4, &bytes); /* With the implementation, records becomes 0 even though the last block was partial, and since EOF occurred before skipping the extra bytes, bytes remains nonzero (2). */ TEST_ASSERT_EQUAL_INT64(0, rem); TEST_ASSERT_EQUAL_UINT64(2, (unsigned long long)bytes); /* input_offset advanced by exactly the bytes read */ TEST_ASSERT_EQUAL_INT64(10, (long long)input_offset); restore_fd(STDIN_FILENO, saved_stdin); } /* Test 4: non-seekable STDIN (pipe), EOF before finishing all records. Expect returned remaining records to be >0. */ void test_skip_stdin_fallback_pipe_early_eof_records_remaining(void) { int saved_stdin = save_fd(STDIN_FILENO); int p[2]; TEST_ASSERT_MESSAGE(pipe(p) == 0, "pipe failed"); /* Write only 5 bytes; request 3 records of 4 bytes (12 total) */ const char payload[5] = {0}; ssize_t wr = write(p[1], payload, sizeof(payload)); TEST_ASSERT_EQUAL_INT64(5, wr); close(p[1]); (void)dup2(p[0], STDIN_FILENO); close(p[0]); input_file = "pipe"; input_offset = 0; idx_t bytes = 0; intmax_t rem = skip(STDIN_FILENO, input_file, 3, (idx_t)4, &bytes); /* We read 4 (records->2), then 1 (records->1), then EOF => remaining records should be 1 */ TEST_ASSERT_EQUAL_INT64(1, rem); TEST_ASSERT_EQUAL_INT64(5, (long long)input_offset); restore_fd(STDIN_FILENO, saved_stdin); } /* Test 5: seekable STDOUT, lseek succeeds; expect bytes cleared and 0 remaining records. */ void test_skip_stdout_seekable_with_bytes_zeroed(void) { int saved_stdout = save_fd(STDOUT_FILENO); char path[64] = "/tmp/ddtest5-XXXXXX"; make_temp_file(path, sizeof(path)); set_file_size(path, 0); output_file = path; reopen_stdout_to(path); /* Seek 2 records of 4 plus 1 byte; on success for output, bytes is set to 0 */ idx_t bytes = 1; intmax_t rem = skip(STDOUT_FILENO, output_file, 2, (idx_t)4, &bytes); TEST_ASSERT_EQUAL_INT64(0, rem); TEST_ASSERT_EQUAL_UINT64(0, (unsigned long long)bytes); off_t pos = lseek(STDOUT_FILENO, 0, SEEK_CUR); TEST_ASSERT_EQUAL_INT64(9, (long long)pos); restore_fd(STDOUT_FILENO, saved_stdout); unlink(path); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_skip_stdin_seek_within_file); RUN_TEST(test_skip_stdin_seek_past_eof); RUN_TEST(test_skip_stdin_fallback_pipe_partial_blocks_and_bytes); RUN_TEST(test_skip_stdin_fallback_pipe_early_eof_records_remaining); RUN_TEST(test_skip_stdout_seekable_with_bytes_zeroed); return UNITY_END(); }