You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

413 lines
12 KiB
C

/*
polyfill: mingw32 polyfills for missing C/POSIX functions
Written by Giovanni Bajo <giovannibajo@gmail.com>
This tool is part of the Libdragon SDK.
This is free and unencumbered software released into the public domain.
For more information, please refer to <http://unlicense.org/>
*/
#ifndef LIBDRAGON_TOOLS_POLYFILL_H
#define LIBDRAGON_TOOLS_POLYFILL_H
#ifdef __MINGW32__
// NOTE: we include both stdio.h and cstdio because both of them will undef
// tmpfile and redefine it to something else. Since we want to provide our
// own implementation, we need to make sure both are included first, so that
// we don't run the risk either of them is included after this file.
#include <stdio.h>
#ifdef __cplusplus
#include <cstdio>
#endif
// NOTE: include both assert.h and cassert because we need to make sure
// _assert and _wassert are declared properly before we mess with them later.
#include <assert.h>
#ifdef __cplusplus
#include <cassert>
#endif
#include <stdlib.h>
#include <errno.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <process.h>
#include <share.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
#ifdef __cplusplus
extern "C" {
#endif
// if typedef doesn't exist (msvc, blah)
typedef intptr_t ssize_t;
/* Implementation of 'getline' fetched from: https://stackoverflow.com/a/47229318 */
/* The original code is public domain -- Will Hartung 4/9/09 */
/* Modifications, public domain as well, by Antti Haapala, 11/10/17
- Switched to getc on 5/23/19 */
__attribute__((used))
static ssize_t getline(char **lineptr, size_t *n, FILE *stream) {
size_t pos;
int c;
if (lineptr == NULL || stream == NULL || n == NULL) {
errno = EINVAL;
return -1;
}
c = getc(stream);
if (c == EOF) {
return -1;
}
if (*lineptr == NULL) {
*lineptr = (char*)malloc(128);
if (*lineptr == NULL) {
return -1;
}
*n = 128;
}
pos = 0;
while(c != EOF) {
if (pos + 1 >= *n) {
size_t new_size = *n + (*n >> 2);
if (new_size < 128) {
new_size = 128;
}
char *new_ptr = (char*)realloc(*lineptr, new_size);
if (new_ptr == NULL) {
return -1;
}
*n = new_size;
*lineptr = new_ptr;
}
((unsigned char *)(*lineptr))[pos ++] = c;
if (c == '\n') {
break;
}
c = getc(stream);
}
(*lineptr)[pos] = '\0';
return pos;
}
// Provide implementation of strndup for MSYS2 environments that are missing it (before April 13 2026).
#if defined(MSYS2_RUNTIME_PACMAN_AGE) && MSYS2_RUNTIME_PACMAN_AGE <= 20260413
__attribute__((used))
static char *msys2_fallback_strndup(const char *s, size_t n)
{
size_t len = strnlen(s, n);
char *ret = (char*)malloc(len + 1);
if (!ret) return NULL;
memcpy(ret, s, len);
ret[len] = '\0';
return ret;
}
#ifdef strndup
#undef strndup
#endif
#define strndup msys2_fallback_strndup
#endif
// Replace 'assert' so that it has 'noreturn' behaviour on Windows.
// (soft reverts this MSYS2 commit: https://sourceforge.net/p/mingw-w64/mingw-w64/ci/ecf2328a328d11dec7044b40b2b5e93b5b2b9d9e/)
#if defined(MSYS2_RUNTIME_PACMAN_AGE) && MSYS2_RUNTIME_PACMAN_AGE >= 20260611 && !defined(NDEBUG)
#if defined(_UNICODE) || defined(UNICODE)
__attribute__((used)) __attribute__((noreturn))
static void msys2_wassert_fix(const wchar_t* _Message, const wchar_t* _File, unsigned _Line)
{
_wassert(_Message, _File, _Line);
abort();
__builtin_unreachable();
}
#define _wassert(a,b,c) msys2_wassert_fix(a,b,c)
#else
__attribute__((used)) __attribute__((noreturn))
static void msys2_assert_fix(const char* _Message, const char* _File, unsigned _Line)
{
_assert(_Message, _File, _Line);
abort();
__builtin_unreachable();
}
#define _assert(a,b,c) msys2_assert_fix(a,b,c)
#endif // defined(_UNICODE) || defined(UNICODE)
#endif
#if defined(__MSVCRT_VERSION__) && __MSVCRT_VERSION__ >= 0xE00
// UCRT is being used so we can use runtime-provided tmpfile() implementation
#else
// tmpfile() in MINGW64 is broken for use (it uses MSVCRT that tries to
// create a file in C:\, which is non-writable nowadays)
#ifdef tmpfile
#undef tmpfile
#endif
#define tmpfile() mingw_tmpfile()
#endif
typedef void* HANDLE;
typedef const char* LPCSTR;
typedef int BOOL;
#define INVALID_HANDLE_VALUE ((HANDLE)(long)-1)
struct _SECURITY_ATTRIBUTES;
// Access rights
#define GENERIC_READ 0x80000000
#define GENERIC_WRITE 0x40000000
// Share modes
#define FILE_SHARE_READ 0x00000001
#define FILE_SHARE_WRITE 0x00000002
#define FILE_SHARE_DELETE 0x00000004
// Creation disposition
#define CREATE_NEW 1
// Flags and attributes
#define FILE_ATTRIBUTE_TEMPORARY 0x00000100
#define FILE_FLAG_DELETE_ON_CLOSE 0x04000000
__declspec(dllimport) HANDLE __stdcall CreateFileA(
LPCSTR lpFileName,
unsigned long dwDesiredAccess,
unsigned long dwShareMode,
struct _SECURITY_ATTRIBUTES* lpSecurityAttributes,
unsigned long dwCreationDisposition,
unsigned long dwFlagsAndAttributes,
HANDLE hTemplateFile
);
__declspec(dllimport) int __stdcall CloseHandle(HANDLE);
__declspec(dllimport) unsigned long __stdcall GetLastError(void);
__declspec(dllimport) unsigned long __stdcall GetTickCount(void);
__attribute__((used))
static FILE *mingw_tmpfile(void) {
static int counter = 0;
char path[260];
for (int i = 0; i < 4096; i++) {
// Generate a random filename. Notice we *purposedly* not make this
// very random with PID, timestamp, etc. because this is the third
// iteration of mingw_tmpfile(): the previous ones were misbehaving
// in various ways on various CRTs, Windows versions, etc.
// We want this code to be exercised often so that it is robust.
snprintf(path, sizeof(path), "mksprite-%04x.tmp", counter++);
HANDLE h = CreateFileA(
path,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,
CREATE_NEW,
FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE,
NULL
);
if (h != INVALID_HANDLE_VALUE) {
int fd = _open_osfhandle((intptr_t)h, _O_RDWR | _O_BINARY);
if (fd == -1) {
CloseHandle(h);
return NULL;
}
return fdopen(fd, "w+b");
}
// 80 = ERROR_FILE_EXISTS
if (GetLastError() != 80)
break;
}
return NULL;
}
__attribute__((used))
static char* strcasestr(const char* haystack, const char* needle)
{
size_t needle_len = strlen(needle);
size_t haystack_len = strlen(haystack);
size_t i;
if (needle_len > haystack_len)
return NULL;
for (i = 0; i <= haystack_len - needle_len; i++)
{
if (strncasecmp(haystack + i, needle, needle_len) == 0)
return (char*)(haystack + i);
}
return NULL;
}
// Implementation from FreeBSD
__attribute__((used))
static void *memmem(const void *l, size_t l_len, const void *s, size_t s_len)
{
char *cur, *last;
const char *cl = (const char *)l;
const char *cs = (const char *)s;
/* we need something to compare */
if (l_len == 0 || s_len == 0)
return NULL;
/* "s" must be smaller or equal to "l" */
if (l_len < s_len)
return NULL;
/* special case where s_len == 1 */
if (s_len == 1)
return memchr(l, (int)*cs, l_len);
/* the last position where its possible to find "s" in "l" */
last = (char *)cl + l_len - s_len;
for (cur = (char *)cl; cur <= last; cur++)
if (cur[0] == cs[0] && memcmp(cur, cs, s_len) == 0)
return cur;
return NULL;
}
// rename() that ovewrites the destination file
#define rename(a,b) mingw_rename(a,b)
#define MOVEFILE_REPLACE_EXISTING 0x00000001
#define MOVEFILE_WRITE_THROUGH 0x00000008
__declspec(dllimport) int __stdcall MoveFileExA(const char *lpExistingFileName,
const char *lpNewFileName,
unsigned long dwFlags);
__declspec(dllimport) unsigned long __stdcall GetLastError(void);
static void map_windows_error_to_errno(unsigned long err) {
switch (err) {
case 2: errno = ENOENT; break; // ERROR_FILE_NOT_FOUND
case 3: errno = ENOENT; break; // ERROR_PATH_NOT_FOUND
case 5: errno = EACCES; break; // ERROR_ACCESS_DENIED
case 32: errno = EBUSY; break; // ERROR_SHARING_VIOLATION
case 80: errno = EEXIST; break; // ERROR_FILE_EXISTS
case 183: errno = EEXIST; break; // ERROR_ALREADY_EXISTS
default: errno = EIO; break; // Generic error
}
}
__attribute__((used))
static int mingw_rename(const char *oldpath, const char *newpath) {
if (MoveFileExA(oldpath, newpath, MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH)) {
return 0;
} else {
map_windows_error_to_errno(GetLastError());
return -1;
}
}
// POISX mkdir has a mode argument, but mingw's mkdir doesn't
#define mkdir(path, mode) mkdir(path)
typedef struct _PROCESS_BASIC_INFORMATION_MIN {
void* Reserved1;
void* PebBaseAddress;
void* Reserved2[2];
void* UniqueProcessId;
void* InheritedFromUniqueProcessId;
} PROCESS_BASIC_INFORMATION_MIN;
#define CURRENT_PROCESS ((HANDLE)(intptr_t)-1)
__declspec(dllimport) long __stdcall NtQueryInformationProcess(
HANDLE ProcessHandle, unsigned long ProcessInformationClass,
void* ProcessInformation, unsigned long ProcessInformationLength,
unsigned long *ReturnLength);
// Enable long path support in the current process.
// This is a hacky workaround for a limitation in Windows where
// file paths longer than 260 characters are not supported.
// The documented ways involve also setting a registry key, and thus
// is not suitable for a tool like this.
// This same approach is used by the Go runtime on Windows, so it is
// reasonably safe to use it here as well.
__attribute__((used))
static void enable_peb_long_path_unsafe(void)
{
PROCESS_BASIC_INFORMATION_MIN pbi; unsigned long retlen;
if (NtQueryInformationProcess(CURRENT_PROCESS, 0, &pbi, sizeof(pbi), &retlen) < 0)
return;
volatile uint8_t *peb = (volatile uint8_t*)pbi.PebBaseAddress;
enum { PEB_BITFIELD_OFFSET = 3, IS_LONG_PATH_AWARE_MASK = 0x80 };
peb[PEB_BITFIELD_OFFSET] |= (uint8_t)IS_LONG_PATH_AWARE_MASK;
}
// If it was defined, undefine it as there's no limit on path length after the hack above
#undef MAX_PATH
__declspec(dllimport) int __stdcall SetConsoleOutputCP(unsigned int);
__declspec(dllimport) int __stdcall SetConsoleCP(unsigned int);
__declspec(dllimport) unsigned int __stdcall GetConsoleOutputCP(void);
__declspec(dllimport) unsigned int __stdcall GetConsoleCP(void);
#define CP_UTF8 65001
static int old_out_cp = 0;
static int old_in_cp = 0;
__attribute__((used))
static void winconsole_restore(void) {
if (old_out_cp)
SetConsoleOutputCP(old_out_cp);
if (old_in_cp)
SetConsoleCP(old_in_cp);
}
__attribute__((used))
static void winconsole_utf8(void) {
// Enable long path support in the current process
enable_peb_long_path_unsafe();
// Save the current code page
old_out_cp = GetConsoleOutputCP();
old_in_cp = GetConsoleCP();
// Enable UTF-8 in the console
SetConsoleOutputCP(CP_UTF8);
SetConsoleCP(CP_UTF8);
atexit(winconsole_restore);
}
#ifdef __cplusplus
}
#endif
#else /* __MINGW32__ */
#ifdef __cplusplus
extern "C" {
#endif
__attribute__((used))
static void winconsole_utf8(void) {}
#ifdef __cplusplus
}
#endif
#endif /* __MINGW32__ */
#endif