From 44902cb49cd51e2bb2f1cef05cb3c0e799b83434 Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Sat, 3 Aug 2024 16:11:57 +0100 Subject: Rework OS abstractions - As much as possible avoid dragging system headers into translation units. This should avoid namespace pollution and, hopefully, speed up builds a little bit. - Avoid leaning on the UCRT so much on Windows - prefer native win32 calls and native file handles except where doing so is inconvenient (in particular, for stat(), which we might try and replace later). - Also, switch from SystemFunction036 to ProcessPrng on Windows. This requires us to generate a stub for bcryptprimitives.dll because Microsoft haven't bothered to provide a link library, but the function is better-documented and seems to be a more direct under-the-hood call as well. Apparently it's what's used by the major web browsers these days, which seems like a good indication it's stable and trusted. - Lastly, remove a bunch of functions and macros and stuff that weren't actually being used. It seems good to try and keep the scope of OS-dependent stuff relatively contained and only add to it when actually required. --- src/os.h | 260 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 225 insertions(+), 35 deletions(-) (limited to 'src/os.h') diff --git a/src/os.h b/src/os.h index c954d1a..b599523 100644 --- a/src/os.h +++ b/src/os.h @@ -1,5 +1,5 @@ /* - * Copyright © 2022 Michael Smith + * Copyright © 2024 Michael Smith * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -17,48 +17,238 @@ #ifndef INC_OS_H #define INC_OS_H -/* - * Here we declare an absolute ton of wrappers, macros, compatibility shims, - * reimplementations and so on to try in vain to sweep the inconsistencies - * between Windows and not-Windows under the rug. - */ +#include +#include // XXX: try abstracting stat() and avoiding ucrt dep here -#include -#include #ifdef _WIN32 -#include -#include -#include -// DUMB HACK: noreturn.h is alphabetically before os.h so including it after -// looks weird and inconsistent. including it before breaks Windows.h though -// because of __declspec(noreturn). Undef for now, and restore at the end of -// this header. -#undef noreturn -#include -#include -#else -#include -#include -#include -#include -#include -#include -#include -#include -#include + +#include // XXX: there's kind of a lot of junk in this header! + +#define IMPORT __declspec(dllimport) // only needed for variables +#define EXPORT __declspec(dllexport) + +typedef unsigned short os_char; +#define _OS_CAT(x, y) x##y +#define OS_LIT(x) _OS_CAT(L, x) +#define os_strcmp wcscmp +#define os_strlen wcslen +// ucrt defines __stat64 to _stat64. we want _wstat64 to be the actual function +#define _stat64(path, buf) _wstat64(path, buf) +#define os_stat _stat64 + +#define OS_DLPREFIX "" +#define OS_DLSUFFIX ".dll" + +#define OS_MAIN wmain + +// add to these as needed... +#define OS_EEXIST /*ERROR_ALREADY_EXISTS*/ 183 +#define OS_ENOENT /*ERROR_PATH_NOT_FOUND*/ 3 + +// Further Windows-specific workarounds because Windows is great... + +#ifndef PATH_MAX +// XXX: win32/crt has this dumb 260 limit even though the actual kernel imposes +// no limit (though apparently NTFS has a limit of 65535). Theoretically we +// could do some memes with UNC paths to extend it to at least have parity with +// Unix PATH_MAX (4096), but for now we just kind of accept that Windows is a +// disaster. +#define PATH_MAX 260 #endif -// Things were getting unwieldy so they're now split into these headers... -#ifdef _WIN32 -#include "os-win32.h" -// DUMB HACK part 2: restore what the #including source file had asked for -#ifdef INC_NORETURN_H -#define noreturn _Noreturn void +// windows doesn't define this for some reason!? note: add others as needed... +#define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) + +#define alloca _alloca // ugh! + +// one last thing: mprot constants are part of os.h API, whether or not +// Windows.h was included. this is a bit of a hack, but whatever! +#ifndef _INC_WINDOWS +#define PAGE_NOACCESS 1 +#define PAGE_READONLY 2 +#define PAGE_READWRITE 4 +#define PAGE_EXECUTE_READ 32 +#define PAGE_EXECUTE_READWRITE 64 #endif + #else -#include "os-unix.h" + +#include // meh + +#define IMPORT +#ifdef __GNUC__ +#define EXPORT __attribute__((visibility("default")) +#else +#define EXPORT int exp[-!!"compiler needs a way to export symbols!"]; +#endif + +// trying to avoid pulling in unnecessary headers as much as possible: define +// our own constants for os_mprot() / mprotect() +#if defined(__linux__) // apparently linux is pretty much the sole oddball here! +#define _OS_PROT_READ 4 +#define _OS_PROT_WRITE 2 +#define _OS_PROT_EXEC 1 +#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || \ + defined(__DragonFly__) || defined(__sun) || defined(__serenity) +#define _OS_PROT_READ 1 +#define _OS_PROT_WRITE 2 +#define _OS_PROT_EXEC 4 +#elif !defined(_WIN32) // what else could this possibly even be!? +#include // just fall back on this. not the end of the world... +#define _OS_PROT_READ PROT_READ +#define _OS_PROT_WRITE PROT_WRITE +#define _OS_PROT_EXEC PROT_EXEC #endif +typedef char os_char; +#define OS_LIT(x) x +#define os_strcmp strcmp +#define os_strlen strlen +#define os_stat stat + +#define OS_DLPREFIX "lib" +#define OS_DLSUFFIX ".so" + +#define OS_MAIN main + +// unix mprotect flags are much nicer but cannot be defined in terms of the +// windows ones, so we use the windows ones and define them in terms of the unix +// ones. another victory for stupid! +#define PAGE_NOACCESS 0 +#define PAGE_READONLY _OS_PROT_READ +#define PAGE_READWRITE _OS_PROT_READ | _OS_PROT_WRITE +#define PAGE_EXECUTE_READ _OS_PROT_READ | _OS_PROT_EXEC +#define PAGE_EXECUTE_READWRITE _OS_PROT_READ | _OS_PROT_WRITE | _OS_PROT_EXEC + +#define OS_EEXIST EEXIST +#define OS_ENOENT ENOENT + +#endif + +/* Copies n characters from src to dest, using the OS-specific char type. */ +static inline void os_spancopy(os_char *restrict dest, + const os_char *restrict src, int n) { + memcpy(dest, src, n * sizeof(os_char)); +} + +/* + * Returns the last error code from an OS function - equivalent to calling + * GetLastError() in Windows and reading errno in Unix-like operating systems. + * For standard libc functions (implemented by UCRT on Windows), the value of + * errno should be used directly instead. + */ +int os_lasterror(void); + +/* + * Opens a file for reading. Returns an OS-specific file handle, or -1 on error. + */ +int os_open_read(const os_char *path); + +/* + * Opens a file for writing, creating it if required. Returns an OS-specific + * file handle, or -1 on error. + */ +int os_open_write(const os_char *path); + +/* + * Opens a file for writing, creating it if required, or truncating it if it + * already exists. Returns an OS-specific file handle, or -1 on error. + */ +int os_open_writetrunc(const os_char *path); + +/* + * Reads up to max bytes from OS-specific file handle f into buf. Returns the + * number of bytes read, or -1 on error. + */ +int os_read(int f, void *buf, int max); + +/* + * Reads up to len bytes from buf to OS-specific file handle f. Returns the + * number of bytes written, or -1 on error. Generally the number of bytes + * written will be len, unless writing to a pipe/socket, which has a limited + * internal buffer, or possibly being preempted by a signal handler. + */ +int os_write(int f, const void *buf, int len); + +/* + * Returns the length of the on-disk file referred to by OS-specific file handle + * f, or -1 on error (the most likely error would be that the file is not + * actually on disk and instead refers to a pipe or something). + */ +long long os_fsize(int f); + +/* + * Closes the OS-specific file handle f. On Windows, this causes pending writes + * to be flushed; on Unix-likes, this generally happens asynchronously. If + * blocking is a concern when writing files to disk, some sort of thread pool + * should always be used. + */ +void os_close(int f); + +/* + * Gets the current working directory, which may be up to PATH_MAX characters in + * length (using the OS-specific character type). + */ +void os_getcwd(os_char buf[static PATH_MAX]); + +/* + * Tries to create a directory at path. Returns true on success, false on + * failure. One may wish to ignore a failure if the directory already exists; + * this can be done by checking if os_lasterror() returns OS_EEXIST. + */ +bool os_mkdir(const os_char *path); + +/* + * Tries to delete a file(name) at path. Returns true on success, false on + * failure. One may wish to ignore a failure if the file already doesn't exist; + * this can be done by checking if os_lasterror() returns OS_ENOENT. + * + * On some Unix-like operating systems, this may work on empty directories, but + * for portably reliable results, one should call os_rmdir() for that instead. + */ +bool os_unlink(const os_char *path); + +/* + * Tries to delete a directory at path. Returns true on success, false on + * failure. One may wish to ignore a failure if the directory already doesn't + * exist; this can be done by checking if os_lasterror() returns OS_ENOENT. + */ +bool os_rmdir(const os_char *path); + +/* + * Tries to look up the symbol sym from the shared library handle lib. Returns + * an opaque pointer on success, or null on failure. + */ +void *os_dlsym(void *restrict lib, const char *restrict sym); + +#if defined(_WIN32) || defined(__linux__) +/* + * Tries to get a handle to an already-loaded shared library matching name. + * Returns the library handle on success, or null on failure. + */ +void *os_dlhandle(const os_char *name); + +/* + * Tries to determine the file path to the shared library handle lib, and stores + * it in buf (with max length given by sz). Returns the length of the resulting + * string, or -1 on failure. + */ +int os_dlfile(void *lib, os_char *buf, int sz); +#endif + +/* + * Changes memory protection for the address range given by addr and len, using + * one of the Win32-style PAGE_* flags specified above. Returns true on success + * and false on failure. + */ +bool os_mprot(void *addr, int len, int mode); + +/* + * Fills buf with up to sz cryptographically random bytes. sz has an OS-specific + * upper limit - a safe value across all major operating systems is 256. + */ +void os_randombytes(void *buf, int sz); + #endif // vi: sw=4 ts=4 noet tw=80 cc=80 -- cgit v1.2.3