#include "sys/system.h" #include "sys/encoding.h" #include #include #include #include #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; static tiny_sys_io_counters_t tiny_sys_io_counters; void tiny_sys_io_counters_reset(void) { memset(&tiny_sys_io_counters, 0, sizeof(tiny_sys_io_counters)); } void tiny_sys_io_counters_get(tiny_sys_io_counters_t *counters) { if (counters != NULL) { *counters = tiny_sys_io_counters; } } 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 #include #include 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; if (result) { tiny_sys_io_counters.write_calls++; tiny_sys_io_counters.write_bytes += 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) { bool result = tiny_sys_file_valid(file) && !tiny_sys_test_should_fail(TINY_SYS_TEST_TRUNCATE) && SetEndOfFile(native_file(file)) != 0; tiny_sys_io_counters.truncate_calls += result ? 1U : 0U; return result; } bool tiny_sys_file_flush(tiny_sys_file_t *file) { bool result = tiny_sys_file_valid(file) && !tiny_sys_test_should_fail(TINY_SYS_TEST_FLUSH) && FlushFileBuffers(native_file(file)) != 0; tiny_sys_io_counters.flush_calls += result ? 1U : 0U; return result; } 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); tiny_sys_io_counters.replace_calls += result ? 1U : 0U; 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 #include #include #include #include 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; if (count >= 0) { tiny_sys_io_counters.write_calls++; tiny_sys_io_counters.write_bytes += *actual; } 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); if (position >= 0 && ftruncate(native_file(file), position) == 0) { tiny_sys_io_counters.truncate_calls++; return true; } return false; } bool tiny_sys_file_flush(tiny_sys_file_t *file) { bool result = tiny_sys_file_valid(file) && !tiny_sys_test_should_fail(TINY_SYS_TEST_FLUSH) && fsync(native_file(file)) == 0; tiny_sys_io_counters.flush_calls += result ? 1U : 0U; return result; } bool tiny_sys_path_replace(const char *source, const char *destination) { bool result = source != NULL && destination != NULL && !tiny_sys_test_should_fail(TINY_SYS_TEST_REPLACE) && rename(source, destination) == 0; tiny_sys_io_counters.replace_calls += result ? 1U : 0U; return result; } 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)); }