diff --git a/.gitignore b/.gitignore index 20ac073..c884c28 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ CLAUDE.md programs/lib/bearssl ramdisk.tar PLANS/.mcp.json +toolchain/native/ diff --git a/kernel/src/Api/BuildNo.hpp b/kernel/src/Api/BuildNo.hpp index 4adff58..5e11ce8 100644 --- a/kernel/src/Api/BuildNo.hpp +++ b/kernel/src/Api/BuildNo.hpp @@ -12,4 +12,4 @@ #pragma once -#define MONTAUK_BUILD_NUMBER 7 +#define MONTAUK_BUILD_NUMBER 10 diff --git a/programs/include/libc/errno.h b/programs/include/libc/errno.h index cdbda4d..7767205 100644 --- a/programs/include/libc/errno.h +++ b/programs/include/libc/errno.h @@ -9,18 +9,48 @@ extern "C" { extern int errno; -#define ENOENT 2 -#define EIO 5 -#define ENOMEM 12 -#define EACCES 13 -#define EINVAL 22 -#define ERANGE 34 -#define ENOSYS 38 -#define EISDIR 21 -#define ENOTDIR 20 -#define EEXIST 17 -#define EBADF 9 -#define EPERM 1 +/* Linux errno numbering. */ +#define EPERM 1 +#define ENOENT 2 +#define ESRCH 3 +#define EINTR 4 +#define EIO 5 +#define ENXIO 6 +#define E2BIG 7 +#define ENOEXEC 8 +#define EBADF 9 +#define ECHILD 10 +#define EAGAIN 11 +#define ENOMEM 12 +#define EACCES 13 +#define EFAULT 14 +#define EBUSY 16 +#define EEXIST 17 +#define EXDEV 18 +#define ENODEV 19 +#define ENOTDIR 20 +#define EISDIR 21 +#define EINVAL 22 +#define ENFILE 23 +#define EMFILE 24 +#define ENOTTY 25 +#define EFBIG 27 +#define ENOSPC 28 +#define ESPIPE 29 +#define EROFS 30 +#define EMLINK 31 +#define EPIPE 32 +#define EDOM 33 +#define ERANGE 34 +#define EDEADLK 35 +#define ENAMETOOLONG 36 +#define ENOLCK 37 +#define ENOSYS 38 +#define ENOTEMPTY 39 +#define ELOOP 40 +#define EWOULDBLOCK EAGAIN +#define EOVERFLOW 75 +#define ETIMEDOUT 110 #ifdef __cplusplus } diff --git a/programs/include/libc/fcntl.h b/programs/include/libc/fcntl.h index 46890eb..dc2206e 100644 --- a/programs/include/libc/fcntl.h +++ b/programs/include/libc/fcntl.h @@ -7,13 +7,27 @@ extern "C" { #endif -#define O_RDONLY 0 -#define O_WRONLY 1 -#define O_RDWR 2 -#define O_CREAT 0x40 -#define O_TRUNC 0x200 +#define O_RDONLY 0 +#define O_WRONLY 1 +#define O_RDWR 2 +#define O_ACCMODE 3 +#define O_CREAT 0x40 +#define O_EXCL 0x80 +#define O_TRUNC 0x200 +#define O_APPEND 0x400 +#define O_NONBLOCK 0x800 +#define O_CLOEXEC 0x80000 + +#define F_DUPFD 0 +#define F_GETFD 1 +#define F_SETFD 2 +#define F_GETFL 3 +#define F_SETFL 4 + +#define FD_CLOEXEC 1 int open(const char *path, int flags, ...); +int fcntl(int fd, int cmd, ...); #ifdef __cplusplus } diff --git a/programs/include/libc/inttypes.h b/programs/include/libc/inttypes.h index 398a5fc..e2a01b5 100644 --- a/programs/include/libc/inttypes.h +++ b/programs/include/libc/inttypes.h @@ -23,5 +23,38 @@ #define PRIX16 "X" #define PRIX32 "X" #define PRIX64 "lX" +#define PRIo8 "o" +#define PRIo16 "o" +#define PRIo32 "o" +#define PRIo64 "lo" + +#define PRIdPTR "ld" +#define PRIiPTR "li" +#define PRIuPTR "lu" +#define PRIxPTR "lx" +#define PRIXPTR "lX" +#define PRIdMAX "ld" +#define PRIuMAX "lu" +#define PRIxMAX "lx" + +#define SCNd8 "d" +#define SCNd16 "d" +#define SCNd32 "d" +#define SCNd64 "ld" +#define SCNi8 "i" +#define SCNi16 "i" +#define SCNi32 "i" +#define SCNi64 "li" +#define SCNu8 "u" +#define SCNu16 "u" +#define SCNu32 "u" +#define SCNu64 "lu" +#define SCNx8 "x" +#define SCNx16 "x" +#define SCNx32 "x" +#define SCNx64 "lx" +#define SCNdPTR "ld" +#define SCNuPTR "lu" +#define SCNxPTR "lx" #endif /* _LIBC_INTTYPES_H */ diff --git a/programs/include/libc/memory.h b/programs/include/libc/memory.h new file mode 100644 index 0000000..b99e2ec --- /dev/null +++ b/programs/include/libc/memory.h @@ -0,0 +1,9 @@ +#ifndef _LIBC_MEMORY_H +#define _LIBC_MEMORY_H + +#pragma once + +/* Traditional alias for string.h. */ +#include + +#endif /* _LIBC_MEMORY_H */ diff --git a/programs/include/libc/signal.h b/programs/include/libc/signal.h index 945a391..cbfcecc 100644 --- a/programs/include/libc/signal.h +++ b/programs/include/libc/signal.h @@ -3,6 +3,8 @@ #pragma once +#include + #ifdef __cplusplus extern "C" { #endif @@ -10,7 +12,26 @@ extern "C" { typedef int sig_atomic_t; typedef void (*sighandler_t)(int); -#define SIGINT 2 +/* Linux signal numbering. Only SIGINT is ever delivered today; the + rest exist so hosted code can name them. */ +#define SIGHUP 1 +#define SIGINT 2 +#define SIGQUIT 3 +#define SIGILL 4 +#define SIGTRAP 5 +#define SIGABRT 6 +#define SIGBUS 7 +#define SIGFPE 8 +#define SIGKILL 9 +#define SIGUSR1 10 +#define SIGSEGV 11 +#define SIGUSR2 12 +#define SIGPIPE 13 +#define SIGALRM 14 +#define SIGTERM 15 +#define SIGCHLD 17 +#define SIGCONT 18 +#define SIGSTOP 19 #define SIG_DFL ((sighandler_t)0) #define SIG_IGN ((sighandler_t)1) @@ -18,6 +39,7 @@ typedef void (*sighandler_t)(int); sighandler_t signal(int sig, sighandler_t handler); int raise(int sig); +int kill(pid_t pid, int sig); #ifdef __cplusplus } diff --git a/programs/include/libc/stdio.h b/programs/include/libc/stdio.h index b88194d..0a2dac3 100644 --- a/programs/include/libc/stdio.h +++ b/programs/include/libc/stdio.h @@ -36,6 +36,12 @@ extern FILE *stdin; extern FILE *stdout; extern FILE *stderr; +int putc(int c, FILE *stream); +void rewind(FILE *stream); +int getchar(void); +int fileno(FILE *stream); +FILE *fdopen(int fd, const char *mode); + int printf(const char *fmt, ...); int fprintf(FILE *stream, const char *fmt, ...); int sprintf(char *str, const char *fmt, ...); diff --git a/programs/include/libc/stdlib.h b/programs/include/libc/stdlib.h index 37d1c83..741bcd8 100644 --- a/programs/include/libc/stdlib.h +++ b/programs/include/libc/stdlib.h @@ -49,10 +49,20 @@ int unsetenv(const char *name); int putenv(char *string); void qsort(void *base, size_t nmemb, size_t size, - int (*compar)(const void *, const void *)); + int (*compar)(const void *, const void *)); +void *bsearch(const void *key, const void *base, size_t nmemb, size_t size, + int (*compar)(const void *, const void *)); long strtol(const char *nptr, char **endptr, int base); unsigned long strtoul(const char *nptr, char **endptr, int base); +long long strtoll(const char *nptr, char **endptr, int base); +unsigned long long strtoull(const char *nptr, char **endptr, int base); +long long atoll(const char *nptr); +int mkstemp(char *template_); +char *mktemp(char *template_); +char *realpath(const char *path, char *resolved); +size_t mbstowcs(wchar_t *dst, const char *src, size_t n); +int mblen(const char *s, size_t n); double strtod(const char *nptr, char **endptr); float strtof(const char *nptr, char **endptr); long double strtold(const char *nptr, char **endptr); diff --git a/programs/include/libc/sys/param.h b/programs/include/libc/sys/param.h new file mode 100644 index 0000000..b72791b --- /dev/null +++ b/programs/include/libc/sys/param.h @@ -0,0 +1,23 @@ +#ifndef _LIBC_SYS_PARAM_H +#define _LIBC_SYS_PARAM_H + +#pragma once + +#include +#include + +#ifndef MAXPATHLEN +#define MAXPATHLEN 4096 +#endif + +#ifndef MIN +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif +#ifndef MAX +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#endif + +#define howmany(x, y) (((x) + ((y) - 1)) / (y)) +#define roundup(x, y) ((((x) + ((y) - 1)) / (y)) * (y)) + +#endif /* _LIBC_SYS_PARAM_H */ diff --git a/programs/include/libc/sys/stat.h b/programs/include/libc/sys/stat.h index 60c38f7..e25d3f5 100644 --- a/programs/include/libc/sys/stat.h +++ b/programs/include/libc/sys/stat.h @@ -12,19 +12,54 @@ extern "C" { #define S_IFMT 0170000 #define S_IFDIR 0040000 #define S_IFREG 0100000 +#define S_IFCHR 0020000 +#define S_IFLNK 0120000 -#define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) -#define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG) +#define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) +#define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG) +#define S_ISCHR(mode) (((mode) & S_IFMT) == S_IFCHR) +#define S_ISLNK(mode) (((mode) & S_IFMT) == S_IFLNK) +#define S_ISFIFO(mode) (0) +#define S_ISSOCK(mode) (0) +#define S_ISBLK(mode) (0) + +/* Permission bits are accepted and preserved by the API for POSIX + compatibility, but the Montauk VFS does not store them. */ +#define S_IRWXU 0700 +#define S_IRUSR 0400 +#define S_IWUSR 0200 +#define S_IXUSR 0100 +#define S_IRWXG 0070 +#define S_IRGRP 0040 +#define S_IWGRP 0020 +#define S_IXGRP 0010 +#define S_IRWXO 0007 +#define S_IROTH 0004 +#define S_IWOTH 0002 +#define S_IXOTH 0001 struct stat { + unsigned long st_dev; + unsigned long st_ino; mode_t st_mode; + unsigned long st_nlink; + uid_t st_uid; + gid_t st_gid; unsigned long st_size; + unsigned long st_blksize; + unsigned long st_blocks; + time_t st_atime; time_t st_mtime; + time_t st_ctime; }; -int mkdir(const char *path, unsigned int mode); -int stat(const char *path, struct stat *buf); -int fstat(int fd, struct stat *buf); +int mkdir(const char *path, unsigned int mode); +int stat(const char *path, struct stat *buf); +int fstat(int fd, struct stat *buf); +int lstat(const char *path, struct stat *buf); +int chmod(const char *path, mode_t mode); +int fchmod(int fd, mode_t mode); +mode_t umask(mode_t mask); #ifdef __cplusplus } diff --git a/programs/include/libc/sys/wait.h b/programs/include/libc/sys/wait.h new file mode 100644 index 0000000..169855a --- /dev/null +++ b/programs/include/libc/sys/wait.h @@ -0,0 +1,31 @@ +#ifndef _LIBC_SYS_WAIT_H +#define _LIBC_SYS_WAIT_H + +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Status decoding. The Montauk kernel does not yet report exit codes + through SYS_WAITPID, so waited children always decode as exit 0. */ +#define WIFEXITED(s) (((s) & 0x7F) == 0) +#define WEXITSTATUS(s) (((s) >> 8) & 0xFF) +#define WIFSIGNALED(s) (0) +#define WTERMSIG(s) (0) +#define WIFSTOPPED(s) (0) +#define WSTOPSIG(s) (0) + +#define WNOHANG 1 +#define WUNTRACED 2 + +pid_t wait(int *status); +pid_t waitpid(pid_t pid, int *status, int options); + +#ifdef __cplusplus +} +#endif + +#endif /* _LIBC_SYS_WAIT_H */ diff --git a/programs/include/libc/time.h b/programs/include/libc/time.h index 77c5cca..d88a2cc 100644 --- a/programs/include/libc/time.h +++ b/programs/include/libc/time.h @@ -34,6 +34,9 @@ struct tm *localtime(const time_t *timer); time_t mktime(struct tm *tm); size_t strftime(char *s, size_t max, const char *format, const struct tm *tm); +char *asctime(const struct tm *tm); +char *ctime(const time_t *timep); + #ifdef __cplusplus } #endif diff --git a/programs/include/libc/unistd.h b/programs/include/libc/unistd.h index fe665a1..364bc3c 100644 --- a/programs/include/libc/unistd.h +++ b/programs/include/libc/unistd.h @@ -4,11 +4,16 @@ #pragma once #include +#include #ifdef __cplusplus extern "C" { #endif +#define STDIN_FILENO 0 +#define STDOUT_FILENO 1 +#define STDERR_FILENO 2 + #define F_OK 0 #define X_OK 1 #define W_OK 2 @@ -22,6 +27,19 @@ int chdir(const char *path); char *getcwd(char *buf, size_t size); int access(const char *path, int mode); int isatty(int fd); +int unlink(const char *path); +int rmdir(const char *path); +int dup(int fd); +int dup2(int oldfd, int newfd); +pid_t getpid(void); +void _exit(int status) __attribute__((noreturn)); +pid_t fork(void); +int execv(const char *path, char *const argv[]); +int execve(const char *path, char *const argv[], char *const envp[]); +int execvp(const char *file, char *const argv[]); +int pipe(int fds[2]); + +extern char **environ; unsigned int sleep(unsigned int seconds); diff --git a/programs/include/libc/utime.h b/programs/include/libc/utime.h new file mode 100644 index 0000000..00327b7 --- /dev/null +++ b/programs/include/libc/utime.h @@ -0,0 +1,25 @@ +#ifndef _LIBC_UTIME_H +#define _LIBC_UTIME_H + +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +struct utimbuf { + time_t actime; + time_t modtime; +}; + +/* Accepted for POSIX compatibility; the Montauk VFS does not support + setting file times. */ +int utime(const char *path, const struct utimbuf *times); + +#ifdef __cplusplus +} +#endif + +#endif /* _LIBC_UTIME_H */ diff --git a/programs/include/libc/wchar.h b/programs/include/libc/wchar.h new file mode 100644 index 0000000..70154b8 --- /dev/null +++ b/programs/include/libc/wchar.h @@ -0,0 +1,24 @@ +#ifndef _LIBC_WCHAR_H +#define _LIBC_WCHAR_H + +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef unsigned int wint_t; + +#define WEOF ((wint_t)-1) + +/* Byte-oriented C locale only: one byte, one character. */ +size_t mbstowcs(wchar_t *dst, const char *src, size_t n); +int mblen(const char *s, size_t n); + +#ifdef __cplusplus +} +#endif + +#endif /* _LIBC_WCHAR_H */ diff --git a/programs/lib/libc/libc.c b/programs/lib/libc/libc.c index 42476f6..ec8ff57 100644 --- a/programs/lib/libc/libc.c +++ b/programs/lib/libc/libc.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -110,6 +111,9 @@ static inline long _zos_syscall4(long nr, long a1, long a2, long a3, long a4) { #define SYS_FRENAME 94 #define SYS_GETCWD 95 #define SYS_CHDIR 96 +#define SYS_GETPID 3 +#define SYS_DUPHANDLE 98 +#define SYS_KILL 62 /* ======================================================================== errno @@ -2035,8 +2039,12 @@ int sscanf(const char *str, const char *fmt, ...) { 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++; @@ -2045,7 +2053,8 @@ int sscanf(const char *str, const char *fmt, ...) { else if (*s == '+') s++; if (!isdigit((unsigned char)*s)) goto done; long val = 0; - while (isdigit((unsigned char)*s)) + 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; @@ -2055,7 +2064,8 @@ int sscanf(const char *str, const char *fmt, ...) { while (isspace((unsigned char)*s)) s++; if (!isdigit((unsigned char)*s)) goto done; unsigned long val = 0; - while (isdigit((unsigned char)*s)) + 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; @@ -2065,7 +2075,8 @@ int sscanf(const char *str, const char *fmt, ...) { if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) s += 2; if (!isxdigit((unsigned char)*s)) goto done; unsigned long val = 0; - while (isxdigit((unsigned char)*s)) { + 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; @@ -2080,7 +2091,9 @@ int sscanf(const char *str, const char *fmt, ...) { while (isspace((unsigned char)*s)) s++; char *dst = va_arg(ap, char *); if (*s == '\0') goto done; - while (*s && !isspace((unsigned char)*s)) + int n = 0; + while (*s && !isspace((unsigned char)*s) + && (width == 0 || n++ < width)) *dst++ = *s++; *dst = '\0'; matched++; @@ -2152,6 +2165,10 @@ int open(const char *path, int flags, ...) { 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); @@ -2160,6 +2177,9 @@ int open(const char *path, int flags, ...) { if (h >= 0 && h < _FD_POS_MAX) { _fd_pos[h] = 0; + if (flags & O_APPEND) { + _fd_pos[h] = (unsigned long)_zos_syscall1(SYS_GETSIZE, (long)h); + } } if (h < 0) { errno = ENOENT; @@ -2267,6 +2287,16 @@ int stat(const char *path, struct stat *buf) { buf->st_mode = S_IFREG; buf->st_size = (unsigned long)_zos_syscall1(SYS_GETSIZE, (long)h); _zos_syscall1(SYS_CLOSE, (long)h); + /* No inodes in the VFS API: derive a stable id from the path + so that distinct paths compare as distinct files. */ + unsigned long ino = 5381; + for (const char *p = path; *p; p++) { + ino = ino * 33 + (unsigned char)*p; + } + buf->st_ino = ino | 1; + buf->st_nlink = 1; + buf->st_blksize = 4096; + buf->st_blocks = (buf->st_size + 511) / 512; return 0; } @@ -2288,6 +2318,12 @@ int fstat(int fd, struct stat *buf) { memset(buf, 0, sizeof(*buf)); buf->st_mode = S_IFREG; buf->st_size = (unsigned long)_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 = (buf->st_size + 511) / 512; return 0; } @@ -3110,3 +3146,484 @@ unsigned int sleep(unsigned int seconds) { _zos_syscall1(SYS_SLEEP_MS, (long)seconds * 1000L); return 0; } + +/* ======================================================================== + 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) { + (void)times; + if (path == NULL) { + errno = EINVAL; + return -1; + } + return 0; /* file times cannot be set through the VFS */ +} + +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; +} + +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 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 (oldfd < 0 || newfd < 0) { + 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; + } + if (h < _FD_POS_MAX) { + _fd_pos[h] = (oldfd < _FD_POS_MAX) ? _fd_pos[oldfd] : 0; + } + return newfd; +} + +/* SYS_WAITPID blocks until the child exits but does not report its + exit code yet, so successful waits decode as exit status 0. */ +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; + } + _zos_syscall1(SYS_WAITPID, (long)pid); + if (status != NULL) { + *status = 0; + } + 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; +} + +/* The libc environment lives in name/value slots (getenv/setenv), not + NAME=VALUE strings, and exec* is unimplemented, so nothing can + consume a populated environ yet. An empty, valid vector satisfies + POSIX-shaped code until the posix_spawn work builds the real one. */ +static char *_environ_empty[] = { NULL }; +char **environ = _environ_empty; + +/* 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) { + int h = (int)_zos_syscall1(SYS_DUPHANDLE, (long)fd); + if (h < 0) { + errno = EBADF; + return -1; + } + if (h < _FD_POS_MAX) { + _fd_pos[h] = (fd >= 0 && fd < _FD_POS_MAX) ? _fd_pos[fd] : 0; + } + 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 < 0 || 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 < _FD_POS_MAX) ? _fd_pos[fd] : 0; + 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; +} diff --git a/programs/lib/libc/obj/libc.o b/programs/lib/libc/obj/libc.o index 69a4160..b751d94 100644 Binary files a/programs/lib/libc/obj/libc.o and b/programs/lib/libc/obj/libc.o differ diff --git a/programs/lib/libjpeg/libjpeg.a b/programs/lib/libjpeg/libjpeg.a index d345c68..6d83c31 100644 Binary files a/programs/lib/libjpeg/libjpeg.a and b/programs/lib/libjpeg/libjpeg.a differ diff --git a/programs/lib/libjpegwrite/libjpegwrite.a b/programs/lib/libjpegwrite/libjpegwrite.a index 89c9ddb..65d381b 100644 Binary files a/programs/lib/libjpegwrite/libjpegwrite.a and b/programs/lib/libjpegwrite/libjpegwrite.a differ diff --git a/programs/lib/tls/libtls.a b/programs/lib/tls/libtls.a index f4af39e..1d595e7 100644 Binary files a/programs/lib/tls/libtls.a and b/programs/lib/tls/libtls.a differ diff --git a/programs/src/tcc/montauk_compat.h b/programs/src/tcc/montauk_compat.h index eeb8804..8a71874 100644 --- a/programs/src/tcc/montauk_compat.h +++ b/programs/src/tcc/montauk_compat.h @@ -2,83 +2,20 @@ * montauk_compat.h * POSIX compatibility shims for TCC on MontaukOS * Copyright (c) 2026 Daniel Hammer + * + * Historical note: this header used to carry static fallbacks for + * unlink/chmod/execvp/realpath/fdopen before the Montauk libc grew + * them (native binutils port). It is now a thin passthrough. */ #ifndef MONTAUK_COMPAT_H #define MONTAUK_COMPAT_H -#include -#include - -/* getcwd is provided by libc now */ -char *getcwd(char *buf, size_t size); - -/* chmod - no-op on MontaukOS */ -static inline int chmod(const char *path, unsigned int mode) { - (void)path; (void)mode; - return 0; -} - -/* close (provided by libc) */ -int close(int fd); - -/* lseek - provided by libc, tracks fd positions */ -long lseek(int fd, long offset, int whence); - -/* realpath - just return a copy of the path */ -static inline char *realpath(const char *path, char *resolved) { - if (path == NULL) return NULL; - if (resolved == NULL) { - size_t len = 0; - const char *p = path; - while (*p++) len++; - resolved = (char*)malloc(len + 1); - if (!resolved) return NULL; - } - { - const char *s = path; - char *d = resolved; - while ((*d++ = *s++)); - } - return resolved; -} - -/* dlfcn stubs not needed -- TCC_IS_NATIVE is disabled for MontaukOS */ - -/* ==================================================================== - ssize_t (used in tccelf.c) - ==================================================================== */ - -#ifndef _SSIZE_T_DEFINED -#define _SSIZE_T_DEFINED -typedef long ssize_t; -#endif - -/* ==================================================================== - POSIX I/O (provided by libc) - ==================================================================== */ - -int read(int fd, void *buf, size_t count); -int write(int fd, const void *buf, size_t count); - -/* unlink = remove */ -static inline int unlink(const char *path) { - return remove(path); -} - -/* fdopen - MontaukOS doesn't have fd-to-FILE conversion. - TCC uses fdopen after creating a file with open(). - We return NULL and let TCC fall back to error path, - or we can use a workaround. */ -FILE *fdopen(int fd, const char *mode); - -/* ==================================================================== - Process stubs - ==================================================================== */ - -static inline int execvp(const char *file, char *const argv[]) { - (void)file; (void)argv; - return -1; -} +#include +#include +#include +#include +#include +#include #endif /* MONTAUK_COMPAT_H */ diff --git a/programs/src/tcc/montauk_main.c b/programs/src/tcc/montauk_main.c index d923709..ec1e39f 100644 --- a/programs/src/tcc/montauk_main.c +++ b/programs/src/tcc/montauk_main.c @@ -40,40 +40,8 @@ static inline long _sys2(long nr, long a1, long a2) { #define SYS_GETSIZE 8 #define SYS_GETARGS 25 -/* ==================================================================== - fdopen - wrap a raw fd in a FILE (needed by tccelf.c) - ==================================================================== */ - -FILE *fdopen(int fd, const char *mode) { - (void)mode; - if (fd < 0) return NULL; - - unsigned long fileSize = (unsigned long)_sys1(SYS_GETSIZE, (long)fd); - - FILE *f = (FILE *)malloc(sizeof(FILE)); - if (f == NULL) return NULL; - - f->handle = fd; - f->pos = 0; - f->size = fileSize; - f->eof = 0; - f->error = 0; - f->is_std = 0; - f->ungetc_buf = -1; - return f; -} - -/* ==================================================================== - strtoll / strtoull (needed by TCC, not in MontaukOS libc) - ==================================================================== */ - -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); -} +/* fdopen, strtoll and strtoull used to be defined here; they moved + into the Montauk libc with the native binutils port. */ /* ==================================================================== _start entry point diff --git a/toolchain/README.md b/toolchain/README.md index f5149bd..6a98a9e 100644 --- a/toolchain/README.md +++ b/toolchain/README.md @@ -61,3 +61,36 @@ toolchain/ local/ # install prefix (ignored) sysroot/ # generated target sysroot (ignored) ``` + +## Native binutils (runs on MontaukOS) + +Binutils can be cross-compiled to run *on* MontaukOS itself +(`--host=x86_64-montauk`), the first step toward a self-hosted GCC: + +```bash +mkdir -p toolchain/build/binutils-native && cd toolchain/build/binutils-native +export PATH=$PWD/../../local/bin:$PATH +../../src/binutils-2.43.1/configure \ + --build=x86_64-pc-linux-gnu --host=x86_64-montauk \ + --target=x86_64-montauk --prefix=/usr \ + --disable-nls --disable-werror --disable-gprofng --disable-gold \ + --disable-plugins --disable-shared --enable-static +make -j$(nproc) all-gas all-ld all-binutils +make install-strip-gas install-strip-ld install-strip-binutils \ + DESTDIR=$PWD/../../native +``` + +Notes: +- Build only `all-gas all-ld all-binutils`. gprof needs fscanf with + %[] scansets (not in the Montauk libc); gold and gprofng are + disabled outright. +- The libc gained a large POSIX surface for this port (fd functions, + full errno/signal sets, struct stat, sys/wait.h, utime.h, wchar.h, + sys/param.h, memory.h, bsearch, sscanf field widths, ...). pex-style + process spawning (fork/exec/pipe) is stubbed to fail with ENOSYS: + the tools themselves never spawn, and real process plumbing is the + posix_spawn milestone that needs kernel support (exit codes in + SYS_WAITPID, fd redirection wiring). +- The staged tree in toolchain/native/ is not yet integrated into the + OS image; that needs a target-side sysroot layout decision (where + headers, libc.a and crt objects live on 0:/).