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
101
kernel/fs/fs.c
101
kernel/fs/fs.c
|
|
@ -1675,6 +1675,11 @@ static struct {
|
|||
char path[MAXPATH];
|
||||
char source[MAXPATH];
|
||||
char type[16];
|
||||
// The root of whoever mounted it, 0 for the system's. One filesystem can
|
||||
// be mounted twice -- /dev, and /dev again inside a chroot -- and ".."
|
||||
// out of its root has to know which mount it is leaving. See mount_up().
|
||||
uint ctxdev;
|
||||
uint64 ctxinum;
|
||||
} m[NMOUNT];
|
||||
} mtable;
|
||||
|
||||
|
|
@ -1722,6 +1727,9 @@ mount_add(uint dev, uint64 inum, uint fsdev, uint64 root,
|
|||
mstrcpy(mtable.m[i].path, path, MAXPATH);
|
||||
mstrcpy(mtable.m[i].source, source, MAXPATH);
|
||||
mstrcpy(mtable.m[i].type, type, 16);
|
||||
struct inode *r = myproc() ? myproc()->root : 0;
|
||||
mtable.m[i].ctxdev = r ? r->dev : 0;
|
||||
mtable.m[i].ctxinum = r ? r->inum : 0;
|
||||
release(&mtable.lock);
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -1794,24 +1802,43 @@ mount_down(uint dev, uint64 inum, uint *fsdev, uint64 *root)
|
|||
static int
|
||||
mount_up(uint dev, uint64 inum, uint *pdev, uint64 *pinum)
|
||||
{
|
||||
// Of several mounts of the same filesystem, the one made under the
|
||||
// caller's own root. namex_from() asks this only for a walk that started
|
||||
// inside a mount; one that crossed into it knows which mount it took.
|
||||
struct inode *r = myproc() ? myproc()->root : 0;
|
||||
uint cdev = r ? r->dev : 0;
|
||||
uint64 cinum = r ? r->inum : 0;
|
||||
int found = -1;
|
||||
|
||||
acquire(&mtable.lock);
|
||||
for (int i = 0; i < NMOUNT; i++)
|
||||
if (mtable.m[i].used && mtable.m[i].fsdev == dev &&
|
||||
mtable.m[i].root == inum) {
|
||||
*pdev = mtable.m[i].dev;
|
||||
*pinum = mtable.m[i].inum;
|
||||
release(&mtable.lock);
|
||||
return 1;
|
||||
if (found < 0 ||
|
||||
(mtable.m[i].ctxdev == cdev && mtable.m[i].ctxinum == cinum))
|
||||
found = i;
|
||||
}
|
||||
if (found >= 0) {
|
||||
*pdev = mtable.m[found].dev;
|
||||
*pinum = mtable.m[found].inum;
|
||||
}
|
||||
release(&mtable.lock);
|
||||
return 0;
|
||||
return found >= 0;
|
||||
}
|
||||
|
||||
// Follow every mount stacked on this inode, releasing each one as we go. A
|
||||
// loop rather than a single step because nothing stops a second filesystem
|
||||
// being mounted on the first one's root.
|
||||
// The mounts one lookup has crossed, innermost last, so that ".." leaves each
|
||||
// the way the walk came in. See namex_from().
|
||||
#define NCROSS 16
|
||||
struct crossing {
|
||||
int n;
|
||||
struct { uint rdev; uint64 rinum; uint cdev; uint64 cinum; } c[NCROSS];
|
||||
};
|
||||
|
||||
static struct inode *
|
||||
mount_cross(struct inode *ip)
|
||||
mount_cross_rec(struct inode *ip, struct crossing *x)
|
||||
{
|
||||
uint fsdev;
|
||||
uint64 root;
|
||||
|
|
@ -1820,6 +1847,11 @@ mount_cross(struct inode *ip)
|
|||
while (mount_down(ip->dev, ip->inum, &fsdev, &root)) {
|
||||
if (++hops > NMOUNT)
|
||||
break; /* mounts stacked in a cycle */
|
||||
if (x && x->n < NCROSS) {
|
||||
x->c[x->n].rdev = fsdev; x->c[x->n].rinum = root;
|
||||
x->c[x->n].cdev = ip->dev; x->c[x->n].cinum = ip->inum;
|
||||
x->n++;
|
||||
}
|
||||
struct inode *next = iget(fsdev, root);
|
||||
iput(ip);
|
||||
ip = next;
|
||||
|
|
@ -1827,6 +1859,23 @@ mount_cross(struct inode *ip)
|
|||
return ip;
|
||||
}
|
||||
|
||||
// Where an absolute path starts: the caller's root, or the system's.
|
||||
static struct inode *
|
||||
root_inode(void)
|
||||
{
|
||||
struct proc *p = myproc();
|
||||
if (p && p->root)
|
||||
return idup(p->root);
|
||||
return iget(SCHFS_DEV, schfs_kroot(SCHFS_DEV));
|
||||
}
|
||||
|
||||
static int
|
||||
is_proc_root(struct inode *ip)
|
||||
{
|
||||
struct proc *p = myproc();
|
||||
return p && p->root && ip->dev == p->root->dev && ip->inum == p->root->inum;
|
||||
}
|
||||
|
||||
// Resolves `path` starting from `start` (idup'd internally — caller keeps
|
||||
// ownership of its own reference) rather than always `myproc()->cwd`. This is
|
||||
// what makes a real (non-AT_FDCWD) dirfd argument to the *at() syscalls
|
||||
|
|
@ -1872,22 +1921,43 @@ namex_from(struct inode *start, char *path, int nameiparent, char *name,
|
|||
// Root is a SchFS object, not a v1 inode. ops_for(SCHFS_DEV) then routes
|
||||
// every operation on it through schfs_ops, which is the whole point: the v1
|
||||
// format code in this file is no longer reachable from `/`.
|
||||
ip = (*spath == '/') ? iget(SCHFS_DEV, schfs_kroot(SCHFS_DEV)) : idup(start);
|
||||
struct crossing x;
|
||||
x.n = 0;
|
||||
ip = (*spath == '/') ? root_inode() : idup(start);
|
||||
// The root itself can be a mount point, and a relative walk can start on one.
|
||||
ip = mount_cross(ip);
|
||||
ip = mount_cross_rec(ip, &x);
|
||||
|
||||
while ((spath = skipelem(spath, name, &toolong)) != 0) {
|
||||
// ".." out of a mounted filesystem's root leaves it. The filesystem down
|
||||
// there does not know it is mounted anywhere and would answer with its own
|
||||
// root, which turns the mount into a subtree you can enter and never
|
||||
// leave. Step up to the covered directory first and resolve ".." there.
|
||||
if (name[0] == '.' && name[1] == '.' && name[2] == '\0') {
|
||||
// ".." at a chroot's root is the root: that is what makes it one.
|
||||
if (is_proc_root(ip))
|
||||
continue;
|
||||
// ".." out of a mounted filesystem's root leaves it. The filesystem down
|
||||
// there does not know it is mounted anywhere and would answer with its
|
||||
// own root, which turns the mount into a subtree you can enter and never
|
||||
// leave. Step up to the covered directory first and resolve ".." there
|
||||
// -- the one this walk came in through, if it did, since the same
|
||||
// filesystem can be mounted in more than one place.
|
||||
uint pdev;
|
||||
uint64 pinum;
|
||||
if (mount_up(ip->dev, ip->inum, &pdev, &pinum)) {
|
||||
int up = 0;
|
||||
if (x.n > 0 && x.c[x.n - 1].rdev == ip->dev &&
|
||||
x.c[x.n - 1].rinum == ip->inum) {
|
||||
x.n--;
|
||||
pdev = x.c[x.n].cdev;
|
||||
pinum = x.c[x.n].cinum;
|
||||
up = 1;
|
||||
} else {
|
||||
up = mount_up(ip->dev, ip->inum, &pdev, &pinum);
|
||||
}
|
||||
if (up) {
|
||||
struct inode *cover = iget(pdev, pinum);
|
||||
iput(ip);
|
||||
ip = cover;
|
||||
// The covered directory may itself be the chroot's root -- a chroot
|
||||
// made at a mount point.
|
||||
if (is_proc_root(ip))
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1907,7 +1977,7 @@ namex_from(struct inode *start, char *path, int nameiparent, char *name,
|
|||
goto fail;
|
||||
}
|
||||
iunlock(ip);
|
||||
next = mount_cross(next);
|
||||
next = mount_cross_rec(next, &x);
|
||||
|
||||
ilock(next);
|
||||
if (next->type != T_SYMLINK || (*spath == '\0' && (!follow || nameiparent))) {
|
||||
|
|
@ -1945,7 +2015,8 @@ namex_from(struct inode *start, char *path, int nameiparent, char *name,
|
|||
spath = safe;
|
||||
if (*spath == '/') {
|
||||
iput(ip);
|
||||
ip = mount_cross(iget(SCHFS_DEV, schfs_kroot(SCHFS_DEV)));
|
||||
x.n = 0;
|
||||
ip = mount_cross_rec(root_inode(), &x);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue