/* winlibc.c -- Basic (incomplete) C library for Windows with UTF-8 support Copyright © 2021-2024 Samuel Lidén Borell Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /* NOTE: The tests are in ../unittest/test_winlib.c */ #define _CSLULWINLIBC_SOURCE #include #include "assert.h" #include "errno.h" #include "limits.h" #include "setjmp.h" #include "stdint.h" #include "stdio.h" #include "stdlib.h" #include "string.h" #include "sys/types.h" #include "sys/stat.h" #include "windef.h" #undef errno /* some C library header gets included above and overriddes it */ #define errno _CSlulWinLibC_errno #if (defined(__GNUC__) && __GNUC__ > 2) || defined(__clang__) # define LIKELY(cond) __builtin_expect((cond), 1) # define UNLIKELY(cond) __builtin_expect((cond), 0) #else # define LIKELY(cond) (cond) # define UNLIKELY(cond) (cond) #endif #ifdef __GNUC__ # define WINAPI_UNLESS_GCC #else # define WINAPI_UNLESS_GCC WINAPI #endif #define PTRSIZE sizeof(void *) #undef ERROR_INVALID_NAME #define ERROR_INVALID_NAME 123 #undef ERROR_ALREADY_EXISTS #define ERROR_ALREADY_EXISTS 183 /* WinAPI definitions - Functions */ #define IMPORT __declspec(dllimport) #define EXPORT EXPORT int WINAPI_UNLESS_GCC mainCRTStartup(void); #ifdef CSLULWINLIBC_USE_KERNEL32_INTERNAL /* Not official API functions. Also not available on UWP */ IMPORT void WINAPI RtlMoveMemory(void *, const void *, size_t); IMPORT void WINAPI RtlZeroMemory(void *, size_t); IMPORT size_t WINAPI RtlCompareMemory(const void *, const void *, size_t); #endif IMPORT HANDLE WINAPI GetProcessHeap(void); IMPORT LPVOID WINAPI HeapAlloc(HANDLE, DWORD, size_t); IMPORT LPVOID WINAPI HeapReAlloc(HANDLE, DWORD, LPVOID, size_t); IMPORT BOOL WINAPI HeapFree(HANDLE, DWORD, LPVOID); IMPORT int WINAPI MultiByteToWideChar(unsigned, DWORD, LPCCH, int, LPWSTR, int); IMPORT int WINAPI WideCharToMultiByte(unsigned, DWORD, LPCWSTR, int, char *, int, const char *, BOOL *); IMPORT HANDLE WINAPI GetStdHandle(DWORD); IMPORT BOOL WINAPI WriteConsoleA(HANDLE, const void *lpBuffer, DWORD, LPDWORD, LPVOID); IMPORT BOOL WINAPI WriteConsoleW(HANDLE, const void *lpBuffer, DWORD, LPDWORD, LPVOID); IMPORT BOOL WINAPI GetConsoleMode(HANDLE, LPDWORD); IMPORT _CSLULWINLIBC_NORETURN void WINAPI ExitProcess(unsigned); IMPORT LPWSTR WINAPI GetCommandLineW(void); IMPORT DWORD WINAPI GetLastError(void); IMPORT void WINAPI SetLastError(DWORD); IMPORT DWORD WINAPI GetEnvironmentVariableW(LPCWSTR, LPWSTR, DWORD); IMPORT HANDLE WINAPI CreateFileW(LPCWSTR, DWORD, DWORD, /*LPSECURITY_ATTRIBUTES*/LPVOID, DWORD, DWORD, HANDLE); IMPORT BOOL WINAPI CloseHandle(HANDLE); IMPORT BOOL WINAPI FlushFileBuffers(HANDLE); IMPORT BOOL WINAPI ReadFile(HANDLE, LPVOID, DWORD, LPDWORD, /*LPOVERLAPPED*/LPVOID); IMPORT BOOL WINAPI WriteFile(HANDLE, LPCVOID, DWORD, LPDWORD, /*LPOVERLAPPED*/LPVOID); IMPORT BOOL WINAPI DeleteFileW(LPCWSTR); IMPORT BOOL WINAPI CreateDirectoryW(LPCWSTR, /*LPSECURITY_ATTRIBUTES*/LPVOID); IMPORT DWORD WINAPI FormatMessageW(DWORD, const void *, DWORD, DWORD, LPWSTR, DWORD, va_list *); IMPORT void *WINAPI LocalFree(void *); IMPORT DWORD WINAPI GetModuleFileNameW(HMODULE, LPWSTR, DWORD); extern int NAME_OF_MAIN(int argc, char **argv); #ifndef BUFSIZ #define BUFSIZ 512 #endif static const char hexdigits[16] = { '0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f' }; FILE *stdout; FILE *stderr; static char *lastenv = NULL; int exiting = 0, aborting = 0; int _CSlulWinLibC_errno = 0; int num_exit_handlers = 0; static void (*exit_handlers[ATEXIT_MAX])(void); static int build_cmdline(LPWSTR cmdlineW, int *argc, char ***argv); static void call_atexit(void); static FILE *alloc_file(HANDLE hfile); static void init_error(void); /* Calls main. Does not support any envp parameter. This function is supposed to have WINAPI in the definition, but due to GCC bugs it has been removed. With WINAPI there are two problems: * Then the entry point is set to the start of .text (i.e. mainCRTStartup will have to come first). * Then there is a warning printed by ld: "warning: resolving _mainCRTStartup by linking to _mainCRTStartup@0" See https://nullprogram.com/blog/2023/02/15/ for details about mainCRTStartup */ #if defined(__has_attribute) #if __has_attribute(externally_visible) __attribute__((externally_visible)) /* needed to support -fwhole-program */ #endif #endif #ifdef __i386__ __attribute__((force_align_arg_pointer)) /* align to 16 byte boundary */ #endif int WINAPI_UNLESS_GCC mainCRTStartup(void) { int ret; int argc; char **argv; LPWSTR cmdlineW; /* TODO pre-allocate in data section, to avoid syscalls at startup */ stdout = alloc_file(INVALID_HANDLE_VALUE); if (!stdout) init_error(); stdout->special_init = 1; stderr = alloc_file(INVALID_HANDLE_VALUE); if (!stderr) init_error(); stderr->special_init = 2; stderr->is_unbuffered = 1; cmdlineW = GetCommandLineW(); if (build_cmdline(cmdlineW, &argc, &argv) < 0) init_error(); /* Debugging code */ /*{int i;printf("Calling main... (argc = %d)\n", argc); for (i = 0; i <= argc; i++) { printf("arg %d: >%s<\n", i, argv[i]); }}*/ ret = NAME_OF_MAIN(argc, argv); exit(ret); } static LPWSTR utf8_to_utf16(const char *utf8, int inlen, int *pOutLen) { /* XXX does this pass through lone surrogate halves as it should? */ HANDLE heap = GetProcessHeap(); LPWSTR ret; int outlen; outlen = MultiByteToWideChar(CP_UTF8, /*MB_ERR_INVALID_CHARS*/0, utf8, inlen, NULL, 0); ret = HeapAlloc(heap, 0, (outlen+1)*sizeof(WCHAR)); if (!ret) goto error; if (!MultiByteToWideChar(CP_UTF8, /*MB_ERR_INVALID_CHARS*/0, utf8, inlen, ret, outlen)) goto error; ret[outlen] = '\0'; if (pOutLen) *pOutLen = outlen; return ret; error: if (ret) HeapFree(heap, 0, ret); return NULL; } static char *utf16_to_utf8(LPCWSTR wstr) { HANDLE heap = GetProcessHeap(); char *ret; int outlen; /* FIXME this will most likely not handle non-BMP characters correctly. At least on old versions of Windows */ /* WC_NO_BEST_FIT_CHARS ? */ outlen = WideCharToMultiByte(CP_UTF8, /*WC_ERR_INVALID_CHARS*/0, wstr, -1, NULL, 0, NULL, NULL); ret = HeapAlloc(heap, 0, outlen+1); if (!ret) goto error; if (!WideCharToMultiByte(CP_UTF8, /*WC_ERR_INVALID_CHARS*/0, wstr, -1, ret, outlen, NULL, NULL)) goto error; ret[outlen] = '\0'; return ret; error: if (ret) HeapFree(heap, 0, ret); return NULL; } #define WCONV_START(varname, err_return) \ LPWSTR varname ## W; \ varname ## W = utf8_to_utf16(varname, strlen(varname), NULL); \ if (!varname) return err_return; #define WCONV_DONE(varname) HeapFree(GetProcessHeap(), 0, varname ## W); static FILE *alloc_file(HANDLE hfile) { FILE *ret = malloc(sizeof(FILE)); if (ret) { ret->hfile = hfile; ret->error = 0; ret->is_unbuffered = 0; ret->never_flush = 0; ret->is_console = 0; ret->special_init = 0; ret->buff = NULL; /* These are used in the putc() macro! */ ret->buffp = NULL; ret->buffend = NULL; } return ret; } static int special_init(FILE *f) { DWORD dummy; f->hfile = GetStdHandle(f->special_init == 1 ? STD_OUTPUT_HANDLE : STD_ERROR_HANDLE); f->special_init = 0; f->is_console = GetConsoleMode(f->hfile, &dummy); return 0; } static void init_error(void) { #if 0 static const char message[] = "Failed to initialize.\r\n"; /* TODO */ WriteConsoleA(console, message, sizeof(message)-1, /*num_written*/NULL, NULL); #endif ExitProcess(1); } static void call_atexit(void) { int i; for (i = num_exit_handlers-1; i >= 0; i--) { exit_handlers[i](); } /* FIXME flush all buffers here */ } static int parse_cmdline(LPCWSTR cmdlineW, int *argc_ptr, char ***argv_ptr, void *buff) { int in_arg = 0, in_quote = 0; int num_args = 0; int size = 0; const WCHAR *ip = cmdlineW; char **argv; unsigned char *op; if (!buff) { argv = NULL; op = NULL; } else { argv = (char **)buff; op = (unsigned char *)buff + ((*argc_ptr + 1) * sizeof(char *)); argv[0] = (char *)op; } for (;;) { WCHAR ic = *(ip++); if (!ic) break; if (!in_quote && (ic == ' ' || ic == '\t')) { if (in_arg) { /* End of argument */ if (op) *(op++) = '\0'; size += sizeof(char *) + 1; num_args++; if (argv) argv[num_args] = (char *)op; in_arg = 0; } else { /* Repeated space between arguments -> ignore */ } continue; } in_arg = 1; if (ic == '"') { in_quote = !in_quote; } else if (ic == '\\') { ic = *(ip++); if (ic != '\\' && ic != '"') { /* Invalid escape */ if (op) *(op++) = '\\'; size++; } if (op) *(op++) = ic; size++; } else if (ic < 0x7F) { /* Plain ASCII character */ if (op) *(op++) = (char)ic; size++; } else if (ic < 0x7FF) { /* 2 byte BMP character */ if (op) { *(op++) = 0xC0 | (ic >> 6); *(op++) = 0x80 | (ic & 0x3F); } size += 2; } else if (ic < 0xD800 || ic > 0xDFFF) { /* 3 byte BMP character */ if (op) { *(op++) = 0xE0 | (ic >> 12); *(op++) = 0x80 | ((ic >> 6) & 0x3F); *(op++) = 0x80 | (ic & 0x3F); } size += 3; } else if (ic < 0xDC00 && *ip >= 0xDC00 && *ip <= 0xDFFF) { /* Non-BMP character encoding as a high + low surrogate */ DWORD codepoint = (((ic-0xD800)<<10) | (*ip-0xDC00)) + 0x10000; ip++; if (op) { *(op++) = 0xF0 | (codepoint >> 18); *(op++) = 0x80 | ((codepoint >> 12) & 0x3F); *(op++) = 0x80 | ((codepoint >> 6) & 0x3F); *(op++) = 0x80 | (codepoint & 0x3F); } size += 4; } else { /* Bad pairing of surrogate characters */ /* TODO encode 0xDC00 char here */ } } if (in_arg || !num_args) { /* End of argument */ if (op) *(op++) = '\0'; size += sizeof(char *) + 1; num_args++; } /* Trailing NULL argument */ size += sizeof(char *); if (argv) { argv[num_args] = NULL; *argv_ptr = argv; } *argc_ptr = num_args; return size; } static int build_cmdline(LPWSTR cmdlineW, int *argc, char ***argv) { int buffsize; void *buff; buffsize = parse_cmdline(cmdlineW, argc, NULL, NULL); if (buffsize < 0) return -1; buff = malloc(buffsize); if (!buff) return -1; return parse_cmdline(cmdlineW, argc, argv, buff); } static void set_errno(void) { int lasterr = GetLastError(); if (lasterr == ERROR_ALREADY_EXISTS) { /* On Windows, there are two error codes that correspond to EEXIST. ERROR_ALREADY_EXISTS is used by e.g. CreateDirectory or when the CREATE_NEW file disposition is used. ERROR_FILE_EXISTS is used in other cases. */ errno = EEXIST; } else { errno = lasterr; } } /* ========== Memory (copy/cmp) functions ========== */ void *memcpy(void *dest, const void *source, size_t size) { #ifdef CSLULWINLIBC_USE_KERNEL32_INTERNAL RtlMoveMemory(dest, source, size); #else unsigned char *dp = dest; const unsigned char *sp = source; for (; size; size--) { *(dp++) = *(sp++); } #endif return dest; } void *memset(void *dest, int value, size_t size) { #ifdef CSLULWINLIBC_USE_KERNEL32_INTERNAL if (LIKELY(value == 0)) RtlZeroMemory(dest, size); else #endif { unsigned char *p = dest; for (; size--; p++) *p = value; } return dest; } int memcmp(const void *a, const void *b, size_t size) { #ifdef CSLULWINLIBC_USE_KERNEL32_INTERNAL size_t numsame = RtlCompareMemory(a, b, size); if (numsame == size) return 0; else return ((unsigned char *)a)[numsame] - ((unsigned char *)b)[numsame]; #else const unsigned char *ap = a, *bp = b; for (; size; size--) { int diff = *(ap++) - *(bp++); if (diff) return diff; } return 0; #endif } void *memchr(const void *buff, int c, size_t n) { const unsigned char *p = buff; while (n--) { if (*p == c) return (void *)p; p++; } return NULL; } void *memrchr(const void *buff, int c, size_t n) { const unsigned char *p = (const unsigned char *)buff + n; while (n--) { p--; if (*p == c) return (void *)p; } return NULL; } /* ========== String functions ========== */ char *strchr(const char *s, int c) { char ch = c; while (*s) { if (*s == ch) return (char *)s; s++; } return NULL; } char *strrchr(const char *s, int c) { char ch = c; const char *p = s + strlen(s); for (;;) { if (p == s) return NULL; p--; if (*p == ch) return (char *)p; } } int strcmp(const char *a, const char *b) { for (;;) { unsigned char ac = (unsigned char)*a; unsigned char bc = (unsigned char)*b; if (ac != bc) return ac - bc; if (ac == '\0') return 0; a++; b++; } } int strncmp(const char *a, const char *b, size_t len) { while (len--) { unsigned char ac = (unsigned char)*a; unsigned char bc = (unsigned char)*b; if (ac != bc) return ac - bc; if (ac == '\0') return 0; a++; b++; } return 0; } size_t strlen(const char *s) { size_t len = 0; while (*(s++)) len++; return len; } size_t strnlen(const char *s, size_t max) { size_t len = 0; while (max-- && *(s++)) len++; return len; } size_t strcspn(const char *s, const char *reject) { const char *start = s; while (*s) { if (strchr(reject, *s)) break; s++; } return s - start; } size_t strspn(const char *s, const char *accept) { const char *start = s; while (*s) { if (!strchr(accept, *s)) break; s++; } return s - start; } char *strdup(const char *s) { size_t len = strlen(s); char *d = malloc(len+1); if (!d) return NULL; memcpy(d, s, len); d[len] = '\0'; return d; } char *strndup(const char *s, size_t max) { size_t len = strlen(s); char *d; if (len > max) len = max; d = malloc(len+1); if (!d) return NULL; memcpy(d, s, len); d[len] = '\0'; return d; } /* ========== Heap allocation functions ========== */ void *malloc(size_t size) { if (size != 0) { return HeapAlloc(GetProcessHeap(), 0, size); } else { return NULL; } } void *realloc(void *p, size_t size) { if (p != NULL) { if (size != 0) { return HeapReAlloc(GetProcessHeap(), 0, p, size); } else { free(p); return NULL; } } else { return malloc(size); } } void free(void *p) { if (p) { HeapFree(GetProcessHeap(), 0, p); } } /* ========== Printing functions ========== */ static int init_buffer(FILE *f) { char *b = malloc(BUFSIZ); if (!b) return -1; f->buff = b; f->buffp = b; f->buffend = b + BUFSIZ; if (f->special_init) special_init(f); return 0; } static int flush_buffer(FILE *f) { /* TODO how to handle \n -> \r\n translation? */ size_t numwritten = fwrite(f->buff, f->buffp - f->buff, 1, f); f->buffp = f->buff; return numwritten == 1 ? 0 : -1; } int vfprintf(FILE *f, const char *fmt, va_list ap) { int charsprinted = 0; int savebuffstate = f->is_unbuffered; const char *s = fmt; f->is_unbuffered = 0; /* always buffer during the printf call */ while (*s) { if (*s != '%') { if (putc(*s, f) < 0) goto err; charsprinted++; s++; } else { /* == '%' */ int width = -1; int precision = -1; int altform = 0; int leftjust = 0; int startindex = charsprinted; char filler = ' '; char forcesign = 0; enum { TS_INT, TS_L, TS_LL } tsize = TS_INT; s++; for (;;) { if (*s == '0') { s++; filler = '0'; } else if (*s == '#') { /* Alternate form */ s++; altform = 1; } else if (*s == '-') { /* Left justification */ s++; leftjust = 1; } else if (*s == '+') { /* Always show sign */ s++; forcesign = '+'; } else if (*s == ' ') { /* Pad with one space if >0 */ s++; if (!forcesign) forcesign = ' '; } else break; } if (*s == '*') { width = va_arg(ap, int); s++; } else if (*s >= '1' && *s <= '9') { width = *s - '0'; for (;;) { char c = *++s; if (c < '0' || c > '9') break; width = 10*width + (c - '0'); } } if (leftjust) leftjust = width; if (*s == '.') { /* Precision */ filler = ' '; /* this is required for d, i, o, u, x and X */ s++; if (*s == '*') { precision = va_arg(ap, int); s++; } else { precision = *s - '0'; for (;;) { char c = *++s; if (c < '0' || c > '9') break; precision = 10*precision + (c - '0'); } } } while (*s == 'l') { tsize++; s++; } switch (*(s++)) { case '%': if (putc('%', f) < 0) goto err; charsprinted++; break; case 'c': { int v; if (!leftjust) { while (width > 1) { if (putc(' ', f) < 0) goto err; charsprinted++; width--; } } v = va_arg(ap, int); if (putc(v, f) < 0) goto err; charsprinted++; break; } case 's': { const char *str = va_arg(ap, const char *); size_t len; if (!str) str = "(null)"; len = precision>=0 ? strnlen(str, precision) : strlen(str); if (!leftjust && width >= 0) { while (len < (size_t)width) { if (putc(filler, f) < 0) goto err; width--; charsprinted++; } } while (len) { if (putc(*str, f) < 0) goto err; str++; len--; charsprinted++; } break; } case 'u': { /* and %d, see below */ uint64_t absnum; char nbuff[20]; /* length of 2^64 */ int len, neg, extra; neg = 0; if (tsize == TS_INT) absnum = va_arg(ap, unsigned); else if (tsize == TS_L) absnum = va_arg(ap, unsigned long); else absnum = va_arg(ap, unsigned long long); if (0) { int64_t num; case 'd': case 'i': if (tsize == TS_INT) num = va_arg(ap, int); else if (tsize == TS_L) num = va_arg(ap, long); else num = va_arg(ap, long long); if (num >= 0) { absnum = num; neg = 0; } else { absnum = -num; neg = 1; } } len = 0; if (absnum != 0) { for (;;) { int digit = absnum%10; int stop = !absnum; absnum /= 10; if (stop) break; nbuff[len++] = '0'+digit; } } else if (precision != 0) { nbuff[0] = '0'; len = 1; } /* Pad with space and/or 0 */ extra = (neg||forcesign) ? 1 : 0; if (len > precision) precision = len; width -= extra; if (filler == '0' && width > precision) precision = width; if (!leftjust) { while (precision < width) { if (putc(' ', f) < 0) goto err; width--; charsprinted++; } } if (neg) { if (putc('-', f) < 0) goto err; charsprinted++; } else if (forcesign) { if (putc(forcesign, f) < 0) goto err; charsprinted++; } while (len < precision) { if (putc('0', f) < 0) goto err; precision--; charsprinted++; } while (len) { len--; if (putc(nbuff[len], f) < 0) goto err; charsprinted++; } break; } case 'X': /* TODO %X should print with uppercase letters */ case 'x': { /* and %p, see below */ char nbuff[16]; /* length of 2^64 in hex */ uint64_t num; int len; if (tsize == TS_INT) num = va_arg(ap, unsigned); else if (tsize == TS_L) num = va_arg(ap, unsigned long); else num = va_arg(ap, unsigned long long); if (0) { case 'p': num = (_CSlulWinLibC_uintptr_t)va_arg(ap, void *); altform = 1; } len = 0; if (num != 0) { for (;;) { int digit = num & 0xF; int stop = !num; num >>= 4; if (stop) break; nbuff[len++] = hexdigits[digit]; } } else { altform = 0; /* no 0x prefix for zero */ if (precision != 0) { nbuff[0] = '0'; len = 1; } } if (precision > width) { filler = '0'; width = precision; } /* Pad with space or 0 */ if (altform) { width -= 2; precision -= 2; } if (len > precision) precision = len; if (filler == '0' && width > precision) precision = width; if (!leftjust) { while (precision < width) { if (putc(' ', f) < 0) goto err; width--; charsprinted++; } } if (altform) { if (putc('0', f) < 0) goto err; charsprinted++; if (putc('x', f) < 0) goto err; charsprinted++; } while (len < precision) { if (putc('0', f) < 0) goto err; precision--; charsprinted++; } while (len) { len--; if (putc(nbuff[len], f) < 0) goto err; charsprinted++; } break; } } /* Left justification */ if (leftjust) { int count = leftjust - (charsprinted-startindex); for (; count > 0; count--) { if (putc(' ', f) < 0) goto err; charsprinted++; } } } } f->is_unbuffered = savebuffstate; return charsprinted; err: f->is_unbuffered = savebuffstate; return -1; } int fputc(int c, FILE *f) { if (UNLIKELY(!f->buff)) { if (init_buffer(f) < 0) return EOF; } if (UNLIKELY(f->buffp == f->buffend)) { if (flush_buffer(f) < 0) return EOF; } *(f->buffp++) = c; if (f->is_unbuffered || (c == '\n' && !f->never_flush)) { if (flush_buffer(f) < 0) return EOF; } return (unsigned char)c; } int fputs(const char *s, FILE *f) { size_t len, bytesfree; if (UNLIKELY(!f->buff)) { if (init_buffer(f) < 0) return EOF; } len = strlen(s); bytesfree = BUFSIZ-(f->buffp-f->buff); if (LIKELY(len <= bytesfree)) { memcpy(f->buffp, s, len); f->buffp += len; if (f->is_unbuffered || memrchr(s, '\n', len) != NULL) { if (flush_buffer(f) < 0) return EOF; } return 0; } /* Does not fit in buffer */ memcpy(f->buffp, s, bytesfree); f->buffp += bytesfree; assert(f->buffp == f->buffend); if (flush_buffer(f) < 0) return EOF; s += bytesfree; len -= bytesfree; if (len < BUFSIZ) { memcpy(f->buffp, s, len); f->buffp += len; } else { /* TODO how to handle \n -> \r\n translation? */ return fwrite(s, len, 1, f) == 1 ? 0 : -1; } return 0; } int fprintf(FILE *f, const char *fmt, ...) { int ret; va_list ap; va_start(ap, fmt); ret = vfprintf(f, fmt, ap); va_end(ap); return ret; } int printf(const char *fmt, ...) { int ret; va_list ap; va_start(ap, fmt); ret = vfprintf(stdout, fmt, ap); va_end(ap); return ret; } int vsprintf(char *s, const char *fmt, va_list ap) { FILE dummy; int ret; dummy.hfile = INVALID_HANDLE_VALUE; dummy.error = 0; dummy.is_unbuffered = 0; dummy.is_console = 0; dummy.never_flush = 1; dummy.special_init = 0; dummy.buff = s; dummy.buffp = s; dummy.buffend = NULL; ret = vfprintf(&dummy, fmt, ap); s[ret] = '\0'; return ret; } int sprintf(char *s, const char *fmt, ...) { int ret; va_list ap; va_start(ap, fmt); ret = vsprintf(s, fmt, ap); va_end(ap); return ret; } int puts(const char *s) { return printf("%s\n", s); } void perror(const char *s) { LPWSTR buffW; if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_ARGUMENT_ARRAY, NULL, errno, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&buffW, 0, NULL)) { char *buffU = utf16_to_utf8(buffW); LocalFree(buffW); if (s) { fprintf(stderr, "%s: %s\n", s, buffU); } else { fprintf(stderr, "%s\n", buffU); } free(buffU); } else { fprintf(stderr, "%s%sError code 0x%x\n", s?s:"", s?": ":"", errno); } } /* ========== setjmp/longjmp ========== */ #ifndef _CSLULWINLIBC_HAVE_SETJMP /* If implemented, it is a macro. This function is provided for unsupported compilers, so code using longjmp will still compile (but abort at runtime) */ _CSLULWINLIBC_NORETURN void _CSlulWinLibC_longjmp(jmp_buf env, int val) { (void)env; (void)val; fputs("*** longjmp is unavailable! ***\n", stderr); abort(); } #endif /* ========== System functions ========== */ _CSLULWINLIBC_NORETURN void exit(int status) { if (!exiting) { exiting = 1; call_atexit(); } ExitProcess(status); } _CSLULWINLIBC_NORETURN void abort(void) { if (!aborting) { aborting = 1; fputs("*** Abnormal program termination! ***\n", stderr); ExitProcess(1); } else { ExitProcess(99); /* Recursive abortion */ } } void _assert(const char *expr, const char *filename, unsigned line) { fprintf(stderr, "Assertion failed: %s:%u: %s\n", filename, line, expr); abort(); } int atexit(void (*func)(void)) { if (num_exit_handlers == ATEXIT_MAX) return -1; exit_handlers[num_exit_handlers++] = func; return 0; } char *getenv(const char *name) { DWORD size; LPWSTR buffW; if (lastenv) { free(lastenv); lastenv = NULL; } { WCONV_START(name, NULL) size = GetEnvironmentVariableW(nameW, NULL, 0); if (size == 0) return NULL; buffW = malloc(size*2); if (!buffW) goto out; if (GetEnvironmentVariableW(nameW, buffW, size+1) < size) { lastenv = utf16_to_utf8(buffW); } free(buffW); out: WCONV_DONE(name); return lastenv; } } static int check_filename(const char *filename) { if (!filename || !*filename) { errno = ERROR_INVALID_NAME; return 0; } return 1; } int mkdir(const char *filename, mode_t mode) { BOOL ok; if (!check_filename(filename)) { return -1; } { WCONV_START(filename, -1) (void)mode; ok = CreateDirectoryW(filenameW, NULL); WCONV_DONE(filename) } if (ok) { return 0; } else { set_errno(); return -1; } } FILE *fopen(const char *filename, const char *mode) { HANDLE file; if (!check_filename(filename)) { return NULL; } { DWORD access, share, disp, attrs; WCONV_START(filename, NULL) /* FIXME only checks first char, supports only r and w (not + etc) */ if (*mode == 'w') { access = GENERIC_WRITE; share = FILE_SHARE_DELETE; disp = CREATE_ALWAYS; attrs = FILE_ATTRIBUTE_NORMAL; } else { access = GENERIC_READ; share = FILE_SHARE_DELETE|FILE_SHARE_READ; disp = OPEN_EXISTING; attrs = FILE_FLAG_SEQUENTIAL_SCAN; } file = CreateFileW(filenameW, access, share, NULL, disp, attrs, NULL); WCONV_DONE(filename) } if (file != INVALID_HANDLE_VALUE) { return alloc_file(file); } else { set_errno(); return NULL; } } int fclose(FILE *file) { BOOL ok = CloseHandle(file->hfile); if (file->buff) { free(file->buff); } free(file); return ok ? 0 : -1; } int fflush(FILE *file) { return FlushFileBuffers(file->hfile) ? 0 : -1; } int ferror(FILE *file) { return file->error; } size_t fread(void *buffer, size_t size, size_t nmemb, FILE *file) { DWORD totalsize = size*nmemb; DWORD numread; SetLastError(ERROR_SUCCESS); if (LIKELY(ReadFile(file->hfile, buffer, totalsize, &numread, NULL))) { return numread/size; } else { file->error = 1; return 0; } } size_t fwrite(const void *buffer, size_t size, size_t nmemb, FILE *file) { DWORD totalsize = size*nmemb; DWORD numwritten; SetLastError(ERROR_SUCCESS); if (UNLIKELY(file->hfile == INVALID_HANDLE_VALUE)) { if (file->special_init) special_init(file); else goto err; } if (UNLIKELY(!size || !nmemb)) return 0; if (file->is_console) { /* Console output */ BOOL ok; int ucs2len; LPWSTR bufferW = utf8_to_utf16(buffer, totalsize, &ucs2len); if (!bufferW) goto err; ok = WriteConsoleW(file->hfile, bufferW, ucs2len, NULL, NULL); WCONV_DONE(buffer) if (!ok) goto err; return nmemb; } else { /* File output */ if (!WriteFile(file->hfile, buffer, totalsize, &numwritten, NULL)) { goto err; } return numwritten/size; } err: file->error = 1; return 0; } /* TODO fseek/fseeko SetFilePointer has a low/high order DWORD. SetFilePointerEx has a "LARGE_INTEGER", but which Windows versions support it? */ int remove(const char *filename) { BOOL ok; if (!check_filename(filename)) { return -1; } { WCONV_START(filename, -1) ok = DeleteFileW(filenameW); WCONV_DONE(filename) } if (ok) { return 0; } else { set_errno(); return -1; } } /** * Checks if exe_filepath ends with \SUBDIR\FILENAME, where SUBDIR is * exe_subdir and FILENAME is any filename. If so, it returns the * file path without the \SUBDIR\FILENAME ending, and optionally * with an a string appended (such as a sub-directory) */ static char *extract_appdir(const char *exe_filepath, const char *exe_subdir, const char *append) { const char *fs = exe_filepath + strlen(exe_filepath); const char *ss = exe_subdir + strlen(exe_subdir); char *ret; size_t rootlen, appendlen; /* Find last \ (or /), i.e. separator between directory and EXE filename */ while (fs != exe_filepath) { char ch = *--fs; if (ch == '\\' || ch == '/') break; } /* Compare directory name, case insensitive. */ while (fs != exe_filepath && ss != exe_subdir) { unsigned char fc = *--fs; unsigned char sc = *--ss; if (fc >= 'a' && fc <= 'z') fc &= ~0x20; if (sc >= 'a' && sc <= 'z') sc &= ~0x20; if (fc != sc) return NULL; } if (ss != exe_subdir) return NULL; /* e.g. "in" instead of "bin" */ if (fs == exe_filepath) return NULL; if (fs[-1] != '\\' && fs[-1] != '/') return NULL; appendlen = append ? strlen(append) : 0; rootlen = fs-exe_filepath; ret = malloc(rootlen+appendlen+1); if (!ret) return NULL; memcpy(ret, exe_filepath, rootlen); if (append) { memcpy(ret+rootlen, append, appendlen); } ret[rootlen+appendlen] = '\0'; return ret; } char *cslulwinlibc_getappdir(const char *exe_subdir, const char *append) { WCHAR selfpathW[MAX_PATH+1]; /* +1 is probably not needed */ char *selfpath, *ret; int len; len = GetModuleFileNameW(NULL, selfpathW, MAX_PATH); if (len <= 0) goto err; selfpathW[len] = '\0'; selfpath = utf16_to_utf8(selfpathW); ret = extract_appdir(selfpath, exe_subdir, append); free(selfpath); return ret; err: return NULL; }