Initial commit: Schunix as it stands before the SchOS rename
An x86-64 operating system built from xv6, now largely rewritten: SMP, a copy-on-write root filesystem (SchFS), PCI/USB/virtio drivers, a TTF rasteriser, and a Linux-numbered syscall ABI. No history before this point -- the tree was never under version control. This commit exists so the rename that follows has something to undo to.
This commit is contained in:
commit
d0f7acf9eb
275 changed files with 51417 additions and 0 deletions
524
kernel/fs/procfs.c
Normal file
524
kernel/fs/procfs.c
Normal file
|
|
@ -0,0 +1,524 @@
|
|||
// /proc as a real filesystem.
|
||||
//
|
||||
// It used to be a handful of character devices: init ran `mknod("/proc/uptime",
|
||||
// 6, 2)` and friends, so every file was a real inode on the root filesystem
|
||||
// with a major/minor pair behind it. That works for a fixed set of files and
|
||||
// cannot work at all for the thing /proc is mostly *for* -- one directory per
|
||||
// process. You cannot mknod a directory for a process that does not exist yet
|
||||
// and will not exist in a minute, which is why `ps` and `top` have never
|
||||
// worked: they call dirtree_flagread("/proc") and get a list with no pids in
|
||||
// it.
|
||||
//
|
||||
// So this is a synthetic filesystem behind the same `struct fs_ops` vtable
|
||||
// SchFS uses. Nothing is stored: every directory listing is generated from the
|
||||
// process table at the moment it is asked for, and every file's contents are
|
||||
// generated when it is read.
|
||||
//
|
||||
// The global files (meminfo, uptime, pci, ...) keep their existing generators
|
||||
// in kernel/dev/procfs.c; this layer only decides what exists and hands the
|
||||
// reads over. That split is deliberate -- the generators are about content and
|
||||
// this file is about namespace, and they change for different reasons.
|
||||
|
||||
#include "types.h"
|
||||
#include "defs.h"
|
||||
#include "param.h"
|
||||
#include "memlayout.h"
|
||||
#include "mmu.h"
|
||||
#include "proc.h"
|
||||
#include "spinlock.h"
|
||||
#include "sleeplock.h"
|
||||
#include "fs.h"
|
||||
#include "file.h"
|
||||
#include "stat.h"
|
||||
#include "uaccess.h"
|
||||
#include "errno.h"
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Inode numbering
|
||||
//
|
||||
// An inode number has to survive being handed out, stored in a struct inode,
|
||||
// and given back later with no other context -- so it encodes the whole
|
||||
// identity of the object.
|
||||
//
|
||||
// 1 the root, /proc
|
||||
// PROCFS_GLOBAL | id a global file, id being the PROC_* minor that
|
||||
// kernel/dev/procfs.c already uses
|
||||
// pid << 8 the directory /proc/<pid>
|
||||
// (pid << 8) | fileid a file inside it
|
||||
//
|
||||
// pid is bounded by NPROC and file ids by PF_MAX, so the ranges cannot
|
||||
// collide. Shifting by 8 rather than packing tightly keeps the numbers
|
||||
// readable in a debugger, which matters more than density here.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Size of the scratch buffer every generator renders into. Must stay equal to
|
||||
// PROC_BUFSZ in kernel/dev/procfs.c: the SchFS and PCI generators there write
|
||||
// up to PROC_BUFSZ regardless of the cap they are handed, so a smaller value
|
||||
// here would be a buffer overrun rather than a truncation.
|
||||
#define PROCFS_GENBUF 2048
|
||||
|
||||
#define PROCFS_ROOT 1ULL
|
||||
#define PROCFS_GLOBAL 0x10000ULL
|
||||
|
||||
#define PF_DIR 0 // the directory itself
|
||||
#define PF_STAT 1
|
||||
#define PF_STATUS 2
|
||||
#define PF_CMDLINE 3
|
||||
#define PF_STATM 4
|
||||
#define PF_EXE 5
|
||||
#define PF_MAX 6
|
||||
|
||||
#define INO_PID(ino) ((int)((ino) >> 8))
|
||||
#define INO_FILE(ino) ((int)((ino) & 0xFF))
|
||||
#define INO_IS_GLOBAL(ino) (((ino) & PROCFS_GLOBAL) != 0)
|
||||
|
||||
// The global files, by the name they appear under and the PROC_* id that
|
||||
// kernel/dev/procfs.c generates them from.
|
||||
struct global_file { const char *name; int id; };
|
||||
|
||||
static const struct global_file globals[] = {
|
||||
{ "meminfo", 1 },
|
||||
{ "uptime", 2 },
|
||||
{ "cpuinfo", 3 },
|
||||
{ "version", 4 },
|
||||
{ "stat", 5 },
|
||||
{ "loadavg", 6 },
|
||||
{ "mounts", 7 },
|
||||
{ "filesystems", 8 },
|
||||
{ "schfs", 9 },
|
||||
{ "sysrq", 10 },
|
||||
{ "pci", 11 },
|
||||
{ "interrupts", 12 },
|
||||
{ "devices", 13 },
|
||||
};
|
||||
#define NGLOBALS ((int)(sizeof(globals) / sizeof(globals[0])))
|
||||
|
||||
// Per-process files. `statm` and `status` are here because ps reads them; a
|
||||
// missing file is not an error to it, but a present one is cheaper than the
|
||||
// fallback paths it takes without them.
|
||||
static const struct global_file pidfiles[] = {
|
||||
{ "stat", PF_STAT },
|
||||
{ "status", PF_STATUS },
|
||||
{ "cmdline", PF_CMDLINE },
|
||||
{ "statm", PF_STATM },
|
||||
{ "exe", PF_EXE },
|
||||
};
|
||||
#define NPIDFILES ((int)(sizeof(pidfiles) / sizeof(pidfiles[0])))
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Content
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// A tiny append-only formatter. procfs files are small and generated whole,
|
||||
// so there is no reason for anything cleverer.
|
||||
struct sb { char *p; int n, cap; };
|
||||
|
||||
static void sbputc(struct sb *s, char c)
|
||||
{ if (s->n < s->cap - 1) s->p[s->n++] = c; }
|
||||
|
||||
static void sbputs(struct sb *s, const char *t)
|
||||
{ while (*t) sbputc(s, *t++); }
|
||||
|
||||
static void sbputi(struct sb *s, int64 v)
|
||||
{
|
||||
char tmp[24];
|
||||
int i = 0, neg = 0;
|
||||
uint64 u;
|
||||
if (v < 0) { neg = 1; u = (uint64)(-v); } else u = (uint64)v;
|
||||
do { tmp[i++] = '0' + (int)(u % 10); u /= 10; } while (u);
|
||||
if (neg) sbputc(s, '-');
|
||||
while (i--) sbputc(s, tmp[i]);
|
||||
}
|
||||
|
||||
// Linux's /proc/<pid>/stat, as far as anything here reads it.
|
||||
//
|
||||
// The field order is not negotiable: ps counts fields by position, so a
|
||||
// missing one silently shifts everything after it. Fields Schunix does not
|
||||
// track are zero, which is what Linux itself reports for things a given
|
||||
// process has never done.
|
||||
static char
|
||||
state_char(int st)
|
||||
{
|
||||
switch (st) {
|
||||
case RUNNING: return 'R';
|
||||
case RUNNABLE: return 'R';
|
||||
case SLEEPING: return 'S';
|
||||
case ZOMBIE: return 'Z';
|
||||
case EMBRYO: return 'R';
|
||||
default: return '?';
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gen_pid_stat(struct sb *s, const struct procinfo *pi)
|
||||
{
|
||||
int i;
|
||||
|
||||
sbputi(s, pi->pid);
|
||||
sbputs(s, " (");
|
||||
sbputs(s, pi->name);
|
||||
sbputs(s, ") ");
|
||||
sbputc(s, state_char(pi->state));
|
||||
sbputc(s, ' ');
|
||||
sbputi(s, pi->ppid); // 4 ppid
|
||||
sbputc(s, ' ');
|
||||
sbputi(s, pi->pgid); // 5 pgrp
|
||||
sbputc(s, ' ');
|
||||
sbputi(s, pi->sid); // 6 session
|
||||
sbputs(s, " 0 -1 0"); // 7 tty_nr, 8 tpgid, 9 flags
|
||||
|
||||
// 10..17: minflt cminflt majflt cmajflt utime stime cutime cstime.
|
||||
// No per-process accounting yet, so these are honestly zero rather than
|
||||
// invented -- a fabricated CPU time is worse than none, because top would
|
||||
// draw a plausible graph of a number nobody measured.
|
||||
for (i = 0; i < 8; i++)
|
||||
sbputs(s, " 0");
|
||||
|
||||
sbputs(s, " 20 0 1 0 0 "); // priority nice num_threads itrealvalue starttime
|
||||
sbputi(s, (int64)pi->sz); // 23 vsize, in bytes
|
||||
sbputc(s, ' ');
|
||||
sbputi(s, (int64)(pi->sz / PGSIZE)); // 24 rss, in pages
|
||||
sbputs(s, " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n");
|
||||
}
|
||||
|
||||
static void
|
||||
gen_pid_status(struct sb *s, const struct procinfo *pi)
|
||||
{
|
||||
sbputs(s, "Name:\t"); sbputs(s, pi->name); sbputc(s, '\n');
|
||||
sbputs(s, "State:\t"); sbputc(s, state_char(pi->state));
|
||||
sbputs(s, "\nTgid:\t"); sbputi(s, pi->pid);
|
||||
sbputs(s, "\nPid:\t"); sbputi(s, pi->pid);
|
||||
sbputs(s, "\nPPid:\t"); sbputi(s, pi->ppid);
|
||||
sbputs(s, "\nUid:\t"); sbputi(s, pi->uid);
|
||||
sbputc(s, '\t'); sbputi(s, pi->uid);
|
||||
sbputc(s, '\t'); sbputi(s, pi->uid);
|
||||
sbputc(s, '\t'); sbputi(s, pi->uid);
|
||||
sbputs(s, "\nVmSize:\t"); sbputi(s, (int64)(pi->sz / 1024));
|
||||
sbputs(s, " kB\nThreads:\t1\n");
|
||||
}
|
||||
|
||||
static void
|
||||
gen_pid_statm(struct sb *s, const struct procinfo *pi)
|
||||
{
|
||||
int64 pages = (int64)(pi->sz / PGSIZE);
|
||||
sbputi(s, pages); sbputc(s, ' ');
|
||||
sbputi(s, pages); sbputs(s, " 0 0 0 0 0\n");
|
||||
}
|
||||
|
||||
// argv is not kept after exec, so the best honest answer is the process name.
|
||||
// Linux separates arguments with NUL and ps turns those into spaces; one
|
||||
// NUL-terminated string is a valid degenerate case of that.
|
||||
static void
|
||||
gen_pid_cmdline(struct sb *s, const struct procinfo *pi)
|
||||
{
|
||||
sbputs(s, pi->name);
|
||||
sbputc(s, '\0');
|
||||
}
|
||||
|
||||
// Generate the whole of one file into `buf`. Returns its length.
|
||||
static int
|
||||
gen_file(uint64 ino, char *buf, int cap)
|
||||
{
|
||||
struct sb s = { buf, 0, cap };
|
||||
struct procinfo pi;
|
||||
|
||||
if (INO_IS_GLOBAL(ino))
|
||||
return proc_gen_global((int)(ino & 0xFF), buf, cap);
|
||||
|
||||
if (proc_snapshot_pid(INO_PID(ino), &pi) < 0)
|
||||
return 0; // it exited between lookup and read
|
||||
|
||||
switch (INO_FILE(ino)) {
|
||||
case PF_STAT: gen_pid_stat(&s, &pi); break;
|
||||
case PF_STATUS: gen_pid_status(&s, &pi); break;
|
||||
case PF_CMDLINE: gen_pid_cmdline(&s, &pi); break;
|
||||
case PF_STATM: gen_pid_statm(&s, &pi); break;
|
||||
case PF_EXE: sbputs(&s, pi.exe[0] ? pi.exe : pi.name); break;
|
||||
default: return 0;
|
||||
}
|
||||
return s.n;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// fs_ops
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static void
|
||||
procfs_iload(struct inode *ip)
|
||||
{
|
||||
uint64 ino = ip->inum;
|
||||
|
||||
ip->valid = 1;
|
||||
ip->nlink = 1;
|
||||
ip->major = ip->minor = 0;
|
||||
// Read-only, with one exception: /proc/sysrq is the deliberate write target
|
||||
// (`echo c > /proc/sysrq` to crash on purpose). Marking everything 2 made
|
||||
// filewrite() refuse it with "permission denied", which the USB suite
|
||||
// caught immediately -- three of its checks drive the disk through sysrq.
|
||||
ip->rwmode = (INO_IS_GLOBAL(ino) && (ino & 0xFF) == 10) ? 3 : 2;
|
||||
ip->showmode = 1;
|
||||
ip->atime = ip->mtime = ip->ctime = 0;
|
||||
|
||||
if (ino == PROCFS_ROOT || (!INO_IS_GLOBAL(ino) && INO_FILE(ino) == PF_DIR)) {
|
||||
ip->type = T_DIR;
|
||||
ip->size = 0;
|
||||
ip->nlink = 2;
|
||||
return;
|
||||
}
|
||||
|
||||
ip->type = T_FILE;
|
||||
// Size is reported as zero.
|
||||
//
|
||||
// A synthetic file has no length until it is generated, and generating it
|
||||
// here -- on every stat(), for every process -- to fill in a number almost
|
||||
// nobody reads would be absurd. Linux does exactly the same thing, which is
|
||||
// why `ls -l /proc` shows a column of zeroes, and why every tool that reads
|
||||
// /proc reads until EOF instead of trusting st_size.
|
||||
ip->size = 0;
|
||||
}
|
||||
|
||||
static void procfs_istore(struct inode *ip) { (void)ip; }
|
||||
static void procfs_ifree(struct inode *ip) { (void)ip; }
|
||||
static void procfs_truncate(struct inode *ip, uint64 n) { (void)ip; (void)n; }
|
||||
|
||||
static int
|
||||
procfs_read(struct inode *ip, int user_dst, uint64 dst, uint64 off, uint n)
|
||||
{
|
||||
char *buf;
|
||||
int len, r;
|
||||
|
||||
if (ip->type == T_DIR)
|
||||
return -EISDIR;
|
||||
|
||||
// The 2048-byte scratch used to be a local. KSTACKSIZE is 4096, so that was
|
||||
// half the kernel stack in one array, on a path already several frames deep
|
||||
// -- a generator with any locals of its own, or one more frame below it,
|
||||
// runs off the end. /proc/schfs is what finally did it: the canary at the
|
||||
// bottom of the stack came back zeroed. Nothing about SchFS caused this; it
|
||||
// only pushed an existing overrun over the edge.
|
||||
//
|
||||
// A whole page for a 2 KB buffer is not wasteful here -- kalloc's unit is a
|
||||
// page either way, and this is not a hot path.
|
||||
if ((buf = kalloc()) == 0)
|
||||
return -ENOMEM;
|
||||
|
||||
len = gen_file(ip->inum, buf, PROCFS_GENBUF);
|
||||
if (off >= (uint64)len) {
|
||||
kfree(buf);
|
||||
return 0;
|
||||
}
|
||||
if (off + n > (uint64)len)
|
||||
n = (uint)((uint64)len - off);
|
||||
|
||||
r = (int)n;
|
||||
if (user_dst) {
|
||||
if (copy_to_user(dst, buf + off, n) < 0)
|
||||
r = -EFAULT;
|
||||
} else {
|
||||
memmove((char *)dst, buf + off, n);
|
||||
}
|
||||
|
||||
kfree(buf);
|
||||
return r;
|
||||
}
|
||||
|
||||
static int
|
||||
procfs_write(struct inode *ip, int user_src, uint64 src, uint64 off, uint n)
|
||||
{
|
||||
// Only /proc/sysrq accepts writes, and its handler lives with the other
|
||||
// generators. Everything else is read-only, and says so.
|
||||
if (INO_IS_GLOBAL(ip->inum) && (ip->inum & 0xFF) == 10)
|
||||
return proc_write_sysrq(user_src, src, n);
|
||||
(void)off;
|
||||
return -EROFS;
|
||||
}
|
||||
|
||||
// Decimal string -> pid, or -1 if it is not all digits. This is also what
|
||||
// makes a name like "meminfo" fall through to the global table.
|
||||
static int
|
||||
name_to_pid(const char *name)
|
||||
{
|
||||
int v = 0;
|
||||
|
||||
if (!name || !*name)
|
||||
return -1;
|
||||
while (*name) {
|
||||
if (*name < '0' || *name > '9')
|
||||
return -1;
|
||||
v = v * 10 + (*name++ - '0');
|
||||
if (v > 1 << 20)
|
||||
return -1;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
static struct inode *
|
||||
procfs_dirlookup(struct inode *dp, char *name, uint *poff)
|
||||
{
|
||||
struct procinfo pi;
|
||||
int i, pid;
|
||||
|
||||
if (poff)
|
||||
*poff = 0;
|
||||
|
||||
if (namecmp(name, ".") == 0)
|
||||
return iget(dp->dev, dp->inum);
|
||||
if (namecmp(name, "..") == 0)
|
||||
return iget(dp->dev, PROCFS_ROOT); // /proc/<pid>/.. is /proc
|
||||
|
||||
// Inside /proc/<pid>/
|
||||
if (dp->inum != PROCFS_ROOT) {
|
||||
for (i = 0; i < NPIDFILES; i++)
|
||||
if (namecmp(name, (char *)pidfiles[i].name) == 0)
|
||||
return iget(dp->dev, (dp->inum & ~0xFFULL) | pidfiles[i].id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// In /proc itself: a number is a process, a word is a global file.
|
||||
pid = name_to_pid(name);
|
||||
if (pid > 0) {
|
||||
if (proc_snapshot_pid(pid, &pi) < 0)
|
||||
return 0;
|
||||
return iget(dp->dev, ((uint64)pid << 8) | PF_DIR);
|
||||
}
|
||||
|
||||
for (i = 0; i < NGLOBALS; i++)
|
||||
if (namecmp(name, (char *)globals[i].name) == 0)
|
||||
return iget(dp->dev, PROCFS_GLOBAL | globals[i].id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Nothing in /proc can be created, removed or linked. Saying so with an error
|
||||
// is better than a silent no-op: a program that thinks it wrote a file and
|
||||
// finds it missing later has a much harder problem to debug.
|
||||
static int procfs_dirlink(struct inode *dp, char *name, uint64 inum)
|
||||
{ (void)dp; (void)name; (void)inum; return -EROFS; }
|
||||
static int procfs_dirunlink(struct inode *dp, char *name, uint off)
|
||||
{ (void)dp; (void)name; (void)off; return -EROFS; }
|
||||
static int procfs_dirempty(struct inode *dp) { (void)dp; return 0; }
|
||||
static struct inode *procfs_ialloc(uint dev, short type)
|
||||
{ (void)dev; (void)type; return 0; }
|
||||
|
||||
// One getdents64 record.
|
||||
struct linux_dirent64 {
|
||||
uint64 d_ino;
|
||||
int64 d_off;
|
||||
ushort d_reclen;
|
||||
uchar d_type;
|
||||
char d_name[];
|
||||
};
|
||||
|
||||
#define DT_DIR_ 4
|
||||
#define DT_REG_ 8
|
||||
|
||||
static int
|
||||
emit(char *out, int cap, int used, uint64 ino, int type, const char *name)
|
||||
{
|
||||
struct linux_dirent64 *de;
|
||||
int nlen = 0, reclen;
|
||||
|
||||
while (name[nlen]) nlen++;
|
||||
reclen = (int)sizeof(*de) + nlen + 1;
|
||||
reclen = (reclen + 7) & ~7; // 8-byte aligned, as getdents64 requires
|
||||
if (used + reclen > cap)
|
||||
return -1;
|
||||
|
||||
de = (struct linux_dirent64 *)(out + used);
|
||||
de->d_ino = ino;
|
||||
de->d_off = used + reclen;
|
||||
de->d_reclen = (ushort)reclen;
|
||||
de->d_type = (uchar)type;
|
||||
memmove(de->d_name, name, nlen);
|
||||
de->d_name[nlen] = 0;
|
||||
return reclen;
|
||||
}
|
||||
|
||||
static void
|
||||
utoa(uint v, char *buf)
|
||||
{
|
||||
char t[12];
|
||||
int i = 0, j = 0;
|
||||
do { t[i++] = '0' + (v % 10); v /= 10; } while (v);
|
||||
while (i) buf[j++] = t[--i];
|
||||
buf[j] = 0;
|
||||
}
|
||||
|
||||
// The listing is generated fresh each time, and *off is an index into it.
|
||||
//
|
||||
// A process that exits between two getdents calls therefore shifts everything
|
||||
// after it up by one, and one entry can be missed. That is a real race and it
|
||||
// is the same one Linux has -- /proc has no stable ordering to offer, and
|
||||
// anything that wants a consistent snapshot has to re-read.
|
||||
static int
|
||||
procfs_getdents(struct inode *dp, char *out, int cap, uint64 *off)
|
||||
{
|
||||
struct procinfo pi;
|
||||
char name[16];
|
||||
int used = 0, idx = 0, i, r;
|
||||
uint64 want = *off;
|
||||
|
||||
#define EMIT(ino, type, nm) do { \
|
||||
if (idx++ >= (int)want) { \
|
||||
r = emit(out, cap, used, (ino), (type), (nm)); \
|
||||
if (r < 0) goto full; \
|
||||
used += r; (*off)++; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
if (dp->inum == PROCFS_ROOT) {
|
||||
EMIT(PROCFS_ROOT, DT_DIR_, ".");
|
||||
EMIT(PROCFS_ROOT, DT_DIR_, "..");
|
||||
for (i = 0; i < NGLOBALS; i++)
|
||||
EMIT(PROCFS_GLOBAL | globals[i].id, DT_REG_, globals[i].name);
|
||||
for (i = 0; proc_snapshot(i, &pi) == 0; i++) {
|
||||
utoa((uint)pi.pid, name);
|
||||
EMIT(((uint64)pi.pid << 8) | PF_DIR, DT_DIR_, name);
|
||||
}
|
||||
} else {
|
||||
EMIT(dp->inum, DT_DIR_, ".");
|
||||
EMIT(PROCFS_ROOT, DT_DIR_, "..");
|
||||
for (i = 0; i < NPIDFILES; i++)
|
||||
EMIT((dp->inum & ~0xFFULL) | pidfiles[i].id, DT_REG_, pidfiles[i].name);
|
||||
}
|
||||
|
||||
full:
|
||||
#undef EMIT
|
||||
return used;
|
||||
}
|
||||
|
||||
const struct fs_ops procfs_ops = {
|
||||
.name = "procfs",
|
||||
.iload = procfs_iload,
|
||||
.istore = procfs_istore,
|
||||
.ifree = procfs_ifree,
|
||||
.read = procfs_read,
|
||||
.write = procfs_write,
|
||||
.truncate = procfs_truncate,
|
||||
.dirlookup = procfs_dirlookup,
|
||||
.dirlink = procfs_dirlink,
|
||||
.dirunlink = procfs_dirunlink,
|
||||
.dirempty = procfs_dirempty,
|
||||
.ialloc = procfs_ialloc,
|
||||
.getdents = procfs_getdents,
|
||||
};
|
||||
|
||||
// Graft this filesystem over /proc.
|
||||
//
|
||||
// Called by init once it has made the directory. The mknod'd files that used
|
||||
// to live there are simply covered: path resolution stops at the mount point,
|
||||
// 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
|
||||
// between "the mount is wrong" and "the nodes are missing".
|
||||
void
|
||||
procfs_mount(void)
|
||||
{
|
||||
struct inode *dp = namei("/proc");
|
||||
|
||||
if (dp == 0) {
|
||||
cprintf("procfs: /proc does not exist; not mounted\n");
|
||||
return;
|
||||
}
|
||||
if (mount_add(dp->dev, dp->inum, PROCFS_DEV, PROCFS_ROOT) < 0)
|
||||
cprintf("procfs: /proc already has something mounted on it\n");
|
||||
iput(dp);
|
||||
}
|
||||
Loading…
Reference in a new issue