Commit Diff


commit - /dev/null
commit + 0a04a7c0dfaaf17dd49a7e4ed11d1952375614c6
blob - /dev/null
blob + b08600347e76b7f83bcd5007aef65da71483e99e (mode 644)
--- /dev/null
+++ Makefile
@@ -0,0 +1,17 @@
+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
@@ -0,0 +1,244 @@
+
+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
@@ -0,0 +1,641 @@
+#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));
+}