diff --git a/Source/DrangPlatform/Source/win32/common.c b/Source/DrangPlatform/Source/win32/common.c new file mode 100644 index 0000000..ba1e89a --- /dev/null +++ b/Source/DrangPlatform/Source/win32/common.c @@ -0,0 +1,35 @@ +#include "common.h" +#include +int win32_error_to_drang(const DWORD error) +{ + switch (error) { + case ERROR_SUCCESS: + return DRANG_EOK; + case ERROR_FILE_NOT_FOUND: + case ERROR_PATH_NOT_FOUND: + case ERROR_INVALID_DRIVE: + return DRANG_ENOENT; + case ERROR_ACCESS_DENIED: + case ERROR_SHARING_VIOLATION: + case ERROR_LOCK_VIOLATION: + return DRANG_EPERM; + case ERROR_ALREADY_EXISTS: + case ERROR_FILE_EXISTS: + return DRANG_EBUSY; + case ERROR_NOT_ENOUGH_MEMORY: + case ERROR_OUTOFMEMORY: + return DRANG_ENOMEM; + case ERROR_DISK_FULL: + return DRANG_ENOSPC; + case ERROR_INVALID_PARAMETER: + return DRANG_EINVAL; + case ERROR_WRITE_PROTECT: + return DRANG_EROFS; + case ERROR_IO_DEVICE: + case ERROR_IO_PENDING: + return DRANG_EIO; + case ERROR_CRC: + default: + return DRANG_EPLATFORM; // Unknown or unhandled error + } +} diff --git a/Source/DrangPlatform/Source/win32/common.h b/Source/DrangPlatform/Source/win32/common.h new file mode 100644 index 0000000..30768f6 --- /dev/null +++ b/Source/DrangPlatform/Source/win32/common.h @@ -0,0 +1,11 @@ +#pragma once +#define WIN32_LEAN_AND_MEAN +#include +#include + + +DRANG_BEGIN_DECLS + +DRANG_PLATFORM_API int win32_error_to_drang(DWORD error); + +DRANG_END_DECLS diff --git a/Source/DrangPlatform/Source/win32/fs/file.c b/Source/DrangPlatform/Source/win32/fs/file.c new file mode 100644 index 0000000..c0ecdc0 --- /dev/null +++ b/Source/DrangPlatform/Source/win32/fs/file.c @@ -0,0 +1,438 @@ +#include "drang/sync.h" +#include "internal.h" +#include "win32/common.h" +#include +#include + +static void mode_to_access(drang_fs_mode_t mode, DWORD *out_access, DWORD *out_creation) +{ + DWORD access = 0; + DWORD creation = 0; + if (mode & drang_fs_mode_read) { + access |= GENERIC_READ; + } + if (mode & drang_fs_mode_write) { + access |= GENERIC_WRITE; + } + if (mode & drang_fs_mode_append) { + access |= FILE_APPEND_DATA; + } + if (mode & drang_fs_mode_create) { + if (mode & drang_fs_mode_exclusive) { + creation = CREATE_NEW; + } else { + creation = OPEN_ALWAYS; + } + } else if (mode & drang_fs_mode_truncate) { + creation = TRUNCATE_EXISTING; + } else { + creation = OPEN_EXISTING; + } + *out_access = access; + *out_creation = creation; +} + +int drang_fs_open(const char *path, drang_fs_mode_t mode, drang_fs_file_t **out_file) +{ + struct drang_fs_file *file = NULL; + HANDLE handle = INVALID_HANDLE_VALUE; + DRANG_BEGIN_TRY() + DRANG_FAIL_IF_NULL(path, DRANG_EINVAL); + DRANG_FAIL_IF_NULL(out_file, DRANG_EINVAL); + + DWORD access = 0; + DWORD creation = 0; + mode_to_access(mode, &access, &creation); + handle = CreateFileA(path, + access, + 0, //TODO: Implement sharing + NULL, + creation, + FILE_ATTRIBUTE_NORMAL, + NULL); + if (handle == INVALID_HANDLE_VALUE) { + DRANG_FAIL(win32_error_to_drang(GetLastError())); + } + + file = DRANG_TRY_CALLOC_T(struct drang_fs_file); + file->handle = handle; + file->mode = mode; + DRANG_TRY(drang_rwlock_init(&file->lock)); + *out_file = file; + + DRANG_CATCH(_) + { + if (handle != INVALID_HANDLE_VALUE) { + CloseHandle(handle); + } + if (file) { + drang_rwlock_fini(&file->lock); + drang_free(file); + } + } + DRANG_END_TRY() +} + +int drang_fs_close(drang_fs_file_t *file) +{ + bool locked = false; + DRANG_BEGIN_TRY() + DRANG_FAIL_IF_NULL(file, DRANG_EINVAL); + + DRANG_TRY(drang_rwlock_wrlock(&file->lock)); + locked = true; + if (file->handle != INVALID_HANDLE_VALUE) { + DRANG_CHECK(CloseHandle(file->handle), win32_error_to_drang(GetLastError())); + file->handle = INVALID_HANDLE_VALUE; + } + + drang_rwlock_fini(&file->lock); + locked = false; + drang_free(file); + + DRANG_CATCH(_) + { + if (locked) { + drang_rwlock_wrunlock(&file->lock); + } + } + + DRANG_END_TRY() +} + +int drang_fs_read(drang_fs_file_t *file, void *buffer, size_t count, size_t size, size_t *bytes_read) +{ + bool locked = false; + DRANG_BEGIN_TRY() + DRANG_FAIL_IF_NULL(file, DRANG_EINVAL); + DRANG_FAIL_IF_NULL(buffer, DRANG_EINVAL); + if (count == 0 || size == 0) { + if (bytes_read) + *bytes_read = 0; + DRANG_RETURN(); + } + DRANG_CHECK(file->mode & drang_fs_mode_read, DRANG_EPERM); + + DRANG_TRY(drang_rwlock_rdlock(&file->lock)); + locked = true; + DWORD total_bytes = 0; + DRANG_CHECK(!DRANG_MUL_OVERFLOW(count, size, &total_bytes), DRANG_EOVERFLOW); + + DWORD read_bytes = 0; + if (!ReadFile(file->handle, buffer, total_bytes, &read_bytes, NULL)) { + DRANG_FAIL(win32_error_to_drang(GetLastError())); + } + + if (bytes_read) { + *bytes_read = (size_t) read_bytes; + } + + drang_rwlock_rdunlock(&file->lock); + locked = false; + + DRANG_CATCH(_) + { + if (locked) { + drang_rwlock_rdunlock(&file->lock); + } + } + + DRANG_END_TRY() +} + +int drang_fs_write(drang_fs_file_t *file, const void *buffer, size_t count, size_t size, size_t *bytes_written) +{ + bool locked = false; + DRANG_BEGIN_TRY() + DRANG_FAIL_IF_NULL(file, DRANG_EINVAL); + DRANG_FAIL_IF_NULL(buffer, DRANG_EINVAL); + if (count == 0 || size == 0) { + if (bytes_written) + *bytes_written = 0; + DRANG_RETURN(); + } + DRANG_CHECK(file->mode & (drang_fs_mode_write | drang_fs_mode_append), DRANG_EPERM); + DRANG_TRY(drang_rwlock_wrlock(&file->lock)); + locked = true; + DWORD total_bytes = 0; + DRANG_CHECK(!DRANG_MUL_OVERFLOW(count, size, &total_bytes), DRANG_EOVERFLOW); + if (file->mode & drang_fs_mode_append) { + const LARGE_INTEGER zero = {0}; + if (!SetFilePointerEx(file->handle, zero, NULL, FILE_END)) { + DRANG_FAIL(win32_error_to_drang(GetLastError())); + } + } + DWORD written_bytes = 0; + if (!WriteFile(file->handle, buffer, total_bytes, &written_bytes, NULL)) { + DRANG_FAIL(win32_error_to_drang(GetLastError())); + } + if (bytes_written) { + *bytes_written = (size_t) written_bytes; + } + drang_rwlock_wrunlock(&file->lock); + locked = false; + DRANG_CATCH(_) + { + if (locked) { + drang_rwlock_wrunlock(&file->lock); + } + } + DRANG_END_TRY() +} + +int drang_fs_seek(drang_fs_file_t *file, int64_t offset, drang_seek_origin_t origin) +{ + bool locked = false; + DRANG_BEGIN_TRY() + DRANG_FAIL_IF_NULL(file, DRANG_EINVAL); + DRANG_TRY(drang_rwlock_wrlock(&file->lock)); + locked = true; + DWORD move_method = 0; + switch (origin) { + case drang_seek_origin_begin: + move_method = FILE_BEGIN; + break; + case drang_seek_origin_current: + move_method = FILE_CURRENT; + break; + case drang_seek_origin_end: + move_method = FILE_END; + break; + } + LARGE_INTEGER li; + li.QuadPart = offset; + if (!SetFilePointerEx(file->handle, li, NULL, move_method)) { + DRANG_FAIL(win32_error_to_drang(GetLastError())); + } + drang_rwlock_wrunlock(&file->lock); + locked = false; + DRANG_CATCH(_) + { + if (locked) { + drang_rwlock_wrunlock(&file->lock); + } + } + DRANG_END_TRY() +} + +int drang_fs_tell(drang_fs_file_t *file, uint64_t *position) +{ + bool locked = false; + DRANG_BEGIN_TRY() + DRANG_FAIL_IF_NULL(file, DRANG_EINVAL); + DRANG_FAIL_IF_NULL(position, DRANG_EINVAL); + DRANG_TRY(drang_rwlock_rdlock(&file->lock)); + locked = true; + LARGE_INTEGER zero = {0}; + LARGE_INTEGER pos = {0}; + if (!SetFilePointerEx(file->handle, zero, &pos, FILE_CURRENT)) { + DRANG_FAIL(win32_error_to_drang(GetLastError())); + } + *position = (uint64_t) pos.QuadPart; + drang_rwlock_rdunlock(&file->lock); + locked = false; + DRANG_CATCH(_) + { + if (locked) { + drang_rwlock_rdunlock(&file->lock); + } + } + DRANG_END_TRY() +} + +int drang_fs_flush(drang_fs_file_t *file) +{ + bool locked = false; + DRANG_BEGIN_TRY() + DRANG_FAIL_IF_NULL(file, DRANG_EINVAL); + DRANG_CHECK(file->mode & (drang_fs_mode_write | drang_fs_mode_append), DRANG_EPERM); + DRANG_TRY(drang_rwlock_wrlock(&file->lock)); + locked = true; + if (!FlushFileBuffers(file->handle)) { + DRANG_FAIL(win32_error_to_drang(GetLastError())); + } + drang_rwlock_wrunlock(&file->lock); + locked = false; + DRANG_CATCH(_) + { + if (locked) { + drang_rwlock_wrunlock(&file->lock); + } + } + DRANG_END_TRY() +} +int drang_fs_truncate(drang_fs_file_t *file, uint64_t size) +{ + bool locked = false; + DRANG_BEGIN_TRY() + DRANG_FAIL_IF_NULL(file, DRANG_EINVAL); + DRANG_CHECK(file->mode & drang_fs_mode_write, DRANG_EPERM); + DRANG_TRY(drang_rwlock_wrlock(&file->lock)); + locked = true; + LARGE_INTEGER li; + li.QuadPart = (LONGLONG) size; + if (!SetFilePointerEx(file->handle, li, NULL, FILE_BEGIN)) { + DRANG_FAIL(win32_error_to_drang(GetLastError())); + } + if (!SetEndOfFile(file->handle)) { + DRANG_FAIL(win32_error_to_drang(GetLastError())); + } + drang_rwlock_wrunlock(&file->lock); + locked = false; + DRANG_CATCH(_) + { + if (locked) { + drang_rwlock_wrunlock(&file->lock); + } + } + DRANG_END_TRY() +} + +int drang_fs_create(const char *path, drang_fs_permissions_t permissions) +{ + HANDLE handle = INVALID_HANDLE_VALUE; + DRANG_UNUSED(permissions); + DRANG_BEGIN_TRY() + DRANG_FAIL_IF_NULL(path, DRANG_EINVAL); + + // Create the file with CREATE_NEW to fail if it already exists + handle = CreateFileA(path, + GENERIC_WRITE, // Need write access to create + 0, // No sharing during creation + NULL, // Default security attributes + CREATE_NEW, // Fail if file already exists + FILE_ATTRIBUTE_NORMAL, // Normal file attributes + NULL); // No template file + + DRANG_CHECK(handle != INVALID_HANDLE_VALUE, win32_error_to_drang(GetLastError())); + + // Close the file handle immediately since we're just creating an empty file + CloseHandle(handle); + + // Note: Windows doesn't support Unix-style permissions directly. + // The permissions parameter is ignored on Windows as documented behavior + // may vary between platforms. For a full implementation, we could use + // SetFileSecurity() with ACLs, but that's complex and not typically needed. + + DRANG_CATCH(_) + { + if (handle != INVALID_HANDLE_VALUE) { + CloseHandle(handle); + } + } + DRANG_END_TRY() +} + +int drang_fs_remove(const char *path) +{ + DRANG_BEGIN_TRY() + DRANG_FAIL_IF_NULL(path, DRANG_EINVAL); + + if (!DeleteFileA(path)) { + DRANG_FAIL(win32_error_to_drang(GetLastError())); + } + + // No cleanup needed for this function + DRANG_END_TRY_IGNORE() +} + +int drang_fs_move(const char *old_path, const char *new_path) +{ + DRANG_BEGIN_TRY() + DRANG_FAIL_IF_NULL(old_path, DRANG_EINVAL); + DRANG_FAIL_IF_NULL(new_path, DRANG_EINVAL); + if (!MoveFileA(old_path, new_path)) { + DRANG_FAIL(win32_error_to_drang(GetLastError())); + } + DRANG_END_TRY_IGNORE() +} + +bool drang_fs_exists(const char *path) +{ + if (path == NULL) + return false; + const DWORD attrs = GetFileAttributesA(path); + if (attrs == INVALID_FILE_ATTRIBUTES) { + false; + } + + return true; +} + +int drang_fs_stat(const char *path, struct drang_fs_stat *out_stat) +{ + DRANG_BEGIN_TRY() + DRANG_FAIL_IF_NULL(path, DRANG_EINVAL); + DRANG_FAIL_IF_NULL(out_stat, DRANG_EINVAL); + + WIN32_FILE_ATTRIBUTE_DATA file_data; + if (!GetFileAttributesExA(path, GetFileExInfoStandard, &file_data)) { + DRANG_FAIL(win32_error_to_drang(GetLastError())); + } + + // Determine file type + if (file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + out_stat->type = drang_fs_type_directory; + out_stat->size = 0; // Directories have no size + } else if (file_data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { + // This might be a symlink, but we need to check further + out_stat->type = drang_fs_type_symlink; + out_stat->size = ((uint64_t)file_data.nFileSizeHigh << 32) | file_data.nFileSizeLow; + } else { + out_stat->type = drang_fs_type_file; + out_stat->size = ((uint64_t)file_data.nFileSizeHigh << 32) | file_data.nFileSizeLow; + } + + // Convert Windows permissions to Unix-style permissions + // Windows doesn't have the same permission model, so we'll approximate + out_stat->permissions = DRANG_FS_PERM_USER_READ | DRANG_FS_PERM_GROUP_READ | DRANG_FS_PERM_OTHER_READ; + + if (!(file_data.dwFileAttributes & FILE_ATTRIBUTE_READONLY)) { + out_stat->permissions |= DRANG_FS_PERM_USER_WRITE | DRANG_FS_PERM_GROUP_WRITE | DRANG_FS_PERM_OTHER_WRITE; + } + + // For directories and executables, add execute permissions + if ((file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) || + (strlen(path) > 4 && + (_stricmp(path + strlen(path) - 4, ".exe") == 0 || + _stricmp(path + strlen(path) - 4, ".bat") == 0 || + _stricmp(path + strlen(path) - 4, ".cmd") == 0))) { + out_stat->permissions |= DRANG_FS_PERM_USER_EXEC | DRANG_FS_PERM_GROUP_EXEC | DRANG_FS_PERM_OTHER_EXEC; + } + + // Convert FILETIME to Unix timestamp (seconds since epoch) + // Windows FILETIME is 100-nanosecond intervals since January 1, 1601 + // Unix timestamp is seconds since January 1, 1970 + const uint64_t WINDOWS_EPOCH_DIFF = 11644473600ULL; // Seconds between 1601 and 1970 + + // Convert creation time + uint64_t created_100ns = ((uint64_t)file_data.ftCreationTime.dwHighDateTime << 32) | + file_data.ftCreationTime.dwLowDateTime; + out_stat->created_time = (created_100ns / 10000000ULL) - WINDOWS_EPOCH_DIFF; + + // Convert modification time + uint64_t modified_100ns = ((uint64_t)file_data.ftLastWriteTime.dwHighDateTime << 32) | + file_data.ftLastWriteTime.dwLowDateTime; + out_stat->modified_time = (modified_100ns / 10000000ULL) - WINDOWS_EPOCH_DIFF; + + // Convert access time + uint64_t accessed_100ns = ((uint64_t)file_data.ftLastAccessTime.dwHighDateTime << 32) | + file_data.ftLastAccessTime.dwLowDateTime; + out_stat->accessed_time = (accessed_100ns / 10000000ULL) - WINDOWS_EPOCH_DIFF; + + DRANG_END_TRY_IGNORE() +} + +int drang_fs_set_permissions(const char *path, drang_fs_permissions_t permissions) +{ + DRANG_BEGIN_TRY() + DRANG_FAIL_IF_NULL(path, DRANG_EINVAL); + + // Windows doesn't support Unix-style permissions directly. + // The permissions parameter is ignored on Windows as documented behavior + // may vary between platforms. For a full implementation, we could use + // SetFileSecurity() with ACLs, but that's complex and not typically needed. + DRANG_UNUSED(permissions); + + DRANG_END_TRY_IGNORE() +} \ No newline at end of file diff --git a/Source/DrangPlatform/Source/win32/fs/internal.h b/Source/DrangPlatform/Source/win32/fs/internal.h new file mode 100644 index 0000000..0551fe5 --- /dev/null +++ b/Source/DrangPlatform/Source/win32/fs/internal.h @@ -0,0 +1,16 @@ +#pragma once +#include +#define WIN32_LEAN_AND_MEAN +#include + +#include "../sync/internal.h" + + + +struct drang_fs_file +{ + HANDLE handle; + drang_fs_mode_t mode; + struct drang_rwlock lock; + +}; \ No newline at end of file