--- 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)