kernel: chroot, #! scripts, rename and mmap as apk needs them
Found by running Alpine's apk on SchOS, one failure at a time: - chroot(2) was a stub returning EPERM. Each process now has a root: where an absolute path starts and where .. stops. Mounts remember whose root they were made under, and a walk leaves a mount by the way it came in, so one filesystem can be mounted twice. - devtmpfs and proc mounted only on /dev and /proc, whatever the target; a chroot got "already mounted". - mmap never reused freed address space, so musl's malloc used up the 256 mapping records while 780 MB were free. A mergeable mapping now goes straight after an anonymous one whose end has room. - unlinkat returned -1 (EPERM) where apk expects ENOENT, and renameat was an old copy of rename: no replacing the target, and v1 dirents written into SchFS. Both now share rename's implementation. - execve runs #! scripts, and says ENOEXEC for what it cannot run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
This commit is contained in:
parent
6f3175d8a5
commit
2cfa3f0bf2
9 changed files with 361 additions and 129 deletions
|
|
@ -1083,23 +1083,26 @@ addr_t sys_truncate(void)
|
|||
// no two directory locks are ever held at once -- each is locked, used, and
|
||||
// unlocked before the next -- and `ip` is unlocked throughout, held alive by
|
||||
// the nlink bump instead.
|
||||
addr_t sys_rename(void)
|
||||
//
|
||||
// renameat() is this with directory fds. It used to be a copy of the old
|
||||
// rename, both faults included, and apk -- which renames every file it
|
||||
// installs and its database -- met them both.
|
||||
static addr_t
|
||||
do_rename(int olddirfd, char *oldpath, int newdirfd, char *newpath)
|
||||
{
|
||||
char *oldpath, *newpath;
|
||||
struct inode *ip, *dp_old, *dp_new, *ip_src, *ip_dst;
|
||||
char oldname[VFS_NAME_MAX + 1], newname[VFS_NAME_MAX + 1];
|
||||
uint off, dstoff;
|
||||
int err = EPERM;
|
||||
if (argstr(0, &oldpath) < 0 || argstr(1, &newpath) < 0) return -EFAULT;
|
||||
begin_op();
|
||||
if (!(ip = namei(oldpath))) { end_op(); return -ENOENT; }
|
||||
if (!(ip = at_namei(olddirfd, oldpath))) { end_op(); return -ENOENT; }
|
||||
ilock(ip);
|
||||
// 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);
|
||||
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) {
|
||||
err = (!dp_old || !dp_new) ? ENOENT : EXDEV;
|
||||
if (dp_old) iput(dp_old);
|
||||
|
|
@ -1155,6 +1158,13 @@ bad_rename:
|
|||
ilock(ip); ip->nlink--; iupdate(ip); iunlockput(ip); end_op(); return -err;
|
||||
}
|
||||
|
||||
addr_t sys_rename(void)
|
||||
{
|
||||
char *oldpath, *newpath;
|
||||
if (argstr(0, &oldpath) < 0 || argstr(1, &newpath) < 0) return -EFAULT;
|
||||
return do_rename(AT_FDCWD, oldpath, AT_FDCWD, newpath);
|
||||
}
|
||||
|
||||
// symlink(2).
|
||||
//
|
||||
// The errno matters more here than anywhere else in this file, because the one
|
||||
|
|
@ -1922,7 +1932,26 @@ addr_t sys_fstatfs(void)
|
|||
}
|
||||
|
||||
// chroot: not supported.
|
||||
addr_t sys_chroot(void) { return -EPERM; }
|
||||
// Make `path` this process's root: where an absolute path starts and where
|
||||
// ".." stops (see namex_from). The working directory is left where it is, as
|
||||
// on Linux; a program that means to stay inside chdir()s there next.
|
||||
addr_t sys_chroot(void)
|
||||
{
|
||||
char *path;
|
||||
struct inode *ip;
|
||||
|
||||
if (argstr(0, &path) < 0) return -EFAULT;
|
||||
begin_op();
|
||||
if ((ip = namei(path)) == 0) { end_op(); return -ENOENT; }
|
||||
ilock(ip);
|
||||
if (ip->type != T_DIR) { iunlockput(ip); end_op(); return -ENOTDIR; }
|
||||
iunlock(ip);
|
||||
if (myproc()->root)
|
||||
iput(myproc()->root);
|
||||
myproc()->root = ip;
|
||||
end_op();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// at-syscalls
|
||||
|
|
@ -1963,86 +1992,50 @@ addr_t sys_mknodat(void)
|
|||
return do_mknod(dirfd, path, (uint32)mode, dev);
|
||||
}
|
||||
|
||||
// The errors unlink_path() gives, for the same reason: apk removes each file
|
||||
// before it writes the new one and expects ENOENT when there was none. Every
|
||||
// failure here was -1, read as EPERM, and every file failed to extract.
|
||||
addr_t sys_unlinkat(void)
|
||||
{
|
||||
int dirfd, flags;
|
||||
int dirfd, flags, err;
|
||||
char *path;
|
||||
struct inode *ip, *dp;
|
||||
char name[VFS_NAME_MAX + 1];
|
||||
uint off;
|
||||
argint(0, &dirfd);
|
||||
if (argstr(1, &path) < 0) return -1;
|
||||
if (argstr(1, &path) < 0) return -EFAULT;
|
||||
argint(2, &flags);
|
||||
begin_op();
|
||||
if (!(dp = at_nameiparent(dirfd, path, name))) { end_op(); return -1; }
|
||||
if (!(dp = at_nameiparent(dirfd, path, name))) { end_op(); return -ENOENT; }
|
||||
ilock(dp);
|
||||
if (namecmp(name, ".") == 0 || namecmp(name, "..") == 0) {
|
||||
iunlockput(dp); end_op(); return -1;
|
||||
}
|
||||
if (!(ip = dirlookup(dp, name, &off))) {
|
||||
iunlockput(dp); end_op(); return -1;
|
||||
}
|
||||
if (namecmp(name, ".") == 0 || namecmp(name, "..") == 0) { err = EINVAL; goto bad; }
|
||||
if (!(ip = dirlookup(dp, name, &off))) { err = ENOENT; goto bad; }
|
||||
ilock(ip);
|
||||
if (ip->nlink < 1) panic("unlinkat: nlink < 1");
|
||||
if (ip->type == T_DIR && !(flags & AT_REMOVEDIR)) {
|
||||
iunlockput(ip); iunlockput(dp); end_op(); return -1;
|
||||
}
|
||||
if (ip->type == T_DIR && dirempty(ip) != 1) {
|
||||
iunlockput(ip); iunlockput(dp); end_op(); return -1;
|
||||
}
|
||||
if (dirunlink(dp, name, off) < 0) {
|
||||
iunlockput(ip); iunlockput(dp); end_op(); return -1;
|
||||
}
|
||||
if (ip->type == T_DIR && !(flags & AT_REMOVEDIR)) { err = EISDIR; goto badip; }
|
||||
if (ip->type != T_DIR && (flags & AT_REMOVEDIR)) { err = ENOTDIR; goto badip; }
|
||||
if (ip->type == T_DIR && dirempty(ip) != 1) { err = ENOTEMPTY; goto badip; }
|
||||
if (dirunlink(dp, name, off) < 0) { err = EIO; goto badip; }
|
||||
if (ip->type == T_DIR) { dp->nlink--; iupdate(dp); }
|
||||
iunlockput(dp); ip->nlink--; iupdate(ip); iunlockput(ip);
|
||||
end_op();
|
||||
return 0;
|
||||
badip:
|
||||
iunlockput(ip);
|
||||
bad:
|
||||
iunlockput(dp);
|
||||
end_op();
|
||||
return -err;
|
||||
}
|
||||
|
||||
addr_t sys_renameat(void)
|
||||
{
|
||||
int olddirfd, newdirfd;
|
||||
char *oldpath, *newpath;
|
||||
argint(0, &olddirfd); argstr(1, &oldpath);
|
||||
argint(2, &newdirfd); argstr(3, &newpath);
|
||||
// Delegate to sys_rename internals — fake the args for rename.
|
||||
// Easiest: place path pointers in registers and call rename logic directly.
|
||||
struct inode *ip, *dp_old, *dp_new, *ip_src;
|
||||
char oldname[VFS_NAME_MAX + 1], newname[VFS_NAME_MAX + 1];
|
||||
uint off;
|
||||
struct dirent de;
|
||||
begin_op();
|
||||
if (!(ip = at_namei(olddirfd, oldpath))) { end_op(); return -ENOENT; }
|
||||
ilock(ip);
|
||||
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 -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 -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 -ENOENT;
|
||||
}
|
||||
iput(ip_src);
|
||||
memset(&de, 0, sizeof(de));
|
||||
if (writei(dp_old, 0, (uint64)&de, off, sizeof(de)) != sizeof(de))
|
||||
panic("renameat: writei");
|
||||
iunlockput(dp_old);
|
||||
ilock(ip); ip->nlink--; iupdate(ip); iunlockput(ip);
|
||||
end_op();
|
||||
return 0;
|
||||
argint(0, &olddirfd);
|
||||
argint(2, &newdirfd);
|
||||
if (argstr(1, &oldpath) < 0 || argstr(3, &newpath) < 0) return -EFAULT;
|
||||
return do_rename(olddirfd, oldpath, newdirfd, newpath);
|
||||
}
|
||||
|
||||
addr_t sys_renameat2(void) { return sys_renameat(); } // ignore flags
|
||||
|
|
@ -2216,17 +2209,13 @@ addr_t sys_mount(void)
|
|||
|
||||
// proc: a synthetic filesystem, so there is no source device to check and
|
||||
// no volume to have found at boot. `mount -t proc proc /proc`.
|
||||
if (strncmp(fstype, "proc", 5) == 0) {
|
||||
procfs_mount();
|
||||
return 0;
|
||||
}
|
||||
if (strncmp(fstype, "proc", 5) == 0)
|
||||
return procfs_mount(target);
|
||||
|
||||
// devtmpfs: the same, and the one init actually needs. /dev used to be
|
||||
// twenty mknod() calls; see the note at the top of kernel/fs/devfs.c.
|
||||
if (strncmp(fstype, "devtmpfs", 9) == 0 || strncmp(fstype, "dev", 4) == 0) {
|
||||
devfs_mount();
|
||||
return 0;
|
||||
}
|
||||
if (strncmp(fstype, "devtmpfs", 9) == 0 || strncmp(fstype, "dev", 4) == 0)
|
||||
return devfs_mount(target);
|
||||
|
||||
// sysfs: the hardware, as Linux's /sys lays it out. kernel/fs/sysfs.c.
|
||||
if (strncmp(fstype, "sysfs", 6) == 0)
|
||||
|
|
|
|||
Loading…
Reference in a new issue