feature: implement Win32 file handling functions including open, close, read, write, and permissions management
This commit is contained in:
parent
7cd3fe2905
commit
053f4f9043
4 changed files with 500 additions and 0 deletions
35
Source/DrangPlatform/Source/win32/common.c
Normal file
35
Source/DrangPlatform/Source/win32/common.c
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
#include "common.h"
|
||||
#include <drang/error.h>
|
||||
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
|
||||
}
|
||||
}
|
||||
11
Source/DrangPlatform/Source/win32/common.h
Normal file
11
Source/DrangPlatform/Source/win32/common.h
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
#pragma once
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <Windows.h>
|
||||
#include <drang/platform.h>
|
||||
|
||||
|
||||
DRANG_BEGIN_DECLS
|
||||
|
||||
DRANG_PLATFORM_API int win32_error_to_drang(DWORD error);
|
||||
|
||||
DRANG_END_DECLS
|
||||
438
Source/DrangPlatform/Source/win32/fs/file.c
Normal file
438
Source/DrangPlatform/Source/win32/fs/file.c
Normal file
|
|
@ -0,0 +1,438 @@
|
|||
#include "drang/sync.h"
|
||||
#include "internal.h"
|
||||
#include "win32/common.h"
|
||||
#include <drang/alloc.h>
|
||||
#include <drang/error.h>
|
||||
|
||||
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()
|
||||
}
|
||||
16
Source/DrangPlatform/Source/win32/fs/internal.h
Normal file
16
Source/DrangPlatform/Source/win32/fs/internal.h
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#pragma once
|
||||
#include <drang/fs.h>
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <Windows.h>
|
||||
|
||||
#include "../sync/internal.h"
|
||||
|
||||
|
||||
|
||||
struct drang_fs_file
|
||||
{
|
||||
HANDLE handle;
|
||||
drang_fs_mode_t mode;
|
||||
struct drang_rwlock lock;
|
||||
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue