4508 lines
128 KiB
C
4508 lines
128 KiB
C
/*
|
|
* 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 <stddef.h>
|
|
#include <stdint.h>
|
|
#include <stdarg.h>
|
|
#include <limits.h>
|
|
#include <dirent.h>
|
|
#include <locale.h>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <fcntl.h>
|
|
#include <math.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <errno.h>
|
|
#include <sys/stat.h>
|
|
#include <utime.h>
|
|
#include <spawn.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/time.h>
|
|
#include <unistd.h>
|
|
|
|
/* ========================================================================
|
|
Raw syscall wrappers (C versions matching kernel ABI)
|
|
======================================================================== */
|
|
|
|
static inline long _zos_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 _zos_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 _zos_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 _zos_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 _zos_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_FREE 12
|
|
#define SYS_GETMILLISECONDS 14
|
|
#define SYS_GETCHAR 18
|
|
#define SYS_GETCHAR_NB 166
|
|
#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)_zos_syscall0(SYS_GETTZ);
|
|
}
|
|
|
|
static time_t _current_time_epoch(void) {
|
|
struct _mtk_datetime dt = {};
|
|
_zos_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)_zos_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 _zos_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)_zos_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)
|
|
|
|
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)
|
|
_zos_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 *)_zos_syscall1(SYS_ALLOC, (long)slab);
|
|
if (mem == NULL && slab > want) {
|
|
/* Big slab refused (low memory): retry with the exact need. */
|
|
slab = want;
|
|
mem = (void *)_zos_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;
|
|
struct HeapHeader *hdr = (struct HeapHeader *)
|
|
_zos_syscall1(SYS_ALLOC, (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) {
|
|
_zos_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]();
|
|
_zos_syscall1(SYS_EXIT, (long)status);
|
|
__builtin_unreachable();
|
|
}
|
|
|
|
void abort(void) {
|
|
_zos_syscall1(SYS_PRINT, (long)"abort() called\n");
|
|
_zos_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)_zos_syscall2(SYS_SPAWN, (long)path, (long)args);
|
|
if (pid < 0) return -1;
|
|
_zos_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);
|
|
_zos_syscall1(SYS_PRINT, (long)_printbuf);
|
|
return ret;
|
|
}
|
|
|
|
int puts(const char *s) {
|
|
_zos_syscall1(SYS_PRINT, (long)s);
|
|
_zos_syscall1(SYS_PUTCHAR, (long)'\n');
|
|
return 0;
|
|
}
|
|
|
|
int putchar(int c) {
|
|
_zos_syscall1(SYS_PUTCHAR, (long)c);
|
|
return c;
|
|
}
|
|
|
|
/* ========================================================================
|
|
assert.h support
|
|
======================================================================== */
|
|
|
|
void __assert_fail(const char *expr, const char *file, int line, const char *func) {
|
|
_zos_syscall1(SYS_PRINT, (long)"Assertion failed: ");
|
|
_zos_syscall1(SYS_PRINT, (long)expr);
|
|
_zos_syscall1(SYS_PRINT, (long)" at ");
|
|
_zos_syscall1(SYS_PRINT, (long)file);
|
|
_zos_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') {
|
|
_zos_syscall1(SYS_PUTCHAR, '\b');
|
|
_zos_syscall1(SYS_PUTCHAR, ' ');
|
|
_zos_syscall1(SYS_PUTCHAR, '\b');
|
|
return;
|
|
}
|
|
_zos_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)_zos_syscall0(SYS_GETCHAR)
|
|
: (int)_zos_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 */
|
|
_zos_syscall1(SYS_FDELETE, (long)path);
|
|
handle = (int)_zos_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)_zos_syscall1(SYS_OPEN, (long)path);
|
|
if (handle < 0) {
|
|
handle = (int)_zos_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)_zos_syscall1(SYS_OPEN, (long)path);
|
|
if (handle < 0) return NULL;
|
|
}
|
|
|
|
unsigned long fileSize = (unsigned long)_zos_syscall1(SYS_GETSIZE, (long)handle);
|
|
|
|
FILE *f = (FILE *)malloc(sizeof(FILE));
|
|
if (f == NULL) {
|
|
_zos_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;
|
|
|
|
_zos_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)_zos_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++)
|
|
_zos_syscall1(SYS_PUTCHAR, (long)(unsigned char)src[i]);
|
|
return nmemb;
|
|
}
|
|
|
|
size_t total = size * nmemb;
|
|
int ret = (int)_zos_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) {
|
|
_zos_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)_zos_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) {
|
|
_zos_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) {
|
|
_zos_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)_zos_syscall1(SYS_FDELETE, (long)path);
|
|
}
|
|
|
|
int rename(const char *oldpath, const char *newpath) {
|
|
if (oldpath == NULL || newpath == NULL) return -1;
|
|
return (int)_zos_syscall2(SYS_FRENAME, (long)oldpath, (long)newpath);
|
|
}
|
|
|
|
void perror(const char *s) {
|
|
if (s != NULL && s[0] != '\0') {
|
|
_zos_syscall1(SYS_PRINT, (long)s);
|
|
_zos_syscall1(SYS_PRINT, (long)": ");
|
|
}
|
|
_zos_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;
|
|
|
|
_zos_syscall1(SYS_FMKDIR, (long)"0:/tmp");
|
|
snprintf(out, L_tmpnam, "0:/tmp/tmp%lu.tmp",
|
|
(unsigned long)_zos_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)_zos_syscall1(SYS_OPEN, (long)path);
|
|
if (h >= 0) {
|
|
_zos_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)_zos_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)_zos_syscall1(SYS_FCREATE, (long)path);
|
|
}
|
|
} else if (wants_create && (flags & O_EXCL)) {
|
|
_zos_syscall1(SYS_CLOSE, (long)h);
|
|
errno = EEXIST;
|
|
return -1;
|
|
} else if (wants_trunc) {
|
|
_zos_syscall1(SYS_CLOSE, (long)h);
|
|
_zos_syscall1(SYS_FDELETE, (long)path);
|
|
h = (int)_zos_syscall1(SYS_FCREATE, (long)path);
|
|
}
|
|
|
|
if (h >= _FD_POS_MAX) {
|
|
_zos_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)_zos_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)_zos_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++)
|
|
_zos_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)_zos_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)_zos_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)_zos_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)_zos_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)_zos_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)_zos_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)_zos_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)_zos_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)_zos_syscall2(SYS_GETCWD, (long)buf, (long)size);
|
|
if (result < 0) {
|
|
errno = result == -2 ? ERANGE : EIO;
|
|
return NULL;
|
|
}
|
|
return buf;
|
|
}
|
|
|
|
int isatty(int fd) {
|
|
return fd >= 0 && fd <= 2;
|
|
}
|
|
|
|
/* ========================================================================
|
|
sys/stat.h functions
|
|
======================================================================== */
|
|
|
|
int mkdir(const char *path, unsigned int mode) {
|
|
(void)mode;
|
|
if (path == NULL) return -1;
|
|
return (int)_zos_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 (_zos_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)_zos_syscall1(SYS_OPEN, (long)path);
|
|
if (h >= 0) {
|
|
buf->st_mode = S_IFREG;
|
|
buf->st_size = (off_t)_zos_syscall1(SYS_GETSIZE, (long)h);
|
|
_zos_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)_zos_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)_zos_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)_zos_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)_zos_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) {
|
|
_zos_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;
|
|
_zos_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 = _zos_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;
|
|
}
|
|
|
|
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)_zos_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)_zos_syscall1(SYS_DUPHANDLE, (long)fd);
|
|
if (probe < 0) {
|
|
errno = EBADF;
|
|
return -1;
|
|
}
|
|
_zos_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)_zos_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)_zos_syscall1(SYS_DUPHANDLE, (long)oldfd);
|
|
if (probe < 0) {
|
|
errno = EBADF;
|
|
return -1;
|
|
}
|
|
_zos_syscall1(SYS_CLOSE, (long)probe);
|
|
return newfd;
|
|
}
|
|
_zos_syscall1(SYS_CLOSE, (long)newfd);
|
|
int h = (int)_zos_syscall1(SYS_DUPHANDLE, (long)oldfd);
|
|
if (h < 0) {
|
|
errno = EBADF;
|
|
return -1;
|
|
}
|
|
if (h != newfd) {
|
|
_zos_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 = _zos_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) {
|
|
_zos_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)_zos_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 *)_zos_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 (_zos_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 (_zos_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 = _zos_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 = _zos_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)_zos_syscall1(SYS_DUPHANDLE, (long)fd);
|
|
if (h < 0) {
|
|
errno = EBADF;
|
|
return -1;
|
|
}
|
|
if (!_fd_pos_valid(h)) {
|
|
_zos_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)_zos_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)_zos_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)_zos_syscall1(SYS_OPEN, (long)template_);
|
|
if (h < 0) {
|
|
return template_; /* name is free */
|
|
}
|
|
_zos_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)_zos_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;
|
|
}
|