commit - /dev/null
commit + 0a04a7c0dfaaf17dd49a7e4ed11d1952375614c6
blob - /dev/null
blob + b08600347e76b7f83bcd5007aef65da71483e99e (mode 644)
--- /dev/null
+++ Makefile
+CFLAGS = -Wall -Wextra -O2 -pipe
+LIBS = -lutil -lkvm -lfuse
+
+all: procfs
+
+procfs: procfs.c files.h
+ ${CC} -o procfs procfs.c ${CFLAGS} ${LIBS}
+
+clean:
+ rm -f procfs
+
+mount: procfs
+ mkdir -p mp
+ doas ./procfs -f mp
+
+umount:
+ doas umount mp
blob - /dev/null
blob + 9caddb70f332c3075760d096ac33443c09f2c32b (mode 644)
--- /dev/null
+++ files.h
+
+static int
+copy_str(const char *field, size_t max, char *buf, size_t size, off_t off)
+{
+ const size_t n = strnlen(field, max);
+ if ((size_t)off < n) {
+ if ((n - off) < size)
+ size = n - off;
+ memcpy(buf, field + off, size);
+ return n - off;
+ } else if ((size_t)off > n) {
+ return 0;
+ } else {
+ buf[0] = '\n';
+ return 1;
+ }
+}
+
+static int
+copy_str2(const char *field, char *buf, size_t size, off_t off)
+{
+ const size_t n = strlen(field);
+ if ((size_t)off < n) {
+ if ((n - off) < size)
+ size = n - off;
+ memcpy(buf, field + off, size);
+ return n - off;
+ } else {
+ return 0;
+ }
+}
+
+static int
+copy_uint(unsigned int field, char *buf, size_t size, off_t off)
+{
+ char buffer[32];
+ int n;
+
+ n = snprintf(buffer, sizeof (buffer), "%u\n", field);
+ if (n >= (int)sizeof (buffer)) {
+ warnx("copy_uint(%u): Failed to format field.", field);
+ return -EIO;
+ }
+
+ return copy_str2(buffer, buf, size, off);
+}
+
+static int
+copy_int(int field, char *buf, size_t size, off_t off)
+{
+ char buffer[32];
+ int n;
+
+ n = snprintf(buffer, sizeof (buffer), "%d\n", field);
+ if (n >= (int)sizeof (buffer)) {
+ warnx("copy_int(%u): Failed to format field.", field);
+ return -EIO;
+ }
+
+ return copy_str2(buffer, buf, size, off);
+}
+
+#define def_uint(arg, name, value) \
+static int \
+name(arg, char *buf, size_t size, off_t off) \
+{ \
+ return copy_uint((value), buf, size, off); \
+}
+
+#define def_int(arg, name, value) \
+static int \
+name(arg, char *buf, size_t size, off_t off) \
+{ \
+ return copy_int((value), buf, size, off); \
+}
+
+#define def_str(arg, name, value, max) \
+static int \
+name(arg, char *buf, size_t size, off_t off) \
+{ \
+ return copy_str((value), max, buf, size, off); \
+}
+
+def_uint(struct kinfo_proc *kp, read_pid, kp->p_pid);
+def_str(struct kinfo_proc *kp, read_comm, kp->p_comm, KI_MAXCOMLEN);
+def_str(struct kinfo_proc *kp, read_name, kp->p_name, KI_MAXCOMLEN);
+def_str(struct kinfo_proc *kp, read_emul, kp->p_emul, KI_EMULNAMELEN);
+def_str(struct kinfo_proc *kp, read_wmesg, kp->p_wmesg, KI_WMESGLEN);
+def_str(struct kinfo_proc *kp, read_login, kp->p_login, KI_MAXLOGNAME);
+def_uint(struct kinfo_proc *kp, read_uid, kp->p_uid);
+def_uint(struct kinfo_proc *kp, read_ruid, kp->p_ruid);
+def_uint(struct kinfo_proc *kp, read_gid, kp->p_gid);
+def_uint(struct kinfo_proc *kp, read_rgid, kp->p_rgid);
+def_uint(struct kinfo_proc *kp, read_priority, kp->p_priority);
+def_int(struct kinfo_proc *kp, read_nice, kp->p_nice - 20);
+def_int(struct kinfo_proc *kp, read_vm_rssize, kp->p_vm_rssize);
+def_int(struct kinfo_proc *kp, read_vm_tsize, kp->p_vm_tsize);
+def_int(struct kinfo_proc *kp, read_vm_dsize, kp->p_vm_dsize);
+def_int(struct kinfo_proc *kp, read_vm_ssize, kp->p_vm_ssize);
+
+static int
+read_flag(struct kinfo_proc *kp, char *buf, size_t size, off_t off)
+{
+ struct flag {
+ const char *name;
+ uint32_t mask;
+ };
+ static struct flag flags[] = {
+ { .name = "inktr", .mask = P_INKTR, },
+ { .name = "profpend", .mask = P_PROFPEND, },
+ { .name = "alrmpend", .mask = P_ALRMPEND, },
+ { .name = "sigsuspend", .mask = P_SIGSUSPEND, },
+ { .name = "cantsleep", .mask = P_CANTSLEEP, },
+ { .name = "wsleep", .mask = P_WSLEEP, },
+ { .name = "sintr", .mask = P_SINTR, },
+ { .name = "system", .mask = P_SYSTEM, },
+ { .name = "timeout", .mask = P_TIMEOUT, },
+ { .name = "wexit", .mask = P_WEXIT, },
+ { .name = "oweupc", .mask = P_OWEUPC, },
+ { .name = "suspsingle", .mask = P_SUSPSINGLE, },
+ { .name = "continued", .mask = P_CONTINUED, },
+ { .name = "thread", .mask = P_THREAD, },
+ { .name = "suspsig", .mask = P_SUSPSIG, },
+ { .name = "softdep", .mask = P_SOFTDEP, },
+ { .name = "cpupeg", .mask = P_CPUPEG, },
+ { .name = NULL, .mask = 0, },
+ };
+ static char buffer[200];
+
+ buffer[0] = '\0';
+ for (const struct flag *f = flags; f->name != NULL; ++f) {
+ if ((kp->p_flag & f->mask) == f->mask) {
+ strlcat(buffer, f->name, sizeof (buffer));
+ strlcat(buffer, "\n", sizeof (buffer));
+ }
+ }
+ return copy_str2(buffer, buf, size, off);
+}
+
+static int
+read_groups(struct kinfo_proc *kp, char *buf, size_t size, off_t off)
+{
+ static char buffer[KI_NGROUPS * 32];
+
+ buffer[0] = '\0';
+
+ for (int i = 0; i < kp->p_ngroups; ++i) {
+ char tmp[32];
+ snprintf(tmp, sizeof (tmp), "%u\n", kp->p_groups[i]);
+ strlcat(buffer, tmp, sizeof (buffer));
+ }
+ return copy_str2(buffer, buf, size, off);
+}
+
+static int
+read_status(struct kinfo_proc *kp, char *buf, size_t size, off_t off)
+{
+ const char *s;
+ switch (kp->p_stat) {
+ case SIDL: s = "idle"; break;
+ case SRUN: s = "runnable"; break;
+ case SSLEEP: s = "sleeping"; break;
+ case SSTOP: s = "stopped"; break;
+ case SZOMB: s = "zomb"; break; // unused
+ case SDEAD: s = "zombie"; break;
+ case SONPROC: s = "running"; break;
+ default: s = "unknown"; break;
+ }
+ return copy_str2(s, buf, size, off);
+}
+
+static int
+read_pledge(struct kinfo_proc *kp, char *buf, size_t size, off_t off)
+{
+ static char buffer[sizeof (pledgenames) / sizeof (*pledgenames) * 16];
+
+ buffer[0] = '\0';
+
+ for (int i = 0; pledgenames[i].bits != 0; ++i) {
+ const uint64_t mask = pledgenames[i].bits;
+ if ((kp->p_pledge & mask) == mask) {
+ strlcat(buffer, pledgenames[i].name, sizeof (buffer));
+ strlcat(buffer, "\n", sizeof (buffer));
+ }
+ }
+
+ return copy_str2(buffer, buf, size, off);
+}
+
+// TODO: argv, envv, fd
+
+static struct myfile files[] = {
+ { .name = "/pid", .read = read_pid, },
+ { .name = "/comm", .read = read_comm, },
+ { .name = "/name", .read = read_name, },
+ { .name = "/emul", .read = read_emul, },
+ { .name = "/wmesg", .read = read_wmesg, },
+ { .name = "/login", .read = read_login, },
+ { .name = "/uid", .read = read_uid, },
+ { .name = "/ruid", .read = read_ruid, },
+ { .name = "/gid", .read = read_gid, },
+ { .name = "/rgid", .read = read_rgid, },
+ { .name = "/priority", .read = read_priority, },
+ { .name = "/nice", .read = read_nice, },
+ { .name = "/vm_rssize", .read = read_vm_rssize, },
+ { .name = "/vm_tsize", .read = read_vm_tsize, },
+ { .name = "/vm_dsize", .read = read_vm_dsize, },
+ { .name = "/vm_ssize", .read = read_vm_ssize, },
+ { .name = "/flag", .read = read_flag, },
+ { .name = "/groups", .read = read_groups, },
+ { .name = "/status", .read = read_status, },
+ { .name = "/pledge", .read = read_pledge, },
+ { .name = NULL, .read = NULL, },
+};
+
+static int
+read_fd_type(struct kinfo_file *f, char *buf, size_t size, off_t off)
+{
+ const char *s;
+ switch (f->f_type) {
+ case DTYPE_VNODE: s = "vnode\n"; break;
+ case DTYPE_SOCKET: s = "socket\n"; break;
+ case DTYPE_PIPE: s = "pipe\n"; break;
+ case DTYPE_KQUEUE: s = "kqueue\n"; break;
+ case DTYPE_DMABUF: s = "dmabuf\n"; break;
+ case DTYPE_SYNC: s = "sync\n"; break;
+ }
+ return copy_str2(s, buf, size, off);
+}
+def_uint(struct kinfo_file *f, read_fd_fd, f->fd_fd);
+def_uint(struct kinfo_file *f, read_fd_pid, f->p_pid);
+def_uint(struct kinfo_file *f, read_fd_uid, f->f_uid);
+def_uint(struct kinfo_file *f, read_fd_gid, f->f_gid);
+def_str(struct kinfo_file *f, read_fd_comm, f->p_comm, KI_MAXCOMLEN);
+
+static struct myfdfile fd_files[] = {
+ { .name = "/type", .read = read_fd_type, },
+ { .name = "/fd", .read = read_fd_fd, },
+ { .name = "/pid", .read = read_fd_pid, },
+ { .name = "/uid", .read = read_fd_uid, },
+ { .name = "/gid", .read = read_fd_gid, },
+ { .name = "/comm", .read = read_fd_comm, },
+ { .name = NULL, .read = NULL, },
+};
blob - /dev/null
blob + 7099c363ed3e95148cd005e7917e3c4463e71ab1 (mode 644)
--- /dev/null
+++ procfs.c
+#define PLEDGENAMES
+#include <sys/param.h>
+#include <sys/sysctl.h>
+#include <sys/proc.h>
+#include <sys/file.h>
+#include <sys/pledge.h>
+
+#include <stdbool.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <fuse.h>
+#include <kvm.h>
+#include <err.h>
+
+// TODO: envv: not idx, but envar name
+
+#define MY_KERN_PROC KERN_PROC_KTHREAD
+
+static kvm_t *kd;
+struct myfile {
+ const char *name;
+ int(*read)(struct kinfo_proc *, char *, size_t, off_t);
+};
+struct myfdfile {
+ const char *name;
+ int(*read)(struct kinfo_file *, char *, size_t, off_t);
+};
+
+#include "files.h"
+
+enum path_type {
+ PATH_ROOT,
+ PATH_PID,
+ PATH_PID_FILE,
+ PATH_PID_ARGV,
+ PATH_PID_ARGV_NUM,
+ PATH_PID_ENVV,
+ PATH_PID_ENVV_NAME,
+ PATH_PID_FD,
+ PATH_PID_FD_NUM,
+ PATH_PID_FD_NUM_FILE,
+};
+
+struct path {
+ enum path_type type;
+ int pid;
+ int fd;
+ int num;
+ const char *file;
+};
+
+static int
+parse_path(const char *path, struct path *p)
+{
+ const char *suffix;
+ int num;
+
+ memset(p, 0, sizeof (*p));
+
+ if (strcmp(path, "/") == 0) {
+ p->type = PATH_ROOT;
+ return 0;
+ } else if (sscanf(path, "/%d%n", &p->pid, &num) == 1) {
+ suffix = path + num;
+
+ if (*suffix == '\0') {
+ p->type = PATH_PID;
+ return 0;
+ } else if (strncmp(suffix, "/argv", 5) == 0) {
+ suffix += 5;
+ if (*path == '\0') {
+ p->type = PATH_PID_ARGV;
+ return 0;
+ } else if (sscanf(suffix, "/%d%n", &p->num, &num) == 1) {
+ p->type = PATH_PID_ARGV_NUM;
+ return 0;
+ } else {
+ return -ENOENT;
+ }
+ } else if (strncmp(suffix, "/envv", 5) == 0) {
+ suffix += 5;
+ if (*path == '\0') {
+ p->type = PATH_PID_ENVV;
+ return 0;
+ } else if (strchr(suffix + 1, '/') == NULL) {
+ p->type = PATH_PID_ENVV_NAME;
+ p->file = suffix + 1;
+ return 0;
+ } else {
+ return -ENOENT;
+ }
+ } else if (strncmp(suffix, "/fd", 3) == 0) {
+ suffix += 3;
+ if (*path == '\0') {
+ p->type = PATH_PID_FD;
+ return 0;
+ } else if (sscanf(suffix, "/%d%n", &p->fd, &num) == 1) {
+ suffix += num;
+ if (*path == '\0') {
+ p->type = PATH_PID_FD_NUM;
+ return 0;
+ } else {
+ return -ENOENT;
+ }
+ } else {
+ return -ENOENT;
+ }
+ } else {
+ // TODO
+ return -ENOENT;
+ }
+ } else {
+ return -ENOENT;
+ }
+}
+
+static int
+fs_readdir(const char *path, void *data, fuse_fill_dir_t filler,
+ off_t off, struct fuse_file_info *ffi)
+{
+ int pid, num;
+
+ (void)ffi;
+
+ printf("readdir(%s, off=%u);\n", path, (unsigned int)off);
+
+ if (strcmp(path, "/") == 0) {
+ struct kinfo_proc *kp;
+ int nentries;
+
+ kp = kvm_getprocs(kd, MY_KERN_PROC, 0, sizeof(*kp), &nentries);
+ if (kp == NULL) {
+ warnx("readdir(/): %s", kvm_geterr(kd));
+ return -EIO;
+ }
+
+ filler(data, ".", NULL, 0);
+ filler(data, "..", NULL, 0);
+ for (int i = 0; i < nentries; ++i) {
+ char str[32];
+ snprintf (str, sizeof(str), "%d", kp[i].p_pid);
+ filler(data, str, NULL, 0);
+ }
+ return 0;
+ } else if (sscanf(path, "/%d%n", &pid, &num) == 1) {
+ const char *suffix = path + num;
+ struct kinfo_proc *kp;
+ unsigned int idx;
+ int nentries;
+
+ kp = kvm_getprocs(kd, KERN_PROC_PID, pid, sizeof(*kp), &nentries);
+ if (kp == NULL || nentries == 0) {
+ warnx("readdir(%s): %s", path, kvm_geterr(kd));
+ return -ENOENT;
+ } else if (nentries > 1) {
+ warnx("readdir(%s): Too many results", path);
+ return -EIO;
+ }
+
+ if (strcmp(suffix, "") == 0) {
+ filler(data, ".", NULL, 0);
+ filler(data, "..", NULL, 0);
+ filler(data, "parent", NULL, 0);
+ filler(data, "argv", NULL, 0);
+ filler(data, "envv", NULL, 0);
+ filler(data, "fd", NULL, 0);
+ for (const struct myfile *f = files; f->name != NULL; ++f)
+ filler(data, f->name + 1, NULL, 0);
+ return 0;
+ } else if (strcmp(suffix, "/argv") == 0) {
+ char **argv = kvm_getargv(kd, kp, 0);
+
+ if (argv == NULL) {
+ warnx("readdir(%s): %s", path, kvm_geterr(kd));
+ return -EIO;
+ }
+
+ filler(data, ".", NULL, 0);
+ filler(data, "..", NULL, 0);
+ for (size_t i = 0; argv[i] != NULL; ++i) {
+ char buffer[32];
+ int n;
+
+ n = snprintf(buffer, sizeof (buffer), "%zu", i);
+ assert(n < (int)sizeof (buffer));
+
+ filler(data, buffer, NULL, 0);
+ }
+ return 0;
+ } else if (strcmp(suffix, "/envv") == 0) {
+ char **envv = kvm_getenvv(kd, kp, 0);
+
+ if (envv == NULL) {
+ warnx("readdir(%s): %s", path, kvm_geterr(kd));
+ return -EIO;
+ }
+
+ filler(data, ".", NULL, 0);
+ filler(data, "..", NULL, 0);
+ for (size_t i = 0; envv[i] != NULL; ++i) {
+ char buffer[32];
+ int n;
+
+ n = snprintf(buffer, sizeof (buffer), "%zu", i);
+ assert(n < (int)sizeof (buffer));
+
+ filler(data, buffer, NULL, 0);
+ }
+ return 0;
+ } else if (strcmp(suffix, "/fd") == 0) {
+ struct kinfo_file *files;
+
+ files = kvm_getfiles (kd, KERN_FILE_BYPID, pid, sizeof (*files), &nentries);
+
+ if (files == NULL) {
+ warnx("readdir(%s): %s", path, kvm_geterr(kd));
+ return -EIO;
+ }
+
+ filler(data, ".", NULL, 0);
+ filler(data, "..", NULL, 0);
+
+ printf("nentries = %d\n", nentries);
+
+ for (int i = 0; i < nentries; ++i) {
+ char buffer[32];
+ snprintf(buffer, sizeof (buffer), "%d", i);
+ filler(data, buffer, NULL, 0);
+ }
+
+ return 0;
+ } else if (sscanf(suffix, "/fd/%u%n", &idx, &num) == 1) {
+ struct kinfo_file *files;
+
+ suffix += num;
+ files = kvm_getfiles(kd, KERN_FILE_BYPID, pid, sizeof (*files), &nentries);
+ if (files == NULL) {
+ warnx("readdir(%s): %s", path, kvm_geterr(kd));
+ return -EIO;
+ }
+
+ if (idx >= (unsigned int)nentries)
+ return -ENOENT;
+
+ if (strcmp(suffix, "") == 0) {
+ filler(data, ".", NULL, 0);
+ filler(data, "..", NULL, 0);
+ for (const struct myfdfile *f = fd_files; f->name != NULL; ++f) {
+ filler(data, f->name + 1, NULL, 0);
+ }
+ return 0;
+ } else {
+ for (const struct myfdfile *f = fd_files; f->name != NULL; ++f) {
+ if (strcmp(suffix, f->name) == 0)
+ return -ENOTDIR;
+ }
+ return -ENOENT;
+ }
+ } else {
+ for (const struct myfile *f = files; f->name != NULL; ++f) {
+ if (strcmp(suffix, f->name) == 0)
+ return -ENOTDIR;
+ }
+ return -ENOENT;
+ }
+ } else {
+ return -ENOENT;
+ }
+}
+
+static int
+fs_read(
+ const char *path,
+ char *buf,
+ size_t size,
+ off_t off,
+ struct fuse_file_info *ffi
+) {
+ int pid, num;
+
+ (void)ffi;
+
+ printf("read(%s);\n", path);
+
+ if (strcmp(path, "/") == 0) {
+ return -EISDIR;
+ } else if (sscanf(path, "/%d%n", &pid, &num) == 1) {
+ const char *suffix = path + num;
+ struct kinfo_proc *kp;
+ unsigned int idx;
+ int nentries;
+
+ kp = kvm_getprocs(kd, KERN_PROC_PID, pid, sizeof (*kp), &nentries);
+ if (kp == NULL || nentries == 0) {
+ warnx("read(%s): %s", path, kvm_geterr(kd));
+ return -ENOENT;
+ } else if (nentries > 1) {
+ warnx("read(%s): Too many results", path);
+ return -EIO;
+ }
+
+ if (strcmp(suffix, "") == 0) {
+ return -EISDIR;
+ } else if (strcmp(suffix, "/argv") == 0) {
+ return -EISDIR;
+ } else if (sscanf(suffix, "/argv/%u%n", &idx, &num) == 1) {
+ char **argv;
+
+ if (suffix[num] != '\0')
+ return -ENOENT;
+
+ argv = kvm_getargv(kd, kp, 0);
+ if (argv == NULL) {
+ warnx("read(%s): %s", path, kvm_geterr(kd));
+ return -EIO;
+ }
+
+ for (nentries = 0; argv[nentries] != NULL; ++nentries);
+
+ if (idx > (unsigned int)nentries)
+ return -ENOENT;
+
+ return copy_str(argv[idx], strlen(argv[idx]), buf, off, size);
+ } else if (strcmp(suffix, "/envv") == 0) {
+ return -EISDIR;
+ } else if (sscanf(suffix, "/envv/%u%n", &idx, &num) == 1) {
+ char **envv;
+
+ if (suffix[num] != '\0')
+ return -ENOENT;
+
+ envv = kvm_getenvv(kd, kp, 0);
+ if (envv == NULL) {
+ warnx("read(%s): %s", path, kvm_geterr(kd));
+ return -EIO;
+ }
+
+ for (nentries = 0; envv[nentries] != NULL; ++nentries);
+
+ if (idx > (unsigned int)nentries)
+ return -ENOENT;
+
+ return copy_str(envv[idx], strlen(envv[idx]), buf, off, size);
+ } else if (sscanf(suffix, "/fd/%u%n", &idx, &num) == 1) {
+ struct kinfo_file *files;
+
+ suffix += num;
+ files = kvm_getfiles(kd, KERN_FILE_BYPID, pid, sizeof (*files), &nentries);
+ if (files == NULL) {
+ warnx("read(%s): %s", path, kvm_geterr(kd));
+ return -EIO;
+ }
+
+ if (idx >= (unsigned int)nentries)
+ return -ENOENT;
+
+ if (strcmp(suffix, "") == 0) {
+ return -EISDIR;
+ } else {
+ for (const struct myfdfile *f = fd_files; f->name != NULL; ++f) {
+ if (strcmp(suffix, f->name) == 0)
+ return f->read(&files[idx], buf, size, off);
+ }
+ return -ENOENT;
+ }
+ } else {
+ for (const struct myfile *f = files; f->name != NULL; ++f) {
+ if (strcmp(suffix, f->name) == 0)
+ return f->read(kp, buf, size, off);
+ }
+ return -ENOENT;
+ }
+ } else {
+ return -ENOENT;
+ }
+}
+
+static int
+fs_open(const char *path, struct fuse_file_info *ffi)
+{
+ int pid, num;
+
+ (void)ffi;
+
+ printf("open(%s);\n", path);
+
+ if (strcmp(path, "/") == 0) {
+ return 0;
+ } else if (sscanf(path, "/%d%n", &pid, &num) == 1) {
+ const char *suffix = path + num;
+ struct kinfo_proc *kp;
+ unsigned int idx;
+ int nentries;
+ bool found;
+
+ kp = kvm_getprocs(kd, KERN_PROC_PID, pid, sizeof (*kp), &nentries);
+ if (kp == NULL || nentries == 0) {
+ warnx("open(%s): %s", path, kvm_geterr(kd));
+ return -ENOENT;
+ } else if (nentries > 1) {
+ warnx("open(%s): Too many results", path);
+ return -EIO;
+ }
+
+ if (strcmp(suffix, "/argv") == 0) {
+ return 0;
+ } else if (sscanf(suffix, "/argv/%u%n", &idx, &num) == 1) {
+ if (suffix[num] != '\0')
+ return -ENOENT;
+ return 0;
+ } else if (strcmp(suffix, "/envv") == 0) {
+ return 0;
+ } else if (sscanf(suffix, "/envv/%u%n", &idx, &num) == 1) {
+ if (suffix[num] != '\0')
+ return -ENOENT;
+ return 0;
+ } else if (sscanf(suffix, "/fd/%u%n", &idx, &num) == 1) {
+ suffix += num;
+ if (strcmp(suffix, "") == 0) {
+ return 0;
+ } else {
+ found = false;
+ for (const struct myfdfile *f = fd_files; f->name != NULL; ++f) {
+ if (strcmp(suffix, f->name) == 0) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ printf("Invalid fd suffix: '%s'\n", suffix);
+ return -ENOENT;
+ }
+ return 0;
+ }
+ } else {
+ found = false;
+ for (const struct myfile *f = files; f->name != NULL; ++f) {
+ if (strcmp(suffix, f->name) == 0) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ printf("Invalid suffix: '%s'\n", suffix);
+ return -ENOENT;
+ }
+ return 0;
+ }
+ } else {
+ return -ENOENT;
+ }
+}
+
+static int
+fs_getattr(const char *path, struct stat *st)
+{
+ int pid, num;
+
+ printf("getattr(%s);\n", path);
+
+ st->st_blksize = 512;
+
+ if (strcmp(path, "/") == 0) {
+ st->st_mode = S_IFDIR | 0755;
+ st->st_nlink = 2;
+ st->st_size = 512;
+ return 0;
+ } else if (sscanf(path, "/%d%n", &pid, &num) == 1) {
+ const char *suffix = path + num;
+ struct kinfo_proc *kp;
+ int nentries;
+ unsigned int idx;
+
+ kp = kvm_getprocs(kd, KERN_PROC_PID, pid, sizeof (*kp), &nentries);
+ if (kp == NULL || nentries == 0) {
+ warnx("getattr(%s): %s", path, kvm_geterr(kd));
+ return -ENOENT;
+ } else if (nentries > 1) {
+ warnx("getattr(%s): Too many results", path);
+ return -EIO;
+ }
+
+ if (strcmp(suffix, "") == 0) {
+ st->st_mode = S_IFDIR | 0755;
+ st->st_nlink = 2;
+ st->st_size = 512;
+ return 0;
+ } else if (strcmp(suffix, "/parent") == 0) {
+ st->st_mode = S_IFLNK | 0755;
+ st->st_nlink = 1;
+ st->st_size = snprintf(NULL, 0, "%d", kp->p_ppid);
+ return 0;
+ } else if (strcmp(suffix, "/argv") == 0) {
+ st->st_mode = S_IFDIR | 0755;
+ st->st_nlink = 2;
+ st->st_size = 512;
+ return 0;
+ } else if (sscanf(suffix, "/argv/%u%n", &idx, &num) == 1) {
+ char **argv;
+
+ if (suffix[num] != '\0')
+ return -ENOENT;
+
+ argv = kvm_getargv(kd, kp, 0);
+ if (argv == NULL) {
+ warnx("getattr(%s): %s", path, kvm_geterr(kd));
+ return -EIO;
+ }
+
+ for (nentries = 0; argv[nentries] != NULL; ++nentries);
+ if (idx >= (unsigned int)nentries)
+ return -ENOENT;
+
+ st->st_mode = S_IFREG | 0644;
+ st->st_nlink = 1;
+ st->st_size = strlen(argv[idx]);
+ return 0;
+ } else if (strcmp(suffix, "/envv") == 0) {
+ st->st_mode = S_IFDIR | 0755;
+ st->st_nlink = 2;
+ st->st_size = 512;
+ return 0;
+ } else if (sscanf(suffix, "/envv/%u%n", &idx, &num) == 1) {
+ char **envv;
+
+ if (suffix[num] != '\0')
+ return -ENOENT;
+
+ envv = kvm_getenvv(kd, kp, 0);
+ if (envv == NULL) {
+ warnx("getattr(%s): %s", path, kvm_geterr(kd));
+ return -EIO;
+ }
+
+ for (nentries = 0; envv[nentries] != NULL; ++nentries);
+ if (idx >= (unsigned int)nentries)
+ return -ENOENT;
+
+ st->st_mode = S_IFREG | 0644;
+ st->st_nlink = 1;
+ st->st_size = strlen(envv[idx]);
+ return 0;
+ } else if (strcmp(suffix, "/fd") == 0) {
+ st->st_mode = S_IFDIR | 0755;
+ st->st_nlink = 2;
+ st->st_size = 512;
+ return 0;
+ } else if (sscanf(suffix, "/fd/%u%n", &idx, &num) == 1) {
+ struct kinfo_file *files;
+
+ suffix += num;
+ files = kvm_getfiles(kd, KERN_FILE_BYPID, pid, sizeof (*files), &nentries);
+ if (files == NULL) {
+ warnx("getattr(%s): %s", path, kvm_geterr(kd));
+ return -EIO;
+ }
+
+ if (strcmp(suffix, "") == 0) {
+ st->st_mode = S_IFDIR | 0755;
+ st->st_nlink = 2;
+ st->st_size = 512;
+ return 0;
+ } else {
+ for (const struct myfdfile *f = fd_files; f->name != NULL; ++f) {
+ if (strcmp(suffix, f->name) == 0) {
+ st->st_mode = S_IFREG | 0644;
+ st->st_nlink = 1;
+ st->st_size = 0;
+ return 0;
+ }
+ }
+ return -ENOENT;
+ }
+ } else {
+ for (const struct myfile *f = files; f->name != NULL; ++f) {
+ if (strcmp(suffix, f->name) == 0) {
+ st->st_mode = S_IFREG;
+ st->st_nlink = 1;
+ st->st_size = 0;
+ return 0;
+ }
+ }
+ return -ENOENT;
+ }
+ } else {
+ return -ENOENT;
+ }
+}
+
+static int
+fs_readlink(const char *path, char *buf, size_t size)
+{
+ int pid, num;
+
+ if (sscanf(path, "/%d%n", &pid, &num) == 1) {
+ const char *suffix = path + num;
+ struct kinfo_proc *kp;
+ int nentries;
+
+ kp = kvm_getprocs(kd, KERN_PROC_PID, pid, sizeof (*kp), &nentries);
+ if (kp == NULL || nentries == 0) {
+ warnx("readlink(%s): %s", path, kvm_geterr(kd));
+ return -ENOENT;
+ } else if (nentries > 1) {
+ warnx("readlink(%s): Too many results", path);
+ return -EIO;
+ }
+
+ if (strcmp(suffix, "/parent") == 0) {
+ snprintf(buf, size, "../%d", kp->p_ppid);
+ return 0;
+ } else {
+ return -EINVAL;
+ }
+ } else {
+ return -ENOENT;
+ }
+}
+
+struct fuse_operations fsops = {
+ .readdir = fs_readdir,
+ .read = fs_read,
+ .open = fs_open,
+ .getattr = fs_getattr,
+ .readlink = fs_readlink,
+};
+
+int
+main(int argc, char **argv)
+{
+ char errbuf[_POSIX2_LINE_MAX];
+ kd = kvm_openfiles(NULL, NULL, NULL, KVM_NO_FILES, errbuf);
+ if (kd == NULL)
+ errx(1, "%s", errbuf);
+
+ return (fuse_main(argc, argv, &fsops, NULL));
+}