/* * libc.c * Minimal C standard library for MontaukOS userspace programs * Based on the proven libc from the DOOM port. * Copyright (c) 2025-2026 Daniel Hammer */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* ======================================================================== Raw syscall wrappers (C versions matching kernel ABI) ======================================================================== */ static inline long _mtk_syscall0(long nr) { long ret; __asm__ volatile("syscall" : "=a"(ret) : "a"(nr) : "rcx", "r11", "rdi", "rsi", "rdx", "r8", "r9", "r10", "memory"); return ret; } static inline long _mtk_syscall1(long nr, long a1) { long ret; __asm__ volatile( "mov %[a1], %%rdi\n\t" "syscall" : "=a"(ret) : "a"(nr), [a1] "r"(a1) : "rcx", "r11", "rdi", "rsi", "rdx", "r8", "r9", "r10", "memory"); return ret; } static inline long _mtk_syscall2(long nr, long a1, long a2) { long ret; __asm__ volatile( "mov %[a1], %%rdi\n\t" "mov %[a2], %%rsi\n\t" "syscall" : "=a"(ret) : "a"(nr), [a1] "r"(a1), [a2] "r"(a2) : "rcx", "r11", "rdi", "rsi", "rdx", "r8", "r9", "r10", "memory"); return ret; } static inline long _mtk_syscall3(long nr, long a1, long a2, long a3) { long ret; __asm__ volatile( "mov %[a1], %%rdi\n\t" "mov %[a2], %%rsi\n\t" "mov %[a3], %%rdx\n\t" "syscall" : "=a"(ret) : "a"(nr), [a1] "r"(a1), [a2] "r"(a2), [a3] "r"(a3) : "rcx", "r11", "rdi", "rsi", "rdx", "r8", "r9", "r10", "memory"); return ret; } static inline long _mtk_syscall4(long nr, long a1, long a2, long a3, long a4) { long ret; __asm__ volatile( "mov %[a1], %%rdi\n\t" "mov %[a2], %%rsi\n\t" "mov %[a3], %%rdx\n\t" "mov %[a4], %%r10\n\t" "syscall" : "=a"(ret) : "a"(nr), [a1] "r"(a1), [a2] "r"(a2), [a3] "r"(a3), [a4] "r"(a4) : "rcx", "r11", "rdi", "rsi", "rdx", "r8", "r9", "r10", "memory"); return ret; } /* Syscall numbers */ #define SYS_EXIT 0 #define SYS_YIELD 1 #define SYS_SLEEP_MS 2 #define SYS_PRINT 4 #define SYS_PUTCHAR 5 #define SYS_OPEN 6 #define SYS_READ 7 #define SYS_GETSIZE 8 #define SYS_CLOSE 9 #define SYS_READDIR 10 #define SYS_ALLOC 11 #define SYS_ALLOC_EAGER 187 #define SYS_FREE 12 #define SYS_GETMILLISECONDS 14 #define SYS_GETCHAR 18 #define SYS_GETCHAR_NB 166 #define SYS_TERMINAL_ATTACHED 177 #define SYS_SPAWN 20 #define SYS_WAITPID 23 #define SYS_GETARGS 25 #define SYS_GETTIME 28 #define SYS_FWRITE 41 #define SYS_FCREATE 42 #define SYS_FDELETE 77 #define SYS_FMKDIR 78 #define SYS_GETTZ 91 #define SYS_FRENAME 94 #define SYS_GETCWD 95 #define SYS_CHDIR 96 #define SYS_GETPID 3 #define SYS_DUPHANDLE 98 #define SYS_KILL 62 #define SYS_STAT 152 #define SYS_UTIME 167 #define SYS_MMAP_ANON 168 #define SYS_MUNMAP 169 #define SYS_MPROTECT 170 #define SYS_GETENVIRON 171 #define SYS_SETENVIRON 172 #define SYS_SPAWN_ENV 173 /* ======================================================================== errno ======================================================================== */ int errno = 0; /* ======================================================================== Internal helpers ======================================================================== */ struct _mtk_datetime { uint16_t year; uint8_t month; uint8_t day; uint8_t hour; uint8_t minute; uint8_t second; }; /* Mirrors montauk::abi::FileStat, the SYS_STAT output layout. Kept in step with Api/Syscall.hpp by hand, like the syscall numbers above. */ struct _mtk_filestat { uint64_t size; int64_t mtime; int64_t ctime; int64_t atime; uint32_t mode; /* POSIX i_mode bits: type + permissions */ uint32_t isDir; }; struct _DIR { int count; int index; struct dirent entry; char names[256][NAME_MAX + 1]; }; struct _env_entry { char *name; char *value; char *string; }; static struct _env_entry _env_entries[64]; static char *_environ_vector[65]; char **environ = _environ_vector; static int _environ_initialized; static int _environ_loading; void __libc_init_environ(const char *blob, size_t len); static sighandler_t _signal_handlers[32]; static const char *_weekday_short[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; static const char *_weekday_long[] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }; static const char *_month_short[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; static const char *_month_long[] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; static int _is_leap_year(int year) { return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); } static int _days_in_month(int year, int month) { static const int days[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; if (month == 1 && _is_leap_year(year)) return 29; return days[month]; } static int _day_of_year(int year, int month, int day) { int yday = 0; for (int i = 0; i < month; i++) { yday += _days_in_month(year, i); } return yday + day - 1; } static time_t _epoch_from_datetime(int year, int month, int day, int hour, int minute, int second) { time_t days = 0; for (int y = 1970; y < year; y++) { days += _is_leap_year(y) ? 366 : 365; } for (int m = 1; m < month; m++) { days += _days_in_month(year, m - 1); } days += day - 1; return days * 86400 + hour * 3600 + minute * 60 + second; } static void _tm_from_epoch(time_t epoch, struct tm *out) { time_t days = epoch / 86400; time_t rem = epoch % 86400; int year = 1970; if (rem < 0) { rem += 86400; days--; } while (days < 0) { year--; days += _is_leap_year(year) ? 366 : 365; } while (1) { int diy = _is_leap_year(year) ? 366 : 365; if (days < diy) break; days -= diy; year++; } int month = 0; while (month < 11) { int dim = _days_in_month(year, month); if (days < dim) break; days -= dim; month++; } out->tm_year = year - 1900; out->tm_mon = month; out->tm_mday = (int)days + 1; out->tm_hour = (int)(rem / 3600); rem %= 3600; out->tm_min = (int)(rem / 60); out->tm_sec = (int)(rem % 60); out->tm_yday = _day_of_year(year, month, out->tm_mday); out->tm_wday = (int)(((epoch / 86400) + 4) % 7); if (out->tm_wday < 0) out->tm_wday += 7; out->tm_isdst = 0; } static int _get_tz_offset_minutes(void) { return (int)_mtk_syscall0(SYS_GETTZ); } static time_t _current_time_epoch(void) { struct _mtk_datetime dt = {}; _mtk_syscall1(SYS_GETTIME, (long)&dt); return _epoch_from_datetime((int)dt.year, (int)dt.month, (int)dt.day, (int)dt.hour, (int)dt.minute, (int)dt.second) - (time_t)_get_tz_offset_minutes() * 60; } static int _append_char(char *buf, size_t max, size_t *pos, char c) { if (*pos + 1 >= max) return 0; buf[(*pos)++] = c; return 1; } static int _append_str(char *buf, size_t max, size_t *pos, const char *s) { while (*s) { if (!_append_char(buf, max, pos, *s++)) return 0; } return 1; } static int _append_num(char *buf, size_t max, size_t *pos, int value, int width, char pad) { char tmp[16]; int i = 0; if (value < 0) value = -value; do { tmp[i++] = (char)('0' + (value % 10)); value /= 10; } while (value > 0 && i < (int)sizeof(tmp)); while (i < width) { tmp[i++] = pad; } while (i > 0) { if (!_append_char(buf, max, pos, tmp[--i])) return 0; } return 1; } static int _path_is_directory(const char *path) { const char *names[1]; return (int)_mtk_syscall3(SYS_READDIR, (long)path, (long)names, 1L) >= 0; } static int _path_local_prefix(const char *path, char *buf, size_t size) { if (buf == NULL || size == 0) return 0; buf[0] = '\0'; if (path == NULL) return 0; const char *local = path; for (int i = 0; local[i]; i++) { if (local[i] == ':') { local += i + 1; if (local[0] == '/') local++; break; } } size_t len = strlen(local); while (len > 0 && local[len - 1] == '/') len--; if (len == 0) return 0; if (len >= size - 1) len = size - 2; memcpy(buf, local, len); buf[len++] = '/'; buf[len] = '\0'; return (int)len; } static int _find_env_slot(const char *name) { for (int i = 0; i < (int)(sizeof(_env_entries) / sizeof(_env_entries[0])); i++) { if (_env_entries[i].name != NULL && strcmp(_env_entries[i].name, name) == 0) return i; } return -1; } static int _alloc_env_slot(void) { for (int i = 0; i < (int)(sizeof(_env_entries) / sizeof(_env_entries[0])); i++) { if (_env_entries[i].name == NULL) return i; } return -1; } static void _rebuild_environ(void) { int out = 0; for (int i = 0; i < (int)(sizeof(_env_entries) / sizeof(_env_entries[0])); i++) { if (_env_entries[i].string != NULL) _environ_vector[out++] = _env_entries[i].string; } _environ_vector[out] = NULL; } static int _sync_environ_to_kernel(void) { static char blob[4096]; size_t out = 0; for (int i = 0; i < (int)(sizeof(_env_entries) / sizeof(_env_entries[0])); i++) { if (_env_entries[i].string == NULL) continue; size_t len = strlen(_env_entries[i].string) + 1; if (out + len >= sizeof(blob)) return -1; memcpy(blob + out, _env_entries[i].string, len); out += len; } blob[out++] = '\0'; return _mtk_syscall2(SYS_SETENVIRON, (long)blob, (long)out) < 0 ? -1 : 0; } static void _ensure_environ_initialized(void) { if (_environ_initialized) return; static char blob[4096]; int len = (int)_mtk_syscall2(SYS_GETENVIRON, (long)blob, (long)sizeof(blob)); if (len > 0) __libc_init_environ(blob, (size_t)len); else _environ_initialized = 1; } /* ======================================================================== string.h functions ======================================================================== */ void *memcpy(void *dest, const void *src, size_t n) { unsigned char *d = (unsigned char *)dest; const unsigned char *s = (const unsigned char *)src; /* Byte copy until 8-byte aligned */ while (n && ((uint64_t)d & 7)) { *d++ = *s++; n--; } /* Bulk 8-byte copy */ uint64_t *d8 = (uint64_t *)d; const uint64_t *s8 = (const uint64_t *)s; size_t words = n / 8; for (size_t i = 0; i < words; i++) d8[i] = s8[i]; /* Remainder */ d = (unsigned char *)(d8 + words); s = (const unsigned char *)(s8 + words); for (size_t i = 0; i < (n & 7); i++) d[i] = s[i]; return dest; } void *memset(void *s, int c, size_t n) { unsigned char *p = (unsigned char *)s; unsigned char v = (unsigned char)c; /* Byte fill until 8-byte aligned */ while (n && ((uint64_t)p & 7)) { *p++ = v; n--; } /* Bulk 8-byte fill */ uint64_t v8 = v; v8 |= v8 << 8; v8 |= v8 << 16; v8 |= v8 << 32; uint64_t *p8 = (uint64_t *)p; size_t words = n / 8; for (size_t i = 0; i < words; i++) p8[i] = v8; /* Remainder */ p = (unsigned char *)(p8 + words); for (size_t i = 0; i < (n & 7); i++) p[i] = v; return s; } void *memmove(void *dest, const void *src, size_t n) { unsigned char *d = (unsigned char *)dest; const unsigned char *s = (const unsigned char *)src; if (d < s || d >= s + n) { memcpy(dest, src, n); } else { /* Backward copy — bulk 8 bytes at a time from end */ d += n; s += n; while (n && ((uint64_t)d & 7)) { *--d = *--s; n--; } uint64_t *d8 = (uint64_t *)d; const uint64_t *s8 = (const uint64_t *)s; size_t words = n / 8; for (size_t i = 1; i <= words; i++) d8[-(long)i] = s8[-(long)i]; d = (unsigned char *)(d8 - words); s = (const unsigned char *)(s8 - words); for (size_t i = 1; i <= (n & 7); i++) d[-(long)i] = s[-(long)i]; } return dest; } int memcmp(const void *s1, const void *s2, size_t n) { const unsigned char *a = (const unsigned char *)s1; const unsigned char *b = (const unsigned char *)s2; for (size_t i = 0; i < n; i++) { if (a[i] != b[i]) return a[i] < b[i] ? -1 : 1; } return 0; } void *memchr(const void *s, int c, size_t n) { const unsigned char *p = (const unsigned char *)s; unsigned char uc = (unsigned char)c; for (size_t i = 0; i < n; i++) { if (p[i] == uc) return (void *)(p + i); } return (void *)0; } size_t strlen(const char *s) { const char *p = s; while (*p) p++; return p - s; } size_t strspn(const char *s, const char *accept) { size_t n = 0; while (s[n]) { if (strchr(accept, s[n]) == NULL) break; n++; } return n; } size_t strcspn(const char *s, const char *reject) { size_t n = 0; while (s[n]) { if (strchr(reject, s[n]) != NULL) break; n++; } return n; } int strcmp(const char *a, const char *b) { const char *pa = a, *pb = b; while (*pa && *pa == *pb) { pa++; pb++; } return (unsigned char)*pa - (unsigned char)*pb; } int strncmp(const char *a, const char *b, size_t n) { for (size_t i = 0; i < n; i++) { if (a[i] != b[i] || a[i] == 0) return (unsigned char)a[i] - (unsigned char)b[i]; } return 0; } char *strcpy(char *dest, const char *src) { char *d = dest; while ((*d++ = *src++)); return dest; } char *strncpy(char *dest, const char *src, size_t n) { size_t i; for (i = 0; i < n && src[i]; i++) dest[i] = src[i]; for (; i < n; i++) dest[i] = '\0'; return dest; } char *strcat(char *dest, const char *src) { char *d = dest + strlen(dest); while ((*d++ = *src++)); return dest; } char *strncat(char *dest, const char *src, size_t n) { char *d = dest + strlen(dest); size_t i; for (i = 0; i < n && src[i]; i++) d[i] = src[i]; d[i] = '\0'; return dest; } char *strchr(const char *s, int c) { while (*s) { if (*s == (char)c) return (char *)s; s++; } return (c == 0) ? (char *)s : NULL; } char *strrchr(const char *s, int c) { const char *last = NULL; while (*s) { if (*s == (char)c) last = s; s++; } if (c == 0) return (char *)s; return (char *)last; } char *strpbrk(const char *s, const char *accept) { while (*s) { if (strchr(accept, *s) != NULL) return (char *)s; s++; } return NULL; } int strcasecmp(const char *a, const char *b) { while (*a && ((*a >= 'A' && *a <= 'Z') ? *a + 32 : *a) == ((*b >= 'A' && *b <= 'Z') ? *b + 32 : *b)) { a++; b++; } int ca = (*a >= 'A' && *a <= 'Z') ? *a + 32 : *a; int cb = (*b >= 'A' && *b <= 'Z') ? *b + 32 : *b; return ca - cb; } int strncasecmp(const char *a, const char *b, size_t n) { for (size_t i = 0; i < n; i++) { int ca = (a[i] >= 'A' && a[i] <= 'Z') ? a[i] + 32 : a[i]; int cb = (b[i] >= 'A' && b[i] <= 'Z') ? b[i] + 32 : b[i]; if (ca != cb || ca == 0) return ca - cb; } return 0; } int strcoll(const char *s1, const char *s2) { return strcmp(s1, s2); } char *strstr(const char *haystack, const char *needle) { if (!*needle) return (char *)haystack; size_t nlen = strlen(needle); while (*haystack) { if (strncmp(haystack, needle, nlen) == 0) return (char *)haystack; haystack++; } return NULL; } /* Forward declaration for strdup */ void *malloc(size_t size); char *strdup(const char *s) { size_t len = strlen(s) + 1; char *d = (char *)malloc(len); if (d) memcpy(d, s, len); return d; } char *strndup(const char *s, size_t n) { size_t len = 0; char *d; if (s == NULL) return NULL; while (len < n && s[len] != '\0') len++; d = (char *)malloc(len + 1); if (d == NULL) return NULL; memcpy(d, s, len); d[len] = '\0'; return d; } const char *strerror(int errnum) { switch (errnum) { case 0: return "success"; case EPERM: return "operation not permitted"; case ENOENT: return "no such file or directory"; case EIO: return "input/output error"; case EBADF: return "bad file descriptor"; case ENOMEM: return "not enough memory"; case EACCES: return "permission denied"; case EEXIST: return "file exists"; case ENOTDIR:return "not a directory"; case EISDIR: return "is a directory"; case EINVAL: return "invalid argument"; case ERANGE: return "result out of range"; case ENOSYS: return "function not implemented"; default: return "error"; } } /* ======================================================================== ctype.h functions ======================================================================== */ int isalpha(int c) { return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); } int isdigit(int c) { return c >= '0' && c <= '9'; } int isalnum(int c) { return isalpha(c) || isdigit(c); } int isspace(int c) { return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\v'; } int isupper(int c) { return c >= 'A' && c <= 'Z'; } int islower(int c) { return c >= 'a' && c <= 'z'; } int isprint(int c) { return c >= 0x20 && c <= 0x7E; } int ispunct(int c) { return isprint(c) && !isalnum(c) && c != ' '; } int isxdigit(int c) { return isdigit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); } int iscntrl(int c) { return (c >= 0 && c < 0x20) || c == 0x7F; } int isgraph(int c) { return c > 0x20 && c <= 0x7E; } int toupper(int c) { return (c >= 'a' && c <= 'z') ? c - 32 : c; } int tolower(int c) { return (c >= 'A' && c <= 'Z') ? c + 32 : c; } /* ======================================================================== Heap allocator (free-list, backed by SYS_ALLOC) ======================================================================== */ #define HEAP_MAGIC 0x4D544B4845415041ULL /* "MTKHEAPA" */ #define DIRECT_MAGIC 0x4D544B4449524543ULL /* "MTKDIREC" */ #define FREED_MAGIC 0x4D544B4652454544ULL /* "MTKFREED" */ #define HEAP_ALIGN 16ULL #define DIRECT_THRESHOLD (256ULL * 1024ULL) /* A direct mapping is one object the caller sized itself, so it is nearly always written end to end (an image buffer, a loaded file). Committing it up front replaces one page fault, one kernel mutex acquire and one VMA walk per 4 KiB with a single loop -- thousands of traps for a decoded image. Beyond the cap, stay lazy: a very large mapping is more likely to be a sparsely touched reservation, and eager commit would pin the lot. */ #define EAGER_DIRECT_LIMIT (32ULL * 1024ULL * 1024ULL) struct HeapHeader { uint64_t magic; uint64_t requested_size; uint64_t block_size; uint64_t cookie; }; struct FreeNode { uint64_t magic; uint64_t size; struct FreeNode *next; }; /* Segregated free lists for small and medium blocks. The minimum class must hold both an allocated header and a free-list node. */ #define NUM_BUCKETS 7 static const uint64_t BUCKET_SIZES[NUM_BUCKETS] = { 64, 128, 256, 512, 1024, 2048, 4096 }; static struct FreeNode *g_buckets[NUM_BUCKETS] = {}; static struct FreeNode g_overflow = { FREED_MAGIC, 0, NULL }; static int g_heapInit = 0; static volatile uint32_t g_heapLock = 0; static void heap_lock(void) { while (__atomic_exchange_n(&g_heapLock, 1, __ATOMIC_ACQUIRE) != 0) _mtk_syscall0(SYS_YIELD); } static void heap_unlock(void) { __atomic_store_n(&g_heapLock, 0, __ATOMIC_RELEASE); } static int heap_total_size(size_t requested, uint64_t *out) { uint64_t value = (uint64_t)requested; if (value > UINT64_MAX - sizeof(struct HeapHeader) - (HEAP_ALIGN - 1)) return 0; *out = (value + sizeof(struct HeapHeader) + (HEAP_ALIGN - 1)) & ~(HEAP_ALIGN - 1); return 1; } static uint64_t heap_cookie(const struct HeapHeader *hdr, uint64_t block_size) { return ((uint64_t)(uintptr_t)hdr >> 4) ^ block_size ^ hdr->magic; } static int heap_bucket_index(uint64_t blockSize) { if (blockSize <= 64) return 0; if (blockSize <= 128) return 1; if (blockSize <= 256) return 2; if (blockSize <= 512) return 3; if (blockSize <= 1024) return 4; if (blockSize <= 2048) return 5; if (blockSize <= 4096) return 6; return -1; } /* Insert into overflow list (sorted by address, with coalescing) */ static void heap_insert_overflow(void *ptr, uint64_t size) { struct FreeNode *node = (struct FreeNode *)ptr; node->magic = FREED_MAGIC; node->size = size; struct FreeNode *prev = &g_overflow; struct FreeNode *cur = g_overflow.next; while (cur != NULL && cur < node) { prev = cur; cur = cur->next; } int merged_prev = 0; if (prev != &g_overflow && (uint8_t *)prev + prev->size == (uint8_t *)node) { prev->size += size; node = prev; merged_prev = 1; } if (cur != NULL && (uint8_t *)node + node->size == (uint8_t *)cur) { node->size += cur->size; node->next = cur->next; if (!merged_prev) prev->next = node; } else if (!merged_prev) { node->next = cur; prev->next = node; } } /* Take a block >= needed from overflow. Splits remainder back. */ static void *heap_take_overflow(uint64_t needed, uint64_t *actual_size) { struct FreeNode *prev = &g_overflow; struct FreeNode *cur = g_overflow.next; while (cur != NULL) { if (cur->size >= needed) { uint64_t blockSize = cur->size; prev->next = cur->next; uint64_t min_free = (sizeof(struct FreeNode) + HEAP_ALIGN - 1) & ~(HEAP_ALIGN - 1); if (blockSize >= needed + min_free) { heap_insert_overflow((uint8_t *)cur + needed, blockSize - needed); blockSize = needed; } *actual_size = blockSize; return (void *)cur; } prev = cur; cur = cur->next; } return NULL; } /* Next slab size for heap growth. Doubling slabs keeps syscall and VMA metadata traffic logarithmic in total heap size. */ static uint64_t g_heap_slab = 16 * 0x1000; static void heap_grow(uint64_t bytes) { uint64_t want = (bytes + 0xFFF) & ~0xFFFULL; if (want < 0x4000) want = 0x4000; uint64_t slab = (want > g_heap_slab) ? want : g_heap_slab; if (g_heap_slab < 4 * 1024 * 1024) g_heap_slab *= 2; void *mem = (void *)_mtk_syscall1(SYS_ALLOC, (long)slab); if (mem == NULL && slab > want) { /* Big slab refused (low memory): retry with the exact need. */ slab = want; mem = (void *)_mtk_syscall1(SYS_ALLOC, (long)slab); } if (mem != NULL) heap_insert_overflow(mem, slab); } /* Refill a small-block bucket from overflow */ static int heap_refill_bucket(int idx) { uint64_t bsize = BUCKET_SIZES[idx]; uint64_t chunk = (bsize < 4096) ? 4096 : bsize; uint64_t actual = 0; void *block = heap_take_overflow(chunk, &actual); if (block == NULL) { heap_grow(chunk); block = heap_take_overflow(chunk, &actual); if (block == NULL) return 0; } /* A small unsplittable tail belongs to the overflow list, rather than disappearing when the main chunk is carved into bucket blocks. */ if (actual - chunk >= ((sizeof(struct FreeNode) + HEAP_ALIGN - 1) & ~(HEAP_ALIGN - 1))) heap_insert_overflow((uint8_t *)block + chunk, actual - chunk); uint64_t count = chunk / bsize; for (uint64_t i = 0; i < count; i++) { struct FreeNode *node = (struct FreeNode *)((uint8_t *)block + i * bsize); node->magic = FREED_MAGIC; node->size = bsize; node->next = g_buckets[idx]; g_buckets[idx] = node; } return 1; } static void *heap_malloc_locked(size_t size) { uint64_t needed; if (!heap_total_size(size, &needed)) return NULL; /* Large objects get their own page mapping, so free can promptly return both physical memory and virtual space to the kernel. */ if (needed >= DIRECT_THRESHOLD) { if (needed > UINT64_MAX - 0xFFFULL) return NULL; uint64_t mapping_size = (needed + 0xFFFULL) & ~0xFFFULL; long alloc_nr = (mapping_size <= EAGER_DIRECT_LIMIT) ? SYS_ALLOC_EAGER : SYS_ALLOC; struct HeapHeader *hdr = (struct HeapHeader *) _mtk_syscall1(alloc_nr, (long)mapping_size); if (hdr == NULL) return NULL; hdr->magic = DIRECT_MAGIC; hdr->requested_size = size; hdr->block_size = mapping_size; hdr->cookie = heap_cookie(hdr, mapping_size); return (uint8_t *)hdr + sizeof(*hdr); } if (!g_heapInit) { heap_grow(16 * 0x1000); g_heapInit = 1; } int idx = heap_bucket_index(needed); if (idx >= 0) { /* Small allocation — use segregated bucket (O(1)) */ if (g_buckets[idx] == NULL && !heap_refill_bucket(idx)) return NULL; struct FreeNode *node = g_buckets[idx]; g_buckets[idx] = node->next; uint64_t block_size = node->size; struct HeapHeader *hdr = (struct HeapHeader *)node; hdr->magic = HEAP_MAGIC; hdr->requested_size = size; hdr->block_size = block_size; hdr->cookie = heap_cookie(hdr, hdr->block_size); return (void *)((uint8_t *)hdr + sizeof(struct HeapHeader)); } /* Large allocation — search overflow list */ uint64_t actual = 0; void *block = heap_take_overflow(needed, &actual); if (block == NULL) { heap_grow(needed); block = heap_take_overflow(needed, &actual); if (block == NULL) return NULL; } struct HeapHeader *hdr = (struct HeapHeader *)block; hdr->magic = HEAP_MAGIC; hdr->requested_size = size; hdr->block_size = actual; hdr->cookie = heap_cookie(hdr, actual); return (void *)((uint8_t *)hdr + sizeof(struct HeapHeader)); } static int heap_header_valid(const struct HeapHeader *hdr) { return (hdr->magic == HEAP_MAGIC || hdr->magic == DIRECT_MAGIC) && hdr->block_size >= sizeof(struct HeapHeader) && (hdr->block_size & (HEAP_ALIGN - 1)) == 0 && hdr->cookie == heap_cookie(hdr, hdr->block_size) && hdr->requested_size <= hdr->block_size - sizeof(struct HeapHeader); } static void heap_free_locked(void *ptr) { if (ptr == NULL) return; struct HeapHeader *hdr = (struct HeapHeader *)((uint8_t *)ptr - sizeof(struct HeapHeader)); if (!heap_header_valid(hdr)) return; uint64_t blockSize = hdr->block_size; int direct = hdr->magic == DIRECT_MAGIC; hdr->magic = FREED_MAGIC; if (direct) { _mtk_syscall1(SYS_FREE, (long)hdr); return; } int idx = heap_bucket_index(blockSize); if (idx >= 0) { /* Small block — push onto bucket (O(1)) */ struct FreeNode *node = (struct FreeNode *)hdr; node->magic = FREED_MAGIC; node->size = BUCKET_SIZES[idx]; node->next = g_buckets[idx]; g_buckets[idx] = node; } else { /* Large block — sorted insert with coalescing */ heap_insert_overflow((void *)hdr, blockSize); } } void *malloc(size_t size) { void *result; heap_lock(); result = heap_malloc_locked(size); heap_unlock(); return result; } void free(void *ptr) { if (ptr == NULL) return; heap_lock(); heap_free_locked(ptr); heap_unlock(); } void *calloc(size_t nmemb, size_t size) { /* Check for multiplication overflow */ if (nmemb != 0 && size > (size_t)-1 / nmemb) return NULL; size_t total = nmemb * size; void *p = malloc(total); if (p) memset(p, 0, total); return p; } void *realloc(void *ptr, size_t size) { if (ptr == NULL) return malloc(size); if (size == 0) { free(ptr); return NULL; } uint64_t newNeed; if (!heap_total_size(size, &newNeed)) return NULL; heap_lock(); struct HeapHeader *hdr = (struct HeapHeader *)((uint8_t *)ptr - sizeof(struct HeapHeader)); if (!heap_header_valid(hdr)) { heap_unlock(); return NULL; } uint64_t old = hdr->requested_size; uint64_t oldBlock = hdr->block_size; if (newNeed <= oldBlock) { /* Retain the actual extent. Losing it here makes the tail impossible to recover when this block is later freed. */ hdr->requested_size = size; hdr->cookie = heap_cookie(hdr, oldBlock); heap_unlock(); return ptr; } void *newp = heap_malloc_locked(size); if (newp == NULL) { heap_unlock(); return NULL; } size_t copySize = old < size ? old : size; memcpy(newp, ptr, copySize); heap_free_locked(ptr); heap_unlock(); return newp; } /* ======================================================================== stdlib.h functions ======================================================================== */ int abs(int x) { return x < 0 ? -x : x; } long labs(long x) { return x < 0 ? -x : x; } int atoi(const char *s) { int neg = 0, val = 0; while (isspace((unsigned char)*s)) s++; if (*s == '-') { neg = 1; s++; } else if (*s == '+') { s++; } while (isdigit((unsigned char)*s)) { val = val * 10 + (*s - '0'); s++; } return neg ? -val : val; } long strtol(const char *nptr, char **endptr, int base) { const char *s = nptr; long val = 0; int neg = 0; while (isspace((unsigned char)*s)) s++; if (*s == '-') { neg = 1; s++; } else if (*s == '+') { s++; } if (base == 0) { if (*s == '0' && (s[1] == 'x' || s[1] == 'X')) { base = 16; s += 2; } else if (*s == '0') { base = 8; s++; } else base = 10; } else if (base == 16 && *s == '0' && (s[1] == 'x' || s[1] == 'X')) { s += 2; } while (*s) { int digit; if (*s >= '0' && *s <= '9') digit = *s - '0'; else if (*s >= 'a' && *s <= 'z') digit = *s - 'a' + 10; else if (*s >= 'A' && *s <= 'Z') digit = *s - 'A' + 10; else break; if (digit >= base) break; val = val * base + digit; s++; } if (endptr) *endptr = (char *)s; return neg ? -val : val; } unsigned long strtoul(const char *nptr, char **endptr, int base) { const char *s = nptr; unsigned long val = 0; while (isspace((unsigned char)*s)) s++; /* strtoul allows an optional leading + or - (result is negated for -) */ int neg = 0; if (*s == '-') { neg = 1; s++; } else if (*s == '+') { s++; } if (base == 0) { if (*s == '0' && (s[1] == 'x' || s[1] == 'X')) { base = 16; s += 2; } else if (*s == '0') { base = 8; s++; } else base = 10; } else if (base == 16 && *s == '0' && (s[1] == 'x' || s[1] == 'X')) { s += 2; } while (*s) { int digit; if (*s >= '0' && *s <= '9') digit = *s - '0'; else if (*s >= 'a' && *s <= 'z') digit = *s - 'a' + 10; else if (*s >= 'A' && *s <= 'Z') digit = *s - 'A' + 10; else break; if (digit >= base) break; val = val * (unsigned long)base + (unsigned long)digit; s++; } if (endptr) *endptr = (char *)s; return neg ? (unsigned long)(-(long)val) : val; } char *getenv(const char *name) { if (name == NULL || name[0] == '\0') return NULL; _ensure_environ_initialized(); int slot = _find_env_slot(name); if (slot < 0) return NULL; return _env_entries[slot].value; } int setenv(const char *name, const char *value, int overwrite) { if (name == NULL || name[0] == '\0' || strchr(name, '=') != NULL || value == NULL) { errno = EINVAL; return -1; } _ensure_environ_initialized(); int slot = _find_env_slot(name); if (slot >= 0 && !overwrite) { return 0; } size_t required = 1 + strlen(name) + strlen(value) + 2; /* final NUL, '=', entry NUL */ for (int i = 0; i < (int)(sizeof(_env_entries) / sizeof(_env_entries[0])); i++) { if (i != slot && _env_entries[i].string != NULL) required += strlen(_env_entries[i].string) + 1; } if (required >= 4096) { errno = E2BIG; return -1; } if (slot < 0) { slot = _alloc_env_slot(); if (slot < 0) { errno = ENOMEM; return -1; } } char *new_name = strdup(name); char *new_value = strdup(value); char *new_string = (char *)malloc(strlen(name) + strlen(value) + 2); if (new_name == NULL || new_value == NULL || new_string == NULL) { free(new_name); free(new_value); free(new_string); errno = ENOMEM; return -1; } strcpy(new_string, name); strcat(new_string, "="); strcat(new_string, value); free(_env_entries[slot].name); free(_env_entries[slot].value); free(_env_entries[slot].string); _env_entries[slot].name = new_name; _env_entries[slot].value = new_value; _env_entries[slot].string = new_string; _rebuild_environ(); if (!_environ_loading && _sync_environ_to_kernel() < 0) { errno = EIO; return -1; } return 0; } int unsetenv(const char *name) { if (name == NULL || name[0] == '\0' || strchr(name, '=') != NULL) { errno = EINVAL; return -1; } _ensure_environ_initialized(); int slot = _find_env_slot(name); if (slot < 0) return 0; free(_env_entries[slot].name); free(_env_entries[slot].value); free(_env_entries[slot].string); _env_entries[slot].name = NULL; _env_entries[slot].value = NULL; _env_entries[slot].string = NULL; _rebuild_environ(); if (_sync_environ_to_kernel() < 0) { errno = EIO; return -1; } return 0; } int putenv(char *string) { if (string == NULL) { errno = EINVAL; return -1; } char *eq = strchr(string, '='); if (eq == NULL || eq == string) { errno = EINVAL; return -1; } size_t name_len = (size_t)(eq - string); char *name = (char *)malloc(name_len + 1); if (name == NULL) { errno = ENOMEM; return -1; } memcpy(name, string, name_len); name[name_len] = '\0'; int rc = setenv(name, eq + 1, 1); free(name); return rc; } static void (*_atexit_funcs[32])(void); static int _atexit_count = 0; int atexit(void (*func)(void)) { if (_atexit_count >= 32 || func == NULL) return -1; _atexit_funcs[_atexit_count++] = func; return 0; } void exit(int status) { for (int i = _atexit_count - 1; i >= 0; i--) _atexit_funcs[i](); _mtk_syscall1(SYS_EXIT, (long)status); __builtin_unreachable(); } void abort(void) { _mtk_syscall1(SYS_PRINT, (long)"abort() called\n"); _mtk_syscall1(SYS_EXIT, 1); __builtin_unreachable(); } static int _system_parse_drive_prefix(const char *s) { if (s == NULL || s[0] < '0' || s[0] > '9') return -1; int drive = 0; int i = 0; while (s[i] >= '0' && s[i] <= '9') { drive = drive * 10 + (s[i] - '0'); i++; } return (s[i] == ':') ? drive : -1; } static void _system_build_drive_path(int drive, const char *leaf, char *out, size_t out_size) { snprintf(out, out_size, "%d:/%s", drive, (leaf != NULL) ? leaf : ""); } static int _system_try_spawn(const char *path, const char *args) { int pid = (int)_mtk_syscall2(SYS_SPAWN, (long)path, (long)args); if (pid < 0) return -1; _mtk_syscall1(SYS_WAITPID, (long)pid); return 0; } int system(const char *command) { char command_buf[256]; char cwd[128] = ""; char path[256]; char leaf[128]; char *args = NULL; int current_drive = 0; int has_slash = 0; if (command == NULL) return -1; while (*command == ' ' || *command == '\t' || *command == '\n') command++; if (*command == '\0') return 0; size_t command_len = strlen(command); if (command_len >= sizeof(command_buf)) { errno = EINVAL; return -1; } memcpy(command_buf, command, command_len + 1); char *prog = command_buf; while (*prog == ' ' || *prog == '\t' || *prog == '\n') prog++; if (*prog == '\0') return 0; char *split = prog; while (*split != '\0' && *split != ' ' && *split != '\t' && *split != '\n') { if (*split == '/') has_slash = 1; split++; } if (*split != '\0') { *split++ = '\0'; while (*split == ' ' || *split == '\t' || *split == '\n') split++; if (*split != '\0') args = split; } if (getcwd(cwd, sizeof(cwd)) != NULL) { int drive = _system_parse_drive_prefix(cwd); if (drive >= 0) current_drive = drive; } if (_system_parse_drive_prefix(prog) >= 0 || prog[0] == '/' || prog[0] == '.' || has_slash) { if (_system_try_spawn(prog, args) == 0) return 0; snprintf(path, sizeof(path), "%s.elf", prog); if (_system_try_spawn(path, args) == 0) return 0; errno = ENOENT; return -1; } snprintf(leaf, sizeof(leaf), "%s", prog); if (cwd[0] != '\0') { const char *sep = cwd[strlen(cwd) - 1] == '/' ? "" : "/"; snprintf(path, sizeof(path), "%s%s%s", cwd, sep, leaf); if (_system_try_spawn(path, args) == 0) return 0; snprintf(path, sizeof(path), "%s%s%s.elf", cwd, sep, leaf); if (_system_try_spawn(path, args) == 0) return 0; } snprintf(path, sizeof(path), "0:/os/%s.elf", leaf); if (_system_try_spawn(path, args) == 0) return 0; snprintf(path, sizeof(path), "0:/os/%s", leaf); if (_system_try_spawn(path, args) == 0) return 0; snprintf(path, sizeof(path), "0:/games/%s.elf", leaf); if (_system_try_spawn(path, args) == 0) return 0; if (current_drive != 0) { _system_build_drive_path(current_drive, leaf, path, sizeof(path)); if (_system_try_spawn(path, args) == 0) return 0; snprintf(leaf, sizeof(leaf), "%s.elf", prog); _system_build_drive_path(current_drive, leaf, path, sizeof(path)); if (_system_try_spawn(path, args) == 0) return 0; } errno = ENOENT; return -1; } /* ======================================================================== printf family — vsnprintf core ======================================================================== */ #include "printf_internal.h" /* Weak: resolves to 0 unless the program pulls in printf_float.o with -Wl,-u,_pf_putfloat. See the %e/%f/%g case in vsnprintf below. */ extern __attribute__((weak)) void _pf_putfloat(struct _pf_state *st, double v, char conv, int precision, int width, char pad, int left_align, int plus, int space, int alt); void _pf_putc(struct _pf_state *st, char c) { if (st->pos < st->max) st->buf[st->pos] = c; st->pos++; } static void _pf_puts(struct _pf_state *st, const char *s) { while (*s) _pf_putc(st, *s++); } static void _pf_putnum(struct _pf_state *st, unsigned long val, int base, int upper, int width, char pad, int neg, int precision) { char tmp[24]; int i = 0; const char *digits = upper ? "0123456789ABCDEF" : "0123456789abcdef"; if (val == 0) { tmp[i++] = '0'; } else { while (val > 0) { tmp[i++] = digits[val % base]; val /= base; } } int digitCount = i; int precPad = 0; if (precision > digitCount) precPad = precision - digitCount; int total = (neg ? 1 : 0) + precPad + digitCount; if (neg && pad == '0' && precision < 0) { _pf_putc(st, '-'); } if (precision < 0 && pad == '0') { for (int w = total; w < width; w++) _pf_putc(st, '0'); } else { for (int w = total; w < width; w++) _pf_putc(st, ' '); } if (neg && !(pad == '0' && precision < 0)) { _pf_putc(st, '-'); } for (int p = 0; p < precPad; p++) _pf_putc(st, '0'); while (i > 0) _pf_putc(st, tmp[--i]); } int vsnprintf(char *buf, size_t size, const char *fmt, va_list ap) { struct _pf_state st; st.buf = buf; st.pos = 0; st.max = size > 0 ? size - 1 : 0; while (*fmt) { if (*fmt != '%') { _pf_putc(&st, *fmt++); continue; } fmt++; char pad = ' '; int left_align = 0, plus = 0, space = 0, alt = 0; while (*fmt == '0' || *fmt == '-' || *fmt == '+' || *fmt == ' ' || *fmt == '#') { if (*fmt == '0') pad = '0'; else if (*fmt == '-') left_align = 1; else if (*fmt == '+') plus = 1; else if (*fmt == ' ') space = 1; else if (*fmt == '#') alt = 1; fmt++; } if (left_align) pad = ' '; int width = 0; if (*fmt == '*') { width = va_arg(ap, int); fmt++; } else { while (*fmt >= '0' && *fmt <= '9') { width = width * 10 + (*fmt - '0'); fmt++; } } int precision = -1; if (*fmt == '.') { fmt++; precision = 0; if (*fmt == '*') { precision = va_arg(ap, int); fmt++; } else { while (*fmt >= '0' && *fmt <= '9') { precision = precision * 10 + (*fmt - '0'); fmt++; } } } int is_long = 0; if (*fmt == 'l') { is_long = 1; fmt++; if (*fmt == 'l') { is_long = 2; fmt++; } } else if (*fmt == 'h') { fmt++; if (*fmt == 'h') fmt++; } else if (*fmt == 'z') { is_long = 1; fmt++; } switch (*fmt) { case 'd': case 'i': { long val; if (is_long >= 1) val = va_arg(ap, long); else val = va_arg(ap, int); int neg = 0; unsigned long uval; if (val < 0) { neg = 1; uval = (unsigned long)(-val); } else uval = (unsigned long)val; if (left_align) { size_t before = st.pos; _pf_putnum(&st, uval, 10, 0, 0, pad, neg, precision); size_t len = st.pos - before; for (size_t w = len; (int)w < width; w++) _pf_putc(&st, ' '); } else { _pf_putnum(&st, uval, 10, 0, width, pad, neg, precision); } break; } case 'u': { unsigned long val; if (is_long >= 1) val = va_arg(ap, unsigned long); else val = va_arg(ap, unsigned int); if (left_align) { size_t before = st.pos; _pf_putnum(&st, val, 10, 0, 0, ' ', 0, precision); size_t len = st.pos - before; for (size_t w = len; (int)w < width; w++) _pf_putc(&st, ' '); } else { _pf_putnum(&st, val, 10, 0, width, pad, 0, precision); } break; } case 'x': case 'X': { unsigned long val; if (is_long >= 1) val = va_arg(ap, unsigned long); else val = va_arg(ap, unsigned int); int upper = (*fmt == 'X'); if (left_align) { size_t before = st.pos; _pf_putnum(&st, val, 16, upper, 0, pad, 0, precision); size_t len = st.pos - before; for (size_t w = len; (int)w < width; w++) _pf_putc(&st, ' '); } else { _pf_putnum(&st, val, 16, upper, width, pad, 0, precision); } break; } case 'o': { unsigned long val; if (is_long >= 1) val = va_arg(ap, unsigned long); else val = va_arg(ap, unsigned int); /* %#o: force a leading zero digit */ if (alt && val != 0 && precision < 0) { _pf_putc(&st, '0'); if (width > 0) width--; } if (left_align) { size_t before = st.pos; _pf_putnum(&st, val, 8, 0, 0, pad, 0, precision); size_t len = st.pos - before; for (size_t w = len; (int)w < width; w++) _pf_putc(&st, ' '); } else { _pf_putnum(&st, val, 8, 0, width, pad, 0, precision); } break; } case 'p': { void *val = va_arg(ap, void *); _pf_puts(&st, "0x"); _pf_putnum(&st, (unsigned long)val, 16, 0, 0, '0', 0, -1); break; } case 's': { const char *s = va_arg(ap, const char *); if (s == NULL) s = "(null)"; int slen = (int)strlen(s); if (precision >= 0 && precision < slen) slen = precision; if (!left_align) { for (int w = slen; w < width; w++) _pf_putc(&st, ' '); } for (int i = 0; i < slen; i++) _pf_putc(&st, s[i]); if (left_align) { for (int w = slen; w < width; w++) _pf_putc(&st, ' '); } break; } case 'c': { char c = (char)va_arg(ap, int); _pf_putc(&st, c); break; } case 'f': case 'F': case 'e': case 'E': case 'g': case 'G': { /* Consume the argument either way: skipping it would desync every later conversion in the same format string. */ double dv = va_arg(ap, double); if (_pf_putfloat) { _pf_putfloat(&st, dv, *fmt, precision, width, pad, left_align, plus, space, alt); } else { /* printf_float.o was not linked in; echo the spec so the gap is visible rather than silently printing a wrong number. */ _pf_putc(&st, '%'); _pf_putc(&st, *fmt); } break; } case '%': _pf_putc(&st, '%'); break; default: _pf_putc(&st, '%'); _pf_putc(&st, *fmt); break; } if (*fmt) fmt++; } if (size > 0) { if (st.pos < size) st.buf[st.pos] = '\0'; else st.buf[size - 1] = '\0'; } return (int)st.pos; } int snprintf(char *buf, size_t size, const char *fmt, ...) { va_list ap; va_start(ap, fmt); int ret = vsnprintf(buf, size, fmt, ap); va_end(ap); return ret; } int sprintf(char *buf, const char *fmt, ...) { va_list ap; va_start(ap, fmt); int ret = vsnprintf(buf, (size_t)-1, fmt, ap); va_end(ap); return ret; } static char _printbuf[4096]; int printf(const char *fmt, ...) { va_list ap; va_start(ap, fmt); int ret = vsnprintf(_printbuf, sizeof(_printbuf), fmt, ap); va_end(ap); _mtk_syscall1(SYS_PRINT, (long)_printbuf); return ret; } int puts(const char *s) { _mtk_syscall1(SYS_PRINT, (long)s); _mtk_syscall1(SYS_PUTCHAR, (long)'\n'); return 0; } int putchar(int c) { _mtk_syscall1(SYS_PUTCHAR, (long)c); return c; } /* ======================================================================== assert.h support ======================================================================== */ void __assert_fail(const char *expr, const char *file, int line, const char *func) { _mtk_syscall1(SYS_PRINT, (long)"Assertion failed: "); _mtk_syscall1(SYS_PRINT, (long)expr); _mtk_syscall1(SYS_PRINT, (long)" at "); _mtk_syscall1(SYS_PRINT, (long)file); _mtk_syscall1(SYS_PRINT, (long)"\n"); (void)line; (void)func; abort(); } /* ======================================================================== stdio.h FILE-based I/O ======================================================================== */ /* Static FILE objects for standard streams */ static FILE _stdin_file = { .handle = -1, .pos = 0, .size = 0, .eof = 0, .error = 0, .is_std = 1, .ungetc_buf = -1 }; static FILE _stdout_file = { .handle = -1, .pos = 0, .size = 0, .eof = 0, .error = 0, .is_std = 2, .ungetc_buf = -1 }; static FILE _stderr_file = { .handle = -1, .pos = 0, .size = 0, .eof = 0, .error = 0, .is_std = 3, .ungetc_buf = -1 }; FILE *stdin = &_stdin_file; FILE *stdout = &_stdout_file; FILE *stderr = &_stderr_file; /* ------------------------------------------------------------------------ stdin line editing. Input is gathered a line at a time so backspace can still take a character back. The gather is incremental: _stdin_pump() may be called without blocking, in which case a half-typed line simply stays in the buffer until the next call. That is what lets montauk_stdin_ready() answer "is a line available?" without stealing keystrokes -- see the select() shim in the NetSurf port, which is the reason this is not just a blocking read. _stdin_line_ready distinguishes "buffer holds a complete line" from "buffer holds a partial one"; readers must never look at the buffer without it. ------------------------------------------------------------------------ */ static char _stdin_linebuf[512]; static size_t _stdin_line_len = 0; static size_t _stdin_line_pos = 0; static int _stdin_line_ready = 0; static void _stdin_echo_char(char c) { if (c == '\b') { _mtk_syscall1(SYS_PUTCHAR, '\b'); _mtk_syscall1(SYS_PUTCHAR, ' '); _mtk_syscall1(SYS_PUTCHAR, '\b'); return; } _mtk_syscall1(SYS_PUTCHAR, (unsigned char)c); } /* Gather keystrokes into _stdin_linebuf. Returns 1 when a complete line is buffered. With block == 0, returns 0 as soon as input runs dry, keeping whatever has been typed so far for the next call. */ static int _stdin_pump(int block) { if (_stdin_line_ready) { if (_stdin_line_pos < _stdin_line_len) return 1; /* Previous line fully read; start gathering the next one. */ _stdin_line_ready = 0; _stdin_line_len = 0; _stdin_line_pos = 0; } for (;;) { int c = block ? (int)_mtk_syscall0(SYS_GETCHAR) : (int)_mtk_syscall0(SYS_GETCHAR_NB); if (c <= 0) { if (block) continue; return 0; } if (c == '\r') c = '\n'; if (c == '\b' || c == 127) { if (_stdin_line_len > 0) { _stdin_line_len--; _stdin_echo_char('\b'); } continue; } if (c == '\n') { _stdin_linebuf[_stdin_line_len++] = (char)c; _stdin_echo_char((char)c); _stdin_line_ready = 1; return 1; } if (c >= ' ' || c == '\t') { _stdin_linebuf[_stdin_line_len++] = (char)c; _stdin_echo_char((char)c); } /* Full buffer: break the line here rather than dropping input. */ if (_stdin_line_len + 1 >= sizeof(_stdin_linebuf)) { _stdin_linebuf[_stdin_line_len++] = '\n'; _stdin_echo_char('\n'); _stdin_line_ready = 1; return 1; } } } static int _stdin_fill_line(void) { return _stdin_pump(1); } int montauk_stdin_ready(void) { return _stdin_pump(0); } int montauk_stdin_wait(void) { return _stdin_pump(1); } FILE *fopen(const char *path, const char *mode) { if (path == NULL || mode == NULL) return NULL; int handle = -1; int want_read = 0; int want_write = 0; int want_append = 0; if (mode[0] == 'r') { want_read = 1; if (mode[1] == '+') want_write = 1; } else if (mode[0] == 'w') { want_write = 1; if (mode[1] == '+') want_read = 1; /* Truncate: delete then create */ _mtk_syscall1(SYS_FDELETE, (long)path); handle = (int)_mtk_syscall1(SYS_FCREATE, (long)path); if (handle < 0) return NULL; } else if (mode[0] == 'a') { want_write = 1; want_append = 1; if (mode[1] == '+') want_read = 1; /* Try open existing, create if not found */ handle = (int)_mtk_syscall1(SYS_OPEN, (long)path); if (handle < 0) { handle = (int)_mtk_syscall1(SYS_FCREATE, (long)path); if (handle < 0) return NULL; } } else { return NULL; } /* For read and read+ modes, just open existing */ if (mode[0] == 'r') { handle = (int)_mtk_syscall1(SYS_OPEN, (long)path); if (handle < 0) return NULL; } unsigned long fileSize = (unsigned long)_mtk_syscall1(SYS_GETSIZE, (long)handle); FILE *f = (FILE *)malloc(sizeof(FILE)); if (f == NULL) { _mtk_syscall1(SYS_CLOSE, (long)handle); return NULL; } f->handle = handle; f->size = fileSize; f->pos = want_append ? fileSize : 0; f->eof = 0; f->error = 0; f->is_std = 0; f->ungetc_buf = -1; (void)want_read; (void)want_write; return f; } int fclose(FILE *stream) { if (stream == NULL) return EOF; if (stream->is_std) return 0; _mtk_syscall1(SYS_CLOSE, (long)stream->handle); free(stream); return 0; } size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream) { if (stream == NULL || ptr == NULL || size == 0 || nmemb == 0) return 0; if (stream->is_std == 1) { size_t total = size * nmemb; char *dst = (char *)ptr; size_t i = 0; for (; i < total; i++) { int c = fgetc(stream); if (c == EOF) { stream->eof = 1; break; } dst[i] = (char)c; } return i / size; } size_t total = size * nmemb; size_t remaining = (stream->pos < stream->size) ? stream->size - stream->pos : 0; if (total > remaining) { total = remaining; stream->eof = 1; } if (total == 0) return 0; int ret = (int)_mtk_syscall4(SYS_READ, (long)stream->handle, (long)ptr, (long)stream->pos, (long)total); if (ret < 0) { stream->error = 1; return 0; } stream->pos += (unsigned long)ret; return (size_t)ret / size; } size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream) { if (stream == NULL || ptr == NULL || size == 0 || nmemb == 0) return 0; if (stream->is_std) { /* stdout/stderr: print via SYS_PRINT/SYS_PUTCHAR */ size_t total = size * nmemb; const char *src = (const char *)ptr; for (size_t i = 0; i < total; i++) _mtk_syscall1(SYS_PUTCHAR, (long)(unsigned char)src[i]); return nmemb; } size_t total = size * nmemb; int ret = (int)_mtk_syscall4(SYS_FWRITE, (long)stream->handle, (long)ptr, (long)stream->pos, (long)total); if (ret < 0) { stream->error = 1; return 0; } stream->pos += (unsigned long)ret; /* Update size if we wrote past the old end */ if (stream->pos > stream->size) stream->size = stream->pos; return (size_t)ret / size; } int fseek(FILE *stream, long offset, int whence) { if (stream == NULL || stream->is_std) return -1; unsigned long newpos; switch (whence) { case SEEK_SET: newpos = (unsigned long)offset; break; case SEEK_CUR: newpos = stream->pos + (unsigned long)offset; break; case SEEK_END: newpos = stream->size + (unsigned long)offset; break; default: return -1; } stream->pos = newpos; stream->eof = 0; return 0; } long ftell(FILE *stream) { if (stream == NULL) return -1; return (long)stream->pos; } int fflush(FILE *stream) { /* No buffering in this implementation */ (void)stream; return 0; } FILE *freopen(const char *path, const char *mode, FILE *stream) { if (stream == NULL) return NULL; if (path == NULL) return stream; FILE *fresh = fopen(path, mode); if (fresh == NULL) return NULL; if (!stream->is_std) { _mtk_syscall1(SYS_CLOSE, (long)stream->handle); } *stream = *fresh; free(fresh); return stream; } int setvbuf(FILE *stream, char *buf, int mode, size_t size) { (void)stream; (void)buf; (void)mode; (void)size; return 0; } int feof(FILE *stream) { if (stream == NULL) return 0; return stream->eof; } int ferror(FILE *stream) { if (stream == NULL) return 0; return stream->error; } void clearerr(FILE *stream) { if (stream == NULL) return; stream->eof = 0; stream->error = 0; } int fgetc(FILE *stream) { if (stream == NULL) return EOF; /* Check ungetc buffer first */ if (stream->ungetc_buf >= 0) { int c = stream->ungetc_buf; stream->ungetc_buf = -1; return c; } if (stream->is_std == 1) { if (!_stdin_line_ready || _stdin_line_pos >= _stdin_line_len) { if (!_stdin_fill_line()) { stream->eof = 1; return EOF; } } return (unsigned char)_stdin_linebuf[_stdin_line_pos++]; } if (stream->pos >= stream->size) { stream->eof = 1; return EOF; } unsigned char c; int ret = (int)_mtk_syscall4(SYS_READ, (long)stream->handle, (long)&c, (long)stream->pos, 1L); if (ret <= 0) { stream->eof = 1; return EOF; } stream->pos++; return (int)c; } int getc(FILE *stream) { return fgetc(stream); } int fputc(int c, FILE *stream) { unsigned char ch = (unsigned char)c; size_t n = fwrite(&ch, 1, 1, stream); return (n == 1) ? c : EOF; } int ungetc(int c, FILE *stream) { if (stream == NULL || c == EOF) return EOF; stream->ungetc_buf = c; stream->eof = 0; return c; } char *fgets(char *s, int size, FILE *stream) { if (s == NULL || size <= 0 || stream == NULL) return NULL; int i = 0; while (i < size - 1) { int c = fgetc(stream); if (c == EOF) { if (i == 0) return NULL; break; } s[i++] = (char)c; if (c == '\n') break; } s[i] = '\0'; return s; } int fputs(const char *s, FILE *stream) { if (s == NULL || stream == NULL) return EOF; size_t len = strlen(s); size_t written = fwrite(s, 1, len, stream); return (written == len) ? 0 : EOF; } int fprintf(FILE *stream, const char *fmt, ...) { char buf[4096]; va_list ap; va_start(ap, fmt); int n = vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); if (stream == NULL || stream->is_std) { _mtk_syscall1(SYS_PRINT, (long)buf); } else { fwrite(buf, 1, (size_t)(n > 0 ? n : 0), stream); } return n; } int vfprintf(FILE *stream, const char *fmt, va_list ap) { char buf[4096]; int n = vsnprintf(buf, sizeof(buf), fmt, ap); if (stream == NULL || stream->is_std) { _mtk_syscall1(SYS_PRINT, (long)buf); } else { fwrite(buf, 1, (size_t)(n > 0 ? n : 0), stream); } return n; } int vprintf(const char *fmt, va_list ap) { return vfprintf(stdout, fmt, ap); } int vsprintf(char *str, const char *fmt, va_list ap) { return vsnprintf(str, (size_t)-1, fmt, ap); } int remove(const char *path) { if (path == NULL) return -1; return (int)_mtk_syscall1(SYS_FDELETE, (long)path); } int rename(const char *oldpath, const char *newpath) { if (oldpath == NULL || newpath == NULL) return -1; return (int)_mtk_syscall2(SYS_FRENAME, (long)oldpath, (long)newpath); } void perror(const char *s) { if (s != NULL && s[0] != '\0') { _mtk_syscall1(SYS_PRINT, (long)s); _mtk_syscall1(SYS_PRINT, (long)": "); } _mtk_syscall1(SYS_PRINT, (long)"error\n"); } FILE *tmpfile(void) { char path[L_tmpnam]; if (tmpnam(path) == NULL) return NULL; return fopen(path, "w+"); } char *tmpnam(char *s) { static char internal[L_tmpnam]; static unsigned long counter = 0; char *out = (s != NULL) ? s : internal; _mtk_syscall1(SYS_FMKDIR, (long)"0:/tmp"); snprintf(out, L_tmpnam, "0:/tmp/tmp%lu.tmp", (unsigned long)_mtk_syscall0(SYS_GETMILLISECONDS) + counter++); return out; } /* ======================================================================== sscanf (minimal: %d, %x, %s, %c, %u, %ld, %lu) ======================================================================== */ int sscanf(const char *str, const char *fmt, ...) { va_list ap; va_start(ap, fmt); int matched = 0; const char *s = str; while (*fmt) { if (*fmt == '%') { fmt++; int width = 0; while (isdigit((unsigned char)*fmt)) width = width * 10 + (*fmt++ - '0'); int is_long = 0; if (*fmt == 'l') { is_long = 1; fmt++; } if (*fmt == 'l') { fmt++; } /* ll behaves as l on LP64 */ if (*fmt == 'd' || *fmt == 'i') { while (isspace((unsigned char)*s)) s++; int neg = 0; if (*s == '-') { neg = 1; s++; } else if (*s == '+') s++; if (!isdigit((unsigned char)*s)) goto done; long val = 0; int n = 0; while (isdigit((unsigned char)*s) && (width == 0 || n++ < width)) val = val * 10 + (*s++ - '0'); if (neg) val = -val; if (is_long) *va_arg(ap, long *) = val; else *va_arg(ap, int *) = (int)val; matched++; } else if (*fmt == 'u') { while (isspace((unsigned char)*s)) s++; if (!isdigit((unsigned char)*s)) goto done; unsigned long val = 0; int n = 0; while (isdigit((unsigned char)*s) && (width == 0 || n++ < width)) val = val * 10 + (unsigned long)(*s++ - '0'); if (is_long) *va_arg(ap, unsigned long *) = val; else *va_arg(ap, unsigned int *) = (unsigned int)val; matched++; } else if (*fmt == 'x' || *fmt == 'X') { while (isspace((unsigned char)*s)) s++; if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) s += 2; if (!isxdigit((unsigned char)*s)) goto done; unsigned long val = 0; int n = 0; while (isxdigit((unsigned char)*s) && (width == 0 || n++ < width)) { int d; if (*s >= '0' && *s <= '9') d = *s - '0'; else if (*s >= 'a' && *s <= 'f') d = *s - 'a' + 10; else d = *s - 'A' + 10; val = val * 16 + (unsigned long)d; s++; } if (is_long) *va_arg(ap, unsigned long *) = val; else *va_arg(ap, unsigned int *) = (unsigned int)val; matched++; } else if (*fmt == 's') { while (isspace((unsigned char)*s)) s++; char *dst = va_arg(ap, char *); if (*s == '\0') goto done; int n = 0; while (*s && !isspace((unsigned char)*s) && (width == 0 || n++ < width)) *dst++ = *s++; *dst = '\0'; matched++; } else if (*fmt == 'c') { if (*s == '\0') goto done; *va_arg(ap, char *) = *s++; matched++; } else if (*fmt == '%') { if (*s != '%') goto done; s++; } fmt++; } else if (isspace((unsigned char)*fmt)) { while (isspace((unsigned char)*s)) s++; fmt++; } else { if (*s != *fmt) goto done; s++; fmt++; } } done: va_end(ap); return matched; } /* ======================================================================== fcntl.h / unistd.h POSIX-style file I/O ======================================================================== */ /* fd position tracking for POSIX read/write (MontaukOS uses explicit offsets). OPEN_MAX is part of the userspace ABI and matches the kernel handle table. */ #define _FD_POS_MAX OPEN_MAX static unsigned long _fd_pos[_FD_POS_MAX]; static int _fd_pos_valid(int fd) { return fd >= 0 && fd < _FD_POS_MAX; } int access(const char *path, int mode) { if (path == NULL) { errno = EINVAL; return -1; } (void)mode; int h = (int)_mtk_syscall1(SYS_OPEN, (long)path); if (h >= 0) { _mtk_syscall1(SYS_CLOSE, (long)h); return 0; } if (_path_is_directory(path)) { return 0; } errno = ENOENT; return -1; } int open(const char *path, int flags, ...) { if (path == NULL) { errno = EINVAL; return -1; } int h = (int)_mtk_syscall1(SYS_OPEN, (long)path); int wants_create = (flags & O_CREAT) != 0; int wants_trunc = (flags & O_TRUNC) != 0; if (h < 0) { if (wants_create) { h = (int)_mtk_syscall1(SYS_FCREATE, (long)path); } } else if (wants_create && (flags & O_EXCL)) { _mtk_syscall1(SYS_CLOSE, (long)h); errno = EEXIST; return -1; } else if (wants_trunc) { _mtk_syscall1(SYS_CLOSE, (long)h); _mtk_syscall1(SYS_FDELETE, (long)path); h = (int)_mtk_syscall1(SYS_FCREATE, (long)path); } if (h >= _FD_POS_MAX) { _mtk_syscall1(SYS_CLOSE, (long)h); errno = EMFILE; return -1; } if (h >= 0) { _fd_pos[h] = 0; if (flags & O_APPEND) { _fd_pos[h] = (unsigned long)_mtk_syscall1(SYS_GETSIZE, (long)h); } } if (h < 0) { errno = ENOENT; } return h; } /* * The standard descriptors are the terminal, NOT file handles. * * 0/1/2 are not handles the kernel ever issued -- SYS_OPEN allocates from its * own space and would have to return 0 for these to line up, which it does * not. Passing them to SYS_READ/SYS_FWRITE addresses whatever handle happens * to hold that number, or fails: before this, every write(2, ...) was * silently discarded, so a POSIX program that reports errors with write() * rather than fprintf() printed nothing at all. stdio already routes std * streams to SYS_PUTCHAR (see fwrite/fprintf); these do the same, so the two * paths agree. */ int read(int fd, void *buf, size_t count) { if (buf == NULL) return -1; if (fd == STDIN_FILENO) { /* Line-oriented, like the terminal itself: return as soon as a line is complete rather than blocking for the full count. */ char *dst = (char *)buf; size_t i = 0; while (i < count) { int c = fgetc(stdin); if (c == EOF) break; dst[i++] = (char)c; if (c == '\n') break; } return (int)i; } if (!_fd_pos_valid(fd)) { errno = EBADF; return -1; } unsigned long pos = _fd_pos[fd]; int ret = (int)_mtk_syscall4(SYS_READ, (long)fd, (long)buf, (long)pos, (long)count); if (ret > 0) _fd_pos[fd] += (unsigned long)ret; return ret; } int write(int fd, const void *buf, size_t count) { if (buf == NULL) return -1; if (fd == STDOUT_FILENO || fd == STDERR_FILENO) { const char *src = (const char *)buf; for (size_t i = 0; i < count; i++) _mtk_syscall1(SYS_PUTCHAR, (long)(unsigned char)src[i]); return (int)count; } if (!_fd_pos_valid(fd)) { errno = EBADF; return -1; } unsigned long pos = _fd_pos[fd]; int ret = (int)_mtk_syscall4(SYS_FWRITE, (long)fd, (long)buf, (long)pos, (long)count); if (ret > 0) _fd_pos[fd] += (unsigned long)ret; return ret; } /* * ftruncate: EXTEND ONLY. * * The kernel has no truncate syscall, so growth is emulated by writing zeros * at the tail. Shrinking cannot be emulated and fails with ENOSYS rather than * silently reporting success -- a caller that believes a file shrank when it * did not is worse off than one told it is unsupported. */ int ftruncate(int fd, long length) { unsigned long size; static const unsigned char zeros[512] = { 0 }; if (fd < 0 || length < 0) { errno = EINVAL; return -1; } size = (unsigned long)_mtk_syscall1(SYS_GETSIZE, (long)fd); if ((unsigned long)length == size) return 0; if ((unsigned long)length < size) { errno = ENOSYS; /* no kernel truncate; cannot shrink */ return -1; } while (size < (unsigned long)length) { unsigned long chunk = (unsigned long)length - size; int written; if (chunk > sizeof(zeros)) chunk = sizeof(zeros); written = (int)_mtk_syscall4(SYS_FWRITE, (long)fd, (long)zeros, (long)size, (long)chunk); if (written <= 0) { errno = EIO; return -1; } size += (unsigned long)written; } return 0; } /* * Positioned I/O. SYS_READ/SYS_FWRITE already take an explicit file position, * so these pass the caller's offset straight through and never consult or * update _fd_pos -- unlike an lseek/read/lseek-back emulation, the shared file * offset is genuinely left alone. */ ssize_t pread(int fd, void *buf, size_t count, off_t offset) { if (!_fd_pos_valid(fd)) { errno = EBADF; return -1; } if (buf == NULL || offset < 0) { errno = EINVAL; return -1; } return (ssize_t)_mtk_syscall4(SYS_READ, (long)fd, (long)buf, (long)offset, (long)count); } ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset) { if (!_fd_pos_valid(fd)) { errno = EBADF; return -1; } if (buf == NULL || offset < 0) { errno = EINVAL; return -1; } return (ssize_t)_mtk_syscall4(SYS_FWRITE, (long)fd, (long)buf, (long)offset, (long)count); } int close(int fd) { if (!_fd_pos_valid(fd)) { errno = EBADF; return -1; } _fd_pos[fd] = 0; if ((int)_mtk_syscall1(SYS_CLOSE, (long)fd) < 0) { errno = EBADF; return -1; } return 0; } long lseek(int fd, long offset, int whence) { if (!_fd_pos_valid(fd)) { errno = EBADF; return -1; } unsigned long pos = _fd_pos[fd]; unsigned long newpos; switch (whence) { case 0: /* SEEK_SET */ newpos = (unsigned long)offset; break; case 1: /* SEEK_CUR */ newpos = pos + (unsigned long)offset; break; case 2: /* SEEK_END */ newpos = (unsigned long)_mtk_syscall1(SYS_GETSIZE, (long)fd) + (unsigned long)offset; break; default: errno = EINVAL; return -1; } _fd_pos[fd] = newpos; return (long)newpos; } int chdir(const char *path) { if (path == NULL) { errno = EINVAL; return -1; } if ((int)_mtk_syscall1(SYS_CHDIR, (long)path) < 0) { errno = ENOENT; return -1; } return 0; } char *getcwd(char *buf, size_t size) { if (buf == NULL || size == 0) { errno = EINVAL; return NULL; } int result = (int)_mtk_syscall2(SYS_GETCWD, (long)buf, (long)size); if (result < 0) { errno = result == -2 ? ERANGE : EIO; return NULL; } return buf; } int isatty(int fd) { if (fd < STDIN_FILENO || fd > STDERR_FILENO) return 0; return _mtk_syscall0(SYS_TERMINAL_ATTACHED) != 0; } /* ======================================================================== sys/stat.h functions ======================================================================== */ int mkdir(const char *path, unsigned int mode) { (void)mode; if (path == NULL) return -1; return (int)_mtk_syscall1(SYS_FMKDIR, (long)path); } /* The VFS exposes no inode numbers, so derive a stable id from the path and let distinct paths compare as distinct files. GCC's include-path setup deduplicates directories by (st_dev, st_ino); leaving this 0 made every directory "the same" and all but one include dir was silently dropped. SYS_STAT supplies no inode either, so this stays in use regardless. */ static ino_t _path_fake_ino(const char *path) { unsigned long ino = 5381; for (const char *p = path; *p; p++) { ino = ino * 33 + (unsigned char)*p; } return (ino_t)(ino | 1); } int stat(const char *path, struct stat *buf) { if (path == NULL || buf == NULL) { errno = EINVAL; return -1; } memset(buf, 0, sizeof(*buf)); buf->st_ino = _path_fake_ino(path); buf->st_nlink = 1; buf->st_blksize = 4096; /* Preferred path: real metadata from the filesystem driver. */ struct _mtk_filestat st; if (_mtk_syscall2(SYS_STAT, (long)path, (long)&st) == 0) { buf->st_mode = (mode_t)st.mode; /* A driver that reports no type bits still tells us whether the entry is a directory; without this S_ISREG/S_ISDIR both fail. */ if ((buf->st_mode & S_IFMT) == 0) { buf->st_mode |= st.isDir ? S_IFDIR : S_IFREG; } buf->st_size = (off_t)st.size; buf->st_atime = (time_t)st.atime; buf->st_mtime = (time_t)st.mtime; buf->st_ctime = (time_t)st.ctime; buf->st_blocks = (blkcnt_t)((buf->st_size + 511) / 512); return 0; } /* Fallback for a filesystem whose driver has no Stat entry point: probe with open/readdir as before. Size is recoverable this way, timestamps and permissions are not. */ int h = (int)_mtk_syscall1(SYS_OPEN, (long)path); if (h >= 0) { buf->st_mode = S_IFREG; buf->st_size = (off_t)_mtk_syscall1(SYS_GETSIZE, (long)h); _mtk_syscall1(SYS_CLOSE, (long)h); buf->st_blocks = (blkcnt_t)((buf->st_size + 511) / 512); return 0; } if (_path_is_directory(path)) { buf->st_mode = S_IFDIR; buf->st_size = 0; return 0; } errno = ENOENT; return -1; } int fstat(int fd, struct stat *buf) { if (buf == NULL) { errno = EINVAL; return -1; } memset(buf, 0, sizeof(*buf)); /* SYS_STAT is path-based and handles do not map back to paths, so this cannot report real timestamps or permissions the way stat() does. Callers needing those must stat the path instead. */ buf->st_mode = S_IFREG; buf->st_size = (off_t)_mtk_syscall1(SYS_GETSIZE, (long)fd); /* Handles do not map back to paths, so fake a per-handle inode in a range disjoint from the path hashes used by stat(). */ buf->st_ino = 0x4000000000000000UL + (unsigned long)fd; buf->st_nlink = 1; buf->st_blksize = 4096; buf->st_blocks = (blkcnt_t)((buf->st_size + 511) / 512); return 0; } /* ======================================================================== dirent.h functions ======================================================================== */ DIR *opendir(const char *name) { if (name == NULL) { errno = EINVAL; return NULL; } DIR *dir = (DIR *)malloc(sizeof(DIR)); if (dir == NULL) { errno = ENOMEM; return NULL; } const char *raw_names[256]; int count = (int)_mtk_syscall3(SYS_READDIR, (long)name, (long)raw_names, 256L); if (count < 0) { free(dir); errno = ENOTDIR; return NULL; } memset(dir, 0, sizeof(*dir)); dir->count = count; dir->index = 0; char local_prefix[256]; int prefix_len = _path_local_prefix(name, local_prefix, sizeof(local_prefix)); for (int i = 0; i < count && i < 256; i++) { const char *entry = raw_names[i]; if (prefix_len > 0 && strncmp(entry, local_prefix, (size_t)prefix_len) == 0) { entry += prefix_len; } strncpy(dir->names[i], entry, NAME_MAX); dir->names[i][NAME_MAX] = '\0'; } return dir; } struct dirent *readdir(DIR *dirp) { if (dirp == NULL || dirp->index >= dirp->count) { return NULL; } memset(&dirp->entry, 0, sizeof(dirp->entry)); dirp->entry.d_type = DT_UNKNOWN; strncpy(dirp->entry.d_name, dirp->names[dirp->index], NAME_MAX); dirp->entry.d_name[NAME_MAX] = '\0'; dirp->index++; return &dirp->entry; } /* * scandir/alphasort, built on opendir/readdir. The array and every entry are * malloc'd; the caller frees each entry then the array, as POSIX requires. */ int alphasort(const struct dirent **a, const struct dirent **b) { return strcmp((*a)->d_name, (*b)->d_name); } int scandir(const char *dirp, struct dirent ***namelist, int (*filter)(const struct dirent *), int (*compar)(const struct dirent **, const struct dirent **)) { DIR *d; struct dirent *ent; struct dirent **list = NULL; size_t count = 0, cap = 0; if (dirp == NULL || namelist == NULL) { errno = EINVAL; return -1; } d = opendir(dirp); if (d == NULL) return -1; while ((ent = readdir(d)) != NULL) { struct dirent *copy; if (filter != NULL && filter(ent) == 0) continue; if (count == cap) { size_t newcap = (cap == 0) ? 16 : cap * 2; struct dirent **grown = (struct dirent **)realloc(list, newcap * sizeof(*list)); if (grown == NULL) goto nomem; list = grown; cap = newcap; } copy = (struct dirent *)malloc(sizeof(*copy)); if (copy == NULL) goto nomem; *copy = *ent; list[count++] = copy; } closedir(d); if (compar != NULL && count > 1) { qsort(list, count, sizeof(*list), (int (*)(const void *, const void *))compar); } *namelist = list; return (int)count; nomem: while (count > 0) free(list[--count]); free(list); closedir(d); errno = ENOMEM; return -1; } int closedir(DIR *dirp) { if (dirp == NULL) { errno = EINVAL; return -1; } free(dirp); return 0; } void rewinddir(DIR *dirp) { if (dirp == NULL) return; dirp->index = 0; } /* ======================================================================== stdlib.h: qsort ======================================================================== */ void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *)) { if (nmemb < 2 || base == NULL || compar == NULL) return; /* Simple insertion sort for small arrays, good enough for libc */ unsigned char *b = (unsigned char *)base; unsigned char tmp[256]; int use_heap = (size > sizeof(tmp)); unsigned char *t = use_heap ? (unsigned char *)malloc(size) : tmp; if (t == NULL) return; for (size_t i = 1; i < nmemb; i++) { unsigned char *cur = b + i * size; size_t j = i; while (j > 0) { unsigned char *prev = b + (j - 1) * size; if (compar(prev, cur) <= 0) break; j--; cur = prev; } if (j != i) { unsigned char *dst = b + j * size; unsigned char *src = b + i * size; memcpy(t, src, size); memmove(dst + size, dst, (i - j) * size); memcpy(dst, t, size); } } if (use_heap) free(t); } /* ======================================================================== stdlib.h: rand / srand ======================================================================== */ static unsigned int _rand_seed = 1; int rand(void) { _rand_seed = _rand_seed * 1103515245 + 12345; return (int)((_rand_seed >> 16) & 0x7fff); } void srand(unsigned int seed) { _rand_seed = seed; } div_t div(int numer, int denom) { div_t r; r.quot = numer / denom; r.rem = numer % denom; return r; } ldiv_t ldiv(long numer, long denom) { ldiv_t r; r.quot = numer / denom; r.rem = numer % denom; return r; } long atol(const char *s) { return strtol(s, NULL, 10); } double strtod(const char *nptr, char **endptr) { double result = 0.0; double sign = 1.0; const char *s = nptr; while (isspace((unsigned char)*s)) s++; if (*s == '-') { sign = -1.0; s++; } else if (*s == '+') s++; while (*s >= '0' && *s <= '9') result = result * 10.0 + (*s++ - '0'); if (*s == '.') { s++; double frac = 0.1; while (*s >= '0' && *s <= '9') { result += (*s++ - '0') * frac; frac *= 0.1; } } if (*s == 'e' || *s == 'E') { s++; int exp_sign = 1; int exp_val = 0; if (*s == '-') { exp_sign = -1; s++; } else if (*s == '+') s++; while (*s >= '0' && *s <= '9') exp_val = exp_val * 10 + (*s++ - '0'); double exp_mult = 1.0; for (int i = 0; i < exp_val; i++) exp_mult *= 10.0; if (exp_sign > 0) result *= exp_mult; else result /= exp_mult; } if (nptr[0] == '0' && (nptr[1] == 'x' || nptr[1] == 'X')) { s = nptr + 2; result = 0.0; while (1) { int d; if (*s >= '0' && *s <= '9') d = *s - '0'; else if (*s >= 'a' && *s <= 'f') d = *s - 'a' + 10; else if (*s >= 'A' && *s <= 'F') d = *s - 'A' + 10; else break; result = result * 16.0 + d; s++; } if (*s == '.') { s++; double frac = 1.0 / 16.0; while (1) { int d; if (*s >= '0' && *s <= '9') d = *s - '0'; else if (*s >= 'a' && *s <= 'f') d = *s - 'a' + 10; else if (*s >= 'A' && *s <= 'F') d = *s - 'A' + 10; else break; result += d * frac; frac /= 16.0; s++; } } if (*s == 'p' || *s == 'P') { s++; int exp_sign = 1; int exp_val = 0; if (*s == '-') { exp_sign = -1; s++; } else if (*s == '+') s++; while (*s >= '0' && *s <= '9') exp_val = exp_val * 10 + (*s++ - '0'); result = ldexp(result, exp_sign * exp_val); } } if (endptr) *endptr = (char *)s; return sign * result; } float strtof(const char *nptr, char **endptr) { return (float)strtod(nptr, endptr); } long double strtold(const char *nptr, char **endptr) { return (long double)strtod(nptr, endptr); } /* ======================================================================== math.h functions ======================================================================== */ /* Constants */ #define M_PI 3.14159265358979323846 #define M_PI_2 1.57079632679489661923 #define M_LN2 0.69314718055994530942 #define M_LOG2E 1.44269504088896340736 double fabs(double x) { return x < 0 ? -x : x; } double frexp(double x, int *exp) { int e = 0; double ax = fabs(x); if (ax == 0.0) { if (exp) *exp = 0; return 0.0; } while (ax >= 1.0) { ax *= 0.5; e++; } while (ax < 0.5) { ax *= 2.0; e--; } if (exp) *exp = e; return x < 0.0 ? -ax : ax; } double ldexp(double x, int n) { if (x == 0.0 || n == 0) return x; while (n > 0) { x *= 2.0; n--; } while (n < 0) { x *= 0.5; n++; } return x; } long double ldexpl(long double x, int n) { return (long double)ldexp((double)x, n); } double floor(double x) { double t = (double)(long long)x; return (x < t) ? t - 1.0 : t; } double ceil(double x) { double f = floor(x); return (x > f) ? f + 1.0 : f; } double fmod(double x, double y) { if (y == 0.0) return 0.0; return x - (double)((long long)(x / y)) * y; } double sqrt(double x) { if (x <= 0.0) return 0.0; double guess = x; for (int i = 0; i < 20; i++) guess = (guess + x / guess) * 0.5; return guess; } /* ---- sin / cos via range reduction + minimax polynomial ---- */ /* Reduce x to [-pi, pi] */ static double _reduce_angle(double x) { /* Bring into [-2pi, 2pi] via fmod, then into [-pi, pi] */ x = fmod(x, 2.0 * M_PI); if (x > M_PI) x -= 2.0 * M_PI; else if (x < -M_PI) x += 2.0 * M_PI; return x; } /* Core sin approximation for x in [-pi/2, pi/2]. Taylor series to degree 17 for < 1e-11 accuracy at the boundary. */ static double _sin_core(double x) { double x2 = x * x; return x * (1.0 + x2 * (-1.0/6.0 + x2 * (1.0/120.0 + x2 * (-1.0/5040.0 + x2 * (1.0/362880.0 + x2 * (-1.0/39916800.0 + x2 * (1.0/6227020800.0 + x2 * (-1.0/1307674368000.0)))))))); } double sin(double x) { x = _reduce_angle(x); /* Reduce to [-pi/2, pi/2] using sin(pi - x) = sin(x) */ if (x > M_PI_2) x = M_PI - x; else if (x < -M_PI_2) x = -M_PI - x; return _sin_core(x); } double cos(double x) { return sin(x + M_PI_2); } /* ---- log via exponent extraction + polynomial on [1, 2) ---- */ double log(double x) { if (x <= 0.0) return -HUGE_VAL; if (x == 1.0) return 0.0; /* Extract exponent and mantissa: x = m * 2^e, where m in [1, 2) */ _dbl_bits bits; bits.d = x; int e = (int)((bits.u >> 52) & 0x7FF) - 1023; bits.u = (bits.u & 0x000FFFFFFFFFFFFFULL) | 0x3FF0000000000000ULL; double m = bits.d; /* log(x) = e * ln(2) + log(m), where m in [1, 2) Use log(m) = log((1+f)/(1-f)) = 2*(f + f^3/3 + f^5/5 + ...) where f = (m-1)/(m+1) */ double f = (m - 1.0) / (m + 1.0); double f2 = f * f; double ln_m = 2.0 * f * (1.0 + f2 * (1.0/3.0 + f2 * (1.0/5.0 + f2 * (1.0/7.0 + f2 * (1.0/9.0 + f2 * (1.0/11.0 + f2 * (1.0/13.0 + f2 * (1.0/15.0 + f2 * (1.0/17.0))))))))); return (double)e * M_LN2 + ln_m; } /* ---- exp via range reduction to [0, ln2) + polynomial ---- */ double exp(double x) { if (x == 0.0) return 1.0; if (x < -708.0) return 0.0; if (x > 709.0) return HUGE_VAL; /* Range reduction: exp(x) = 2^k * exp(r), where x = k*ln(2) + r, |r| <= ln(2)/2 */ double k_real = floor(x * M_LOG2E + 0.5); int k = (int)k_real; double r = x - k_real * M_LN2; /* Pade-like polynomial for exp(r), |r| <= ~0.347: 1 + r + r^2/2 + r^3/6 + r^4/24 + r^5/120 + r^6/720 + r^7/5040 */ double exp_r = 1.0 + r * (1.0 + r * (1.0/2.0 + r * (1.0/6.0 + r * (1.0/24.0 + r * (1.0/120.0 + r * (1.0/720.0 + r * (1.0/5040.0))))))); /* Multiply by 2^k via bit manipulation */ _dbl_bits bits; bits.d = exp_r; bits.u += (uint64_t)k << 52; return bits.d; } /* ---- pow via exp(exp * log(base)) ---- */ double pow(double base, double e) { if (e == 0.0) return 1.0; if (base == 0.0) return 0.0; if (base == 1.0) return 1.0; if (e == 1.0) return base; /* Integer exponent fast path */ if (e == (double)(long long)e && fabs(e) < 64) { long long ei = (long long)e; int neg = 0; if (ei < 0) { neg = 1; ei = -ei; } double r = 1.0; double b = base; while (ei > 0) { if (ei & 1) r *= b; b *= b; ei >>= 1; } return neg ? 1.0 / r : r; } /* General case */ if (base < 0.0) return 0.0; /* negative base with fractional exp is undefined (in reals) */ return exp(e * log(base)); } double tan(double x) { double c = cos(x); if (c == 0.0) return (sin(x) > 0.0) ? HUGE_VAL : -HUGE_VAL; return sin(x) / c; } double asin(double x) { if (x < -1.0 || x > 1.0) return 0.0; return atan2(x, sqrt(1.0 - x * x)); } double acos(double x) { if (x < -1.0 || x > 1.0) return 0.0; return atan2(sqrt(1.0 - x * x), x); } double log2(double x) { return log(x) * M_LOG2E; } double log10(double x) { return log(x) * 0.43429448190325182765; /* 1/ln(10) */ } double sinh(double x) { return 0.5 * (exp(x) - exp(-x)); } double cosh(double x) { return 0.5 * (exp(x) + exp(-x)); } double tanh(double x) { double ex = exp(x); double enx = exp(-x); return (ex - enx) / (ex + enx); } /* ---- atan / atan2 via polynomial approximation ---- */ /* Core atan for |x| <= ~0.414 (= tan(pi/8)). Taylor series converges well in this small range. */ static double _atan_small(double x) { double x2 = x * x; return x * (1.0 + x2 * (-1.0/3.0 + x2 * (1.0/5.0 + x2 * (-1.0/7.0 + x2 * (1.0/9.0 + x2 * (-1.0/11.0 + x2 * (1.0/13.0 + x2 * (-1.0/15.0 + x2 * (1.0/17.0))))))))); } #define M_PI_4 0.78539816339744830962 /* atan for x >= 0, using range reduction: - |x| <= tan(pi/8) ~ 0.4142: polynomial directly - 0.4142 < |x| <= 1: atan(x) = pi/4 + atan((x-1)/(x+1)) - |x| > 1: atan(x) = pi/2 - atan(1/x) */ static double _atan_positive(double x) { if (x <= 0.41421356237309504) { return _atan_small(x); } else if (x <= 1.0) { return M_PI_4 + _atan_small((x - 1.0) / (x + 1.0)); } else { return M_PI_2 - _atan_positive(1.0 / x); } } double atan2(double y, double x) { if (x == 0.0 && y == 0.0) return 0.0; if (x == 0.0) return (y > 0.0) ? M_PI_2 : -M_PI_2; if (y == 0.0) return (x > 0.0) ? 0.0 : M_PI; double a = _atan_positive(fabs(y) / fabs(x)); /* Map to correct quadrant */ if (x < 0.0) a = M_PI - a; if (y < 0.0) a = -a; return a; } double atan(double x) { if (x >= 0.0) return _atan_positive(x); return -_atan_positive(-x); } double round(double x) { return floor(x + 0.5); } /* ---- atof: basic floating-point string parser ---- */ double atof(const char *s) { if (s == NULL) return 0.0; while (isspace((unsigned char)*s)) s++; int neg = 0; if (*s == '-') { neg = 1; s++; } else if (*s == '+') s++; /* Integer part */ double val = 0.0; while (isdigit((unsigned char)*s)) { val = val * 10.0 + (*s - '0'); s++; } /* Fractional part */ if (*s == '.') { s++; double place = 0.1; while (isdigit((unsigned char)*s)) { val += (*s - '0') * place; place *= 0.1; s++; } } /* Exponent part */ if (*s == 'e' || *s == 'E') { s++; int eneg = 0; if (*s == '-') { eneg = 1; s++; } else if (*s == '+') s++; int ev = 0; while (isdigit((unsigned char)*s)) { ev = ev * 10 + (*s - '0'); s++; } double mul = 1.0; while (ev-- > 0) mul *= 10.0; if (eneg) val /= mul; else val *= mul; } return neg ? -val : val; } float floorf(float x) { return (float)floor((double)x); } float ceilf(float x) { return (float)ceil((double)x); } float fabsf(float x) { return x < 0.0f ? -x : x; } float sqrtf(float x) { return (float)sqrt((double)x); } float sinf(float x) { return (float)sin((double)x); } float cosf(float x) { return (float)cos((double)x); } /* ======================================================================== locale.h / signal.h functions ======================================================================== */ char *setlocale(int category, const char *locale) { static char current[] = "C"; (void)category; if (locale == NULL) return current; if (strcmp(locale, "C") == 0 || strcmp(locale, "POSIX") == 0 || locale[0] == '\0') return current; return NULL; } struct lconv *localeconv(void) { static struct lconv conv = { (char *)"." }; return &conv; } sighandler_t signal(int sig, sighandler_t handler) { if (sig <= 0 || sig >= (int)(sizeof(_signal_handlers) / sizeof(_signal_handlers[0]))) { errno = EINVAL; return SIG_ERR; } sighandler_t prev = _signal_handlers[sig]; _signal_handlers[sig] = handler; return prev ? prev : SIG_DFL; } int raise(int sig) { if (sig <= 0 || sig >= (int)(sizeof(_signal_handlers) / sizeof(_signal_handlers[0]))) { errno = EINVAL; return -1; } sighandler_t handler = _signal_handlers[sig]; if (handler == NULL || handler == SIG_DFL || handler == SIG_IGN) { return 0; } handler(sig); return 0; } /* ======================================================================== time.h / sys/time.h functions ======================================================================== */ clock_t clock(void) { return (clock_t)_mtk_syscall0(SYS_GETMILLISECONDS); } time_t time(time_t *tloc) { time_t now = _current_time_epoch(); if (tloc != NULL) *tloc = now; return now; } double difftime(time_t time1, time_t time0) { return (double)(time1 - time0); } struct tm *gmtime(const time_t *timer) { static struct tm out; if (timer == NULL) return NULL; _tm_from_epoch(*timer, &out); return &out; } struct tm *localtime(const time_t *timer) { static struct tm out; if (timer == NULL) return NULL; _tm_from_epoch(*timer + (time_t)_get_tz_offset_minutes() * 60, &out); return &out; } time_t mktime(struct tm *tm) { if (tm == NULL) return (time_t)-1; while (tm->tm_sec < 0) { tm->tm_sec += 60; tm->tm_min--; } while (tm->tm_sec >= 60) { tm->tm_sec -= 60; tm->tm_min++; } while (tm->tm_min < 0) { tm->tm_min += 60; tm->tm_hour--; } while (tm->tm_min >= 60) { tm->tm_min -= 60; tm->tm_hour++; } while (tm->tm_hour < 0) { tm->tm_hour += 24; tm->tm_mday--; } while (tm->tm_hour >= 24) { tm->tm_hour -= 24; tm->tm_mday++; } while (tm->tm_mon < 0) { tm->tm_mon += 12; tm->tm_year--; } while (tm->tm_mon >= 12) { tm->tm_mon -= 12; tm->tm_year++; } for (;;) { int dim = _days_in_month(tm->tm_year + 1900, tm->tm_mon); if (tm->tm_mday >= 1 && tm->tm_mday <= dim) break; if (tm->tm_mday <= 0) { tm->tm_mon--; if (tm->tm_mon < 0) { tm->tm_mon = 11; tm->tm_year--; } tm->tm_mday += _days_in_month(tm->tm_year + 1900, tm->tm_mon); } else { tm->tm_mday -= dim; tm->tm_mon++; if (tm->tm_mon >= 12) { tm->tm_mon = 0; tm->tm_year++; } } } time_t epoch = _epoch_from_datetime(tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec) - (time_t)_get_tz_offset_minutes() * 60; _tm_from_epoch(epoch + (time_t)_get_tz_offset_minutes() * 60, tm); return epoch; } size_t strftime(char *s, size_t max, const char *format, const struct tm *tm) { if (s == NULL || max == 0 || format == NULL || tm == NULL) return 0; size_t pos = 0; int tz = _get_tz_offset_minutes(); while (*format) { if (*format != '%') { if (!_append_char(s, max, &pos, *format++)) return 0; continue; } format++; if (*format == '\0') break; switch (*format) { case '%': if (!_append_char(s, max, &pos, '%')) return 0; break; case 'a': if (!_append_str(s, max, &pos, _weekday_short[tm->tm_wday])) return 0; break; case 'A': if (!_append_str(s, max, &pos, _weekday_long[tm->tm_wday])) return 0; break; case 'b': if (!_append_str(s, max, &pos, _month_short[tm->tm_mon])) return 0; break; case 'B': if (!_append_str(s, max, &pos, _month_long[tm->tm_mon])) return 0; break; case 'c': if (!_append_str(s, max, &pos, _weekday_short[tm->tm_wday])) return 0; if (!_append_char(s, max, &pos, ' ')) return 0; if (!_append_str(s, max, &pos, _month_short[tm->tm_mon])) return 0; if (!_append_char(s, max, &pos, ' ')) return 0; if (!_append_num(s, max, &pos, tm->tm_mday, 2, '0')) return 0; if (!_append_char(s, max, &pos, ' ')) return 0; if (!_append_num(s, max, &pos, tm->tm_hour, 2, '0')) return 0; if (!_append_char(s, max, &pos, ':')) return 0; if (!_append_num(s, max, &pos, tm->tm_min, 2, '0')) return 0; if (!_append_char(s, max, &pos, ':')) return 0; if (!_append_num(s, max, &pos, tm->tm_sec, 2, '0')) return 0; if (!_append_char(s, max, &pos, ' ')) return 0; if (!_append_num(s, max, &pos, tm->tm_year + 1900, 4, '0')) return 0; break; case 'd': if (!_append_num(s, max, &pos, tm->tm_mday, 2, '0')) return 0; break; case 'e': if (!_append_num(s, max, &pos, tm->tm_mday, 2, ' ')) return 0; break; case 'F': if (!_append_num(s, max, &pos, tm->tm_year + 1900, 4, '0')) return 0; if (!_append_char(s, max, &pos, '-')) return 0; if (!_append_num(s, max, &pos, tm->tm_mon + 1, 2, '0')) return 0; if (!_append_char(s, max, &pos, '-')) return 0; if (!_append_num(s, max, &pos, tm->tm_mday, 2, '0')) return 0; break; case 'H': if (!_append_num(s, max, &pos, tm->tm_hour, 2, '0')) return 0; break; case 'I': { int hour = tm->tm_hour % 12; if (hour == 0) hour = 12; if (!_append_num(s, max, &pos, hour, 2, '0')) return 0; break; } case 'j': if (!_append_num(s, max, &pos, tm->tm_yday + 1, 3, '0')) return 0; break; case 'm': if (!_append_num(s, max, &pos, tm->tm_mon + 1, 2, '0')) return 0; break; case 'M': if (!_append_num(s, max, &pos, tm->tm_min, 2, '0')) return 0; break; case 'p': if (!_append_str(s, max, &pos, tm->tm_hour < 12 ? "AM" : "PM")) return 0; break; case 'R': if (!_append_num(s, max, &pos, tm->tm_hour, 2, '0')) return 0; if (!_append_char(s, max, &pos, ':')) return 0; if (!_append_num(s, max, &pos, tm->tm_min, 2, '0')) return 0; break; case 'S': if (!_append_num(s, max, &pos, tm->tm_sec, 2, '0')) return 0; break; case 'T': case 'X': if (!_append_num(s, max, &pos, tm->tm_hour, 2, '0')) return 0; if (!_append_char(s, max, &pos, ':')) return 0; if (!_append_num(s, max, &pos, tm->tm_min, 2, '0')) return 0; if (!_append_char(s, max, &pos, ':')) return 0; if (!_append_num(s, max, &pos, tm->tm_sec, 2, '0')) return 0; break; case 'u': { int wday = tm->tm_wday == 0 ? 7 : tm->tm_wday; if (!_append_num(s, max, &pos, wday, 1, '0')) return 0; break; } case 'w': if (!_append_num(s, max, &pos, tm->tm_wday, 1, '0')) return 0; break; case 'x': if (!_append_num(s, max, &pos, tm->tm_mon + 1, 2, '0')) return 0; if (!_append_char(s, max, &pos, '/')) return 0; if (!_append_num(s, max, &pos, tm->tm_mday, 2, '0')) return 0; if (!_append_char(s, max, &pos, '/')) return 0; if (!_append_num(s, max, &pos, (tm->tm_year + 1900) % 100, 2, '0')) return 0; break; case 'y': if (!_append_num(s, max, &pos, (tm->tm_year + 1900) % 100, 2, '0')) return 0; break; case 'Y': if (!_append_num(s, max, &pos, tm->tm_year + 1900, 4, '0')) return 0; break; case 'z': { int sign = tz >= 0 ? '+' : '-'; int abs_tz = tz >= 0 ? tz : -tz; if (!_append_char(s, max, &pos, (char)sign)) return 0; if (!_append_num(s, max, &pos, abs_tz / 60, 2, '0')) return 0; if (!_append_num(s, max, &pos, abs_tz % 60, 2, '0')) return 0; break; } case 'Z': if (!_append_str(s, max, &pos, "UTC")) return 0; break; default: if (!_append_char(s, max, &pos, '%')) return 0; if (!_append_char(s, max, &pos, *format)) return 0; break; } format++; } s[pos] = '\0'; return pos; } int gettimeofday(struct timeval *tv, struct timezone *tz) { long ms = (long)_mtk_syscall0(SYS_GETMILLISECONDS); if (tv != NULL) { tv->tv_sec = (long)time(NULL); tv->tv_usec = (ms % 1000) * 1000; } if (tz != NULL) { int offset = _get_tz_offset_minutes(); tz->tz_minuteswest = -offset; tz->tz_dsttime = 0; } return 0; } /* ======================================================================== unistd.h: sleep ======================================================================== */ unsigned int sleep(unsigned int seconds) { _mtk_syscall1(SYS_SLEEP_MS, (long)seconds * 1000L); return 0; } /* * Sub-second sleeps. The kernel only offers millisecond granularity, so * anything finer rounds UP to 1ms rather than to zero -- a caller asking for a * short delay wants to yield, not to spin. */ int usleep(unsigned long usec) { unsigned long ms = usec / 1000UL; if (ms == 0 && usec != 0) ms = 1; _mtk_syscall1(SYS_SLEEP_MS, (long)ms); return 0; } /* ======================================================================== math.h: modf and C99 float variants (wrappers over the double implementations; assumed present by hosted libstdc++) ======================================================================== */ double modf(double x, double *iptr) { double i = (x < 0.0) ? ceil(x) : floor(x); *iptr = i; return x - i; } float modff(float x, float *iptr) { double di; double r = modf((double)x, &di); *iptr = (float)di; return (float)r; } double hypot(double x, double y) { return sqrt(x * x + y * y); } float hypotf(float x, float y) { return (float)hypot((double)x, (double)y); } float acosf(float x) { return (float)acos((double)x); } float asinf(float x) { return (float)asin((double)x); } float atanf(float x) { return (float)atan((double)x); } float atan2f(float y, float x) { return (float)atan2((double)y, (double)x); } float coshf(float x) { return (float)cosh((double)x); } float expf(float x) { return (float)exp((double)x); } float fmodf(float x, float y) { return (float)fmod((double)x, (double)y); } float frexpf(float x, int *e) { return (float)frexp((double)x, e); } float ldexpf(float x, int e) { return (float)ldexp((double)x, e); } float logf(float x) { return (float)log((double)x); } float log10f(float x) { return (float)log10((double)x); } float powf(float x, float y) { return (float)pow((double)x, (double)y); } float sinhf(float x) { return (float)sinh((double)x); } float tanf(float x) { return (float)tan((double)x); } float tanhf(float x) { return (float)tanh((double)x); } /* ======================================================================== POSIX additions for hosted programs (native binutils/gcc ports) ======================================================================== */ /* Byte-oriented C locale: every byte is one character, nothing is an invalid sequence. */ void rewind(FILE *stream) { if (stream == NULL || stream->is_std) { return; } stream->pos = 0; stream->eof = 0; stream->error = 0; stream->ungetc_buf = -1; } char *asctime(const struct tm *tm) { static char buf[26]; if (tm == NULL) { return NULL; } strftime(buf, sizeof(buf), "%a %b %d %H:%M:%S %Y\n", tm); return buf; } char *ctime(const time_t *timep) { if (timep == NULL) { return NULL; } return asctime(localtime(timep)); } int fchmod(int fd, mode_t mode) { (void)fd; (void)mode; return 0; /* no permission bits in the VFS */ } int utime(const char *path, const struct utimbuf *times) { if (path == NULL) { errno = EINVAL; return -1; } long result = _mtk_syscall4(SYS_UTIME, (long)path, times != NULL ? (long)times->actime : 0, times != NULL ? (long)times->modtime : 0, times == NULL); if (result == 0) return 0; if (result == -2) errno = ENOTSUP; else if (result == -3) errno = EOVERFLOW; else if (result == -4) errno = EIO; else errno = ENOENT; return -1; } void *bsearch(const void *key, const void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *)) { size_t lo = 0; size_t hi = nmemb; while (lo < hi) { size_t mid = lo + (hi - lo) / 2; const void *elem = (const char *)base + mid * size; int c = compar(key, elem); if (c == 0) { return (void *)elem; } if (c < 0) { hi = mid; } else { lo = mid + 1; } } return NULL; } size_t mbstowcs(wchar_t *dst, const char *src, size_t n) { if (src == NULL) { errno = EINVAL; return (size_t)-1; } if (dst == NULL) { return strlen(src); } size_t i = 0; for (; i < n && src[i]; i++) { dst[i] = (wchar_t)(unsigned char)src[i]; } if (i < n) { dst[i] = 0; } return i; } /* Byte-oriented C locale: one byte, one character. */ int mbtowc(wchar_t *pwc, const char *s, size_t n) { if (s == NULL) return 0; /* no shift states */ if (n == 0) return -1; if (*s == '\0') { if (pwc != NULL) *pwc = 0; return 0; } if (pwc != NULL) *pwc = (wchar_t)(unsigned char)*s; return 1; } /* Restartable form of mbtowc. The C locale carries no shift state, so ps is ignored; binutils reaches for this once configure sees mbstate_t. */ size_t mbrtowc(wchar_t *pwc, const char *s, size_t n, mbstate_t *ps) { (void)ps; if (s == NULL) return 0; /* query: the C locale is stateless */ if (n == 0) return (size_t)-2; /* incomplete: no bytes to examine */ if (*s == '\0') { if (pwc != NULL) *pwc = 0; return 0; } if (pwc != NULL) *pwc = (wchar_t)(unsigned char)*s; return 1; } int wctomb(char *s, wchar_t wc) { if (s == NULL) return 0; *s = (char)wc; return 1; } char *strtok_r(char *str, const char *delim, char **saveptr) { char *s = (str != NULL) ? str : *saveptr; if (s == NULL) { return NULL; } s += strspn(s, delim); if (*s == '\0') { *saveptr = NULL; return NULL; } char *tok = s; s += strcspn(s, delim); if (*s != '\0') { *s++ = '\0'; *saveptr = s; } else { *saveptr = NULL; } return tok; } char *strtok(char *str, const char *delim) { static char *_strtok_save; return strtok_r(str, delim, &_strtok_save); } /* No collation beyond byte order in the C locale: transform = copy. */ size_t strxfrm(char *dest, const char *src, size_t n) { size_t len = strlen(src); if (n > 0) { size_t c = (len < n - 1) ? len : n - 1; memcpy(dest, src, c); dest[c] = '\0'; } return len; } int mblen(const char *s, size_t n) { if (s == NULL) return 0; if (n == 0) return -1; return (*s != '\0') ? 1 : 0; } /* Absolute paths on Montauk start with a drive prefix ("0:/...""). A path is made absolute against the cwd, then . and .. segments are folded out. No symlinks exist, so this is purely lexical. */ char *realpath(const char *path, char *resolved) { if (path == NULL) { errno = EINVAL; return NULL; } char abs[1024]; int has_drive = 0; for (const char *p = path; *p; p++) { if (*p == ':') { has_drive = 1; break; } if (*p == '/') break; } if (has_drive) { strncpy(abs, path, sizeof(abs) - 1); abs[sizeof(abs) - 1] = '\0'; } else { if (getcwd(abs, sizeof(abs)) == NULL) { errno = ENOENT; return NULL; } size_t cl = strlen(abs); if (cl > 0 && abs[cl - 1] != '/' && cl + 1 < sizeof(abs)) { abs[cl++] = '/'; abs[cl] = '\0'; } strncpy(abs + cl, path, sizeof(abs) - cl - 1); abs[sizeof(abs) - 1] = '\0'; } /* Fold . and .. segments in place, keeping the drive prefix. */ char *body = strchr(abs, ':'); body = body ? body + 1 : abs; /* points at "/..." */ char out[1024]; size_t o = 0; const char *p = body; while (*p) { while (*p == '/') p++; if (*p == '\0') break; const char *seg = p; while (*p && *p != '/') p++; size_t seglen = (size_t)(p - seg); if (seglen == 1 && seg[0] == '.') { continue; } if (seglen == 2 && seg[0] == '.' && seg[1] == '.') { while (o > 0 && out[--o] != '/') { } continue; } if (o + seglen + 2 >= sizeof(out)) { errno = ENAMETOOLONG; return NULL; } out[o++] = '/'; memcpy(out + o, seg, seglen); o += seglen; } if (o == 0) { out[o++] = '/'; } out[o] = '\0'; size_t prefix = (size_t)(body - abs); size_t total = prefix + o + 1; if (resolved == NULL) { resolved = (char *)malloc(total); if (resolved == NULL) { errno = ENOMEM; return NULL; } } memcpy(resolved, abs, prefix); memcpy(resolved + prefix, out, o + 1); return resolved; } int fgetpos(FILE *stream, fpos_t *pos) { if (stream == NULL || pos == NULL) { errno = EINVAL; return -1; } long p = ftell(stream); if (p < 0) return -1; *pos = (fpos_t)p; return 0; } int fsetpos(FILE *stream, const fpos_t *pos) { if (stream == NULL || pos == NULL) { errno = EINVAL; return -1; } return fseek(stream, (long)*pos, SEEK_SET); } /* Streams are unbuffered in this libc; buffering hints are no-ops. */ void setbuf(FILE *stream, char *buf) { (void)stream; (void)buf; } /* Character-driven scanf core over FILE (fgetc/ungetc). Supports the same conversions as sscanf: %d %i %u %x %s %c %% with field widths and the l/ll length modifiers. */ int vfscanf(FILE *stream, const char *fmt, va_list ap) { int matched = 0; int c; while (*fmt) { if (isspace((unsigned char)*fmt)) { /* Whitespace in the format skips any input whitespace. */ do { c = fgetc(stream); } while (c != EOF && isspace(c)); if (c != EOF) ungetc(c, stream); fmt++; continue; } if (*fmt != '%') { c = fgetc(stream); if (c == EOF) goto done; if ((char)c != *fmt) { ungetc(c, stream); goto done; } fmt++; continue; } fmt++; if (*fmt == '%') { c = fgetc(stream); if (c != '%') { if (c != EOF) ungetc(c, stream); goto done; } fmt++; continue; } int width = 0; while (isdigit((unsigned char)*fmt)) { width = width * 10 + (*fmt++ - '0'); } int is_long = 0; if (*fmt == 'l') { is_long = 1; fmt++; } if (*fmt == 'l') { fmt++; } /* ll behaves as l on LP64 */ if (*fmt == 'd' || *fmt == 'i' || *fmt == 'u' || *fmt == 'x' || *fmt == 'X') { int base = (*fmt == 'x' || *fmt == 'X') ? 16 : 10; do { c = fgetc(stream); } while (c != EOF && isspace(c)); if (c == EOF) goto done; int neg = 0; int n = 0; if ((*fmt == 'd' || *fmt == 'i') && (c == '-' || c == '+')) { neg = (c == '-'); c = fgetc(stream); } if (base == 16 && c == '0') { int c2 = fgetc(stream); if (c2 == 'x' || c2 == 'X') { c = fgetc(stream); } else if (c2 != EOF) { ungetc(c2, stream); } } unsigned long val = 0; int any = 0; for (;;) { int digit = -1; if (c >= '0' && c <= '9') digit = c - '0'; else if (base == 16 && c >= 'a' && c <= 'f') digit = c - 'a' + 10; else if (base == 16 && c >= 'A' && c <= 'F') digit = c - 'A' + 10; if (digit < 0 || (width > 0 && n >= width)) break; val = val * (unsigned long)base + (unsigned long)digit; any = 1; n++; c = fgetc(stream); if (c == EOF) break; } if (c != EOF) ungetc(c, stream); if (!any) goto done; if (*fmt == 'd' || *fmt == 'i') { long sv = neg ? -(long)val : (long)val; if (is_long) *va_arg(ap, long *) = sv; else *va_arg(ap, int *) = (int)sv; } else { if (is_long) *va_arg(ap, unsigned long *) = val; else *va_arg(ap, unsigned int *) = (unsigned int)val; } matched++; } else if (*fmt == 's') { do { c = fgetc(stream); } while (c != EOF && isspace(c)); if (c == EOF) goto done; char *dst = va_arg(ap, char *); int n = 0; while (c != EOF && !isspace(c) && (width == 0 || n < width)) { *dst++ = (char)c; n++; c = fgetc(stream); } *dst = '\0'; if (c != EOF) ungetc(c, stream); if (n == 0) goto done; matched++; } else if (*fmt == 'c') { int n = (width > 0) ? width : 1; char *dst = va_arg(ap, char *); int got = 0; while (got < n) { c = fgetc(stream); if (c == EOF) break; dst[got++] = (char)c; } if (got < n) goto done; matched++; } else { goto done; /* unsupported conversion */ } fmt++; } done: return (matched == 0) ? EOF : matched; } int fscanf(FILE *stream, const char *fmt, ...) { va_list ap; va_start(ap, fmt); int r = vfscanf(stream, fmt, ap); va_end(ap); return r; } int scanf(const char *fmt, ...) { va_list ap; va_start(ap, fmt); int r = vfscanf(stdin, fmt, ap); va_end(ap); return r; } int putc(int c, FILE *stream) { return fputc(c, stream); } int getchar(void) { return fgetc(stdin); } int unlink(const char *path) { if (path == NULL) { errno = EINVAL; return -1; } if ((int)_mtk_syscall1(SYS_FDELETE, (long)path) < 0) { errno = ENOENT; return -1; } return 0; } int rmdir(const char *path) { return unlink(path); } /* Handle validity is probed via SYS_DUPHANDLE since the kernel has no direct query. Flag state is not tracked per handle; F_GETFL reports O_RDWR and the set commands succeed as no-ops. */ int fcntl(int fd, int cmd, ...) { switch (cmd) { case F_GETFD: case F_GETFL: { int probe = (int)_mtk_syscall1(SYS_DUPHANDLE, (long)fd); if (probe < 0) { errno = EBADF; return -1; } _mtk_syscall1(SYS_CLOSE, (long)probe); return (cmd == F_GETFL) ? O_RDWR : 0; } case F_SETFD: case F_SETFL: return 0; case F_DUPFD: return dup(fd); default: errno = EINVAL; return -1; } } pid_t getpid(void) { return (pid_t)_mtk_syscall0(SYS_GETPID); } /* True dup2 needs kernel support for duplicating into a chosen slot. Best effort: freeing newfd first makes the kernel's lowest-free-slot allocation usually land there. The oldfd == newfd form (the common validity probe) is handled exactly. */ int dup2(int oldfd, int newfd) { if (!_fd_pos_valid(oldfd) || !_fd_pos_valid(newfd)) { errno = EBADF; return -1; } if (oldfd == newfd) { int probe = (int)_mtk_syscall1(SYS_DUPHANDLE, (long)oldfd); if (probe < 0) { errno = EBADF; return -1; } _mtk_syscall1(SYS_CLOSE, (long)probe); return newfd; } _mtk_syscall1(SYS_CLOSE, (long)newfd); int h = (int)_mtk_syscall1(SYS_DUPHANDLE, (long)oldfd); if (h < 0) { errno = EBADF; return -1; } if (h != newfd) { _mtk_syscall1(SYS_CLOSE, (long)h); errno = EBUSY; return -1; } _fd_pos[h] = _fd_pos[oldfd]; return newfd; } /* SYS_WAITPID blocks until the child exits and returns 0..255 for a normal exit or 256+signal for a killed/crashed child. */ pid_t waitpid(pid_t pid, int *status, int options) { (void)options; if (pid <= 0) { errno = ECHILD; /* no process groups / wait-any support */ return -1; } long code = _mtk_syscall1(SYS_WAITPID, (long)pid); if (status != NULL) { if (code >= 256) { *status = (int)((code - 256) & 0x7F); /* signaled */ } else { *status = (int)((code & 0xFF) << 8); /* exited */ } } return pid; } void _exit(int status) { _mtk_syscall1(SYS_EXIT, (long)status); __builtin_unreachable(); } /* SYS_KILL terminates the target outright; the signal number is accepted for API shape but not delivered as a handler. */ int kill(pid_t pid, int sig) { (void)sig; if ((int)_mtk_syscall1(SYS_KILL, (long)pid) < 0) { errno = ESRCH; return -1; } return 0; } int getpagesize(void) { return 4096; } long sysconf(int name) { switch (name) { case 4: /* _SC_OPEN_MAX */ return OPEN_MAX; case 30: /* _SC_PAGESIZE */ return 4096; case 84: /* _SC_NPROCESSORS_ONLN */ return 1; default: errno = EINVAL; return -1; } } /* Anonymous private mappings with kernel-enforced page permissions. */ void *mmap(void *addr, size_t length, int prot, int flags, int fd, long offset) { int supported_flags = MAP_PRIVATE | MAP_ANONYMOUS; if (length == 0 || fd != -1 || offset != 0 || (flags & supported_flags) != supported_flags || (flags & ~supported_flags) != 0) { errno = EINVAL; return MAP_FAILED; } /* Address hints may be ignored; fixed placement is intentionally rejected by the flag validation above. */ (void)addr; if ((prot & ~(PROT_READ | PROT_WRITE | PROT_EXEC)) != 0 || !(prot & PROT_READ) || ((prot & PROT_WRITE) && (prot & PROT_EXEC))) { errno = EINVAL; return MAP_FAILED; } void *p = (void *)_mtk_syscall2(SYS_MMAP_ANON, (long)length, (long)prot); if (p == NULL) { errno = ENOMEM; return MAP_FAILED; } return p; } int munmap(void *addr, size_t length) { if (addr == NULL || addr == MAP_FAILED || length == 0 || ((uintptr_t)addr & 0xFFFULL) != 0) { errno = EINVAL; return -1; } if (_mtk_syscall2(SYS_MUNMAP, (long)addr, (long)length) < 0) { errno = EINVAL; return -1; } return 0; } int mprotect(void *addr, size_t length, int prot) { if (addr == NULL || length == 0 || ((uintptr_t)addr & 0xFFFULL) != 0 || (prot & ~(PROT_READ | PROT_WRITE | PROT_EXEC)) != 0 || !(prot & PROT_READ) || ((prot & PROT_WRITE) && (prot & PROT_EXEC))) { errno = EINVAL; return -1; } if (_mtk_syscall3(SYS_MPROTECT, (long)addr, (long)length, (long)prot) < 0) { errno = EINVAL; return -1; } return 0; } long pathconf(const char *path, int name) { (void)path; switch (name) { case 3: /* _PC_NAME_MAX */ return 255; case 4: /* _PC_PATH_MAX */ return 4096; default: errno = EINVAL; return -1; } } /* ======================================================================== posix_spawn (spawn.h) over SYS_SPAWN ======================================================================== */ int posix_spawnattr_init(posix_spawnattr_t *attr) { if (attr == NULL) return EINVAL; attr->flags = 0; return 0; } int posix_spawnattr_destroy(posix_spawnattr_t *attr) { (void)attr; return 0; } int posix_spawnattr_setflags(posix_spawnattr_t *attr, short flags) { if (attr == NULL) return EINVAL; attr->flags = flags; return 0; } int posix_spawnattr_getflags(const posix_spawnattr_t *attr, short *flags) { if (attr == NULL || flags == NULL) return EINVAL; *flags = attr->flags; return 0; } int posix_spawn_file_actions_init(posix_spawn_file_actions_t *actions) { if (actions == NULL) return EINVAL; actions->action_count = 0; return 0; } int posix_spawn_file_actions_destroy(posix_spawn_file_actions_t *actions) { (void)actions; return 0; } /* Actions are counted but cannot be honored yet (no kernel support for stdio redirection on spawn); posix_spawn refuses non-empty sets. */ int posix_spawn_file_actions_adddup2(posix_spawn_file_actions_t *actions, int fd, int newfd) { (void)fd; (void)newfd; if (actions == NULL) return EINVAL; actions->action_count++; return 0; } int posix_spawn_file_actions_addclose(posix_spawn_file_actions_t *actions, int fd) { (void)fd; if (actions == NULL) return EINVAL; actions->action_count++; return 0; } int posix_spawn_file_actions_addopen(posix_spawn_file_actions_t *actions, int fd, const char *path, int oflag, mode_t mode) { (void)fd; (void)path; (void)oflag; (void)mode; if (actions == NULL) return EINVAL; actions->action_count++; return 0; } int posix_spawn(pid_t *pid, const char *path, const posix_spawn_file_actions_t *actions, const posix_spawnattr_t *attr, char *const argv[], char *const envp[]) { (void)attr; if (path == NULL) { return EINVAL; } if (actions != NULL && actions->action_count > 0) { return ENOTSUP; /* redirection needs kernel support */ } /* Join argv[1..] into the kernel's single args string. The kernel tokenizer has no quoting, so embedded spaces cannot round-trip. */ static char argsbuf[4096]; size_t o = 0; argsbuf[0] = '\0'; if (argv != NULL) { for (int i = 1; argv[i] != NULL; i++) { size_t alen = strlen(argv[i]); for (size_t k = 0; k < alen; k++) { if (argv[i][k] == ' ') { return EINVAL; } } if (o + alen + 2 >= sizeof(argsbuf)) { return E2BIG; } if (o > 0) { argsbuf[o++] = ' '; } memcpy(argsbuf + o, argv[i], alen); o += alen; } } argsbuf[o] = '\0'; long child; if (envp == NULL) { child = _mtk_syscall2(SYS_SPAWN, (long)path, (long)argsbuf); } else { static char envbuf[4096]; size_t envlen = 0; for (int i = 0; envp[i] != NULL; i++) { const char *eq = strchr(envp[i], '='); if (eq == NULL || eq == envp[i]) return EINVAL; size_t len = strlen(envp[i]) + 1; if (envlen + len >= sizeof(envbuf)) return E2BIG; memcpy(envbuf + envlen, envp[i], len); envlen += len; } envbuf[envlen++] = '\0'; child = _mtk_syscall4(SYS_SPAWN_ENV, (long)path, (long)argsbuf, (long)envbuf, (long)envlen); } if (child < 0) { return ENOENT; } if (pid != NULL) { *pid = (pid_t)child; } return 0; } int posix_spawnp(pid_t *pid, const char *file, const posix_spawn_file_actions_t *actions, const posix_spawnattr_t *attr, char *const argv[], char *const envp[]) { if (file == NULL) { return EINVAL; } int has_slash = 0; for (const char *p = file; *p; p++) { if (*p == '/') { has_slash = 1; break; } } if (has_slash) { return posix_spawn(pid, file, actions, attr, argv, envp); } /* Bare name: search the SDK and system tool directories. */ static const char *prefixes[] = { "/sdk/bin/", "/os/", "" }; char candidate[512]; for (int pi = 0; pi < 3; pi++) { for (int ext = 0; ext < 2; ext++) { snprintf(candidate, sizeof(candidate), "%s%s%s", prefixes[pi], file, ext ? "" : ".elf"); if (access(candidate, F_OK) == 0) { return posix_spawn(pid, candidate, actions, attr, argv, envp); } } } return ENOENT; } /* Called by crt1 before constructors. The kernel stores the inherited environment as consecutive NAME=VALUE strings followed by an extra NUL. */ void __libc_init_environ(const char *blob, size_t len) { if (_environ_initialized || blob == NULL || len == 0) return; _environ_initialized = 1; _environ_loading = 1; size_t off = 0; while (off < len && blob[off] != '\0') { size_t end = off; while (end < len && blob[end] != '\0') end++; if (end == len) break; size_t eq = off; while (eq < end && blob[eq] != '=') eq++; if (eq > off && eq < end) { size_t name_len = eq - off; char *name = (char *)malloc(name_len + 1); if (name == NULL) break; memcpy(name, blob + off, name_len); name[name_len] = '\0'; if (setenv(name, blob + eq + 1, 1) < 0) { free(name); break; } free(name); } off = end + 1; } _environ_loading = 0; } /* No fork/exec process model on Montauk (spawn-style only, see SYS_SPAWN). These exist so POSIX code links; they fail cleanly at runtime. A posix_spawn implementation is the planned real path. */ pid_t fork(void) { errno = ENOSYS; return -1; } int execv(const char *path, char *const argv[]) { (void)path; (void)argv; errno = ENOSYS; return -1; } int execve(const char *path, char *const argv[], char *const envp[]) { (void)path; (void)argv; (void)envp; errno = ENOSYS; return -1; } int execvp(const char *file, char *const argv[]) { (void)file; (void)argv; errno = ENOSYS; return -1; } /* Byte pipes exist in the kernel (SYS_STREAM_CREATE), but the libc read()/write() paths are positional file calls; wiring stream handles into the fd space is future work alongside posix_spawn. */ int pipe(int fds[2]) { (void)fds; errno = ENOSYS; return -1; } pid_t wait(int *status) { (void)status; errno = ECHILD; /* wait-any needs kernel support */ return -1; } int dup(int fd) { if (!_fd_pos_valid(fd)) { errno = EBADF; return -1; } int h = (int)_mtk_syscall1(SYS_DUPHANDLE, (long)fd); if (h < 0) { errno = EBADF; return -1; } if (!_fd_pos_valid(h)) { _mtk_syscall1(SYS_CLOSE, (long)h); errno = EMFILE; return -1; } _fd_pos[h] = _fd_pos[fd]; return h; } int fileno(FILE *stream) { if (stream == NULL) { errno = EBADF; return -1; } if (stream->is_std) { return stream->is_std - 1; /* stdin 0, stdout 1, stderr 2 */ } return stream->handle; } FILE *fdopen(int fd, const char *mode) { if (!_fd_pos_valid(fd) || mode == NULL) { errno = EBADF; return NULL; } FILE *f = (FILE *)malloc(sizeof(FILE)); if (f == NULL) { errno = ENOMEM; return NULL; } f->handle = fd; f->pos = _fd_pos[fd]; f->size = (unsigned long)_mtk_syscall1(SYS_GETSIZE, (long)fd); f->eof = 0; f->error = 0; f->is_std = 0; f->ungetc_buf = -1; if (mode[0] == 'a') { f->pos = f->size; } return f; } /* long is 64-bit on x86_64-montauk, so the ll variants are aliases. */ long long strtoll(const char *nptr, char **endptr, int base) { return (long long)strtol(nptr, endptr, base); } unsigned long long strtoull(const char *nptr, char **endptr, int base) { return (unsigned long long)strtoul(nptr, endptr, base); } long long atoll(const char *nptr) { return (long long)atol(nptr); } /* The Montauk VFS does not store permission bits; accept and ignore. */ int chmod(const char *path, mode_t mode) { (void)mode; if (path == NULL) { errno = EINVAL; return -1; } return 0; } static mode_t _umask_cur = 022; mode_t umask(mode_t mask) { mode_t old = _umask_cur; _umask_cur = mask & 0777; return old; } int lstat(const char *path, struct stat *buf) { return stat(path, buf); /* no symlinks in the Montauk VFS */ } char *mktemp(char *template_) { if (template_ == NULL) { errno = EINVAL; return NULL; } size_t len = strlen(template_); if (len < 6 || strcmp(template_ + len - 6, "XXXXXX") != 0) { errno = EINVAL; if (len > 0) template_[0] = '\0'; return template_; } static const char cs[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; unsigned long seed = (unsigned long)_mtk_syscall0(SYS_GETMILLISECONDS) ^ ((unsigned long)getpid() << 16); for (int attempt = 0; attempt < 128; attempt++) { unsigned long v = seed + (unsigned long)attempt * 7919UL; for (int i = 0; i < 6; i++) { template_[len - 6 + i] = cs[v % (sizeof(cs) - 1)]; v /= sizeof(cs) - 1; } int h = (int)_mtk_syscall1(SYS_OPEN, (long)template_); if (h < 0) { return template_; /* name is free */ } _mtk_syscall1(SYS_CLOSE, (long)h); } template_[0] = '\0'; errno = EEXIST; return template_; } int mkstemp(char *template_) { if (template_ == NULL) { errno = EINVAL; return -1; } size_t len = strlen(template_); if (len < 6 || strcmp(template_ + len - 6, "XXXXXX") != 0) { errno = EINVAL; return -1; } static const char cs[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; unsigned long seed = (unsigned long)_mtk_syscall0(SYS_GETMILLISECONDS) ^ ((unsigned long)getpid() << 16); for (int attempt = 0; attempt < 128; attempt++) { unsigned long v = seed + (unsigned long)attempt * 7919UL; for (int i = 0; i < 6; i++) { template_[len - 6 + i] = cs[v % (sizeof(cs) - 1)]; v /= sizeof(cs) - 1; } int fd = open(template_, O_RDWR | O_CREAT | O_EXCL); if (fd >= 0) { return fd; } if (errno != EEXIST) { return -1; } } errno = EEXIST; return -1; }