369 lines
8.8 KiB
C
369 lines
8.8 KiB
C
|
|
/* retoor <retoor@molodetz.nl> */
|
||
|
|
|
||
|
|
#include <stdlib.h>
|
||
|
|
#include <string.h>
|
||
|
|
#include <time.h>
|
||
|
|
#include "cache.h"
|
||
|
|
|
||
|
|
static int64_t current_time_ms(void) {
|
||
|
|
struct timespec ts;
|
||
|
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||
|
|
return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int entry_is_valid(cache_entry_t *entry) {
|
||
|
|
if (!entry) return 0;
|
||
|
|
int64_t now = current_time_ms();
|
||
|
|
int64_t age = now - (int64_t)entry->created_at;
|
||
|
|
return age < entry->ttl_ms;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void entry_free(cache_entry_t *entry) {
|
||
|
|
if (!entry) return;
|
||
|
|
free(entry->key);
|
||
|
|
free(entry->value);
|
||
|
|
free(entry);
|
||
|
|
}
|
||
|
|
|
||
|
|
cache_t *cache_init(int max_entries, int default_ttl_ms) {
|
||
|
|
cache_t *cache = calloc(1, sizeof(cache_t));
|
||
|
|
if (!cache) return NULL;
|
||
|
|
|
||
|
|
cache->max_entries = max_entries;
|
||
|
|
cache->default_ttl_ms = default_ttl_ms;
|
||
|
|
cache->current_entries = 0;
|
||
|
|
cache->head = NULL;
|
||
|
|
|
||
|
|
if (pthread_mutex_init(&cache->lock, NULL) != 0) {
|
||
|
|
free(cache);
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
return cache;
|
||
|
|
}
|
||
|
|
|
||
|
|
void cache_destroy(cache_t *cache) {
|
||
|
|
if (!cache) return;
|
||
|
|
|
||
|
|
pthread_mutex_lock(&cache->lock);
|
||
|
|
|
||
|
|
cache_entry_t *entry = cache->head;
|
||
|
|
while (entry) {
|
||
|
|
cache_entry_t *next = entry->next;
|
||
|
|
entry_free(entry);
|
||
|
|
entry = next;
|
||
|
|
}
|
||
|
|
|
||
|
|
pthread_mutex_unlock(&cache->lock);
|
||
|
|
pthread_mutex_destroy(&cache->lock);
|
||
|
|
free(cache);
|
||
|
|
}
|
||
|
|
|
||
|
|
static cache_entry_t *find_entry(cache_t *cache, const char *key,
|
||
|
|
cache_entry_t **prev) {
|
||
|
|
if (prev) *prev = NULL;
|
||
|
|
|
||
|
|
cache_entry_t *entry = cache->head;
|
||
|
|
cache_entry_t *previous = NULL;
|
||
|
|
|
||
|
|
while (entry) {
|
||
|
|
if (strcmp(entry->key, key) == 0) {
|
||
|
|
if (prev) *prev = previous;
|
||
|
|
return entry;
|
||
|
|
}
|
||
|
|
previous = entry;
|
||
|
|
entry = entry->next;
|
||
|
|
}
|
||
|
|
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
static void remove_entry(cache_t *cache, cache_entry_t *entry,
|
||
|
|
cache_entry_t *prev) {
|
||
|
|
if (prev) {
|
||
|
|
prev->next = entry->next;
|
||
|
|
} else {
|
||
|
|
cache->head = entry->next;
|
||
|
|
}
|
||
|
|
cache->current_entries--;
|
||
|
|
entry_free(entry);
|
||
|
|
}
|
||
|
|
|
||
|
|
static void evict_oldest(cache_t *cache) {
|
||
|
|
if (!cache->head) return;
|
||
|
|
|
||
|
|
cache_entry_t *oldest = cache->head;
|
||
|
|
cache_entry_t *oldest_prev = NULL;
|
||
|
|
cache_entry_t *entry = cache->head->next;
|
||
|
|
cache_entry_t *prev = cache->head;
|
||
|
|
|
||
|
|
while (entry) {
|
||
|
|
if (entry->created_at < oldest->created_at) {
|
||
|
|
oldest = entry;
|
||
|
|
oldest_prev = prev;
|
||
|
|
}
|
||
|
|
prev = entry;
|
||
|
|
entry = entry->next;
|
||
|
|
}
|
||
|
|
|
||
|
|
remove_entry(cache, oldest, oldest_prev);
|
||
|
|
}
|
||
|
|
|
||
|
|
int cache_get_stat(cache_t *cache, const char *key, struct stat *st) {
|
||
|
|
if (!cache || !key || !st) return -1;
|
||
|
|
|
||
|
|
pthread_mutex_lock(&cache->lock);
|
||
|
|
|
||
|
|
cache_entry_t *prev;
|
||
|
|
cache_entry_t *entry = find_entry(cache, key, &prev);
|
||
|
|
|
||
|
|
if (!entry) {
|
||
|
|
pthread_mutex_unlock(&cache->lock);
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!entry_is_valid(entry)) {
|
||
|
|
remove_entry(cache, entry, prev);
|
||
|
|
pthread_mutex_unlock(&cache->lock);
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
|
||
|
|
memcpy(st, entry->value, sizeof(struct stat));
|
||
|
|
|
||
|
|
pthread_mutex_unlock(&cache->lock);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
int cache_put_stat(cache_t *cache, const char *key, const struct stat *st) {
|
||
|
|
if (!cache || !key || !st) return -1;
|
||
|
|
|
||
|
|
pthread_mutex_lock(&cache->lock);
|
||
|
|
|
||
|
|
cache_entry_t *prev;
|
||
|
|
cache_entry_t *existing = find_entry(cache, key, &prev);
|
||
|
|
|
||
|
|
if (existing) {
|
||
|
|
memcpy(existing->value, st, sizeof(struct stat));
|
||
|
|
existing->created_at = (time_t)current_time_ms();
|
||
|
|
pthread_mutex_unlock(&cache->lock);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (cache->current_entries >= cache->max_entries) {
|
||
|
|
evict_oldest(cache);
|
||
|
|
}
|
||
|
|
|
||
|
|
cache_entry_t *entry = calloc(1, sizeof(cache_entry_t));
|
||
|
|
if (!entry) {
|
||
|
|
pthread_mutex_unlock(&cache->lock);
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
|
||
|
|
entry->key = strdup(key);
|
||
|
|
entry->value = malloc(sizeof(struct stat));
|
||
|
|
if (!entry->key || !entry->value) {
|
||
|
|
free(entry->key);
|
||
|
|
free(entry->value);
|
||
|
|
free(entry);
|
||
|
|
pthread_mutex_unlock(&cache->lock);
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
|
||
|
|
memcpy(entry->value, st, sizeof(struct stat));
|
||
|
|
entry->value_size = sizeof(struct stat);
|
||
|
|
entry->created_at = (time_t)current_time_ms();
|
||
|
|
entry->ttl_ms = cache->default_ttl_ms;
|
||
|
|
|
||
|
|
entry->next = cache->head;
|
||
|
|
cache->head = entry;
|
||
|
|
cache->current_entries++;
|
||
|
|
|
||
|
|
pthread_mutex_unlock(&cache->lock);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static dir_listing_t *copy_dir_listing(const dir_listing_t *src) {
|
||
|
|
if (!src) return NULL;
|
||
|
|
|
||
|
|
dir_listing_t *dst = calloc(1, sizeof(dir_listing_t));
|
||
|
|
if (!dst) return NULL;
|
||
|
|
|
||
|
|
if (src->count == 0) {
|
||
|
|
dst->entries = NULL;
|
||
|
|
dst->count = 0;
|
||
|
|
return dst;
|
||
|
|
}
|
||
|
|
|
||
|
|
dst->entries = calloc((size_t)src->count, sizeof(dir_entry_t));
|
||
|
|
if (!dst->entries) {
|
||
|
|
free(dst);
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
dst->count = src->count;
|
||
|
|
|
||
|
|
for (int i = 0; i < src->count; i++) {
|
||
|
|
dst->entries[i].name = strdup(src->entries[i].name);
|
||
|
|
dst->entries[i].is_directory = src->entries[i].is_directory;
|
||
|
|
dst->entries[i].size = src->entries[i].size;
|
||
|
|
dst->entries[i].mtime = src->entries[i].mtime;
|
||
|
|
|
||
|
|
if (!dst->entries[i].name) {
|
||
|
|
for (int j = 0; j < i; j++) {
|
||
|
|
free(dst->entries[j].name);
|
||
|
|
}
|
||
|
|
free(dst->entries);
|
||
|
|
free(dst);
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return dst;
|
||
|
|
}
|
||
|
|
|
||
|
|
void dir_listing_free(dir_listing_t *listing) {
|
||
|
|
if (!listing) return;
|
||
|
|
|
||
|
|
for (int i = 0; i < listing->count; i++) {
|
||
|
|
free(listing->entries[i].name);
|
||
|
|
}
|
||
|
|
free(listing->entries);
|
||
|
|
free(listing);
|
||
|
|
}
|
||
|
|
|
||
|
|
int cache_get_dir(cache_t *cache, const char *key, dir_listing_t **listing) {
|
||
|
|
if (!cache || !key || !listing) return -1;
|
||
|
|
|
||
|
|
pthread_mutex_lock(&cache->lock);
|
||
|
|
|
||
|
|
cache_entry_t *prev;
|
||
|
|
cache_entry_t *entry = find_entry(cache, key, &prev);
|
||
|
|
|
||
|
|
if (!entry) {
|
||
|
|
pthread_mutex_unlock(&cache->lock);
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!entry_is_valid(entry)) {
|
||
|
|
remove_entry(cache, entry, prev);
|
||
|
|
pthread_mutex_unlock(&cache->lock);
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
|
||
|
|
*listing = copy_dir_listing((dir_listing_t *)entry->value);
|
||
|
|
|
||
|
|
pthread_mutex_unlock(&cache->lock);
|
||
|
|
return *listing ? 0 : -1;
|
||
|
|
}
|
||
|
|
|
||
|
|
int cache_put_dir(cache_t *cache, const char *key, const dir_listing_t *listing) {
|
||
|
|
if (!cache || !key || !listing) return -1;
|
||
|
|
|
||
|
|
pthread_mutex_lock(&cache->lock);
|
||
|
|
|
||
|
|
cache_entry_t *prev;
|
||
|
|
cache_entry_t *existing = find_entry(cache, key, &prev);
|
||
|
|
|
||
|
|
if (existing) {
|
||
|
|
dir_listing_free((dir_listing_t *)existing->value);
|
||
|
|
existing->value = copy_dir_listing(listing);
|
||
|
|
existing->created_at = (time_t)current_time_ms();
|
||
|
|
pthread_mutex_unlock(&cache->lock);
|
||
|
|
return existing->value ? 0 : -1;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (cache->current_entries >= cache->max_entries) {
|
||
|
|
evict_oldest(cache);
|
||
|
|
}
|
||
|
|
|
||
|
|
cache_entry_t *entry = calloc(1, sizeof(cache_entry_t));
|
||
|
|
if (!entry) {
|
||
|
|
pthread_mutex_unlock(&cache->lock);
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
|
||
|
|
entry->key = strdup(key);
|
||
|
|
entry->value = copy_dir_listing(listing);
|
||
|
|
if (!entry->key || !entry->value) {
|
||
|
|
free(entry->key);
|
||
|
|
dir_listing_free((dir_listing_t *)entry->value);
|
||
|
|
free(entry);
|
||
|
|
pthread_mutex_unlock(&cache->lock);
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
|
||
|
|
entry->created_at = (time_t)current_time_ms();
|
||
|
|
entry->ttl_ms = cache->default_ttl_ms;
|
||
|
|
|
||
|
|
entry->next = cache->head;
|
||
|
|
cache->head = entry;
|
||
|
|
cache->current_entries++;
|
||
|
|
|
||
|
|
pthread_mutex_unlock(&cache->lock);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
void cache_invalidate(cache_t *cache, const char *key) {
|
||
|
|
if (!cache || !key) return;
|
||
|
|
|
||
|
|
pthread_mutex_lock(&cache->lock);
|
||
|
|
|
||
|
|
cache_entry_t *prev;
|
||
|
|
cache_entry_t *entry = find_entry(cache, key, &prev);
|
||
|
|
|
||
|
|
if (entry) {
|
||
|
|
remove_entry(cache, entry, prev);
|
||
|
|
}
|
||
|
|
|
||
|
|
pthread_mutex_unlock(&cache->lock);
|
||
|
|
}
|
||
|
|
|
||
|
|
void cache_invalidate_prefix(cache_t *cache, const char *prefix) {
|
||
|
|
if (!cache || !prefix) return;
|
||
|
|
|
||
|
|
size_t prefix_len = strlen(prefix);
|
||
|
|
|
||
|
|
pthread_mutex_lock(&cache->lock);
|
||
|
|
|
||
|
|
cache_entry_t *entry = cache->head;
|
||
|
|
cache_entry_t *prev = NULL;
|
||
|
|
|
||
|
|
while (entry) {
|
||
|
|
cache_entry_t *next = entry->next;
|
||
|
|
|
||
|
|
if (strncmp(entry->key, prefix, prefix_len) == 0) {
|
||
|
|
if (prev) {
|
||
|
|
prev->next = next;
|
||
|
|
} else {
|
||
|
|
cache->head = next;
|
||
|
|
}
|
||
|
|
cache->current_entries--;
|
||
|
|
entry_free(entry);
|
||
|
|
} else {
|
||
|
|
prev = entry;
|
||
|
|
}
|
||
|
|
|
||
|
|
entry = next;
|
||
|
|
}
|
||
|
|
|
||
|
|
pthread_mutex_unlock(&cache->lock);
|
||
|
|
}
|
||
|
|
|
||
|
|
void cache_clear(cache_t *cache) {
|
||
|
|
if (!cache) return;
|
||
|
|
|
||
|
|
pthread_mutex_lock(&cache->lock);
|
||
|
|
|
||
|
|
cache_entry_t *entry = cache->head;
|
||
|
|
while (entry) {
|
||
|
|
cache_entry_t *next = entry->next;
|
||
|
|
entry_free(entry);
|
||
|
|
entry = next;
|
||
|
|
}
|
||
|
|
|
||
|
|
cache->head = NULL;
|
||
|
|
cache->current_entries = 0;
|
||
|
|
|
||
|
|
pthread_mutex_unlock(&cache->lock);
|
||
|
|
}
|