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
+218
View File
@@ -0,0 +1,218 @@
# git for MontaukOS
#
# Upstream: https://github.com/git/git (submodule at upstream/, pinned to
# v2.39.5).
#
# git is built as a plain system program (0:/os/git.elf), not an app bundle:
# it is a command-line tool driven from the terminal.
#
# CONFIGURATION
# The build must not pick up any host defaults. git derives its platform
# settings from `uname -s` through config.mak.uname, which on this host
# would answer "Linux" and switch on clock_gettime, getdelim, /dev/tty and
# friends -- none of which the Montauk libc has. Every uname_* variable is
# therefore overridden to a name config.mak.uname does not know, leaving
# the knobs below as the only configuration.
#
# The NO_* list is not "things git can live without" so much as an
# inventory of what MontaukOS does not provide yet: no threads, no
# sockets, no iconv, no regex, no file-backed mmap. git ships portable
# fallbacks for all of them (compat/), which is what makes this port
# tractable at all.
#
# COMPAT SHIMS (compat/) supply the headers and functions the Montauk libc
# has no equivalent of -- see compat/README for what each one does and, more
# importantly, what it does not.
UPSTREAM := upstream
COMPAT := $(CURDIR)/compat
PORT_NAME := git
include ../../mk/port.mk
GIT_BIN := $(UPSTREAM)/git
GIT_INSTALL := $(MONTAUKOS)/programs/bin/os
# Where git's compiled-in paths point on the target. programs/bin/os is the
# staging directory for 0:/os, so GIT_PREFIX and GIT_STAGE are two names for
# the same place -- one as the OS sees it, one as the build host does.
# RUNTIME_PREFIX is off (it wants /proc/self/exe), so these are baked in:
#
# $(GIT_PREFIX)/share/git-core/templates `git init` skeleton
# $(GIT_PREFIX)/etc/gitconfig system-wide config
# $(GIT_PREFIX)/libexec/git-core external subcommands (none yet;
# every command in this build is a
# builtin, which is what makes git
# usable without fork/exec)
GIT_PREFIX := 0:/os/git
GIT_STAGE := $(GIT_INSTALL)/git
# `git version` string. Without an override GIT-VERSION-GEN runs `git
# describe` on the checkout and, because the patch series is applied in the
# working tree, reports "2.39.5.dirty" -- true but unhelpful. Name the port
# instead: the tree really is patched, and this says by whom.
GIT_UPSTREAM_TAG := $(shell git -C $(UPSTREAM) describe --tags --abbrev=0 2>/dev/null)
GIT_VERSION_STR := $(patsubst v%,%,$(GIT_UPSTREAM_TAG))-montauk
# ==== Toolchain flags =================================================
# CFLAGS/LDFLAGS/EXTLIBS must be passed on git's make COMMAND LINE, not
# through the environment: its Makefile assigns `CFLAGS = -g -O2 -Wall`
# outright, and a plain assignment beats an environment value. Only a
# command-line assignment wins. Overriding them is safe -- git keeps its own
# required includes and defines in BASIC_CFLAGS, which is separate and is
# appended to ALL_CFLAGS after ours.
GIT_CFLAGS := \
-std=gnu99 \
$(PORT_CFLAGS_COMMON) \
-Wno-unused-parameter \
-Wno-sign-compare \
-I $(COMPAT) \
-include $(COMPAT)/montauk-paths.h \
-include $(COMPAT)/montauk-ownership.h \
-isystem $(shell $(CC) -print-file-name=include)
# crt1.o carries _start; -nostdlib (from PORT_LDFLAGS_COMMON) drops the
# driver's own startup files, so name them here.
GIT_CRT := \
$(SDK_LIB)/libc/crt1.o \
$(SDK_LIB)/libc/crti.o \
$(SDK_LIB)/libc/crtn.o
COMPAT_LIB := $(CURDIR)/compat/libgitcompat.a
GIT_LDFLAGS := $(PORT_LDFLAGS_COMMON) $(GIT_CRT)
# EXTLIBS lands on the link line after the objects, which is where the compat
# archive, zlib and libc have to sit for the static link to resolve. Naming
# libz.a here rather than letting ZLIB_PATH add -lz is deliberate: overriding
# EXTLIBS drops git's own `EXTLIBS += -lz` append. ZLIB_PATH is still set, for
# the -I it contributes to BASIC_CFLAGS.
GIT_EXTLIBS := $(COMPAT_LIB) $(PORT_SYSROOT_LIB)/libz.a $(LIBC_A)
# ==== Build configuration =============================================
#
# DEFAULT_PAGER=cat is load-bearing, not a preference: git_pager() treats the
# literal "cat" as "no pager at all" and skips the fork entirely (pager.c).
# Left at its default it would try to spawn `less` on every `git log` and
# fail, since MontaukOS cannot fork.
#
# OBJECT_CREATION_USES_RENAMES=1 because there are no hard links. Every loose
# object is written to a temp file and then finalized; by default git tries
# link()+unlink() first and only falls back to rename() when link fails, so
# without this every single object write makes a doomed syscall and travels an
# error path. The knob picks rename() up front, which is what happens anyway.
#
# DEFAULT_EDITOR is deliberately NOT set. With an empty environment TERM is
# unset, so git considers the terminal dumb, git_editor() returns NULL, and
# `git commit` with no -m says "terminal is dumb, but EDITOR unset. Please
# supply the message using either -m or -F option." Naming an editor here
# would replace that with a failed spawn.
GIT_ARGS := \
CC=$(CC) \
AR=$(AR) \
CFLAGS="$(GIT_CFLAGS)" \
LDFLAGS="$(GIT_LDFLAGS)" \
EXTLIBS="$(GIT_EXTLIBS)" \
uname_S=MontaukOS \
uname_M=x86_64 \
uname_O=MontaukOS \
uname_R=1 \
uname_P=x86_64 \
uname_V=1 \
prefix=$(GIT_PREFIX) \
GIT_VERSION=$(GIT_VERSION_STR) \
ZLIB_PATH=$(PORT_SYSROOT) \
BLK_SHA1=1 \
NO_CURL=1 \
NO_EXPAT=1 \
NO_OPENSSL=1 \
NO_ICONV=1 \
NO_GETTEXT=1 \
NO_PTHREADS=1 \
NO_PERL=1 \
NO_PYTHON=1 \
NO_TCLTK=1 \
NO_REGEX=1 \
NO_MMAP=1 \
NO_IPV6=1 \
NO_INET_NTOP=1 \
NO_INET_PTON=1 \
NO_HSTRERROR=1 \
NO_SYS_SELECT_H=1 \
NO_UNIX_SOCKETS=1 \
NO_SETITIMER=1 \
NO_STRUCT_ITIMERVAL=1 \
NO_LIBGEN_H=1 \
NO_MEMMEM=1 \
NO_STRCASESTR=1 \
NO_STRLCPY=1 \
NO_STRTOUMAX=1 \
NO_MKDTEMP=1 \
NO_INITGROUPS=1 \
NO_GECOS_IN_PWENT=1 \
NO_NSEC=1 \
NO_INSTALL_HARDLINKS=1 \
NO_TRUSTABLE_FILEMODE=1 \
NO_SYMLINK_HEAD=1 \
INTERNAL_QSORT=1 \
OBJECT_CREATION_USES_RENAMES=1 \
DEFAULT_PAGER=cat
GIT_MAKE := MAKEFLAGS= $(MAKE)
.PHONY: all clean git-build install uninstall
all: git-build
$(COMPAT_LIB): $(wildcard compat/*.c) $(wildcard compat/*.h)
$(CC) $(GIT_CFLAGS) -c compat/montauk-compat.c -o compat/montauk-compat.o
$(AR) rcs $@ compat/montauk-compat.o
@echo "Built: $@"
# The templates/ sub-make builds the skeleton `git init` copies into a new
# repository (info/exclude, the sample hooks, the description file) into
# templates/blt. Without it every `git init` warns that templates are
# missing. It is a subdirectory build, not a target of the top Makefile, so
# it is invoked directly -- `make templates` up there just matches the
# directory name and does nothing.
git-build: $(COMPAT_LIB)
@$(call ns_patch,$(UPSTREAM))
@# git's link rule knows nothing about EXTLIBS or the crt objects, so a
@# rebuilt compat archive -- or a rebuilt libc, which is the one that
@# actually catches people out -- would otherwise leave the old binary
@# in place. Drop it and let the sub-make link again.
@for lib in $(GIT_EXTLIBS) $(GIT_CRT); do \
if [ -f $(GIT_BIN) ] && [ "$$lib" -nt $(GIT_BIN) ]; then \
echo " RELINK: $$(basename $$lib) is newer than $(GIT_BIN)"; \
rm -f $(GIT_BIN); \
fi; \
done
$(GIT_MAKE) -C $(UPSTREAM) $(GIT_ARGS) git
$(GIT_MAKE) -C $(UPSTREAM)/templates
@test -f $(GIT_BIN) || { echo "git: expected $(GIT_BIN)" >&2; exit 1; }
@echo "Built: $(GIT_BIN) ($$(stat -c%s $(GIT_BIN)) bytes)"
# Installed as a system program, not an app bundle: git is a command line
# tool with no window. It carries no manifest and no .port marker, so
# remember to uninstall before cutting a release ISO.
#
# The binary is STRIPPED on the way in -- the debug info is five sixths of
# its size (17 MB -> 3 MB), and it all has to fit in the ramdisk.
install: git-build
mkdir -p $(GIT_INSTALL)
$(TOOLCHAIN_PREFIX)strip -o $(GIT_INSTALL)/git.elf $(GIT_BIN)
mkdir -p $(GIT_STAGE)/share/git-core
rm -rf $(GIT_STAGE)/share/git-core/templates
cp -rL $(UPSTREAM)/templates/blt $(GIT_STAGE)/share/git-core/templates
@echo "installed: 0:/os/git.elf + $(GIT_PREFIX)/share/git-core/templates"
uninstall:
rm -rf $(GIT_INSTALL)/git.elf $(GIT_STAGE)
clean:
@$(GIT_MAKE) -C $(UPSTREAM) $(GIT_ARGS) clean >/dev/null 2>&1 || true
@git -C $(UPSTREAM) checkout -- . 2>/dev/null || true
rm -f $(COMPAT_LIB) compat/*.o
+143
View File
@@ -0,0 +1,143 @@
# git for MontaukOS
Upstream [git/git](https://github.com/git/git), pinned at **v2.39.5**
(`upstream/`, submodule). Reports itself as `2.39.5-montauk`.
Installs as a system program, not an app bundle:
| Path | What |
|------|------|
| `0:/os/git.elf` | the binary (~3 MB stripped) |
| `0:/os/git/share/git-core/templates` | `git init` skeleton |
| `0:/os/git/etc/gitconfig` | system config (not created by the install) |
```bash
make -C dev-vcs/git # build
make -C dev-vcs/git install # -> MontaukOS/programs/bin/os/
```
`make ports` from the MontaukOS tree does both (git is in the top-level
Makefile's `SELF_INSTALL_PORTS`).
## Status
First run on hardware: `git` started, died at exit 128, printed nothing. Two
bugs, both outside git and both now fixed -- **needs a retest**:
1. **`sanitize_stdfds()` opened `/dev/null`**, which MontaukOS does not have,
so `xopen()` died before any command dispatched. That function guards
against inheriting closed descriptors, which cannot happen on a system that
spawns rather than forks; `patches/0002` makes it a no-op.
2. **Nothing git wrote with `write(2, ...)` ever appeared** -- which is why
the fatal error was invisible. The libc's `read()`/`write()` passed fd 0/1/2
straight to `SYS_READ`/`SYS_FWRITE` as file handles, and the kernel hands
out file handles starting at 0, so those numbers named real files. A
program's third `open()` owned "stderr". Fixed in the OS: the kernel no
longer allocates handles 0/1/2, and the libc routes them to the terminal.
Second run got as far as `git init` (works, repository created) and then hit
`fatal: detected dubious ownership`. That is git's safe.directory check
comparing `lstat()`'s `st_uid` against `geteuid()`; MontaukOS stores no
per-file owner, so it is false for every directory on the system. The port now
answers that check directly -- see `compat/montauk-ownership.h`, which also
records what has to change when the OS grows real ownership.
Confirmed working on hardware so far: startup, error output, `git init`, and
quoted arguments. The rest is still only what the build configuration
implies.
## What should work
Everything git does inside one process and one filesystem:
- `init`, `add`, `rm`, `mv`, `status`, `diff`, `commit`, `log`, `show`
- `branch`, `checkout`, `switch`, `restore`, `merge`, `reset`, `revert`
- `tag`, `stash`, `cherry-pick`, `rebase` (the builtin, non-interactive path)
- `cat-file`, `rev-parse`, `ls-files`, `ls-tree`, `hash-object`, `fsck`
- `config`, `blame`, `grep`, `describe`, `bisect`
- `clone`/`fetch`/`push` **against a local path**, e.g.
`git clone 0:/users/dan/repo`
## What does not work, and why
**Anything that starts another process.** MontaukOS spawns (`SYS_SPAWN`); it
has no `fork()`, no `exec()` and no `pipe()`, and a spawned process cannot
inherit a pipe for its stdio. Every git feature built on sub-processes fails
with `ENOSYS` rather than misbehaving:
- hooks (`pre-commit`, `commit-msg`, ...) -- the samples are installed but
never run
- `$EDITOR`, so `git commit` needs `-m` or `-F`, and interactive rebase and
`git add -p` are out
- the pager (harmless -- output goes straight to the terminal, and
`DEFAULT_PAGER=cat` is compiled in anyway)
- aliases that shell out (`!command`), `git submodule`, `difftool`,
`mergetool`, `git gc` (it re-runs itself as `git repack`)
**Network transports.** No `http://`, `https://`, `git://` or `ssh://`. The
port is built `NO_CURL NO_OPENSSL`, and the socket shims fail: MontaukOS TCP
lives behind IPC handles rather than file descriptors, and DNS resolution is
inside the kernel with no userspace interface. Local paths are the transport.
**Symlinks.** The VFS cannot create them, so git behaves as if
`core.symlinks=false` -- a symlink in a tree checks out as a regular file
containing the link target.
**File modes.** Built `NO_TRUSTABLE_FILEMODE`, so the executable bit is not
read back from the filesystem; `core.fileMode` defaults to false.
**Threads.** `NO_PTHREADS`, so packing, delta search and `git grep` are
single-threaded. Correct, just slower.
**`mmap`.** `NO_MMAP`, so pack and index access reads into memory instead of
mapping. Large repositories will be heavier on RAM than on Linux.
## Before the first commit
The environment MontaukOS hands a process is empty, so the port sets `HOME`
itself from the session user (`0:/users/<name>`) before git reads any config
-- that is what makes `--global` work. Set an identity:
```
git config --global user.name "Your Name"
git config --global user.email [email protected]
```
This is not optional. Left unset, git builds a fallback address from the
session user and the hostname -- and since `gethostname()` answers `montauk`,
with no domain in it, git marks the address bogus, appends `.(none)` and
refuses:
```
fatal: unable to auto-detect email address (got 'admin@montauk.(none)')
```
That is stock git behaviour on any host without a domain name, not something
the port introduces.
## Quoting
Arguments are split by the C runtime, not by a shell. `crt1.c` understands
`'...'` and `"..."` (added for this port), so
```
git commit -m "a message with spaces"
```
reaches git as three arguments. There is no escaping and no substitution --
a literal quote character cannot be passed.
## How the port is put together
`Makefile` drives upstream's own build with the toolchain and a long list of
`NO_*` knobs; the header comment there explains the ones that are not
self-evident. Two mechanisms fill the gaps:
- `compat/` -- headers MontaukOS has no equivalent of, plus
`libgitcompat.a` with the POSIX functions the libc does not carry. See
`compat/README`.
- `patches/` -- two patches: calling `montauk_startup()` from `main()`, and
emptying `sanitize_stdfds()`, which cannot run without a `/dev/null`.
Everything else upstream needed was already reachable through git's own
portability knobs, which is the reason this port is small.
+57
View File
@@ -0,0 +1,57 @@
compat/ -- what MontaukOS does not have, and what git gets instead.
Two kinds of file live here.
HEADER OVERLAYS use #include_next to pull in the Montauk libc's own header
and then add what git needs on top. They are found first because -I compat
precedes the libc include path.
unistd.h identity (getuid/geteuid/...), symlink/readlink/link,
chown, fsync, process groups, execl/execlp, getpass, alarm
signal.h the sigaction() family and NSIG
time.h localtime_r/gmtime_r/ctime_r/asctime_r
sys/stat.h S_ISUID/S_ISGID/S_ISVTX and the S_IF* type constants
sys/time.h ITIMER_* selectors
sys/types.h the BSD aliases (u_char, u_int, ...) the ISC-derived
inet_ntop/inet_pton want
STANDALONE HEADERS are for things the libc has no header for at all:
poll.h struct pollfd; the implementation reports everything ready
pwd.h struct passwd, answered from the session user
grp.h struct group; every lookup fails (no group database)
termios.h accepted and ignored; there is no line discipline
netdb.h resolver types; every lookup fails
syslog.h discards (only `git daemon` logs, and it is not built)
sys/socket.h BSD socket API; every call fails with ENOSYS
sys/un.h struct sockaddr_un, for the type only
sys/ioctl.h TIOCGWINSZ, which IS implemented (SYS_TERMSIZE)
sys/utsname.h uname(), static MontaukOS strings
sys/resource.h getrlimit says "unlimited"; getrusage fails
sys/statvfs.h fails; no filesystem statistics syscall
netinet/in.h, netinet/tcp.h, arpa/inet.h address types and byte order
THESE FILES ARE NOT SHIMS. They are the port's own code:
montauk-paths.h force-included into every translation unit, ahead of
git-compat-util.h, so git recognises "0:/path" as an
absolute path. Without it git treats every MontaukOS
absolute path as relative and prepends the cwd to it.
montauk-ownership.h also force-included: answers git's safe.directory
"is this repository mine?" check with yes. MontaukOS
stores no per-file owner, so the default lstat-vs-geteuid
comparison is false for every directory on the system.
Read the header before changing it -- it says what this
gives up once the OS grows real ownership.
montauk-init.h declares montauk_startup(), called from main() by the
patch series.
montauk-compat.c the implementations, each commented with what it
really does. Built into libgitcompat.a, which sits on
the link line ahead of liblibc.a.
THE RULE followed throughout: a shim either does the real thing, or fails
with an errno git already knows how to report. Nothing silently pretends to
have done work it did not do -- with one deliberate exception, fsync(),
which reports success because git treats a failed fsync as fatal and would
abort every commit. That one is called out at its definition.
+18
View File
@@ -0,0 +1,18 @@
/*
* <arpa/inet.h> for the MontaukOS git port.
*
* inet_ntop/inet_pton are not declared here: the port builds with
* NO_INET_NTOP=1 / NO_INET_PTON=1, so git compiles its own portable versions
* (compat/inet_ntop.c, compat/inet_pton.c) and declares them itself in
* git-compat-util.h. Everything else an address needs comes from
* <netinet/in.h>.
*/
#ifndef _MONTAUK_GIT_ARPA_INET_H_
#define _MONTAUK_GIT_ARPA_INET_H_
#include <netinet/in.h>
in_addr_t inet_addr(const char *cp);
char *inet_ntoa(struct in_addr in);
#endif /* _MONTAUK_GIT_ARPA_INET_H_ */
+25
View File
@@ -0,0 +1,25 @@
/*
* <grp.h> stub for the MontaukOS git port.
*
* Included by git-compat-util.h for the group lookups in daemon.c and
* setup.c's shared-repository handling. MontaukOS has no group database, so
* every lookup fails and git falls back to its "no group" paths.
*
* Implementation in montauk-compat.c.
*/
#ifndef _MONTAUK_GIT_GRP_H_
#define _MONTAUK_GIT_GRP_H_
#include <sys/types.h>
struct group {
char *gr_name;
char *gr_passwd;
gid_t gr_gid;
char **gr_mem;
};
struct group *getgrnam(const char *name);
struct group *getgrgid(gid_t gid);
#endif /* _MONTAUK_GIT_GRP_H_ */
+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;
}
+13
View File
@@ -0,0 +1,13 @@
/*
* montauk-init.h -- one-time startup fixups, called from main().
*
* See montauk_startup() in montauk-compat.c for what it does and why it
* cannot be an __attribute__((constructor)): programs linked with
* programs/link.ld have no .init_array section, so constructors never run.
*/
#ifndef _MONTAUK_GIT_INIT_H_
#define _MONTAUK_GIT_INIT_H_
void montauk_startup(void);
#endif /* _MONTAUK_GIT_INIT_H_ */
+45
View File
@@ -0,0 +1,45 @@
/*
* montauk-ownership.h -- answer git's "is this repository mine?" question.
*
* FORCE-INCLUDED into every translation unit (-include, see the port
* Makefile), because it has to win the #ifndef guard in git-compat-util.h.
*
* git's safe.directory check exists because on a multi-user system a
* repository owned by somebody else can attack you the moment you run git in
* it -- its config names hooks, pagers and external diff commands that would
* run as you. The default implementation answers by comparing the owner
* reported by lstat() against geteuid().
*
* Neither side of that comparison means anything on MontaukOS. The VFS stores
* no owner at all -- stat() zero-fills st_uid, on the ramdisk, on ext2 and on
* FAT32 alike -- and there is one session user, so the compat layer reports a
* single fixed uid for everybody (see montauk-compat.c). The comparison is
* therefore 0 == 1000, false for EVERY repository: unpatched, git refuses to
* work in any directory on the system, which is what
*
* fatal: detected dubious ownership in repository at '0:/users/...'
*
* was. Picking the uid to match the zero from stat() would only paper over it,
* and would mean claiming to be root.
*
* So answer honestly: every file belongs to the one user, and the check has
* nothing to distinguish. This is the same seam Windows uses for the same
* reason (compat/mingw.h defines is_path_owned_by_current_sid here).
*
* WHAT THIS GIVES UP: nothing today, and something real later. When MontaukOS
* grows per-file ownership, this must go back to comparing owners -- otherwise
* git in another user's repository will run that repository's hooks as you.
*/
#ifndef _MONTAUK_GIT_OWNERSHIP_H_
#define _MONTAUK_GIT_OWNERSHIP_H_
/*
* A macro, not a function: this header is force-included before anything
* declares struct strbuf, so the report parameter cannot be named in a
* prototype here. Both arguments are still evaluated-as-void so an unused
* variable at a call site does not start warning.
*/
#define is_path_owned_by_current_user(path, report) \
((void)(path), (void)(report), 1)
#endif /* _MONTAUK_GIT_OWNERSHIP_H_ */
+71
View File
@@ -0,0 +1,71 @@
/*
* montauk-paths.h -- teach git what an absolute path looks like on MontaukOS.
*
* FORCE-INCLUDED into every translation unit (-include, see the port
* Makefile), because it has to win the #ifndef guards in git-compat-util.h.
*
* MontaukOS paths are drive-prefixed: "0:/users/dan/repo". getcwd() returns
* one of those, and the kernel's path resolver (kernel/src/Api/Path.hpp)
* accepts three forms -- "N:/abs", "/abs" (the current drive's root) and
* "rel/ative" against the process cwd.
*
* Without this header git's is_absolute_path() is just is_dir_sep(path[0]),
* so every MontaukOS absolute path reads as RELATIVE and git helpfully
* prepends the cwd to it: "0:/users/dan/repo/0:/users/dan/repo". That breaks
* setup_git_directory(), real_path() and every worktree path it stores.
*
* The fix reuses git's existing Windows abstraction rather than inventing
* one: has_dos_drive_prefix / skip_dos_drive_prefix / offset_1st_component
* are exactly the hooks the DOS "C:/" case goes through, and a MontaukOS
* drive differs only in being a digit run rather than a letter. The bodies
* below mirror compat/win32/path-utils.c, minus the UNC handling, which has
* no MontaukOS equivalent.
*/
#ifndef _MONTAUK_GIT_PATHS_H_
#define _MONTAUK_GIT_PATHS_H_
/*
* "12:" as well as "0:" -- ParseDrivePrefix() accepts a multi-digit drive
* number, so this must too, or a two-digit drive would silently be treated
* as a relative path.
*/
static inline int montauk_has_drive_prefix(const char *path)
{
int i = 0;
if (!path)
return 0;
while (path[i] >= '0' && path[i] <= '9')
i++;
return i > 0 && path[i] == ':';
}
#define has_dos_drive_prefix montauk_has_drive_prefix
static inline int montauk_skip_drive_prefix(char **path)
{
int i = 0;
if (!montauk_has_drive_prefix(*path))
return 0;
while ((*path)[i] >= '0' && (*path)[i] <= '9')
i++;
*path += i + 1; /* the digits and the ':' */
return 1;
}
#define skip_dos_drive_prefix montauk_skip_drive_prefix
/*
* Length of the leading "root" of the path: the drive prefix plus one
* separator if present. Mirrors win32_offset_1st_component().
*/
static inline int montauk_offset_1st_component(const char *path)
{
char *pos = (char *)path;
montauk_skip_drive_prefix(&pos);
return (int)(pos - path) + (*pos == '/');
}
#define offset_1st_component montauk_offset_1st_component
#endif /* _MONTAUK_GIT_PATHS_H_ */
+82
View File
@@ -0,0 +1,82 @@
/*
* <netdb.h> for the MontaukOS git port.
*
* The port builds NO_IPV6=1, so git resolves names through gethostbyname()
* and getservbyname() rather than getaddrinfo(). Both fail here: MontaukOS
* resolves DNS inside the kernel network stack, and no userspace resolver
* interface is exposed. `git clone git://host/repo` therefore reports that it
* cannot look up the host. getaddrinfo() is still declared because
* git-compat-util.h references the types unconditionally.
*
* Implementations in montauk-compat.c.
*/
#ifndef _MONTAUK_GIT_NETDB_H_
#define _MONTAUK_GIT_NETDB_H_
#include <sys/socket.h>
#include <netinet/in.h>
struct hostent {
char *h_name;
char **h_aliases;
int h_addrtype;
int h_length;
char **h_addr_list;
};
#define h_addr h_addr_list[0]
struct servent {
char *s_name;
char **s_aliases;
int s_port;
char *s_proto;
};
struct addrinfo {
int ai_flags;
int ai_family;
int ai_socktype;
int ai_protocol;
socklen_t ai_addrlen;
struct sockaddr *ai_addr;
char *ai_canonname;
struct addrinfo *ai_next;
};
#define AI_PASSIVE 0x0001
#define AI_CANONNAME 0x0002
#define AI_NUMERICHOST 0x0004
#define AI_NUMERICSERV 0x0400
#define NI_MAXHOST 1025
#define NI_MAXSERV 32
#define NI_NUMERICHOST 1
#define NI_NUMERICSERV 2
#define NI_NAMEREQD 8
#define EAI_NONAME -2
#define EAI_AGAIN -3
#define EAI_FAIL -4
#define EAI_FAMILY -6
#define EAI_SYSTEM -11
#define HOST_NOT_FOUND 1
#define TRY_AGAIN 2
#define NO_RECOVERY 3
#define NO_DATA 4
extern int h_errno;
struct hostent *gethostbyname(const char *name);
struct servent *getservbyname(const char *name, const char *proto);
int getaddrinfo(const char *node, const char *service,
const struct addrinfo *hints, struct addrinfo **res);
void freeaddrinfo(struct addrinfo *res);
int getnameinfo(const struct sockaddr *sa, socklen_t salen,
char *host, socklen_t hostlen,
char *serv, socklen_t servlen, int flags);
const char *gai_strerror(int errcode);
#endif /* _MONTAUK_GIT_NETDB_H_ */
+64
View File
@@ -0,0 +1,64 @@
/*
* <netinet/in.h> for the MontaukOS git port.
*
* Address types only. See compat/sys/socket.h for why nothing here connects
* to anything yet. The byte-order helpers are real (x86-64 is little-endian,
* so they are byte swaps).
*/
#ifndef _MONTAUK_GIT_NETINET_IN_H_
#define _MONTAUK_GIT_NETINET_IN_H_
#include <stdint.h>
#include <sys/socket.h>
typedef uint16_t in_port_t;
typedef uint32_t in_addr_t;
struct in_addr {
in_addr_t s_addr;
};
struct in6_addr {
union {
uint8_t __u6_addr8[16];
uint16_t __u6_addr16[8];
uint32_t __u6_addr32[4];
} __in6_u;
};
#define s6_addr __in6_u.__u6_addr8
#define s6_addr16 __in6_u.__u6_addr16
#define s6_addr32 __in6_u.__u6_addr32
struct sockaddr_in {
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
unsigned char sin_zero[8];
};
struct sockaddr_in6 {
sa_family_t sin6_family;
in_port_t sin6_port;
uint32_t sin6_flowinfo;
struct in6_addr sin6_addr;
uint32_t sin6_scope_id;
};
#define INADDR_ANY ((in_addr_t)0x00000000)
#define INADDR_LOOPBACK ((in_addr_t)0x7f000001)
#define INADDR_NONE ((in_addr_t)0xffffffff)
#define INET_ADDRSTRLEN 16
#define INET6_ADDRSTRLEN 46
#define IPPROTO_IP 0
#define IPPROTO_TCP 6
#define IPPROTO_UDP 17
static inline uint16_t htons(uint16_t x) { return __builtin_bswap16(x); }
static inline uint16_t ntohs(uint16_t x) { return __builtin_bswap16(x); }
static inline uint32_t htonl(uint32_t x) { return __builtin_bswap32(x); }
static inline uint32_t ntohl(uint32_t x) { return __builtin_bswap32(x); }
#endif /* _MONTAUK_GIT_NETINET_IN_H_ */
+15
View File
@@ -0,0 +1,15 @@
/*
* <netinet/tcp.h> for the MontaukOS git port.
*
* Socket option names only; setsockopt() itself fails here. See
* compat/sys/socket.h.
*/
#ifndef _MONTAUK_GIT_NETINET_TCP_H_
#define _MONTAUK_GIT_NETINET_TCP_H_
#define TCP_NODELAY 1
#define TCP_KEEPIDLE 4
#define TCP_KEEPINTVL 5
#define TCP_KEEPCNT 6
#endif /* _MONTAUK_GIT_NETINET_TCP_H_ */
+37
View File
@@ -0,0 +1,37 @@
/*
* <poll.h> for the MontaukOS git port.
*
* git polls only descriptors it created itself with pipe() -- the stdout and
* stderr of a spawned sub-process (run-command.c's pump loop, sideband
* demultiplexing). MontaukOS has neither pipe() nor fork(), so those code
* paths are unreachable at runtime; this header exists so they compile, and
* the implementation reports every descriptor ready so that a caller which
* somehow gets here makes progress into the read() that reports the real
* error, instead of spinning in the poll loop forever.
*
* git's own compat/poll (NO_POLL=1) is not used: it is written against
* select() and <sys/select.h>, neither of which exists here.
*
* Implementation in montauk-compat.c.
*/
#ifndef _MONTAUK_GIT_POLL_H_
#define _MONTAUK_GIT_POLL_H_
typedef unsigned long nfds_t;
struct pollfd {
int fd;
short events;
short revents;
};
#define POLLIN 0x001
#define POLLPRI 0x002
#define POLLOUT 0x004
#define POLLERR 0x008
#define POLLHUP 0x010
#define POLLNVAL 0x020
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
#endif /* _MONTAUK_GIT_POLL_H_ */
+31
View File
@@ -0,0 +1,31 @@
/*
* <pwd.h> for the MontaukOS git port.
*
* MontaukOS has users (0:/users/<name>, see montauk/user.h) but no
* /etc/passwd database and no numeric uid model -- everything runs as the
* session user. getpwuid()/getpwnam() therefore answer from the current
* session rather than from a database, which is exactly what git wants them
* for: the fallback identity in ident.c ("You gotta tell me who you are") and
* the home directory behind "~".
*
* Implementation in montauk-compat.c.
*/
#ifndef _MONTAUK_GIT_PWD_H_
#define _MONTAUK_GIT_PWD_H_
#include <sys/types.h>
struct passwd {
char *pw_name;
char *pw_passwd;
uid_t pw_uid;
gid_t pw_gid;
char *pw_gecos;
char *pw_dir;
char *pw_shell;
};
struct passwd *getpwuid(uid_t uid);
struct passwd *getpwnam(const char *name);
#endif /* _MONTAUK_GIT_PWD_H_ */
+49
View File
@@ -0,0 +1,49 @@
/*
* <signal.h> overlay for the MontaukOS git port.
*
* The libc supplies signal()/raise()/kill() and the signal numbers; this adds
* the sigaction() family that git uses in a handful of places (fast-import's
* SIGUSR1 checkpoint handler, sigchain's SIGPIPE and SIGINT handling).
*
* MontaukOS delivers only SIGINT, and has no signal masks: a handler is
* either installed or it is not. sigaction() below is therefore a wrapper
* around signal() that honours sa_handler and ignores sa_mask and sa_flags,
* and the sigset_t operations manipulate a plain bitmask that nothing
* consults. See montauk-compat.c.
*/
#ifndef _MONTAUK_GIT_SIGNAL_H_
#define _MONTAUK_GIT_SIGNAL_H_
#include_next <signal.h>
/* One past the highest signal number the libc names (SIGSTOP, 19). */
#define NSIG 32
typedef unsigned long sigset_t;
struct sigaction {
sighandler_t sa_handler;
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
#define SA_RESTART 0x10000000
#define SA_NOCLDSTOP 0x00000001
#define SA_SIGINFO 0x00000004
#define SA_NODEFER 0x40000000
#define SA_RESETHAND 0x80000000
#define SIG_BLOCK 0
#define SIG_UNBLOCK 1
#define SIG_SETMASK 2
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);
int sigismember(const sigset_t *set, int signum);
int sigaction(int signum, const struct sigaction *act, struct sigaction *old);
int sigprocmask(int how, const sigset_t *set, sigset_t *old);
#endif /* _MONTAUK_GIT_SIGNAL_H_ */
+24
View File
@@ -0,0 +1,24 @@
/*
* <sys/ioctl.h> for the MontaukOS git port.
*
* git uses exactly one ioctl: TIOCGWINSZ, to find the terminal width for
* `git column`, the progress meter and `git help -a`. MontaukOS has no ioctl
* syscall, so the request fails and git falls back to its default 80 columns.
*
* Implementation in montauk-compat.c.
*/
#ifndef _MONTAUK_GIT_SYS_IOCTL_H_
#define _MONTAUK_GIT_SYS_IOCTL_H_
#define TIOCGWINSZ 0x5413
struct winsize {
unsigned short ws_row;
unsigned short ws_col;
unsigned short ws_xpixel;
unsigned short ws_ypixel;
};
int ioctl(int fd, unsigned long request, ...);
#endif /* _MONTAUK_GIT_SYS_IOCTL_H_ */
+63
View File
@@ -0,0 +1,63 @@
/*
* <sys/resource.h> stub for the MontaukOS git port.
*
* Reached only through git-compat-util.h's unconditional include and
* trace2's resource accounting, which is compiled out without getrusage.
* MontaukOS keeps no per-process resource accounting, so getrlimit reports
* "unlimited" and everything else fails.
*/
#ifndef _MONTAUK_GIT_SYS_RESOURCE_H_
#define _MONTAUK_GIT_SYS_RESOURCE_H_
#include <sys/time.h>
#include <errno.h>
#define RLIMIT_NOFILE 7
typedef unsigned long rlim_t;
#define RLIM_INFINITY (~(rlim_t)0)
struct rlimit {
rlim_t rlim_cur;
rlim_t rlim_max;
};
struct rusage {
struct timeval ru_utime;
struct timeval ru_stime;
long ru_maxrss;
long ru_minflt;
long ru_majflt;
long ru_inblock;
long ru_oublock;
long ru_nvcsw;
long ru_nivcsw;
};
#define RUSAGE_SELF 0
static inline int getrlimit(int resource, struct rlimit *rlim)
{
(void) resource;
if (rlim) {
rlim->rlim_cur = RLIM_INFINITY;
rlim->rlim_max = RLIM_INFINITY;
}
return 0;
}
static inline int setrlimit(int resource, const struct rlimit *rlim)
{
(void) resource; (void) rlim;
return 0;
}
static inline int getrusage(int who, struct rusage *usage)
{
(void) who; (void) usage;
errno = ENOSYS;
return -1;
}
#endif /* _MONTAUK_GIT_SYS_RESOURCE_H_ */
+81
View File
@@ -0,0 +1,81 @@
/*
* <sys/socket.h> for the MontaukOS git port.
*
* MontaukOS does have TCP -- the kernel exposes it as IPC handles through
* montauk::socket(), not as BSD descriptors -- but nothing bridges the two
* into the fd space that read()/write() use, and git's transports assume a
* descriptor it can hand to a sub-process. Until that bridge exists, every
* call here fails with ENOSYS, which surfaces as "unable to connect" from
* `git clone git://...` rather than as a crash.
*
* That leaves the local transports -- init, add, commit, log, diff, and
* clone/fetch/push against a path on disk -- which are what this port is for.
*
* Implementations in montauk-compat.c.
*/
#ifndef _MONTAUK_GIT_SYS_SOCKET_H_
#define _MONTAUK_GIT_SYS_SOCKET_H_
#include <sys/types.h>
typedef unsigned int socklen_t;
typedef unsigned short sa_family_t;
#define AF_UNSPEC 0
#define AF_UNIX 1
#define AF_LOCAL 1
#define AF_INET 2
#define AF_INET6 10
#define PF_UNSPEC AF_UNSPEC
#define PF_UNIX AF_UNIX
#define PF_INET AF_INET
#define PF_INET6 AF_INET6
#define SOCK_STREAM 1
#define SOCK_DGRAM 2
#define SOL_SOCKET 1
#define SO_REUSEADDR 2
#define SO_ERROR 4
#define SO_KEEPALIVE 9
#define SO_LINGER 13
#define SHUT_RD 0
#define SHUT_WR 1
#define SHUT_RDWR 2
#define MSG_PEEK 2
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
};
/* Large enough for sockaddr_in6, aligned like the real thing. */
struct sockaddr_storage {
sa_family_t ss_family;
unsigned long __ss_align;
char __ss_padding[112];
};
struct linger {
int l_onoff;
int l_linger;
};
int socket(int domain, int type, int protocol);
int connect(int fd, const struct sockaddr *addr, socklen_t len);
int bind(int fd, const struct sockaddr *addr, socklen_t len);
int listen(int fd, int backlog);
int accept(int fd, struct sockaddr *addr, socklen_t *len);
int shutdown(int fd, int how);
int setsockopt(int fd, int level, int name, const void *val, socklen_t len);
int getsockopt(int fd, int level, int name, void *val, socklen_t *len);
int getsockname(int fd, struct sockaddr *addr, socklen_t *len);
int getpeername(int fd, struct sockaddr *addr, socklen_t *len);
ssize_t send(int fd, const void *buf, size_t len, int flags);
ssize_t recv(int fd, void *buf, size_t len, int flags);
#endif /* _MONTAUK_GIT_SYS_SOCKET_H_ */
+43
View File
@@ -0,0 +1,43 @@
/*
* <sys/stat.h> overlay for the MontaukOS git port.
*
* The libc header comes in through #include_next; this only adds the mode
* bits it leaves out. They are not idle definitions:
*
* S_ISUID builtin/fast-import.c borrows it as a private in-memory flag
* (NO_DELTA), so it must exist even though no file ever carries
* it on MontaukOS.
* S_ISGID setup.c uses it for shared-repository directory modes.
* S_ISVTX referenced when normalising modes read from an index.
*
* The S_IF* type constants below round out the set for the same reason: git
* masks against them. MontaukOS never reports a fifo, socket or block device
* from stat(), and the libc's S_ISFIFO/S_ISSOCK/S_ISBLK are hard-coded false
* to say exactly that -- they are deliberately left alone here.
*/
#ifndef _MONTAUK_GIT_SYS_STAT_H_
#define _MONTAUK_GIT_SYS_STAT_H_
#include_next <sys/stat.h>
#ifndef S_ISUID
#define S_ISUID 04000
#endif
#ifndef S_ISGID
#define S_ISGID 02000
#endif
#ifndef S_ISVTX
#define S_ISVTX 01000
#endif
#ifndef S_IFIFO
#define S_IFIFO 0010000
#endif
#ifndef S_IFBLK
#define S_IFBLK 0060000
#endif
#ifndef S_IFSOCK
#define S_IFSOCK 0140000
#endif
#endif /* _MONTAUK_GIT_SYS_STAT_H_ */
+47
View File
@@ -0,0 +1,47 @@
/*
* <sys/statvfs.h> stub for the MontaukOS git port.
*
* git calls statvfs() in one place: fsmonitor's "is this a network
* filesystem" probe, which this port does not build. MontaukOS exposes no
* filesystem statistics syscall, so the call fails and callers take their
* "unknown filesystem" branch.
*/
#ifndef _MONTAUK_GIT_SYS_STATVFS_H_
#define _MONTAUK_GIT_SYS_STATVFS_H_
#include <sys/types.h>
#include <errno.h>
typedef unsigned long fsblkcnt_t;
typedef unsigned long fsfilcnt_t;
struct statvfs {
unsigned long f_bsize;
unsigned long f_frsize;
fsblkcnt_t f_blocks;
fsblkcnt_t f_bfree;
fsblkcnt_t f_bavail;
fsfilcnt_t f_files;
fsfilcnt_t f_ffree;
fsfilcnt_t f_favail;
unsigned long f_fsid;
unsigned long f_flag;
unsigned long f_namemax;
unsigned long f_type;
};
static inline int statvfs(const char *path, struct statvfs *buf)
{
(void) path; (void) buf;
errno = ENOSYS;
return -1;
}
static inline int fstatvfs(int fd, struct statvfs *buf)
{
(void) fd; (void) buf;
errno = ENOSYS;
return -1;
}
#endif /* _MONTAUK_GIT_SYS_STATVFS_H_ */
+23
View File
@@ -0,0 +1,23 @@
/*
* <sys/time.h> overlay for the MontaukOS git port.
*
* Adds the ITIMER_* selectors on top of the libc header. MontaukOS has no
* interval timers, so the port is built NO_SETITIMER=1 and NO_STRUCT_ITIMERVAL=1
* -- git supplies its own struct itimerval and a setitimer() that returns
* success without arming anything. The selectors still have to exist, because
* the call sites name them (builtin/log.c's early-output timer, progress.c).
*
* What that costs: `git log`'s half-second early-output nudge never fires, so
* the first commits appear when the traversal produces them rather than on a
* timer. Nothing depends on it for correctness.
*/
#ifndef _MONTAUK_GIT_SYS_TIME_H_
#define _MONTAUK_GIT_SYS_TIME_H_
#include_next <sys/time.h>
#define ITIMER_REAL 0
#define ITIMER_VIRTUAL 1
#define ITIMER_PROF 2
#endif /* _MONTAUK_GIT_SYS_TIME_H_ */
+21
View File
@@ -0,0 +1,21 @@
/*
* <sys/types.h> overlay for the MontaukOS git port.
*
* Adds the historical BSD type aliases on top of the libc header. git's
* compat/inet_ntop.c and compat/inet_pton.c come from the ISC resolver and
* are written in terms of u_char/u_int; both are compiled here because the
* port sets NO_INET_NTOP=1 / NO_INET_PTON=1.
*/
#ifndef _MONTAUK_GIT_SYS_TYPES_H_
#define _MONTAUK_GIT_SYS_TYPES_H_
#include_next <sys/types.h>
typedef unsigned char u_char;
typedef unsigned short u_short;
typedef unsigned int u_int;
typedef unsigned long u_long;
typedef char *caddr_t;
#endif /* _MONTAUK_GIT_SYS_TYPES_H_ */
+18
View File
@@ -0,0 +1,18 @@
/*
* <sys/un.h> for the MontaukOS git port.
*
* Included unconditionally by git-compat-util.h. The port builds with
* NO_UNIX_SOCKETS=1, so nothing constructs one of these -- the type just has
* to exist.
*/
#ifndef _MONTAUK_GIT_SYS_UN_H_
#define _MONTAUK_GIT_SYS_UN_H_
#include <sys/socket.h>
struct sockaddr_un {
sa_family_t sun_family;
char sun_path[108];
};
#endif /* _MONTAUK_GIT_SYS_UN_H_ */
+23
View File
@@ -0,0 +1,23 @@
/*
* <sys/utsname.h> for the MontaukOS git port.
*
* Used by help.c for the "uname" lines of `git version --build-options` and
* by the bugreport builtin. Answers static MontaukOS strings; there is no
* kernel interface for the release/version fields yet.
*
* Implementation in montauk-compat.c.
*/
#ifndef _MONTAUK_GIT_SYS_UTSNAME_H_
#define _MONTAUK_GIT_SYS_UTSNAME_H_
struct utsname {
char sysname[65];
char nodename[65];
char release[65];
char version[65];
char machine[65];
};
int uname(struct utsname *buf);
#endif /* _MONTAUK_GIT_SYS_UTSNAME_H_ */
+43
View File
@@ -0,0 +1,43 @@
/*
* <syslog.h> stub for the MontaukOS git port.
*
* git-compat-util.h includes this unconditionally, but the only caller is
* daemon.c (`git daemon`), which this port does not build -- there are no
* sockets to serve on. The declarations exist so the include resolves; the
* inline bodies discard everything, which is the honest behaviour on a system
* with no system logger.
*/
#ifndef _MONTAUK_GIT_SYSLOG_H_
#define _MONTAUK_GIT_SYSLOG_H_
#include <stdarg.h>
#define LOG_EMERG 0
#define LOG_ALERT 1
#define LOG_CRIT 2
#define LOG_ERR 3
#define LOG_WARNING 4
#define LOG_NOTICE 5
#define LOG_INFO 6
#define LOG_DEBUG 7
#define LOG_PID 0x01
#define LOG_CONS 0x02
#define LOG_NDELAY 0x08
#define LOG_DAEMON (3 << 3)
#define LOG_USER (1 << 3)
static inline void openlog(const char *ident, int option, int facility)
{
(void) ident; (void) option; (void) facility;
}
static inline void closelog(void) { }
static inline void syslog(int priority, const char *fmt, ...)
{
(void) priority; (void) fmt;
}
#endif /* _MONTAUK_GIT_SYSLOG_H_ */
+59
View File
@@ -0,0 +1,59 @@
/*
* <termios.h> for the MontaukOS git port.
*
* compat/terminal.c uses tcgetattr/tcsetattr to turn off echo while reading a
* passphrase, and to put the terminal in raw mode for single-keystroke reads
* ("git add -p"). The MontaukOS terminal has no line discipline to configure:
* the libc reads stdin through SYS_GETCHAR and does its own echoing.
*
* These calls therefore succeed and do nothing rather than failing, because
* git treats a tcgetattr() failure as "not a terminal" and a tcsetattr()
* failure as fatal in some paths. The visible consequence is that a typed
* passphrase is echoed -- there is no credential helper in this port, so
* nothing prompts for one yet.
*
* Implementation in montauk-compat.c.
*/
#ifndef _MONTAUK_GIT_TERMIOS_H_
#define _MONTAUK_GIT_TERMIOS_H_
#include <sys/types.h>
typedef unsigned int tcflag_t;
typedef unsigned char cc_t;
typedef unsigned int speed_t;
#define NCCS 32
struct termios {
tcflag_t c_iflag;
tcflag_t c_oflag;
tcflag_t c_cflag;
tcflag_t c_lflag;
cc_t c_line;
cc_t c_cc[NCCS];
speed_t c_ispeed;
speed_t c_ospeed;
};
/* Linux values, so code that masks bits it never applies still builds. */
#define ECHO 0000010
#define ECHONL 0000100
#define ICANON 0000002
#define ISIG 0000001
#define IEXTEN 0100000
#define IXON 0002000
#define ICRNL 0000400
#define OPOST 0000001
#define VMIN 6
#define VTIME 5
#define TCSANOW 0
#define TCSADRAIN 1
#define TCSAFLUSH 2
int tcgetattr(int fd, struct termios *t);
int tcsetattr(int fd, int actions, const struct termios *t);
#endif /* _MONTAUK_GIT_TERMIOS_H_ */
+22
View File
@@ -0,0 +1,22 @@
/*
* <time.h> overlay for the MontaukOS git port.
*
* Adds the reentrant conversions on top of the libc's own <time.h>. The libc
* has localtime()/gmtime() with the usual static buffer; the _r forms below
* are thin wrappers that copy that buffer out. Single-threaded programs see
* no difference, and git is built NO_PTHREADS here, so there is no shared
* buffer to race over.
*
* Implementations in montauk-compat.c.
*/
#ifndef _MONTAUK_GIT_TIME_H_
#define _MONTAUK_GIT_TIME_H_
#include_next <time.h>
struct tm *localtime_r(const time_t *timep, struct tm *result);
struct tm *gmtime_r(const time_t *timep, struct tm *result);
char *ctime_r(const time_t *timep, char *buf);
char *asctime_r(const struct tm *tm, char *buf);
#endif /* _MONTAUK_GIT_TIME_H_ */
+79
View File
@@ -0,0 +1,79 @@
/*
* <unistd.h> overlay for the MontaukOS git port.
*
* The Montauk libc's own <unistd.h> is pulled in with #include_next -- this
* file only ADDS the POSIX calls git expects and the libc does not declare.
* Keeping them here rather than in the libc is deliberate: each one below is
* either meaningless on MontaukOS today (there is one user, no groups, no
* symlinks in the ramdisk) or needs kernel support that does not exist, and a
* libc declaration would promise more than the system delivers.
*
* Implementations in montauk-compat.c, each documented there with what it
* actually does.
*/
#ifndef _MONTAUK_GIT_UNISTD_H_
#define _MONTAUK_GIT_UNISTD_H_
#include_next <unistd.h>
#include <sys/types.h>
/* Identity. MontaukOS runs everything as the session user; these report a
* single fixed non-root id so that git's "am I root?" and "does this repo
* belong to me?" checks answer sensibly (yes, it is mine; no, I am not root). */
uid_t getuid(void);
uid_t geteuid(void);
gid_t getgid(void);
gid_t getegid(void);
pid_t getppid(void);
int gethostname(char *name, size_t len);
/* No interval timers and no SIGALRM delivery, so this arms nothing.
* upload-pack's idle timeout is its only caller here, and upload-pack cannot
* run without sockets anyway. */
unsigned int alarm(unsigned int seconds);
/* Symlinks. Neither the ramdisk nor the VFS represents them, so these fail;
* git falls back to writing a regular file with the link text, which is what
* core.symlinks=false already asks for. */
int symlink(const char *target, const char *linkpath);
ssize_t readlink(const char *path, char *buf, size_t bufsiz);
int link(const char *oldpath, const char *newpath);
/* Ownership. No uid/gid model to change anything to. */
int chown(const char *path, uid_t owner, gid_t group);
int lchown(const char *path, uid_t owner, gid_t group);
int fchown(int fd, uid_t owner, gid_t group);
/* Durability. The kernel writes through its block cache on close; there is no
* per-descriptor flush syscall, so these report success rather than failing a
* commit that is in fact on disk. See SYS_FS_SYNC for the whole-filesystem
* flush that shutdown uses. */
int fsync(int fd);
int fdatasync(int fd);
int setsid(void);
/* Process groups. MontaukOS has none: a process is its own group, and a
* program reading the terminal is by definition the foreground one. These
* answer consistently so that progress.c's "am I in the foreground?" test
* (tcgetpgrp(fd) == getpgid(0)) says yes and the progress meter is drawn. */
pid_t getpgid(pid_t pid);
pid_t getpgrp(void);
pid_t tcgetpgrp(int fd);
int setpgid(pid_t pid, pid_t pgid);
/* The variadic exec forms. The libc has execv/execvp (both of which fail:
* MontaukOS spawns rather than execs), so these only repack their arguments
* and hand them over -- they exist because `git help` calls execlp() to run
* the man viewer. */
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
/* Reads a line from the terminal. It CANNOT suppress echo -- see the
* implementation. compat/terminal.c calls it for every interactive prompt on
* a platform without /dev/tty, which is what MontaukOS is. */
char *getpass(const char *prompt);
#endif /* _MONTAUK_GIT_UNISTD_H_ */
@@ -0,0 +1,26 @@
diff --git a/common-main.c b/common-main.c
index 0a22861f1..4653d18b5 100644
--- a/common-main.c
+++ b/common-main.c
@@ -1,6 +1,7 @@
#include "cache.h"
#include "exec-cmd.h"
#include "attr.h"
+#include <montauk-init.h>
/*
* Many parts of Git have subprograms communicate via pipe, expect the
@@ -28,6 +29,13 @@ int main(int argc, const char **argv)
int result;
struct strbuf tmp = STRBUF_INIT;
+ /*
+ * MontaukOS: fill in what the process environment cannot carry
+ * (HOME). Must run before any config is read. See
+ * compat/montauk-compat.c in the port.
+ */
+ montauk_startup();
+
trace2_initialize_clock();
/*
@@ -0,0 +1,30 @@
diff --git a/setup.c b/setup.c
index c3301f5ab..6652c1dff 100644
--- a/setup.c
+++ b/setup.c
@@ -1691,11 +1691,20 @@ const char *resolve_gitdir_gently(const char *suspect, int *return_error_code)
/* if any standard file descriptor is missing open it to /dev/null */
void sanitize_stdfds(void)
{
- int fd = xopen("/dev/null", O_RDWR);
- while (fd < 2)
- fd = xdup(fd);
- if (fd > 2)
- close(fd);
+ /*
+ * MontaukOS: nothing to sanitize, and nothing to sanitize it with.
+ *
+ * This guards against being started with 0/1/2 closed, so that a
+ * later open() cannot land on a standard descriptor and have die()
+ * write its message into a data file. On MontaukOS that cannot
+ * happen: the kernel gives every process the terminal on 0/1/2 (the
+ * libc routes those descriptors to SYS_PUTCHAR/SYS_GETCHAR rather
+ * than to any handle), and a process is spawned, never forked with a
+ * doctored descriptor table.
+ *
+ * The upstream body cannot run in any case -- there is no /dev/null,
+ * so xopen() would die here and take every git invocation with it.
+ */
}
int daemonize(void)