feat: ports - git port

This commit is contained in:
2026-08-09 10:47:07 +02:00
parent cfb8d91442
commit 6f3cf99667
34 changed files with 2255 additions and 2 deletions
+764
View File
@@ -0,0 +1,764 @@
/*
* montauk-compat.c -- POSIX calls git needs that the Montauk libc does not
* have. Compiled into libgitcompat.a and placed on the link line ahead of
* liblibc.a.
*
* The rule followed here: a shim either does the real thing, or it fails with
* an errno git already knows how to report. Nothing pretends to have done
* work it did not do -- with one deliberate exception, fsync(), noted at its
* definition.
*/
#include <errno.h>
#include <stddef.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <pwd.h>
#include <grp.h>
#include <netdb.h>
#include <poll.h>
#include <signal.h>
#include <termios.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/utsname.h>
/*
* <montauk.h> is the C mirror of the syscall ABI (the C++ SDK headers under montauk/
* are C++). Only two calls are used below: SYS_GETUSER for the session user
* and SYS_TERMSIZE for the terminal geometry.
*/
#include <montauk.h>
/* ==== Identity =======================================================
*
* MontaukOS has named users but no numeric uid space: a process runs as
* whoever is logged into the session. A single fixed non-root id is reported,
* which makes git's two questions answer correctly -- "am I root?" (no, so it
* does not relax its safety checks) and "does this repository belong to me?"
* (yes, since every path reports the same owner).
*/
#define MONTAUK_UID 1000
#define MONTAUK_GID 1000
uid_t getuid(void) { return MONTAUK_UID; }
uid_t geteuid(void) { return MONTAUK_UID; }
gid_t getgid(void) { return MONTAUK_GID; }
gid_t getegid(void) { return MONTAUK_GID; }
/* No process tree above us that userspace can see. */
pid_t getppid(void) { return 1; }
int setsid(void) { return getpid(); }
/*
* Process groups do not exist: every process is its own group, and whatever
* is reading the terminal is the foreground job. Reporting getpid() from all
* three keeps progress.c's foreground test (tcgetpgrp(fd) == getpgid(0))
* true, so the progress meter is drawn instead of suppressed.
*/
pid_t getpgid(pid_t pid) { return pid ? pid : getpid(); }
pid_t getpgrp(void) { return getpid(); }
pid_t tcgetpgrp(int fd) { (void) fd; return getpid(); }
int setpgid(pid_t pid, pid_t pgid)
{
(void) pid; (void) pgid;
return 0;
}
int gethostname(char *name, size_t len)
{
static const char host[] = "montauk";
if (!name || len == 0) {
errno = EINVAL;
return -1;
}
if (len < sizeof(host)) {
errno = ENAMETOOLONG;
return -1;
}
memcpy(name, host, sizeof(host));
return 0;
}
/*
* No interval timers exist and SIGALRM is never delivered, so nothing is
* armed and nothing was pending. Only upload-pack's idle timeout calls this,
* and upload-pack needs sockets to be reachable at all.
*/
unsigned int alarm(unsigned int seconds)
{
(void) seconds;
return 0;
}
/* ==== Passwd / group =================================================
*
* Answered from the session rather than from a database. git uses this for
* the fallback author identity and to expand "~"; a wrong-but-plausible entry
* beats a NULL return, which git reports as "You don't exist. Go away!".
*
* getpwnam() only recognises the current user: there is nobody else to look
* up, and inventing entries for arbitrary names would make "~someone" resolve
* to a directory that does not exist.
*/
static char pw_name_buf[64];
static char pw_dir_buf[128];
static struct passwd *fill_passwd(void)
{
static struct passwd pw;
int len = (int) _mtk_syscall2(MTK_SYS_GETUSER,
(long) pw_name_buf,
(long) sizeof(pw_name_buf));
if (len <= 0 || pw_name_buf[0] == '\0')
strcpy(pw_name_buf, "user");
pw_name_buf[sizeof(pw_name_buf) - 1] = '\0';
snprintf(pw_dir_buf, sizeof(pw_dir_buf), "0:/users/%s", pw_name_buf);
pw.pw_name = pw_name_buf;
pw.pw_passwd = (char *) "";
pw.pw_uid = MONTAUK_UID;
pw.pw_gid = MONTAUK_GID;
pw.pw_gecos = pw_name_buf;
pw.pw_dir = pw_dir_buf;
pw.pw_shell = (char *) "";
return &pw;
}
/*
* Called first thing from main() (patches/0001-montauk-startup.patch).
*
* MontaukOS starts every process with an EMPTY environment -- SYS_SPAWN
* transfers nothing, so environ is bare no matter who launched git. Without
* HOME, git silently skips the per-user config: ~/.gitconfig never resolves,
* so `git config --global user.email ...` has nowhere to write, and the
* repository config becomes the only place a setting can live. Pointing HOME
* at the session user's directory is what makes --global work.
*
* This is deliberately NOT an __attribute__((constructor)): programs linked
* with programs/link.ld get no .init_array section (the script does not name
* one, so the linker drops it), which means constructors silently never run.
* A call from main() is the only reliable hook.
*
* An inherited HOME, should the environment ever start carrying one, wins.
*/
void montauk_startup(void)
{
if (getenv("HOME"))
return;
setenv("HOME", fill_passwd()->pw_dir, 0);
}
struct passwd *getpwuid(uid_t uid)
{
if (uid != MONTAUK_UID) {
errno = 0; /* "no such user", not an error, per POSIX */
return NULL;
}
return fill_passwd();
}
struct passwd *getpwnam(const char *name)
{
struct passwd *pw = fill_passwd();
if (!name || strcmp(name, pw->pw_name) != 0) {
errno = 0;
return NULL;
}
return pw;
}
struct group *getgrnam(const char *name)
{
(void) name;
errno = 0;
return NULL;
}
struct group *getgrgid(gid_t gid)
{
(void) gid;
errno = 0;
return NULL;
}
/* ==== Filesystem =====================================================
*
* Symlinks have no representation in the MontaukOS VFS (the ramdisk drops
* them, ext2 support is read-side only for them), so these fail rather than
* silently creating something else. git's core.symlinks=false path handles
* the failure by writing a regular file whose contents are the link target.
*/
int symlink(const char *target, const char *linkpath)
{
(void) target; (void) linkpath;
errno = EPERM;
return -1;
}
ssize_t readlink(const char *path, char *buf, size_t bufsiz)
{
(void) path; (void) buf; (void) bufsiz;
errno = EINVAL; /* "not a symbolic link" -- and nothing here is */
return -1;
}
int link(const char *oldpath, const char *newpath)
{
(void) oldpath; (void) newpath;
errno = EPERM; /* no hard links; callers fall back to copying */
return -1;
}
int chown(const char *path, uid_t owner, gid_t group)
{
(void) path; (void) owner; (void) group;
errno = EPERM;
return -1;
}
int lchown(const char *path, uid_t owner, gid_t group)
{
(void) path; (void) owner; (void) group;
errno = EPERM;
return -1;
}
int fchown(int fd, uid_t owner, gid_t group)
{
(void) fd; (void) owner; (void) group;
errno = EPERM;
return -1;
}
/*
* fsync()/fdatasync() report success without doing anything, which is the one
* shim here that claims more than it delivers. Failing instead is worse: git
* treats a failed fsync as a fatal error and aborts the write, so every
* commit would fail. Data does reach the disk -- the kernel writes through
* its block cache -- but there is no per-descriptor barrier, so ordering
* across a power loss is not guaranteed. SYS_FS_SYNC (whole-filesystem
* flush, used by shutdown) is the closest thing that exists.
*/
int fsync(int fd)
{
(void) fd;
return 0;
}
int fdatasync(int fd)
{
(void) fd;
return 0;
}
/* ==== Terminal input =================================================
*
* MontaukOS has no /dev/tty, so compat/terminal.c compiles its fallback
* branch, and every interactive prompt git makes -- passphrases included --
* arrives here.
*
* THE INPUT IS ECHOED. The libc reads stdin through SYS_GETCHAR and echoes
* each character itself; there is no line discipline to turn that off (see
* tcsetattr below, which is why it cannot help). Nothing in this port prompts
* for a secret today, since there are no network transports and no credential
* helper, but anything added later must not assume this hides what is typed.
*/
char *getpass(const char *prompt)
{
static char buf[256];
size_t len;
if (prompt && *prompt) {
fputs(prompt, stderr);
fflush(stderr);
}
if (!fgets(buf, sizeof(buf), stdin))
return NULL;
len = strlen(buf);
while (len > 0 && (buf[len - 1] == '\n' || buf[len - 1] == '\r'))
buf[--len] = '\0';
return buf;
}
/* ==== exec ===========================================================
*
* execl()/execlp() collect their variadic arguments into an argv and defer to
* the libc's execv()/execvp(). Those fail with ENOSYS -- MontaukOS spawns new
* processes (SYS_SPAWN) rather than replacing the current image -- so these
* fail too, which is what the one caller (`git help`, launching a man viewer)
* is prepared for.
*
* MAX_EXEC_ARGV is generous for that caller; a longer list is rejected
* outright rather than silently truncated into a different command.
*/
#define MAX_EXEC_ARGV 64
static int collect_exec_argv(const char *arg, va_list ap,
const char *argv[], int max)
{
int argc = 0;
argv[argc++] = arg;
while (argc < max) {
const char *next = va_arg(ap, const char *);
argv[argc++] = next;
if (!next)
return argc;
}
return -1;
}
int execl(const char *path, const char *arg, ...)
{
const char *argv[MAX_EXEC_ARGV];
va_list ap;
int argc;
va_start(ap, arg);
argc = collect_exec_argv(arg, ap, argv, MAX_EXEC_ARGV);
va_end(ap);
if (argc < 0) {
errno = E2BIG;
return -1;
}
return execv(path, (char *const *) argv);
}
int execlp(const char *file, const char *arg, ...)
{
const char *argv[MAX_EXEC_ARGV];
va_list ap;
int argc;
va_start(ap, arg);
argc = collect_exec_argv(arg, ap, argv, MAX_EXEC_ARGV);
va_end(ap);
if (argc < 0) {
errno = E2BIG;
return -1;
}
return execvp(file, (char *const *) argv);
}
/* ==== Signals ========================================================
*
* sigaction() over signal(). MontaukOS delivers SIGINT and nothing else, and
* has no per-process signal mask, so sa_mask and sa_flags (including
* SA_RESTART) are accepted and ignored -- there are no restartable syscalls
* to interrupt. The sigset_t operations maintain a bitmask that nothing
* reads; they exist so callers that build a mask before installing a handler
* compile and behave predictably.
*/
int sigemptyset(sigset_t *set)
{
if (set)
*set = 0;
return 0;
}
int sigfillset(sigset_t *set)
{
if (set)
*set = ~(sigset_t)0;
return 0;
}
int sigaddset(sigset_t *set, int signum)
{
if (!set || signum <= 0 || signum >= (int)(8 * sizeof(sigset_t))) {
errno = EINVAL;
return -1;
}
*set |= (sigset_t)1 << signum;
return 0;
}
int sigdelset(sigset_t *set, int signum)
{
if (!set || signum <= 0 || signum >= (int)(8 * sizeof(sigset_t))) {
errno = EINVAL;
return -1;
}
*set &= ~((sigset_t)1 << signum);
return 0;
}
int sigismember(const sigset_t *set, int signum)
{
if (!set || signum <= 0 || signum >= (int)(8 * sizeof(sigset_t))) {
errno = EINVAL;
return -1;
}
return (*set & ((sigset_t)1 << signum)) != 0;
}
int sigaction(int signum, const struct sigaction *act, struct sigaction *old)
{
sighandler_t prev;
if (act) {
prev = signal(signum, act->sa_handler);
} else {
/* Query only: read the handler back by reinstalling it. */
prev = signal(signum, SIG_DFL);
if (prev != SIG_ERR)
signal(signum, prev);
}
if (prev == SIG_ERR)
return -1;
if (old) {
memset(old, 0, sizeof(*old));
old->sa_handler = prev;
}
return 0;
}
/* No signal mask exists to block anything with; report an empty one. */
int sigprocmask(int how, const sigset_t *set, sigset_t *old)
{
(void) how; (void) set;
if (old)
*old = 0;
return 0;
}
/* ==== Time ===========================================================
*
* The libc's localtime()/gmtime() use a static buffer; copy it out. Safe
* here because the port is built NO_PTHREADS.
*/
struct tm *localtime_r(const time_t *timep, struct tm *result)
{
struct tm *tmp = localtime(timep);
if (!tmp || !result)
return NULL;
*result = *tmp;
return result;
}
struct tm *gmtime_r(const time_t *timep, struct tm *result)
{
struct tm *tmp = gmtime(timep);
if (!tmp || !result)
return NULL;
*result = *tmp;
return result;
}
char *ctime_r(const time_t *timep, char *buf)
{
char *s = ctime(timep);
if (!s || !buf)
return NULL;
strcpy(buf, s); /* caller supplies the POSIX-required 26 bytes */
return buf;
}
char *asctime_r(const struct tm *tm, char *buf)
{
char *s = asctime(tm);
if (!s || !buf)
return NULL;
strcpy(buf, s);
return buf;
}
/* ==== Terminal =======================================================
*
* There is no line discipline to query or configure: the libc reads stdin a
* character at a time through SYS_GETCHAR and echoes it itself. tcgetattr
* hands back a zeroed struct and tcsetattr accepts whatever it is given,
* because git treats a tcgetattr failure as "this is not a terminal" -- which
* would disable interactive prompting altogether -- and dies on some
* tcsetattr failures.
*
* The practical consequence: echo cannot be turned off, so a passphrase typed
* at a git prompt is visible. Nothing in this port prompts for one today
* (there are no network transports and no credential helper).
*/
int tcgetattr(int fd, struct termios *t)
{
if (!isatty(fd)) {
errno = ENOTTY;
return -1;
}
if (t)
memset(t, 0, sizeof(*t));
return 0;
}
int tcsetattr(int fd, int actions, const struct termios *t)
{
(void) actions; (void) t;
if (!isatty(fd)) {
errno = ENOTTY;
return -1;
}
return 0;
}
/*
* The only ioctl git issues is TIOCGWINSZ, and MontaukOS answers that one
* directly through SYS_TERMSIZE, so the progress meter and `git column` get
* the real terminal width instead of the 80-column fallback. Every other
* request fails.
*/
int ioctl(int fd, unsigned long request, ...)
{
(void) fd;
if (request == TIOCGWINSZ) {
va_list ap;
struct winsize *ws;
int cols = 0, rows = 0;
va_start(ap, request);
ws = va_arg(ap, struct winsize *);
va_end(ap);
if (!ws) {
errno = EFAULT;
return -1;
}
mtk_termsize(&cols, &rows);
if (cols <= 0 || rows <= 0) {
errno = ENOTTY;
return -1;
}
ws->ws_col = (unsigned short) cols;
ws->ws_row = (unsigned short) rows;
ws->ws_xpixel = 0;
ws->ws_ypixel = 0;
return 0;
}
errno = ENOTTY;
return -1;
}
/* ==== poll ===========================================================
*
* Only ever called on the pipe ends of a spawned sub-process, and MontaukOS
* has neither pipe() nor fork(), so this is unreachable in practice. It
* reports every descriptor ready rather than failing, so that a caller which
* does get here proceeds to the read() that reports the real error instead of
* spinning.
*/
int poll(struct pollfd *fds, nfds_t nfds, int timeout)
{
nfds_t i;
(void) timeout;
if (!fds)
return 0;
for (i = 0; i < nfds; i++)
fds[i].revents = fds[i].events;
return (int) nfds;
}
/* ==== Sockets ========================================================
*
* MontaukOS TCP lives behind IPC handles (montauk::socket), not file
* descriptors, and git hands transport descriptors to sub-processes. Until
* something bridges the two, every call fails with ENOSYS; `git clone
* git://...` reports that it cannot connect. Local paths are unaffected.
*/
#define SOCKET_STUB_BODY do { errno = ENOSYS; return -1; } while (0)
int socket(int domain, int type, int protocol)
{
(void) domain; (void) type; (void) protocol;
SOCKET_STUB_BODY;
}
int connect(int fd, const struct sockaddr *addr, socklen_t len)
{
(void) fd; (void) addr; (void) len;
SOCKET_STUB_BODY;
}
int bind(int fd, const struct sockaddr *addr, socklen_t len)
{
(void) fd; (void) addr; (void) len;
SOCKET_STUB_BODY;
}
int listen(int fd, int backlog)
{
(void) fd; (void) backlog;
SOCKET_STUB_BODY;
}
int accept(int fd, struct sockaddr *addr, socklen_t *len)
{
(void) fd; (void) addr; (void) len;
SOCKET_STUB_BODY;
}
int shutdown(int fd, int how)
{
(void) fd; (void) how;
SOCKET_STUB_BODY;
}
int setsockopt(int fd, int level, int name, const void *val, socklen_t len)
{
(void) fd; (void) level; (void) name; (void) val; (void) len;
SOCKET_STUB_BODY;
}
int getsockopt(int fd, int level, int name, void *val, socklen_t *len)
{
(void) fd; (void) level; (void) name; (void) val; (void) len;
SOCKET_STUB_BODY;
}
int getsockname(int fd, struct sockaddr *addr, socklen_t *len)
{
(void) fd; (void) addr; (void) len;
SOCKET_STUB_BODY;
}
int getpeername(int fd, struct sockaddr *addr, socklen_t *len)
{
(void) fd; (void) addr; (void) len;
SOCKET_STUB_BODY;
}
ssize_t send(int fd, const void *buf, size_t len, int flags)
{
(void) fd; (void) buf; (void) len; (void) flags;
errno = ENOSYS;
return -1;
}
ssize_t recv(int fd, void *buf, size_t len, int flags)
{
(void) fd; (void) buf; (void) len; (void) flags;
errno = ENOSYS;
return -1;
}
/* ==== Name resolution ================================================
*
* DNS is resolved inside the kernel network stack; no resolver interface is
* exposed to userspace. Lookups therefore fail, which git reports as an
* unresolvable host.
*/
int h_errno;
struct hostent *gethostbyname(const char *name)
{
(void) name;
h_errno = HOST_NOT_FOUND;
return NULL;
}
struct servent *getservbyname(const char *name, const char *proto)
{
(void) name; (void) proto;
return NULL;
}
int getaddrinfo(const char *node, const char *service,
const struct addrinfo *hints, struct addrinfo **res)
{
(void) node; (void) service; (void) hints;
if (res)
*res = NULL;
return EAI_FAIL;
}
void freeaddrinfo(struct addrinfo *res)
{
(void) res;
}
int getnameinfo(const struct sockaddr *sa, socklen_t salen,
char *host, socklen_t hostlen,
char *serv, socklen_t servlen, int flags)
{
(void) sa; (void) salen; (void) host; (void) hostlen;
(void) serv; (void) servlen; (void) flags;
return EAI_FAIL;
}
const char *gai_strerror(int errcode)
{
(void) errcode;
return "name resolution is not available on MontaukOS";
}
in_addr_t inet_addr(const char *cp)
{
(void) cp;
return INADDR_NONE;
}
char *inet_ntoa(struct in_addr in)
{
static char buf[16];
unsigned int a = (unsigned int) in.s_addr;
snprintf(buf, sizeof(buf), "%u.%u.%u.%u",
a & 0xff, (a >> 8) & 0xff, (a >> 16) & 0xff, (a >> 24) & 0xff);
return buf;
}
/* ==== uname ==========================================================
*
* Static answers: there is no kernel interface for the release/version
* strings. Feeds `git version --build-options` and `git bugreport`.
*/
int uname(struct utsname *buf)
{
if (!buf) {
errno = EFAULT;
return -1;
}
memset(buf, 0, sizeof(*buf));
strcpy(buf->sysname, "MontaukOS");
strcpy(buf->nodename, "montauk");
strcpy(buf->release, "");
strcpy(buf->version, "");
strcpy(buf->machine, "x86_64");
return 0;
}