?
/**
@file lru_hashtable.c
@brief LRU hashtable implementation
@details Copyright (c) 2026 Acronis International GmbH
@author Bruce Wang (bruce.wang@acronis.com)
@since $Id: $
*/
#include "lru_hashtable.h"
#include "hash_fast.h"
#include "path_tools.h"
#include "memory.h"
#include <linux/jiffies.h>
#include <linux/list.h>
#define LRU_TABLE_SIZE (1 << (LRU_TABLE_SIZE_BITS - 1)) // 16384
#define LRU_TABLE_CLEAN_SIZE 256
#ifndef list_first_entry_or_null
#define list_first_entry_or_null(ptr, type, member) (list_empty(ptr) ? NULL : list_first_entry(ptr, type, member))
#endif
#ifdef KERNEL_MOCK
#include "mock/mock.h"
#endif
// 30 seconds
#define TTL msecs_to_jiffies(30000)
typedef struct
{
atomic_t refcount;
struct hlist_node hash_node;
struct list_head lru_list_node;
bool lru_list_inserted;
unsigned long lru_deadline;
struct rcu_head rcu;
lru_hashtable_key_t key;
} lru_hashtable_node_t;
// MARK: lru hashtable node
static lru_hashtable_node_t *node_alloc(const lru_hashtable_key_t *key, lru_hashtable_key_type_t key_type)
{
lru_hashtable_node_t *node = mem_alloc(sizeof(lru_hashtable_node_t));
if (!node)
return NULL;
atomic_set(&node->refcount, 1);
node->lru_list_inserted = false;
INIT_HLIST_NODE(&node->hash_node);
INIT_LIST_HEAD(&node->lru_list_node);
switch (key_type)
{
case LRU_HASHTABLE_KEY_TYPE_PROCFS:
node->key.procfs_key = key->procfs_key;
break;
case LRU_HASHTABLE_KEY_TYPE_PTRACE:
node->key.ptrace_key = key->ptrace_key;
break;
default:
mem_free(node);
return NULL;
}
return node;
}
static void node_rcu_free(struct rcu_head *rcu)
{
lru_hashtable_node_t *node = container_of(rcu, lru_hashtable_node_t, rcu);
mem_free(node);
}
static void node_put(lru_hashtable_node_t *node)
{
if (atomic_dec_and_test(&node->refcount))
call_rcu(&node->rcu, node_rcu_free);
}
static bool key_equal(const lru_hashtable_key_t *k1, const lru_hashtable_key_t *k2, lru_hashtable_key_type_t key_type)
{
// TODO: merge access_types and access_modes
switch (key_type)
{
case LRU_HASHTABLE_KEY_TYPE_PROCFS:
return k1->procfs_key.access_type == k2->procfs_key.access_type &&
k1->procfs_key.caller_pid_version == k2->procfs_key.caller_pid_version &&
k1->procfs_key.target_pid_version == k2->procfs_key.target_pid_version;
case LRU_HASHTABLE_KEY_TYPE_PTRACE:
return k1->ptrace_key.access_mode == k2->ptrace_key.access_mode &&
k1->ptrace_key.caller_pid_version == k2->ptrace_key.caller_pid_version &&
k1->ptrace_key.target_pid_version == k2->ptrace_key.target_pid_version;
default:
return false;
}
}
static int key_hash(const lru_hashtable_key_t *key, lru_hashtable_key_type_t key_type)
{
switch (key_type)
{
case LRU_HASHTABLE_KEY_TYPE_PROCFS:
return murmur_hash(&key->procfs_key.caller_pid_version, sizeof(key->procfs_key)) >> (64 - LRU_TABLE_SIZE_BITS);
case LRU_HASHTABLE_KEY_TYPE_PTRACE:
return murmur_hash(&key->ptrace_key.caller_pid_version, sizeof(key->ptrace_key)) >> (64 - LRU_TABLE_SIZE_BITS);
default:
return -1;
}
}
static lru_hashtable_node_t *find_ref_rcu(struct lru_hashtable_manager *manager, int hash, const lru_hashtable_key_t *key, lru_hashtable_key_type_t key_type)
{
lru_hashtable_node_t *search_node;
hlist_for_each_entry_rcu(search_node, &manager->seen_entries_hashtable[hash], hash_node)
{
if (!key_equal(&search_node->key, key, key_type))
continue;
if (atomic_inc_not_zero(&search_node->refcount))
return search_node;
else
return NULL;
}
return NULL;
}
static lru_hashtable_node_t *find(struct lru_hashtable_manager *manager, int hash, const lru_hashtable_key_t *key, lru_hashtable_key_type_t key_type)
{
lru_hashtable_node_t *search_node;
hlist_for_each_entry(search_node, &manager->seen_entries_hashtable[hash], hash_node)
{
if (key_equal(&search_node->key, key, key_type))
return search_node;
}
return NULL;
}
static void erase_impl(struct lru_hashtable_manager *manager, lru_hashtable_node_t *node)
{
hash_del_rcu(&node->hash_node);
list_del(&node->lru_list_node);
node->lru_list_inserted = false;
manager->entries_count--;
node_put(node);
}
static void refresh_impl(struct lru_hashtable_manager *manager, lru_hashtable_node_t *node)
{
if (node->lru_list_inserted)
{
node->lru_deadline = jiffies + TTL;
list_del(&node->lru_list_node);
list_add_tail(&node->lru_list_node, &manager->seen_entries_lru_list);
}
}
// MARK: lru hashtable manager
int lru_hashtable_manager_init(struct lru_hashtable_manager **out_manager)
{
*out_manager = vmem_alloc(sizeof(lru_hashtable_manager_t));
if (!*out_manager)
return -ENOMEM;
mutex_init(&(*out_manager)->table_writer_lock);
(*out_manager)->active = false;
(*out_manager)->entries_count = 0;
hash_init((*out_manager)->seen_entries_hashtable);
INIT_LIST_HEAD(&(*out_manager)->seen_entries_lru_list);
return 0;
}
void lru_hashtable_manager_deinit(struct lru_hashtable_manager *manager)
{
if (!manager)
return;
vmem_free(manager);
}
void lru_hashtable_manager_activate(struct lru_hashtable_manager *manager)
{
mutex_lock(&manager->table_writer_lock);
manager->active = true;
mutex_unlock(&manager->table_writer_lock);
}
void lru_hashtable_manager_deactivate(struct lru_hashtable_manager *manager)
{
mutex_lock(&manager->table_writer_lock);
if (manager->active)
{
while (1)
{
lru_hashtable_node_t *node = list_first_entry_or_null(&manager->seen_entries_lru_list, lru_hashtable_node_t, lru_list_node);
if (!node)
break;
erase_impl(manager, node);
}
manager->active = false;
}
mutex_unlock(&manager->table_writer_lock);
}
static void sweep_impl(struct lru_hashtable_manager *manager)
{
while (1)
{
lru_hashtable_node_t *node = list_first_entry_or_null(&manager->seen_entries_lru_list, lru_hashtable_node_t, lru_list_node);
if (!node)
break;
if (time_after(jiffies, node->lru_deadline))
erase_impl(manager, node);
else
break;
}
while (manager->entries_count >= LRU_TABLE_SIZE - LRU_TABLE_CLEAN_SIZE)
{
lru_hashtable_node_t *node = list_first_entry_or_null(&manager->seen_entries_lru_list, lru_hashtable_node_t, lru_list_node);
if (!node)
break;
erase_impl(manager, node);
}
}
bool lru_hashtable_manager_key_exist(struct lru_hashtable_manager *manager, const lru_hashtable_key_t *key, lru_hashtable_key_type_t key_type)
{
int hash;
lru_hashtable_node_t *node;
if (!manager || !manager->active)
{
return false;
}
hash = key_hash(key, key_type);
if(hash == -1)
{
return false;
}
rcu_read_lock();
node = find_ref_rcu(manager, hash, key, key_type);
rcu_read_unlock();
if (node)
{
mutex_lock(&manager->table_writer_lock);
refresh_impl(manager, node);
mutex_unlock(&manager->table_writer_lock);
node_put(node);
return true;
}
mutex_lock(&manager->table_writer_lock);
if (!READ_ONCE(manager->active))
{
mutex_unlock(&manager->table_writer_lock);
return false;
}
node = find(manager, hash, key, key_type);
if (node)
{
refresh_impl(manager, node);
mutex_unlock(&manager->table_writer_lock);
return true;
}
sweep_impl(manager);
node = node_alloc(key, key_type);
if (!node)
{
mutex_unlock(&manager->table_writer_lock);
return false;
}
hlist_add_head_rcu(&node->hash_node, &manager->seen_entries_hashtable[hash]);
node->lru_deadline = jiffies + TTL;
list_add_tail(&node->lru_list_node, &manager->seen_entries_lru_list);
node->lru_list_inserted = true;
manager->entries_count++;
mutex_unlock(&manager->table_writer_lock);
return false;
}