feature: implement Win32 file handling functions including open, close, read, write, and permissions management

This commit is contained in:
MechSlayer 2025-09-09 03:58:22 +02:00
parent 7cd3fe2905
commit 053f4f9043
4 changed files with 500 additions and 0 deletions

View 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
}
}

View 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

View 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()
}

View 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;
};