/* retoor */ #include #include #include #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); }