diff options
Diffstat (limited to 'sys/nfs4client/nfs4_dev.c')
-rw-r--r-- | sys/nfs4client/nfs4_dev.c | 451 |
1 files changed, 451 insertions, 0 deletions
diff --git a/sys/nfs4client/nfs4_dev.c b/sys/nfs4client/nfs4_dev.c new file mode 100644 index 0000000..09c9d57 --- /dev/null +++ b/sys/nfs4client/nfs4_dev.c @@ -0,0 +1,451 @@ +/* $FreeBSD$ */ +/* $Id: nfs4_dev.c,v 1.10 2003/11/05 14:58:59 rees Exp $ */ + +/* + * copyright (c) 2003 + * the regents of the university of michigan + * all rights reserved + * + * permission is granted to use, copy, create derivative works and redistribute + * this software and such derivative works for any purpose, so long as the name + * of the university of michigan is not used in any advertising or publicity + * pertaining to the use or distribution of this software without specific, + * written prior authorization. if the above copyright notice or any other + * identification of the university of michigan is included in any copy of any + * portion of this software, then the disclaimer below must also be included. + * + * this software is provided as is, without representation from the university + * of michigan as to its fitness for any purpose, and without warranty by the + * university of michigan of any kind, either express or implied, including + * without limitation the implied warranties of merchantability and fitness for + * a particular purpose. the regents of the university of michigan shall not be + * liable for any damages, including special, indirect, incidental, or + * consequential damages, with respect to any claim arising out of or in + * connection with the use of the software, even if it has been or is hereafter + * advised of the possibility of such damages. + */ + +#include <sys/param.h> +#include <sys/conf.h> +#include <sys/queue.h> +#include <sys/malloc.h> +#include <sys/kernel.h> +#include <sys/poll.h> +#include <sys/mutex.h> +#include <sys/stat.h> +#include <sys/systm.h> +#include <sys/proc.h> +#include <sys/wait.h> +#include <sys/signalvar.h> + +#include <nfs4client/nfs4_dev.h> + +#ifdef NFS4DEVVERBOSE +#define NFS4DEV_DEBUG(X...) printf(X) +#else +#define NFS4DEV_DEBUG(X...) +#endif + +#define NFS4DEV_NAME "nfs4" +#define CDEV_MAJOR 29 /* XXX where are these numbers assigned!?!? */ +#define CDEV_MINOR 1 + +MALLOC_DEFINE(M_NFS4DEV, "NFS4 dev", "NFS4 device"); + +struct nfs4dev_upcall { + /* request msg */ + struct nfs4dev_msg up_reqmsg; + size_t up_reqmsglen; + + /* reply (payload only) */ + caddr_t up_rep; + size_t * up_replen; + + int up_copied; /* non-zero when reply has been copied to + '*up_rep' */ + + int up_error; /* non-zero if an error occured */ + + TAILQ_ENTRY(nfs4dev_upcall) up_entry; +}; + + +#define nfs4dev_upcall_get(MP) MALLOC((MP), struct nfs4dev_upcall *, sizeof(struct nfs4dev_upcall), M_NFS4DEV, M_WAITOK | M_ZERO) + +#define nfs4dev_upcall_put(MP) FREE((MP), M_NFS4DEV) + +static int nfs4dev_nopen = 0; +static struct thread * nfs4dev_reader = NULL; +static dev_t nfs4device = 0; +static struct mtx nfs4dev_daemon_mtx; + +static int nfs4dev_xid = 0; +/* queue of pending upcalls */ +TAILQ_HEAD(, nfs4dev_upcall) nfs4dev_newq; +static struct mtx nfs4dev_newq_mtx; + +/* queue of upcalls waiting for replys */ +TAILQ_HEAD(, nfs4dev_upcall) nfs4dev_waitq; +static struct mtx nfs4dev_waitq_mtx; + +/* dev hooks */ +static d_open_t nfs4dev_open; +static d_close_t nfs4dev_close; +static d_ioctl_t nfs4dev_ioctl; +static d_poll_t nfs4dev_poll; + +static struct cdevsw nfs4dev_cdevsw = { + .d_open = nfs4dev_open, + .d_close = nfs4dev_close, + .d_ioctl = nfs4dev_ioctl, + .d_poll = nfs4dev_poll, + .d_name = NFS4DEV_NAME, + .d_maj = CDEV_MAJOR +}; + +static int nfs4dev_reply(caddr_t); +static int nfs4dev_request(caddr_t); + +/* Userland requests a new operation to service */ +static int +nfs4dev_request(caddr_t addr) +{ + struct nfs4dev_upcall * u; + struct nfs4dev_msg * m = (struct nfs4dev_msg *) addr; + + mtx_lock(&nfs4dev_newq_mtx); + + if (TAILQ_EMPTY(&nfs4dev_newq)) { + mtx_unlock(&nfs4dev_newq_mtx); + return EAGAIN; + } + + u = TAILQ_FIRST(&nfs4dev_newq); + TAILQ_REMOVE(&nfs4dev_newq, u, up_entry); + mtx_unlock(&nfs4dev_newq_mtx); + + bcopy(&u->up_reqmsg, m, sizeof(struct nfs4dev_msg)); + + mtx_lock(&nfs4dev_waitq_mtx); + TAILQ_INSERT_TAIL(&nfs4dev_waitq, u, up_entry); + mtx_unlock(&nfs4dev_waitq_mtx); + + return 0; +} + +static int +nfs4dev_reply(caddr_t addr) +{ + struct nfs4dev_upcall * u; + struct nfs4dev_msg * m = (struct nfs4dev_msg *) addr; + int error; + + if (m->msg_vers != NFS4DEV_VERSION) { + printf("nfs4dev version mismatch\n"); + return EINVAL; + } + + if (m->msg_type > NFS4DEV_MAX_TYPE) { + NFS4DEV_DEBUG("nfs4dev: unsupported message type\n"); + return EINVAL; + } + + if (m->msg_len == 0 || m->msg_len > NFS4DEV_MSG_MAX_DATALEN) { + NFS4DEV_DEBUG("bad message length\n"); + return EINVAL; + } + + /* match the reply with a request */ + mtx_lock(&nfs4dev_waitq_mtx); + TAILQ_FOREACH(u, &nfs4dev_waitq, up_entry) { + if (m->msg_xid == u->up_reqmsg.msg_xid) { + if (m->msg_type == u->up_reqmsg.msg_type) + goto found; + NFS4DEV_DEBUG("nfs4dev: op type mismatch!\n"); + break; + } + } + mtx_unlock(&nfs4dev_waitq_mtx); + + NFS4DEV_DEBUG("nfs4dev msg op: %d xid: %x not found.\n", + m->msg_type, m->msg_xid); + + error = EIO; + goto bad; + +found: + TAILQ_REMOVE(&nfs4dev_waitq, u, up_entry); + mtx_unlock(&nfs4dev_waitq_mtx); + + if (m->msg_error) { + error = m->msg_error; + goto bad; + } + + if (m->msg_len > *u->up_replen) { + error = EFAULT; + goto bad; + } + + bcopy(m->msg_data, u->up_rep, m->msg_len); + *u->up_replen = m->msg_len; + + u->up_copied = m->msg_len; + wakeup(u); + + return 0; +bad: + u->up_error = error; + wakeup(u); + return error; +} + +void +nfs4dev_init(void) +{ + nfs4dev_xid = arc4random(); + TAILQ_INIT(&nfs4dev_newq); + TAILQ_INIT(&nfs4dev_waitq); + mtx_init(&nfs4dev_newq_mtx, "nfs4dev newq", NULL, MTX_DEF); + mtx_init(&nfs4dev_waitq_mtx, "nfs4dev waitq", NULL, MTX_DEF); + + mtx_init(&nfs4dev_daemon_mtx, "nfs4dev state", NULL, MTX_DEF); + + nfs4device = make_dev(&nfs4dev_cdevsw, CDEV_MINOR, (uid_t)0, (gid_t)0, + S_IRUSR | S_IWUSR, "nfs4"); +} + +void +nfs4dev_uninit(void) +{ + struct proc * dead = NULL; + + mtx_lock(&nfs4dev_daemon_mtx); + if (nfs4dev_nopen) { + if (nfs4dev_reader == NULL) { + NFS4DEV_DEBUG("nfs4dev uninit(): unregistered reader\n"); + } else { + dead = nfs4dev_reader->td_proc; + } + } + mtx_unlock(&nfs4dev_daemon_mtx); + + if (dead != NULL) { + NFS4DEV_DEBUG("nfs4dev_uninit(): you forgot to kill attached daemon (pid: %u)\n", + dead->p_pid); + PROC_LOCK(dead); + psignal(dead, SIGTERM); + PROC_UNLOCK(dead); + } + + /* XXX moot? */ + nfs4dev_purge(); + + mtx_destroy(&nfs4dev_newq_mtx); + mtx_destroy(&nfs4dev_waitq_mtx); + mtx_destroy(&nfs4dev_daemon_mtx); + + destroy_dev(nfs4device); +} + +/* device interface functions */ +static int +nfs4dev_open(dev_t dev, int flags, int fmt, d_thread_t *td) +{ + if (dev != nfs4device) + return ENODEV; + + mtx_lock(&nfs4dev_daemon_mtx); + if (nfs4dev_nopen) { + mtx_unlock(&nfs4dev_daemon_mtx); + return EBUSY; + } + + nfs4dev_nopen++; + nfs4dev_reader = curthread; + mtx_unlock(&nfs4dev_daemon_mtx); + + return (0); +} + +static int +nfs4dev_close(dev_t dev, int flags, int fmt, d_thread_t *td) +{ + struct nfs4dev_upcall * u; + + if (dev != nfs4device) + return ENODEV; + + mtx_lock(&nfs4dev_daemon_mtx); + if (!nfs4dev_nopen) { + mtx_unlock(&nfs4dev_daemon_mtx); + return ENOENT; + } + + nfs4dev_nopen--; + nfs4dev_reader = NULL; + mtx_unlock(&nfs4dev_daemon_mtx); + + mtx_lock(&nfs4dev_waitq_mtx); + + while (!TAILQ_EMPTY(&nfs4dev_waitq)) { + u = TAILQ_FIRST(&nfs4dev_waitq); + TAILQ_REMOVE(&nfs4dev_waitq, u, up_entry); + u->up_error = EINTR; + wakeup(u); + } + + mtx_unlock(&nfs4dev_waitq_mtx); + + return 0; +} + +static int +nfs4dev_ioctl(dev_t dev, u_long cmd, caddr_t data, int fflag, struct thread *td) +{ + int error; + + if (dev != nfs4device) + return ENODEV; + + if (data == NULL) + return EFAULT; + + if (nfs4dev_reader != curthread) + nfs4dev_reader = curthread; + + switch (cmd) { + case NFS4DEVIOCGET: + error = nfs4dev_request(data); + break; + case NFS4DEVIOCPUT: + error = nfs4dev_reply(data); + break; + default: + NFS4DEV_DEBUG("nfs4dev_ioctl: unkown ioctl cmd %d\n", (int)cmd); + error = EOPNOTSUPP; + break; + } + + return error; +} + +static int +nfs4dev_poll(dev_t dev, int events, struct thread *td) +{ + int revents; + + if (dev != nfs4device) + return EINVAL; + + mtx_lock(&nfs4dev_daemon_mtx); + if (nfs4dev_nopen == 0) { + mtx_unlock(&nfs4dev_daemon_mtx); + return 0; + } + mtx_unlock(&nfs4dev_daemon_mtx); + + revents = 0; + + /* check readable data */ + mtx_lock(&nfs4dev_newq_mtx); + if (!TAILQ_EMPTY(&nfs4dev_newq)) + revents |= POLLIN; + mtx_unlock(&nfs4dev_newq_mtx); + + mtx_lock(&nfs4dev_waitq_mtx); + if (!TAILQ_EMPTY(&nfs4dev_waitq)) + revents |= POLLOUT; + mtx_unlock(&nfs4dev_waitq_mtx); + + return revents; +} + +int +nfs4dev_call(uint32_t type, caddr_t req_data, size_t req_len, caddr_t rep_data, size_t * rep_lenp) +{ + struct nfs4dev_upcall * u; + int error = 0; + unsigned int xtmp; + + mtx_lock(&nfs4dev_daemon_mtx); + if (nfs4dev_nopen == 0) { + mtx_unlock(&nfs4dev_daemon_mtx); + return EINVAL; + } + mtx_unlock(&nfs4dev_daemon_mtx); + + if (type > NFS4DEV_MAX_TYPE) + return EOPNOTSUPP; + + NFS4DEV_DEBUG("upcall %d/%d:%d\n", type, req_len, *rep_lenp); + + nfs4dev_upcall_get(u); + + u->up_error = 0; + u->up_rep = rep_data; + u->up_replen = rep_lenp; + u->up_copied = 0; + + u->up_reqmsg.msg_vers = NFS4DEV_VERSION; + /* XXX efficient copying */ + bcopy(req_data, u->up_reqmsg.msg_data, req_len); + u->up_reqmsg.msg_len = req_len; + + mtx_lock(&nfs4dev_newq_mtx); + + /* get new XID */ + while ((xtmp = arc4random() % 256) == 0); + nfs4dev_xid += xtmp; + u->up_reqmsg.msg_xid = nfs4dev_xid; + + TAILQ_INSERT_TAIL(&nfs4dev_newq, u, up_entry); + mtx_unlock(&nfs4dev_newq_mtx); + + + NFS4DEV_DEBUG("nfs4dev op: %d xid: %x sleeping\n", u->up_reqmsg.msg_type, u->up_reqmsg.msg_xid); + + do { + tsleep(u, PLOCK, "nfs4dev", 0); + } while (u->up_copied == 0 && u->up_error == 0); + + /* upcall now removed from the queue */ + + NFS4DEV_DEBUG("nfs4dev prog: %d xid: %x continues...\n", + u->up_reqmsg.msg_type, u->up_reqmsg.msg_xid); + + if (u->up_error) { + error = u->up_error; + NFS4DEV_DEBUG("nfs4dev prog: %d xid: %x error: %d\n", + u->up_reqmsg.msg_type, u->up_reqmsg.msg_xid, u->up_error); + goto out; + } + +out: + nfs4dev_upcall_put(u); + return error; +} + +void +nfs4dev_purge(void) +{ + struct nfs4dev_upcall * u; + + mtx_lock(&nfs4dev_newq_mtx); + while (!TAILQ_EMPTY(&nfs4dev_newq)) { + u = TAILQ_FIRST(&nfs4dev_newq); + TAILQ_REMOVE(&nfs4dev_newq, u, up_entry); + u->up_error = EINTR; + wakeup(u); + } + mtx_unlock(&nfs4dev_newq_mtx); + + mtx_lock(&nfs4dev_waitq_mtx); + while (!TAILQ_EMPTY(&nfs4dev_waitq)) { + u = TAILQ_FIRST(&nfs4dev_waitq); + TAILQ_REMOVE(&nfs4dev_waitq, u, up_entry); + u->up_error = EINTR; + wakeup(u); + } + mtx_unlock(&nfs4dev_waitq_mtx); +} |