blob: f8f2eebb6528bbebe709d4b6d2b1bd3494f417e1 [file] [log] [blame]
/* 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;
struct tmpfs {
struct tree_filesystem tfs;
atomic_t qid;
struct kref users;
};
static uint64_t tmpfs_get_qid_path(struct tmpfs *tmpfs)
{
return atomic_fetch_and_add(&tmpfs->qid, 1);
}
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((struct tmpfs*)tf->tfs);
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);
kfree(tmpfs);
}
static struct tmpfs *chan_to_tmpfs(struct chan *c)
{
struct tree_file *tf = chan_to_tree_file(c);
return (struct tmpfs*)(tf->tfs);
}
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 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);
/* 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 = devreset,
.init = devinit,
.shutdown = devshutdown,
.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,
};