diff options
| author | tanaka takashi <isazaka@gmail.com> | 2026-06-29 21:43:54 +0900 |
|---|---|---|
| committer | tanaka takashi <isazaka@gmail.com> | 2026-06-30 01:23:51 +0900 |
| commit | 4bc721c3bf7589b519c5730d093f704ce56e3f09 (patch) | |
| tree | 38da055ab15fd70f5c92584560ea73486e5ddce0 /src/sys | |
| parent | 4c6a831aa2a57e2bcc20aa3fc2f1b7172f5be285 (diff) | |
refactor: MCP層をmcpc APIベースに刷新
- 独自JSON-RPC処理をmcpcへ置き換え
- 各MCPツールを個別ソースへ分離
- 共通コンテキストと登録処理を整理
- mcpc互換修正と統合テストを更新
Diffstat (limited to 'src/sys')
| -rw-r--r-- | src/sys/README.md | 21 | ||||
| -rw-r--r-- | src/sys/encoding.c | 208 | ||||
| -rw-r--r-- | src/sys/encoding.h | 24 | ||||
| -rw-r--r-- | src/sys/system.c | 890 | ||||
| -rw-r--r-- | src/sys/system.h | 90 |
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 |
