| /* Copyright (c) 2018 Google Inc | 
 |  * Barret Rhoden <brho@cs.berkeley.edu> | 
 |  * See LICENSE for details. | 
 |  * | 
 |  * #tmpfs, in-memory ram filesystem instance. | 
 |  * | 
 |  * Compared to #kfs, there can be many #tmpfs instances, all of which are | 
 |  * independent and clean themeselves up when they are detached and the last chan | 
 |  * is closed.  A few notes: | 
 |  * - The FS root ("/") can't be removed.  When you unmount, it gets | 
 |  *   closed/decreffed. | 
 |  * - The tmpfs will exist for the life of any open chans, including the mount. | 
 |  *   You can e.g. cd into a subdir, unmount /, and the tmpfs will stay alive til | 
 |  *   you leave the directory (basically close the chan).  You can do the same | 
 |  *   thing with open_at(). | 
 |  * - The root tree_file will not be deleted so long as you have an open chan. | 
 |  *   Any open chan on a subdir/subfile will hold refs on the root.  The mount | 
 |  *   point will also hold those refs.  We also hold an additional +1 on the root | 
 |  *   TF, which we drop once we have no users and we've purged the tree. */ | 
 |  | 
 | #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 tmpfs_devtab; | 
 |  | 
 | /* This is a little wasteful - a u16_pool will work too, though I'd like to | 
 |  * exercise bits of the arena code. */ | 
 | static struct arena *tmpfs_ids; | 
 |  | 
 | struct tmpfs { | 
 | 	struct tree_filesystem		tfs; | 
 | 	atomic_t			qid_file; | 
 | 	uint16_t			id; | 
 | 	struct kref			users; | 
 | }; | 
 |  | 
 | static struct tmpfs *tree_file_to_tmpfs(struct tree_file *tf) | 
 | { | 
 | 	return (struct tmpfs*)(tf->tfs); | 
 | } | 
 |  | 
 | #define TMPFS_MAX_QID_FILE ((1ULL << 48) - 1) | 
 | #define TMPFS_MAX_ID ((1 << 16) - 1) | 
 |  | 
 | /* Qids are made up of 16 bits for the ID, 48 for the file. */ | 
 | static uint64_t tmpfs_get_qid_path(struct tmpfs *tmpfs) | 
 | { | 
 | 	uint64_t ret = atomic_fetch_and_add(&tmpfs->qid_file, 1); | 
 |  | 
 | 	/* Wrap-around of qid_file won't happen in our lifetime */ | 
 | 	if (ret > TMPFS_MAX_QID_FILE) | 
 | 		error(ENOSPC, "out of tmpfs files"); | 
 | 	return ((uint64_t)tmpfs->id << 16) | ret; | 
 | } | 
 |  | 
 | static char *devname(void) | 
 | { | 
 | 	return tmpfs_devtab.name; | 
 | } | 
 |  | 
 | static void tmpfs_tf_free(struct tree_file *tf) | 
 | { | 
 | 	/* We have nothing special hanging off the TF */ | 
 | } | 
 |  | 
 | static void tmpfs_tf_unlink(struct tree_file *parent, struct tree_file *child) | 
 | { | 
 | 	/* This is the "+1 for existing" ref. */ | 
 | 	tf_kref_put(child); | 
 | } | 
 |  | 
 | static void __tmpfs_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 = tmpfs_get_qid_path(tree_file_to_tmpfs(tf)); | 
 | 	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 tmpfs_tf_create(struct tree_file *parent, struct tree_file *child, | 
 |                             int perm) | 
 | { | 
 | 	__tmpfs_tf_init(child, parent->file.dir.type, parent->file.dir.dev, | 
 | 			&eve, perm); | 
 | } | 
 |  | 
 | static void tmpfs_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 tmpfs_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 tmpfs_tf_ops = { | 
 | 	.free = tmpfs_tf_free, | 
 | 	.unlink = tmpfs_tf_unlink, | 
 | 	.lookup = NULL, | 
 | 	.create = tmpfs_tf_create, | 
 | 	.rename = tmpfs_tf_rename, | 
 | 	.has_children = tmpfs_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 tmpfs_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 tmpfs_pm_writepage(struct page_map *pm, struct page *pg) | 
 | { | 
 | 	return 0; | 
 | } | 
 |  | 
 | static void tmpfs_fs_punch_hole(struct fs_file *f, off64_t begin, off64_t end) | 
 | { | 
 | } | 
 |  | 
 | static bool tmpfs_fs_can_grow_to(struct fs_file *f, size_t len) | 
 | { | 
 | 	/* TODO: implement some sort of memory limit */ | 
 | 	return true; | 
 | } | 
 |  | 
 | struct fs_file_ops tmpfs_fs_ops = { | 
 | 	.readpage = tmpfs_pm_readpage, | 
 | 	.writepage = tmpfs_pm_writepage, | 
 | 	.punch_hole = tmpfs_fs_punch_hole, | 
 | 	.can_grow_to = tmpfs_fs_can_grow_to, | 
 | }; | 
 |  | 
 | static void purge_cb(struct tree_file *tf) | 
 | { | 
 | 	/* this is the +1 for existing */ | 
 | 	tf_kref_put(tf); | 
 | } | 
 |  | 
 | static void tmpfs_release(struct kref *kref) | 
 | { | 
 | 	struct tmpfs *tmpfs = container_of(kref, struct tmpfs, users); | 
 |  | 
 | 	tfs_frontend_purge(&tmpfs->tfs, purge_cb); | 
 | 	/* this is the ref from attach */ | 
 | 	assert(kref_refcnt(&tmpfs->tfs.root->kref) == 1); | 
 | 	tf_kref_put(tmpfs->tfs.root); | 
 | 	/* ensures __tf_free() happens before tfs_destroy */ | 
 | 	rcu_barrier(); | 
 | 	tfs_destroy(&tmpfs->tfs); | 
 | 	arena_free(tmpfs_ids, (void*)(uintptr_t)tmpfs->id, 1); | 
 | 	kfree(tmpfs); | 
 | } | 
 |  | 
 | static struct tmpfs *chan_to_tmpfs(struct chan *c) | 
 | { | 
 | 	return tree_file_to_tmpfs(chan_to_tree_file(c)); | 
 | } | 
 |  | 
 | static void incref_tmpfs_chan(struct chan *c) | 
 | { | 
 | 	kref_get(&chan_to_tmpfs(c)->users, 1); | 
 | } | 
 |  | 
 | static void decref_tmpfs_chan(struct chan *c) | 
 | { | 
 | 	kref_put(&chan_to_tmpfs(c)->users); | 
 | } | 
 |  | 
 | static void tmpfs_reset(void) | 
 | { | 
 | 	/* Note you can't make an arena that hands out '0'.  That's baked into | 
 | 	 * the interface.  I'm OK with it for now.  Because of that, the *size* | 
 | 	 * of the span is 2^16 - 1; can't count '0'. */ | 
 | 	tmpfs_ids = arena_create("tmpfs_ids", (void*)1, TMPFS_MAX_ID, 1, | 
 | 				 NULL, NULL, NULL, 0, ARENA_NEXTFIT | MEM_WAIT); | 
 | } | 
 |  | 
 | /* This is pretty hokey - we never call shutdown, and I don't know if 9ns has | 
 |  * any protections about device use before calling shutdown. */ | 
 | static void tmpfs_shutdown(void) | 
 | { | 
 | 	arena_destroy(tmpfs_ids); | 
 | 	tmpfs_ids = NULL; | 
 | } | 
 |  | 
 | static struct chan *tmpfs_attach(char *spec) | 
 | { | 
 | 	struct tree_filesystem *tfs = kzmalloc(sizeof(struct tmpfs), MEM_WAIT); | 
 | 	struct tmpfs *tmpfs = (struct tmpfs*)tfs; | 
 |  | 
 | 	/* All distinct chans get a ref on the filesystem, so that we can | 
 | 	 * destroy it when the last user disconnects/closes. */ | 
 | 	kref_init(&tmpfs->users, tmpfs_release, 1); | 
 | 	tmpfs->id = (uint16_t)(uintptr_t)arena_alloc(tmpfs_ids, 1, MEM_WAIT); | 
 |  | 
 | 	/* This gives us one ref on root, dropped during tmpfs_release(). */ | 
 | 	tfs_init(tfs); | 
 | 	tfs->tf_ops = tmpfs_tf_ops; | 
 | 	tfs->fs_ops = tmpfs_fs_ops; | 
 |  | 
 | 	/* This gives us an extra refcnt on tfs->root.  This is "+1 for | 
 | 	 * existing." It is decreffed during the purge CB. */ | 
 | 	__tmpfs_tf_init(tfs->root, &tmpfs_devtab - devtab, 0, &eve, DMDIR | | 
 | 			0777); | 
 | 	/* This also increfs, copying tfs->root's ref for the chan it returns.*/ | 
 | 	return tree_file_alloc_chan(tfs->root, &tmpfs_devtab, "#tmpfs"); | 
 | } | 
 |  | 
 | static struct walkqid *tmpfs_walk(struct chan *c, struct chan *nc, char **name, | 
 |                                   unsigned int nname) | 
 | { | 
 | 	struct walkqid *wq = tree_chan_walk(c, nc, name, nname); | 
 |  | 
 | 	if (wq && wq->clone && (wq->clone != c)) | 
 | 		incref_tmpfs_chan(wq->clone); | 
 | 	return wq; | 
 | } | 
 |  | 
 | static void tmpfs_close(struct chan *c) | 
 | { | 
 | 	tree_chan_close(c); | 
 | 	decref_tmpfs_chan(c); | 
 | } | 
 |  | 
 | static void tmpfs_remove(struct chan *c) | 
 | { | 
 | 	ERRSTACK(1); | 
 | 	struct tmpfs *tmpfs = chan_to_tmpfs(c); | 
 |  | 
 | 	/* This is a bit of a pain - when remove fails, we won't get a chance to | 
 | 	 * close the chan.  See notes in tree_chan_remove() and sysremove(). */ | 
 | 	if (waserror()) { | 
 | 		kref_put(&tmpfs->users); | 
 | 		nexterror(); | 
 | 	} | 
 | 	tree_chan_remove(c); | 
 | 	kref_put(&tmpfs->users); | 
 | 	poperror(); | 
 | } | 
 |  | 
 | void tmpfs_rename(struct chan *c, struct chan *new_p_c, const char *name, | 
 |                   int flags) | 
 | { | 
 | 	struct tmpfs *tmpfs_old = chan_to_tmpfs(c); | 
 | 	struct tmpfs *tmpfs_new = chan_to_tmpfs(new_p_c); | 
 |  | 
 | 	/* namec checked that both were from the same device.  It is our | 
 | 	 * responsibility to make sure they are the same version. */ | 
 | 	if (tmpfs_old != tmpfs_new) | 
 | 		error(EXDEV, "can't rename across tmpfs instances"); | 
 | 	tree_chan_rename(c, new_p_c, name, flags); | 
 | } | 
 |  | 
 | static unsigned long tmpfs_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 tmpfs_devtab __devtab = { | 
 | 	.name = "tmpfs", | 
 | 	.reset = tmpfs_reset, | 
 | 	.init = devinit, | 
 | 	.shutdown = tmpfs_shutdown, | 
 | 	.attach = tmpfs_attach, | 
 | 	.walk = tmpfs_walk, | 
 | 	.stat = tree_chan_stat, | 
 | 	.open = tree_chan_open, | 
 | 	.create = tree_chan_create, | 
 | 	.close = tmpfs_close, | 
 | 	.read = tree_chan_read, | 
 | 	.bread = devbread, | 
 | 	.write = tree_chan_write, | 
 | 	.bwrite = devbwrite, | 
 | 	.remove = tmpfs_remove, | 
 | 	.rename = tmpfs_rename, | 
 | 	.wstat = tree_chan_wstat, | 
 | 	.power = devpower, | 
 | 	.chaninfo = devchaninfo, | 
 | 	.mmap = tree_chan_mmap, | 
 | 	.chan_ctl = tmpfs_chan_ctl, | 
 | }; |