// /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 << 8) | fileid a file inside it // (pid << 8) | 0x80 | fd /proc//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//fd #define PF_TASK 8 // the directory /proc//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/ -- 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//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, pi->stopped ? 'T' : 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, pi->stopped ? 'T' : 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//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//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//.. 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//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//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// 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; }