607 lines
16 KiB
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);
|
|
}
|