orca/src/platform/win32_io.c

607 lines
16 KiB
C

/*************************************************************************
*
* Orca
* Copyright 2023 Martin Fouilleul and the Orca project contributors
* See LICENSE.txt for licensing information
*
**************************************************************************/
#include <errno.h>
#include <limits.h>
#include <shlwapi.h>
#include <winioctl.h>
#include "platform_io_common.c"
#include "platform_io_internal.c"
#include "win32_string_helpers.h"
oc_io_error oc_io_raw_last_error()
{
oc_io_error error = 0;
int winError = GetLastError();
switch(winError)
{
case ERROR_SUCCESS:
error = OC_IO_OK;
break;
case ERROR_ACCESS_DENIED:
error = OC_IO_ERR_PERM;
break;
case ERROR_FILE_NOT_FOUND:
case ERROR_PATH_NOT_FOUND:
case ERROR_INVALID_DRIVE:
case ERROR_DIRECTORY:
error = OC_IO_ERR_NO_ENTRY;
break;
case ERROR_TOO_MANY_OPEN_FILES:
error = OC_IO_ERR_MAX_FILES;
break;
case ERROR_NOT_ENOUGH_MEMORY:
case ERROR_OUTOFMEMORY:
error = OC_IO_ERR_MEM;
break;
case ERROR_DEV_NOT_EXIST:
error = OC_IO_ERR_NO_DEVICE;
break;
case ERROR_FILE_EXISTS:
case ERROR_ALREADY_EXISTS:
error = OC_IO_ERR_EXISTS;
break;
case ERROR_BUFFER_OVERFLOW:
case ERROR_FILENAME_EXCED_RANGE:
error = OC_IO_ERR_PATH_LENGTH;
break;
case ERROR_FILE_TOO_LARGE:
error = OC_IO_ERR_FILE_SIZE;
break;
//TODO: complete
default:
error = OC_IO_ERR_UNKNOWN;
break;
}
return (error);
}
oc_str16 win32_path_from_handle_null_terminated(oc_arena* arena, HANDLE handle)
{
oc_str16 res = { 0 };
res.len = GetFinalPathNameByHandleW(handle, NULL, 0, FILE_NAME_NORMALIZED);
if(res.len)
{
res.ptr = oc_arena_push_array(arena, u16, res.len);
if(!GetFinalPathNameByHandleW(handle, res.ptr, res.len, FILE_NAME_NORMALIZED))
{
res.len = 0;
res.ptr = 0;
}
}
return (res);
}
typedef HANDLE oc_file_desc;
oc_file_desc oc_file_desc_nil()
{
return (INVALID_HANDLE_VALUE);
}
bool oc_file_desc_is_nil(oc_file_desc fd)
{
return (fd == NULL || fd == INVALID_HANDLE_VALUE);
}
static oc_str16 win32_get_path_at_null_terminated(oc_arena* arena, oc_file_desc dirFd, oc_str8 path)
{
oc_str16 result = { 0 };
oc_arena_scope scratch = oc_scratch_begin_next(arena);
oc_str16 dirPathW = win32_path_from_handle_null_terminated(scratch.arena, dirFd);
oc_str16 pathW = oc_win32_utf8_to_wide(scratch.arena, path);
if(dirPathW.len && pathW.len)
{
u64 fullPathWSize = dirPathW.len + pathW.len;
LPWSTR fullPathW = oc_arena_push_array(scratch.arena, u16, fullPathWSize);
memcpy(fullPathW, dirPathW.ptr, (dirPathW.len - 1) * sizeof(u16));
fullPathW[dirPathW.len - 1] = '\\';
memcpy(fullPathW + dirPathW.len, pathW.ptr, pathW.len * sizeof(u16));
result.len = fullPathWSize;
result.ptr = oc_arena_push_array(arena, wchar_t, result.len);
if(PathCanonicalizeW(result.ptr, fullPathW))
{
result.len = lstrlenW(result.ptr);
}
else
{
result.ptr = 0;
result.len = 0;
}
}
oc_scratch_end(scratch);
return (result);
}
oc_file_desc oc_io_raw_open_at(oc_file_desc dirFd, oc_str8 path, oc_file_access accessRights, oc_file_open_flags openFlags)
{
HANDLE handle = INVALID_HANDLE_VALUE;
// convert flags
DWORD win32AccessFlags = 0;
DWORD win32ShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE;
DWORD win32CreateFlags = 0;
DWORD win32AttributeFlags = FILE_ATTRIBUTE_NORMAL
| FILE_FLAG_BACKUP_SEMANTICS;
if(accessRights & OC_FILE_ACCESS_READ)
{
win32AccessFlags |= GENERIC_READ;
}
if(accessRights & OC_FILE_ACCESS_WRITE)
{
if(accessRights & OC_FILE_OPEN_APPEND)
{
win32AccessFlags |= FILE_APPEND_DATA;
}
else
{
win32AccessFlags |= GENERIC_WRITE;
}
}
if(openFlags & OC_FILE_OPEN_TRUNCATE)
{
if(openFlags & OC_FILE_OPEN_CREATE)
{
win32CreateFlags |= CREATE_ALWAYS;
}
else
{
win32CreateFlags |= TRUNCATE_EXISTING;
}
}
if(openFlags & OC_FILE_OPEN_CREATE)
{
if(!(win32CreateFlags & CREATE_ALWAYS))
{
win32CreateFlags |= OPEN_ALWAYS;
}
}
if(!(win32CreateFlags & OPEN_ALWAYS)
&& !(win32CreateFlags & CREATE_ALWAYS)
&& !(win32CreateFlags & TRUNCATE_EXISTING))
{
win32CreateFlags |= OPEN_EXISTING;
}
if(openFlags & OC_FILE_OPEN_SYMLINK)
{
win32AttributeFlags |= FILE_FLAG_OPEN_REPARSE_POINT;
}
oc_arena_scope scratch = oc_scratch_begin();
oc_str16 pathW = oc_win32_utf8_to_wide(scratch.arena, path);
if(dirFd == NULL || dirFd == INVALID_HANDLE_VALUE)
{
handle = CreateFileW(pathW.ptr, win32AccessFlags, win32ShareMode, NULL, win32CreateFlags, win32AttributeFlags, NULL);
}
else
{
oc_str16 fullPathW = win32_get_path_at_null_terminated(scratch.arena, dirFd, path);
if(fullPathW.len)
{
handle = CreateFileW(fullPathW.ptr, win32AccessFlags, win32ShareMode, NULL, win32CreateFlags, win32AttributeFlags, NULL);
}
}
oc_scratch_end(scratch);
return (handle);
}
void oc_io_raw_close(oc_file_desc fd)
{
CloseHandle(fd);
}
bool oc_io_raw_file_exists_at(oc_file_desc dirFd, oc_str8 path, oc_file_open_flags openFlags)
{
bool result = false;
oc_file_desc fd = oc_io_raw_open_at(dirFd, path, OC_FILE_ACCESS_NONE, (openFlags & OC_FILE_OPEN_SYMLINK));
if(!oc_file_desc_is_nil(fd))
{
result = true;
oc_io_raw_close(fd);
}
return (result);
}
enum
{
OC_NTP_01_JAN_1601 = -9435484800LL,
OC_WIN32_TICKS_PER_SECOND = 10000000LL
};
oc_datestamp oc_datestamp_from_win32_filetime(FILETIME ft)
{
oc_datestamp d = { 0 };
i64 win32Ticks = (((u64)ft.dwHighDateTime) << 32) | (u64)ft.dwLowDateTime;
i64 win32Seconds = win32Ticks / OC_WIN32_TICKS_PER_SECOND;
u64 win32Rem = 0;
if(win32Ticks < 0)
{
win32Seconds -= OC_WIN32_TICKS_PER_SECOND;
win32Rem = win32Ticks - win32Seconds * OC_WIN32_TICKS_PER_SECOND;
}
else
{
win32Rem = win32Ticks % OC_WIN32_TICKS_PER_SECOND;
}
d.seconds = win32Seconds + OC_NTP_01_JAN_1601;
d.fraction = (win32Rem * (1ULL << 32)) / OC_WIN32_TICKS_PER_SECOND;
return (d);
}
oc_io_error oc_io_raw_fstat(oc_file_desc fd, oc_file_status* status)
{
oc_io_error error = OC_IO_OK;
BY_HANDLE_FILE_INFORMATION info;
if(!GetFileInformationByHandle(fd, &info))
{
error = oc_io_raw_last_error();
}
else
{
status->size = (((u64)info.nFileSizeHigh) << 32) | ((u64)info.nFileSizeLow);
status->uid = ((u64)info.nFileIndexHigh << 32) | ((u64)info.nFileIndexLow);
DWORD attrRegularSet = FILE_ATTRIBUTE_ARCHIVE
| FILE_ATTRIBUTE_COMPRESSED
| FILE_ATTRIBUTE_ENCRYPTED
| FILE_ATTRIBUTE_HIDDEN
| FILE_ATTRIBUTE_NORMAL
| FILE_ATTRIBUTE_NOT_CONTENT_INDEXED
| FILE_ATTRIBUTE_OFFLINE
| FILE_ATTRIBUTE_READONLY
| FILE_ATTRIBUTE_SPARSE_FILE
| FILE_ATTRIBUTE_SYSTEM
| FILE_ATTRIBUTE_TEMPORARY;
if((info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT))
{
FILE_ATTRIBUTE_TAG_INFO tagInfo;
if(!GetFileInformationByHandleEx(fd, FileAttributeTagInfo, &tagInfo, sizeof(tagInfo)))
{
error = oc_io_raw_last_error();
}
else if(tagInfo.ReparseTag == IO_REPARSE_TAG_SYMLINK)
{
status->type = OC_FILE_SYMLINK;
}
else
{
status->type = OC_FILE_UNKNOWN;
}
}
else if(info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
status->type = OC_FILE_DIRECTORY;
}
else if(info.dwFileAttributes & attrRegularSet)
{
status->type = OC_FILE_REGULAR;
}
else
{
//TODO: might want to check for socket/block/character devices? (otoh MS STL impl. doesn't seem to do it)
status->type = OC_FILE_UNKNOWN;
}
status->perm = OC_FILE_OWNER_READ | OC_FILE_GROUP_READ | OC_FILE_OTHER_READ;
if(!(info.dwFileAttributes & FILE_ATTRIBUTE_READONLY))
{
status->perm = OC_FILE_OWNER_WRITE | OC_FILE_GROUP_WRITE | OC_FILE_OTHER_WRITE;
}
FILETIME win32CreationDate;
FILETIME win32AccessDate;
FILETIME win32ModificationDate;
GetFileTime(fd, &win32CreationDate, &win32AccessDate, &win32ModificationDate);
status->creationDate = oc_datestamp_from_win32_filetime(win32CreationDate);
status->accessDate = oc_datestamp_from_win32_filetime(win32AccessDate);
status->modificationDate = oc_datestamp_from_win32_filetime(win32ModificationDate);
}
return (error);
}
oc_io_error oc_io_raw_fstat_at(oc_file_desc dirFd, oc_str8 name, oc_file_open_flags openFlags, oc_file_status* status)
{
oc_io_error error = OC_IO_OK;
oc_file_desc fd = oc_io_raw_open_at(dirFd, name, OC_FILE_ACCESS_NONE, OC_FILE_OPEN_SYMLINK);
if(oc_file_desc_is_nil(fd))
{
error = oc_io_raw_last_error();
}
else
{
error = oc_io_raw_fstat(fd, status);
oc_io_raw_close(fd);
}
return (error);
}
typedef struct
{
ULONG ReparseTag;
USHORT ReparseDataLength;
USHORT Reserved;
union
{
struct
{
USHORT SubstituteNameOffset;
USHORT SubstituteNameLength;
USHORT PrintNameOffset;
USHORT PrintNameLength;
ULONG Flags;
WCHAR PathBuffer[1];
} SymbolicLinkReparseBuffer;
struct
{
USHORT SubstituteNameOffset;
USHORT SubstituteNameLength;
USHORT PrintNameOffset;
USHORT PrintNameLength;
WCHAR PathBuffer[1];
} MountPointReparseBuffer;
struct
{
UCHAR DataBuffer[1];
} GenericReparseBuffer;
};
} oc_win32_reparse_data_buffer;
oc_io_raw_read_link_result oc_io_raw_read_link(oc_arena* arena, oc_file_desc fd)
{
oc_io_raw_read_link_result result = { 0 };
char buffer[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
DWORD bytesReturned;
if(!DeviceIoControl(fd, FSCTL_GET_REPARSE_POINT, NULL, 0, buffer, MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &bytesReturned, 0))
{
result.error = oc_io_raw_last_error();
}
else
{
oc_win32_reparse_data_buffer* reparse = (oc_win32_reparse_data_buffer*)buffer;
if(reparse->ReparseTag == IO_REPARSE_TAG_SYMLINK)
{
oc_str16 nameW = { 0 };
nameW.len = reparse->SymbolicLinkReparseBuffer.SubstituteNameLength / sizeof(wchar_t);
nameW.ptr = (u16*)((char*)reparse->SymbolicLinkReparseBuffer.PathBuffer + reparse->SymbolicLinkReparseBuffer.SubstituteNameOffset);
result.target = oc_win32_wide_to_utf8(arena, nameW);
}
else
{
result.error = OC_IO_ERR_UNKNOWN;
}
}
return (result);
}
oc_io_raw_read_link_result oc_io_raw_read_link_at(oc_arena* arena, oc_file_desc dirFd, oc_str8 name)
{
oc_file_desc fd = oc_io_raw_open_at(dirFd, name, OC_FILE_ACCESS_READ, OC_FILE_OPEN_SYMLINK);
oc_io_raw_read_link_result result = oc_io_raw_read_link(arena, fd);
oc_io_raw_close(fd);
return (result);
}
static oc_io_cmp oc_io_close(oc_file_slot* slot, oc_io_req* req, oc_file_table* table)
{
oc_io_cmp cmp = { 0 };
if(slot->fd)
{
CloseHandle(slot->fd);
}
oc_file_slot_recycle(table, slot);
return (cmp);
}
static oc_io_cmp oc_io_fstat(oc_file_slot* slot, oc_io_req* req)
{
oc_io_cmp cmp = { 0 };
if(req->size < sizeof(oc_file_status))
{
cmp.error = OC_IO_ERR_ARG;
}
else
{
slot->error = oc_io_raw_fstat(slot->fd, (oc_file_status*)req->buffer);
cmp.error = slot->error;
}
return (cmp);
}
static oc_io_cmp oc_io_seek(oc_file_slot* slot, oc_io_req* req)
{
oc_io_cmp cmp = { 0 };
DWORD whence;
switch(req->whence)
{
case OC_FILE_SEEK_CURRENT:
whence = FILE_CURRENT;
break;
case OC_FILE_SEEK_SET:
whence = FILE_BEGIN;
break;
case OC_FILE_SEEK_END:
whence = FILE_END;
}
LARGE_INTEGER off = { .QuadPart = req->offset };
LARGE_INTEGER newPos = { 0 };
if(!SetFilePointerEx(slot->fd, off, &newPos, whence))
{
slot->error = oc_io_raw_last_error();
cmp.error = slot->error;
}
else
{
cmp.result = newPos.QuadPart;
}
return (cmp);
}
static oc_io_cmp oc_io_read(oc_file_slot* slot, oc_io_req* req)
{
oc_io_cmp cmp = { 0 };
if(slot->type != OC_FILE_REGULAR)
{
slot->error = OC_IO_ERR_PERM;
cmp.error = slot->error;
}
else
{
DWORD bytesRead = 0;
if(!ReadFile(slot->fd, req->buffer, req->size, &bytesRead, NULL))
{
slot->error = oc_io_raw_last_error();
cmp.result = 0;
cmp.error = slot->error;
}
else
{
cmp.result = bytesRead;
}
}
return (cmp);
}
static oc_io_cmp oc_io_write(oc_file_slot* slot, oc_io_req* req)
{
oc_io_cmp cmp = { 0 };
if(slot->type != OC_FILE_REGULAR)
{
slot->error = OC_IO_ERR_PERM;
cmp.error = slot->error;
}
else
{
DWORD bytesWritten = 0;
if(!WriteFile(slot->fd, req->buffer, req->size, &bytesWritten, NULL))
{
slot->error = oc_io_raw_last_error();
cmp.result = 0;
cmp.error = slot->error;
}
else
{
cmp.result = bytesWritten;
}
}
return (cmp);
}
static oc_io_cmp oc_io_get_error(oc_file_slot* slot, oc_io_req* req)
{
oc_io_cmp cmp = { 0 };
cmp.result = slot->error;
return (cmp);
}
oc_io_cmp oc_io_wait_single_req_for_table(oc_io_req* req, oc_file_table* table)
{
oc_io_cmp cmp = { 0 };
oc_file_slot* slot = oc_file_slot_from_handle(table, req->handle);
if(!slot)
{
if(req->op != OC_IO_OPEN_AT)
{
cmp.error = OC_IO_ERR_HANDLE;
}
}
else if(slot->fatal && req->op != OC_IO_CLOSE && req->op != OC_OC_IO_ERROR)
{
cmp.error = OC_IO_ERR_PREV;
}
if(cmp.error == OC_IO_OK)
{
switch(req->op)
{
case OC_IO_OPEN_AT:
cmp = oc_io_open_at(slot, req, table);
break;
case OC_IO_FSTAT:
cmp = oc_io_fstat(slot, req);
break;
case OC_IO_CLOSE:
cmp = oc_io_close(slot, req, table);
break;
case OC_IO_READ:
cmp = oc_io_read(slot, req);
break;
case OC_IO_WRITE:
cmp = oc_io_write(slot, req);
break;
case OC_IO_SEEK:
cmp = oc_io_seek(slot, req);
break;
case OC_OC_IO_ERROR:
cmp = oc_io_get_error(slot, req);
break;
default:
cmp.error = OC_IO_ERR_OP;
if(slot)
{
slot->error = cmp.error;
}
break;
}
}
return (cmp);
}