Commit d652def6 authored by Naman Dixit's avatar Naman Dixit

Done, hopefully

parents
server/data_store
server/server
client/client
Team members:
Naman Dixit
19305R005
namandixit@cse.iitb.ac.in
Varad Bhatnagar
19305R005
varadhbhatnagar@cse.iitb.ac.in
Instructions:
Server:
Compilation: Run `make` in "server/" directory.
Execution: Run `./server` in "server/" directory.
It supports the following command line parameters:
1. -port=XXX (the port number on which the server
will listen)
2. -threadPoolSize=YYY (number of threads in the
thread pool)
3. -numSetsInCache=ZZZ (number of sets in the cache)
4. -sizeOfSet=AAA (associativity of sets)
Storage: The server stores the data in "${PWD}/data_store/" directory.
A file of special note is "${PWD}/data_store/kvstore.xml" which
contains an XML dump of the stored key-value pairs. This is the
file to be used for grading, and is generated in a lazy fashion
as instructed here:
moodle.iitb.ac.in/mod/forum/discuss.php?d=125057#p191609
Logs: When the server start, it prints a log message:
Log: Waiting for connection on port <port>...
When a new connection is established, it prints:
Log: Connection made: client_fd=<client_fd>
If there are any errors, it will quit after printing a message
prefixed with "Error:".
Client:
Compilation: Run `make` in "client/" directory.
Execution: The client can be launched inside "client/" directory
in either:
1. interactive mode: `./client -i`
2. batch mode: `./client <input_file> <output_file>`
It supports the following command line parameters:
1. -i (run in interactive mode)
2. -port=XXX (the port number on which the client
will connect)
Logs: When the server start, it prints a log message:
Log: Starting communication with server on port <port>...
If there are any errors, it will quit after printing a message
prefixed with "Error:". If wrong command line parameters are
given, it quits after printing a help message prefixed with
"Help:".
ClientSource := client.c
ClientTarget := client
.PHONY: all ClientTarget
all: ClientTarget
ClientTarget:
@echo "Building client..."
@gcc -g3 -O0 -fno-strict-aliasing -fwrapv -msse2 -I../common \
--std=c11 -DBUILD_INTERNAL -DBUILD_SLOW -D_POSIX_C_SOURCE=200809L \
-Wall -Wextra -Wpedantic -pedantic-errors -Werror \
$(ClientSource) -o ${ClientTarget} \
-Wl,-rpath=\$$ORIGIN -Wl,-z,origin -Wl,--enable-new-dtags \
-static-libgcc
/*
* Creator: Naman Dixit
* Notice: © Copyright 2019 Naman Dixit
*/
#include "aux.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#include <ctype.h>
#if 0
# define log(...) printf("Log: " __VA_ARGS__)
#else
# define log(...)
#endif
#include "xml.h"
Char* tokenGet(Char *line, Char separator, Size *position)
{
Char *elem = NULL;
Size i = *position;
Size line_len = strlen(line);
while ((i < line_len) && (line[i] != '\n') && (line[i] != separator)) {
sbufPrint(elem, "%c", line[i]);
i++;
}
sbufPrint(elem, "%c", '\0');
*position = i + 1;
return elem;
}
int main(int argc, char** argv)
{
B32 interactive_mode = false;
Char *port = NULL;
for (Size i = 1; i < (Size)argc; i++) {
Size arg_pos = 0;
if ((arg_pos = strprefix("-i", argv[i]))) {
interactive_mode = true;
} else if ((arg_pos = strprefix("--interactive", argv[i]))) {
interactive_mode = true;
} else if ((arg_pos = strprefix("-port=", argv[i]))) {
port = &(argv[i][arg_pos]);
}
}
if (port == NULL) port = "8080";
FILE *file_input = NULL, *file_output = NULL;
if (interactive_mode == false) {
if (argc != 3) {
printf("Help: Incorrect command line parameters\n");
printf("Help: See README.txt file for info on correct invocation\n");
exit(-1);
}
Char *file_input_name = argv[1];
Char *file_output_name = argv[2];
if((file_input = fopen(file_input_name, "r")) == NULL) {
fprintf(stderr, "Error: The input file %s cannot be opened\n", file_input_name);
exit(-1);
}
if((file_output = fopen(file_output_name, "w")) == NULL) {
fprintf(stderr, "Error: The output file %s cannot be opened\n", file_output_name);
exit(-1);
}
} else {
file_input = stdin;
file_output = stdout;
}
Sint sock_fd = socket(AF_INET, SOCK_STREAM, 0);
struct addrinfo hints = {.ai_family = AF_INET,
.ai_socktype = SOCK_STREAM};
struct addrinfo *result = NULL;
Sint s = getaddrinfo(NULL, "8080", &hints, &result);
if (s != 0) {
fprintf(stderr, "Error: getaddrinfo: %s\n", gai_strerror(s));
exit(-1);
}
while (connect(sock_fd, result->ai_addr, result->ai_addrlen) == -1) {
fprintf(stderr, "Error: Couldn't connect on port %s, trying again in one second...\n", port);
sleep(1);
}
printf("Log: Starting communication with server on port %s...\n", port);
while (true) {
if (interactive_mode == true) {
printf("===QUERY: ");
}
Char *command = NULL;
{ // Read command
Sint c = 0;
while (((c = getc(file_input)) != '\n') && (c != EOF)) {
sbufPrint(command, "%c", (Char)c);
}
if (interactive_mode && (c == EOF)) {
printf("\n");
break;
}
if (command == NULL) {
break;
}
sbufPrint(command, "%c", '\0');
}
if (interactive_mode == true) {
printf("RESPONSE: ");
}
Char *xml_request_message = NULL;
// Response message will be of proper legth, since server will make sure of that.
Size response_size = KiB(300);
Char *xml_response_message = calloc(response_size, sizeof(*xml_response_message));
Size index = 0;
Char *query_type = tokenGet(command, ',', &index);
Char *query_key = tokenGet(command, ',', &index);
if(strcmp(query_type, "GET") == 0) {
xml_request_message = xmlCreateMessage(XML_Message_Kind_GET,
query_key, NULL, NULL);
write(sock_fd, xml_request_message, strlen(xml_request_message));
read(sock_fd, xml_response_message, response_size);
} else if(strcmp(query_type, "SET") == 0) {
Char *query_value = tokenGet(command, '\n', &index);
xml_request_message = xmlCreateMessage(XML_Message_Kind_PUT,
query_key, query_value, NULL);
sbufDelete(query_value);
write(sock_fd, xml_request_message, strlen(xml_request_message));
read(sock_fd, xml_response_message, response_size);
} else if(strcmp(query_type,"DEL") == 0) {
xml_request_message = xmlCreateMessage(XML_Message_Kind_DELETE,
query_key, NULL, NULL);
write(sock_fd, xml_request_message, strlen(xml_request_message));
read(sock_fd, xml_response_message, response_size);
}
sbufDelete(query_type);
sbufDelete(query_key);
XML_Message msg = xmlParseMessage(xml_response_message);
switch (msg.kind) {
case XML_Message_Kind_RESP_GET: {
fprintf(file_output, "%s,%s\n", msg.key, msg.value);
} break;
case XML_Message_Kind_RESP_PUT_DELETE: {
fprintf(file_output, "success\n");
} break;
case XML_Message_Kind_RESP_ERROR: {
fprintf(file_output, "error,%s\n", msg.error);
} break;
default: {
} break;
}
sbufDelete(xml_request_message);
free(xml_response_message);
free(msg.key);
free(msg.value);
free(msg.error);
}
return 0;
}
#if !defined(AUX_H_INCLUDE_GUARD)
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdalign.h>
typedef int8_t S8;
typedef int16_t S16;
typedef int32_t S32;
typedef int64_t S64;
typedef int Sint;
typedef uint8_t U8;
typedef uint16_t U16;
typedef uint32_t U32;
typedef uint64_t U64;
typedef unsigned Uint;
typedef size_t Size;
typedef uintptr_t Uptr;
typedef intptr_t Sptr;
typedef ptrdiff_t Dptr;
typedef float F32;
typedef double F64;
typedef U8 B8;
typedef U16 B16;
typedef U32 B32;
typedef U64 B64;
# define true 1U
# define false 0U
typedef unsigned char Byte;
typedef char Char;
# define elemin(array) (sizeof(array)/sizeof((array)[0]))
# if !defined(max)
# define max(x, y) ((x) >= (y) ? (x) : (y))
# endif
# if !defined(min)
# define min(x, y) ((x) <= (y) ? (x) : (y))
# endif
# define KiB(x) ( (x) * 1024ULL)
# define MiB(x) (KiB(x) * 1024ULL)
# define GiB(x) (MiB(x) * 1024ULL)
# define TiB(x) (GiB(x) * 1024ULL)
# define THOUSAND 1000L
# define MILLION 1000000L
# define BILLION 1000000000L
# define unused_variable(var) (void)var
# define global_variable static
# define persistent_value static
# define internal_function static
# define header_function static inline
#define MEM_MAX_ALIGN_MINUS_ONE (alignof(max_align_t) - 1u)
#define memAlignUp(p) (((p) + MEM_MAX_ALIGN_MINUS_ONE) & (~ MEM_MAX_ALIGN_MINUS_ONE))
#define memAlignDown(p) (mem_ALIGN_UP((p) - MEM_MAX_ALIGN_MINUS_ONE))
typedef enum MemAllocMode {
MemAllocMode_NONE,
MemAllocMode_CREATE,
MemAllocMode_ALLOC,
MemAllocMode_REALLOC,
MemAllocMode_DEALLOC,
MemAllocMode_DEALLOC_ALL,
} MemAllocMode;
# define MEM_ALLOCATOR(allocator) \
void* allocator(MemAllocMode mode, \
Size size, void* old_ptr, \
void *data)
typedef MEM_ALLOCATOR(MemAllocator);
struct MemCRT_Header {
Size size;
};
# define memCRTAlloc(size) memCRT(MemAllocMode_ALLOC, size, NULL, NULL)
# define memCRTRealloc(ptr, size) memCRT(MemAllocMode_REALLOC, size, ptr, NULL)
# define memCRTDealloc(ptr) memCRT(MemAllocMode_DEALLOC, 0, ptr, NULL)
header_function
MEM_ALLOCATOR(memCRT)
{
unused_variable(data);
switch (mode) {
case MemAllocMode_CREATE: {
// NOTE(naman): Not needed for now
} break;
case MemAllocMode_ALLOC: {
Size memory_size = memAlignUp(size);
Size header_size = memAlignUp(sizeof(struct MemCRT_Header));
Size total_size = memory_size + header_size;
Byte *mem = malloc(total_size);
struct MemCRT_Header *header = (struct MemCRT_Header *)mem;
header->size = memory_size;
Byte *result = mem + header_size;
memset(result, 0, memory_size);
return result;
} break;
case MemAllocMode_REALLOC: {
Size memory_size = memAlignUp(size);
Size header_size = memAlignUp(sizeof(struct MemCRT_Header));
Size total_size = memory_size + header_size;
Byte *mem = malloc(total_size);
struct MemCRT_Header *header = (struct MemCRT_Header *)mem;
header->size = memory_size;
Byte *result = mem + header_size;
if (old_ptr != NULL) {
Byte *previous_mem = (Byte*)old_ptr - header_size;
struct MemCRT_Header *previous_header = (struct MemCRT_Header *)previous_mem;
Size previous_size = previous_header->size;
memcpy(result, old_ptr, previous_size);
memset(result + previous_size, 0, memory_size - previous_size);
memCRTDealloc(old_ptr);
} else {
memset(result, 0, memory_size);
}
return result;
} break;
case MemAllocMode_DEALLOC: {
if (old_ptr == NULL) {
return NULL;
}
Size header_size = memAlignUp(sizeof(struct MemCRT_Header));
Byte *mem = (Byte*)old_ptr - header_size;
free(mem);
} break;
case MemAllocMode_DEALLOC_ALL: {
// TODO(naman): Maybe we should use a off-the-shelf malloc that allows this?
} break;
case MemAllocMode_NONE: {
} break;
}
return NULL;
}
/* ==============
* Strechy Buffer
*/
/* API ----------------------------------------
* Size sbufAdd (T *ptr, T elem)
* void sbufDelete (T *ptr)
* T* sbufEnd (T *ptr)
*
* Size sbufSizeof (T *ptr)
* Size sbufElemin (T *ptr)
* Size sbufMaxSizeof (T *ptr)
* Size sbufMaxElemin (T *ptr)
*/
typedef struct Sbuf_Header {
Size cap; // NOTE(naman): Maximum number of elements that can be stored
Size len; // NOTE(naman): Count of elements actually stored
Byte buf[];
} Sbuf_Header;
# define sbuf_GetHeader(sb) ((Sbuf_Header*)(void*)((Byte*)(sb) - offsetof(Sbuf_Header, buf)))
# define sbuf_Len(sb) ((sb) ? sbuf_GetHeader(sb)->len : 0U)
# define sbuf_Cap(sb) ((sb) ? sbuf_GetHeader(sb)->cap : 0U)
# define sbufAdd(sb, ...) ((sb) = sbuf_Grow((sb), sizeof(*(sb))), \
(sb)[sbuf_Len(sb)] = (__VA_ARGS__), \
((sbuf_GetHeader(sb))->len)++)
# define sbufDelete(sb) ((sb) ? \
(memCRTDealloc(sbuf_GetHeader(sb)), (sb) = NULL) : \
0)
# define sbufClear(sb) ((sb) ? \
(memset((sb), 0, sbufSizeof(sb)), \
sbuf_GetHeader(sb)->len = 0) : \
0)
# define sbufResize(sb, n) (((n) > sbufMaxElemin(sb)) ? \
((sb) = sbuf_Resize(sb, n, sizeof(*(sb)))) : \
0)
# define sbufSizeof(sb) (sbuf_Len(sb) * sizeof(*(sb)))
# define sbufElemin(sb) (sbuf_Len(sb))
# define sbufMaxSizeof(sb) (sbuf_Cap(sb) * sizeof(*(sb)))
# define sbufMaxElemin(sb) (sbuf_Cap(sb))
# define sbufEnd(sb) ((sb) + sbuf_Len(sb))
#define sbufPrint(sb, ...) ((sb) = sbuf_Print((sb), __VA_ARGS__))
#define sbufUnsortedDelete(sb, i, z) (((sb)[(i)] = (sb)[sbuf_Len(sb) - 1]), \
((sb)[sbuf_Len(sb) - 1] = (z)), \
((sbuf_GetHeader(sb)->len)--))
# if defined(COMPILER_CLANG)
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wcast-align"
# endif
header_function
void* sbuf_Grow (void *buf, Size elem_size)
{
if ((sbuf_Len(buf) + 1) <= sbuf_Cap(buf)) {
return buf;
} else {
Size new_cap = max(2 * sbuf_Cap(buf), 4);
Size new_size = (new_cap * elem_size) + sizeof(Sbuf_Header);
Sbuf_Header *new_header = NULL;
if (buf != NULL) {
new_header = memCRTRealloc(sbuf_GetHeader(buf), new_size);
} else {
new_header = memCRTAlloc(new_size);
}
new_header->cap = new_cap;
return new_header->buf;
}
}
header_function
void* sbuf_Resize (void *buf, Size elem_count, Size elem_size)
{
Size new_cap = elem_count;
Size new_size = (new_cap * elem_size) + sizeof(Sbuf_Header);
Sbuf_Header *new_header = NULL;
if (buf != NULL) {
new_header = memCRTRealloc(sbuf_GetHeader(buf), new_size);
} else {
new_header = memCRTAlloc(new_size);
}
new_header->cap = new_cap;
return new_header->buf;
}
header_function
Char* sbuf_Print(Char *buf, const Char *fmt, ...)
{
va_list args;
va_start(args, fmt);
Size cap = sbufMaxElemin(buf) - sbufElemin(buf);
Size n = 1 + vsnprintf(sbufEnd(buf), cap, fmt, args);
va_end(args);
if (n > cap) {
sbufResize(buf, n + sbufElemin(buf));
va_start(args, fmt);
size_t new_cap = sbufMaxElemin(buf) - sbufElemin(buf);
n = 1 + vsnprintf(sbufEnd(buf), new_cap, fmt, args);
va_end(args);
}
sbuf_GetHeader(buf)->len += (n - 1);
return buf;
}
header_function
U64 hashFNV1a (Char *str)
{
U64 hash = 0xcbf29ce484222325; // FNV_offset_basis
for (Size i = 0; str[i] != '\0'; i++) {
hash = hash ^ str[i];
hash = hash * 0x100000001b3; // FNV_prime
}
return hash;
}
/* __builtin_clzll(x) returns the leading number of 0-bits in x, starting from
* most significant position.
*
* If b is the bit-width of the number,
* p is the closest lower power of two and
* lz is the number of leading 0-bits; then
* then a number between 2^p and 2^(p+1) has the form: (b-p-1 0-bits) 1 (p bits)
*
* => lz = b-p-1
* => p = b-(lz+1)
*
* Thus, the rounded-down log2 of the number is b-(lz+1).
*/
header_function
U64 u64Log2(U64 x)
{
U64 result = 64ULL - ((U64)__builtin_clzll(x) + 1ULL);
return result;
}
/* Linear Congruential Generator
*
* If x is the last random number,
* m is a number greater than zero that is a power of two,
* a is a number between 0 and m,
* then the next random number is ((x * a) % m).
*
* Unfortunately, the lower bits don't have enought randomness in them. The LSB doesn't
* change at all, the second LSB alternates, the one after that toggles every 2 turns and so
* on. Therefore, we try to get rid of the LSBs by pulling in some MSBs.
*
* We do the multiplcation twice because Chi-Square Test indicated that this method
* gives better randomness. Don't ask.
*
* NOTE(naman): Seed should be an odd number or the randomness might drop drastically.
*/
header_function
U64 u64Rand (U64 seed)
{
U64 previous = seed;
if (previous == 0) {
// This seed has been tested and should be preferred in normal circumstances.
previous = 2531011ULL;
}
U64 a = 214013ULL;
__uint128_t product = (__uint128_t)previous * (__uint128_t)a;
U64 upper = product >> 64, lower = (U64)product;
U64 log_upper = u64Log2(upper);
U64 shift_amount = 64 - (log_upper + 1);
upper = (upper << shift_amount) | (lower >> log_upper);
U64 result = upper * a;
return result;
}
/* ==========================
* Pointer-Pointer Hash Map
*/
typedef struct Hashmap {
struct HashmapKeys {
U64 hash;
Uptr key;
} *keys;
Uptr *values;
MemAllocator *allocator;
Size total_slots, filled_slots;
U64 a, b, m; /* Hashing constants */
U64 r; /* Last random number */
} Hashmap;
typedef enum HM_Flag {
HM_Flag_EMPTY,
HM_Flag_FILLED,
HM_Flag_VACATED, // Previously filled
} HM_Flag;
/* API ---------------------------------------------------
* Hashmap hmCreate (MemAllocator allocator,
* Size min_slots);
* void hmDelete (Hashmap hm);
* void* hmInsert (Hashmap *hm, void *key, void *value);
* Uptr hmInsertI (Hashmap *hm, Uptr key, Uptr value);
* void* hmLookup (Hashmap *hm, void *key);
* Uptr hmLookupI (Hashmap *hm, Uptr key);
* void* hmRemove (Hashmap *hm, void *key);
* Uptr hmRemoveI (Hashmap *hm, Uptr key);
*/
#define hm_GetFlag(hash) ((hash) >> 62)
#define hm_GetHash(hash) ((hash) & 0x3FFFFFFFFFFFFFFFULL)
#define hm_SetFlag(hash, flag) (hm_GetHash(hash) | (((U64)(flag)) << 62))
/*
* https://en.wikipedia.org/wiki/Universal_hashing#Avoiding_modular_arithmetic
* w is number of bits in machine word (64 in our case)
* s is the number of buckets/bins (slots in the hash table) to which the
* universe of hashable objects is to be mapped
* m is log2(s) (=> m = 2^s) and is equal to the number of bits in the final hash
* a is a random odd positive integer < 2^w (fitting in w bits)
* b is a random non-negative integer < 2^(w-m) (fitting in (w-m) bits)
*
* r is the last random number generated and is just an implementation detail.
*/
header_function
void hm_UpdateConstants (Hashmap *hm)
{
hm->m = hm->m + 1;
hm->total_slots = 1ULL << (hm->m);
do {
hm->r = u64Rand(hm->r);
hm->a = hm->r;
} while ((hm->a & 0x01) != 0x01); // Make sure that hm.a is odd
hm->r = u64Rand(hm->r);
// b should be (64 - m) bits long
hm->b = hm->r >> hm->m;
}
header_function
U64 hm_Hash (Hashmap *hm, Uptr key)
{
U64 result = ((hm->a * key) + hm->b) & (0xFFFFFFFFFFFFFFFFULL >> (64 - hm->m));
return result;
}
header_function
Hashmap hmCreate (MemAllocator allocator,
Size min_slots)
{
Hashmap hm = {0};
hm.allocator = allocator;
hm.m = u64Log2(min_slots); // Log of closest lower power of two
// This will make m log of closest upper power of two
hm_UpdateConstants(&hm);
hm.keys = allocator(MemAllocMode_ALLOC,
(hm.total_slots) * sizeof(*(hm.keys)),
NULL, NULL);
hm.values = allocator(MemAllocMode_ALLOC,
(hm.total_slots) * sizeof(*(hm.values)),
NULL, NULL);
// For NULL keys
hm.keys[0].hash = hm_SetFlag(hm.keys[0].hash, HM_Flag_FILLED);
hm.filled_slots = 1;
return hm;
}
header_function
void hmDelete (Hashmap hm)
{
hm.allocator(MemAllocMode_DEALLOC, 0, hm.keys, NULL);
hm.allocator(MemAllocMode_DEALLOC, 0, hm.values, NULL);
}
header_function
Size hm_LinearProbeSearch (Hashmap *hm, Uptr key)
{
if (key == 0) return 0;
U64 hash = hm_Hash(hm, key);
U64 hash_real = hm_GetHash(hash);
Size index = 0, i = 0;