| /* Copyright (c) 2014 The Regents of the University of California |
| * Barret Rhoden <brho@cs.berkeley.edu> |
| * See LICENSE for details. |
| * |
| * #s (srv) - a chan sharing service. This was originally based off the Inferno |
| * #s, but it's been completely rewritten to act like what I remember the plan9 |
| * one to be like. |
| * |
| * |
| * I tried a style where we hang reference counted objects off c->aux, specific |
| * to each chan. Instead of looking up via qid.path, we just look at the c->aux |
| * for our struct. I originally tried having those be reference counted |
| * structs, but that fails for a bunch of reasons. Without them being reference |
| * counted, we're really just using c->aux as if it was qid.path. |
| * |
| * We can't hang an external reference to an item off c->aux, and have that item |
| * change as we gen (but we can use it as a weak ref, uncounted ref). The main |
| * thing is that devclone makes a 'half-chan' with a copy of c->aux. This chan |
| * may or may not be closed later. If we transfer refs via a gen, we first |
| * assumed we had a ref in the first place (devclone doesn't incref our srv), |
| * and then we might not close. This ends up decreffing top_dir too much, and |
| * giving it's refs to some other file in the walk. */ |
| |
| #include <slab.h> |
| #include <kmalloc.h> |
| #include <kref.h> |
| #include <string.h> |
| #include <stdio.h> |
| #include <assert.h> |
| #include <error.h> |
| #include <cpio.h> |
| #include <pmap.h> |
| #include <smp.h> |
| #include <net/ip.h> |
| #include <sys/queue.h> |
| |
| struct dev srvdevtab; |
| |
| static char *devname(void) |
| { |
| return srvdevtab.name; |
| } |
| |
| #define Qtopdir 1 |
| #define Qsrvfile 2 |
| |
| struct srvfile { |
| TAILQ_ENTRY(srvfile) link; |
| char *name; |
| struct chan *chan; |
| struct kref ref; /* +1 for existing on create, -1 on remove */ |
| char *user; |
| uint32_t perm; |
| atomic_t opens; /* used for exclusive open checks */ |
| }; |
| |
| struct srvfile *top_dir; |
| TAILQ_HEAD(srvfilelist, srvfile) srvfiles = TAILQ_HEAD_INITIALIZER(srvfiles); |
| /* the lock protects the list and its members. we don't incref from a list ref |
| * without the lock. (if you're on the list, we can grab a ref). */ |
| spinlock_t srvlock = SPINLOCK_INITIALIZER; |
| |
| atomic_t nr_srvs = 0; /* debugging - concerned about leaking mem */ |
| |
| /* Given a pointer (internal ref), we attempt to get a kref */ |
| static bool grab_ref(struct srvfile *srv) |
| { |
| bool ret = FALSE; |
| struct srvfile *srv_i; |
| |
| spin_lock(&srvlock); |
| TAILQ_FOREACH(srv_i, &srvfiles, link) { |
| if (srv_i == srv) { |
| ret = kref_get_not_zero(&srv_i->ref, 1); |
| break; |
| } |
| } |
| spin_unlock(&srvlock); |
| return ret; |
| } |
| |
| static void srv_release(struct kref *kref) |
| { |
| struct srvfile *srv = container_of(kref, struct srvfile, ref); |
| |
| kfree(srv->user); |
| kfree(srv->name); |
| if (srv->chan) |
| cclose(srv->chan); |
| kfree(srv); |
| atomic_dec(&nr_srvs); |
| } |
| |
| static int srvgen(struct chan *c, char *name, struct dirtab *tab, |
| int ntab, int s, struct dir *dp) |
| { |
| struct srvfile *prev, *next; |
| struct qid q; |
| |
| if (s == DEVDOTDOT) { |
| /* changing whatever c->aux was to be topdir */ |
| mkqid(&q, Qtopdir, 0, QTDIR); |
| devdir(c, q, devname(), 0, eve.name, 0555, dp); |
| return 1; |
| } |
| spin_lock(&srvlock); |
| TAILQ_FOREACH(next, &srvfiles, link) { |
| /* come in with s == 0 on the first run */ |
| if (s-- == 0) |
| break; |
| } |
| if (!next) { |
| spin_unlock(&srvlock); |
| return -1; |
| } |
| /* update c to point to our new srvfile. this keeps the chan and its |
| * srv in sync with what we're genning. */ |
| c->aux = next; /* uncounted ref */ |
| mkqid(&q, Qsrvfile, 0, QTFILE); |
| /* once we release the lock, next could disappear, including next->name |
| */ |
| strlcpy(get_cur_genbuf(), next->name, GENBUF_SZ); |
| devdir(c, q, get_cur_genbuf(), 1 /* length */ , next->user, next->perm, |
| dp); |
| spin_unlock(&srvlock); |
| return 1; |
| } |
| |
| static void __srvinit(void) |
| { |
| top_dir = kzmalloc(sizeof(struct srvfile), MEM_WAIT); |
| /* kstrdup, just in case we free this later */ |
| kstrdup(&top_dir->name, "srv"); |
| kstrdup(&top_dir->user, current ? current->user.name : "eve"); |
| top_dir->perm = DMDIR | 0770; |
| /* +1 for existing, should never decref this */ |
| kref_init(&top_dir->ref, fake_release, 1); |
| atomic_set(&top_dir->opens, 0); |
| } |
| |
| static void srvinit(void) |
| { |
| run_once(__srvinit()); |
| } |
| |
| static struct chan *srvattach(char *spec) |
| { |
| /* the inferno attach was pretty complicated, but |
| * we're not sure that complexity is needed. */ |
| struct chan *c = devattach(devname(), spec); |
| |
| mkqid(&c->qid, Qtopdir, 0, QTDIR); |
| /* c->aux is an uncounted ref */ |
| c->aux = top_dir; |
| return c; |
| } |
| |
| static struct walkqid *srvwalk(struct chan *c, struct chan *nc, char **name, |
| unsigned int nname) |
| { |
| return devwalk(c, nc, name, nname, 0, 0, srvgen); |
| } |
| |
| static size_t srvstat(struct chan *c, uint8_t *db, size_t n) |
| { |
| return devstat(c, db, n, 0, 0, srvgen); |
| } |
| |
| char *srvname(struct chan *c) |
| { |
| struct srvfile *srv_i; |
| char *s; |
| |
| spin_lock(&srvlock); |
| TAILQ_FOREACH(srv_i, &srvfiles, link) { |
| if(srv_i->chan == c){ |
| int len = 3 + strlen(srv_i->name) + 1; |
| s = kzmalloc(len, 0); |
| snprintf(s, len, "#s/%s", srv_i->name); |
| spin_unlock(&srvlock); |
| return s; |
| } |
| } |
| spin_unlock(&srvlock); |
| return NULL; |
| } |
| |
| static struct chan *srvopen(struct chan *c, int omode) |
| { |
| ERRSTACK(1); |
| struct srvfile *srv; |
| |
| /* used as an error checker in plan9, does little now */ |
| openmode(omode); |
| if (c->qid.type & QTDIR) { |
| if (omode & O_WRITE) |
| error(EISDIR, ERROR_FIXME); |
| c->mode = openmode(omode); |
| c->flag |= COPEN; |
| c->offset = 0; |
| return c; |
| } |
| srv = c->aux; |
| if (!grab_ref(srv)) |
| error(EFAIL, "Unable to open srv file, concurrent removal"); |
| if (waserror()) { |
| kref_put(&srv->ref); |
| nexterror(); |
| } |
| devpermcheck(srv->user, srv->perm, omode); |
| if ((srv->perm & DMEXCL) && atomic_read(&srv->opens)) |
| error(EBUSY, ERROR_FIXME); |
| /* srv->chan is write-once, so we don't need to sync. */ |
| if (!srv->chan) |
| error(EFAIL, "srv file has no chan yet"); |
| /* this is more than just the ref - 1, since there will be refs in |
| * flight as gens work their way through the list */ |
| atomic_inc(&srv->opens); |
| /* the magic of srv: open c, get c->srv->chan back */ |
| cclose(c); |
| c = srv->chan; |
| chan_incref(c); |
| poperror(); |
| kref_put(&srv->ref); |
| return c; |
| } |
| |
| static void srvcreate(struct chan *c, char *name, int omode, uint32_t perm, |
| char *ext) |
| { |
| struct srvfile *srv; |
| |
| if (perm & DMSYMLINK) |
| error(EINVAL, "#%s doesn't support symlinks", devname()); |
| srv = kzmalloc(sizeof(struct srvfile), MEM_WAIT); |
| kstrdup(&srv->name, name); |
| kstrdup(&srv->user, current ? current->user.name : "eve"); |
| srv->perm = 0770; /* TODO need some security thoughts */ |
| atomic_set(&srv->opens, 1); /* we return it opened */ |
| mkqid(&c->qid, Qsrvfile, 0, QTFILE); |
| c->aux = srv; |
| c->mode = openmode(omode); |
| /* one ref for being on the list */ |
| kref_init(&srv->ref, srv_release, 1); |
| spin_lock(&srvlock); |
| TAILQ_INSERT_TAIL(&srvfiles, srv, link); |
| spin_unlock(&srvlock); |
| atomic_inc(&nr_srvs); |
| } |
| |
| static void srvremove(struct chan *c) |
| { |
| struct srvfile *srv_i, *temp; |
| |
| spin_lock(&srvlock); |
| TAILQ_FOREACH_SAFE(srv_i, &srvfiles, link, temp) { |
| if (srv_i == c->aux) { |
| TAILQ_REMOVE(&srvfiles, srv_i, link); |
| break; |
| } |
| } |
| spin_unlock(&srvlock); |
| if (srv_i) |
| kref_put(&srv_i->ref); /* dropping ref from the list */ |
| } |
| |
| static void srvclose(struct chan *c) |
| { |
| struct srvfile *srv = c->aux; |
| |
| if (!grab_ref(srv)) |
| return; |
| atomic_dec(&srv->opens); |
| kref_put(&srv->ref); |
| if (c->flag & O_REMCLO) |
| srvremove(c); |
| } |
| |
| /* N.B. srvopen gives the chan back. The only 'reading' we do |
| * in srv is of the top level directory. |
| */ |
| static size_t srvread(struct chan *c, void *va, size_t count, off64_t offset) |
| { |
| return devdirread(c, va, count, 0, 0, srvgen); |
| } |
| |
| static size_t srvwrite(struct chan *c, void *va, size_t count, off64_t offset) |
| { |
| ERRSTACK(2); |
| struct srvfile *srv; |
| struct chan *new_chan; |
| char *kbuf = 0; |
| int fd; |
| |
| if (c->qid.type & QTDIR) |
| error(EPERM, ERROR_FIXME); |
| srv = c->aux; |
| if (!grab_ref(srv)) |
| error(EFAIL, "Unable to write srv file, concurrent removal"); |
| if (waserror()) { |
| kref_put(&srv->ref); |
| nexterror(); |
| } |
| if (srv->chan) |
| error(EFAIL, "srv file already has a stored chan!"); |
| if (waserror()) { |
| kfree(kbuf); |
| nexterror(); |
| } |
| kbuf = kmalloc(count + 1, MEM_WAIT); |
| strlcpy(kbuf, va, count + 1); |
| fd = strtoul(kbuf, 0, 10); |
| /* the magic of srv: srv stores the chan corresponding to the fd. -1 |
| * for mode, so we just get the chan with no checks (RDWR would work |
| * too). */ |
| new_chan = fdtochan(¤t->open_files, fd, -1, FALSE, TRUE); |
| /* fdtochan already increffed for us */ |
| if (!__sync_bool_compare_and_swap(&srv->chan, 0, new_chan)) { |
| cclose(new_chan); |
| error(EFAIL, "srv file already has a stored chan!"); |
| } |
| poperror(); |
| kfree(kbuf); |
| poperror(); |
| kref_put(&srv->ref); |
| return count; |
| } |
| |
| struct dev srvdevtab __devtab = { |
| .name = "srv", |
| |
| .reset = devreset, |
| .init = srvinit, |
| .shutdown = devshutdown, |
| .attach = srvattach, |
| .walk = srvwalk, |
| .stat = srvstat, |
| .open = srvopen, |
| .create = srvcreate, |
| .close = srvclose, |
| .read = srvread, |
| .bread = devbread, |
| .write = srvwrite, |
| .bwrite = devbwrite, |
| .remove = srvremove, |
| .wstat = devwstat, |
| .power = devpower, |
| .chaninfo = devchaninfo, |
| }; |