/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * 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 /* ==== 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; }