diff --git a/include/pager.h b/include/pager.h index f8c9a39..d545206 100644 --- a/include/pager.h +++ b/include/pager.h @@ -11,7 +11,8 @@ typedef enum { PAGER_OK = 0, PAGER_INVALID_ARGUMENT, PAGER_IO_ERROR, - PAGER_OUT_OF_PAGES + PAGER_OUT_OF_PAGES, + PAGER_CORRUPT } PagerResult; typedef struct { diff --git a/src/database.c b/src/database.c index afc946d..2d09ad5 100644 --- a/src/database.c +++ b/src/database.c @@ -55,7 +55,7 @@ Table *table_open(const char *filename, SqliteResult *result) PagerResult pager_result; Pager *pager = pager_open(filename, &pager_result); if (pager == NULL) { - *result = SQLITE_IO_ERROR; + *result = pager_result == PAGER_CORRUPT ? SQLITE_CORRUPT : SQLITE_IO_ERROR; return NULL; } diff --git a/src/pager.c b/src/pager.c index 0e9c4f3..8e8d451 100644 --- a/src/pager.c +++ b/src/pager.c @@ -4,7 +4,7 @@ static size_t page_count_for_length(size_t file_length) { - return (file_length + PAGE_SIZE - 1U) / PAGE_SIZE; + return file_length / PAGE_SIZE; } Pager *pager_open(const char *filename, PagerResult *result) @@ -38,6 +38,12 @@ Pager *pager_open(const char *filename, PagerResult *result) return NULL; } + if ((size_t)length % PAGE_SIZE != 0U) { + fclose(file); + *result = PAGER_CORRUPT; + return NULL; + } + Pager *pager = calloc(1, sizeof(*pager)); if (pager == NULL) { fclose(file); @@ -154,6 +160,8 @@ const char *pager_result_string(PagerResult result) return "I/O error"; case PAGER_OUT_OF_PAGES: return "database exceeds maximum page count"; + case PAGER_CORRUPT: + return "database file has a partial page"; default: return "unknown error"; } diff --git a/tests/test_database.c b/tests/test_database.c index db152b3..9dd3e67 100644 --- a/tests/test_database.c +++ b/tests/test_database.c @@ -228,6 +228,21 @@ static void test_corrupt_node_count_is_rejected(void) remove(filename); } +static void test_partial_page_database_is_reported_as_corrupt(void) +{ + const char *filename = "build/test_partial_page.db"; + FILE *file = fopen(filename, "wb"); + assert(file != NULL); + assert(fputc(0, file) != EOF); + assert(fclose(file) == 0); + + SqliteResult result = SQLITE_OK; + Table *table = table_open(filename, &result); + assert(table == NULL); + assert(result == SQLITE_CORRUPT); + remove(filename); +} + static void test_corrupt_metadata_page_is_rejected(void) { const char *filename = "build/test_corrupt_metadata.db"; @@ -271,6 +286,7 @@ int main(void) test_validation(); test_corrupt_node_count_is_rejected(); test_corrupt_metadata_page_is_rejected(); + test_partial_page_database_is_reported_as_corrupt(); test_corrupt_child_reference_is_rejected(); return 0; } diff --git a/tests/test_pager.c b/tests/test_pager.c index 33c8f87..54e97c4 100644 --- a/tests/test_pager.c +++ b/tests/test_pager.c @@ -4,6 +4,33 @@ #include #include +static void write_file_of_size(const char *filename, size_t size) +{ + unsigned char bytes[PAGE_SIZE + 1U] = {0}; + FILE *file = fopen(filename, "wb"); + assert(file != NULL); + assert(size <= sizeof(bytes)); + assert(fwrite(bytes, 1U, size, file) == size); + assert(fclose(file) == 0); +} + +static void test_partial_page_files_are_rejected(void) +{ + const char *filename = "build/test_pager_partial.db"; + const size_t invalid_sizes[] = {1U, PAGE_SIZE - 1U, PAGE_SIZE + 1U}; + + for (size_t i = 0; i < sizeof(invalid_sizes) / sizeof(invalid_sizes[0]); ++i) { + write_file_of_size(filename, invalid_sizes[i]); + + PagerResult result = PAGER_OK; + Pager *pager = pager_open(filename, &result); + assert(pager == NULL); + assert(result == PAGER_CORRUPT); + } + + remove(filename); +} + static void test_pager_round_trip(void) { const char *filename = "build/test_pager.db"; @@ -39,5 +66,6 @@ static void test_pager_round_trip(void) int main(void) { test_pager_round_trip(); + test_partial_page_files_are_rejected(); return 0; }