/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* Fluent Bit * ========== * Copyright (C) 2015-2022 The Fluent Bit Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include static inline void flb_hash_table_entry_free(struct flb_hash_table *ht, struct flb_hash_table_entry *entry) { mk_list_del(&entry->_head); mk_list_del(&entry->_head_parent); entry->table->count--; ht->total_count--; flb_free(entry->key); if (entry->val && entry->val_size > 0) { flb_free(entry->val); } flb_free(entry); } struct flb_hash_table *flb_hash_table_create(int evict_mode, size_t size, int max_entries) { int i; struct flb_hash_table_chain *tmp; struct flb_hash_table *ht; if (size <= 0) { return NULL; } ht = flb_malloc(sizeof(struct flb_hash_table)); if (!ht) { flb_errno(); return NULL; } mk_list_init(&ht->entries); ht->evict_mode = evict_mode; ht->max_entries = max_entries; ht->size = size; ht->total_count = 0; ht->cache_ttl = 0; ht->table = flb_calloc(1, sizeof(struct flb_hash_table_chain) * size); if (!ht->table) { flb_errno(); flb_free(ht); return NULL; } /* Initialize chains list head */ for (i = 0; i < size; i++) { tmp = &ht->table[i]; tmp->count = 0; mk_list_init(&tmp->chains); } return ht; } struct flb_hash_table *flb_hash_table_create_with_ttl(int cache_ttl, int evict_mode, size_t size, int max_entries) { struct flb_hash_table *ht; ht = flb_hash_table_create(evict_mode, size, max_entries); if (!ht) { flb_errno(); return NULL; } ht->cache_ttl = cache_ttl; return ht; } int flb_hash_table_del_ptr(struct flb_hash_table *ht, const char *key, int key_len, void *ptr) { int id; uint64_t hash; struct mk_list *head; struct flb_hash_table_entry *entry = NULL; struct flb_hash_table_chain *table; /* Generate hash number */ hash = cfl_hash_64bits(key, key_len); id = (hash % ht->size); /* Link the new entry in our table at the end of the list */ table = &ht->table[id]; mk_list_foreach(head, &table->chains) { entry = mk_list_entry(head, struct flb_hash_table_entry, _head); if (strncmp(entry->key, key, key_len) == 0 && entry->val == ptr) { break; } entry = NULL; } if (!entry) { return -1; } /* delete the entry */ flb_hash_table_entry_free(ht, entry); return 0; } void flb_hash_table_destroy(struct flb_hash_table *ht) { int i; struct mk_list *tmp; struct mk_list *head; struct flb_hash_table_entry *entry; struct flb_hash_table_chain *table; for (i = 0; i < ht->size; i++) { table = &ht->table[i]; mk_list_foreach_safe(head, tmp, &table->chains) { entry = mk_list_entry(head, struct flb_hash_table_entry, _head); flb_hash_table_entry_free(ht, entry); } } flb_free(ht->table); flb_free(ht); } static void flb_hash_table_evict_random(struct flb_hash_table *ht) { int id; int count = 0; struct mk_list *tmp; struct mk_list *head; struct flb_hash_table_entry *entry; id = random() % ht->total_count; mk_list_foreach_safe(head, tmp, &ht->entries) { if (id == count) { entry = mk_list_entry(head, struct flb_hash_table_entry, _head_parent); flb_hash_table_entry_free(ht, entry); break; } count++; } } static void flb_hash_table_evict_less_used(struct flb_hash_table *ht) { struct mk_list *head; struct flb_hash_table_entry *entry; struct flb_hash_table_entry *entry_less_used = NULL; mk_list_foreach(head, &ht->entries) { entry = mk_list_entry(head, struct flb_hash_table_entry, _head_parent); if (!entry_less_used) { entry_less_used = entry; } else if (entry->hits < entry_less_used->hits) { entry_less_used = entry; } } flb_hash_table_entry_free(ht, entry_less_used); } static void flb_hash_table_evict_older(struct flb_hash_table *ht) { struct flb_hash_table_entry *entry; entry = mk_list_entry_first(&ht->entries, struct flb_hash_table_entry, _head_parent); flb_hash_table_entry_free(ht, entry); } static struct flb_hash_table_entry *hash_get_entry(struct flb_hash_table *ht, const char *key, int key_len, int *out_id) { int id; uint64_t hash; struct mk_list *head; struct flb_hash_table_chain *table; struct flb_hash_table_entry *entry; if (!key || key_len <= 0) { return NULL; } hash = cfl_hash_64bits(key, key_len); id = (hash % ht->size); table = &ht->table[id]; if (table->count == 0) { return NULL; } if (table->count == 1) { entry = mk_list_entry_first(&table->chains, struct flb_hash_table_entry, _head); if (entry->key_len != key_len || strncmp(entry->key, key, key_len) != 0) { entry = NULL; } } else { /* Iterate entries */ mk_list_foreach(head, &table->chains) { entry = mk_list_entry(head, struct flb_hash_table_entry, _head); if (entry->key_len != key_len) { entry = NULL; continue; } if (strncmp(entry->key, key, key_len) == 0) { break; } entry = NULL; } } if (entry) { *out_id = id; } return entry; } static int entry_set_value(struct flb_hash_table_entry *entry, void *val, size_t val_size) { char *ptr; /* * If the entry already contains a previous value in the heap, just remove * the previously assigned memory. */ if (entry->val_size > 0) { flb_free(entry->val); } /* * Now set the new value. If val_size > 0, we create a new memory area, otherwise * it means the caller just wants to store a pointer address, no allocation * is required. */ if (val_size > 0) { entry->val = flb_malloc(val_size + 1); if (!entry->val) { flb_errno(); return -1; } /* * Copy the buffer and append a NULL byte in case the caller set and * expects a string. */ memcpy(entry->val, val, val_size); ptr = (char *) entry->val; ptr[val_size] = '\0'; entry->val_size = val_size; } else { /* just do a reference */ entry->val = val; entry->val_size = -1; } entry->created = time(NULL); return 0; } int flb_hash_table_add(struct flb_hash_table *ht, const char *key, int key_len, void *val, ssize_t val_size) { int id; int ret; uint64_t hash; struct flb_hash_table_entry *entry; struct flb_hash_table_chain *table; if (!key || key_len <= 0) { return -1; } /* Check capacity */ if (ht->max_entries > 0 && ht->total_count >= ht->max_entries) { if (ht->evict_mode == FLB_HASH_TABLE_EVICT_NONE) { /* Do nothing */ } else if (ht->evict_mode == FLB_HASH_TABLE_EVICT_OLDER) { flb_hash_table_evict_older(ht); } else if (ht->evict_mode == FLB_HASH_TABLE_EVICT_LESS_USED) { flb_hash_table_evict_less_used(ht); } else if (ht->evict_mode == FLB_HASH_TABLE_EVICT_RANDOM) { flb_hash_table_evict_random(ht); } } /* Check if this is a replacement */ entry = hash_get_entry(ht, key, key_len, &id); if (entry) { /* * The key already exists, just perform a value replacement, check if the * value refers to our own previous allocation. */ ret = entry_set_value(entry, val, val_size); if (ret == -1) { return -1; } return id; } /* * Below is just code to handle the creation of a new entry in the table */ /* Generate hash number */ hash = cfl_hash_64bits(key, key_len); id = (hash % ht->size); /* Allocate the entry */ entry = flb_calloc(1, sizeof(struct flb_hash_table_entry)); if (!entry) { flb_errno(); return -1; } entry->created = time(NULL); entry->hash = hash; entry->hits = 0; /* Store the key and value as a new memory region */ entry->key = flb_strndup(key, key_len); entry->key_len = key_len; entry->val_size = 0; /* store or reference the value */ ret = entry_set_value(entry, val, val_size); if (ret == -1) { flb_free(entry); return -1; } /* Link the new entry in our table at the end of the list */ table = &ht->table[id]; entry->table = table; /* Add the new entry */ mk_list_add(&entry->_head, &table->chains); mk_list_add(&entry->_head_parent, &ht->entries); /* Update counters */ table->count++; ht->total_count++; return id; } int flb_hash_table_get(struct flb_hash_table *ht, const char *key, int key_len, void **out_buf, size_t *out_size) { int id; struct flb_hash_table_entry *entry; time_t expiration; entry = hash_get_entry(ht, key, key_len, &id); if (!entry) { return -1; } if (ht->cache_ttl > 0) { expiration = entry->created + ht->cache_ttl; if (time(NULL) > expiration) { flb_hash_table_entry_free(ht, entry); return -1; } } entry->hits++; *out_buf = entry->val; *out_size = entry->val_size; return id; } /* check if a hash exists */ int flb_hash_table_exists(struct flb_hash_table *ht, uint64_t hash) { int id; struct mk_list *head; struct flb_hash_table_chain *table; struct flb_hash_table_entry *entry; id = (hash % ht->size); table = &ht->table[id]; /* Iterate entries */ mk_list_foreach(head, &table->chains) { entry = mk_list_entry(head, struct flb_hash_table_entry, _head); if (entry->hash == hash) { return FLB_TRUE; } } return FLB_FALSE; } /* * Get an entry based in the table id. Note that a table id might have multiple * entries so the 'key' parameter is required to get an exact match. */ int flb_hash_table_get_by_id(struct flb_hash_table *ht, int id, const char *key, const char **out_buf, size_t * out_size) { struct mk_list *head; struct flb_hash_table_entry *entry = NULL; struct flb_hash_table_chain *table; if (ht->size <= id) { return -1; } table = &ht->table[id]; if (table->count == 0) { return -1; } if (table->count == 1) { entry = mk_list_entry_first(&table->chains, struct flb_hash_table_entry, _head); } else { mk_list_foreach(head, &table->chains) { entry = mk_list_entry(head, struct flb_hash_table_entry, _head); if (strcmp(entry->key, key) == 0) { break; } entry = NULL; } } if (!entry) { return -1; } *out_buf = entry->val; *out_size = entry->val_size; return 0; } void *flb_hash_table_get_ptr(struct flb_hash_table *ht, const char *key, int key_len) { int id; struct flb_hash_table_entry *entry; entry = hash_get_entry(ht, key, key_len, &id); if (!entry) { return NULL; } entry->hits++; return entry->val; } int flb_hash_table_del(struct flb_hash_table *ht, const char *key) { int id; int len; uint64_t hash; struct mk_list *head; struct flb_hash_table_entry *entry = NULL; struct flb_hash_table_chain *table; if (!key) { return -1; } len = strlen(key); if (len == 0) { return -1; } hash = cfl_hash_64bits(key, len); id = (hash % ht->size); table = &ht->table[id]; if (table->count == 1) { entry = mk_list_entry_first(&table->chains, struct flb_hash_table_entry, _head); if (strcmp(entry->key, key) != 0) { entry = NULL; } } else { mk_list_foreach(head, &table->chains) { entry = mk_list_entry(head, struct flb_hash_table_entry, _head); if (strcmp(entry->key, key) == 0) { break; } entry = NULL; } } if (!entry) { return -1; } flb_hash_table_entry_free(ht, entry); return 0; }