Rework base IO functions on Windows using HANDLE

Switch to using the Windows-specific `HANDLE` and Windows API functions instead of `FILE *` and standard library functions in all Windows IO function implementations.

Using `CreateFileW` instead of `_wfsopen` to open files is necessary, as only `CreateFileW` allows specifying all necessary sharing flags. The sharing flags `FILE_SHARE_READ` and `FILE_SHARE_WRITE` were previously already specified by using `_SH_DENYNO` with `_wfsopen`, but `FILE_SHARE_DELETE` can only be set when using `CreateFileW`. The flags are necessary so files which are in use by the game can still be opened by other processes. In particular, `FILE_SHARE_DELETE` allows deleting/renaming of open files, which was previously not possible. This was causing the smart editor saving process that renames files to fail if a map file is currently in use.

Because `CreateFileW` returns a `HANDLE` instead of a `FILE *` we have to use the `HANDLE`-based Windows IO functions everywhere.

See: https://devblogs.microsoft.com/oldnewthing/20211022-00/?p=105822

Closes #6922.
This commit is contained in:
Robert Müller 2023-07-27 20:23:50 +02:00
parent 03fd1b440b
commit ef5ffedee6
3 changed files with 121 additions and 22 deletions

View file

@ -7,7 +7,7 @@
#include <mutex>
#include <vector>
typedef struct IOINTERNAL *IOHANDLE;
typedef void *IOHANDLE;
/**
* @ingroup Log

View file

@ -91,10 +91,30 @@
IOHANDLE io_stdin()
{
return (IOHANDLE)stdin;
#if defined(CONF_FAMILY_WINDOWS)
return GetStdHandle(STD_INPUT_HANDLE);
#else
return stdin;
#endif
}
IOHANDLE io_stdout()
{
#if defined(CONF_FAMILY_WINDOWS)
return GetStdHandle(STD_OUTPUT_HANDLE);
#else
return stdout;
#endif
}
IOHANDLE io_stderr()
{
#if defined(CONF_FAMILY_WINDOWS)
return GetStdHandle(STD_ERROR_HANDLE);
#else
return stderr;
#endif
}
IOHANDLE io_stdout() { return (IOHANDLE)stdout; }
IOHANDLE io_stderr() { return (IOHANDLE)stderr; }
IOHANDLE io_current_exe()
{
@ -258,21 +278,52 @@ IOHANDLE io_open_impl(const char *filename, int flags)
dbg_assert(flags == (IOFLAG_READ | IOFLAG_SKIP_BOM) || flags == IOFLAG_READ || flags == IOFLAG_WRITE || flags == IOFLAG_APPEND, "flags must be read, read+skipbom, write or append");
#if defined(CONF_FAMILY_WINDOWS)
const std::wstring wide_filename = windows_utf8_to_wide(filename);
DWORD desired_access;
DWORD creation_disposition;
if((flags & IOFLAG_READ) != 0)
return (IOHANDLE)_wfsopen(wide_filename.c_str(), L"rb", _SH_DENYNO);
if(flags == IOFLAG_WRITE)
return (IOHANDLE)_wfsopen(wide_filename.c_str(), L"wb", _SH_DENYNO);
if(flags == IOFLAG_APPEND)
return (IOHANDLE)_wfsopen(wide_filename.c_str(), L"ab", _SH_DENYNO);
return 0x0;
{
desired_access = FILE_READ_DATA;
creation_disposition = OPEN_EXISTING;
}
else if(flags == IOFLAG_WRITE)
{
desired_access = FILE_WRITE_DATA;
creation_disposition = OPEN_ALWAYS;
}
else if(flags == IOFLAG_APPEND)
{
desired_access = FILE_APPEND_DATA;
creation_disposition = OPEN_ALWAYS;
}
else
{
dbg_assert(false, "logic error");
return nullptr;
}
HANDLE handle = CreateFileW(wide_filename.c_str(), desired_access, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, creation_disposition, FILE_ATTRIBUTE_NORMAL, nullptr);
if(handle == INVALID_HANDLE_VALUE)
return nullptr; // otherwise all existing checks don't work for the invalid handle
return handle;
#else
const char *open_mode;
if((flags & IOFLAG_READ) != 0)
return (IOHANDLE)fopen(filename, "rb");
if(flags == IOFLAG_WRITE)
return (IOHANDLE)fopen(filename, "wb");
if(flags == IOFLAG_APPEND)
return (IOHANDLE)fopen(filename, "ab");
return 0x0;
{
open_mode = "rb";
}
else if(flags == IOFLAG_WRITE)
{
open_mode = "wb";
}
else if(flags == IOFLAG_APPEND)
{
open_mode = "ab";
}
else
{
dbg_assert(false, "logic error");
return nullptr;
}
return fopen(filename, open_mode);
#endif
}
@ -293,7 +344,13 @@ IOHANDLE io_open(const char *filename, int flags)
unsigned io_read(IOHANDLE io, void *buffer, unsigned size)
{
#if defined(CONF_FAMILY_WINDOWS)
DWORD actual_size;
ReadFile((HANDLE)io, buffer, size, &actual_size, nullptr);
return actual_size;
#else
return fread(buffer, 1, size, (FILE *)io);
#endif
}
void io_read_all(IOHANDLE io, void **result, unsigned *result_len)
@ -343,14 +400,31 @@ char *io_read_all_str(IOHANDLE io)
unsigned io_skip(IOHANDLE io, int size)
{
fseek((FILE *)io, size, SEEK_CUR);
return size;
return io_seek(io, size, IOSEEK_CUR);
}
int io_seek(IOHANDLE io, int offset, int origin)
{
#if defined(CONF_FAMILY_WINDOWS)
DWORD move_method;
switch(origin)
{
case IOSEEK_START:
move_method = FILE_BEGIN;
break;
case IOSEEK_CUR:
move_method = FILE_CURRENT;
break;
case IOSEEK_END:
move_method = FILE_END;
break;
default:
dbg_assert(false, "origin invalid");
return -1;
}
return SetFilePointer((HANDLE)io, offset, nullptr, move_method) == INVALID_SET_FILE_POINTER ? -1 : 0;
#else
int real_origin;
switch(origin)
{
case IOSEEK_START:
@ -363,15 +437,21 @@ int io_seek(IOHANDLE io, int offset, int origin)
real_origin = SEEK_END;
break;
default:
dbg_assert(false, "origin invalid");
return -1;
}
return fseek((FILE *)io, offset, real_origin);
#endif
}
long int io_tell(IOHANDLE io)
{
#if defined(CONF_FAMILY_WINDOWS)
const DWORD position = SetFilePointer((HANDLE)io, 0, nullptr, FILE_CURRENT);
return position == INVALID_SET_FILE_POINTER ? -1 : position;
#else
return ftell((FILE *)io);
#endif
}
long int io_length(IOHANDLE io)
@ -385,12 +465,23 @@ long int io_length(IOHANDLE io)
int io_error(IOHANDLE io)
{
#if defined(CONF_FAMILY_WINDOWS)
// Only works when called directly after the operation that failed
return GetLastError();
#else
return ferror((FILE *)io);
#endif
}
unsigned io_write(IOHANDLE io, const void *buffer, unsigned size)
{
#if defined(CONF_FAMILY_WINDOWS)
DWORD actual_size;
WriteFile((HANDLE)io, buffer, size, &actual_size, nullptr);
return actual_size;
#else
return fwrite(buffer, 1, size, (FILE *)io);
#endif
}
bool io_write_newline(IOHANDLE io)
@ -404,12 +495,20 @@ bool io_write_newline(IOHANDLE io)
int io_close(IOHANDLE io)
{
#if defined(CONF_FAMILY_WINDOWS)
return CloseHandle((HANDLE)io) == 0;
#else
return fclose((FILE *)io) != 0;
#endif
}
int io_flush(IOHANDLE io)
{
#if defined(CONF_FAMILY_WINDOWS)
return FlushFileBuffers((HANDLE)io) == FALSE;
#else
return fflush((FILE *)io);
#endif
}
int io_sync(IOHANDLE io)
@ -419,7 +518,7 @@ int io_sync(IOHANDLE io)
return 1;
}
#if defined(CONF_FAMILY_WINDOWS)
return FlushFileBuffers((HANDLE)_get_osfhandle(_fileno((FILE *)io))) == 0;
return FlushFileBuffers((HANDLE)io) == 0;
#else
return fsync(fileno((FILE *)io)) != 0;
#endif

View file

@ -223,7 +223,7 @@ enum
IO_MAX_PATH_LENGTH = 512,
};
typedef struct IOINTERNAL *IOHANDLE;
typedef void *IOHANDLE;
/**
* Opens a file.