diff --git a/examples/example_large.c b/examples/example_large.c new file mode 100644 index 0000000..534b324 --- /dev/null +++ b/examples/example_large.c @@ -0,0 +1,105 @@ +/* + * example_large.c - Test with data larger than CHUNK_SIZE (32 bytes). + * + * This tests multi-chunk PUT/GET to expose potential bugs in + * chunk transfer logic. + */ + +#include +#include +#include +#include + +static const char *error_name(ToastyFS_Error e) +{ + switch (e) { + case TOASTYFS_ERROR_VOID: return "OK"; + case TOASTYFS_ERROR_OUT_OF_MEMORY: return "OUT_OF_MEMORY"; + case TOASTYFS_ERROR_UNEXPECTED_MESSAGE: return "UNEXPECTED_MESSAGE"; + case TOASTYFS_ERROR_REJECTED: return "REJECTED"; + case TOASTYFS_ERROR_FULL: return "FULL"; + case TOASTYFS_ERROR_NOT_FOUND: return "NOT_FOUND"; + case TOASTYFS_ERROR_TRANSFER_FAILED: return "TRANSFER_FAILED"; + } + return "UNKNOWN"; +} + +int main(void) +{ + char addr1[] = "127.0.0.1:8081"; + char addr2[] = "127.0.0.1:8082"; + char addr3[] = "127.0.0.1:8083"; + char *addrs[] = { addr1, addr2, addr3 }; + + printf("Connecting to cluster...\n"); + ToastyFS *tfs = toastyfs_init(1, addrs, 3); + if (tfs == NULL) { + fprintf(stderr, "toastyfs_init failed\n"); + return 1; + } + + ToastyFS_Result res; + int ret; + + // Create 100-byte test data (4 chunks: 32+32+32+4) + char data[100]; + for (int i = 0; i < 100; i++) + data[i] = 'A' + (i % 26); + + char key[] = "testkey"; + + printf("PUT key=\"%s\" data_len=%d (should be 4 chunks of 32 bytes)\n", + key, (int)sizeof(data)); + + ret = toastyfs_put(tfs, key, strlen(key), data, sizeof(data), &res); + if (ret < 0) { + fprintf(stderr, "toastyfs_put returned %d\n", ret); + toastyfs_free(tfs); + return 1; + } + printf(" result: %s\n", error_name(res.error)); + if (res.error != TOASTYFS_ERROR_VOID) { + fprintf(stderr, "PUT failed: %s\n", error_name(res.error)); + toastyfs_free(tfs); + return 1; + } + + printf("GET key=\"%s\"\n", key); + + ret = toastyfs_get(tfs, key, strlen(key), &res); + if (ret < 0) { + fprintf(stderr, "toastyfs_get returned %d\n", ret); + toastyfs_free(tfs); + return 1; + } + printf(" result: %s\n", error_name(res.error)); + + if (res.error == TOASTYFS_ERROR_VOID && res.data) { + printf(" size: %d (expected %d)\n", res.size, (int)sizeof(data)); + + if (res.size != (int)sizeof(data)) { + printf(" FAIL - size mismatch\n"); + } else if (memcmp(res.data, data, res.size) == 0) { + printf(" PASS - data matches\n"); + } else { + printf(" FAIL - data mismatch!\n"); + printf(" First differing byte:\n"); + for (int i = 0; i < res.size; i++) { + if (res.data[i] != data[i]) { + printf(" offset %d: got 0x%02x ('%c'), expected 0x%02x ('%c')\n", + i, + (unsigned char)res.data[i], res.data[i], + (unsigned char)data[i], data[i]); + break; + } + } + } + free(res.data); + } else { + printf(" FAIL - error or no data: %s\n", error_name(res.error)); + } + + printf("Done.\n"); + toastyfs_free(tfs); + return 0; +} diff --git a/quakey/src/quakey.c b/quakey/src/quakey.c index 4a9ffc9..10906aa 100644 --- a/quakey/src/quakey.c +++ b/quakey/src/quakey.c @@ -3241,6 +3241,12 @@ int mock_close(int fd) return 0; } +// NOTE: mock_access must be implemented for simulation to work correctly. +// chunk_store_exists() uses file_exists() which calls access(). Without a +// working mock_access, the simulation cannot check chunk existence and +// must fall back to file_open (O_CREAT), which creates empty files as a +// side effect and leads to data corruption (servers return uninitialized +// data for chunks they don't actually have). int mock_access(const char *path, int mode) { assert(0); // TODO diff --git a/src/chunk_store.c b/src/chunk_store.c index 30ef06e..deefc69 100644 --- a/src/chunk_store.c +++ b/src/chunk_store.c @@ -87,13 +87,7 @@ bool chunk_store_exists(ChunkStore *cs, SHA256 hash) if (path.len == 0) return false; - // Use file_open instead of file_exists (access) because - // mock_access is not implemented in the Quakey simulation. - Handle fd; - if (file_open(path, &fd) < 0) - return false; - file_close(fd); - return true; + return file_exists(path); } int chunk_store_delete(ChunkStore *cs, SHA256 hash) diff --git a/src/client.c b/src/client.c index c12baf8..79e2fae 100644 --- a/src/client.c +++ b/src/client.c @@ -165,6 +165,9 @@ void toastyfs_free(ToastyFS *tfs) free(tfs->put_data); } +static int find_completed_transfer_for_hash(ToastyFS *tfs, SHA256 hash); +static bool all_chunk_transfers_completed(ToastyFS *tfs); + static bool transfer_for_hash_already_started(ToastyFS *tfs, SHA256 hash) { @@ -400,12 +403,11 @@ static int process_message(ToastyFS *tfs, mark_waiting_transfers_for_hash_as_aborted(tfs, resp.hash); client_log(tfs, "RECV FETCH_RESP", "size=%u", resp.size); - if (begin_transfers(tfs) == 0) { + begin_transfers(tfs); + if (all_chunk_transfers_completed(tfs)) { tfs->error = TOASTYFS_ERROR_VOID; tfs->step = STEP_GET_DONE; client_log_simple(tfs, "ALL CHUNKS FETCHED"); - } else { - tfs->step = STEP_FETCH_CHUNK; } } else { @@ -434,7 +436,8 @@ static int process_message(ToastyFS *tfs, tfs->transfers[transfer_idx].state = TRANSFER_COMPLETED; mark_waiting_transfers_for_hash_as_aborted(tfs, ack.hash); - if (begin_transfers(tfs) == 0) { + begin_transfers(tfs); + if (all_chunk_transfers_completed(tfs)) { CommitPutMessage msg = { .base = { @@ -462,9 +465,6 @@ static int process_message(ToastyFS *tfs, tfs->step = STEP_COMMIT; client_log(tfs, "SEND COMMIT_PUT", "key=%s chunks=%d req=%lu", tfs->key, tfs->num_chunks, tfs->request_id); - - } else { - tfs->step = STEP_STORE_CHUNK; } } else { @@ -748,10 +748,10 @@ int toastyfs_register_events(ToastyFS *tfs, void **ctxs, struct pollfd *pdata, i } static void -choose_store_locations_for_chunk(ToastyFS *tfs, int *locations) +choose_store_locations_for_chunk(ToastyFS *tfs, int chunk_idx, int *locations) { - for (int i = 0; i < REPLICATION_FACTOR; i++) { - locations[i] = i % tfs->num_servers; + for (int j = 0; j < REPLICATION_FACTOR; j++) { + locations[j] = (chunk_idx + j) % tfs->num_servers; } } @@ -802,7 +802,7 @@ int toastyfs_async_put(ToastyFS *tfs, char *key, int key_len, SHA256 hash = compute_chunk_hash(data_copy + offset, size); int locations[REPLICATION_FACTOR]; - choose_store_locations_for_chunk(tfs, locations); + choose_store_locations_for_chunk(tfs, i, locations); for (int j = 0; j < REPLICATION_FACTOR; j++) add_transfer(tfs, hash, locations[j], data_copy + offset, size); diff --git a/src/server.c b/src/server.c index 18de29f..d7f8c8f 100644 --- a/src/server.c +++ b/src/server.c @@ -949,7 +949,7 @@ found_size:; return HR_OUT_OF_MEMORY; int ret = chunk_store_read(&state->chunk_store, message.hash, chunk_data, chunk_size); - if (ret < 0) { + if (ret <= 0) { free(chunk_data); FetchChunkResponseMessage response = { .base = { diff --git a/src/tcp.c b/src/tcp.c index 938b80f..431a4cc 100644 --- a/src/tcp.c +++ b/src/tcp.c @@ -38,7 +38,8 @@ static SOCKET create_listen_socket(Address addr) return INVALID_SOCKET; } - // TODO: mark address as reusable in debug builds + int reuse = 1; + setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)); if (!addr.is_ipv4) { assert(0); // TODO