From 6f3cf996672464c664599b2ccbfb580172f7860a Mon Sep 17 00:00:00 2001 From: Daniel Hammer Date: Sun, 9 Aug 2026 10:45:12 +0200 Subject: [PATCH] feat: ports - git port --- .gitmodules | 3 + Makefile | 15 +- README.md | 4 +- dev-vcs/git/Makefile | 218 +++++ dev-vcs/git/README.md | 143 ++++ dev-vcs/git/compat/README | 57 ++ dev-vcs/git/compat/arpa/inet.h | 18 + dev-vcs/git/compat/grp.h | 25 + dev-vcs/git/compat/montauk-compat.c | 764 ++++++++++++++++++ dev-vcs/git/compat/montauk-init.h | 13 + dev-vcs/git/compat/montauk-ownership.h | 45 ++ dev-vcs/git/compat/montauk-paths.h | 71 ++ dev-vcs/git/compat/netdb.h | 82 ++ dev-vcs/git/compat/netinet/in.h | 64 ++ dev-vcs/git/compat/netinet/tcp.h | 15 + dev-vcs/git/compat/poll.h | 37 + dev-vcs/git/compat/pwd.h | 31 + dev-vcs/git/compat/signal.h | 49 ++ dev-vcs/git/compat/sys/ioctl.h | 24 + dev-vcs/git/compat/sys/resource.h | 63 ++ dev-vcs/git/compat/sys/socket.h | 81 ++ dev-vcs/git/compat/sys/stat.h | 43 + dev-vcs/git/compat/sys/statvfs.h | 47 ++ dev-vcs/git/compat/sys/time.h | 23 + dev-vcs/git/compat/sys/types.h | 21 + dev-vcs/git/compat/sys/un.h | 18 + dev-vcs/git/compat/sys/utsname.h | 23 + dev-vcs/git/compat/syslog.h | 43 + dev-vcs/git/compat/termios.h | 59 ++ dev-vcs/git/compat/time.h | 22 + dev-vcs/git/compat/unistd.h | 79 ++ .../git/patches/0001-montauk-startup.patch | 26 + .../0002-montauk-sanitize-stdfds.patch | 30 + dev-vcs/git/upstream | 1 + 34 files changed, 2255 insertions(+), 2 deletions(-) create mode 100644 dev-vcs/git/Makefile create mode 100644 dev-vcs/git/README.md create mode 100644 dev-vcs/git/compat/README create mode 100644 dev-vcs/git/compat/arpa/inet.h create mode 100644 dev-vcs/git/compat/grp.h create mode 100644 dev-vcs/git/compat/montauk-compat.c create mode 100644 dev-vcs/git/compat/montauk-init.h create mode 100644 dev-vcs/git/compat/montauk-ownership.h create mode 100644 dev-vcs/git/compat/montauk-paths.h create mode 100644 dev-vcs/git/compat/netdb.h create mode 100644 dev-vcs/git/compat/netinet/in.h create mode 100644 dev-vcs/git/compat/netinet/tcp.h create mode 100644 dev-vcs/git/compat/poll.h create mode 100644 dev-vcs/git/compat/pwd.h create mode 100644 dev-vcs/git/compat/signal.h create mode 100644 dev-vcs/git/compat/sys/ioctl.h create mode 100644 dev-vcs/git/compat/sys/resource.h create mode 100644 dev-vcs/git/compat/sys/socket.h create mode 100644 dev-vcs/git/compat/sys/stat.h create mode 100644 dev-vcs/git/compat/sys/statvfs.h create mode 100644 dev-vcs/git/compat/sys/time.h create mode 100644 dev-vcs/git/compat/sys/types.h create mode 100644 dev-vcs/git/compat/sys/un.h create mode 100644 dev-vcs/git/compat/sys/utsname.h create mode 100644 dev-vcs/git/compat/syslog.h create mode 100644 dev-vcs/git/compat/termios.h create mode 100644 dev-vcs/git/compat/time.h create mode 100644 dev-vcs/git/compat/unistd.h create mode 100644 dev-vcs/git/patches/0001-montauk-startup.patch create mode 100644 dev-vcs/git/patches/0002-montauk-sanitize-stdfds.patch create mode 160000 dev-vcs/git/upstream diff --git a/.gitmodules b/.gitmodules index 0b016eb..9769985 100644 --- a/.gitmodules +++ b/.gitmodules @@ -43,3 +43,6 @@ [submodule "www-client/netsurf/upstream"] path = www-client/netsurf/upstream url = https://github.com/netsurf-browser/netsurf.git +[submodule "dev-vcs/git/upstream"] + path = dev-vcs/git/upstream + url = https://github.com/git/git diff --git a/Makefile b/Makefile index 31d3973..f70d4aa 100644 --- a/Makefile +++ b/Makefile @@ -38,11 +38,21 @@ PORTS := \ media-libs/libnsgif \ media-libs/libnsbmp \ www-client/netsurf \ - games-fps/doom + games-fps/doom \ + dev-vcs/git # Bundle directories are keyed by port name, not category path. PORT_NAMES := $(notdir $(PORTS)) +# Ports that install themselves rather than producing an app bundle: command +# line tools, which land in 0:/os/ with no manifest and no launcher icon. +# `install` below delegates to their own install target. +# +# www-client/netsurf is deliberately NOT here: it also has an install target, +# but it drops a resource tree next to the binary and is not something every +# image should carry, so it stays a manual `make -C www-client/netsurf install`. +SELF_INSTALL_PORTS := dev-vcs/git + .PHONY: all clean install $(PORTS) $(PORT_NAMES) all: $(PORTS) @@ -68,6 +78,9 @@ install: all echo "installed port: $$p"; \ fi; \ done + @for p in $(SELF_INSTALL_PORTS); do \ + $(MAKE) -C $$p install; \ + done @echo "ports: installed into $(MONTAUKOS)/programs/bin/apps/" @echo "ports: run 'make ramdisk' (or 'make') in $(MONTAUKOS) to rebuild the image" diff --git a/README.md b/README.md index b88b5ad..758483b 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,8 @@ git clone --recurse-submodules ~/dev/mtkports cd ~/dev/mtkports make # build every port into out/apps// make doom # build one port -make install # copy bundles into MontaukOS/programs/bin/apps/ +make install # copy bundles into MontaukOS/programs/bin/apps/, and + # run the install target of ports that are not bundles ``` Then from the MontaukOS tree: @@ -37,3 +38,4 @@ make ramdisk | libnsbmp | [netsurf-browser/libnsbmp](https://github.com/netsurf-browser/libnsbmp) | BMP/ICO decoder. | | netsurf | [netsurf-browser/netsurf](https://github.com/netsurf-browser/netsurf) | NetSurf web browser - monkey frontend only. **Not** an app bundle. | | doom | [ozkl/doomgeneric](https://github.com/ozkl/doomgeneric) | `doom1.wad` is proprietary and deliberately not bundled. | +| git | [git/git](https://github.com/git/git) | v2.39.5. WIP - installs to `0:/os/git.elf`. Local repositories only. See [dev-vcs/git/README.md](dev-vcs/git/README.md). | diff --git a/dev-vcs/git/Makefile b/dev-vcs/git/Makefile new file mode 100644 index 0000000..1bb7246 --- /dev/null +++ b/dev-vcs/git/Makefile @@ -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 diff --git a/dev-vcs/git/README.md b/dev-vcs/git/README.md new file mode 100644 index 0000000..d911b09 --- /dev/null +++ b/dev-vcs/git/README.md @@ -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/`) 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 you@example.com +``` + +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. diff --git a/dev-vcs/git/compat/README b/dev-vcs/git/compat/README new file mode 100644 index 0000000..90988d0 --- /dev/null +++ b/dev-vcs/git/compat/README @@ -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. diff --git a/dev-vcs/git/compat/arpa/inet.h b/dev-vcs/git/compat/arpa/inet.h new file mode 100644 index 0000000..c5366f3 --- /dev/null +++ b/dev-vcs/git/compat/arpa/inet.h @@ -0,0 +1,18 @@ +/* + * 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 + * . + */ +#ifndef _MONTAUK_GIT_ARPA_INET_H_ +#define _MONTAUK_GIT_ARPA_INET_H_ + +#include + +in_addr_t inet_addr(const char *cp); +char *inet_ntoa(struct in_addr in); + +#endif /* _MONTAUK_GIT_ARPA_INET_H_ */ diff --git a/dev-vcs/git/compat/grp.h b/dev-vcs/git/compat/grp.h new file mode 100644 index 0000000..7ccc684 --- /dev/null +++ b/dev-vcs/git/compat/grp.h @@ -0,0 +1,25 @@ +/* + * 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 + +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_ */ diff --git a/dev-vcs/git/compat/montauk-compat.c b/dev-vcs/git/compat/montauk-compat.c new file mode 100644 index 0000000..457e978 --- /dev/null +++ b/dev-vcs/git/compat/montauk-compat.c @@ -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 +#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; +} diff --git a/dev-vcs/git/compat/montauk-init.h b/dev-vcs/git/compat/montauk-init.h new file mode 100644 index 0000000..3cf7435 --- /dev/null +++ b/dev-vcs/git/compat/montauk-init.h @@ -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_ */ diff --git a/dev-vcs/git/compat/montauk-ownership.h b/dev-vcs/git/compat/montauk-ownership.h new file mode 100644 index 0000000..c9d8d1b --- /dev/null +++ b/dev-vcs/git/compat/montauk-ownership.h @@ -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_ */ diff --git a/dev-vcs/git/compat/montauk-paths.h b/dev-vcs/git/compat/montauk-paths.h new file mode 100644 index 0000000..19b064e --- /dev/null +++ b/dev-vcs/git/compat/montauk-paths.h @@ -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_ */ diff --git a/dev-vcs/git/compat/netdb.h b/dev-vcs/git/compat/netdb.h new file mode 100644 index 0000000..fcf5b76 --- /dev/null +++ b/dev-vcs/git/compat/netdb.h @@ -0,0 +1,82 @@ +/* + * 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 +#include + +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_ */ diff --git a/dev-vcs/git/compat/netinet/in.h b/dev-vcs/git/compat/netinet/in.h new file mode 100644 index 0000000..e27decf --- /dev/null +++ b/dev-vcs/git/compat/netinet/in.h @@ -0,0 +1,64 @@ +/* + * 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 +#include + +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_ */ diff --git a/dev-vcs/git/compat/netinet/tcp.h b/dev-vcs/git/compat/netinet/tcp.h new file mode 100644 index 0000000..0cd24c2 --- /dev/null +++ b/dev-vcs/git/compat/netinet/tcp.h @@ -0,0 +1,15 @@ +/* + * 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_ */ diff --git a/dev-vcs/git/compat/poll.h b/dev-vcs/git/compat/poll.h new file mode 100644 index 0000000..708ad21 --- /dev/null +++ b/dev-vcs/git/compat/poll.h @@ -0,0 +1,37 @@ +/* + * 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 , 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_ */ diff --git a/dev-vcs/git/compat/pwd.h b/dev-vcs/git/compat/pwd.h new file mode 100644 index 0000000..443d945 --- /dev/null +++ b/dev-vcs/git/compat/pwd.h @@ -0,0 +1,31 @@ +/* + * for the MontaukOS git port. + * + * MontaukOS has users (0:/users/, 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 + +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_ */ diff --git a/dev-vcs/git/compat/signal.h b/dev-vcs/git/compat/signal.h new file mode 100644 index 0000000..e39fa63 --- /dev/null +++ b/dev-vcs/git/compat/signal.h @@ -0,0 +1,49 @@ +/* + * 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 + +/* 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_ */ diff --git a/dev-vcs/git/compat/sys/ioctl.h b/dev-vcs/git/compat/sys/ioctl.h new file mode 100644 index 0000000..68450eb --- /dev/null +++ b/dev-vcs/git/compat/sys/ioctl.h @@ -0,0 +1,24 @@ +/* + * 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_ */ diff --git a/dev-vcs/git/compat/sys/resource.h b/dev-vcs/git/compat/sys/resource.h new file mode 100644 index 0000000..cdc549b --- /dev/null +++ b/dev-vcs/git/compat/sys/resource.h @@ -0,0 +1,63 @@ +/* + * 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 +#include + +#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_ */ diff --git a/dev-vcs/git/compat/sys/socket.h b/dev-vcs/git/compat/sys/socket.h new file mode 100644 index 0000000..b9b3d39 --- /dev/null +++ b/dev-vcs/git/compat/sys/socket.h @@ -0,0 +1,81 @@ +/* + * 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 + +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_ */ diff --git a/dev-vcs/git/compat/sys/stat.h b/dev-vcs/git/compat/sys/stat.h new file mode 100644 index 0000000..8661188 --- /dev/null +++ b/dev-vcs/git/compat/sys/stat.h @@ -0,0 +1,43 @@ +/* + * 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 + +#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_ */ diff --git a/dev-vcs/git/compat/sys/statvfs.h b/dev-vcs/git/compat/sys/statvfs.h new file mode 100644 index 0000000..cb0d503 --- /dev/null +++ b/dev-vcs/git/compat/sys/statvfs.h @@ -0,0 +1,47 @@ +/* + * 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 +#include + +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_ */ diff --git a/dev-vcs/git/compat/sys/time.h b/dev-vcs/git/compat/sys/time.h new file mode 100644 index 0000000..f60302c --- /dev/null +++ b/dev-vcs/git/compat/sys/time.h @@ -0,0 +1,23 @@ +/* + * 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 + +#define ITIMER_REAL 0 +#define ITIMER_VIRTUAL 1 +#define ITIMER_PROF 2 + +#endif /* _MONTAUK_GIT_SYS_TIME_H_ */ diff --git a/dev-vcs/git/compat/sys/types.h b/dev-vcs/git/compat/sys/types.h new file mode 100644 index 0000000..2e79a20 --- /dev/null +++ b/dev-vcs/git/compat/sys/types.h @@ -0,0 +1,21 @@ +/* + * 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 + +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_ */ diff --git a/dev-vcs/git/compat/sys/un.h b/dev-vcs/git/compat/sys/un.h new file mode 100644 index 0000000..1055d11 --- /dev/null +++ b/dev-vcs/git/compat/sys/un.h @@ -0,0 +1,18 @@ +/* + * 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 + +struct sockaddr_un { + sa_family_t sun_family; + char sun_path[108]; +}; + +#endif /* _MONTAUK_GIT_SYS_UN_H_ */ diff --git a/dev-vcs/git/compat/sys/utsname.h b/dev-vcs/git/compat/sys/utsname.h new file mode 100644 index 0000000..d83bef1 --- /dev/null +++ b/dev-vcs/git/compat/sys/utsname.h @@ -0,0 +1,23 @@ +/* + * 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_ */ diff --git a/dev-vcs/git/compat/syslog.h b/dev-vcs/git/compat/syslog.h new file mode 100644 index 0000000..db39857 --- /dev/null +++ b/dev-vcs/git/compat/syslog.h @@ -0,0 +1,43 @@ +/* + * 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 + +#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_ */ diff --git a/dev-vcs/git/compat/termios.h b/dev-vcs/git/compat/termios.h new file mode 100644 index 0000000..90d4333 --- /dev/null +++ b/dev-vcs/git/compat/termios.h @@ -0,0 +1,59 @@ +/* + * 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 + +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_ */ diff --git a/dev-vcs/git/compat/time.h b/dev-vcs/git/compat/time.h new file mode 100644 index 0000000..8cb2ee4 --- /dev/null +++ b/dev-vcs/git/compat/time.h @@ -0,0 +1,22 @@ +/* + * overlay for the MontaukOS git port. + * + * Adds the reentrant conversions on top of the libc's own . 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 + +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_ */ diff --git a/dev-vcs/git/compat/unistd.h b/dev-vcs/git/compat/unistd.h new file mode 100644 index 0000000..b9ad956 --- /dev/null +++ b/dev-vcs/git/compat/unistd.h @@ -0,0 +1,79 @@ +/* + * overlay for the MontaukOS git port. + * + * The Montauk libc's own 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 + +#include + +/* 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_ */ diff --git a/dev-vcs/git/patches/0001-montauk-startup.patch b/dev-vcs/git/patches/0001-montauk-startup.patch new file mode 100644 index 0000000..17d13a2 --- /dev/null +++ b/dev-vcs/git/patches/0001-montauk-startup.patch @@ -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 + + /* + * 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(); + + /* diff --git a/dev-vcs/git/patches/0002-montauk-sanitize-stdfds.patch b/dev-vcs/git/patches/0002-montauk-sanitize-stdfds.patch new file mode 100644 index 0000000..768bf82 --- /dev/null +++ b/dev-vcs/git/patches/0002-montauk-sanitize-stdfds.patch @@ -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) diff --git a/dev-vcs/git/upstream b/dev-vcs/git/upstream new file mode 160000 index 0000000..cc7d11c --- /dev/null +++ b/dev-vcs/git/upstream @@ -0,0 +1 @@ +Subproject commit cc7d11c16782041a6bb73e2fb56417b7d4c6d186