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:
parent
eacb13ce35
commit
75ccf9841c
11 changed files with 330 additions and 42 deletions
|
|
@ -250,6 +250,26 @@ existing mechanisms for keeping that true mechanically rather than by intention.
|
|||
|
||||
---
|
||||
|
||||
## `/dev/random` and `/dev/urandom`
|
||||
|
||||
Seeded once at boot, from `RDRAND` where the CPU has it and from the cycle
|
||||
counter mixed with the wall clock where it does not. A verbose boot says which:
|
||||
|
||||
```
|
||||
random: seeded from RDRAND
|
||||
```
|
||||
|
||||
That is worth a section only because of what it replaced. It 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 program asking for a
|
||||
key, a nonce, a temporary filename or a hash seed got one it shared with every
|
||||
other SchOS machine that had ever run.
|
||||
|
||||
**It is still not a CSPRNG and does not pretend to be.** It is an LCG, re-stirred
|
||||
from the cycle counter on each byte so two readers do not continue one
|
||||
sequence. What changed is the property that made it dangerous rather than
|
||||
merely poor.
|
||||
|
||||
## What it does not do
|
||||
|
||||
- **It is a flat list, not a device tree.** There is no parent/child nesting,
|
||||
|
|
|
|||
|
|
@ -189,8 +189,50 @@ selftest, `t` text specimen, `n` the netdev ARP selftest.
|
|||
|
||||
---
|
||||
|
||||
## Errno, and why `return -1` is a bug
|
||||
|
||||
A syscall that fails returns a negative errno. `-1` is therefore **`-EPERM`**,
|
||||
"operation not permitted" — and about ninety error paths in `kernel/sys/sys_fs.c`
|
||||
still say `return -1` because that is what xv6 did, where the convention was
|
||||
just "negative means no".
|
||||
|
||||
It is not a cosmetic difference. Callers branch on `errno`, and the standard
|
||||
shape for removing a file you are not sure about is:
|
||||
|
||||
```c
|
||||
if (unlink(p) < 0 && errno != ENOENT)
|
||||
complain();
|
||||
```
|
||||
|
||||
Against `-1` every such test takes the complaining branch. That is how it was
|
||||
found — zsh, saving its history, reported failures for things that had
|
||||
succeeded:
|
||||
|
||||
```
|
||||
zsh: locking failed for /root/.zsh_history: operation not permitted
|
||||
zsh: failed to write history file /root/.zsh_history.new: operation not permitted
|
||||
```
|
||||
|
||||
`link`, `unlink`, `rename` and `renameat` return real values now — `ENOENT`,
|
||||
`EEXIST`, `EXDEV`, `EISDIR`, `ENOTEMPTY`, `EFAULT`. **The rest of the file does
|
||||
not**, and that is the honest state: these four were corrected because these
|
||||
four were the ones a program tripped over.
|
||||
|
||||
## What it does not do
|
||||
|
||||
- **`rename()` will not replace an existing target.** POSIX says it must, and
|
||||
write-to-a-temporary-then-rename is how every careful program saves a file —
|
||||
so the second save of anything fails with `EEXIST` where it should succeed.
|
||||
It is the reason `zsh: can't rename /root/.zsh_history.new to $HISTFILE`
|
||||
appears on every shell exit after the first.
|
||||
|
||||
An attempt to fix it by unlinking the target first was **reverted**: the
|
||||
directory is an HTree, `dirunlink` followed by `dirlink` of the same name did
|
||||
not behave, and the failure mode was the worst possible one — the old file
|
||||
removed and the new link then refused, losing both. Doing this properly means
|
||||
understanding the HTree's slot reuse, not patching around it.
|
||||
|
||||
|
||||
- **No page cache.** `mmap` of a file reads the whole thing in at map time, and
|
||||
writable `MAP_SHARED` of a file is refused rather than faked. This is the
|
||||
single largest gap in the layer.
|
||||
|
|
|
|||
|
|
@ -238,6 +238,29 @@ immediate wrap, a reverse-video `#` appeared above every prompt.
|
|||
Anything that moves the cursor deliberately -- CR, LF, backspace, an escape
|
||||
sequence -- clears the debt.
|
||||
|
||||
## Ctrl-C used to kill the shell
|
||||
|
||||
Worth its own note, because the cause was one character and the effect was a
|
||||
machine you could not use.
|
||||
|
||||
When Ctrl-C arrives, `consoleread()` discards the partial line and breaks off
|
||||
the read in progress. It returned **`-1`** to do so — and `-1` is `-EPERM`. So a
|
||||
program that handles `SIGINT` by carrying on saw a permanent error instead of
|
||||
an interrupted call, and exited:
|
||||
|
||||
```
|
||||
zsh: error on TTY read: operation not permitted
|
||||
schinitd: restarting os.schos.console-shell
|
||||
```
|
||||
|
||||
and then the next Ctrl-C did the same thing to the replacement. A shell that
|
||||
cannot survive Ctrl-C is a shell nobody can use, and the supervisor faithfully
|
||||
restarting it made it a loop.
|
||||
|
||||
It returns **`EINTR`** now, which is what a signal-interrupted read is required
|
||||
to give: the whole convention — retry, or return to the caller — is built on
|
||||
receiving that and not something else.
|
||||
|
||||
## What it does not do
|
||||
|
||||
- **No scrollback.** The grid is exactly the visible screen. Shift+PageUp does
|
||||
|
|
|
|||
Loading…
Reference in a new issue