Files
ShadowMountPlus/src/sm_image_cache.c
T

643 lines
20 KiB
C

#include "sm_platform.h"
#include <pthread.h>
#include "sm_image_cache.h"
#include "sm_limits.h"
struct ImageCache {
uint64_t generation;
char path[MAX_PATH];
char mount_point[MAX_PATH];
int unit_id;
attach_backend_t backend;
attached_device_state_t state;
attached_unit_detach_state_t detach_state;
bool detach_attempt_active;
bool valid;
};
static struct ImageCache g_image_cache[MAX_IMAGE_MOUNTS];
static pthread_mutex_t g_image_cache_mutex = PTHREAD_MUTEX_INITIALIZER;
static uint64_t g_image_cache_generation;
enum {
IMAGE_CACHE_INDEX_CONFLICT = -2,
};
static uint64_t next_cache_generation(void) {
++g_image_cache_generation;
if (g_image_cache_generation == 0)
++g_image_cache_generation;
return g_image_cache_generation;
}
static bool attach_backend_is_valid(attach_backend_t backend) {
return backend == ATTACH_BACKEND_MD || backend == ATTACH_BACKEND_LVD;
}
static bool device_identity_matches(
const attached_unit_detach_state_t *left,
const attached_unit_detach_state_t *right) {
if (!left->node_identity_valid || !right->node_identity_valid)
return false;
return left->node_device == right->node_device &&
left->node_inode == right->node_inode &&
left->node_rdev == right->node_rdev;
}
static bool refresh_attached_unit_identity(struct ImageCache *entry) {
if (!attach_backend_is_valid(entry->backend) || entry->unit_id < 0)
return false;
if (entry->detach_state.node_identity_valid)
return true;
const char *prefix =
entry->backend == ATTACH_BACKEND_MD ? "/dev/md" : "/dev/lvd";
char devname[64];
snprintf(devname, sizeof(devname), "%s%d", prefix, entry->unit_id);
struct stat st;
if (stat(devname, &st) != 0)
return false;
entry->detach_state.node_identity_valid = true;
entry->detach_state.node_device = (uint64_t)st.st_dev;
entry->detach_state.node_inode = (uint64_t)st.st_ino;
entry->detach_state.node_rdev = (uint64_t)st.st_rdev;
return true;
}
static void reset_attached_unit_identity(struct ImageCache *entry) {
memset(&entry->detach_state, 0, sizeof(entry->detach_state));
(void)refresh_attached_unit_identity(entry);
}
static void copy_cache_entry(int index, image_cache_entry_t *entry_out) {
memset(entry_out, 0, sizeof(*entry_out));
entry_out->generation = g_image_cache[index].generation;
(void)strlcpy(entry_out->path, g_image_cache[index].path,
sizeof(entry_out->path));
(void)strlcpy(entry_out->mount_point, g_image_cache[index].mount_point,
sizeof(entry_out->mount_point));
entry_out->unit_id = g_image_cache[index].unit_id;
entry_out->backend = g_image_cache[index].backend;
entry_out->state = g_image_cache[index].state;
entry_out->detach_state = g_image_cache[index].detach_state;
}
static int find_source_path_index(const char *path) {
if (!path)
return -1;
for (int k = 0; k < MAX_IMAGE_MOUNTS; k++) {
if (!g_image_cache[k].valid)
continue;
if (strcmp(g_image_cache[k].path, path) == 0)
return k;
}
return -1;
}
static int find_mount_point_index(const char *mount_point) {
if (!mount_point)
return -1;
for (int k = 0; k < MAX_IMAGE_MOUNTS; k++) {
if (!g_image_cache[k].valid)
continue;
if (strcmp(g_image_cache[k].mount_point, mount_point) == 0)
return k;
}
return -1;
}
static bool cache_entry_is_metadata_only(const struct ImageCache *entry) {
return entry->state == ATTACHED_DEVICE_RELEASED && entry->unit_id < 0 &&
entry->backend == ATTACH_BACKEND_NONE &&
!entry->detach_attempt_active;
}
static bool find_mapping_indices(const char *path, const char *mount_point,
int *path_index_out,
int *mount_index_out) {
*path_index_out = -1;
*mount_index_out = -1;
for (int k = 0; k < MAX_IMAGE_MOUNTS; ++k) {
if (!g_image_cache[k].valid)
continue;
if (strcmp(g_image_cache[k].path, path) == 0) {
if (*path_index_out >= 0) {
errno = EEXIST;
return false;
}
*path_index_out = k;
}
if (strcmp(g_image_cache[k].mount_point, mount_point) == 0) {
if (*mount_index_out >= 0) {
errno = EEXIST;
return false;
}
*mount_index_out = k;
}
}
return true;
}
static int resolve_or_merge_mapping_index(const char *path,
const char *mount_point,
bool *merged_out) {
int path_index = -1;
int mount_index = -1;
*merged_out = false;
if (!find_mapping_indices(path, mount_point, &path_index, &mount_index))
return IMAGE_CACHE_INDEX_CONFLICT;
if (path_index < 0 || mount_index < 0 || path_index == mount_index)
return path_index >= 0 ? path_index : mount_index;
struct ImageCache *path_entry = &g_image_cache[path_index];
struct ImageCache *mount_entry = &g_image_cache[mount_index];
if (!cache_entry_is_metadata_only(path_entry) ||
!cache_entry_is_metadata_only(mount_entry)) {
errno = EBUSY;
return IMAGE_CACHE_INDEX_CONFLICT;
}
// Both IDs are metadata-only. The requested pair supersedes their stale
// half-mappings, so merge them into the source-path owner.
memset(mount_entry, 0, sizeof(*mount_entry));
*merged_out = true;
return path_index;
}
static struct ImageCache *find_attachment_record(
const char *path, uint64_t generation, int unit_id,
attach_backend_t backend) {
int index = find_source_path_index(path);
if (index < 0 || g_image_cache[index].generation != generation ||
g_image_cache[index].unit_id != unit_id ||
g_image_cache[index].backend != backend) {
return NULL;
}
return &g_image_cache[index];
}
static int upsert_image_source_mapping(const char *path,
const char *mount_point) {
if (!path || path[0] == '\0' || !mount_point || mount_point[0] == '\0') {
errno = EINVAL;
return -1;
}
if (strnlen(path, MAX_PATH) >= MAX_PATH ||
strnlen(mount_point, MAX_PATH) >= MAX_PATH) {
errno = ENAMETOOLONG;
return -1;
}
bool merged = false;
int entry_index =
resolve_or_merge_mapping_index(path, mount_point, &merged);
if (entry_index == IMAGE_CACHE_INDEX_CONFLICT)
return -1;
if (entry_index >= 0) {
struct ImageCache *entry = &g_image_cache[entry_index];
bool mapping_changed = merged || strcmp(entry->path, path) != 0 ||
strcmp(entry->mount_point, mount_point) != 0;
if (!cache_entry_is_metadata_only(entry) && mapping_changed) {
errno = EBUSY;
return -1;
}
(void)strlcpy(entry->path, path, sizeof(entry->path));
(void)strlcpy(entry->mount_point, mount_point, sizeof(entry->mount_point));
if (mapping_changed)
entry->generation = next_cache_generation();
return entry_index;
}
for (int k = 0; k < MAX_IMAGE_MOUNTS; k++) {
if (!g_image_cache[k].valid) {
(void)strlcpy(g_image_cache[k].path, path, sizeof(g_image_cache[k].path));
(void)strlcpy(g_image_cache[k].mount_point, mount_point,
sizeof(g_image_cache[k].mount_point));
g_image_cache[k].unit_id = -1;
g_image_cache[k].backend = ATTACH_BACKEND_NONE;
g_image_cache[k].state = ATTACHED_DEVICE_RELEASED;
g_image_cache[k].generation = next_cache_generation();
g_image_cache[k].valid = true;
return k;
}
}
errno = ENOSPC;
return -1;
}
bool cache_image_source_mapping(const char *path, const char *mount_point) {
pthread_mutex_lock(&g_image_cache_mutex);
bool ok = upsert_image_source_mapping(path, mount_point) >= 0;
pthread_mutex_unlock(&g_image_cache_mutex);
return ok;
}
bool begin_image_attachment(const char *path, const char *mount_point,
uint64_t *generation_out) {
if (!path || !mount_point || !generation_out) {
errno = EINVAL;
return false;
}
pthread_mutex_lock(&g_image_cache_mutex);
int entry_index = upsert_image_source_mapping(path, mount_point);
if (entry_index < 0) {
pthread_mutex_unlock(&g_image_cache_mutex);
return false;
}
if (!cache_entry_is_metadata_only(&g_image_cache[entry_index])) {
pthread_mutex_unlock(&g_image_cache_mutex);
errno = EBUSY;
return false;
}
struct ImageCache *entry = &g_image_cache[entry_index];
entry->generation = next_cache_generation();
entry->unit_id = -1;
entry->backend = ATTACH_BACKEND_NONE;
entry->state = ATTACHED_DEVICE_ATTACHING;
memset(&entry->detach_state, 0, sizeof(entry->detach_state));
entry->detach_attempt_active = false;
*generation_out = entry->generation;
pthread_mutex_unlock(&g_image_cache_mutex);
return true;
}
bool cancel_image_attachment(const char *path, uint64_t generation) {
if (!path || generation == 0)
return false;
pthread_mutex_lock(&g_image_cache_mutex);
int index = find_source_path_index(path);
if (index < 0 || g_image_cache[index].generation != generation ||
g_image_cache[index].state != ATTACHED_DEVICE_ATTACHING ||
g_image_cache[index].unit_id >= 0 ||
g_image_cache[index].backend != ATTACH_BACKEND_NONE) {
pthread_mutex_unlock(&g_image_cache_mutex);
return false;
}
memset(&g_image_cache[index], 0, sizeof(g_image_cache[index]));
pthread_mutex_unlock(&g_image_cache_mutex);
return true;
}
bool publish_image_attachment(const char *path, uint64_t generation,
int unit_id, attach_backend_t backend) {
if (!path || generation == 0 || unit_id < 0 ||
!attach_backend_is_valid(backend)) {
errno = EINVAL;
return false;
}
pthread_mutex_lock(&g_image_cache_mutex);
int index = find_source_path_index(path);
if (index < 0 || g_image_cache[index].generation != generation ||
g_image_cache[index].state != ATTACHED_DEVICE_ATTACHING ||
g_image_cache[index].unit_id >= 0 ||
g_image_cache[index].backend != ATTACH_BACKEND_NONE) {
pthread_mutex_unlock(&g_image_cache_mutex);
return false;
}
struct ImageCache *entry = &g_image_cache[index];
entry->unit_id = unit_id;
entry->backend = backend;
(void)refresh_attached_unit_identity(entry);
pthread_mutex_unlock(&g_image_cache_mutex);
return true;
}
bool complete_image_attachment(const char *path, uint64_t generation,
int unit_id, attach_backend_t backend) {
if (!path || generation == 0 || unit_id < 0 ||
!attach_backend_is_valid(backend)) {
errno = EINVAL;
return false;
}
pthread_mutex_lock(&g_image_cache_mutex);
int index = find_source_path_index(path);
if (index < 0 || g_image_cache[index].generation != generation ||
g_image_cache[index].unit_id != unit_id ||
g_image_cache[index].backend != backend ||
g_image_cache[index].state != ATTACHED_DEVICE_ATTACHING) {
pthread_mutex_unlock(&g_image_cache_mutex);
return false;
}
g_image_cache[index].state = ATTACHED_DEVICE_MOUNTED;
(void)refresh_attached_unit_identity(&g_image_cache[index]);
pthread_mutex_unlock(&g_image_cache_mutex);
return true;
}
bool cache_image_mount(const char *path, const char *mount_point, int unit_id,
attach_backend_t backend) {
if (!path || !mount_point || unit_id < 0 ||
!attach_backend_is_valid(backend)) {
errno = EINVAL;
return false;
}
pthread_mutex_lock(&g_image_cache_mutex);
int entry_index = upsert_image_source_mapping(path, mount_point);
if (entry_index < 0) {
pthread_mutex_unlock(&g_image_cache_mutex);
return false;
}
struct ImageCache *entry = &g_image_cache[entry_index];
entry->generation = next_cache_generation();
entry->unit_id = unit_id;
entry->backend = backend;
entry->state = ATTACHED_DEVICE_MOUNTED;
entry->detach_attempt_active = false;
reset_attached_unit_identity(entry);
pthread_mutex_unlock(&g_image_cache_mutex);
return true;
}
bool get_image_cache_entry(int index, image_cache_entry_t *entry_out) {
pthread_mutex_lock(&g_image_cache_mutex);
if (index < 0 || index >= MAX_IMAGE_MOUNTS || !g_image_cache[index].valid) {
pthread_mutex_unlock(&g_image_cache_mutex);
return false;
}
copy_cache_entry(index, entry_out);
pthread_mutex_unlock(&g_image_cache_mutex);
return true;
}
bool find_image_cache_entry(const char *path, image_cache_entry_t *entry_out,
int *index_out) {
if (!path || !entry_out)
return false;
pthread_mutex_lock(&g_image_cache_mutex);
int index = find_source_path_index(path);
if (index < 0) {
pthread_mutex_unlock(&g_image_cache_mutex);
return false;
}
copy_cache_entry(index, entry_out);
if (index_out)
*index_out = index;
pthread_mutex_unlock(&g_image_cache_mutex);
return true;
}
bool mark_image_cache_detach_requested(const char *path, uint64_t generation,
int unit_id,
attach_backend_t backend) {
if (!path || generation == 0 || unit_id < 0 ||
!attach_backend_is_valid(backend)) {
errno = EINVAL;
return false;
}
pthread_mutex_lock(&g_image_cache_mutex);
struct ImageCache *entry = find_attachment_record(
path, generation, unit_id, backend);
if (!entry) {
pthread_mutex_unlock(&g_image_cache_mutex);
errno = ESTALE;
return false;
}
entry->state = ATTACHED_DEVICE_DETACH_REQUESTED;
if (!refresh_attached_unit_identity(entry)) {
pthread_mutex_unlock(&g_image_cache_mutex);
errno = EAGAIN;
return false;
}
pthread_mutex_unlock(&g_image_cache_mutex);
return true;
}
bool claim_image_cache_detach(const char *path, uint64_t generation,
int unit_id, attach_backend_t backend,
image_cache_entry_t *entry_out) {
if (!path || generation == 0 || unit_id < 0 ||
!attach_backend_is_valid(backend) || !entry_out) {
errno = EINVAL;
return false;
}
pthread_mutex_lock(&g_image_cache_mutex);
struct ImageCache *entry =
find_attachment_record(path, generation, unit_id, backend);
if (!entry) {
pthread_mutex_unlock(&g_image_cache_mutex);
errno = ESTALE;
return false;
}
if (entry->detach_attempt_active) {
pthread_mutex_unlock(&g_image_cache_mutex);
errno = EINPROGRESS;
return false;
}
if (!refresh_attached_unit_identity(entry)) {
pthread_mutex_unlock(&g_image_cache_mutex);
errno = EAGAIN;
return false;
}
entry->state = ATTACHED_DEVICE_DETACH_REQUESTED;
entry->detach_attempt_active = true;
copy_cache_entry((int)(entry - g_image_cache), entry_out);
pthread_mutex_unlock(&g_image_cache_mutex);
return true;
}
bool finish_image_cache_detach_attempt(
const char *path, uint64_t generation, int unit_id,
attach_backend_t backend,
const attached_unit_detach_state_t *detach_state) {
if (!path || generation == 0 || unit_id < 0 ||
!attach_backend_is_valid(backend) || !detach_state ||
!detach_state->node_identity_valid) {
errno = EINVAL;
return false;
}
pthread_mutex_lock(&g_image_cache_mutex);
struct ImageCache *entry = find_attachment_record(
path, generation, unit_id, backend);
if (!entry || !entry->detach_attempt_active ||
!device_identity_matches(&entry->detach_state, detach_state)) {
pthread_mutex_unlock(&g_image_cache_mutex);
errno = ESTALE;
return false;
}
entry->detach_state = *detach_state;
entry->state = ATTACHED_DEVICE_DETACH_REQUESTED;
entry->detach_attempt_active = false;
pthread_mutex_unlock(&g_image_cache_mutex);
return true;
}
bool invalidate_image_cache_entry(int index,
const image_cache_entry_t *expected) {
if (!expected) {
errno = EINVAL;
return false;
}
pthread_mutex_lock(&g_image_cache_mutex);
if (index < 0 || index >= MAX_IMAGE_MOUNTS ||
!g_image_cache[index].valid) {
pthread_mutex_unlock(&g_image_cache_mutex);
errno = ESTALE;
return false;
}
const struct ImageCache *entry = &g_image_cache[index];
if (entry->generation != expected->generation ||
strcmp(entry->path, expected->path) != 0 ||
strcmp(entry->mount_point, expected->mount_point) != 0 ||
entry->unit_id != expected->unit_id ||
entry->backend != expected->backend ||
entry->state != expected->state || entry->detach_attempt_active ||
entry->detach_state.requested != expected->detach_state.requested ||
!device_identity_matches(&entry->detach_state,
&expected->detach_state)) {
pthread_mutex_unlock(&g_image_cache_mutex);
errno = ESTALE;
return false;
}
g_image_cache[index].state = ATTACHED_DEVICE_RELEASED;
memset(&g_image_cache[index], 0, sizeof(g_image_cache[index]));
pthread_mutex_unlock(&g_image_cache_mutex);
return true;
}
bool resolve_device_from_mount_cache(const char *mount_point,
attach_backend_t *backend_out,
int *unit_out) {
pthread_mutex_lock(&g_image_cache_mutex);
int entry_index = find_mount_point_index(mount_point);
if (entry_index < 0) {
pthread_mutex_unlock(&g_image_cache_mutex);
return false;
}
const struct ImageCache *entry = &g_image_cache[entry_index];
if (entry->backend == ATTACH_BACKEND_NONE || entry->unit_id < 0) {
pthread_mutex_unlock(&g_image_cache_mutex);
return false;
}
*backend_out = entry->backend;
*unit_out = entry->unit_id;
pthread_mutex_unlock(&g_image_cache_mutex);
return true;
}
bool resolve_image_source_from_mount_cache(const char *mount_point,
char *path_out,
size_t path_out_size) {
pthread_mutex_lock(&g_image_cache_mutex);
int entry_index = find_mount_point_index(mount_point);
if (entry_index < 0) {
pthread_mutex_unlock(&g_image_cache_mutex);
return false;
}
const struct ImageCache *entry = &g_image_cache[entry_index];
path_out[0] = '\0';
(void)strlcpy(path_out, entry->path, path_out_size);
pthread_mutex_unlock(&g_image_cache_mutex);
return true;
}
static int find_owning_cache_index(const char *path) {
int best_index = -1;
size_t best_length = 0;
for (int i = 0; i < MAX_IMAGE_MOUNTS; ++i) {
if (!g_image_cache[i].valid)
continue;
size_t length = strlen(g_image_cache[i].mount_point);
if (length <= best_length || strncmp(path, g_image_cache[i].mount_point,
length) != 0) {
continue;
}
if (path[length] != '\0' && path[length] != '/')
continue;
best_index = i;
best_length = length;
}
return best_index;
}
static size_t find_owning_chain_indices(const char *path,
int indices[MAX_IMAGE_CHAIN_DEPTH]) {
char current[MAX_PATH];
(void)strlcpy(current, path, sizeof(current));
size_t count = 0;
while (count < MAX_IMAGE_CHAIN_DEPTH) {
int index = find_owning_cache_index(current);
if (index < 0)
break;
indices[count++] = index;
(void)strlcpy(current, g_image_cache[index].path, sizeof(current));
}
return count;
}
bool resolve_owning_image_source_from_mount_cache(const char *path,
char *path_out,
size_t path_out_size) {
if (!path || !path_out || path_out_size == 0)
return false;
pthread_mutex_lock(&g_image_cache_mutex);
int index = find_owning_cache_index(path);
if (index >= 0)
(void)strlcpy(path_out, g_image_cache[index].path, path_out_size);
pthread_mutex_unlock(&g_image_cache_mutex);
return index >= 0;
}
size_t resolve_image_source_chain_from_mount_cache(
const char *path,
char chain[MAX_IMAGE_CHAIN_DEPTH][MAX_PATH]) {
if (!path || !chain)
return 0;
int indices[MAX_IMAGE_CHAIN_DEPTH];
pthread_mutex_lock(&g_image_cache_mutex);
size_t count = find_owning_chain_indices(path, indices);
for (size_t i = 0; i < count; ++i)
(void)strlcpy(chain[i], g_image_cache[indices[count - i - 1u]].path,
MAX_PATH);
pthread_mutex_unlock(&g_image_cache_mutex);
return count;
}
bool resolve_outermost_image_source_from_mount_cache(
const char *path, char *path_out, size_t path_out_size) {
if (!path || !path_out || path_out_size == 0)
return false;
int indices[MAX_IMAGE_CHAIN_DEPTH];
pthread_mutex_lock(&g_image_cache_mutex);
size_t count = find_owning_chain_indices(path, indices);
if (count > 0)
(void)strlcpy(path_out, g_image_cache[indices[count - 1u]].path,
path_out_size);
pthread_mutex_unlock(&g_image_cache_mutex);
return count > 0;
}