diff --git a/.clang-format b/.clang-format index 3d4d9d7..a6079ec 100644 --- a/.clang-format +++ b/.clang-format @@ -1,5 +1,5 @@ BasedOnStyle: Google -ColumnLimit: 120 +ColumnLimit: 160 UseTab: Always IndentWidth: 4 TabWidth: 4 diff --git a/include/server/server.h b/include/server/server.h index 11509fd..1ebe261 100644 --- a/include/server/server.h +++ b/include/server/server.h @@ -80,17 +80,17 @@ int cws_server_accept_client(int sockfd, struct sockaddr_storage *their_sa, sock /** * @brief Closes all the file descriptors opened * - * @param[in] bucket The hash map + * @param[in] hashmap Clients hash map */ -void cws_server_close_all_fds(cws_bucket *bucket); +void cws_server_close_all_fds(cws_hashmap *hashmap); /** * @brief Disconnect a client * * @param[in] epfd Epoll file descriptor * @param[in] client_fd Client file descriptor - * @param[in] bucket Clients hash map + * @param[in] hashmap Clients hash map */ -void cws_server_close_client(int epfd, int client_fd, cws_bucket *bucket); +void cws_server_close_client(int epfd, int client_fd, cws_hashmap *hashmap); #endif diff --git a/include/utils/hashmap.h b/include/utils/hashmap.h index e110ec1..dffaabf 100644 --- a/include/utils/hashmap.h +++ b/include/utils/hashmap.h @@ -2,87 +2,101 @@ #define CWS_HASHMAP_H #include -#include -/* Each process on Linux can have a maximum of 1024 open file descriptors */ -#define CWS_HASHMAP_MAX_CLIENTS 1024 +#define CWS_HASHMAP_SIZE 1024 /**< Number of buckets in the hash map */ /** - * @brief Client Hashmap struct - * + * @brief A single bucket in the hash map. */ typedef struct cws_bucket_t { - int sockfd; /**< Client socket descriptor */ - struct sockaddr_storage sas; /**< Associated socket address */ - struct cws_bucket_t *next; /**< Next node in case of collision */ - struct cws_bucket_t *prev; /**< Previous node in case of collision */ + void *key; /**< Pointer to the key */ + void *value; /**< Pointer to the value */ + struct cws_bucket_t *next; /**< Pointer to the next bucket in case of collision */ } cws_bucket; /** - * @brief Calculates the hash code of a given file descriptor + * @brief Function pointer type for a hash function * - * @param[in] sockfd The file descriptor - * @return Returns the hash code + * @param[in] key Pointer to the key to hash + * @return The computed hash as an integer */ -int cws_hm_hash(int sockfd); +typedef int cws_hash_fn(void *key); /** - * @brief Initializes the hash map + * @brief Function pointer type for a key comparison function * - * @param[out] bucket The hash map uninitialized + * @param[in] key_a Pointer to the first key + * @param[in] key_b Pointer to the second key + * @return true if the keys are considered equal, false otherwise */ -void cws_hm_init(cws_bucket *bucket); +typedef bool cws_equal_fn(void *key_a, void *key_b); /** - * @brief Inserts a key in the hash map + * @brief Function pointer type for freeing a key * - * @param[out] bucket The hash map - * @param[in] sockfd The file descriptor (value) - * @param[in] sas The sockaddr (value) + * @param[in] key Pointer to the key to free */ -void cws_hm_push(cws_bucket *bucket, int sockfd, struct sockaddr_storage *sas); +typedef void cws_free_key_fn(void *key); /** - * @brief Removes a key from the hash map + * @brief Function pointer type for freeing a value * - * @param[out] bucket The hash map - * @param[in] sockfd The key + * @param[in] value Pointer to the value to free */ -void cws_hm_remove(cws_bucket *bucket, int sockfd); +typedef void cws_free_value_fn(void *value); /** - * @brief Searches for a key in the hash map - * - * @param[in] bucket The hash map - * @param[in] sockfd The file descriptor (key) - * @return Returns NULL or the key pointer + * @brief Main structure representing the hash map */ -cws_bucket *cws_hm_lookup(cws_bucket *bucket, int sockfd); +typedef struct cws_hashmap_t { + cws_hash_fn *hash_fn; /**< Hash function */ + cws_equal_fn *equal_fn; /**< Equality comparison function */ + cws_free_key_fn *free_key_fn; /**< Key deallocation function */ + cws_free_value_fn *free_value_fn; /**< Value deallocation function */ + + cws_bucket map[CWS_HASHMAP_SIZE]; /**< Array of bucket chains */ +} cws_hashmap; /** - * @brief Cleans the hash map + * @brief Initializes a new hash map with user-defined behavior * - * @param[out] bucket The hash map + * @param[in] hash_fn Function used to hash keys + * @param[in] equal_fn Function used to compare keys + * @param[in] free_key_fn Function used to free keys + * @param[in] free_value_fn Function used to free values + * @return A pointer to the newly initialized hash map */ -void cws_hm_free(cws_bucket *bucket); +cws_hashmap *cws_hm_init(cws_hash_fn *hash_fn, cws_equal_fn *equal_fn, cws_free_key_fn *free_key_fn, cws_free_value_fn *free_value_fn); /** - * @brief Checks if a file descriptor is in the bucket array (not linked list) + * @brief Frees all resources used by the hash map * - * @param[in] bucket - * @param[in] sockfd - * @return true If the file descriptor is in the bucket array - * @return false If the file descriptor is not in the bucket array (check with hm_lookup()) + * @param[in] hashmap Pointer to the hash map to free */ -bool cws_hm_is_in_bucket_array(cws_bucket *bucket, int sockfd); +void cws_hm_free(cws_hashmap *hashmap); /** - * @brief This function will add a key even if it exists (use hm_push() instead) + * @brief Inserts or updates a key-value pair in the hash map * - * @param bucket - * @param sockfd - * @param sas + * @param[in] hashmap Pointer to the hash map + * @param[in] key Pointer to the key to insert + * @param[in] value Pointer to the value to insert + * @return true if the operation succeeded, false otherwise */ -void cws_hm_insert(cws_bucket *bucket, int sockfd, struct sockaddr_storage *sas); +bool cws_hm_set(cws_hashmap *hashmap, void *key, void *value); -#endif \ No newline at end of file +/** + * @brief Retrieves a bucket by key + * + * @param[in] hashmap Pointer to the hash map + * @param[in] key Pointer to the key to search for + * @return Pointer to the found bucket, or NULL if not found + */ +cws_bucket *cws_hm_get(cws_hashmap *hashmap, void *key); + +/** + * TODO: Implement cws_hm_remove() + * @brief Removes a key-value pair from the hash map + */ + +#endif diff --git a/include/utils/utils.h b/include/utils/utils.h index 9584f9d..d468076 100644 --- a/include/utils/utils.h +++ b/include/utils/utils.h @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -23,4 +24,10 @@ void cws_utils_print_ips(const char *hostname, const char *port); */ void cws_utils_get_client_ip(struct sockaddr_storage *sa, char *ip); +/* TODO: add docs */ +/* Functions used for hash maps */ +int my_hash_fn(void *key); +bool my_equal_fn(void *a, void *b); +void my_free_value_fn(void *value); + #endif diff --git a/meson.build b/meson.build index 32e5c82..8421d1c 100644 --- a/meson.build +++ b/meson.build @@ -11,3 +11,5 @@ add_global_arguments('-DUSE_COLORS', language : 'c') add_global_arguments('-DEVELOPER', language : 'c') executable('cws', server, include_directories : incdir, dependencies : [libyaml, libcyaml]) + +executable('testbuild', test, include_directories : incdir) diff --git a/src/http/http.c b/src/http/http.c index 2f4466e..e96d292 100644 --- a/src/http/http.c +++ b/src/http/http.c @@ -12,6 +12,7 @@ cws_http *cws_http_parse(char *request_str, int sockfd) { if (request == NULL) { return NULL; } + memset(request, 0, sizeof(cws_http)); /* Insert socket file descriptor */ request->sockfd = sockfd; diff --git a/src/main.c b/src/main.c index e978220..99dacfb 100644 --- a/src/main.c +++ b/src/main.c @@ -14,18 +14,18 @@ int main(int argc, char **argv) { cws_config *config = cws_config_init(); if (config == NULL) { - CWS_LOG_ERROR("[server] Unable to read config file"); + CWS_LOG_ERROR("Unable to read config file"); return 1; } struct sigaction act = {.sa_handler = cws_signal_handler}; ret = sigaction(SIGINT, &act, NULL); - CWS_LOG_INFO("[server] Running cws on http://%s:%s...", config->host, config->port); + CWS_LOG_INFO("Running cws on http://%s:%s...", config->host, config->port); ret = cws_server_start(config->host, config->port); if (ret < 0) { - CWS_LOG_ERROR("[server] Unable to start web server"); + CWS_LOG_ERROR("Unable to start web server"); } cws_config_free(config); diff --git a/src/meson.build b/src/meson.build index 59f2b66..2a44c6f 100644 --- a/src/meson.build +++ b/src/meson.build @@ -1,3 +1,6 @@ server = files('main.c', 'server/server.c') server += files('utils/utils.c', 'utils/hashmap.c', 'utils/config.c') server += files('http/http.c') + +test = files('../test/hashmap_test.c') +test += files('utils/hashmap.c') diff --git a/src/server/server.c b/src/server/server.c index 0138eb2..6a0ad95 100644 --- a/src/server/server.c +++ b/src/server/server.c @@ -26,7 +26,7 @@ int cws_server_start(const char *hostname, const char *service) { int status = getaddrinfo(hostname, service, &hints, &res); if (status != 0) { - CWS_LOG_ERROR("[server] getaddrinfo() error: %s", gai_strerror(status)); + CWS_LOG_ERROR("getaddrinfo() error: %s", gai_strerror(status)); exit(EXIT_FAILURE); } @@ -35,19 +35,19 @@ int cws_server_start(const char *hostname, const char *service) { const int opt = 1; status = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof opt); if (status != 0) { - CWS_LOG_ERROR("[server] setsockopt(): %s", strerror(errno)); + CWS_LOG_ERROR("setsockopt(): %s", strerror(errno)); exit(EXIT_FAILURE); } status = bind(sockfd, res->ai_addr, res->ai_addrlen); if (status != 0) { - CWS_LOG_ERROR("[server] bind(): %s", strerror(errno)); + CWS_LOG_ERROR("bind(): %s", strerror(errno)); exit(EXIT_FAILURE); } status = listen(sockfd, CWS_SERVER_BACKLOG); if (status != 0) { - CWS_LOG_ERROR("[server] listen(): %s", gai_strerror(status)); + CWS_LOG_ERROR("listen(): %s", gai_strerror(status)); exit(EXIT_FAILURE); } @@ -78,8 +78,7 @@ void cws_server_loop(int sockfd) { struct sockaddr_storage their_sa; socklen_t theirsa_size = sizeof their_sa; - cws_bucket clients[CWS_HASHMAP_MAX_CLIENTS]; - cws_hm_init(clients); + cws_hashmap *clients = cws_hm_init(my_hash_fn, my_equal_fn, NULL, NULL); int epfd = epoll_create1(0); cws_fd_set_nonblocking(sockfd); @@ -97,24 +96,27 @@ void cws_server_loop(int sockfd) { char ip[INET_ADDRSTRLEN]; client_fd = cws_server_accept_client(sockfd, &their_sa, &theirsa_size); cws_utils_get_client_ip(&their_sa, ip); - CWS_LOG_INFO("[server] Client (%s) connected", ip); + CWS_LOG_INFO("Client (%s) connected", ip); cws_fd_set_nonblocking(client_fd); cws_epoll_add(epfd, client_fd, EPOLLIN); - cws_hm_push(clients, client_fd, &their_sa); + cws_hm_set(clients, &client_fd, &their_sa); } else { char data[4096] = {0}; + char ip[INET_ADDRSTRLEN] = {0}; /* Incoming data */ client_fd = revents[i].data.fd; const ssize_t bytes_read = recv(client_fd, data, sizeof data, 0); - char ip[INET_ADDRSTRLEN]; - cws_bucket *client = cws_hm_lookup(clients, client_fd); - cws_utils_get_client_ip(&client->sas, ip); + + /* Retrieve client ip */ + cws_bucket *client = cws_hm_get(clients, &client_fd); + struct sockaddr_storage client_sas = *(struct sockaddr_storage *)client->value; + cws_utils_get_client_ip(&client_sas, ip); if (bytes_read == 0) { /* Client disconnected */ - CWS_LOG_INFO("[server] Client (%s) disconnected", ip); + CWS_LOG_INFO("Client (%s) disconnected", ip); cws_server_close_client(epfd, client_fd, clients); continue; } @@ -139,7 +141,7 @@ void cws_server_loop(int sockfd) { } cws_http_send_response(request); - CWS_LOG_INFO("[server] Client (%s) disconnected", ip); + CWS_LOG_INFO("Client (%s) disconnected", ip); cws_server_close_client(epfd, client_fd, clients); cws_http_free(request); @@ -154,7 +156,7 @@ void cws_server_loop(int sockfd) { close(epfd); cws_server_close_all_fds(clients); cws_hm_free(clients); - CWS_LOG_INFO("[server] Closing..."); + CWS_LOG_INFO("Closing..."); } void cws_epoll_add(int epfd, int sockfd, uint32_t events) { @@ -164,7 +166,7 @@ void cws_epoll_add(int epfd, int sockfd, uint32_t events) { const int status = epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &event); if (status != 0) { - CWS_LOG_ERROR("[server] epoll_ctl_add(): %s", strerror(errno)); + CWS_LOG_ERROR("epoll_ctl_add(): %s", strerror(errno)); exit(EXIT_FAILURE); } } @@ -173,7 +175,7 @@ void cws_epoll_del(int epfd, int sockfd) { const int status = epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, NULL); if (status != 0) { - CWS_LOG_ERROR("[server] epoll_ctl_del(): %s", strerror(errno)); + CWS_LOG_ERROR("epoll_ctl_del(): %s", strerror(errno)); exit(EXIT_FAILURE); } } @@ -182,7 +184,7 @@ void cws_fd_set_nonblocking(int sockfd) { const int status = fcntl(sockfd, F_SETFL, O_NONBLOCK); if (status == -1) { - CWS_LOG_ERROR("[server] fcntl(): %s", gai_strerror(status)); + CWS_LOG_ERROR("fcntl(): %s", gai_strerror(status)); exit(EXIT_FAILURE); } } @@ -192,7 +194,7 @@ int cws_server_accept_client(int sockfd, struct sockaddr_storage *their_sa, sock if (client_fd == -1) { if (errno != EWOULDBLOCK) { - CWS_LOG_ERROR("[server] accept(): %s", strerror(errno)); + CWS_LOG_ERROR("accept(): %s", strerror(errno)); } return -1; } @@ -200,11 +202,12 @@ int cws_server_accept_client(int sockfd, struct sockaddr_storage *their_sa, sock return client_fd; } -void cws_server_close_all_fds(cws_bucket *bucket) { - for (size_t i = 0; i < CWS_HASHMAP_MAX_CLIENTS; ++i) { +void cws_server_close_all_fds(cws_hashmap *hashmap) { + /* TODO: fix this */ + + /*for (size_t i = 0; i < CWS_HASHMAP_SIZE; ++i) { close(bucket[i].sockfd); if (bucket[i].next != NULL) { - /* Close the fds */ cws_bucket *p = bucket[i].next; cws_bucket *next = p->next; do { @@ -213,11 +216,11 @@ void cws_server_close_all_fds(cws_bucket *bucket) { next = p != NULL ? p->next : NULL; } while (p != NULL); } - } + }*/ } -void cws_server_close_client(int epfd, int client_fd, cws_bucket *clients) { +void cws_server_close_client(int epfd, int client_fd, cws_hashmap *hashmap) { cws_epoll_del(epfd, client_fd); - cws_hm_remove(clients, client_fd); + /* TODO: cws_hm_remove() */ close(client_fd); } diff --git a/src/utils/hashmap.c b/src/utils/hashmap.c index 79c4005..b36ca0d 100644 --- a/src/utils/hashmap.c +++ b/src/utils/hashmap.c @@ -1,101 +1,137 @@ #include "utils/hashmap.h" +#include #include #include -int cws_hm_hash(int sockfd) { return sockfd % CWS_HASHMAP_MAX_CLIENTS; } +cws_hashmap *cws_hm_init(cws_hash_fn *hash_fn, cws_equal_fn *equal_fn, cws_free_key_fn *free_key_fn, cws_free_value_fn *free_value_fn) { + /* Allocate hash map struct */ + cws_hashmap *hashmap = (cws_hashmap *)malloc(sizeof(cws_hashmap)); -void cws_hm_init(cws_bucket *bucket) { - /* Initialize everything to 0 for the struct, then -1 for fd and next to NULL */ - memset(bucket, 0, sizeof(cws_bucket) * CWS_HASHMAP_MAX_CLIENTS); - - for (size_t i = 0; i < CWS_HASHMAP_MAX_CLIENTS; ++i) { - bucket[i].sockfd = -1; - bucket[i].next = NULL; - bucket[i].prev = NULL; - } -} - -void cws_hm_insert(cws_bucket *bucket, int sockfd, struct sockaddr_storage *sas) { - int index = cws_hm_hash(sockfd); - - if (bucket[index].sockfd == -1) { - /* Current slot is empty */ - bucket[index].sockfd = sockfd; - bucket[index].sas = *sas; - } else { - /* Append the new key to the head (not the first element because it belongs to the array) of the linked - * list */ - cws_bucket *p = &bucket[index]; - cws_bucket *new = malloc(sizeof(cws_bucket)); - new->sockfd = sockfd; - new->sas = *sas; - new->next = p->next; - new->prev = p; - p->next = new; - } -} - -cws_bucket *cws_hm_lookup(cws_bucket *bucket, int sockfd) { - int index = cws_hm_hash(sockfd); - - if (bucket[index].sockfd != sockfd) { - cws_bucket *p; - for (p = bucket[index].next; p != NULL && p->sockfd != sockfd; p = p->next); - return p; - } else { - return &bucket[index]; + if (hashmap == NULL) { + return NULL; } - return NULL; + /* Populate hash map with given parameters */ + hashmap->hash_fn = hash_fn; + hashmap->equal_fn = equal_fn; + hashmap->free_key_fn = free_key_fn; + hashmap->free_value_fn = free_value_fn; + /* Clear cws_bucket map */ + memset(hashmap->map, 0, sizeof(hashmap->map)); + + return hashmap; } -void cws_hm_push(cws_bucket *bucket, int sockfd, struct sockaddr_storage *sas) { - if (cws_hm_lookup(bucket, sockfd) == NULL) { - cws_hm_insert(bucket, sockfd, sas); - } -} - -void cws_hm_remove(cws_bucket *bucket, int sockfd) { - if (cws_hm_is_in_bucket_array(bucket, sockfd)) { - /* Instead of doing this I could copy the memory of the next node into the head */ - int index = cws_hm_hash(sockfd); - bucket[index].sockfd = -1; - +void cws_hm_free(cws_hashmap *hashmap) { + if (hashmap == NULL) { return; } - /* Key not in the bucket array, let's search in the linked list */ - cws_bucket *p = cws_hm_lookup(bucket, sockfd); - if (p == NULL) return; - p->prev->next = p->next; - if (p->next != NULL) { - /* If there's a next node update the previous node */ - p->next->prev = p->prev; - } - free(p); -} + for (size_t i = 0; i < CWS_HASHMAP_SIZE; ++i) { + cws_bucket *bucket = &hashmap->map[i]; + if (bucket->key != NULL) { + if (hashmap->free_key_fn != NULL) { + hashmap->free_key_fn(bucket->key); + } + if (hashmap->free_value_fn != NULL) { + hashmap->free_value_fn(bucket->value); + } + } -void cws_hm_free(cws_bucket *bucket) { - cws_bucket *p, *next; - - for (size_t i = 0; i < CWS_HASHMAP_MAX_CLIENTS; ++i) { - if (bucket[i].next != NULL) { - /* Free the malloc */ - p = bucket[i].next; - next = p->next; - do { - free(p); - p = next; - next = p != NULL ? p->next : NULL; - } while (p != NULL); + bucket = bucket->next; + while (bucket) { + if (hashmap->free_key_fn != NULL) { + hashmap->free_key_fn(bucket->key); + } + if (hashmap->free_value_fn != NULL) { + hashmap->free_value_fn(bucket->value); + } + cws_bucket *next = bucket->next; + free(bucket); + bucket = next; } } + + free(hashmap); } -bool cws_hm_is_in_bucket_array(cws_bucket *bucket, int sockfd) { - int index = cws_hm_hash(sockfd); - if (bucket[index].sockfd == sockfd) return true; +bool cws_hm_set(cws_hashmap *hashmap, void *key, void *value) { + /* Get hash index */ + int index = hashmap->hash_fn(key); + cws_bucket *bucket = &hashmap->map[index]; - return false; + /* Check if the key at index is empty */ + if (bucket->key == NULL) { + /* Bucket is empty */ + /* Set the key and value */ + bucket->key = key; + bucket->value = value; + bucket->next = NULL; + + return true; + } + + /* Check if bucket is already set */ + if (hashmap->equal_fn(bucket->key, key)) { + /* Same key, free value and update it */ + if (hashmap->free_value_fn != NULL) { + hashmap->free_value_fn(bucket->value); + } + bucket->value = value; + + return true; + } + + /* Key not found, iterate through the linked list */ + cws_bucket *next = bucket->next; + while (next) { + if (hashmap->equal_fn(next->key, key)) { + /* Same key, free value and update it */ + if (hashmap->free_value_fn != NULL) { + hashmap->free_value_fn(next->value); + } + next->value = value; + return true; + } + next = next->next; + } + + /* Append the new key/value to the head of the linked list */ + next = (cws_bucket *)malloc(sizeof(cws_bucket)); + if (next == NULL) { + return false; + } + next->key = key; + next->value = value; + next->next = bucket->next; + bucket->next = next; + + return true; +} + +cws_bucket *cws_hm_get(cws_hashmap *hashmap, void *key) { + /* Return if key is null */ + if (key == NULL) { + return NULL; + } + + int index = hashmap->hash_fn(key); + cws_bucket *bucket = &hashmap->map[index]; + + /* Key is not in the hash map */ + if (bucket == NULL || bucket->key == NULL) { + return NULL; + } + + /* Iterate through the linked list */ + while (bucket) { + if (hashmap->equal_fn(bucket->key, key)) { + return bucket; + } + bucket = bucket->next; + } + + /* Key not found */ + return NULL; } diff --git a/src/utils/utils.c b/src/utils/utils.c index b9aa4a4..ea3c468 100644 --- a/src/utils/utils.c +++ b/src/utils/utils.c @@ -43,4 +43,27 @@ void cws_utils_get_client_ip(struct sockaddr_storage *sa, char *ip) { struct sockaddr_in *sin = (struct sockaddr_in *)sa; inet_ntop(AF_INET, &sin->sin_addr, ip, INET_ADDRSTRLEN); -} \ No newline at end of file +} + +int my_hash_fn(void *key) { + char *key_str = (char *)key; + size_t key_len = strlen(key_str); + + int total = 0; + + for (size_t i = 0; i < key_len; ++i) { + total += (int)key_str[i]; + } + + return total % 2069; +} + +bool my_equal_fn(void *a, void *b) { + if (strcmp((char *)a, (char *)b) == 0) { + return true; + } + + return false; +} + +void my_free_str_fn(void *value) { free(value); } diff --git a/test/hashmap_test.c b/test/hashmap_test.c new file mode 100644 index 0000000..652de70 --- /dev/null +++ b/test/hashmap_test.c @@ -0,0 +1,65 @@ +#include "utils/hashmap.h" + +#include +#include +#include + +#include "utils/colors.h" + +int my_hash_fn(void *key) { + char *key_str = (char *)key; + size_t key_len = strlen(key_str); + + int total = 0; + + for (size_t i = 0; i < key_len; ++i) { + total += (int)key_str[i]; + } + + return total % 2069; +} + +bool my_equal_fn(void *a, void *b) { + if (strcmp((char *)a, (char *)b) == 0) { + return true; + } + + return false; +} + +void my_free_fn(void *value) { free(value); } + +int main(void) { + bool ret; + cws_hashmap *str_hashmap = cws_hm_init(my_hash_fn, my_equal_fn, my_free_fn, my_free_fn); + + char *key = strdup("test1"); + char *value = strdup("value1"); + + if (key == NULL || value == NULL) { + CWS_LOG_ERROR("strdup()"); + + return 1; + } + + ret = cws_hm_set(str_hashmap, (void *)key, (void *)value); + if (!ret) { + CWS_LOG_WARNING("Unable to set %s:%s", key, value); + } + + cws_bucket *bucket = cws_hm_get(str_hashmap, key); + CWS_LOG_DEBUG("Set %s:%s", (char *)bucket->key, (char *)bucket->value); + + char *another_value = strdup("another value1"); + ret = cws_hm_set(str_hashmap, (void *)key, (void *)another_value); + if (!ret) { + CWS_LOG_WARNING("Unable to set %s:%s", key, another_value); + } + + bucket = cws_hm_get(str_hashmap, key); + CWS_LOG_DEBUG("Set %s:%s", (char *)bucket->key, (char *)bucket->value); + + cws_hm_free(str_hashmap); + + return 0; +} diff --git a/www/index.html b/www/index.html index 4b77ebe..574aa99 100644 --- a/www/index.html +++ b/www/index.html @@ -14,7 +14,7 @@

Hello from CWS!

-

Try to open an image here.

+

Open an image here.