Full rewrite with VSR support
This commit is contained in:
-128
@@ -1,128 +0,0 @@
|
||||
#if defined(MAIN_SIMULATION) || defined(MAIN_TEST)
|
||||
#define QUAKEY_ENABLE_MOCKS
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include <quakey.h>
|
||||
|
||||
#include "basic.h"
|
||||
|
||||
bool streq(string s1, string s2)
|
||||
{
|
||||
if (s1.len != s2.len)
|
||||
return false;
|
||||
for (int i = 0; i < s1.len; i++)
|
||||
if (s1.ptr[i] != s2.ptr[i])
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Returns the current time in nanoseconds since
|
||||
// an unspecified time in the past (useful to calculate
|
||||
// elapsed time intervals)
|
||||
Time get_current_time(void)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
{
|
||||
int64_t count;
|
||||
int64_t freq;
|
||||
int ok;
|
||||
|
||||
ok = QueryPerformanceCounter((LARGE_INTEGER*) &count);
|
||||
if (!ok) return INVALID_TIME;
|
||||
|
||||
ok = QueryPerformanceFrequency((LARGE_INTEGER*) &freq);
|
||||
if (!ok) return INVALID_TIME;
|
||||
|
||||
uint64_t res = 1000000000 * (double) count / freq;
|
||||
return res;
|
||||
}
|
||||
#else
|
||||
{
|
||||
struct timespec time;
|
||||
|
||||
if (clock_gettime(CLOCK_REALTIME, &time))
|
||||
return INVALID_TIME;
|
||||
|
||||
uint64_t res;
|
||||
|
||||
uint64_t sec = time.tv_sec;
|
||||
if (sec > UINT64_MAX / 1000000000)
|
||||
return INVALID_TIME;
|
||||
res = sec * 1000000000;
|
||||
|
||||
uint64_t nsec = time.tv_nsec;
|
||||
if (res > UINT64_MAX - nsec)
|
||||
return INVALID_TIME;
|
||||
res += nsec;
|
||||
|
||||
return res;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void nearest_deadline(Time *a, Time b)
|
||||
{
|
||||
if (*a == INVALID_TIME || *a > b)
|
||||
*a = b;
|
||||
}
|
||||
|
||||
int deadline_to_timeout(Time deadline, Time current_time)
|
||||
{
|
||||
if (deadline == INVALID_TIME)
|
||||
return -1;
|
||||
return (deadline - current_time) / 1000000;
|
||||
}
|
||||
|
||||
bool getargb(int argc, char **argv, char *name)
|
||||
{
|
||||
for (int i = 0; i < argc; i++)
|
||||
if (!strcmp(argv[i], name))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
string getargs(int argc, char **argv, char *name, char *fallback)
|
||||
{
|
||||
for (int i = 0; i < argc; i++)
|
||||
if (!strcmp(argv[i], name)) {
|
||||
i++;
|
||||
if (i == argc)
|
||||
break;
|
||||
return (string) { argv[i], strlen(argv[i]) };
|
||||
}
|
||||
return (string) { fallback, strlen(fallback) };
|
||||
}
|
||||
|
||||
int getargi(int argc, char **argv, char *name, int fallback)
|
||||
{
|
||||
for (int i = 0; i < argc; i++)
|
||||
if (!strcmp(argv[i], name)) {
|
||||
|
||||
i++;
|
||||
if (i == argc)
|
||||
break;
|
||||
|
||||
errno = 0;
|
||||
char *end;
|
||||
long val = strtol(argv[i], &end, 10);
|
||||
|
||||
if (end == argv[i] || *end != '\0' || errno == ERANGE)
|
||||
break;
|
||||
|
||||
if (val < INT_MIN || val > INT_MAX)
|
||||
break;
|
||||
|
||||
return (int) val;
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
void append_hex_as_str(char *out, SHA256 hash)
|
||||
{
|
||||
char table[] = "0123456789abcdef";
|
||||
for (int i = 0; i < (int) sizeof(hash); i++) {
|
||||
out[(i << 1) + 0] = table[(uint8_t) hash.data[i] >> 4];
|
||||
out[(i << 1) + 1] = table[(uint8_t) hash.data[i] & 0xF];
|
||||
}
|
||||
}
|
||||
-54
@@ -1,54 +0,0 @@
|
||||
#ifndef BASIC_INCLUDED
|
||||
#define BASIC_INCLUDED
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef struct {
|
||||
char data[32];
|
||||
} SHA256;
|
||||
|
||||
typedef struct {
|
||||
uint32_t data;
|
||||
} IPv4;
|
||||
|
||||
typedef struct {
|
||||
uint16_t data[8];
|
||||
} IPv6;
|
||||
|
||||
typedef struct {
|
||||
union {
|
||||
IPv4 ipv4;
|
||||
IPv6 ipv6;
|
||||
};
|
||||
bool is_ipv4;
|
||||
uint16_t port;
|
||||
} Address;
|
||||
|
||||
typedef struct {
|
||||
char *ptr;
|
||||
int len;
|
||||
} string;
|
||||
|
||||
typedef uint64_t Time;
|
||||
#define INVALID_TIME ((Time) -1)
|
||||
|
||||
#define S(X) ((string) { (X), (int) sizeof(X)-1 })
|
||||
|
||||
#define MIN(X, Y) ((X) < (Y) ? (X) : (Y))
|
||||
|
||||
#define UNREACHABLE __builtin_trap();
|
||||
|
||||
bool streq(string s1, string s2);
|
||||
|
||||
Time get_current_time(void);
|
||||
void nearest_deadline(Time *a, Time b);
|
||||
int deadline_to_timeout(Time deadline, Time current_time);
|
||||
|
||||
bool getargb(int argc, char **argv, char *name);
|
||||
string getargs(int argc, char **argv, char *name, char *fallback);
|
||||
int getargi(int argc, char **argv, char *name, int fallback);
|
||||
|
||||
void append_hex_as_str(char *out, SHA256 hash);
|
||||
|
||||
#endif // BASIC_INCLUDED
|
||||
@@ -0,0 +1,675 @@
|
||||
#if defined(MAIN_SIMULATION) || defined(MAIN_TEST)
|
||||
#define QUAKEY_ENABLE_MOCKS
|
||||
#endif
|
||||
|
||||
#include <quakey.h>
|
||||
#include <stdint.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "blob_client.h"
|
||||
#include "server.h"
|
||||
|
||||
#define TIME_FMT "%7.3fs"
|
||||
#define TIME_VAL(t) ((double)(t) / 1000000000.0)
|
||||
|
||||
static uint64_t next_blob_client_id = 100;
|
||||
|
||||
static uint64_t blob_random(void)
|
||||
{
|
||||
#if defined(MAIN_SIMULATION) || defined(MAIN_TEST)
|
||||
return quakey_random();
|
||||
#else
|
||||
return (uint64_t)rand();
|
||||
#endif
|
||||
}
|
||||
|
||||
static void blob_log_impl(BlobClientState *state, Time now, const char *event, const char *detail)
|
||||
{
|
||||
printf("[" TIME_FMT "] BLOB %lu | %-20s %s\n",
|
||||
TIME_VAL(now),
|
||||
state->client_id,
|
||||
event,
|
||||
detail ? detail : "");
|
||||
}
|
||||
|
||||
#define blob_log(state, now, event, fmt, ...) do { \
|
||||
char _detail[256]; \
|
||||
snprintf(_detail, sizeof(_detail), fmt, ##__VA_ARGS__); \
|
||||
blob_log_impl(state, now, event, _detail); \
|
||||
} while (0)
|
||||
|
||||
#define blob_log_simple(state, now, event) \
|
||||
blob_log_impl(state, now, event, NULL)
|
||||
|
||||
static int leader_idx(BlobClientState *state)
|
||||
{
|
||||
return state->view_number % state->num_servers;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
// Chunk ack counting
|
||||
///////////////////////////////////////////////////////////////
|
||||
|
||||
static int count_acks(uint32_t mask)
|
||||
{
|
||||
int n = 0;
|
||||
for (int i = 0; i < 32; i++)
|
||||
if (mask & (1u << i)) n++;
|
||||
return n;
|
||||
}
|
||||
|
||||
static bool all_chunks_acked(BlobClientState *state)
|
||||
{
|
||||
for (int i = 0; i < state->num_chunks; i++) {
|
||||
if (count_acks(state->chunks[i].ack_mask) < state->f_plus_one)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool all_chunks_fetched(BlobClientState *state)
|
||||
{
|
||||
for (int i = 0; i < state->num_chunks; i++) {
|
||||
if (!state->chunks[i].fetched)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
// Generate a test blob
|
||||
///////////////////////////////////////////////////////////////
|
||||
|
||||
static void generate_test_blob(BlobClientState *state)
|
||||
{
|
||||
// Generate random bucket/key
|
||||
snprintf(state->bucket, META_BUCKET_MAX, "blob-b%d", (int)(blob_random() % 4));
|
||||
snprintf(state->key, META_KEY_MAX, "blob-k%d", (int)(blob_random() % 64));
|
||||
|
||||
// 1-3 chunks per blob
|
||||
state->num_chunks = 1 + blob_random() % 3;
|
||||
state->blob_size = 0;
|
||||
|
||||
for (int i = 0; i < state->num_chunks; i++) {
|
||||
// Generate random hash for each chunk
|
||||
for (int j = 0; j < 32; j++)
|
||||
state->chunks[i].hash.data[j] = blob_random() & 0xFF;
|
||||
state->chunks[i].size = BLOB_TEST_CHUNK_SIZE;
|
||||
state->chunks[i].ack_mask = 0;
|
||||
state->chunks[i].fetched = false;
|
||||
state->blob_size += BLOB_TEST_CHUNK_SIZE;
|
||||
}
|
||||
|
||||
// Content hash (random for now)
|
||||
for (int j = 0; j < 32; j++)
|
||||
state->content_hash.data[j] = blob_random() & 0xFF;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
// Send operations
|
||||
///////////////////////////////////////////////////////////////
|
||||
|
||||
// Send StoreChunk for a given chunk to a specific server.
|
||||
// The chunk data is the hash bytes (32 bytes).
|
||||
static void send_store_chunk(BlobClientState *state, int chunk_idx, int server_idx)
|
||||
{
|
||||
int conn_idx = tcp_index_from_tag(&state->tcp, server_idx);
|
||||
if (conn_idx < 0) {
|
||||
tcp_connect(&state->tcp, state->server_addrs[server_idx], server_idx, NULL);
|
||||
return; // Will retry next tick
|
||||
}
|
||||
|
||||
ByteQueue *output = tcp_output_buffer(&state->tcp, conn_idx);
|
||||
if (output == NULL) return;
|
||||
|
||||
StoreChunkMessage msg = {
|
||||
.base = {
|
||||
.version = MESSAGE_VERSION,
|
||||
.type = MESSAGE_TYPE_STORE_CHUNK,
|
||||
.length = sizeof(StoreChunkMessage) + state->chunks[chunk_idx].size,
|
||||
},
|
||||
.hash = state->chunks[chunk_idx].hash,
|
||||
.size = state->chunks[chunk_idx].size,
|
||||
};
|
||||
|
||||
byte_queue_write(output, &msg, sizeof(msg));
|
||||
// Chunk data = hash bytes (BLOB_TEST_CHUNK_SIZE = 32 = sizeof(SHA256))
|
||||
byte_queue_write(output, state->chunks[chunk_idx].hash.data, state->chunks[chunk_idx].size);
|
||||
}
|
||||
|
||||
static void send_commit_put(BlobClientState *state)
|
||||
{
|
||||
int conn_idx = tcp_index_from_tag(&state->tcp, leader_idx(state));
|
||||
if (conn_idx < 0) {
|
||||
tcp_connect(&state->tcp, state->server_addrs[leader_idx(state)], leader_idx(state), NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
ByteQueue *output = tcp_output_buffer(&state->tcp, conn_idx);
|
||||
if (output == NULL) return;
|
||||
|
||||
CommitPutMessage msg = {
|
||||
.base = {
|
||||
.version = MESSAGE_VERSION,
|
||||
.type = MESSAGE_TYPE_COMMIT_PUT,
|
||||
.length = sizeof(CommitPutMessage),
|
||||
},
|
||||
.oper = {
|
||||
.type = META_OPER_PUT,
|
||||
.size = state->blob_size,
|
||||
.content_hash = state->content_hash,
|
||||
.num_chunks = state->num_chunks,
|
||||
},
|
||||
.client_id = state->client_id,
|
||||
.request_id = state->request_id,
|
||||
};
|
||||
memcpy(msg.oper.bucket, state->bucket, META_BUCKET_MAX);
|
||||
memcpy(msg.oper.key, state->key, META_KEY_MAX);
|
||||
for (int i = 0; i < state->num_chunks; i++) {
|
||||
msg.oper.chunks[i].hash = state->chunks[i].hash;
|
||||
msg.oper.chunks[i].size = state->chunks[i].size;
|
||||
}
|
||||
|
||||
byte_queue_write(output, &msg, sizeof(msg));
|
||||
}
|
||||
|
||||
static void send_get_blob(BlobClientState *state, int server_idx)
|
||||
{
|
||||
int conn_idx = tcp_index_from_tag(&state->tcp, server_idx);
|
||||
if (conn_idx < 0) {
|
||||
tcp_connect(&state->tcp, state->server_addrs[server_idx], server_idx, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
ByteQueue *output = tcp_output_buffer(&state->tcp, conn_idx);
|
||||
if (output == NULL) return;
|
||||
|
||||
GetBlobMessage msg = {
|
||||
.base = {
|
||||
.version = MESSAGE_VERSION,
|
||||
.type = MESSAGE_TYPE_GET_BLOB,
|
||||
.length = sizeof(GetBlobMessage),
|
||||
},
|
||||
};
|
||||
memcpy(msg.bucket, state->bucket, META_BUCKET_MAX);
|
||||
memcpy(msg.key, state->key, META_KEY_MAX);
|
||||
|
||||
byte_queue_write(output, &msg, sizeof(msg));
|
||||
}
|
||||
|
||||
static void send_fetch_chunk(BlobClientState *state, int chunk_idx, int server_idx)
|
||||
{
|
||||
int conn_idx = tcp_index_from_tag(&state->tcp, server_idx);
|
||||
if (conn_idx < 0) {
|
||||
tcp_connect(&state->tcp, state->server_addrs[server_idx], server_idx, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
ByteQueue *output = tcp_output_buffer(&state->tcp, conn_idx);
|
||||
if (output == NULL) return;
|
||||
|
||||
FetchChunkMessage msg = {
|
||||
.base = {
|
||||
.version = MESSAGE_VERSION,
|
||||
.type = MESSAGE_TYPE_FETCH_CHUNK,
|
||||
.length = sizeof(FetchChunkMessage),
|
||||
},
|
||||
.hash = state->chunks[chunk_idx].hash,
|
||||
.sender_idx = -1, // Client (not a peer server)
|
||||
};
|
||||
|
||||
byte_queue_write(output, &msg, sizeof(msg));
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
// Message processing
|
||||
///////////////////////////////////////////////////////////////
|
||||
|
||||
static int
|
||||
process_message(BlobClientState *state,
|
||||
int conn_idx, uint8_t type, ByteView msg)
|
||||
{
|
||||
(void) conn_idx;
|
||||
Time now = get_current_time();
|
||||
|
||||
switch (type) {
|
||||
|
||||
case MESSAGE_TYPE_REDIRECT: {
|
||||
RedirectMessage redirect;
|
||||
if (msg.len != sizeof(RedirectMessage))
|
||||
return -1;
|
||||
memcpy(&redirect, msg.ptr, sizeof(redirect));
|
||||
if (redirect.view_number > state->view_number) {
|
||||
blob_log(state, now, "RECV REDIRECT", "view=%lu -> %lu",
|
||||
(unsigned long)state->view_number,
|
||||
(unsigned long)redirect.view_number);
|
||||
state->view_number = redirect.view_number;
|
||||
// Re-send CommitPut to new leader
|
||||
if (state->phase == BLOB_COMMITTING) {
|
||||
send_commit_put(state);
|
||||
state->phase_time = now;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
case MESSAGE_TYPE_STORE_CHUNK_ACK: {
|
||||
if (state->phase != BLOB_UPLOADING)
|
||||
return 0;
|
||||
|
||||
StoreChunkAckMessage ack;
|
||||
if (msg.len != sizeof(StoreChunkAckMessage))
|
||||
return -1;
|
||||
memcpy(&ack, msg.ptr, sizeof(ack));
|
||||
|
||||
// Find which chunk this ack is for
|
||||
for (int i = 0; i < state->num_chunks; i++) {
|
||||
if (memcmp(&state->chunks[i].hash, &ack.hash, sizeof(SHA256)) == 0) {
|
||||
int tag = tcp_get_tag(&state->tcp, conn_idx);
|
||||
if (tag >= 0 && tag < 32 && ack.success)
|
||||
state->chunks[i].ack_mask |= (1u << tag);
|
||||
blob_log(state, now, "RECV STORE_ACK", "chunk=%d server=%d ok=%d acks=%d/%d",
|
||||
i, tag, ack.success,
|
||||
count_acks(state->chunks[i].ack_mask), state->f_plus_one);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if all chunks have f+1 acks
|
||||
if (all_chunks_acked(state)) {
|
||||
blob_log(state, now, "UPLOAD DONE", "%s/%s chunks=%d",
|
||||
state->bucket, state->key, state->num_chunks);
|
||||
|
||||
// Move to commit phase
|
||||
state->phase = BLOB_COMMITTING;
|
||||
state->phase_time = now;
|
||||
state->request_id++;
|
||||
send_commit_put(state);
|
||||
blob_log(state, now, "SEND COMMIT_PUT", "%s/%s req=%lu",
|
||||
state->bucket, state->key, state->request_id);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
case MESSAGE_TYPE_REPLY: {
|
||||
if (state->phase != BLOB_COMMITTING)
|
||||
return 0;
|
||||
|
||||
ReplyMessage reply;
|
||||
if (msg.len != sizeof(ReplyMessage))
|
||||
return -1;
|
||||
memcpy(&reply, msg.ptr, sizeof(reply));
|
||||
|
||||
if (reply.request_id != state->request_id)
|
||||
return 0;
|
||||
|
||||
if (reply.rejected) {
|
||||
blob_log(state, now, "RECV REPLY", "REJECTED, retrying");
|
||||
state->phase_time = now;
|
||||
send_commit_put(state);
|
||||
return 0;
|
||||
}
|
||||
|
||||
state->puts_completed++;
|
||||
blob_log(state, now, "PUT DONE", "%s/%s puts=%d",
|
||||
state->bucket, state->key, state->puts_completed);
|
||||
|
||||
// Next: GET this blob back to verify
|
||||
state->do_get_next = true;
|
||||
state->phase = BLOB_IDLE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
case MESSAGE_TYPE_GET_BLOB_RESPONSE: {
|
||||
if (state->phase != BLOB_FETCHING_META)
|
||||
return 0;
|
||||
|
||||
GetBlobResponseMessage resp;
|
||||
if (msg.len != sizeof(GetBlobResponseMessage))
|
||||
return -1;
|
||||
memcpy(&resp, msg.ptr, sizeof(resp));
|
||||
|
||||
if (!resp.found) {
|
||||
// Blob not yet committed on this server, retry another
|
||||
blob_log(state, now, "RECV GET_BLOB", "NOT_FOUND, retrying");
|
||||
state->fetch_server_idx = (state->fetch_server_idx + 1) % state->num_servers;
|
||||
state->phase_time = now;
|
||||
send_get_blob(state, state->fetch_server_idx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
blob_log(state, now, "RECV GET_BLOB", "%s/%s chunks=%u",
|
||||
state->bucket, state->key, resp.num_chunks);
|
||||
|
||||
// Verify metadata matches what we uploaded
|
||||
if (resp.num_chunks != (uint32_t)state->num_chunks) {
|
||||
fprintf(stderr, "BLOB CLIENT: metadata mismatch! expected %d chunks, got %u\n",
|
||||
state->num_chunks, resp.num_chunks);
|
||||
return -1;
|
||||
}
|
||||
for (int i = 0; i < state->num_chunks; i++) {
|
||||
if (memcmp(&resp.chunks[i].hash, &state->chunks[i].hash, sizeof(SHA256)) != 0) {
|
||||
fprintf(stderr, "BLOB CLIENT: chunk %d hash mismatch!\n", i);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Start fetching chunk data
|
||||
state->phase = BLOB_FETCHING_DATA;
|
||||
state->phase_time = now;
|
||||
state->fetch_chunk_idx = 0;
|
||||
state->fetch_server_idx = 0;
|
||||
for (int i = 0; i < state->num_chunks; i++)
|
||||
state->chunks[i].fetched = false;
|
||||
|
||||
// Send FetchChunk for the first chunk
|
||||
send_fetch_chunk(state, 0, state->fetch_server_idx);
|
||||
blob_log(state, now, "SEND FETCH_CHUNK", "chunk=0 server=%d", state->fetch_server_idx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
case MESSAGE_TYPE_FETCH_CHUNK_RESPONSE: {
|
||||
if (state->phase != BLOB_FETCHING_DATA)
|
||||
return 0;
|
||||
|
||||
FetchChunkResponseMessage resp;
|
||||
if (msg.len < sizeof(FetchChunkResponseMessage))
|
||||
return -1;
|
||||
memcpy(&resp, msg.ptr, sizeof(resp));
|
||||
|
||||
// Find which chunk this is for
|
||||
int chunk_idx = -1;
|
||||
for (int i = 0; i < state->num_chunks; i++) {
|
||||
if (memcmp(&state->chunks[i].hash, &resp.hash, sizeof(SHA256)) == 0) {
|
||||
chunk_idx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (chunk_idx < 0)
|
||||
return 0; // Unknown chunk, ignore
|
||||
|
||||
if (resp.size == 0) {
|
||||
// Server doesn't have the chunk. Try next server.
|
||||
blob_log(state, now, "RECV FETCH_RESP", "chunk=%d NOT_FOUND, trying next server", chunk_idx);
|
||||
state->fetch_server_idx = (state->fetch_server_idx + 1) % state->num_servers;
|
||||
send_fetch_chunk(state, chunk_idx, state->fetch_server_idx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Verify data size
|
||||
uint32_t data_size = msg.len - sizeof(FetchChunkResponseMessage);
|
||||
if (data_size != resp.size || resp.size != state->chunks[chunk_idx].size) {
|
||||
fprintf(stderr, "BLOB CLIENT: chunk %d size mismatch! expected %u, got %u\n",
|
||||
chunk_idx, state->chunks[chunk_idx].size, resp.size);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Verify data content (data should equal hash bytes)
|
||||
uint8_t *data = (uint8_t *)(msg.ptr + sizeof(FetchChunkResponseMessage));
|
||||
if (memcmp(data, state->chunks[chunk_idx].hash.data, BLOB_TEST_CHUNK_SIZE) != 0) {
|
||||
fprintf(stderr, "BLOB CLIENT: chunk %d data verification FAILED!\n", chunk_idx);
|
||||
return -1;
|
||||
}
|
||||
|
||||
state->chunks[chunk_idx].fetched = true;
|
||||
blob_log(state, now, "RECV FETCH_RESP", "chunk=%d VERIFIED", chunk_idx);
|
||||
|
||||
// Check if all chunks fetched
|
||||
if (all_chunks_fetched(state)) {
|
||||
state->gets_completed++;
|
||||
state->gets_verified++;
|
||||
blob_log(state, now, "GET DONE", "%s/%s gets=%d verified=%d",
|
||||
state->bucket, state->key, state->gets_completed, state->gets_verified);
|
||||
|
||||
state->do_get_next = false;
|
||||
state->phase = BLOB_IDLE;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Fetch next unfetched chunk
|
||||
for (int i = 0; i < state->num_chunks; i++) {
|
||||
if (!state->chunks[i].fetched) {
|
||||
state->fetch_chunk_idx = i;
|
||||
state->fetch_server_idx = 0;
|
||||
send_fetch_chunk(state, i, 0);
|
||||
blob_log(state, now, "SEND FETCH_CHUNK", "chunk=%d server=0", i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////
|
||||
// Init / Tick / Free
|
||||
///////////////////////////////////////////////////////////////
|
||||
|
||||
int blob_client_init(void *state_, int argc, char **argv,
|
||||
void **ctxs, struct pollfd *pdata, int pcap, int *pnum,
|
||||
int *timeout)
|
||||
{
|
||||
BlobClientState *state = state_;
|
||||
|
||||
state->num_servers = 0;
|
||||
|
||||
for (int i = 1; i < argc; i++) {
|
||||
if (!strcmp(argv[i], "--server")) {
|
||||
i++;
|
||||
if (i == argc) {
|
||||
fprintf(stderr, "Option --server missing value\n");
|
||||
return -1;
|
||||
}
|
||||
if (state->num_servers == NODE_LIMIT) {
|
||||
fprintf(stderr, "Node limit reached\n");
|
||||
return -1;
|
||||
}
|
||||
if (parse_addr_arg(argv[i], &state->server_addrs[state->num_servers++]) < 0) {
|
||||
fprintf(stderr, "Malformed address\n");
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
// Ignore unknown options
|
||||
}
|
||||
}
|
||||
|
||||
addr_sort(state->server_addrs, state->num_servers);
|
||||
|
||||
if (tcp_context_init(&state->tcp) < 0) {
|
||||
fprintf(stderr, "Blob client :: Couldn't setup TCP context\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// f = (num_servers - 1) / 2, so f+1 = (num_servers + 1) / 2
|
||||
state->f_plus_one = (state->num_servers + 1) / 2;
|
||||
|
||||
state->view_number = 0;
|
||||
state->request_id = 0;
|
||||
state->client_id = next_blob_client_id++;
|
||||
state->phase = BLOB_IDLE;
|
||||
state->phase_time = 0;
|
||||
state->do_get_next = false;
|
||||
state->puts_completed = 0;
|
||||
state->gets_completed = 0;
|
||||
state->gets_verified = 0;
|
||||
state->reconnect_time = 0;
|
||||
state->upload_server_cursor = 0;
|
||||
|
||||
// Connect to all servers
|
||||
for (int i = 0; i < state->num_servers; i++) {
|
||||
if (tcp_connect(&state->tcp, state->server_addrs[i], i, NULL) < 0) {
|
||||
fprintf(stderr, "Blob client :: Couldn't connect to server %d\n", i);
|
||||
tcp_context_free(&state->tcp);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
Time now = get_current_time();
|
||||
blob_log(state, now, "INIT", "servers=%d f+1=%d", state->num_servers, state->f_plus_one);
|
||||
}
|
||||
|
||||
*timeout = 0;
|
||||
if (pcap < TCP_POLL_CAPACITY) {
|
||||
fprintf(stderr, "Blob client :: Not enough poll capacity\n");
|
||||
return -1;
|
||||
}
|
||||
*pnum = tcp_register_events(&state->tcp, ctxs, pdata);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int blob_client_tick(void *state_, void **ctxs,
|
||||
struct pollfd *pdata, int pcap, int *pnum, int *timeout)
|
||||
{
|
||||
BlobClientState *state = state_;
|
||||
|
||||
Event events[TCP_EVENT_CAPACITY];
|
||||
int num_events = tcp_translate_events(&state->tcp, events, ctxs, pdata, *pnum);
|
||||
|
||||
for (int i = 0; i < num_events; i++) {
|
||||
|
||||
if (events[i].type == EVENT_DISCONNECT) {
|
||||
int conn_idx = events[i].conn_idx;
|
||||
tcp_close(&state->tcp, conn_idx);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (events[i].type != EVENT_MESSAGE)
|
||||
continue;
|
||||
|
||||
int conn_idx = events[i].conn_idx;
|
||||
for (;;) {
|
||||
ByteView msg;
|
||||
uint16_t msg_type;
|
||||
int ret = tcp_next_message(&state->tcp, conn_idx, &msg, &msg_type);
|
||||
if (ret == 0)
|
||||
break;
|
||||
if (ret < 0) {
|
||||
tcp_close(&state->tcp, conn_idx);
|
||||
break;
|
||||
}
|
||||
|
||||
ret = process_message(state, conn_idx, msg_type, msg);
|
||||
if (ret < 0) {
|
||||
tcp_close(&state->tcp, conn_idx);
|
||||
break;
|
||||
}
|
||||
|
||||
tcp_consume_message(&state->tcp, conn_idx);
|
||||
}
|
||||
}
|
||||
|
||||
Time now = get_current_time();
|
||||
|
||||
// Timeout handling: if we've been in any phase too long, retry
|
||||
if (state->phase != BLOB_IDLE) {
|
||||
Time phase_deadline = state->phase_time + PRIMARY_DEATH_TIMEOUT_SEC * 1000000000ULL;
|
||||
if (phase_deadline <= now) {
|
||||
blob_log(state, now, "TIMEOUT", "phase=%d, retrying", state->phase);
|
||||
|
||||
switch (state->phase) {
|
||||
case BLOB_UPLOADING:
|
||||
// Re-send StoreChunk for chunks that need more acks
|
||||
for (int c = 0; c < state->num_chunks; c++) {
|
||||
if (count_acks(state->chunks[c].ack_mask) < state->f_plus_one) {
|
||||
for (int s = 0; s < state->f_plus_one; s++) {
|
||||
if (!(state->chunks[c].ack_mask & (1u << s)))
|
||||
send_store_chunk(state, c, s);
|
||||
}
|
||||
}
|
||||
}
|
||||
state->phase_time = now;
|
||||
break;
|
||||
|
||||
case BLOB_COMMITTING:
|
||||
state->view_number++;
|
||||
send_commit_put(state);
|
||||
state->phase_time = now;
|
||||
break;
|
||||
|
||||
case BLOB_FETCHING_META:
|
||||
state->fetch_server_idx = (state->fetch_server_idx + 1) % state->num_servers;
|
||||
send_get_blob(state, state->fetch_server_idx);
|
||||
state->phase_time = now;
|
||||
break;
|
||||
|
||||
case BLOB_FETCHING_DATA:
|
||||
state->fetch_server_idx = (state->fetch_server_idx + 1) % state->num_servers;
|
||||
send_fetch_chunk(state, state->fetch_chunk_idx, state->fetch_server_idx);
|
||||
state->phase_time = now;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure connections to all servers (needed during all phases for
|
||||
// retransmission after network partitions or server restarts)
|
||||
for (int i = 0; i < state->num_servers; i++) {
|
||||
int ci = tcp_index_from_tag(&state->tcp, i);
|
||||
if (ci < 0)
|
||||
tcp_connect(&state->tcp, state->server_addrs[i], i, NULL);
|
||||
}
|
||||
|
||||
// Start new operation when idle
|
||||
if (state->phase == BLOB_IDLE) {
|
||||
|
||||
if (state->do_get_next) {
|
||||
// GET the blob we just PUT
|
||||
state->phase = BLOB_FETCHING_META;
|
||||
state->phase_time = now;
|
||||
state->fetch_server_idx = 0;
|
||||
|
||||
send_get_blob(state, state->fetch_server_idx);
|
||||
blob_log(state, now, "SEND GET_BLOB", "%s/%s server=%d",
|
||||
state->bucket, state->key, state->fetch_server_idx);
|
||||
|
||||
} else {
|
||||
// PUT a new blob
|
||||
generate_test_blob(state);
|
||||
|
||||
state->phase = BLOB_UPLOADING;
|
||||
state->phase_time = now;
|
||||
|
||||
blob_log(state, now, "START PUT", "%s/%s chunks=%d",
|
||||
state->bucket, state->key, state->num_chunks);
|
||||
|
||||
// Send StoreChunk for each chunk to the first f+1 servers
|
||||
for (int c = 0; c < state->num_chunks; c++) {
|
||||
for (int s = 0; s < state->f_plus_one; s++) {
|
||||
send_store_chunk(state, c, s);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set timeout
|
||||
Time deadline = INVALID_TIME;
|
||||
if (state->phase != BLOB_IDLE) {
|
||||
nearest_deadline(&deadline, state->phase_time + PRIMARY_DEATH_TIMEOUT_SEC * 1000000000ULL);
|
||||
}
|
||||
*timeout = deadline_to_timeout(deadline, now);
|
||||
if (pcap < TCP_POLL_CAPACITY)
|
||||
return -1;
|
||||
*pnum = tcp_register_events(&state->tcp, ctxs, pdata);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int blob_client_free(void *state_)
|
||||
{
|
||||
BlobClientState *state = state_;
|
||||
{
|
||||
Time now = get_current_time();
|
||||
blob_log(state, now, "SHUTDOWN", "puts=%d gets=%d verified=%d",
|
||||
state->puts_completed, state->gets_completed, state->gets_verified);
|
||||
}
|
||||
tcp_context_free(&state->tcp);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
#ifndef BLOB_CLIENT_INCLUDED
|
||||
#define BLOB_CLIENT_INCLUDED
|
||||
|
||||
#include <lib/tcp.h>
|
||||
#include <lib/basic.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "metadata.h"
|
||||
|
||||
// Maximum number of chunks per blob in the test client.
|
||||
// Kept small for simulation; real blobs use META_CHUNKS_MAX.
|
||||
#define BLOB_MAX_CHUNKS 8
|
||||
|
||||
// Chunk data size for the test client (32 bytes = SHA256 hash length).
|
||||
// Each chunk's data is its hash bytes, making verification trivial.
|
||||
#define BLOB_TEST_CHUNK_SIZE 32
|
||||
|
||||
typedef enum {
|
||||
BLOB_IDLE, // Ready to start a new operation
|
||||
BLOB_UPLOADING, // Sending StoreChunk, waiting for acks
|
||||
BLOB_COMMITTING, // Sent CommitPut, waiting for REPLY
|
||||
BLOB_FETCHING_META, // Sent GetBlob, waiting for response
|
||||
BLOB_FETCHING_DATA, // Sending FetchChunk, waiting for responses
|
||||
} BlobPhase;
|
||||
|
||||
typedef struct {
|
||||
SHA256 hash;
|
||||
uint32_t size;
|
||||
uint32_t ack_mask; // Bitmask: which servers acked this chunk
|
||||
bool fetched; // GET: whether chunk data was received and verified
|
||||
} BlobChunkState;
|
||||
|
||||
typedef struct {
|
||||
|
||||
TCP tcp;
|
||||
|
||||
Address server_addrs[NODE_LIMIT];
|
||||
int num_servers;
|
||||
int f_plus_one; // Number of servers to upload each chunk to
|
||||
|
||||
uint64_t view_number;
|
||||
uint64_t client_id;
|
||||
uint64_t request_id;
|
||||
|
||||
BlobPhase phase;
|
||||
Time phase_time; // When we entered this phase
|
||||
|
||||
// Current blob metadata
|
||||
char bucket[META_BUCKET_MAX];
|
||||
char key[META_KEY_MAX];
|
||||
uint64_t blob_size;
|
||||
SHA256 content_hash;
|
||||
int num_chunks;
|
||||
BlobChunkState chunks[BLOB_MAX_CHUNKS];
|
||||
|
||||
// Upload tracking
|
||||
int upload_server_cursor; // Next server index to try for StoreChunk
|
||||
|
||||
// Download tracking
|
||||
int fetch_chunk_idx; // Which chunk we're currently fetching
|
||||
int fetch_server_idx; // Which server to try next
|
||||
|
||||
// Alternation between PUT and GET
|
||||
bool do_get_next; // If true, next op is GET; else PUT
|
||||
|
||||
// Statistics
|
||||
int puts_completed;
|
||||
int gets_completed;
|
||||
int gets_verified;
|
||||
|
||||
Time reconnect_time;
|
||||
|
||||
} BlobClientState;
|
||||
|
||||
struct pollfd;
|
||||
|
||||
int blob_client_init(void *state, int argc, char **argv,
|
||||
void **ctxs, struct pollfd *pdata, int pcap, int *pnum,
|
||||
int *timeout);
|
||||
|
||||
int blob_client_tick(void *state, void **ctxs,
|
||||
struct pollfd *pdata, int pcap, int *pnum, int *timeout);
|
||||
|
||||
int blob_client_free(void *state);
|
||||
|
||||
#endif // BLOB_CLIENT_INCLUDED
|
||||
@@ -1,306 +0,0 @@
|
||||
#if defined(MAIN_SIMULATION) || defined(MAIN_TEST)
|
||||
#define QUAKEY_ENABLE_MOCKS
|
||||
#endif
|
||||
|
||||
#include <quakey.h>
|
||||
#include <stdint.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "byte_queue.h"
|
||||
|
||||
// This is the implementation of a byte queue useful
|
||||
// for systems that need to process engs of bytes.
|
||||
//
|
||||
// It features sticky errors, a zero-copy interface,
|
||||
// and a safe mechanism to patch previously written
|
||||
// bytes.
|
||||
//
|
||||
// Only up to 4GB of data can be stored at once.
|
||||
|
||||
// Initialize the queue
|
||||
void byte_queue_init(ByteQueue *queue, uint32_t limit)
|
||||
{
|
||||
queue->flags = 0;
|
||||
queue->head = 0;
|
||||
queue->size = 0;
|
||||
queue->used = 0;
|
||||
queue->curs = 0;
|
||||
queue->limit = limit;
|
||||
queue->data = NULL;
|
||||
queue->read_target = NULL;
|
||||
}
|
||||
|
||||
// Deinitialize the queue
|
||||
void byte_queue_free(ByteQueue *queue)
|
||||
{
|
||||
if (queue->read_target) {
|
||||
if (queue->read_target != queue->data)
|
||||
free(queue->read_target);
|
||||
queue->read_target = NULL;
|
||||
queue->read_target_size = 0;
|
||||
}
|
||||
|
||||
free(queue->data);
|
||||
queue->data = NULL;
|
||||
}
|
||||
|
||||
int byte_queue_error(ByteQueue *queue)
|
||||
{
|
||||
return queue->flags & BYTE_QUEUE_ERROR;
|
||||
}
|
||||
|
||||
int byte_queue_empty(ByteQueue *queue)
|
||||
{
|
||||
return queue->used == 0;
|
||||
}
|
||||
|
||||
int byte_queue_full(ByteQueue *queue)
|
||||
{
|
||||
return queue->used == queue->limit;
|
||||
}
|
||||
|
||||
// Start a read operation on the queue.
|
||||
//
|
||||
// This function returnes the pointer to the memory region containing the bytes
|
||||
// to read. Callers can't read more than [*len] bytes from it. To complete the
|
||||
// read, the [byte_queue_read_ack] function must be called with the number of
|
||||
// bytes that were acknowledged by the caller.
|
||||
//
|
||||
// Note:
|
||||
// - You can't have more than one pending read.
|
||||
ByteView byte_queue_read_buf(ByteQueue *queue)
|
||||
{
|
||||
if (queue->flags & BYTE_QUEUE_ERROR)
|
||||
return (ByteView) {NULL, 0};
|
||||
|
||||
assert((queue->flags & BYTE_QUEUE_READ) == 0);
|
||||
queue->flags |= BYTE_QUEUE_READ;
|
||||
queue->read_target = queue->data;
|
||||
queue->read_target_size = queue->size;
|
||||
|
||||
if (queue->data == NULL)
|
||||
return (ByteView) {NULL, 0};
|
||||
|
||||
return (ByteView) { queue->data + queue->head, queue->used };
|
||||
}
|
||||
|
||||
// Complete a previously started operation on the queue.
|
||||
void byte_queue_read_ack(ByteQueue *queue, uint32_t num)
|
||||
{
|
||||
if (queue->flags & BYTE_QUEUE_ERROR)
|
||||
return;
|
||||
|
||||
if ((queue->flags & BYTE_QUEUE_READ) == 0)
|
||||
return;
|
||||
|
||||
queue->flags &= ~BYTE_QUEUE_READ;
|
||||
|
||||
assert((uint32_t) num <= queue->used);
|
||||
queue->head += (uint32_t) num;
|
||||
queue->used -= (uint32_t) num;
|
||||
queue->curs += (uint32_t) num;
|
||||
|
||||
if (queue->read_target) {
|
||||
if (queue->read_target != queue->data)
|
||||
free(queue->read_target);
|
||||
queue->read_target = NULL;
|
||||
queue->read_target_size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
ByteView byte_queue_write_buf(ByteQueue *queue)
|
||||
{
|
||||
if ((queue->flags & BYTE_QUEUE_ERROR) || queue->data == NULL)
|
||||
return (ByteView) {NULL, 0};
|
||||
|
||||
assert((queue->flags & BYTE_QUEUE_WRITE) == 0);
|
||||
queue->flags |= BYTE_QUEUE_WRITE;
|
||||
|
||||
return (ByteView) {
|
||||
queue->data + (queue->head + queue->used),
|
||||
queue->size - (queue->head + queue->used),
|
||||
};
|
||||
}
|
||||
|
||||
void byte_queue_write_ack(ByteQueue *queue, uint32_t num)
|
||||
{
|
||||
if (queue->flags & BYTE_QUEUE_ERROR)
|
||||
return;
|
||||
|
||||
if ((queue->flags & BYTE_QUEUE_WRITE) == 0)
|
||||
return;
|
||||
|
||||
queue->flags &= ~BYTE_QUEUE_WRITE;
|
||||
queue->used += num;
|
||||
}
|
||||
|
||||
// Sets the minimum capacity for the next write operation
|
||||
// and returns 1 if the content of the queue was moved, else
|
||||
// 0 is returned.
|
||||
//
|
||||
// You must not call this function while a write is pending.
|
||||
// In other words, you must do this:
|
||||
//
|
||||
// byte_queue_write_setmincap(queue, mincap);
|
||||
// dst = byte_queue_write_buf(queue, &cap);
|
||||
// ...
|
||||
// byte_queue_write_ack(num);
|
||||
//
|
||||
// And NOT this:
|
||||
//
|
||||
// dst = byte_queue_write_buf(queue, &cap);
|
||||
// byte_queue_write_setmincap(queue, mincap); <-- BAD
|
||||
// ...
|
||||
// byte_queue_write_ack(num);
|
||||
//
|
||||
int byte_queue_write_setmincap(ByteQueue *queue, uint32_t mincap)
|
||||
{
|
||||
// Sticky error
|
||||
if (queue->flags & BYTE_QUEUE_ERROR)
|
||||
return 0;
|
||||
|
||||
// In general, the queue's contents look like this:
|
||||
//
|
||||
// size
|
||||
// v
|
||||
// [___xxxxxxxxxxxx________]
|
||||
// ^ ^ ^
|
||||
// 0 head head + used
|
||||
//
|
||||
// This function needs to make sure that at least [mincap]
|
||||
// bytes are available on the right side of the content.
|
||||
//
|
||||
// We have 3 cases:
|
||||
//
|
||||
// 1) If there is enough memory already, this function doesn't
|
||||
// need to do anything.
|
||||
//
|
||||
// 2) If there isn't enough memory on the right but there is
|
||||
// enough free memory if we cound the left unused region,
|
||||
// then the content is moved back to the
|
||||
// start of the buffer.
|
||||
//
|
||||
// 3) If there isn't enough memory considering both sides, this
|
||||
// function needs to allocate a new buffer.
|
||||
//
|
||||
// If there are pending read or write operations, the application
|
||||
// is holding pointers to the buffer, so we need to make sure
|
||||
// to not invalidate them. The only real problem is pending reads
|
||||
// since this function can only be called before starting a write
|
||||
// opearation.
|
||||
//
|
||||
// To avoid invalidating the read pointer when we allocate a new
|
||||
// buffer, we don't free the old buffer. Instead, we store the
|
||||
// pointer in the "old" field so that the read ack function can
|
||||
// free it.
|
||||
//
|
||||
// To avoid invalidating the pointer when we are moving back the
|
||||
// content since there is enough memory at the start of the buffer,
|
||||
// we just avoid that. Even if there is enough memory considering
|
||||
// left and right free regions, we allocate a new buffer.
|
||||
|
||||
assert((queue->flags & BYTE_QUEUE_WRITE) == 0);
|
||||
|
||||
uint32_t total_free_space = queue->size - queue->used;
|
||||
uint32_t free_space_after_data = queue->size - queue->used - queue->head;
|
||||
|
||||
int moved = 0;
|
||||
if (free_space_after_data < mincap) {
|
||||
|
||||
if (total_free_space < mincap || (queue->read_target == queue->data)) {
|
||||
// Resize required
|
||||
|
||||
if (queue->used + mincap > queue->limit) {
|
||||
queue->flags |= BYTE_QUEUE_ERROR;
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t size;
|
||||
if (queue->size > UINT32_MAX / 2)
|
||||
size = UINT32_MAX;
|
||||
else
|
||||
size = 2 * queue->size;
|
||||
|
||||
if (size < queue->used + mincap)
|
||||
size = queue->used + mincap;
|
||||
|
||||
if (size > queue->limit)
|
||||
size = queue->limit;
|
||||
|
||||
uint8_t *data = malloc(size);
|
||||
if (!data) {
|
||||
queue->flags |= BYTE_QUEUE_ERROR;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (queue->used > 0)
|
||||
memcpy(data, queue->data + queue->head, queue->used);
|
||||
|
||||
if (queue->read_target != queue->data)
|
||||
free(queue->data);
|
||||
|
||||
queue->data = data;
|
||||
queue->head = 0;
|
||||
queue->size = size;
|
||||
|
||||
} else {
|
||||
// Move required
|
||||
memmove(queue->data, queue->data + queue->head, queue->used);
|
||||
queue->head = 0;
|
||||
}
|
||||
|
||||
moved = 1;
|
||||
}
|
||||
|
||||
return moved;
|
||||
}
|
||||
|
||||
void byte_queue_write(ByteQueue *queue, void *ptr, uint32_t len)
|
||||
{
|
||||
byte_queue_write_setmincap(queue, len);
|
||||
ByteView dst = byte_queue_write_buf(queue);
|
||||
if (dst.ptr) {
|
||||
memcpy(dst.ptr, ptr, len);
|
||||
byte_queue_write_ack(queue, len);
|
||||
}
|
||||
}
|
||||
|
||||
ByteQueueOffset byte_queue_offset(ByteQueue *queue)
|
||||
{
|
||||
if (queue->flags & BYTE_QUEUE_ERROR)
|
||||
return (ByteQueueOffset) { 0 };
|
||||
return (ByteQueueOffset) { queue->curs + queue->used };
|
||||
}
|
||||
|
||||
void byte_queue_patch(ByteQueue *queue, ByteQueueOffset off,
|
||||
void *src, uint32_t len)
|
||||
{
|
||||
if (queue->flags & BYTE_QUEUE_ERROR)
|
||||
return;
|
||||
|
||||
// Check that the offset is in range
|
||||
assert(off >= queue->curs && off - queue->curs < queue->used);
|
||||
|
||||
// Check that the length is in range
|
||||
assert(len <= queue->used - (off - queue->curs));
|
||||
|
||||
// Perform the patch
|
||||
uint8_t *dst = queue->data + queue->head + (off - queue->curs);
|
||||
memcpy(dst, src, len);
|
||||
}
|
||||
|
||||
uint32_t byte_queue_size_from_offset(ByteQueue *queue, ByteQueueOffset off)
|
||||
{
|
||||
return queue->curs + queue->used - off;
|
||||
}
|
||||
|
||||
void byte_queue_remove_from_offset(ByteQueue *queue, ByteQueueOffset offset)
|
||||
{
|
||||
if (queue->flags & BYTE_QUEUE_ERROR)
|
||||
return;
|
||||
|
||||
uint64_t num = (queue->curs + queue->used) - offset;
|
||||
assert(num <= queue->used);
|
||||
|
||||
queue->used -= num;
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
#ifndef BYTE_QUEUE_INCLUDED
|
||||
#define BYTE_QUEUE_INCLUDED
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "basic.h"
|
||||
|
||||
typedef struct {
|
||||
uint8_t *ptr;
|
||||
size_t len;
|
||||
} ByteView;
|
||||
|
||||
typedef struct {
|
||||
uint64_t curs;
|
||||
uint8_t* data;
|
||||
uint32_t head;
|
||||
uint32_t size;
|
||||
uint32_t used;
|
||||
uint32_t limit;
|
||||
uint8_t* read_target;
|
||||
uint32_t read_target_size;
|
||||
int flags;
|
||||
} ByteQueue;
|
||||
|
||||
typedef uint64_t ByteQueueOffset;
|
||||
|
||||
enum {
|
||||
BYTE_QUEUE_ERROR = 1 << 0,
|
||||
BYTE_QUEUE_READ = 1 << 1,
|
||||
BYTE_QUEUE_WRITE = 1 << 2,
|
||||
};
|
||||
|
||||
void byte_queue_init(ByteQueue *queue, uint32_t limit);
|
||||
void byte_queue_free(ByteQueue *queue);
|
||||
|
||||
int byte_queue_error(ByteQueue *queue);
|
||||
int byte_queue_empty(ByteQueue *queue);
|
||||
int byte_queue_full(ByteQueue *queue);
|
||||
|
||||
ByteView byte_queue_read_buf(ByteQueue *queue);
|
||||
void byte_queue_read_ack(ByteQueue *queue, uint32_t num);
|
||||
|
||||
ByteView byte_queue_write_buf(ByteQueue *queue);
|
||||
void byte_queue_write_ack(ByteQueue *queue, uint32_t num);
|
||||
int byte_queue_write_setmincap(ByteQueue *queue, uint32_t mincap);
|
||||
void byte_queue_write(ByteQueue *queue, void *ptr, uint32_t len);
|
||||
|
||||
ByteQueueOffset byte_queue_offset(ByteQueue *queue);
|
||||
void byte_queue_patch(ByteQueue *queue, ByteQueueOffset off, void *src, uint32_t len);
|
||||
uint32_t byte_queue_size_from_offset(ByteQueue *queue, ByteQueueOffset off);
|
||||
void byte_queue_remove_from_offset(ByteQueue *queue, ByteQueueOffset offset);
|
||||
|
||||
#endif // BYTE_QUEUE_INCLUDED
|
||||
-1069
File diff suppressed because it is too large
Load Diff
@@ -1,70 +0,0 @@
|
||||
#ifndef CHUNK_SERVER_INCLUDED
|
||||
#define CHUNK_SERVER_INCLUDED
|
||||
|
||||
#include "tcp.h"
|
||||
#include "basic.h"
|
||||
#include "hash_set.h"
|
||||
|
||||
#define TAG_METADATA_SERVER 1
|
||||
#define TAG_CHUNK_SERVER 2
|
||||
|
||||
typedef struct {
|
||||
Address addr;
|
||||
SHA256 hash;
|
||||
} DownloadTarget;
|
||||
|
||||
typedef struct {
|
||||
DownloadTarget *items;
|
||||
int count;
|
||||
int capacity;
|
||||
} DownloadTargets;
|
||||
|
||||
typedef struct {
|
||||
|
||||
char path[PATH_MAX];
|
||||
|
||||
bool trace;
|
||||
|
||||
Address local_addr;
|
||||
Address remote_addr;
|
||||
|
||||
Time disconnect_time;
|
||||
Time last_sync_time;
|
||||
int reconnect_delay; // In seconds
|
||||
|
||||
// --- Subsystems ---
|
||||
|
||||
TCP tcp;
|
||||
|
||||
// --- Download Management ---
|
||||
|
||||
bool downloading;
|
||||
SHA256 current_download_target_hash;
|
||||
DownloadTargets download_targets;
|
||||
|
||||
// --- Chunk Management ---
|
||||
|
||||
// List of chunks added since the last update
|
||||
HashSet cs_add_list;
|
||||
|
||||
// List of chunks marked for removal after a timeout
|
||||
TimedHashSet cs_rem_list;
|
||||
|
||||
// List of chunks that were lost due to errors or forceful removals of chunk files
|
||||
HashSet cs_lst_list;
|
||||
|
||||
} ChunkServer;
|
||||
|
||||
struct pollfd;
|
||||
|
||||
int chunk_server_init(void *state, int argc, char **argv,
|
||||
void **ctxs, struct pollfd *pdata, int pcap, int *pnum,
|
||||
int *timeout);
|
||||
|
||||
int chunk_server_tick(void *state, void **ctxs,
|
||||
struct pollfd *pdata, int pcap, int *pnum,
|
||||
int *timeout);
|
||||
|
||||
int chunk_server_free(void *state);
|
||||
|
||||
#endif // CHUNK_SERVER_INCLUDED
|
||||
@@ -0,0 +1,107 @@
|
||||
#if defined(MAIN_SIMULATION) || defined(MAIN_TEST)
|
||||
#define QUAKEY_ENABLE_MOCKS
|
||||
#endif
|
||||
|
||||
#include <quakey.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "chunk_store.h"
|
||||
#include <lib/file_system.h>
|
||||
|
||||
// Build the full path for a chunk: "base_path/HEX_HASH"
|
||||
// SHA256 hex = 64 chars. Returns string pointing into buf.
|
||||
static string chunk_path(ChunkStore *cs, SHA256 hash, char *buf, int buf_size)
|
||||
{
|
||||
int base_len = strlen(cs->base_path);
|
||||
if (base_len + 1 + 64 + 1 > buf_size)
|
||||
return (string){ buf, 0 };
|
||||
|
||||
memcpy(buf, cs->base_path, base_len);
|
||||
buf[base_len] = '/';
|
||||
append_hex_as_str(buf + base_len + 1, hash);
|
||||
buf[base_len + 1 + 64] = '\0';
|
||||
|
||||
return (string){ buf, base_len + 1 + 64 };
|
||||
}
|
||||
|
||||
int chunk_store_init(ChunkStore *cs, const char *base_path)
|
||||
{
|
||||
int len = strlen(base_path);
|
||||
if (len >= (int) sizeof(cs->base_path))
|
||||
return -1;
|
||||
|
||||
memcpy(cs->base_path, base_path, len + 1);
|
||||
|
||||
string path = { cs->base_path, len };
|
||||
create_dir(path); // Ignore error if already exists
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void chunk_store_free(ChunkStore *cs)
|
||||
{
|
||||
(void) cs;
|
||||
}
|
||||
|
||||
int chunk_store_write(ChunkStore *cs, SHA256 hash, char *data, uint32_t size)
|
||||
{
|
||||
char buf[512];
|
||||
string path = chunk_path(cs, hash, buf, sizeof(buf));
|
||||
if (path.len == 0)
|
||||
return -1;
|
||||
|
||||
Handle fd;
|
||||
if (file_open(path, &fd) < 0)
|
||||
return -1;
|
||||
|
||||
if (file_truncate(fd, 0) < 0) {
|
||||
file_close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int ret = file_write_exact(fd, data, size);
|
||||
file_close(fd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int chunk_store_read(ChunkStore *cs, SHA256 hash, char *dst, uint32_t size)
|
||||
{
|
||||
char buf[512];
|
||||
string path = chunk_path(cs, hash, buf, sizeof(buf));
|
||||
if (path.len == 0)
|
||||
return -1;
|
||||
|
||||
Handle fd;
|
||||
if (file_open(path, &fd) < 0)
|
||||
return -1;
|
||||
|
||||
int ret = file_read_exact(fd, dst, size);
|
||||
file_close(fd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool chunk_store_exists(ChunkStore *cs, SHA256 hash)
|
||||
{
|
||||
char buf[512];
|
||||
string path = chunk_path(cs, hash, buf, sizeof(buf));
|
||||
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;
|
||||
}
|
||||
|
||||
int chunk_store_delete(ChunkStore *cs, SHA256 hash)
|
||||
{
|
||||
char buf[512];
|
||||
string path = chunk_path(cs, hash, buf, sizeof(buf));
|
||||
if (path.len == 0)
|
||||
return -1;
|
||||
|
||||
return remove_file_or_dir(path);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
#ifndef CHUNK_STORE_INCLUDED
|
||||
#define CHUNK_STORE_INCLUDED
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <lib/basic.h>
|
||||
|
||||
typedef struct {
|
||||
char base_path[256];
|
||||
} ChunkStore;
|
||||
|
||||
int chunk_store_init(ChunkStore *cs, const char *base_path);
|
||||
void chunk_store_free(ChunkStore *cs);
|
||||
int chunk_store_write(ChunkStore *cs, SHA256 hash, char *data, uint32_t size);
|
||||
int chunk_store_read(ChunkStore *cs, SHA256 hash, char *dst, uint32_t size);
|
||||
bool chunk_store_exists(ChunkStore *cs, SHA256 hash);
|
||||
int chunk_store_delete(ChunkStore *cs, SHA256 hash);
|
||||
|
||||
#endif // CHUNK_STORE_INCLUDED
|
||||
+292
-2474
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,49 @@
|
||||
#ifndef CLIENT_INCLUDED
|
||||
#define CLIENT_INCLUDED
|
||||
|
||||
#include <lib/tcp.h>
|
||||
#include <lib/basic.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "metadata.h"
|
||||
|
||||
typedef struct {
|
||||
|
||||
TCP tcp;
|
||||
|
||||
// True if we are waiting for a response
|
||||
bool pending;
|
||||
Time request_time; // When the current request was sent
|
||||
|
||||
// The operation sent in the current pending request (for logging)
|
||||
MetaOper last_oper;
|
||||
|
||||
// Checker support
|
||||
MetaResult last_result;
|
||||
bool last_was_timeout;
|
||||
bool last_was_rejected;
|
||||
|
||||
Address server_addrs[NODE_LIMIT];
|
||||
int num_servers;
|
||||
|
||||
uint64_t view_number;
|
||||
|
||||
uint64_t client_id;
|
||||
uint64_t request_id;
|
||||
|
||||
Time reconnect_time; // Earliest time to retry connecting to leader
|
||||
|
||||
} ClientState;
|
||||
|
||||
struct pollfd;
|
||||
|
||||
int client_init(void *state, int argc, char **argv,
|
||||
void **ctxs, struct pollfd *pdata, int pcap, int *pnum,
|
||||
int *timeout);
|
||||
|
||||
int client_tick(void *state, void **ctxs,
|
||||
struct pollfd *pdata, int pcap, int *pnum, int *timeout);
|
||||
|
||||
int client_free(void *state);
|
||||
|
||||
#endif // CLIENT_INCLUDED
|
||||
@@ -0,0 +1,70 @@
|
||||
#if defined(MAIN_SIMULATION) || defined(MAIN_TEST)
|
||||
#define QUAKEY_ENABLE_MOCKS
|
||||
#endif
|
||||
|
||||
#include <quakey.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "client_table.h"
|
||||
|
||||
void client_table_init(ClientTable *client_table)
|
||||
{
|
||||
client_table->count = 0;
|
||||
client_table->capacity = 0;
|
||||
client_table->entries = NULL;
|
||||
}
|
||||
|
||||
void client_table_free(ClientTable *client_table)
|
||||
{
|
||||
free(client_table->entries);
|
||||
}
|
||||
|
||||
ClientTableEntry *client_table_find(ClientTable *client_table, uint64_t client_id)
|
||||
{
|
||||
for (int i = 0; i < client_table->count; i++) {
|
||||
if (client_table->entries[i].client_id == client_id)
|
||||
return &client_table->entries[i];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int client_table_add(ClientTable *client_table, uint64_t client_id, uint64_t request_id, int conn_tag)
|
||||
{
|
||||
if (client_table->count == client_table->capacity) {
|
||||
int n = 2 * client_table->capacity;
|
||||
if (n == 0) n = 8;
|
||||
void *p = realloc(client_table->entries, n * sizeof(ClientTableEntry));
|
||||
if (p == NULL)
|
||||
return -1;
|
||||
client_table->capacity = n;
|
||||
client_table->entries = p;
|
||||
}
|
||||
|
||||
client_table->entries[client_table->count++] = (ClientTableEntry) {
|
||||
.client_id = client_id,
|
||||
.last_request_id = request_id,
|
||||
.pending = true,
|
||||
.conn_tag = conn_tag,
|
||||
};
|
||||
return 0;
|
||||
}
|
||||
|
||||
int client_table_insert(ClientTable *client_table, uint64_t client_id, uint64_t request_id)
|
||||
{
|
||||
if (client_table->count == client_table->capacity) {
|
||||
int n = 2 * client_table->capacity;
|
||||
if (n == 0) n = 8;
|
||||
void *p = realloc(client_table->entries, n * sizeof(ClientTableEntry));
|
||||
if (p == NULL)
|
||||
return -1;
|
||||
client_table->capacity = n;
|
||||
client_table->entries = p;
|
||||
}
|
||||
|
||||
client_table->entries[client_table->count++] = (ClientTableEntry) {
|
||||
.client_id=client_id,
|
||||
.last_request_id=request_id,
|
||||
.pending=true,
|
||||
};
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
#ifndef CLIENT_TABLE_INCLUDED
|
||||
#define CLIENT_TABLE_INCLUDED
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "metadata.h"
|
||||
|
||||
typedef struct {
|
||||
uint64_t client_id;
|
||||
uint64_t last_request_id;
|
||||
MetaResult last_result;
|
||||
bool pending; // Only meaningful on the leader that received
|
||||
// the REQUEST. After a view change, a new leader
|
||||
// may find stale entries with pending=false from
|
||||
// a previous view when it was leader before.
|
||||
int conn_tag;
|
||||
} ClientTableEntry;
|
||||
|
||||
typedef struct {
|
||||
int count;
|
||||
int capacity;
|
||||
ClientTableEntry *entries;
|
||||
} ClientTable;
|
||||
|
||||
void client_table_init(ClientTable *client_table);
|
||||
void client_table_free(ClientTable *client_table);
|
||||
ClientTableEntry *client_table_find(ClientTable *client_table, uint64_t client_id);
|
||||
int client_table_add(ClientTable *client_table, uint64_t client_id, uint64_t request_id, int conn_tag);
|
||||
int client_table_insert(ClientTable *client_table, uint64_t client_id, uint64_t request_id);
|
||||
|
||||
#endif // CLIENT_TABLE_INCLUDED
|
||||
+9
-9
@@ -1,13 +1,13 @@
|
||||
#ifndef CONFIG_INCLUDED
|
||||
#define CONFIG_INCLUDED
|
||||
|
||||
#define MAX_SERVER_ADDRS 4
|
||||
#define MAX_CHUNK_SERVERS 128
|
||||
#define MAX_OPERATIONS 128
|
||||
#define MAX_REQUESTS_PER_QUEUE 128
|
||||
#define REPLICATION_FACTOR 3
|
||||
#define SYNC_INTERVAL 10
|
||||
#define RESPONSE_TIME_LIMIT 90
|
||||
#define DELETION_TIMEOUT 30
|
||||
#define NODE_LIMIT 32
|
||||
#define FUTURE_LIMIT 32
|
||||
#define HEARTBEAT_INTERVAL_SEC 1
|
||||
#define PRIMARY_DEATH_TIMEOUT_SEC 5
|
||||
#define RECOVERY_TIMEOUT_SEC 3
|
||||
#define RECOVERY_ATTEMPT_LIMIT 10
|
||||
#define STATE_TRANSFER_TIMEOUT_SEC 2
|
||||
#define VIEW_CHANGE_TIMEOUT_SEC 20
|
||||
|
||||
#endif // CONFIG_INCLUDED
|
||||
#endif // CONFIG_INCLUDED
|
||||
@@ -1,402 +0,0 @@
|
||||
#if defined(MAIN_SIMULATION) || defined(MAIN_TEST)
|
||||
#define QUAKEY_ENABLE_MOCKS
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include <quakey.h>
|
||||
|
||||
#include "file_system.h"
|
||||
|
||||
int rename_file_or_dir(string oldpath, string newpath);
|
||||
|
||||
int file_open(string path, Handle *fd)
|
||||
{
|
||||
#ifdef __linux__
|
||||
char zt[1<<10];
|
||||
if (path.len >= (int) sizeof(zt))
|
||||
return -1;
|
||||
memcpy(zt, path.ptr, path.len);
|
||||
zt[path.len] = '\0';
|
||||
|
||||
int ret = open(zt, O_RDWR | O_CREAT | O_APPEND, 0644);
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
|
||||
*fd = (Handle) { (uint64_t) ret };
|
||||
return 0;
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
WCHAR wpath[MAX_PATH];
|
||||
MultiByteToWideChar(CP_UTF8, 0, path.ptr, path.len, wpath, MAX_PATH);
|
||||
wpath[path.len] = L'\0';
|
||||
|
||||
HANDLE h = CreateFileW(
|
||||
wpath,
|
||||
GENERIC_WRITE | GENERIC_READ,
|
||||
0,
|
||||
NULL,
|
||||
OPEN_ALWAYS,
|
||||
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH,
|
||||
NULL
|
||||
);
|
||||
if (h == INVALID_HANDLE_VALUE)
|
||||
return -1;
|
||||
|
||||
*fd = (Handle) { (uint64_t) h };
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
void file_close(Handle fd)
|
||||
{
|
||||
#ifdef __linux__
|
||||
close((int) fd.data);
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
CloseHandle((HANDLE) fd.data);
|
||||
#endif
|
||||
}
|
||||
|
||||
int file_set_offset(Handle fd, int off)
|
||||
{
|
||||
#ifdef __linux__
|
||||
off_t ret = lseek((int) fd.data, off, SEEK_SET);
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
return 0;
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
LARGE_INTEGER distance;
|
||||
distance.QuadPart = off;
|
||||
if (!SetFilePointer((HANDLE) fd.data, distance.LowPart, &distance.HighPart, FILE_BEGIN))
|
||||
if (GetLastError() != 0)
|
||||
return -1;
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
int file_get_offset(Handle fd, int *off)
|
||||
{
|
||||
#ifdef __linux__
|
||||
off_t ret = lseek((int) fd.data, 0, SEEK_CUR);
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
*off = (int) ret;
|
||||
return 0;
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
DWORD pos = SetFilePointer((HANDLE) fd.data, 0, NULL, FILE_CURRENT);
|
||||
if (pos == INVALID_SET_FILE_POINTER && GetLastError() != 0)
|
||||
return -1;
|
||||
*off = (int) pos;
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
int file_lock(Handle fd)
|
||||
{
|
||||
#ifdef __linux__
|
||||
if (flock((int) fd.data, LOCK_EX) < 0)
|
||||
return -1;
|
||||
return 0;
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
if (!LockFile((HANDLE) fd.data, 0, 0, MAXDWORD, MAXDWORD))
|
||||
return -1;
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
int file_unlock(Handle fd)
|
||||
{
|
||||
#ifdef __linux__
|
||||
if (flock((int) fd.data, LOCK_UN) < 0)
|
||||
return -1;
|
||||
return 0;
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
if (!UnlockFile((HANDLE) fd.data, 0, 0, MAXDWORD, MAXDWORD))
|
||||
return -1;
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
int file_sync(Handle fd)
|
||||
{
|
||||
#ifdef __linux__
|
||||
if (fsync((int) fd.data) < 0)
|
||||
return -1;
|
||||
return 0;
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
if (!FlushFileBuffers((HANDLE) fd.data))
|
||||
return -1;
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
int file_read(Handle fd, char *dst, int max)
|
||||
{
|
||||
#ifdef __linux__
|
||||
return read((int) fd.data, dst, max);
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
DWORD num;
|
||||
if (!ReadFile((HANDLE) fd.data, dst, max, &num, NULL))
|
||||
return -1;
|
||||
if (num > INT_MAX)
|
||||
return -1;
|
||||
return num;
|
||||
#endif
|
||||
}
|
||||
|
||||
int file_write(Handle fd, char *src, int len)
|
||||
{
|
||||
#ifdef __linux__
|
||||
return write((int) fd.data, src, len);
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
DWORD num;
|
||||
if (!WriteFile((HANDLE) fd.data, src, len, &num, NULL))
|
||||
return -1;
|
||||
if (num > INT_MAX)
|
||||
return -1;
|
||||
return num;
|
||||
#endif
|
||||
}
|
||||
|
||||
int file_size(Handle fd, size_t *len)
|
||||
{
|
||||
#ifdef __linux__
|
||||
struct stat buf;
|
||||
if (fstat((int) fd.data, &buf) < 0)
|
||||
return -1;
|
||||
if (buf.st_size < 0 || (uint64_t) buf.st_size > SIZE_MAX)
|
||||
return -1;
|
||||
*len = (size_t) buf.st_size;
|
||||
return 0;
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
LARGE_INTEGER buf;
|
||||
if (!GetFileSizeEx((HANDLE) fd.data, &buf))
|
||||
return -1;
|
||||
if (buf.QuadPart < 0 || (uint64_t) buf.QuadPart > SIZE_MAX)
|
||||
return -1;
|
||||
*len = buf.QuadPart;
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
int create_dir(string path)
|
||||
{
|
||||
char zt[PATH_MAX];
|
||||
if (path.len >= (int) sizeof(zt))
|
||||
return -1;
|
||||
memcpy(zt, path.ptr, path.len);
|
||||
zt[path.len] = '\0';
|
||||
|
||||
#ifdef _WIN32
|
||||
if (_mkdir(zt) < 0)
|
||||
return -1;
|
||||
#else
|
||||
if (mkdir(zt, 0766))
|
||||
return -1;
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int rename_file_or_dir(string oldpath, string newpath)
|
||||
{
|
||||
char oldpath_zt[PATH_MAX];
|
||||
if (oldpath.len >= (int) sizeof(oldpath_zt))
|
||||
return -1;
|
||||
memcpy(oldpath_zt, oldpath.ptr, oldpath.len);
|
||||
oldpath_zt[oldpath.len] = '\0';
|
||||
|
||||
char newpath_zt[PATH_MAX];
|
||||
if (newpath.len >= (int) sizeof(newpath_zt))
|
||||
return -1;
|
||||
memcpy(newpath_zt, newpath.ptr, newpath.len);
|
||||
newpath_zt[newpath.len] = '\0';
|
||||
|
||||
if (rename(oldpath_zt, newpath_zt))
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int remove_file_or_dir(string path)
|
||||
{
|
||||
char path_zt[PATH_MAX];
|
||||
if (path.len >= (int) sizeof(path_zt))
|
||||
return -1;
|
||||
memcpy(path_zt, path.ptr, path.len);
|
||||
path_zt[path.len] = '\0';
|
||||
|
||||
if (remove(path_zt))
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int get_full_path(string path, char *dst)
|
||||
{
|
||||
char path_zt[PATH_MAX];
|
||||
if (path.len >= (int) sizeof(path_zt))
|
||||
return -1;
|
||||
memcpy(path_zt, path.ptr, path.len);
|
||||
path_zt[path.len] = '\0';
|
||||
|
||||
#ifdef __linux__
|
||||
if (realpath(path_zt, dst) == NULL)
|
||||
return -1;
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
if (_fullpath(path_zt, dst, PATH_MAX) == NULL)
|
||||
return -1;
|
||||
#endif
|
||||
|
||||
size_t path_len = strlen(dst);
|
||||
if (path_len > 0 && dst[path_len-1] == '/')
|
||||
dst[path_len-1] = '\0';
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int file_read_all(string path, string *data)
|
||||
{
|
||||
Handle fd;
|
||||
int ret = file_open(path, &fd);
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
|
||||
size_t len;
|
||||
ret = file_size(fd, &len);
|
||||
if (ret < 0) {
|
||||
file_close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
char *dst = malloc(len);
|
||||
if (dst == NULL) {
|
||||
file_close(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int copied = 0;
|
||||
while ((size_t) copied < len) {
|
||||
ret = file_read(fd, dst + copied, len - copied);
|
||||
if (ret < 0) {
|
||||
free(dst);
|
||||
file_close(fd);
|
||||
return -1;
|
||||
}
|
||||
copied += ret;
|
||||
}
|
||||
|
||||
*data = (string) { dst, len };
|
||||
file_close(fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
int directory_scanner_init(DirectoryScanner *scanner, string path)
|
||||
{
|
||||
char pattern[PATH_MAX];
|
||||
int ret = snprintf(pattern, sizeof(pattern), "%.*s\\*", path.len, path.ptr);
|
||||
if (ret < 0 || ret >= (int) sizeof(pattern))
|
||||
return -1;
|
||||
|
||||
scanner->handle = FindFirstFileA(pattern, &scanner->find_data);
|
||||
if (scanner->handle == INVALID_HANDLE_VALUE) {
|
||||
if (GetLastError() == ERROR_FILE_NOT_FOUND) {
|
||||
scanner->done = true;
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
scanner->done = false;
|
||||
scanner->first = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int directory_scanner_next(DirectoryScanner *scanner, string *name)
|
||||
{
|
||||
if (scanner->done)
|
||||
return 1;
|
||||
|
||||
if (!scanner->first) {
|
||||
BOOL ok = FindNextFileA(scanner->handle, &scanner->find_data);
|
||||
if (!ok) {
|
||||
scanner->done = true;
|
||||
if (GetLastError() == ERROR_NO_MORE_FILES)
|
||||
return 1;
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
scanner->first = false;
|
||||
}
|
||||
|
||||
char *p = scanner->find_data.cFileName;
|
||||
*name = (string) { p, strlen(p) };
|
||||
return 0;
|
||||
}
|
||||
|
||||
void directory_scanner_free(DirectoryScanner *scanner)
|
||||
{
|
||||
FindClose(scanner->handle);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
int directory_scanner_init(DirectoryScanner *scanner, string path)
|
||||
{
|
||||
char path_copy[PATH_MAX];
|
||||
if (path.len >= PATH_MAX)
|
||||
return -1;
|
||||
memcpy(path_copy, path.ptr, path.len);
|
||||
path_copy[path.len] = '\0';
|
||||
|
||||
scanner->d = opendir(path_copy);
|
||||
if (scanner->d == NULL) {
|
||||
scanner->done = true;
|
||||
return -1;
|
||||
}
|
||||
|
||||
scanner->done = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int directory_scanner_next(DirectoryScanner *scanner, string *name)
|
||||
{
|
||||
if (scanner->done)
|
||||
return 1;
|
||||
|
||||
scanner->e = readdir(scanner->d);
|
||||
if (scanner->e == NULL) {
|
||||
scanner->done = true;
|
||||
return 1;
|
||||
}
|
||||
|
||||
*name = (string) { scanner->e->d_name, strlen(scanner->e->d_name) };
|
||||
return 0;
|
||||
}
|
||||
|
||||
void directory_scanner_free(DirectoryScanner *scanner)
|
||||
{
|
||||
closedir(scanner->d);
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -1,53 +0,0 @@
|
||||
#ifndef FILE_SYSTEM_INCLUDED
|
||||
#define FILE_SYSTEM_INCLUDED
|
||||
|
||||
#if defined(MAIN_SIMULATION) || defined(MAIN_TEST)
|
||||
#define QUAKEY_ENABLE_MOCKS
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include <quakey.h>
|
||||
|
||||
#include "basic.h"
|
||||
|
||||
typedef struct {
|
||||
uint64_t data;
|
||||
} Handle;
|
||||
|
||||
#ifdef _WIN32
|
||||
typedef struct {
|
||||
HANDLE handle;
|
||||
WIN32_FIND_DATA find_data;
|
||||
bool first;
|
||||
bool done;
|
||||
} DirectoryScanner;
|
||||
#else
|
||||
typedef struct {
|
||||
DIR *d;
|
||||
struct dirent *e;
|
||||
bool done;
|
||||
} DirectoryScanner;
|
||||
#endif
|
||||
|
||||
int file_open(string path, Handle *fd);
|
||||
void file_close(Handle fd);
|
||||
int file_set_offset(Handle fd, int off);
|
||||
int file_get_offset(Handle fd, int *off);
|
||||
int file_lock(Handle fd);
|
||||
int file_unlock(Handle fd);
|
||||
int file_sync(Handle fd);
|
||||
int file_read(Handle fd, char *dst, int max);
|
||||
int file_write(Handle fd, char *src, int len);
|
||||
int file_size(Handle fd, size_t *len);
|
||||
int file_write_atomic(string path, string content);
|
||||
int create_dir(string path);
|
||||
int rename_file_or_dir(string oldpath, string newpath);
|
||||
int remove_file_or_dir(string path);
|
||||
int get_full_path(string path, char *dst);
|
||||
int file_read_all(string path, string *data);
|
||||
|
||||
int directory_scanner_init(DirectoryScanner *scanner, string path);
|
||||
int directory_scanner_next(DirectoryScanner *scanner, string *name);
|
||||
void directory_scanner_free(DirectoryScanner *scanner);
|
||||
|
||||
#endif // FILE_SYSTEM_INCLUDED
|
||||
-820
@@ -1,820 +0,0 @@
|
||||
#if defined(MAIN_SIMULATION) || defined(MAIN_TEST)
|
||||
#define QUAKEY_ENABLE_MOCKS
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include <assert.h>
|
||||
#include <quakey.h>
|
||||
|
||||
#include "basic.h"
|
||||
#include "file_tree.h"
|
||||
|
||||
static int parse_path(string path, string *comps, int max)
|
||||
{
|
||||
bool is_absolute = false;
|
||||
if (path.len > 0 && path.ptr[0] == '/') {
|
||||
is_absolute = true;
|
||||
path.ptr++;
|
||||
path.len--;
|
||||
if (path.len == 0)
|
||||
return 0; // Absolute paths with no components are allowed
|
||||
}
|
||||
|
||||
int num = 0;
|
||||
uint32_t i = 0;
|
||||
for (;;) {
|
||||
|
||||
uint32_t off = i;
|
||||
while (i < (uint32_t) path.len && path.ptr[i] != '/')
|
||||
i++;
|
||||
uint32_t len = i - off;
|
||||
|
||||
if (len == 0)
|
||||
return -1; // Empty component
|
||||
|
||||
string comp = { path.ptr + off, len };
|
||||
if (comp.len == 2 && comp.ptr[0] == '.' && comp.ptr[1] == '.') {
|
||||
if (num == 0) {
|
||||
// For absolute paths, ".." at root is ignored (stays at root)
|
||||
// For relative paths, ".." with no components references parent, which is invalid
|
||||
if (!is_absolute)
|
||||
return -1;
|
||||
// Otherwise, ignore the ".." (absolute path, already at root)
|
||||
} else {
|
||||
num--;
|
||||
}
|
||||
} else if (comp.len != 1 || comp.ptr[0] != '.') {
|
||||
if (num == max)
|
||||
return -1; // To many components
|
||||
comps[num++] = comp;
|
||||
}
|
||||
|
||||
if (i == (uint32_t) path.len)
|
||||
break;
|
||||
|
||||
assert(path.ptr[i] == '/');
|
||||
i++;
|
||||
|
||||
if (i == (uint32_t) path.len)
|
||||
break;
|
||||
}
|
||||
|
||||
return num;
|
||||
}
|
||||
|
||||
static int dir_find(Dir *parent, string name)
|
||||
{
|
||||
for (uint64_t i = 0; i < parent->num_children; i++)
|
||||
if (streq((string) { parent->children[i].name, parent->children[i].name_len }, name))
|
||||
return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static Entity *resolve_path(Entity *root, string *comps, int num_comps)
|
||||
{
|
||||
assert(root->is_dir);
|
||||
|
||||
Entity *current = root;
|
||||
for (int i = 0; i < num_comps; i++) {
|
||||
|
||||
if (!current->is_dir)
|
||||
return NULL;
|
||||
|
||||
int j = dir_find(¤t->d, comps[i]);
|
||||
if (j == -1)
|
||||
return NULL;
|
||||
|
||||
current = ¤t->d.children[j];
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
static void entity_free(Entity *e);
|
||||
static bool entity_uses_hash(Entity *e, SHA256 hash);
|
||||
|
||||
static void dir_init(Dir *d)
|
||||
{
|
||||
d->num_children = 0;
|
||||
d->max_children = 0;
|
||||
d->children = NULL;
|
||||
}
|
||||
|
||||
static void dir_free(Dir *d)
|
||||
{
|
||||
for (uint64_t i = 0; i < d->num_children; i++)
|
||||
entity_free(&d->children[i]);
|
||||
free(d->children);
|
||||
}
|
||||
|
||||
static bool gen_match(uint64_t expected_gen, uint64_t entity_gen)
|
||||
{
|
||||
assert(entity_gen != NO_GENERATION);
|
||||
assert(entity_gen != MISSING_FILE_GENERATION);
|
||||
|
||||
// NO_GENERATION means "skip generation check"
|
||||
if (expected_gen == NO_GENERATION)
|
||||
return true;
|
||||
|
||||
// MISSING_FILE_GENERATION means "expect file to NOT exist"
|
||||
// Since we're checking against an existing entity, this is a mismatch
|
||||
if (expected_gen == MISSING_FILE_GENERATION)
|
||||
return false;
|
||||
|
||||
return expected_gen == entity_gen;
|
||||
}
|
||||
|
||||
static uint64_t create_generation(uint64_t *next_gen)
|
||||
{
|
||||
(*next_gen)++;
|
||||
if (*next_gen == 0 || *next_gen == UINT64_MAX)
|
||||
*next_gen = 1;
|
||||
return *next_gen;
|
||||
}
|
||||
|
||||
static int dir_remove(Dir *d, int idx, uint64_t expected_gen)
|
||||
{
|
||||
if (!gen_match(expected_gen, d->children[idx].gen))
|
||||
return -1;
|
||||
|
||||
// TODO: pretty sure this leaks memory
|
||||
d->children[idx] = d->children[--d->num_children];
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool dir_uses_hash(Dir *d, SHA256 hash)
|
||||
{
|
||||
for (uint64_t i = 0; i < d->num_children; i++)
|
||||
if (entity_uses_hash(&d->children[i], hash))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static void file_init(File *f, uint64_t chunk_size)
|
||||
{
|
||||
f->chunk_size = chunk_size;
|
||||
f->num_chunks = 0;
|
||||
f->file_size = 0;
|
||||
f->chunks = NULL;
|
||||
}
|
||||
|
||||
static void file_free(File *f)
|
||||
{
|
||||
free(f->chunks);
|
||||
f->chunks = NULL;
|
||||
}
|
||||
|
||||
static bool file_uses_hash(File *f, SHA256 hash)
|
||||
{
|
||||
for (uint64_t i = 0; i < f->num_chunks; i++)
|
||||
if (!memcmp(&f->chunks[i], &hash, sizeof(SHA256)))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Fails when the name is too long
|
||||
static int entity_init(Entity *e, char *name, int name_len,
|
||||
bool is_dir, uint64_t chunk_size, uint64_t *next_gen)
|
||||
{
|
||||
if (name_len >= (int) sizeof(e->name))
|
||||
return -1;
|
||||
|
||||
e->gen = create_generation(next_gen);
|
||||
assert(e->gen != NO_GENERATION);
|
||||
|
||||
memcpy(e->name, name, name_len);
|
||||
e->name[name_len] = '\0';
|
||||
e->name_len = (uint16_t) name_len;
|
||||
|
||||
e->is_dir = is_dir;
|
||||
if (is_dir)
|
||||
dir_init(&e->d);
|
||||
else
|
||||
file_init(&e->f, chunk_size);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void entity_free(Entity *e)
|
||||
{
|
||||
if (e->is_dir)
|
||||
dir_free(&e->d);
|
||||
else
|
||||
file_free(&e->f);
|
||||
}
|
||||
|
||||
static bool entity_uses_hash(Entity *e, SHA256 hash)
|
||||
{
|
||||
if (e->is_dir)
|
||||
return dir_uses_hash(&e->d, hash);
|
||||
else
|
||||
return file_uses_hash(&e->f, hash);
|
||||
}
|
||||
|
||||
int file_tree_init(FileTree *ft)
|
||||
{
|
||||
ft->next_gen = 1;
|
||||
|
||||
int ret = entity_init(&ft->root, "", 0, true, 0, &ft->next_gen);
|
||||
if (ret < 0) return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void file_tree_free(FileTree *ft)
|
||||
{
|
||||
entity_free(&ft->root);
|
||||
}
|
||||
|
||||
bool file_tree_uses_hash(FileTree *ft, SHA256 hash)
|
||||
{
|
||||
return entity_uses_hash(&ft->root, hash);
|
||||
}
|
||||
|
||||
int file_tree_list(FileTree *ft, string path,
|
||||
ListItem *items, int max_items, uint64_t *gen)
|
||||
{
|
||||
int num_comps;
|
||||
string comps[MAX_COMPS];
|
||||
|
||||
num_comps = parse_path(path, comps, MAX_COMPS);
|
||||
if (num_comps < 0)
|
||||
return FILETREE_BADPATH;
|
||||
|
||||
Entity *e = resolve_path(&ft->root, comps, num_comps);
|
||||
|
||||
if (e == NULL)
|
||||
return FILETREE_NOENT;
|
||||
|
||||
if (!e->is_dir)
|
||||
return FILETREE_NOTDIR;
|
||||
|
||||
Dir *d = &e->d;
|
||||
|
||||
int num_items = d->num_children;
|
||||
if (num_items > max_items) num_items = max_items;
|
||||
for (int i = 0; i < num_items; i++) {
|
||||
|
||||
Entity *c = &d->children[i];
|
||||
|
||||
int name_cpy = c->name_len;
|
||||
if (name_cpy > (int) sizeof(items[i].name)-1)
|
||||
name_cpy = (int) sizeof(items[i].name)-1;
|
||||
|
||||
memcpy(items[i].name, c->name, name_cpy);
|
||||
items[i].name[name_cpy] = '\0';
|
||||
|
||||
items[i].name_len = name_cpy;
|
||||
items[i].is_dir = c->is_dir;
|
||||
items[i].gen = c->gen;
|
||||
}
|
||||
|
||||
assert(e->gen != NO_GENERATION);
|
||||
*gen = e->gen;
|
||||
|
||||
return d->num_children;
|
||||
}
|
||||
|
||||
int file_tree_create_entity(FileTree *ft, string path,
|
||||
bool is_dir, uint64_t chunk_size, uint64_t *gen)
|
||||
{
|
||||
int num_comps;
|
||||
string comps[MAX_COMPS];
|
||||
|
||||
num_comps = parse_path(path, comps, MAX_COMPS);
|
||||
|
||||
if (num_comps < 0)
|
||||
// Couldn't parse path
|
||||
return FILETREE_BADPATH;
|
||||
|
||||
if (num_comps == 0)
|
||||
// Path is empty, which means the caller is referencing the root,
|
||||
// which exists already.
|
||||
return FILETREE_EXISTS;
|
||||
|
||||
// Resolve the path up to the second last component
|
||||
Entity *e = resolve_path(&ft->root, comps, num_comps-1);
|
||||
|
||||
if (e == NULL)
|
||||
// Parent directory doesn't exist
|
||||
return FILETREE_NOENT;
|
||||
|
||||
if (!e->is_dir)
|
||||
// Parent entity is not a directory
|
||||
return FILETREE_NOTDIR;
|
||||
|
||||
string name = comps[num_comps-1];
|
||||
if (dir_find(&e->d, name) != -1)
|
||||
return FILETREE_EXISTS;
|
||||
|
||||
Dir *d = &e->d;
|
||||
if (d->num_children == d->max_children) {
|
||||
|
||||
int new_max = 2 * d->max_children;
|
||||
if (new_max == 0)
|
||||
new_max = 8;
|
||||
|
||||
Entity *p = malloc(sizeof(Entity) * new_max);
|
||||
if (p == NULL)
|
||||
return FILETREE_NOMEM;
|
||||
|
||||
for (uint64_t i = 0; i < d->num_children; i++)
|
||||
p[i] = d->children[i];
|
||||
|
||||
free(d->children);
|
||||
d->children = p;
|
||||
d->max_children = new_max;
|
||||
}
|
||||
Entity *c = &d->children[d->num_children];
|
||||
|
||||
int ret = entity_init(c, (char*) name.ptr, name.len, is_dir, chunk_size, &ft->next_gen);
|
||||
if (ret < 0)
|
||||
// Invalid name for the new file
|
||||
return FILETREE_BADPATH;
|
||||
|
||||
assert(e->gen != NO_GENERATION);
|
||||
*gen = e->gen;
|
||||
|
||||
d->num_children++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// TODO: this should return the list of unreferenced hashes
|
||||
int file_tree_delete_entity(FileTree *ft, string path,
|
||||
uint64_t expected_gen)
|
||||
{
|
||||
int num_comps;
|
||||
string comps[MAX_COMPS];
|
||||
|
||||
num_comps = parse_path(path, comps, MAX_COMPS);
|
||||
if (num_comps < 0)
|
||||
return FILETREE_BADPATH;
|
||||
if (num_comps == 0)
|
||||
return FILETREE_BADOP;
|
||||
|
||||
Entity *e = resolve_path(&ft->root, comps, num_comps-1);
|
||||
if (e == NULL)
|
||||
return FILETREE_NOENT;
|
||||
if (!e->is_dir)
|
||||
return FILETREE_NOTDIR;
|
||||
|
||||
int i = dir_find(&e->d, comps[num_comps-1]);
|
||||
if (i == -1) {
|
||||
// File doesn't exist
|
||||
// If caller expected it not to exist (MISSING_FILE_GENERATION), succeed
|
||||
if (expected_gen == MISSING_FILE_GENERATION)
|
||||
return 0;
|
||||
return FILETREE_NOENT;
|
||||
}
|
||||
|
||||
// File exists - check generation
|
||||
if (dir_remove(&e->d, i, expected_gen) < 0)
|
||||
return FILETREE_BADGEN;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int file_tree_write(
|
||||
FileTree* ft,
|
||||
string path,
|
||||
uint64_t off,
|
||||
uint64_t len,
|
||||
uint32_t num_chunks,
|
||||
uint64_t expect_gen,
|
||||
uint64_t* new_gen,
|
||||
SHA256* hashes,
|
||||
SHA256* removed_hashes,
|
||||
int* num_removed,
|
||||
bool truncate_after)
|
||||
{
|
||||
// WRITE operations cannot use expect_gen=0
|
||||
if (expect_gen == NO_GENERATION)
|
||||
return FILETREE_BADGEN;
|
||||
|
||||
int num_comps;
|
||||
string comps[MAX_COMPS];
|
||||
|
||||
num_comps = parse_path(path, comps, MAX_COMPS);
|
||||
if (num_comps < 0)
|
||||
return FILETREE_BADPATH;
|
||||
|
||||
Entity *e = resolve_path(&ft->root, comps, num_comps);
|
||||
|
||||
if (e == NULL) {
|
||||
// File doesn't exist
|
||||
// If caller expected it not to exist (MISSING_FILE_GENERATION), that's correct
|
||||
// but we still can't write to a non-existent file (need CREATE_IF_MISSING flag in client layer)
|
||||
if (expect_gen == MISSING_FILE_GENERATION)
|
||||
return FILETREE_NOENT; // Expected behavior: file missing as expected, but can't write
|
||||
return FILETREE_NOENT;
|
||||
}
|
||||
|
||||
if (e->is_dir)
|
||||
return FILETREE_ISDIR;
|
||||
|
||||
// Check generation - will fail if expect_gen is MISSING_FILE_GENERATION (expects missing but file exists)
|
||||
if (!gen_match(expect_gen, e->gen))
|
||||
return FILETREE_BADGEN;
|
||||
|
||||
File *f = &e->f;
|
||||
|
||||
uint64_t first_chunk_index = off / f->chunk_size;
|
||||
uint64_t last_chunk_index = first_chunk_index + (len - 1) / f->chunk_size;
|
||||
|
||||
assert(last_chunk_index - first_chunk_index + 1 == num_chunks);
|
||||
|
||||
if (last_chunk_index >= f->num_chunks) {
|
||||
uint64_t old_num_chunks = f->num_chunks;
|
||||
SHA256 *new_chunks = malloc((last_chunk_index+1) * sizeof(SHA256));
|
||||
if (new_chunks == NULL)
|
||||
return FILETREE_NOMEM;
|
||||
if (f->chunks) {
|
||||
if (f->num_chunks > 0)
|
||||
memcpy(new_chunks, f->chunks, f->num_chunks * sizeof(SHA256));
|
||||
free(f->chunks);
|
||||
}
|
||||
f->chunks = new_chunks;
|
||||
f->num_chunks = last_chunk_index+1;
|
||||
for (uint64_t i = old_num_chunks; i < last_chunk_index+1; i++)
|
||||
memset(&f->chunks[i], 0, sizeof(SHA256));
|
||||
}
|
||||
|
||||
int num_overwritten_hashes = 0;
|
||||
SHA256 overwritten_hashes[100]; // TODO: fix this limit
|
||||
if (num_chunks > 100) {
|
||||
assert(0); // TODO
|
||||
}
|
||||
|
||||
// Update chunks
|
||||
for (uint64_t i = first_chunk_index; i <= last_chunk_index; i++) {
|
||||
overwritten_hashes[num_overwritten_hashes++] = f->chunks[i];
|
||||
f->chunks[i] = hashes[i - first_chunk_index];
|
||||
}
|
||||
|
||||
// Update file size (last byte written + 1)
|
||||
uint64_t new_size = off + len;
|
||||
if (truncate_after) {
|
||||
// With truncation, set file size to exactly new_size and remove chunks beyond
|
||||
uint64_t new_num_chunks = last_chunk_index + 1;
|
||||
|
||||
// Add any chunks beyond the write to the overwritten list (they'll be removed)
|
||||
for (uint64_t i = new_num_chunks; i < f->num_chunks; i++) {
|
||||
if (num_overwritten_hashes < 100) { // Respect the limit
|
||||
overwritten_hashes[num_overwritten_hashes++] = f->chunks[i];
|
||||
}
|
||||
}
|
||||
|
||||
f->num_chunks = new_num_chunks;
|
||||
f->file_size = new_size;
|
||||
} else {
|
||||
// Without truncation, only grow the file
|
||||
if (new_size > f->file_size)
|
||||
f->file_size = new_size;
|
||||
}
|
||||
|
||||
// Now check which old hashes are no longer used
|
||||
// anywhere in the tree
|
||||
//
|
||||
// NOTE: If removed_hashes is NULL, the caller isn't
|
||||
// interested in which hashes are no longer reachable.
|
||||
if (removed_hashes != NULL) {
|
||||
*num_removed = 0;
|
||||
for (int i = 0; i < num_overwritten_hashes; i++) {
|
||||
|
||||
SHA256 hash = overwritten_hashes[i];
|
||||
|
||||
// Skip zero hashes
|
||||
bool is_zero = true;
|
||||
for (int j = 0; j < (int) sizeof(SHA256); j++) {
|
||||
if (hash.data[j] != 0) {
|
||||
is_zero = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (is_zero)
|
||||
continue;
|
||||
|
||||
// Check if this hash is still used anywhere in the tree
|
||||
if (!entity_uses_hash(&ft->root, hash)) {
|
||||
removed_hashes[*num_removed] = hash;
|
||||
(*num_removed)++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
e->gen = create_generation(&ft->next_gen);
|
||||
assert(e->gen != NO_GENERATION);
|
||||
|
||||
*new_gen = e->gen;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int file_tree_read(FileTree *ft, string path,
|
||||
uint64_t off, uint64_t len, uint64_t *gen, uint64_t *chunk_size,
|
||||
SHA256 *hashes, int max_hashes, uint64_t *actual_bytes)
|
||||
{
|
||||
// Initialize gen to NO_GENERATION so error paths have a well-defined value
|
||||
*gen = NO_GENERATION;
|
||||
|
||||
int num_comps;
|
||||
string comps[MAX_COMPS];
|
||||
|
||||
num_comps = parse_path(path, comps, MAX_COMPS);
|
||||
if (num_comps < 0)
|
||||
return FILETREE_BADPATH;
|
||||
|
||||
Entity *e = resolve_path(&ft->root, comps, num_comps);
|
||||
|
||||
if (e == NULL)
|
||||
return FILETREE_NOENT;
|
||||
|
||||
if (e->is_dir)
|
||||
return FILETREE_ISDIR;
|
||||
|
||||
File *f = &e->f;
|
||||
|
||||
*chunk_size = f->chunk_size;
|
||||
|
||||
// Calculate actual bytes that can be read based on actual file size
|
||||
if (off >= f->file_size) {
|
||||
*actual_bytes = 0;
|
||||
} else if (off + len > f->file_size) {
|
||||
*actual_bytes = f->file_size - off;
|
||||
} else {
|
||||
*actual_bytes = len;
|
||||
}
|
||||
|
||||
if (len == 0) {
|
||||
assert(e->gen != NO_GENERATION);
|
||||
*gen = e->gen;
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t first_chunk_index = off / f->chunk_size;
|
||||
uint64_t last_chunk_index = first_chunk_index + (len - 1) / f->chunk_size;
|
||||
|
||||
if (first_chunk_index >= f->num_chunks) {
|
||||
*gen = e->gen;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (last_chunk_index >= f->num_chunks) {
|
||||
if (f->num_chunks == 0) {
|
||||
assert(e->gen != NO_GENERATION);
|
||||
*gen = e->gen;
|
||||
return 0;
|
||||
}
|
||||
last_chunk_index = f->num_chunks-1;
|
||||
}
|
||||
|
||||
int num_hashes = 0;
|
||||
for (uint32_t i = first_chunk_index; i <= last_chunk_index; i++) {
|
||||
|
||||
SHA256 hash = f->chunks[i];
|
||||
|
||||
if (num_hashes < max_hashes)
|
||||
hashes[num_hashes] = hash;
|
||||
num_hashes++;
|
||||
}
|
||||
|
||||
assert(e->gen != NO_GENERATION);
|
||||
*gen = e->gen;
|
||||
|
||||
return num_hashes;
|
||||
}
|
||||
|
||||
string file_tree_strerror(int code)
|
||||
{
|
||||
switch (code) {
|
||||
case FILETREE_NOMEM : return S("Out of memory");
|
||||
case FILETREE_NOENT : return S("No such file or directory");
|
||||
case FILETREE_NOTDIR : return S("Entity is not a directory");
|
||||
case FILETREE_ISDIR : return S("Entity is a directory");
|
||||
case FILETREE_EXISTS : return S("File or directory already exists");
|
||||
case FILETREE_BADPATH: return S("Invalid path");
|
||||
case FILETREE_BADOP : return S("Invalid operation");
|
||||
case FILETREE_BADGEN : return S("Generation counter mismatch or invalid value");
|
||||
default:break;
|
||||
}
|
||||
return S("Unknown error");
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
int (*write_fn)(char*,int,void*);
|
||||
void *write_data;
|
||||
char *buffer;
|
||||
int buffer_size;
|
||||
int buffer_used;
|
||||
bool error;
|
||||
} SerializeContext;
|
||||
|
||||
static void sc_flush(SerializeContext *sc)
|
||||
{
|
||||
if (sc->error)
|
||||
return;
|
||||
|
||||
int ret = sc->write_fn(sc->buffer, sc->buffer_used, sc->write_data);
|
||||
if (ret < 0) {
|
||||
sc->error = true;
|
||||
return;
|
||||
}
|
||||
|
||||
sc->buffer_used = 0;
|
||||
}
|
||||
|
||||
static void sc_write_mem(SerializeContext *sc, char *src, int len)
|
||||
{
|
||||
if (sc->error)
|
||||
return;
|
||||
|
||||
if (sc->buffer_size - sc->buffer_used < len) {
|
||||
|
||||
if (len > sc->buffer_size) {
|
||||
sc->error = true;
|
||||
return;
|
||||
}
|
||||
|
||||
sc_flush(sc);
|
||||
if (sc->error)
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(sc->buffer + sc->buffer_used, src, len);
|
||||
sc->buffer_used += len;
|
||||
}
|
||||
static void sc_write_u8 (SerializeContext *sc, uint8_t value) { sc_write_mem(sc, (char*) &value, (int) sizeof(value)); }
|
||||
static void sc_write_u16 (SerializeContext *sc, uint16_t value) { sc_write_mem(sc, (char*) &value, (int) sizeof(value)); }
|
||||
static void sc_write_u64 (SerializeContext *sc, uint64_t value) { sc_write_mem(sc, (char*) &value, (int) sizeof(value)); }
|
||||
static void sc_write_hash(SerializeContext *sc, SHA256 value) { sc_write_mem(sc, (char*) &value, (int) sizeof(value)); }
|
||||
|
||||
static void file_serialize(SerializeContext *sc, File *f)
|
||||
{
|
||||
sc_write_u64(sc, f->chunk_size);
|
||||
sc_write_u64(sc, f->num_chunks);
|
||||
sc_write_u64(sc, f->file_size);
|
||||
for (uint64_t i = 0; i < f->num_chunks; i++)
|
||||
sc_write_hash(sc, f->chunks[i]);
|
||||
}
|
||||
|
||||
static void entity_serialize(SerializeContext *sc, Entity *e);
|
||||
|
||||
static void dir_serialize(SerializeContext *sc, Dir *d)
|
||||
{
|
||||
sc_write_u64(sc, d->num_children);
|
||||
for (uint64_t i = 0; i < d->num_children; i++)
|
||||
entity_serialize(sc, &d->children[i]);
|
||||
}
|
||||
|
||||
static void entity_serialize(SerializeContext *sc, Entity *e)
|
||||
{
|
||||
sc_write_u16(sc, e->name_len);
|
||||
sc_write_mem(sc, e->name, e->name_len);
|
||||
sc_write_u8(sc, e->is_dir);
|
||||
if (e->is_dir)
|
||||
dir_serialize(sc, &e->d);
|
||||
else
|
||||
file_serialize(sc, &e->f);
|
||||
}
|
||||
|
||||
int file_tree_serialize(FileTree *ft, int (*write_fn)(char*,int,void*), void *write_data)
|
||||
{
|
||||
SerializeContext sc;
|
||||
sc.write_fn = write_fn;
|
||||
sc.write_data = write_data;
|
||||
sc.buffer_used = 0;
|
||||
sc.buffer_size = 1<<10;
|
||||
sc.buffer = malloc(sc.buffer_size);
|
||||
sc.error = false;
|
||||
if (sc.buffer == NULL)
|
||||
sc.error = true;
|
||||
entity_serialize(&sc, &ft->root);
|
||||
sc_flush(&sc);
|
||||
free(sc.buffer);
|
||||
if (sc.error)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
int (*read_fn)(char*,int,void*);
|
||||
void *read_data;
|
||||
char *buffer;
|
||||
int buffer_size;
|
||||
int buffer_used;
|
||||
int buffer_head;
|
||||
bool error;
|
||||
uint64_t total_read;
|
||||
} DeserializeContext;
|
||||
|
||||
static void dc_read_mem(DeserializeContext *dc, void *dst, int len)
|
||||
{
|
||||
if (dc->error)
|
||||
return;
|
||||
|
||||
if (dc->buffer_used < len) {
|
||||
|
||||
if (dc->buffer_size < len) {
|
||||
dc->error = true;
|
||||
return;
|
||||
}
|
||||
|
||||
memmove(dc->buffer, dc->buffer + dc->buffer_head, dc->buffer_used);
|
||||
dc->buffer_head = 0;
|
||||
|
||||
int ret = dc->read_fn(
|
||||
dc->buffer + dc->buffer_used,
|
||||
dc->buffer_size - dc->buffer_used,
|
||||
dc->read_data);
|
||||
if (ret < 0) {
|
||||
dc->error = true;
|
||||
return;
|
||||
}
|
||||
dc->buffer_used += ret;
|
||||
|
||||
if (dc->buffer_used < len) {
|
||||
dc->error = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
memcpy(dst, dc->buffer + dc->buffer_head, len);
|
||||
dc->buffer_head += len;
|
||||
dc->buffer_used -= len;
|
||||
dc->total_read += len;
|
||||
}
|
||||
static void dc_read_u8 (DeserializeContext *dc, uint8_t *dst) { dc_read_mem(dc, dst, sizeof(*dst)); }
|
||||
static void dc_read_u16(DeserializeContext *dc, uint16_t *dst) { dc_read_mem(dc, dst, sizeof(*dst)); }
|
||||
static void dc_read_u64(DeserializeContext *dc, uint64_t *dst) { dc_read_mem(dc, dst, sizeof(*dst)); }
|
||||
static void dc_read_hash(DeserializeContext *dc, SHA256 *dst) { dc_read_mem(dc, dst, sizeof(*dst)); }
|
||||
|
||||
static void file_deserialize(DeserializeContext *dc, File *f)
|
||||
{
|
||||
dc_read_u64(dc, &f->chunk_size);
|
||||
dc_read_u64(dc, &f->num_chunks);
|
||||
dc_read_u64(dc, &f->file_size);
|
||||
|
||||
f->chunks = malloc(f->num_chunks * sizeof(SHA256));
|
||||
if (f->chunks == NULL) {
|
||||
assert(0); // TODO
|
||||
}
|
||||
|
||||
for (uint64_t i = 0; i < f->num_chunks; i++)
|
||||
dc_read_hash(dc, &f->chunks[i]);
|
||||
}
|
||||
|
||||
static void entity_deserialize(DeserializeContext *dc, Entity *e);
|
||||
|
||||
static void dir_deserialize(DeserializeContext *dc, Dir *d)
|
||||
{
|
||||
dc_read_u64(dc, &d->num_children);
|
||||
|
||||
d->max_children = d->num_children;
|
||||
d->children = malloc(d->num_children * sizeof(Entity));
|
||||
if (d->children == NULL) {
|
||||
assert(0); // TODO
|
||||
}
|
||||
|
||||
// TODO: not checking for errors is not okay as
|
||||
// the code will branch based on garbage
|
||||
// values.
|
||||
for (uint64_t i = 0; i < d->num_children; i++)
|
||||
entity_deserialize(dc, &d->children[i]);
|
||||
}
|
||||
|
||||
static void entity_deserialize(DeserializeContext *dc, Entity *e)
|
||||
{
|
||||
dc_read_u16(dc, &e->name_len); // TODO: make sure this doesn't go over the static buffer
|
||||
dc_read_mem(dc, e->name, e->name_len);
|
||||
|
||||
uint8_t is_dir;
|
||||
dc_read_u8 (dc, &is_dir);
|
||||
e->is_dir = (is_dir != 0);
|
||||
|
||||
if (e->is_dir)
|
||||
dir_deserialize(dc, &e->d);
|
||||
else
|
||||
file_deserialize(dc, &e->f);
|
||||
}
|
||||
|
||||
int file_tree_deserialize(FileTree *ft, int (*read_fn)(char*,int,void*), void *read_data)
|
||||
{
|
||||
DeserializeContext dc;
|
||||
dc.read_fn = read_fn;
|
||||
dc.read_data = read_data;
|
||||
dc.buffer_head = 0;
|
||||
dc.buffer_used = 0;
|
||||
dc.buffer_size = 1<<10;
|
||||
dc.buffer = malloc(dc.buffer_size);
|
||||
dc.error = false;
|
||||
if (dc.buffer == NULL)
|
||||
dc.error = true;
|
||||
dc.total_read = 0;
|
||||
entity_deserialize(&dc, &ft->root);
|
||||
free(dc.buffer);
|
||||
if (dc.error)
|
||||
return -1;
|
||||
if (dc.total_read > INT_MAX) {
|
||||
assert(0); // TODO
|
||||
}
|
||||
return dc.total_read;
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
#ifndef FILE_TREE_INCLUDED
|
||||
#define FILE_TREE_INCLUDED
|
||||
|
||||
#include "basic.h"
|
||||
|
||||
// Special generation counter values:
|
||||
// NO_GENERATION (0): Skip generation check (accept any generation)
|
||||
// MISSING_FILE_GENERATION (UINT64_MAX): Expect file/directory to NOT exist
|
||||
#define NO_GENERATION ((uint64_t) 0)
|
||||
#define MISSING_FILE_GENERATION ((uint64_t) UINT64_MAX)
|
||||
|
||||
enum {
|
||||
FILETREE_NOMEM = -1,
|
||||
FILETREE_NOENT = -2,
|
||||
FILETREE_NOTDIR = -3,
|
||||
FILETREE_ISDIR = -4,
|
||||
FILETREE_EXISTS = -5,
|
||||
FILETREE_BADPATH = -6,
|
||||
FILETREE_BADOP = -7,
|
||||
FILETREE_BADGEN = -8,
|
||||
};
|
||||
|
||||
typedef struct Entity Entity;
|
||||
|
||||
typedef struct {
|
||||
uint64_t chunk_size; // TODO: this should be an u32
|
||||
uint64_t num_chunks; // TODO: and this too
|
||||
uint64_t file_size; // Offset of last byte written + 1
|
||||
SHA256 *chunks;
|
||||
} File;
|
||||
|
||||
typedef struct {
|
||||
uint64_t max_children;
|
||||
uint64_t num_children;
|
||||
Entity *children;
|
||||
} Dir;
|
||||
|
||||
struct Entity {
|
||||
uint64_t gen;
|
||||
char name[1<<8];
|
||||
uint16_t name_len;
|
||||
bool is_dir;
|
||||
union {
|
||||
Dir d;
|
||||
File f;
|
||||
};
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
Entity root;
|
||||
uint64_t next_gen;
|
||||
} FileTree;
|
||||
|
||||
typedef struct {
|
||||
char name[1<<8];
|
||||
int name_len;
|
||||
bool is_dir;
|
||||
uint64_t gen;
|
||||
} ListItem;
|
||||
|
||||
#define MAX_COMPS 32
|
||||
|
||||
int file_tree_init(FileTree *ft);
|
||||
|
||||
void file_tree_free(FileTree *ft);
|
||||
|
||||
bool file_tree_uses_hash(FileTree *ft, SHA256 hash);
|
||||
|
||||
int file_tree_list(FileTree *ft, string path,
|
||||
ListItem *items, int max_items, uint64_t *gen);
|
||||
|
||||
int file_tree_create_entity(FileTree *ft, string path,
|
||||
bool is_dir, uint64_t chunk_size, uint64_t *gen);
|
||||
|
||||
int file_tree_delete_entity(FileTree *ft, string path,
|
||||
uint64_t expected_gen);
|
||||
|
||||
int file_tree_write(FileTree *ft, string path,
|
||||
uint64_t off, uint64_t len, uint32_t num_chunks,
|
||||
uint64_t expect_gen,
|
||||
uint64_t *new_gen,
|
||||
SHA256 *hashes,
|
||||
SHA256 *removed_hashes,
|
||||
int *num_removed,
|
||||
bool truncate_after);
|
||||
|
||||
int file_tree_read(FileTree *ft, string path,
|
||||
uint64_t off, uint64_t len, uint64_t *gen, uint64_t *chunk_size,
|
||||
SHA256 *hashes, int max_hashes, uint64_t *actual_bytes);
|
||||
|
||||
string file_tree_strerror(int code);
|
||||
|
||||
int file_tree_serialize(FileTree *ft, int (*flush_fn)(char*,int,void*), void *flush_data);
|
||||
|
||||
int file_tree_deserialize(FileTree *ft, int (*read_fn)(char*,int,void*), void *read_data);
|
||||
|
||||
#endif // FILE_TREE_INCLUDED
|
||||
-171
@@ -1,171 +0,0 @@
|
||||
#if defined(MAIN_SIMULATION) || defined(MAIN_TEST)
|
||||
#define QUAKEY_ENABLE_MOCKS
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include <quakey.h>
|
||||
|
||||
#include "hash_set.h"
|
||||
|
||||
void hash_set_init(HashSet *set)
|
||||
{
|
||||
set->items = NULL;
|
||||
set->count = 0;
|
||||
set->capacity = 0;
|
||||
}
|
||||
|
||||
void hash_set_free(HashSet *set)
|
||||
{
|
||||
free(set->items);
|
||||
set->items = NULL;
|
||||
}
|
||||
|
||||
void hash_set_clear(HashSet *set)
|
||||
{
|
||||
free(set->items);
|
||||
set->items = NULL;
|
||||
set->count = 0;
|
||||
set->capacity = 0;
|
||||
}
|
||||
|
||||
int hash_set_insert(HashSet *set, SHA256 hash)
|
||||
{
|
||||
// Avoid duplicates
|
||||
for (int i = 0; i < set->count; i++)
|
||||
if (!memcmp(&set->items[i], &hash, sizeof(SHA256)))
|
||||
return 0; // Already present
|
||||
|
||||
if (set->count == set->capacity) {
|
||||
|
||||
int new_capacity;
|
||||
if (set->items == NULL)
|
||||
new_capacity = 16;
|
||||
else
|
||||
new_capacity = 2 * set->capacity;
|
||||
|
||||
SHA256 *new_items = realloc(set->items, new_capacity * sizeof(SHA256));
|
||||
if (new_items == NULL)
|
||||
return -1;
|
||||
|
||||
set->items = new_items;
|
||||
set->capacity = new_capacity;
|
||||
}
|
||||
|
||||
set->items[set->count++] = hash;
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool hash_set_remove(HashSet *set, SHA256 hash)
|
||||
{
|
||||
for (int i = 0; i < set->count; i++)
|
||||
if (!memcmp(&hash, &set->items[i], sizeof(SHA256))) {
|
||||
set->items[i] = set->items[--set->count];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool hash_set_contains(HashSet *set, SHA256 hash)
|
||||
{
|
||||
for (int i = 0; i < set->count; i++)
|
||||
if (!memcmp(&hash, &set->items[i], sizeof(SHA256)))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
int hash_set_merge(HashSet *dst, HashSet src)
|
||||
{
|
||||
HashSet ret;
|
||||
hash_set_init(&ret);
|
||||
|
||||
for (int i = 0; i < dst->count; i++) {
|
||||
if (hash_set_insert(&ret, dst->items[i]) < 0)
|
||||
goto error;
|
||||
}
|
||||
|
||||
for (int i = 0; i < src.count; i++) {
|
||||
if (hash_set_insert(&ret, src.items[i]) < 0)
|
||||
goto error;
|
||||
}
|
||||
|
||||
hash_set_free(dst);
|
||||
*dst = ret;
|
||||
return 0;
|
||||
|
||||
error:
|
||||
hash_set_free(&ret);
|
||||
return -1;
|
||||
}
|
||||
|
||||
void hash_set_remove_set(HashSet *dst, HashSet src)
|
||||
{
|
||||
for (int i = 0; i < src.count; i++)
|
||||
hash_set_remove(dst, src.items[i]);
|
||||
}
|
||||
|
||||
void timed_hash_set_init(TimedHashSet *set)
|
||||
{
|
||||
set->items = NULL;
|
||||
set->count = 0;
|
||||
set->capacity = 0;
|
||||
}
|
||||
|
||||
void timed_hash_set_free(TimedHashSet *set)
|
||||
{
|
||||
free(set->items);
|
||||
set->items = NULL;
|
||||
}
|
||||
|
||||
int timed_hash_set_find(TimedHashSet *set, SHA256 hash)
|
||||
{
|
||||
for (int i = 0; i < set->count; i++)
|
||||
if (!memcmp(&set->items[i].hash, &hash, sizeof(SHA256)))
|
||||
return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int timed_hash_set_insert(TimedHashSet *set, SHA256 hash, Time time)
|
||||
{
|
||||
// Check if already in set
|
||||
int idx = timed_hash_set_find(set, hash);
|
||||
if (idx >= 0) {
|
||||
// Already marked, keep the original time
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (set->count == set->capacity) {
|
||||
int new_capacity;
|
||||
if (set->capacity == 0)
|
||||
new_capacity = 8;
|
||||
else
|
||||
new_capacity = 2 * set->capacity;
|
||||
|
||||
TimedHash *new_items = malloc(new_capacity * sizeof(TimedHash));
|
||||
if (new_items == NULL)
|
||||
return -1;
|
||||
|
||||
if (set->capacity > 0) {
|
||||
memcpy(new_items, set->items, set->count * sizeof(set->items[0]));
|
||||
free(set->items);
|
||||
}
|
||||
|
||||
set->items = new_items;
|
||||
set->capacity = new_capacity;
|
||||
}
|
||||
|
||||
set->items[set->count++] = (TimedHash) { hash, time };
|
||||
return 0;
|
||||
}
|
||||
|
||||
void timed_hash_set_remove(TimedHashSet *set, SHA256 hash)
|
||||
{
|
||||
int idx = timed_hash_set_find(set, hash);
|
||||
if (idx >= 0) {
|
||||
// Remove by shifting remaining items
|
||||
if (idx < set->count - 1) {
|
||||
memmove(&set->items[idx], &set->items[idx + 1],
|
||||
(set->count - idx - 1) * sizeof(set->items[0]));
|
||||
}
|
||||
set->count--;
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
#ifndef HASH_SET_INCLUDED
|
||||
#define HASH_SET_INCLUDED
|
||||
|
||||
#include "basic.h"
|
||||
|
||||
typedef struct {
|
||||
SHA256 *items;
|
||||
int count;
|
||||
int capacity;
|
||||
} HashSet;
|
||||
|
||||
typedef struct {
|
||||
SHA256 hash;
|
||||
Time time;
|
||||
} TimedHash;
|
||||
|
||||
typedef struct {
|
||||
TimedHash *items;
|
||||
int count;
|
||||
int capacity;
|
||||
} TimedHashSet;
|
||||
|
||||
void hash_set_init (HashSet *set);
|
||||
void hash_set_free (HashSet *set);
|
||||
void hash_set_clear (HashSet *set);
|
||||
int hash_set_insert (HashSet *set, SHA256 hash);
|
||||
bool hash_set_remove (HashSet *set, SHA256 hash);
|
||||
int hash_set_merge (HashSet *dst, HashSet src);
|
||||
void hash_set_remove_set(HashSet *dst, HashSet src);
|
||||
bool hash_set_contains (HashSet *set, SHA256 hash);
|
||||
|
||||
void timed_hash_set_init (TimedHashSet *set);
|
||||
void timed_hash_set_free (TimedHashSet *set);
|
||||
int timed_hash_set_find (TimedHashSet *set, SHA256 hash);
|
||||
int timed_hash_set_insert (TimedHashSet *set, SHA256 hash, Time time);
|
||||
void timed_hash_set_remove (TimedHashSet *set, SHA256 hash);
|
||||
|
||||
#endif // HASH_SET_INCLUDED
|
||||
@@ -0,0 +1,445 @@
|
||||
// VSR invariant checker with external shadow log tracking.
|
||||
//
|
||||
// This file runs in the main simulation loop outside Quakey-scheduled
|
||||
// processes. It includes node.h for struct definitions, then restores
|
||||
// real allocators since mock_malloc/realloc/free abort outside process
|
||||
// context.
|
||||
|
||||
#include "server.h"
|
||||
#include "chunk_store.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
// Restore real allocators (see checker/linearizability.c for precedent).
|
||||
#undef malloc
|
||||
#undef realloc
|
||||
#undef free
|
||||
#include <stdlib.h>
|
||||
|
||||
// These helpers are static in node.c; duplicated here for the checker.
|
||||
|
||||
static int self_idx(ServerState *state)
|
||||
{
|
||||
for (int i = 0; i < state->num_nodes; i++)
|
||||
if (addr_eql(state->node_addrs[i], state->self_addr))
|
||||
return i;
|
||||
UNREACHABLE;
|
||||
}
|
||||
|
||||
static int leader_idx(ServerState *state)
|
||||
{
|
||||
return state->view_number % state->num_nodes;
|
||||
}
|
||||
|
||||
static bool is_leader(ServerState *state)
|
||||
{
|
||||
if (state->status == STATUS_RECOVERY)
|
||||
return false;
|
||||
return self_idx(state) == leader_idx(state);
|
||||
}
|
||||
|
||||
static int shadow_log_append(InvariantChecker *ic, MetaOper oper)
|
||||
{
|
||||
if (ic->shadow_count == ic->shadow_capacity) {
|
||||
int n = 2 * ic->shadow_capacity;
|
||||
if (n < 8)
|
||||
n = 8;
|
||||
MetaOper *p = realloc(ic->shadow_log, n * sizeof(MetaOper));
|
||||
if (p == NULL)
|
||||
return -1;
|
||||
ic->shadow_log = p;
|
||||
ic->shadow_capacity = n;
|
||||
}
|
||||
ic->shadow_log[ic->shadow_count++] = oper;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void invariant_checker_init(InvariantChecker *ic)
|
||||
{
|
||||
ic->last_min_commit = -1;
|
||||
ic->last_max_commit = -1;
|
||||
for (int i = 0; i < NODE_LIMIT; i++)
|
||||
ic->prev_status[i] = STATUS_NORMAL;
|
||||
ic->shadow_log = NULL;
|
||||
ic->shadow_count = 0;
|
||||
ic->shadow_capacity = 0;
|
||||
}
|
||||
|
||||
void invariant_checker_free(InvariantChecker *ic)
|
||||
{
|
||||
fprintf(stderr, "INVARIANT CHECKER: shadow log tracked %d committed entries\n",
|
||||
ic->shadow_count);
|
||||
free(ic->shadow_log);
|
||||
}
|
||||
|
||||
void invariant_checker_run(InvariantChecker *ic, ServerState **nodes, int num_nodes,
|
||||
unsigned long long *node_handles)
|
||||
{
|
||||
int min_commit = -1;
|
||||
int max_commit = -1;
|
||||
|
||||
bool primary = false;
|
||||
uint64_t primary_view_number = 0;
|
||||
|
||||
bool min_commit_just_recovered = false;
|
||||
|
||||
uint64_t max_view_number = 0;
|
||||
for (int i = 0; i < num_nodes; i++) {
|
||||
if (nodes[i])
|
||||
max_view_number = MAX(max_view_number, nodes[i]->view_number);
|
||||
}
|
||||
|
||||
for (int i = 0; i < num_nodes; i++) {
|
||||
ServerState *n = nodes[i];
|
||||
if (n == NULL || n->status == STATUS_RECOVERY)
|
||||
continue;
|
||||
|
||||
if (min_commit < 0 || min_commit > n->commit_index) {
|
||||
min_commit = n->commit_index;
|
||||
min_commit_just_recovered = (ic->prev_status[i] == STATUS_RECOVERY);
|
||||
}
|
||||
|
||||
if (max_commit < 0 || max_commit < n->commit_index) {
|
||||
max_commit = n->commit_index;
|
||||
}
|
||||
|
||||
if (is_leader(n) && n->view_number > primary_view_number) {
|
||||
primary = true;
|
||||
primary_view_number = n->view_number;
|
||||
}
|
||||
}
|
||||
|
||||
// If the primary isn't up to date, it's not the
|
||||
// real primary.
|
||||
if (primary_view_number < max_view_number)
|
||||
primary = false;
|
||||
|
||||
if (min_commit < 0) {
|
||||
assert(ic->last_min_commit == -1);
|
||||
} else {
|
||||
// The minimum number of committed entries should
|
||||
// only increase, but there are some corner-cases
|
||||
// when this is not true.
|
||||
// When a node completes the recovery state, its
|
||||
// log is technically outdated as it was sent some
|
||||
// point in the past. If operations are committed
|
||||
// while the recovery response is in transit over
|
||||
// the network, the minimum number of committed
|
||||
// entries will decrease.
|
||||
if (!min_commit_just_recovered) {
|
||||
assert(ic->last_min_commit <= min_commit);
|
||||
}
|
||||
}
|
||||
|
||||
if (max_commit < 0) {
|
||||
assert(ic->last_max_commit == -1);
|
||||
} else {
|
||||
// The maximum number of committed entries
|
||||
// should only increase. The primary generally
|
||||
// has more committed entries than replicas. If
|
||||
// the primary dies, the maximum commit number
|
||||
// of the live nodes may decrease. This is still
|
||||
// okay since the new primary will commit up to
|
||||
// that point as the view change completes. This
|
||||
// implies that the maximum commit number should
|
||||
// always increase unless there is no primary.
|
||||
if (!primary) {
|
||||
max_commit = ic->last_max_commit;
|
||||
} else {
|
||||
//assert(ic->last_max_commit <= max_commit);
|
||||
}
|
||||
}
|
||||
|
||||
ic->last_min_commit = min_commit;
|
||||
ic->last_max_commit = max_commit;
|
||||
for (int i = 0; i < num_nodes; i++) {
|
||||
if (nodes[i])
|
||||
ic->prev_status[i] = nodes[i]->status;
|
||||
}
|
||||
|
||||
for (int i = 0; i < num_nodes; i++) {
|
||||
ServerState *s = nodes[i];
|
||||
if (s == NULL)
|
||||
continue;
|
||||
|
||||
// 1. commit_index <= log.count
|
||||
// A node cannot have committed more entries than it has in its log.
|
||||
if (s->commit_index > s->log.count) {
|
||||
fprintf(stderr, "INVARIANT VIOLATED: node %d: commit_index (%d) > log.count (%d)\n",
|
||||
i, s->commit_index, s->log.count);
|
||||
__builtin_trap();
|
||||
}
|
||||
|
||||
// 2. commit_index >= 0
|
||||
if (s->commit_index < 0) {
|
||||
fprintf(stderr, "INVARIANT VIOLATED: node %d: commit_index (%d) < 0\n",
|
||||
i, s->commit_index);
|
||||
__builtin_trap();
|
||||
}
|
||||
|
||||
// 4. Future buffer count is in valid range.
|
||||
if (s->num_future < 0 || s->num_future > FUTURE_LIMIT) {
|
||||
fprintf(stderr, "INVARIANT VIOLATED: node %d: num_future (%d) out of range [0, %d]\n",
|
||||
i, s->num_future, FUTURE_LIMIT);
|
||||
__builtin_trap();
|
||||
}
|
||||
|
||||
// 7. last_normal_view <= view_number
|
||||
// The most recent view in which this node was in NORMAL status
|
||||
// cannot exceed its current view number.
|
||||
if (s->last_normal_view > s->view_number) {
|
||||
fprintf(stderr, "INVARIANT VIOLATED: node %d: last_normal_view (%lu) > view_number (%lu)\n",
|
||||
i, (unsigned long)s->last_normal_view, (unsigned long)s->view_number);
|
||||
__builtin_trap();
|
||||
}
|
||||
|
||||
// 8. When status is NORMAL, last_normal_view must equal view_number.
|
||||
// Every transition to NORMAL status sets last_normal_view = view_number.
|
||||
// If they diverge while in NORMAL, a transition forgot to update it.
|
||||
if (s->status == STATUS_NORMAL && s->last_normal_view != s->view_number) {
|
||||
fprintf(stderr, "INVARIANT VIOLATED: node %d: status is NORMAL but "
|
||||
"last_normal_view (%lu) != view_number (%lu)\n",
|
||||
i, (unsigned long)s->last_normal_view, (unsigned long)s->view_number);
|
||||
__builtin_trap();
|
||||
}
|
||||
|
||||
// 9. Log entry view numbers must not exceed the node's current view.
|
||||
// Entries are created in the view they were proposed. No entry
|
||||
// should carry a view number from the future.
|
||||
for (int k = 0; k < s->log.count; k++) {
|
||||
if ((uint64_t)s->log.entries[k].view_number > s->view_number) {
|
||||
fprintf(stderr, "INVARIANT VIOLATED: node %d: log[%d].view_number (%d) "
|
||||
"> view_number (%lu)\n",
|
||||
i, k, s->log.entries[k].view_number,
|
||||
(unsigned long)s->view_number);
|
||||
__builtin_trap();
|
||||
}
|
||||
}
|
||||
|
||||
// 10. For a leader in NORMAL status, every uncommitted log entry
|
||||
// must have the leader's own vote bit set. The leader always
|
||||
// votes for its own entries.
|
||||
if (s->status == STATUS_NORMAL && is_leader(s)) {
|
||||
int idx = self_idx(s);
|
||||
for (int k = s->commit_index; k < s->log.count; k++) {
|
||||
if (!(s->log.entries[k].votes & (1 << idx))) {
|
||||
fprintf(stderr, "INVARIANT VIOLATED: node %d (leader): "
|
||||
"uncommitted log[%d] missing leader's own vote bit\n",
|
||||
i, k);
|
||||
__builtin_trap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cross-node invariants
|
||||
|
||||
// 5. At most one leader in normal status per view.
|
||||
for (int i = 0; i < num_nodes; i++) {
|
||||
if (nodes[i] == NULL || nodes[i]->status != STATUS_NORMAL || !is_leader(nodes[i]))
|
||||
continue;
|
||||
for (int j = i + 1; j < num_nodes; j++) {
|
||||
if (nodes[j] == NULL || nodes[j]->status != STATUS_NORMAL || !is_leader(nodes[j]))
|
||||
continue;
|
||||
if (nodes[i]->view_number == nodes[j]->view_number) {
|
||||
fprintf(stderr, "INVARIANT VIOLATED: two normal leaders in view %lu: node %d and node %d\n",
|
||||
(unsigned long)nodes[i]->view_number, i, j);
|
||||
__builtin_trap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Committed prefix agreement (State Machine Safety).
|
||||
// For any two nodes, their logs must agree on all entries up to
|
||||
// min(commit_index_i, commit_index_j). This is the core safety
|
||||
// property of VSR: all committed operations are identical across
|
||||
// replicas.
|
||||
for (int i = 0; i < num_nodes; i++) {
|
||||
if (nodes[i] == NULL)
|
||||
continue;
|
||||
for (int j = i + 1; j < num_nodes; j++) {
|
||||
if (nodes[j] == NULL)
|
||||
continue;
|
||||
|
||||
int mc = nodes[i]->commit_index;
|
||||
if (nodes[j]->commit_index < mc)
|
||||
mc = nodes[j]->commit_index;
|
||||
|
||||
for (int k = 0; k < mc; k++) {
|
||||
if (memcmp(&nodes[i]->log.entries[k].oper, &nodes[j]->log.entries[k].oper, sizeof(MetaOper)) != 0) {
|
||||
fprintf(stderr, "INVARIANT VIOLATED: committed log operation mismatch at index %d "
|
||||
"between node %d and node %d\n", k, i, j);
|
||||
__builtin_trap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Shadow log: external commit tracking
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Phase 1: Find the observed max commit index and a source node.
|
||||
int observed_max_commit = 0;
|
||||
int source_node_idx = -1;
|
||||
|
||||
for (int i = 0; i < num_nodes; i++) {
|
||||
if (nodes[i] == NULL)
|
||||
continue;
|
||||
if (nodes[i]->status == STATUS_RECOVERY)
|
||||
continue;
|
||||
if (nodes[i]->commit_index > observed_max_commit) {
|
||||
observed_max_commit = nodes[i]->commit_index;
|
||||
source_node_idx = i;
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 2: Append newly committed entries to the shadow log.
|
||||
if (source_node_idx >= 0 && observed_max_commit > ic->shadow_count) {
|
||||
|
||||
ServerState *source = nodes[source_node_idx];
|
||||
assert(source->log.count >= observed_max_commit);
|
||||
|
||||
for (int k = ic->shadow_count; k < observed_max_commit; k++) {
|
||||
|
||||
MetaOper *source_oper = &source->log.entries[k].oper;
|
||||
|
||||
// Cross-validate against other live non-recovering nodes
|
||||
// that have also committed this entry.
|
||||
for (int j = 0; j < num_nodes; j++) {
|
||||
if (j == source_node_idx)
|
||||
continue;
|
||||
if (nodes[j] == NULL)
|
||||
continue;
|
||||
if (nodes[j]->status == STATUS_RECOVERY)
|
||||
continue;
|
||||
if (nodes[j]->commit_index <= k)
|
||||
continue;
|
||||
if (nodes[j]->log.count <= k)
|
||||
continue;
|
||||
|
||||
if (memcmp(&nodes[j]->log.entries[k].oper, source_oper, sizeof(MetaOper)) != 0) {
|
||||
fprintf(stderr, "INVARIANT VIOLATED: committed entry mismatch at index %d "
|
||||
"between source node %d and node %d during shadow log append\n",
|
||||
k, source_node_idx, j);
|
||||
__builtin_trap();
|
||||
}
|
||||
}
|
||||
|
||||
if (shadow_log_append(ic, *source_oper) < 0) {
|
||||
fprintf(stderr, "INVARIANT CHECKER: shadow log allocation failed\n");
|
||||
__builtin_trap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 3: Verify shadow log against the cluster.
|
||||
|
||||
// Sub-check A: Committed entries must match the shadow log.
|
||||
for (int k = 0; k < ic->shadow_count; k++) {
|
||||
for (int i = 0; i < num_nodes; i++) {
|
||||
if (nodes[i] == NULL)
|
||||
continue;
|
||||
if (nodes[i]->log.count <= k)
|
||||
continue;
|
||||
if (nodes[i]->commit_index <= k)
|
||||
continue;
|
||||
|
||||
if (memcmp(&nodes[i]->log.entries[k].oper, &ic->shadow_log[k], sizeof(MetaOper)) != 0) {
|
||||
char shadow_buf[128], node_buf[128];
|
||||
meta_snprint_oper(shadow_buf, sizeof(shadow_buf), &ic->shadow_log[k]);
|
||||
meta_snprint_oper(node_buf, sizeof(node_buf), &nodes[i]->log.entries[k].oper);
|
||||
fprintf(stderr, "INVARIANT VIOLATED: shadow log mismatch at index %d on node %d\n"
|
||||
" shadow: %s\n"
|
||||
" node: %s\n",
|
||||
k, i, shadow_buf, node_buf);
|
||||
__builtin_trap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sub-check B: When commit regresses, previously committed entries
|
||||
// must still be held by a majority of the cluster.
|
||||
// Recovering nodes are treated like dead nodes: they haven't
|
||||
// restored their log yet and may still recover the entry through
|
||||
// the recovery protocol.
|
||||
if (observed_max_commit < ic->shadow_count) {
|
||||
for (int k = observed_max_commit; k < ic->shadow_count; k++) {
|
||||
int holders = 0;
|
||||
int num_dead = 0;
|
||||
|
||||
for (int i = 0; i < num_nodes; i++) {
|
||||
if (nodes[i] == NULL) {
|
||||
num_dead++;
|
||||
continue;
|
||||
}
|
||||
if (nodes[i]->status == STATUS_RECOVERY) {
|
||||
num_dead++;
|
||||
continue;
|
||||
}
|
||||
if (nodes[i]->log.count <= k)
|
||||
continue;
|
||||
if (memcmp(&nodes[i]->log.entries[k].oper, &ic->shadow_log[k], sizeof(MetaOper)) == 0)
|
||||
holders++;
|
||||
}
|
||||
|
||||
if (holders + num_dead <= num_nodes / 2) {
|
||||
char oper_buf[128];
|
||||
meta_snprint_oper(oper_buf, sizeof(oper_buf), &ic->shadow_log[k]);
|
||||
fprintf(stderr, "INVARIANT VIOLATED: previously committed entry at index %d "
|
||||
"no longer held by majority (holders=%d, dead=%d, total=%d)\n"
|
||||
" entry: %s\n",
|
||||
k, holders, num_dead, num_nodes, oper_buf);
|
||||
__builtin_trap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
// Blob invariants: newly committed PUT entries must have their
|
||||
// chunks stored on at least one live server's disk.
|
||||
////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Only check newly committed entries (since last run) to avoid
|
||||
// O(total_entries * ticks) cost. Uses quakey_enter_host to access
|
||||
// each server's mock filesystem.
|
||||
if (node_handles != NULL && observed_max_commit > 0) {
|
||||
|
||||
// Check the most recently committed entries for chunk presence
|
||||
for (int k = MAX(0, ic->shadow_count - 5); k < ic->shadow_count; k++) {
|
||||
MetaOper *oper = &ic->shadow_log[k];
|
||||
if (oper->type != META_OPER_PUT)
|
||||
continue;
|
||||
|
||||
for (uint32_t c = 0; c < oper->num_chunks; c++) {
|
||||
SHA256 hash = oper->chunks[c].hash;
|
||||
|
||||
int holders = 0;
|
||||
int num_dead = 0;
|
||||
for (int i = 0; i < num_nodes; i++) {
|
||||
if (nodes[i] == NULL) {
|
||||
num_dead++;
|
||||
continue;
|
||||
}
|
||||
// Enter host context to access its mock filesystem
|
||||
quakey_enter_host(node_handles[i]);
|
||||
bool has_chunk = chunk_store_exists(&nodes[i]->chunk_store, hash);
|
||||
quakey_leave_host();
|
||||
|
||||
if (has_chunk)
|
||||
holders++;
|
||||
}
|
||||
|
||||
// Chunk must exist on at least one server, OR all
|
||||
// non-holders are dead (they may have had it before crash).
|
||||
if (holders == 0 && num_dead < num_nodes) {
|
||||
fprintf(stderr, "INVARIANT VIOLATED: committed blob %s/%s "
|
||||
"chunk %u not found on any live server "
|
||||
"(holders=%d, dead=%d)\n",
|
||||
oper->bucket, oper->key, c, holders, num_dead);
|
||||
__builtin_trap();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
#if defined(MAIN_SIMULATION) || defined(MAIN_TEST)
|
||||
#define QUAKEY_ENABLE_MOCKS
|
||||
#endif
|
||||
|
||||
#include <quakey.h>
|
||||
|
||||
#include "log.h"
|
||||
|
||||
void log_init(Log *log)
|
||||
{
|
||||
log->count = 0;
|
||||
log->capacity = 0;
|
||||
log->entries = NULL;
|
||||
}
|
||||
|
||||
int log_init_from_network(Log *log, void *src, int num)
|
||||
{
|
||||
log->count = num;
|
||||
log->capacity = num;
|
||||
log->entries = malloc(num * sizeof(LogEntry));
|
||||
if (log->entries == NULL)
|
||||
return -1;
|
||||
memcpy(log->entries, src, num * sizeof(LogEntry));
|
||||
return 0;
|
||||
}
|
||||
|
||||
void log_free(Log *log)
|
||||
{
|
||||
free(log->entries);
|
||||
}
|
||||
|
||||
void log_move(Log *dst, Log *src)
|
||||
{
|
||||
log_free(dst);
|
||||
*dst = *src;
|
||||
log_init(src);
|
||||
}
|
||||
|
||||
int log_append(Log *log, LogEntry entry)
|
||||
{
|
||||
if (log->count == log->capacity) {
|
||||
int n= 2 * log->capacity;
|
||||
if (n < 8)
|
||||
n = 8;
|
||||
LogEntry *p = realloc(log->entries, n * sizeof(LogEntry));
|
||||
if (p == NULL)
|
||||
return -1;
|
||||
|
||||
log->entries = p;
|
||||
log->capacity = n;
|
||||
}
|
||||
|
||||
log->entries[log->count++] = entry;
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
#ifndef LOG_INCLUDED
|
||||
#define LOG_INCLUDED
|
||||
|
||||
#include "metadata.h"
|
||||
|
||||
#include "config.h"
|
||||
|
||||
typedef struct {
|
||||
MetaOper oper;
|
||||
uint32_t votes;
|
||||
int view_number;
|
||||
uint64_t client_id;
|
||||
uint64_t request_id;
|
||||
} LogEntry;
|
||||
|
||||
_Static_assert(NODE_LIMIT <= 32, "");
|
||||
|
||||
typedef struct {
|
||||
int count;
|
||||
int capacity;
|
||||
LogEntry *entries;
|
||||
} Log;
|
||||
|
||||
void log_init(Log *log);
|
||||
int log_init_from_network(Log *log, void *src, int num);
|
||||
void log_free(Log *log);
|
||||
void log_move(Log *dst, Log *src);
|
||||
int log_append(Log *log, LogEntry entry);
|
||||
|
||||
#endif // LOG_INCLUDED
|
||||
+215
-264
@@ -1,116 +1,14 @@
|
||||
#ifdef MAIN_SIMULATION
|
||||
#define QUAKEY_ENABLE_MOCKS
|
||||
#else
|
||||
#define POLL_CAPACITY 1024
|
||||
#endif
|
||||
|
||||
#include <signal.h>
|
||||
#include <stdint.h>
|
||||
#include <quakey.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "chunk_server.h"
|
||||
#include "random_client.h"
|
||||
#include "metadata_server.h"
|
||||
|
||||
#ifdef MAIN_METADATA_SERVER
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int ret;
|
||||
MetadataServer state;
|
||||
|
||||
void* poll_ctxs[POLL_CAPACITY];
|
||||
struct pollfd poll_array[POLL_CAPACITY];
|
||||
int poll_count;
|
||||
int poll_timeout;
|
||||
|
||||
ret = metadata_server_init(
|
||||
&state,
|
||||
argc,
|
||||
argv,
|
||||
poll_ctxs,
|
||||
poll_array,
|
||||
POLL_CAPACITY,
|
||||
&poll_count,
|
||||
&poll_timeout
|
||||
);
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
|
||||
for (;;) {
|
||||
|
||||
#ifdef _WIN32
|
||||
WSAPoll(poll_array, poll_count, poll_timeout);
|
||||
#else
|
||||
poll(poll_array, poll_count, poll_timeout);
|
||||
#endif
|
||||
|
||||
ret = metadata_server_tick(
|
||||
&state,
|
||||
poll_ctxs,
|
||||
poll_array,
|
||||
POLL_CAPACITY,
|
||||
&poll_count,
|
||||
&poll_timeout
|
||||
);
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
metadata_server_free(&state);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef MAIN_CHUNK_SERVER
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int ret;
|
||||
ChunkServer state;
|
||||
|
||||
void* poll_ctxs[POLL_CAPACITY];
|
||||
struct pollfd poll_array[POLL_CAPACITY];
|
||||
int poll_count;
|
||||
int poll_timeout;
|
||||
|
||||
ret = chunk_server_init(
|
||||
&state,
|
||||
argc,
|
||||
argv,
|
||||
poll_ctxs,
|
||||
poll_array,
|
||||
POLL_CAPACITY,
|
||||
&poll_count,
|
||||
&poll_timeout
|
||||
);
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
|
||||
for (;;) {
|
||||
|
||||
#ifdef _WIN32
|
||||
WSAPoll(poll_array, poll_count, poll_timeout);
|
||||
#else
|
||||
poll(poll_array, poll_count, poll_timeout);
|
||||
#endif
|
||||
|
||||
ret = chunk_server_tick(
|
||||
&state,
|
||||
poll_ctxs,
|
||||
poll_array,
|
||||
POLL_CAPACITY,
|
||||
&poll_count,
|
||||
&poll_timeout
|
||||
);
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
chunk_server_free(&state);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef MAIN_SIMULATION
|
||||
#include <signal.h>
|
||||
#include "server.h"
|
||||
#include "client.h"
|
||||
#include "blob_client.h"
|
||||
|
||||
static volatile int simulation_running = 1;
|
||||
|
||||
@@ -120,233 +18,286 @@ static void sigint_handler(int sig)
|
||||
simulation_running = 0;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
signal(SIGINT, sigint_handler);
|
||||
|
||||
QuakeyUInt64 seed = 1;
|
||||
QuakeyUInt64 time_limit_ns = 0; // 0 means no limit
|
||||
|
||||
for (int i = 1; i < argc; i++) {
|
||||
if (strcmp(argv[i], "--seed") == 0 && i + 1 < argc) {
|
||||
seed = strtoull(argv[++i], NULL, 10);
|
||||
} else if (strcmp(argv[i], "--time") == 0 && i + 1 < argc) {
|
||||
time_limit_ns = strtoull(argv[++i], NULL, 10) * 1000000000ULL;
|
||||
}
|
||||
}
|
||||
|
||||
Quakey *quakey;
|
||||
int ret = quakey_init(&quakey, 1);
|
||||
int ret = quakey_init(&quakey, seed);
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
|
||||
QuakeyNode node_1;
|
||||
QuakeyNode node_2;
|
||||
QuakeyNode node_3;
|
||||
|
||||
// Client 1
|
||||
{
|
||||
QuakeySpawn config = {
|
||||
.name = "cli1",
|
||||
.state_size = sizeof(RandomClient),
|
||||
.init_func = random_client_init,
|
||||
.tick_func = random_client_tick,
|
||||
.free_func = random_client_free,
|
||||
.name = "rndcli1",
|
||||
.state_size = sizeof(ClientState),
|
||||
.init_func = client_init,
|
||||
.tick_func = client_tick,
|
||||
.free_func = client_free,
|
||||
.addrs = (char*[]) { "127.0.0.2" },
|
||||
.num_addrs = 1,
|
||||
.disk_size = 10<<20,
|
||||
.platform = QUAKEY_LINUX,
|
||||
};
|
||||
quakey_spawn(quakey, config, "cli --server 127.0.0.4 8080");
|
||||
(void) quakey_spawn(quakey, config, "cli --server 127.0.0.4:8080 --server 127.0.0.5:8080 --server 127.0.0.6:8080");
|
||||
}
|
||||
|
||||
// Client 2
|
||||
{
|
||||
QuakeySpawn config = {
|
||||
.name = "cli2",
|
||||
.state_size = sizeof(RandomClient),
|
||||
.init_func = random_client_init,
|
||||
.tick_func = random_client_tick,
|
||||
.free_func = random_client_free,
|
||||
.name = "rndcli2",
|
||||
.state_size = sizeof(ClientState),
|
||||
.init_func = client_init,
|
||||
.tick_func = client_tick,
|
||||
.free_func = client_free,
|
||||
.addrs = (char*[]) { "127.0.0.3" },
|
||||
.num_addrs = 1,
|
||||
.disk_size = 10<<20,
|
||||
.platform = QUAKEY_LINUX,
|
||||
};
|
||||
quakey_spawn(quakey, config, "cli --server 127.0.0.4 8080");
|
||||
(void) quakey_spawn(quakey, config, "cli --server 127.0.0.4:8080 --server 127.0.0.5:8080 --server 127.0.0.6:8080");
|
||||
}
|
||||
|
||||
// Metadata Server
|
||||
// Blob Client
|
||||
{
|
||||
QuakeySpawn config = {
|
||||
.name = "ms",
|
||||
.state_size = sizeof(MetadataServer),
|
||||
.init_func = metadata_server_init,
|
||||
.tick_func = metadata_server_tick,
|
||||
.free_func = metadata_server_free,
|
||||
.addrs = (char*[]) { "127.0.0.4" },
|
||||
.num_addrs = 1,
|
||||
.disk_size = 10<<20,
|
||||
.platform = QUAKEY_LINUX,
|
||||
};
|
||||
quakey_spawn(quakey, config, "ms --addr 127.0.0.4 --port 8080");
|
||||
}
|
||||
|
||||
// Chunk Server 1
|
||||
{
|
||||
QuakeySpawn config = {
|
||||
.name = "cs1",
|
||||
.state_size = sizeof(ChunkServer),
|
||||
.init_func = chunk_server_init,
|
||||
.tick_func = chunk_server_tick,
|
||||
.free_func = chunk_server_free,
|
||||
.addrs = (char*[]) { "127.0.0.5" },
|
||||
.num_addrs = 1,
|
||||
.disk_size = 10<<20,
|
||||
.platform = QUAKEY_LINUX,
|
||||
};
|
||||
quakey_spawn(quakey, config, "cs --addr 127.0.0.5 --port 8081 --remote-addr 127.0.0.4 --remote-port 8080");
|
||||
}
|
||||
|
||||
// Chunk Server 2
|
||||
{
|
||||
QuakeySpawn config = {
|
||||
.name = "cs2",
|
||||
.state_size = sizeof(ChunkServer),
|
||||
.init_func = chunk_server_init,
|
||||
.tick_func = chunk_server_tick,
|
||||
.free_func = chunk_server_free,
|
||||
.addrs = (char*[]) { "127.0.0.6" },
|
||||
.num_addrs = 1,
|
||||
.disk_size = 10<<20,
|
||||
.platform = QUAKEY_LINUX,
|
||||
};
|
||||
quakey_spawn(quakey, config, "cs --addr 127.0.0.6 --port 8082 --remote-addr 127.0.0.4 --remote-port 8080");
|
||||
}
|
||||
|
||||
// Chunk Server 3
|
||||
{
|
||||
QuakeySpawn config = {
|
||||
.name = "cs3",
|
||||
.state_size = sizeof(ChunkServer),
|
||||
.init_func = chunk_server_init,
|
||||
.tick_func = chunk_server_tick,
|
||||
.free_func = chunk_server_free,
|
||||
.name = "blobcli",
|
||||
.state_size = sizeof(BlobClientState),
|
||||
.init_func = blob_client_init,
|
||||
.tick_func = blob_client_tick,
|
||||
.free_func = blob_client_free,
|
||||
.addrs = (char*[]) { "127.0.0.7" },
|
||||
.num_addrs = 1,
|
||||
.disk_size = 10<<20,
|
||||
.platform = QUAKEY_LINUX,
|
||||
};
|
||||
quakey_spawn(quakey, config, "cs --addr 127.0.0.7 --port 8083 --remote-addr 127.0.0.4 --remote-port 8080");
|
||||
(void) quakey_spawn(quakey, config, "blob --server 127.0.0.4:8080 --server 127.0.0.5:8080 --server 127.0.0.6:8080");
|
||||
}
|
||||
|
||||
while (simulation_running)
|
||||
quakey_schedule_one(quakey);
|
||||
|
||||
quakey_free(quakey);
|
||||
return 0;
|
||||
}
|
||||
#endif // MAIN_SIMULATION
|
||||
|
||||
#ifdef MAIN_TEST
|
||||
#include <signal.h>
|
||||
#include "test_client.h"
|
||||
|
||||
static volatile int simulation_running = 1;
|
||||
|
||||
static void sigint_handler(int sig)
|
||||
{
|
||||
(void)sig;
|
||||
simulation_running = 0;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
signal(SIGINT, sigint_handler);
|
||||
|
||||
Quakey *quakey;
|
||||
int ret = quakey_init(&quakey, 1);
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
|
||||
// Client 1
|
||||
// Node 1
|
||||
{
|
||||
QuakeySpawn config = {
|
||||
.name = "test_cli_1",
|
||||
.state_size = sizeof(TestClient),
|
||||
.init_func = test_client_init,
|
||||
.tick_func = test_client_tick,
|
||||
.free_func = test_client_free,
|
||||
.addrs = (char*[]) { "127.0.0.2" },
|
||||
.num_addrs = 1,
|
||||
.disk_size = 10<<20,
|
||||
.platform = QUAKEY_LINUX,
|
||||
};
|
||||
quakey_spawn(quakey, config, "cli --server 127.0.0.3 8080");
|
||||
}
|
||||
|
||||
// Metadata Server
|
||||
{
|
||||
QuakeySpawn config = {
|
||||
.name = "ms",
|
||||
.state_size = sizeof(MetadataServer),
|
||||
.init_func = metadata_server_init,
|
||||
.tick_func = metadata_server_tick,
|
||||
.free_func = metadata_server_free,
|
||||
.addrs = (char*[]) { "127.0.0.3" },
|
||||
.num_addrs = 1,
|
||||
.disk_size = 10<<20,
|
||||
.platform = QUAKEY_LINUX,
|
||||
};
|
||||
quakey_spawn(quakey, config, "ms --addr 127.0.0.3 --port 8080");
|
||||
}
|
||||
|
||||
// Chunk Server 1
|
||||
{
|
||||
QuakeySpawn config = {
|
||||
.name = "cs1",
|
||||
.state_size = sizeof(ChunkServer),
|
||||
.init_func = chunk_server_init,
|
||||
.tick_func = chunk_server_tick,
|
||||
.free_func = chunk_server_free,
|
||||
.name = "server1",
|
||||
.state_size = sizeof(ServerState),
|
||||
.init_func = server_init,
|
||||
.tick_func = server_tick,
|
||||
.free_func = server_free,
|
||||
.addrs = (char*[]) { "127.0.0.4" },
|
||||
.num_addrs = 1,
|
||||
.disk_size = 10<<20,
|
||||
.platform = QUAKEY_LINUX,
|
||||
};
|
||||
quakey_spawn(quakey, config, "cs --addr 127.0.0.4 --port 8081 --remote-addr 127.0.0.3 --remote-port 8080");
|
||||
node_1 = quakey_spawn(quakey, config, "nd --addr 127.0.0.4:8080 --peer 127.0.0.5:8080 --peer 127.0.0.6:8080");
|
||||
}
|
||||
|
||||
// Chunk Server 2
|
||||
// Node 2
|
||||
{
|
||||
QuakeySpawn config = {
|
||||
.name = "cs2",
|
||||
.state_size = sizeof(ChunkServer),
|
||||
.init_func = chunk_server_init,
|
||||
.tick_func = chunk_server_tick,
|
||||
.free_func = chunk_server_free,
|
||||
.name = "server2",
|
||||
.state_size = sizeof(ServerState),
|
||||
.init_func = server_init,
|
||||
.tick_func = server_tick,
|
||||
.free_func = server_free,
|
||||
.addrs = (char*[]) { "127.0.0.5" },
|
||||
.num_addrs = 1,
|
||||
.disk_size = 10<<20,
|
||||
.platform = QUAKEY_LINUX,
|
||||
};
|
||||
quakey_spawn(quakey, config, "cs --addr 127.0.0.5 --port 8082 --remote-addr 127.0.0.3 --remote-port 8080");
|
||||
node_2 = quakey_spawn(quakey, config, "nd --peer 127.0.0.4:8080 --addr 127.0.0.5:8080 --peer 127.0.0.6:8080");
|
||||
}
|
||||
|
||||
// Chunk Server 3
|
||||
// Node 3
|
||||
{
|
||||
QuakeySpawn config = {
|
||||
.name = "cs3",
|
||||
.state_size = sizeof(ChunkServer),
|
||||
.init_func = chunk_server_init,
|
||||
.tick_func = chunk_server_tick,
|
||||
.free_func = chunk_server_free,
|
||||
.name = "server3",
|
||||
.state_size = sizeof(ServerState),
|
||||
.init_func = server_init,
|
||||
.tick_func = server_tick,
|
||||
.free_func = server_free,
|
||||
.addrs = (char*[]) { "127.0.0.6" },
|
||||
.num_addrs = 1,
|
||||
.disk_size = 10<<20,
|
||||
.platform = QUAKEY_LINUX,
|
||||
};
|
||||
quakey_spawn(quakey, config, "cs --addr 127.0.0.6 --port 8083 --remote-addr 127.0.0.3 --remote-port 8080");
|
||||
node_3 = quakey_spawn(quakey, config, "nd --peer 127.0.0.4:8080 --peer 127.0.0.5:8080 --addr 127.0.0.6:8080");
|
||||
}
|
||||
|
||||
while (simulation_running) {
|
||||
// Limit crashes to 1 node at a time (within fault tolerance of f=1).
|
||||
// This is dynamically adjusted below: crashes are disabled while
|
||||
// any node is still recovering.
|
||||
quakey_set_max_crashes(quakey, 1);
|
||||
|
||||
quakey_network_partitioning(quakey, true);
|
||||
|
||||
InvariantChecker invariant_checker;
|
||||
invariant_checker_init(&invariant_checker);
|
||||
|
||||
while (simulation_running && (time_limit_ns == 0 || quakey_current_time(quakey) < time_limit_ns)) {
|
||||
|
||||
quakey_schedule_one(quakey);
|
||||
|
||||
QuakeySignal signal;
|
||||
while (quakey_get_signal(quakey, &signal)) {
|
||||
ServerState *arr[] = {
|
||||
quakey_node_state(node_1),
|
||||
quakey_node_state(node_2),
|
||||
quakey_node_state(node_3),
|
||||
};
|
||||
unsigned long long handles[] = { node_1, node_2, node_3 };
|
||||
invariant_checker_run(&invariant_checker, arr, sizeof(arr)/sizeof(arr[0]), handles);
|
||||
|
||||
if (!strcmp(signal.name, "exit"))
|
||||
simulation_running = false;
|
||||
// VR-Revisited Section 8.2: "a replica is considered failed
|
||||
// until it has recovered its state." Disable crashes while
|
||||
// any node is recovering to avoid exceeding f simultaneous
|
||||
// failures (dead + recovering).
|
||||
bool any_recovering = false;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
if (arr[i] && arr[i]->status == STATUS_RECOVERY)
|
||||
any_recovering = true;
|
||||
}
|
||||
quakey_set_max_crashes(quakey, any_recovering ? 0 : 1);
|
||||
}
|
||||
|
||||
invariant_checker_free(&invariant_checker);
|
||||
quakey_free(quakey);
|
||||
return 0;
|
||||
}
|
||||
#endif // MAIN_TEST
|
||||
|
||||
#endif // MAIN_SIMULATION
|
||||
////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////
|
||||
#ifdef MAIN_CLIENT
|
||||
|
||||
#include <poll.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "client.h"
|
||||
|
||||
#define POLL_CAPACITY 1024
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
srand((unsigned)time(NULL) ^ (unsigned)getpid());
|
||||
|
||||
int ret;
|
||||
ClientState state;
|
||||
|
||||
void* poll_ctxs[POLL_CAPACITY];
|
||||
struct pollfd poll_array[POLL_CAPACITY];
|
||||
int poll_count;
|
||||
int poll_timeout;
|
||||
|
||||
ret = client_init(
|
||||
&state,
|
||||
argc,
|
||||
argv,
|
||||
poll_ctxs,
|
||||
poll_array,
|
||||
POLL_CAPACITY,
|
||||
&poll_count,
|
||||
&poll_timeout
|
||||
);
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
|
||||
for (;;) {
|
||||
|
||||
#ifdef _WIN32
|
||||
WSAPoll(poll_array, poll_count, poll_timeout);
|
||||
#else
|
||||
poll(poll_array, poll_count, poll_timeout);
|
||||
#endif
|
||||
|
||||
ret = client_tick(
|
||||
&state,
|
||||
poll_ctxs,
|
||||
poll_array,
|
||||
POLL_CAPACITY,
|
||||
&poll_count,
|
||||
&poll_timeout
|
||||
);
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
client_free(&state);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif // MAIN_CLIENT
|
||||
////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////
|
||||
#ifdef MAIN_SERVER
|
||||
|
||||
#include <poll.h>
|
||||
|
||||
#include "server.h"
|
||||
|
||||
#define POLL_CAPACITY 1024
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int ret;
|
||||
ServerState state;
|
||||
|
||||
void* poll_ctxs[POLL_CAPACITY];
|
||||
struct pollfd poll_array[POLL_CAPACITY];
|
||||
int poll_count;
|
||||
int poll_timeout;
|
||||
|
||||
ret = server_init(
|
||||
&state,
|
||||
argc,
|
||||
argv,
|
||||
poll_ctxs,
|
||||
poll_array,
|
||||
POLL_CAPACITY,
|
||||
&poll_count,
|
||||
&poll_timeout
|
||||
);
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
|
||||
for (;;) {
|
||||
|
||||
#ifdef _WIN32
|
||||
WSAPoll(poll_array, poll_count, poll_timeout);
|
||||
#else
|
||||
poll(poll_array, poll_count, poll_timeout);
|
||||
#endif
|
||||
|
||||
ret = server_tick(
|
||||
&state,
|
||||
poll_ctxs,
|
||||
poll_array,
|
||||
POLL_CAPACITY,
|
||||
&poll_count,
|
||||
&poll_timeout
|
||||
);
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
server_free(&state);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif // MAIN_SERVER
|
||||
-485
@@ -1,485 +0,0 @@
|
||||
#if defined(MAIN_SIMULATION) || defined(MAIN_TEST)
|
||||
#define QUAKEY_ENABLE_MOCKS
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include <quakey.h>
|
||||
|
||||
#include "message.h"
|
||||
|
||||
bool binary_read(BinaryReader *reader, void *dst, int len)
|
||||
{
|
||||
if (reader->len - reader->cur < len)
|
||||
return false;
|
||||
if (dst)
|
||||
memcpy(dst, reader->src + reader->cur, len);
|
||||
reader->cur += len;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool binary_read_addr_ipv4(BinaryReader *reader, Address *addr)
|
||||
{
|
||||
if (!binary_read(reader, &addr->ipv4, sizeof(IPv4))) return false;
|
||||
if (!binary_read(reader, &addr->port, sizeof(uint16_t))) return false;
|
||||
addr->is_ipv4 = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool binary_read_addr_ipv6(BinaryReader *reader, Address *addr)
|
||||
{
|
||||
if (!binary_read(reader, &addr->ipv6, sizeof(IPv6))) return false;
|
||||
if (!binary_read(reader, &addr->port, sizeof(uint16_t))) return false;
|
||||
addr->is_ipv4 = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
int binary_read_addr_list(BinaryReader *reader, Address *addrs, int max_addrs)
|
||||
{
|
||||
uint32_t num_ipv4;
|
||||
uint32_t num_ipv6;
|
||||
if (!binary_read(reader, &num_ipv4, sizeof(num_ipv4)))
|
||||
return -1;
|
||||
if (!binary_read(reader, &num_ipv6, sizeof(num_ipv6)))
|
||||
return -1;
|
||||
int num = 0;
|
||||
for (uint32_t i = 0; i < num_ipv4; i++) {
|
||||
Address tmp;
|
||||
if (!binary_read_addr_ipv4(reader, &tmp))
|
||||
return -1;
|
||||
if (num < max_addrs)
|
||||
addrs[num++] = tmp;
|
||||
}
|
||||
for (uint32_t i = 0; i < num_ipv6; i++) {
|
||||
Address tmp;
|
||||
if (!binary_read_addr_ipv6(reader, &tmp))
|
||||
return -1;
|
||||
if (num < max_addrs)
|
||||
addrs[num++] = tmp;
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
void message_writer_init(MessageWriter *writer, ByteQueue *output, uint16_t type)
|
||||
{
|
||||
uint16_t version = MESSAGE_VERSION;
|
||||
uint32_t dummy = 0; // Dummy value
|
||||
writer->output = output;
|
||||
writer->start = byte_queue_offset(output);
|
||||
byte_queue_write(output, &version, sizeof(version));
|
||||
byte_queue_write(output, &type, sizeof(type));
|
||||
writer->patch = byte_queue_offset(output);
|
||||
byte_queue_write(output, &dummy, sizeof(dummy));
|
||||
}
|
||||
|
||||
bool message_writer_free(MessageWriter *writer)
|
||||
{
|
||||
uint32_t length = byte_queue_size_from_offset(writer->output, writer->start);
|
||||
byte_queue_patch(writer->output, writer->patch, &length, sizeof(length));
|
||||
if (byte_queue_error(writer->output)) // TODO: is it possible to restore the state of the queue to before the failure?
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void message_write(MessageWriter *writer, void *mem, int len)
|
||||
{
|
||||
byte_queue_write(writer->output, mem, len);
|
||||
}
|
||||
|
||||
void message_write_u8(MessageWriter *writer, uint8_t value)
|
||||
{
|
||||
message_write(writer, &value, (int) sizeof(value));
|
||||
}
|
||||
|
||||
void message_write_u32(MessageWriter *writer, uint32_t value)
|
||||
{
|
||||
message_write(writer, &value, (int) sizeof(value));
|
||||
}
|
||||
|
||||
void message_write_hash(MessageWriter *writer, SHA256 value)
|
||||
{
|
||||
message_write(writer, &value, (int) sizeof(value));
|
||||
}
|
||||
|
||||
int message_peek(ByteView msg, uint16_t *type, uint32_t *len)
|
||||
{
|
||||
if (msg.len < (int) sizeof(MessageHeader))
|
||||
return 0;
|
||||
|
||||
MessageHeader header;
|
||||
memcpy(&header, msg.ptr, sizeof(header));
|
||||
|
||||
// (We ignore endianess for now)
|
||||
|
||||
if (header.version != MESSAGE_VERSION)
|
||||
return -1;
|
||||
|
||||
if (header.length > msg.len)
|
||||
return 0;
|
||||
|
||||
if (type) *type = header.type;
|
||||
if (len) *len = header.length;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static char *message_type_to_str(uint16_t type)
|
||||
{
|
||||
switch (type) {
|
||||
// Client -> Metadata server
|
||||
case MESSAGE_TYPE_CREATE: return "CREATE";
|
||||
case MESSAGE_TYPE_DELETE: return "DELETE";
|
||||
case MESSAGE_TYPE_LIST: return "LIST";
|
||||
case MESSAGE_TYPE_READ: return "READ";
|
||||
case MESSAGE_TYPE_WRITE: return "WRITE";
|
||||
|
||||
// Client -> Chunk server
|
||||
case MESSAGE_TYPE_CREATE_CHUNK: return "CREATE_CHUNK";
|
||||
case MESSAGE_TYPE_UPLOAD_CHUNK: return "UPLOAD_CHUNK";
|
||||
case MESSAGE_TYPE_DOWNLOAD_CHUNK: return "DOWNLOAD_CHUNK";
|
||||
|
||||
// Metadata server -> Client
|
||||
case MESSAGE_TYPE_CREATE_ERROR: return "CREATE_ERROR";
|
||||
case MESSAGE_TYPE_CREATE_SUCCESS: return "CREATE_SUCCESS";
|
||||
case MESSAGE_TYPE_DELETE_ERROR: return "DELETE_ERROR";
|
||||
case MESSAGE_TYPE_DELETE_SUCCESS: return "DELETE_SUCCESS";
|
||||
case MESSAGE_TYPE_LIST_ERROR: return "LIST_ERROR";
|
||||
case MESSAGE_TYPE_LIST_SUCCESS: return "LIST_SUCCESS";
|
||||
case MESSAGE_TYPE_READ_ERROR: return "READ_ERROR";
|
||||
case MESSAGE_TYPE_READ_SUCCESS: return "READ_SUCCESS";
|
||||
case MESSAGE_TYPE_WRITE_ERROR: return "WRITE_ERROR";
|
||||
case MESSAGE_TYPE_WRITE_SUCCESS: return "WRITE_SUCCESS";
|
||||
|
||||
// Metadata server -> Chunk server
|
||||
case MESSAGE_TYPE_SYNC_2: return "SYNC_2";
|
||||
case MESSAGE_TYPE_SYNC_4: return "SYNC_4";
|
||||
case MESSAGE_TYPE_DOWNLOAD_LOCATIONS: return "DOWNLOAD_LOCATIONS";
|
||||
|
||||
// Chunk server -> Metadata server
|
||||
case MESSAGE_TYPE_AUTH: return "AUTH";
|
||||
case MESSAGE_TYPE_SYNC: return "SYNC";
|
||||
case MESSAGE_TYPE_SYNC_3: return "SYNC_3";
|
||||
|
||||
// Chunk server -> Client
|
||||
case MESSAGE_TYPE_CREATE_CHUNK_ERROR: return "CREATE_CHUNK_ERROR";
|
||||
case MESSAGE_TYPE_CREATE_CHUNK_SUCCESS: return "CREATE_CHUNK_SUCCESS";
|
||||
case MESSAGE_TYPE_UPLOAD_CHUNK_ERROR: return "UPLOAD_CHUNK_ERROR";
|
||||
case MESSAGE_TYPE_UPLOAD_CHUNK_SUCCESS: return "UPLOAD_CHUNK_SUCCESS";
|
||||
case MESSAGE_TYPE_DOWNLOAD_CHUNK_ERROR: return "DOWNLOAD_CHUNK_ERROR";
|
||||
case MESSAGE_TYPE_DOWNLOAD_CHUNK_SUCCESS: return "DOWNLOAD_CHUNK_SUCCESS";
|
||||
}
|
||||
|
||||
return "???";
|
||||
}
|
||||
|
||||
void message_dump(FILE *stream, ByteView msg)
|
||||
{
|
||||
BinaryReader reader = { msg.ptr, msg.len, 0 };
|
||||
|
||||
fprintf(stream, "message:\n");
|
||||
|
||||
fprintf(stream, " header:\n");
|
||||
uint16_t version;
|
||||
if (!binary_read(&reader, &version, sizeof(version))) {
|
||||
fprintf(stream, " (incomplete)\n");
|
||||
return;
|
||||
}
|
||||
fprintf(stream, " version: %d\n", version);
|
||||
|
||||
uint16_t type;
|
||||
if (!binary_read(&reader, &type, sizeof(type))) {
|
||||
fprintf(stream, " (incomplete)\n");
|
||||
return;
|
||||
}
|
||||
fprintf(stream, " type: %s\n", message_type_to_str(type));
|
||||
|
||||
uint32_t length;
|
||||
if (!binary_read(&reader, &length, sizeof(length))) {
|
||||
fprintf(stream, " (incomplete)\n");
|
||||
return;
|
||||
}
|
||||
fprintf(stream, " length: %d\n", length);
|
||||
|
||||
fprintf(stream, " body:\n");
|
||||
switch (type) {
|
||||
// Client -> Metadata server
|
||||
|
||||
case MESSAGE_TYPE_CREATE:
|
||||
{
|
||||
uint16_t path_len;
|
||||
if (!binary_read(&reader, &path_len, sizeof(path_len))) {
|
||||
fprintf(stream, " (incomplete)\n");
|
||||
return;
|
||||
}
|
||||
fprintf(stream, " path_len: %d\n", path_len);
|
||||
|
||||
char *path = (char*) reader.src + reader.cur;
|
||||
if (!binary_read(&reader, NULL, path_len)) {
|
||||
fprintf(stream, " (incomplete)\n");
|
||||
return;
|
||||
}
|
||||
fprintf(stream, " path: %.*s\n", (int) path_len, path);
|
||||
|
||||
uint8_t is_dir;
|
||||
if (!binary_read(&reader, &is_dir, sizeof(is_dir))) {
|
||||
fprintf(stream, " (incomplete)\n");
|
||||
return;
|
||||
}
|
||||
fprintf(stream, " is_dir: %s\n", is_dir ? "true" : "false");
|
||||
|
||||
if (!is_dir) {
|
||||
uint32_t chunk_size;
|
||||
if (!binary_read(&reader, &chunk_size, sizeof(chunk_size))) {
|
||||
fprintf(stream, " (incomplete)\n");
|
||||
return;
|
||||
}
|
||||
fprintf(stream, " chunk_size: %d\n", chunk_size);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case MESSAGE_TYPE_DELETE:
|
||||
{
|
||||
uint16_t path_len;
|
||||
if (!binary_read(&reader, &path_len, sizeof(path_len))) {
|
||||
fprintf(stream, " (incomplete)\n");
|
||||
return;
|
||||
}
|
||||
fprintf(stream, " path_len: %d\n", path_len);
|
||||
|
||||
char *path = (char*) reader.src + reader.cur;
|
||||
if (!binary_read(&reader, NULL, path_len)) {
|
||||
fprintf(stream, " (incomplete)\n");
|
||||
return;
|
||||
}
|
||||
fprintf(stream, " path: %.*s\n", (int) path_len, path);
|
||||
}
|
||||
break;
|
||||
|
||||
case MESSAGE_TYPE_LIST:
|
||||
{
|
||||
uint16_t path_len;
|
||||
if (!binary_read(&reader, &path_len, sizeof(path_len))) {
|
||||
fprintf(stream, " (incomplete)\n");
|
||||
return;
|
||||
}
|
||||
fprintf(stream, " path_len: %d\n", path_len);
|
||||
|
||||
char *path = (char*) reader.src + reader.cur;
|
||||
if (!binary_read(&reader, NULL, path_len)) {
|
||||
fprintf(stream, " (incomplete)\n");
|
||||
return;
|
||||
}
|
||||
fprintf(stream, " path: %.*s\n", (int) path_len, path);
|
||||
}
|
||||
break;
|
||||
|
||||
case MESSAGE_TYPE_READ:
|
||||
{
|
||||
uint16_t path_len;
|
||||
if (!binary_read(&reader, &path_len, sizeof(path_len))) {
|
||||
fprintf(stream, " (incomplete)\n");
|
||||
return;
|
||||
}
|
||||
fprintf(stream, " path_len: %d\n", path_len);
|
||||
|
||||
char *path = (char*) reader.src + reader.cur;
|
||||
if (!binary_read(&reader, NULL, path_len)) {
|
||||
fprintf(stream, " (incomplete)\n");
|
||||
return;
|
||||
}
|
||||
fprintf(stream, " path: %.*s\n", (int) path_len, path);
|
||||
|
||||
uint32_t offset;
|
||||
if (!binary_read(&reader, &offset, sizeof(offset))) {
|
||||
fprintf(stream, " (incomplete)\n");
|
||||
return;
|
||||
}
|
||||
fprintf(stream, " offset: %d\n", offset);
|
||||
|
||||
uint32_t length;
|
||||
if (!binary_read(&reader, &length, sizeof(length))) {
|
||||
fprintf(stream, " (incomplete)\n");
|
||||
return;
|
||||
}
|
||||
fprintf(stream, " length: %d\n", length);
|
||||
}
|
||||
break;
|
||||
|
||||
case MESSAGE_TYPE_WRITE:
|
||||
{
|
||||
uint16_t path_len;
|
||||
if (!binary_read(&reader, &path_len, sizeof(path_len))) {
|
||||
fprintf(stream, " (incomplete)\n");
|
||||
return;
|
||||
}
|
||||
fprintf(stream, " path_len: %d\n", path_len);
|
||||
|
||||
char *path = (char*) reader.src + reader.cur;
|
||||
if (!binary_read(&reader, NULL, path_len)) {
|
||||
fprintf(stream, " (incomplete)\n");
|
||||
return;
|
||||
}
|
||||
fprintf(stream, " path: %.*s\n", (int) path_len, path);
|
||||
|
||||
uint32_t offset;
|
||||
if (!binary_read(&reader, &offset, sizeof(offset))) {
|
||||
fprintf(stream, " (incomplete)\n");
|
||||
return;
|
||||
}
|
||||
fprintf(stream, " offset: %d\n", offset);
|
||||
|
||||
uint32_t length;
|
||||
if (!binary_read(&reader, &length, sizeof(length))) {
|
||||
fprintf(stream, " (incomplete)\n");
|
||||
return;
|
||||
}
|
||||
fprintf(stream, " length: %d\n", length);
|
||||
|
||||
uint32_t num_chunks;
|
||||
if (!binary_read(&reader, &num_chunks, sizeof(num_chunks))) {
|
||||
fprintf(stream, " (incomplete)\n");
|
||||
return;
|
||||
}
|
||||
fprintf(stream, " num_chunks: %d\n", num_chunks);
|
||||
|
||||
for (uint32_t i = 0; i < num_chunks; i++) {
|
||||
|
||||
char hash_str[64];
|
||||
|
||||
SHA256 old_hash;
|
||||
if (!binary_read(&reader, &old_hash, sizeof(old_hash))) {
|
||||
fprintf(stream, " (incomplete)\n");
|
||||
return;
|
||||
}
|
||||
append_hex_as_str(hash_str, old_hash);
|
||||
fprintf(stream, " old_hash: %.64s\n", hash_str);
|
||||
|
||||
SHA256 new_hash;
|
||||
if (!binary_read(&reader, &new_hash, sizeof(new_hash))) {
|
||||
fprintf(stream, " (incomplete)\n");
|
||||
return;
|
||||
}
|
||||
append_hex_as_str(hash_str, new_hash);
|
||||
fprintf(stream, " new_hash: %.64s\n", hash_str);
|
||||
|
||||
uint32_t num_locations;
|
||||
if (!binary_read(&reader, &num_locations, sizeof(num_locations))) {
|
||||
fprintf(stream, " (incomplete)\n");
|
||||
return;
|
||||
}
|
||||
fprintf(stream, " num_locations: %d\n", num_locations);
|
||||
|
||||
for (uint32_t j = 0; j < num_locations; j++) {
|
||||
|
||||
uint8_t is_ipv4;
|
||||
if (!binary_read(&reader, &is_ipv4, sizeof(is_ipv4))) {
|
||||
fprintf(stream, " (incomplete)\n");
|
||||
return;
|
||||
}
|
||||
fprintf(stream, " is_ipv4: %s (%d)\n", is_ipv4 ? "true" : "false", is_ipv4);
|
||||
|
||||
if (is_ipv4) {
|
||||
IPv4 ipv4;
|
||||
if (!binary_read(&reader, &ipv4, sizeof(ipv4))) {
|
||||
fprintf(stream, " (incomplete)\n");
|
||||
return;
|
||||
}
|
||||
char ip_str[INET_ADDRSTRLEN];
|
||||
inet_ntop(AF_INET, &ipv4, ip_str, sizeof(ip_str));
|
||||
fprintf(stream, " ipv4: %s\n", ip_str);
|
||||
} else {
|
||||
IPv6 ipv6;
|
||||
if (!binary_read(&reader, &ipv6, sizeof(ipv6))) {
|
||||
fprintf(stream, " (incomplete)\n");
|
||||
return;
|
||||
}
|
||||
char ip_str[INET_ADDRSTRLEN];
|
||||
inet_ntop(AF_INET6, &ipv6, ip_str, sizeof(ip_str));
|
||||
fprintf(stream, " ipv6: %s\n", ip_str);
|
||||
}
|
||||
|
||||
uint16_t port;
|
||||
if (!binary_read(&reader, &port, sizeof(port))) {
|
||||
fprintf(stream, " (incomplete)\n");
|
||||
return;
|
||||
}
|
||||
fprintf(stream, " port: %d\n", port);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
// Client -> Chunk server
|
||||
|
||||
case MESSAGE_TYPE_CREATE_CHUNK:
|
||||
{
|
||||
uint32_t chunk_size;
|
||||
if (!binary_read(&reader, &chunk_size, sizeof(chunk_size))) {
|
||||
fprintf(stream, " (incomplete)\n");
|
||||
return;
|
||||
}
|
||||
fprintf(stream, " chunk_size: %d\n", chunk_size);
|
||||
|
||||
uint32_t offset;
|
||||
if (!binary_read(&reader, &offset, sizeof(offset))) {
|
||||
fprintf(stream, " (incomplete)\n");
|
||||
return;
|
||||
}
|
||||
fprintf(stream, " offset: %d\n", offset);
|
||||
|
||||
uint32_t length;
|
||||
if (!binary_read(&reader, &length, sizeof(length))) {
|
||||
fprintf(stream, " (incomplete)\n");
|
||||
return;
|
||||
}
|
||||
fprintf(stream, " length: %d\n", length);
|
||||
|
||||
if (!binary_read(&reader, NULL, length)) {
|
||||
fprintf(stream, " (incomplete)\n");
|
||||
return;
|
||||
}
|
||||
fprintf(stream, " data: (...)\n");
|
||||
}
|
||||
break;
|
||||
|
||||
case MESSAGE_TYPE_UPLOAD_CHUNK:
|
||||
{
|
||||
SHA256 hash;
|
||||
if (!binary_read(&reader, &hash, sizeof(hash))) {
|
||||
fprintf(stream, " (incomplete)\n");
|
||||
return;
|
||||
}
|
||||
char hash_str[64];
|
||||
append_hex_as_str(hash_str, hash);
|
||||
fprintf(stream, " hash: %.64s\n", hash_str);
|
||||
|
||||
uint32_t offset;
|
||||
if (!binary_read(&reader, &offset, sizeof(offset))) {
|
||||
fprintf(stream, " (incomplete)\n");
|
||||
return;
|
||||
}
|
||||
fprintf(stream, " offset: %d\n", offset);
|
||||
|
||||
uint32_t length;
|
||||
if (!binary_read(&reader, &length, sizeof(length))) {
|
||||
fprintf(stream, " (incomplete)\n");
|
||||
return;
|
||||
}
|
||||
fprintf(stream, " length: %d\n", length);
|
||||
|
||||
if (!binary_read(&reader, NULL, length)) {
|
||||
fprintf(stream, " (incomplete)\n");
|
||||
return;
|
||||
}
|
||||
fprintf(stream, " data: (...)\n");
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
printf(" (TODO)\n");
|
||||
break;
|
||||
}
|
||||
|
||||
if (binary_read(&reader, NULL, 1))
|
||||
fprintf(stream, " (unexpected bytes)\n");
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
#ifndef MESSAGE_INCLUDED
|
||||
#define MESSAGE_INCLUDED
|
||||
|
||||
#if defined(MAIN_SIMULATION) || defined(MAIN_TEST)
|
||||
#define QUAKEY_ENABLE_MOCKS
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include <quakey.h>
|
||||
|
||||
#include "basic.h"
|
||||
#include "byte_queue.h"
|
||||
|
||||
enum {
|
||||
|
||||
// Client -> Metadata server
|
||||
MESSAGE_TYPE_CREATE,
|
||||
MESSAGE_TYPE_DELETE,
|
||||
MESSAGE_TYPE_LIST,
|
||||
MESSAGE_TYPE_READ,
|
||||
MESSAGE_TYPE_WRITE,
|
||||
|
||||
// Client -> Chunk server
|
||||
MESSAGE_TYPE_CREATE_CHUNK,
|
||||
MESSAGE_TYPE_UPLOAD_CHUNK,
|
||||
MESSAGE_TYPE_DOWNLOAD_CHUNK,
|
||||
|
||||
// Metadata server -> Client
|
||||
MESSAGE_TYPE_CREATE_ERROR,
|
||||
MESSAGE_TYPE_CREATE_SUCCESS,
|
||||
MESSAGE_TYPE_DELETE_ERROR,
|
||||
MESSAGE_TYPE_DELETE_SUCCESS,
|
||||
MESSAGE_TYPE_LIST_ERROR,
|
||||
MESSAGE_TYPE_LIST_SUCCESS,
|
||||
MESSAGE_TYPE_READ_ERROR,
|
||||
MESSAGE_TYPE_READ_SUCCESS,
|
||||
MESSAGE_TYPE_WRITE_ERROR,
|
||||
MESSAGE_TYPE_WRITE_SUCCESS,
|
||||
|
||||
// Metadata server -> Chunk server
|
||||
MESSAGE_TYPE_AUTH_RESPONSE,
|
||||
MESSAGE_TYPE_SYNC_2,
|
||||
MESSAGE_TYPE_SYNC_4,
|
||||
MESSAGE_TYPE_DOWNLOAD_LOCATIONS,
|
||||
|
||||
// Chunk server -> Metadata server
|
||||
MESSAGE_TYPE_AUTH,
|
||||
MESSAGE_TYPE_SYNC,
|
||||
MESSAGE_TYPE_SYNC_3,
|
||||
|
||||
// Chunk server -> Client
|
||||
MESSAGE_TYPE_CREATE_CHUNK_ERROR,
|
||||
MESSAGE_TYPE_CREATE_CHUNK_SUCCESS,
|
||||
MESSAGE_TYPE_UPLOAD_CHUNK_ERROR,
|
||||
MESSAGE_TYPE_UPLOAD_CHUNK_SUCCESS,
|
||||
MESSAGE_TYPE_DOWNLOAD_CHUNK_ERROR,
|
||||
MESSAGE_TYPE_DOWNLOAD_CHUNK_SUCCESS,
|
||||
};
|
||||
|
||||
#define MESSAGE_VERSION 1
|
||||
|
||||
typedef struct {
|
||||
uint8_t *src;
|
||||
int len;
|
||||
int cur;
|
||||
} BinaryReader;
|
||||
|
||||
typedef struct {
|
||||
uint16_t version;
|
||||
uint16_t type;
|
||||
uint32_t length;
|
||||
} MessageHeader;
|
||||
|
||||
typedef struct {
|
||||
ByteQueue *output;
|
||||
ByteQueueOffset start;
|
||||
ByteQueueOffset patch;
|
||||
} MessageWriter;
|
||||
|
||||
bool binary_read(BinaryReader *reader, void *dst, int len);
|
||||
|
||||
bool binary_read_addr_ipv4(BinaryReader *reader, Address *addr);
|
||||
bool binary_read_addr_ipv6(BinaryReader *reader, Address *addr);
|
||||
int binary_read_addr_list(BinaryReader *reader, Address *addrs, int max_addr);
|
||||
|
||||
void message_writer_init(MessageWriter *writer, ByteQueue *output, uint16_t type);
|
||||
bool message_writer_free(MessageWriter *writer);
|
||||
void message_write(MessageWriter *writer, void *mem, int len);
|
||||
void message_write_u8(MessageWriter *writer, uint8_t value);
|
||||
void message_write_u32(MessageWriter *writer, uint32_t value);
|
||||
void message_write_hash(MessageWriter *writer, SHA256 value);
|
||||
|
||||
int message_peek(ByteView msg, uint16_t *type, uint32_t *len);
|
||||
void message_dump(FILE *stream, ByteView msg);
|
||||
|
||||
#endif // MESSAGE_INCLUDED
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
#if defined(MAIN_SIMULATION) || defined(MAIN_TEST)
|
||||
#define QUAKEY_ENABLE_MOCKS
|
||||
#endif
|
||||
|
||||
#include <quakey.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "metadata.h"
|
||||
|
||||
void meta_store_init(MetaStore *ms)
|
||||
{
|
||||
memset(ms, 0, sizeof(*ms));
|
||||
}
|
||||
|
||||
void meta_store_free(MetaStore *ms)
|
||||
{
|
||||
(void) ms;
|
||||
}
|
||||
|
||||
static int find_entry(MetaStore *ms, const char *bucket, const char *key)
|
||||
{
|
||||
for (int i = 0; i < ms->count; i++) {
|
||||
if (!ms->entries[i].deleted
|
||||
&& strncmp(ms->entries[i].bucket, bucket, META_BUCKET_MAX) == 0
|
||||
&& strncmp(ms->entries[i].key, key, META_KEY_MAX) == 0)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void copy_oper_to_entry(ObjectMeta *entry, MetaOper *oper)
|
||||
{
|
||||
memcpy(entry->bucket, oper->bucket, META_BUCKET_MAX);
|
||||
memcpy(entry->key, oper->key, META_KEY_MAX);
|
||||
entry->size = oper->size;
|
||||
entry->content_hash = oper->content_hash;
|
||||
entry->num_chunks = oper->num_chunks;
|
||||
memcpy(entry->chunks, oper->chunks, oper->num_chunks * sizeof(ChunkRef));
|
||||
entry->deleted = false;
|
||||
}
|
||||
|
||||
MetaResult meta_store_update(MetaStore *ms, MetaOper *oper)
|
||||
{
|
||||
MetaResult result;
|
||||
|
||||
switch (oper->type) {
|
||||
|
||||
case META_OPER_NOOP:
|
||||
result.type = META_RESULT_OK;
|
||||
break;
|
||||
|
||||
case META_OPER_PUT:
|
||||
{
|
||||
// Try to find existing entry with same bucket/key
|
||||
int i = find_entry(ms, oper->bucket, oper->key);
|
||||
if (i >= 0) {
|
||||
// Overwrite in-place
|
||||
copy_oper_to_entry(&ms->entries[i], oper);
|
||||
result.type = META_RESULT_OK;
|
||||
break;
|
||||
}
|
||||
|
||||
// Try to reuse a tombstoned slot
|
||||
for (i = 0; i < ms->count; i++) {
|
||||
if (ms->entries[i].deleted) {
|
||||
copy_oper_to_entry(&ms->entries[i], oper);
|
||||
result.type = META_RESULT_OK;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate new slot
|
||||
if (ms->count >= META_ENTRY_LIMIT) {
|
||||
result.type = META_RESULT_FULL;
|
||||
break;
|
||||
}
|
||||
copy_oper_to_entry(&ms->entries[ms->count++], oper);
|
||||
result.type = META_RESULT_OK;
|
||||
}
|
||||
break;
|
||||
|
||||
case META_OPER_DELETE:
|
||||
{
|
||||
int i = find_entry(ms, oper->bucket, oper->key);
|
||||
if (i < 0) {
|
||||
result.type = META_RESULT_NOT_FOUND;
|
||||
} else {
|
||||
ms->entries[i].deleted = true;
|
||||
result.type = META_RESULT_OK;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(0);
|
||||
break;
|
||||
}
|
||||
|
||||
done:
|
||||
return result;
|
||||
}
|
||||
|
||||
ObjectMeta *meta_store_lookup(MetaStore *ms, const char *bucket, const char *key)
|
||||
{
|
||||
int i = find_entry(ms, bucket, key);
|
||||
if (i < 0)
|
||||
return NULL;
|
||||
return &ms->entries[i];
|
||||
}
|
||||
|
||||
int meta_snprint_oper(char *buf, int size, MetaOper *oper)
|
||||
{
|
||||
switch (oper->type) {
|
||||
case META_OPER_NOOP:
|
||||
return snprintf(buf, size, "NOOP");
|
||||
case META_OPER_PUT:
|
||||
return snprintf(buf, size, "PUT(%s/%s, %u chunks)",
|
||||
oper->bucket, oper->key, oper->num_chunks);
|
||||
case META_OPER_DELETE:
|
||||
return snprintf(buf, size, "DELETE(%s/%s)",
|
||||
oper->bucket, oper->key);
|
||||
default:
|
||||
return snprintf(buf, size, "???");
|
||||
}
|
||||
}
|
||||
|
||||
int meta_snprint_result(char *buf, int size, MetaResult result)
|
||||
{
|
||||
switch (result.type) {
|
||||
case META_RESULT_OK: return snprintf(buf, size, "OK");
|
||||
case META_RESULT_NOT_FOUND: return snprintf(buf, size, "NOT_FOUND");
|
||||
case META_RESULT_FULL: return snprintf(buf, size, "FULL");
|
||||
default: return snprintf(buf, size, "???");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
#ifndef METADATA_INCLUDED
|
||||
#define METADATA_INCLUDED
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <lib/basic.h>
|
||||
|
||||
typedef struct {
|
||||
SHA256 hash;
|
||||
uint32_t size;
|
||||
} ChunkRef;
|
||||
|
||||
#define META_BUCKET_MAX 64
|
||||
#define META_KEY_MAX 512
|
||||
#define META_CHUNKS_MAX 256
|
||||
|
||||
typedef enum {
|
||||
META_OPER_NOOP,
|
||||
META_OPER_PUT,
|
||||
META_OPER_DELETE,
|
||||
} MetaOperType;
|
||||
|
||||
typedef struct {
|
||||
MetaOperType type;
|
||||
char bucket[META_BUCKET_MAX];
|
||||
char key[META_KEY_MAX];
|
||||
uint64_t size;
|
||||
SHA256 content_hash;
|
||||
uint32_t num_chunks;
|
||||
ChunkRef chunks[META_CHUNKS_MAX];
|
||||
} MetaOper;
|
||||
|
||||
typedef enum {
|
||||
META_RESULT_OK,
|
||||
META_RESULT_NOT_FOUND,
|
||||
META_RESULT_FULL,
|
||||
} MetaResultType;
|
||||
|
||||
typedef struct {
|
||||
MetaResultType type;
|
||||
} MetaResult;
|
||||
|
||||
typedef struct {
|
||||
char bucket[META_BUCKET_MAX];
|
||||
char key[META_KEY_MAX];
|
||||
uint64_t size;
|
||||
SHA256 content_hash;
|
||||
uint32_t num_chunks;
|
||||
ChunkRef chunks[META_CHUNKS_MAX];
|
||||
bool deleted;
|
||||
} ObjectMeta;
|
||||
|
||||
#define META_ENTRY_LIMIT 4096
|
||||
|
||||
typedef struct {
|
||||
int count;
|
||||
ObjectMeta entries[META_ENTRY_LIMIT];
|
||||
} MetaStore;
|
||||
|
||||
void meta_store_init(MetaStore *ms);
|
||||
void meta_store_free(MetaStore *ms);
|
||||
MetaResult meta_store_update(MetaStore *ms, MetaOper *oper);
|
||||
ObjectMeta *meta_store_lookup(MetaStore *ms, const char *bucket, const char *key);
|
||||
|
||||
int meta_snprint_oper(char *buf, int size, MetaOper *oper);
|
||||
int meta_snprint_result(char *buf, int size, MetaResult result);
|
||||
|
||||
#endif // METADATA_INCLUDED
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,66 +0,0 @@
|
||||
#ifndef METADATA_SERVER_INCLUDED
|
||||
#define METADATA_SERVER_INCLUDED
|
||||
|
||||
#include "tcp.h"
|
||||
#include "wal.h"
|
||||
#include "basic.h"
|
||||
#include "config.h"
|
||||
#include "hash_set.h"
|
||||
#include "file_tree.h"
|
||||
|
||||
#define CONNECTION_TAG_CLIENT -2
|
||||
#define CONNECTION_TAG_UNKNOWN -3
|
||||
|
||||
typedef struct {
|
||||
|
||||
bool used;
|
||||
bool auth;
|
||||
|
||||
int num_addrs;
|
||||
Address addrs[MAX_SERVER_ADDRS];
|
||||
|
||||
// List of chunks that are known to be held by CS
|
||||
HashSet ms_old_list; // TODO: rename all *_list symbols to *_set
|
||||
|
||||
// List of chunks that should be held by CS
|
||||
HashSet ms_add_list;
|
||||
|
||||
// List of chunks that may be held by CS but should removed from it
|
||||
HashSet ms_rem_list;
|
||||
|
||||
// Time when last STATE_UPDATE was sent
|
||||
Time last_sync_time;
|
||||
bool last_sync_done;
|
||||
|
||||
// Time when last response was received
|
||||
Time last_response_time; // TODO: don't init to INVALID_TIME but current_time
|
||||
|
||||
} ChunkServerPeer;
|
||||
|
||||
typedef struct {
|
||||
|
||||
TCP tcp;
|
||||
WAL wal;
|
||||
|
||||
FileTree file_tree;
|
||||
|
||||
bool trace;
|
||||
int replication_factor;
|
||||
|
||||
int num_chunk_servers;
|
||||
ChunkServerPeer chunk_servers[MAX_CHUNK_SERVERS];
|
||||
|
||||
} MetadataServer;
|
||||
|
||||
struct pollfd;
|
||||
|
||||
int metadata_server_init(void *state, int argc, char **argv,
|
||||
void **ctxs, struct pollfd *pdata, int pcap, int *pnum,
|
||||
int *timeout);
|
||||
|
||||
int metadata_server_tick(void *state, void **ctxs,
|
||||
struct pollfd *pdata, int pcap, int *pnum, int *timeout);
|
||||
|
||||
int metadata_server_free(void *state);
|
||||
|
||||
#endif // METADATA_SERVER_INCLUDED
|
||||
@@ -1,278 +0,0 @@
|
||||
#ifdef MAIN_SIMULATION
|
||||
|
||||
#define QUAKEY_ENABLE_MOCKS
|
||||
#include <quakey.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "tcp.h"
|
||||
#include "random_client.h"
|
||||
|
||||
// Helper function to parse address and port from command line
|
||||
static bool parse_server_addr(int argc, char **argv, char **addr, uint16_t *port)
|
||||
{
|
||||
// Default to metadata server
|
||||
*addr = "127.0.0.1";
|
||||
*port = 8080;
|
||||
|
||||
for (int i = 0; i < argc - 1; i++) {
|
||||
if (!strcmp(argv[i], "--server") || !strcmp(argv[i], "-s")) {
|
||||
*addr = argv[i + 1];
|
||||
if (i + 2 < argc) {
|
||||
|
||||
errno = 0;
|
||||
char *end;
|
||||
long val = strtol(argv[i+2], &end, 10);
|
||||
|
||||
if (end == argv[i+2] || *end != '\0' || errno == ERANGE)
|
||||
break;
|
||||
|
||||
if (val < 0 || val > UINT16_MAX)
|
||||
break;
|
||||
|
||||
*port = (uint16_t) val;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int random_client_init(void *state_, int argc, char **argv,
|
||||
void **ctxs, struct pollfd *pdata, int pcap, int *pnum,
|
||||
int *timeout)
|
||||
{
|
||||
RandomClient *client = state_;
|
||||
|
||||
char *addr;
|
||||
uint16_t port;
|
||||
parse_server_addr(argc, argv, &addr, &port);
|
||||
|
||||
client->toasty = toasty_connect((ToastyString) { addr, strlen(addr) }, port);
|
||||
if (client->toasty == NULL)
|
||||
return -1;
|
||||
|
||||
client->num_pending = 0;
|
||||
|
||||
printf("Client set up (remote=%s:%d)\n", addr, port);
|
||||
|
||||
*timeout = 0;
|
||||
if (pcap < TCP_POLL_CAPACITY)
|
||||
return -1;
|
||||
*pnum = toasty_process_events(client->toasty, ctxs, pdata, *pnum);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int random_in_range(int min, int max)
|
||||
{
|
||||
uint64_t n = quakey_random();
|
||||
return min + n % (max - min + 1);
|
||||
}
|
||||
|
||||
int random_client_tick(void *state_, void **ctxs,
|
||||
struct pollfd *pdata, int pcap, int *pnum,
|
||||
int *timeout)
|
||||
{
|
||||
RandomClient *client = state_;
|
||||
|
||||
// Process any pending events from the network and get new poll descriptors
|
||||
*pnum = toasty_process_events(client->toasty, ctxs, pdata, *pnum);
|
||||
|
||||
for (int i = 0; i < client->num_pending; i++) {
|
||||
|
||||
ToastyResult result;
|
||||
if (toasty_get_result(client->toasty, client->pending[i].handle, &result) != 0)
|
||||
continue;
|
||||
|
||||
PendingOperation pending = client->pending[i];
|
||||
switch (result.type) {
|
||||
|
||||
case TOASTY_RESULT_EMPTY:
|
||||
assert(0);
|
||||
break;
|
||||
|
||||
case TOASTY_RESULT_CREATE_ERROR:
|
||||
assert(pending.type == PENDING_OPERATION_CREATE);
|
||||
//printf("[Client] create error\n");
|
||||
break;
|
||||
|
||||
case TOASTY_RESULT_CREATE_SUCCESS:
|
||||
assert(pending.type == PENDING_OPERATION_CREATE);
|
||||
//printf("[Client] create success\n");
|
||||
break;
|
||||
|
||||
case TOASTY_RESULT_DELETE_ERROR:
|
||||
assert(pending.type == PENDING_OPERATION_DELETE);
|
||||
//printf("[Client] delete error\n");
|
||||
break;
|
||||
|
||||
case TOASTY_RESULT_DELETE_SUCCESS:
|
||||
assert(pending.type == PENDING_OPERATION_DELETE);
|
||||
//printf("[Client] delete success\n");
|
||||
break;
|
||||
|
||||
case TOASTY_RESULT_LIST_ERROR:
|
||||
assert(pending.type == PENDING_OPERATION_LIST);
|
||||
//printf("[Client] list error\n");
|
||||
break;
|
||||
|
||||
case TOASTY_RESULT_LIST_SUCCESS:
|
||||
assert(pending.type == PENDING_OPERATION_LIST);
|
||||
//printf("[Client] list success\n");
|
||||
break;
|
||||
|
||||
case TOASTY_RESULT_READ_ERROR:
|
||||
assert(pending.type == PENDING_OPERATION_READ);
|
||||
//printf("[Client] read error\n");
|
||||
break;
|
||||
|
||||
case TOASTY_RESULT_READ_SUCCESS:
|
||||
assert(pending.type == PENDING_OPERATION_READ);
|
||||
//printf("[Client] read success\n");
|
||||
break;
|
||||
|
||||
case TOASTY_RESULT_WRITE_ERROR:
|
||||
assert(pending.type == PENDING_OPERATION_WRITE);
|
||||
//printf("[Client] write error\n");
|
||||
break;
|
||||
|
||||
case TOASTY_RESULT_WRITE_SUCCESS:
|
||||
assert(pending.type == PENDING_OPERATION_WRITE);
|
||||
//printf("[Client] write success\n");
|
||||
break;
|
||||
}
|
||||
free(pending.ptr);
|
||||
toasty_free_result(&result);
|
||||
client->pending[i--] = client->pending[--client->num_pending];
|
||||
}
|
||||
|
||||
while (client->num_pending < MAX_PENDING_OPERATION) {
|
||||
|
||||
typedef struct {
|
||||
ToastyString path;
|
||||
bool is_dir;
|
||||
} TableEntry;
|
||||
|
||||
static const TableEntry table[] = {
|
||||
{ TOASTY_STR("/f0"), false },
|
||||
{ TOASTY_STR("/f1"), false },
|
||||
{ TOASTY_STR("/d0"), true },
|
||||
{ TOASTY_STR("/d1"), true },
|
||||
{ TOASTY_STR("/d0/f1"), false },
|
||||
{ TOASTY_STR("/d0/f2"), false },
|
||||
{ TOASTY_STR("/d0/d0"), true },
|
||||
{ TOASTY_STR("/d0/d1"), true },
|
||||
{ TOASTY_STR("/d1/f1"), false },
|
||||
{ TOASTY_STR("/d1/f2"), false },
|
||||
{ TOASTY_STR("/d1/d0"), true },
|
||||
{ TOASTY_STR("/d1/d1"), true },
|
||||
};
|
||||
static const int table_len = sizeof(table) / sizeof(table[0]);
|
||||
|
||||
static const PendingOperationType type_table[] = {
|
||||
PENDING_OPERATION_CREATE,
|
||||
PENDING_OPERATION_DELETE,
|
||||
PENDING_OPERATION_LIST,
|
||||
PENDING_OPERATION_READ,
|
||||
PENDING_OPERATION_WRITE,
|
||||
};
|
||||
static const int type_table_len = sizeof(type_table)/sizeof(type_table[0]);
|
||||
|
||||
void *ptr = NULL;
|
||||
int off = 0;
|
||||
int len = 0;
|
||||
ToastyHandle handle;
|
||||
|
||||
PendingOperationType type = type_table[random_in_range(0, type_table_len-1)];
|
||||
switch (type) {
|
||||
|
||||
TableEntry entry;
|
||||
uint32_t chunk_size;
|
||||
uint32_t flags;
|
||||
|
||||
case PENDING_OPERATION_CREATE:
|
||||
entry = table[random_in_range(0, table_len-1)];
|
||||
if (entry.is_dir) {
|
||||
handle = toasty_begin_create_dir(client->toasty, entry.path);
|
||||
} else {
|
||||
chunk_size = random_in_range(0, 5000);
|
||||
handle = toasty_begin_create_file(client->toasty, entry.path, chunk_size);
|
||||
}
|
||||
//printf("[Client] submit create (path=%s, is_dir=%s, chunk_size=%d)\n", entry.path, entry.is_dir ? "true" : "false", chunk_size);
|
||||
break;
|
||||
|
||||
case PENDING_OPERATION_DELETE:
|
||||
entry = table[random_in_range(0, table_len-1)];
|
||||
handle = toasty_begin_delete(client->toasty, entry.path, TOASTY_VERSION_TAG_EMPTY);
|
||||
//printf("[Client] submit delete (path=%s)\n", entry.path);
|
||||
break;
|
||||
|
||||
case PENDING_OPERATION_LIST:
|
||||
entry = table[random_in_range(0, table_len-1)];
|
||||
handle = toasty_begin_list(client->toasty, entry.path, TOASTY_VERSION_TAG_EMPTY);
|
||||
//printf("[Client] submit list (path=%s)\n", entry.path);
|
||||
break;
|
||||
|
||||
case PENDING_OPERATION_READ:
|
||||
entry = table[random_in_range(0, table_len-1)];
|
||||
off = random_in_range(0, 10000);
|
||||
len = random_in_range(0, 5000);
|
||||
ptr = malloc(len);
|
||||
if (ptr == NULL) assert(0);
|
||||
handle = toasty_begin_read(client->toasty, entry.path, off, ptr, len, TOASTY_VERSION_TAG_EMPTY);
|
||||
//printf("[Client] submit read (path=%s, off=%d, len=%d)\n", entry.path, off, len);
|
||||
break;
|
||||
|
||||
case PENDING_OPERATION_WRITE:
|
||||
entry = table[random_in_range(0, table_len-1)];
|
||||
off = random_in_range(0, 10000);
|
||||
len = random_in_range(0, 5000);
|
||||
ptr = malloc(len);
|
||||
if (ptr == NULL) assert(0);
|
||||
memset(ptr, 'a', len);
|
||||
flags = 0;
|
||||
switch (random_in_range(0, 3)) {
|
||||
case 0:
|
||||
flags = 0;
|
||||
break;
|
||||
case 1:
|
||||
flags = TOASTY_WRITE_CREATE_IF_MISSING;
|
||||
break;
|
||||
case 2:
|
||||
flags = TOASTY_WRITE_TRUNCATE_AFTER;
|
||||
break;
|
||||
case 3:
|
||||
flags = TOASTY_WRITE_CREATE_IF_MISSING
|
||||
| TOASTY_WRITE_TRUNCATE_AFTER;
|
||||
break;
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
handle = toasty_begin_write(client->toasty, entry.path, off, ptr, len, TOASTY_VERSION_TAG_EMPTY, flags);
|
||||
//printf("[Client] submit write (path=%s, off=%d, len=%d)\n", entry.path, off, len);
|
||||
break;
|
||||
}
|
||||
if (handle == TOASTY_INVALID)
|
||||
break;
|
||||
client->pending[client->num_pending++] = (PendingOperation) { .type=type, .handle=handle, .ptr=ptr };
|
||||
}
|
||||
|
||||
if (client->num_pending == 0)
|
||||
*timeout = 10;
|
||||
else
|
||||
*timeout = -1;
|
||||
|
||||
if (pcap < TCP_POLL_CAPACITY)
|
||||
return -1;
|
||||
*pnum = toasty_process_events(client->toasty, ctxs, pdata, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int random_client_free(void *state_)
|
||||
{
|
||||
RandomClient *client = state_;
|
||||
|
||||
toasty_disconnect(client->toasty);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif // MAIN_SIMULATION
|
||||
@@ -1,39 +0,0 @@
|
||||
#ifndef RANDOM_CLIENT_INCLUDED
|
||||
#define RANDOM_CLIENT_INCLUDED
|
||||
|
||||
#include "ToastyFS.h"
|
||||
|
||||
#define MAX_PENDING_OPERATION 8
|
||||
|
||||
typedef enum {
|
||||
PENDING_OPERATION_CREATE,
|
||||
PENDING_OPERATION_DELETE,
|
||||
PENDING_OPERATION_LIST,
|
||||
PENDING_OPERATION_READ,
|
||||
PENDING_OPERATION_WRITE,
|
||||
} PendingOperationType;
|
||||
|
||||
typedef struct {
|
||||
PendingOperationType type;
|
||||
ToastyHandle handle;
|
||||
void *ptr;
|
||||
} PendingOperation;
|
||||
|
||||
typedef struct {
|
||||
ToastyFS *toasty;
|
||||
int num_pending;
|
||||
PendingOperation pending[MAX_PENDING_OPERATION];
|
||||
} RandomClient;
|
||||
|
||||
struct pollfd;
|
||||
|
||||
int random_client_init(void *state, int argc, char **argv,
|
||||
void **ctxs, struct pollfd *pdata, int pcap, int *pnum,
|
||||
int *timeout);
|
||||
|
||||
int random_client_tick(void *state, void **ctxs,
|
||||
struct pollfd *pdata, int pcap, int *pnum, int *timeout);
|
||||
|
||||
int random_client_free(void *state);
|
||||
|
||||
#endif // RANDOM_CLIENT_INCLUDED
|
||||
+2006
File diff suppressed because it is too large
Load Diff
+301
@@ -0,0 +1,301 @@
|
||||
#ifndef NODE_INCLUDED
|
||||
#define NODE_INCLUDED
|
||||
|
||||
#include <lib/tcp.h>
|
||||
#include <lib/basic.h>
|
||||
#include <lib/message.h>
|
||||
|
||||
#include "log.h"
|
||||
#include "config.h"
|
||||
#include "metadata.h"
|
||||
#include "chunk_store.h"
|
||||
#include "client_table.h"
|
||||
|
||||
enum {
|
||||
|
||||
// Normal Protocol
|
||||
MESSAGE_TYPE_REQUEST,
|
||||
MESSAGE_TYPE_REPLY,
|
||||
MESSAGE_TYPE_PREPARE,
|
||||
MESSAGE_TYPE_PREPARE_OK,
|
||||
MESSAGE_TYPE_COMMIT,
|
||||
|
||||
// View Change Protocol
|
||||
MESSAGE_TYPE_BEGIN_VIEW_CHANGE,
|
||||
MESSAGE_TYPE_DO_VIEW_CHANGE,
|
||||
MESSAGE_TYPE_BEGIN_VIEW,
|
||||
|
||||
// Recovery Protocol
|
||||
MESSAGE_TYPE_RECOVERY,
|
||||
MESSAGE_TYPE_RECOVERY_RESPONSE,
|
||||
|
||||
// State Transfer Protocol
|
||||
MESSAGE_TYPE_GET_STATE,
|
||||
MESSAGE_TYPE_NEW_STATE,
|
||||
|
||||
// Client Redirect
|
||||
MESSAGE_TYPE_REDIRECT,
|
||||
|
||||
// Chunk Storage Protocol (bypasses log)
|
||||
MESSAGE_TYPE_STORE_CHUNK,
|
||||
MESSAGE_TYPE_STORE_CHUNK_ACK,
|
||||
MESSAGE_TYPE_FETCH_CHUNK,
|
||||
MESSAGE_TYPE_FETCH_CHUNK_RESPONSE,
|
||||
|
||||
// Blob Client Protocol
|
||||
MESSAGE_TYPE_COMMIT_PUT,
|
||||
MESSAGE_TYPE_GET_BLOB,
|
||||
MESSAGE_TYPE_GET_BLOB_RESPONSE,
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
MessageHeader base;
|
||||
MetaOper oper;
|
||||
uint64_t client_id;
|
||||
uint64_t request_id;
|
||||
} RequestMessage;
|
||||
|
||||
typedef struct {
|
||||
MessageHeader base;
|
||||
MetaOper oper;
|
||||
int sender_idx;
|
||||
int log_index;
|
||||
int commit_index;
|
||||
uint64_t view_number;
|
||||
uint64_t client_id;
|
||||
uint64_t request_id;
|
||||
} PrepareMessage;
|
||||
|
||||
typedef struct {
|
||||
MessageHeader base;
|
||||
int sender_idx;
|
||||
int log_index;
|
||||
uint64_t view_number;
|
||||
} PrepareOKMessage;
|
||||
|
||||
typedef struct {
|
||||
MessageHeader base;
|
||||
uint64_t view_number;
|
||||
int sender_idx;
|
||||
int commit_index;
|
||||
} CommitMessage;
|
||||
|
||||
typedef struct {
|
||||
MessageHeader base;
|
||||
bool rejected;
|
||||
MetaResult result;
|
||||
uint64_t request_id;
|
||||
} ReplyMessage;
|
||||
|
||||
typedef struct {
|
||||
MessageHeader base;
|
||||
uint64_t view_number;
|
||||
int sender_idx;
|
||||
} BeginViewChangeMessage;
|
||||
|
||||
typedef struct {
|
||||
MessageHeader base;
|
||||
uint64_t view_number; // The new view number
|
||||
uint64_t old_view_number; // Last view number when replica was in normal status
|
||||
int op_number; // Number of entries in the log
|
||||
int commit_index;
|
||||
int sender_idx;
|
||||
// Followed by: LogEntry log[op_number]
|
||||
} DoViewChangeMessage;
|
||||
|
||||
typedef struct {
|
||||
MessageHeader base;
|
||||
uint64_t view_number;
|
||||
int commit_index;
|
||||
int op_number; // Number of log entries that follow
|
||||
// Followed by: LogEntry log[op_number]
|
||||
} BeginViewMessage;
|
||||
|
||||
typedef struct {
|
||||
MessageHeader base;
|
||||
int sender_idx;
|
||||
uint64_t nonce;
|
||||
} RecoveryMessage;
|
||||
|
||||
typedef struct {
|
||||
MessageHeader base;
|
||||
uint64_t view_number;
|
||||
int op_number;
|
||||
uint64_t nonce;
|
||||
int commit_index;
|
||||
int sender_idx;
|
||||
} RecoveryResponseMessage;
|
||||
|
||||
typedef struct {
|
||||
MessageHeader base;
|
||||
uint64_t view_number;
|
||||
int op_number; // Requester's current log count
|
||||
int sender_idx;
|
||||
} GetStateMessage;
|
||||
|
||||
typedef struct {
|
||||
MessageHeader base;
|
||||
uint64_t view_number;
|
||||
int op_number; // Number of log entries that follow
|
||||
int commit_index;
|
||||
int start_index; // Global log index of the first entry in the suffix
|
||||
// Followed by: LogEntry log[op_number]
|
||||
} NewStateMessage;
|
||||
|
||||
typedef struct {
|
||||
MessageHeader base;
|
||||
uint64_t view_number;
|
||||
} RedirectMessage;
|
||||
|
||||
// StoreChunk: client -> any server. Carries chunk data as trailing bytes.
|
||||
typedef struct {
|
||||
MessageHeader base;
|
||||
SHA256 hash;
|
||||
uint32_t size;
|
||||
// Followed by: uint8_t data[size]
|
||||
} StoreChunkMessage;
|
||||
|
||||
// StoreChunkAck: server -> client.
|
||||
typedef struct {
|
||||
MessageHeader base;
|
||||
SHA256 hash;
|
||||
bool success;
|
||||
} StoreChunkAckMessage;
|
||||
|
||||
// FetchChunk: client/server -> server. Request a chunk by hash.
|
||||
typedef struct {
|
||||
MessageHeader base;
|
||||
SHA256 hash;
|
||||
int sender_idx; // -1 if from a client
|
||||
} FetchChunkMessage;
|
||||
|
||||
// FetchChunkResponse: server -> client/server. Chunk data as trailing bytes.
|
||||
typedef struct {
|
||||
MessageHeader base;
|
||||
SHA256 hash;
|
||||
uint32_t size; // 0 if chunk not found
|
||||
// Followed by: uint8_t data[size]
|
||||
} FetchChunkResponseMessage;
|
||||
|
||||
// CommitPut: blob client -> leader. Commits metadata after chunks uploaded.
|
||||
// Processed like a REQUEST (goes through VSR log).
|
||||
typedef struct {
|
||||
MessageHeader base;
|
||||
MetaOper oper;
|
||||
uint64_t client_id;
|
||||
uint64_t request_id;
|
||||
} CommitPutMessage;
|
||||
|
||||
// GetBlob: client -> any server. Request metadata for a blob.
|
||||
typedef struct {
|
||||
MessageHeader base;
|
||||
char bucket[META_BUCKET_MAX];
|
||||
char key[META_KEY_MAX];
|
||||
} GetBlobMessage;
|
||||
|
||||
// GetBlobResponse: server -> client. Returns blob metadata.
|
||||
typedef struct {
|
||||
MessageHeader base;
|
||||
bool found;
|
||||
uint64_t size;
|
||||
SHA256 content_hash;
|
||||
uint32_t num_chunks;
|
||||
ChunkRef chunks[META_CHUNKS_MAX];
|
||||
} GetBlobResponseMessage;
|
||||
|
||||
typedef enum {
|
||||
STATUS_NORMAL,
|
||||
STATUS_CHANGE_VIEW,
|
||||
STATUS_RECOVERY,
|
||||
} Status;
|
||||
|
||||
typedef struct {
|
||||
|
||||
TCP tcp;
|
||||
|
||||
Address self_addr;
|
||||
Address node_addrs[NODE_LIMIT];
|
||||
int num_nodes;
|
||||
|
||||
Status status;
|
||||
|
||||
ClientTable client_table;
|
||||
int next_client_tag;
|
||||
|
||||
uint64_t view_number;
|
||||
uint64_t last_normal_view; // Latest view where status was NORMAL
|
||||
|
||||
// These fields are used in recovery mode
|
||||
uint32_t recovery_votes;
|
||||
uint64_t recovery_nonce;
|
||||
uint64_t recovery_view;
|
||||
Log recovery_log;
|
||||
uint64_t recovery_log_view;
|
||||
Time recovery_time;
|
||||
int recovery_commit;
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
// VIEW CHANGE
|
||||
|
||||
uint32_t view_change_begin_votes;
|
||||
uint32_t view_change_apply_votes;
|
||||
Log view_change_log; // Best log seen
|
||||
uint64_t view_change_old_view; // Best old_view_number seen in DoViewChange
|
||||
int view_change_commit; // Best commit_index seen
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
// If this is the leader, commit_index is the index of the next uncommitted element
|
||||
// of the log (if no uncommitted elements are present, it's equal to the total number
|
||||
// of log elements)
|
||||
int commit_index;
|
||||
|
||||
PrepareMessage future[FUTURE_LIMIT];
|
||||
int num_future;
|
||||
|
||||
bool state_transfer_pending;
|
||||
Time state_transfer_time;
|
||||
|
||||
Log log;
|
||||
|
||||
Time heartbeat;
|
||||
|
||||
MetaStore metastore;
|
||||
ChunkStore chunk_store;
|
||||
|
||||
// Set at each wakeup
|
||||
Time now;
|
||||
|
||||
} ServerState;
|
||||
|
||||
struct pollfd;
|
||||
|
||||
int server_init(void *state, int argc, char **argv,
|
||||
void **ctxs, struct pollfd *pdata, int pcap, int *pnum,
|
||||
int *timeout);
|
||||
|
||||
int server_tick(void *state, void **ctxs,
|
||||
struct pollfd *pdata, int pcap, int *pnum, int *timeout);
|
||||
|
||||
int server_free(void *state);
|
||||
|
||||
typedef struct {
|
||||
int last_min_commit;
|
||||
int last_max_commit;
|
||||
Status prev_status[NODE_LIMIT];
|
||||
|
||||
// External shadow log of committed operations (unbounded, dynamically allocated)
|
||||
MetaOper *shadow_log;
|
||||
int shadow_count;
|
||||
int shadow_capacity;
|
||||
} InvariantChecker;
|
||||
|
||||
void invariant_checker_init(InvariantChecker *ic);
|
||||
void invariant_checker_free(InvariantChecker *ic);
|
||||
|
||||
// node_handles: opaque QuakeyNode handles for entering host context.
|
||||
// Pass NULL when running outside the simulation (real mode).
|
||||
void invariant_checker_run(InvariantChecker *ic, ServerState **nodes, int num_nodes,
|
||||
unsigned long long *node_handles);
|
||||
|
||||
#endif // NODE_INCLUDED
|
||||
-249
@@ -1,249 +0,0 @@
|
||||
#include "sha256.h"
|
||||
|
||||
//usr/bin/env clang -Ofast -Wall -Wextra -pedantic ${0} -o ${0%%.c*} $* ;exit $?
|
||||
//
|
||||
// SHA-256 implementation, Mark 2
|
||||
//
|
||||
// Copyright (c) 2010,2014 Literatecode, http://www.literatecode.com
|
||||
// Copyright (c) 2022 Ilia Levin (ilia@levin.sg)
|
||||
//
|
||||
// Permission to use, copy, modify, and distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
//
|
||||
|
||||
#define SHA256_SIZE_BYTES (32)
|
||||
|
||||
typedef struct {
|
||||
uint8_t buf[64];
|
||||
uint32_t hash[8];
|
||||
uint32_t bits[2];
|
||||
uint32_t len;
|
||||
uint32_t rfu__;
|
||||
uint32_t W[64];
|
||||
} sha256_context;
|
||||
|
||||
#ifndef _cbmc_
|
||||
#define __CPROVER_assume(...) do {} while(0)
|
||||
#endif
|
||||
|
||||
#define FN_ static inline __attribute__((const))
|
||||
|
||||
static const uint32_t K[64] = {
|
||||
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5,
|
||||
0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
||||
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
|
||||
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
||||
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc,
|
||||
0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
||||
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7,
|
||||
0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
||||
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
|
||||
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
||||
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3,
|
||||
0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
||||
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5,
|
||||
0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
||||
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
|
||||
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
|
||||
};
|
||||
|
||||
FN_ uint8_t _shb(uint32_t x, uint32_t n)
|
||||
{
|
||||
return ((x >> (n & 31)) & 0xff);
|
||||
}
|
||||
|
||||
FN_ uint32_t _shw(uint32_t x, uint32_t n)
|
||||
{
|
||||
return ((x << (n & 31)) & 0xffffffff);
|
||||
}
|
||||
|
||||
FN_ uint32_t _r(uint32_t x, uint8_t n)
|
||||
{
|
||||
return ((x >> n) | _shw(x, 32 - n));
|
||||
}
|
||||
|
||||
FN_ uint32_t _Ch(uint32_t x, uint32_t y, uint32_t z)
|
||||
{
|
||||
return ((x & y) ^ ((~x) & z));
|
||||
}
|
||||
|
||||
FN_ uint32_t _Ma(uint32_t x, uint32_t y, uint32_t z)
|
||||
{
|
||||
return ((x & y) ^ (x & z) ^ (y & z));
|
||||
}
|
||||
|
||||
FN_ uint32_t _S0(uint32_t x)
|
||||
{
|
||||
return (_r(x, 2) ^ _r(x, 13) ^ _r(x, 22));
|
||||
}
|
||||
|
||||
FN_ uint32_t _S1(uint32_t x)
|
||||
{
|
||||
return (_r(x, 6) ^ _r(x, 11) ^ _r(x, 25));
|
||||
}
|
||||
|
||||
FN_ uint32_t _G0(uint32_t x)
|
||||
{
|
||||
return (_r(x, 7) ^ _r(x, 18) ^ (x >> 3));
|
||||
}
|
||||
|
||||
FN_ uint32_t _G1(uint32_t x)
|
||||
{
|
||||
return (_r(x, 17) ^ _r(x, 19) ^ (x >> 10));
|
||||
}
|
||||
|
||||
FN_ uint32_t _word(uint8_t *c)
|
||||
{
|
||||
return (_shw(c[0], 24) | _shw(c[1], 16) | _shw(c[2], 8) | (c[3]));
|
||||
}
|
||||
|
||||
static void _addbits(sha256_context *ctx, uint32_t n)
|
||||
{
|
||||
__CPROVER_assume(__CPROVER_DYNAMIC_OBJECT(ctx));
|
||||
|
||||
if (ctx->bits[0] > (0xffffffff - n)) {
|
||||
ctx->bits[1] = (ctx->bits[1] + 1) & 0xFFFFFFFF;
|
||||
}
|
||||
ctx->bits[0] = (ctx->bits[0] + n) & 0xFFFFFFFF;
|
||||
} // _addbits
|
||||
|
||||
static void _hash(sha256_context *ctx)
|
||||
{
|
||||
__CPROVER_assume(__CPROVER_DYNAMIC_OBJECT(ctx));
|
||||
|
||||
register uint32_t a, b, c, d, e, f, g, h;
|
||||
uint32_t t[2];
|
||||
|
||||
a = ctx->hash[0];
|
||||
b = ctx->hash[1];
|
||||
c = ctx->hash[2];
|
||||
d = ctx->hash[3];
|
||||
e = ctx->hash[4];
|
||||
f = ctx->hash[5];
|
||||
g = ctx->hash[6];
|
||||
h = ctx->hash[7];
|
||||
|
||||
for (uint32_t i = 0; i < 64; i++) {
|
||||
if (i < 16) {
|
||||
ctx->W[i] = _word(&ctx->buf[_shw(i, 2)]);
|
||||
} else {
|
||||
ctx->W[i] = _G1(ctx->W[i - 2]) + ctx->W[i - 7] +
|
||||
_G0(ctx->W[i - 15]) + ctx->W[i - 16];
|
||||
}
|
||||
|
||||
t[0] = h + _S1(e) + _Ch(e, f, g) + K[i] + ctx->W[i];
|
||||
t[1] = _S0(a) + _Ma(a, b, c);
|
||||
h = g;
|
||||
g = f;
|
||||
f = e;
|
||||
e = d + t[0];
|
||||
d = c;
|
||||
c = b;
|
||||
b = a;
|
||||
a = t[0] + t[1];
|
||||
}
|
||||
|
||||
ctx->hash[0] += a;
|
||||
ctx->hash[1] += b;
|
||||
ctx->hash[2] += c;
|
||||
ctx->hash[3] += d;
|
||||
ctx->hash[4] += e;
|
||||
ctx->hash[5] += f;
|
||||
ctx->hash[6] += g;
|
||||
ctx->hash[7] += h;
|
||||
}
|
||||
|
||||
static void sha256_init(sha256_context *ctx)
|
||||
{
|
||||
if (ctx != NULL) {
|
||||
ctx->bits[0] = ctx->bits[1] = ctx->len = 0;
|
||||
ctx->hash[0] = 0x6a09e667;
|
||||
ctx->hash[1] = 0xbb67ae85;
|
||||
ctx->hash[2] = 0x3c6ef372;
|
||||
ctx->hash[3] = 0xa54ff53a;
|
||||
ctx->hash[4] = 0x510e527f;
|
||||
ctx->hash[5] = 0x9b05688c;
|
||||
ctx->hash[6] = 0x1f83d9ab;
|
||||
ctx->hash[7] = 0x5be0cd19;
|
||||
}
|
||||
}
|
||||
|
||||
static void sha256_hash(sha256_context *ctx, const void *data, size_t len)
|
||||
{
|
||||
const uint8_t *bytes = (const uint8_t *)data;
|
||||
|
||||
if ((ctx != NULL) && (bytes != NULL) && (ctx->len < sizeof(ctx->buf))) {
|
||||
__CPROVER_assume(__CPROVER_DYNAMIC_OBJECT(bytes));
|
||||
__CPROVER_assume(__CPROVER_DYNAMIC_OBJECT(ctx));
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
ctx->buf[ctx->len++] = bytes[i];
|
||||
if (ctx->len == sizeof(ctx->buf)) {
|
||||
_hash(ctx);
|
||||
_addbits(ctx, sizeof(ctx->buf) * 8);
|
||||
ctx->len = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void sha256_done(sha256_context *ctx, uint8_t *hash)
|
||||
{
|
||||
register uint32_t i, j;
|
||||
|
||||
if (ctx != NULL) {
|
||||
j = ctx->len % sizeof(ctx->buf);
|
||||
ctx->buf[j] = 0x80;
|
||||
for (i = j + 1; i < sizeof(ctx->buf); i++) {
|
||||
ctx->buf[i] = 0x00;
|
||||
}
|
||||
|
||||
if (ctx->len > 55) {
|
||||
_hash(ctx);
|
||||
for (j = 0; j < sizeof(ctx->buf); j++) {
|
||||
ctx->buf[j] = 0x00;
|
||||
}
|
||||
}
|
||||
|
||||
_addbits(ctx, ctx->len * 8);
|
||||
ctx->buf[63] = _shb(ctx->bits[0], 0);
|
||||
ctx->buf[62] = _shb(ctx->bits[0], 8);
|
||||
ctx->buf[61] = _shb(ctx->bits[0], 16);
|
||||
ctx->buf[60] = _shb(ctx->bits[0], 24);
|
||||
ctx->buf[59] = _shb(ctx->bits[1], 0);
|
||||
ctx->buf[58] = _shb(ctx->bits[1], 8);
|
||||
ctx->buf[57] = _shb(ctx->bits[1], 16);
|
||||
ctx->buf[56] = _shb(ctx->bits[1], 24);
|
||||
_hash(ctx);
|
||||
|
||||
if (hash != NULL) {
|
||||
for (i = 0, j = 24; i < 4; i++, j -= 8) {
|
||||
hash[i + 0] = _shb(ctx->hash[0], j);
|
||||
hash[i + 4] = _shb(ctx->hash[1], j);
|
||||
hash[i + 8] = _shb(ctx->hash[2], j);
|
||||
hash[i + 12] = _shb(ctx->hash[3], j);
|
||||
hash[i + 16] = _shb(ctx->hash[4], j);
|
||||
hash[i + 20] = _shb(ctx->hash[5], j);
|
||||
hash[i + 24] = _shb(ctx->hash[6], j);
|
||||
hash[i + 28] = _shb(ctx->hash[7], j);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void sha256(const void *data, size_t len, uint8_t *hash)
|
||||
{
|
||||
sha256_context ctx;
|
||||
|
||||
sha256_init(&ctx);
|
||||
sha256_hash(&ctx, data, len);
|
||||
sha256_done(&ctx, hash);
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
#ifndef SHA256_INCLUDED
|
||||
#define SHA256_INCLUDED
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
void sha256(const void *data, size_t len, uint8_t *hash);
|
||||
|
||||
#endif // SHA256_INCLUDED
|
||||
@@ -1,513 +0,0 @@
|
||||
#if defined(MAIN_SIMULATION) || defined(MAIN_TEST)
|
||||
#define QUAKEY_ENABLE_MOCKS
|
||||
#endif
|
||||
|
||||
#include <quakey.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "tcp.h"
|
||||
#include "message.h"
|
||||
|
||||
bool addr_eql(Address a, Address b)
|
||||
{
|
||||
if (a.is_ipv4 != b.is_ipv4)
|
||||
return false;
|
||||
|
||||
if (a.port != b.port)
|
||||
return false;
|
||||
|
||||
if (a.is_ipv4) {
|
||||
if (memcmp(&a.ipv4, &b.ipv4, sizeof(a.ipv4)))
|
||||
return false;
|
||||
} else {
|
||||
if (memcmp(&a.ipv6, &b.ipv6, sizeof(a.ipv6)))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int set_socket_blocking(SOCKET sock, bool value)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
u_long mode = !value;
|
||||
if (ioctlsocket(sock, FIONBIO, &mode) == SOCKET_ERROR)
|
||||
return -1;
|
||||
#else
|
||||
int flags = fcntl(sock, F_GETFL, 0);
|
||||
if (flags < 0)
|
||||
return -1;
|
||||
if (value) flags &= ~O_NONBLOCK;
|
||||
else flags |= O_NONBLOCK;
|
||||
if (fcntl(sock, F_SETFL, flags) < 0)
|
||||
return -1;
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static SOCKET create_listen_socket(string addr, uint16_t port)
|
||||
{
|
||||
SOCKET fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (fd == INVALID_SOCKET)
|
||||
return INVALID_SOCKET;
|
||||
|
||||
if (set_socket_blocking(fd, false) < 0) {
|
||||
CLOSE_SOCKET(fd);
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
|
||||
// TODO: mark address as reusable in debug builds
|
||||
|
||||
char tmp[1<<10];
|
||||
if (addr.len >= (int) sizeof(tmp)) {
|
||||
CLOSE_SOCKET(fd);
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
memcpy(tmp, addr.ptr, addr.len);
|
||||
tmp[addr.len] = '\0';
|
||||
|
||||
struct sockaddr_in bind_buf;
|
||||
bind_buf.sin_family = AF_INET;
|
||||
bind_buf.sin_port = htons(port);
|
||||
if (inet_pton(AF_INET, tmp, &bind_buf.sin_addr) != 1) {
|
||||
CLOSE_SOCKET(fd);
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
|
||||
if (bind(fd, (struct sockaddr*) &bind_buf, sizeof(bind_buf))) {
|
||||
CLOSE_SOCKET(fd);
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
|
||||
int backlog = 32;
|
||||
if (listen(fd, backlog) < 0) {
|
||||
CLOSE_SOCKET(fd);
|
||||
return INVALID_SOCKET;
|
||||
}
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
static int create_socket_pair(SOCKET *a, SOCKET *b)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
if (sock == INVALID_SOCKET)
|
||||
return -1;
|
||||
|
||||
// Bind to loopback address with port 0 (dynamic port assignment)
|
||||
struct sockaddr_in addr;
|
||||
int addr_len = sizeof(addr);
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sin_family = AF_INET;
|
||||
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); // 127.0.0.1
|
||||
addr.sin_port = 0; // Let system choose port
|
||||
|
||||
if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) {
|
||||
closesocket(sock);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (getsockname(sock, (struct sockaddr*)&addr, &addr_len) == SOCKET_ERROR) {
|
||||
closesocket(sock);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) {
|
||||
closesocket(sock);
|
||||
return -1;
|
||||
}
|
||||
|
||||
*a = sock;
|
||||
*b = sock;
|
||||
|
||||
// Optional: Set socket to non-blocking mode
|
||||
// This prevents send() from blocking if the receive buffer is full
|
||||
u_long mode = 1;
|
||||
ioctlsocket(sock, FIONBIO, &mode); // TODO: does this fail?
|
||||
return 0;
|
||||
#else
|
||||
int fds[2];
|
||||
if (pipe(fds) < 0)
|
||||
return -1;
|
||||
*a = fds[0];
|
||||
*b = fds[1];
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void close_socket_pair(SOCKET a, SOCKET b)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
closesocket(a);
|
||||
(void) b;
|
||||
#else
|
||||
close(a);
|
||||
close(b);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void conn_init(Connection *conn, SOCKET fd, bool connecting)
|
||||
{
|
||||
conn->fd = fd;
|
||||
conn->tag = -1;
|
||||
conn->connecting = connecting;
|
||||
conn->closing = false;
|
||||
conn->msglen = 0;
|
||||
byte_queue_init(&conn->input, 1<<20);
|
||||
byte_queue_init(&conn->output, 1<<20);
|
||||
}
|
||||
|
||||
static void conn_free(Connection *conn)
|
||||
{
|
||||
CLOSE_SOCKET(conn->fd);
|
||||
byte_queue_free(&conn->input);
|
||||
byte_queue_free(&conn->output);
|
||||
}
|
||||
|
||||
static int conn_events(Connection *conn)
|
||||
{
|
||||
int events = 0;
|
||||
|
||||
if (conn->connecting)
|
||||
events |= POLLOUT;
|
||||
else {
|
||||
|
||||
assert(!byte_queue_full(&conn->input));
|
||||
if (!conn->closing)
|
||||
events |= POLLIN;
|
||||
|
||||
if (!byte_queue_empty(&conn->output))
|
||||
events |= POLLOUT;
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
int tcp_context_init(TCP *tcp)
|
||||
{
|
||||
tcp->listen_fd = INVALID_SOCKET;
|
||||
tcp->num_conns = 0;
|
||||
|
||||
if (create_socket_pair(&tcp->wait_fd, &tcp->signal_fd) < 0)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void tcp_context_free(TCP *tcp)
|
||||
{
|
||||
// Free all connection byte queues without closing sockets
|
||||
// (sockets are managed by the simulation and will be cleaned up separately)
|
||||
for (int i = 0; i < tcp->num_conns; i++) {
|
||||
byte_queue_free(&tcp->conns[i].input);
|
||||
byte_queue_free(&tcp->conns[i].output);
|
||||
}
|
||||
tcp->num_conns = 0;
|
||||
|
||||
if (tcp->listen_fd != INVALID_SOCKET)
|
||||
CLOSE_SOCKET(tcp->listen_fd);
|
||||
|
||||
close_socket_pair(tcp->wait_fd, tcp->signal_fd);
|
||||
}
|
||||
|
||||
int tcp_wakeup(TCP *tcp)
|
||||
{
|
||||
send(tcp->signal_fd, "0", 1, 0); // TODO: Handle error
|
||||
return 0;
|
||||
}
|
||||
|
||||
int tcp_index_from_tag(TCP *tcp, int tag)
|
||||
{
|
||||
for (int i = 0; i < tcp->num_conns; i++)
|
||||
if (tcp->conns[i].tag == tag)
|
||||
return i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int tcp_listen(TCP *tcp, string addr, uint16_t port)
|
||||
{
|
||||
SOCKET listen_fd = create_listen_socket(addr, port);
|
||||
if (listen_fd == INVALID_SOCKET)
|
||||
return -1;
|
||||
|
||||
tcp->listen_fd = listen_fd;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int tcp_next_message(TCP *tcp, int conn_idx, ByteView *msg, uint16_t *type)
|
||||
{
|
||||
*msg = byte_queue_read_buf(&tcp->conns[conn_idx].input);
|
||||
|
||||
uint32_t len;
|
||||
int ret = message_peek(*msg, type, &len);
|
||||
|
||||
// Invalid message?
|
||||
if (ret < 0) {
|
||||
byte_queue_read_ack(&tcp->conns[conn_idx].input, 0);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Still buffering header?
|
||||
if (ret == 0) {
|
||||
byte_queue_read_ack(&tcp->conns[conn_idx].input, 0);
|
||||
if (byte_queue_full(&tcp->conns[conn_idx].input))
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Message received
|
||||
assert(ret > 0);
|
||||
msg->len = len;
|
||||
tcp->conns[conn_idx].msglen = len;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void tcp_consume_message(TCP *tcp, int conn_idx)
|
||||
{
|
||||
byte_queue_read_ack(&tcp->conns[conn_idx].input, tcp->conns[conn_idx].msglen);
|
||||
tcp->conns[conn_idx].msglen = 0;
|
||||
}
|
||||
|
||||
int tcp_register_events(TCP *tcp, void **contexts, struct pollfd *polled)
|
||||
{
|
||||
int num_polled = 0;
|
||||
|
||||
polled[num_polled].fd = tcp->wait_fd;
|
||||
polled[num_polled].events = POLLIN;
|
||||
polled[num_polled].revents = 0;
|
||||
contexts[num_polled] = NULL;
|
||||
num_polled++;
|
||||
|
||||
if (tcp->listen_fd != INVALID_SOCKET && tcp->num_conns < TCP_CONNECTION_LIMIT) {
|
||||
polled[num_polled].fd = tcp->listen_fd;
|
||||
polled[num_polled].events = POLLIN;
|
||||
polled[num_polled].revents = 0;
|
||||
contexts[num_polled] = NULL;
|
||||
num_polled++;
|
||||
}
|
||||
|
||||
for (int i = 0; i < tcp->num_conns; i++) {
|
||||
int events = conn_events(&tcp->conns[i]);
|
||||
if (events) {
|
||||
polled[num_polled].fd = tcp->conns[i].fd;
|
||||
polled[num_polled].events = events;
|
||||
polled[num_polled].revents = 0;
|
||||
contexts[num_polled] = &tcp->conns[i];
|
||||
num_polled++;
|
||||
}
|
||||
}
|
||||
|
||||
return num_polled;
|
||||
}
|
||||
|
||||
// The "events" array must be an array of capacity TCP_EVENT_CAPACITY,
|
||||
// while "contexts" and "polled" must have capacity TCP_POLL_CAPACITY.
|
||||
int tcp_translate_events(TCP *tcp, Event *events, void **contexts, struct pollfd *polled, int num_polled)
|
||||
{
|
||||
bool removed[TCP_POLL_CAPACITY];
|
||||
for (int i = 0; i < TCP_POLL_CAPACITY; i++)
|
||||
removed[i] = false;
|
||||
|
||||
int num_events = 0;
|
||||
for (int i = 1; i < num_polled; i++) {
|
||||
|
||||
if (polled[i].fd == tcp->wait_fd) {
|
||||
|
||||
if (polled[i].revents & POLLIN) {
|
||||
char buf[100];
|
||||
recv(tcp->wait_fd, buf, sizeof(buf), 0); // TODO: Make sure all bytes are consumed
|
||||
events[num_events++] = (Event) { EVENT_WAKEUP, -1, -1 };
|
||||
}
|
||||
|
||||
} else if (polled[i].fd == tcp->listen_fd) {
|
||||
|
||||
assert(contexts[i] == NULL);
|
||||
|
||||
if (polled[i].revents & POLLIN) {
|
||||
SOCKET new_fd = accept(tcp->listen_fd, NULL, NULL);
|
||||
if (new_fd != INVALID_SOCKET) {
|
||||
|
||||
if (set_socket_blocking(new_fd, false) < 0)
|
||||
CLOSE_SOCKET(new_fd);
|
||||
else {
|
||||
conn_init(&tcp->conns[tcp->num_conns++], new_fd, false);
|
||||
events[num_events++] = (Event) { EVENT_CONNECT, tcp->num_conns-1, tcp->conns[tcp->num_conns-1].tag };
|
||||
}
|
||||
}
|
||||
}
|
||||
removed[i] = false;
|
||||
|
||||
} else {
|
||||
|
||||
Connection *conn = contexts[i];
|
||||
bool defer_close = false;
|
||||
bool defer_ready = false;
|
||||
|
||||
if (conn->connecting) {
|
||||
|
||||
// Check for error conditions on the socket
|
||||
if (polled[i].revents & (POLLERR | POLLHUP | POLLNVAL)) {
|
||||
defer_close = true;
|
||||
} else if (polled[i].revents & POLLOUT) {
|
||||
|
||||
int err = 0;
|
||||
socklen_t len = sizeof(err);
|
||||
if (getsockopt(conn->fd, SOL_SOCKET, SO_ERROR, (void*) &err, &len) < 0 || err != 0)
|
||||
defer_close = true;
|
||||
else {
|
||||
conn->connecting = false;
|
||||
events[num_events++] = (Event) { EVENT_CONNECT, conn - tcp->conns, conn->tag };
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
if (polled[i].revents & POLLIN) {
|
||||
byte_queue_write_setmincap(&conn->input, 1<<9);
|
||||
ByteView buf = byte_queue_write_buf(&conn->input);
|
||||
int num = recv(conn->fd, (char*) buf.ptr, buf.len, 0);
|
||||
if (num == 0)
|
||||
defer_close = true;
|
||||
else if (num < 0) {
|
||||
if (errno != EINTR && errno != EWOULDBLOCK && errno != EAGAIN) // TODO: does Windows return these error codes or not?
|
||||
defer_close = true;
|
||||
num = 0;
|
||||
}
|
||||
byte_queue_write_ack(&conn->input, num);
|
||||
ByteView msg = byte_queue_read_buf(&conn->input);
|
||||
int ret = message_peek(msg, NULL, NULL);
|
||||
byte_queue_read_ack(&conn->input, 0);
|
||||
if (ret < 0) {
|
||||
// Invalid message
|
||||
defer_close = true;
|
||||
} else if (ret == 0) {
|
||||
// Still buffering
|
||||
if (byte_queue_full(&conn->input))
|
||||
defer_close = true;
|
||||
} else {
|
||||
// Message received
|
||||
assert(ret > 0);
|
||||
defer_ready = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (polled[i].revents & POLLOUT) {
|
||||
ByteView buf = byte_queue_read_buf(&conn->output);
|
||||
int num = send(conn->fd, (char*) buf.ptr, buf.len, 0);
|
||||
if (num < 0) {
|
||||
if (errno != EINTR && errno != EWOULDBLOCK && errno != EAGAIN)
|
||||
defer_close = true;
|
||||
num = 0;
|
||||
}
|
||||
byte_queue_read_ack(&conn->output, num);
|
||||
if (conn->closing && byte_queue_empty(&conn->output))
|
||||
defer_close = true;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: byte_queue_error here?
|
||||
|
||||
removed[i] = defer_close;
|
||||
if (0) {}
|
||||
else if (defer_close) events[num_events++] = (Event) { EVENT_DISCONNECT, conn - tcp->conns, conn->tag };
|
||||
else if (defer_ready) events[num_events++] = (Event) { EVENT_MESSAGE, conn - tcp->conns, conn->tag };
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 1; i < num_polled; i++) {
|
||||
if (removed[i]) {
|
||||
Connection *conn = contexts[i];
|
||||
assert(conn);
|
||||
conn_free(conn);
|
||||
*conn = tcp->conns[--tcp->num_conns];
|
||||
}
|
||||
}
|
||||
|
||||
return num_events;
|
||||
}
|
||||
|
||||
ByteQueue *tcp_output_buffer(TCP *tcp, int conn_idx)
|
||||
{
|
||||
return &tcp->conns[conn_idx].output;
|
||||
}
|
||||
|
||||
int tcp_connect(TCP *tcp, Address addr, int tag, ByteQueue **output)
|
||||
{
|
||||
if (tcp->num_conns == TCP_CONNECTION_LIMIT)
|
||||
return -1;
|
||||
int conn_idx = tcp->num_conns;
|
||||
|
||||
SOCKET fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (fd == INVALID_SOCKET)
|
||||
return -1;
|
||||
|
||||
if (set_socket_blocking(fd, false) < 0) {
|
||||
CLOSE_SOCKET(fd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int ret;
|
||||
if (addr.is_ipv4) {
|
||||
struct sockaddr_in buf;
|
||||
buf.sin_family = AF_INET;
|
||||
buf.sin_port = htons(addr.port);
|
||||
memcpy(&buf.sin_addr, &addr.ipv4, sizeof(IPv4));
|
||||
ret = connect(fd, (struct sockaddr*) &buf, sizeof(buf));
|
||||
} else {
|
||||
struct sockaddr_in6 buf;
|
||||
buf.sin6_family = AF_INET6;
|
||||
buf.sin6_port = htons(addr.port);
|
||||
memcpy(&buf.sin6_addr, &addr.ipv6, sizeof(IPv6));
|
||||
ret = connect(fd, (struct sockaddr*) &buf, sizeof(buf));
|
||||
}
|
||||
|
||||
bool connecting;
|
||||
if (ret == 0) {
|
||||
connecting = false;
|
||||
} else {
|
||||
if (errno != EINPROGRESS) {
|
||||
CLOSE_SOCKET(fd);
|
||||
return -1;
|
||||
}
|
||||
connecting = true;
|
||||
}
|
||||
|
||||
// Check that this tag wasn't already used
|
||||
for (int i = 0; i < tcp->num_conns; i++)
|
||||
assert(tcp->conns[i].tag != tag);
|
||||
|
||||
conn_init(&tcp->conns[conn_idx], fd, connecting);
|
||||
tcp->conns[conn_idx].tag = tag;
|
||||
|
||||
if (output)
|
||||
*output = &tcp->conns[conn_idx].output;
|
||||
|
||||
tcp->num_conns++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void tcp_close(TCP *tcp, int conn_idx)
|
||||
{
|
||||
tcp->conns[conn_idx].closing = true;
|
||||
// TODO: if no event will be triggered, the connection will not be closed
|
||||
// if the output buffer is empty, the connection should be closed here.
|
||||
}
|
||||
|
||||
void tcp_set_tag(TCP *tcp, int conn_idx, int tag, bool unique)
|
||||
{
|
||||
assert(tag != -1);
|
||||
|
||||
if (unique) {
|
||||
for (int i = 0; i < tcp->num_conns; i++)
|
||||
assert(tcp->conns[i].tag != tag);
|
||||
}
|
||||
|
||||
tcp->conns[conn_idx].tag = tag;
|
||||
}
|
||||
|
||||
int tcp_get_tag(TCP *tcp, int conn_idx)
|
||||
{
|
||||
return tcp->conns[conn_idx].tag;
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
#ifndef TCP_INCLUDED
|
||||
#define TCP_INCLUDED
|
||||
|
||||
#if defined(MAIN_SIMULATION) || defined(MAIN_TEST)
|
||||
# define QUAKEY_ENABLE_MOCKS
|
||||
# include <quakey.h>
|
||||
#else
|
||||
# ifdef _WIN32
|
||||
# include <winsock2.h>
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#include "byte_queue.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#define CLOSE_SOCKET closesocket
|
||||
#else
|
||||
#define SOCKET int
|
||||
#define INVALID_SOCKET -1
|
||||
#define CLOSE_SOCKET close
|
||||
#endif
|
||||
|
||||
#ifndef TCP_CONNECTION_LIMIT
|
||||
// Maximum number of connections that can be managed
|
||||
// simultaneously.
|
||||
#define TCP_CONNECTION_LIMIT 512
|
||||
#endif
|
||||
|
||||
// This is the maximum number of descriptors that the
|
||||
// TCP system will want to wait at any given time.
|
||||
// One descriptor per connection plus a listener socket
|
||||
// and a self-pipe handle for wakeup.
|
||||
#define TCP_POLL_CAPACITY (TCP_CONNECTION_LIMIT+2)
|
||||
|
||||
// Number of TCP events that can be returned at a given
|
||||
// time by "tcp_translate_events". There may be a single
|
||||
// event per connection (MESSAGE, CONNECT, DISCONNECT)
|
||||
// plus a general WAKEUP event.
|
||||
#define TCP_EVENT_CAPACITY (TCP_CONNECTION_LIMIT+1)
|
||||
|
||||
typedef enum {
|
||||
EVENT_WAKEUP,
|
||||
EVENT_MESSAGE,
|
||||
EVENT_CONNECT,
|
||||
EVENT_DISCONNECT,
|
||||
} EventType;
|
||||
|
||||
typedef struct {
|
||||
EventType type;
|
||||
int conn_idx;
|
||||
int tag;
|
||||
} Event;
|
||||
|
||||
typedef struct {
|
||||
SOCKET fd;
|
||||
int tag;
|
||||
bool connecting;
|
||||
bool closing;
|
||||
uint32_t msglen;
|
||||
ByteQueue input;
|
||||
ByteQueue output;
|
||||
} Connection;
|
||||
|
||||
typedef struct {
|
||||
SOCKET listen_fd;
|
||||
SOCKET wait_fd;
|
||||
SOCKET signal_fd;
|
||||
int num_conns;
|
||||
Connection conns[TCP_CONNECTION_LIMIT];
|
||||
} TCP;
|
||||
|
||||
bool addr_eql(Address a, Address b);
|
||||
int tcp_context_init(TCP *tcp);
|
||||
void tcp_context_free(TCP *tcp);
|
||||
int tcp_wakeup(TCP *tcp);
|
||||
int tcp_index_from_tag(TCP *tcp, int tag);
|
||||
int tcp_listen(TCP *tcp, string addr, uint16_t port);
|
||||
int tcp_next_message(TCP *tcp, int conn_idx, ByteView *msg, uint16_t *type);
|
||||
void tcp_consume_message(TCP *tcp, int conn_idx);
|
||||
int tcp_translate_events(TCP *tcp, Event *events, void **contexts, struct pollfd *polled, int num_polled);
|
||||
int tcp_register_events(TCP *tcp, void **contexts, struct pollfd *polled);
|
||||
ByteQueue *tcp_output_buffer(TCP *tcp, int conn_idx);
|
||||
int tcp_connect(TCP *tcp, Address addr, int tag, ByteQueue **output);
|
||||
void tcp_close(TCP *tcp, int conn_idx);
|
||||
void tcp_set_tag(TCP *tcp, int conn_idx, int tag, bool unique);
|
||||
int tcp_get_tag(TCP *tcp, int conn_idx);
|
||||
|
||||
#endif // TCP_INCLUDED
|
||||
@@ -1,640 +0,0 @@
|
||||
#ifdef MAIN_TEST
|
||||
|
||||
#define QUAKEY_ENABLE_MOCKS
|
||||
#include <quakey.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "tcp.h"
|
||||
#include "test_client.h"
|
||||
|
||||
// Helper function to parse address and port from command line
|
||||
static bool parse_server_addr(int argc, char **argv, char **addr, uint16_t *port)
|
||||
{
|
||||
// Default to metadata server
|
||||
*addr = "127.0.0.1";
|
||||
*port = 8080;
|
||||
|
||||
for (int i = 0; i < argc - 1; i++) {
|
||||
if (!strcmp(argv[i], "--server") || !strcmp(argv[i], "-s")) {
|
||||
*addr = argv[i + 1];
|
||||
if (i + 2 < argc) {
|
||||
|
||||
errno = 0;
|
||||
char *end;
|
||||
long val = strtol(argv[i+2], &end, 10);
|
||||
|
||||
if (end == argv[i+2] || *end != '\0' || errno == ERANGE)
|
||||
break;
|
||||
|
||||
if (val < 0 || val > UINT16_MAX)
|
||||
break;
|
||||
|
||||
*port = (uint16_t) val;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int test_client_init(void *state_, int argc, char **argv,
|
||||
void **ctxs, struct pollfd *pdata, int pcap, int *pnum,
|
||||
int *timeout)
|
||||
{
|
||||
TestClient *client = state_;
|
||||
|
||||
char *addr;
|
||||
uint16_t port;
|
||||
parse_server_addr(argc, argv, &addr, &port);
|
||||
|
||||
client->toasty = toasty_connect((ToastyString) { addr, strlen(addr) }, port);
|
||||
if (client->toasty == NULL)
|
||||
return -1;
|
||||
|
||||
client->state = TEST_CLIENT_STATE_INIT;
|
||||
client->tick = 0;
|
||||
client->tests_passed = 0;
|
||||
|
||||
printf("Client set up (remote=%s:%d)\n", addr, port);
|
||||
fflush(stdout);
|
||||
|
||||
*timeout = -1;
|
||||
if (pcap < TCP_POLL_CAPACITY)
|
||||
return -1;
|
||||
*pnum = toasty_process_events(client->toasty, ctxs, pdata, *pnum);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Static test data
|
||||
static char lorem_ipsum[] =
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do "
|
||||
"eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut "
|
||||
"enim ad minim veniam, quis nostrud exercitation ullamco laboris "
|
||||
"nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor "
|
||||
"in reprehenderit in voluptate velit esse cillum dolore eu fugiat "
|
||||
"nulla pariatur. Excepteur sint occaecat cupidatat non proident, "
|
||||
"sunt in culpa qui officia deserunt mollit anim id est laborum.";
|
||||
|
||||
static char short_msg[] = "Hello World!";
|
||||
static char offset_msg[] = "INSERTED_DATA";
|
||||
static char truncate_msg[] = "Truncated content";
|
||||
|
||||
// Test path constants
|
||||
static ToastyString file_path = TOASTY_STR("some_file.txt");
|
||||
static ToastyString dir_path = TOASTY_STR("/testdir");
|
||||
static ToastyString subdir_path = TOASTY_STR("/testdir/subdir");
|
||||
static ToastyString file_in_dir_path = TOASTY_STR("/testdir/nested_file.txt");
|
||||
static ToastyString auto_create_path = TOASTY_STR("/auto_created_file.txt");
|
||||
static ToastyString truncate_file_path = TOASTY_STR("/truncate_test.txt");
|
||||
static ToastyString offset_file_path = TOASTY_STR("/offset_test.txt");
|
||||
|
||||
#define TEST_PASS(name) do { \
|
||||
client->tests_passed++; \
|
||||
printf("Test PASSED: %s\n", name); \
|
||||
fflush(stdout); \
|
||||
} while(0)
|
||||
|
||||
#define TEST_FAIL(name) do { \
|
||||
printf("Test FAILED: %s\n", name); \
|
||||
fflush(stdout); \
|
||||
exit(1); \
|
||||
} while(0)
|
||||
|
||||
int test_client_tick(void *state_, void **ctxs,
|
||||
struct pollfd *pdata, int pcap, int *pnum,
|
||||
int *timeout)
|
||||
{
|
||||
TestClient *client = state_;
|
||||
|
||||
// Process any pending events from the network and get new poll descriptors
|
||||
*pnum = toasty_process_events(client->toasty, ctxs, pdata, *pnum);
|
||||
|
||||
int ret;
|
||||
ToastyResult result;
|
||||
|
||||
switch (client->state) {
|
||||
|
||||
//==========================================================================
|
||||
// PHASE 1: Basic file create/write/read test
|
||||
//==========================================================================
|
||||
case TEST_CLIENT_STATE_INIT:
|
||||
if (client->tick < 10) {
|
||||
*timeout = 0;
|
||||
client->tick++;
|
||||
return 0;
|
||||
}
|
||||
printf("\n=== Phase 1: Basic file operations ===\n");
|
||||
client->handle = toasty_begin_create_file(client->toasty, file_path, 128);
|
||||
if (client->handle == TOASTY_INVALID) {
|
||||
TEST_FAIL("begin create file");
|
||||
}
|
||||
printf("Create file started\n");
|
||||
client->state = TEST_CLIENT_STATE_CREATE_FILE_WAIT;
|
||||
break;
|
||||
|
||||
case TEST_CLIENT_STATE_CREATE_FILE_WAIT:
|
||||
ret = toasty_get_result(client->toasty, client->handle, &result);
|
||||
if (ret < 0) TEST_FAIL("get result for create file");
|
||||
if (ret == 1) break; // Still in progress
|
||||
if (result.type != TOASTY_RESULT_CREATE_SUCCESS) {
|
||||
TEST_FAIL("create file result");
|
||||
}
|
||||
TEST_PASS("Create file");
|
||||
toasty_free_result(&result);
|
||||
|
||||
// Start write
|
||||
client->handle = toasty_begin_write(client->toasty, file_path, 0,
|
||||
lorem_ipsum, sizeof(lorem_ipsum)-1, TOASTY_VERSION_TAG_EMPTY, 0);
|
||||
if (client->handle == TOASTY_INVALID) {
|
||||
TEST_FAIL("begin write");
|
||||
}
|
||||
printf("Write started\n");
|
||||
client->state = TEST_CLIENT_STATE_WRITE_WAIT;
|
||||
break;
|
||||
|
||||
case TEST_CLIENT_STATE_WRITE_WAIT:
|
||||
ret = toasty_get_result(client->toasty, client->handle, &result);
|
||||
if (ret < 0) TEST_FAIL("get result for write");
|
||||
if (ret == 1) break;
|
||||
if (result.type != TOASTY_RESULT_WRITE_SUCCESS) {
|
||||
TEST_FAIL("write result");
|
||||
}
|
||||
TEST_PASS("Write data");
|
||||
toasty_free_result(&result);
|
||||
|
||||
// Start read
|
||||
client->handle = toasty_begin_read(client->toasty, file_path, 0,
|
||||
client->buf, sizeof(client->buf), TOASTY_VERSION_TAG_EMPTY);
|
||||
if (client->handle == TOASTY_INVALID) {
|
||||
TEST_FAIL("begin read");
|
||||
}
|
||||
printf("Read started\n");
|
||||
client->state = TEST_CLIENT_STATE_READ_WAIT;
|
||||
break;
|
||||
|
||||
case TEST_CLIENT_STATE_READ_WAIT:
|
||||
ret = toasty_get_result(client->toasty, client->handle, &result);
|
||||
if (ret < 0) TEST_FAIL("get result for read");
|
||||
if (ret == 1) break;
|
||||
if (result.type != TOASTY_RESULT_READ_SUCCESS) {
|
||||
TEST_FAIL("read result type");
|
||||
}
|
||||
if (result.bytes_read != sizeof(lorem_ipsum)-1) {
|
||||
printf("Expected %zu bytes, got %d\n", sizeof(lorem_ipsum)-1, result.bytes_read);
|
||||
TEST_FAIL("read bytes count");
|
||||
}
|
||||
if (memcmp(client->buf, lorem_ipsum, sizeof(lorem_ipsum)-1)) {
|
||||
TEST_FAIL("read data mismatch");
|
||||
}
|
||||
TEST_PASS("Read data matches written data");
|
||||
toasty_free_result(&result);
|
||||
client->state = TEST_CLIENT_STATE_CREATE_DIR;
|
||||
*timeout = 0; // Trigger immediate next iteration
|
||||
return 0;
|
||||
|
||||
//==========================================================================
|
||||
// PHASE 2: Directory operations
|
||||
//==========================================================================
|
||||
case TEST_CLIENT_STATE_CREATE_DIR:
|
||||
printf("\n=== Phase 2: Directory operations ===\n");
|
||||
client->handle = toasty_begin_create_dir(client->toasty, dir_path);
|
||||
if (client->handle == TOASTY_INVALID) {
|
||||
TEST_FAIL("begin create dir");
|
||||
}
|
||||
printf("Create directory started\n");
|
||||
client->state = TEST_CLIENT_STATE_CREATE_DIR_WAIT;
|
||||
break;
|
||||
|
||||
case TEST_CLIENT_STATE_CREATE_DIR_WAIT:
|
||||
ret = toasty_get_result(client->toasty, client->handle, &result);
|
||||
if (ret < 0) TEST_FAIL("get result for create dir");
|
||||
if (ret == 1) break;
|
||||
if (result.type != TOASTY_RESULT_CREATE_SUCCESS) {
|
||||
TEST_FAIL("create dir result");
|
||||
}
|
||||
TEST_PASS("Create directory");
|
||||
toasty_free_result(&result);
|
||||
client->state = TEST_CLIENT_STATE_CREATE_SUBDIR;
|
||||
*timeout = 0; return 0;
|
||||
|
||||
case TEST_CLIENT_STATE_CREATE_SUBDIR:
|
||||
client->handle = toasty_begin_create_dir(client->toasty, subdir_path);
|
||||
if (client->handle == TOASTY_INVALID) {
|
||||
TEST_FAIL("begin create subdir");
|
||||
}
|
||||
printf("Create subdirectory started\n");
|
||||
client->state = TEST_CLIENT_STATE_CREATE_SUBDIR_WAIT;
|
||||
break;
|
||||
|
||||
case TEST_CLIENT_STATE_CREATE_SUBDIR_WAIT:
|
||||
ret = toasty_get_result(client->toasty, client->handle, &result);
|
||||
if (ret < 0) TEST_FAIL("get result for create subdir");
|
||||
if (ret == 1) break;
|
||||
if (result.type != TOASTY_RESULT_CREATE_SUCCESS) {
|
||||
TEST_FAIL("create subdir result");
|
||||
}
|
||||
TEST_PASS("Create subdirectory");
|
||||
toasty_free_result(&result);
|
||||
client->state = TEST_CLIENT_STATE_CREATE_FILE_IN_DIR;
|
||||
*timeout = 0; return 0;
|
||||
|
||||
case TEST_CLIENT_STATE_CREATE_FILE_IN_DIR:
|
||||
client->handle = toasty_begin_create_file(client->toasty, file_in_dir_path, 256);
|
||||
if (client->handle == TOASTY_INVALID) {
|
||||
TEST_FAIL("begin create file in dir");
|
||||
}
|
||||
printf("Create file in directory started\n");
|
||||
client->state = TEST_CLIENT_STATE_CREATE_FILE_IN_DIR_WAIT;
|
||||
break;
|
||||
|
||||
case TEST_CLIENT_STATE_CREATE_FILE_IN_DIR_WAIT:
|
||||
ret = toasty_get_result(client->toasty, client->handle, &result);
|
||||
if (ret < 0) TEST_FAIL("get result for create file in dir");
|
||||
if (ret == 1) break;
|
||||
if (result.type != TOASTY_RESULT_CREATE_SUCCESS) {
|
||||
TEST_FAIL("create file in dir result");
|
||||
}
|
||||
TEST_PASS("Create file in directory");
|
||||
toasty_free_result(&result);
|
||||
client->state = TEST_CLIENT_STATE_LIST_DIR;
|
||||
*timeout = 0; return 0;
|
||||
|
||||
case TEST_CLIENT_STATE_LIST_DIR:
|
||||
client->handle = toasty_begin_list(client->toasty, dir_path, TOASTY_VERSION_TAG_EMPTY);
|
||||
if (client->handle == TOASTY_INVALID) {
|
||||
TEST_FAIL("begin list dir");
|
||||
}
|
||||
printf("List directory started\n");
|
||||
client->state = TEST_CLIENT_STATE_LIST_DIR_WAIT;
|
||||
break;
|
||||
|
||||
case TEST_CLIENT_STATE_LIST_DIR_WAIT:
|
||||
ret = toasty_get_result(client->toasty, client->handle, &result);
|
||||
if (ret < 0) TEST_FAIL("get result for list dir");
|
||||
if (ret == 1) break;
|
||||
if (result.type != TOASTY_RESULT_LIST_SUCCESS) {
|
||||
TEST_FAIL("list dir result type");
|
||||
}
|
||||
printf("Directory listing count: %d\n", result.listing.count);
|
||||
// Directory should contain subdir and nested_file.txt
|
||||
if (result.listing.count != 2) {
|
||||
printf("Expected 2 entries, got %d\n", result.listing.count);
|
||||
TEST_FAIL("list dir count");
|
||||
}
|
||||
// Print the entries
|
||||
for (int i = 0; i < result.listing.count; i++) {
|
||||
printf(" Entry %d: %s (is_dir=%d)\n", i,
|
||||
result.listing.items[i].name,
|
||||
result.listing.items[i].is_dir);
|
||||
}
|
||||
TEST_PASS("List directory");
|
||||
toasty_free_result(&result);
|
||||
client->state = TEST_CLIENT_STATE_LIST_ROOT;
|
||||
*timeout = 0; return 0;
|
||||
|
||||
case TEST_CLIENT_STATE_LIST_ROOT:
|
||||
client->handle = toasty_begin_list(client->toasty, TOASTY_STR("/"), TOASTY_VERSION_TAG_EMPTY);
|
||||
if (client->handle == TOASTY_INVALID) {
|
||||
TEST_FAIL("begin list root");
|
||||
}
|
||||
printf("List root directory started\n");
|
||||
client->state = TEST_CLIENT_STATE_LIST_ROOT_WAIT;
|
||||
break;
|
||||
|
||||
case TEST_CLIENT_STATE_LIST_ROOT_WAIT:
|
||||
ret = toasty_get_result(client->toasty, client->handle, &result);
|
||||
if (ret < 0) TEST_FAIL("get result for list root");
|
||||
if (ret == 1) break;
|
||||
if (result.type != TOASTY_RESULT_LIST_SUCCESS) {
|
||||
TEST_FAIL("list root result type");
|
||||
}
|
||||
printf("Root listing count: %d\n", result.listing.count);
|
||||
for (int i = 0; i < result.listing.count; i++) {
|
||||
printf(" Entry %d: %s (is_dir=%d)\n", i,
|
||||
result.listing.items[i].name,
|
||||
result.listing.items[i].is_dir);
|
||||
}
|
||||
// Root should have some entries at this point
|
||||
if (result.listing.count < 1) {
|
||||
TEST_FAIL("list root count");
|
||||
}
|
||||
TEST_PASS("List root directory");
|
||||
toasty_free_result(&result);
|
||||
client->state = TEST_CLIENT_STATE_WRITE_CREATE_IF_MISSING;
|
||||
*timeout = 0; return 0;
|
||||
|
||||
//==========================================================================
|
||||
// PHASE 3: Write flags tests
|
||||
//==========================================================================
|
||||
case TEST_CLIENT_STATE_WRITE_CREATE_IF_MISSING:
|
||||
printf("\n=== Phase 3: Additional write tests ===\n");
|
||||
// Create a file for testing additional writes
|
||||
client->handle = toasty_begin_create_file(client->toasty, auto_create_path, 256);
|
||||
if (client->handle == TOASTY_INVALID) {
|
||||
TEST_FAIL("begin create auto file");
|
||||
}
|
||||
printf("Create auto file started\n");
|
||||
client->state = TEST_CLIENT_STATE_WRITE_CREATE_IF_MISSING_WAIT;
|
||||
break;
|
||||
|
||||
case TEST_CLIENT_STATE_WRITE_CREATE_IF_MISSING_WAIT:
|
||||
ret = toasty_get_result(client->toasty, client->handle, &result);
|
||||
if (ret < 0) TEST_FAIL("get result for create auto file");
|
||||
if (ret == 1) break;
|
||||
if (result.type != TOASTY_RESULT_CREATE_SUCCESS) {
|
||||
TEST_FAIL("create auto file result");
|
||||
}
|
||||
TEST_PASS("Create auto file");
|
||||
toasty_free_result(&result);
|
||||
|
||||
// Now write to the file
|
||||
client->handle = toasty_begin_write(client->toasty, auto_create_path, 0,
|
||||
short_msg, sizeof(short_msg)-1, TOASTY_VERSION_TAG_EMPTY, 0);
|
||||
if (client->handle == TOASTY_INVALID) {
|
||||
TEST_FAIL("begin write to auto file");
|
||||
}
|
||||
printf("Write to auto file started\n");
|
||||
client->state = TEST_CLIENT_STATE_READ_CREATED_FILE;
|
||||
break;
|
||||
|
||||
case TEST_CLIENT_STATE_READ_CREATED_FILE:
|
||||
ret = toasty_get_result(client->toasty, client->handle, &result);
|
||||
if (ret < 0) TEST_FAIL("get result for write to auto file");
|
||||
if (ret == 1) break;
|
||||
if (result.type != TOASTY_RESULT_WRITE_SUCCESS) {
|
||||
TEST_FAIL("write to auto file result");
|
||||
}
|
||||
TEST_PASS("Write to auto file");
|
||||
toasty_free_result(&result);
|
||||
|
||||
// Read back the file
|
||||
memset(client->buf, 0, sizeof(client->buf));
|
||||
client->handle = toasty_begin_read(client->toasty, auto_create_path, 0,
|
||||
client->buf, sizeof(client->buf), TOASTY_VERSION_TAG_EMPTY);
|
||||
if (client->handle == TOASTY_INVALID) {
|
||||
TEST_FAIL("begin read auto file");
|
||||
}
|
||||
printf("Read auto file started\n");
|
||||
client->state = TEST_CLIENT_STATE_READ_CREATED_FILE_WAIT;
|
||||
break;
|
||||
|
||||
case TEST_CLIENT_STATE_READ_CREATED_FILE_WAIT:
|
||||
ret = toasty_get_result(client->toasty, client->handle, &result);
|
||||
if (ret < 0) TEST_FAIL("get result for read auto file");
|
||||
if (ret == 1) break;
|
||||
if (result.type != TOASTY_RESULT_READ_SUCCESS) {
|
||||
TEST_FAIL("read auto file result type");
|
||||
}
|
||||
if (result.bytes_read != sizeof(short_msg)-1) {
|
||||
printf("Expected %zu bytes, got %d\n", sizeof(short_msg)-1, result.bytes_read);
|
||||
TEST_FAIL("read auto file bytes count");
|
||||
}
|
||||
if (memcmp(client->buf, short_msg, sizeof(short_msg)-1)) {
|
||||
TEST_FAIL("read auto file data mismatch");
|
||||
}
|
||||
TEST_PASS("Read auto file");
|
||||
toasty_free_result(&result);
|
||||
client->state = TEST_CLIENT_STATE_WRITE_TRUNCATE;
|
||||
*timeout = 0; return 0;
|
||||
|
||||
case TEST_CLIENT_STATE_WRITE_TRUNCATE:
|
||||
// Create a file for testing multiple writes
|
||||
client->handle = toasty_begin_create_file(client->toasty, truncate_file_path, 256);
|
||||
if (client->handle == TOASTY_INVALID) {
|
||||
TEST_FAIL("begin create multi-write file");
|
||||
}
|
||||
printf("Create multi-write test file started\n");
|
||||
client->state = TEST_CLIENT_STATE_WRITE_TRUNCATE_WAIT;
|
||||
break;
|
||||
|
||||
case TEST_CLIENT_STATE_WRITE_TRUNCATE_WAIT:
|
||||
ret = toasty_get_result(client->toasty, client->handle, &result);
|
||||
if (ret < 0) TEST_FAIL("get result for create multi-write file");
|
||||
if (ret == 1) break;
|
||||
if (result.type != TOASTY_RESULT_CREATE_SUCCESS) {
|
||||
TEST_FAIL("create multi-write file result");
|
||||
}
|
||||
TEST_PASS("Create multi-write file");
|
||||
toasty_free_result(&result);
|
||||
|
||||
// Write initial content
|
||||
client->handle = toasty_begin_write(client->toasty, truncate_file_path, 0,
|
||||
lorem_ipsum, sizeof(lorem_ipsum)-1, TOASTY_VERSION_TAG_EMPTY, 0);
|
||||
if (client->handle == TOASTY_INVALID) {
|
||||
TEST_FAIL("begin write multi-write content");
|
||||
}
|
||||
printf("Write multi-write content started\n");
|
||||
client->state = TEST_CLIENT_STATE_READ_TRUNCATED;
|
||||
break;
|
||||
|
||||
case TEST_CLIENT_STATE_READ_TRUNCATED:
|
||||
ret = toasty_get_result(client->toasty, client->handle, &result);
|
||||
if (ret < 0) TEST_FAIL("get result for write multi-write content");
|
||||
if (ret == 1) break;
|
||||
if (result.type != TOASTY_RESULT_WRITE_SUCCESS) {
|
||||
TEST_FAIL("write multi-write content result");
|
||||
}
|
||||
TEST_PASS("Write multi-write content");
|
||||
toasty_free_result(&result);
|
||||
|
||||
// Read back the file
|
||||
memset(client->buf, 0, sizeof(client->buf));
|
||||
client->handle = toasty_begin_read(client->toasty, truncate_file_path, 0,
|
||||
client->buf, sizeof(client->buf), TOASTY_VERSION_TAG_EMPTY);
|
||||
if (client->handle == TOASTY_INVALID) {
|
||||
TEST_FAIL("begin read multi-write file");
|
||||
}
|
||||
printf("Read multi-write file started\n");
|
||||
client->state = TEST_CLIENT_STATE_READ_TRUNCATED_WAIT;
|
||||
break;
|
||||
|
||||
case TEST_CLIENT_STATE_READ_TRUNCATED_WAIT:
|
||||
ret = toasty_get_result(client->toasty, client->handle, &result);
|
||||
if (ret < 0) TEST_FAIL("get result for read multi-write file");
|
||||
if (ret == 1) break;
|
||||
if (result.type != TOASTY_RESULT_READ_SUCCESS) {
|
||||
TEST_FAIL("read multi-write file result type");
|
||||
}
|
||||
if (result.bytes_read != sizeof(lorem_ipsum)-1) {
|
||||
printf("Expected %zu bytes, got %d\n", sizeof(lorem_ipsum)-1, result.bytes_read);
|
||||
TEST_FAIL("read multi-write file bytes count");
|
||||
}
|
||||
TEST_PASS("Read multi-write file");
|
||||
toasty_free_result(&result);
|
||||
client->state = TEST_CLIENT_STATE_WRITE_AT_OFFSET;
|
||||
*timeout = 0; return 0;
|
||||
|
||||
//==========================================================================
|
||||
// PHASE 4: Offset read/write tests
|
||||
//==========================================================================
|
||||
case TEST_CLIENT_STATE_WRITE_AT_OFFSET:
|
||||
printf("\n=== Phase 4: Offset read/write tests ===\n");
|
||||
// Create a new file for offset testing with larger chunk size
|
||||
client->handle = toasty_begin_create_file(client->toasty, offset_file_path, 256);
|
||||
if (client->handle == TOASTY_INVALID) {
|
||||
TEST_FAIL("begin create offset file");
|
||||
}
|
||||
printf("Create offset test file started\n");
|
||||
client->state = TEST_CLIENT_STATE_WRITE_AT_OFFSET_WAIT;
|
||||
break;
|
||||
|
||||
case TEST_CLIENT_STATE_WRITE_AT_OFFSET_WAIT:
|
||||
ret = toasty_get_result(client->toasty, client->handle, &result);
|
||||
if (ret < 0) TEST_FAIL("get result for create offset file");
|
||||
if (ret == 1) break;
|
||||
if (result.type != TOASTY_RESULT_CREATE_SUCCESS) {
|
||||
TEST_FAIL("create offset file result");
|
||||
}
|
||||
TEST_PASS("Create offset file");
|
||||
toasty_free_result(&result);
|
||||
|
||||
// Write initial data (small amount)
|
||||
client->handle = toasty_begin_write(client->toasty, offset_file_path, 0,
|
||||
short_msg, sizeof(short_msg)-1, TOASTY_VERSION_TAG_EMPTY, 0);
|
||||
if (client->handle == TOASTY_INVALID) {
|
||||
TEST_FAIL("begin write initial offset data");
|
||||
}
|
||||
printf("Write initial offset data started\n");
|
||||
client->state = TEST_CLIENT_STATE_READ_AT_OFFSET;
|
||||
break;
|
||||
|
||||
case TEST_CLIENT_STATE_READ_AT_OFFSET:
|
||||
ret = toasty_get_result(client->toasty, client->handle, &result);
|
||||
if (ret < 0) TEST_FAIL("get result for write initial offset");
|
||||
if (ret == 1) break;
|
||||
if (result.type != TOASTY_RESULT_WRITE_SUCCESS) {
|
||||
TEST_FAIL("write initial offset result");
|
||||
}
|
||||
TEST_PASS("Write initial offset data");
|
||||
toasty_free_result(&result);
|
||||
|
||||
// Read from the beginning to verify
|
||||
memset(client->buf, 0, sizeof(client->buf));
|
||||
client->handle = toasty_begin_read(client->toasty, offset_file_path, 0,
|
||||
client->buf, sizeof(short_msg)-1, TOASTY_VERSION_TAG_EMPTY);
|
||||
if (client->handle == TOASTY_INVALID) {
|
||||
TEST_FAIL("begin read offset file");
|
||||
}
|
||||
printf("Read offset file started\n");
|
||||
client->state = TEST_CLIENT_STATE_READ_AT_OFFSET_WAIT;
|
||||
break;
|
||||
|
||||
case TEST_CLIENT_STATE_READ_AT_OFFSET_WAIT:
|
||||
ret = toasty_get_result(client->toasty, client->handle, &result);
|
||||
if (ret < 0) TEST_FAIL("get result for read offset file");
|
||||
if (ret == 1) break;
|
||||
if (result.type != TOASTY_RESULT_READ_SUCCESS) {
|
||||
TEST_FAIL("read offset file result type");
|
||||
}
|
||||
if (result.bytes_read != sizeof(short_msg)-1) {
|
||||
printf("Expected %zu bytes, got %d\n", sizeof(short_msg)-1, result.bytes_read);
|
||||
TEST_FAIL("read offset file bytes count");
|
||||
}
|
||||
TEST_PASS("Read offset file");
|
||||
toasty_free_result(&result);
|
||||
client->state = TEST_CLIENT_STATE_DELETE_FILE;
|
||||
*timeout = 0; return 0;
|
||||
|
||||
//==========================================================================
|
||||
// PHASE 5: Delete operations
|
||||
//==========================================================================
|
||||
case TEST_CLIENT_STATE_DELETE_FILE:
|
||||
printf("\n=== Phase 5: Delete operations ===\n");
|
||||
// Delete the auto-created file
|
||||
client->handle = toasty_begin_delete(client->toasty, auto_create_path, TOASTY_VERSION_TAG_EMPTY);
|
||||
if (client->handle == TOASTY_INVALID) {
|
||||
TEST_FAIL("begin delete file");
|
||||
}
|
||||
printf("Delete auto file started\n");
|
||||
client->state = TEST_CLIENT_STATE_DELETE_FILE_WAIT;
|
||||
break;
|
||||
|
||||
case TEST_CLIENT_STATE_DELETE_FILE_WAIT:
|
||||
ret = toasty_get_result(client->toasty, client->handle, &result);
|
||||
if (ret < 0) TEST_FAIL("get result for delete file");
|
||||
if (ret == 1) break;
|
||||
if (result.type != TOASTY_RESULT_DELETE_SUCCESS) {
|
||||
TEST_FAIL("delete file result");
|
||||
}
|
||||
TEST_PASS("Delete file");
|
||||
toasty_free_result(&result);
|
||||
|
||||
// Delete the nested file first (before deleting directory)
|
||||
client->handle = toasty_begin_delete(client->toasty, file_in_dir_path, TOASTY_VERSION_TAG_EMPTY);
|
||||
if (client->handle == TOASTY_INVALID) {
|
||||
TEST_FAIL("begin delete nested file");
|
||||
}
|
||||
printf("Delete nested file started\n");
|
||||
client->state = TEST_CLIENT_STATE_DELETE_DIR;
|
||||
break;
|
||||
|
||||
case TEST_CLIENT_STATE_DELETE_DIR:
|
||||
ret = toasty_get_result(client->toasty, client->handle, &result);
|
||||
if (ret < 0) TEST_FAIL("get result for delete nested file");
|
||||
if (ret == 1) break;
|
||||
if (result.type != TOASTY_RESULT_DELETE_SUCCESS) {
|
||||
TEST_FAIL("delete nested file result");
|
||||
}
|
||||
TEST_PASS("Delete nested file");
|
||||
toasty_free_result(&result);
|
||||
|
||||
// Delete the subdirectory
|
||||
client->handle = toasty_begin_delete(client->toasty, subdir_path, TOASTY_VERSION_TAG_EMPTY);
|
||||
if (client->handle == TOASTY_INVALID) {
|
||||
TEST_FAIL("begin delete subdir");
|
||||
}
|
||||
printf("Delete subdirectory started\n");
|
||||
client->state = TEST_CLIENT_STATE_DELETE_DIR_WAIT;
|
||||
break;
|
||||
|
||||
case TEST_CLIENT_STATE_DELETE_DIR_WAIT:
|
||||
ret = toasty_get_result(client->toasty, client->handle, &result);
|
||||
if (ret < 0) TEST_FAIL("get result for delete subdir");
|
||||
if (ret == 1) break;
|
||||
if (result.type != TOASTY_RESULT_DELETE_SUCCESS) {
|
||||
TEST_FAIL("delete subdir result");
|
||||
}
|
||||
TEST_PASS("Delete subdirectory");
|
||||
toasty_free_result(&result);
|
||||
|
||||
// Delete the parent directory
|
||||
client->handle = toasty_begin_delete(client->toasty, dir_path, TOASTY_VERSION_TAG_EMPTY);
|
||||
if (client->handle == TOASTY_INVALID) {
|
||||
TEST_FAIL("begin delete dir");
|
||||
}
|
||||
printf("Delete directory started\n");
|
||||
client->state = TEST_CLIENT_STATE_DONE;
|
||||
break;
|
||||
|
||||
case TEST_CLIENT_STATE_DONE:
|
||||
ret = toasty_get_result(client->toasty, client->handle, &result);
|
||||
if (ret < 0) TEST_FAIL("get result for delete dir");
|
||||
if (ret == 1) break;
|
||||
if (result.type != TOASTY_RESULT_DELETE_SUCCESS) {
|
||||
TEST_FAIL("delete dir result");
|
||||
}
|
||||
TEST_PASS("Delete directory");
|
||||
toasty_free_result(&result);
|
||||
|
||||
printf("\n========================================\n");
|
||||
printf("All %d tests PASSED!\n", client->tests_passed);
|
||||
printf("========================================\n");
|
||||
QUAKEY_SIGNAL("exit");
|
||||
}
|
||||
|
||||
*timeout = -1;
|
||||
if (pcap < TCP_POLL_CAPACITY)
|
||||
return -1;
|
||||
*pnum = toasty_process_events(client->toasty, ctxs, pdata, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int test_client_free(void *state_)
|
||||
{
|
||||
TestClient *client = state_;
|
||||
|
||||
toasty_disconnect(client->toasty);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif // MAIN_TEST
|
||||
@@ -1,72 +0,0 @@
|
||||
#ifndef TEST_CLIENT_INCLUDED
|
||||
#define TEST_CLIENT_INCLUDED
|
||||
|
||||
#include "ToastyFS.h"
|
||||
|
||||
typedef enum {
|
||||
// Basic file create/write/read test (existing)
|
||||
TEST_CLIENT_STATE_INIT,
|
||||
TEST_CLIENT_STATE_CREATE_FILE_WAIT,
|
||||
TEST_CLIENT_STATE_WRITE_WAIT,
|
||||
TEST_CLIENT_STATE_READ_WAIT,
|
||||
|
||||
// Directory tests
|
||||
TEST_CLIENT_STATE_CREATE_DIR,
|
||||
TEST_CLIENT_STATE_CREATE_DIR_WAIT,
|
||||
TEST_CLIENT_STATE_CREATE_SUBDIR,
|
||||
TEST_CLIENT_STATE_CREATE_SUBDIR_WAIT,
|
||||
TEST_CLIENT_STATE_CREATE_FILE_IN_DIR,
|
||||
TEST_CLIENT_STATE_CREATE_FILE_IN_DIR_WAIT,
|
||||
TEST_CLIENT_STATE_LIST_DIR,
|
||||
TEST_CLIENT_STATE_LIST_DIR_WAIT,
|
||||
TEST_CLIENT_STATE_LIST_ROOT,
|
||||
TEST_CLIENT_STATE_LIST_ROOT_WAIT,
|
||||
|
||||
// Delete tests
|
||||
TEST_CLIENT_STATE_DELETE_FILE,
|
||||
TEST_CLIENT_STATE_DELETE_FILE_WAIT,
|
||||
TEST_CLIENT_STATE_DELETE_DIR,
|
||||
TEST_CLIENT_STATE_DELETE_DIR_WAIT,
|
||||
|
||||
// Write flags tests
|
||||
TEST_CLIENT_STATE_WRITE_CREATE_IF_MISSING,
|
||||
TEST_CLIENT_STATE_WRITE_CREATE_IF_MISSING_WAIT,
|
||||
TEST_CLIENT_STATE_READ_CREATED_FILE,
|
||||
TEST_CLIENT_STATE_READ_CREATED_FILE_WAIT,
|
||||
TEST_CLIENT_STATE_WRITE_TRUNCATE,
|
||||
TEST_CLIENT_STATE_WRITE_TRUNCATE_WAIT,
|
||||
TEST_CLIENT_STATE_READ_TRUNCATED,
|
||||
TEST_CLIENT_STATE_READ_TRUNCATED_WAIT,
|
||||
|
||||
// Offset read/write tests
|
||||
TEST_CLIENT_STATE_WRITE_AT_OFFSET,
|
||||
TEST_CLIENT_STATE_WRITE_AT_OFFSET_WAIT,
|
||||
TEST_CLIENT_STATE_READ_AT_OFFSET,
|
||||
TEST_CLIENT_STATE_READ_AT_OFFSET_WAIT,
|
||||
|
||||
// Test done
|
||||
TEST_CLIENT_STATE_DONE,
|
||||
} TestClientState;
|
||||
|
||||
typedef struct {
|
||||
ToastyFS* toasty;
|
||||
ToastyHandle handle;
|
||||
TestClientState state;
|
||||
int tick;
|
||||
int tests_passed;
|
||||
char buf[1<<10];
|
||||
char buf2[1<<10]; // Secondary buffer for additional reads
|
||||
} TestClient;
|
||||
|
||||
struct pollfd;
|
||||
|
||||
int test_client_init(void *state, int argc, char **argv,
|
||||
void **ctxs, struct pollfd *pdata, int pcap, int *pnum,
|
||||
int *timeout);
|
||||
|
||||
int test_client_tick(void *state, void **ctxs,
|
||||
struct pollfd *pdata, int pcap, int *pnum, int *timeout);
|
||||
|
||||
int test_client_free(void *state);
|
||||
|
||||
#endif // TEST_CLIENT_INCLUDED
|
||||
@@ -1,630 +0,0 @@
|
||||
|
||||
#if defined(MAIN_SIMULATION) || defined(MAIN_TEST)
|
||||
#define QUAKEY_ENABLE_MOCKS
|
||||
#endif
|
||||
|
||||
#include <quakey.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "wal.h"
|
||||
#include "file_tree.h"
|
||||
#include "file_system.h"
|
||||
|
||||
#define WAL_MAGIC 0xcafebebe
|
||||
#define WAL_VERSION 1
|
||||
|
||||
typedef struct {
|
||||
uint32_t magic;
|
||||
uint32_t version;
|
||||
uint64_t reserved;
|
||||
} WALHeader;
|
||||
|
||||
typedef enum {
|
||||
WAL_ENTRY_CREATE,
|
||||
WAL_ENTRY_DELETE,
|
||||
WAL_ENTRY_WRITE,
|
||||
} WALEntryType;
|
||||
|
||||
typedef struct {
|
||||
WALEntryType type;
|
||||
|
||||
// create, delete, write
|
||||
string path;
|
||||
|
||||
// create, write
|
||||
uint64_t chunk_size;
|
||||
|
||||
// delete, write
|
||||
uint64_t expect_gen;
|
||||
|
||||
// create
|
||||
bool is_dir;
|
||||
|
||||
// write
|
||||
uint64_t offset;
|
||||
uint64_t length;
|
||||
uint32_t num_chunks;
|
||||
SHA256 *hashes;
|
||||
|
||||
} WALEntry;
|
||||
|
||||
static int write_exact(Handle handle, char *src, int len)
|
||||
{
|
||||
int copied = 0;
|
||||
while (copied < len) {
|
||||
int ret = file_write(handle, src + copied, len - copied);
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
copied += ret;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
Handle handle;
|
||||
} WriteSnapshotContext;
|
||||
|
||||
static int
|
||||
serialize_callback(char *src, int num, void *data)
|
||||
{
|
||||
WriteSnapshotContext *wsc = data;
|
||||
return write_exact(wsc->handle, src, num);
|
||||
}
|
||||
|
||||
static int write_snapshot(FileTree *file_tree, Handle handle)
|
||||
{
|
||||
WriteSnapshotContext wsc;
|
||||
wsc.handle = handle;
|
||||
if (file_tree_serialize(file_tree, serialize_callback, &wsc) < 0)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
Handle handle;
|
||||
} ReadSnapshotContext;
|
||||
|
||||
static int
|
||||
deserialize_callback(char *dst, int num, void *data)
|
||||
{
|
||||
ReadSnapshotContext *rsc = data;
|
||||
int copied = 0;
|
||||
while (copied < num) {
|
||||
int ret = file_read(rsc->handle, dst + copied, num - copied);
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
if (ret == 0)
|
||||
break;
|
||||
copied += ret;
|
||||
}
|
||||
return copied;
|
||||
}
|
||||
|
||||
static int read_snapshot(FileTree *file_tree, Handle handle)
|
||||
{
|
||||
ReadSnapshotContext rsc;
|
||||
rsc.handle = handle;
|
||||
int num = file_tree_deserialize(file_tree, deserialize_callback, &rsc);
|
||||
if (num < 0)
|
||||
return -1;
|
||||
return num;
|
||||
}
|
||||
|
||||
static int swap_file(WAL *wal)
|
||||
{
|
||||
// Create a temporary file path
|
||||
char temp_path_buf[1<<10];
|
||||
if (wal->file_path.len + 5 > (int) sizeof(temp_path_buf))
|
||||
return -1;
|
||||
|
||||
memcpy(temp_path_buf, wal->file_path.ptr, wal->file_path.len);
|
||||
memcpy(temp_path_buf + wal->file_path.len, ".tmp", 4);
|
||||
temp_path_buf[wal->file_path.len + 4] = '\0';
|
||||
|
||||
string temp_path = { temp_path_buf, wal->file_path.len + 4 };
|
||||
|
||||
// Create and open the temporary file
|
||||
Handle temp_handle;
|
||||
if (file_open(temp_path, &temp_handle) < 0)
|
||||
return -1;
|
||||
|
||||
// Write the WAL header
|
||||
WALHeader header;
|
||||
header.magic = WAL_MAGIC;
|
||||
header.version = WAL_VERSION;
|
||||
header.reserved = 0;
|
||||
|
||||
if (write_exact(temp_handle, (char*) &header, sizeof(header)) < 0) {
|
||||
file_close(temp_handle);
|
||||
remove_file_or_dir(temp_path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Serialize the current file tree to the new file
|
||||
if (write_snapshot(wal->file_tree, temp_handle) < 0) {
|
||||
file_close(temp_handle);
|
||||
remove_file_or_dir(temp_path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Sync the temporary file to ensure it's written to disk
|
||||
if (file_sync(temp_handle) < 0) {
|
||||
file_close(temp_handle);
|
||||
remove_file_or_dir(temp_path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Lock the new file before switching
|
||||
if (file_lock(temp_handle) < 0) {
|
||||
file_close(temp_handle);
|
||||
remove_file_or_dir(temp_path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Unlock and close the old file
|
||||
file_unlock(wal->handle);
|
||||
file_close(wal->handle);
|
||||
|
||||
// Atomically rename the temporary file to replace the old file
|
||||
// On Unix: rename() atomically replaces the destination
|
||||
// On Windows: we use MoveFileEx with MOVEFILE_REPLACE_EXISTING for atomicity
|
||||
#ifdef _WIN32
|
||||
// On Windows, use MoveFileEx for atomic replace
|
||||
char old_path_zt[1<<10];
|
||||
if (wal->file_path.len >= (int) sizeof(old_path_zt)) {
|
||||
file_unlock(temp_handle);
|
||||
file_close(temp_handle);
|
||||
remove_file_or_dir(temp_path);
|
||||
return -1;
|
||||
}
|
||||
memcpy(old_path_zt, wal->file_path.ptr, wal->file_path.len);
|
||||
old_path_zt[wal->file_path.len] = '\0';
|
||||
|
||||
WCHAR old_path_w[MAX_PATH];
|
||||
WCHAR temp_path_w[MAX_PATH];
|
||||
|
||||
if (!MultiByteToWideChar(CP_UTF8, 0, old_path_zt, -1, old_path_w, MAX_PATH) ||
|
||||
!MultiByteToWideChar(CP_UTF8, 0, temp_path_buf, -1, temp_path_w, MAX_PATH)) {
|
||||
file_unlock(temp_handle);
|
||||
file_close(temp_handle);
|
||||
remove_file_or_dir(temp_path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// MOVEFILE_REPLACE_EXISTING allows atomic overwrite
|
||||
if (!MoveFileExW(temp_path_w, old_path_w, MOVEFILE_REPLACE_EXISTING)) {
|
||||
file_unlock(temp_handle);
|
||||
file_close(temp_handle);
|
||||
remove_file_or_dir(temp_path);
|
||||
return -1;
|
||||
}
|
||||
#else
|
||||
// On Unix/Linux, rename() atomically replaces the destination
|
||||
if (rename_file_or_dir(temp_path, wal->file_path) < 0) {
|
||||
file_unlock(temp_handle);
|
||||
file_close(temp_handle);
|
||||
remove_file_or_dir(temp_path);
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Update the WAL to use the new file handle
|
||||
wal->handle = temp_handle;
|
||||
wal->entry_count = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int read_exact(Handle handle, char *dst, int len)
|
||||
{
|
||||
int copied = 0;
|
||||
while (copied < len) {
|
||||
int ret = file_read(handle, dst + copied, len - copied);
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
if (ret == 0)
|
||||
return 0; // EOF
|
||||
copied += ret;
|
||||
}
|
||||
return copied;
|
||||
}
|
||||
|
||||
static int read_u8(Handle handle, uint8_t *value)
|
||||
{
|
||||
return read_exact(handle, (char*) value, sizeof(*value));
|
||||
}
|
||||
|
||||
static int read_u16(Handle handle, uint16_t *value)
|
||||
{
|
||||
return read_exact(handle, (char*) value, sizeof(*value));
|
||||
}
|
||||
|
||||
static int read_u32(Handle handle, uint32_t *value)
|
||||
{
|
||||
return read_exact(handle, (char*) value, sizeof(*value));
|
||||
}
|
||||
|
||||
static int read_u64(Handle handle, uint64_t *value)
|
||||
{
|
||||
return read_exact(handle, (char*) value, sizeof(*value));
|
||||
}
|
||||
|
||||
static int next_entry(Handle handle, WALEntry *entry)
|
||||
{
|
||||
// Initialize pointers to NULL for cleanup on error
|
||||
entry->path.ptr = NULL;
|
||||
entry->hashes = NULL;
|
||||
|
||||
uint8_t type;
|
||||
int ret = read_u8(handle, &type);
|
||||
if (ret == 0)
|
||||
return 0; // EOF
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
|
||||
entry->type = (WALEntryType) type;
|
||||
|
||||
uint16_t path_len;
|
||||
if (read_u16(handle, &path_len) <= 0)
|
||||
return -1;
|
||||
|
||||
// Dynamically allocate path buffer
|
||||
char *path_buffer = malloc(path_len);
|
||||
if (!path_buffer)
|
||||
return -1;
|
||||
|
||||
if (read_exact(handle, path_buffer, path_len) <= 0) {
|
||||
free(path_buffer);
|
||||
return -1;
|
||||
}
|
||||
|
||||
entry->path.ptr = path_buffer;
|
||||
entry->path.len = path_len;
|
||||
|
||||
switch (entry->type) {
|
||||
case WAL_ENTRY_CREATE:
|
||||
{
|
||||
uint8_t is_dir;
|
||||
if (read_u8(handle, &is_dir) <= 0)
|
||||
goto cleanup_error;
|
||||
entry->is_dir = is_dir;
|
||||
|
||||
if (!is_dir) {
|
||||
if (read_u64(handle, &entry->chunk_size) <= 0)
|
||||
goto cleanup_error;
|
||||
} else {
|
||||
entry->chunk_size = 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case WAL_ENTRY_DELETE:
|
||||
{
|
||||
if (read_u64(handle, &entry->expect_gen) <= 0)
|
||||
goto cleanup_error;
|
||||
}
|
||||
break;
|
||||
|
||||
case WAL_ENTRY_WRITE:
|
||||
{
|
||||
if (read_u64(handle, &entry->expect_gen) <= 0)
|
||||
goto cleanup_error;
|
||||
if (read_u64(handle, &entry->offset) <= 0)
|
||||
goto cleanup_error;
|
||||
if (read_u64(handle, &entry->length) <= 0)
|
||||
goto cleanup_error;
|
||||
if (read_u32(handle, &entry->num_chunks) <= 0)
|
||||
goto cleanup_error;
|
||||
|
||||
// Dynamically allocate hash buffers
|
||||
SHA256 *hashes_buffer = malloc(entry->num_chunks * sizeof(SHA256));
|
||||
if (!hashes_buffer) {
|
||||
goto cleanup_error;
|
||||
}
|
||||
|
||||
if (read_exact(handle, (char*) hashes_buffer, entry->num_chunks * sizeof(SHA256)) <= 0) {
|
||||
free(hashes_buffer);
|
||||
goto cleanup_error;
|
||||
}
|
||||
|
||||
entry->hashes = hashes_buffer;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
goto cleanup_error;
|
||||
}
|
||||
|
||||
return 1;
|
||||
|
||||
cleanup_error:
|
||||
if (entry->path.ptr)
|
||||
free((char*) entry->path.ptr);
|
||||
if (entry->hashes)
|
||||
free(entry->hashes);
|
||||
return -1;
|
||||
}
|
||||
|
||||
int wal_open(WAL *wal, FileTree *file_tree, string file_path, int entry_limit)
|
||||
{
|
||||
wal->entry_count = 0;
|
||||
wal->entry_limit = entry_limit;
|
||||
wal->file_tree = file_tree;
|
||||
wal->file_path.ptr = NULL;
|
||||
|
||||
// Copy file_path since the passed string may not have the same lifetime as WAL
|
||||
char *path_copy = malloc(file_path.len);
|
||||
if (!path_copy)
|
||||
return -1;
|
||||
memcpy(path_copy, file_path.ptr, file_path.len);
|
||||
wal->file_path.ptr = path_copy;
|
||||
wal->file_path.len = file_path.len;
|
||||
|
||||
Handle handle;
|
||||
if (file_open(file_path, &handle) < 0)
|
||||
goto error_cleanup_path;
|
||||
|
||||
if (file_lock(handle) < 0) {
|
||||
file_close(handle);
|
||||
goto error_cleanup_path;
|
||||
}
|
||||
|
||||
// Check if the file is empty (newly created) and initialize it
|
||||
size_t size;
|
||||
if (file_size(handle, &size) < 0) {
|
||||
file_close(handle);
|
||||
goto error_cleanup_path;
|
||||
}
|
||||
|
||||
if (size == 0) {
|
||||
// Initialize a new WAL file
|
||||
WALHeader header;
|
||||
header.magic = WAL_MAGIC;
|
||||
header.version = WAL_VERSION;
|
||||
header.reserved = 0;
|
||||
|
||||
if (write_exact(handle, (char*) &header, sizeof(header)) < 0) {
|
||||
file_close(handle);
|
||||
goto error_cleanup_path;
|
||||
}
|
||||
|
||||
if (write_snapshot(file_tree, handle) < 0) {
|
||||
file_close(handle);
|
||||
goto error_cleanup_path;
|
||||
}
|
||||
|
||||
if (file_sync(handle) < 0) {
|
||||
file_close(handle);
|
||||
goto error_cleanup_path;
|
||||
}
|
||||
|
||||
// Reset to beginning after initialization
|
||||
if (file_set_offset(handle, 0) < 0) {
|
||||
file_close(handle);
|
||||
goto error_cleanup_path;
|
||||
}
|
||||
}
|
||||
|
||||
// Read file header
|
||||
// NOTE: For now we don't worry about fixing endianess
|
||||
WALHeader header;
|
||||
for (int copied = 0; copied < (int) sizeof(header); ) {
|
||||
int ret = file_read(handle, (char*) &header + copied, (int) sizeof(header) - copied);
|
||||
if (ret <= 0) {
|
||||
file_close(handle); // TODO: what happens if I close a file without unlocking it?
|
||||
goto error_cleanup_path;
|
||||
}
|
||||
copied += ret;
|
||||
}
|
||||
|
||||
// Validate header fields
|
||||
if (header.magic != WAL_MAGIC) {
|
||||
file_close(handle);
|
||||
goto error_cleanup_path;
|
||||
}
|
||||
if (header.version != WAL_VERSION) {
|
||||
file_close(handle);
|
||||
goto error_cleanup_path;
|
||||
}
|
||||
|
||||
// The read_snapshot function may read more
|
||||
// bytes than necessary from the buffer, so
|
||||
// we need to save our current position to
|
||||
// later restore it to this offset plus what
|
||||
// read_snapshot really consumed.
|
||||
int saved_offset;
|
||||
if (file_get_offset(handle, &saved_offset) < 0) {
|
||||
file_close(handle);
|
||||
goto error_cleanup_path;
|
||||
}
|
||||
|
||||
int num = read_snapshot(file_tree, handle);
|
||||
if (num < 0) {
|
||||
file_close(handle);
|
||||
goto error_cleanup_path;
|
||||
}
|
||||
|
||||
// Now restore the offset to the correct position.
|
||||
if (num > INT_MAX - saved_offset) {
|
||||
file_close(handle);
|
||||
goto error_cleanup_path;
|
||||
}
|
||||
if (file_set_offset(handle, saved_offset + num) < 0) {
|
||||
file_close(handle);
|
||||
goto error_cleanup_path;
|
||||
}
|
||||
|
||||
WALEntry entry;
|
||||
for (;;) {
|
||||
|
||||
int ret = next_entry(handle, &entry);
|
||||
if (ret == 0)
|
||||
break;
|
||||
if (ret < 0) {
|
||||
file_close(handle);
|
||||
goto error_cleanup_path;
|
||||
}
|
||||
assert(ret == 1);
|
||||
|
||||
switch (entry.type) {
|
||||
uint64_t gen;
|
||||
case WAL_ENTRY_CREATE:
|
||||
file_tree_create_entity(file_tree, entry.path, entry.is_dir, entry.chunk_size, &gen);
|
||||
break;
|
||||
case WAL_ENTRY_DELETE:
|
||||
file_tree_delete_entity(file_tree, entry.path, entry.expect_gen);
|
||||
break;
|
||||
case WAL_ENTRY_WRITE:
|
||||
// WAL replay: use false for truncate_after since truncation was already handled
|
||||
file_tree_write(file_tree, entry.path, entry.offset, entry.length, entry.num_chunks, entry.expect_gen, &gen, entry.hashes, NULL, NULL, false);
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE;
|
||||
}
|
||||
|
||||
// Free dynamically allocated fields from next_entry
|
||||
if (entry.path.ptr)
|
||||
free((char*) entry.path.ptr);
|
||||
if (entry.hashes)
|
||||
free(entry.hashes);
|
||||
|
||||
wal->entry_count++;
|
||||
}
|
||||
|
||||
wal->handle = handle;
|
||||
|
||||
if (wal->entry_count >= wal->entry_limit) {
|
||||
if (swap_file(wal) < 0) {
|
||||
goto error_cleanup_path;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
|
||||
error_cleanup_path:
|
||||
free(path_copy);
|
||||
return -1;
|
||||
}
|
||||
|
||||
void wal_close(WAL *wal)
|
||||
{
|
||||
file_unlock(wal->handle);
|
||||
file_close(wal->handle);
|
||||
if (wal->file_path.ptr)
|
||||
free((char*) wal->file_path.ptr);
|
||||
}
|
||||
|
||||
static int write_u8(Handle handle, uint8_t value)
|
||||
{
|
||||
return write_exact(handle, (char*) &value, sizeof(value));
|
||||
}
|
||||
|
||||
static int write_u16(Handle handle, uint16_t value)
|
||||
{
|
||||
return write_exact(handle, (char*) &value, sizeof(value));
|
||||
}
|
||||
|
||||
static int write_u32(Handle handle, uint32_t value)
|
||||
{
|
||||
return write_exact(handle, (char*) &value, sizeof(value));
|
||||
}
|
||||
|
||||
static int write_u64(Handle handle, uint64_t value)
|
||||
{
|
||||
return write_exact(handle, (char*) &value, sizeof(value));
|
||||
}
|
||||
|
||||
static int write_str(Handle handle, string value)
|
||||
{
|
||||
return write_exact(handle, value.ptr, value.len);
|
||||
}
|
||||
|
||||
static int append_begin(WAL *wal)
|
||||
{
|
||||
if (wal->entry_count >= wal->entry_limit) {
|
||||
if (swap_file(wal) < 0)
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int append_end(WAL *wal)
|
||||
{
|
||||
if (file_sync(wal->handle) < 0)
|
||||
return -1;
|
||||
wal->entry_count++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int wal_append_create(WAL *wal, string path, bool is_dir, uint64_t chunk_size)
|
||||
{
|
||||
if (path.len > UINT16_MAX)
|
||||
return -1;
|
||||
|
||||
if (append_begin(wal) < 0)
|
||||
return -1;
|
||||
|
||||
write_u8(wal->handle, WAL_ENTRY_CREATE);
|
||||
write_u16(wal->handle, path.len);
|
||||
write_str(wal->handle, path);
|
||||
write_u8(wal->handle, is_dir);
|
||||
if (!is_dir)
|
||||
write_u64(wal->handle, chunk_size);
|
||||
|
||||
if (append_end(wal) < 0)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int wal_append_delete(WAL *wal, string path, uint64_t expect_gen)
|
||||
{
|
||||
if (path.len > UINT16_MAX)
|
||||
return -1;
|
||||
|
||||
if (append_begin(wal) < 0)
|
||||
return -1;
|
||||
|
||||
// TODO: check for errors
|
||||
write_u8(wal->handle, WAL_ENTRY_DELETE);
|
||||
write_u16(wal->handle, path.len);
|
||||
write_str(wal->handle, path);
|
||||
write_u64(wal->handle, expect_gen);
|
||||
|
||||
if (append_end(wal) < 0)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int wal_append_write(WAL *wal, string path, uint64_t off,
|
||||
uint64_t len, uint32_t num_chunks, uint64_t expect_gen,
|
||||
SHA256 *hashes)
|
||||
{
|
||||
if (path.len > UINT16_MAX)
|
||||
return -1;
|
||||
|
||||
if (append_begin(wal) < 0)
|
||||
return -1;
|
||||
|
||||
if (write_u8(wal->handle, WAL_ENTRY_WRITE) < 0)
|
||||
return -1;
|
||||
if (write_u16(wal->handle, path.len) < 0)
|
||||
return -1;
|
||||
if (write_str(wal->handle, path) < 0)
|
||||
return -1;
|
||||
if (write_u64(wal->handle, expect_gen) < 0)
|
||||
return -1;
|
||||
if (write_u64(wal->handle, off) < 0)
|
||||
return -1;
|
||||
if (write_u64(wal->handle, len) < 0)
|
||||
return -1;
|
||||
if (write_u32(wal->handle, num_chunks) < 0)
|
||||
return -1;
|
||||
if (write_exact(wal->handle, (char*) hashes, num_chunks * sizeof(SHA256)) < 0)
|
||||
return -1;
|
||||
|
||||
if (append_end(wal) < 0)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
#ifndef WAL_INCLUDED
|
||||
#define WAL_INCLUDED
|
||||
|
||||
#include "file_tree.h"
|
||||
#include "file_system.h"
|
||||
|
||||
typedef struct {
|
||||
Handle handle;
|
||||
FileTree *file_tree;
|
||||
string file_path;
|
||||
int entry_count;
|
||||
int entry_limit;
|
||||
} WAL;
|
||||
|
||||
int wal_open(WAL *wal, FileTree *file_tree, string file_path, int entry_limit);
|
||||
|
||||
void wal_close(WAL *wal);
|
||||
|
||||
int wal_append_create(WAL *wal, string path, bool is_dir, uint64_t chunk_size);
|
||||
|
||||
int wal_append_delete(WAL *wal, string path, uint64_t expect_gen);
|
||||
|
||||
int wal_append_write(WAL *wal, string path, uint64_t off,
|
||||
uint64_t len, uint32_t num_chunks, uint64_t expect_gen,
|
||||
SHA256 *hashes);
|
||||
|
||||
#endif // WAL_INCLUDED
|
||||
Reference in New Issue
Block a user