Compare commits
10 Commits
de9807166e
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| e7f2d6cdeb | |||
| f8dcae213d | |||
| f15d94b02c | |||
| 19c461f089 | |||
| d93eef112a | |||
| c71986a5f6 | |||
| 900254f023 | |||
| 0fa14e8c37 | |||
| 824de36448 | |||
| b47854cdda |
@@ -15,14 +15,16 @@ All the features listed are Thread-safe.
|
|||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
Clone the repo, and then using `meson` install it:
|
Clone the repo, cd into it and then install it using:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ meson setup build
|
$ meson setup build
|
||||||
$ cd build
|
$ cd build
|
||||||
$ meson install
|
$ sudo ninja install
|
||||||
```
|
```
|
||||||
|
|
||||||
|
To uninstall, simply run `sudo ninja uninstall`.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
See the `test/` folder for examples.
|
See the `test/` folder for examples.
|
||||||
|
|||||||
@@ -5,45 +5,60 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* @brief Returns the mutex ID.
|
* @brief Returns the mutex ID based on hash value.
|
||||||
*/
|
*/
|
||||||
static size_t get_mutex(hashmap_s *hashmap, size_t hash) {
|
static inline size_t get_mutex(hashmap_s *hashmap, size_t hash) {
|
||||||
return hash % hashmap->num_locks;
|
return hash % hashmap->num_locks;
|
||||||
}
|
}
|
||||||
|
|
||||||
static size_t get_bucket_index(hashmap_s *hashmap, void *key) {
|
/*
|
||||||
|
* @brief Returns the bucket index for a given key.
|
||||||
|
*/
|
||||||
|
static inline size_t get_bucket_index(hashmap_s *hashmap, void *key) {
|
||||||
unsigned int hash = hashmap->hash(key);
|
unsigned int hash = hashmap->hash(key);
|
||||||
return hash % MYCLIB_HASHMAP_SIZE;
|
return (size_t)(hash % MYCLIB_HASHMAP_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @brief Free the contents of a bucket (key and value).
|
||||||
|
*/
|
||||||
static void free_bucket_content(hashmap_s *hashmap, bucket_s *bucket) {
|
static void free_bucket_content(hashmap_s *hashmap, bucket_s *bucket) {
|
||||||
if (bucket == NULL) {
|
if (bucket == NULL) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Free key if free function is provided */
|
if (bucket->key != NULL) {
|
||||||
if (hashmap->free_key != NULL && bucket->key != NULL) {
|
if (hashmap->free_key != NULL) {
|
||||||
hashmap->free_key(bucket->key);
|
hashmap->free_key(bucket->key);
|
||||||
|
} else {
|
||||||
|
free(bucket->key);
|
||||||
|
}
|
||||||
|
bucket->key = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Free value if free function is provided */
|
if (bucket->value != NULL) {
|
||||||
if (hashmap->free_value != NULL && bucket->value != NULL) {
|
if (hashmap->free_value != NULL) {
|
||||||
hashmap->free_value(bucket->value);
|
hashmap->free_value(bucket->value);
|
||||||
|
} else {
|
||||||
|
free(bucket->value);
|
||||||
|
}
|
||||||
|
bucket->value = NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @brief Find a bucket by key in the chain.
|
||||||
|
* @param[out] prev Set to the previous bucket in the chain (or NULL if first).
|
||||||
|
*/
|
||||||
static bucket_s *find_bucket(hashmap_s *hashmap, void *key, bucket_s **prev) {
|
static bucket_s *find_bucket(hashmap_s *hashmap, void *key, bucket_s **prev) {
|
||||||
size_t index = get_bucket_index(hashmap, key);
|
size_t index = get_bucket_index(hashmap, key);
|
||||||
bucket_s *bucket = &hashmap->map[index];
|
bucket_s *bucket = &hashmap->map[index];
|
||||||
|
|
||||||
*prev = NULL;
|
*prev = NULL;
|
||||||
|
|
||||||
/* Return NULL if first bucket is empty */
|
|
||||||
if (bucket->key == NULL) {
|
if (bucket->key == NULL) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Search through the collision chain */
|
|
||||||
while (bucket != NULL) {
|
while (bucket != NULL) {
|
||||||
if (hashmap->equal(bucket->key, key)) {
|
if (hashmap->equal(bucket->key, key)) {
|
||||||
return bucket;
|
return bucket;
|
||||||
@@ -57,6 +72,10 @@ static bucket_s *find_bucket(hashmap_s *hashmap, void *key, bucket_s **prev) {
|
|||||||
|
|
||||||
hashmap_s *hm_new(hash_f *hash_fn, equal_f *equal_fn, free_key_f *free_key_fn,
|
hashmap_s *hm_new(hash_f *hash_fn, equal_f *equal_fn, free_key_f *free_key_fn,
|
||||||
free_value_f *free_value_fn, size_t key_size, size_t value_size) {
|
free_value_f *free_value_fn, size_t key_size, size_t value_size) {
|
||||||
|
if (hash_fn == NULL || equal_fn == NULL || key_size == 0 || value_size == 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
hashmap_s *hashmap = malloc(sizeof(hashmap_s));
|
hashmap_s *hashmap = malloc(sizeof(hashmap_s));
|
||||||
if (hashmap == NULL) {
|
if (hashmap == NULL) {
|
||||||
return NULL;
|
return NULL;
|
||||||
@@ -69,27 +88,23 @@ hashmap_s *hm_new(hash_f *hash_fn, equal_f *equal_fn, free_key_f *free_key_fn,
|
|||||||
hashmap->key_size = key_size;
|
hashmap->key_size = key_size;
|
||||||
hashmap->value_size = value_size;
|
hashmap->value_size = value_size;
|
||||||
|
|
||||||
hashmap->size = 0;
|
atomic_init(&hashmap->size, 0);
|
||||||
|
|
||||||
hashmap->num_locks = 64;
|
hashmap->num_locks = 64;
|
||||||
hashmap->locks = malloc(sizeof(mtx_t) * hashmap->num_locks);
|
hashmap->locks = malloc(sizeof(mtx_t) * hashmap->num_locks);
|
||||||
if (hashmap->locks == NULL) {
|
if (hashmap->locks == NULL) {
|
||||||
free(hashmap);
|
free(hashmap);
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
int ret;
|
|
||||||
for (size_t i = 0; i < hashmap->num_locks; ++i) {
|
for (size_t i = 0; i < hashmap->num_locks; ++i) {
|
||||||
ret = mtx_init(&(hashmap->locks[i]), mtx_recursive);
|
if (mtx_init(&(hashmap->locks[i]), mtx_plain) != thrd_success) {
|
||||||
if (ret != thrd_success) {
|
|
||||||
/* Mutex failed */
|
|
||||||
for (size_t j = 0; j < i; ++j) {
|
for (size_t j = 0; j < i; ++j) {
|
||||||
mtx_destroy(&(hashmap->locks[j]));
|
mtx_destroy(&(hashmap->locks[j]));
|
||||||
}
|
}
|
||||||
|
|
||||||
free(hashmap->locks);
|
free(hashmap->locks);
|
||||||
free(hashmap);
|
free(hashmap);
|
||||||
|
return NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,16 +118,13 @@ void hm_free(hashmap_s *hashmap) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Iterate through all buckets in the hash map */
|
|
||||||
for (size_t i = 0; i < MYCLIB_HASHMAP_SIZE; ++i) {
|
for (size_t i = 0; i < MYCLIB_HASHMAP_SIZE; ++i) {
|
||||||
bucket_s *bucket = &hashmap->map[i];
|
bucket_s *bucket = &hashmap->map[i];
|
||||||
|
|
||||||
/* Free the first bucket if it contains data */
|
if (bucket->key != NULL || bucket->value != NULL) {
|
||||||
if (bucket->key != NULL) {
|
|
||||||
free_bucket_content(hashmap, bucket);
|
free_bucket_content(hashmap, bucket);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Free all chained buckets */
|
|
||||||
bucket = bucket->next;
|
bucket = bucket->next;
|
||||||
while (bucket != NULL) {
|
while (bucket != NULL) {
|
||||||
bucket_s *next = bucket->next;
|
bucket_s *next = bucket->next;
|
||||||
@@ -122,13 +134,11 @@ void hm_free(hashmap_s *hashmap) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Free the mutex */
|
|
||||||
for (size_t i = 0; i < hashmap->num_locks; ++i) {
|
for (size_t i = 0; i < hashmap->num_locks; ++i) {
|
||||||
mtx_destroy(&(hashmap->locks[i]));
|
mtx_destroy(&(hashmap->locks[i]));
|
||||||
}
|
}
|
||||||
free(hashmap->locks);
|
free(hashmap->locks);
|
||||||
|
|
||||||
/* Free the hash map structure itself */
|
|
||||||
free(hashmap);
|
free(hashmap);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,48 +157,47 @@ bool hm_set(hashmap_s *hashmap, void *key, void *value) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t mutex_id = get_mutex(hashmap, hashmap->hash(key));
|
unsigned int hash = hashmap->hash(key);
|
||||||
|
size_t mutex_id = get_mutex(hashmap, hash);
|
||||||
mtx_t *mutex = &(hashmap->locks[mutex_id]);
|
mtx_t *mutex = &(hashmap->locks[mutex_id]);
|
||||||
|
|
||||||
if (mtx_lock(mutex) != thrd_success) {
|
if (mtx_lock(mutex) != thrd_success) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bucket_s *prev;
|
bucket_s *prev = NULL;
|
||||||
bucket_s *existing = find_bucket(hashmap, key, &prev);
|
bucket_s *existing = find_bucket(hashmap, key, &prev);
|
||||||
|
|
||||||
if (existing != NULL) {
|
if (existing != NULL) {
|
||||||
/* Key exists, update value */
|
/* Key exists - update value */
|
||||||
if (hashmap->free_value != NULL && existing->value != NULL) {
|
void *new_value = malloc(hashmap->value_size);
|
||||||
hashmap->free_value(existing->value);
|
if (new_value == NULL) {
|
||||||
}
|
|
||||||
|
|
||||||
existing->value = malloc(hashmap->value_size);
|
|
||||||
if (existing->value == NULL) {
|
|
||||||
mtx_unlock(mutex);
|
mtx_unlock(mutex);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
memcpy(new_value, value, hashmap->value_size);
|
||||||
|
|
||||||
memcpy(existing->value, value, hashmap->value_size);
|
/* Free old value and assign new one */
|
||||||
|
if (hashmap->free_value != NULL && existing->value != NULL) {
|
||||||
/* Increase size */
|
hashmap->free_value(existing->value);
|
||||||
hashmap->size++;
|
} else if (existing->value != NULL) {
|
||||||
|
free(existing->value);
|
||||||
|
}
|
||||||
|
existing->value = new_value;
|
||||||
|
|
||||||
mtx_unlock(mutex);
|
mtx_unlock(mutex);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Key doesn't exist, need to insert new bucket */
|
/* Key doesn't exist - insert new bucket */
|
||||||
size_t index = get_bucket_index(hashmap, key);
|
size_t index = get_bucket_index(hashmap, key);
|
||||||
bucket_s *bucket = &hashmap->map[index];
|
bucket_s *bucket = &hashmap->map[index];
|
||||||
|
|
||||||
if (bucket->key == NULL) {
|
if (bucket->key == NULL) {
|
||||||
/* First bucket is empty, use it */
|
/* Primary bucket is empty */
|
||||||
bucket->key = malloc(hashmap->key_size);
|
bucket->key = malloc(hashmap->key_size);
|
||||||
if (bucket->key == NULL) {
|
if (bucket->key == NULL) {
|
||||||
mtx_unlock(mutex);
|
mtx_unlock(mutex);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,24 +206,22 @@ bool hm_set(hashmap_s *hashmap, void *key, void *value) {
|
|||||||
free(bucket->key);
|
free(bucket->key);
|
||||||
bucket->key = NULL;
|
bucket->key = NULL;
|
||||||
mtx_unlock(mutex);
|
mtx_unlock(mutex);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
hashmap->size++;
|
|
||||||
memcpy(bucket->key, key, hashmap->key_size);
|
memcpy(bucket->key, key, hashmap->key_size);
|
||||||
memcpy(bucket->value, value, hashmap->value_size);
|
memcpy(bucket->value, value, hashmap->value_size);
|
||||||
bucket->next = NULL;
|
bucket->next = NULL;
|
||||||
mtx_unlock(mutex);
|
|
||||||
|
|
||||||
|
atomic_fetch_add(&hashmap->size, 1);
|
||||||
|
mtx_unlock(mutex);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Create new bucket and insert at head of collision chain */
|
/* Collision - create new bucket and chain it */
|
||||||
bucket_s *new_bucket = malloc(sizeof(bucket_s));
|
bucket_s *new_bucket = malloc(sizeof(bucket_s));
|
||||||
if (new_bucket == NULL) {
|
if (new_bucket == NULL) {
|
||||||
mtx_unlock(mutex);
|
mtx_unlock(mutex);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,7 +229,6 @@ bool hm_set(hashmap_s *hashmap, void *key, void *value) {
|
|||||||
if (new_bucket->key == NULL) {
|
if (new_bucket->key == NULL) {
|
||||||
free(new_bucket);
|
free(new_bucket);
|
||||||
mtx_unlock(mutex);
|
mtx_unlock(mutex);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,44 +237,55 @@ bool hm_set(hashmap_s *hashmap, void *key, void *value) {
|
|||||||
free(new_bucket->key);
|
free(new_bucket->key);
|
||||||
free(new_bucket);
|
free(new_bucket);
|
||||||
mtx_unlock(mutex);
|
mtx_unlock(mutex);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
hashmap->size++;
|
|
||||||
memcpy(new_bucket->key, key, hashmap->key_size);
|
memcpy(new_bucket->key, key, hashmap->key_size);
|
||||||
memcpy(new_bucket->value, value, hashmap->value_size);
|
memcpy(new_bucket->value, value, hashmap->value_size);
|
||||||
|
|
||||||
|
/* Insert at head of chain */
|
||||||
new_bucket->next = bucket->next;
|
new_bucket->next = bucket->next;
|
||||||
bucket->next = new_bucket;
|
bucket->next = new_bucket;
|
||||||
|
|
||||||
|
atomic_fetch_add(&hashmap->size, 1);
|
||||||
mtx_unlock(mutex);
|
mtx_unlock(mutex);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @brief Create a copy of a bucket.
|
||||||
|
*/
|
||||||
static bucket_s *get_bucket_copy(bucket_s *from, size_t key_size, size_t value_size) {
|
static bucket_s *get_bucket_copy(bucket_s *from, size_t key_size, size_t value_size) {
|
||||||
|
if (from == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
bucket_s *copy = malloc(sizeof(bucket_s));
|
bucket_s *copy = malloc(sizeof(bucket_s));
|
||||||
if (copy == NULL) {
|
if (copy == NULL) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
memcpy(copy, from, sizeof(bucket_s));
|
copy->key = NULL;
|
||||||
|
copy->value = NULL;
|
||||||
|
copy->next = NULL;
|
||||||
|
|
||||||
|
if (from->key != NULL) {
|
||||||
copy->key = malloc(key_size);
|
copy->key = malloc(key_size);
|
||||||
if (copy->key == NULL) {
|
if (copy->key == NULL) {
|
||||||
free(copy);
|
free(copy);
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
memcpy(copy->key, from->key, key_size);
|
memcpy(copy->key, from->key, key_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (from->value != NULL) {
|
||||||
copy->value = malloc(value_size);
|
copy->value = malloc(value_size);
|
||||||
if (copy->value == NULL) {
|
if (copy->value == NULL) {
|
||||||
free(copy->key);
|
free(copy->key);
|
||||||
free(copy);
|
free(copy);
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
memcpy(copy->value, from->value, value_size);
|
memcpy(copy->value, from->value, value_size);
|
||||||
|
}
|
||||||
|
|
||||||
return copy;
|
return copy;
|
||||||
}
|
}
|
||||||
@@ -278,26 +295,24 @@ bucket_s *hm_get(hashmap_s *hashmap, void *key) {
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t mutex_id = get_mutex(hashmap, hashmap->hash(key));
|
unsigned int hash = hashmap->hash(key);
|
||||||
|
size_t mutex_id = get_mutex(hashmap, hash);
|
||||||
mtx_t *mutex = &(hashmap->locks[mutex_id]);
|
mtx_t *mutex = &(hashmap->locks[mutex_id]);
|
||||||
|
|
||||||
if (mtx_lock(mutex) != thrd_success) {
|
if (mtx_lock(mutex) != thrd_success) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
bucket_s *prev;
|
bucket_s *prev = NULL;
|
||||||
bucket_s *found = find_bucket(hashmap, key, &prev);
|
bucket_s *found = find_bucket(hashmap, key, &prev);
|
||||||
|
|
||||||
if (found) {
|
bucket_s *copy = NULL;
|
||||||
bucket_s *copy = get_bucket_copy(found, hashmap->key_size, hashmap->value_size);
|
if (found != NULL) {
|
||||||
|
copy = get_bucket_copy(found, hashmap->key_size, hashmap->value_size);
|
||||||
mtx_unlock(mutex);
|
|
||||||
|
|
||||||
return copy;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mtx_unlock(mutex);
|
mtx_unlock(mutex);
|
||||||
|
return copy;
|
||||||
return NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool hm_remove(hashmap_s *hashmap, void *key) {
|
bool hm_remove(hashmap_s *hashmap, void *key) {
|
||||||
@@ -305,50 +320,50 @@ bool hm_remove(hashmap_s *hashmap, void *key) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t mutex_id = get_mutex(hashmap, hashmap->hash(key));
|
unsigned int hash = hashmap->hash(key);
|
||||||
|
size_t mutex_id = get_mutex(hashmap, hash);
|
||||||
mtx_t *mutex = &(hashmap->locks[mutex_id]);
|
mtx_t *mutex = &(hashmap->locks[mutex_id]);
|
||||||
|
|
||||||
if (mtx_lock(mutex) != thrd_success) {
|
if (mtx_lock(mutex) != thrd_success) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bucket_s *prev;
|
bucket_s *prev = NULL;
|
||||||
bucket_s *to_remove = find_bucket(hashmap, key, &prev);
|
bucket_s *to_remove = find_bucket(hashmap, key, &prev);
|
||||||
|
|
||||||
if (to_remove == NULL) {
|
if (to_remove == NULL) {
|
||||||
mtx_unlock(mutex);
|
mtx_unlock(mutex);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Free the content of the bucket */
|
if (prev == NULL) {
|
||||||
|
/* Removing primary bucket */
|
||||||
|
if (to_remove->next != NULL) {
|
||||||
|
/* Move next bucket content to primary */
|
||||||
|
bucket_s *next_bucket = to_remove->next;
|
||||||
|
|
||||||
free_bucket_content(hashmap, to_remove);
|
free_bucket_content(hashmap, to_remove);
|
||||||
|
|
||||||
/* Handle removal based on position in chain */
|
|
||||||
if (prev == NULL) {
|
|
||||||
/* Removing first bucket in chain */
|
|
||||||
if (to_remove->next != NULL) {
|
|
||||||
/* Move next bucket's content to first bucket and free the next bucket */
|
|
||||||
bucket_s *next_bucket = to_remove->next;
|
|
||||||
to_remove->key = next_bucket->key;
|
to_remove->key = next_bucket->key;
|
||||||
to_remove->value = next_bucket->value;
|
to_remove->value = next_bucket->value;
|
||||||
to_remove->next = next_bucket->next;
|
to_remove->next = next_bucket->next;
|
||||||
|
|
||||||
free(next_bucket);
|
free(next_bucket);
|
||||||
} else {
|
} else {
|
||||||
/* No next bucket, mark first bucket as empty */
|
/* No chain, just clear */
|
||||||
to_remove->key = NULL;
|
free_bucket_content(hashmap, to_remove);
|
||||||
to_remove->value = NULL;
|
|
||||||
to_remove->next = NULL;
|
to_remove->next = NULL;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
/* Removing bucket from middle/end of chain */
|
/* Removing from chain */
|
||||||
prev->next = to_remove->next;
|
prev->next = to_remove->next;
|
||||||
|
free_bucket_content(hashmap, to_remove);
|
||||||
free(to_remove);
|
free(to_remove);
|
||||||
}
|
}
|
||||||
|
|
||||||
hashmap->size--;
|
atomic_fetch_sub(&hashmap->size, 1);
|
||||||
|
|
||||||
mtx_unlock(mutex);
|
mtx_unlock(mutex);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -357,17 +372,7 @@ size_t hm_size(hashmap_s *hashmap) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Use the first mutex */
|
return atomic_load(&hashmap->size);
|
||||||
mtx_t *mutex = &hashmap->locks[0];
|
|
||||||
if (mtx_lock(mutex) != thrd_success) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t size = hashmap->size;
|
|
||||||
|
|
||||||
mtx_unlock(mutex);
|
|
||||||
|
|
||||||
return size;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool hm_contains(hashmap_s *hashmap, void *key) {
|
bool hm_contains(hashmap_s *hashmap, void *key) {
|
||||||
@@ -375,24 +380,13 @@ bool hm_contains(hashmap_s *hashmap, void *key) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool res = true;
|
bucket_s *b = hm_get(hashmap, key);
|
||||||
|
if (b != NULL) {
|
||||||
|
hm_free_bucket(b);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
size_t mutex_id = get_mutex(hashmap, hashmap->hash(key));
|
|
||||||
mtx_t *mutex = &hashmap->locks[mutex_id];
|
|
||||||
if (mtx_lock(mutex) != thrd_success) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
bucket_s *bucket = hm_get(hashmap, key);
|
|
||||||
if (bucket == NULL) {
|
|
||||||
res = false;
|
|
||||||
} else {
|
|
||||||
hm_free_bucket(bucket);
|
|
||||||
}
|
|
||||||
|
|
||||||
mtx_unlock(mutex);
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void hm_foreach(hashmap_s *hashmap, void (*callback)(bucket_s *bucket)) {
|
void hm_foreach(hashmap_s *hashmap, void (*callback)(bucket_s *bucket)) {
|
||||||
@@ -400,7 +394,37 @@ void hm_foreach(hashmap_s *hashmap, void (*callback)(bucket_s *bucket)) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
/* Lock all mutexes */
|
||||||
|
for (size_t i = 0; i < hashmap->num_locks; ++i) {
|
||||||
|
mtx_lock(&hashmap->locks[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < MYCLIB_HASHMAP_SIZE; ++i) {
|
||||||
|
bucket_s *bucket = &hashmap->map[i];
|
||||||
|
|
||||||
|
if (bucket->key != NULL) {
|
||||||
|
bucket_s *copy = get_bucket_copy(bucket, hashmap->key_size, hashmap->value_size);
|
||||||
|
if (copy != NULL) {
|
||||||
|
callback(copy);
|
||||||
|
hm_free_bucket(copy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bucket = bucket->next;
|
||||||
|
while (bucket != NULL) {
|
||||||
|
bucket_s *copy = get_bucket_copy(bucket, hashmap->key_size, hashmap->value_size);
|
||||||
|
if (copy != NULL) {
|
||||||
|
callback(copy);
|
||||||
|
hm_free_bucket(copy);
|
||||||
|
}
|
||||||
|
bucket = bucket->next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Unlock all mutexes */
|
||||||
|
for (size_t i = 0; i < hashmap->num_locks; ++i) {
|
||||||
|
mtx_unlock(&hashmap->locks[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void hm_clear(hashmap_s *hashmap) {
|
void hm_clear(hashmap_s *hashmap) {
|
||||||
@@ -408,5 +432,144 @@ void hm_clear(hashmap_s *hashmap) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
/* Lock all mutexes */
|
||||||
|
for (size_t i = 0; i < hashmap->num_locks; ++i) {
|
||||||
|
mtx_lock(&hashmap->locks[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < MYCLIB_HASHMAP_SIZE; ++i) {
|
||||||
|
bucket_s *bucket = &hashmap->map[i];
|
||||||
|
|
||||||
|
if (bucket->key != NULL || bucket->value != NULL) {
|
||||||
|
free_bucket_content(hashmap, bucket);
|
||||||
|
}
|
||||||
|
|
||||||
|
bucket = bucket->next;
|
||||||
|
while (bucket != NULL) {
|
||||||
|
bucket_s *next = bucket->next;
|
||||||
|
free_bucket_content(hashmap, bucket);
|
||||||
|
free(bucket);
|
||||||
|
bucket = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
hashmap->map[i].key = NULL;
|
||||||
|
hashmap->map[i].value = NULL;
|
||||||
|
hashmap->map[i].next = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
atomic_store(&hashmap->size, 0);
|
||||||
|
|
||||||
|
/* Unlock all mutexes */
|
||||||
|
for (size_t i = 0; i < hashmap->num_locks; ++i) {
|
||||||
|
mtx_unlock(&hashmap->locks[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void **hm_get_keys(hashmap_s *hashmap, size_t *count) {
|
||||||
|
if (hashmap == NULL || count == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Lock all mutexes */
|
||||||
|
for (size_t i = 0; i < hashmap->num_locks; ++i) {
|
||||||
|
mtx_lock(&hashmap->locks[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t size = atomic_load(&hashmap->size);
|
||||||
|
*count = 0;
|
||||||
|
|
||||||
|
if (size == 0) {
|
||||||
|
for (size_t i = 0; i < hashmap->num_locks; ++i) {
|
||||||
|
mtx_unlock(&hashmap->locks[i]);
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Allocate array for key pointers */
|
||||||
|
void **keys = malloc(sizeof(void *) * size);
|
||||||
|
if (keys == NULL) {
|
||||||
|
for (size_t i = 0; i < hashmap->num_locks; ++i) {
|
||||||
|
mtx_unlock(&hashmap->locks[i]);
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t index = 0;
|
||||||
|
|
||||||
|
/* Iterate through all buckets */
|
||||||
|
for (size_t i = 0; i < MYCLIB_HASHMAP_SIZE && index < size; ++i) {
|
||||||
|
bucket_s *bucket = &hashmap->map[i];
|
||||||
|
|
||||||
|
if (bucket->key != NULL) {
|
||||||
|
keys[index] = malloc(hashmap->key_size);
|
||||||
|
if (keys[index] == NULL) {
|
||||||
|
/* Cleanup on failure */
|
||||||
|
for (size_t j = 0; j < index; ++j) {
|
||||||
|
if (hashmap->free_key != NULL) {
|
||||||
|
hashmap->free_key(keys[j]);
|
||||||
|
} else {
|
||||||
|
free(keys[j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(keys);
|
||||||
|
for (size_t j = 0; j < hashmap->num_locks; ++j) {
|
||||||
|
mtx_unlock(&hashmap->locks[j]);
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
memcpy(keys[index], bucket->key, hashmap->key_size);
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Iterate through chain */
|
||||||
|
bucket = bucket->next;
|
||||||
|
while (bucket != NULL && index < size) {
|
||||||
|
keys[index] = malloc(hashmap->key_size);
|
||||||
|
if (keys[index] == NULL) {
|
||||||
|
/* Cleanup on failure */
|
||||||
|
for (size_t j = 0; j < index; ++j) {
|
||||||
|
if (hashmap->free_key != NULL) {
|
||||||
|
hashmap->free_key(keys[j]);
|
||||||
|
} else {
|
||||||
|
free(keys[j]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(keys);
|
||||||
|
for (size_t j = 0; j < hashmap->num_locks; ++j) {
|
||||||
|
mtx_unlock(&hashmap->locks[j]);
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
memcpy(keys[index], bucket->key, hashmap->key_size);
|
||||||
|
index++;
|
||||||
|
bucket = bucket->next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*count = index;
|
||||||
|
|
||||||
|
/* Unlock all mutexes */
|
||||||
|
for (size_t i = 0; i < hashmap->num_locks; ++i) {
|
||||||
|
mtx_unlock(&hashmap->locks[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
void hm_free_keys(hashmap_s *hashmap, void **keys, size_t count) {
|
||||||
|
if (keys == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < count; ++i) {
|
||||||
|
if (keys[i] != NULL) {
|
||||||
|
if (hashmap != NULL && hashmap->free_key != NULL) {
|
||||||
|
hashmap->free_key(keys[i]);
|
||||||
|
} else {
|
||||||
|
free(keys[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(keys);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#ifndef MYCLIB_HASHMAP_H
|
#ifndef MYCLIB_HASHMAP_H
|
||||||
#define MYCLIB_HASHMAP_H
|
#define MYCLIB_HASHMAP_H
|
||||||
|
|
||||||
|
#include <stdatomic.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <threads.h>
|
#include <threads.h>
|
||||||
@@ -50,6 +51,7 @@ typedef void free_value_f(void *value);
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Main structure representing the hash map.
|
* @brief Main structure representing the hash map.
|
||||||
|
* Thread-safe for concurrent operations on different keys.
|
||||||
*/
|
*/
|
||||||
typedef struct hashmap {
|
typedef struct hashmap {
|
||||||
hash_f *hash; /**< Hash function */
|
hash_f *hash; /**< Hash function */
|
||||||
@@ -59,7 +61,7 @@ typedef struct hashmap {
|
|||||||
size_t key_size; /**< Size in bytes of the key */
|
size_t key_size; /**< Size in bytes of the key */
|
||||||
size_t value_size; /**< Size in bytes of the value */
|
size_t value_size; /**< Size in bytes of the value */
|
||||||
bucket_s map[MYCLIB_HASHMAP_SIZE]; /**< Array of bucket chains */
|
bucket_s map[MYCLIB_HASHMAP_SIZE]; /**< Array of bucket chains */
|
||||||
size_t size; /* Hashmap size (number of keys) */
|
atomic_size_t size; /**< Hashmap size (number of keys) - atomic */
|
||||||
mtx_t *locks; /**< Mutex array */
|
mtx_t *locks; /**< Mutex array */
|
||||||
size_t num_locks; /**< Number of mutex */
|
size_t num_locks; /**< Number of mutex */
|
||||||
} hashmap_s;
|
} hashmap_s;
|
||||||
@@ -113,7 +115,7 @@ bool hm_set(hashmap_s *hashmap, void *key, void *value);
|
|||||||
* @param[in] hashmap Pointer to the hash map.
|
* @param[in] hashmap Pointer to the hash map.
|
||||||
* @param[in] key Pointer to the key to search for.
|
* @param[in] key Pointer to the key to search for.
|
||||||
* @return Pointer to the copy of the bucket or NULL on failure.
|
* @return Pointer to the copy of the bucket or NULL on failure.
|
||||||
* @note Free after use.
|
* @note Free after use with hm_free_bucket().
|
||||||
*/
|
*/
|
||||||
bucket_s *hm_get(hashmap_s *hashmap, void *key);
|
bucket_s *hm_get(hashmap_s *hashmap, void *key);
|
||||||
|
|
||||||
@@ -129,9 +131,57 @@ bucket_s *hm_get(hashmap_s *hashmap, void *key);
|
|||||||
*/
|
*/
|
||||||
bool hm_remove(hashmap_s *hashmap, void *key);
|
bool hm_remove(hashmap_s *hashmap, void *key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the number of entries in the hash map.
|
||||||
|
*
|
||||||
|
* @param[in] hashmap Pointer to the hash map.
|
||||||
|
* @return Number of key-value pairs in the map.
|
||||||
|
*/
|
||||||
size_t hm_size(hashmap_s *hashmap);
|
size_t hm_size(hashmap_s *hashmap);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if a key exists in the hash map.
|
||||||
|
*
|
||||||
|
* @param[in] hashmap Pointer to the hash map.
|
||||||
|
* @param[in] key Pointer to the key to search for.
|
||||||
|
* @return true if the key exists, false otherwise.
|
||||||
|
*/
|
||||||
bool hm_contains(hashmap_s *hashmap, void *key);
|
bool hm_contains(hashmap_s *hashmap, void *key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Iterate over all entries in the hash map.
|
||||||
|
*
|
||||||
|
* @param[in] hashmap Pointer to the hash map.
|
||||||
|
* @param[in] callback Function called for each bucket.
|
||||||
|
*/
|
||||||
void hm_foreach(hashmap_s *hashmap, void (*callback)(bucket_s *bucket));
|
void hm_foreach(hashmap_s *hashmap, void (*callback)(bucket_s *bucket));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Remove all entries from the hash map.
|
||||||
|
*
|
||||||
|
* @param[in] hashmap Pointer to the hash map.
|
||||||
|
*/
|
||||||
void hm_clear(hashmap_s *hashmap);
|
void hm_clear(hashmap_s *hashmap);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get all keys from the hash map.
|
||||||
|
*
|
||||||
|
* Returns an array of pointers to copies of all keys in the hashmap.
|
||||||
|
* The caller is responsible for freeing the array and each key.
|
||||||
|
*
|
||||||
|
* @param[in] hashmap Pointer to the hash map.
|
||||||
|
* @param[out] count Pointer to store the number of keys returned.
|
||||||
|
* @return Array of key pointers, or NULL on failure. Must be freed with hm_free_keys().
|
||||||
|
*/
|
||||||
|
void **hm_get_keys(hashmap_s *hashmap, size_t *count);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Free the array returned by hm_get_keys.
|
||||||
|
*
|
||||||
|
* @param[in] hashmap Pointer to the hash map (needed for free_key function).
|
||||||
|
* @param[in] keys Array of keys returned by hm_get_keys.
|
||||||
|
* @param[in] count Number of keys in the array.
|
||||||
|
*/
|
||||||
|
void hm_free_keys(hashmap_s *hashmap, void **keys, size_t count);
|
||||||
|
|
||||||
#endif /* MYCLIB_HASHMAP_H */
|
#endif /* MYCLIB_HASHMAP_H */
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ project(
|
|||||||
|
|
||||||
cc = meson.get_compiler('c')
|
cc = meson.get_compiler('c')
|
||||||
|
|
||||||
|
# Add global posix feature macro
|
||||||
|
add_global_arguments('-D_POSIX_C_SOURCE=200112L', language: 'c')
|
||||||
|
|
||||||
# Sources without test files
|
# Sources without test files
|
||||||
lib_src = files(
|
lib_src = files(
|
||||||
'hashmap/myhashmap.c',
|
'hashmap/myhashmap.c',
|
||||||
|
|||||||
@@ -1,64 +1,60 @@
|
|||||||
#include "mysocket.h"
|
#include "mysocket.h"
|
||||||
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
|
||||||
int sock_platform_init() {
|
int sock_platform_init(void) {
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
WSADATA wsaData;
|
WSADATA wsaData;
|
||||||
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
|
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
|
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
|
||||||
WSACleanup();
|
WSACleanup();
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int sock_close(int socket) {
|
int sock_close(socket_t socket) {
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
ret = closesocket(socket);
|
ret = closesocket(socket);
|
||||||
#else
|
#else
|
||||||
ret = close(socket);
|
ret = close(socket);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
int sock_platform_shutdown() {
|
int sock_platform_shutdown(void) {
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
WSACleanup();
|
WSACleanup();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int sock_readall(int sockfd, void *buf, size_t bufsize) {
|
int sock_readall(socket_t sockfd, void *buf, size_t bufsize) {
|
||||||
char *p = (char *)buf;
|
char *p = (char *)buf;
|
||||||
size_t total_read = 0;
|
size_t total_read = 0;
|
||||||
|
|
||||||
while (1) {
|
while (total_read < bufsize) {
|
||||||
int n = recv(sockfd, p, bufsize - total_read, 0);
|
int n = recv(sockfd, p, bufsize - total_read, 0);
|
||||||
|
|
||||||
if (n > 0) {
|
if (n > 0) {
|
||||||
|
/* Data received */
|
||||||
p += n;
|
p += n;
|
||||||
total_read += n;
|
total_read += n;
|
||||||
|
|
||||||
if (total_read == bufsize) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else if (n == 0) {
|
} else if (n == 0) {
|
||||||
|
/* Connection closed */
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
/* Error */
|
||||||
break;
|
if (errno == EINTR) {
|
||||||
} else if (errno == EINTR) {
|
/* Try again */
|
||||||
continue;
|
continue;
|
||||||
|
} else if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||||
|
/* Socket non-blocking, no data right now */
|
||||||
|
/* Returns what has read */
|
||||||
|
return total_read;
|
||||||
} else {
|
} else {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@@ -68,8 +64,8 @@ int sock_readall(int sockfd, void *buf, size_t bufsize) {
|
|||||||
return total_read;
|
return total_read;
|
||||||
}
|
}
|
||||||
|
|
||||||
int sock_writeall(int socket, const void *buf, size_t n) {
|
int sock_writeall(socket_t socket, const void *buf, size_t n) {
|
||||||
const char *p = (char *)buf;
|
const char *p = (const char *)buf;
|
||||||
size_t bytes_to_write = n;
|
size_t bytes_to_write = n;
|
||||||
int bytes_written;
|
int bytes_written;
|
||||||
|
|
||||||
@@ -82,11 +78,13 @@ int sock_writeall(int socket, const void *buf, size_t n) {
|
|||||||
} else {
|
} else {
|
||||||
if (errno == EINTR) {
|
if (errno == EINTR) {
|
||||||
continue;
|
continue;
|
||||||
}
|
} else if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||||
|
return n - bytes_to_write;
|
||||||
|
} else {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,38 +1,36 @@
|
|||||||
#ifndef MYCLIB_SOCKET_H
|
#ifndef MYCLIB_SOCKET_H
|
||||||
#define MYCLIB_SOCKET_H
|
#define MYCLIB_SOCKET_H
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
/* Windows */
|
|
||||||
#define _WIN32_WINNT 0x0600
|
|
||||||
#include <winsock2.h>
|
#include <winsock2.h>
|
||||||
#include <ws2tcpip.h>
|
#include <ws2tcpip.h>
|
||||||
|
typedef int socket_t;
|
||||||
#else
|
#else
|
||||||
/* Unix */
|
|
||||||
|
|
||||||
#ifndef __USE_XOPEN2K
|
|
||||||
#define __USE_XOPEN2K 1
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <arpa/inet.h>
|
|
||||||
#include <netdb.h>
|
|
||||||
#include <netinet/in.h>
|
|
||||||
#include <sys/socket.h>
|
#include <sys/socket.h>
|
||||||
#include <sys/types.h>
|
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
typedef int socket_t;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Run this before everything */
|
/* Initialize the socket system */
|
||||||
int sock_platform_init();
|
int sock_platform_init(void);
|
||||||
|
|
||||||
/* Use this to close a socket */
|
/* Close a socket */
|
||||||
int sock_close(int socket);
|
int sock_close(socket_t socket);
|
||||||
|
|
||||||
/* Read/Write all to socket */
|
/* Clean the socket system */
|
||||||
int sock_readall(int sockfd, void *buf, size_t bufsize);
|
int sock_platform_shutdown(void);
|
||||||
int sock_writeall(int socket, const void *buf, size_t n);
|
|
||||||
|
|
||||||
/* Use at exit */
|
/*
|
||||||
int sock_platform_shutdown();
|
* Read 'bufsize' bytes from socket
|
||||||
|
* Returns the read bytes, -1 on failure, 0 connection closed without issues
|
||||||
|
*/
|
||||||
|
int sock_readall(socket_t sockfd, void *buf, size_t bufsize);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Writes 'n' bytes to socket
|
||||||
|
*/
|
||||||
|
int sock_writeall(socket_t socket, const void *buf, size_t n);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -8,32 +8,10 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <threads.h>
|
#include <threads.h>
|
||||||
|
|
||||||
/* Initialize Thread-Specific Storage */
|
|
||||||
|
|
||||||
static tss_t buffer_key;
|
|
||||||
static once_flag buffer_once = ONCE_FLAG_INIT;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
char *buf; /**< Allocated buffer */
|
|
||||||
size_t cap; /**< Buffer's capacity */
|
|
||||||
} tl_buffer_s;
|
|
||||||
|
|
||||||
static void buffer_destructor(void *buf) {
|
|
||||||
tl_buffer_s *tb = (tl_buffer_s *)buf;
|
|
||||||
if (tb == NULL) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
free(tb->buf);
|
|
||||||
free(tb);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void buffer_key_init(void) {
|
|
||||||
tss_create(&buffer_key, buffer_destructor);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Returns the next power of two of a number */
|
/* Returns the next power of two of a number */
|
||||||
static size_t next_power_two(size_t len) {
|
static size_t next_power_two(size_t len) {
|
||||||
if (len == 0) return 1;
|
if (len == 0)
|
||||||
|
return 1;
|
||||||
|
|
||||||
size_t p = 1;
|
size_t p = 1;
|
||||||
while (p < len) {
|
while (p < len) {
|
||||||
@@ -226,65 +204,59 @@ size_t string_cap(string_s *string) {
|
|||||||
return cap;
|
return cap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int string_lock(string_s *string) {
|
||||||
|
if (string == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mtx_lock(&string->lock) != thrd_success) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int string_unlock(string_s *string) {
|
||||||
|
if (string == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mtx_unlock(&string->lock) != thrd_success) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
char *string_cstr(string_s *string) {
|
char *string_cstr(string_s *string) {
|
||||||
if (string == NULL || string->data == NULL) {
|
if (string == NULL || string->data == NULL) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
call_once(&buffer_once, buffer_key_init);
|
return string->data;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *string_copy(string_s *string) {
|
||||||
|
if (string == NULL || string->data == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
if (mtx_lock(&string->lock) != thrd_success) {
|
if (mtx_lock(&string->lock) != thrd_success) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t need = string->size + 1;
|
char *cpy = malloc(string->size + 1);
|
||||||
|
if (!cpy) {
|
||||||
/* Retrieve thread local buffer */
|
|
||||||
tl_buffer_s *tb = (tl_buffer_s *)tss_get(buffer_key);
|
|
||||||
if (tb == NULL) {
|
|
||||||
/* Not found, make a new one */
|
|
||||||
tb = malloc(sizeof(tl_buffer_s));
|
|
||||||
if (tb == NULL) {
|
|
||||||
mtx_unlock(&string->lock);
|
mtx_unlock(&string->lock);
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
tb->cap = next_power_two(need);
|
memcpy(cpy, string->data, string->size);
|
||||||
tb->buf = malloc(tb->cap);
|
cpy[string->size] = '\0';
|
||||||
if (tb->buf == NULL) {
|
|
||||||
free(tb);
|
|
||||||
mtx_unlock(&string->lock);
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tss_set(buffer_key, tb) != thrd_success) {
|
|
||||||
free(tb->buf);
|
|
||||||
free(tb);
|
|
||||||
mtx_unlock(&string->lock);
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
} else if (tb->cap < need) {
|
|
||||||
/* Found, but we need a bigger buffer */
|
|
||||||
size_t newcap = next_power_two(need);
|
|
||||||
char *tmp = realloc(tb->buf, newcap);
|
|
||||||
if (tmp == NULL) {
|
|
||||||
mtx_unlock(&string->lock);
|
|
||||||
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
tb->buf = tmp;
|
|
||||||
tb->cap = newcap;
|
|
||||||
}
|
|
||||||
|
|
||||||
memcpy(tb->buf, string->data, need);
|
|
||||||
|
|
||||||
mtx_unlock(&string->lock);
|
mtx_unlock(&string->lock);
|
||||||
|
|
||||||
return tb->buf;
|
return cpy;
|
||||||
}
|
}
|
||||||
|
|
||||||
int string_compare(string_s *s1, string_s *s2) {
|
int string_compare(string_s *s1, string_s *s2) {
|
||||||
|
|||||||
@@ -77,13 +77,40 @@ size_t string_cap(string_s *string);
|
|||||||
* @brief Get a pointer to a null-terminated C-string.
|
* @brief Get a pointer to a null-terminated C-string.
|
||||||
*
|
*
|
||||||
* @param string String to read.
|
* @param string String to read.
|
||||||
* @return Pointer to a thread-local buffer, or NULL on failure.
|
* @return Pointer to string->data.
|
||||||
*
|
*
|
||||||
* @note Valid until the next call in the same thread. Do NOT free the returned pointer.
|
* @note See string_lock().
|
||||||
* Do NOT call more than once this function in a print function.
|
|
||||||
*/
|
*/
|
||||||
char *string_cstr(string_s *string);
|
char *string_cstr(string_s *string);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Create a heap-allocated copy of the string content.
|
||||||
|
*
|
||||||
|
* @param string String to copy.
|
||||||
|
* @return Newly allocated null-terminated buffer, or NULL on failure.
|
||||||
|
*
|
||||||
|
* @note The caller is responsible for freeing the returned buffer with free().
|
||||||
|
*/
|
||||||
|
char *string_copy(string_s *string);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Lock the string for safe reading or writing.
|
||||||
|
*
|
||||||
|
* @param string String to lock.
|
||||||
|
* @return 0 on success, -1 on failure.
|
||||||
|
*
|
||||||
|
* @note Use this before calling string_cstr() if you want a stable pointer.
|
||||||
|
*/
|
||||||
|
int string_lock(string_s *string);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Unlock a previously locked string.
|
||||||
|
*
|
||||||
|
* @param string String to unlock.
|
||||||
|
* @return 0 on success, -1 on failure.
|
||||||
|
*/
|
||||||
|
int string_unlock(string_s *string);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Compare two strings.
|
* @brief Compare two strings.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
#include "../hashmap/myhashmap.h"
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <myclib/myhashmap.h>
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
#include "../queue/myqueue.h"
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <myclib/myqueue.h>
|
|
||||||
|
|
||||||
void test_queue1(void) {
|
void test_queue1(void) {
|
||||||
/* Allocate a new queue */
|
/* Allocate a new queue */
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
#define _XOPEN_SOURCE 700
|
#include "../socket/mysocket.h"
|
||||||
#include <myclib/mysocket.h>
|
#include <arpa/inet.h>
|
||||||
|
#include <netdb.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
void test_socket1(void) {
|
void test_socket1(void) {
|
||||||
sock_platform_init();
|
sock_platform_init();
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
#include "../stack/mystack.h"
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <myclib/mystack.h>
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
void test_stack1(void) {
|
void test_stack1(void) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
#include "../string/mystring.h"
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <myclib/mystring.h>
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
#include "../string/mystring.h"
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <myclib/mystring.h>
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
void test_str2(void) {
|
void test_str2(void) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
#include "../string/mystring.h"
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <myclib/mystring.h>
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
void test_str3(void) {
|
void test_str3(void) {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ void test_vec1(void);
|
|||||||
|
|
||||||
void test_stack1(void);
|
void test_stack1(void);
|
||||||
|
|
||||||
void test_socket1();
|
void test_socket1(void);
|
||||||
|
|
||||||
int main(void) {
|
int main(void) {
|
||||||
puts("==== [Running Hashmap tests] ====");
|
puts("==== [Running Hashmap tests] ====");
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
#include "../vector/myvector.h"
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <myclib/myvector.h>
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
|
|
||||||
typedef struct my_elem {
|
typedef struct my_elem {
|
||||||
@@ -29,7 +29,7 @@ int my_cmp(const void *a, const void *b) {
|
|||||||
return ma->age - mb->age;
|
return ma->age - mb->age;
|
||||||
}
|
}
|
||||||
|
|
||||||
void test_vec1() {
|
void test_vec1(void) {
|
||||||
/* Allocate a new vector */
|
/* Allocate a new vector */
|
||||||
size_t elem_size = sizeof(my_elem_s);
|
size_t elem_size = sizeof(my_elem_s);
|
||||||
vec_s *v = vec_new(10, elem_size);
|
vec_s *v = vec_new(10, elem_size);
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
|
|
||||||
/* Returns the next power of two of a number */
|
/* Returns the next power of two of a number */
|
||||||
static size_t next_power_two(size_t len) {
|
static size_t next_power_two(size_t len) {
|
||||||
if (len == 0) return 1;
|
if (len == 0)
|
||||||
|
return 1;
|
||||||
|
|
||||||
size_t p = 1;
|
size_t p = 1;
|
||||||
while (p < len) {
|
while (p < len) {
|
||||||
|
|||||||
Reference in New Issue
Block a user