Split project over multiple files
This commit is contained in:
@@ -9,24 +9,22 @@ else
|
||||
EXT = .out
|
||||
endif
|
||||
|
||||
CFILES = $(shell find src -name '*.c')
|
||||
HFILES = $(shell find src -name '*.h')
|
||||
|
||||
.PHONY: all clean
|
||||
|
||||
all: metadata_server$(EXT) chunk_server$(EXT) example$(EXT)
|
||||
all: tinydfs_server$(EXT) example_client$(EXT)
|
||||
|
||||
metadata_server$(EXT): TinyDFS.c TinyDFS.h
|
||||
gcc -o $@ TinyDFS.c -DBUILD_METADATA_SERVER $(CFLAGS) $(LFLAGS)
|
||||
tinydfs_server$(EXT): $(CFILES) $(HFILES)
|
||||
gcc -o $@ $(CFILES) $(CFLAGS) $(LFLAGS) -Iinc
|
||||
|
||||
chunk_server$(EXT): TinyDFS.c TinyDFS.h
|
||||
gcc -o $@ TinyDFS.c -DBUILD_CHUNK_SERVER $(CFLAGS) $(LFLAGS)
|
||||
|
||||
example$(EXT): examples/main.c TinyDFS.c TinyDFS.h
|
||||
gcc -o $@ examples/main.c TinyDFS.c $(CFLAGS) $(LFLAGS) -I.
|
||||
example_client$(EXT): examples/main.c $(CFILES) $(HFILES)
|
||||
gcc -o $@ examples/main.c $(CFILES) $(CFLAGS) $(LFLAGS) -Iinc
|
||||
|
||||
clean:
|
||||
rm \
|
||||
metadata_server.exe \
|
||||
matadata_server.out \
|
||||
chunk_server.exe \
|
||||
chunk_server.out \
|
||||
example.exe \
|
||||
rm \
|
||||
tinydfs_server.exe \
|
||||
tinydfs_server.out \
|
||||
example.exe \
|
||||
example.out
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
- Chunks should not be separated between the main and orphaned folders. The concept of "orphans" or "to be removed" should only be held in-memory. If a chunk server crashes, upon restarting and reconnecting to the master any chunk will be re-marked for removal when speaking to the master.
|
||||
- Implement write on the client
|
||||
- Implement proper operation handles that allow users to wait for specific events
|
||||
- Clean up the read operation for clients
|
||||
- Check that the corner case where read and writes go over the length of the file work correctly
|
||||
- When a client received metadata and starts reading or writing to a chunk server, it should try connecting to all addresses of a chunk servers, not just the first one.
|
||||
- Return the number of bytes read or written in the TinyDFS_Result struct
|
||||
- Make parallel uploads/downloads configurable
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
#ifdef _WIN32
|
||||
#else
|
||||
#include <time.h>
|
||||
#endif
|
||||
|
||||
#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 milliseconds 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 = 1000 * (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 * 1000;
|
||||
|
||||
uint64_t nsec = time.tv_nsec;
|
||||
if (res > UINT64_MAX - nsec)
|
||||
return INVALID_TIME;
|
||||
res += nsec / 1000000;
|
||||
|
||||
return res;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
#ifndef BASIC_INCLUDED
|
||||
#define BASIC_INCLUDED
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
typedef struct {
|
||||
char data[64];
|
||||
} SHA256;
|
||||
|
||||
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);
|
||||
|
||||
#endif // BASIC_INCLUDED
|
||||
@@ -0,0 +1,315 @@
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <stdlib.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.
|
||||
|
||||
static void *mymalloc(ByteQueue *queue, uint32_t len)
|
||||
{
|
||||
(void) queue;
|
||||
return malloc(len);
|
||||
}
|
||||
|
||||
static void myfree(ByteQueue *queue, void *ptr, uint32_t len)
|
||||
{
|
||||
(void) queue;
|
||||
(void) len,
|
||||
free(ptr);
|
||||
}
|
||||
|
||||
// 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)
|
||||
myfree(queue, queue->read_target, queue->read_target_size);
|
||||
queue->read_target = NULL;
|
||||
queue->read_target_size = 0;
|
||||
}
|
||||
|
||||
myfree(queue, queue->data, queue->size);
|
||||
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)
|
||||
myfree(queue, queue->read_target, queue->read_target_size);
|
||||
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 = mymalloc(queue, 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)
|
||||
myfree(queue, queue->data, queue->size);
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
#ifndef BYTE_QUEUE_INCLUDED
|
||||
#define BYTE_QUEUE_INCLUDED
|
||||
|
||||
#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
|
||||
@@ -0,0 +1,942 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "sha256.h"
|
||||
#include "message.h"
|
||||
#include "file_system.h"
|
||||
#include "chunk_server.h"
|
||||
|
||||
static void
|
||||
pending_download_list_init(PendingDownloadList *list)
|
||||
{
|
||||
list->count = 0;
|
||||
list->capacity = 0;
|
||||
list->items = NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
pending_download_list_free(PendingDownloadList *list)
|
||||
{
|
||||
free(list->items);
|
||||
}
|
||||
|
||||
static int
|
||||
pending_download_list_add(PendingDownloadList *list, Address addr, SHA256 hash)
|
||||
{
|
||||
// Avoid duplicates
|
||||
for (int i = 0; i < list->count; i++)
|
||||
if (addr_eql(list->items[i].addr, addr) && !memcmp(&list->items[i].hash, &hash, sizeof(SHA256)))
|
||||
return 0;
|
||||
|
||||
if (list->count == list->capacity) {
|
||||
|
||||
int new_capacity;
|
||||
if (list->capacity == 0) new_capacity = 8;
|
||||
else new_capacity = 2 * list->capacity;
|
||||
|
||||
PendingDownload *new_items = malloc(new_capacity * sizeof(PendingDownload));
|
||||
if (new_items == NULL)
|
||||
return -1;
|
||||
|
||||
if (list->capacity > 0) {
|
||||
memcpy(new_items, list->items, list->count * sizeof(list->items[0]));
|
||||
free(list->items);
|
||||
}
|
||||
|
||||
list->items = new_items;
|
||||
list->capacity = new_capacity;
|
||||
}
|
||||
|
||||
list->items[list->count++] = (PendingDownload) { addr, hash };
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int chunk_store_init(ChunkStore *store, string path)
|
||||
{
|
||||
if (create_dir(path) && errno != EEXIST)
|
||||
return -1;
|
||||
|
||||
if (get_full_path(path, store->path) < 0)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void chunk_store_free(ChunkStore *store)
|
||||
{
|
||||
(void) store;
|
||||
}
|
||||
|
||||
static 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[hash.data[i] >> 4];
|
||||
out[(i << 1) + 1] = table[hash.data[i] & 0xF];
|
||||
}
|
||||
}
|
||||
|
||||
static string hash2path(ChunkStore *store, SHA256 hash, char *out)
|
||||
{
|
||||
strcpy(out, store->path);
|
||||
strcat(out, "/");
|
||||
|
||||
size_t tmp = strlen(out);
|
||||
|
||||
append_hex_as_str(out + tmp, hash);
|
||||
|
||||
out[tmp + 64] = '\0';
|
||||
|
||||
return (string) { out, strlen(out) };
|
||||
}
|
||||
|
||||
static int load_chunk(ChunkStore *store, SHA256 hash, string *data)
|
||||
{
|
||||
char buf[PATH_MAX];
|
||||
string path = hash2path(store, hash, buf);
|
||||
return file_read_all(path, data);
|
||||
}
|
||||
|
||||
static int store_chunk(ChunkStore *store, string data, SHA256 *hash)
|
||||
{
|
||||
sha256(data.ptr, data.len, (uint8_t*) hash->data);
|
||||
char buf[PATH_MAX];
|
||||
string path = hash2path(store, *hash, buf);
|
||||
return file_write_atomic(path, data);
|
||||
}
|
||||
|
||||
static int chunk_store_get(ChunkStore *store, SHA256 hash, string *data)
|
||||
{
|
||||
return load_chunk(store, hash, data);
|
||||
}
|
||||
|
||||
static int chunk_store_add(ChunkStore *store, string data)
|
||||
{
|
||||
SHA256 dummy;
|
||||
return store_chunk(store, data, &dummy);
|
||||
}
|
||||
|
||||
static void chunk_store_remove(ChunkStore *store, SHA256 hash)
|
||||
{
|
||||
char buf[PATH_MAX];
|
||||
string path = hash2path(store, hash, buf);
|
||||
|
||||
remove_file_or_dir(path);
|
||||
}
|
||||
|
||||
static int chunk_store_patch(ChunkStore *store, SHA256 target_chunk,
|
||||
uint64_t patch_off, string patch, SHA256 *new_hash)
|
||||
{
|
||||
string data;
|
||||
int ret = load_chunk(store, target_chunk, &data);
|
||||
if (ret < 0)
|
||||
return -1;
|
||||
|
||||
if (patch_off > SIZE_MAX - patch.len) {
|
||||
free(data.ptr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (patch_off + (size_t) patch.len > (size_t) data.len) {
|
||||
free(data.ptr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
memcpy(data.ptr + patch_off, patch.ptr, patch.len);
|
||||
|
||||
ret = store_chunk(store, data, new_hash);
|
||||
if (ret < 0) {
|
||||
free(data.ptr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
free(data.ptr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int send_error(TCP *tcp, int conn_idx,
|
||||
bool close, uint16_t type, string msg)
|
||||
{
|
||||
MessageWriter writer;
|
||||
|
||||
ByteQueue *output = tcp_output_buffer(tcp, conn_idx);
|
||||
message_writer_init(&writer, output, type);
|
||||
|
||||
uint16_t len = MIN(msg.len, UINT16_MAX);
|
||||
message_write(&writer, &len, sizeof(len));
|
||||
message_write(&writer, msg.ptr, len);
|
||||
if (!message_writer_free(&writer))
|
||||
return -1;
|
||||
if (close)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void start_download_if_necessary(ChunkServer *state)
|
||||
{
|
||||
if (state->pending_download_list.count == 0 || state->downloading)
|
||||
return;
|
||||
|
||||
ByteQueue *output;
|
||||
if (tcp_connect(&state->tcp, state->pending_download_list.items[0].addr, TAG_CHUNK_SERVER, &output) < 0) {
|
||||
// Failed to connect, remove this download from the list and try next time
|
||||
if (state->pending_download_list.count > 1) {
|
||||
memmove(&state->pending_download_list.items[0],
|
||||
&state->pending_download_list.items[1],
|
||||
(state->pending_download_list.count - 1) * sizeof(PendingDownload));
|
||||
}
|
||||
state->pending_download_list.count--;
|
||||
return;
|
||||
}
|
||||
|
||||
state->downloading = true;
|
||||
|
||||
MessageWriter writer;
|
||||
message_writer_init(&writer, output, MESSAGE_TYPE_DOWNLOAD_CHUNK);
|
||||
|
||||
// Write the hash of the chunk to download
|
||||
message_write(&writer, &state->pending_download_list.items[0].hash,
|
||||
sizeof(state->pending_download_list.items[0].hash));
|
||||
|
||||
// Request the entire chunk: offset = 0
|
||||
uint32_t offset = 0;
|
||||
message_write(&writer, &offset, sizeof(offset));
|
||||
|
||||
// Request maximum reasonable chunk size (64MB)
|
||||
uint32_t length = 64 * 1024 * 1024;
|
||||
message_write(&writer, &length, sizeof(length));
|
||||
|
||||
if (!message_writer_free(&writer)) {
|
||||
// Failed to send message, close connection and retry
|
||||
state->downloading = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
process_metadata_server_state_update(ChunkServer *state, int conn_idx, ByteView msg)
|
||||
{
|
||||
uint32_t add_count;
|
||||
uint32_t rem_count;
|
||||
|
||||
BinaryReader reader = { msg.ptr, msg.len, 0 };
|
||||
|
||||
// Read header
|
||||
if (!binary_read(&reader, NULL, sizeof(MessageHeader)))
|
||||
return send_error(&state->tcp, conn_idx, true, MESSAGE_TYPE_STATE_UPDATE_ERROR, S("Invalid message"));
|
||||
|
||||
if (!binary_read(&reader, &add_count, sizeof(add_count)))
|
||||
return send_error(&state->tcp, conn_idx, true, MESSAGE_TYPE_STATE_UPDATE_ERROR, S("Invalid message"));
|
||||
|
||||
if (!binary_read(&reader, &rem_count, sizeof(rem_count)))
|
||||
return send_error(&state->tcp, conn_idx, true, MESSAGE_TYPE_STATE_UPDATE_ERROR, S("Invalid message"));
|
||||
|
||||
SHA256 *add_list = malloc(add_count * sizeof(SHA256));
|
||||
SHA256 *rem_list = malloc(rem_count * sizeof(SHA256));
|
||||
if (add_list == NULL || rem_list == NULL) {
|
||||
free(add_list);
|
||||
free(rem_list);
|
||||
return send_error(&state->tcp, conn_idx, false, MESSAGE_TYPE_STATE_UPDATE_ERROR, S("Out of memory"));
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < add_count; i++) {
|
||||
if (!binary_read(&reader, &add_list[i], sizeof(SHA256))) {
|
||||
free(add_list);
|
||||
free(rem_list);
|
||||
return send_error(&state->tcp, conn_idx, true, MESSAGE_TYPE_STATE_UPDATE_ERROR, S("Invalid message"));
|
||||
}
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < rem_count; i++) {
|
||||
if (!binary_read(&reader, &rem_list[i], sizeof(SHA256))) {
|
||||
free(add_list);
|
||||
free(rem_list);
|
||||
return send_error(&state->tcp, conn_idx, true, MESSAGE_TYPE_STATE_UPDATE_ERROR, S("Invalid message"));
|
||||
}
|
||||
}
|
||||
|
||||
if (binary_read(&reader, NULL, 1)) {
|
||||
free(add_list);
|
||||
free(rem_list);
|
||||
return send_error(&state->tcp, conn_idx, true, MESSAGE_TYPE_STATE_UPDATE_ERROR, S("Invalid message"));
|
||||
}
|
||||
|
||||
// Process the state update:
|
||||
// 1. Move chunks in rem_list from main to orphaned directory (mark for deletion)
|
||||
// 2. Move chunks in add_list from orphaned to main directory (unmark for deletion)
|
||||
// 3. Check that all chunks in add_list exist
|
||||
|
||||
SHA256 *missing_chunks = NULL;
|
||||
uint32_t missing_count = 0;
|
||||
|
||||
// Process add_list: ensure chunks exist and move from orphaned if needed
|
||||
for (uint32_t i = 0; i < add_count; i++) {
|
||||
char main_path[PATH_MAX];
|
||||
char orphaned_path[PATH_MAX];
|
||||
|
||||
// Get paths for main and orphaned locations
|
||||
hash2path(&state->store, add_list[i], main_path);
|
||||
snprintf(orphaned_path, sizeof(orphaned_path), "%s/orphaned/", state->store.path);
|
||||
string orphaned_dir = { orphaned_path, strlen(orphaned_path) };
|
||||
string orphaned_file = hash2path(&state->store, add_list[i], orphaned_path);
|
||||
orphaned_file.ptr = orphaned_path;
|
||||
|
||||
// Build orphaned path properly
|
||||
strcpy(orphaned_path, state->store.path);
|
||||
strcat(orphaned_path, "/orphaned/");
|
||||
size_t tmp = strlen(orphaned_path);
|
||||
append_hex_as_str(orphaned_path + tmp, add_list[i]);
|
||||
orphaned_path[tmp + 64] = '\0';
|
||||
|
||||
// Check if chunk exists in main directory
|
||||
Handle fd;
|
||||
if (file_open((string) { main_path, strlen(main_path) }, &fd) == 0) {
|
||||
file_close(fd);
|
||||
// Chunk is in main directory, nothing to do
|
||||
} else if (file_open((string) { orphaned_path, strlen(orphaned_path) }, &fd) == 0) {
|
||||
file_close(fd);
|
||||
// Chunk is in orphaned directory, move it back to main
|
||||
if (rename_file_or_dir((string) { orphaned_path, strlen(orphaned_path) },
|
||||
(string) { main_path, strlen(main_path) }) < 0) {
|
||||
// Failed to move, treat as missing
|
||||
if (missing_chunks == NULL)
|
||||
missing_chunks = malloc(add_count * sizeof(SHA256));
|
||||
if (missing_chunks)
|
||||
missing_chunks[missing_count++] = add_list[i];
|
||||
}
|
||||
} else {
|
||||
// Chunk is missing in both locations
|
||||
if (missing_chunks == NULL)
|
||||
missing_chunks = malloc(add_count * sizeof(SHA256));
|
||||
if (missing_chunks)
|
||||
missing_chunks[missing_count++] = add_list[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Process rem_list: move chunks from main to orphaned directory
|
||||
// First ensure orphaned directory exists
|
||||
char orphaned_dir_path[PATH_MAX];
|
||||
snprintf(orphaned_dir_path, sizeof(orphaned_dir_path), "%s/orphaned", state->store.path);
|
||||
create_dir((string) { orphaned_dir_path, strlen(orphaned_dir_path) });
|
||||
|
||||
for (uint32_t i = 0; i < rem_count; i++) {
|
||||
char main_path[PATH_MAX];
|
||||
char orphaned_path[PATH_MAX];
|
||||
|
||||
hash2path(&state->store, rem_list[i], main_path);
|
||||
|
||||
strcpy(orphaned_path, state->store.path);
|
||||
strcat(orphaned_path, "/orphaned/");
|
||||
size_t tmp = strlen(orphaned_path);
|
||||
append_hex_as_str(orphaned_path + tmp, rem_list[i]);
|
||||
orphaned_path[tmp + 64] = '\0';
|
||||
|
||||
// Move from main to orphaned (ignore errors, chunk might not exist)
|
||||
rename_file_or_dir((string) { main_path, strlen(main_path) },
|
||||
(string) { orphaned_path, strlen(orphaned_path) });
|
||||
}
|
||||
|
||||
free(add_list);
|
||||
free(rem_list);
|
||||
|
||||
// Send response
|
||||
if (missing_count > 0) {
|
||||
// Send error with list of missing chunks
|
||||
ByteQueue *output = tcp_output_buffer(&state->tcp, conn_idx);
|
||||
MessageWriter writer;
|
||||
message_writer_init(&writer, output, MESSAGE_TYPE_STATE_UPDATE_ERROR);
|
||||
|
||||
uint16_t error_len = 15; // "Missing chunks"
|
||||
message_write(&writer, &error_len, sizeof(error_len));
|
||||
message_write(&writer, "Missing chunks", error_len);
|
||||
|
||||
message_write(&writer, &missing_count, sizeof(missing_count));
|
||||
for (uint32_t i = 0; i < missing_count; i++)
|
||||
message_write(&writer, &missing_chunks[i], sizeof(SHA256));
|
||||
|
||||
free(missing_chunks);
|
||||
|
||||
if (!message_writer_free(&writer))
|
||||
return -1;
|
||||
} else {
|
||||
// Send success
|
||||
ByteQueue *output = tcp_output_buffer(&state->tcp, conn_idx);
|
||||
MessageWriter writer;
|
||||
message_writer_init(&writer, output, MESSAGE_TYPE_STATE_UPDATE_SUCCESS);
|
||||
|
||||
if (!message_writer_free(&writer))
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
process_metadata_server_download_locations(ChunkServer *state, int conn_idx, ByteView msg)
|
||||
{
|
||||
(void) conn_idx;
|
||||
|
||||
// The metadata server wants us to download chunks from other chunk servers
|
||||
|
||||
BinaryReader reader = { msg.ptr, msg.len, 0 };
|
||||
|
||||
// Read header
|
||||
if (!binary_read(&reader, NULL, sizeof(MessageHeader)))
|
||||
return -1;
|
||||
|
||||
// The message layout is this:
|
||||
//
|
||||
// struct IPv4Pair {
|
||||
// IPv4 addr;
|
||||
// uint16_t port;
|
||||
// }
|
||||
//
|
||||
// struct IPv6Pair {
|
||||
// IPv6 addr;
|
||||
// uint16_t port;
|
||||
// }
|
||||
//
|
||||
// struct AddressList {
|
||||
// uint8_t num_ipv4;
|
||||
// uint8_t num_ipv6;
|
||||
// IPv4Pair ipv4[num_ipv4];
|
||||
// IPv6Pair ipv6[num_ipv6];
|
||||
// }
|
||||
//
|
||||
// struct Group {
|
||||
// AddressList address_list;
|
||||
// uint32_t num_hashes;
|
||||
// SHA256 hashes[num_hashes];
|
||||
// }
|
||||
//
|
||||
// struct Message {
|
||||
// uint16_t num_groups;
|
||||
// Group groups[num_groups]
|
||||
// }
|
||||
|
||||
uint16_t num_groups;
|
||||
if (binary_read(&reader, &num_groups, sizeof(num_groups)))
|
||||
return -1;
|
||||
|
||||
for (uint16_t i = 0; i < num_groups; i++) {
|
||||
|
||||
uint8_t num_ipv4;
|
||||
if (binary_read(&reader, &num_ipv4, sizeof(num_ipv4)))
|
||||
return -1;
|
||||
|
||||
uint8_t num_ipv6;
|
||||
if (binary_read(&reader, &num_ipv6, sizeof(num_ipv6)))
|
||||
return -1;
|
||||
|
||||
IPv4 ipv4[UINT8_MAX];
|
||||
IPv6 ipv6[UINT8_MAX];
|
||||
uint8_t ipv4_port[UINT8_MAX];
|
||||
uint16_t ipv6_port[UINT8_MAX];
|
||||
|
||||
for (uint8_t j = 0; j < num_ipv4; j++) {
|
||||
if (binary_read(&reader, &ipv4[i], sizeof(ipv4[i])))
|
||||
return -1;
|
||||
if (binary_read(&reader, &ipv4_port[i], sizeof(ipv4_port[i])))
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (uint8_t j = 0; j < num_ipv6; j++) {
|
||||
if (binary_read(&reader, &ipv6[i], sizeof(ipv6[i])))
|
||||
return -1;
|
||||
if (binary_read(&reader, &ipv6_port[i], sizeof(ipv6_port[i])))
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint32_t num_hashes;
|
||||
if (binary_read(&reader, &num_hashes, sizeof(num_hashes)))
|
||||
return -1;
|
||||
|
||||
for (uint32_t j = 0; j < num_hashes; j++) {
|
||||
|
||||
SHA256 hash;
|
||||
if (binary_read(&reader, &hash, sizeof(hash)))
|
||||
return -1;
|
||||
|
||||
for (uint8_t k = 0; k < num_ipv4; k++)
|
||||
pending_download_list_add(
|
||||
&state->pending_download_list,
|
||||
(Address) { .is_ipv4=true, .ipv4=ipv4[k], .port=ipv4_port[i] },
|
||||
hash
|
||||
);
|
||||
|
||||
for (uint8_t k = 0; k < num_ipv6; k++)
|
||||
pending_download_list_add(
|
||||
&state->pending_download_list,
|
||||
(Address) { .is_ipv4=false, .ipv6=ipv6[k], .port=ipv6_port[i] },
|
||||
hash
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (binary_read(&reader, NULL, 1))
|
||||
return -1;
|
||||
|
||||
start_download_if_necessary(state);
|
||||
|
||||
// There is no need to respond here
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
process_metadata_server_message(ChunkServer *state, int conn_idx, uint16_t type, ByteView msg)
|
||||
{
|
||||
switch (type) {
|
||||
|
||||
case MESSAGE_TYPE_STATE_UPDATE:
|
||||
return process_metadata_server_state_update(state, conn_idx, msg);
|
||||
|
||||
case MESSAGE_TYPE_DOWNLOAD_LOCATIONS:
|
||||
return process_metadata_server_download_locations(state, conn_idx, msg);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int
|
||||
process_chunk_server_download_error(ChunkServer *state, int conn_idx, ByteView msg)
|
||||
{
|
||||
(void) msg;
|
||||
(void) conn_idx;
|
||||
|
||||
// Download failed, mark as not downloading and remove the failed item
|
||||
state->downloading = false;
|
||||
|
||||
if (state->pending_download_list.count > 0) {
|
||||
// Remove the first item (the one that failed)
|
||||
if (state->pending_download_list.count > 1) {
|
||||
memmove(&state->pending_download_list.items[0],
|
||||
&state->pending_download_list.items[1],
|
||||
(state->pending_download_list.count - 1) * sizeof(PendingDownload));
|
||||
}
|
||||
state->pending_download_list.count--;
|
||||
}
|
||||
|
||||
// Try next download if any pending
|
||||
start_download_if_necessary(state);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
process_chunk_server_download_success(ChunkServer *state, int conn_idx, ByteView msg)
|
||||
{
|
||||
(void) conn_idx;
|
||||
|
||||
BinaryReader reader = { msg.ptr, msg.len, 0 };
|
||||
|
||||
// Read header
|
||||
if (!binary_read(&reader, NULL, sizeof(MessageHeader)))
|
||||
return -1;
|
||||
|
||||
// Read data length
|
||||
uint32_t data_len;
|
||||
if (!binary_read(&reader, &data_len, sizeof(data_len)))
|
||||
return -1;
|
||||
|
||||
// Read the chunk data
|
||||
if (reader.cur + data_len > reader.len)
|
||||
return -1;
|
||||
|
||||
string data = { reader.src + reader.cur, data_len };
|
||||
|
||||
// Store the downloaded chunk
|
||||
if (chunk_store_add(&state->store, data) < 0) {
|
||||
// Failed to store, treat as error
|
||||
state->downloading = false;
|
||||
if (state->pending_download_list.count > 0) {
|
||||
if (state->pending_download_list.count > 1) {
|
||||
memmove(&state->pending_download_list.items[0],
|
||||
&state->pending_download_list.items[1],
|
||||
(state->pending_download_list.count - 1) * sizeof(PendingDownload));
|
||||
}
|
||||
state->pending_download_list.count--;
|
||||
}
|
||||
start_download_if_necessary(state);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Download succeeded, mark as not downloading and remove the completed item
|
||||
state->downloading = false;
|
||||
|
||||
if (state->pending_download_list.count > 0) {
|
||||
// Remove the first item (the one that succeeded)
|
||||
if (state->pending_download_list.count > 1) {
|
||||
memmove(&state->pending_download_list.items[0],
|
||||
&state->pending_download_list.items[1],
|
||||
(state->pending_download_list.count - 1) * sizeof(PendingDownload));
|
||||
}
|
||||
state->pending_download_list.count--;
|
||||
}
|
||||
|
||||
// Try next download if any pending
|
||||
start_download_if_necessary(state);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
process_chunk_server_message(ChunkServer *state, int conn_idx, uint16_t msg_type, ByteView msg)
|
||||
{
|
||||
switch (msg_type) {
|
||||
|
||||
case MESSAGE_TYPE_DOWNLOAD_CHUNK_ERROR:
|
||||
return process_chunk_server_download_error(state, conn_idx, msg);
|
||||
|
||||
case MESSAGE_TYPE_DOWNLOAD_CHUNK_SUCCESS:
|
||||
return process_chunk_server_download_success(state, conn_idx, msg);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int
|
||||
process_client_create_chunk(ChunkServer *state, int conn_idx, ByteView msg)
|
||||
{
|
||||
BinaryReader reader = { msg.ptr, msg.len, 0 };
|
||||
|
||||
// Read header
|
||||
if (!binary_read(&reader, NULL, sizeof(MessageHeader)))
|
||||
return send_error(&state->tcp, conn_idx, true, MESSAGE_TYPE_CREATE_CHUNK_ERROR, S("Invalid message"));
|
||||
|
||||
uint32_t chunk_size;
|
||||
if (!binary_read(&reader, &chunk_size, sizeof(chunk_size)))
|
||||
return send_error(&state->tcp, conn_idx, true, MESSAGE_TYPE_CREATE_CHUNK_ERROR, S("Invalid message"));
|
||||
|
||||
uint32_t target_off;
|
||||
if (!binary_read(&reader, &target_off, sizeof(target_off)))
|
||||
return send_error(&state->tcp, conn_idx, true, MESSAGE_TYPE_CREATE_CHUNK_ERROR, S("Invalid message"));
|
||||
|
||||
uint32_t target_len;
|
||||
if (!binary_read(&reader, &target_len, sizeof(target_len)))
|
||||
return send_error(&state->tcp, conn_idx, true, MESSAGE_TYPE_CREATE_CHUNK_ERROR, S("Invalid message"));
|
||||
|
||||
string data = { reader.src + reader.cur, target_len };
|
||||
if (!binary_read(&reader, NULL, target_len))
|
||||
return send_error(&state->tcp, conn_idx, true, MESSAGE_TYPE_CREATE_CHUNK_ERROR, S("Invalid message"));
|
||||
|
||||
// Check that there are no more bytes to read
|
||||
if (binary_read(&reader, NULL, 1))
|
||||
return send_error(&state->tcp, conn_idx, true, MESSAGE_TYPE_CREATE_CHUNK_ERROR, S("Invalid message"));
|
||||
|
||||
char *mem = malloc(chunk_size);
|
||||
if (mem == NULL)
|
||||
return send_error(&state->tcp, conn_idx, false, MESSAGE_TYPE_CREATE_CHUNK_ERROR, S("Out of memory"));
|
||||
|
||||
assert(target_off + data.len <= chunk_size);
|
||||
|
||||
memset(mem, 0, chunk_size);
|
||||
memcpy(mem + target_off, data.ptr, data.len);
|
||||
|
||||
SHA256 new_hash;
|
||||
sha256(mem, chunk_size, (uint8_t*) new_hash.data);
|
||||
|
||||
int ret = chunk_store_add(&state->store, (string) { mem, chunk_size });
|
||||
|
||||
free(mem);
|
||||
|
||||
if (ret < 0)
|
||||
return send_error(&state->tcp, conn_idx, false, MESSAGE_TYPE_CREATE_CHUNK_ERROR, S("I/O error"));
|
||||
|
||||
MessageWriter writer;
|
||||
|
||||
ByteQueue *output = tcp_output_buffer(&state->tcp, conn_idx);
|
||||
message_writer_init(&writer, output, MESSAGE_TYPE_CREATE_CHUNK_SUCCESS);
|
||||
|
||||
message_write(&writer, &new_hash, sizeof(new_hash));
|
||||
|
||||
if (!message_writer_free(&writer))
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
process_client_upload_chunk(ChunkServer *state, int conn_idx, ByteView msg)
|
||||
{
|
||||
BinaryReader reader = { msg.ptr, msg.len, 0 };
|
||||
|
||||
// Read header
|
||||
if (!binary_read(&reader, NULL, sizeof(MessageHeader)))
|
||||
return send_error(&state->tcp, conn_idx, true, MESSAGE_TYPE_UPLOAD_CHUNK_ERROR, S("Invalid message"));
|
||||
|
||||
SHA256 target_hash;
|
||||
if (!binary_read(&reader, &target_hash, sizeof(target_hash)))
|
||||
return send_error(&state->tcp, conn_idx, true, MESSAGE_TYPE_UPLOAD_CHUNK_ERROR, S("Invalid message"));
|
||||
|
||||
uint32_t target_off;
|
||||
if (!binary_read(&reader, &target_off, sizeof(target_off)))
|
||||
return send_error(&state->tcp, conn_idx, true, MESSAGE_TYPE_UPLOAD_CHUNK_ERROR, S("Invalid message"));
|
||||
|
||||
uint32_t data_len;
|
||||
if (!binary_read(&reader, &data_len, sizeof(data_len)))
|
||||
return send_error(&state->tcp, conn_idx, true, MESSAGE_TYPE_UPLOAD_CHUNK_ERROR, S("Invalid message"));
|
||||
|
||||
string data = { reader.src + reader.cur, data_len };
|
||||
|
||||
// Check that there are no more bytes to read
|
||||
if (binary_read(&reader, NULL, 1))
|
||||
return send_error(&state->tcp, conn_idx, true, MESSAGE_TYPE_UPLOAD_CHUNK_ERROR, S("Invalid message"));
|
||||
|
||||
SHA256 new_hash;
|
||||
int ret = chunk_store_patch(&state->store, target_hash, target_off, data, &new_hash);
|
||||
|
||||
if (ret < 0)
|
||||
return send_error(&state->tcp, conn_idx, false, MESSAGE_TYPE_UPLOAD_CHUNK_ERROR, S("I/O error"));
|
||||
|
||||
MessageWriter writer;
|
||||
|
||||
ByteQueue *output = tcp_output_buffer(&state->tcp, conn_idx);
|
||||
message_writer_init(&writer, output, MESSAGE_TYPE_UPLOAD_CHUNK_SUCCESS);
|
||||
|
||||
if (!message_writer_free(&writer))
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
process_client_download_chunk(ChunkServer *state, int conn_idx, ByteView msg)
|
||||
{
|
||||
BinaryReader reader = { msg.ptr, msg.len, 0 };
|
||||
|
||||
// Read header
|
||||
if (!binary_read(&reader, NULL, sizeof(MessageHeader)))
|
||||
return send_error(&state->tcp, conn_idx, true, MESSAGE_TYPE_DOWNLOAD_CHUNK_ERROR, S("Invalid message"));
|
||||
|
||||
SHA256 target_hash;
|
||||
if (!binary_read(&reader, &target_hash, sizeof(target_hash)))
|
||||
return send_error(&state->tcp, conn_idx, true, MESSAGE_TYPE_DOWNLOAD_CHUNK_ERROR, S("Invalid message"));
|
||||
|
||||
uint32_t target_off;
|
||||
if (!binary_read(&reader, &target_off, sizeof(target_off)))
|
||||
return send_error(&state->tcp, conn_idx, true, MESSAGE_TYPE_DOWNLOAD_CHUNK_ERROR, S("Invalid message"));
|
||||
|
||||
uint32_t target_len;
|
||||
if (!binary_read(&reader, &target_len, sizeof(target_len)))
|
||||
return send_error(&state->tcp, conn_idx, true, MESSAGE_TYPE_DOWNLOAD_CHUNK_ERROR, S("Invalid message"));
|
||||
|
||||
// Check that there are no more bytes to read
|
||||
if (binary_read(&reader, NULL, 1))
|
||||
return send_error(&state->tcp, conn_idx, true, MESSAGE_TYPE_DOWNLOAD_CHUNK_ERROR, S("Invalid message"));
|
||||
|
||||
string data;
|
||||
int ret = chunk_store_get(&state->store, target_hash, &data);
|
||||
|
||||
if (ret < 0)
|
||||
return send_error(&state->tcp, conn_idx, false, MESSAGE_TYPE_DOWNLOAD_CHUNK_ERROR, S("I/O error"));
|
||||
|
||||
if (target_off >= (size_t) data.len || target_len > (size_t) data.len - target_off) {
|
||||
free(data.ptr);
|
||||
return send_error(&state->tcp, conn_idx, false, MESSAGE_TYPE_DOWNLOAD_CHUNK_ERROR, S("Invalid range"));
|
||||
}
|
||||
string slice = { data.ptr + target_off, target_len };
|
||||
|
||||
MessageWriter writer;
|
||||
|
||||
ByteQueue *output = tcp_output_buffer(&state->tcp, conn_idx);
|
||||
message_writer_init(&writer, output, MESSAGE_TYPE_DOWNLOAD_CHUNK_SUCCESS);
|
||||
|
||||
message_write(&writer, &target_len, sizeof(target_len));
|
||||
|
||||
message_write(&writer, slice.ptr, slice.len);
|
||||
|
||||
free(data.ptr);
|
||||
|
||||
if (!message_writer_free(&writer))
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
process_client_message(ChunkServer *state, int conn_idx, uint16_t type, ByteView msg)
|
||||
{
|
||||
switch (type) {
|
||||
case MESSAGE_TYPE_CREATE_CHUNK: return process_client_create_chunk(state, conn_idx, msg);
|
||||
case MESSAGE_TYPE_UPLOAD_CHUNK: return process_client_upload_chunk(state, conn_idx, msg);
|
||||
case MESSAGE_TYPE_DOWNLOAD_CHUNK: return process_client_download_chunk(state, conn_idx, msg);
|
||||
default:break;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int chunk_server_init(ChunkServer *state, int argc, char **argv)
|
||||
{
|
||||
(void) argc;
|
||||
(void) argv;
|
||||
|
||||
char addr[] = "127.0.0.1";
|
||||
uint16_t port = 8080;
|
||||
string path = S("chunk_server_data_0/");
|
||||
|
||||
char metadata_server_addr[] = "127.0.0.1";
|
||||
uint16_t metadata_server_port = 8081;
|
||||
|
||||
tcp_context_init(&state->tcp);
|
||||
|
||||
int ret = tcp_listen(&state->tcp, addr, port);
|
||||
if (ret < 0) {
|
||||
tcp_context_free(&state->tcp);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = chunk_store_init(&state->store, path);
|
||||
if (ret < 0) {
|
||||
tcp_context_free(&state->tcp);
|
||||
return -1;
|
||||
}
|
||||
|
||||
state->downloading = false;
|
||||
pending_download_list_init(&state->pending_download_list);
|
||||
|
||||
// Initialize metadata server address
|
||||
// // TODO: This should also support IPv6
|
||||
state->metadata_server_addr.is_ipv4 = true;
|
||||
if (inet_pton(AF_INET, metadata_server_addr, &state->metadata_server_addr.ipv4) != 1) {
|
||||
tcp_context_free(&state->tcp);
|
||||
chunk_store_free(&state->store);
|
||||
return -1;
|
||||
}
|
||||
state->metadata_server_addr.port = metadata_server_port;
|
||||
|
||||
state->metadata_server_disconnect_time = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int chunk_server_free(ChunkServer *state)
|
||||
{
|
||||
pending_download_list_free(&state->pending_download_list);
|
||||
chunk_store_free(&state->store);
|
||||
tcp_context_free(&state->tcp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int chunk_server_step(ChunkServer *state)
|
||||
{
|
||||
Event events[MAX_CONNS+1];
|
||||
int num_events = tcp_process_events(&state->tcp, events);
|
||||
|
||||
Time current_time = get_current_time();
|
||||
if (current_time == INVALID_TIME)
|
||||
return -1;
|
||||
|
||||
for (int i = 0; i < num_events; i++) {
|
||||
int conn_idx = events[i].conn_idx;
|
||||
switch (events[i].type) {
|
||||
|
||||
case EVENT_CONNECT:
|
||||
if (tcp_get_tag(&state->tcp, conn_idx) == TAG_METADATA_SERVER)
|
||||
state->metadata_server_disconnect_time = 0;
|
||||
break;
|
||||
|
||||
case EVENT_DISCONNECT:
|
||||
switch (tcp_get_tag(&state->tcp, conn_idx)) {
|
||||
case TAG_METADATA_SERVER:
|
||||
state->metadata_server_disconnect_time = current_time;
|
||||
break;
|
||||
|
||||
case TAG_CHUNK_SERVER:
|
||||
// Connection to chunk server disconnected during download
|
||||
if (state->downloading) {
|
||||
// Mark as not downloading and retry
|
||||
state->downloading = false;
|
||||
// The current download item will be retried on next call
|
||||
// to start_download_if_necessary
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case EVENT_MESSAGE:
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
switch (tcp_get_tag(&state->tcp, conn_idx)) {
|
||||
case TAG_METADATA_SERVER:
|
||||
ret = process_metadata_server_message(state, conn_idx, msg_type, msg);
|
||||
break;
|
||||
|
||||
case TAG_CHUNK_SERVER:
|
||||
ret = process_chunk_server_message(state, conn_idx, msg_type, msg);
|
||||
break;
|
||||
|
||||
default:
|
||||
ret = process_client_message(state, conn_idx, msg_type, msg);
|
||||
break;
|
||||
}
|
||||
|
||||
if (ret < 0) {
|
||||
tcp_close(&state->tcp, conn_idx);
|
||||
break;
|
||||
}
|
||||
|
||||
tcp_consume_message(&state->tcp, conn_idx);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: periodically look for chunks that have their hashes messed up and delete them
|
||||
|
||||
// TODO: periodically start downloads if some are pending and weren't started yet
|
||||
// start_download_if_necessary(state);
|
||||
|
||||
if (state->metadata_server_disconnect_time > 0 && current_time - state->metadata_server_disconnect_time > CHUNK_SERVER_RECONNECT_TIME) {
|
||||
ByteQueue *output;
|
||||
if (tcp_connect(&state->tcp, state->metadata_server_addr, TAG_METADATA_SERVER, &output) < 0)
|
||||
state->metadata_server_disconnect_time = current_time;
|
||||
else {
|
||||
state->metadata_server_disconnect_time = 0;
|
||||
|
||||
// Send AUTH message to authenticate with metadata server
|
||||
MessageWriter writer;
|
||||
message_writer_init(&writer, output, MESSAGE_TYPE_AUTH);
|
||||
|
||||
// Send our listening address(es)
|
||||
// For now, we only support IPv4 (as noted in program_init)
|
||||
uint32_t num_ipv4 = 1;
|
||||
message_write(&writer, &num_ipv4, sizeof(num_ipv4));
|
||||
|
||||
// Write our IPv4 address and port
|
||||
IPv4 our_ipv4;
|
||||
if (inet_pton(AF_INET, "127.0.0.1", &our_ipv4) == 1) {
|
||||
message_write(&writer, &our_ipv4, sizeof(our_ipv4));
|
||||
uint16_t our_port = 8080; // From program_init
|
||||
message_write(&writer, &our_port, sizeof(our_port));
|
||||
} else {
|
||||
// Failed to parse our address, send 0 IPv4s
|
||||
num_ipv4 = 0;
|
||||
// We already wrote 1, this is an error case
|
||||
// For now, continue with the bad data
|
||||
}
|
||||
|
||||
// No IPv6 addresses for now
|
||||
uint32_t num_ipv6 = 0;
|
||||
message_write(&writer, &num_ipv6, sizeof(num_ipv6));
|
||||
|
||||
if (!message_writer_free(&writer)) {
|
||||
// Failed to send AUTH, will retry on next reconnect
|
||||
state->metadata_server_disconnect_time = current_time;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
#ifndef CHUNK_SERVER_INCLUDED
|
||||
#define CHUNK_SERVER_INCLUDED
|
||||
|
||||
#include <limits.h>
|
||||
|
||||
#include "tcp.h"
|
||||
|
||||
#define TAG_METADATA_SERVER 1
|
||||
#define TAG_CHUNK_SERVER 2
|
||||
|
||||
#define CHUNK_SERVER_RECONNECT_TIME 10000
|
||||
|
||||
typedef struct {
|
||||
char path[PATH_MAX];
|
||||
} ChunkStore;
|
||||
|
||||
typedef struct {
|
||||
Address addr;
|
||||
SHA256 hash;
|
||||
} PendingDownload;
|
||||
|
||||
typedef struct {
|
||||
int count;
|
||||
int capacity;
|
||||
PendingDownload *items;
|
||||
} PendingDownloadList;
|
||||
|
||||
typedef struct {
|
||||
Address metadata_server_addr;
|
||||
Time metadata_server_disconnect_time;
|
||||
TCP tcp;
|
||||
ChunkStore store;
|
||||
|
||||
bool downloading;
|
||||
PendingDownloadList pending_download_list;
|
||||
} ChunkServer;
|
||||
|
||||
int chunk_server_init(ChunkServer *state, int argc, char **argv);
|
||||
int chunk_server_free(ChunkServer *state);
|
||||
int chunk_server_step(ChunkServer *state);
|
||||
|
||||
#endif // CHUNK_SERVER_INCLUDED
|
||||
+1011
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,363 @@
|
||||
#include <stdio.h>
|
||||
#include <limits.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
|
||||
#ifdef __linux__
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/file.h>
|
||||
#include <sys/stat.h>
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#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_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
|
||||
}
|
||||
|
||||
// TODO: test this
|
||||
static string parent_path(string path)
|
||||
{
|
||||
if (path.len > 0 && path.ptr[path.len-1] == '/')
|
||||
path.len--;
|
||||
|
||||
if (path.len == 0)
|
||||
return S("");
|
||||
|
||||
while (path.len > 0 && path.ptr[path.len-1] != '/')
|
||||
path.len--;
|
||||
|
||||
if (path.len > 0)
|
||||
path.len--;
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
static int write_bytes(int fd, string data)
|
||||
{
|
||||
size_t written = 0;
|
||||
while (written < (size_t) data.len) {
|
||||
int ret = write(fd, data.ptr + written, data.len - written);
|
||||
if (ret < 0) {
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
return -1;
|
||||
}
|
||||
written += (size_t) ret;
|
||||
}
|
||||
assert((size_t) data.len == written);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int file_write_atomic(string path, string content)
|
||||
{
|
||||
string parent = parent_path(path);
|
||||
|
||||
char pattern[] = "/tmp_XXXXXXXX";
|
||||
|
||||
char tmp_path[PATH_MAX];
|
||||
if (parent.len + strlen(pattern) >= (int) sizeof(tmp_path))
|
||||
return -1;
|
||||
memcpy(tmp_path, parent.ptr, parent.len);
|
||||
memcpy(tmp_path + parent.len, pattern, strlen(pattern));
|
||||
tmp_path[parent.len + strlen(pattern)] = '\0';
|
||||
|
||||
int fd = mkstemp(tmp_path);
|
||||
if (fd < 0)
|
||||
return -1;
|
||||
|
||||
if (write_bytes(fd, content) < 0) {
|
||||
close(fd);
|
||||
remove(tmp_path);
|
||||
return -1;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
if (_commit(fd)) {
|
||||
close(fd);
|
||||
remove(tmp_path);
|
||||
return -1;
|
||||
}
|
||||
#else
|
||||
if (fsync(fd)) {
|
||||
close(fd);
|
||||
remove(tmp_path);
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
close(fd);
|
||||
|
||||
if (rename_file_or_dir((string) { tmp_path, strlen(tmp_path) }, path)) {
|
||||
remove(tmp_path);
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
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) {
|
||||
file_close(fd);
|
||||
return -1;
|
||||
}
|
||||
copied += ret;
|
||||
}
|
||||
|
||||
*data = (string) { dst, len };
|
||||
file_close(fd);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#ifndef FILE_SYSTEM_INCLUDED
|
||||
#define FILE_SYSTEM_INCLUDED
|
||||
|
||||
#include "basic.h"
|
||||
|
||||
typedef struct {
|
||||
uint64_t data;
|
||||
} Handle;
|
||||
|
||||
int file_open(string path, Handle *fd);
|
||||
void file_close(Handle fd);
|
||||
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);
|
||||
|
||||
#endif // FILE_SYSTEM_INCLUDED
|
||||
+455
@@ -0,0 +1,455 @@
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.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 void dir_remove(Dir *d, int idx)
|
||||
{
|
||||
d->children[idx] = d->children[--d->num_children];
|
||||
}
|
||||
|
||||
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->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)
|
||||
{
|
||||
if (name_len >= (int) sizeof(e->name))
|
||||
return -1;
|
||||
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)
|
||||
{
|
||||
int ret = entity_init(&ft->root, "", 0, true, 0);
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
return d->num_children;
|
||||
}
|
||||
|
||||
int file_tree_create_entity(FileTree *ft, string path,
|
||||
bool is_dir, uint64_t chunk_size)
|
||||
{
|
||||
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);
|
||||
if (ret < 0)
|
||||
// Invalid name for the new file
|
||||
return FILETREE_BADPATH;
|
||||
|
||||
d->num_children++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int file_tree_delete_entity(FileTree *ft, string path)
|
||||
{
|
||||
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)
|
||||
return FILETREE_NOENT;
|
||||
|
||||
dir_remove(&e->d, i);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int file_tree_write(FileTree *ft, string path,
|
||||
uint64_t off, uint64_t len, SHA256 *prev_hashes,
|
||||
SHA256 *hashes, SHA256 *removed_hashes, int *num_removed)
|
||||
{
|
||||
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;
|
||||
|
||||
uint64_t first_chunk_index = off / f->chunk_size;
|
||||
uint64_t last_chunk_index = (off + len - 1) / f->chunk_size;
|
||||
|
||||
if (last_chunk_index >= 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);
|
||||
free(f->chunks);
|
||||
}
|
||||
f->chunks = new_chunks;
|
||||
f->num_chunks = last_chunk_index+1;
|
||||
for (uint64_t i = f->num_chunks; i < last_chunk_index+1; i++)
|
||||
memset(&f->chunks[i], 0, sizeof(SHA256));
|
||||
}
|
||||
|
||||
// Verify prev_hashes match
|
||||
for (uint64_t i = first_chunk_index; i <= last_chunk_index; i++)
|
||||
if (memcmp(&f->chunks[i], &prev_hashes[i - first_chunk_index], sizeof(SHA256)))
|
||||
return -1;
|
||||
|
||||
// Update chunks
|
||||
for (uint64_t i = first_chunk_index; i <= last_chunk_index; i++)
|
||||
f->chunks[i] = hashes[i - first_chunk_index];
|
||||
|
||||
// Now check which old hashes are no longer used anywhere in the tree
|
||||
*num_removed = 0;
|
||||
for (uint64_t i = first_chunk_index; i <= last_chunk_index; i++) {
|
||||
SHA256 old_hash = prev_hashes[i - first_chunk_index];
|
||||
|
||||
// Skip zero hashes
|
||||
bool is_zero = true;
|
||||
for (int j = 0; j < (int) sizeof(SHA256); j++) {
|
||||
if (old_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, old_hash)) {
|
||||
// Not used - add to removed list
|
||||
if (removed_hashes)
|
||||
removed_hashes[*num_removed] = old_hash;
|
||||
(*num_removed)++;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define ZERO_HASH ((SHA256) { .data={0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } })
|
||||
|
||||
int file_tree_read(FileTree *ft, string path,
|
||||
uint64_t off, uint64_t len, uint64_t *chunk_size,
|
||||
SHA256 *hashes, int max_hashes)
|
||||
{
|
||||
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;
|
||||
|
||||
File *f = &e->f;
|
||||
|
||||
if (len == 0)
|
||||
return 0;
|
||||
|
||||
*chunk_size = f->chunk_size;
|
||||
|
||||
uint64_t first_chunk_index = off / f->chunk_size;
|
||||
uint64_t last_chunk_index = (off + len - 1) / f->chunk_size;
|
||||
|
||||
int num_hashes = 0;
|
||||
for (uint32_t i = first_chunk_index; i <= last_chunk_index; i++) {
|
||||
|
||||
SHA256 hash;
|
||||
if (i >= f->num_chunks)
|
||||
hash = ZERO_HASH;
|
||||
else
|
||||
hash = f->chunks[i];
|
||||
|
||||
if (num_hashes < max_hashes)
|
||||
hashes[num_hashes] = hash;
|
||||
num_hashes++;
|
||||
}
|
||||
|
||||
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");
|
||||
default:break;
|
||||
}
|
||||
return S("Unknown error");
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
#ifndef FILE_TREE_INCLUDED
|
||||
#define FILE_TREE_INCLUDED
|
||||
|
||||
#include "basic.h"
|
||||
|
||||
enum {
|
||||
FILETREE_NOMEM = -1,
|
||||
FILETREE_NOENT = -2,
|
||||
FILETREE_NOTDIR = -3,
|
||||
FILETREE_ISDIR = -4,
|
||||
FILETREE_EXISTS = -5,
|
||||
FILETREE_BADPATH = -6,
|
||||
FILETREE_BADOP = -7,
|
||||
};
|
||||
|
||||
typedef struct Entity Entity;
|
||||
|
||||
typedef struct {
|
||||
uint64_t chunk_size;
|
||||
uint64_t num_chunks;
|
||||
SHA256 *chunks;
|
||||
} File;
|
||||
|
||||
typedef struct {
|
||||
uint64_t max_children;
|
||||
uint64_t num_children;
|
||||
Entity *children;
|
||||
} Dir;
|
||||
|
||||
struct Entity {
|
||||
char name[1<<8];
|
||||
uint16_t name_len;
|
||||
bool is_dir;
|
||||
union {
|
||||
Dir d;
|
||||
File f;
|
||||
};
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
Entity root;
|
||||
} FileTree;
|
||||
|
||||
typedef struct {
|
||||
char name[1<<8];
|
||||
int name_len;
|
||||
bool is_dir;
|
||||
} 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);
|
||||
int file_tree_create_entity(FileTree *ft, string path, bool is_dir, uint64_t chunk_size);
|
||||
int file_tree_delete_entity(FileTree *ft, string path);
|
||||
int file_tree_write(FileTree *ft, string path, uint64_t off, uint64_t len, SHA256 *prev_hashes, SHA256 *hashes, SHA256 *removed_hashes, int *num_removed);
|
||||
int file_tree_read(FileTree *ft, string path, uint64_t off, uint64_t len, uint64_t *chunk_size, SHA256 *hashes, int max_hashes);
|
||||
string file_tree_strerror(int code);
|
||||
|
||||
#endif // FILE_TREE_INCLUDED
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
#include <string.h>
|
||||
|
||||
#include "chunk_server.h"
|
||||
#include "metadata_server.h"
|
||||
|
||||
bool is_leader(int argc, char **argv)
|
||||
{
|
||||
for (int i = 1; i < argc; i++)
|
||||
if (!strcmp(argv[i], "--leader") || !strcmp(argv[i], "-l"))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
int ret;
|
||||
if (is_leader(argc, argv)) {
|
||||
|
||||
MetadataServer state;
|
||||
ret = metadata_server_init(&state, argc, argv);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
for (;;) {
|
||||
metadata_server_step(&state);
|
||||
}
|
||||
|
||||
metadata_server_free(&state);
|
||||
|
||||
} else {
|
||||
|
||||
ChunkServer state;
|
||||
ret = chunk_server_init(&state, argc, argv);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
for (;;) {
|
||||
chunk_server_step(&state);
|
||||
}
|
||||
|
||||
chunk_server_free(&state);
|
||||
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
#include <string.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;
|
||||
}
|
||||
|
||||
void message_writer_init(MessageWriter *writer, ByteQueue *output, uint16_t type)
|
||||
{
|
||||
uint16_t version = MESSAGE_VERSION;
|
||||
uint16_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))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void message_write(MessageWriter *writer, void *mem, int len)
|
||||
{
|
||||
byte_queue_write(writer->output, mem, len);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
#ifndef MESSAGE_INCLUDED
|
||||
#define MESSAGE_INCLUDED
|
||||
|
||||
#include <stdbool.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_STATE_UPDATE,
|
||||
MESSAGE_TYPE_DOWNLOAD_LOCATIONS,
|
||||
|
||||
// Chunk server -> Metadata server
|
||||
MESSAGE_TYPE_AUTH,
|
||||
MESSAGE_TYPE_STATE_UPDATE_ERROR,
|
||||
MESSAGE_TYPE_STATE_UPDATE_SUCCESS,
|
||||
|
||||
// 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);
|
||||
|
||||
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);
|
||||
|
||||
int message_peek(ByteView msg, uint16_t *type, uint32_t *len);
|
||||
|
||||
#endif // MESSAGE_INCLUDED
|
||||
@@ -0,0 +1,856 @@
|
||||
#define _GNU_SOURCE
|
||||
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "message.h"
|
||||
#include "metadata_server.h"
|
||||
|
||||
static void hash_list_init(HashList *hash_list)
|
||||
{
|
||||
hash_list->count = 0;
|
||||
hash_list->capacity = 0;
|
||||
hash_list->items = NULL;
|
||||
}
|
||||
|
||||
static void hash_list_free(HashList *hash_list)
|
||||
{
|
||||
free(hash_list->items);
|
||||
}
|
||||
|
||||
static int hash_list_insert(HashList *hash_list, SHA256 hash)
|
||||
{
|
||||
// Avoid duplicates
|
||||
for (int i = 0; i < hash_list->count; i++)
|
||||
if (!memcmp(&hash_list->items[i], &hash, sizeof(SHA256)))
|
||||
return 0; // Already present
|
||||
|
||||
if (hash_list->count == hash_list->capacity) {
|
||||
|
||||
int new_capacity = hash_list->capacity ? hash_list->capacity * 2 : 16;
|
||||
|
||||
SHA256 *new_items = realloc(hash_list->items, new_capacity * sizeof(SHA256));
|
||||
if (new_items == NULL)
|
||||
return -1;
|
||||
|
||||
hash_list->items = new_items;
|
||||
hash_list->capacity = new_capacity;
|
||||
}
|
||||
|
||||
hash_list->items[hash_list->count++] = hash;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static bool hash_list_contains(HashList *hash_list, SHA256 hash)
|
||||
{
|
||||
for (int j = 0; j < hash_list->count; j++)
|
||||
if (!memcmp(&hash, &hash_list->items[j], sizeof(SHA256)))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static void chunk_server_peer_init(ChunkServerPeer *chunk_server)
|
||||
{
|
||||
chunk_server->auth = false;
|
||||
chunk_server->num_addrs = 0;
|
||||
hash_list_init(&chunk_server->old_list);
|
||||
hash_list_init(&chunk_server->add_list);
|
||||
hash_list_init(&chunk_server->rem_list);
|
||||
}
|
||||
|
||||
static void chunk_server_peer_free(ChunkServerPeer *chunk_server)
|
||||
{
|
||||
hash_list_free(&chunk_server->rem_list);
|
||||
hash_list_free(&chunk_server->add_list);
|
||||
hash_list_free(&chunk_server->old_list);
|
||||
}
|
||||
|
||||
static bool chunk_server_peer_contains(ChunkServerPeer *chunk_server, SHA256 hash)
|
||||
{
|
||||
return hash_list_contains(&chunk_server->old_list, hash)
|
||||
|| hash_list_contains(&chunk_server->add_list, hash);
|
||||
}
|
||||
|
||||
static bool chunk_server_peer_load(ChunkServerPeer *chunk_server)
|
||||
{
|
||||
return chunk_server->old_list.count + chunk_server->add_list.count;
|
||||
}
|
||||
|
||||
// Returns all chunk servers holding the given chunk
|
||||
//
|
||||
// The indices of the chunk servers is stored into "out", but at
|
||||
// most "max" indices are written. The return value is the number
|
||||
// of indices that would be written if "max" were large enough to
|
||||
// hold all indices.
|
||||
static int
|
||||
all_chunk_servers_holding_chunk(MetadataServer *state, SHA256 hash, int *out, int max)
|
||||
{
|
||||
int num = 0;
|
||||
for (int i = 0; i < state->num_chunk_servers; i++) {
|
||||
if (num < max && chunk_server_peer_contains(&state->chunk_servers[i], hash))
|
||||
out[num] = i;
|
||||
num++;
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
static int compare_chunk_servers(void *data, const void *p1, const void *p2)
|
||||
#else
|
||||
static int compare_chunk_servers(const void *p1, const void *p2, void *data)
|
||||
#endif
|
||||
{
|
||||
int a = *(int*) p1;
|
||||
int b = *(int*) p2;
|
||||
MetadataServer *state = data;
|
||||
int l1 = chunk_server_peer_load(&state->chunk_servers[a]);
|
||||
int l2 = chunk_server_peer_load(&state->chunk_servers[b]);
|
||||
return l1 - l2;
|
||||
}
|
||||
|
||||
// Returns the indices of chunk servers with lowest load in
|
||||
// the "out" array. The return value is the number of indices
|
||||
// written, but no more than "max" are written.
|
||||
static int choose_servers_for_write(MetadataServer *state, int *out, int max)
|
||||
{
|
||||
int num = state->num_chunk_servers;
|
||||
int indices[MAX_CHUNK_SERVERS];
|
||||
|
||||
for (int i = 0; i < num; i++)
|
||||
indices[i] = i;
|
||||
|
||||
#ifdef _WIN32
|
||||
qsort_s(indices, num, sizeof(*indices), compare_chunk_servers, state);
|
||||
#else
|
||||
qsort_r(indices, num, sizeof(*indices), compare_chunk_servers, state);
|
||||
#endif
|
||||
|
||||
if (max > num) max = num;
|
||||
|
||||
for (int i = 0; i < max; i++)
|
||||
out[i] = indices[i]; // Or maybe the other way around? indices[max - i - 1]?
|
||||
|
||||
return num;
|
||||
}
|
||||
|
||||
static int find_chunk_server_by_addr(MetadataServer *state, Address addr)
|
||||
{
|
||||
for (int i = 0; i < state->num_chunk_servers; i++)
|
||||
for (int j = 0; j < state->chunk_servers[i].num_addrs; j++)
|
||||
if (addr_eql(state->chunk_servers[i].addrs[j], addr))
|
||||
return j;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Serialize the list of addresses for the specified
|
||||
// chunk server.
|
||||
static void
|
||||
message_write_server_addr(MessageWriter *writer, ChunkServerPeer *server)
|
||||
{
|
||||
uint32_t num_ipv4 = 0;
|
||||
for (int i = 0; i < server->num_addrs; i++)
|
||||
if (server->addrs[i].is_ipv4)
|
||||
num_ipv4++;
|
||||
|
||||
message_write(writer, &num_ipv4, sizeof(num_ipv4));
|
||||
for (int i = 0; i < server->num_addrs; i++)
|
||||
if (server->addrs[i].is_ipv4) {
|
||||
message_write(writer, &server->addrs[i].ipv4, sizeof(server->addrs[i].ipv4));
|
||||
message_write(writer, &server->addrs[i].port, sizeof(server->addrs[i].port));
|
||||
}
|
||||
|
||||
uint32_t num_ipv6 = 0;
|
||||
for (int i = 0; i < server->num_addrs; i++)
|
||||
if (!server->addrs[i].is_ipv4)
|
||||
num_ipv6++;
|
||||
|
||||
message_write(writer, &num_ipv6, sizeof(num_ipv6));
|
||||
for (int i = 0; i < server->num_addrs; i++)
|
||||
if (!server->addrs[i].is_ipv4) {
|
||||
message_write(writer, &server->addrs[i].ipv6, sizeof(server->addrs[i].ipv6));
|
||||
message_write(writer, &server->addrs[i].port, sizeof(server->addrs[i].port));
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
process_client_create(MetadataServer *state, int conn_idx, ByteView msg)
|
||||
{
|
||||
BinaryReader reader = { msg.ptr, msg.len, 0 };
|
||||
|
||||
// Read header
|
||||
if (!binary_read(&reader, NULL, sizeof(MessageHeader)))
|
||||
return -1;
|
||||
|
||||
char path_mem[1<<10];
|
||||
uint16_t path_len;
|
||||
|
||||
if (binary_read(&reader, &path_len, sizeof(path_len)))
|
||||
return -1;
|
||||
|
||||
if (path_len > sizeof(path_mem))
|
||||
return -2;
|
||||
|
||||
if (binary_read(&reader, &path_mem, path_len))
|
||||
return -1;
|
||||
|
||||
string path = { path_mem, path_len };
|
||||
|
||||
uint8_t is_dir;
|
||||
if (binary_read(&reader, &is_dir, sizeof(path_len)))
|
||||
return -1;
|
||||
|
||||
uint32_t chunk_size;
|
||||
if (is_dir)
|
||||
chunk_size = 0;
|
||||
else {
|
||||
if (binary_read(&reader, &chunk_size, sizeof(chunk_size)))
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Check that there are no more bytes to read
|
||||
if (binary_read(&reader, NULL, 1))
|
||||
return -1;
|
||||
|
||||
int ret = file_tree_create_entity(&state->file_tree, path, is_dir, chunk_size);
|
||||
|
||||
if (ret < 0) {
|
||||
|
||||
string desc = file_tree_strerror(ret);
|
||||
|
||||
MessageWriter writer;
|
||||
|
||||
ByteQueue *output = tcp_output_buffer(&state->tcp, conn_idx);
|
||||
message_writer_init(&writer, output, MESSAGE_TYPE_CREATE_ERROR);
|
||||
|
||||
uint16_t len = desc.len;
|
||||
message_write(&writer, &len, sizeof(len));
|
||||
message_write(&writer, desc.ptr, desc.len);
|
||||
|
||||
if (!message_writer_free(&writer))
|
||||
return -1;
|
||||
|
||||
} else {
|
||||
|
||||
MessageWriter writer;
|
||||
|
||||
ByteQueue *output = tcp_output_buffer(&state->tcp, conn_idx);
|
||||
message_writer_init(&writer, output, MESSAGE_TYPE_CREATE_SUCCESS);
|
||||
|
||||
if (!message_writer_free(&writer))
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
process_client_delete(MetadataServer *state, int conn_idx, ByteView msg)
|
||||
{
|
||||
BinaryReader reader = { msg.ptr, msg.len, 0 };
|
||||
|
||||
// Read header
|
||||
if (!binary_read(&reader, NULL, sizeof(MessageHeader)))
|
||||
return -1;
|
||||
|
||||
char path_mem[1<<10];
|
||||
uint16_t path_len;
|
||||
|
||||
if (binary_read(&reader, &path_len, sizeof(path_len)))
|
||||
return -1;
|
||||
|
||||
if (path_len > sizeof(path_mem))
|
||||
return -2;
|
||||
|
||||
if (binary_read(&reader, &path_mem, path_len))
|
||||
return -1;
|
||||
|
||||
string path = { path_mem, path_len };
|
||||
|
||||
// Check that there are no more bytes to read
|
||||
if (binary_read(&reader, NULL, 1))
|
||||
return -1;
|
||||
|
||||
int ret = file_tree_delete_entity(&state->file_tree, path);
|
||||
|
||||
if (ret < 0) {
|
||||
|
||||
string desc = file_tree_strerror(ret);
|
||||
|
||||
MessageWriter writer;
|
||||
|
||||
ByteQueue *output = tcp_output_buffer(&state->tcp, conn_idx);
|
||||
message_writer_init(&writer, output, MESSAGE_TYPE_DELETE_ERROR);
|
||||
|
||||
uint16_t len = desc.len;
|
||||
message_write(&writer, &len, sizeof(len));
|
||||
message_write(&writer, desc.ptr, desc.len);
|
||||
|
||||
if (!message_writer_free(&writer))
|
||||
return -1;
|
||||
|
||||
} else {
|
||||
|
||||
MessageWriter writer;
|
||||
|
||||
ByteQueue *output = tcp_output_buffer(&state->tcp, conn_idx);
|
||||
message_writer_init(&writer, output, MESSAGE_TYPE_DELETE_SUCCESS);
|
||||
|
||||
if (!message_writer_free(&writer))
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
process_client_list(MetadataServer *state, int conn_idx, ByteView msg)
|
||||
{
|
||||
BinaryReader reader = { msg.ptr, msg.len, 0 };
|
||||
|
||||
// Read header
|
||||
if (!binary_read(&reader, NULL, sizeof(MessageHeader)))
|
||||
return -1;
|
||||
|
||||
char path_mem[1<<10];
|
||||
uint16_t path_len;
|
||||
|
||||
if (binary_read(&reader, &path_len, sizeof(path_len)))
|
||||
return -1;
|
||||
|
||||
if (path_len > sizeof(path_mem))
|
||||
return -2;
|
||||
|
||||
if (binary_read(&reader, &path_mem, path_len))
|
||||
return -1;
|
||||
|
||||
string path = { path_mem, path_len };
|
||||
|
||||
// Check that there are no more bytes to read
|
||||
if (binary_read(&reader, NULL, 1))
|
||||
return -1;
|
||||
|
||||
#define MAX_LIST_SIZE 128
|
||||
|
||||
ListItem items[MAX_LIST_SIZE];
|
||||
int ret = file_tree_list(&state->file_tree, path, items, MAX_LIST_SIZE);
|
||||
|
||||
if (ret < 0) {
|
||||
|
||||
string desc = file_tree_strerror(ret);
|
||||
|
||||
MessageWriter writer;
|
||||
|
||||
ByteQueue *output = tcp_output_buffer(&state->tcp, conn_idx);
|
||||
message_writer_init(&writer, output, MESSAGE_TYPE_LIST_ERROR);
|
||||
|
||||
uint16_t len = desc.len;
|
||||
message_write(&writer, &len, sizeof(len));
|
||||
message_write(&writer, desc.ptr, desc.len);
|
||||
|
||||
if (!message_writer_free(&writer))
|
||||
return -1;
|
||||
|
||||
} else {
|
||||
|
||||
MessageWriter writer;
|
||||
|
||||
ByteQueue *output = tcp_output_buffer(&state->tcp, conn_idx);
|
||||
message_writer_init(&writer, output, MESSAGE_TYPE_LIST_SUCCESS);
|
||||
|
||||
uint32_t item_count = ret;
|
||||
uint8_t truncated = 0;
|
||||
|
||||
if (ret > MAX_LIST_SIZE) {
|
||||
truncated = 1;
|
||||
item_count = MAX_LIST_SIZE;
|
||||
}
|
||||
|
||||
message_write(&writer, &item_count, sizeof(item_count));
|
||||
message_write(&writer, &truncated, sizeof(truncated));
|
||||
|
||||
for (int i = 0; i < ret && i < MAX_LIST_SIZE; i++) {
|
||||
|
||||
uint8_t is_dir = items[i].is_dir;
|
||||
message_write(&writer, &is_dir, sizeof(is_dir));
|
||||
|
||||
if (items[i].name_len > UINT16_MAX)
|
||||
return -1;
|
||||
uint16_t name_len = items[i].name_len;
|
||||
message_write(&writer, &name_len, sizeof(name_len));
|
||||
|
||||
message_write(&writer, items[i].name, name_len);
|
||||
}
|
||||
|
||||
if (!message_writer_free(&writer))
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
process_client_read(MetadataServer *state, int conn_idx, ByteView msg)
|
||||
{
|
||||
BinaryReader reader = { msg.ptr, msg.len, 0 };
|
||||
|
||||
// Read header
|
||||
if (!binary_read(&reader, NULL, sizeof(MessageHeader)))
|
||||
return -1;
|
||||
|
||||
char path_mem[1<<10];
|
||||
uint16_t path_len;
|
||||
|
||||
if (binary_read(&reader, &path_len, sizeof(path_len)))
|
||||
return -1;
|
||||
|
||||
if (path_len > sizeof(path_mem))
|
||||
return -2;
|
||||
|
||||
if (binary_read(&reader, &path_mem, path_len))
|
||||
return -1;
|
||||
|
||||
string path = { path_mem, path_len };
|
||||
|
||||
uint32_t offset;
|
||||
if (binary_read(&reader, &offset, sizeof(offset)))
|
||||
return -1;
|
||||
|
||||
uint32_t length;
|
||||
if (binary_read(&reader, &length, sizeof(length)))
|
||||
return -1;
|
||||
|
||||
// Check that there are no more bytes to read
|
||||
if (binary_read(&reader, NULL, 1))
|
||||
return -1;
|
||||
|
||||
#define MAX_READ_HASHES 128
|
||||
|
||||
uint64_t chunk_size;
|
||||
SHA256 hashes[MAX_READ_HASHES];
|
||||
int ret = file_tree_read(&state->file_tree, path, offset, length, &chunk_size, hashes, MAX_READ_HASHES);
|
||||
|
||||
if (ret < 0) {
|
||||
|
||||
string desc = file_tree_strerror(ret);
|
||||
|
||||
MessageWriter writer;
|
||||
|
||||
ByteQueue *output = tcp_output_buffer(&state->tcp, conn_idx);
|
||||
message_writer_init(&writer, output, MESSAGE_TYPE_READ_ERROR);
|
||||
|
||||
uint16_t len = desc.len;
|
||||
message_write(&writer, &len, sizeof(len));
|
||||
message_write(&writer, desc.ptr, desc.len);
|
||||
|
||||
if (!message_writer_free(&writer))
|
||||
return -1;
|
||||
|
||||
} else {
|
||||
|
||||
MessageWriter writer;
|
||||
|
||||
ByteQueue *output = tcp_output_buffer(&state->tcp, conn_idx);
|
||||
message_writer_init(&writer, output, MESSAGE_TYPE_READ_SUCCESS);
|
||||
|
||||
if (chunk_size > UINT32_MAX) {
|
||||
message_writer_free(&writer);
|
||||
return -1;
|
||||
}
|
||||
uint32_t tmp = chunk_size;
|
||||
message_write(&writer, &tmp, sizeof(tmp));
|
||||
|
||||
uint32_t num_hashes = ret;
|
||||
message_write(&writer, &num_hashes, sizeof(num_hashes));
|
||||
|
||||
for (uint32_t i = 0; i < num_hashes; i++) {
|
||||
|
||||
int holders[MAX_CHUNK_SERVERS];
|
||||
int num_holders = all_chunk_servers_holding_chunk(state, hashes[i], holders, state->replication_factor);
|
||||
|
||||
message_write(&writer, &hashes[i], sizeof(hashes[i]));
|
||||
|
||||
uint32_t tmp = num_holders;
|
||||
message_write(&writer, &tmp, sizeof(tmp));
|
||||
|
||||
for (int j = 0; j < num_holders; j++)
|
||||
message_write_server_addr(&writer, &state->chunk_servers[holders[j]]);
|
||||
}
|
||||
|
||||
int locations[MAX_CHUNK_SERVERS];
|
||||
int num_locations = choose_servers_for_write(state, locations, state->replication_factor);
|
||||
|
||||
for (int j = 0; j < num_locations; j++)
|
||||
message_write_server_addr(&writer, &state->chunk_servers[locations[j]]);
|
||||
|
||||
if (!message_writer_free(&writer))
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
process_client_write(MetadataServer *state, int conn_idx, ByteView msg)
|
||||
{
|
||||
BinaryReader reader = { msg.ptr, msg.len, 0 };
|
||||
|
||||
// Read header
|
||||
if (!binary_read(&reader, NULL, sizeof(MessageHeader)))
|
||||
return -1;
|
||||
|
||||
char path_mem[1<<10];
|
||||
uint16_t path_len;
|
||||
|
||||
if (binary_read(&reader, &path_len, sizeof(path_len)))
|
||||
return -1;
|
||||
|
||||
if (path_len > sizeof(path_mem))
|
||||
return -2;
|
||||
|
||||
if (binary_read(&reader, &path_mem, path_len))
|
||||
return -1;
|
||||
|
||||
string path = { path_mem, path_len };
|
||||
|
||||
uint32_t offset;
|
||||
if (binary_read(&reader, &offset, sizeof(offset)))
|
||||
return -1;
|
||||
|
||||
uint32_t length;
|
||||
if (binary_read(&reader, &length, sizeof(length)))
|
||||
return -1;
|
||||
|
||||
uint32_t num_chunks;
|
||||
if (binary_read(&reader, &num_chunks, sizeof(num_chunks)))
|
||||
return -1;
|
||||
|
||||
#define MAX_CHUNKS_PER_WRITE 32
|
||||
|
||||
Address addrs[MAX_CHUNKS_PER_WRITE];
|
||||
SHA256 new_hashes[MAX_CHUNKS_PER_WRITE];
|
||||
SHA256 old_hashes[MAX_CHUNKS_PER_WRITE];
|
||||
|
||||
for (uint32_t i = 0; i < num_chunks; i++) {
|
||||
|
||||
SHA256 old_hash;
|
||||
if (binary_read(&reader, &old_hash, sizeof(old_hash)))
|
||||
return -1;
|
||||
|
||||
SHA256 new_hash;
|
||||
if (binary_read(&reader, &new_hash, sizeof(new_hash)))
|
||||
return -1;
|
||||
|
||||
uint8_t is_ipv4;
|
||||
if (binary_read(&reader, &is_ipv4, sizeof(is_ipv4)))
|
||||
return -1;
|
||||
|
||||
Address addr;
|
||||
addr.is_ipv4 = is_ipv4;
|
||||
|
||||
if (is_ipv4) {
|
||||
if (binary_read(&reader, &addr.ipv4, sizeof(addr.ipv4)))
|
||||
return -1;
|
||||
} else {
|
||||
if (binary_read(&reader, &addr.ipv6, sizeof(addr.ipv6)))
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (binary_read(&reader, &addr.port, sizeof(addr.port)))
|
||||
return -1;
|
||||
|
||||
addrs[i] = addr;
|
||||
new_hashes[i] = new_hash;
|
||||
old_hashes[i] = old_hash;
|
||||
}
|
||||
|
||||
// Check that there are no more bytes to read
|
||||
if (binary_read(&reader, NULL, 1))
|
||||
return -1;
|
||||
|
||||
// Array to collect hashes that are no longer used anywhere in the file tree
|
||||
SHA256 removed_hashes[MAX_CHUNKS_PER_WRITE];
|
||||
int num_removed = 0;
|
||||
|
||||
int ret = file_tree_write(&state->file_tree, path, offset, length,
|
||||
old_hashes, new_hashes, removed_hashes, &num_removed);
|
||||
|
||||
if (ret < 0) {
|
||||
|
||||
string desc = file_tree_strerror(ret);
|
||||
|
||||
MessageWriter writer;
|
||||
|
||||
ByteQueue *output = tcp_output_buffer(&state->tcp, conn_idx);
|
||||
message_writer_init(&writer, output, MESSAGE_TYPE_WRITE_ERROR);
|
||||
|
||||
uint16_t len = desc.len;
|
||||
message_write(&writer, &len, sizeof(len));
|
||||
message_write(&writer, desc.ptr, desc.len);
|
||||
|
||||
if (!message_writer_free(&writer))
|
||||
return -1;
|
||||
|
||||
} else {
|
||||
|
||||
// Add new chunks to add_list
|
||||
for (uint32_t i = 0; i < num_chunks; i++) {
|
||||
int j = find_chunk_server_by_addr(state, addrs[i]);
|
||||
if (j == -1)
|
||||
return -1;
|
||||
|
||||
if (!hash_list_insert(&state->chunk_servers[j].add_list, new_hashes[i]))
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Mark removed chunks for deletion on all chunk servers that have them
|
||||
// These are chunks that were overwritten and are no longer referenced anywhere
|
||||
for (int i = 0; i < num_removed; i++) {
|
||||
SHA256 removed_hash = removed_hashes[i];
|
||||
|
||||
// Add to rem_list for all chunk servers that have this chunk
|
||||
for (int j = 0; j < state->num_chunk_servers; j++) {
|
||||
if (chunk_server_peer_contains(&state->chunk_servers[j], removed_hash)) {
|
||||
if (!hash_list_insert(&state->chunk_servers[j].rem_list, removed_hash))
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MessageWriter writer;
|
||||
|
||||
ByteQueue *output = tcp_output_buffer(&state->tcp, conn_idx);
|
||||
message_writer_init(&writer, output, MESSAGE_TYPE_WRITE_SUCCESS);
|
||||
|
||||
if (!message_writer_free(&writer))
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
process_client_message(MetadataServer *state,
|
||||
int conn_idx, uint8_t type, ByteView msg)
|
||||
{
|
||||
switch (type) {
|
||||
case MESSAGE_TYPE_CREATE: return process_client_create(state, conn_idx, msg);
|
||||
case MESSAGE_TYPE_DELETE: return process_client_delete(state, conn_idx, msg);
|
||||
case MESSAGE_TYPE_LIST : return process_client_list (state, conn_idx, msg);
|
||||
case MESSAGE_TYPE_READ : return process_client_read (state, conn_idx, msg);
|
||||
case MESSAGE_TYPE_WRITE : return process_client_write (state, conn_idx, msg);
|
||||
default:break;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static ChunkServerPeer*
|
||||
chunk_server_from_conn(MetadataServer *state, int conn_idx)
|
||||
{
|
||||
int tag = tcp_get_tag(&state->tcp, conn_idx);
|
||||
assert(tag >= 0);
|
||||
|
||||
return &state->chunk_servers[tag];
|
||||
}
|
||||
|
||||
static int process_chunk_server_auth(MetadataServer *state,
|
||||
int conn_idx, ByteView msg)
|
||||
{
|
||||
ChunkServerPeer *chunk_server = chunk_server_from_conn(state, conn_idx);
|
||||
chunk_server->num_addrs = 0;
|
||||
|
||||
BinaryReader reader = { msg.ptr, msg.len, 0 };
|
||||
|
||||
// Read header
|
||||
if (!binary_read(&reader, NULL, sizeof(MessageHeader)))
|
||||
return -1;
|
||||
|
||||
// Read IPv4s
|
||||
{
|
||||
uint32_t num_ipv4;
|
||||
if (!binary_read(&reader, &num_ipv4, sizeof(num_ipv4)))
|
||||
return -1;
|
||||
|
||||
for (uint32_t i = 0; i < num_ipv4; i++) {
|
||||
|
||||
IPv4 ipv4;
|
||||
if (!binary_read(&reader, &ipv4, sizeof(ipv4)))
|
||||
return -1;
|
||||
|
||||
uint16_t port;
|
||||
if (!binary_read(&reader, &port, sizeof(port)))
|
||||
return -1;
|
||||
|
||||
if (chunk_server->num_addrs < MAX_SERVER_ADDRS)
|
||||
chunk_server->addrs[chunk_server->num_addrs++] =
|
||||
(Address) { .ipv4=ipv4, .is_ipv4=true, .port=port };
|
||||
}
|
||||
}
|
||||
|
||||
// Read IPv6s
|
||||
{
|
||||
uint32_t num_ipv6;
|
||||
if (!binary_read(&reader, &num_ipv6, sizeof(num_ipv6)))
|
||||
return -1;
|
||||
|
||||
for (uint32_t i = 0; i < num_ipv6; i++) {
|
||||
|
||||
IPv6 ipv6;
|
||||
if (!binary_read(&reader, &ipv6, sizeof(ipv6)))
|
||||
return -1;
|
||||
|
||||
uint16_t port;
|
||||
if (!binary_read(&reader, &port, sizeof(port)))
|
||||
return -1;
|
||||
|
||||
if (chunk_server->num_addrs < MAX_SERVER_ADDRS)
|
||||
chunk_server->addrs[chunk_server->num_addrs++] =
|
||||
(Address) { .is_ipv4=true, .ipv6=ipv6, .port=port };
|
||||
}
|
||||
}
|
||||
|
||||
// No addresses were wpecified
|
||||
if (chunk_server->num_addrs == 0)
|
||||
return -1;
|
||||
|
||||
// Check that there are no more bytes to read
|
||||
if (binary_read(&reader, NULL, 1))
|
||||
return -1;
|
||||
|
||||
// NOTE: In a production system, this should verify the authentication
|
||||
// using the shared secret key mentioned in the architecture. For now,
|
||||
// we accept all connections that provide valid address information.
|
||||
chunk_server->auth = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
process_chunk_server_message(MetadataServer *state,
|
||||
int conn_idx, uint8_t type, ByteView msg)
|
||||
{
|
||||
switch (type) {
|
||||
case MESSAGE_TYPE_AUTH:
|
||||
return process_chunk_server_auth(state, conn_idx, msg);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static bool is_chunk_server_message_type(uint16_t type)
|
||||
{
|
||||
switch (type) {
|
||||
case MESSAGE_TYPE_AUTH:
|
||||
case MESSAGE_TYPE_STATE_UPDATE_ERROR:
|
||||
case MESSAGE_TYPE_STATE_UPDATE_SUCCESS:
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int metadata_server_init(MetadataServer *state, int argc, char **argv)
|
||||
{
|
||||
(void) argc;
|
||||
(void) argv;
|
||||
|
||||
char addr[] = "127.0.0.1";
|
||||
uint16_t port = 8080;
|
||||
|
||||
state->replication_factor = 3;
|
||||
if (state->replication_factor > MAX_CHUNK_SERVERS)
|
||||
return -1;
|
||||
|
||||
state->num_chunk_servers = 0;
|
||||
|
||||
tcp_context_init(&state->tcp);
|
||||
|
||||
int ret = tcp_listen(&state->tcp, addr, port);
|
||||
if (ret < 0) {
|
||||
tcp_context_free(&state->tcp);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ret = file_tree_init(&state->file_tree);
|
||||
if (ret < 0) {
|
||||
tcp_context_free(&state->tcp);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int metadata_server_free(MetadataServer *state)
|
||||
{
|
||||
file_tree_free(&state->file_tree);
|
||||
tcp_context_free(&state->tcp);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int metadata_server_step(MetadataServer *state)
|
||||
{
|
||||
Event events[MAX_CONNS+1];
|
||||
int num_events = tcp_process_events(&state->tcp, events);
|
||||
|
||||
for (int i = 0; i < num_events; i++) {
|
||||
int conn_idx = events[i].conn_idx;
|
||||
switch (events[i].type) {
|
||||
|
||||
case EVENT_CONNECT:
|
||||
tcp_set_tag(&state->tcp, conn_idx, CONNECTION_TAG_UNKNOWN);
|
||||
break;
|
||||
|
||||
case EVENT_DISCONNECT:
|
||||
{
|
||||
int tag = tcp_get_tag(&state->tcp, conn_idx);
|
||||
if (tag >= 0) {
|
||||
chunk_server_peer_free(&state->chunk_servers[tag]);
|
||||
state->num_chunk_servers--;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case EVENT_MESSAGE:
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
if (tcp_get_tag(&state->tcp, conn_idx) == CONNECTION_TAG_UNKNOWN) {
|
||||
if (is_chunk_server_message_type(msg_type)) {
|
||||
int chunk_server_idx = state->num_chunk_servers++;
|
||||
chunk_server_peer_init(&state->chunk_servers[chunk_server_idx]);
|
||||
tcp_set_tag(&state->tcp, conn_idx, chunk_server_idx);
|
||||
} else {
|
||||
tcp_set_tag(&state->tcp, conn_idx, CONNECTION_TAG_CLIENT);
|
||||
}
|
||||
}
|
||||
|
||||
if (tcp_get_tag(&state->tcp, conn_idx) == CONNECTION_TAG_CLIENT)
|
||||
ret = process_client_message(state, conn_idx, msg_type, msg);
|
||||
else
|
||||
ret = process_chunk_server_message(state, conn_idx, msg_type, msg);
|
||||
|
||||
if (ret < 0) {
|
||||
tcp_close(&state->tcp, conn_idx);
|
||||
break;
|
||||
}
|
||||
|
||||
tcp_consume_message(&state->tcp, conn_idx);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
#ifndef METADATA_SERVER_INCLUDED
|
||||
#define METADATA_SERVER_INCLUDED
|
||||
|
||||
#include "tcp.h"
|
||||
#include "file_tree.h"
|
||||
|
||||
#define CONNECTION_TAG_CLIENT -1
|
||||
#define CONNECTION_TAG_UNKNOWN -2
|
||||
|
||||
typedef struct {
|
||||
int count;
|
||||
int capacity;
|
||||
SHA256 *items;
|
||||
} HashList;
|
||||
|
||||
typedef struct {
|
||||
|
||||
bool auth;
|
||||
|
||||
int num_addrs;
|
||||
Address addrs[MAX_SERVER_ADDRS];
|
||||
|
||||
// Chunks held by the chunk server during
|
||||
// the last update
|
||||
HashList old_list;
|
||||
|
||||
// Chunks added to the chunk server since
|
||||
// the last update
|
||||
HashList add_list;
|
||||
|
||||
// Chunks removed from the chunk server
|
||||
// since the last update
|
||||
HashList rem_list;
|
||||
|
||||
} ChunkServerPeer;
|
||||
|
||||
typedef struct {
|
||||
|
||||
TCP tcp;
|
||||
|
||||
FileTree file_tree;
|
||||
|
||||
int replication_factor;
|
||||
|
||||
int num_chunk_servers;
|
||||
ChunkServerPeer chunk_servers[MAX_CHUNK_SERVERS];
|
||||
|
||||
} MetadataServer;
|
||||
|
||||
int metadata_server_init(MetadataServer *state, int argc, char **argv);
|
||||
int metadata_server_free(MetadataServer *state);
|
||||
int metadata_server_step(MetadataServer *state);
|
||||
|
||||
#endif // METADATA_SERVER_INCLUDED
|
||||
+249
@@ -0,0 +1,249 @@
|
||||
#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);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#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
|
||||
@@ -0,0 +1,337 @@
|
||||
#include <assert.h>
|
||||
#include <string.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 SOCKET create_listen_socket(char *addr, uint16_t port)
|
||||
{
|
||||
SOCKET fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (fd == INVALID_SOCKET)
|
||||
return INVALID_SOCKET;
|
||||
|
||||
struct sockaddr_in bind_buf;
|
||||
bind_buf.sin_family = AF_INET;
|
||||
bind_buf.sin_port = htons(port);
|
||||
if (inet_pton(AF_INET, addr, &bind_buf.sin_addr) != 1)
|
||||
return INVALID_SOCKET;
|
||||
|
||||
if (bind(fd, (struct sockaddr*) &bind_buf, sizeof(bind_buf)))
|
||||
return INVALID_SOCKET;
|
||||
|
||||
int backlog = 32;
|
||||
if (listen(fd, backlog) < 0)
|
||||
return INVALID_SOCKET;
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void tcp_context_init(TCP *tcp)
|
||||
{
|
||||
tcp->listen_fd = INVALID_SOCKET;
|
||||
tcp->num_conns = 0;
|
||||
}
|
||||
|
||||
void tcp_context_free(TCP *tcp)
|
||||
{
|
||||
if (tcp->listen_fd != INVALID_SOCKET)
|
||||
CLOSE_SOCKET(tcp->listen_fd);
|
||||
}
|
||||
|
||||
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, char *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;
|
||||
}
|
||||
|
||||
// The "events" array must be an array of capacity MAX_CONNS+1
|
||||
int tcp_process_events(TCP *tcp, Event *events)
|
||||
{
|
||||
struct pollfd polled[MAX_CONNS + 1];
|
||||
void *contexts[MAX_CONNS + 1];
|
||||
int num_polled = 0;
|
||||
|
||||
if (tcp->listen_fd != INVALID_SOCKET && tcp->num_conns < MAX_CONNS) {
|
||||
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++;
|
||||
}
|
||||
}
|
||||
|
||||
POLL(polled, num_polled, -1);
|
||||
|
||||
bool removed[MAX_CONNS+1];
|
||||
|
||||
int num_events = 0;
|
||||
for (int i = 0; i < num_polled; i++) {
|
||||
|
||||
if (polled[i].fd == tcp->listen_fd) {
|
||||
|
||||
SOCKET new_fd = accept(tcp->listen_fd, NULL, NULL);
|
||||
if (new_fd != INVALID_SOCKET) {
|
||||
events[num_events++] = (Event) { EVENT_CONNECT, tcp->num_conns };
|
||||
conn_init(&tcp->conns[tcp->num_conns++], new_fd, 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 };
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
if (polled[i].revents & POLLIN) {
|
||||
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)
|
||||
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);
|
||||
if (ret < 0) {
|
||||
// Invalid message
|
||||
byte_queue_read_ack(&conn->input, 0);
|
||||
defer_close = true;
|
||||
} else if (ret == 0) {
|
||||
// Still buffering
|
||||
byte_queue_read_ack(&conn->input, 0);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
removed[i] = defer_close;
|
||||
if (0) {}
|
||||
else if (defer_close) events[num_events++] = (Event) { EVENT_DISCONNECT, conn - tcp->conns };
|
||||
else if (defer_ready) events[num_events++] = (Event) { EVENT_MESSAGE, conn - tcp->conns };
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < tcp->num_conns; i++)
|
||||
if (removed[i]) {
|
||||
conn_free(&tcp->conns[i]);
|
||||
tcp->conns[i] = 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 == MAX_CONNS)
|
||||
return -1;
|
||||
int conn_idx = tcp->num_conns;
|
||||
|
||||
SOCKET fd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (fd == INVALID_SOCKET)
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
void tcp_set_tag(TCP *tcp, int conn_idx, int tag)
|
||||
{
|
||||
tcp->conns[conn_idx].tag = tag;
|
||||
}
|
||||
|
||||
int tcp_get_tag(TCP *tcp, int conn_idx)
|
||||
{
|
||||
return tcp->conns[conn_idx].tag;
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
#ifndef TCP_INCLUDED
|
||||
#define TCP_INCLUDED
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#else
|
||||
#include <poll.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <arpa/inet.h>
|
||||
#define POLL poll
|
||||
#define CLOSE_SOCKET close
|
||||
#define SOCKET int
|
||||
#define INVALID_SOCKET -1
|
||||
#endif
|
||||
|
||||
#include "byte_queue.h"
|
||||
|
||||
#define MAX_CONNS 512
|
||||
|
||||
typedef enum {
|
||||
EVENT_MESSAGE,
|
||||
EVENT_CONNECT,
|
||||
EVENT_DISCONNECT,
|
||||
} EventType;
|
||||
|
||||
typedef struct {
|
||||
EventType type;
|
||||
int conn_idx;
|
||||
} Event;
|
||||
|
||||
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 {
|
||||
SOCKET fd;
|
||||
int tag;
|
||||
bool connecting;
|
||||
bool closing;
|
||||
uint32_t msglen;
|
||||
ByteQueue input;
|
||||
ByteQueue output;
|
||||
} Connection;
|
||||
|
||||
typedef struct {
|
||||
SOCKET listen_fd;
|
||||
int num_conns;
|
||||
Connection conns[MAX_CONNS];
|
||||
} TCP;
|
||||
|
||||
bool addr_eql(Address a, Address b);
|
||||
void tcp_context_init(TCP *tcp);
|
||||
void tcp_context_free(TCP *tcp);
|
||||
int tcp_index_from_tag(TCP *tcp, int tag);
|
||||
int tcp_listen(TCP *tcp, char *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_process_events(TCP *tcp, Event *events);
|
||||
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);
|
||||
int tcp_get_tag(TCP *tcp, int conn_idx);
|
||||
|
||||
#endif // TCP_INCLUDED
|
||||
Reference in New Issue
Block a user