SchOS/etc/zsh/zshrc
Nexoniarz 14f0e4d0e2 kernel: processes have an environment now, and zsh is the shell
Porting zsh found four defects in the kernel and one in the filesystem. None
of them were about zsh; all of them had been there the whole time.

**exec threw the environment away.** kernel/fs/exec.c wrote a single zero for
envp with the comment "(no environment)" -- not yet, but ever, for every
process. musl has been passing a perfectly good envp to execve (Linux 59, which
SchOS answers) and it was never read. So $TERM was unset, $PATH was whatever
each program invented, and a variable exported in a shell did not survive the
exec meant to carry it. exec() takes an envp, sys_exec reads argument three,
and schinit manifests grew an Environment key. The userland two-argument
exec(path, argv) stays as a wrapper that passes null explicitly -- the raw stub
is execve now, so a short caller cannot leave %rdx holding a stray pointer.

**Which overflowed the kernel stack.** Three pointer arrays, ~1.8 KB, on a
4096-byte stack, under exec -> loaduvm -> readi -> SchFS -> btree. Pid 1 went
down on the first exec. The canary caught it and named the process, which is
what it is for. They live in one kalloc'd page now.

**waitpid returned a bare -1** for both "no children" and "interrupted". musl
reads [-4095,-1] as -errno, so userspace saw EPERM; zsh printed "wait failed:
operation not permitted" and abandoned the rest of /etc/zsh/zshrc. -ECHILD and
-EINTR now. Third time a bare -1 has cost real time -- on a system with no
permission model, EPERM should be read as "a syscall returned -1".

**NVMA was 64.** musl's allocator takes a mapping per size class and does not
release them eagerly, so compinit ran out of slots and got -1, which is EPERM,
which malloc turns into NULL, which zsh reports as "fatal error: out of memory"
-- on a machine with 1000 MB free. 256 slots, and -ENOMEM.

**And a small write to SchFS costs 58 ms.** 200 writes to one open file take
11.75 s; the same to /dev/null take 0.14 s. That is the design stated at the
top of kmount.c -- one transaction group per operation, and a commit is two
flushes and an uberblock -- not a bug. But compdump writes its digest with
thousands of separate prints, so it takes minutes and looks like a hang.
compinit -D, which skips the dump, finishes in 3.6 s. This one is a decision
rather than a fix, so it is written up and left: /etc/zsh/zshrc keeps compinit
switched off and explains exactly why, with the measurements.

zsh runs as /bin/zsh and is the console shell. ZLE works -- arrow keys recall
history through terminfo, $COLUMNS reads the real 160 because TIOCGWINSZ was
fixed, colour and prompt expansion work, startup is 0.14 s. mksh keeps /bin/sh,
so schinit's no-jobs fallback is a different program from the one that could
have failed.

Also fixed while here: AT_CLKTCK was the literal 100 and is USER_HZ; the
extract rule in four ports could move a source tree inside itself when make
re-ran it, which is how zsh's configure came to write modfile=zsh-5.9/... into
config.modules and fail three steps later.

schfs-check passes. Full writeup in docs/history/kernel.md section 14.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
2026-09-12 23:26:50 +02:00

