summaryrefslogtreecommitdiff
path: root/src/storage
diff options
context:
space:
mode:
Diffstat (limited to 'src/storage')
-rw-r--r--src/storage/README.md3
-rw-r--r--src/storage/codec.c76
-rw-r--r--src/storage/codec.h13
-rw-r--r--src/storage/store.c647
-rw-r--r--src/storage/store.h3
5 files changed, 398 insertions, 344 deletions
diff --git a/src/storage/README.md b/src/storage/README.md
index 3c03dba..79874b0 100644
--- a/src/storage/README.md
+++ b/src/storage/README.md
@@ -2,7 +2,8 @@
The storage adapter owns `active.log`, `snapshot.bin`, the data-directory lock,
in-memory live/deleted state, synchronous PUT/DEL writes, startup replay, and
-compaction. It uses Win32 64-bit file APIs and `FlushFileBuffers`.
+compaction. It depends only on the UTF-8, opaque-handle API in `src/sys`; OS
+file and durability primitives do not leak into this layer.
The binary format is private but versioned. Invalid complete records are never
skipped; only an incomplete EOF record is truncated. Snapshot generation must
diff --git a/src/storage/codec.c b/src/storage/codec.c
new file mode 100644
index 0000000..af061ce
--- /dev/null
+++ b/src/storage/codec.c
@@ -0,0 +1,76 @@
+#include "storage/codec.h"
+
+void
+tiny_codec_put_u32(unsigned char *output, uint32_t value)
+{
+ if (output == NULL)
+ {
+ return;
+ }
+ output[0] = (unsigned char)value;
+ output[1] = (unsigned char)(value >> 8);
+ output[2] = (unsigned char)(value >> 16);
+ output[3] = (unsigned char)(value >> 24);
+}
+
+void
+tiny_codec_put_u64(unsigned char *output, uint64_t value)
+{
+ unsigned index;
+ if (output == NULL)
+ {
+ return;
+ }
+ for (index = 0; index < 8; ++index)
+ {
+ output[index] = (unsigned char)(value >> (index * 8));
+ }
+}
+
+uint32_t
+tiny_codec_get_u32(const unsigned char *input)
+{
+ if (input == NULL)
+ {
+ return 0;
+ }
+ return (uint32_t)input[0] | ((uint32_t)input[1] << 8) | ((uint32_t)input[2] << 16)
+ | ((uint32_t)input[3] << 24);
+}
+
+uint64_t
+tiny_codec_get_u64(const unsigned char *input)
+{
+ uint64_t value = 0;
+ int index;
+ if (input == NULL)
+ {
+ return 0;
+ }
+ for (index = 7; index >= 0; --index)
+ {
+ value = (value << 8) | input[index];
+ }
+ return value;
+}
+
+uint32_t
+tiny_codec_crc32(const unsigned char *data, size_t length)
+{
+ uint32_t crc = 0xffffffffU;
+ size_t index;
+ if (data == NULL && length != 0)
+ {
+ return 0;
+ }
+ for (index = 0; index < length; ++index)
+ {
+ unsigned bit;
+ crc ^= data[index];
+ for (bit = 0; bit < 8; ++bit)
+ {
+ crc = (crc >> 1) ^ (0xedb88320U & (uint32_t)-(int32_t)(crc & 1));
+ }
+ }
+ return ~crc;
+}
diff --git a/src/storage/codec.h b/src/storage/codec.h
new file mode 100644
index 0000000..aafd174
--- /dev/null
+++ b/src/storage/codec.h
@@ -0,0 +1,13 @@
+#ifndef TINY_CONVMEM_STORAGE_CODEC_H
+#define TINY_CONVMEM_STORAGE_CODEC_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+void tiny_codec_put_u32(unsigned char *output, uint32_t value);
+void tiny_codec_put_u64(unsigned char *output, uint64_t value);
+uint32_t tiny_codec_get_u32(const unsigned char *input);
+uint64_t tiny_codec_get_u64(const unsigned char *input);
+uint32_t tiny_codec_crc32(const unsigned char *data, size_t length);
+
+#endif
diff --git a/src/storage/store.c b/src/storage/store.c
index 9d26e0f..296f4d0 100644
--- a/src/storage/store.c
+++ b/src/storage/store.c
@@ -1,13 +1,13 @@
-#define WIN32_LEAN_AND_MEAN
-#include <windows.h>
-#include <bcrypt.h>
-
#include "storage/store.h"
+#include "storage/codec.h"
+#include "sys/system.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include "base/allocator.h"
+
#define RECORD_HEADER_SIZE 24U
#define RECORD_VERSION 1U
#define RECORD_PUT 1U
@@ -18,11 +18,11 @@
struct tiny_store
{
- HANDLE lock;
- HANDLE log;
- wchar_t *directory;
- wchar_t *log_path;
- wchar_t *snapshot_path;
+ tiny_sys_file_t lock;
+ tiny_sys_file_t log;
+ char *directory;
+ char *log_path;
+ char *snapshot_path;
tiny_store_entry_t *entries;
size_t entry_count;
size_t entry_capacity;
@@ -37,89 +37,19 @@ struct tiny_store
};
static void
-put_u32(unsigned char *p, uint32_t v)
-{
- p[0] = (unsigned char)v;
- p[1] = (unsigned char)(v >> 8);
- p[2] = (unsigned char)(v >> 16);
- p[3] = (unsigned char)(v >> 24);
-}
-static void
-put_u64(unsigned char *p, uint64_t v)
-{
- unsigned i;
- for (i = 0; i < 8; ++i)
- {
- p[i] = (unsigned char)(v >> (i * 8));
- }
-}
-static uint32_t
-get_u32(const unsigned char *p)
-{
- return (uint32_t)p[0] | ((uint32_t)p[1] << 8) | ((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);
-}
-static uint64_t
-get_u64(const unsigned char *p)
-{
- uint64_t v = 0;
- int i;
- for (i = 7; i >= 0; --i)
- {
- v = (v << 8) | p[i];
- }
- return v;
-}
-
-static uint32_t
-crc32_bytes(const unsigned char *data, size_t length)
-{
- uint32_t crc = 0xffffffffU;
- size_t i;
- for (i = 0; i < length; ++i)
- {
- unsigned bit;
- crc ^= data[i];
- for (bit = 0; bit < 8; ++bit)
- {
- crc = (crc >> 1) ^ (0xedb88320U & (uint32_t)-(int32_t)(crc & 1));
- }
- }
- return ~crc;
-}
-
-static wchar_t *
-join_path(const wchar_t *directory, const wchar_t *name)
-{
- const size_t left = wcslen(directory), right = wcslen(name);
- const bool separator = left > 0 && directory[left - 1] != L'\\' && directory[left - 1] != L'/';
- wchar_t *result = malloc((left + right + (separator ? 2 : 1)) * sizeof(wchar_t));
- if (result == NULL)
- {
- return NULL;
- }
- wcscpy_s(result, left + right + (separator ? 2 : 1), directory);
- if (separator)
- {
- wcscat_s(result, left + right + 2, L"\\");
- }
- wcscat_s(result, left + right + (separator ? 2 : 1), name);
- return result;
-}
-
-static void
set_error(tiny_store_t *store, const char *message)
{
- strncpy_s(store->last_error, sizeof(store->last_error), message, _TRUNCATE);
+ snprintf(store->last_error, sizeof(store->last_error), "%s", message);
}
static bool
-write_all(HANDLE file, const void *data, uint32_t length)
+write_all(tiny_sys_file_t *file, const void *data, uint32_t length)
{
const unsigned char *position = data;
while (length > 0)
{
- DWORD written = 0;
- if (!WriteFile(file, position, length, &written, NULL) || written == 0)
+ uint32_t written = 0;
+ if (!tiny_sys_file_write(file, position, length, &written) || written == 0)
{
return false;
}
@@ -130,14 +60,14 @@ write_all(HANDLE file, const void *data, uint32_t length)
}
static bool
-read_all(HANDLE file, void *data, uint32_t length, uint32_t *actual)
+read_all(tiny_sys_file_t *file, void *data, uint32_t length, uint32_t *actual)
{
unsigned char *position = data;
uint32_t total = 0;
while (total < length)
{
- DWORD count = 0;
- if (!ReadFile(file, position + total, length - total, &count, NULL))
+ uint32_t count = 0;
+ if (!tiny_sys_file_read(file, position + total, length - total, &count))
{
return false;
}
@@ -154,12 +84,7 @@ read_all(HANDLE file, void *data, uint32_t length, uint32_t *actual)
static uint64_t
now_milliseconds(void)
{
- FILETIME ft;
- ULARGE_INTEGER ticks;
- GetSystemTimeAsFileTime(&ft);
- ticks.LowPart = ft.dwLowDateTime;
- ticks.HighPart = ft.dwHighDateTime;
- return ticks.QuadPart / 10000ULL - 11644473600000ULL;
+ return tiny_sys_now_milliseconds();
}
static void
@@ -171,11 +96,9 @@ make_ulid(tiny_store_t *store, uint64_t timestamp, char output[27])
if (timestamp > store->last_ulid_ms)
{
store->last_ulid_ms = timestamp;
- if (BCryptGenRandom(
- NULL, store->last_random, sizeof(store->last_random), BCRYPT_USE_SYSTEM_PREFERRED_RNG)
- != 0)
+ if (!tiny_sys_random_bytes(store->last_random, sizeof(store->last_random)))
{
- uint64_t fallback = GetTickCount64() ^ timestamp;
+ uint64_t fallback = tiny_sys_monotonic_milliseconds() ^ timestamp;
for (index = 0; index < sizeof(store->last_random); ++index)
{
store->last_random[index] = (unsigned char)(fallback >> ((index % 8) * 8));
@@ -274,19 +197,19 @@ serialize_put(const tiny_message_t *message, unsigned char **payload, uint32_t *
return false;
}
offset = 0;
- put_u64(data + offset, (uint64_t)message->timestamp_ms);
+ tiny_codec_put_u64(data + offset, (uint64_t)message->timestamp_ms);
offset += 8;
memcpy(data + offset, message->message_id, 26);
offset += 26;
data[offset++] = (unsigned char)message->role;
data[offset++] = (unsigned char)message->metadata.memory_tier;
- put_u32(data + offset, (uint32_t)session_len);
+ tiny_codec_put_u32(data + offset, (uint32_t)session_len);
offset += 4;
- put_u32(data + offset, (uint32_t)content_len);
+ tiny_codec_put_u32(data + offset, (uint32_t)content_len);
offset += 4;
- put_u32(data + offset, (uint32_t)tool_len);
+ tiny_codec_put_u32(data + offset, (uint32_t)tool_len);
offset += 4;
- put_u32(data + offset, (uint32_t)message->metadata.tag_count);
+ tiny_codec_put_u32(data + offset, (uint32_t)message->metadata.tag_count);
offset += 4;
memcpy(data + offset, message->session_id, session_len);
offset += session_len;
@@ -300,7 +223,7 @@ serialize_put(const tiny_message_t *message, unsigned char **payload, uint32_t *
for (index = 0; index < message->metadata.tag_count; ++index)
{
const size_t tag_len = strlen(message->metadata.tags[index]);
- put_u32(data + offset, (uint32_t)tag_len);
+ tiny_codec_put_u32(data + offset, (uint32_t)tag_len);
offset += 4;
memcpy(data + offset, message->metadata.tags[index], tag_len);
offset += tag_len;
@@ -323,6 +246,13 @@ copy_field(const unsigned char *data, size_t length)
}
static bool
+deserialize_failed(tiny_message_t *message)
+{
+ tiny_message_free(message);
+ return false;
+}
+
+static bool
deserialize_put(const unsigned char *data, uint32_t length, tiny_message_t *message)
{
uint32_t session_len, content_len, tool_len, tag_count;
@@ -332,20 +262,20 @@ deserialize_put(const unsigned char *data, uint32_t length, tiny_message_t *mess
{
return false;
}
- message->timestamp_ms = (int64_t)get_u64(data + offset);
+ message->timestamp_ms = (int64_t)tiny_codec_get_u64(data + offset);
offset += 8;
memcpy(message->message_id, data + offset, 26);
message->message_id[26] = '\0';
offset += 26;
message->role = (tiny_role_t)data[offset++];
message->metadata.memory_tier = (tiny_memory_tier_t)data[offset++];
- session_len = get_u32(data + offset);
+ session_len = tiny_codec_get_u32(data + offset);
offset += 4;
- content_len = get_u32(data + offset);
+ content_len = tiny_codec_get_u32(data + offset);
offset += 4;
- tool_len = get_u32(data + offset);
+ tool_len = tiny_codec_get_u32(data + offset);
offset += 4;
- tag_count = get_u32(data + offset);
+ tag_count = tiny_codec_get_u32(data + offset);
offset += 4;
if (!tiny_message_id_valid(message->message_id)
|| (message->role < TINY_ROLE_USER || message->role > TINY_ROLE_SYSTEM)
@@ -369,33 +299,33 @@ deserialize_put(const unsigned char *data, uint32_t length, tiny_message_t *mess
if (message->session_id == NULL || message->content == NULL
|| (tool_len && !message->metadata.tool_call_id))
{
- goto fail;
+ return deserialize_failed(message);
}
if (tag_count > 0)
{
message->metadata.tags = calloc(tag_count, sizeof(char *));
if (message->metadata.tags == NULL)
{
- goto fail;
+ return deserialize_failed(message);
}
for (index = 0; index < tag_count; ++index)
{
uint32_t tag_len;
if (offset + 4 > length)
{
- goto fail;
+ return deserialize_failed(message);
}
- tag_len = get_u32(data + offset);
+ tag_len = tiny_codec_get_u32(data + offset);
offset += 4;
if (tag_len == 0 || tag_len > TINY_TAG_MAX || offset + tag_len > length)
{
- goto fail;
+ return deserialize_failed(message);
}
message->metadata.tags[index] = copy_field(data + offset, tag_len);
offset += tag_len;
if (message->metadata.tags[index] == NULL)
{
- goto fail;
+ return deserialize_failed(message);
}
message->metadata.tag_count++;
}
@@ -403,16 +333,13 @@ deserialize_put(const unsigned char *data, uint32_t length, tiny_message_t *mess
if (offset != length || !tiny_utf8_valid(message->session_id, session_len)
|| !tiny_utf8_valid(message->content, content_len))
{
- goto fail;
+ return deserialize_failed(message);
}
return true;
-fail:
- tiny_message_free(message);
- return false;
}
static tiny_store_status_t
-append_record_to(HANDLE file,
+append_record_to(tiny_sys_file_t *file,
uint8_t type,
uint64_t sequence,
const unsigned char *payload,
@@ -420,16 +347,14 @@ append_record_to(HANDLE file,
bool flush)
{
unsigned char header[RECORD_HEADER_SIZE] = { 0 };
- LARGE_INTEGER end;
memcpy(header, "TCMR", 4);
header[4] = RECORD_VERSION;
header[5] = type;
- put_u32(header + 8, payload_length);
- put_u64(header + 12, sequence);
- put_u32(header + 20, crc32_bytes(payload, payload_length));
- end.QuadPart = 0;
- if (!SetFilePointerEx(file, end, NULL, FILE_END) || !write_all(file, header, sizeof(header))
- || !write_all(file, payload, payload_length) || (flush && !FlushFileBuffers(file)))
+ tiny_codec_put_u32(header + 8, payload_length);
+ tiny_codec_put_u64(header + 12, sequence);
+ tiny_codec_put_u32(header + 20, tiny_codec_crc32(payload, payload_length));
+ if (!tiny_sys_file_seek(file, 0, TINY_SYS_SEEK_END) || !write_all(file, header, sizeof(header))
+ || !write_all(file, payload, payload_length) || (flush && !tiny_sys_file_flush(file)))
{
return TINY_STORE_IO_ERROR;
}
@@ -469,38 +394,51 @@ clear_entries(tiny_store_t *store)
}
static tiny_store_status_t
+recover_torn_tail(tiny_store_t *store, uint64_t boundary)
+{
+ if (boundary > INT64_MAX
+ || !tiny_sys_file_seek(&store->log, (int64_t)boundary, TINY_SYS_SEEK_BEGIN)
+ || !tiny_sys_file_truncate(&store->log) || !tiny_sys_file_flush(&store->log))
+ {
+ return TINY_STORE_IO_ERROR;
+ }
+ store->recovered_tail = true;
+ set_error(store, "an incomplete trailing record was truncated during recovery");
+ return TINY_STORE_OK;
+}
+
+static tiny_store_status_t
replay_log(tiny_store_t *store, uint64_t starting_offset)
{
- LARGE_INTEGER start, file_size, boundary;
- start.QuadPart = (LONGLONG)starting_offset;
- boundary = start;
- if (!SetFilePointerEx(store->log, start, NULL, FILE_BEGIN)
- || !GetFileSizeEx(store->log, &file_size))
+ uint64_t file_size, boundary = starting_offset;
+ if (starting_offset > INT64_MAX
+ || !tiny_sys_file_seek(&store->log, (int64_t)starting_offset, TINY_SYS_SEEK_BEGIN)
+ || !tiny_sys_file_size(&store->log, &file_size))
{
return TINY_STORE_IO_ERROR;
}
- while (boundary.QuadPart < file_size.QuadPart)
+ while (boundary < file_size)
{
unsigned char header[RECORD_HEADER_SIZE], *payload = NULL;
uint32_t actual = 0, payload_length, expected_crc;
uint64_t sequence;
tiny_store_status_t status = TINY_STORE_OK;
- if (!read_all(store->log, header, sizeof(header), &actual))
+ if (!read_all(&store->log, header, sizeof(header), &actual))
{
return TINY_STORE_IO_ERROR;
}
if (actual < sizeof(header))
{
- goto torn_tail;
+ return recover_torn_tail(store, boundary);
}
if (memcmp(header, "TCMR", 4) != 0 || header[4] != RECORD_VERSION
|| (header[5] != RECORD_PUT && header[5] != RECORD_DEL && header[5] != RECORD_MARKER))
{
return TINY_STORE_CORRUPT;
}
- payload_length = get_u32(header + 8);
- sequence = get_u64(header + 12);
- expected_crc = get_u32(header + 20);
+ payload_length = tiny_codec_get_u32(header + 8);
+ sequence = tiny_codec_get_u64(header + 12);
+ expected_crc = tiny_codec_get_u32(header + 20);
if (payload_length > RECORD_PAYLOAD_MAX
|| (header[5] != RECORD_MARKER && sequence <= store->last_sequence))
{
@@ -511,7 +449,7 @@ replay_log(tiny_store_t *store, uint64_t starting_offset)
{
return TINY_STORE_NO_MEMORY;
}
- if (!read_all(store->log, payload, payload_length, &actual))
+ if (!read_all(&store->log, payload, payload_length, &actual))
{
free(payload);
return TINY_STORE_IO_ERROR;
@@ -519,53 +457,62 @@ replay_log(tiny_store_t *store, uint64_t starting_offset)
if (actual < payload_length)
{
free(payload);
- goto torn_tail;
+ return recover_torn_tail(store, boundary);
}
- if (crc32_bytes(payload, payload_length) != expected_crc)
+ if (tiny_codec_crc32(payload, payload_length) != expected_crc)
{
free(payload);
return TINY_STORE_CORRUPT;
}
- if (header[5] == RECORD_MARKER)
+ switch (header[5])
{
- if (boundary.QuadPart != 0 || sequence != 0
- || payload_length != sizeof(store->generation))
+ case RECORD_MARKER:
+ if (boundary != 0 || sequence != 0 || payload_length != sizeof(store->generation))
{
free(payload);
return TINY_STORE_CORRUPT;
}
memcpy(store->generation, payload, sizeof(store->generation));
store->has_generation = true;
- }
- else if (header[5] == RECORD_PUT)
- {
- tiny_message_t message;
- if (!deserialize_put(payload, payload_length, &message))
- {
- free(payload);
- return TINY_STORE_CORRUPT;
- }
- status = apply_put(store, &message, sequence);
- tiny_message_free(&message);
- }
- else
- {
- char id[27];
- tiny_store_entry_t *entry;
- if (payload_length != 34)
- {
- free(payload);
- return TINY_STORE_CORRUPT;
- }
- memcpy(id, payload, 26);
- id[26] = '\0';
- entry = find_entry(store, id);
- if (entry == NULL || entry->deleted || !tiny_message_id_valid(id))
- {
- free(payload);
- return TINY_STORE_CORRUPT;
- }
- entry->deleted = true;
+ break;
+
+ case RECORD_PUT:
+ {
+ tiny_message_t message;
+ if (!deserialize_put(payload, payload_length, &message))
+ {
+ free(payload);
+ return TINY_STORE_CORRUPT;
+ }
+ status = apply_put(store, &message, sequence);
+ tiny_message_free(&message);
+ }
+ break;
+
+ case RECORD_DEL:
+ {
+ char id[27];
+ tiny_store_entry_t *entry;
+ if (payload_length != 34)
+ {
+ free(payload);
+ return TINY_STORE_CORRUPT;
+ }
+ memcpy(id, payload, 26);
+ id[26] = '\0';
+ entry = find_entry(store, id);
+ if (entry == NULL || entry->deleted || !tiny_message_id_valid(id))
+ {
+ free(payload);
+ return TINY_STORE_CORRUPT;
+ }
+ entry->deleted = true;
+ }
+ break;
+
+ default:
+ free(payload);
+ return TINY_STORE_CORRUPT;
}
free(payload);
if (status != TINY_STORE_OK)
@@ -576,49 +523,43 @@ replay_log(tiny_store_t *store, uint64_t starting_offset)
{
store->last_sequence = sequence;
}
- boundary.QuadPart += RECORD_HEADER_SIZE + payload_length;
+ boundary += RECORD_HEADER_SIZE + payload_length;
}
return TINY_STORE_OK;
-
-torn_tail:
- if (!SetFilePointerEx(store->log, boundary, NULL, FILE_BEGIN) || !SetEndOfFile(store->log)
- || !FlushFileBuffers(store->log))
- {
- return TINY_STORE_IO_ERROR;
- }
- store->recovered_tail = true;
- set_error(store, "an incomplete trailing record was truncated during recovery");
- return TINY_STORE_OK;
}
static tiny_store_status_t
write_snapshot(tiny_store_t *store)
{
- wchar_t *temporary = join_path(store->directory, L"snapshot.tmp");
- HANDLE file;
+ char *temporary = tiny_sys_path_join(store->directory, "snapshot.tmp");
+ tiny_sys_file_t file;
unsigned char data[SNAPSHOT_HEADER_SIZE] = { 0 };
- LARGE_INTEGER size;
+ uint64_t size;
bool ok;
size_t index;
if (temporary == NULL)
{
return TINY_STORE_NO_MEMORY;
}
- file = CreateFileW(temporary, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
- if (file == INVALID_HANDLE_VALUE)
+ if (tiny_sys_file_open(&file, temporary, TINY_SYS_FILE_WRITE_NEW) != TINY_SYS_OK)
{
free(temporary);
return TINY_STORE_IO_ERROR;
}
- GetFileSizeEx(store->log, &size);
+ if (!tiny_sys_file_size(&store->log, &size))
+ {
+ tiny_sys_file_close(&file);
+ free(temporary);
+ return TINY_STORE_IO_ERROR;
+ }
memcpy(data, "TCMS", 4);
data[4] = 1;
memcpy(data + 8, store->generation, 16);
- put_u64(data + 24, store->last_sequence);
- put_u64(data + 32, (uint64_t)size.QuadPart);
- put_u64(data + 40, (uint64_t)store->entry_count);
- put_u32(data + 48, crc32_bytes(data, 48));
- ok = write_all(file, data, sizeof(data));
+ tiny_codec_put_u64(data + 24, store->last_sequence);
+ tiny_codec_put_u64(data + 32, size);
+ tiny_codec_put_u64(data + 40, (uint64_t)store->entry_count);
+ tiny_codec_put_u32(data + 48, tiny_codec_crc32(data, 48));
+ ok = write_all(&file, data, sizeof(data));
for (index = 0; ok && index < store->entry_count; ++index)
{
unsigned char prefix[16], *payload = NULL;
@@ -628,22 +569,20 @@ write_snapshot(tiny_store_t *store)
ok = false;
break;
}
- put_u64(prefix, store->entries[index].sequence);
- put_u32(prefix + 8, length);
- put_u32(prefix + 12, crc32_bytes(payload, length));
- ok = write_all(file, prefix, sizeof(prefix)) && write_all(file, payload, length);
+ tiny_codec_put_u64(prefix, store->entries[index].sequence);
+ tiny_codec_put_u32(prefix + 8, length);
+ tiny_codec_put_u32(prefix + 12, tiny_codec_crc32(payload, length));
+ ok = write_all(&file, prefix, sizeof(prefix)) && write_all(&file, payload, length);
free(payload);
}
if (ok)
{
- ok = FlushFileBuffers(file) != 0;
+ ok = tiny_sys_file_flush(&file);
}
- CloseHandle(file);
+ tiny_sys_file_close(&file);
if (ok)
{
- ok = MoveFileExW(
- temporary, store->snapshot_path, MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)
- != 0;
+ ok = tiny_sys_path_replace(temporary, store->snapshot_path);
}
free(temporary);
store->snapshot_valid = ok;
@@ -655,13 +594,12 @@ log_generation_matches(tiny_store_t *store, const unsigned char generation[16])
{
unsigned char header[RECORD_HEADER_SIZE], payload[16];
uint32_t actual = 0;
- LARGE_INTEGER zero = { 0 };
- if (!SetFilePointerEx(store->log, zero, NULL, FILE_BEGIN)
- || !read_all(store->log, header, sizeof(header), &actual) || actual != sizeof(header)
+ if (!tiny_sys_file_seek(&store->log, 0, TINY_SYS_SEEK_BEGIN)
+ || !read_all(&store->log, header, sizeof(header), &actual) || actual != sizeof(header)
|| memcmp(header, "TCMR", 4) != 0 || header[4] != RECORD_VERSION || header[5] != RECORD_MARKER
- || get_u64(header + 12) != 0 || get_u32(header + 8) != 16
- || !read_all(store->log, payload, sizeof(payload), &actual) || actual != sizeof(payload)
- || get_u32(header + 20) != crc32_bytes(payload, sizeof(payload)))
+ || tiny_codec_get_u64(header + 12) != 0 || tiny_codec_get_u32(header + 8) != 16
+ || !read_all(&store->log, payload, sizeof(payload), &actual) || actual != sizeof(payload)
+ || tiny_codec_get_u32(header + 20) != tiny_codec_crc32(payload, sizeof(payload)))
{
return false;
}
@@ -669,36 +607,43 @@ log_generation_matches(tiny_store_t *store, const unsigned char generation[16])
}
static bool
+snapshot_invalid(tiny_store_t *store, tiny_sys_file_t *file, bool clear_loaded_entries)
+{
+ if (clear_loaded_entries)
+ {
+ clear_entries(store);
+ store->has_generation = false;
+ }
+ set_error(store, "snapshot validation failed; the active log was replayed from the beginning");
+ tiny_sys_file_close(file);
+ return false;
+}
+
+static bool
load_snapshot(tiny_store_t *store, uint64_t *log_offset)
{
- HANDLE file = CreateFileW(store->snapshot_path,
- GENERIC_READ,
- FILE_SHARE_READ,
- NULL,
- OPEN_EXISTING,
- FILE_ATTRIBUTE_NORMAL,
- NULL);
+ tiny_sys_file_t file;
unsigned char header[SNAPSHOT_HEADER_SIZE];
uint32_t actual = 0;
uint64_t count, index, expected_sequence, offset;
- LARGE_INTEGER log_size;
- if (file == INVALID_HANDLE_VALUE)
+ uint64_t log_size;
+ if (tiny_sys_file_open(&file, store->snapshot_path, TINY_SYS_FILE_READ) != TINY_SYS_OK)
{
return false;
}
- if (!read_all(file, header, sizeof(header), &actual) || actual != sizeof(header)
+ if (!read_all(&file, header, sizeof(header), &actual) || actual != sizeof(header)
|| memcmp(header, "TCMS", 4) != 0 || header[4] != 1
- || get_u32(header + 48) != crc32_bytes(header, 48) || !GetFileSizeEx(store->log, &log_size))
+ || tiny_codec_get_u32(header + 48) != tiny_codec_crc32(header, 48)
+ || !tiny_sys_file_size(&store->log, &log_size))
{
- goto invalid;
+ return snapshot_invalid(store, &file, false);
}
- expected_sequence = get_u64(header + 24);
- offset = get_u64(header + 32);
- count = get_u64(header + 40);
- if (offset > (uint64_t)log_size.QuadPart || count > SIZE_MAX
- || !log_generation_matches(store, header + 8))
+ expected_sequence = tiny_codec_get_u64(header + 24);
+ offset = tiny_codec_get_u64(header + 32);
+ count = tiny_codec_get_u64(header + 40);
+ if (offset > log_size || count > SIZE_MAX || !log_generation_matches(store, header + 8))
{
- goto invalid;
+ return snapshot_invalid(store, &file, false);
}
memcpy(store->generation, header + 8, 16);
store->has_generation = true;
@@ -708,99 +653,102 @@ load_snapshot(tiny_store_t *store, uint64_t *log_offset)
uint32_t length, crc;
tiny_message_t message;
tiny_store_status_t status;
- if (!read_all(file, prefix, sizeof(prefix), &actual) || actual != sizeof(prefix))
+ if (!read_all(&file, prefix, sizeof(prefix), &actual) || actual != sizeof(prefix))
{
- goto invalid_entries;
+ return snapshot_invalid(store, &file, true);
}
- length = get_u32(prefix + 8);
- crc = get_u32(prefix + 12);
+ length = tiny_codec_get_u32(prefix + 8);
+ crc = tiny_codec_get_u32(prefix + 12);
if (length > RECORD_PAYLOAD_MAX)
{
- goto invalid_entries;
+ return snapshot_invalid(store, &file, true);
}
payload = malloc(length ? length : 1);
if (!payload)
{
- goto invalid_entries;
+ return snapshot_invalid(store, &file, true);
}
- if (!read_all(file, payload, length, &actual) || actual != length
- || crc32_bytes(payload, length) != crc || !deserialize_put(payload, length, &message))
+ if (!read_all(&file, payload, length, &actual) || actual != length
+ || tiny_codec_crc32(payload, length) != crc
+ || !deserialize_put(payload, length, &message))
{
free(payload);
- goto invalid_entries;
+ return snapshot_invalid(store, &file, true);
}
free(payload);
- status = apply_put(store, &message, get_u64(prefix));
+ status = apply_put(store, &message, tiny_codec_get_u64(prefix));
tiny_message_free(&message);
if (status != TINY_STORE_OK)
{
- goto invalid_entries;
+ return snapshot_invalid(store, &file, true);
}
- store->last_sequence = get_u64(prefix);
+ store->last_sequence = tiny_codec_get_u64(prefix);
}
if (store->last_sequence != expected_sequence)
{
- goto invalid_entries;
+ return snapshot_invalid(store, &file, true);
}
- CloseHandle(file);
+ tiny_sys_file_close(&file);
*log_offset = offset;
store->snapshot_valid = true;
return true;
-invalid_entries:
- clear_entries(store);
- store->has_generation = false;
-invalid:
- set_error(store, "snapshot validation failed; the active log was replayed from the beginning");
- CloseHandle(file);
- return false;
+}
+
+static tiny_store_status_t
+store_open_failed(tiny_store_t *store, char *lock_path, tiny_store_status_t status)
+{
+ free(lock_path);
+ tiny_store_close(store);
+ return status;
}
tiny_store_status_t
-tiny_store_open(tiny_store_t **output, const wchar_t *directory)
+tiny_store_open(tiny_store_t **output, const char *directory)
{
- tiny_store_t *store = calloc(1, sizeof(*store));
- wchar_t *lock_path = NULL;
+ tiny_store_t *store;
+ char *lock_path = NULL;
+ tiny_sys_status_t sys_status;
tiny_store_status_t status;
+ if (output == NULL)
+ {
+ return TINY_STORE_IO_ERROR;
+ }
+ *output = NULL;
+ if (directory == NULL || directory[0] == '\0')
+ {
+ return TINY_STORE_IO_ERROR;
+ }
+ store = calloc(1, sizeof(*store));
if (store == NULL)
{
return TINY_STORE_NO_MEMORY;
}
- store->lock = INVALID_HANDLE_VALUE;
- store->log = INVALID_HANDLE_VALUE;
- store->directory = _wcsdup(directory);
- store->log_path = join_path(directory, L"active.log");
- store->snapshot_path = join_path(directory, L"snapshot.bin");
- lock_path = join_path(directory, L"store.lock");
+ tiny_sys_file_init(&store->lock);
+ tiny_sys_file_init(&store->log);
+ store->directory = copy_field((const unsigned char *)directory, strlen(directory));
+ store->log_path = tiny_sys_path_join(directory, "active.log");
+ store->snapshot_path = tiny_sys_path_join(directory, "snapshot.bin");
+ lock_path = tiny_sys_path_join(directory, "store.lock");
if (!store->directory || !store->log_path || !store->snapshot_path || !lock_path)
{
- status = TINY_STORE_NO_MEMORY;
- goto fail;
+ return store_open_failed(store, lock_path, TINY_STORE_NO_MEMORY);
}
- if (!CreateDirectoryW(directory, NULL) && GetLastError() != ERROR_ALREADY_EXISTS)
+ if (tiny_sys_directory_create(directory) != TINY_SYS_OK)
{
- status = TINY_STORE_IO_ERROR;
- goto fail;
+ return store_open_failed(store, lock_path, TINY_STORE_IO_ERROR);
}
- store->lock = CreateFileW(
- lock_path, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
+ sys_status = tiny_sys_file_open(&store->lock, lock_path, TINY_SYS_FILE_LOCK);
free(lock_path);
lock_path = NULL;
- if (store->lock == INVALID_HANDLE_VALUE)
+ if (sys_status != TINY_SYS_OK)
{
- status = GetLastError() == ERROR_SHARING_VIOLATION ? TINY_STORE_BUSY : TINY_STORE_IO_ERROR;
- goto fail;
+ status = sys_status == TINY_SYS_BUSY ? TINY_STORE_BUSY : TINY_STORE_IO_ERROR;
+ return store_open_failed(store, lock_path, status);
}
- store->log = CreateFileW(store->log_path,
- GENERIC_READ | GENERIC_WRITE,
- FILE_SHARE_READ,
- NULL,
- OPEN_ALWAYS,
- FILE_ATTRIBUTE_NORMAL,
- NULL);
- if (store->log == INVALID_HANDLE_VALUE)
+ if (tiny_sys_file_open(&store->log, store->log_path, TINY_SYS_FILE_READ_WRITE_CREATE)
+ != TINY_SYS_OK)
{
- status = TINY_STORE_IO_ERROR;
- goto fail;
+ return store_open_failed(store, lock_path, TINY_STORE_IO_ERROR);
}
{
uint64_t replay_offset = 0;
@@ -809,14 +757,10 @@ tiny_store_open(tiny_store_t **output, const wchar_t *directory)
}
if (status != TINY_STORE_OK)
{
- goto fail;
+ return store_open_failed(store, lock_path, status);
}
*output = store;
return TINY_STORE_OK;
-fail:
- free(lock_path);
- tiny_store_close(store);
- return status;
}
void
@@ -827,14 +771,8 @@ tiny_store_close(tiny_store_t *store)
{
return;
}
- if (store->log != INVALID_HANDLE_VALUE)
- {
- CloseHandle(store->log);
- }
- if (store->lock != INVALID_HANDLE_VALUE)
- {
- CloseHandle(store->lock);
- }
+ tiny_sys_file_close(&store->log);
+ tiny_sys_file_close(&store->lock);
for (index = 0; index < store->entry_count; ++index)
{
tiny_message_free(&store->entries[index].message);
@@ -873,6 +811,15 @@ tiny_store_write(tiny_store_t *store, const tiny_message_t *input, tiny_message_
unsigned char *payload = NULL;
uint32_t length = 0;
tiny_store_status_t status;
+ if (written != NULL)
+ {
+ tiny_message_init(written);
+ }
+ if (store == NULL || input == NULL || written == NULL || input->session_id == NULL
+ || input->content == NULL)
+ {
+ return TINY_STORE_IO_ERROR;
+ }
tiny_message_init(&message);
tiny_message_init(&indexed);
if (tiny_message_copy(&message, input) != 0)
@@ -890,7 +837,7 @@ tiny_store_write(tiny_store_t *store, const tiny_message_t *input, tiny_message_
return TINY_STORE_NO_MEMORY;
}
status
- = append_record_to(store->log, RECORD_PUT, store->last_sequence + 1, payload, length, true);
+ = append_record_to(&store->log, RECORD_PUT, store->last_sequence + 1, payload, length, true);
free(payload);
if (status != TINY_STORE_OK)
{
@@ -911,18 +858,23 @@ tiny_store_write(tiny_store_t *store, const tiny_message_t *input, tiny_message_
tiny_store_status_t
tiny_store_delete(tiny_store_t *store, const char *message_id, const char *session_id)
{
- tiny_store_entry_t *entry = find_entry(store, message_id);
+ tiny_store_entry_t *entry;
unsigned char payload[34];
tiny_store_status_t status;
+ if (store == NULL || message_id == NULL || !tiny_message_id_valid(message_id))
+ {
+ return TINY_STORE_NOT_FOUND;
+ }
+ entry = find_entry(store, message_id);
if (!entry || entry->deleted
|| (session_id && strcmp(entry->message.session_id, session_id) != 0))
{
return TINY_STORE_NOT_FOUND;
}
memcpy(payload, message_id, 26);
- put_u64(payload + 26, now_milliseconds());
+ tiny_codec_put_u64(payload + 26, now_milliseconds());
status = append_record_to(
- store->log, RECORD_DEL, store->last_sequence + 1, payload, sizeof(payload), true);
+ &store->log, RECORD_DEL, store->last_sequence + 1, payload, sizeof(payload), true);
if (status != TINY_STORE_OK)
{
set_error(store, "failed to append and flush a DEL record");
@@ -936,35 +888,45 @@ tiny_store_delete(tiny_store_t *store, const char *message_id, const char *sessi
tiny_store_status_t
tiny_store_rebuild(tiny_store_t *store, tiny_rebuild_report_t *report)
{
- wchar_t *temporary = join_path(store->directory, L"active.tmp");
- HANDLE file;
- LARGE_INTEGER before, after;
+ char *temporary;
+ tiny_sys_file_t file;
+ uint64_t before = 0, after = 0;
size_t read_index, write_index = 0;
uint64_t removed = 0, max_sequence = 0;
tiny_store_status_t status = TINY_STORE_OK;
+ unsigned char previous_generation[sizeof(store->generation)];
+ bool previous_has_generation;
+ bool log_replaced = false;
+ if (report != NULL)
+ {
+ memset(report, 0, sizeof(*report));
+ }
+ if (store == NULL || report == NULL)
+ {
+ return TINY_STORE_IO_ERROR;
+ }
+ memcpy(previous_generation, store->generation, sizeof(previous_generation));
+ previous_has_generation = store->has_generation;
+ temporary = tiny_sys_path_join(store->directory, "active.tmp");
if (!temporary)
{
return TINY_STORE_NO_MEMORY;
}
- GetFileSizeEx(store->log, &before);
- file = CreateFileW(
- temporary, GENERIC_WRITE | GENERIC_READ, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
- if (file == INVALID_HANDLE_VALUE)
+ if (!tiny_sys_file_size(&store->log, &before)
+ || tiny_sys_file_open(&file, temporary, TINY_SYS_FILE_WRITE_NEW) != TINY_SYS_OK)
{
free(temporary);
return TINY_STORE_IO_ERROR;
}
- if (BCryptGenRandom(
- NULL, store->generation, sizeof(store->generation), BCRYPT_USE_SYSTEM_PREFERRED_RNG)
- != 0)
+ if (!tiny_sys_random_bytes(store->generation, sizeof(store->generation)))
{
- CloseHandle(file);
+ tiny_sys_file_close(&file);
free(temporary);
return TINY_STORE_IO_ERROR;
}
store->has_generation = true;
status = append_record_to(
- file, RECORD_MARKER, 0, store->generation, sizeof(store->generation), false);
+ &file, RECORD_MARKER, 0, store->generation, sizeof(store->generation), false);
for (read_index = 0; read_index < store->entry_count; ++read_index)
{
if (status == TINY_STORE_OK && !store->entries[read_index].deleted)
@@ -977,7 +939,7 @@ tiny_store_rebuild(tiny_store_t *store, tiny_rebuild_report_t *report)
break;
}
status = append_record_to(
- file, RECORD_PUT, store->entries[read_index].sequence, payload, length, false);
+ &file, RECORD_PUT, store->entries[read_index].sequence, payload, length, false);
free(payload);
if (status != TINY_STORE_OK)
{
@@ -993,28 +955,24 @@ tiny_store_rebuild(tiny_store_t *store, tiny_rebuild_report_t *report)
removed++;
}
}
- if (status == TINY_STORE_OK && !FlushFileBuffers(file))
+ if (status == TINY_STORE_OK && !tiny_sys_file_flush(&file))
{
status = TINY_STORE_IO_ERROR;
}
- CloseHandle(file);
+ tiny_sys_file_close(&file);
if (status == TINY_STORE_OK)
{
- CloseHandle(store->log);
- store->log = INVALID_HANDLE_VALUE;
- if (!MoveFileExW(
- temporary, store->log_path, MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH))
+ tiny_sys_file_close(&store->log);
+ if (!tiny_sys_path_replace(temporary, store->log_path))
{
status = TINY_STORE_IO_ERROR;
}
- store->log = CreateFileW(store->log_path,
- GENERIC_READ | GENERIC_WRITE,
- FILE_SHARE_READ,
- NULL,
- OPEN_ALWAYS,
- FILE_ATTRIBUTE_NORMAL,
- NULL);
- if (store->log == INVALID_HANDLE_VALUE)
+ else
+ {
+ log_replaced = true;
+ }
+ if (tiny_sys_file_open(&store->log, store->log_path, TINY_SYS_FILE_READ_WRITE_CREATE)
+ != TINY_SYS_OK)
{
status = TINY_STORE_IO_ERROR;
}
@@ -1038,15 +996,20 @@ tiny_store_rebuild(tiny_store_t *store, tiny_rebuild_report_t *report)
}
store->entry_count = write_index;
store->last_sequence = max_sequence;
- GetFileSizeEx(store->log, &after);
+ (void)tiny_sys_file_size(&store->log, &after);
report->messages_indexed = write_index;
report->records_removed = removed;
- report->bytes_before = (uint64_t)before.QuadPart;
- report->bytes_after = (uint64_t)after.QuadPart;
+ report->bytes_before = before;
+ report->bytes_after = after;
status = write_snapshot(store);
}
if (status != TINY_STORE_OK)
{
+ if (!log_replaced)
+ {
+ memcpy(store->generation, previous_generation, sizeof(store->generation));
+ store->has_generation = previous_has_generation;
+ }
set_error(store,
"compaction did not complete; the previous valid log is retained when replacement "
"had not begun");
@@ -1058,28 +1021,30 @@ tiny_store_rebuild(tiny_store_t *store, tiny_rebuild_report_t *report)
void
tiny_store_health(const tiny_store_t *store, tiny_store_health_t *health)
{
- LARGE_INTEGER size;
+ uint64_t size;
size_t index;
+ if (health == NULL)
+ {
+ return;
+ }
memset(health, 0, sizeof(*health));
- if (GetFileSizeEx(store->log, &size))
+ if (store == NULL)
+ {
+ return;
+ }
+ if (tiny_sys_file_size(&store->log, &size))
{
- health->log_size = (uint64_t)size.QuadPart;
+ health->log_size = size;
}
{
- HANDLE f = CreateFileW(store->snapshot_path,
- GENERIC_READ,
- FILE_SHARE_READ,
- NULL,
- OPEN_EXISTING,
- FILE_ATTRIBUTE_NORMAL,
- NULL);
- if (f != INVALID_HANDLE_VALUE)
+ tiny_sys_file_t file;
+ if (tiny_sys_file_open(&file, store->snapshot_path, TINY_SYS_FILE_READ) == TINY_SYS_OK)
{
- if (GetFileSizeEx(f, &size))
+ if (tiny_sys_file_size(&file, &size))
{
- health->snapshot_size = (uint64_t)size.QuadPart;
+ health->snapshot_size = size;
}
- CloseHandle(f);
+ tiny_sys_file_close(&file);
}
}
for (index = 0; index < store->entry_count; ++index)
@@ -1096,16 +1061,16 @@ tiny_store_health(const tiny_store_t *store, tiny_store_health_t *health)
health->last_sequence = store->last_sequence;
health->recovered_tail = store->recovered_tail;
health->snapshot_valid = store->snapshot_valid;
- strncpy_s(health->last_error, sizeof(health->last_error), store->last_error, _TRUNCATE);
+ snprintf(health->last_error, sizeof(health->last_error), "%s", store->last_error);
}
size_t
tiny_store_entry_count(const tiny_store_t *store)
{
- return store->entry_count;
+ return store == NULL ? 0 : store->entry_count;
}
const tiny_store_entry_t *
tiny_store_entry(const tiny_store_t *store, size_t index)
{
- return index < store->entry_count ? &store->entries[index] : NULL;
+ return store != NULL && index < store->entry_count ? &store->entries[index] : NULL;
}
diff --git a/src/storage/store.h b/src/storage/store.h
index cc14a80..619f86f 100644
--- a/src/storage/store.h
+++ b/src/storage/store.h
@@ -4,7 +4,6 @@
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
-#include <wchar.h>
#include "domain/message.h"
@@ -47,7 +46,7 @@ typedef struct tiny_store_health
char last_error[160];
} tiny_store_health_t;
-tiny_store_status_t tiny_store_open(tiny_store_t **store, const wchar_t *directory);
+tiny_store_status_t tiny_store_open(tiny_store_t **store, const char *directory);
void tiny_store_close(tiny_store_t *store);
const char *tiny_store_status_name(tiny_store_status_t status);