From dda9555a325afafe91b1816f754d6edebdb6e8fd Mon Sep 17 00:00:00 2001 From: Pawel Balawender Date: Thu, 3 Aug 2023 19:28:00 +0200 Subject: [PATCH] move code from the original repository --- .gitignore | 11 + CMakeLists.txt | 21 ++ HashMap.c | 134 +++++++++++ HashMap.h | 57 +++++ Tree.c | 633 +++++++++++++++++++++++++++++++++++++++++++++++++ Tree.h | 25 ++ drd.sh | 1 + err.c | 38 +++ err.h | 11 + gdb.sh | 1 + helgrind.sh | 1 + main.c | 35 +++ path_utils.c | 125 ++++++++++ path_utils.h | 53 +++++ run.sh | 2 + rwlock.c | 84 +++++++ rwlock.h | 10 + valgrind.sh | 2 + 18 files changed, 1244 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 HashMap.c create mode 100644 HashMap.h create mode 100644 Tree.c create mode 100644 Tree.h create mode 100755 drd.sh create mode 100644 err.c create mode 100644 err.h create mode 100755 gdb.sh create mode 100755 helgrind.sh create mode 100644 main.c create mode 100755 path_utils.c create mode 100755 path_utils.h create mode 100755 run.sh create mode 100644 rwlock.c create mode 100644 rwlock.h create mode 100755 valgrind.sh 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