113 lines
5.1 KiB
Shell
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# /etc/zsh/zshrc — the system-wide interactive zsh configuration for SchOS.
#
# Deliberately short. This sets up the things an interactive shell is useless
# without -- history, completion, and the keys a console sends -- and stops.
# Anything beyond that belongs in a user's own ~/.zshrc, which is the file zsh
# reads after this one.
# ---------------------------------------------------------------------------
# History
# ---------------------------------------------------------------------------
HISTFILE=${HOME:-/root}/.zsh_history
HISTSIZE=2000
SAVEHIST=2000
setopt APPEND_HISTORY INC_APPEND_HISTORY SHARE_HISTORY
setopt HIST_IGNORE_DUPS HIST_IGNORE_SPACE HIST_REDUCE_BLANKS
# ---------------------------------------------------------------------------
# Behaviour
# ---------------------------------------------------------------------------
setopt AUTO_CD # `cd /tmp` is implied by typing `/tmp`
setopt EXTENDED_GLOB
setopt INTERACTIVE_COMMENTS # a # in an interactive line is a comment
setopt NO_BEEP # SchOS has no audio, but it would also scroll
unsetopt FLOW_CONTROL # ^S/^Q are worth more as keys than as flow control
# ---------------------------------------------------------------------------
# Keys
# ---------------------------------------------------------------------------
# Emacs bindings, because that is what every other line editor on the system
# does -- mksh's default and the kernel's own canonical-mode editing both.
bindkey -e
# Home, End, Delete and the arrows, taken from terminfo rather than written
# out as escape sequences. The sequences differ between the SchOS console and
# a serial terminal; the terminal's own description does not.
typeset -A _sch_keys=(
"${terminfo[khome]}" beginning-of-line
"${terminfo[kend]}" end-of-line
"${terminfo[kdch1]}" delete-char
"${terminfo[kich1]}" overwrite-mode
"${terminfo[kcuu1]}" up-line-or-search
"${terminfo[kcud1]}" down-line-or-search
"${terminfo[kcub1]}" backward-char
"${terminfo[kcuf1]}" forward-char
)
for _k _w in "${(@kv)_sch_keys}"; do
[[ -n $_k ]] && bindkey -- "$_k" "$_w"
done
unset _sch_keys _k _w
# A terminal that has just been resized, or that zsh has just taken over, may
# not have told terminfo yet. This puts the keypad in the mode the escape
# sequences above were described for, and takes it back out on exit.
if (( ${+terminfo[smkx]} && ${+terminfo[rmkx]} )); then
autoload -Uz add-zle-hook-widget 2>/dev/null
function _sch_zle_init { echoti smkx }
function _sch_zle_line_finish { echoti rmkx }
zle -N zle-line-init _sch_zle_init
zle -N zle-line-finish _sch_zle_line_finish
fi
# ---------------------------------------------------------------------------
# Completion
# ---------------------------------------------------------------------------
# Completion is NOT initialised here yet, and the reason is a filesystem
# measurement rather than anything to do with zsh.
#
# A small write to SchFS costs about 58 ms, because every operation is its own
# transaction group and a commit is two cache flushes and an uberblock write --
# see the comment at the top of kernel/fs/schfs/kmount.c, which describes the
# trade-off exactly. Measured on this machine, 200 writes:
#
# to one open file on SchFS 11.75 s (58 ms each)
# open + write + close each 21.39 s (107 ms each)
# to /dev/null 0.14 s (0.7 ms each)
#
# compdump writes the digest with a few thousand separate `print` calls, so
# compinit -d takes minutes and looks exactly like a hang. compinit itself is
# fine: with -D, which skips the dump, it finishes in 3.6 seconds.
#
# So this waits for deferred commits. When small writes stop costing a disk
# barrier each, delete this comment and uncomment the three lines below.
#
# autoload -Uz compinit
# compinit -u -d ${HOME:-/root}/.zcompdump
#
# Until then, completion is available on demand in a shell you do not mind
# waiting three seconds for:
#
# autoload -Uz compinit && compinit -D -u
#
# -u is not optional whenever this is turned back on. Without it compinit runs
# compaudit, decides the function directories are "insecure" because SchOS has
# no ownership model to check, and *prompts* for an answer on a shell that is
# still starting up -- which is a real hang, unlike the one above.
# These take effect as soon as compinit is turned on above; a zstyle with no
# completion system to read it is inert, not an error.
zstyle ':completion:*' menu select
zstyle ':completion:*' matcher-list 'm:{a-z}={A-Za-z}' # case-insensitive
zstyle ':completion:*' list-colors ''
zstyle ':completion:*:descriptions' format '%F{244}%d%f'
# ---------------------------------------------------------------------------
# Prompt
# ---------------------------------------------------------------------------
# The accent violet from include/theme.h, which is 99 in the 256-colour cube
# and falls back to magenta on the 8-colour console. The last-status marker is
# the only thing here that ever turns red -- same rule as the rest of the
# system: normal states are calm, only failure is loud.
setopt PROMPT_SUBST
PROMPT='%F{magenta}%B%n@schos%b%f %F{cyan}%~%f %(?.%F{green}❯.%F{red}❯)%f '
RPROMPT=''