| /* Copyright (c) 2018 Google Inc | 
 |  * Barret Rhoden <brho@cs.berkeley.edu> | 
 |  * See LICENSE for details. | 
 |  * | 
 |  * #kfs, in-memory ram filesystem, pulling from the kernel's embedded CPIO | 
 |  */ | 
 |  | 
 | #include <ns.h> | 
 | #include <kmalloc.h> | 
 | #include <string.h> | 
 | #include <stdio.h> | 
 | #include <assert.h> | 
 | #include <error.h> | 
 | #include <tree_file.h> | 
 | #include <pmap.h> | 
 | #include <cpio.h> | 
 |  | 
 | struct dev kfs_devtab; | 
 |  | 
 | struct kfs { | 
 | 	struct tree_filesystem		tfs; | 
 | 	atomic_t			qid; | 
 | } kfs; | 
 |  | 
 | static uint64_t kfs_get_qid_path(void) | 
 | { | 
 | 	return atomic_fetch_and_add(&kfs.qid, 1); | 
 | } | 
 |  | 
 | static char *devname(void) | 
 | { | 
 | 	return kfs_devtab.name; | 
 | } | 
 |  | 
 | static void kfs_tf_free(struct tree_file *tf) | 
 | { | 
 | 	/* We have nothing special hanging off the TF */ | 
 | } | 
 |  | 
 | static void kfs_tf_unlink(struct tree_file *parent, struct tree_file *child) | 
 | { | 
 | 	/* This is the "+1 for existing" ref. */ | 
 | 	tf_kref_put(child); | 
 | } | 
 |  | 
 | static void __kfs_tf_init(struct tree_file *tf, int dir_type, int dir_dev, | 
 |                           struct username *user, int perm) | 
 | { | 
 | 	struct dir *dir = &tf->file.dir; | 
 |  | 
 | 	fs_file_init_dir(&tf->file, dir_type, dir_dev, user, perm); | 
 | 	dir->qid.path = kfs_get_qid_path(); | 
 | 	dir->qid.vers = 0; | 
 | 	/* This is the "+1 for existing" ref.  There is no backing store for the | 
 | 	 * FS, such as a disk or 9p, so we can't get rid of a file until it is | 
 | 	 * unlinked and decreffed.  Note that KFS doesn't use pruners or | 
 | 	 * anything else. */ | 
 | 	__kref_get(&tf->kref, 1); | 
 | } | 
 |  | 
 | /* Note: If your TFS doesn't support symlinks, you need to error out */ | 
 | static void kfs_tf_create(struct tree_file *parent, struct tree_file *child, | 
 |                           int perm) | 
 | { | 
 | 	__kfs_tf_init(child, parent->file.dir.type, parent->file.dir.dev, &eve, | 
 | 	              perm); | 
 | } | 
 |  | 
 | static void kfs_tf_rename(struct tree_file *tf, struct tree_file *old_parent, | 
 |                           struct tree_file *new_parent, const char *name, | 
 |                           int flags) | 
 | { | 
 | 	/* We don't have a backend, so we don't need to do anything additional | 
 | 	 * for rename. */ | 
 | } | 
 |  | 
 | static bool kfs_tf_has_children(struct tree_file *parent) | 
 | { | 
 | 	/* The tree_file parent list is complete and not merely a cache for a | 
 | 	 * real backend. */ | 
 | 	return !list_empty(&parent->children); | 
 | } | 
 |  | 
 | struct tree_file_ops kfs_tf_ops = { | 
 | 	.free = kfs_tf_free, | 
 | 	.unlink = kfs_tf_unlink, | 
 | 	.lookup = NULL, | 
 | 	.create = kfs_tf_create, | 
 | 	.rename = kfs_tf_rename, | 
 | 	.has_children = kfs_tf_has_children, | 
 | }; | 
 |  | 
 | /* Fills page with its contents from its backing store file.  For KFS, that | 
 |  * means we're creating or extending a file, and the contents are 0.  Note the | 
 |  * page/offset might be beyond the current file length, based on the current | 
 |  * pagemap code. */ | 
 | static int kfs_pm_readpage(struct page_map *pm, struct page *pg) | 
 | { | 
 | 	memset(page2kva(pg), 0, PGSIZE); | 
 | 	atomic_or(&pg->pg_flags, PG_UPTODATE); | 
 | 	/* Pretend that we blocked while filing this page.  This catches a lot | 
 | 	 * of bugs.  It does slightly slow down the kernel, but it's only when | 
 | 	 * filling the page cache, and considering we are using a RAMFS, you | 
 | 	 * shouldn't measure things that actually rely on KFS's performance. */ | 
 | 	kthread_usleep(1); | 
 | 	return 0; | 
 | } | 
 |  | 
 | /* Meant to take the page from PM and flush to backing store.  There is no | 
 |  * backing store. */ | 
 | static int kfs_pm_writepage(struct page_map *pm, struct page *pg) | 
 | { | 
 | 	return 0; | 
 | } | 
 |  | 
 | static void kfs_fs_punch_hole(struct fs_file *f, off64_t begin, off64_t end) | 
 | { | 
 | } | 
 |  | 
 | static bool kfs_fs_can_grow_to(struct fs_file *f, size_t len) | 
 | { | 
 | 	/* TODO: implement some sort of memory limit */ | 
 | 	return true; | 
 | } | 
 |  | 
 | struct fs_file_ops kfs_fs_ops = { | 
 | 	.readpage = kfs_pm_readpage, | 
 | 	.writepage = kfs_pm_writepage, | 
 | 	.punch_hole = kfs_fs_punch_hole, | 
 | 	.can_grow_to = kfs_fs_can_grow_to, | 
 | }; | 
 |  | 
 | /* Consumes root's chan, even on error. */ | 
 | static struct chan *__add_kfs_dir(struct chan *root, char *path, | 
 |                                   struct cpio_bin_hdr *c_bhdr) | 
 | { | 
 | 	ERRSTACK(1); | 
 | 	struct chan *c; | 
 |  | 
 | 	if (waserror()) { | 
 | 		warn("failed to add %s", path); | 
 | 		cclose(root); | 
 | 		poperror(); | 
 | 		return NULL; | 
 | 	} | 
 | 	c = namec_from(root, path, Acreate, O_EXCL, DMDIR | c_bhdr->c_mode, | 
 | 		       NULL); | 
 | 	poperror(); | 
 | 	return c; | 
 | } | 
 |  | 
 | static struct chan *__add_kfs_symlink(struct chan *root, char *path, | 
 |                                       struct cpio_bin_hdr *c_bhdr) | 
 | { | 
 | 	ERRSTACK(1); | 
 | 	struct chan *c; | 
 | 	char target[c_bhdr->c_filesize + 1]; | 
 |  | 
 | 	if (waserror()) { | 
 | 		warn("failed to add %s", path); | 
 | 		cclose(root); | 
 | 		poperror(); | 
 | 		return NULL; | 
 | 	} | 
 | 	strncpy(target, c_bhdr->c_filestart, c_bhdr->c_filesize); | 
 | 	target[c_bhdr->c_filesize] = 0; | 
 | 	c = namec_from(root, path, Acreate, O_EXCL, | 
 | 	               DMSYMLINK | S_IRWXU | S_IRWXG | S_IRWXO, target); | 
 | 	poperror(); | 
 | 	return c; | 
 | } | 
 |  | 
 | static struct chan *__add_kfs_file(struct chan *root, char *path, | 
 |                                    struct cpio_bin_hdr *c_bhdr) | 
 | { | 
 | 	ERRSTACK(1); | 
 | 	struct chan *c; | 
 | 	off64_t offset = 0; | 
 | 	size_t ret, amt = c_bhdr->c_filesize; | 
 | 	void *buf = c_bhdr->c_filestart; | 
 |  | 
 | 	if (waserror()) { | 
 | 		warn("failed to add %s", path); | 
 | 		cclose(root); | 
 | 		poperror(); | 
 | 		return NULL; | 
 | 	} | 
 | 	c = namec_from(root, path, Acreate, O_EXCL | O_RDWR, c_bhdr->c_mode, | 
 | 		       NULL); | 
 | 	poperror(); | 
 | 	if (waserror()) { | 
 | 		warn("failed to modify %s", path); | 
 | 		cclose(c); | 
 | 		poperror(); | 
 | 		return NULL; | 
 | 	} | 
 | 	while (amt) { | 
 | 		ret = devtab[c->type].write(c, buf + offset, amt, offset); | 
 | 		amt -= ret; | 
 | 		offset += ret; | 
 | 	} | 
 | 	poperror(); | 
 | 	return c; | 
 | } | 
 |  | 
 | static int add_kfs_entry(struct cpio_bin_hdr *c_bhdr, void *cb_arg) | 
 | { | 
 | 	struct tree_file *root = cb_arg; | 
 | 	char *path = c_bhdr->c_filename; | 
 | 	struct chan *c; | 
 | 	struct tree_file *tf; | 
 | 	struct timespec ts; | 
 |  | 
 | 	/* Root of the FS, already part of KFS */ | 
 | 	if (!strcmp(path, ".")) | 
 | 		return 0; | 
 | 	c = tree_file_alloc_chan(root, &kfs_devtab, "#kfs"); | 
 | 	switch (c_bhdr->c_mode & CPIO_FILE_MASK) { | 
 | 	case (CPIO_DIRECTORY): | 
 | 		c = __add_kfs_dir(c, path, c_bhdr); | 
 | 		break; | 
 | 	case (CPIO_SYMLINK): | 
 | 		c = __add_kfs_symlink(c, path, c_bhdr); | 
 | 		break; | 
 | 	case (CPIO_REG_FILE): | 
 | 		c = __add_kfs_file(c, path, c_bhdr); | 
 | 		break; | 
 | 	default: | 
 | 		cclose(c); | 
 | 		warn("Unknown file type %d in the CPIO!", | 
 | 		     c_bhdr->c_mode & CPIO_FILE_MASK); | 
 | 		return -1; | 
 | 	} | 
 | 	if (!c) | 
 | 		return -1; | 
 | 	tf = chan_to_tree_file(c); | 
 | 	ts.tv_sec = c_bhdr->c_mtime; | 
 | 	ts.tv_nsec = 0; | 
 | 	/* Lockless */ | 
 | 	__set_acmtime_to(&tf->file, FSF_ATIME | FSF_BTIME | FSF_CTIME | | 
 | 			 FSF_MTIME, &ts); | 
 | 	/* TODO: consider UID/GID.  Right now, everything is owned by eve. */ | 
 | 	cclose(c); | 
 | 	return 0; | 
 | } | 
 |  | 
 | struct cpio_info { | 
 | 	void *base; | 
 | 	size_t sz; | 
 | }; | 
 |  | 
 | static void kfs_get_cpio_info(struct cpio_info *ci) | 
 | { | 
 | 	extern uint8_t _binary_obj_kern_initramfs_cpio_size[]; | 
 | 	extern uint8_t _binary_obj_kern_initramfs_cpio_start[]; | 
 |  | 
 | 	ci->base = (void*)_binary_obj_kern_initramfs_cpio_start; | 
 | 	ci->sz = (size_t)_binary_obj_kern_initramfs_cpio_size; | 
 | } | 
 |  | 
 | static void kfs_extract_cpio(struct cpio_info *ci) | 
 | { | 
 | 	parse_cpio_entries(ci->base, ci->sz, add_kfs_entry, kfs.tfs.root); | 
 | } | 
 |  | 
 | static void kfs_free_cpio(struct cpio_info *ci) | 
 | { | 
 | 	void *base = ci->base; | 
 | 	size_t sz = ci->sz; | 
 |  | 
 | 	/* The base arena requires page aligned, page sized segments. */ | 
 | 	sz -= ROUNDUP(base, PGSIZE) - base; | 
 | 	sz = ROUNDDOWN(sz, PGSIZE); | 
 | 	base = ROUNDUP(base, PGSIZE); | 
 | 	/* Careful - the CPIO is part of the kernel blob and a code address. */ | 
 | 	base = KBASEADDR(base); | 
 | 	printk("Freeing %d MB of CPIO RAM\n", sz >> 20); | 
 | 	arena_add(base_arena, base, sz, MEM_WAIT); | 
 | } | 
 |  | 
 | static void kfs_init(void) | 
 | { | 
 | 	struct tree_filesystem *tfs = &kfs.tfs; | 
 | 	struct cpio_info ci[1]; | 
 |  | 
 | 	/* This gives us one ref on tfs->root. */ | 
 | 	tfs_init(tfs); | 
 | 	tfs->tf_ops = kfs_tf_ops; | 
 | 	tfs->fs_ops = kfs_fs_ops; | 
 | 	/* Note this gives us the "+1 for existing" ref on tfs->root. */ | 
 | 	__kfs_tf_init(tfs->root, &kfs_devtab - devtab, 0, &eve, DMDIR | 0777); | 
 | 	/* Other devices might want to create things like kthreads that run the | 
 | 	 * LRU pruner or PM sweeper. */ | 
 | 	kfs_get_cpio_info(ci); | 
 | 	kfs_extract_cpio(ci); | 
 | 	kfs_free_cpio(ci); | 
 | 	/* This has another kref.  Note that each attach gets a ref and each new | 
 | 	 * process gets a ref. */ | 
 | 	kern_slash = tree_file_alloc_chan(kfs.tfs.root, &kfs_devtab, "/"); | 
 | } | 
 |  | 
 | static struct chan *kfs_attach(char *spec) | 
 | { | 
 | 	/* The root TF has a new kref for the attach chan */ | 
 | 	return tree_file_alloc_chan(kfs.tfs.root, &kfs_devtab, "#kfs"); | 
 | } | 
 |  | 
 | static unsigned long kfs_chan_ctl(struct chan *c, int op, unsigned long a1, | 
 |                                   unsigned long a2, unsigned long a3, | 
 |                                   unsigned long a4) | 
 | { | 
 | 	switch (op) { | 
 | 	case CCTL_SYNC: | 
 | 		return 0; | 
 | 	default: | 
 | 		return tree_chan_ctl(c, op, a1, a2, a3, a4); | 
 | 	} | 
 | } | 
 |  | 
 | struct dev kfs_devtab __devtab = { | 
 | 	.name = "kfs", | 
 | 	.reset = devreset, | 
 | 	.init = kfs_init, | 
 | 	.shutdown = devshutdown, | 
 | 	.attach = kfs_attach, | 
 | 	.walk = tree_chan_walk, | 
 | 	.stat = tree_chan_stat, | 
 | 	.open = tree_chan_open, | 
 | 	.create = tree_chan_create, | 
 | 	.close = tree_chan_close, | 
 | 	.read = tree_chan_read, | 
 | 	.bread = devbread, | 
 | 	.write = tree_chan_write, | 
 | 	.bwrite = devbwrite, | 
 | 	.remove = tree_chan_remove, | 
 | 	.rename = tree_chan_rename, | 
 | 	.wstat = tree_chan_wstat, | 
 | 	.power = devpower, | 
 | 	.chaninfo = devchaninfo, | 
 | 	.mmap = tree_chan_mmap, | 
 | 	.chan_ctl = kfs_chan_ctl, | 
 | }; |