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