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
|
|
@ -339,8 +339,8 @@ int procwrite(struct inode*, uint64, char*, int);
|
||||||
// namespace, the other the content.
|
// namespace, the other the content.
|
||||||
int proc_gen_global(int id, char *buf, int cap);
|
int proc_gen_global(int id, char *buf, int cap);
|
||||||
int proc_write_sysrq(int user_src, uint64 src, uint n);
|
int proc_write_sysrq(int user_src, uint64 src, uint n);
|
||||||
void procfs_mount(void);
|
int procfs_mount(const char *target);
|
||||||
void devfs_mount(void);
|
int devfs_mount(const char *target);
|
||||||
|
|
||||||
// input.c -- raw evdev event devices
|
// input.c -- raw evdev event devices
|
||||||
void inputinit(void);
|
void inputinit(void);
|
||||||
|
|
|
||||||
|
|
@ -313,6 +313,10 @@ struct proc {
|
||||||
// rt_sigreturn. See "The FPU" above scheduler() in kernel/proc/proc.c.
|
// rt_sigreturn. See "The FPU" above scheduler() in kernel/proc/proc.c.
|
||||||
uint8 fpu[512] __attribute__((aligned(16)));
|
uint8 fpu[512] __attribute__((aligned(16)));
|
||||||
uint8 sigfpu[512] __attribute__((aligned(16)));
|
uint8 sigfpu[512] __attribute__((aligned(16)));
|
||||||
|
|
||||||
|
// chroot(2): where an absolute path starts, and where ".." stops. 0 is the
|
||||||
|
// system's root. Inherited across fork, held as a reference like cwd.
|
||||||
|
struct inode *root;
|
||||||
};
|
};
|
||||||
#define FD_CLOEXEC 1
|
#define FD_CLOEXEC 1
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -318,18 +318,19 @@ const struct fs_ops devfs_ops = {
|
||||||
.getdents = devfs_getdents,
|
.getdents = devfs_getdents,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Graft it over /dev. Called by init, which has made the directory.
|
// Graft it over `target` -- /dev from init, and again wherever a chroot
|
||||||
void
|
// wants one. It was always /dev whatever was asked for, so a second mount
|
||||||
devfs_mount(void)
|
// failed as "already mounted" and a chroot had no devices.
|
||||||
|
int
|
||||||
|
devfs_mount(const char *target)
|
||||||
{
|
{
|
||||||
struct inode *dp = namei("/dev");
|
struct inode *dp = namei((char *)target);
|
||||||
|
int r;
|
||||||
|
|
||||||
if (dp == 0) {
|
if (dp == 0)
|
||||||
cprintf("devfs: /dev does not exist; not mounted\n");
|
return -ENOENT;
|
||||||
return;
|
r = mount_add(dp->dev, dp->inum, DEVFS_DEV, DEVFS_ROOT, (char *)target, "dev",
|
||||||
}
|
"devtmpfs");
|
||||||
if (mount_add(dp->dev, dp->inum, DEVFS_DEV, DEVFS_ROOT, "/dev", "dev",
|
|
||||||
"devtmpfs") < 0)
|
|
||||||
cprintf("devfs: /dev already has something mounted on it\n");
|
|
||||||
iput(dp);
|
iput(dp);
|
||||||
|
return r < 0 ? -EBUSY : 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
143
kernel/fs/exec.c
143
kernel/fs/exec.c
|
|
@ -32,16 +32,31 @@ extern int linux_syscall_map_size;
|
||||||
// rewrite the array between a check and a use, and a pointer swapped for a
|
// rewrite the array between a check and a use, and a pointer swapped for a
|
||||||
// kernel address afterwards would have copied kernel memory into the new
|
// kernel address afterwards would have copied kernel memory into the new
|
||||||
// program's stack.
|
// program's stack.
|
||||||
|
//
|
||||||
|
// `pre` goes in front, always from the kernel, and `skip` drops that many of
|
||||||
|
// the array's own: a #! script runs as `interp [arg] script argv[1]...`, which
|
||||||
|
// is the caller's array less its argv[0] behind three strings of ours.
|
||||||
struct argsrc {
|
struct argsrc {
|
||||||
int user;
|
int user;
|
||||||
char **k;
|
char **k;
|
||||||
addr_t u;
|
addr_t u;
|
||||||
|
char *pre[3];
|
||||||
|
int npre;
|
||||||
|
uint64 skip;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Pointer i of the array, 0 past its end; -1 on a fault.
|
// Pointer i of the array, 0 past its end; -1 on a fault. *kern says which
|
||||||
|
// side of the boundary it points to.
|
||||||
static int
|
static int
|
||||||
arg_ptr(struct argsrc *s, uint64 i, addr_t *p)
|
arg_ptr(struct argsrc *s, uint64 i, addr_t *p, int *kern)
|
||||||
{
|
{
|
||||||
|
if (i < (uint64)s->npre) {
|
||||||
|
*p = (addr_t)s->pre[i];
|
||||||
|
*kern = 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
i = i - s->npre + s->skip;
|
||||||
|
*kern = !s->user;
|
||||||
if (s->user)
|
if (s->user)
|
||||||
return s->u ? fetchaddr(s->u + i * sizeof(addr_t), p) : (*p = 0, 0);
|
return s->u ? fetchaddr(s->u + i * sizeof(addr_t), p) : (*p = 0, 0);
|
||||||
*p = s->k ? (addr_t)s->k[i] : 0;
|
*p = s->k ? (addr_t)s->k[i] : 0;
|
||||||
|
|
@ -49,9 +64,9 @@ arg_ptr(struct argsrc *s, uint64 i, addr_t *p)
|
||||||
}
|
}
|
||||||
|
|
||||||
static long
|
static long
|
||||||
arg_len(struct argsrc *s, addr_t p)
|
arg_len(addr_t p, int kern)
|
||||||
{
|
{
|
||||||
return s->user ? strlen_user(p) : (long)strlen((char *)p);
|
return kern ? (long)strlen((char *)p) : strlen_user(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Count an array and its bytes, against Linux's limits: ARG_MAX in all,
|
// Count an array and its bytes, against Linux's limits: ARG_MAX in all,
|
||||||
|
|
@ -61,13 +76,14 @@ arg_measure(struct argsrc *s, uint64 *count, uint64 *bytes, uint64 already)
|
||||||
{
|
{
|
||||||
for (uint64 i = 0; ; i++) {
|
for (uint64 i = 0; ; i++) {
|
||||||
addr_t p;
|
addr_t p;
|
||||||
if (arg_ptr(s, i, &p) < 0)
|
int kern;
|
||||||
|
if (arg_ptr(s, i, &p, &kern) < 0)
|
||||||
return -EFAULT;
|
return -EFAULT;
|
||||||
if (p == 0) {
|
if (p == 0) {
|
||||||
*count = i;
|
*count = i;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
long n = arg_len(s, p);
|
long n = arg_len(p, kern);
|
||||||
if (n < 0)
|
if (n < 0)
|
||||||
return n == -EFAULT ? -EFAULT : -E2BIG;
|
return n == -EFAULT ? -EFAULT : -E2BIG;
|
||||||
if (n + 1 > ARG_STRLEN_MAX)
|
if (n + 1 > ARG_STRLEN_MAX)
|
||||||
|
|
@ -113,16 +129,17 @@ arg_copy(struct argsrc *s, uint64 count, pml4e_t *pgdir, addr_t *at,
|
||||||
{
|
{
|
||||||
for (uint64 i = 0; i < count; i++) {
|
for (uint64 i = 0; i < count; i++) {
|
||||||
addr_t p;
|
addr_t p;
|
||||||
if (arg_ptr(s, i, &p) < 0 || p == 0)
|
int kern;
|
||||||
|
if (arg_ptr(s, i, &p, &kern) < 0 || p == 0)
|
||||||
return -1;
|
return -1;
|
||||||
long n = arg_len(s, p);
|
long n = arg_len(p, kern);
|
||||||
if (n < 0 || *at + (addr_t)n + 1 > end)
|
if (n < 0 || *at + (addr_t)n + 1 > end)
|
||||||
return -1;
|
return -1;
|
||||||
if (tbl_put(t, *at) < 0)
|
if (tbl_put(t, *at) < 0)
|
||||||
return -1;
|
return -1;
|
||||||
for (long done = 0; done < n; ) {
|
for (long done = 0; done < n; ) {
|
||||||
long c = n - done > PGSIZE ? PGSIZE : n - done;
|
long c = n - done > PGSIZE ? PGSIZE : n - done;
|
||||||
if (s->user) {
|
if (!kern) {
|
||||||
if (copy_from_user(bounce, p + done, c) < 0)
|
if (copy_from_user(bounce, p + done, c) < 0)
|
||||||
return -1;
|
return -1;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -141,6 +158,7 @@ arg_copy(struct argsrc *s, uint64 count, pml4e_t *pgdir, addr_t *at,
|
||||||
}
|
}
|
||||||
|
|
||||||
static int do_exec(char *path, struct argsrc *av, struct argsrc *ev);
|
static int do_exec(char *path, struct argsrc *av, struct argsrc *ev);
|
||||||
|
static int exec_elf(char *path, struct argsrc *av, struct argsrc *ev);
|
||||||
|
|
||||||
// --- Dynamic linking ---------------------------------------------------------
|
// --- Dynamic linking ---------------------------------------------------------
|
||||||
//
|
//
|
||||||
|
|
@ -252,8 +270,98 @@ exec_user(char *path, addr_t uargv, addr_t uenvp)
|
||||||
return do_exec(path, &a, &e);
|
return do_exec(path, &a, &e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- #! scripts --------------------------------------------------------------
|
||||||
|
//
|
||||||
|
// A file that starts with #! names the program that runs it, and one optional
|
||||||
|
// argument, on its first line. Without this every script had to be started
|
||||||
|
// as `sh script`; apk runs its packages' triggers and install scripts with
|
||||||
|
// execve() directly, and each one failed with "Operation not permitted".
|
||||||
|
//
|
||||||
|
// If `path` is a script, puts "interp\0arg\0" in `line` (a page) and returns
|
||||||
|
// 1, with *argp pointing at the argument or 0. 0 if it is not one; <0 errno.
|
||||||
|
static int
|
||||||
|
script_interp(char *path, char *line, char **argp)
|
||||||
|
{
|
||||||
|
struct inode *ip;
|
||||||
|
int err, n;
|
||||||
|
|
||||||
|
begin_op();
|
||||||
|
if ((ip = nameifollow(0, path, &err)) == 0) {
|
||||||
|
end_op();
|
||||||
|
return -err;
|
||||||
|
}
|
||||||
|
ilock(ip);
|
||||||
|
n = readi(ip, 0, (uint64)line, 0, 256);
|
||||||
|
iunlockput(ip);
|
||||||
|
end_op();
|
||||||
|
if (n < 2 || line[0] != '#' || line[1] != '!')
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
// Linux's rules: the line ends at a newline or after 255 bytes; blanks
|
||||||
|
// separate the interpreter from the argument, and everything after the
|
||||||
|
// first blank run is one argument, spaces and all.
|
||||||
|
line[n < 256 ? n : 255] = 0;
|
||||||
|
char *nl = line;
|
||||||
|
while (*nl && *nl != '\n')
|
||||||
|
nl++;
|
||||||
|
*nl = 0;
|
||||||
|
char *q = line + 2;
|
||||||
|
while (*q == ' ' || *q == '\t')
|
||||||
|
q++;
|
||||||
|
if (*q == 0)
|
||||||
|
return -ENOEXEC;
|
||||||
|
char *ie = q;
|
||||||
|
while (*ie && *ie != ' ' && *ie != '\t')
|
||||||
|
ie++;
|
||||||
|
char *a = ie;
|
||||||
|
while (*a == ' ' || *a == '\t')
|
||||||
|
a++;
|
||||||
|
char *ae = a + strlen(a);
|
||||||
|
while (ae > a && (ae[-1] == ' ' || ae[-1] == '\t' || ae[-1] == '\r'))
|
||||||
|
ae--;
|
||||||
|
*ae = 0;
|
||||||
|
*ie = 0;
|
||||||
|
memmove(line, q, ie - q + 1);
|
||||||
|
if (*a) {
|
||||||
|
int il = ie - q + 1;
|
||||||
|
memmove(line + il, a, ae - a + 1);
|
||||||
|
*argp = line + il;
|
||||||
|
} else {
|
||||||
|
*argp = 0;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
do_exec(char *path, struct argsrc *av, struct argsrc *ev)
|
do_exec(char *path, struct argsrc *av, struct argsrc *ev)
|
||||||
|
{
|
||||||
|
char *page = kalloc();
|
||||||
|
if (!page)
|
||||||
|
return -ENOMEM;
|
||||||
|
char *line = page, *spath = page + 2048, *arg;
|
||||||
|
int r = script_interp(path, line, &arg);
|
||||||
|
if (r <= 0) {
|
||||||
|
kfree(page);
|
||||||
|
return r < 0 ? r : exec_elf(path, av, ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
// One level: an interpreter that is itself a script is ENOEXEC, which
|
||||||
|
// exec_elf() says by itself when it finds no ELF header there.
|
||||||
|
struct argsrc a = *av;
|
||||||
|
safestrcpy(spath, path, 2048);
|
||||||
|
a.npre = 0;
|
||||||
|
a.pre[a.npre++] = line;
|
||||||
|
if (arg)
|
||||||
|
a.pre[a.npre++] = arg;
|
||||||
|
a.pre[a.npre++] = spath;
|
||||||
|
a.skip = av->skip + 1;
|
||||||
|
r = exec_elf(line, &a, ev);
|
||||||
|
kfree(page);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
exec_elf(char *path, struct argsrc *av, struct argsrc *ev)
|
||||||
{
|
{
|
||||||
uint64 _t0 = rdtsc();
|
uint64 _t0 = rdtsc();
|
||||||
fsstat.exec_calls++;
|
fsstat.exec_calls++;
|
||||||
|
|
@ -293,6 +401,7 @@ do_exec(char *path, struct argsrc *av, struct argsrc *ev)
|
||||||
|
|
||||||
begin_op();
|
begin_op();
|
||||||
int in_op = 1; // the error path closes the transaction only once
|
int in_op = 1; // the error path closes the transaction only once
|
||||||
|
int ret = -1;
|
||||||
|
|
||||||
int err;
|
int err;
|
||||||
if((ip = nameifollow(0, path, &err)) == 0){
|
if((ip = nameifollow(0, path, &err)) == 0){
|
||||||
|
|
@ -305,11 +414,17 @@ do_exec(char *path, struct argsrc *av, struct argsrc *ev)
|
||||||
ilock(ip);
|
ilock(ip);
|
||||||
pgdir = 0;
|
pgdir = 0;
|
||||||
|
|
||||||
if(readi(ip, 0, (uint64)&elf, 0, sizeof(elf)) != sizeof(elf))
|
if(readi(ip, 0, (uint64)&elf, 0, sizeof(elf)) != sizeof(elf)){
|
||||||
|
ret = -ENOEXEC; // too short to be anything we can run
|
||||||
goto bad;
|
goto bad;
|
||||||
|
}
|
||||||
|
|
||||||
if(elf.magic != ELF_MAGIC)
|
// Neither ELF nor, by now, a script. ENOEXEC is what tells musl's
|
||||||
|
// execvp() to try it with /bin/sh.
|
||||||
|
if(elf.magic != ELF_MAGIC){
|
||||||
|
ret = -ENOEXEC;
|
||||||
goto bad;
|
goto bad;
|
||||||
|
}
|
||||||
|
|
||||||
if((pgdir = setupkvm()) == 0)
|
if((pgdir = setupkvm()) == 0)
|
||||||
goto bad;
|
goto bad;
|
||||||
|
|
@ -327,8 +442,10 @@ do_exec(char *path, struct argsrc *av, struct argsrc *ev)
|
||||||
if (interp[0]) {
|
if (interp[0]) {
|
||||||
struct elfhdr ielf;
|
struct elfhdr ielf;
|
||||||
uint64 ihdr;
|
uint64 ihdr;
|
||||||
if ((ip = nameifollow(0, interp, &err)) == 0)
|
if ((ip = nameifollow(0, interp, &err)) == 0) {
|
||||||
|
ret = -err;
|
||||||
goto bad;
|
goto bad;
|
||||||
|
}
|
||||||
ilock(ip);
|
ilock(ip);
|
||||||
if (readi(ip, 0, (uint64)&ielf, 0, sizeof(ielf)) != sizeof(ielf) ||
|
if (readi(ip, 0, (uint64)&ielf, 0, sizeof(ielf)) != sizeof(ielf) ||
|
||||||
ielf.magic != ELF_MAGIC || ielf.type != ET_DYN)
|
ielf.magic != ELF_MAGIC || ielf.type != ET_DYN)
|
||||||
|
|
@ -529,5 +646,5 @@ do_exec(char *path, struct argsrc *av, struct argsrc *ev)
|
||||||
iunlockput(ip);
|
iunlockput(ip);
|
||||||
if(in_op)
|
if(in_op)
|
||||||
end_op();
|
end_op();
|
||||||
return -1;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
101
kernel/fs/fs.c
101
kernel/fs/fs.c
|
|
@ -1675,6 +1675,11 @@ static struct {
|
||||||
char path[MAXPATH];
|
char path[MAXPATH];
|
||||||
char source[MAXPATH];
|
char source[MAXPATH];
|
||||||
char type[16];
|
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];
|
} m[NMOUNT];
|
||||||
} mtable;
|
} 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].path, path, MAXPATH);
|
||||||
mstrcpy(mtable.m[i].source, source, MAXPATH);
|
mstrcpy(mtable.m[i].source, source, MAXPATH);
|
||||||
mstrcpy(mtable.m[i].type, type, 16);
|
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);
|
release(&mtable.lock);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
@ -1794,24 +1802,43 @@ mount_down(uint dev, uint64 inum, uint *fsdev, uint64 *root)
|
||||||
static int
|
static int
|
||||||
mount_up(uint dev, uint64 inum, uint *pdev, uint64 *pinum)
|
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);
|
acquire(&mtable.lock);
|
||||||
for (int i = 0; i < NMOUNT; i++)
|
for (int i = 0; i < NMOUNT; i++)
|
||||||
if (mtable.m[i].used && mtable.m[i].fsdev == dev &&
|
if (mtable.m[i].used && mtable.m[i].fsdev == dev &&
|
||||||
mtable.m[i].root == inum) {
|
mtable.m[i].root == inum) {
|
||||||
*pdev = mtable.m[i].dev;
|
if (found < 0 ||
|
||||||
*pinum = mtable.m[i].inum;
|
(mtable.m[i].ctxdev == cdev && mtable.m[i].ctxinum == cinum))
|
||||||
release(&mtable.lock);
|
found = i;
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
|
if (found >= 0) {
|
||||||
|
*pdev = mtable.m[found].dev;
|
||||||
|
*pinum = mtable.m[found].inum;
|
||||||
|
}
|
||||||
release(&mtable.lock);
|
release(&mtable.lock);
|
||||||
return 0;
|
return found >= 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Follow every mount stacked on this inode, releasing each one as we go. A
|
// 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
|
// loop rather than a single step because nothing stops a second filesystem
|
||||||
// being mounted on the first one's root.
|
// 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 *
|
static struct inode *
|
||||||
mount_cross(struct inode *ip)
|
mount_cross_rec(struct inode *ip, struct crossing *x)
|
||||||
{
|
{
|
||||||
uint fsdev;
|
uint fsdev;
|
||||||
uint64 root;
|
uint64 root;
|
||||||
|
|
@ -1820,6 +1847,11 @@ mount_cross(struct inode *ip)
|
||||||
while (mount_down(ip->dev, ip->inum, &fsdev, &root)) {
|
while (mount_down(ip->dev, ip->inum, &fsdev, &root)) {
|
||||||
if (++hops > NMOUNT)
|
if (++hops > NMOUNT)
|
||||||
break; /* mounts stacked in a cycle */
|
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);
|
struct inode *next = iget(fsdev, root);
|
||||||
iput(ip);
|
iput(ip);
|
||||||
ip = next;
|
ip = next;
|
||||||
|
|
@ -1827,6 +1859,23 @@ mount_cross(struct inode *ip)
|
||||||
return 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
|
// Resolves `path` starting from `start` (idup'd internally — caller keeps
|
||||||
// ownership of its own reference) rather than always `myproc()->cwd`. This is
|
// 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
|
// 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
|
// 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
|
// every operation on it through schfs_ops, which is the whole point: the v1
|
||||||
// format code in this file is no longer reachable from `/`.
|
// 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.
|
// 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) {
|
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') {
|
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;
|
uint pdev;
|
||||||
uint64 pinum;
|
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);
|
struct inode *cover = iget(pdev, pinum);
|
||||||
iput(ip);
|
iput(ip);
|
||||||
ip = cover;
|
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;
|
goto fail;
|
||||||
}
|
}
|
||||||
iunlock(ip);
|
iunlock(ip);
|
||||||
next = mount_cross(next);
|
next = mount_cross_rec(next, &x);
|
||||||
|
|
||||||
ilock(next);
|
ilock(next);
|
||||||
if (next->type != T_SYMLINK || (*spath == '\0' && (!follow || nameiparent))) {
|
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;
|
spath = safe;
|
||||||
if (*spath == '/') {
|
if (*spath == '/') {
|
||||||
iput(ip);
|
iput(ip);
|
||||||
ip = mount_cross(iget(SCHFS_DEV, schfs_kroot(SCHFS_DEV)));
|
x.n = 0;
|
||||||
|
ip = mount_cross_rec(root_inode(), &x);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -615,16 +615,16 @@ const struct fs_ops procfs_ops = {
|
||||||
// so they are unreachable and harmless. Removing them from init is a separate,
|
// so they are unreachable and harmless. Removing them from init is a separate,
|
||||||
// later cleanup -- doing both at once would make a boot failure ambiguous
|
// later cleanup -- doing both at once would make a boot failure ambiguous
|
||||||
// between "the mount is wrong" and "the nodes are missing".
|
// between "the mount is wrong" and "the nodes are missing".
|
||||||
void
|
int
|
||||||
procfs_mount(void)
|
procfs_mount(const char *target)
|
||||||
{
|
{
|
||||||
struct inode *dp = namei("/proc");
|
struct inode *dp = namei((char *)target);
|
||||||
|
int r;
|
||||||
|
|
||||||
if (dp == 0) {
|
if (dp == 0)
|
||||||
cprintf("procfs: /proc does not exist; not mounted\n");
|
return -ENOENT;
|
||||||
return;
|
r = mount_add(dp->dev, dp->inum, PROCFS_DEV, PROCFS_ROOT, (char *)target,
|
||||||
}
|
"proc", "proc");
|
||||||
if (mount_add(dp->dev, dp->inum, PROCFS_DEV, PROCFS_ROOT, "/proc", "proc", "proc") < 0)
|
|
||||||
cprintf("procfs: /proc already has something mounted on it\n");
|
|
||||||
iput(dp);
|
iput(dp);
|
||||||
|
return r < 0 ? -EBUSY : 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -503,6 +503,7 @@ fork(void)
|
||||||
np->gid = myproc()->gid; np->egid = myproc()->egid; np->sgid = myproc()->sgid;
|
np->gid = myproc()->gid; np->egid = myproc()->egid; np->sgid = myproc()->sgid;
|
||||||
|
|
||||||
np->cwd = idup(myproc()->cwd);
|
np->cwd = idup(myproc()->cwd);
|
||||||
|
np->root = myproc()->root ? idup(myproc()->root) : 0;
|
||||||
memmove(np->cwd_path, myproc()->cwd_path, sizeof(myproc()->cwd_path));
|
memmove(np->cwd_path, myproc()->cwd_path, sizeof(myproc()->cwd_path));
|
||||||
np->fs_base = myproc()->fs_base;
|
np->fs_base = myproc()->fs_base;
|
||||||
|
|
||||||
|
|
@ -662,8 +663,11 @@ exit(int status)
|
||||||
|
|
||||||
begin_op();
|
begin_op();
|
||||||
iput(myproc()->cwd);
|
iput(myproc()->cwd);
|
||||||
|
if (myproc()->root)
|
||||||
|
iput(myproc()->root);
|
||||||
end_op();
|
end_op();
|
||||||
myproc()->cwd = 0;
|
myproc()->cwd = 0;
|
||||||
|
myproc()->root = 0;
|
||||||
|
|
||||||
acquire(&ptable.lock);
|
acquire(&ptable.lock);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1083,23 +1083,26 @@ addr_t sys_truncate(void)
|
||||||
// no two directory locks are ever held at once -- each is locked, used, and
|
// 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
|
// unlocked before the next -- and `ip` is unlocked throughout, held alive by
|
||||||
// the nlink bump instead.
|
// 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;
|
struct inode *ip, *dp_old, *dp_new, *ip_src, *ip_dst;
|
||||||
char oldname[VFS_NAME_MAX + 1], newname[VFS_NAME_MAX + 1];
|
char oldname[VFS_NAME_MAX + 1], newname[VFS_NAME_MAX + 1];
|
||||||
uint off, dstoff;
|
uint off, dstoff;
|
||||||
int err = EPERM;
|
int err = EPERM;
|
||||||
if (argstr(0, &oldpath) < 0 || argstr(1, &newpath) < 0) return -EFAULT;
|
|
||||||
begin_op();
|
begin_op();
|
||||||
if (!(ip = namei(oldpath))) { end_op(); return -ENOENT; }
|
if (!(ip = at_namei(olddirfd, oldpath))) { end_op(); return -ENOENT; }
|
||||||
ilock(ip);
|
ilock(ip);
|
||||||
// Renaming a directory needs the ".." fixup and the loop check that are
|
// Renaming a directory needs the ".." fixup and the loop check that are
|
||||||
// not written here. EISDIR names what is missing; EPERM did not.
|
// not written here. EISDIR names what is missing; EPERM did not.
|
||||||
if (ip->type == T_DIR) { iunlockput(ip); end_op(); return -EISDIR; }
|
if (ip->type == T_DIR) { iunlockput(ip); end_op(); return -EISDIR; }
|
||||||
ip->nlink++; iupdate(ip); iunlock(ip);
|
ip->nlink++; iupdate(ip); iunlock(ip);
|
||||||
dp_old = nameiparent(oldpath, oldname);
|
dp_old = at_nameiparent(olddirfd, oldpath, oldname);
|
||||||
dp_new = nameiparent(newpath, newname);
|
dp_new = at_nameiparent(newdirfd, newpath, newname);
|
||||||
if (!dp_old || !dp_new || dp_old->dev != ip->dev || dp_new->dev != ip->dev) {
|
if (!dp_old || !dp_new || dp_old->dev != ip->dev || dp_new->dev != ip->dev) {
|
||||||
err = (!dp_old || !dp_new) ? ENOENT : EXDEV;
|
err = (!dp_old || !dp_new) ? ENOENT : EXDEV;
|
||||||
if (dp_old) iput(dp_old);
|
if (dp_old) iput(dp_old);
|
||||||
|
|
@ -1155,6 +1158,13 @@ bad_rename:
|
||||||
ilock(ip); ip->nlink--; iupdate(ip); iunlockput(ip); end_op(); return -err;
|
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).
|
// symlink(2).
|
||||||
//
|
//
|
||||||
// The errno matters more here than anywhere else in this file, because the one
|
// 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.
|
// 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
|
// at-syscalls
|
||||||
|
|
@ -1963,86 +1992,50 @@ addr_t sys_mknodat(void)
|
||||||
return do_mknod(dirfd, path, (uint32)mode, dev);
|
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)
|
addr_t sys_unlinkat(void)
|
||||||
{
|
{
|
||||||
int dirfd, flags;
|
int dirfd, flags, err;
|
||||||
char *path;
|
char *path;
|
||||||
struct inode *ip, *dp;
|
struct inode *ip, *dp;
|
||||||
char name[VFS_NAME_MAX + 1];
|
char name[VFS_NAME_MAX + 1];
|
||||||
uint off;
|
uint off;
|
||||||
argint(0, &dirfd);
|
argint(0, &dirfd);
|
||||||
if (argstr(1, &path) < 0) return -1;
|
if (argstr(1, &path) < 0) return -EFAULT;
|
||||||
argint(2, &flags);
|
argint(2, &flags);
|
||||||
begin_op();
|
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);
|
ilock(dp);
|
||||||
if (namecmp(name, ".") == 0 || namecmp(name, "..") == 0) {
|
if (namecmp(name, ".") == 0 || namecmp(name, "..") == 0) { err = EINVAL; goto bad; }
|
||||||
iunlockput(dp); end_op(); return -1;
|
if (!(ip = dirlookup(dp, name, &off))) { err = ENOENT; goto bad; }
|
||||||
}
|
|
||||||
if (!(ip = dirlookup(dp, name, &off))) {
|
|
||||||
iunlockput(dp); end_op(); return -1;
|
|
||||||
}
|
|
||||||
ilock(ip);
|
ilock(ip);
|
||||||
if (ip->nlink < 1) panic("unlinkat: nlink < 1");
|
if (ip->nlink < 1) panic("unlinkat: nlink < 1");
|
||||||
if (ip->type == T_DIR && !(flags & AT_REMOVEDIR)) {
|
if (ip->type == T_DIR && !(flags & AT_REMOVEDIR)) { err = EISDIR; goto badip; }
|
||||||
iunlockput(ip); iunlockput(dp); end_op(); return -1;
|
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 (ip->type == T_DIR && dirempty(ip) != 1) {
|
if (dirunlink(dp, name, off) < 0) { err = EIO; goto badip; }
|
||||||
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) { dp->nlink--; iupdate(dp); }
|
if (ip->type == T_DIR) { dp->nlink--; iupdate(dp); }
|
||||||
iunlockput(dp); ip->nlink--; iupdate(ip); iunlockput(ip);
|
iunlockput(dp); ip->nlink--; iupdate(ip); iunlockput(ip);
|
||||||
end_op();
|
end_op();
|
||||||
return 0;
|
return 0;
|
||||||
|
badip:
|
||||||
|
iunlockput(ip);
|
||||||
|
bad:
|
||||||
|
iunlockput(dp);
|
||||||
|
end_op();
|
||||||
|
return -err;
|
||||||
}
|
}
|
||||||
|
|
||||||
addr_t sys_renameat(void)
|
addr_t sys_renameat(void)
|
||||||
{
|
{
|
||||||
int olddirfd, newdirfd;
|
int olddirfd, newdirfd;
|
||||||
char *oldpath, *newpath;
|
char *oldpath, *newpath;
|
||||||
argint(0, &olddirfd); argstr(1, &oldpath);
|
argint(0, &olddirfd);
|
||||||
argint(2, &newdirfd); argstr(3, &newpath);
|
argint(2, &newdirfd);
|
||||||
// Delegate to sys_rename internals — fake the args for rename.
|
if (argstr(1, &oldpath) < 0 || argstr(3, &newpath) < 0) return -EFAULT;
|
||||||
// Easiest: place path pointers in registers and call rename logic directly.
|
return do_rename(olddirfd, oldpath, newdirfd, newpath);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
addr_t sys_renameat2(void) { return sys_renameat(); } // ignore flags
|
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
|
// 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`.
|
// no volume to have found at boot. `mount -t proc proc /proc`.
|
||||||
if (strncmp(fstype, "proc", 5) == 0) {
|
if (strncmp(fstype, "proc", 5) == 0)
|
||||||
procfs_mount();
|
return procfs_mount(target);
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// devtmpfs: the same, and the one init actually needs. /dev used to be
|
// 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.
|
// 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) {
|
if (strncmp(fstype, "devtmpfs", 9) == 0 || strncmp(fstype, "dev", 4) == 0)
|
||||||
devfs_mount();
|
return devfs_mount(target);
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// sysfs: the hardware, as Linux's /sys lays it out. kernel/fs/sysfs.c.
|
// sysfs: the hardware, as Linux's /sys lays it out. kernel/fs/sysfs.c.
|
||||||
if (strncmp(fstype, "sysfs", 6) == 0)
|
if (strncmp(fstype, "sysfs", 6) == 0)
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,52 @@ vma_note_anon(struct proc *p, uint64 va, uint64 len)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Nothing recorded and nothing mapped anywhere in [va, va+len).
|
||||||
|
static int
|
||||||
|
range_free(struct proc *p, uint64 va, uint64 len)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < NVMA; i++) {
|
||||||
|
struct vma *v = &p->vma[i];
|
||||||
|
if (v->used && va < v->addr + v->length && v->addr < va + len)
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
for (uint64 a = va; a < va + len; a += PGSIZE)
|
||||||
|
if (uva2ka(p->pgdir, (char *)a))
|
||||||
|
return 0;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Where a new mapping of `len` goes.
|
||||||
|
//
|
||||||
|
// Fresh addresses from the bump pointer, except for memory that can merge:
|
||||||
|
// that goes straight after an anonymous mapping whose end has room, so it
|
||||||
|
// extends the record rather than taking a slot. Without this a slot was
|
||||||
|
// spent each time the record that ended at the bump pointer had been trimmed,
|
||||||
|
// and every time munmap split one -- and musl's malloc frees 32 KB pieces out
|
||||||
|
// of the middle of larger ones all day. apk ran out of NVMA slots within a
|
||||||
|
// second of starting to install, with 780 MB free, and said "Out of memory".
|
||||||
|
// It also gives freed address space back, which the bump pointer never did.
|
||||||
|
static uint64
|
||||||
|
mmap_va(struct proc *p, uint64 len, int mergeable)
|
||||||
|
{
|
||||||
|
if (p->mmap_next == 0)
|
||||||
|
p->mmap_next = 0x40000000;
|
||||||
|
if (mergeable) {
|
||||||
|
for (int i = 0; i < NVMA; i++) {
|
||||||
|
struct vma *v = &p->vma[i];
|
||||||
|
uint64 e = v->addr + v->length;
|
||||||
|
if (vma_anon(v) && e >= 0x40000000 && e + len <= p->mmap_next &&
|
||||||
|
range_free(p, e, len))
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uint64 va = p->mmap_next;
|
||||||
|
if (bad_user_range(va, len))
|
||||||
|
return 0;
|
||||||
|
p->mmap_next += len;
|
||||||
|
return va;
|
||||||
|
}
|
||||||
|
|
||||||
// Record [va, va+len) as anonymous memory wherever no mapping already covers
|
// Record [va, va+len) as anonymous memory wherever no mapping already covers
|
||||||
// it, for a MAP_FIXED that may land anywhere: inside an earlier mapping, across
|
// it, for a MAP_FIXED that may land anywhere: inside an earlier mapping, across
|
||||||
// the end of one, or in a gap. Below sz is the program image and its heap,
|
// the end of one, or in a gap. Below sz is the program image and its heap,
|
||||||
|
|
@ -210,13 +256,13 @@ sys_mmap(void)
|
||||||
// slot index, which a single allocation bigger than 8MB — e.g. a large
|
// slot index, which a single allocation bigger than 8MB — e.g. a large
|
||||||
// realloc() growth — could map straight past, panicking mappages("remap")
|
// realloc() growth — could map straight past, panicking mappages("remap")
|
||||||
// when it hit an already-present PTE in the next slot's territory).
|
// when it hit an already-present PTE in the next slot's territory).
|
||||||
// munmap()'d ranges are not reclaimed for reuse; that's fine at this scale.
|
// Anonymous memory and private file copies may also reuse a freed range
|
||||||
if (myproc()->mmap_next == 0)
|
// next to an existing one; see mmap_va().
|
||||||
myproc()->mmap_next = 0x40000000;
|
int mergeable = !(f && (f->type == FD_MEMFD ||
|
||||||
va = myproc()->mmap_next;
|
(f->type == FD_INODE && f->ip->type == T_DEV)));
|
||||||
if (bad_user_range(va, length))
|
va = mmap_va(myproc(), length, mergeable);
|
||||||
|
if (va == 0)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
myproc()->mmap_next += length;
|
|
||||||
|
|
||||||
int is_fb = 0;
|
int is_fb = 0;
|
||||||
int shmid = -1;
|
int shmid = -1;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue