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
|
|
@ -686,45 +686,76 @@ addr_t sys_mknod(void)
|
|||
return 0;
|
||||
}
|
||||
|
||||
// link() and rename() answer with real errno values now, and it is worth saying
|
||||
// why that needed doing.
|
||||
//
|
||||
// Every failure in these used to be `return -1`, and -1 is -EPERM by the time
|
||||
// it reaches a program. So linking a file that does not exist reported
|
||||
// "operation not permitted" rather than "no such file" -- not a cosmetic
|
||||
// difference, because a caller distinguishes the two and acts on it.
|
||||
//
|
||||
// zsh is how it surfaced. Its history locking links a lockfile and its history
|
||||
// save renames a temporary over the real file, and on a machine with no
|
||||
// history yet both legitimately fail the first time:
|
||||
//
|
||||
// zsh: locking failed for /root/.zsh_history: operation not permitted
|
||||
// zsh: failed to write history file /root/.zsh_history.new: operation not
|
||||
// permitted
|
||||
//
|
||||
// -- neither of which is what happened. About ninety other `return -1`s remain
|
||||
// in this file. These are corrected because these were the ones found, and
|
||||
// renameat() is corrected alongside rename() because musl's rename() calls it.
|
||||
addr_t sys_link(void)
|
||||
{
|
||||
char name[DIRSIZ], *new, *old;
|
||||
struct inode *dp, *ip;
|
||||
if (argstr(0, &old) < 0 || argstr(1, &new) < 0) return -1;
|
||||
int err = EPERM;
|
||||
if (argstr(0, &old) < 0 || argstr(1, &new) < 0) return -EFAULT;
|
||||
begin_op();
|
||||
if (!(ip = namei(old))) { end_op(); return -1; }
|
||||
if (!(ip = namei(old))) { end_op(); return -ENOENT; }
|
||||
ilock(ip);
|
||||
if (ip->type == T_DIR) { iunlockput(ip); end_op(); return -1; }
|
||||
// The one place EPERM is right: POSIX names it for a directory.
|
||||
if (ip->type == T_DIR) { iunlockput(ip); end_op(); return -EPERM; }
|
||||
ip->nlink++; iupdate(ip); iunlock(ip);
|
||||
if (!(dp = nameiparent(new, name))) goto bad_link;
|
||||
if (!(dp = nameiparent(new, name))) { err = ENOENT; goto bad_link; }
|
||||
ilock(dp);
|
||||
if (dp->dev != ip->dev || dirlink(dp, name, ip->inum) < 0) { iunlockput(dp); goto bad_link; }
|
||||
if (dp->dev != ip->dev) { iunlockput(dp); err = EXDEV; goto bad_link; }
|
||||
if (dirlink(dp, name, ip->inum) < 0) { iunlockput(dp); err = EEXIST; goto bad_link; }
|
||||
iunlockput(dp); iput(ip); end_op();
|
||||
return 0;
|
||||
bad_link:
|
||||
ilock(ip); ip->nlink--; iupdate(ip); iunlockput(ip); end_op();
|
||||
return -1;
|
||||
return -err;
|
||||
}
|
||||
|
||||
// Same correction as link() and rename() above, and in the same path.
|
||||
//
|
||||
// **ENOENT is the one that mattered.** A program that removes a file it is not
|
||||
// sure exists writes `if (unlink(p) < 0 && errno != ENOENT) complain()`, which
|
||||
// is the standard shape -- and against -1/EPERM every such test took the
|
||||
// complaining branch. zsh saving its history is one of them, and it went on
|
||||
// reporting a failure after the history had in fact been written.
|
||||
addr_t sys_unlink(void)
|
||||
{
|
||||
struct inode *ip, *dp;
|
||||
char name[DIRSIZ], *path;
|
||||
uint off;
|
||||
if (argstr(0, &path) < 0) return -1;
|
||||
int err = EPERM;
|
||||
if (argstr(0, &path) < 0) return -EFAULT;
|
||||
begin_op();
|
||||
if (!(dp = nameiparent(path, name))) { end_op(); return -1; }
|
||||
if (!(dp = nameiparent(path, name))) { end_op(); return -ENOENT; }
|
||||
ilock(dp);
|
||||
if (namecmp(name, ".") == 0 || namecmp(name, "..") == 0) goto bad_unlink;
|
||||
if (!(ip = dirlookup(dp, name, &off))) goto bad_unlink;
|
||||
// "." and ".." are refused by name, which is what POSIX asks for.
|
||||
if (namecmp(name, ".") == 0 || namecmp(name, "..") == 0) { err = EINVAL; goto bad_unlink; }
|
||||
if (!(ip = dirlookup(dp, name, &off))) { err = ENOENT; goto bad_unlink; }
|
||||
ilock(ip);
|
||||
if (ip->nlink < 1) panic("unlink: nlink < 1");
|
||||
if (ip->type == T_DIR && dirempty(ip) != 1) { iunlockput(ip); goto bad_unlink; }
|
||||
if (dirunlink(dp, name, off) < 0) { iunlockput(ip); goto bad_unlink; }
|
||||
if (ip->type == T_DIR && dirempty(ip) != 1) { iunlockput(ip); err = ENOTEMPTY; goto bad_unlink; }
|
||||
if (dirunlink(dp, name, off) < 0) { iunlockput(ip); err = EIO; goto bad_unlink; }
|
||||
if (ip->type == T_DIR) { dp->nlink--; iupdate(dp); }
|
||||
iunlockput(dp); ip->nlink--; iupdate(ip); iunlockput(ip);
|
||||
end_op(); return 0;
|
||||
bad_unlink: iunlockput(dp); end_op(); return -1;
|
||||
bad_unlink: iunlockput(dp); end_op(); return -err;
|
||||
}
|
||||
|
||||
addr_t sys_exec(void)
|
||||
|
|
@ -797,25 +828,32 @@ addr_t sys_rename(void)
|
|||
char oldname[DIRSIZ], newname[DIRSIZ];
|
||||
uint off;
|
||||
struct dirent de;
|
||||
if (argstr(0, &oldpath) < 0 || argstr(1, &newpath) < 0) return -1;
|
||||
int err = EPERM;
|
||||
if (argstr(0, &oldpath) < 0 || argstr(1, &newpath) < 0) return -EFAULT;
|
||||
begin_op();
|
||||
if (!(ip = namei(oldpath))) { end_op(); return -1; }
|
||||
if (!(ip = namei(oldpath))) { end_op(); return -ENOENT; }
|
||||
ilock(ip);
|
||||
if (ip->type == T_DIR) { iunlockput(ip); end_op(); return -1; }
|
||||
// Renaming a directory needs the ".." fixup and the loop check that are
|
||||
// not written here. EISDIR names what is missing; EPERM did not.
|
||||
if (ip->type == T_DIR) { iunlockput(ip); end_op(); return -EISDIR; }
|
||||
ip->nlink++; iupdate(ip); iunlock(ip);
|
||||
dp_old = nameiparent(oldpath, oldname);
|
||||
dp_new = nameiparent(newpath, newname);
|
||||
if (!dp_old || !dp_new || dp_old->dev != ip->dev || dp_new->dev != ip->dev) {
|
||||
err = (!dp_old || !dp_new) ? ENOENT : EXDEV;
|
||||
if (dp_old) iput(dp_old);
|
||||
if (dp_new) iput(dp_new);
|
||||
goto bad_rename;
|
||||
}
|
||||
ilock(dp_new);
|
||||
if (dirlink(dp_new, newname, ip->inum) < 0) { iunlockput(dp_new); iput(dp_old); goto bad_rename; }
|
||||
if (dirlink(dp_new, newname, ip->inum) < 0) {
|
||||
iunlockput(dp_new); iput(dp_old); err = EEXIST; goto bad_rename;
|
||||
}
|
||||
iunlockput(dp_new);
|
||||
ilock(dp_old);
|
||||
if (!(ip_src = dirlookup(dp_old, oldname, &off))) {
|
||||
iunlockput(dp_old); ilock(ip); ip->nlink--; iupdate(ip); iunlockput(ip); end_op(); return -1;
|
||||
iunlockput(dp_old); ilock(ip); ip->nlink--; iupdate(ip); iunlockput(ip);
|
||||
end_op(); return -ENOENT;
|
||||
}
|
||||
iput(ip_src);
|
||||
memset(&de, 0, sizeof(de));
|
||||
|
|
@ -824,7 +862,7 @@ addr_t sys_rename(void)
|
|||
ilock(ip); ip->nlink--; iupdate(ip); iunlockput(ip);
|
||||
end_op(); return 0;
|
||||
bad_rename:
|
||||
ilock(ip); ip->nlink--; iupdate(ip); iunlockput(ip); end_op(); return -1;
|
||||
ilock(ip); ip->nlink--; iupdate(ip); iunlockput(ip); end_op(); return -err;
|
||||
}
|
||||
|
||||
addr_t sys_symlink(void)
|
||||
|
|
@ -1493,26 +1531,28 @@ addr_t sys_renameat(void)
|
|||
uint off;
|
||||
struct dirent de;
|
||||
begin_op();
|
||||
if (!(ip = at_namei(olddirfd, oldpath))) { end_op(); return -1; }
|
||||
if (!(ip = at_namei(olddirfd, oldpath))) { end_op(); return -ENOENT; }
|
||||
ilock(ip);
|
||||
if (ip->type == T_DIR) { iunlockput(ip); end_op(); return -1; }
|
||||
if (ip->type == T_DIR) { iunlockput(ip); end_op(); return -EISDIR; }
|
||||
ip->nlink++; iupdate(ip); iunlock(ip);
|
||||
dp_old = at_nameiparent(olddirfd, oldpath, oldname);
|
||||
dp_new = at_nameiparent(newdirfd, newpath, newname);
|
||||
if (!dp_old || !dp_new || dp_old->dev != ip->dev || dp_new->dev != ip->dev) {
|
||||
int e = (!dp_old || !dp_new) ? ENOENT : EXDEV;
|
||||
if (dp_old) iput(dp_old);
|
||||
if (dp_new) iput(dp_new);
|
||||
ilock(ip); ip->nlink--; iupdate(ip); iunlockput(ip); end_op(); return -1;
|
||||
ilock(ip); ip->nlink--; iupdate(ip); iunlockput(ip); end_op(); return -e;
|
||||
}
|
||||
ilock(dp_new);
|
||||
if (dirlink(dp_new, newname, ip->inum) < 0) {
|
||||
iunlockput(dp_new); iput(dp_old);
|
||||
ilock(ip); ip->nlink--; iupdate(ip); iunlockput(ip); end_op(); return -1;
|
||||
ilock(ip); ip->nlink--; iupdate(ip); iunlockput(ip); end_op(); return -EEXIST;
|
||||
}
|
||||
iunlockput(dp_new);
|
||||
ilock(dp_old);
|
||||
if (!(ip_src = dirlookup(dp_old, oldname, &off))) {
|
||||
iunlockput(dp_old); ilock(ip); ip->nlink--; iupdate(ip); iunlockput(ip); end_op(); return -1;
|
||||
iunlockput(dp_old); ilock(ip); ip->nlink--; iupdate(ip); iunlockput(ip);
|
||||
end_op(); return -ENOENT;
|
||||
}
|
||||
iput(ip_src);
|
||||
memset(&de, 0, sizeof(de));
|
||||
|
|
|
|||
Loading…
Reference in a new issue