SchOS/kernel/fs/procfs.c
Nexoniarz fd9d392416 Pseudo-terminals, and what tmux and htop needed around them
- /dev/ptmx and /dev/pts/N: Unix98 pairs with a full line discipline,
  window sizes, job control and hangup (kernel/dev/pty.c).
- The console speaks scroll regions, insert/delete line, SU/SD, RI/IND/NEL,
  DECSC/DECRC; swallows charset designations and private CSI sequences;
  and LF only returns the carriage under ONLCR.
- setsid() drops the controlling terminal, so a pane's pty becomes its ctty.
- pipe2() honours O_NONBLOCK and O_CLOEXEC (libevent's signal pipe).
- File modes are stored and enforced: chmod, fchmod(at), umask, creation
  modes; /tmp is 1777.
- /proc/stat has real user/system/idle; /proc/<pid>/task; kthread flag.
- SO_PEERCRED, IOV_MAX 1024.
- /proc/strace: name a program and its syscalls go to the kernel log.
- World gains htop, btop, tmux, mc, ncdu, fastfetch, games, X toys, NetSurf.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
2026-10-25 02:11:10 +01:00

719 lines
25 KiB
C

// /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 "core/types.h"
#include "core/defs.h"
#include "core/klog.h"
#include "core/param.h"
#include "arch/memlayout.h"
#include "arch/mmu.h"
#include "core/proc.h"
#include "core/spinlock.h"
#include "core/sleeplock.h"
#include "fs/fs.h"
#include "fs/file.h"
#include "core/kmem.h"
#include "uapi/stat.h"
#include "core/uaccess.h"
#include "uapi/errno.h"
#include "dev/pty.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
// 2 /proc/self
// 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 << 8) | 0x80 | fd /proc/<pid>/fd/<fd>
//
// File ids stay below 0x80 and fds below 128 (NOFILE). pids are not bounded
// by NPROC -- they count up -- so the global bit sits far above any pid << 8;
// at 0x10000 it was /proc/256. Shifting by 8 rather than packing tightly keeps
// the numbers readable in a debugger, which matters more than density here.
// ---------------------------------------------------------------------------
#define PROCFS_ROOT 1ULL
#define PROCFS_SELF 2ULL
#define PROCFS_GLOBAL (1ULL << 40)
#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_SCHEDSTAT 6
#define PF_FD 7 // the directory /proc/<pid>/fd
#define PF_TASK 8 // the directory /proc/<pid>/task
#define PF_MAX 8
#define PF_FDENT 0x80 // | fd: one link in it
#define INO_PID(ino) ((int)((ino) >> 8))
#define INO_FILE(ino) ((int)((ino) & 0xFF))
#define INO_IS_FDENT(ino) (!INO_IS_GLOBAL(ino) && (INO_FILE(ino) & PF_FDENT))
#define INO_FD(ino) (INO_FILE(ino) & 0x7F)
#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 },
{ "schinit", 16 },
{ "pci", 11 },
{ "interrupts", 12 },
{ "devices", 13 },
{ "fsstat", 14 },
{ "netif", 15 },
// The nameservers DHCP supplied, in resolv.conf's own syntax. See
// net_resolv() and cmd/resolvconf.
{ "resolv.conf", 18 },
// "quiet" or "verbose" -- how this machine was booted. See cmd/schinitd.
{ "bootmode", 19 },
{ "bootdebug", 30 },
// What the slab allocator is holding. See include/core/kmem.h.
{ "slab", 20 },
// Disks and partitions, in Linux's format. See blkdev_info().
{ "partitions", 21 },
{ "kmsg", 17 },
// A program's name: from its next exec on, it and everything it starts log
// every syscall. Empty to stop. See strace_call() in kernel/sys/syscall.c.
{ "strace", 31 },
};
#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 },
// Three numbers about how the scheduler has treated this process, in the
// order Linux writes them: weighted CPU nanoseconds consumed, then -- where
// Linux reports run-queue wait -- this kernel's nice level, then the number
// of slices it has been given. The middle field differs on purpose and is
// documented in docs/architecture/scheduler.md; nothing here reads it, and
// inventing a wait time that is not measured would be worse than saying
// something true in its place.
{ "schedstat", PF_SCHEDSTAT },
// One symlink per open descriptor, named by its number and pointing at the
// path it was opened by, or at "socket:[ino]" and the like.
{ "fd", PF_FD },
// The process's threads, one directory each, named by thread id. A process
// here is one thread, so task/ holds a single entry, the pid, and it *is*
// /proc/<pid> -- the same inode. htop reads every process through it and
// shows nothing at all without it.
{ "task", PF_TASK },
};
#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 SchOS 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
// 7 tty_nr. Linux's dev_t: major 12 bits above minor's 8. /dev/ttyN is
// major 1, minor 10+N in devfs, so a process whose ctty is 1 reports
// (1 << 8) | 11. This was hardcoded 0, and that is why plain `ps` printed a
// header and nothing else: ps with no arguments shows only the processes on
// *this* terminal, and nothing could ever match. `ps -e` always worked.
sbputc(s, ' ');
// A pty is Linux's (136, N), the same number stat() gives /dev/pts/N.
if (pi->ctty >= PTY_CTTY)
sbputi(s, (136 << 8) | (pi->ctty - PTY_CTTY));
else
sbputi(s, pi->ctty ? ((1 << 8) | (10 + pi->ctty)) : 0);
// 8 tpgid, 9 flags. PF_KTHREAD (0x00200000) on a kernel thread is how htop
// and ps tell one from a process and hide it; without it acpi and dbgd
// listed as programs of four bytes each.
sbputs(s, " -1 ");
sbputi(s, pi->kthread ? 0x00200000 : 0);
// 10..13: minflt cminflt majflt cmajflt. There is no fault accounting, and
// a fabricated number is worse than none -- top would draw a plausible graph
// of something nobody measured.
for (i = 0; i < 4; i++)
sbputs(s, " 0");
// 14..17: utime stime cutime cstime, in ticks, which is the unit the field
// is defined in. These were zero too, which is why `ps` and `top` showed
// 00:00:00 for everything no matter how long it had been running.
sbputc(s, ' '); sbputi(s, (int64)pi->utime);
sbputc(s, ' '); sbputi(s, (int64)pi->stime);
sbputc(s, ' '); sbputi(s, (int64)pi->cutime);
sbputc(s, ' '); sbputi(s, (int64)pi->cstime);
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");
// Where Linux keeps them, under the names Linux uses. The two are worth
// telling apart: a process with a high voluntary count is waiting on
// something, and one with a high involuntary count is being preempted --
// which is the difference between "this is slow" and "this is competing".
sbputs(s, "voluntary_ctxt_switches:\t");
sbputi(s, (int64)pi->nvcsw);
sbputs(s, "\nnonvoluntary_ctxt_switches:\t");
sbputi(s, (int64)pi->nivcsw);
sbputc(s, '\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);
// Whoever is asking: the one file whose content depends on the reader.
if (ino == PROCFS_SELF) {
sbputi(&s, myproc()->pid);
return s.n;
}
if (INO_IS_FDENT(ino)) {
int n = proc_fd_link(INO_PID(ino), INO_FD(ino), buf, cap);
return n > 0 ? n : 0;
}
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;
case PF_SCHEDSTAT:
sbputi(&s, (int64)pi.vruntime); sbputc(&s, ' ');
sbputi(&s, (int64)pi.nice); sbputc(&s, ' ');
sbputi(&s, (int64)(pi.nvcsw + pi.nivcsw));
sbputc(&s, '\n');
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 three exceptions: /proc/sysrq is the deliberate write
// target (`echo c > /proc/sysrq` to crash on purpose), /proc/schinit is where
// PID 1 publishes its job table, and /proc/strace takes a name to trace. Marking everything 2 made filewrite()
// refuse 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 || (ino & 0xFF) == 16 ||
(ino & 0xFF) == 31)) ? 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 || INO_FILE(ino) == PF_FD ||
INO_FILE(ino) == PF_TASK))) {
ip->type = T_DIR;
ip->size = 0;
ip->nlink = 2;
return;
}
// /proc/<pid>/exe is a symlink, as it is on Linux.
//
// It was a regular file holding the path as text, which reads the same from
// `cat` and is wrong to everything that matters: readlink() refused it with
// EINVAL, and `ps -o comm` -- which readlinks it -- fell back to the process
// name in brackets and printed `[init]` for everything.
//
// This one file has to report a real size, and every other procfs file
// deliberately reports zero. readlink(2) bounds its copy by ip->size, so a
// symlink whose size is 0 resolves to the empty string -- which is worse
// than the regular file was, because it looks like it worked. Generating it
// here costs one small render per stat() of one file.
// /proc/self and /proc/<pid>/fd/N are links too. Their targets can change
// under a cached inode, which is why readlink() does not trust this size
// for procfs.
if (ino == PROCFS_SELF || INO_IS_FDENT(ino) ||
(!INO_IS_GLOBAL(ino) && INO_FILE(ino) == PF_EXE)) {
char path[256];
int len = gen_file(ino, path, sizeof(path));
ip->type = T_SYMLINK;
ip->size = len > 0 ? (uint64)len : 0;
ip->nlink = 1;
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.
// /proc/kmsg is 128 KB and comes out of a ring, so it does not go through
// the render-the-whole-file-then-slice path below. See klog_read().
if (INO_IS_GLOBAL(ip->inum) && (int)(ip->inum & 0xFF) == 17) {
char *k;
int got;
if (n > PGSIZE)
n = PGSIZE;
if ((k = kalloc()) == 0)
return -ENOMEM;
got = klog_read(off, k, (int)n);
if (got <= 0) {
kfree(k);
return 0;
}
r = got;
if (user_dst) {
if (copy_to_user(dst, k, got) < 0)
r = -EFAULT;
} else {
memmove((char *)dst, k, got);
}
kfree(k);
return r;
}
// The shared buffer in kernel/dev/procfs.c, 64 KB, held until the copy out
// is done. It was 2 KB from kmalloc, which cut /proc/cpuinfo off at the
// eighteenth processor.
int cap;
buf = proc_genbuf_get(&cap);
len = gen_file(ip->inum, buf, cap);
if (off >= (uint64)len) {
proc_genbuf_put();
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);
}
proc_genbuf_put();
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);
// /proc/schinit is the job table, and PID 1 is the one that knows it. The
// kernel holds the text rather than generating it: walking another
// process's memory to answer a read is not something a procfs generator
// should be doing, and PID 1 already knows when the table changed.
if (INO_IS_GLOBAL(ip->inum) && (ip->inum & 0xFF) == 16)
return proc_write_schinit(user_src, src, n);
if (INO_IS_GLOBAL(ip->inum) && (ip->inum & 0xFF) == 31)
return proc_write_strace(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) // /proc/<pid>/.. is /proc
return iget(dp->dev, INO_FILE(dp->inum) == PF_FD ||
INO_FILE(dp->inum) == PF_TASK ? (dp->inum & ~0xFFULL)
: PROCFS_ROOT);
// Inside /proc/<pid>/task/: the one thread, which is the process.
if (dp->inum != PROCFS_ROOT && INO_FILE(dp->inum) == PF_TASK) {
if (name_to_pid(name) != INO_PID(dp->inum))
return 0;
return iget(dp->dev, (dp->inum & ~0xFFULL) | PF_DIR);
}
// Inside /proc/<pid>/fd/: a number that is an open descriptor.
if (dp->inum != PROCFS_ROOT && INO_FILE(dp->inum) == PF_FD) {
char tmp[4];
int fd = name_to_pid(name);
if (fd < 0 || fd >= NOFILE || fd >= 128 ||
proc_fd_link(INO_PID(dp->inum), fd, tmp, sizeof(tmp)) < 0)
return 0;
return iget(dp->dev, (dp->inum & ~0xFFULL) | PF_FDENT | fd);
}
// 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;
}
if (namecmp(name, "self") == 0)
return iget(dp->dev, PROCFS_SELF);
// 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
#define DT_LNK_ 10
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_, "..");
EMIT(PROCFS_SELF, DT_LNK_, "self");
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 if (INO_FILE(dp->inum) == PF_FD) {
uint64 mask[2];
EMIT(dp->inum, DT_DIR_, ".");
EMIT(dp->inum & ~0xFFULL, DT_DIR_, "..");
if (proc_fd_mask(INO_PID(dp->inum), mask) == 0)
for (i = 0; i < 128; i++)
if (mask[i / 64] & (1ULL << (i % 64))) {
utoa((uint)i, name);
EMIT((dp->inum & ~0xFFULL) | PF_FDENT | i, DT_LNK_, name);
}
} else if (INO_FILE(dp->inum) == PF_TASK) {
EMIT(dp->inum, DT_DIR_, ".");
EMIT(dp->inum & ~0xFFULL, DT_DIR_, "..");
utoa((uint)INO_PID(dp->inum), name);
EMIT((dp->inum & ~0xFFULL) | 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,
pidfiles[i].id == PF_FD || pidfiles[i].id == PF_TASK ? DT_DIR_ :
pidfiles[i].id == PF_EXE ? DT_LNK_ : 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".
int
procfs_mount(const char *target)
{
struct inode *dp = namei((char *)target);
int r;
if (dp == 0)
return -ENOENT;
r = mount_add(dp->dev, dp->inum, PROCFS_DEV, PROCFS_ROOT, (char *)target,
"proc", "proc");
iput(dp);
return r < 0 ? -EBUSY : 0;
}