#include "unity/unity.h" #include "zlib.h" #include #include #include #include #include #include #include #include /* Helpers to create and clean up temporary files */ static char *make_temp_path(void) { char tmpl[] = "/tmp/zlib_gzoffset64_XXXXXX"; int fd = mkstemp(tmpl); if (fd == -1) { return NULL; } close(fd); /* We'll reopen as needed */ char *path = (char *)malloc(strlen(tmpl) + 1); if (!path) return NULL; strcpy(path, tmpl); return path; } static size_t file_size(const char *path) { struct stat st; if (stat(path, &st) != 0) return 0; return (size_t)st.st_size; } /* Write a gzip file with given data bytes */ static int write_gz_file(const char *path, const unsigned char *data, size_t len) { gzFile gz = gzopen(path, "wb"); if (!gz) return -1; size_t total = 0; while (total < len) { unsigned chunk = (unsigned)((len - total) > 4096 ? 4096 : (len - total)); int wrote = gzwrite(gz, data + total, chunk); if (wrote <= 0) { gzclose(gz); return -1; } total += (size_t)wrote; } int rc = gzclose(gz); return rc == Z_OK ? 0 : -1; } /* Generate repetitive data */ static unsigned char *make_data(size_t len) { unsigned char *buf = (unsigned char *)malloc(len); if (!buf) return NULL; for (size_t i = 0; i < len; i++) { buf[i] = (unsigned char)(i * 1315423911u + 0x5bd1e995u); /* simple pattern */ } return buf; } void setUp(void) { /* Setup code here, or leave empty */ } void tearDown(void) { /* Cleanup code here, or leave empty */ } /* Test cases */ void test_gzoffset64_null_returns_minus1(void) { z_off64_t off = gzoffset64(NULL); TEST_ASSERT_EQUAL_INT64(-1, off); } void test_gzoffset64_write_mode_zero_and_increases_after_flush(void) { char *path = make_temp_path(); TEST_ASSERT_NOT_NULL_MESSAGE(path, "Failed to create temp path"); gzFile gz = gzopen(path, "wb"); TEST_ASSERT_NOT_NULL_MESSAGE(gz, "gzopen failed for write"); /* Initially zero */ z_off64_t off0 = gzoffset64(gz); TEST_ASSERT_EQUAL_INT64(0, off0); /* Write some data */ const char *msg1 = "The quick brown fox jumps over the lazy dog.\n"; int w1 = gzwrite(gz, msg1, (unsigned)strlen(msg1)); TEST_ASSERT_TRUE(w1 > 0); /* Before flush, offset may still be 0 due to buffering; don't assert > 0 yet */ z_off64_t off_pre = gzoffset64(gz); TEST_ASSERT_TRUE(off_pre >= 0); /* Flush to force writes to underlying fd */ int fr = gzflush(gz, Z_SYNC_FLUSH); TEST_ASSERT_TRUE(fr == Z_OK || fr == Z_BUF_ERROR); /* Z_BUF_ERROR means nothing to flush */ z_off64_t off1 = gzoffset64(gz); TEST_ASSERT_TRUE_MESSAGE(off1 >= 0, "gzoffset64 should be non-negative after flush"); /* With some data written and flushed, we expect some bytes written */ TEST_ASSERT_TRUE_MESSAGE(off1 > 0, "gzoffset64 should be > 0 after writing and flushing"); /* Write more and flush again */ const char *msg2 = "Pack my box with five dozen liquor jugs.\n"; int w2 = gzwrite(gz, msg2, (unsigned)strlen(msg2)); TEST_ASSERT_TRUE(w2 > 0); fr = gzflush(gz, Z_SYNC_FLUSH); TEST_ASSERT_TRUE(fr == Z_OK || fr == Z_BUF_ERROR); z_off64_t off2 = gzoffset64(gz); TEST_ASSERT_TRUE_MESSAGE(off2 >= off1, "Offset should be non-decreasing after additional writes"); /* Close and check final size is at least as large as last observed offset */ int rc = gzclose(gz); TEST_ASSERT_EQUAL_INT(Z_OK, rc); size_t final_size = file_size(path); TEST_ASSERT_TRUE_MESSAGE((z_off64_t)final_size >= off2, "Final file size should be >= offset observed before close"); /* Cleanup */ remove(path); free(path); } void test_gzoffset64_read_mode_initial_fd_position_via_gzdopen(void) { char *path = make_temp_path(); TEST_ASSERT_NOT_NULL_MESSAGE(path, "Failed to create temp path"); /* Create a regular file with some content */ int fd_create = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0600); TEST_ASSERT_TRUE_MESSAGE(fd_create >= 0, "open for create failed"); unsigned char zeros[1024]; memset(zeros, 0, sizeof(zeros)); ssize_t wr = write(fd_create, zeros, sizeof(zeros)); TEST_ASSERT_TRUE_MESSAGE(wr == (ssize_t)sizeof(zeros), "write failed"); close(fd_create); /* Reopen for read and position to 123 */ int fd = open(path, O_RDONLY); TEST_ASSERT_TRUE_MESSAGE(fd >= 0, "open for read failed"); off_t want = 123; off_t pos = lseek(fd, want, SEEK_SET); TEST_ASSERT_EQUAL_INT64((long long)want, (long long)pos); /* Wrap with gzdopen in read mode */ gzFile gz = gzdopen(fd, "rb"); TEST_ASSERT_NOT_NULL_MESSAGE(gz, "gzdopen failed"); /* Before any reads, avail_in == 0, so gzoffset64 should reflect current fd pos */ z_off64_t off = gzoffset64(gz); TEST_ASSERT_EQUAL_INT64((long long)want, (long long)off); int rc = gzclose(gz); /* closes fd as well */ TEST_ASSERT_EQUAL_INT(Z_OK, rc); remove(path); free(path); } void test_gzoffset64_read_mode_not_greater_than_raw_fd_position_after_read(void) { char *path = make_temp_path(); TEST_ASSERT_NOT_NULL_MESSAGE(path, "Failed to create temp path"); /* Create a valid gzip file with some data */ size_t data_len = 20000; unsigned char *data = make_data(data_len); TEST_ASSERT_NOT_NULL_MESSAGE(data, "Failed to allocate data"); TEST_ASSERT_EQUAL_INT(0, write_gz_file(path, data, data_len)); free(data); /* Open raw fd and wrap with gzdopen */ int fd = open(path, O_RDONLY); TEST_ASSERT_TRUE_MESSAGE(fd >= 0, "open gzip for read failed"); gzFile gz = gzdopen(fd, "rb"); TEST_ASSERT_NOT_NULL_MESSAGE(gz, "gzdopen failed"); /* Initial checks */ off_t raw0 = lseek(fd, 0, SEEK_CUR); TEST_ASSERT_EQUAL_INT64(0, (long long)raw0); z_off64_t off0 = gzoffset64(gz); TEST_ASSERT_EQUAL_INT64(0, (long long)off0); unsigned char buf[128]; int rd1 = gzread(gz, buf, sizeof(buf)); TEST_ASSERT_TRUE_MESSAGE(rd1 > 0, "gzread should read some bytes"); off_t raw1 = lseek(fd, 0, SEEK_CUR); z_off64_t off1 = gzoffset64(gz); TEST_ASSERT_TRUE_MESSAGE(off1 >= 0, "gzoffset64 should be non-negative"); TEST_ASSERT_TRUE_MESSAGE(off1 <= (z_off64_t)raw1, "gzoffset64 should be <= raw fd position after read"); /* Read more and verify monotonicity and <= raw fd pos */ int rd2 = gzread(gz, buf, sizeof(buf)); TEST_ASSERT_TRUE_MESSAGE(rd2 >= 0, "gzread should not error"); off_t raw2 = lseek(fd, 0, SEEK_CUR); z_off64_t off2 = gzoffset64(gz); TEST_ASSERT_TRUE_MESSAGE(off2 >= off1, "gzoffset64 should be non-decreasing after additional reads"); TEST_ASSERT_TRUE_MESSAGE(off2 <= (z_off64_t)raw2, "gzoffset64 should be <= raw fd position after more reads"); int rc = gzclose(gz); TEST_ASSERT_EQUAL_INT(Z_OK, rc); remove(path); free(path); } void test_gzoffset64_unchanged_by_pending_seek_request(void) { /* Prepare gzip file */ char *path = make_temp_path(); TEST_ASSERT_NOT_NULL_MESSAGE(path, "Failed to create temp path"); size_t data_len = 10000; unsigned char *data = make_data(data_len); TEST_ASSERT_NOT_NULL_MESSAGE(data, "Failed to allocate data"); TEST_ASSERT_EQUAL_INT(0, write_gz_file(path, data, data_len)); free(data); /* Open via fd and wrap in gzdopen */ int fd = open(path, O_RDONLY); TEST_ASSERT_TRUE_MESSAGE(fd >= 0, "open for read failed"); gzFile gz = gzdopen(fd, "rb"); TEST_ASSERT_NOT_NULL_MESSAGE(gz, "gzdopen failed"); /* Read a bit to ensure buffers are initialized */ unsigned char tmp[64]; int rd = gzread(gz, tmp, sizeof(tmp)); TEST_ASSERT_TRUE_MESSAGE(rd >= 0, "gzread should not fail"); z_off64_t before = gzoffset64(gz); /* Issue a pending relative seek (SEEK_CUR) which sets state->seek without moving OS fd immediately */ z_off64_t newpos = gzseek64(gz, 10, SEEK_CUR); TEST_ASSERT_TRUE_MESSAGE(newpos >= 0, "gzseek64 should succeed"); z_off64_t after = gzoffset64(gz); /* gzoffset64 should reflect current fd - avail_in, not the pending skip */ TEST_ASSERT_EQUAL_INT64((long long)before, (long long)after); int rc = gzclose(gz); TEST_ASSERT_EQUAL_INT(Z_OK, rc); remove(path); free(path); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_gzoffset64_null_returns_minus1); RUN_TEST(test_gzoffset64_write_mode_zero_and_increases_after_flush); RUN_TEST(test_gzoffset64_read_mode_initial_fd_position_via_gzdopen); RUN_TEST(test_gzoffset64_read_mode_not_greater_than_raw_fd_position_after_read); RUN_TEST(test_gzoffset64_unchanged_by_pending_seek_request); return UNITY_END(); }