akaros/kern/drivers/dev/tmpfs.c
<<
>>
Prefs
   1/* Copyright (c) 2018 Google Inc
   2 * Barret Rhoden <brho@cs.berkeley.edu>
   3 * See LICENSE for details.
   4 *
   5 * #tmpfs, in-memory ram filesystem instance.
   6 *
   7 * Compared to #kfs, there can be many #tmpfs instances, all of which are
   8 * independent and clean themeselves up when they are detached and the last chan
   9 * is closed.  A few notes:
  10 * - The FS root ("/") can't be removed.  When you unmount, it gets
  11 *   closed/decreffed.
  12 * - The tmpfs will exist for the life of any open chans, including the mount.
  13 *   You can e.g. cd into a subdir, unmount /, and the tmpfs will stay alive til
  14 *   you leave the directory (basically close the chan).  You can do the same
  15 *   thing with open_at().
  16 * - The root tree_file will not be deleted so long as you have an open chan.
  17 *   Any open chan on a subdir/subfile will hold refs on the root.  The mount
  18 *   point will also hold those refs.  We also hold an additional +1 on the root
  19 *   TF, which we drop once we have no users and we've purged the tree. */
  20
  21#include <ns.h>
  22#include <kmalloc.h>
  23#include <string.h>
  24#include <stdio.h>
  25#include <assert.h>
  26#include <error.h>
  27#include <tree_file.h>
  28#include <pmap.h>
  29#include <cpio.h>
  30
  31struct dev tmpfs_devtab;
  32
  33/* This is a little wasteful - a u16_pool will work too, though I'd like to
  34 * exercise bits of the arena code. */
  35static struct arena *tmpfs_ids;
  36
  37struct tmpfs {
  38        struct tree_filesystem          tfs;
  39        atomic_t                        qid_file;
  40        uint16_t                        id;
  41        struct kref                     users;
  42};
  43
  44static struct tmpfs *tree_file_to_tmpfs(struct tree_file *tf)
  45{
  46        return (struct tmpfs*)(tf->tfs);
  47}
  48
  49#define TMPFS_MAX_QID_FILE ((1ULL << 48) - 1)
  50#define TMPFS_MAX_ID ((1 << 16) - 1)
  51
  52/* Qids are made up of 16 bits for the ID, 48 for the file. */
  53static uint64_t tmpfs_get_qid_path(struct tmpfs *tmpfs)
  54{
  55        uint64_t ret = atomic_fetch_and_add(&tmpfs->qid_file, 1);
  56
  57        /* Wrap-around of qid_file won't happen in our lifetime */
  58        if (ret > TMPFS_MAX_QID_FILE)
  59                error(ENOSPC, "out of tmpfs files");
  60        return ((uint64_t)tmpfs->id << 16) | ret;
  61}
  62
  63static char *devname(void)
  64{
  65        return tmpfs_devtab.name;
  66}
  67
  68static void tmpfs_tf_free(struct tree_file *tf)
  69{
  70        /* We have nothing special hanging off the TF */
  71}
  72
  73static void tmpfs_tf_unlink(struct tree_file *parent, struct tree_file *child)
  74{
  75        /* This is the "+1 for existing" ref. */
  76        tf_kref_put(child);
  77}
  78
  79static void __tmpfs_tf_init(struct tree_file *tf, int dir_type, int dir_dev,
  80                            struct username *user, int perm)
  81{
  82        struct dir *dir = &tf->file.dir;
  83
  84        fs_file_init_dir(&tf->file, dir_type, dir_dev, user, perm);
  85        dir->qid.path = tmpfs_get_qid_path(tree_file_to_tmpfs(tf));
  86        dir->qid.vers = 0;
  87        /* This is the "+1 for existing" ref.  There is no backing store for the
  88         * FS, such as a disk or 9p, so we can't get rid of a file until it is
  89         * unlinked and decreffed.  Note that KFS doesn't use pruners or
  90         * anything else. */
  91        __kref_get(&tf->kref, 1);
  92}
  93
  94/* Note: If your TFS doesn't support symlinks, you need to error out */
  95static void tmpfs_tf_create(struct tree_file *parent, struct tree_file *child,
  96                            int perm)
  97{
  98        __tmpfs_tf_init(child, parent->file.dir.type, parent->file.dir.dev,
  99                        &eve, perm);
 100}
 101
 102static void tmpfs_tf_rename(struct tree_file *tf, struct tree_file *old_parent,
 103                            struct tree_file *new_parent, const char *name,
 104                            int flags)
 105{
 106        /* We don't have a backend, so we don't need to do anything additional
 107         * for rename. */
 108}
 109
 110static bool tmpfs_tf_has_children(struct tree_file *parent)
 111{
 112        /* The tree_file parent list is complete and not merely a cache for a
 113         * real backend. */
 114        return !list_empty(&parent->children);
 115}
 116
 117struct tree_file_ops tmpfs_tf_ops = {
 118        .free = tmpfs_tf_free,
 119        .unlink = tmpfs_tf_unlink,
 120        .lookup = NULL,
 121        .create = tmpfs_tf_create,
 122        .rename = tmpfs_tf_rename,
 123        .has_children = tmpfs_tf_has_children,
 124};
 125
 126/* Fills page with its contents from its backing store file.  For KFS, that
 127 * means we're creating or extending a file, and the contents are 0.  Note the
 128 * page/offset might be beyond the current file length, based on the current
 129 * pagemap code. */
 130static int tmpfs_pm_readpage(struct page_map *pm, struct page *pg)
 131{
 132        memset(page2kva(pg), 0, PGSIZE);
 133        atomic_or(&pg->pg_flags, PG_UPTODATE);
 134        /* Pretend that we blocked while filing this page.  This catches a lot
 135         * of bugs.  It does slightly slow down the kernel, but it's only when
 136         * filling the page cache, and considering we are using a RAMFS, you
 137         * shouldn't measure things that actually rely on KFS's performance. */
 138        kthread_usleep(1);
 139        return 0;
 140}
 141
 142/* Meant to take the page from PM and flush to backing store.  There is no
 143 * backing store. */
 144static int tmpfs_pm_writepage(struct page_map *pm, struct page *pg)
 145{
 146        return 0;
 147}
 148
 149static void tmpfs_fs_punch_hole(struct fs_file *f, off64_t begin, off64_t end)
 150{
 151}
 152
 153static bool tmpfs_fs_can_grow_to(struct fs_file *f, size_t len)
 154{
 155        /* TODO: implement some sort of memory limit */
 156        return true;
 157}
 158
 159struct fs_file_ops tmpfs_fs_ops = {
 160        .readpage = tmpfs_pm_readpage,
 161        .writepage = tmpfs_pm_writepage,
 162        .punch_hole = tmpfs_fs_punch_hole,
 163        .can_grow_to = tmpfs_fs_can_grow_to,
 164};
 165
 166static void purge_cb(struct tree_file *tf)
 167{
 168        /* this is the +1 for existing */
 169        tf_kref_put(tf);
 170}
 171
 172static void tmpfs_release(struct kref *kref)
 173{
 174        struct tmpfs *tmpfs = container_of(kref, struct tmpfs, users);
 175
 176        tfs_frontend_purge(&tmpfs->tfs, purge_cb);
 177        /* this is the ref from attach */
 178        assert(kref_refcnt(&tmpfs->tfs.root->kref) == 1);
 179        tf_kref_put(tmpfs->tfs.root);
 180        /* ensures __tf_free() happens before tfs_destroy */
 181        rcu_barrier();
 182        tfs_destroy(&tmpfs->tfs);
 183        arena_free(tmpfs_ids, (void*)(uintptr_t)tmpfs->id, 1);
 184        kfree(tmpfs);
 185}
 186
 187static struct tmpfs *chan_to_tmpfs(struct chan *c)
 188{
 189        return tree_file_to_tmpfs(chan_to_tree_file(c));
 190}
 191
 192static void incref_tmpfs_chan(struct chan *c)
 193{
 194        kref_get(&chan_to_tmpfs(c)->users, 1);
 195}
 196
 197static void decref_tmpfs_chan(struct chan *c)
 198{
 199        kref_put(&chan_to_tmpfs(c)->users);
 200}
 201
 202static void tmpfs_reset(void)
 203{
 204        /* Note you can't make an arena that hands out '0'.  That's baked into
 205         * the interface.  I'm OK with it for now.  Because of that, the *size*
 206         * of the span is 2^16 - 1; can't count '0'. */
 207        tmpfs_ids = arena_create("tmpfs_ids", (void*)1, TMPFS_MAX_ID, 1,
 208                                 NULL, NULL, NULL, 0, ARENA_NEXTFIT | MEM_WAIT);
 209}
 210
 211/* This is pretty hokey - we never call shutdown, and I don't know if 9ns has
 212 * any protections about device use before calling shutdown. */
 213static void tmpfs_shutdown(void)
 214{
 215        arena_destroy(tmpfs_ids);
 216        tmpfs_ids = NULL;
 217}
 218
 219static struct chan *tmpfs_attach(char *spec)
 220{
 221        struct tree_filesystem *tfs = kzmalloc(sizeof(struct tmpfs), MEM_WAIT);
 222        struct tmpfs *tmpfs = (struct tmpfs*)tfs;
 223
 224        /* All distinct chans get a ref on the filesystem, so that we can
 225         * destroy it when the last user disconnects/closes. */
 226        kref_init(&tmpfs->users, tmpfs_release, 1);
 227        tmpfs->id = (uint16_t)(uintptr_t)arena_alloc(tmpfs_ids, 1, MEM_WAIT);
 228
 229        /* This gives us one ref on root, dropped during tmpfs_release(). */
 230        tfs_init(tfs);
 231        tfs->tf_ops = tmpfs_tf_ops;
 232        tfs->fs_ops = tmpfs_fs_ops;
 233
 234        /* This gives us an extra refcnt on tfs->root.  This is "+1 for
 235         * existing." It is decreffed during the purge CB. */
 236        __tmpfs_tf_init(tfs->root, &tmpfs_devtab - devtab, 0, &eve, DMDIR |
 237                        0777);
 238        /* This also increfs, copying tfs->root's ref for the chan it returns.*/
 239        return tree_file_alloc_chan(tfs->root, &tmpfs_devtab, "#tmpfs");
 240}
 241
 242static struct walkqid *tmpfs_walk(struct chan *c, struct chan *nc, char **name,
 243                                  unsigned int nname)
 244{
 245        struct walkqid *wq = tree_chan_walk(c, nc, name, nname);
 246
 247        if (wq && wq->clone && (wq->clone != c))
 248                incref_tmpfs_chan(wq->clone);
 249        return wq;
 250}
 251
 252static void tmpfs_close(struct chan *c)
 253{
 254        tree_chan_close(c);
 255        decref_tmpfs_chan(c);
 256}
 257
 258static void tmpfs_remove(struct chan *c)
 259{
 260        ERRSTACK(1);
 261        struct tmpfs *tmpfs = chan_to_tmpfs(c);
 262
 263        /* This is a bit of a pain - when remove fails, we won't get a chance to
 264         * close the chan.  See notes in tree_chan_remove() and sysremove(). */
 265        if (waserror()) {
 266                kref_put(&tmpfs->users);
 267                nexterror();
 268        }
 269        tree_chan_remove(c);
 270        kref_put(&tmpfs->users);
 271        poperror();
 272}
 273
 274void tmpfs_rename(struct chan *c, struct chan *new_p_c, const char *name,
 275                  int flags)
 276{
 277        struct tmpfs *tmpfs_old = chan_to_tmpfs(c);
 278        struct tmpfs *tmpfs_new = chan_to_tmpfs(new_p_c);
 279
 280        /* namec checked that both were from the same device.  It is our
 281         * responsibility to make sure they are the same version. */
 282        if (tmpfs_old != tmpfs_new)
 283                error(EXDEV, "can't rename across tmpfs instances");
 284        tree_chan_rename(c, new_p_c, name, flags);
 285}
 286
 287static unsigned long tmpfs_chan_ctl(struct chan *c, int op, unsigned long a1,
 288                                    unsigned long a2, unsigned long a3,
 289                                    unsigned long a4)
 290{
 291        switch (op) {
 292        case CCTL_SYNC:
 293                return 0;
 294        default:
 295                return tree_chan_ctl(c, op, a1, a2, a3, a4);
 296        }
 297}
 298
 299struct dev tmpfs_devtab __devtab = {
 300        .name = "tmpfs",
 301        .reset = tmpfs_reset,
 302        .init = devinit,
 303        .shutdown = tmpfs_shutdown,
 304        .attach = tmpfs_attach,
 305        .walk = tmpfs_walk,
 306        .stat = tree_chan_stat,
 307        .open = tree_chan_open,
 308        .create = tree_chan_create,
 309        .close = tmpfs_close,
 310        .read = tree_chan_read,
 311        .bread = devbread,
 312        .write = tree_chan_write,
 313        .bwrite = devbwrite,
 314        .remove = tmpfs_remove,
 315        .rename = tmpfs_rename,
 316        .wstat = tree_chan_wstat,
 317        .power = devpower,
 318        .chaninfo = devchaninfo,
 319        .mmap = tree_chan_mmap,
 320        .chan_ctl = tmpfs_chan_ctl,
 321};
 322