diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..46f42f8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +CMakeLists.txt.user +CMakeCache.txt +CMakeFiles +CMakeScripts +Testing +Makefile +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +_deps diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..f5bc52a --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 3.8) +project(MIMUW-FORK C) + +set(CMAKE_CXX_STANDARD "17") +set(CMAKE_C_STANDARD "11") +set(CMAKE_C_FLAGS "-g -Wall -Wextra -Wno-sign-compare") + +add_library(err err.c) +add_library(path_utils path_utils.c) +add_library(HashMap HashMap.c) + +add_library(rwlock rwlock.c) +target_link_libraries(rwlock pthread err) + +add_library(Tree Tree.c) +target_link_libraries(Tree err HashMap path_utils rwlock) + +add_executable(main main.c) +target_link_libraries(main Tree HashMap err pthread) + +install(TARGETS DESTINATION .) diff --git a/HashMap.c b/HashMap.c new file mode 100644 index 0000000..d0e9490 --- /dev/null +++ b/HashMap.c @@ -0,0 +1,134 @@ +#include +#include +#include + +#include "HashMap.h" + +// We fix the number of hash buckets for simplicity. +#define N_BUCKETS 8 + +typedef struct Pair Pair; + +struct Pair { + char* key; + void* value; + Pair* next; // Next item in a single-linked list. +}; + +struct HashMap { + Pair* buckets[N_BUCKETS]; // Linked lists of key-value pairs. + size_t size; // total number of entries in map. +}; + +static unsigned int get_hash(const char* key); + +HashMap* hmap_new() +{ + HashMap* map = malloc(sizeof(HashMap)); + if (!map) + return NULL; + memset(map, 0, sizeof(HashMap)); + return map; +} + +void hmap_free(HashMap* map) +{ + for (int h = 0; h < N_BUCKETS; ++h) { + for (Pair* p = map->buckets[h]; p;) { + Pair* q = p; + p = p->next; + free(q->key); + free(q); + } + } + free(map); +} + +static Pair* hmap_find(HashMap* map, int h, const char* key) +{ + for (Pair* p = map->buckets[h]; p; p = p->next) { + if (strcmp(key, p->key) == 0) + return p; + } + return NULL; +} + +void* hmap_get(HashMap* map, const char* key) +{ + int h = get_hash(key); + Pair* p = hmap_find(map, h, key); + if (p) + return p->value; + else + return NULL; +} + +bool hmap_insert(HashMap* map, const char* key, void* value) +{ + if (!value) + return false; + int h = get_hash(key); + Pair* p = hmap_find(map, h, key); + if (p) + return false; // Already exists. + Pair* new_p = malloc(sizeof(Pair)); + new_p->key = strdup(key); + new_p->value = value; + new_p->next = map->buckets[h]; + map->buckets[h] = new_p; + map->size++; + return true; +} + +bool hmap_remove(HashMap* map, const char* key) +{ + int h = get_hash(key); + Pair** pp = &(map->buckets[h]); + while (*pp) { + Pair* p = *pp; + if (strcmp(key, p->key) == 0) { + *pp = p->next; + free(p->key); + free(p); + map->size--; + return true; + } + pp = &(p->next); + } + return false; +} + +size_t hmap_size(HashMap* map) +{ + return map->size; +} + +HashMapIterator hmap_iterator(HashMap* map) +{ + HashMapIterator it = { 0, map->buckets[0] }; + return it; +} + +bool hmap_next(HashMap* map, HashMapIterator* it, const char** key, void** value) +{ + Pair* p = it->pair; + while (!p && it->bucket < N_BUCKETS - 1) { + p = map->buckets[++it->bucket]; + } + if (!p) + return false; + *key = p->key; + *value = p->value; + it->pair = p->next; + return true; +} + +static unsigned int get_hash(const char* key) +{ + unsigned int hash = 17; + while (*key) { + hash = (hash << 3) + hash + *key; + ++key; + } + return hash % N_BUCKETS; +} diff --git a/HashMap.h b/HashMap.h new file mode 100644 index 0000000..a4cfa95 --- /dev/null +++ b/HashMap.h @@ -0,0 +1,57 @@ +#pragma once +#include +#include + +// A structure representing a mapping from keys to values. +// Keys are C-strings (null-terminated char*), all distinct. +// Values are non-null pointers (void*, which you can cast to any other pointer type). +typedef struct HashMap HashMap; + +// Create a new, empty map. +HashMap* hmap_new(); + +// Clear the map and free its memory. This frees the map and the keys +// copied by hmap_insert, but does not free any values. +void hmap_free(HashMap* map); + +// Get the value stored under `key`, or NULL if not present. +void* hmap_get(HashMap* map, const char* key); + +// Insert a `value` under `key` and return true, +// or do nothing and return false if `key` already exists in the map. +// `value` must not be NULL. +// (The caller can free `key` at any time - the map internally uses a copy of it). +bool hmap_insert(HashMap* map, const char* key, void* value); + +// Remove the value under `key` and return true (the value is not free'd), +// or do nothing and return false if `key` was not present. +bool hmap_remove(HashMap* map, const char* key); + +// Return the number of elements in the map. +size_t hmap_size(HashMap* map); + +typedef struct HashMapIterator HashMapIterator; + +// Return an iterator to the map. See `hmap_next`. +HashMapIterator hmap_iterator(HashMap* map); + +// Set `*key` and `*value` to the current element pointed by iterator and +// move the iterator to the next element. +// If there are no more elements, leaves `*key` and `*value` unchanged and +// returns false. +// +// The map cannot be modified between calls to `hmap_iterator` and `hmap_next`. +// +// Usage: ``` +// const char* key; +// void* value; +// HashMapIterator it = hmap_iterator(map); +// while (hmap_next(map, &it, &key, &value)) +// foo(key, value); +// ``` +bool hmap_next(HashMap* map, HashMapIterator* it, const char** key, void** value); + +struct HashMapIterator { + int bucket; + void* pair; +}; diff --git a/Tree.c b/Tree.c new file mode 100644 index 0000000..4c5dbc5 --- /dev/null +++ b/Tree.c @@ -0,0 +1,633 @@ +#include +#include +#include // NULL +#include // strlen +#include +#include + +#include "Tree.h" +#include "HashMap.h" +#include "err.h" +#include "path_utils.h" +#include "rwlock.h" + +struct Tree { + HashMap *hmap; + rwlock_t *rwlock; +}; + +Tree* tree_new() { + Tree *tree = (Tree *)malloc(sizeof(Tree)); + if (!tree) { bad_malloc(); } + if (!(tree->rwlock = rwlock_new())) { syserr("Unable to create lock"); } + if (!(tree->hmap = hmap_new())) { bad_malloc(); } + return tree; +} + +// Można zakładać, że operacja tree_free zostanie wykonana na danym drzewie dokładnie raz, po zakończeniu wszystkich innych operacji. +// wiec nie musimy blokowac wierzcholkow, caller musi poczekac az sie skoncza +void tree_free(Tree* tree) { + const char *key; + void *value; + HashMapIterator it = hmap_iterator(tree->hmap); + while (hmap_next(tree->hmap, &it, &key, &value)) { + Tree *child = (Tree *)value; + tree_free(child); + } + + rwlock_destroy(tree->rwlock); + hmap_free(tree->hmap); + free(tree); + return; +} + +typedef enum TraverseMode { + WEAK, + LOCK, + UNLOCK +} TraverseMode; + +Tree *path_rdunlock(Tree *tree, const char *path) { + if (!tree) { return NULL; } + assert(path && *path); + + Tree *result = tree; + Tree *subtree = tree; + char component[MAX_FOLDER_NAME_LENGTH + 1]; + const char *subpath = path; + if ((subpath = split_path(subpath, component))) { + assert(subtree); + subtree = (Tree *)hmap_get(subtree->hmap, component); + result = path_rdunlock(subtree, subpath); + rwlock_rdunlock(tree->rwlock); + if (!subtree) { return NULL; } + } + + return result; +} + +// ta funkcja jest kluczowa - zdobywa read-locki na wierzcholkach na sciezce +// od korzenia do wierzcholka pod *path, a potem zwraca szukany folder (niezablokwany) +Tree *get_subfolder(Tree *tree, const char *path, TraverseMode mode) { + assert(path); + if (mode == UNLOCK) { return path_rdunlock(tree, path); } + + Tree *subtree = tree; + + char component[MAX_FOLDER_NAME_LENGTH + 1]; + const char *subpath = path; + while ((subpath = split_path(subpath, component))) { + if (mode == LOCK) { rwlock_rdlock(subtree->rwlock); } + + subtree = (Tree *)hmap_get(subtree->hmap, component); + + if (!subtree) { return NULL; } + } + + return subtree; +} + +char* tree_list(Tree* tree, const char *path) { + if (!is_path_valid(path)) { return NULL; } + + Tree *subtree = get_subfolder(tree, path, LOCK); + if (!subtree) { + assert(get_subfolder(tree, path, UNLOCK) == subtree); + return NULL; + } + + rwlock_rdlock(subtree->rwlock); + char *result = make_map_contents_string(subtree->hmap); + rwlock_rdunlock(subtree->rwlock); + + assert(get_subfolder(tree, path, UNLOCK) == subtree); + return result; +} + +int tree_create(Tree* tree, const char* path) { + if (!is_path_valid(path)) { return EINVAL; } + if (!strcmp(path, "/")) { return EEXIST; } + + char component[MAX_FOLDER_NAME_LENGTH + 1]; + char *parent_path = make_path_to_parent(path, component); + Tree *subtree = get_subfolder(tree, parent_path, LOCK); + if (!subtree) { assert(!get_subfolder(tree, parent_path, UNLOCK)); free(parent_path); return ENOENT; } + + Tree *new_node = tree_new(); + rwlock_wrlock(subtree->rwlock); + bool insert_successful = hmap_insert(subtree->hmap, component, new_node); + rwlock_wrunlock(subtree->rwlock); + + assert(get_subfolder(tree, parent_path, UNLOCK) == subtree); + free(parent_path); + + if (!insert_successful) { + tree_free(new_node); + return EEXIST; + } + return 0; +} + +int tree_remove(Tree* tree, const char* path) { + if (!is_path_valid(path)) { return EINVAL; } + if (!strcmp(path, "/")) { return EBUSY; } + + int result = 0; + + char component[MAX_FOLDER_NAME_LENGTH + 1]; + char *parent_path = make_path_to_parent(path, component); + Tree *parent = get_subfolder(tree, parent_path, LOCK); + if (!parent) { result = ENOENT; goto exit1; } + + rwlock_wrlock(parent->rwlock); + // we have read-write permissions, so no operation is running in the subtree + + Tree *node = (Tree *)hmap_get(parent->hmap, component); + if (!node) { result = ENOENT; goto exit2; } + if (hmap_size(node->hmap)) { result = ENOTEMPTY; goto exit2; } + + assert(hmap_remove(parent->hmap, component)); + tree_free(node); + +exit2: + rwlock_wrunlock(parent->rwlock); +exit1: + assert(get_subfolder(tree, parent_path, UNLOCK) == parent); + free(parent_path); + return result; +} + +// returns true if str starts with prefix and is longer, false otherwise +bool starts_with(const char *str, const char *prefix) { + return strlen(str) > strlen(prefix) && (strncmp(str, prefix, strlen(prefix)) == 0); +} + +Tree *get_lca(Tree *tree, const char* source, const char* target, TraverseMode mode) { + // get longest common prefix of source and target + const char *prefix_end1 = source, *prefix_end2 = target; + while (*prefix_end1 && *prefix_end2 && *prefix_end1 == *prefix_end2) { prefix_end1++; prefix_end2++; } + + // find path of lca + const char *last_slash = source; + for (const char *c=source; c < prefix_end1; ++c) { + if (*c == '/') { last_slash = c; } + } + char lca_path[MAX_PATH_LENGTH + 1]; + strncpy(lca_path, source, last_slash - source + 1); + lca_path[last_slash - source + 1] = '\0'; + + return get_subfolder(tree, lca_path, mode); +} + +/* + +Opis synchronizacji: +Schodząc wgłąd drzewa, na każdym stopniu zbieram rwlocki w trybie czytelnika +To zabezpiecza mnie przed przeniesieniem folderu, na którym aktualnie pracuję +i dziwnymi przeplotami. W każdej z funkcji wyżej zbieram tylko jednego locka +w trybie pisarza, więc tam nie ma zadnych kłopotów z zakleszczeniami - tutaj +jest inaczej. Żeby rozwiązać ten problem, zamiast blokować osobno dwa wierzchołki +(próby tego szybszego rozwiązania są poniżej), od razu blokuję w trybie pisarza +LCA ojców szukanych wierzchołków. Dzięki temu blokujemy tylko jeden wierzchołek, +co zabezpiecza nas przed deadlockami. Ponadto dzięki trybowi pisarza, możemy +dowoli czytać i pisać w całym poddrzewie, zatem pozostałe operacje wykonuejmy +w trybie WEAK, tj. bez zbierania żadnych locków. Locki oddajemy w kolejności +odwrotnej niż je zbieraliśmy, co robimy za pomocą post-order rekurencji w funkcji +path_rdunlock +*/ +int tree_move(Tree *tree, const char *source, const char *target) { + if (!source || !is_path_valid(source)) { return EINVAL; } + if (!target || !is_path_valid(target)) { return EINVAL; } + if (!strcmp(source, "/")) { return EBUSY; } + if (!strcmp(target, "/")) { return EEXIST; } + + char source_component[MAX_FOLDER_NAME_LENGTH + 1]; + char *source_parent_path = make_path_to_parent(source, source_component); + + char target_component[MAX_FOLDER_NAME_LENGTH + 1]; + char *target_parent_path = make_path_to_parent(target, target_component); + + int result = 0; + if (starts_with(target, source)) { result = EINVMV; goto exit0; } + if (starts_with(source, target)) { + Tree *node = get_subfolder(tree, source, LOCK); + assert(get_subfolder(tree, source, UNLOCK) == node); + result = node ? EEXIST : ENOENT; + goto exit0; + } + + Tree *lca = get_lca(tree, source_parent_path, target_parent_path, LOCK); + if (!lca) { result = ENOENT; goto exit1; } + + rwlock_wrlock(lca->rwlock); + + Tree *source_parent = get_subfolder(tree, source_parent_path, WEAK); + if (!source_parent) { result = ENOENT; goto exit2; } + + Tree *target_parent = get_subfolder(tree, target_parent_path, WEAK); + if (!target_parent) { result = ENOENT; goto exit2; } + + Tree *source_node = hmap_get(source_parent->hmap, source_component); + if (!source_node) { result = ENOENT; goto exit2; } + + assert(hmap_remove(source_parent->hmap, source_component)); + bool success = hmap_insert(target_parent->hmap, target_component, source_node); + if (!success) { + assert(hmap_insert(source_parent->hmap, source_component, source_node)); + result = EEXIST; + } + +exit2: + rwlock_wrunlock(lca->rwlock); +exit1: + assert(get_lca(tree, source_parent_path, target_parent_path, UNLOCK) == lca); +exit0: + free(source_parent_path); + free(target_parent_path); + + return result; +} + + + +// tutaj ponizej jest tylko do wgladu owoc mojej dluugiej pracy, niestety +// nie dziala to + + + + +// to jest wersja, ktora nie blokuje LCA; ona dzialala +// (tj. nie generowala deadlockow i byla poprawna) przy zastosowaniu +// rwlocka z pthreads; z moją implementacja rwlocka niestety się kleszczy +int tree_moveSEMI(Tree *tree, const char *source, const char *target) { + if (!source || !is_path_valid(source)) { return EINVAL; } + if (!target || !is_path_valid(target)) { return EINVAL; } + if (!strcmp(source, "/")) { return EBUSY; } + if (!strcmp(target, "/")) { return EEXIST; } + + char source_component[MAX_FOLDER_NAME_LENGTH + 1]; + char *source_parent_path = make_path_to_parent(source, source_component); + + char target_component[MAX_FOLDER_NAME_LENGTH + 1]; + char *target_parent_path = make_path_to_parent(target, target_component); + + int result = 0; + if (starts_with(target, source)) { result = EINVMV; goto exit0; } + if (starts_with(source, target)) { + Tree *node = get_subfolder(tree, source, LOCK); + assert(get_subfolder(tree, source, UNLOCK) == node); + result = node ? EEXIST : ENOENT; + goto exit0; + } + Tree *lca = get_lca(tree, source_parent_path, target_parent_path, LOCK); + if (!lca) { result = ENOENT; goto exit1; } + + int cmp = strcmp(source_parent_path, target_parent_path); + cmp = cmp ? cmp / abs(cmp) : 0; + + TraverseMode mode; + + if (!cmp || starts_with(source_parent_path, target_parent_path) || starts_with(target_parent_path, source_parent_path) ) { + rwlock_wrlock(lca->rwlock); + mode = WEAK; + } else { + mode = LOCK; + } + + // DEADLOCK JEST TU + // TRZEBA WCHODZIC WGŁĄB A NIE WZDLUZ TJ BFSEM TO SZUKAMY + // ale to roziwaze w sumie w ogole problem? + + // Tree *source_parent, *target_parent; + // get_two_subfolders(tree, source_parent_path, target_parent_path, mode); + // if (!source_parent || !target_parent) { result = ENOENT; goto exit2; } + + Tree *source_parent, *target_parent; + + // Tree *source_parent = get_subfolder(tree, source_parent_path, mode); + // if (!source_parent) { result = ENOENT; goto exit2; } + + // Tree *target_parent = get_subfolder(tree, target_parent_path, mode); + // if (!target_parent) { result = ENOENT; goto exit3; } + + // if (source_parent > target_parent) { cmp = 1; } + // else if (source_parent < target_parent) { cmp = -1; } + // else { cmp = 0; } + + if (mode == LOCK) { + if (cmp == -1) { + source_parent = get_subfolder(tree, source_parent_path, mode); + if (!source_parent) { result = ENOENT; goto exit2; } + rwlock_wrlock(source_parent->rwlock); + + target_parent = get_subfolder(tree, target_parent_path, mode); + if (!target_parent) { result = ENOENT; goto exit3; } + rwlock_wrlock(target_parent->rwlock); + } else if (cmp == 1) { + target_parent = get_subfolder(tree, target_parent_path, mode); + if (!target_parent) { result = ENOENT; goto exit2; } + rwlock_wrlock(target_parent->rwlock); + + source_parent = get_subfolder(tree, source_parent_path, mode); + if (!source_parent) { result = ENOENT; goto exit3; } + rwlock_wrlock(source_parent->rwlock); + } else { + fatal("cannot happen"); + } + } else { + source_parent = get_subfolder(tree, source_parent_path, mode); + if (!source_parent) { result = ENOENT; goto exit2; } + + target_parent = get_subfolder(tree, target_parent_path, mode); + if (!target_parent) { result = ENOENT; goto exit3; } + } + + Tree *source_node = hmap_get(source_parent->hmap, source_component); + if (!source_node) { result = ENOENT; goto exit4; } + + assert(hmap_remove(source_parent->hmap, source_component)); + bool success = hmap_insert(target_parent->hmap, target_component, source_node); + if (!success) { + assert(hmap_insert(source_parent->hmap, source_component, source_node)); + result = EEXIST; + } + +exit4: + if (mode == LOCK) { + if (cmp == -1) { + rwlock_wrunlock(target_parent->rwlock); + // rwlock_wrunlock(source_parent->rwlock); + } else if (cmp == 1) { + rwlock_wrunlock(source_parent->rwlock); + // rwlock_wrunlock(target_parent->rwlock); + } + } +exit3: + if (mode == LOCK) { + if (cmp == -1) { + // rwlock_wrunlock(target_parent->rwlock); + rwlock_wrunlock(source_parent->rwlock); + } else if (cmp == 1) { + // rwlock_wrunlock(source_parent->rwlock); + rwlock_wrunlock(target_parent->rwlock); + } + } + if (mode == LOCK) { + assert (cmp == 1 || cmp == -1); + if (cmp == -1) { + assert(get_subfolder(tree, target_parent_path, UNLOCK) == target_parent); + } else if (cmp == 1) { + assert(get_subfolder(tree, source_parent_path, UNLOCK) == source_parent); + } + } +exit2: + if (mode == LOCK) { + assert (cmp == 1 || cmp == -1); + if (cmp == -1) { + assert(get_subfolder(tree, source_parent_path, UNLOCK) == source_parent); + } else if (cmp == 1) { + assert(get_subfolder(tree, target_parent_path, UNLOCK) == target_parent); + } + } else { + rwlock_wrunlock(lca->rwlock); + } + + // if (mode == LOCK) { + // get_two_subfolders(tree, source_parent_path, target_parent_path, UNLOCK); + // } + +exit1: + assert(get_lca(tree, source_parent_path, target_parent_path, UNLOCK) == lca); +exit0: + free(source_parent_path); + free(target_parent_path); + + return result; +} + +typedef enum { + Write, + Weak +} VisitMode; + +void get_two_subfolders( + Tree *tree, + const char *source, + const char *target, + TraverseMode mode, + Tree **source_node, + Tree **target_node, + rwlock_t *mutexes[], + int *n_mutexes, + rwlock_t *end_mutexes[], + int *n_end_mutexes, + VisitMode visit_mode + ) { + + if (mode == LOCK) { assert(visit_mode == Write); } + else if (mode == WEAK) { assert(visit_mode == Weak); } + + if (mode == UNLOCK) { + assert (*n_mutexes >= 2); + + assert(*n_end_mutexes >= 0 && *n_end_mutexes <= 2); + for (int i=*n_end_mutexes - 1; i >= 0; --i) { + if (visit_mode == Write) { + rwlock_wrunlock(end_mutexes[i]); + } + } + + for (int i=*n_mutexes - 1; i >=0; --i) { + rwlock_rdunlock(mutexes[i]); + } + return; + } + assert(is_path_valid(source) && is_path_valid(target)); + + Tree *subtreeA = tree, *subtreeB = tree; + *source_node = NULL; + *target_node = NULL; + + char componentA[MAX_FOLDER_NAME_LENGTH + 1]; + char componentB[MAX_FOLDER_NAME_LENGTH + 1]; + const char *subpathA = source, *subpathB = target; + + bool lockedEndA=false, lockedEndB=false; + *n_mutexes = 0; + *n_end_mutexes = 0; + while (subtreeA || subtreeB) { + if (subpathA) subpathA = split_path(subpathA, componentA); + if (subpathB) subpathB = split_path(subpathB, componentB); + + if (strcmp(componentA, componentB) <= 0) { + + if (subtreeA && !subpathA && !lockedEndA) { + lockedEndA = true; + end_mutexes[(*n_end_mutexes)++] = subtreeA->rwlock; + if (visit_mode == Write) { + rwlock_wrlock(subtreeA->rwlock); + } + } + + if (subtreeB && !subpathB && !lockedEndB) { + lockedEndB = true; + end_mutexes[(*n_end_mutexes)++] = subtreeB->rwlock; + if (visit_mode == Write) { + rwlock_wrlock(subtreeB->rwlock); + } + } + } else { + if (subtreeB && !subpathB && !lockedEndB) { + lockedEndB = true; + end_mutexes[(*n_end_mutexes)++] = subtreeB->rwlock; + if (visit_mode == Write) { + rwlock_wrlock(subtreeB->rwlock); + } + } + + if (subtreeA && !subpathA && !lockedEndA) { + lockedEndA = true; + end_mutexes[(*n_end_mutexes)++] = subtreeA->rwlock; + if (visit_mode == Write) { + rwlock_wrlock(subtreeA->rwlock); + } + } + + } + + + rwlock_t *lockA=NULL, *lockB=NULL; + if (subpathA && subtreeA) { lockA = subtreeA->rwlock; } + if (subpathB && subtreeB) { lockB = subtreeB->rwlock; } + + if (mode == LOCK) { + if (strcmp(componentA, componentB) <= 0) { + if (lockA) { mutexes[(*n_mutexes)++] = lockA; rwlock_rdlock(lockA); } + if (lockB) { mutexes[(*n_mutexes)++] = lockB; rwlock_rdlock(lockB); } + } else { + if (lockB) { mutexes[(*n_mutexes)++] = lockB; rwlock_rdlock(lockB); } + if (lockA) { mutexes[(*n_mutexes)++] = lockA; rwlock_rdlock(lockA); } + } + } + + if (subpathA && subtreeA) { subtreeA = (Tree *)hmap_get(subtreeA->hmap, componentA); } + if (subpathB && subtreeB) { subtreeB = (Tree *)hmap_get(subtreeB->hmap, componentB); } + if (!subpathA && !subpathB) { break; } + } + + *source_node = subtreeA; + *target_node = subtreeB; +} + + +void breathe(Tree* tree) { + return; + const char *key; + void *value; + HashMapIterator it = hmap_iterator(tree->hmap); + while (hmap_next(tree->hmap, &it, &key, &value)) { + Tree *child = (Tree *)value; + breathe(child); + } + + rwlock_rdlock(tree->rwlock); + rwlock_rdunlock(tree->rwlock); + rwlock_wrlock(tree->rwlock); + rwlock_wrunlock(tree->rwlock); + + return; +} + +// to jest juz wersja ostateczna, ktora robi calkowitego BFSa z porzadkowaniem +// na poszczegolnych poziomach +// niestety nie dziala xddd i nie bedzie dzialac +// to zadanie mnie przeroslo, te funkcje sa zdecydowanie zbyt skomplikowane +// i zbyt error prone +int tree_moveFAST(Tree* tree, const char* source, const char* target) { + breathe(tree); + if (!source || !is_path_valid(source)) { return EINVAL; } + if (!target || !is_path_valid(target)) { return EINVAL; } + if (!strcmp(source, "/")) { return EBUSY; } + if (!strcmp(target, "/")) { return EEXIST; } + + char source_component[MAX_FOLDER_NAME_LENGTH + 1]; + char *source_parent_path = make_path_to_parent(source, source_component); + + char target_component[MAX_FOLDER_NAME_LENGTH + 1]; + char *target_parent_path = make_path_to_parent(target, target_component); + + int result = 0; + if (starts_with(target, source)) { result = EINVMV; goto exit0; } + if (starts_with(source, target)) { + Tree *node = get_subfolder(tree, source, LOCK); + assert(get_subfolder(tree, source, UNLOCK) == node); + result = node ? EEXIST : ENOENT; + goto exit0; + } + Tree *lca = NULL; + bool release_lca = false; + + + int cmp = strcmp(source_parent_path, target_parent_path); + cmp = cmp ? cmp / abs(cmp) : 0; + TraverseMode mode; + + if (!cmp || starts_with(source_parent_path, target_parent_path) || starts_with(target_parent_path, source_parent_path) ) { + lca = get_lca(tree, source_parent_path, target_parent_path, LOCK); + release_lca=true; + if (!lca) { result = ENOENT; goto exit1; } + rwlock_wrlock(lca->rwlock); + mode = WEAK; + } else { + mode = LOCK; + } + + Tree *source_parent, *target_parent; + rwlock_t *mutexes[MAX_PATH_LENGTH * 2], *end_mutexes[2]; + int n_mutexes, n_end_mutexes; + + VisitMode visit_mode = mode == LOCK ? Write : Weak; + get_two_subfolders( + tree, + source_parent_path, + target_parent_path, + mode, + &source_parent, + &target_parent, + mutexes, + &n_mutexes, + end_mutexes, + &n_end_mutexes, + visit_mode + ); + if (!source_parent || !target_parent) { result = ENOENT; goto exit2; } + + Tree *source_node = hmap_get(source_parent->hmap, source_component); + if (!source_node) { result = ENOENT; goto exit2; } + + assert(hmap_remove(source_parent->hmap, source_component)); + bool success = hmap_insert(target_parent->hmap, target_component, source_node); + if (!success) { + assert(hmap_insert(source_parent->hmap, source_component, source_node)); + result = EEXIST; + } + +exit2: + if (mode == LOCK) { + get_two_subfolders(tree, NULL, NULL, UNLOCK, NULL, NULL, mutexes, &n_mutexes, end_mutexes, &n_end_mutexes, Write); + } else if (mode == WEAK) { + rwlock_wrunlock(lca->rwlock); + } + +exit1: + if (release_lca) { + assert(get_lca(tree, source_parent_path, target_parent_path, UNLOCK) == lca); + } +exit0: + free(source_parent_path); + free(target_parent_path); + + breathe(tree); + return result; +} + diff --git a/Tree.h b/Tree.h new file mode 100644 index 0000000..219f0e8 --- /dev/null +++ b/Tree.h @@ -0,0 +1,25 @@ +#pragma once + +// Kod błędu zwracany przy próbie przeniesienia folderu do swojego podfolderu +#define EINVMV (-20) + +// Let "Tree" mean the same as "struct Tree". +typedef struct Tree Tree; + +// Tworzy nowe drzewo folderów z jednym, pustym folderem "/". +Tree* tree_new(); + +// Zwalnia całą pamięć związaną z podanym drzewem. +void tree_free(Tree*); + +// Wymienia zawartość danego folderu, zwracając nowy napis postaci "foo,bar,baz" +char* tree_list(Tree* tree, const char* path); + +// Tworzy nowy podfolder (np. dla path="/foo/bar/baz/", tworzy pusty podfolder baz w folderze "/foo/bar/"). +int tree_create(Tree* tree, const char* path); + +// Usuwa folder, o ile jest pusty. +int tree_remove(Tree* tree, const char* path); + +// Przenosi folder source wraz z zawartością na miejsce target (przenoszone jest całe poddrzewo), o ile to możliwe +int tree_move(Tree* tree, const char* source, const char* target); diff --git a/drd.sh b/drd.sh new file mode 100755 index 0000000..305ca3b --- /dev/null +++ b/drd.sh @@ -0,0 +1 @@ +valgrind --tool=drd pb429141/build/main diff --git a/err.c b/err.c new file mode 100644 index 0000000..4848b1e --- /dev/null +++ b/err.c @@ -0,0 +1,38 @@ +#include "err.h" +#include +#include +#include +#include +#include +#include +#include +#include + +void syserr(const char* fmt, ...) +{ + va_list fmt_args; + + fprintf(stderr, "ERROR: "); + + va_start(fmt_args, fmt); + vfprintf(stderr, fmt, fmt_args); + va_end(fmt_args); + fprintf(stderr, " (%d; %s)\n", errno, strerror(errno)); + exit(1); +} + +void fatal(const char* fmt, ...) +{ + va_list fmt_args; + + fprintf(stderr, "ERROR: "); + + va_start(fmt_args, fmt); + vfprintf(stderr, fmt, fmt_args); + va_end(fmt_args); + + fprintf(stderr, "\n"); + exit(1); +} + +void bad_malloc() { syserr("Unable to allocate memory"); } diff --git a/err.h b/err.h new file mode 100644 index 0000000..2e8488c --- /dev/null +++ b/err.h @@ -0,0 +1,11 @@ +#pragma once + +/* wypisuje informacje o błędnym zakończeniu funkcji systemowej +i kończy działanie */ +extern void syserr(const char* fmt, ...); + +/* wypisuje informacje o błędzie i kończy działanie */ +extern void fatal(const char* fmt, ...); + +/* sygnalizuje niepowodzenie alokacji pamięci i kończy działanie */ +extern void bad_malloc(); \ No newline at end of file diff --git a/gdb.sh b/gdb.sh new file mode 100755 index 0000000..2b09ddb --- /dev/null +++ b/gdb.sh @@ -0,0 +1 @@ +gdb pb429141/build/main diff --git a/helgrind.sh b/helgrind.sh new file mode 100755 index 0000000..4b2b330 --- /dev/null +++ b/helgrind.sh @@ -0,0 +1 @@ +valgrind -s --tool=helgrind pb429141/build/main diff --git a/main.c b/main.c new file mode 100644 index 0000000..6603e40 --- /dev/null +++ b/main.c @@ -0,0 +1,35 @@ +#include "HashMap.h" +#include +#include +#include +#include +#include +#include + +void print_map(HashMap* map) { + const char* key = NULL; + void* value = NULL; + printf("Size=%zd\n", hmap_size(map)); + HashMapIterator it = hmap_iterator(map); + while (hmap_next(map, &it, &key, &value)) { + printf("Key=%s Value=%p\n", key, value); + } + printf("\n"); +} + + +int main(void) +{ + HashMap* map = hmap_new(); + hmap_insert(map, "a", hmap_new()); + print_map(map); + + HashMap* child = (HashMap*)hmap_get(map, "a"); + hmap_free(child); + hmap_remove(map, "a"); + print_map(map); + + hmap_free(map); + + return 0; +} \ No newline at end of file diff --git a/path_utils.c b/path_utils.c new file mode 100755 index 0000000..1b46116 --- /dev/null +++ b/path_utils.c @@ -0,0 +1,125 @@ +#include +#include +#include +#include + +#include "path_utils.h" +#include "err.h" + +bool is_path_valid(const char* path) +{ + if (!path) { return false; } + size_t len = strlen(path); + if (len == 0 || len > MAX_PATH_LENGTH) + return false; + if (path[0] != '/' || path[len - 1] != '/') + return false; + const char* name_start = path + 1; // Start of current path component, just after '/'. + while (name_start < path + len) { + char* name_end = strchr(name_start, '/'); // End of current path component, at '/'. + if (!name_end || name_end == name_start || name_end > name_start + MAX_FOLDER_NAME_LENGTH) + return false; + for (const char* p = name_start; p != name_end; ++p) + if (*p < 'a' || *p > 'z') + return false; + name_start = name_end + 1; + } + return true; +} + +const char* split_path(const char* path, char* component) +{ + const char* subpath = strchr(path + 1, '/'); // Pointer to second '/' character. + if (!subpath) // Path is "/". + return NULL; + if (component) { + int len = subpath - (path + 1); + assert(len >= 1 && len <= MAX_FOLDER_NAME_LENGTH); + strncpy(component, path + 1, len); + component[len] = '\0'; + } + return subpath; +} + +char* make_path_to_parent(const char* path, char* component) +{ + size_t len = strlen(path); + if (len == 1) // Path is "/". + return NULL; + const char* p = path + len - 2; // Point before final '/' character. + // Move p to last-but-one '/' character. + while (*p != '/') + p--; + + size_t subpath_len = p - path + 1; // Include '/' at p. + char* result = malloc(subpath_len + 1); // Include terminating null character. + strncpy(result, path, subpath_len); + result[subpath_len] = '\0'; + + if (component) { + size_t component_len = len - subpath_len - 1; // Skip final '/' as well. + assert(component_len >= 1 && component_len <= MAX_FOLDER_NAME_LENGTH); + strncpy(component, p + 1, component_len); + component[component_len] = '\0'; + } + + return result; +} + +// A wrapper for using strcmp in qsort. +// The arguments here are actually pointers to (const char*). +static int compare_string_pointers(const void* p1, const void* p2) +{ + return strcmp(*(const char**)p1, *(const char**)p2); +} + +const char** make_map_contents_array(HashMap* map) +{ + size_t n_keys = hmap_size(map); + const char** result = calloc(n_keys + 1, sizeof(char*)); + if (!result) { bad_malloc(); } + HashMapIterator it = hmap_iterator(map); + const char** key = result; + void* value = NULL; + while (hmap_next(map, &it, key, &value)) { + key++; + } + *key = NULL; // Set last array element to NULL. + qsort(result, n_keys, sizeof(char*), compare_string_pointers); + return result; +} + +char* make_map_contents_string(HashMap* map) +{ + const char** keys = make_map_contents_array(map); + + unsigned int result_size = 0; // Including ending null character. + for (const char** key = keys; *key; ++key) + result_size += strlen(*key) + 1; + + // Return empty string if map is empty. + if (!result_size) { + free(keys); + // Note we can't just return "", as it can't be free'd. + char* result = malloc(1); + if (!result) { bad_malloc(); } + *result = '\0'; + return result; + } + + char* result = malloc(result_size); + if (!result) { bad_malloc(); } + char* position = result; + for (const char** key = keys; *key; ++key) { + size_t keylen = strlen(*key); + assert(position + keylen <= result + result_size); + strcpy(position, *key); // NOLINT: array size already checked. + position += keylen; + *position = ','; + position++; + } + position--; + *position = '\0'; + free(keys); + return result; +} diff --git a/path_utils.h b/path_utils.h new file mode 100755 index 0000000..6b9c479 --- /dev/null +++ b/path_utils.h @@ -0,0 +1,53 @@ +#pragma once + +#include + +#include "HashMap.h" + +// Max length of path (excluding terminating null character). +#define MAX_PATH_LENGTH 4095 + +// Max length of folder name (excluding terminating null character). +#define MAX_FOLDER_NAME_LENGTH 255 + +// Return whether a path is valid. +// Valid paths are '/'-separated sequences of folder names, always starting and ending with '/'. +// Valid paths have length at most MAX_PATH_LENGTH (and at least 1). Valid folder names are are +// sequences of 'a'-'z' ASCII characters, of length from 1 to MAX_FOLDER_NAME_LENGTH. +bool is_path_valid(const char* path); + +// Return the subpath obtained by removing the first component. +// Args: +// - `path`: should be a valid path (see `is_path_valid`). +// - `component`: if not NULL, should be a buffer of size at least MAX_FOLDER_NAME_LENGTH + 1. +// Then the first component will be copied there (without any '/' characters). +// If path is "/", returns NULL and leaves `component` unchanged. +// Otherwise the returns a pointer into `path`, representing a valid subpath. +// +// This can be used to iterate over all components of a path: +// char component[MAX_FOLDER_NAME_LENGTH + 1]; +// const char* subpath = path; +// while (subpath = split_path(subpath, component)) +// printf("%s", component); +const char* split_path(const char* path, char* component); + +// Return a copy of the subpath obtained by removing the last component. +// The caller should free the result, unless it is NULL. +// Args: +// - `path`: should be a valid path (see `is_path_valid`). +// - `component`: if not NULL, should be a buffer of size at least MAX_FOLDER_NAME_LENGTH + 1. +// Then the last component will be copied there (without any '/' characters). +// If path is "/", returns NULL and leaves `component` unchanged. +// Otherwise the result is a valid path. +char* make_path_to_parent(const char* path, char* component); + +// Return an array containing all keys, lexicographically sorted. +// The result is null-terminated. +// Keys are not copied, they are only valid as long as the map. +// The caller should free the result. +const char** make_map_contents_array(HashMap* map); + +// Return a string containing all keys in map, sorted, comma-separated. +// The result has no trailing comma. An empty map yields an empty string. +// The caller should free the result. +char* make_map_contents_string(HashMap* map); diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..1c128cc --- /dev/null +++ b/run.sh @@ -0,0 +1,2 @@ +cd pb429141/build && cmake .. && make && ./main +# ztestuj plik pb429141/build/libTree.a diff --git a/rwlock.c b/rwlock.c new file mode 100644 index 0000000..3011053 --- /dev/null +++ b/rwlock.c @@ -0,0 +1,84 @@ +#include +#include +#include + +#include "rwlock.h" +#include "err.h" + +// Rozwiazanie z labow (przyklady09, readers-writers-template.c) +// Czyli zaadoptowanie rozwiązanie z wykładu/ćwiczeń - nie zagładzamy +// ani czytelników ani pisarzy, poprzez sprawdzanie czy czeka jakiś pisarz +struct rwlock_t { + pthread_mutex_t mutex; + pthread_cond_t can_read; + pthread_cond_t can_write; + int rcount, wcount, rwait, wwait; + int change; +}; + +rwlock_t *rwlock_new() { + rwlock_t *rwlock = (rwlock_t *)malloc(sizeof(rwlock_t)); + if (!rwlock) { return NULL; } + assert(!pthread_mutex_init(&rwlock->mutex, NULL)); + assert(!pthread_cond_init(&rwlock->can_read, NULL)); + assert(!pthread_cond_init(&rwlock->can_write, NULL)); + rwlock->rcount = rwlock->wcount = rwlock->rwait = rwlock->wwait = 0; + rwlock->change = 0; + + return rwlock; +} + +void rwlock_destroy(rwlock_t *rwlock) { + assert(!pthread_mutex_destroy(&rwlock->mutex)); + assert(!pthread_cond_destroy(&rwlock->can_read)); + assert(!pthread_cond_destroy(&rwlock->can_write)); + free(rwlock); +} + +void rwlock_rdlock(rwlock_t *rwlock) { + assert(!pthread_mutex_lock(&rwlock->mutex)); + if (rwlock->wcount + rwlock->wwait > 0 && rwlock->change == 0) { + do { + rwlock->rwait++; + assert(!pthread_cond_wait(&rwlock->can_read, &rwlock->mutex)); + rwlock->rwait--; + } while (rwlock->wcount > 0 && rwlock->change == 0); + } + rwlock->change = 0; + rwlock->rcount++; + + assert(!pthread_mutex_unlock(&rwlock->mutex)); + +} + +void rwlock_rdunlock(rwlock_t *rwlock) { + assert(!pthread_mutex_lock(&rwlock->mutex)); + rwlock->rcount--; + if (rwlock->rcount == 0 && rwlock->wwait > 0) { + assert(!pthread_cond_signal(&rwlock->can_write)); + } + assert(!pthread_mutex_unlock(&rwlock->mutex)); +} + +void rwlock_wrlock(rwlock_t *rwlock) { + assert(!pthread_mutex_lock(&rwlock->mutex)); + while (rwlock->rcount + rwlock->wcount > 0 || rwlock->change == 1) { + rwlock->wwait++; + assert(!pthread_cond_wait(&rwlock->can_write, &rwlock->mutex)); + rwlock->wwait--; + } + rwlock->wcount++; + assert(!pthread_mutex_unlock(&rwlock->mutex)); +} + +void rwlock_wrunlock(rwlock_t *rwlock) { + assert(!pthread_mutex_lock(&rwlock->mutex)); + rwlock->wcount--; + if (rwlock->rwait > 0) { + rwlock->change = 1; + assert(!pthread_cond_broadcast(&rwlock->can_read)); + } else if (rwlock->wwait > 0) { + assert(!pthread_cond_signal(&rwlock->can_write)); + } + assert(!pthread_mutex_unlock(&rwlock->mutex)); +} diff --git a/rwlock.h b/rwlock.h new file mode 100644 index 0000000..bf559d7 --- /dev/null +++ b/rwlock.h @@ -0,0 +1,10 @@ +#pragma once + +typedef struct rwlock_t rwlock_t; + +rwlock_t *rwlock_new(); +void rwlock_destroy(rwlock_t *rwlock); +void rwlock_rdlock(rwlock_t *rwlock); +void rwlock_rdunlock(rwlock_t *rwlock); +void rwlock_wrlock(rwlock_t *rwlock); +void rwlock_wrunlock(rwlock_t *rwlock); diff --git a/valgrind.sh b/valgrind.sh new file mode 100755 index 0000000..f8c2d46 --- /dev/null +++ b/valgrind.sh @@ -0,0 +1,2 @@ +valgrind -s --leak-check=full --show-leak-kinds=all pb429141/build/main +#valgrind -s pb429141/build/main