summaryrefslogtreecommitdiff
path: root/src/sys/system.c
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/system.c
parent4c6a831aa2a57e2bcc20aa3fc2f1b7172f5be285 (diff)
refactor: MCP層をmcpc APIベースに刷新
- 独自JSON-RPC処理をmcpcへ置き換え - 各MCPツールを個別ソースへ分離 - 共通コンテキストと登録処理を整理 - mcpc互換修正と統合テストを更新
Diffstat (limited to 'src/sys/system.c')
-rw-r--r--src/sys/system.c890
1 files changed, 890 insertions, 0 deletions
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));
+}