|
|
#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(); |
|
|
} |