errno: -1 is EPERM, and four places were saying it by accident

Ctrl-C killed the shell. consoleread() breaks off the read in progress when
SIGINT arrives and returned -1 to do it -- and -1 is -EPERM by the time it
reaches a program, so every program that handles SIGINT by carrying on saw a
permanent error instead of an interrupted call:

    zsh: error on TTY read: operation not permitted
    schinitd: restarting os.schos.console-shell

and the next Ctrl-C did it to the replacement. EINTR now, which is what a
signal-interrupted read is required to give. Verified: Ctrl-C an interruptible
command, shell survives, zero restarts.

Same mistake, same file family: link, unlink, rename and renameat returned -1
on every error path. Callers branch on errno -- `if (unlink(p) < 0 && errno !=
ENOENT)` is the standard shape, and against EPERM it always complains -- which
is why zsh reported failures for things that had worked. They return ENOENT,
EEXIST, EXDEV, EISDIR, ENOTEMPTY and EFAULT now. About ninety other `return -1`s
remain in sys_fs.c; these four are done because these four were what a program
tripped over. /dev/mem's unknown minors return ENODEV rather than -1 too.

/dev/urandom was a linear congruential generator seeded with a **constant**, so
every boot produced the same bytes in the same order -- not weak, the same. A
key, a nonce, a temporary filename: shared with every SchOS machine that had
ever run. Seeded at boot from RDRAND where the CPU has it, from the cycle
counter and the wall clock where it does not, and re-stirred per byte. Still an
LCG and still not a CSPRNG; what changed is the part that was dangerous rather
than merely poor.

**Not fixed, and recorded as such:** rename() still will not replace an existing
target, so zsh's history save fails on every exit after the first. An attempt to
unlink the target first was reverted -- the directory is an HTree, dirunlink
followed by dirlink of the same name did not behave, and it panicked in
filewrite with the old file already gone. Losing both files is worse than
refusing, so it refuses until the HTree's slot reuse is understood.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
This commit is contained in:
Nexoniarz 2026-09-14 22:12:28 +02:00
commit 75ccf9841c
11 changed files with 330 additions and 42 deletions

View file

@ -505,14 +505,54 @@ UEFI_CFLAGS = --target=x86_64-unknown-windows -ffreestanding -fshort-wchar
# nothing to do, and the only symptom was a boot screen that disagreed with the
# kernel about what the logo is. The kernel half never showed it because those
# objects are built with -MD -MP and pick up header changes on their own.
#
# **The loader is the one thing here that GCC cannot build.** It is a PE/COFF
# binary for the Microsoft ABI, which clang cross-targets and gcc does not, so
# this rule needs clang and lld-link and no other rule does.
#
# That matters more than it reads, because of *where* it sits: `make` stops at
# the first failed rule, and this one runs before the sysroot is staged and the
# filesystem image is built. So on a machine without clang, touching the loader
# does not produce "the loader did not build" -- it produces a new kernel, an
# old userland, and a boot that behaves like a mix of the two, with the real
# cause forty lines up the screen. That happened, and it cost an afternoon of
# looking at the wrong thing.
#
# Hence the check and the message. It is worth the four lines.
$(BUILD)/uefi/BOOTX64.EFI: boot/uefi/main.c boot/uefi/efi.h include/schboot.h \
boot/common/ui.c boot/common/ui.h \
include/logo.h include/theme.h include/font.h
@mkdir -p $(BUILD)/uefi
@command -v clang >/dev/null 2>&1 || { \
echo ""; \
echo " The UEFI loader needs clang and lld-link, and nothing else here does."; \
echo " It is the only PE/COFF binary in the tree -- gcc cannot target it."; \
echo ""; \
echo " On NixOS: nix-shell --run 'NIX_HARDENING_ENABLE=\"\" make'"; \
echo " Elsewhere: install clang and lld, then make again"; \
echo ""; \
test -f $@ && { \
echo " There is an existing loader at $@."; \
echo " To keep it and build everything else: make UEFI_KEEP=1"; \
echo ""; }; \
exit 1; }
clang $(UEFI_CFLAGS) -c boot/uefi/main.c -o $(BUILD)/uefi/main.obj
clang $(UEFI_CFLAGS) -c boot/common/ui.c -o $(BUILD)/uefi/ui.obj
lld-link -subsystem:efi_application -entry:EfiMain -nodefaultlib $(BUILD)/uefi/main.obj $(BUILD)/uefi/ui.obj -out:$@
# Keep the loader that is already built and carry on with the rest.
#
# For the case the message above describes: a machine with no clang, where the
# loader has not meaningfully changed and the thing being worked on is the
# kernel or the userland. It is opt-in and prints what it is doing, because a
# build that quietly ships a stale bootloader is how you spend an evening
# debugging code that is not running.
ifeq ($(UEFI_KEEP),1)
$(BUILD)/uefi/BOOTX64.EFI:
@test -f $@ || { echo "make UEFI_KEEP=1: there is no loader to keep"; exit 1; }
@echo "uefi: keeping the existing loader (UEFI_KEEP=1) -- it may be out of date"
endif
uefi: $(BUILD)/uefi/BOOTX64.EFI
# The EFI System Partition, as a directory. QEMU serves a directory as FAT