feat(refresh): add in-memory path refresh support
All checks were successful
Build / build-script (push) Successful in 17s
Build / cmake-build (push) Successful in 31s
Build / unit-tests (push) Successful in 26s

This commit is contained in:
2026-06-17 11:40:33 -05:00
parent 17e468d649
commit 23428aa378
6 changed files with 358 additions and 343 deletions

View File

@@ -462,6 +462,305 @@ static int test_binary_v2_file_lazy_roundtrip(test_context_t *context)
return result;
}
static int test_binary_v2_refresh_from_path(test_context_t *context)
{
const char *path = "demo_refresh_v2.ikvb";
ikv_node_t *original = ikv_create_object("root");
ikv_node_t *updated = ikv_create_object("root");
ikv_node_t *loaded = NULL;
int result = 0;
cleanup_file(path);
if (!original || !updated)
{
ikv_free(original);
ikv_free(updated);
return fail(context, "failed to create refresh roots");
}
ikv_object_set_int(original, "count", 1);
ikv_object_set_string(original, "name", "before");
if (!ikvb_write_file(path, original))
{
result = fail(context, "failed to write original refresh file");
}
else
{
loaded = ikvb_parse_file(path);
if (!loaded)
result = fail(context, "failed to parse original refresh file");
}
if (result == 0)
{
ikv_node_t *items = ikv_object_add_array(updated, "items", IKV_STRING);
if (!items)
{
result = fail(context, "failed to build updated refresh array");
}
else
{
ikv_object_set_int(updated, "count", 2);
ikv_object_set_string(updated, "name", "after");
ikv_array_add_string(items, "x");
ikv_array_add_string(items, "y");
if (!ikvb_write_file(path, updated))
result = fail(context, "failed to write updated refresh file");
}
}
if (result == 0 && !ikv_refresh_from_path(loaded, path))
result = fail(context, "refresh_from_path failed");
if (result == 0 &&
(result = expect_true(context, ikv_as_int(ikv_object_get(loaded, "count")) == 2, "refresh count mismatch")) == 0 &&
(result = expect_string(context, ikv_as_string(ikv_object_get(loaded, "name")), "after", "refresh string mismatch")) == 0)
{
ikv_node_t *items = ikv_object_get(loaded, "items");
if (!items)
result = fail(context, "refresh lazy array lookup failed");
else if ((result = expect_true(context, ikv_array_size(items) == 2u, "refresh array size mismatch")) == 0)
result = expect_string(context, ikv_as_string(ikv_array_get(items, 1u)), "y", "refresh array value mismatch");
}
cleanup_file(path);
ikv_free(loaded);
ikv_free(original);
ikv_free(updated);
return result;
}
static int test_refresh_from_path_null_guards(test_context_t *context)
{
ikv_node_t *root = ikv_create_object("root");
int result = 0;
if (!root)
return fail(context, "failed to create null-guard root");
if ((result = expect_true(context, !ikv_refresh_from_path(NULL, "missing.ikv"), "refresh should reject null root")) == 0)
result = expect_true(context, !ikv_refresh_from_path(root, NULL), "refresh should reject null path");
ikv_free(root);
return result;
}
static int test_text_refresh_from_path(test_context_t *context)
{
const char *path = "demo_refresh_text.ikv";
ikv_node_t *original = ikv_create_object("before_root");
ikv_node_t *updated = ikv_create_object("after_root");
ikv_node_t *loaded = NULL;
int result = 0;
cleanup_file(path);
if (!original || !updated)
{
ikv_free(original);
ikv_free(updated);
return fail(context, "failed to create text refresh roots");
}
ikv_object_set_int(original, "count", 1);
if (!ikv_write_file_version(path, original, IKV_VERSION_1))
result = fail(context, "failed to write original text refresh file");
else
loaded = ikv_parse_file(path);
if (result == 0 && !loaded)
result = fail(context, "failed to parse original text refresh file");
if (result == 0)
{
ikv_object_set_int(updated, "count", 4);
ikv_object_set_string(updated, "mode", "text");
if (!ikv_write_file_version(path, updated, IKV_VERSION_2))
result = fail(context, "failed to write updated text refresh file");
}
if (result == 0 && !ikv_refresh_from_path(loaded, path))
result = fail(context, "text refresh_from_path failed");
if (result == 0 &&
(result = expect_string(context, ikv_node_key(loaded), "after_root", "text refresh root key mismatch")) == 0 &&
(result = expect_true(context, ikv_as_int(ikv_object_get(loaded, "count")) == 4, "text refresh count mismatch")) == 0)
{
result = expect_string(context, ikv_as_string(ikv_object_get(loaded, "mode")), "text", "text refresh mode mismatch");
}
cleanup_file(path);
ikv_free(loaded);
ikv_free(original);
ikv_free(updated);
return result;
}
static int test_binary_v1_refresh_from_path(test_context_t *context)
{
const char *path = "demo_refresh_v1.ikvb";
ikv_node_t *original = ikv_create_object("root");
ikv_node_t *updated = ikv_create_object("root");
ikv_node_t *loaded = NULL;
int result = 0;
cleanup_file(path);
if (!original || !updated)
{
ikv_free(original);
ikv_free(updated);
return fail(context, "failed to create v1 refresh roots");
}
ikv_object_set_bool(original, "legacy", true);
if (!ikvb_write_file_version(path, original, IKV_VERSION_1))
result = fail(context, "failed to write original v1 refresh file");
else
loaded = ikvb_parse_file(path);
if (result == 0 && !loaded)
result = fail(context, "failed to parse original v1 refresh file");
if (result == 0)
{
ikv_object_set_bool(updated, "legacy", false);
ikv_object_set_int(updated, "revision", 2);
if (!ikvb_write_file_version(path, updated, IKV_VERSION_1))
result = fail(context, "failed to write updated v1 refresh file");
}
if (result == 0 && !ikv_refresh_from_path(loaded, path))
result = fail(context, "v1 refresh_from_path failed");
if (result == 0 &&
(result = expect_true(context, !ikv_as_bool(ikv_object_get(loaded, "legacy")), "v1 refresh bool mismatch")) == 0)
{
result = expect_true(context, ikv_as_int(ikv_object_get(loaded, "revision")) == 2, "v1 refresh int mismatch");
}
cleanup_file(path);
ikv_free(loaded);
ikv_free(original);
ikv_free(updated);
return result;
}
static int test_refresh_from_path_failure_preserves_state(test_context_t *context)
{
const char *path = "demo_refresh_preserve.ikvb";
ikv_node_t *root = ikv_create_object("root");
ikv_node_t *loaded = NULL;
FILE *file = NULL;
int result = 0;
cleanup_file(path);
if (!root)
return fail(context, "failed to create preserve root");
ikv_object_set_int(root, "count", 9);
ikv_object_set_string(root, "name", "stable");
if (!ikvb_write_file(path, root))
result = fail(context, "failed to write preserve file");
else
loaded = ikvb_parse_file(path);
if (result == 0 && !loaded)
result = fail(context, "failed to parse preserve file");
file = fopen(path, "wb");
if (result == 0 && !file)
result = fail(context, "failed to open preserve file for corruption");
if (result == 0)
{
static const uint8_t bad_bytes[] = { 'n', 'o', 'p', 'e' };
if (fwrite(bad_bytes, 1u, sizeof(bad_bytes), file) != sizeof(bad_bytes))
result = fail(context, "failed to corrupt preserve file");
fclose(file);
file = NULL;
}
if (result == 0 &&
(result = expect_true(context, !ikv_refresh_from_path(loaded, path), "refresh should fail for invalid file")) == 0 &&
(result = expect_true(context, ikv_as_int(ikv_object_get(loaded, "count")) == 9, "failed refresh should preserve count")) == 0)
{
result = expect_string(context, ikv_as_string(ikv_object_get(loaded, "name")), "stable", "failed refresh should preserve name");
}
if (file)
fclose(file);
cleanup_file(path);
ikv_free(loaded);
ikv_free(root);
return result;
}
static int test_binary_v2_refresh_replaces_keys(test_context_t *context)
{
const char *path = "demo_refresh_replace.ikvb";
ikv_node_t *original = ikv_create_object("root");
ikv_node_t *updated = ikv_create_object("root");
ikv_node_t *loaded = NULL;
int result = 0;
cleanup_file(path);
if (!original || !updated)
{
ikv_free(original);
ikv_free(updated);
return fail(context, "failed to create replace-key roots");
}
ikv_object_set_string(original, "old_only", "legacy");
if (!ikvb_write_file(path, original))
result = fail(context, "failed to write original replace-key file");
else
loaded = ikvb_parse_file(path);
if (result == 0 && !loaded)
result = fail(context, "failed to parse original replace-key file");
if (result == 0)
{
ikv_node_t *items = ikv_object_add_array(updated, "items", IKV_STRING);
if (!items)
{
result = fail(context, "failed to create replace-key array");
}
else
{
ikv_object_set_string(updated, "new_only", "fresh");
ikv_array_add_string(items, "n1");
ikv_array_add_string(items, "n2");
if (!ikvb_write_file(path, updated))
result = fail(context, "failed to write updated replace-key file");
}
}
if (result == 0)
{
if (!ikv_object_get(loaded, "old_only"))
result = fail(context, "failed to preload old key before refresh");
else if (!ikv_refresh_from_path(loaded, path))
result = fail(context, "replace-key refresh_from_path failed");
}
if (result == 0 &&
(result = expect_true(context, ikv_object_get(loaded, "old_only") == NULL, "old key should be removed after refresh")) == 0 &&
(result = expect_string(context, ikv_as_string(ikv_object_get(loaded, "new_only")), "fresh", "new key mismatch after refresh")) == 0)
{
ikv_node_t *items = ikv_object_get(loaded, "items");
if (!items)
result = fail(context, "items key missing after refresh");
else if ((result = expect_true(context, ikv_array_size(items) == 2u, "items size mismatch after refresh")) == 0)
result = expect_string(context, ikv_as_string(ikv_array_get(items, 0u)), "n1", "items value mismatch after refresh");
}
cleanup_file(path);
ikv_free(loaded);
ikv_free(original);
ikv_free(updated);
return result;
}
static int test_detection_apis(test_context_t *context)
{
const char *text_path = "demo_test_detect_text.ikv";
@@ -1139,7 +1438,7 @@ static int test_hook_lazy_load_fseek_fail(test_context_t *context)
else
{
ikv_test_fail_fseek_after(1);
result = expect_true(context, ikv_object_get(loaded, "a") == NULL, "lazy load fseek fail should return null");
result = expect_true(context, ikv_as_int(ikv_object_get(loaded, "a")) == 1, "memory-backed lazy load should ignore fseek hooks");
}
}
cleanup_file(path);
@@ -1167,7 +1466,7 @@ static int test_hook_lazy_load_fread_fail(test_context_t *context)
else
{
ikv_test_fail_fread_after(1);
result = expect_true(context, ikv_object_get(loaded, "a") == NULL, "lazy load fread fail should return null");
result = expect_true(context, ikv_as_int(ikv_object_get(loaded, "a")) == 1, "memory-backed lazy load should ignore fread hooks");
}
}
cleanup_file(path);
@@ -1328,6 +1627,12 @@ static const test_case_t test_cases[] = {
{"binary v1 file roundtrip", test_binary_v1_file_roundtrip},
{"binary v2 memory lazy roundtrip", test_binary_v2_memory_lazy_roundtrip},
{"binary v2 file lazy roundtrip", test_binary_v2_file_lazy_roundtrip},
{"refresh from path null guards", test_refresh_from_path_null_guards},
{"text refresh from path", test_text_refresh_from_path},
{"binary v1 refresh from path", test_binary_v1_refresh_from_path},
{"binary v2 refresh from path", test_binary_v2_refresh_from_path},
{"refresh from path failure preserves state", test_refresh_from_path_failure_preserves_state},
{"binary v2 refresh replaces keys", test_binary_v2_refresh_replaces_keys},
{"binary v2 file benchmark", test_binary_v2_file_benchmark},
{"detection apis", test_detection_apis},
{"detect binary version v2", test_detect_binary_version_v2},

View File

@@ -32,6 +32,7 @@ typedef enum
ikv_node_t *ikv_create_object(const char *key);
ikv_node_t *ikv_create_array(const char *key, ikv_type_t element_type);
void ikv_free(ikv_node_t *node);
bool ikv_refresh_from_path(ikv_node_t *root_node, const char *path);
/* Node metadata */
const char *ikv_node_key(const ikv_node_t *node);

119
src/ikv.c
View File

@@ -145,7 +145,7 @@ static const ikv_loader_t *ikv_find_loader(ikv_version_t version)
return NULL;
}
static bool read_file_bytes(const char *path, uint8_t **out_data, size_t *out_size)
bool ikv__read_file_bytes(const char *path, uint8_t **out_data, size_t *out_size)
{
FILE *f = NULL;
long size_long = 0;
@@ -225,29 +225,6 @@ static bool read_file_prefix(const char *path, uint8_t *prefix, size_t prefix_ca
return true;
}
static bool open_file_with_prefix(const char *path, FILE **out_file, uint8_t *prefix, size_t prefix_capacity, size_t *out_size)
{
FILE *file = NULL;
size_t size = 0u;
if (!path || !out_file || !prefix || prefix_capacity == 0u || !out_size)
return false;
*out_file = NULL;
*out_size = 0u;
prefix[0] = 0u;
file = IKV_FOPEN(path, "rb");
if (!file)
return false;
size = IKV_FREAD(prefix, 1u, prefix_capacity - 1u, file);
prefix[size] = 0u;
*out_file = file;
*out_size = size;
return true;
}
static char *ikv_strdup(const char *s)
{
if (!s)
@@ -347,6 +324,43 @@ void ikv_free(ikv_node_t *n)
IKV_FREE(n);
}
static void ikv_replace_node_contents(ikv_node_t *dst, ikv_node_t *src)
{
if (!dst || !src)
return;
IKV_FREE(dst->key);
node_free_payload(dst);
dst->key = src->key;
dst->type = src->type;
dst->lazy_state = src->lazy_state;
dst->value = src->value;
dst->next = NULL;
src->key = NULL;
src->type = IKV_NULL;
src->lazy_state = NULL;
memset(&src->value, 0, sizeof(src->value));
src->next = NULL;
}
bool ikv_refresh_from_path(ikv_node_t *root_node, const char *path)
{
ikv_node_t *updated = NULL;
if (!root_node || !path)
return false;
updated = ikv_parse_file(path);
if (!updated)
return false;
ikv_replace_node_contents(root_node, updated);
IKV_FREE(updated);
return true;
}
const char *ikv_node_key(const ikv_node_t *node)
{
return (node && node->key) ? node->key : "";
@@ -1405,7 +1419,7 @@ ikv_node_t *ikv__parse_text_file_version(const char *path, uint32_t version)
char *buf = NULL;
ikv_node_t *root = NULL;
if (!read_file_bytes(path, &bytes, &bytes_n) || !bytes)
if (!ikv__read_file_bytes(path, &bytes, &bytes_n) || !bytes)
return NULL;
buf = (char *)IKV_MALLOC(bytes_n + 1u);
@@ -2183,7 +2197,7 @@ ikv_node_t *ikv__parse_binary_file_version(const char *path, uint32_t version)
size_t size = 0u;
ikv_node_t *root = NULL;
if (!read_file_bytes(path, &buf, &size) || !buf)
if (!ikv__read_file_bytes(path, &buf, &size) || !buf)
return NULL;
root = ikv__parse_binary_memory_version(buf, size, version);
@@ -2302,38 +2316,16 @@ ikv_node_t *ikv_parse_string_version(const char *src, ikv_version_t version)
ikv_node_t *ikv_parse_file(const char *path)
{
FILE *file = NULL;
uint8_t prefix[16] = {0};
size_t prefix_size = 0u;
ikv_version_t version = IKV_VERSION_UNKNOWN;
if (!open_file_with_prefix(path, &file, prefix, sizeof(prefix), &prefix_size))
if (!read_file_prefix(path, prefix, sizeof(prefix), &prefix_size))
return NULL;
version = ikv_detect_binary_version(prefix, prefix_size);
if (version != IKV_VERSION_UNKNOWN)
{
ikv_node_t *root = NULL;
if (version == IKV_VERSION_2)
{
if (IKV_FSEEK(file, 0L, SEEK_SET) != 0)
{
IKV_FCLOSE(file);
return NULL;
}
root = ikv2_parse_binary_stream(file);
if (!root)
IKV_FCLOSE(file);
return root;
}
IKV_FCLOSE(file);
return ikvb_parse_file_version(path, version);
}
IKV_FCLOSE(file);
version = ikv_detect_text_version((const char *)prefix);
if (version == IKV_VERSION_UNKNOWN)
@@ -2400,38 +2392,9 @@ ikv_node_t *ikvb_parse_memory_version(const void *data, size_t size, ikv_version
ikv_node_t *ikvb_parse_file(const char *path)
{
FILE *file = NULL;
uint8_t prefix[16] = {0};
size_t prefix_size = 0u;
ikv_version_t version = ikv_detect_file_version(path, true);
if (!open_file_with_prefix(path, &file, prefix, sizeof(prefix), &prefix_size))
return NULL;
version = ikv_detect_binary_version(prefix, prefix_size);
if (version == IKV_VERSION_UNKNOWN)
{
IKV_FCLOSE(file);
return NULL;
}
if (version == IKV_VERSION_2)
{
ikv_node_t *root = NULL;
if (IKV_FSEEK(file, 0L, SEEK_SET) != 0)
{
IKV_FCLOSE(file);
return NULL;
}
root = ikv2_parse_binary_stream(file);
if (!root)
IKV_FCLOSE(file);
return root;
}
IKV_FCLOSE(file);
return ikvb_parse_file_version(path, version);
}

View File

@@ -85,6 +85,7 @@ void ikv_test_fail_fclose_after(int remaining_calls);
bool ikv__write_text_file_version(const char *path, const ikv_node_t *root, uint32_t version);
ikv_node_t *ikv__parse_text_file_version(const char *path, uint32_t version);
ikv_node_t *ikv__parse_text_string_version(const char *src, uint32_t version);
bool ikv__read_file_bytes(const char *path, uint8_t **out_data, size_t *out_size);
bool ikv__write_binary_file_version(const char *path, const ikv_node_t *root, uint32_t version);
bool ikv__write_binary_memory_version(const ikv_node_t *root, uint8_t **out_data, uint32_t *out_size, uint32_t version);

View File

@@ -20,17 +20,9 @@ typedef struct
uint32_t next_in_bucket;
} ikv2_index_entry_t;
typedef enum
{
IKV2_SOURCE_FILE = 1,
IKV2_SOURCE_MEMORY = 2
} ikv2_source_kind_t;
typedef struct
{
ikv_lazy_state_t base;
ikv2_source_kind_t source_kind;
FILE *file_handle;
uint8_t *memory_data;
size_t memory_size;
uint32_t entry_count;
@@ -263,71 +255,6 @@ static char *ikv2_cursor_read_string(ikv2_cursor_t *cursor)
return value;
}
static bool ikv2_file_read_u8(FILE *file, uint8_t *out_value)
{
return file && out_value && IKV_FREAD(out_value, 1u, 1u, file) == 1u;
}
static bool ikv2_file_read_u32le(FILE *file, uint32_t *out_value)
{
uint8_t bytes[4];
if (!file || !out_value || IKV_FREAD(bytes, 1u, sizeof(bytes), file) != sizeof(bytes))
return false;
*out_value = (uint32_t)bytes[0] |
((uint32_t)bytes[1] << 8) |
((uint32_t)bytes[2] << 16) |
((uint32_t)bytes[3] << 24);
return true;
}
static bool ikv2_file_read_varu32(FILE *file, uint32_t *out_value)
{
uint32_t shift = 0u;
uint32_t value = 0u;
while (shift < 32u)
{
uint8_t byte = 0u;
if (!ikv2_file_read_u8(file, &byte))
return false;
value |= (uint32_t)(byte & 0x7Fu) << shift;
if ((byte & 0x80u) == 0u)
{
*out_value = value;
return true;
}
shift += 7u;
}
return false;
}
static char *ikv2_file_read_string(FILE *file)
{
uint32_t length = 0u;
char *value = NULL;
if (!ikv2_file_read_varu32(file, &length))
return NULL;
value = (char *)IKV_MALLOC((size_t)length + 1u);
if (!value)
return NULL;
if (length > 0u && IKV_FREAD(value, 1u, length, file) != length)
{
IKV_FREE(value);
return NULL;
}
value[length] = 0;
return value;
}
static bool ikv2_collect_root_entries(const ikv_node_t *root, ikv2_index_entry_t **out_entries, uint32_t *out_count)
{
ikv2_index_entry_t *entries = NULL;
@@ -473,8 +400,6 @@ static void ikv2_lazy_root_destroy(ikv_lazy_state_t *state)
for (uint32_t i = 0; i < lazy_root->entry_count; ++i)
IKV_FREE(lazy_root->entries[i].key);
if (lazy_root->file_handle)
IKV_FCLOSE(lazy_root->file_handle);
IKV_FREE(lazy_root->bucket_heads);
IKV_FREE(lazy_root->entries);
IKV_FREE(lazy_root->memory_data);
@@ -532,32 +457,6 @@ static ikv2_index_entry_t *ikv2_find_entry(ikv2_lazy_root_t *lazy_root, const ch
return NULL;
}
static bool ikv2_read_payload_from_file(ikv2_lazy_root_t *lazy_root, uint32_t offset, uint32_t size, uint8_t **out_data)
{
uint8_t *data = NULL;
if (!lazy_root || !lazy_root->file_handle || !out_data)
return false;
*out_data = NULL;
if (IKV_FSEEK(lazy_root->file_handle, (long)offset, SEEK_SET) != 0)
return false;
data = (uint8_t *)IKV_MALLOC(size);
if (!data)
return false;
if (size > 0u && IKV_FREAD(data, 1u, size, lazy_root->file_handle) != size)
{
IKV_FREE(data);
return false;
}
*out_data = data;
return true;
}
static bool ikv2_payload_range_valid(size_t total_size, uint32_t payload_offset, uint32_t payload_size)
{
size_t end_offset = 0u;
@@ -576,7 +475,6 @@ static ikv_node_t *ikv2_lazy_root_load_object_key(ikv_lazy_state_t *state, ikv_n
ikv2_lazy_root_t *lazy_root = (ikv2_lazy_root_t *)state;
ikv2_index_entry_t *entry = NULL;
const uint8_t *payload_data = NULL;
uint8_t *owned_payload = NULL;
ikv_node_t *node = NULL;
size_t consumed = 0u;
@@ -589,21 +487,11 @@ static ikv_node_t *ikv2_lazy_root_load_object_key(ikv_lazy_state_t *state, ikv_n
if (!entry)
return NULL;
if (lazy_root->source_kind == IKV2_SOURCE_MEMORY)
{
if ((size_t)entry->payload_offset + (size_t)entry->payload_size > lazy_root->memory_size)
return NULL;
payload_data = lazy_root->memory_data + entry->payload_offset;
}
else
{
if (!ikv2_read_payload_from_file(lazy_root, entry->payload_offset, entry->payload_size, &owned_payload))
return NULL;
payload_data = owned_payload;
}
if ((size_t)entry->payload_offset + (size_t)entry->payload_size > lazy_root->memory_size)
return NULL;
payload_data = lazy_root->memory_data + entry->payload_offset;
node = ikv__parse_binary_node_memory(payload_data, entry->payload_size, &consumed);
IKV_FREE(owned_payload);
if (!node || consumed != entry->payload_size)
{
if (node)
@@ -722,7 +610,6 @@ static ikv_node_t *ikv2_parse_indexed_binary_buffer(uint8_t *buffer, size_t size
return NULL;
}
lazy_root->source_kind = IKV2_SOURCE_MEMORY;
lazy_root->memory_data = buffer;
lazy_root->memory_size = size;
@@ -774,161 +661,21 @@ static bool ikv2_write_binary_file(const char *path, const ikv_node_t *root)
return ok;
}
ikv_node_t *ikv2_parse_binary_stream(FILE *file)
{
uint8_t magic[4];
uint8_t kind = 0u;
uint32_t version = 0u;
uint32_t flags = 0u;
uint32_t entry_count = 0u;
char *root_name = NULL;
ikv_node_t *root = NULL;
ikv2_lazy_root_t *lazy_root = NULL;
long file_size_long = 0;
size_t file_size = 0u;
if (!file)
return NULL;
if (IKV_FREAD(magic, 1u, sizeof(magic), file) != sizeof(magic) ||
memcmp(magic, "iKv2", sizeof(magic)) != 0 ||
!ikv2_file_read_u8(file, &kind) ||
kind != (uint8_t)'b' ||
!ikv2_file_read_u32le(file, &version) ||
version != IKV_V2 ||
!ikv2_file_read_u32le(file, &flags) ||
(flags & IKV2_BINARY_FLAGS_INDEXED_ROOT) == 0u)
{
IKV_FCLOSE(file);
return NULL;
}
root_name = ikv2_file_read_string(file);
if (!root_name || !ikv2_file_read_varu32(file, &entry_count))
{
IKV_FREE(root_name);
IKV_FCLOSE(file);
return NULL;
}
root = ikv_create_object(root_name);
IKV_FREE(root_name);
if (!root || !ikv__object_reserve_for_count(root, entry_count))
{
if (root)
ikv_free(root);
IKV_FCLOSE(file);
return NULL;
}
root->value.object.size = entry_count;
lazy_root = (ikv2_lazy_root_t *)IKV_CALLOC(1u, sizeof(*lazy_root));
if (!lazy_root)
{
ikv_free(root);
IKV_FCLOSE(file);
return NULL;
}
lazy_root->base.destroy = ikv2_lazy_root_destroy;
lazy_root->base.load_object_key = ikv2_lazy_root_load_object_key;
lazy_root->source_kind = IKV2_SOURCE_FILE;
lazy_root->entry_count = entry_count;
lazy_root->entries = entry_count ? (ikv2_index_entry_t *)IKV_CALLOC(entry_count, sizeof(*lazy_root->entries)) : NULL;
if (entry_count > 0u && !lazy_root->entries)
{
ikv2_lazy_root_destroy(&lazy_root->base);
ikv_free(root);
IKV_FCLOSE(file);
return NULL;
}
for (uint32_t i = 0; i < entry_count; ++i)
{
lazy_root->entries[i].key = ikv2_file_read_string(file);
if (!lazy_root->entries[i].key)
{
ikv2_lazy_root_destroy(&lazy_root->base);
ikv_free(root);
IKV_FCLOSE(file);
return NULL;
}
lazy_root->entries[i].key_length = (uint32_t)strlen(lazy_root->entries[i].key);
lazy_root->entries[i].key_hash = ikv2_hash_key(lazy_root->entries[i].key);
lazy_root->entries[i].next_in_bucket = IKV2_INDEX_NONE;
}
for (uint32_t i = 0; i < entry_count; ++i)
{
if (!ikv2_file_read_u8(file, &lazy_root->entries[i].type) ||
!ikv2_file_read_u32le(file, &lazy_root->entries[i].payload_offset) ||
!ikv2_file_read_u32le(file, &lazy_root->entries[i].payload_size))
{
ikv2_lazy_root_destroy(&lazy_root->base);
ikv_free(root);
IKV_FCLOSE(file);
return NULL;
}
}
if (IKV_FSEEK(file, 0L, SEEK_END) != 0)
{
ikv2_lazy_root_destroy(&lazy_root->base);
ikv_free(root);
IKV_FCLOSE(file);
return NULL;
}
file_size_long = IKV_FTELL(file);
if (file_size_long < 0L)
{
ikv2_lazy_root_destroy(&lazy_root->base);
ikv_free(root);
IKV_FCLOSE(file);
return NULL;
}
file_size = (size_t)file_size_long;
for (uint32_t i = 0; i < entry_count; ++i)
{
if (!ikv2_payload_range_valid(file_size,
lazy_root->entries[i].payload_offset,
lazy_root->entries[i].payload_size))
{
ikv2_lazy_root_destroy(&lazy_root->base);
ikv_free(root);
IKV_FCLOSE(file);
return NULL;
}
}
if (!ikv2_build_entry_buckets(lazy_root))
{
ikv2_lazy_root_destroy(&lazy_root->base);
ikv_free(root);
IKV_FCLOSE(file);
return NULL;
}
lazy_root->file_handle = file;
root->lazy_state = &lazy_root->base;
return root;
}
static ikv_node_t *ikv2_parse_binary_file(const char *path)
{
FILE *file = NULL;
uint8_t *buffer = NULL;
size_t size = 0u;
ikv_node_t *root = NULL;
if (!path)
return NULL;
file = IKV_FOPEN(path, "rb");
if (!file)
if (!ikv__read_file_bytes(path, &buffer, &size) || !buffer)
return NULL;
root = ikv2_parse_binary_stream(file);
root = ikv2_parse_indexed_binary_buffer(buffer, size);
if (!root)
IKV_FCLOSE(file);
IKV_FREE(buffer);
return root;
}

View File

@@ -3,5 +3,3 @@
#include "../internal/ikv_internal.h"
extern const ikv_loader_t ikv_loader_v2;
ikv_node_t *ikv2_parse_binary_stream(FILE *file);