From 1e5eeb704f51d2535b4b1a7440aaf8f5dc68f5ff Mon Sep 17 00:00:00 2001 From: Theo Tappe Date: Wed, 10 Jun 2026 16:58:35 +0200 Subject: [PATCH] First Commit --- .gitignore | 2 + CMakeLists.txt | 35 + all.txt | 1611 ++++++++++++++++++++++++++++++++++ compile_commands.json | 1 + description.md | 24 + plan.docx | Bin 0 -> 27533 bytes shell.nix | 18 + src/client/client.c | 190 ++++ src/client/scanner.c | 72 ++ src/client/scanner.h | 14 + src/server/server.c | 104 +++ src/shared/array_list.c | 82 ++ src/shared/array_list.h | 20 + src/shared/chunk.c | 263 ++++++ src/shared/chunk.h | 50 ++ src/shared/config.c | 62 ++ src/shared/config.h | 27 + src/shared/log.c | 25 + src/shared/log.h | 13 + src/shared/multiprocessing.c | 66 ++ src/shared/multiprocessing.h | 41 + src/shared/pipeline.h | 6 + src/shared/queue.c | 134 +++ src/shared/queue.h | 31 + src/shared/socket.c | 211 +++++ src/shared/socket.h | 51 ++ src/shared/utils.c | 77 ++ src/shared/utils.h | 9 + test.py | 91 ++ tests/runner.c | 32 + tests/test_array_list.c | 64 ++ tests/test_array_list.h | 6 + tests/test_chunk.c | 147 ++++ tests/test_chunk.h | 6 + tests/test_config.c | 58 ++ tests/test_config.h | 6 + tests/test_queue.c | 206 +++++ tests/test_queue.h | 6 + tests/test_shared_utils.c | 62 ++ tests/test_shared_utils.h | 6 + tests/test_utils.h | 94 ++ to_one_file.py | 14 + 42 files changed, 4037 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 all.txt create mode 120000 compile_commands.json create mode 100644 description.md create mode 100644 plan.docx create mode 100644 shell.nix create mode 100644 src/client/client.c create mode 100644 src/client/scanner.c create mode 100644 src/client/scanner.h create mode 100644 src/server/server.c create mode 100644 src/shared/array_list.c create mode 100644 src/shared/array_list.h create mode 100644 src/shared/chunk.c create mode 100644 src/shared/chunk.h create mode 100644 src/shared/config.c create mode 100644 src/shared/config.h create mode 100644 src/shared/log.c create mode 100644 src/shared/log.h create mode 100644 src/shared/multiprocessing.c create mode 100644 src/shared/multiprocessing.h create mode 100644 src/shared/pipeline.h create mode 100644 src/shared/queue.c create mode 100644 src/shared/queue.h create mode 100644 src/shared/socket.c create mode 100644 src/shared/socket.h create mode 100644 src/shared/utils.c create mode 100644 src/shared/utils.h create mode 100644 test.py create mode 100644 tests/runner.c create mode 100644 tests/test_array_list.c create mode 100644 tests/test_array_list.h create mode 100644 tests/test_chunk.c create mode 100644 tests/test_chunk.h create mode 100644 tests/test_config.c create mode 100644 tests/test_config.h create mode 100644 tests/test_queue.c create mode 100644 tests/test_queue.h create mode 100644 tests/test_shared_utils.c create mode 100644 tests/test_shared_utils.h create mode 100644 tests/test_utils.h create mode 100644 to_one_file.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9e1a350 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build +data_copied diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..d89958c --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,35 @@ +cmake_minimum_required(VERSION 4.1) + +project(FastFileTransfer) + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +set(CMAKE_C_STANDARD 11) +set(CMAKE_C_STANDARD_REQUIRED ON) + +add_compile_options(-Wall -g -O3) + +set(THREADS_PREFER_PTHREAD_FLAG ON) +find_package(Threads REQUIRED) + +find_library(ZSTD_LIBRARY zstd) +if(NOT ZSTD_LIBRARY) + message(FATAL_ERROR "zstd library not found. Ensure it is in your nix-shell!") +endif() + +file(GLOB SHARED_SRCS "src/shared/*.c") +file(GLOB SERVER_SRCS "src/server/*.c") +file(GLOB CLIENT_SRCS "src/client/*.c") +file(GLOB TEST_SRCS "tests/*.c") + +add_executable(server ${SERVER_SRCS} ${SHARED_SRCS}) +target_include_directories(server PRIVATE src/shared src/server src/client) +target_link_libraries(server PRIVATE Threads::Threads ${ZSTD_LIBRARY}) + +add_executable(client ${CLIENT_SRCS} ${SHARED_SRCS}) +target_include_directories(client PRIVATE src/shared src/server src/client) +target_link_libraries(client PRIVATE Threads::Threads ${ZSTD_LIBRARY}) + +add_executable(tests ${TEST_SRCS} ${SHARED_SRCS}) +target_include_directories(tests PRIVATE tests src/shared src/server src/client) +target_link_libraries(tests PRIVATE Threads::Threads ${ZSTD_LIBRARY}) + diff --git a/all.txt b/all.txt new file mode 100644 index 0000000..21b1993 --- /dev/null +++ b/all.txt @@ -0,0 +1,1611 @@ +--- src/client/scanner.h --- + +#ifndef SCANNER_H +#define SCANNER_H + +#include "chunk.h" +#include "queue.h" +typedef struct { + Queue *directories; +} DirectoryScanner; + +DirectoryScanner *directory_scanner_create(char *root_directory); +Chunk *directory_scanner_next(DirectoryScanner *scanner); +void directory_scanner_destroy(DirectoryScanner *scanner); + +#endif +--- src/shared/queue.h --- + +#ifndef QUEUE_H +#define QUEUE_H + +#include +#include + +typedef struct Queue { + void **items; + int front; + int rear; + int size; + int capacity; + void (*item_destroyer)(void *item); +} Queue; + +Queue *queue_create(int capacity, void (*destroyer)(void *item)); +void queue_destroy(Queue *queue); +bool queue_is_empty(Queue *queue); +bool queue_is_full(Queue *queue); +void queue_double_capacity(Queue *queue); +void queue_enqueue(Queue *queue, void *item); +void queue_enqueue_multithreaded(Queue *queue, void *item, mtx_t *mutex, + cnd_t *condition_not_empty, + cnd_t *condition_not_full); +void *queue_dequeue(Queue *queue); +void *queue_dequeue_multithreaded(Queue *queue, mtx_t *mutex, + cnd_t *condition_not_empty, + cnd_t *condition_not_full, + bool *other_thread_done); + +#endif +--- src/shared/config.h --- + +#ifndef CONFIG_H +#define CONFIG_H + +#include + +typedef struct Config { + char *version; + char *send_directory; + char *receive_root_directory; + bool save_to_disk; + bool use_multithreading; + bool use_chunk_serialization; + bool use_compression; + bool use_multiprocessing; + int num_connections; +} Config; + +Config *config_create(char *version, char *send_directory, + char *receive_directory, bool save_to_disk, + bool use_multithreading, bool use_chunk_serialization, + bool use_compression, bool use_multiprocessing, + int num_connections); +void config_delete(Config *config); +void config_send(int file_descriptor, Config *config); +Config *config_receive(int file_descriptor); + +#endif +--- src/shared/array_list.h --- + +#ifndef ARRAY_LIST_H +#define ARRAY_LIST_H + +#define INITIAL_ARRAY_SIZE 100 + +typedef struct ArrayList { + void **items; + int size; + int capacity; + void (*item_destroyer)(void *item); +} ArrayList; + +ArrayList *array_list_create(void (*item_destroyer)(void *item)); +void array_list_delete(ArrayList *array_list); +void array_list_clear(ArrayList *array_list); +void array_list_extend(ArrayList *array_list); +void array_list_add(ArrayList *array_list, void *item); +void **array_list_to_array(ArrayList *array_list); + +#endif +--- src/shared/log.h --- + +#ifndef LOG_H +#define LOG_H + +typedef enum { + LOG_LEVEL_DEBUG, + LOG_LEVEL_INFO, + LOG_LEVEL_WARNING, + LOG_LEVEL_ERROR +} LogLevel; + +void log_message(LogLevel log_level, char *message, ...); + +#endif +--- src/shared/chunk.h --- + +#ifndef CHUNK_H +#define CHUNK_H + +#include "socket.h" +#include + +#define DESIRED_CHUNK_SIZE 10 * 1024 * 1024 +#define FILE_PATH_SEPERATOR "#&&SEPP&&#" +#define FILE_PATH_DATA_SEPERATOR "#&&SEPD&&#" + +typedef struct { + char *path; + struct stat stats; + char *data; +} File; + +typedef struct { + char *path; + DataFragment *data_fragment; +} FileReceive; + +typedef struct { + File **items; + int element_count; +} Chunk; + +typedef struct { + void *data; + unsigned long long data_size; +} Data; + +File *file_create(const char *path, struct stat *stats); +void file_destroy(void *item); +void file_load_data(File *file); +void file_print(void *item); +void file_content_to_buffer(File *file, char *buffer); + +FileReceive *file_receive_create(char *path, DataFragment *data_fragment); +void file_receive_destroy(void *file_receive); + +Chunk *chunk_create(File **items, int element_count); +void chunk_destroy(void *chunk); +void chunk_print(void *chunk); +Data *chunk_format(Chunk *chunk); +Data *chunk_compress(Chunk *chunk); + +Data *chunk_data_create(void *data, unsigned long long data_size); +void chunk_data_delete(void *chunk); +void chunk_data_to_disk(Data *chunk, char *root_directory); +#endif +--- src/shared/utils.h --- + +#ifndef UTILS_H +#define UTILS_H + +void mkdir_r(char *path); +char *str_dup(char *string); +void to_disk(char *path, void *data, unsigned long long data_size); +char *path_cat(char *path1, char *path2); + +#endif +--- src/shared/multiprocessing.h --- + +#ifndef MULTIPROCESSING_H +#define MULTIPROCESSING_H + +#include + +#include "config.h" +#include "queue.h" + +typedef struct { + Config *config; + Queue *queue_scanner; + mtx_t mutex_scanner; + cnd_t condition_not_full_scanner; + cnd_t condition_not_empty_scanner; + bool scanner_done; + Queue *queue_loader; + mtx_t mutex_loader; + cnd_t condition_not_full_loader; + cnd_t condition_not_empty_loader; + bool loader_done; +} PipelineContextSender; + +typedef struct PipelineContextReceiver { + Queue *queue; + Config *config; + int file_descriptor; + mtx_t mutex; + cnd_t condition_not_full; + cnd_t condition_not_empty; + bool receiver_done; +} PipelineContextReceiver; + +PipelineContextSender *pipeline_context_sender_create(Config *config, + Queue *queue_scanner, + Queue *queue_loader); +void pipeline_context_sender_destroy(PipelineContextSender *context); +PipelineContextReceiver *pipeline_context_receiver_create(Config *config, + Queue *queue_receiver, + int file_descriptor); +void pipeline_context_receiver_destroy(PipelineContextReceiver *context); +#endif +--- src/shared/socket.h --- + +#ifndef SOCKET_H +#define SOCKET_H + +#include "array_list.h" +#include + +typedef unsigned long long NET_SIZE; +typedef int Status; +enum NET_STATUS { OK, ERROR, FINISHED, NEXT }; + +typedef struct Server { + struct sockaddr_in address; + unsigned int address_length; + int file_descriptor; +} Server; + +Server *server_create(int port); +void server_listen(Server *server, void (*handler)(int file_descriptor)); +void server_delete(Server *server); + +typedef struct Client { + struct sockaddr_in address; + unsigned int address_length; + int file_descriptor; +} Client; + +typedef struct DataFragment { + void *data; + unsigned long long size; +} DataFragment; + +Client *client_create(); +void client_disconnect(Client *client); +void client_delete(Client *client); +void client_connect(Client *client, char *host, int port); + +DataFragment *data_fragment_create(void *data, unsigned long long size); +void data_fragment_delete(void *data_fragment); + +void send_n_data(int file_descriptor, void *data, NET_SIZE data_size); +void receive_n_data(int file_descriptor, void *data, NET_SIZE data_size); +void send_str(int file_descriptor, char *data); +char *receive_str(int file_descriptor); +void send_data(int file_descriptor, void *data, unsigned long long data_size); +DataFragment *receive_data(int file_descriptor); +void send_int(int file_descriptor, int data); +int receive_int(int file_descriptor); +void send_status(int file_descriptor, Status status); +Status receive_status(int file_descriptor); + +#endif +--- src/client/client.c --- + +#include +#include +#include +#include + +#include "chunk.h" +#include "config.h" +#include "log.h" +#include "multiprocessing.h" +#include "queue.h" +#include "scanner.h" +#include "socket.h" +#include "utils.h" +#include + +int send_chunk(Client *client, Chunk *chunk, bool use_compression) { + if (use_compression) { + Data *data = chunk_compress(chunk); + send_data(client->file_descriptor, data->data, data->data_size); + } else { + for (int i = 0; i < chunk->element_count; i++) { + send_status(client->file_descriptor, NEXT); + File *file = chunk->items[i]; + send_str(client->file_descriptor, file->path); + send_data(client->file_descriptor, file->data, file->stats.st_size); + } + send_status(client->file_descriptor, FINISHED); + if (receive_status(client->file_descriptor) != OK) + return -1; + } + return 0; +} + +int scan_directory_multithreaded(void *pipeline_context) { + PipelineContextSender *context = (PipelineContextSender *)pipeline_context; + mtx_lock(&context->mutex_scanner); + DirectoryScanner *scanner = + directory_scanner_create(context->config->send_directory); + mtx_unlock(&context->mutex_scanner); + + Chunk *current_chunk; + while ((current_chunk = directory_scanner_next(scanner)) != NULL) + queue_enqueue_multithreaded(context->queue_scanner, current_chunk, + &context->mutex_scanner, + &context->condition_not_empty_scanner, + &context->condition_not_full_scanner); + mtx_lock(&context->mutex_scanner); + context->scanner_done = true; + cnd_signal(&context->condition_not_empty_scanner); + mtx_unlock(&context->mutex_scanner); + + directory_scanner_destroy(scanner); + return thrd_success; +} + +int load_files_multithreaded(void *pipeline_context) { + PipelineContextSender *context = (PipelineContextSender *)pipeline_context; + while (true) { + Chunk *chunk = queue_dequeue_multithreaded( + context->queue_scanner, &context->mutex_scanner, + &context->condition_not_empty_scanner, + &context->condition_not_full_scanner, &context->scanner_done); + if (chunk == NULL) { + mtx_lock(&context->mutex_loader); + context->loader_done = true; + cnd_signal(&context->condition_not_empty_loader); + mtx_unlock(&context->mutex_loader); + return thrd_success; + } + for (int i = 0; i < chunk->element_count; i++) + file_load_data(chunk->items[i]); + queue_enqueue_multithreaded(context->queue_loader, chunk, + &context->mutex_loader, + &context->condition_not_empty_loader, + &context->condition_not_full_loader); + } +} + +int send_chunks_multithreaded(void *pipeline_context) { + PipelineContextSender *context = (PipelineContextSender *)pipeline_context; + bool use_compression = context->config->use_compression; + Client *client = client_create(); + client_connect(client, "127.0.0.1", 8080); + config_send(client->file_descriptor, context->config); + + while (true) { + Chunk *current_chunk = queue_dequeue_multithreaded( + context->queue_loader, &context->mutex_loader, + &context->condition_not_empty_loader, + &context->condition_not_full_loader, &context->loader_done); + if (current_chunk == NULL) { + client_disconnect(client); + client_delete(client); + return thrd_success; + } + send_chunk(client, current_chunk, use_compression); + chunk_destroy(current_chunk); + } +} + +int send_files(Config *config) { + Client *client = client_create(); + client_connect(client, "127.0.0.1", 8080); + config_send(client->file_descriptor, config); + DirectoryScanner *scanner = directory_scanner_create(config->send_directory); + Chunk *current_chunk; + while ((current_chunk = directory_scanner_next(scanner)) != NULL) { + chunk_print(current_chunk); + for (int i = 0; i < current_chunk->element_count; i++) + file_load_data(current_chunk->items[i]); + send_chunk(client, current_chunk, config->use_compression); + chunk_destroy(current_chunk); + } + printf("FINISHED"); + directory_scanner_destroy(scanner); + client_disconnect(client); + client_delete(client); + return 0; +} + +void handle_arg(char *argument_given, char *argument_to_set, bool *result, + char *message) { + if (strcmp(argument_given, argument_to_set) == 0) { + *result = true; + log_message(LOG_LEVEL_INFO, message); + } +} + +int send_files_multithreaded(Config *config) { + PipelineContextSender *context = + pipeline_context_sender_create(config, queue_create(100, chunk_destroy), + queue_create(100, chunk_destroy)); + + thrd_t scanner, loader, sender; + if (thrd_create(&scanner, scan_directory_multithreaded, context) != + thrd_success || + thrd_create(&loader, load_files_multithreaded, context) != thrd_success || + thrd_create(&sender, send_chunks_multithreaded, context) != + thrd_success) { + perror("Error creating threads.\n"); + return 1; + } + + thrd_join(scanner, NULL); + thrd_join(loader, NULL); + thrd_join(sender, NULL); + + pipeline_context_sender_destroy(context); + return 0; +} + +int send_files_multiprocessed(Config *config) { + int cores = sysconf(_SC_NPROCESSORS_ONLN); + int pid = fork(); + if (pid == -1) { + perror("Error Forking!"); + return 1; + } else if (pid == 0) { + } + for (int i = 0; i < cores; i++) { + int pid = fork(); + if (pid == -1) { + perror("Error forking!"); + return 1; + } else if (pid == 0) { + } + } + return 0; +} + +int main(int argc, char *argv[]) { + Config *config = config_create( + str_dup("1.0.0"), str_dup("/home/taptap/Nextcloud/Uni/moodle/MINT-Raum"), + str_dup("./data_copied"), false, false, false, false, false, 1); + for (int i = 1; i < argc; i++) { + handle_arg(argv[i], "-m", &config->use_multithreading, + "Enabled Multithreading"); + handle_arg(argv[i], "-s", &config->use_chunk_serialization, + "Enabled Chunk Serialization"); + handle_arg(argv[i], "-c", &config->use_compression, "Enabled Compression"); + handle_arg(argv[i], "-p", &config->use_multiprocessing, + "Enabled Multiprocessing"); + } + + if (config->use_multithreading) + return send_files_multithreaded(config); + else if (config->use_multiprocessing) + return send_files_multiprocessed(config); + return send_files(config); +} +--- src/client/scanner.c --- + +#include "scanner.h" +#include "array_list.h" +#include "chunk.h" +#include "queue.h" +#include "utils.h" +#include +#include +#include +#include + +DirectoryScanner *directory_scanner_create(char *root_directory) { + DirectoryScanner *scanner = malloc(sizeof(DirectoryScanner)); + scanner->directories = queue_create(100, free); + queue_enqueue(scanner->directories, str_dup(root_directory)); + return scanner; +} + +void directory_scanner_destroy(DirectoryScanner *scanner) { + if (scanner == NULL) + return; + queue_destroy(scanner->directories); + free(scanner); +} + +Chunk *chunk_data_to_chunk(ArrayList *chunk_data) { + void **chunk_items = array_list_to_array(chunk_data); + Chunk *chunk = chunk_create((File **)chunk_items, chunk_data->size); + free(chunk_items); + chunk_data->item_destroyer = NULL; + array_list_delete(chunk_data); + return chunk; +} + +Chunk *directory_scanner_next(DirectoryScanner *scanner) { + ArrayList *chunk_data = array_list_create(file_destroy); + unsigned long long chunk_data_size = 0; + + while (!queue_is_empty(scanner->directories)) { + char *path = (char *)queue_dequeue(scanner->directories); + DIR *dir; + struct dirent *entry; + dir = opendir(path); + if (dir == NULL) { + perror("Could not open directory!"); + exit(EXIT_FAILURE); + } + printf("%s", path); + while ((entry = readdir(dir)) != NULL) { + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { + continue; + } + char *cur_path = path_cat(path, entry->d_name); + struct stat stats; + stat(cur_path, &stats); + if (!S_ISREG(stats.st_mode)) + queue_enqueue(scanner->directories, (void *)cur_path); + else { + File *file = file_create(cur_path, &stats); + array_list_add(chunk_data, file); + chunk_data_size += file->stats.st_size; + if (chunk_data_size > DESIRED_CHUNK_SIZE) + return chunk_data_to_chunk(chunk_data); + free(cur_path); + } + } + closedir(dir); + free(path); + } + if (chunk_data->size > 0) + return chunk_data_to_chunk(chunk_data); + return NULL; +} +--- src/shared/multiprocessing.c --- + +#include "multiprocessing.h" +#include "config.h" +#include "queue.h" +#include +#include +#include + +PipelineContextSender *pipeline_context_sender_create(Config *config, + Queue *queue_scanner, + Queue *queue_loader) { + PipelineContextSender *context = malloc(sizeof(PipelineContextSender)); + context->config = config; + context->queue_scanner = queue_scanner; + context->queue_loader = queue_loader; + context->scanner_done = false; + context->loader_done = false; + if (mtx_init(&context->mutex_scanner, mtx_plain) != thrd_success || + cnd_init(&context->condition_not_full_scanner) != thrd_success || + cnd_init(&context->condition_not_empty_scanner) != thrd_success || + mtx_init(&context->mutex_loader, mtx_plain) != thrd_success || + cnd_init(&context->condition_not_full_loader) != thrd_success || + cnd_init(&context->condition_not_empty_loader) != thrd_success) { + perror("Error initializing synchronization objects!"); + exit(EXIT_FAILURE); + } + return context; +} + +void pipeline_context_sender_destroy(PipelineContextSender *context) { + config_delete(context->config); + queue_destroy(context->queue_scanner); + queue_destroy(context->queue_loader); + mtx_destroy(&context->mutex_scanner); + cnd_destroy(&context->condition_not_full_scanner); + cnd_destroy(&context->condition_not_empty_scanner); + mtx_destroy(&context->mutex_loader); + cnd_destroy(&context->condition_not_full_loader); + cnd_destroy(&context->condition_not_empty_loader); + free(context); +} + +PipelineContextReceiver *pipeline_context_receiver_create(Config *config, + Queue *queue, + int file_descriptor) { + PipelineContextReceiver *context = malloc(sizeof(PipelineContextReceiver)); + context->config = config; + context->queue = queue; + context->file_descriptor = file_descriptor; + context->receiver_done = false; + if (mtx_init(&context->mutex, mtx_plain) != thrd_success || + cnd_init(&context->condition_not_full) != thrd_success || + cnd_init(&context->condition_not_empty) != thrd_success) { + perror("Error initializing synchronization objects!"); + exit(EXIT_FAILURE); + } + return context; +} + +void pipeline_context_receiver_destroy(PipelineContextReceiver *context) { + config_delete(context->config); + queue_destroy(context->queue); + mtx_destroy(&context->mutex); + cnd_destroy(&context->condition_not_full); + cnd_destroy(&context->condition_not_empty); + free(context); +} +--- src/shared/array_list.c --- + +#include "array_list.h" +#include +#include +#include + +ArrayList *array_list_create(void (*item_destroyer)(void *item)) { + ArrayList *list = (ArrayList *)malloc(sizeof(ArrayList)); + if (list == NULL) { + perror("FATAL ERROR: Could not allocate memory for array list struct"); + exit(EXIT_FAILURE); + } + + list->items = malloc(INITIAL_ARRAY_SIZE * sizeof(void *)); + if (list->items == NULL) { + perror("FATAL ERROR: Could not allocate memory for list items"); + free(list); + exit(EXIT_FAILURE); + } + list->size = 0; + list->capacity = INITIAL_ARRAY_SIZE; + list->item_destroyer = item_destroyer; + return list; +} + +void array_list_delete(ArrayList *array_list) { + if (array_list == NULL) + return; + if (array_list->item_destroyer != NULL) { + for (int i = 0; i < array_list->size; i++) { + array_list->item_destroyer(array_list->items[i]); + array_list->items[i] = NULL; + } + } + free(array_list->items); + free(array_list); +} + +void array_list_clear(ArrayList *array_list) { + if (array_list == NULL) + return; + for (int i = 0; i < array_list->size; i++) + array_list->items[i] = NULL; + array_list->size = 0; +} + +void array_list_extend(ArrayList *array_list) { + if (array_list == NULL) + return; + int new_capacity = array_list->capacity * 2; + if (new_capacity == 0) + new_capacity = INITIAL_ARRAY_SIZE; + array_list->items = realloc(array_list->items, new_capacity * sizeof(void *)); + if (array_list->items == NULL) { + perror("FATAL ERROR: Could not reallocate memory for array list struct"); + exit(EXIT_FAILURE); + } + array_list->capacity = new_capacity; +} + +void array_list_add(ArrayList *array_list, void *item) { + if (array_list == NULL) { + return; + } + if (array_list->capacity == array_list->size) { + array_list_extend(array_list); + } + array_list->items[array_list->size] = item; + array_list->size += 1; +} + +void **array_list_to_array(ArrayList *array_list) { + if (array_list == NULL) { + return NULL; + } + void **array = malloc(array_list->size * sizeof(void *)); + if (array == NULL) { + perror("Could not malloc space for array from array list!"); + return NULL; + } + memcpy(array, array_list->items, array_list->size * sizeof(void *)); + return array; +} +--- src/shared/utils.c --- + +#include "utils.h" +#include "libgen.h" +#include "sys/stat.h" +#include +#include +#include + +void mkdir_r(char *path) { + char *path_duplicate = malloc(strlen(path) + 1); + strcpy(path_duplicate, path); + char *path_current = (char *)malloc((strlen(path) + 2) * sizeof(char)); + char *path_current_position = path_current; + const char *delimiter = "/"; + char *part = strtok(path_duplicate, delimiter); + // struct stat st; + while (part != NULL) { + strcpy(path_current_position, part); + path_current_position += strlen(part) * sizeof(char); + strcpy(path_current_position, "/"); + path_current_position += sizeof(char); + struct stat st; + if (stat(path_current, &st) != 0) { + if (mkdir(path_current, 0755) != 0) { + perror("Could not create directory"); + exit(EXIT_FAILURE); + } + } + part = strtok(NULL, delimiter); + } + free(path_duplicate); + free(path_current); +} + +char *str_dup(char *string) { + if (string == NULL) + return NULL; + char *new_string = (char *)malloc(strlen(string) + 1); + strcpy(new_string, string); + return new_string; +} + +void to_disk(char *path, void *data, unsigned long long data_size) { + char *directory = str_dup(path); + directory = dirname(directory); + mkdir_r(directory); + FILE *file_pointer = fopen(path, "wb"); + if (file_pointer == NULL) { + perror("Could not open File"); + exit(EXIT_FAILURE); + } + fwrite(data, 1, data_size, file_pointer); + fclose(file_pointer); + free(directory); +} + +char *path_cat(char *path1, char *path2) { + if (path1 == NULL || *path1 == '\0') + return str_dup(path2); + if (path2 == NULL || *path2 == '\0') + return str_dup(path1); + int path1_len = strlen(path1); + int path2_len = strlen(path2); + char *path2_pointer = path2; + if (path1[path1_len - 1] == '/') + path1_len -= 1; + if (path2[0] == '/') { + path2_pointer += 1; + path2_len -= 1; + } + char *new_path = malloc(path1_len + path2_len + 2); + memcpy(new_path, path1, path1_len); + new_path[path1_len] = '/'; + memcpy(new_path + path1_len + 1, path2_pointer, path2_len); + new_path[path1_len + path2_len + 1] = '\0'; + return new_path; +} +--- src/shared/queue.c --- + +#include +#include +#include +#include +#include + +#include "queue.h" + +Queue *queue_create(int capacity, void (*destroyer)(void *item)) { + Queue *queue = (Queue *)malloc(sizeof(Queue)); + if (queue == NULL) { + perror("FATAL ERROR: Could not allocate memory for queue structure"); + exit(EXIT_FAILURE); + } + + queue->items = malloc(capacity * sizeof(void *)); + if (queue->items == NULL) { + perror("FATAL ERROR: Could not allocate memory for queue items"); + free(queue); + exit(EXIT_FAILURE); + } + + for (int i = 0; i < capacity; ++i) { + queue->items[i] = NULL; + } + + queue->capacity = capacity; + queue->front = 0; + queue->rear = 0; + queue->size = 0; + queue->item_destroyer = destroyer; + + return queue; +} + +void queue_destroy(Queue *queue) { + if (queue == NULL) + return; + + for (int i = 0; i < queue->size; ++i) { + int index = (queue->front + i) % queue->capacity; + queue->item_destroyer(queue->items[index]); + } + free(queue->items); + free(queue); +} + +bool queue_is_empty(Queue *queue) { + if (queue == NULL) + return true; + return queue->size == 0; +} + +bool queue_is_full(Queue *queue) { + if (queue == NULL) + return false; + return queue->size == queue->capacity; +} + +void queue_double_capacity(Queue *queue) { + if (queue == NULL) + return; + unsigned int new_capacity = queue->capacity * 2; + if (new_capacity <= 1) + new_capacity = 100; + void **new_items = malloc(new_capacity * sizeof(void *)); + if (new_items == NULL) { + perror("FATAL ERROR: Could not allocate memory for doubling capacity of " + "queue."); + exit(EXIT_FAILURE); + } + for (int i = 0; i < queue->size; i++) + new_items[i] = queue->items[(i + queue->front) % queue->capacity]; + free(queue->items); + queue->items = new_items; + queue->front = 0; + queue->rear = queue->size; + queue->capacity = new_capacity; +} + +void queue_enqueue(Queue *queue, void *item) { + if (queue == NULL || item == NULL) { + perror("ERROR: Cannot enqueue with a null queue or item.\n"); + exit(EXIT_FAILURE); + } + if (queue_is_full(queue)) + queue_double_capacity(queue); + queue->items[queue->rear] = item; + queue->rear = (queue->rear + 1) % queue->capacity; + queue->size++; +} + +void queue_enqueue_multithreaded(Queue *queue, void *item, mtx_t *mutex, + cnd_t *condition_not_empty, + cnd_t *condition_not_full) { + mtx_lock(mutex); + while (queue_is_full(queue)) + cnd_wait(condition_not_full, mutex); + queue_enqueue(queue, item); + cnd_signal(condition_not_empty); + mtx_unlock(mutex); +} + +void *queue_dequeue(Queue *queue) { + if (queue == NULL || queue_is_empty(queue)) { + perror("ERROR: Could not dequeue from null or empty queue."); + return NULL; + } + + void *item = queue->items[queue->front]; + queue->items[queue->front] = NULL; + queue->front = (queue->front + 1) % queue->capacity; + queue->size--; + return item; +} + +void *queue_dequeue_multithreaded(Queue *queue, mtx_t *mutex, + cnd_t *condition_not_empty, + cnd_t *condition_not_full, + bool *other_thread_done) { + mtx_lock(mutex); + while (queue_is_empty(queue) && !*other_thread_done) + cnd_wait(condition_not_empty, mutex); + if (queue_is_empty(queue) && *other_thread_done) { + mtx_unlock(mutex); + return NULL; + } + void *item = queue_dequeue(queue); + cnd_signal(condition_not_full); + mtx_unlock(mutex); + return item; +} +--- src/shared/chunk.c --- + +#include +#include +#include +#include +#include +#include + +#include "chunk.h" +#include "log.h" +#include "socket.h" + +File *file_create(const char *path, struct stat *stats) { + File *file = (File *)malloc(sizeof(File)); + if (file == NULL) { + perror("FATAL ERROR: Could not allocate memory for file struct"); + exit(EXIT_FAILURE); + } + + file->stats = *stats; + + int path_len = strlen(path); + file->path = (char *)malloc(path_len + 1); + if (file->path == NULL) { + perror("FATAL ERROR: Could not allocate memory for path file string"); + free(file); + exit(EXIT_FAILURE); + } + + strcpy(file->path, path); + file->data = NULL; + return file; +} + +void file_destroy(void *item) { + if (item == NULL) + return; + File *file = (File *)item; + free(file->data); + file->data = NULL; + free(file->path); + file->path = NULL; + free(file); +} + +void file_load_data(File *file) { + if (file == NULL) + return; + file->data = malloc(file->stats.st_size); + if (file->data == NULL) { + perror("Could not allocate memeor y for file data!"); + exit(EXIT_FAILURE); + } + file_content_to_buffer(file, file->data); +} + +void file_print(void *item) { + if (item == NULL) + return; + printf("%s\n", ((File *)item)->path); +} + +void file_content_to_buffer(File *file, char *buffer) { + if (buffer == NULL) { + perror("Buffer is to write file content to is NULL!"); + exit(EXIT_FAILURE); + } + FILE *file_pointer = fopen(file->path, "rb"); + if (file_pointer == NULL) { + perror("Could not open the file!"); + exit(EXIT_FAILURE); + } + size_t bytes_read = fread(buffer, 1, file->stats.st_size, file_pointer); + if (bytes_read != (size_t)file->stats.st_size) { + perror("Read to many or to less bytes from File!"); + exit(EXIT_FAILURE); + } + fclose(file_pointer); +} + +FileReceive *file_receive_create(char *path, DataFragment *data_fragment) { + FileReceive *file = malloc(sizeof(FileReceive)); + file->path = path; + file->data_fragment = data_fragment; + return file; +} + +void file_receive_destroy(void *file_receive) { + if (file_receive == NULL) + return; + FileReceive *file = (FileReceive *)file_receive; + data_fragment_delete(file->data_fragment); + free(file->path); + free(file); +} + +Chunk *chunk_create(File **items, int element_count) { + Chunk *chunk = (Chunk *)malloc(sizeof(Chunk)); + if (chunk == NULL) { + perror("FATAL ERROR: Could not allocate memory for chunk structure"); + exit(EXIT_FAILURE); + } + + chunk->items = (File **)malloc(element_count * sizeof(File *)); + if (chunk->items == NULL) { + perror("FATAL ERROR: Could not allocate memory for items of chunk " + "structure"); + free(chunk); + exit(EXIT_FAILURE); + } + + for (int i = 0; i < element_count; i++) { + chunk->items[i] = items[i]; + } + chunk->element_count = element_count; + return chunk; +} + +void chunk_destroy(void *item) { + if (item == NULL) { + return; + } + Chunk *chunk = (Chunk *)item; + for (int i = 0; i < chunk->element_count; ++i) { + if (chunk->items[i] != NULL) { + file_destroy(chunk->items[i]); + } + } + free(chunk->items); + free(chunk); +} + +void chunk_print(void *item) { + if (item == NULL) + return; + Chunk *chunk = (Chunk *)item; + for (int i = 0; i < chunk->element_count; ++i) + if (chunk->items[i] != NULL) + file_print(chunk->items[i]); +} + +Data *chunk_format(Chunk *chunk) { + unsigned long long buffer_size = 0; + for (int i = 0; i < chunk->element_count; ++i) { + buffer_size += sizeof(int); + buffer_size += strlen(chunk->items[i]->path); + buffer_size += sizeof(unsigned long long); + buffer_size += chunk->items[i]->stats.st_size; + } + + char *data = malloc(buffer_size); + if (data == NULL) { + perror("Could not allocate data for ChunkFormated!"); + exit(EXIT_FAILURE); + } + char *current_data_pointer = data; + for (int i = 0; i < chunk->element_count; ++i) { + File *file = chunk->items[i]; + // add path len + int path_length = (int)strlen(file->path); + memcpy(current_data_pointer, &path_length, sizeof(int)); + current_data_pointer += sizeof(int); + // add path + memcpy(current_data_pointer, file->path, path_length); + current_data_pointer += path_length; + // add file data len + unsigned long long file_length = file->stats.st_size; + memcpy(current_data_pointer, &file_length, sizeof(unsigned long long)); + current_data_pointer += sizeof(unsigned long long); + // add file data + file_content_to_buffer(file, current_data_pointer); + current_data_pointer += file_length; + } + if (current_data_pointer - data != (long)(long)buffer_size) { + perror("Buffer of Chunk wasn't filled enough!"); + exit(EXIT_FAILURE); + } + return chunk_data_create(data, buffer_size); +} + +Data *chunk_compress(Chunk *chunk) { + unsigned long long data_size = 0; + for (int i = 0; i < chunk->element_count; i++) { + data_size += sizeof(unsigned long long); + data_size += strlen(chunk->items[i]->path); + data_size += sizeof(unsigned long long); + data_size += chunk->items[i]->stats.st_size; + } + char *data = malloc(data_size); + char *data_pointer = data; + for (int i = 0; i < chunk->element_count; i++) { + // path length + unsigned long long path_len = strlen(chunk->items[i]->path); + memcpy(data_pointer, &path_len, sizeof(unsigned long long)); + data_pointer += sizeof(unsigned long long); + memcpy(data_pointer, chunk->items[i]->path, path_len); + data_pointer += path_len; + // file data + unsigned long long data_size = chunk->items[i]->stats.st_size; + memcpy(data_pointer, &data_size, sizeof(unsigned long long)); + data_pointer += data_size; + memcpy(data_pointer, chunk->items[i]->data, data_size); + data_pointer += data_size; + } + size_t compressed_data_size = ZSTD_compressBound(data_size); + void *compressed_data = malloc(compressed_data_size); + if (compressed_data == NULL) { + log_message(ERROR, "Could not allocate memory for compressed Chunk"); + exit(EXIT_FAILURE); + } + // TODO +} + +Data *chunk_data_create(void *data, unsigned long long data_size) { + Data *chunk_formated = malloc(sizeof(Data)); + if (chunk_formated == NULL) { + perror("Could not allocate memory for ChunkFormated"); + exit(EXIT_FAILURE); + } + chunk_formated->data = data; + chunk_formated->data_size = data_size; + return chunk_formated; +} + +void chunk_data_delete(void *chunk) { + Data *chunk_data = (Data *)chunk; + free(chunk_data->data); + free(chunk_data); +} + +// void chunk_data_to_disk(ChunkData *chunk_formated, char *root_directory) { +// char *current_data_pointer = chunk_formated->data; +// while (current_data_pointer - (char *)chunk_formated->data < +// chunk_formated->data_size) { +// // get path length +// int path_length = 0; +// memcpy(&path_length, (int *)current_data_pointer, sizeof(int)); +// current_data_pointer += sizeof(int); +// // get path +// int path_dir_size = +// (strlen(root_directory) + path_length + 1) * sizeof(char); +// char *path = (char *)malloc(path_dir_size); +// if (path == NULL) { +// perror("Could not allocate memory for path!"); +// exit(EXIT_FAILURE); +// } +// snprintf(path, path_dir_size, "%s%.*s", root_directory, path_length, +// current_data_pointer); +// current_data_pointer += sizeof(char) * path_length; +// // get data length +// unsigned long long data_size = 0; +// memcpy(&data_size, (unsigned long long *)current_data_pointer, +// sizeof(unsigned long long)); +// current_data_pointer += sizeof(unsigned long long); +// // create File Receive +// FileReceive *file = +// file_receive_create(path, data_size, current_data_pointer); +// file_receive_print(file); +// file_receive_to_disk(file); +// current_data_pointer += sizeof(char) * data_size; +// } +// } +--- src/shared/socket.c --- + +#include "socket.h" +#include "log.h" +#include +#include +#include +#include +#include + +Server *server_create(int port) { + Server *server = (Server *)malloc(sizeof(Server)); + if (server == NULL) { + perror("Could not allocate space for Server"); + exit(EXIT_FAILURE); + } + + int file_descriptor = socket(AF_INET, SOCK_STREAM, 0); + if (file_descriptor < 0) { + perror("Could not create Socket!"); + exit(EXIT_FAILURE); + } + server->file_descriptor = file_descriptor; + int opt = 1; + if (setsockopt(server->file_descriptor, SOL_SOCKET, SO_REUSEADDR, &opt, + sizeof(opt))) { + perror("Error setting a socket option!"); + close(server->file_descriptor); + free(server); + exit(EXIT_FAILURE); + } + + server->address.sin_family = AF_INET; + server->address.sin_addr.s_addr = INADDR_ANY; + server->address.sin_port = htons(port); + server->address_length = sizeof(server->address); + + if (bind(server->file_descriptor, (struct sockaddr *)&server->address, + server->address_length) < 0) { + perror("Could not bind server"); + close(server->file_descriptor); + free(server); + exit(EXIT_FAILURE); + } + + return server; +} + +void server_delete(Server *server) { + free(server); + server = NULL; +}; + +void server_listen(Server *server, void (*handler)(int file_descriptor)) { + log_message(LOG_LEVEL_INFO, "Start Listening on Port: %d", + server->address.sin_port); + if (listen(server->file_descriptor, 3) < 0) { + perror("Could not listen on port!"); + exit(EXIT_FAILURE); + } + + int file_descriptor = + accept(server->file_descriptor, (struct sockaddr *)&server->address, + &server->address_length); + if (server->file_descriptor < 0) { + perror("Could not accept the connection"); + exit(EXIT_FAILURE); + } + log_message(LOG_LEVEL_INFO, "Received Connection"); + handler(file_descriptor); + close(server->file_descriptor); + close(file_descriptor); +} + +Client *client_create() { + int file_descriptor = socket(AF_INET, SOCK_STREAM, 0); + if (file_descriptor < 0) { + perror("Could not create Socket!"); + exit(EXIT_FAILURE); + }; + + Client *client = (Client *)malloc(sizeof(Client)); + client->file_descriptor = file_descriptor; + client->address.sin_family = AF_INET; + client->address_length = sizeof(client->address); + return client; +} + +void client_connect(Client *client, char *host, int port) { + client->address.sin_port = htons(port); + + if (inet_pton(AF_INET, host, &client->address.sin_addr) <= 0) { + perror("Could not convert host address!"); + exit(EXIT_FAILURE); + } + + if (connect(client->file_descriptor, (struct sockaddr *)&client->address, + client->address_length) < 0) { + perror("Could not connect to Server!"); + exit(EXIT_FAILURE); + } +} + +void client_disconnect(Client *client) { close(client->file_descriptor); } + +void client_delete(Client *client) { + if (client == NULL) + return; + free(client); +} + +DataFragment *data_fragment_create(void *data, unsigned long long size) { + DataFragment *data_fragment = malloc(sizeof(DataFragment)); + data_fragment->data = data; + data_fragment->size = size; + return data_fragment; +} + +void data_fragment_delete(void *data_fragment) { + if (data_fragment == NULL) + return; + DataFragment *fragment = (DataFragment *)data_fragment; + free(fragment->data); + free(fragment); +} + +void send_n_data(int file_descriptor, void *data, NET_SIZE data_size) { + log_message(LOG_LEVEL_DEBUG, " Sending n Data: %d", data_size); + NET_SIZE total_bytes_send = 0; + while (total_bytes_send < data_size) { + long long bytes_send = + send(file_descriptor, (char *)data + total_bytes_send, + data_size - total_bytes_send, 0); + if (bytes_send == 0) { + perror("Could not send data!"); + exit(EXIT_FAILURE); + } + total_bytes_send += bytes_send; + } + log_message(LOG_LEVEL_DEBUG, " Send n Data: %d", total_bytes_send); +} + +void receive_n_data(int file_descriptor, void *data, NET_SIZE data_size) { + log_message(LOG_LEVEL_DEBUG, " Receiving n Data: %d", data_size); + NET_SIZE total_bytes_received = 0; + while (total_bytes_received < data_size) { + long long bytes_received = + recv(file_descriptor, data + total_bytes_received, + data_size - total_bytes_received, 0); + if (bytes_received == -1 || bytes_received == 0) { + perror("Could not receive bytes!"); + exit(EXIT_FAILURE); + } + total_bytes_received += bytes_received; + } + log_message(LOG_LEVEL_DEBUG, " Received n Data: %d", total_bytes_received); +} + +void send_str(int file_descriptor, char *data) { + NET_SIZE size = strlen(data); + send_n_data(file_descriptor, &size, sizeof(NET_SIZE)); + send_n_data(file_descriptor, data, size); + log_message(LOG_LEVEL_DEBUG, "Send String: %s", data); +} + +char *receive_str(int file_descriptor) { + NET_SIZE size; + receive_n_data(file_descriptor, &size, sizeof(NET_SIZE)); + char *data = (char *)malloc(size + 1); + receive_n_data(file_descriptor, data, size); + data[size] = '\0'; + log_message(LOG_LEVEL_DEBUG, "Received String: %s", data); + return data; +} + +void send_data(int file_descriptor, void *data, unsigned long long data_size) { + send_n_data(file_descriptor, &data_size, sizeof(unsigned long long)); + send_n_data(file_descriptor, data, data_size); + log_message(LOG_LEVEL_DEBUG, "Send %lld data", data_size); +} + +DataFragment *receive_data(int file_descriptor) { + unsigned long long size = 0; + receive_n_data(file_descriptor, &size, sizeof(unsigned long long)); + void *data = malloc(size); + receive_n_data(file_descriptor, data, size); + log_message(LOG_LEVEL_DEBUG, "Received %lld data", size); + return data_fragment_create(data, size); +} + +void send_int(int file_descriptor, int data) { + send_n_data(file_descriptor, &data, sizeof(int)); + log_message(LOG_LEVEL_DEBUG, "Send Int: %d", data); +} + +int receive_int(int file_descriptor) { + int data; + receive_n_data(file_descriptor, &data, sizeof(int)); + log_message(LOG_LEVEL_DEBUG, "Received Int: %d", data); + return data; +} + +void send_status(int file_descriptor, Status status) { + send_n_data(file_descriptor, &status, sizeof(Status)); + log_message(LOG_LEVEL_DEBUG, "Send Status: %d", status); +} + +Status receive_status(int file_descriptor) { + Status data; + receive_n_data(file_descriptor, &data, sizeof(Status)); + log_message(LOG_LEVEL_DEBUG, "Received Status: %d", data); + return data; +} +--- src/shared/log.c --- + +#include "log.h" +#include +#include +#include + +static const char *log_level_strings[] = {"DEBUG", "INFO", "WARN", "ERROR"}; +static LogLevel current_log_level = LOG_LEVEL_DEBUG; + +void log_message(LogLevel log_level, char *format, ...) { + if (log_level < current_log_level) + return; + time_t now = time(NULL); + struct tm *t = localtime(&now); + + // Print timestamp and log level to the file + printf("%04d-%02d-%02d %02d:%02d:%02d [%s]: ", t->tm_year + 1900, + t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec, + log_level_strings[log_level]); + + va_list args; + va_start(args, format); + vprintf(format, args); + va_end(args); + printf("\n"); +} +--- src/shared/config.c --- + +#include "config.h" +#include "socket.h" +#include +#include +#include + +Config *config_create(char *version, char *send_directory, + char *receive_directory, bool save_to_disk, + bool use_multithreading, bool use_chunk_serialization, + bool use_compression, bool use_multiprocessing, + int num_connections) { + + Config *config = malloc(sizeof(Config)); + config->version = version; + config->send_directory = send_directory; + config->receive_root_directory = receive_directory; + config->save_to_disk = save_to_disk; + config->use_multithreading = use_multithreading; + config->use_chunk_serialization = use_chunk_serialization; + config->use_compression = use_compression; + config->use_multiprocessing = use_multiprocessing; + config->num_connections = num_connections; + return config; +} + +void config_delete(Config *config) { + free(config->version); + free(config->send_directory); + free(config->receive_root_directory); + free(config); +} + +void config_send(int file_descriptor, Config *config) { + send_str(file_descriptor, config->version); + send_str(file_descriptor, config->send_directory); + send_str(file_descriptor, config->receive_root_directory); + send_int(file_descriptor, config->save_to_disk); + send_int(file_descriptor, config->use_multithreading); + send_int(file_descriptor, config->use_chunk_serialization); + send_int(file_descriptor, config->use_compression); + send_int(file_descriptor, config->use_multiprocessing); + send_int(file_descriptor, config->num_connections); + if (receive_status(file_descriptor) != OK) { + perror("Error transmitting config!"); + exit(EXIT_FAILURE); + } +} + +Config *config_receive(int file_descriptor) { + Config *config = (Config *)malloc(sizeof(Config)); + config->version = receive_str(file_descriptor); + config->send_directory = receive_str(file_descriptor); + config->receive_root_directory = receive_str(file_descriptor); + config->save_to_disk = receive_int(file_descriptor); + config->use_multithreading = receive_int(file_descriptor); + config->use_chunk_serialization = receive_int(file_descriptor); + config->use_compression = receive_int(file_descriptor); + config->use_multiprocessing = receive_int(file_descriptor); + config->num_connections = receive_int(file_descriptor); + send_status(file_descriptor, OK); + return config; +} +--- src/server/server.c --- + +#include "chunk.h" +#include "config.h" +#include "multiprocessing.h" +#include "queue.h" +#include "socket.h" +#include "unistd.h" +#include "utils.h" +#include +#include +#include + +FileReceive *receive_file_receive(int file_descriptor) { + char *path = (char *)receive_str(file_descriptor); + printf("%s\n", path); + DataFragment *file_data_fragment = receive_data(file_descriptor); + FileReceive *file = file_receive_create(path, file_data_fragment); + return file; +} + +int receive_thread(void *pipeline_context) { + PipelineContextReceiver *context = + (PipelineContextReceiver *)pipeline_context; + mtx_lock(&context->mutex); + int file_descriptor = context->file_descriptor; + mtx_unlock(&context->mutex); + + while (receive_status(file_descriptor) == NEXT) { + FileReceive *file = receive_file_receive(file_descriptor); + queue_enqueue_multithreaded(context->queue, file, &context->mutex, + &context->condition_not_empty, + &context->condition_not_full); + } + mtx_lock(&context->mutex); + context->receiver_done = true; + cnd_signal(&context->condition_not_empty); + mtx_unlock(&context->mutex); + return thrd_success; +} + +int write_thread(void *pipeline_context) { + PipelineContextReceiver *context = + (PipelineContextReceiver *)pipeline_context; + mtx_lock(&context->mutex); + bool save_to_disk = context->config->save_to_disk; + char *root_directory = str_dup(context->config->receive_root_directory); + mtx_unlock(&context->mutex); + + while (true) { + FileReceive *file = queue_dequeue_multithreaded( + context->queue, &context->mutex, &context->condition_not_empty, + &context->condition_not_full, &context->receiver_done); + if (file == NULL) { + free(root_directory); + return thrd_success; + } + if (save_to_disk) + to_disk(path_cat(root_directory, file->path), file->data_fragment->data, + file->data_fragment->size); + } +} + +int receive_files(Config *config, int file_descriptor) { + Status status = receive_status(file_descriptor); + while (status == NEXT) { + FileReceive *file = receive_file_receive(file_descriptor); + if (config->save_to_disk) + to_disk(path_cat(config->receive_root_directory, file->path), + file->data_fragment->data, file->data_fragment->size); + file_receive_destroy(file); + status = receive_status(file_descriptor); + } + if (status != FINISHED) { + send_status(file_descriptor, ERROR); + return -1; + } + send_status(file_descriptor, OK); + return 0; +} + +void handler(int file_descriptor) { + Config *config = config_receive(file_descriptor); + if (config->use_multithreading) { + PipelineContextReceiver *context = pipeline_context_receiver_create( + config, queue_create(100, file_receive_destroy), file_descriptor); + thrd_t receiver, writer; + if (thrd_create(&receiver, receive_thread, context) != thrd_success || + thrd_create(&writer, write_thread, context) != thrd_success) { + perror("Error creating Threads!"); + exit(EXIT_FAILURE); + } + thrd_join(receiver, NULL); + thrd_join(writer, NULL); + pipeline_context_receiver_destroy(context); + } else + receive_files(config, file_descriptor); + close(file_descriptor); +} + +int main() { + Server *server = server_create(8080); + server_listen(server, handler); + server_delete(server); + return 0; +} +--- Makefile --- + +CC = gcc +CFLAGS = -Wall -g -pthread -std=c11 -Isrc/shared -Isrc/server -Isrc/client +LIBS = -lzstd + +OBJ_DIR = obj +SRC_DIR = src +SHARED_SRCS = $(wildcard src/shared/*.c) +SHARED_OBJS = $(patsubst $(SRC_DIR)/shared/%.c, $(OBJ_DIR)/shared/%.o, $(SHARED_SRCS)) + +SERVER = server +SERVER_SRCS = $(wildcard src/server/*.c) +SERVER_OBJS = $(patsubst $(SRC_DIR)/server/%.c, $(OBJ_DIR)/server/%.o, $(SERVER_SRCS)) + +CLIENT = client +CLIENT_SRCS = $(wildcard src/client/*.c) +CLIENT_OBJS = $(patsubst $(SRC_DIR)/client/%.c, $(OBJ_DIR)/client/%.o, $(CLIENT_SRCS)) + +all: $(SERVER) $(CLIENT) + +$(SERVER): $(SHARED_OBJS) $(SERVER_OBJS) + $(CC) $(CFLAGS) -o $@ $^ $(LIBS) +$(CLIENT): $(SHARED_OBJS) $(CLIENT_OBJS) + $(CC) $(CFLAGS) -o $@ $^ $(LIBS) + +$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c + @mkdir -p $(dir $@) + $(CC) $(CFLAGS) -c $< -o $@ + +.PHONY: all clean + +clean: + rm -r $(OBJ_DIR) $(SERVER) $(CLIENT) diff --git a/compile_commands.json b/compile_commands.json new file mode 120000 index 0000000..25eb4b2 --- /dev/null +++ b/compile_commands.json @@ -0,0 +1 @@ +build/compile_commands.json \ No newline at end of file diff --git a/description.md b/description.md new file mode 100644 index 0000000..c7d4b65 --- /dev/null +++ b/description.md @@ -0,0 +1,24 @@ +# jetstream - A High-Performance File Transfer Utility + +## Features + +## Architecture ideas + +### Pipeline Stages Client + +- Directory Scanning -> Files +- File Reading -> SendableBuffer +- optional: Compression -> Sendable Buffer +- Send Data + +### Pipeline Stages Server + +- Receive Data -> Sendable Buffer +- optional: Decompress -> Files +- File Writing + +### Passing Data between Steps + +- use Queue + +## diff --git a/plan.docx b/plan.docx new file mode 100644 index 0000000000000000000000000000000000000000..5a8ed69d648c2ba63dff7a8984f639787211d4ba GIT binary patch literal 27533 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®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 literal 0 HcmV?d00001 diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..362207d --- /dev/null +++ b/shell.nix @@ -0,0 +1,18 @@ +{ pkgs ? import { } }: + +pkgs.mkShell { + nativeBuildInputs = with pkgs; [ + gcc + cmake + gnumake + pkg-config + ]; + + buildInputs = with pkgs; [ + zstd + ]; + + shellHook = '' + echo "fastSync development environment loaded" + ''; +} diff --git a/src/client/client.c b/src/client/client.c new file mode 100644 index 0000000..3aa930d --- /dev/null +++ b/src/client/client.c @@ -0,0 +1,190 @@ +#include +#include +#include +#include + +#include "chunk.h" +#include "config.h" +#include "log.h" +#include "multiprocessing.h" +#include "queue.h" +#include "scanner.h" +#include "socket.h" +#include "utils.h" +#include + +int send_chunk(Client *client, Chunk *chunk, bool use_compression) { + if (use_compression) { + Data *data = chunk_compress(chunk); + send_data(client->file_descriptor, data->data, data->data_size); + } else { + for (int i = 0; i < chunk->element_count; i++) { + send_status(client->file_descriptor, NEXT); + File *file = chunk->items[i]; + send_str(client->file_descriptor, file->path); + send_data(client->file_descriptor, file->data, file->stats.st_size); + } + send_status(client->file_descriptor, FINISHED); + if (receive_status(client->file_descriptor) != OK) + return -1; + } + return 0; +} + +int scan_directory_multithreaded(void *pipeline_context) { + PipelineContextSender *context = (PipelineContextSender *)pipeline_context; + mtx_lock(&context->mutex_scanner); + DirectoryScanner *scanner = + directory_scanner_create(context->config->send_directory); + mtx_unlock(&context->mutex_scanner); + + Chunk *current_chunk; + while ((current_chunk = directory_scanner_next(scanner)) != NULL) + queue_enqueue_multithreaded(context->queue_scanner, current_chunk, + &context->mutex_scanner, + &context->condition_not_empty_scanner, + &context->condition_not_full_scanner); + mtx_lock(&context->mutex_scanner); + context->scanner_done = true; + cnd_signal(&context->condition_not_empty_scanner); + mtx_unlock(&context->mutex_scanner); + + directory_scanner_destroy(scanner); + return thrd_success; +} + +int load_files_multithreaded(void *pipeline_context) { + PipelineContextSender *context = (PipelineContextSender *)pipeline_context; + while (true) { + Chunk *chunk = queue_dequeue_multithreaded( + context->queue_scanner, &context->mutex_scanner, + &context->condition_not_empty_scanner, + &context->condition_not_full_scanner, &context->scanner_done); + if (chunk == NULL) { + mtx_lock(&context->mutex_loader); + context->loader_done = true; + cnd_signal(&context->condition_not_empty_loader); + mtx_unlock(&context->mutex_loader); + return thrd_success; + } + for (int i = 0; i < chunk->element_count; i++) + file_load_data(chunk->items[i]); + queue_enqueue_multithreaded(context->queue_loader, chunk, + &context->mutex_loader, + &context->condition_not_empty_loader, + &context->condition_not_full_loader); + } +} + +int send_chunks_multithreaded(void *pipeline_context) { + PipelineContextSender *context = (PipelineContextSender *)pipeline_context; + bool use_compression = context->config->use_compression; + Client *client = client_create(); + client_connect(client, "127.0.0.1", 8080); + config_send(client->file_descriptor, context->config); + + while (true) { + Chunk *current_chunk = queue_dequeue_multithreaded( + context->queue_loader, &context->mutex_loader, + &context->condition_not_empty_loader, + &context->condition_not_full_loader, &context->loader_done); + if (current_chunk == NULL) { + client_disconnect(client); + client_delete(client); + return thrd_success; + } + send_chunk(client, current_chunk, use_compression); + chunk_destroy(current_chunk); + } +} + +int send_files(Config *config) { + Client *client = client_create(); + client_connect(client, "127.0.0.1", 8080); + config_send(client->file_descriptor, config); + DirectoryScanner *scanner = directory_scanner_create(config->send_directory); + Chunk *current_chunk; + while ((current_chunk = directory_scanner_next(scanner)) != NULL) { + chunk_print(current_chunk); + for (int i = 0; i < current_chunk->element_count; i++) + file_load_data(current_chunk->items[i]); + send_chunk(client, current_chunk, config->use_compression); + chunk_destroy(current_chunk); + } + printf("FINISHED"); + directory_scanner_destroy(scanner); + client_disconnect(client); + client_delete(client); + return 0; +} + +void handle_arg(char *argument_given, char *argument_to_set, bool *result, + char *message) { + if (strcmp(argument_given, argument_to_set) == 0) { + *result = true; + log_message(LOG_LEVEL_INFO, message); + } +} + +int send_files_multithreaded(Config *config) { + PipelineContextSender *context = + pipeline_context_sender_create(config, queue_create(100, chunk_destroy), + queue_create(100, chunk_destroy)); + + thrd_t scanner, loader, sender; + if (thrd_create(&scanner, scan_directory_multithreaded, context) != + thrd_success || + thrd_create(&loader, load_files_multithreaded, context) != thrd_success || + thrd_create(&sender, send_chunks_multithreaded, context) != + thrd_success) { + perror("Error creating threads.\n"); + return 1; + } + + thrd_join(scanner, NULL); + thrd_join(loader, NULL); + thrd_join(sender, NULL); + + pipeline_context_sender_destroy(context); + return 0; +} + +int send_files_multiprocessed(Config *config) { + int cores = sysconf(_SC_NPROCESSORS_ONLN); + int pid = fork(); + if (pid == -1) { + perror("Error Forking!"); + return 1; + } else if (pid == 0) { + } + for (int i = 0; i < cores; i++) { + int pid = fork(); + if (pid == -1) { + perror("Error forking!"); + return 1; + } else if (pid == 0) { + } + } + return 0; +} + +int main(int argc, char *argv[]) { + Config *config = config_create( + str_dup("1.0.0"), str_dup("/home/taptap/Nextcloud/Uni/moodle/MINT-Raum"), + str_dup("./data_copied"), false, false, false, false, false, 1); + for (int i = 1; i < argc; i++) { + handle_arg(argv[i], "-m", &config->use_multithreading, + "Enabled Multithreading"); + handle_arg(argv[i], "-s", &config->use_chunk_serialization, + "Enabled Chunk Serialization"); + handle_arg(argv[i], "-c", &config->use_compression, "Enabled Compression"); + handle_arg(argv[i], "-p", &config->use_multiprocessing, + "Enabled Multiprocessing"); + } + + if (config->use_multithreading) + return send_files_multithreaded(config); + else if (config->use_multiprocessing) + return send_files_multiprocessed(config); + return send_files(config); +} diff --git a/src/client/scanner.c b/src/client/scanner.c new file mode 100644 index 0000000..57dd5db --- /dev/null +++ b/src/client/scanner.c @@ -0,0 +1,72 @@ +#include "scanner.h" +#include "array_list.h" +#include "chunk.h" +#include "queue.h" +#include "utils.h" +#include +#include +#include +#include + +DirectoryScanner *directory_scanner_create(char *root_directory) { + DirectoryScanner *scanner = malloc(sizeof(DirectoryScanner)); + scanner->directories = queue_create(100, free); + queue_enqueue(scanner->directories, str_dup(root_directory)); + return scanner; +} + +void directory_scanner_destroy(DirectoryScanner *scanner) { + if (scanner == NULL) + return; + queue_destroy(scanner->directories); + free(scanner); +} + +Chunk *chunk_data_to_chunk(ArrayList *chunk_data) { + void **chunk_items = array_list_to_array(chunk_data); + Chunk *chunk = chunk_create((File **)chunk_items, chunk_data->size); + free(chunk_items); + chunk_data->item_destroyer = NULL; + array_list_delete(chunk_data); + return chunk; +} + +Chunk *directory_scanner_next(DirectoryScanner *scanner) { + ArrayList *chunk_data = array_list_create(file_destroy); + unsigned long long chunk_data_size = 0; + + while (!queue_is_empty(scanner->directories)) { + char *path = (char *)queue_dequeue(scanner->directories); + DIR *dir; + struct dirent *entry; + dir = opendir(path); + if (dir == NULL) { + perror("Could not open directory!"); + exit(EXIT_FAILURE); + } + printf("%s", path); + while ((entry = readdir(dir)) != NULL) { + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) { + continue; + } + char *cur_path = path_cat(path, entry->d_name); + struct stat stats; + stat(cur_path, &stats); + if (!S_ISREG(stats.st_mode)) + queue_enqueue(scanner->directories, (void *)cur_path); + else { + File *file = file_create(cur_path, &stats); + array_list_add(chunk_data, file); + chunk_data_size += file->stats.st_size; + if (chunk_data_size > DESIRED_CHUNK_SIZE) + return chunk_data_to_chunk(chunk_data); + free(cur_path); + } + } + closedir(dir); + free(path); + } + if (chunk_data->size > 0) + return chunk_data_to_chunk(chunk_data); + return NULL; +} diff --git a/src/client/scanner.h b/src/client/scanner.h new file mode 100644 index 0000000..c8e0205 --- /dev/null +++ b/src/client/scanner.h @@ -0,0 +1,14 @@ +#ifndef SCANNER_H +#define SCANNER_H + +#include "chunk.h" +#include "queue.h" +typedef struct { + Queue *directories; +} DirectoryScanner; + +DirectoryScanner *directory_scanner_create(char *root_directory); +Chunk *directory_scanner_next(DirectoryScanner *scanner); +void directory_scanner_destroy(DirectoryScanner *scanner); + +#endif diff --git a/src/server/server.c b/src/server/server.c new file mode 100644 index 0000000..4b77be6 --- /dev/null +++ b/src/server/server.c @@ -0,0 +1,104 @@ +#include "chunk.h" +#include "config.h" +#include "multiprocessing.h" +#include "queue.h" +#include "socket.h" +#include "unistd.h" +#include "utils.h" +#include +#include +#include + +FileReceive *receive_file_receive(int file_descriptor) { + char *path = (char *)receive_str(file_descriptor); + printf("%s\n", path); + DataFragment *file_data_fragment = receive_data(file_descriptor); + FileReceive *file = file_receive_create(path, file_data_fragment); + return file; +} + +int receive_thread(void *pipeline_context) { + PipelineContextReceiver *context = + (PipelineContextReceiver *)pipeline_context; + mtx_lock(&context->mutex); + int file_descriptor = context->file_descriptor; + mtx_unlock(&context->mutex); + + while (receive_status(file_descriptor) == NEXT) { + FileReceive *file = receive_file_receive(file_descriptor); + queue_enqueue_multithreaded(context->queue, file, &context->mutex, + &context->condition_not_empty, + &context->condition_not_full); + } + mtx_lock(&context->mutex); + context->receiver_done = true; + cnd_signal(&context->condition_not_empty); + mtx_unlock(&context->mutex); + return thrd_success; +} + +int write_thread(void *pipeline_context) { + PipelineContextReceiver *context = + (PipelineContextReceiver *)pipeline_context; + mtx_lock(&context->mutex); + bool save_to_disk = context->config->save_to_disk; + char *root_directory = str_dup(context->config->receive_root_directory); + mtx_unlock(&context->mutex); + + while (true) { + FileReceive *file = queue_dequeue_multithreaded( + context->queue, &context->mutex, &context->condition_not_empty, + &context->condition_not_full, &context->receiver_done); + if (file == NULL) { + free(root_directory); + return thrd_success; + } + if (save_to_disk) + to_disk(path_cat(root_directory, file->path), file->data_fragment->data, + file->data_fragment->size); + } +} + +int receive_files(Config *config, int file_descriptor) { + Status status = receive_status(file_descriptor); + while (status == NEXT) { + FileReceive *file = receive_file_receive(file_descriptor); + if (config->save_to_disk) + to_disk(path_cat(config->receive_root_directory, file->path), + file->data_fragment->data, file->data_fragment->size); + file_receive_destroy(file); + status = receive_status(file_descriptor); + } + if (status != FINISHED) { + send_status(file_descriptor, ERROR); + return -1; + } + send_status(file_descriptor, OK); + return 0; +} + +void handler(int file_descriptor) { + Config *config = config_receive(file_descriptor); + if (config->use_multithreading) { + PipelineContextReceiver *context = pipeline_context_receiver_create( + config, queue_create(100, file_receive_destroy), file_descriptor); + thrd_t receiver, writer; + if (thrd_create(&receiver, receive_thread, context) != thrd_success || + thrd_create(&writer, write_thread, context) != thrd_success) { + perror("Error creating Threads!"); + exit(EXIT_FAILURE); + } + thrd_join(receiver, NULL); + thrd_join(writer, NULL); + pipeline_context_receiver_destroy(context); + } else + receive_files(config, file_descriptor); + close(file_descriptor); +} + +int main() { + Server *server = server_create(8080); + server_listen(server, handler); + server_delete(server); + return 0; +} diff --git a/src/shared/array_list.c b/src/shared/array_list.c new file mode 100644 index 0000000..771d074 --- /dev/null +++ b/src/shared/array_list.c @@ -0,0 +1,82 @@ +#include "array_list.h" +#include +#include +#include + +ArrayList *array_list_create(void (*item_destroyer)(void *item)) { + ArrayList *list = (ArrayList *)malloc(sizeof(ArrayList)); + if (list == NULL) { + perror("FATAL ERROR: Could not allocate memory for array list struct"); + exit(EXIT_FAILURE); + } + + list->items = malloc(INITIAL_ARRAY_SIZE * sizeof(void *)); + if (list->items == NULL) { + perror("FATAL ERROR: Could not allocate memory for list items"); + free(list); + exit(EXIT_FAILURE); + } + list->size = 0; + list->capacity = INITIAL_ARRAY_SIZE; + list->item_destroyer = item_destroyer; + return list; +} + +void array_list_delete(ArrayList *array_list) { + if (array_list == NULL) + return; + if (array_list->item_destroyer != NULL) { + for (int i = 0; i < array_list->size; i++) { + array_list->item_destroyer(array_list->items[i]); + array_list->items[i] = NULL; + } + } + free(array_list->items); + free(array_list); +} + +void array_list_clear(ArrayList *array_list) { + if (array_list == NULL) + return; + for (int i = 0; i < array_list->size; i++) + array_list->items[i] = NULL; + array_list->size = 0; +} + +void array_list_extend(ArrayList *array_list) { + if (array_list == NULL) + return; + int new_capacity = array_list->capacity * 2; + if (new_capacity == 0) + new_capacity = INITIAL_ARRAY_SIZE; + array_list->items = realloc(array_list->items, new_capacity * sizeof(void *)); + if (array_list->items == NULL) { + perror("FATAL ERROR: Could not reallocate memory for array list struct"); + exit(EXIT_FAILURE); + } + array_list->capacity = new_capacity; +} + +void array_list_add(ArrayList *array_list, void *item) { + if (array_list == NULL) { + return; + } + if (array_list->capacity == array_list->size) { + array_list_extend(array_list); + } + array_list->items[array_list->size] = item; + array_list->size += 1; +} + +void **array_list_to_array(ArrayList *array_list) { + if (array_list == NULL) { + return NULL; + } + void **array = malloc(array_list->size * sizeof(void *)); + if (array == NULL) { + perror("Could not malloc space for array from array list!"); + return NULL; + } + memcpy(array, array_list->items, array_list->size * sizeof(void *)); + return array; +} diff --git a/src/shared/array_list.h b/src/shared/array_list.h new file mode 100644 index 0000000..966449f --- /dev/null +++ b/src/shared/array_list.h @@ -0,0 +1,20 @@ +#ifndef ARRAY_LIST_H +#define ARRAY_LIST_H + +#define INITIAL_ARRAY_SIZE 100 + +typedef struct ArrayList { + void **items; + int size; + int capacity; + void (*item_destroyer)(void *item); +} ArrayList; + +ArrayList *array_list_create(void (*item_destroyer)(void *item)); +void array_list_delete(ArrayList *array_list); +void array_list_clear(ArrayList *array_list); +void array_list_extend(ArrayList *array_list); +void array_list_add(ArrayList *array_list, void *item); +void **array_list_to_array(ArrayList *array_list); + +#endif diff --git a/src/shared/chunk.c b/src/shared/chunk.c new file mode 100644 index 0000000..e9d882e --- /dev/null +++ b/src/shared/chunk.c @@ -0,0 +1,263 @@ +#include +#include +#include +#include +#include +#include + +#include "chunk.h" +#include "log.h" +#include "socket.h" + +File *file_create(const char *path, struct stat *stats) { + File *file = (File *)malloc(sizeof(File)); + if (file == NULL) { + perror("FATAL ERROR: Could not allocate memory for file struct"); + exit(EXIT_FAILURE); + } + + file->stats = *stats; + + int path_len = strlen(path); + file->path = (char *)malloc(path_len + 1); + if (file->path == NULL) { + perror("FATAL ERROR: Could not allocate memory for path file string"); + free(file); + exit(EXIT_FAILURE); + } + + strcpy(file->path, path); + file->data = NULL; + return file; +} + +void file_destroy(void *item) { + if (item == NULL) + return; + File *file = (File *)item; + free(file->data); + file->data = NULL; + free(file->path); + file->path = NULL; + free(file); +} + +void file_load_data(File *file) { + if (file == NULL) + return; + file->data = malloc(file->stats.st_size); + if (file->data == NULL) { + perror("Could not allocate memeor y for file data!"); + exit(EXIT_FAILURE); + } + file_content_to_buffer(file, file->data); +} + +void file_print(void *item) { + if (item == NULL) + return; + printf("%s\n", ((File *)item)->path); +} + +void file_content_to_buffer(File *file, char *buffer) { + if (buffer == NULL) { + perror("Buffer is to write file content to is NULL!"); + exit(EXIT_FAILURE); + } + FILE *file_pointer = fopen(file->path, "rb"); + if (file_pointer == NULL) { + perror("Could not open the file!"); + exit(EXIT_FAILURE); + } + size_t bytes_read = fread(buffer, 1, file->stats.st_size, file_pointer); + if (bytes_read != (size_t)file->stats.st_size) { + perror("Read to many or to less bytes from File!"); + exit(EXIT_FAILURE); + } + fclose(file_pointer); +} + +FileReceive *file_receive_create(char *path, DataFragment *data_fragment) { + FileReceive *file = malloc(sizeof(FileReceive)); + file->path = path; + file->data_fragment = data_fragment; + return file; +} + +void file_receive_destroy(void *file_receive) { + if (file_receive == NULL) + return; + FileReceive *file = (FileReceive *)file_receive; + data_fragment_delete(file->data_fragment); + free(file->path); + free(file); +} + +Chunk *chunk_create(File **items, int element_count) { + Chunk *chunk = (Chunk *)malloc(sizeof(Chunk)); + if (chunk == NULL) { + perror("FATAL ERROR: Could not allocate memory for chunk structure"); + exit(EXIT_FAILURE); + } + + chunk->items = (File **)malloc(element_count * sizeof(File *)); + if (chunk->items == NULL) { + perror("FATAL ERROR: Could not allocate memory for items of chunk " + "structure"); + free(chunk); + exit(EXIT_FAILURE); + } + + for (int i = 0; i < element_count; i++) { + chunk->items[i] = items[i]; + } + chunk->element_count = element_count; + return chunk; +} + +void chunk_destroy(void *item) { + if (item == NULL) { + return; + } + Chunk *chunk = (Chunk *)item; + for (int i = 0; i < chunk->element_count; ++i) { + if (chunk->items[i] != NULL) { + file_destroy(chunk->items[i]); + } + } + free(chunk->items); + free(chunk); +} + +void chunk_print(void *item) { + if (item == NULL) + return; + Chunk *chunk = (Chunk *)item; + for (int i = 0; i < chunk->element_count; ++i) + if (chunk->items[i] != NULL) + file_print(chunk->items[i]); +} + +Data *chunk_format(Chunk *chunk) { + unsigned long long buffer_size = 0; + for (int i = 0; i < chunk->element_count; ++i) { + buffer_size += sizeof(int); + buffer_size += strlen(chunk->items[i]->path); + buffer_size += sizeof(unsigned long long); + buffer_size += chunk->items[i]->stats.st_size; + } + + char *data = malloc(buffer_size); + if (data == NULL) { + perror("Could not allocate data for ChunkFormated!"); + exit(EXIT_FAILURE); + } + char *current_data_pointer = data; + for (int i = 0; i < chunk->element_count; ++i) { + File *file = chunk->items[i]; + // add path len + int path_length = (int)strlen(file->path); + memcpy(current_data_pointer, &path_length, sizeof(int)); + current_data_pointer += sizeof(int); + // add path + memcpy(current_data_pointer, file->path, path_length); + current_data_pointer += path_length; + // add file data len + unsigned long long file_length = file->stats.st_size; + memcpy(current_data_pointer, &file_length, sizeof(unsigned long long)); + current_data_pointer += sizeof(unsigned long long); + // add file data + file_content_to_buffer(file, current_data_pointer); + current_data_pointer += file_length; + } + if (current_data_pointer - data != (long)(long)buffer_size) { + perror("Buffer of Chunk wasn't filled enough!"); + exit(EXIT_FAILURE); + } + return chunk_data_create(data, buffer_size); +} + +Data *chunk_compress(Chunk *chunk) { + unsigned long long data_size = 0; + for (int i = 0; i < chunk->element_count; i++) { + data_size += sizeof(unsigned long long); + data_size += strlen(chunk->items[i]->path); + data_size += sizeof(unsigned long long); + data_size += chunk->items[i]->stats.st_size; + } + char *data = malloc(data_size); + char *data_pointer = data; + for (int i = 0; i < chunk->element_count; i++) { + // path length + unsigned long long path_len = strlen(chunk->items[i]->path); + memcpy(data_pointer, &path_len, sizeof(unsigned long long)); + data_pointer += sizeof(unsigned long long); + memcpy(data_pointer, chunk->items[i]->path, path_len); + data_pointer += path_len; + // file data + unsigned long long data_size = chunk->items[i]->stats.st_size; + memcpy(data_pointer, &data_size, sizeof(unsigned long long)); + data_pointer += data_size; + memcpy(data_pointer, chunk->items[i]->data, data_size); + data_pointer += data_size; + } + size_t compressed_data_size = ZSTD_compressBound(data_size); + void *compressed_data = malloc(compressed_data_size); + if (compressed_data == NULL) { + log_message(ERROR, "Could not allocate memory for compressed Chunk"); + exit(EXIT_FAILURE); + } + // TODO + Data *tmp = malloc(sizeof(Data)); + return tmp; +} + +Data *chunk_data_create(void *data, unsigned long long data_size) { + Data *chunk_formated = malloc(sizeof(Data)); + if (chunk_formated == NULL) { + perror("Could not allocate memory for ChunkFormated"); + exit(EXIT_FAILURE); + } + chunk_formated->data = data; + chunk_formated->data_size = data_size; + return chunk_formated; +} + +void chunk_data_delete(void *chunk) { + Data *chunk_data = (Data *)chunk; + free(chunk_data->data); + free(chunk_data); +} + +// void chunk_data_to_disk(ChunkData *chunk_formated, char *root_directory) { +// char *current_data_pointer = chunk_formated->data; +// while (current_data_pointer - (char *)chunk_formated->data < +// chunk_formated->data_size) { +// // get path length +// int path_length = 0; +// memcpy(&path_length, (int *)current_data_pointer, sizeof(int)); +// current_data_pointer += sizeof(int); +// // get path +// int path_dir_size = +// (strlen(root_directory) + path_length + 1) * sizeof(char); +// char *path = (char *)malloc(path_dir_size); +// if (path == NULL) { +// perror("Could not allocate memory for path!"); +// exit(EXIT_FAILURE); +// } +// snprintf(path, path_dir_size, "%s%.*s", root_directory, path_length, +// current_data_pointer); +// current_data_pointer += sizeof(char) * path_length; +// // get data length +// unsigned long long data_size = 0; +// memcpy(&data_size, (unsigned long long *)current_data_pointer, +// sizeof(unsigned long long)); +// current_data_pointer += sizeof(unsigned long long); +// // create File Receive +// FileReceive *file = +// file_receive_create(path, data_size, current_data_pointer); +// file_receive_print(file); +// file_receive_to_disk(file); +// current_data_pointer += sizeof(char) * data_size; +// } +// } diff --git a/src/shared/chunk.h b/src/shared/chunk.h new file mode 100644 index 0000000..2557356 --- /dev/null +++ b/src/shared/chunk.h @@ -0,0 +1,50 @@ +#ifndef CHUNK_H +#define CHUNK_H + +#include "socket.h" +#include + +#define DESIRED_CHUNK_SIZE 10 * 1024 * 1024 +#define FILE_PATH_SEPERATOR "#&&SEPP&&#" +#define FILE_PATH_DATA_SEPERATOR "#&&SEPD&&#" + +typedef struct { + char *path; + struct stat stats; + char *data; +} File; + +typedef struct { + char *path; + DataFragment *data_fragment; +} FileReceive; + +typedef struct { + File **items; + int element_count; +} Chunk; + +typedef struct { + void *data; + unsigned long long data_size; +} Data; + +File *file_create(const char *path, struct stat *stats); +void file_destroy(void *item); +void file_load_data(File *file); +void file_print(void *item); +void file_content_to_buffer(File *file, char *buffer); + +FileReceive *file_receive_create(char *path, DataFragment *data_fragment); +void file_receive_destroy(void *file_receive); + +Chunk *chunk_create(File **items, int element_count); +void chunk_destroy(void *chunk); +void chunk_print(void *chunk); +Data *chunk_format(Chunk *chunk); +Data *chunk_compress(Chunk *chunk); + +Data *chunk_data_create(void *data, unsigned long long data_size); +void chunk_data_delete(void *chunk); +void chunk_data_to_disk(Data *chunk, char *root_directory); +#endif diff --git a/src/shared/config.c b/src/shared/config.c new file mode 100644 index 0000000..70d9a05 --- /dev/null +++ b/src/shared/config.c @@ -0,0 +1,62 @@ +#include "config.h" +#include "socket.h" +#include +#include +#include + +Config *config_create(char *version, char *send_directory, + char *receive_directory, bool save_to_disk, + bool use_multithreading, bool use_chunk_serialization, + bool use_compression, bool use_multiprocessing, + int num_connections) { + + Config *config = malloc(sizeof(Config)); + config->version = version; + config->send_directory = send_directory; + config->receive_root_directory = receive_directory; + config->save_to_disk = save_to_disk; + config->use_multithreading = use_multithreading; + config->use_chunk_serialization = use_chunk_serialization; + config->use_compression = use_compression; + config->use_multiprocessing = use_multiprocessing; + config->num_connections = num_connections; + return config; +} + +void config_delete(Config *config) { + free(config->version); + free(config->send_directory); + free(config->receive_root_directory); + free(config); +} + +void config_send(int file_descriptor, Config *config) { + send_str(file_descriptor, config->version); + send_str(file_descriptor, config->send_directory); + send_str(file_descriptor, config->receive_root_directory); + send_int(file_descriptor, config->save_to_disk); + send_int(file_descriptor, config->use_multithreading); + send_int(file_descriptor, config->use_chunk_serialization); + send_int(file_descriptor, config->use_compression); + send_int(file_descriptor, config->use_multiprocessing); + send_int(file_descriptor, config->num_connections); + if (receive_status(file_descriptor) != OK) { + perror("Error transmitting config!"); + exit(EXIT_FAILURE); + } +} + +Config *config_receive(int file_descriptor) { + Config *config = (Config *)malloc(sizeof(Config)); + config->version = receive_str(file_descriptor); + config->send_directory = receive_str(file_descriptor); + config->receive_root_directory = receive_str(file_descriptor); + config->save_to_disk = receive_int(file_descriptor); + config->use_multithreading = receive_int(file_descriptor); + config->use_chunk_serialization = receive_int(file_descriptor); + config->use_compression = receive_int(file_descriptor); + config->use_multiprocessing = receive_int(file_descriptor); + config->num_connections = receive_int(file_descriptor); + send_status(file_descriptor, OK); + return config; +} diff --git a/src/shared/config.h b/src/shared/config.h new file mode 100644 index 0000000..e6d0e1f --- /dev/null +++ b/src/shared/config.h @@ -0,0 +1,27 @@ +#ifndef CONFIG_H +#define CONFIG_H + +#include + +typedef struct Config { + char *version; + char *send_directory; + char *receive_root_directory; + bool save_to_disk; + bool use_multithreading; + bool use_chunk_serialization; + bool use_compression; + bool use_multiprocessing; + int num_connections; +} Config; + +Config *config_create(char *version, char *send_directory, + char *receive_directory, bool save_to_disk, + bool use_multithreading, bool use_chunk_serialization, + bool use_compression, bool use_multiprocessing, + int num_connections); +void config_delete(Config *config); +void config_send(int file_descriptor, Config *config); +Config *config_receive(int file_descriptor); + +#endif diff --git a/src/shared/log.c b/src/shared/log.c new file mode 100644 index 0000000..ffdc748 --- /dev/null +++ b/src/shared/log.c @@ -0,0 +1,25 @@ +#include "log.h" +#include +#include +#include + +static const char *log_level_strings[] = {"DEBUG", "INFO", "WARN", "ERROR"}; +static LogLevel current_log_level = LOG_LEVEL_DEBUG; + +void log_message(LogLevel log_level, char *format, ...) { + if (log_level < current_log_level) + return; + time_t now = time(NULL); + struct tm *t = localtime(&now); + + // Print timestamp and log level to the file + printf("%04d-%02d-%02d %02d:%02d:%02d [%s]: ", t->tm_year + 1900, + t->tm_mon + 1, t->tm_mday, t->tm_hour, t->tm_min, t->tm_sec, + log_level_strings[log_level]); + + va_list args; + va_start(args, format); + vprintf(format, args); + va_end(args); + printf("\n"); +} diff --git a/src/shared/log.h b/src/shared/log.h new file mode 100644 index 0000000..f1274b1 --- /dev/null +++ b/src/shared/log.h @@ -0,0 +1,13 @@ +#ifndef LOG_H +#define LOG_H + +typedef enum { + LOG_LEVEL_DEBUG, + LOG_LEVEL_INFO, + LOG_LEVEL_WARNING, + LOG_LEVEL_ERROR +} LogLevel; + +void log_message(LogLevel log_level, char *message, ...); + +#endif diff --git a/src/shared/multiprocessing.c b/src/shared/multiprocessing.c new file mode 100644 index 0000000..7b52f3d --- /dev/null +++ b/src/shared/multiprocessing.c @@ -0,0 +1,66 @@ +#include "multiprocessing.h" +#include "config.h" +#include "queue.h" +#include +#include +#include + +PipelineContextSender *pipeline_context_sender_create(Config *config, + Queue *queue_scanner, + Queue *queue_loader) { + PipelineContextSender *context = malloc(sizeof(PipelineContextSender)); + context->config = config; + context->queue_scanner = queue_scanner; + context->queue_loader = queue_loader; + context->scanner_done = false; + context->loader_done = false; + if (mtx_init(&context->mutex_scanner, mtx_plain) != thrd_success || + cnd_init(&context->condition_not_full_scanner) != thrd_success || + cnd_init(&context->condition_not_empty_scanner) != thrd_success || + mtx_init(&context->mutex_loader, mtx_plain) != thrd_success || + cnd_init(&context->condition_not_full_loader) != thrd_success || + cnd_init(&context->condition_not_empty_loader) != thrd_success) { + perror("Error initializing synchronization objects!"); + exit(EXIT_FAILURE); + } + return context; +} + +void pipeline_context_sender_destroy(PipelineContextSender *context) { + config_delete(context->config); + queue_destroy(context->queue_scanner); + queue_destroy(context->queue_loader); + mtx_destroy(&context->mutex_scanner); + cnd_destroy(&context->condition_not_full_scanner); + cnd_destroy(&context->condition_not_empty_scanner); + mtx_destroy(&context->mutex_loader); + cnd_destroy(&context->condition_not_full_loader); + cnd_destroy(&context->condition_not_empty_loader); + free(context); +} + +PipelineContextReceiver *pipeline_context_receiver_create(Config *config, + Queue *queue, + int file_descriptor) { + PipelineContextReceiver *context = malloc(sizeof(PipelineContextReceiver)); + context->config = config; + context->queue = queue; + context->file_descriptor = file_descriptor; + context->receiver_done = false; + if (mtx_init(&context->mutex, mtx_plain) != thrd_success || + cnd_init(&context->condition_not_full) != thrd_success || + cnd_init(&context->condition_not_empty) != thrd_success) { + perror("Error initializing synchronization objects!"); + exit(EXIT_FAILURE); + } + return context; +} + +void pipeline_context_receiver_destroy(PipelineContextReceiver *context) { + config_delete(context->config); + queue_destroy(context->queue); + mtx_destroy(&context->mutex); + cnd_destroy(&context->condition_not_full); + cnd_destroy(&context->condition_not_empty); + free(context); +} diff --git a/src/shared/multiprocessing.h b/src/shared/multiprocessing.h new file mode 100644 index 0000000..909987c --- /dev/null +++ b/src/shared/multiprocessing.h @@ -0,0 +1,41 @@ +#ifndef MULTIPROCESSING_H +#define MULTIPROCESSING_H + +#include + +#include "config.h" +#include "queue.h" + +typedef struct { + Config *config; + Queue *queue_scanner; + mtx_t mutex_scanner; + cnd_t condition_not_full_scanner; + cnd_t condition_not_empty_scanner; + bool scanner_done; + Queue *queue_loader; + mtx_t mutex_loader; + cnd_t condition_not_full_loader; + cnd_t condition_not_empty_loader; + bool loader_done; +} PipelineContextSender; + +typedef struct PipelineContextReceiver { + Queue *queue; + Config *config; + int file_descriptor; + mtx_t mutex; + cnd_t condition_not_full; + cnd_t condition_not_empty; + bool receiver_done; +} PipelineContextReceiver; + +PipelineContextSender *pipeline_context_sender_create(Config *config, + Queue *queue_scanner, + Queue *queue_loader); +void pipeline_context_sender_destroy(PipelineContextSender *context); +PipelineContextReceiver *pipeline_context_receiver_create(Config *config, + Queue *queue_receiver, + int file_descriptor); +void pipeline_context_receiver_destroy(PipelineContextReceiver *context); +#endif diff --git a/src/shared/pipeline.h b/src/shared/pipeline.h new file mode 100644 index 0000000..bd1a927 --- /dev/null +++ b/src/shared/pipeline.h @@ -0,0 +1,6 @@ +#ifndef PIPELINE_H +#define PIPELINE_H + +struct + +#endif diff --git a/src/shared/queue.c b/src/shared/queue.c new file mode 100644 index 0000000..5a131e5 --- /dev/null +++ b/src/shared/queue.c @@ -0,0 +1,134 @@ +#include +#include +#include +#include +#include + +#include "queue.h" + +Queue *queue_create(int capacity, void (*destroyer)(void *item)) { + Queue *queue = (Queue *)malloc(sizeof(Queue)); + if (queue == NULL) { + perror("FATAL ERROR: Could not allocate memory for queue structure"); + exit(EXIT_FAILURE); + } + + queue->items = malloc(capacity * sizeof(void *)); + if (queue->items == NULL) { + perror("FATAL ERROR: Could not allocate memory for queue items"); + free(queue); + exit(EXIT_FAILURE); + } + + for (int i = 0; i < capacity; ++i) { + queue->items[i] = NULL; + } + + queue->capacity = capacity; + queue->front = 0; + queue->rear = 0; + queue->size = 0; + queue->item_destroyer = destroyer; + + return queue; +} + +void queue_destroy(Queue *queue) { + if (queue == NULL) + return; + + if (queue->item_destroyer != NULL) { + for (int i = 0; i < queue->size; ++i) { + int index = (queue->front + i) % queue->capacity; + queue->item_destroyer(queue->items[index]); + } + } + free(queue->items); + free(queue); +} + +bool queue_is_empty(Queue *queue) { + if (queue == NULL) + return true; + return queue->size == 0; +} + +bool queue_is_full(Queue *queue) { + if (queue == NULL) + return false; + return queue->size == queue->capacity; +} + +void queue_double_capacity(Queue *queue) { + if (queue == NULL) + return; + unsigned int new_capacity = queue->capacity * 2; + if (new_capacity <= 1) + new_capacity = 100; + void **new_items = malloc(new_capacity * sizeof(void *)); + if (new_items == NULL) { + perror("FATAL ERROR: Could not allocate memory for doubling capacity of " + "queue."); + exit(EXIT_FAILURE); + } + for (int i = 0; i < queue->size; i++) + new_items[i] = queue->items[(i + queue->front) % queue->capacity]; + free(queue->items); + queue->items = new_items; + queue->front = 0; + queue->rear = queue->size; + queue->capacity = new_capacity; +} + +void queue_enqueue(Queue *queue, void *item) { + if (queue == NULL || item == NULL) { + perror("ERROR: Cannot enqueue with a null queue or item.\n"); + exit(EXIT_FAILURE); + } + if (queue_is_full(queue)) + queue_double_capacity(queue); + queue->items[queue->rear] = item; + queue->rear = (queue->rear + 1) % queue->capacity; + queue->size++; +} + +void queue_enqueue_multithreaded(Queue *queue, void *item, mtx_t *mutex, + cnd_t *condition_not_empty, + cnd_t *condition_not_full) { + mtx_lock(mutex); + while (queue_is_full(queue)) + cnd_wait(condition_not_full, mutex); + queue_enqueue(queue, item); + cnd_signal(condition_not_empty); + mtx_unlock(mutex); +} + +void *queue_dequeue(Queue *queue) { + if (queue == NULL || queue_is_empty(queue)) { + perror("ERROR: Could not dequeue from null or empty queue."); + return NULL; + } + + void *item = queue->items[queue->front]; + queue->items[queue->front] = NULL; + queue->front = (queue->front + 1) % queue->capacity; + queue->size--; + return item; +} + +void *queue_dequeue_multithreaded(Queue *queue, mtx_t *mutex, + cnd_t *condition_not_empty, + cnd_t *condition_not_full, + bool *other_thread_done) { + mtx_lock(mutex); + while (queue_is_empty(queue) && !*other_thread_done) + cnd_wait(condition_not_empty, mutex); + if (queue_is_empty(queue) && *other_thread_done) { + mtx_unlock(mutex); + return NULL; + } + void *item = queue_dequeue(queue); + cnd_signal(condition_not_full); + mtx_unlock(mutex); + return item; +} diff --git a/src/shared/queue.h b/src/shared/queue.h new file mode 100644 index 0000000..de58442 --- /dev/null +++ b/src/shared/queue.h @@ -0,0 +1,31 @@ +#ifndef QUEUE_H +#define QUEUE_H + +#include +#include + +typedef struct Queue { + void **items; + int front; + int rear; + int size; + int capacity; + void (*item_destroyer)(void *item); +} Queue; + +Queue *queue_create(int capacity, void (*destroyer)(void *item)); +void queue_destroy(Queue *queue); +bool queue_is_empty(Queue *queue); +bool queue_is_full(Queue *queue); +void queue_double_capacity(Queue *queue); +void queue_enqueue(Queue *queue, void *item); +void queue_enqueue_multithreaded(Queue *queue, void *item, mtx_t *mutex, + cnd_t *condition_not_empty, + cnd_t *condition_not_full); +void *queue_dequeue(Queue *queue); +void *queue_dequeue_multithreaded(Queue *queue, mtx_t *mutex, + cnd_t *condition_not_empty, + cnd_t *condition_not_full, + bool *other_thread_done); + +#endif diff --git a/src/shared/socket.c b/src/shared/socket.c new file mode 100644 index 0000000..f2f5b92 --- /dev/null +++ b/src/shared/socket.c @@ -0,0 +1,211 @@ +#include "socket.h" +#include "log.h" +#include +#include +#include +#include +#include + +Server *server_create(int port) { + Server *server = (Server *)malloc(sizeof(Server)); + if (server == NULL) { + perror("Could not allocate space for Server"); + exit(EXIT_FAILURE); + } + + int file_descriptor = socket(AF_INET, SOCK_STREAM, 0); + if (file_descriptor < 0) { + perror("Could not create Socket!"); + exit(EXIT_FAILURE); + } + server->file_descriptor = file_descriptor; + int opt = 1; + if (setsockopt(server->file_descriptor, SOL_SOCKET, SO_REUSEADDR, &opt, + sizeof(opt))) { + perror("Error setting a socket option!"); + close(server->file_descriptor); + free(server); + exit(EXIT_FAILURE); + } + + server->address.sin_family = AF_INET; + server->address.sin_addr.s_addr = INADDR_ANY; + server->address.sin_port = htons(port); + server->address_length = sizeof(server->address); + + if (bind(server->file_descriptor, (struct sockaddr *)&server->address, + server->address_length) < 0) { + perror("Could not bind server"); + close(server->file_descriptor); + free(server); + exit(EXIT_FAILURE); + } + + return server; +} + +void server_delete(Server *server) { + free(server); + server = NULL; +}; + +void server_listen(Server *server, void (*handler)(int file_descriptor)) { + log_message(LOG_LEVEL_INFO, "Start Listening on Port: %d", + server->address.sin_port); + if (listen(server->file_descriptor, 3) < 0) { + perror("Could not listen on port!"); + exit(EXIT_FAILURE); + } + + int file_descriptor = + accept(server->file_descriptor, (struct sockaddr *)&server->address, + &server->address_length); + if (server->file_descriptor < 0) { + perror("Could not accept the connection"); + exit(EXIT_FAILURE); + } + log_message(LOG_LEVEL_INFO, "Received Connection"); + handler(file_descriptor); + close(server->file_descriptor); + close(file_descriptor); +} + +Client *client_create() { + int file_descriptor = socket(AF_INET, SOCK_STREAM, 0); + if (file_descriptor < 0) { + perror("Could not create Socket!"); + exit(EXIT_FAILURE); + }; + + Client *client = (Client *)malloc(sizeof(Client)); + client->file_descriptor = file_descriptor; + client->address.sin_family = AF_INET; + client->address_length = sizeof(client->address); + return client; +} + +void client_connect(Client *client, char *host, int port) { + client->address.sin_port = htons(port); + + if (inet_pton(AF_INET, host, &client->address.sin_addr) <= 0) { + perror("Could not convert host address!"); + exit(EXIT_FAILURE); + } + + if (connect(client->file_descriptor, (struct sockaddr *)&client->address, + client->address_length) < 0) { + perror("Could not connect to Server!"); + exit(EXIT_FAILURE); + } +} + +void client_disconnect(Client *client) { close(client->file_descriptor); } + +void client_delete(Client *client) { + if (client == NULL) + return; + free(client); +} + +DataFragment *data_fragment_create(void *data, unsigned long long size) { + DataFragment *data_fragment = malloc(sizeof(DataFragment)); + data_fragment->data = data; + data_fragment->size = size; + return data_fragment; +} + +void data_fragment_delete(void *data_fragment) { + if (data_fragment == NULL) + return; + DataFragment *fragment = (DataFragment *)data_fragment; + free(fragment->data); + free(fragment); +} + +void send_n_data(int file_descriptor, void *data, NET_SIZE data_size) { + log_message(LOG_LEVEL_DEBUG, " Sending n Data: %d", data_size); + NET_SIZE total_bytes_send = 0; + while (total_bytes_send < data_size) { + long long bytes_send = + send(file_descriptor, (char *)data + total_bytes_send, + data_size - total_bytes_send, 0); + if (bytes_send == 0) { + perror("Could not send data!"); + exit(EXIT_FAILURE); + } + total_bytes_send += bytes_send; + } + log_message(LOG_LEVEL_DEBUG, " Send n Data: %d", total_bytes_send); +} + +void receive_n_data(int file_descriptor, void *data, NET_SIZE data_size) { + log_message(LOG_LEVEL_DEBUG, " Receiving n Data: %d", data_size); + NET_SIZE total_bytes_received = 0; + while (total_bytes_received < data_size) { + long long bytes_received = + recv(file_descriptor, data + total_bytes_received, + data_size - total_bytes_received, 0); + if (bytes_received == -1 || bytes_received == 0) { + perror("Could not receive bytes!"); + exit(EXIT_FAILURE); + } + total_bytes_received += bytes_received; + } + log_message(LOG_LEVEL_DEBUG, " Received n Data: %d", total_bytes_received); +} + +void send_str(int file_descriptor, char *data) { + NET_SIZE size = strlen(data); + send_n_data(file_descriptor, &size, sizeof(NET_SIZE)); + send_n_data(file_descriptor, data, size); + log_message(LOG_LEVEL_DEBUG, "Send String: %s", data); +} + +char *receive_str(int file_descriptor) { + NET_SIZE size; + receive_n_data(file_descriptor, &size, sizeof(NET_SIZE)); + char *data = (char *)malloc(size + 1); + receive_n_data(file_descriptor, data, size); + data[size] = '\0'; + log_message(LOG_LEVEL_DEBUG, "Received String: %s", data); + return data; +} + +void send_data(int file_descriptor, void *data, unsigned long long data_size) { + send_n_data(file_descriptor, &data_size, sizeof(unsigned long long)); + send_n_data(file_descriptor, data, data_size); + log_message(LOG_LEVEL_DEBUG, "Send %lld data", data_size); +} + +DataFragment *receive_data(int file_descriptor) { + unsigned long long size = 0; + receive_n_data(file_descriptor, &size, sizeof(unsigned long long)); + void *data = malloc(size); + receive_n_data(file_descriptor, data, size); + log_message(LOG_LEVEL_DEBUG, "Received %lld data", size); + return data_fragment_create(data, size); +} + +void send_int(int file_descriptor, int data) { + send_n_data(file_descriptor, &data, sizeof(int)); + log_message(LOG_LEVEL_DEBUG, "Send Int: %d", data); +} + +int receive_int(int file_descriptor) { + int data; + receive_n_data(file_descriptor, &data, sizeof(int)); + log_message(LOG_LEVEL_DEBUG, "Received Int: %d", data); + return data; +} + +void send_status(int file_descriptor, Status status) { + send_n_data(file_descriptor, &status, sizeof(Status)); + log_message(LOG_LEVEL_DEBUG, "Send Status: %d", status); +} + +Status receive_status(int file_descriptor) { + Status data; + receive_n_data(file_descriptor, &data, sizeof(Status)); + log_message(LOG_LEVEL_DEBUG, "Received Status: %d", data); + return data; +} diff --git a/src/shared/socket.h b/src/shared/socket.h new file mode 100644 index 0000000..2ca98c7 --- /dev/null +++ b/src/shared/socket.h @@ -0,0 +1,51 @@ +#ifndef SOCKET_H +#define SOCKET_H + +#include "array_list.h" +#include + +typedef unsigned long long NET_SIZE; +typedef int Status; +enum NET_STATUS { OK, ERROR, FINISHED, NEXT }; + +typedef struct Server { + struct sockaddr_in address; + unsigned int address_length; + int file_descriptor; +} Server; + +Server *server_create(int port); +void server_listen(Server *server, void (*handler)(int file_descriptor)); +void server_delete(Server *server); + +typedef struct Client { + struct sockaddr_in address; + unsigned int address_length; + int file_descriptor; +} Client; + +typedef struct DataFragment { + void *data; + unsigned long long size; +} DataFragment; + +Client *client_create(); +void client_disconnect(Client *client); +void client_delete(Client *client); +void client_connect(Client *client, char *host, int port); + +DataFragment *data_fragment_create(void *data, unsigned long long size); +void data_fragment_delete(void *data_fragment); + +void send_n_data(int file_descriptor, void *data, NET_SIZE data_size); +void receive_n_data(int file_descriptor, void *data, NET_SIZE data_size); +void send_str(int file_descriptor, char *data); +char *receive_str(int file_descriptor); +void send_data(int file_descriptor, void *data, unsigned long long data_size); +DataFragment *receive_data(int file_descriptor); +void send_int(int file_descriptor, int data); +int receive_int(int file_descriptor); +void send_status(int file_descriptor, Status status); +Status receive_status(int file_descriptor); + +#endif diff --git a/src/shared/utils.c b/src/shared/utils.c new file mode 100644 index 0000000..9a3ead2 --- /dev/null +++ b/src/shared/utils.c @@ -0,0 +1,77 @@ +#include "utils.h" +#include "libgen.h" +#include "sys/stat.h" +#include +#include +#include + +void mkdir_r(char *path) { + char *path_duplicate = malloc(strlen(path) + 1); + strcpy(path_duplicate, path); + char *path_current = (char *)malloc((strlen(path) + 2) * sizeof(char)); + char *path_current_position = path_current; + const char *delimiter = "/"; + char *part = strtok(path_duplicate, delimiter); + // struct stat st; + while (part != NULL) { + strcpy(path_current_position, part); + path_current_position += strlen(part) * sizeof(char); + strcpy(path_current_position, "/"); + path_current_position += sizeof(char); + struct stat st; + if (stat(path_current, &st) != 0) { + if (mkdir(path_current, 0755) != 0) { + perror("Could not create directory"); + exit(EXIT_FAILURE); + } + } + part = strtok(NULL, delimiter); + } + free(path_duplicate); + free(path_current); +} + +char *str_dup(char *string) { + if (string == NULL) + return NULL; + char *new_string = (char *)malloc(strlen(string) + 1); + strcpy(new_string, string); + return new_string; +} + +void to_disk(char *path, void *data, unsigned long long data_size) { + char *directory = str_dup(path); + char *dir_to_free = directory; + directory = dirname(directory); + mkdir_r(directory); + FILE *file_pointer = fopen(path, "wb"); + if (file_pointer == NULL) { + perror("Could not open File"); + exit(EXIT_FAILURE); + } + fwrite(data, 1, data_size, file_pointer); + fclose(file_pointer); + free(dir_to_free); +} + +char *path_cat(char *path1, char *path2) { + if (path1 == NULL || *path1 == '\0') + return str_dup(path2); + if (path2 == NULL || *path2 == '\0') + return str_dup(path1); + int path1_len = strlen(path1); + int path2_len = strlen(path2); + char *path2_pointer = path2; + if (path1[path1_len - 1] == '/') + path1_len -= 1; + if (path2[0] == '/') { + path2_pointer += 1; + path2_len -= 1; + } + char *new_path = malloc(path1_len + path2_len + 2); + memcpy(new_path, path1, path1_len); + new_path[path1_len] = '/'; + memcpy(new_path + path1_len + 1, path2_pointer, path2_len); + new_path[path1_len + path2_len + 1] = '\0'; + return new_path; +} diff --git a/src/shared/utils.h b/src/shared/utils.h new file mode 100644 index 0000000..c1449d2 --- /dev/null +++ b/src/shared/utils.h @@ -0,0 +1,9 @@ +#ifndef UTILS_H +#define UTILS_H + +void mkdir_r(char *path); +char *str_dup(char *string); +void to_disk(char *path, void *data, unsigned long long data_size); +char *path_cat(char *path1, char *path2); + +#endif diff --git a/test.py b/test.py new file mode 100644 index 0000000..7971bb5 --- /dev/null +++ b/test.py @@ -0,0 +1,91 @@ +import subprocess +import time + +# --- Configuration --- +SERVER_CMD = ["./server"] +#original_client_cmd = ["./client"] +original_client_cmd = ["./client" , "-m"] + +# --- Resource Limit Configuration --- +# 💾 Disk throttling settings +DISK_DEVICE = "/dev/nvme0n1p5" # IMPORTANT: Change this to your disk (e.g., /dev/nvme0n1) +READ_BPS_MAX = "15M" # Max read speed (M for megabytes) +WRITE_BPS_MAX = "10M" # Max write speed + +# 🐢 Network throttling settings (Linux tc) +NET_LIMIT = "2mbit" +NET_DELAY = "100ms" +NETWORK_INTERFACE = "lo" +NET_LIMIT_CMD = f"sudo tc qdisc add dev {NETWORK_INTERFACE} root netem rate {NET_LIMIT} delay {NET_DELAY}".split() +NET_RESET_CMD = f"sudo tc qdisc del dev {NETWORK_INTERFACE} root".split() + +# --- Build the final client command with throttling --- +# This uses systemd-run to wrap the original client command with I/O limits. +# The entire command must be run with sudo. +CLIENT_CMD = [ + "sudo", "systemd-run", + "--scope", + "-p", f"IOReadBandwidthMax={DISK_DEVICE} {READ_BPS_MAX}", + "-p", f"IOWriteBandwidthMax={DISK_DEVICE} {WRITE_BPS_MAX}", + # The command to run is placed at the end +] + original_client_cmd + + +# --- Benchmarking --- +server_process = None +print("🚀 Starting benchmark...") +print(f"Limiting Disk I/O: Reads <= {READ_BPS_MAX}, Writes <= {WRITE_BPS_MAX}") +print("Limiting Network: Simulating low bandwidth and high latency") + +try: + # SETUP: Apply network limit + subprocess.run(NET_LIMIT_CMD, check=True) + + # 1. Start the server + print("Starting server...") + server_process = subprocess.Popen(SERVER_CMD) + time.sleep(1) + + # 2. Run the client with all limits applied + print("Running client under network AND disk constraints...") + start_time = time.monotonic() + + # The CLIENT_CMD now includes the sudo and systemd-run wrapper + client_result = subprocess.run(CLIENT_CMD, capture_output=True, text=True) + + end_time = time.monotonic() + + # 3. Calculate and print duration + duration = end_time - start_time + print("-" * 30) + print(f"✅ Client execution time: {duration:.4f} seconds") + print("-" * 30) + + if client_result.returncode != 0: + print(f"⚠️ Client exited with an error (code: {client_result.returncode}).") + print("--- Client STDERR ---") + print(client_result.stderr) + print("-" * 21) + + +except FileNotFoundError as e: + print(f"❌ Error: Command not found - {e.filename}. Is the path correct?") +except subprocess.CalledProcessError as e: + print(f"❌ Error running command: {' '.join(e.cmd)}") + print("Are you running this script with 'sudo'?") + +finally: + # TEARDOWN: Remove network limits and shut down the server + # No disk cleanup is needed because systemd-run handles it! + print("Cleaning up...") + try: + print("Removing network limit...") + subprocess.run(NET_RESET_CMD, check=True, capture_output=True) + except Exception as e: + print(f"⚠️ Could not reset network settings: {e}") + + if server_process: + print("Shutting down server...") + server_process.terminate() + server_process.wait() + print("Cleanup complete.") diff --git a/tests/runner.c b/tests/runner.c new file mode 100644 index 0000000..40dd500 --- /dev/null +++ b/tests/runner.c @@ -0,0 +1,32 @@ +#include "test_array_list.h" +#include "test_chunk.h" +#include "test_config.h" +#include "test_queue.h" +#include "test_shared_utils.h" +#include "test_utils.h" +#include + +// Define global test state variables +int tests_run = 0; +int tests_failed = 0; +bool current_test_failed = false; + +int main() { + printf("\033[1;36m=== RUNNING UNIT TESTS ===\033[0m\n\n"); + + RUN_TEST(test_queue); + RUN_TEST(test_array_list); + RUN_TEST(test_shared_utils); + RUN_TEST(test_chunk); + RUN_TEST(test_config); + + printf("\n\033[1;36m=== TEST SUMMARY ===\033[0m\n"); + printf("Total Tests Run: %d\n", tests_run); + if (tests_failed > 0) { + printf("Status: \033[1;31m%d FAILED\033[0m\n", tests_failed); + return 1; + } else { + printf("Status: \033[1;32mALL PASSED\033[0m\n"); + return 0; + } +} diff --git a/tests/test_array_list.c b/tests/test_array_list.c new file mode 100644 index 0000000..03c11ac --- /dev/null +++ b/tests/test_array_list.c @@ -0,0 +1,64 @@ +#include "test_array_list.h" +#include "array_list.h" +#include "test_utils.h" +#include + +static int destroyer_calls = 0; +static void test_destroyer(void *item) { + destroyer_calls++; + free(item); +} + +void test_array_list() { + ArrayList *list = array_list_create(free); + EXPECT_NOT_NULL(list); + EXPECT_EQ_INT(list->size, 0); + EXPECT_EQ_INT(list->capacity, 100); + + // Test adding + int *val1 = malloc(sizeof(int)); + *val1 = 42; + array_list_add(list, val1); + EXPECT_EQ_INT(list->size, 1); + EXPECT_EQ_INT(*(int *)list->items[0], 42); + + // Test extending capacity + // Initial capacity is 100. Let's add 105 elements. + for (int i = 0; i < 105; i++) { + int *val = malloc(sizeof(int)); + *val = i; + array_list_add(list, val); + } + EXPECT_EQ_INT(list->size, 106); + EXPECT_EQ_INT(list->capacity, 200); // 100 * 2 + + // Verify contents + EXPECT_EQ_INT(*(int *)list->items[0], 42); + EXPECT_EQ_INT(*(int *)list->items[1], 0); + EXPECT_EQ_INT(*(int *)list->items[105], 104); + + // Test array conversion + void **arr = array_list_to_array(list); + EXPECT_NOT_NULL(arr); + EXPECT_EQ_INT(*(int *)arr[0], 42); + EXPECT_EQ_INT(*(int *)arr[105], 104); + free(arr); + + // Delete list, verifying the destroyer is called 106 times + destroyer_calls = 0; + list->item_destroyer = test_destroyer; + array_list_delete(list); + EXPECT_EQ_INT(destroyer_calls, 106); + + // Test clear with NULL destroyer + list = array_list_create(NULL); + int a = 1, b = 2; + array_list_add(list, &a); + array_list_add(list, &b); + EXPECT_EQ_INT(list->size, 2); + array_list_clear(list); + EXPECT_EQ_INT(list->size, 0); + EXPECT_NULL(list->items[0]); + EXPECT_NULL(list->items[1]); + array_list_delete(list); +} diff --git a/tests/test_array_list.h b/tests/test_array_list.h new file mode 100644 index 0000000..cb952b3 --- /dev/null +++ b/tests/test_array_list.h @@ -0,0 +1,6 @@ +#ifndef TEST_ARRAY_LIST_H +#define TEST_ARRAY_LIST_H + +void test_array_list(); + +#endif diff --git a/tests/test_chunk.c b/tests/test_chunk.c new file mode 100644 index 0000000..118a772 --- /dev/null +++ b/tests/test_chunk.c @@ -0,0 +1,147 @@ +#include "test_chunk.h" +#include "chunk.h" +#include "utils.h" +#include "test_utils.h" +#include +#include +#include +#include + +static void test_file_operations() { + char *test_path = "temp_file_test.txt"; + char *test_content = "Hello, Chunk System!"; + unsigned long long test_len = strlen(test_content); + + to_disk(test_path, test_content, test_len); + + struct stat st; + int stat_res = stat(test_path, &st); + EXPECT_EQ_INT(stat_res, 0); + EXPECT_EQ_INT((int)st.st_size, (int)test_len); + + File *f = file_create(test_path, &st); + EXPECT_NOT_NULL(f); + EXPECT_EQ_STR(f->path, test_path); + EXPECT_NULL(f->data); + + file_load_data(f); + EXPECT_NOT_NULL(f->data); + EXPECT_EQ_INT(memcmp(f->data, test_content, test_len), 0); + + file_destroy(f); + unlink(test_path); +} + +static void test_file_receive_operations() { + char *path = str_dup("temp_receive.txt"); + char *data = str_dup("receive data content"); + unsigned long long size = strlen(data); + + DataFragment *df = data_fragment_create(data, size); + EXPECT_NOT_NULL(df); + EXPECT_EQ_INT((int)df->size, (int)size); + EXPECT_EQ_STR(df->data, "receive data content"); + + FileReceive *fr = file_receive_create(path, df); + EXPECT_NOT_NULL(fr); + EXPECT_EQ_STR(fr->path, "temp_receive.txt"); + EXPECT_NOT_NULL(fr->data_fragment); + EXPECT_EQ_STR(fr->data_fragment->data, "receive data content"); + + file_receive_destroy(fr); +} + +static void test_chunk_operations() { + char *path1 = "temp_chunk_1.txt"; + char *content1 = "chunk item 1"; + unsigned long long len1 = strlen(content1); + + char *path2 = "temp_chunk_2.txt"; + char *content2 = "chunk item number 2"; + unsigned long long len2 = strlen(content2); + + to_disk(path1, content1, len1); + to_disk(path2, content2, len2); + + struct stat st1, st2; + stat(path1, &st1); + stat(path2, &st2); + + File *f1 = file_create(path1, &st1); + File *f2 = file_create(path2, &st2); + + File *files[2] = {f1, f2}; + Chunk *chunk = chunk_create(files, 2); + EXPECT_NOT_NULL(chunk); + EXPECT_EQ_INT(chunk->element_count, 2); + EXPECT_NOT_NULL(chunk->items[0]); + EXPECT_NOT_NULL(chunk->items[1]); + + Data *formatted = chunk_format(chunk); + EXPECT_NOT_NULL(formatted); + + unsigned long long expected_size = + (sizeof(int) + strlen(path1) + sizeof(unsigned long long) + len1) + + (sizeof(int) + strlen(path2) + sizeof(unsigned long long) + len2); + EXPECT_EQ_INT((int)formatted->data_size, (int)expected_size); + + char *ptr = (char *)formatted->data; + + // File 1 + int p_len1; + memcpy(&p_len1, ptr, sizeof(int)); + ptr += sizeof(int); + EXPECT_EQ_INT(p_len1, (int)strlen(path1)); + + char read_path1[256]; + memcpy(read_path1, ptr, p_len1); + read_path1[p_len1] = '\0'; + ptr += p_len1; + EXPECT_EQ_STR(read_path1, path1); + + unsigned long long d_len1; + memcpy(&d_len1, ptr, sizeof(unsigned long long)); + ptr += sizeof(unsigned long long); + EXPECT_EQ_INT((int)d_len1, (int)len1); + + char read_content1[256]; + memcpy(read_content1, ptr, d_len1); + read_content1[d_len1] = '\0'; + ptr += d_len1; + EXPECT_EQ_STR(read_content1, content1); + + // File 2 + int p_len2; + memcpy(&p_len2, ptr, sizeof(int)); + ptr += sizeof(int); + EXPECT_EQ_INT(p_len2, (int)strlen(path2)); + + char read_path2[256]; + memcpy(read_path2, ptr, p_len2); + read_path2[p_len2] = '\0'; + ptr += p_len2; + EXPECT_EQ_STR(read_path2, path2); + + unsigned long long d_len2; + memcpy(&d_len2, ptr, sizeof(unsigned long long)); + ptr += sizeof(unsigned long long); + EXPECT_EQ_INT((int)d_len2, (int)len2); + + char read_content2[256]; + memcpy(read_content2, ptr, d_len2); + read_content2[d_len2] = '\0'; + ptr += d_len2; + EXPECT_EQ_STR(read_content2, content2); + + chunk_data_delete(formatted); + chunk_destroy(chunk); + + unlink(path1); + unlink(path2); +} + +void test_chunk() { + test_file_operations(); + test_file_receive_operations(); + test_chunk_operations(); +} diff --git a/tests/test_chunk.h b/tests/test_chunk.h new file mode 100644 index 0000000..5084d22 --- /dev/null +++ b/tests/test_chunk.h @@ -0,0 +1,6 @@ +#ifndef TEST_CHUNK_H +#define TEST_CHUNK_H + +void test_chunk(); + +#endif diff --git a/tests/test_config.c b/tests/test_config.c new file mode 100644 index 0000000..33c6416 --- /dev/null +++ b/tests/test_config.c @@ -0,0 +1,58 @@ +#include "test_config.h" +#include "config.h" +#include "multiprocessing.h" +#include "queue.h" +#include "utils.h" +#include "test_utils.h" +#include + +static void test_config_lifecycle() { + Config *cfg = config_create(str_dup("1.0"), str_dup("/src"), str_dup("/dst"), true, true, false, false, false, 4); + EXPECT_NOT_NULL(cfg); + EXPECT_EQ_STR(cfg->version, "1.0"); + EXPECT_EQ_STR(cfg->send_directory, "/src"); + EXPECT_EQ_STR(cfg->receive_root_directory, "/dst"); + EXPECT_TRUE(cfg->save_to_disk); + EXPECT_TRUE(cfg->use_multithreading); + EXPECT_FALSE(cfg->use_chunk_serialization); + EXPECT_FALSE(cfg->use_compression); + EXPECT_FALSE(cfg->use_multiprocessing); + EXPECT_EQ_INT(cfg->num_connections, 4); + config_delete(cfg); +} + +static void test_pipeline_sender_lifecycle() { + Config *cfg = config_create(str_dup("2.0"), str_dup("/src2"), str_dup("/dst2"), false, false, true, true, true, 8); + Queue *q1 = queue_create(5, NULL); + Queue *q2 = queue_create(15, NULL); + + PipelineContextSender *pcs = pipeline_context_sender_create(cfg, q1, q2); + EXPECT_NOT_NULL(pcs); + EXPECT_EQ_STR(pcs->config->version, "2.0"); + EXPECT_EQ_INT(pcs->queue_scanner->capacity, 5); + EXPECT_EQ_INT(pcs->queue_loader->capacity, 15); + EXPECT_FALSE(pcs->scanner_done); + EXPECT_FALSE(pcs->loader_done); + + pipeline_context_sender_destroy(pcs); +} + +static void test_pipeline_receiver_lifecycle() { + Config *cfg = config_create(str_dup("3.0"), str_dup("/src3"), str_dup("/dst3"), true, true, true, true, true, 2); + Queue *q = queue_create(20, NULL); + + PipelineContextReceiver *pcr = pipeline_context_receiver_create(cfg, q, 42); + EXPECT_NOT_NULL(pcr); + EXPECT_EQ_STR(pcr->config->version, "3.0"); + EXPECT_EQ_INT(pcr->queue->capacity, 20); + EXPECT_EQ_INT(pcr->file_descriptor, 42); + EXPECT_FALSE(pcr->receiver_done); + + pipeline_context_receiver_destroy(pcr); +} + +void test_config() { + test_config_lifecycle(); + test_pipeline_sender_lifecycle(); + test_pipeline_receiver_lifecycle(); +} diff --git a/tests/test_config.h b/tests/test_config.h new file mode 100644 index 0000000..e245193 --- /dev/null +++ b/tests/test_config.h @@ -0,0 +1,6 @@ +#ifndef TEST_CONFIG_H +#define TEST_CONFIG_H + +void test_config(); + +#endif diff --git a/tests/test_queue.c b/tests/test_queue.c new file mode 100644 index 0000000..e04e997 --- /dev/null +++ b/tests/test_queue.c @@ -0,0 +1,206 @@ +#include "test_queue.h" +#include "queue.h" +#include "test_utils.h" +#include +#include +#include +#include + +static void test_queue_basic() { + Queue *q = queue_create(10, NULL); + EXPECT_NOT_NULL(q); + EXPECT_TRUE(queue_is_empty(q)); + EXPECT_FALSE(queue_is_full(q)); + + int *vals[5]; + for (int i = 0; i < 5; i++) { + vals[i] = malloc(sizeof(int)); + *vals[i] = (i + 1) * 10; + queue_enqueue(q, vals[i]); + } + + EXPECT_FALSE(queue_is_empty(q)); + EXPECT_FALSE(queue_is_full(q)); + EXPECT_EQ_INT(q->size, 5); + + int *v1 = (int *)queue_dequeue(q); + EXPECT_NOT_NULL(v1); + EXPECT_EQ_INT(*v1, 10); + free(v1); + + int *v2 = (int *)queue_dequeue(q); + EXPECT_NOT_NULL(v2); + EXPECT_EQ_INT(*v2, 20); + free(v2); + + EXPECT_EQ_INT(q->size, 3); + + int *vals2[3]; + for (int i = 0; i < 3; i++) { + vals2[i] = malloc(sizeof(int)); + *vals2[i] = (i + 6) * 10; + queue_enqueue(q, vals2[i]); + } + + EXPECT_EQ_INT(q->size, 6); + + int expected_vals[] = {30, 40, 50, 60, 70, 80}; + for (int i = 0; i < 6; i++) { + int *v = (int *)queue_dequeue(q); + EXPECT_NOT_NULL(v); + EXPECT_EQ_INT(*v, expected_vals[i]); + free(v); + } + + EXPECT_TRUE(queue_is_empty(q)); + queue_destroy(q); +} + +static void test_queue_resize() { + Queue *q = queue_create(3, NULL); + EXPECT_NOT_NULL(q); + EXPECT_EQ_INT(q->capacity, 3); + + int a = 1, b = 2, c = 3, d = 4, e = 5; + + queue_enqueue(q, &a); + queue_enqueue(q, &b); + queue_enqueue(q, &c); + + EXPECT_TRUE(queue_is_full(q)); + + int *v1 = (int *)queue_dequeue(q); + EXPECT_NOT_NULL(v1); + EXPECT_EQ_INT(*v1, 1); + + // Now front = 1, rear = 0, size = 2 (wrapped state) + queue_enqueue(q, &d); + EXPECT_TRUE(queue_is_full(q)); + + // This enqueue triggers capacity doubling + queue_enqueue(q, &e); + EXPECT_FALSE(queue_is_full(q)); + EXPECT_EQ_INT(q->capacity, 6); + EXPECT_EQ_INT(q->size, 4); + + // Dequeue all and check order: B, C, D, E + int *v2 = (int *)queue_dequeue(q); + EXPECT_NOT_NULL(v2); + EXPECT_EQ_INT(*v2, 2); + + int *v3 = (int *)queue_dequeue(q); + EXPECT_NOT_NULL(v3); + EXPECT_EQ_INT(*v3, 3); + + int *v4 = (int *)queue_dequeue(q); + EXPECT_NOT_NULL(v4); + EXPECT_EQ_INT(*v4, 4); + + int *v5 = (int *)queue_dequeue(q); + EXPECT_NOT_NULL(v5); + EXPECT_EQ_INT(*v5, 5); + + EXPECT_TRUE(queue_is_empty(q)); + queue_destroy(q); +} + +static int destroyer_calls = 0; +static void my_destroyer(void *item) { + destroyer_calls++; + free(item); +} + +static void test_queue_destroyer() { + destroyer_calls = 0; + Queue *q = queue_create(5, my_destroyer); + EXPECT_NOT_NULL(q); + + for (int i = 0; i < 3; i++) { + int *val = malloc(sizeof(int)); + *val = i; + queue_enqueue(q, val); + } + + int *v = (int *)queue_dequeue(q); + EXPECT_NOT_NULL(v); + EXPECT_EQ_INT(*v, 0); + free(v); + + queue_destroy(q); + EXPECT_EQ_INT(destroyer_calls, 2); +} + +typedef struct { + Queue *q; + mtx_t *mutex; + cnd_t *cnd_empty; + cnd_t *cnd_full; + bool done; + int sum; +} ThreadContext; + +static int consumer_func(void *arg) { + ThreadContext *ctx = (ThreadContext *)arg; + while (true) { + int *val = (int *)queue_dequeue_multithreaded(ctx->q, ctx->mutex, ctx->cnd_empty, ctx->cnd_full, &ctx->done); + if (val == NULL) { + break; + } + ctx->sum += *val; + free(val); + } + return 0; +} + +static void test_queue_multithreaded() { + Queue *q = queue_create(2, NULL); + mtx_t mutex; + cnd_t cnd_empty; + cnd_t cnd_full; + + mtx_init(&mutex, mtx_plain); + cnd_init(&cnd_empty); + cnd_init(&cnd_full); + + ThreadContext ctx = { + .q = q, + .mutex = &mutex, + .cnd_empty = &cnd_empty, + .cnd_full = &cnd_full, + .done = false, + .sum = 0 + }; + + thrd_t consumer; + int res = thrd_create(&consumer, consumer_func, &ctx); + EXPECT_EQ_INT(res, thrd_success); + + for (int i = 1; i <= 100; i++) { + int *val = malloc(sizeof(int)); + *val = i; + queue_enqueue_multithreaded(q, val, &mutex, &cnd_empty, &cnd_full); + } + + mtx_lock(&mutex); + ctx.done = true; + cnd_signal(&cnd_empty); + mtx_unlock(&mutex); + + int join_res; + thrd_join(consumer, &join_res); + + EXPECT_EQ_INT(ctx.sum, 5050); + EXPECT_TRUE(queue_is_empty(q)); + + queue_destroy(q); + mtx_destroy(&mutex); + cnd_destroy(&cnd_empty); + cnd_destroy(&cnd_full); +} + +void test_queue() { + test_queue_basic(); + test_queue_resize(); + test_queue_destroyer(); + test_queue_multithreaded(); +} diff --git a/tests/test_queue.h b/tests/test_queue.h new file mode 100644 index 0000000..1f53e69 --- /dev/null +++ b/tests/test_queue.h @@ -0,0 +1,6 @@ +#ifndef TEST_QUEUE_H +#define TEST_QUEUE_H + +void test_queue(); + +#endif diff --git a/tests/test_shared_utils.c b/tests/test_shared_utils.c new file mode 100644 index 0000000..3470d8b --- /dev/null +++ b/tests/test_shared_utils.c @@ -0,0 +1,62 @@ +#include "test_shared_utils.h" +#include "utils.h" +#include "test_utils.h" +#include +#include + +void test_shared_utils() { + // Test str_dup + char *dup_null = str_dup(NULL); + EXPECT_NULL(dup_null); + + char *dup_empty = str_dup(""); + EXPECT_NOT_NULL(dup_empty); + EXPECT_EQ_STR(dup_empty, ""); + free(dup_empty); + + char *dup_normal = str_dup("hello world"); + EXPECT_NOT_NULL(dup_normal); + EXPECT_EQ_STR(dup_normal, "hello world"); + free(dup_normal); + + // Test path_cat + char *cat1 = path_cat("/foo", "/bar"); + EXPECT_NOT_NULL(cat1); + EXPECT_EQ_STR(cat1, "/foo/bar"); + free(cat1); + + char *cat2 = path_cat("/foo/", "/bar"); + EXPECT_NOT_NULL(cat2); + EXPECT_EQ_STR(cat2, "/foo/bar"); + free(cat2); + + char *cat3 = path_cat("/foo", "bar"); + EXPECT_NOT_NULL(cat3); + EXPECT_EQ_STR(cat3, "/foo/bar"); + free(cat3); + + char *cat4 = path_cat("/foo/", "bar"); + EXPECT_NOT_NULL(cat4); + EXPECT_EQ_STR(cat4, "/foo/bar"); + free(cat4); + + char *cat_empty1 = path_cat("", "/bar"); + EXPECT_NOT_NULL(cat_empty1); + EXPECT_EQ_STR(cat_empty1, "/bar"); + free(cat_empty1); + + char *cat_empty2 = path_cat("/foo", ""); + EXPECT_NOT_NULL(cat_empty2); + EXPECT_EQ_STR(cat_empty2, "/foo"); + free(cat_empty2); + + char *cat_null1 = path_cat(NULL, "/bar"); + EXPECT_NOT_NULL(cat_null1); + EXPECT_EQ_STR(cat_null1, "/bar"); + free(cat_null1); + + char *cat_null2 = path_cat("/foo", NULL); + EXPECT_NOT_NULL(cat_null2); + EXPECT_EQ_STR(cat_null2, "/foo"); + free(cat_null2); +} diff --git a/tests/test_shared_utils.h b/tests/test_shared_utils.h new file mode 100644 index 0000000..b33b7a2 --- /dev/null +++ b/tests/test_shared_utils.h @@ -0,0 +1,6 @@ +#ifndef TEST_SHARED_UTILS_H +#define TEST_SHARED_UTILS_H + +void test_shared_utils(); + +#endif diff --git a/tests/test_utils.h b/tests/test_utils.h new file mode 100644 index 0000000..dda6bb6 --- /dev/null +++ b/tests/test_utils.h @@ -0,0 +1,94 @@ +#ifndef TEST_UTILS_H +#define TEST_UTILS_H + +#include +#include +#include + +// Global test suite status +extern int tests_run; +extern int tests_failed; +extern bool current_test_failed; + +// Helper to run a test function +#define RUN_TEST(test_func) \ + do { \ + printf("Running %s...\n", #test_func); \ + tests_run++; \ + current_test_failed = false; \ + test_func(); \ + if (current_test_failed) { \ + tests_failed++; \ + printf(" \033[1;31m[FAILED]\033[0m %s\n", #test_func); \ + } else { \ + printf(" \033[1;32m[PASSED]\033[0m %s\n", #test_func); \ + } \ + } while (0) + +// Assertion macros +#define EXPECT_TRUE(condition) \ + do { \ + if (!(condition)) { \ + printf(" \033[1;31m[FAIL]\033[0m %s:%d: Assertion failed: %s is false\n", __FILE__, __LINE__, #condition); \ + current_test_failed = true; \ + return; \ + } \ + } while (0) + +#define EXPECT_FALSE(condition) \ + do { \ + if (condition) { \ + printf(" \033[1;31m[FAIL]\033[0m %s:%d: Assertion failed: %s is true\n", __FILE__, __LINE__, #condition); \ + current_test_failed = true; \ + return; \ + } \ + } while (0) + +#define EXPECT_EQ_INT(actual, expected) \ + do { \ + int act = (actual); \ + int exp = (expected); \ + if (act != exp) { \ + printf(" \033[1;31m[FAIL]\033[0m %s:%d: Expected %d, got %d\n", __FILE__, __LINE__, exp, act); \ + current_test_failed = true; \ + return; \ + } \ + } while (0) + +#define EXPECT_EQ_STR(actual, expected) \ + do { \ + const char *act = (actual); \ + const char *exp = (expected); \ + if (act == NULL || exp == NULL) { \ + if (act != exp) { \ + printf(" \033[1;31m[FAIL]\033[0m %s:%d: Expected %s, got %s\n", __FILE__, __LINE__, \ + exp ? exp : "NULL", act ? act : "NULL"); \ + current_test_failed = true; \ + return; \ + } \ + } else if (strcmp(act, exp) != 0) { \ + printf(" \033[1;31m[FAIL]\033[0m %s:%d: Expected \"%s\", got \"%s\"\n", __FILE__, __LINE__, exp, act); \ + current_test_failed = true; \ + return; \ + } \ + } while (0) + +#define EXPECT_NOT_NULL(ptr) \ + do { \ + if ((ptr) == NULL) { \ + printf(" \033[1;31m[FAIL]\033[0m %s:%d: Expected non-null pointer, got NULL\n", __FILE__, __LINE__); \ + current_test_failed = true; \ + return; \ + } \ + } while (0) + +#define EXPECT_NULL(ptr) \ + do { \ + if ((ptr) != NULL) { \ + printf(" \033[1;31m[FAIL]\033[0m %s:%d: Expected NULL, got %p\n", __FILE__, __LINE__, (void*)(ptr)); \ + current_test_failed = true; \ + return; \ + } \ + } while (0) + +#endif diff --git a/to_one_file.py b/to_one_file.py new file mode 100644 index 0000000..bc70b50 --- /dev/null +++ b/to_one_file.py @@ -0,0 +1,14 @@ +from pathlib import Path + +path = Path(".") +text = "" +for file in path.glob("**/*.h"): + text += "--- " + str(file) + " ---\n\n" + text += file.read_text() +for file in path.glob("**/*.c"): + text += "--- " + str(file) + " ---\n\n" + text += file.read_text() +for file in [Path("Makefile")]: + text += "--- " + str(file) + " ---\n\n" + text += file.read_text() +Path("all.txt").write_text(text)