summaryrefslogtreecommitdiff
path: root/src/sys
diff options
context:
space:
mode:
authortanaka takashi <isazaka@gmail.com>2026-06-29 21:43:54 +0900
committertanaka takashi <isazaka@gmail.com>2026-06-30 01:23:51 +0900
commit4bc721c3bf7589b519c5730d093f704ce56e3f09 (patch)
tree38da055ab15fd70f5c92584560ea73486e5ddce0 /src/sys
parent4c6a831aa2a57e2bcc20aa3fc2f1b7172f5be285 (diff)
refactor: MCP層をmcpc APIベースに刷新
- 独自JSON-RPC処理をmcpcへ置き換え - 各MCPツールを個別ソースへ分離 - 共通コンテキストと登録処理を整理 - mcpc互換修正と統合テストを更新
Diffstat (limited to 'src/sys')
-rw-r--r--src/sys/README.md21
-rw-r--r--src/sys/encoding.c208
-rw-r--r--src/sys/encoding.h24
-rw-r--r--src/sys/system.c890
-rw-r--r--src/sys/system.h90
5 files changed, 1233 insertions, 0 deletions
diff --git a/src/sys/README.md b/src/sys/README.md
new file mode 100644
index 0000000..22802d5
--- /dev/null
+++ b/src/sys/README.md
@@ -0,0 +1,21 @@
+# System abstraction
+
+`system.h` is the only OS-facing API used by the application and storage
+layers. Paths and process arguments are UTF-8 strings on every platform.
+
+The API owns these responsibilities:
+
+- process argument conversion and absolute/path-join operations;
+- directory creation, temporary directories, and path removal/replacement;
+- opaque file handles, locking, read/write/seek/truncate/flush operations;
+- wall-clock and monotonic milliseconds;
+- cryptographically secure random bytes.
+
+Returned strings are heap allocated and must be released with `free()`. Files
+must be initialized or opened through this API and closed with
+`tiny_sys_file_close()`.
+
+Platform selection is intentionally confined to the single conditional block
+in `system.c`. Windows uses wide Win32 calls internally; non-Windows builds use
+the POSIX implementation. No Win32 or POSIX type is exposed by `system.h`.
+
diff --git a/src/sys/encoding.c b/src/sys/encoding.c
new file mode 100644
index 0000000..ccdcc0a
--- /dev/null
+++ b/src/sys/encoding.c
@@ -0,0 +1,208 @@
+#include "sys/encoding.h"
+
+#include "base/allocator.h"
+
+#include <stdbool.h>
+#include <string.h>
+
+static bool
+utf8_codepoint(const unsigned char *input, size_t length, size_t *used, uint32_t *codepoint)
+{
+ const unsigned char first = input[0];
+ size_t width, index;
+ uint32_t value;
+ if (first < 0x80)
+ {
+ width = 1;
+ value = first;
+ }
+ else if (first >= 0xc2 && first <= 0xdf)
+ {
+ width = 2;
+ value = first & 0x1f;
+ }
+ else if (first >= 0xe0 && first <= 0xef)
+ {
+ width = 3;
+ value = first & 0x0f;
+ }
+ else if (first >= 0xf0 && first <= 0xf4)
+ {
+ width = 4;
+ value = first & 0x07;
+ }
+ else
+ {
+ return false;
+ }
+ if (width > length)
+ {
+ return false;
+ }
+ for (index = 1; index < width; ++index)
+ {
+ if ((input[index] & 0xc0) != 0x80)
+ {
+ return false;
+ }
+ value = (value << 6) | (input[index] & 0x3f);
+ }
+ if ((width == 3 && ((first == 0xe0 && input[1] < 0xa0) || (first == 0xed && input[1] >= 0xa0)))
+ || (width == 4
+ && ((first == 0xf0 && input[1] < 0x90) || (first == 0xf4 && input[1] >= 0x90))))
+ {
+ return false;
+ }
+ *used = width;
+ *codepoint = value;
+ return true;
+}
+
+tiny_encoding_status_t
+tiny_utf8_to_utf16(const char *input, size_t input_length, uint16_t **output, size_t *output_length)
+{
+ size_t offset = 0, units = 0;
+ uint16_t *result;
+ if (output == NULL || output_length == NULL)
+ {
+ return TINY_ENCODING_INVALID;
+ }
+ *output = NULL;
+ *output_length = 0;
+ if (input == NULL && input_length != 0)
+ {
+ return TINY_ENCODING_INVALID;
+ }
+ while (offset < input_length)
+ {
+ size_t used;
+ uint32_t point;
+ if (!utf8_codepoint(
+ (const unsigned char *)input + offset, input_length - offset, &used, &point))
+ {
+ return TINY_ENCODING_INVALID;
+ }
+ if (units > SIZE_MAX - (point >= 0x10000 ? 2U : 1U))
+ {
+ return TINY_ENCODING_OVERFLOW;
+ }
+ units += point >= 0x10000 ? 2U : 1U;
+ offset += used;
+ }
+ if (units == SIZE_MAX || units + 1 > SIZE_MAX / sizeof(*result))
+ {
+ return TINY_ENCODING_OVERFLOW;
+ }
+ result = malloc((units + 1) * sizeof(*result));
+ if (result == NULL)
+ {
+ return TINY_ENCODING_NO_MEMORY;
+ }
+ offset = 0;
+ units = 0;
+ while (offset < input_length)
+ {
+ size_t used;
+ uint32_t point;
+ (void)utf8_codepoint(
+ (const unsigned char *)input + offset, input_length - offset, &used, &point);
+ if (point < 0x10000)
+ {
+ result[units++] = (uint16_t)point;
+ }
+ else
+ {
+ point -= 0x10000;
+ result[units++] = (uint16_t)(0xd800 + (point >> 10));
+ result[units++] = (uint16_t)(0xdc00 + (point & 0x3ff));
+ }
+ offset += used;
+ }
+ result[units] = 0;
+ *output = result;
+ *output_length = units;
+ return TINY_ENCODING_OK;
+}
+
+tiny_encoding_status_t
+tiny_utf16_to_utf8(const uint16_t *input, size_t input_length, char **output, size_t *output_length)
+{
+ size_t index = 0, bytes = 0;
+ char *result;
+ if (output == NULL || output_length == NULL)
+ {
+ return TINY_ENCODING_INVALID;
+ }
+ *output = NULL;
+ *output_length = 0;
+ if (input == NULL && input_length != 0)
+ {
+ return TINY_ENCODING_INVALID;
+ }
+ while (index < input_length)
+ {
+ uint32_t point = input[index++];
+ if (point >= 0xd800 && point <= 0xdbff)
+ {
+ if (index >= input_length || input[index] < 0xdc00 || input[index] > 0xdfff)
+ {
+ return TINY_ENCODING_INVALID;
+ }
+ point = 0x10000 + ((point - 0xd800) << 10) + (input[index++] - 0xdc00);
+ }
+ else if (point >= 0xdc00 && point <= 0xdfff)
+ {
+ return TINY_ENCODING_INVALID;
+ }
+ if (bytes > SIZE_MAX - (point < 0x80 ? 1U : point < 0x800 ? 2U : point < 0x10000 ? 3U : 4U))
+ {
+ return TINY_ENCODING_OVERFLOW;
+ }
+ bytes += point < 0x80 ? 1U : point < 0x800 ? 2U : point < 0x10000 ? 3U : 4U;
+ }
+ if (bytes == SIZE_MAX)
+ {
+ return TINY_ENCODING_OVERFLOW;
+ }
+ result = malloc(bytes + 1);
+ if (result == NULL)
+ {
+ return TINY_ENCODING_NO_MEMORY;
+ }
+ index = 0;
+ bytes = 0;
+ while (index < input_length)
+ {
+ uint32_t point = input[index++];
+ if (point >= 0xd800 && point <= 0xdbff)
+ {
+ point = 0x10000 + ((point - 0xd800) << 10) + (input[index++] - 0xdc00);
+ }
+ if (point < 0x80)
+ {
+ result[bytes++] = (char)point;
+ }
+ else if (point < 0x800)
+ {
+ result[bytes++] = (char)(0xc0 | (point >> 6));
+ result[bytes++] = (char)(0x80 | (point & 0x3f));
+ }
+ else if (point < 0x10000)
+ {
+ result[bytes++] = (char)(0xe0 | (point >> 12));
+ result[bytes++] = (char)(0x80 | ((point >> 6) & 0x3f));
+ result[bytes++] = (char)(0x80 | (point & 0x3f));
+ }
+ else
+ {
+ result[bytes++] = (char)(0xf0 | (point >> 18));
+ result[bytes++] = (char)(0x80 | ((point >> 12) & 0x3f));
+ result[bytes++] = (char)(0x80 | ((point >> 6) & 0x3f));
+ result[bytes++] = (char)(0x80 | (point & 0x3f));
+ }
+ }
+ result[bytes] = '\0';
+ *output = result;
+ *output_length = bytes;
+ return TINY_ENCODING_OK;
+}
diff --git a/src/sys/encoding.h b/src/sys/encoding.h
new file mode 100644
index 0000000..386d319
--- /dev/null
+++ b/src/sys/encoding.h
@@ -0,0 +1,24 @@
+#ifndef TINY_CONVMEM_SYS_ENCODING_H
+#define TINY_CONVMEM_SYS_ENCODING_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+typedef enum tiny_encoding_status
+{
+ TINY_ENCODING_OK = 0,
+ TINY_ENCODING_INVALID,
+ TINY_ENCODING_NO_MEMORY,
+ TINY_ENCODING_OVERFLOW
+} tiny_encoding_status_t;
+
+tiny_encoding_status_t tiny_utf8_to_utf16(const char *input,
+ size_t input_length,
+ uint16_t **output,
+ size_t *output_length);
+tiny_encoding_status_t tiny_utf16_to_utf8(const uint16_t *input,
+ size_t input_length,
+ char **output,
+ size_t *output_length);
+
+#endif
diff --git a/src/sys/system.c b/src/sys/system.c
new file mode 100644
index 0000000..63d8297
--- /dev/null
+++ b/src/sys/system.c
@@ -0,0 +1,890 @@
+#include "sys/system.h"
+#include "sys/encoding.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "base/allocator.h"
+
+typedef struct tiny_sys_test_state
+{
+ size_t calls[TINY_SYS_TEST_OPERATION_COUNT];
+ size_t failures[TINY_SYS_TEST_OPERATION_COUNT];
+ uint32_t max_transfer;
+} tiny_sys_test_state_t;
+
+static tiny_sys_test_state_t tiny_sys_test_state;
+
+void
+tiny_sys_test_reset(void)
+{
+ memset(&tiny_sys_test_state, 0, sizeof(tiny_sys_test_state));
+}
+
+bool
+tiny_sys_test_fail_after(tiny_sys_test_operation_t operation, size_t call_index)
+{
+ if (operation < TINY_SYS_TEST_OPEN || operation >= TINY_SYS_TEST_OPERATION_COUNT
+ || call_index == 0)
+ {
+ return false;
+ }
+ tiny_sys_test_state.calls[operation] = 0;
+ tiny_sys_test_state.failures[operation] = call_index;
+ return true;
+}
+
+void
+tiny_sys_test_max_transfer(uint32_t length)
+{
+ tiny_sys_test_state.max_transfer = length;
+}
+
+static bool
+tiny_sys_test_should_fail(tiny_sys_test_operation_t operation)
+{
+ ++tiny_sys_test_state.calls[operation];
+ return tiny_sys_test_state.failures[operation] != 0
+ && tiny_sys_test_state.calls[operation] == tiny_sys_test_state.failures[operation];
+}
+
+static uint32_t
+tiny_sys_test_transfer_length(uint32_t length)
+{
+ return tiny_sys_test_state.max_transfer != 0 && length > tiny_sys_test_state.max_transfer
+ ? tiny_sys_test_state.max_transfer
+ : length;
+}
+
+#if defined(_WIN32)
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <bcrypt.h>
+#include <shellapi.h>
+
+static wchar_t *
+utf8_to_wide(const char *value)
+{
+ uint16_t *result;
+ size_t length;
+ _Static_assert(sizeof(wchar_t) == sizeof(uint16_t), "Windows wchar_t must be UTF-16");
+ if (value == NULL)
+ {
+ return NULL;
+ }
+ if (tiny_utf8_to_utf16(value, strlen(value), &result, &length) != TINY_ENCODING_OK)
+ {
+ return NULL;
+ }
+ return (wchar_t *)result;
+}
+
+static char *
+wide_to_utf8(const wchar_t *value)
+{
+ char *result;
+ size_t length;
+ if (value == NULL)
+ {
+ return NULL;
+ }
+ if (tiny_utf16_to_utf8((const uint16_t *)value, wcslen(value), &result, &length)
+ != TINY_ENCODING_OK)
+ {
+ return NULL;
+ }
+ return result;
+}
+
+static HANDLE
+native_file(const tiny_sys_file_t *file)
+{
+ return (HANDLE)file->native;
+}
+
+bool
+tiny_sys_arguments_init(tiny_sys_arguments_t *arguments, int argc, char **argv)
+{
+ wchar_t **wide_arguments;
+ int wide_count, index;
+ (void)argc;
+ (void)argv;
+ if (arguments == NULL)
+ {
+ return false;
+ }
+ memset(arguments, 0, sizeof(*arguments));
+ wide_arguments = CommandLineToArgvW(GetCommandLineW(), &wide_count);
+ if (wide_arguments == NULL)
+ {
+ return false;
+ }
+ arguments->values = calloc((size_t)wide_count, sizeof(*arguments->values));
+ if (arguments->values == NULL)
+ {
+ LocalFree(wide_arguments);
+ return false;
+ }
+ arguments->count = wide_count;
+ for (index = 0; index < wide_count; ++index)
+ {
+ arguments->values[index] = wide_to_utf8(wide_arguments[index]);
+ if (arguments->values[index] == NULL)
+ {
+ LocalFree(wide_arguments);
+ tiny_sys_arguments_free(arguments);
+ return false;
+ }
+ }
+ LocalFree(wide_arguments);
+ return true;
+}
+
+char *
+tiny_sys_path_absolute(const char *path)
+{
+ wchar_t *wide_path = utf8_to_wide(path);
+ wchar_t *wide_result;
+ char *result;
+ DWORD required;
+ if (wide_path == NULL)
+ {
+ return NULL;
+ }
+ required = GetFullPathNameW(wide_path, 0, NULL, NULL);
+ if (required == 0)
+ {
+ free(wide_path);
+ return NULL;
+ }
+ wide_result = malloc((size_t)required * sizeof(*wide_result));
+ if (wide_result == NULL || GetFullPathNameW(wide_path, required, wide_result, NULL) == 0)
+ {
+ free(wide_path);
+ free(wide_result);
+ return NULL;
+ }
+ result = wide_to_utf8(wide_result);
+ free(wide_path);
+ free(wide_result);
+ return result;
+}
+
+char *
+tiny_sys_path_join(const char *directory, const char *name)
+{
+ size_t left, right, required;
+ if (directory == NULL || name == NULL)
+ {
+ return NULL;
+ }
+ left = strlen(directory);
+ right = strlen(name);
+ const bool separator = left > 0 && directory[left - 1] != '\\' && directory[left - 1] != '/';
+ if (left > SIZE_MAX - right - (separator ? 2u : 1u))
+ {
+ return NULL;
+ }
+ required = left + right + (separator ? 2u : 1u);
+ char *result = malloc(required);
+ if (result == NULL)
+ {
+ return NULL;
+ }
+ memcpy(result, directory, left);
+ if (separator)
+ {
+ result[left] = '\\';
+ }
+ memcpy(result + left + (separator ? 1 : 0), name, right + 1);
+ return result;
+}
+
+tiny_sys_status_t
+tiny_sys_directory_create(const char *path)
+{
+ wchar_t *wide_path = utf8_to_wide(path);
+ DWORD error;
+ if (wide_path == NULL)
+ {
+ return TINY_SYS_ERROR;
+ }
+ if (CreateDirectoryW(wide_path, NULL))
+ {
+ free(wide_path);
+ return TINY_SYS_OK;
+ }
+ error = GetLastError();
+ free(wide_path);
+ return error == ERROR_ALREADY_EXISTS ? TINY_SYS_OK : TINY_SYS_ERROR;
+}
+
+tiny_sys_status_t
+tiny_sys_file_open(tiny_sys_file_t *file, const char *path, tiny_sys_file_mode_t mode)
+{
+ wchar_t *wide_path = utf8_to_wide(path);
+ DWORD access = 0, share = 0, creation = OPEN_EXISTING, error;
+ HANDLE handle;
+ if (file == NULL)
+ {
+ free(wide_path);
+ return TINY_SYS_ERROR;
+ }
+ tiny_sys_file_init(file);
+ if (wide_path == NULL || tiny_sys_test_should_fail(TINY_SYS_TEST_OPEN))
+ {
+ free(wide_path);
+ return TINY_SYS_ERROR;
+ }
+ switch (mode)
+ {
+ case TINY_SYS_FILE_READ:
+ access = GENERIC_READ;
+ share = FILE_SHARE_READ;
+ break;
+ case TINY_SYS_FILE_WRITE_NEW:
+ access = GENERIC_WRITE;
+ creation = CREATE_ALWAYS;
+ break;
+ case TINY_SYS_FILE_READ_WRITE:
+ access = GENERIC_READ | GENERIC_WRITE;
+ share = FILE_SHARE_READ;
+ break;
+ case TINY_SYS_FILE_READ_WRITE_CREATE:
+ access = GENERIC_READ | GENERIC_WRITE;
+ share = FILE_SHARE_READ;
+ creation = OPEN_ALWAYS;
+ break;
+ case TINY_SYS_FILE_LOCK:
+ access = GENERIC_READ | GENERIC_WRITE;
+ creation = OPEN_ALWAYS;
+ break;
+ default:
+ free(wide_path);
+ return TINY_SYS_ERROR;
+ }
+ handle = CreateFileW(wide_path, access, share, NULL, creation, FILE_ATTRIBUTE_NORMAL, NULL);
+ free(wide_path);
+ if (handle != INVALID_HANDLE_VALUE)
+ {
+ file->native = (intptr_t)handle;
+ return TINY_SYS_OK;
+ }
+ error = GetLastError();
+ if (error == ERROR_FILE_NOT_FOUND || error == ERROR_PATH_NOT_FOUND)
+ {
+ return TINY_SYS_NOT_FOUND;
+ }
+ if (error == ERROR_SHARING_VIOLATION || error == ERROR_LOCK_VIOLATION)
+ {
+ return TINY_SYS_BUSY;
+ }
+ return TINY_SYS_ERROR;
+}
+
+void
+tiny_sys_file_init(tiny_sys_file_t *file)
+{
+ if (file != NULL)
+ {
+ file->native = 0;
+ }
+}
+
+bool
+tiny_sys_file_valid(const tiny_sys_file_t *file)
+{
+ return file != NULL && file->native != 0;
+}
+
+void
+tiny_sys_file_close(tiny_sys_file_t *file)
+{
+ if (tiny_sys_file_valid(file))
+ {
+ CloseHandle(native_file(file));
+ tiny_sys_file_init(file);
+ }
+}
+
+bool
+tiny_sys_file_read(tiny_sys_file_t *file, void *data, uint32_t length, uint32_t *actual)
+{
+ DWORD count = 0;
+ bool result;
+ if (actual == NULL)
+ {
+ return false;
+ }
+ *actual = 0;
+ if (!tiny_sys_file_valid(file) || (data == NULL && length != 0)
+ || tiny_sys_test_should_fail(TINY_SYS_TEST_READ))
+ {
+ return false;
+ }
+ length = tiny_sys_test_transfer_length(length);
+ result = ReadFile(native_file(file), data, length, &count, NULL) != 0;
+ *actual = count;
+ return result;
+}
+
+bool
+tiny_sys_file_write(tiny_sys_file_t *file, const void *data, uint32_t length, uint32_t *actual)
+{
+ DWORD count = 0;
+ bool result;
+ if (actual == NULL)
+ {
+ return false;
+ }
+ *actual = 0;
+ if (!tiny_sys_file_valid(file) || (data == NULL && length != 0)
+ || tiny_sys_test_should_fail(TINY_SYS_TEST_WRITE))
+ {
+ return false;
+ }
+ length = tiny_sys_test_transfer_length(length);
+ result = WriteFile(native_file(file), data, length, &count, NULL) != 0;
+ *actual = count;
+ return result;
+}
+
+bool
+tiny_sys_file_seek(tiny_sys_file_t *file, int64_t offset, tiny_sys_seek_origin_t origin)
+{
+ LARGE_INTEGER distance;
+ DWORD method;
+ if (!tiny_sys_file_valid(file) || tiny_sys_test_should_fail(TINY_SYS_TEST_SEEK))
+ {
+ return false;
+ }
+ distance.QuadPart = offset;
+ switch (origin)
+ {
+ case TINY_SYS_SEEK_BEGIN:
+ method = FILE_BEGIN;
+ break;
+ case TINY_SYS_SEEK_CURRENT:
+ method = FILE_CURRENT;
+ break;
+ case TINY_SYS_SEEK_END:
+ method = FILE_END;
+ break;
+ default:
+ return false;
+ }
+ return SetFilePointerEx(native_file(file), distance, NULL, method) != 0;
+}
+
+bool
+tiny_sys_file_size(const tiny_sys_file_t *file, uint64_t *size)
+{
+ LARGE_INTEGER native_size;
+ if (size == NULL)
+ {
+ return false;
+ }
+ *size = 0;
+ if (!tiny_sys_file_valid(file) || tiny_sys_test_should_fail(TINY_SYS_TEST_SIZE)
+ || !GetFileSizeEx(native_file(file), &native_size))
+ {
+ return false;
+ }
+ *size = (uint64_t)native_size.QuadPart;
+ return true;
+}
+
+bool
+tiny_sys_file_truncate(tiny_sys_file_t *file)
+{
+ return tiny_sys_file_valid(file) && !tiny_sys_test_should_fail(TINY_SYS_TEST_TRUNCATE)
+ && SetEndOfFile(native_file(file)) != 0;
+}
+
+bool
+tiny_sys_file_flush(tiny_sys_file_t *file)
+{
+ return tiny_sys_file_valid(file) && !tiny_sys_test_should_fail(TINY_SYS_TEST_FLUSH)
+ && FlushFileBuffers(native_file(file)) != 0;
+}
+
+bool
+tiny_sys_path_replace(const char *source, const char *destination)
+{
+ wchar_t *wide_source = utf8_to_wide(source);
+ wchar_t *wide_destination = utf8_to_wide(destination);
+ bool result = false;
+ if (wide_source != NULL && wide_destination != NULL
+ && !tiny_sys_test_should_fail(TINY_SYS_TEST_REPLACE))
+ {
+ result = MoveFileExW(wide_source,
+ wide_destination,
+ MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)
+ != 0;
+ }
+ free(wide_source);
+ free(wide_destination);
+ return result;
+}
+
+bool
+tiny_sys_file_remove(const char *path)
+{
+ wchar_t *wide_path = utf8_to_wide(path);
+ bool result = wide_path != NULL && DeleteFileW(wide_path) != 0;
+ free(wide_path);
+ return result;
+}
+
+bool
+tiny_sys_directory_remove(const char *path)
+{
+ wchar_t *wide_path = utf8_to_wide(path);
+ bool result = wide_path != NULL && RemoveDirectoryW(wide_path) != 0;
+ free(wide_path);
+ return result;
+}
+
+char *
+tiny_sys_temp_directory_create(const char *prefix)
+{
+ wchar_t temporary[MAX_PATH], candidate[MAX_PATH], wide_prefix[4] = L"tcm";
+ char *result;
+ size_t index;
+ if (prefix == NULL)
+ {
+ return NULL;
+ }
+ for (index = 0; index < 3 && prefix[index] != '\0'; ++index)
+ {
+ wide_prefix[index] = (wchar_t)(unsigned char)prefix[index];
+ }
+ if (GetTempPathW(MAX_PATH, temporary) == 0
+ || GetTempFileNameW(temporary, wide_prefix, 0, candidate) == 0 || !DeleteFileW(candidate)
+ || !CreateDirectoryW(candidate, NULL))
+ {
+ return NULL;
+ }
+ result = wide_to_utf8(candidate);
+ if (result == NULL)
+ {
+ RemoveDirectoryW(candidate);
+ }
+ return result;
+}
+
+uint64_t
+tiny_sys_now_milliseconds(void)
+{
+ FILETIME time;
+ ULARGE_INTEGER ticks;
+ GetSystemTimeAsFileTime(&time);
+ ticks.LowPart = time.dwLowDateTime;
+ ticks.HighPart = time.dwHighDateTime;
+ return ticks.QuadPart / 10000ULL - 11644473600000ULL;
+}
+
+uint64_t
+tiny_sys_monotonic_milliseconds(void)
+{
+ return GetTickCount64();
+}
+
+bool
+tiny_sys_random_bytes(void *data, size_t length)
+{
+ return (data != NULL || length == 0) && !tiny_sys_test_should_fail(TINY_SYS_TEST_RANDOM)
+ && length <= ULONG_MAX
+ && BCryptGenRandom(NULL, data, (ULONG)length, BCRYPT_USE_SYSTEM_PREFERRED_RNG) == 0;
+}
+
+#else
+
+#include <fcntl.h>
+#include <sys/file.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <unistd.h>
+
+static char *
+duplicate_string(const char *value)
+{
+ const size_t length = strlen(value);
+ char *copy = malloc(length + 1);
+ if (copy != NULL)
+ {
+ memcpy(copy, value, length + 1);
+ }
+ return copy;
+}
+
+static int
+native_file(const tiny_sys_file_t *file)
+{
+ return (int)file->native;
+}
+
+bool
+tiny_sys_arguments_init(tiny_sys_arguments_t *arguments, int argc, char **argv)
+{
+ int index;
+ if (arguments == NULL || argc < 0 || (argc != 0 && argv == NULL))
+ {
+ return false;
+ }
+ memset(arguments, 0, sizeof(*arguments));
+ arguments->values = calloc((size_t)argc, sizeof(*arguments->values));
+ if (arguments->values == NULL)
+ {
+ return false;
+ }
+ arguments->count = argc;
+ for (index = 0; index < argc; ++index)
+ {
+ arguments->values[index] = duplicate_string(argv[index]);
+ if (arguments->values[index] == NULL)
+ {
+ tiny_sys_arguments_free(arguments);
+ return false;
+ }
+ }
+ return true;
+}
+
+char *
+tiny_sys_path_join(const char *directory, const char *name)
+{
+ size_t left, right, required;
+ if (directory == NULL || name == NULL)
+ {
+ return NULL;
+ }
+ left = strlen(directory);
+ right = strlen(name);
+ const bool separator = left > 0 && directory[left - 1] != '/';
+ if (left > SIZE_MAX - right - (separator ? 2u : 1u))
+ {
+ return NULL;
+ }
+ required = left + right + (separator ? 2u : 1u);
+ char *result = malloc(required);
+ if (result == NULL)
+ {
+ return NULL;
+ }
+ memcpy(result, directory, left);
+ if (separator)
+ {
+ result[left] = '/';
+ }
+ memcpy(result + left + (separator ? 1 : 0), name, right + 1);
+ return result;
+}
+
+char *
+tiny_sys_path_absolute(const char *path)
+{
+ char *working_directory, *result;
+ if (path == NULL)
+ {
+ return NULL;
+ }
+ if (path[0] == '/')
+ {
+ return duplicate_string(path);
+ }
+ working_directory = getcwd(NULL, 0);
+ if (working_directory == NULL)
+ {
+ return NULL;
+ }
+ result = tiny_sys_path_join(working_directory, path);
+ free(working_directory);
+ return result;
+}
+
+tiny_sys_status_t
+tiny_sys_directory_create(const char *path)
+{
+ return mkdir(path, 0700) == 0 || errno == EEXIST ? TINY_SYS_OK : TINY_SYS_ERROR;
+}
+
+void
+tiny_sys_file_init(tiny_sys_file_t *file)
+{
+ if (file != NULL)
+ {
+ file->native = -1;
+ }
+}
+
+bool
+tiny_sys_file_valid(const tiny_sys_file_t *file)
+{
+ return file != NULL && file->native >= 0;
+}
+
+tiny_sys_status_t
+tiny_sys_file_open(tiny_sys_file_t *file, const char *path, tiny_sys_file_mode_t mode)
+{
+ int flags, descriptor;
+ if (file == NULL)
+ {
+ return TINY_SYS_ERROR;
+ }
+ tiny_sys_file_init(file);
+ if (path == NULL || tiny_sys_test_should_fail(TINY_SYS_TEST_OPEN))
+ {
+ return TINY_SYS_ERROR;
+ }
+ switch (mode)
+ {
+ case TINY_SYS_FILE_READ:
+ flags = O_RDONLY;
+ break;
+ case TINY_SYS_FILE_WRITE_NEW:
+ flags = O_WRONLY | O_CREAT | O_TRUNC;
+ break;
+ case TINY_SYS_FILE_READ_WRITE:
+ flags = O_RDWR;
+ break;
+ case TINY_SYS_FILE_READ_WRITE_CREATE:
+ case TINY_SYS_FILE_LOCK:
+ flags = O_RDWR | O_CREAT;
+ break;
+ default:
+ return TINY_SYS_ERROR;
+ }
+ descriptor = open(path, flags, 0600);
+ if (descriptor < 0)
+ {
+ return errno == ENOENT ? TINY_SYS_NOT_FOUND : TINY_SYS_ERROR;
+ }
+ if (mode == TINY_SYS_FILE_LOCK && flock(descriptor, LOCK_EX | LOCK_NB) != 0)
+ {
+ close(descriptor);
+ return errno == EWOULDBLOCK ? TINY_SYS_BUSY : TINY_SYS_ERROR;
+ }
+ file->native = descriptor;
+ return TINY_SYS_OK;
+}
+
+void
+tiny_sys_file_close(tiny_sys_file_t *file)
+{
+ if (tiny_sys_file_valid(file))
+ {
+ close(native_file(file));
+ tiny_sys_file_init(file);
+ }
+}
+
+bool
+tiny_sys_file_read(tiny_sys_file_t *file, void *data, uint32_t length, uint32_t *actual)
+{
+ ssize_t count;
+ if (actual == NULL)
+ {
+ return false;
+ }
+ *actual = 0;
+ if (!tiny_sys_file_valid(file) || (data == NULL && length != 0)
+ || tiny_sys_test_should_fail(TINY_SYS_TEST_READ))
+ {
+ return false;
+ }
+ length = tiny_sys_test_transfer_length(length);
+ do
+ {
+ count = read(native_file(file), data, length);
+ }
+ while (count < 0 && errno == EINTR);
+ *actual = count > 0 ? (uint32_t)count : 0;
+ return count >= 0;
+}
+
+bool
+tiny_sys_file_write(tiny_sys_file_t *file, const void *data, uint32_t length, uint32_t *actual)
+{
+ ssize_t count;
+ if (actual == NULL)
+ {
+ return false;
+ }
+ *actual = 0;
+ if (!tiny_sys_file_valid(file) || (data == NULL && length != 0)
+ || tiny_sys_test_should_fail(TINY_SYS_TEST_WRITE))
+ {
+ return false;
+ }
+ length = tiny_sys_test_transfer_length(length);
+ do
+ {
+ count = write(native_file(file), data, length);
+ }
+ while (count < 0 && errno == EINTR);
+ *actual = count > 0 ? (uint32_t)count : 0;
+ return count >= 0;
+}
+
+bool
+tiny_sys_file_seek(tiny_sys_file_t *file, int64_t offset, tiny_sys_seek_origin_t origin)
+{
+ static const int origins[] = { SEEK_SET, SEEK_CUR, SEEK_END };
+ if (!tiny_sys_file_valid(file) || origin < TINY_SYS_SEEK_BEGIN || origin > TINY_SYS_SEEK_END
+ || tiny_sys_test_should_fail(TINY_SYS_TEST_SEEK))
+ {
+ return false;
+ }
+ return lseek(native_file(file), (off_t)offset, origins[origin]) >= 0;
+}
+
+bool
+tiny_sys_file_size(const tiny_sys_file_t *file, uint64_t *size)
+{
+ struct stat information;
+ if (size == NULL)
+ {
+ return false;
+ }
+ *size = 0;
+ if (!tiny_sys_file_valid(file) || tiny_sys_test_should_fail(TINY_SYS_TEST_SIZE)
+ || fstat(native_file(file), &information) != 0)
+ {
+ return false;
+ }
+ *size = (uint64_t)information.st_size;
+ return true;
+}
+
+bool
+tiny_sys_file_truncate(tiny_sys_file_t *file)
+{
+ off_t position;
+ if (!tiny_sys_file_valid(file) || tiny_sys_test_should_fail(TINY_SYS_TEST_TRUNCATE))
+ {
+ return false;
+ }
+ position = lseek(native_file(file), 0, SEEK_CUR);
+ return position >= 0 && ftruncate(native_file(file), position) == 0;
+}
+
+bool
+tiny_sys_file_flush(tiny_sys_file_t *file)
+{
+ return tiny_sys_file_valid(file) && !tiny_sys_test_should_fail(TINY_SYS_TEST_FLUSH)
+ && fsync(native_file(file)) == 0;
+}
+
+bool
+tiny_sys_path_replace(const char *source, const char *destination)
+{
+ return source != NULL && destination != NULL && !tiny_sys_test_should_fail(TINY_SYS_TEST_REPLACE)
+ && rename(source, destination) == 0;
+}
+
+bool
+tiny_sys_file_remove(const char *path)
+{
+ return unlink(path) == 0;
+}
+
+bool
+tiny_sys_directory_remove(const char *path)
+{
+ return rmdir(path) == 0;
+}
+
+char *
+tiny_sys_temp_directory_create(const char *prefix)
+{
+ const char *base = getenv("TMPDIR");
+ char *pattern, *result;
+ size_t length;
+ if (prefix == NULL)
+ {
+ return NULL;
+ }
+ if (base == NULL)
+ {
+ base = "/tmp";
+ }
+ length = strlen(base) + strlen(prefix) + 9;
+ pattern = malloc(length);
+ if (pattern == NULL)
+ {
+ return NULL;
+ }
+ snprintf(pattern, length, "%s/%sXXXXXX", base, prefix);
+ result = mkdtemp(pattern);
+ if (result == NULL)
+ {
+ free(pattern);
+ return NULL;
+ }
+ return pattern;
+}
+
+uint64_t
+tiny_sys_now_milliseconds(void)
+{
+ struct timespec time;
+ clock_gettime(CLOCK_REALTIME, &time);
+ return (uint64_t)time.tv_sec * 1000ULL + (uint64_t)time.tv_nsec / 1000000ULL;
+}
+
+uint64_t
+tiny_sys_monotonic_milliseconds(void)
+{
+ struct timespec time;
+ clock_gettime(CLOCK_MONOTONIC, &time);
+ return (uint64_t)time.tv_sec * 1000ULL + (uint64_t)time.tv_nsec / 1000000ULL;
+}
+
+bool
+tiny_sys_random_bytes(void *data, size_t length)
+{
+ tiny_sys_file_t file;
+ unsigned char *position = data;
+ size_t total = 0;
+ if ((data == NULL && length != 0) || tiny_sys_test_should_fail(TINY_SYS_TEST_RANDOM)
+ || tiny_sys_file_open(&file, "/dev/urandom", TINY_SYS_FILE_READ) != TINY_SYS_OK)
+ {
+ return false;
+ }
+ while (total < length)
+ {
+ uint32_t actual = 0;
+ const uint32_t requested
+ = length - total > UINT32_MAX ? UINT32_MAX : (uint32_t)(length - total);
+ if (!tiny_sys_file_read(&file, position + total, requested, &actual) || actual == 0)
+ {
+ tiny_sys_file_close(&file);
+ return false;
+ }
+ total += actual;
+ }
+ tiny_sys_file_close(&file);
+ return true;
+}
+
+#endif
+
+void
+tiny_sys_arguments_free(tiny_sys_arguments_t *arguments)
+{
+ int index;
+ if (arguments == NULL)
+ {
+ return;
+ }
+ for (index = 0; index < arguments->count; ++index)
+ {
+ free(arguments->values[index]);
+ }
+ free(arguments->values);
+ memset(arguments, 0, sizeof(*arguments));
+}
diff --git a/src/sys/system.h b/src/sys/system.h
new file mode 100644
index 0000000..b526ec5
--- /dev/null
+++ b/src/sys/system.h
@@ -0,0 +1,90 @@
+#ifndef TINY_CONVMEM_SYS_SYSTEM_H
+#define TINY_CONVMEM_SYS_SYSTEM_H
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+typedef enum tiny_sys_status
+{
+ TINY_SYS_OK = 0,
+ TINY_SYS_NOT_FOUND,
+ TINY_SYS_BUSY,
+ TINY_SYS_ERROR
+} tiny_sys_status_t;
+
+typedef enum tiny_sys_file_mode
+{
+ TINY_SYS_FILE_READ = 0,
+ TINY_SYS_FILE_WRITE_NEW,
+ TINY_SYS_FILE_READ_WRITE,
+ TINY_SYS_FILE_READ_WRITE_CREATE,
+ TINY_SYS_FILE_LOCK
+} tiny_sys_file_mode_t;
+
+typedef enum tiny_sys_seek_origin
+{
+ TINY_SYS_SEEK_BEGIN = 0,
+ TINY_SYS_SEEK_CURRENT,
+ TINY_SYS_SEEK_END
+} tiny_sys_seek_origin_t;
+
+typedef struct tiny_sys_file
+{
+ intptr_t native;
+} tiny_sys_file_t;
+
+typedef struct tiny_sys_arguments
+{
+ int count;
+ char **values;
+} tiny_sys_arguments_t;
+
+/* Internal test seam. A call index of 1 fails the next matching operation. */
+typedef enum tiny_sys_test_operation
+{
+ TINY_SYS_TEST_OPEN = 0,
+ TINY_SYS_TEST_READ,
+ TINY_SYS_TEST_WRITE,
+ TINY_SYS_TEST_SEEK,
+ TINY_SYS_TEST_SIZE,
+ TINY_SYS_TEST_TRUNCATE,
+ TINY_SYS_TEST_FLUSH,
+ TINY_SYS_TEST_REPLACE,
+ TINY_SYS_TEST_RANDOM,
+ TINY_SYS_TEST_OPERATION_COUNT
+} tiny_sys_test_operation_t;
+
+void tiny_sys_test_reset(void);
+bool tiny_sys_test_fail_after(tiny_sys_test_operation_t operation, size_t call_index);
+void tiny_sys_test_max_transfer(uint32_t length);
+
+bool tiny_sys_arguments_init(tiny_sys_arguments_t *arguments, int argc, char **argv);
+void tiny_sys_arguments_free(tiny_sys_arguments_t *arguments);
+
+char *tiny_sys_path_absolute(const char *path);
+char *tiny_sys_path_join(const char *directory, const char *name);
+char *tiny_sys_temp_directory_create(const char *prefix);
+tiny_sys_status_t tiny_sys_directory_create(const char *path);
+bool tiny_sys_file_remove(const char *path);
+bool tiny_sys_directory_remove(const char *path);
+bool tiny_sys_path_replace(const char *source, const char *destination);
+
+void tiny_sys_file_init(tiny_sys_file_t *file);
+bool tiny_sys_file_valid(const tiny_sys_file_t *file);
+tiny_sys_status_t
+tiny_sys_file_open(tiny_sys_file_t *file, const char *path, tiny_sys_file_mode_t mode);
+void tiny_sys_file_close(tiny_sys_file_t *file);
+bool tiny_sys_file_read(tiny_sys_file_t *file, void *data, uint32_t length, uint32_t *actual);
+bool
+tiny_sys_file_write(tiny_sys_file_t *file, const void *data, uint32_t length, uint32_t *actual);
+bool tiny_sys_file_seek(tiny_sys_file_t *file, int64_t offset, tiny_sys_seek_origin_t origin);
+bool tiny_sys_file_size(const tiny_sys_file_t *file, uint64_t *size);
+bool tiny_sys_file_truncate(tiny_sys_file_t *file);
+bool tiny_sys_file_flush(tiny_sys_file_t *file);
+
+uint64_t tiny_sys_now_milliseconds(void);
+uint64_t tiny_sys_monotonic_milliseconds(void);
+bool tiny_sys_random_bytes(void *data, size_t length);
+
+#endif