/* $FreeBSD$ */ /* $Id: rpc_dev.c,v 1.8 2004/06/14 19:38:56 marius Exp $ */ /* * copyright (c) 2003 * the regents of the university of michigan * all rights reserved * * Weston Andros Adamson * * 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 #include #include #include #include #include #include #ifndef __APPLE__ #include #endif #if defined(__APPLE__) #include #endif #include #include #include #include #include #include /* XXX */ #ifdef __APPLE__ #include #else #include #endif #ifdef RPCDEVVERBOSE #define RPCDEV_DEBUG(X...) do { \ printf("%s> ", __FUNCTION__ ); \ printf(X); \ printf(".\n"); \ } while(0) #else #define RPCDEV_DEBUG(X...) #endif #if defined(__FreeBSD__) MALLOC_DEFINE(M_RPCDEV, "RPC dev", "RPC device"); #else #define M_RPCDEV M_TEMP #endif #define rpcdev_dev2type(X) (minor(X)-1) #define rpcdev_type2devminor(X) ((X)+1) struct rpcdev_upcall { uint32_t up_type; uint32_t up_xid; /* request */ caddr_t up_req; int up_reqlen; /* reply */ caddr_t up_rep; size_t * up_replen; int up_error; /* non-zero if an error occured */ short up_sleeponreply; TAILQ_ENTRY(rpcdev_upcall) up_entry; }; #define rpcdev_upcall_get(MP) MALLOC((MP), struct rpcdev_upcall *, sizeof(struct rpcdev_upcall), M_RPCDEV, M_WAITOK | M_ZERO) #define rpcdev_upcall_put(MP) FREE((MP), M_RPCDEV) static int rpcdev_xid = 0; cmtx_t rpcdev_xid_mtx; struct rpcdev_svc { int svc_type; /* also used for CDEV_MINOR */ char *svc_name; /* queue of pending upcalls */ TAILQ_HEAD(, rpcdev_upcall) svc_newq; cmtx_t svc_newq_mtx; /* queue of upcalls waiting for replys */ TAILQ_HEAD(, rpcdev_upcall) svc_waitq; cmtx_t svc_waitq_mtx; cthread_t *svc_opener; dev_t svc_device; #ifdef __APPLE__ void * svc_devfsid; #endif /* __APPLE__ */ cmtx_t svc_opener_mtx; struct selinfo svc_si; }; static struct rpcdev_svc rpcdev_services[RPCDEV_MAX_TYPE + 1] = { {RPCDEV_TYPE_IDMAP, "idmap", }, {RPCDEV_TYPE_RPCD, "rpcd", } }; /* dev hooks */ static d_open_t rpcdev_open; static d_close_t rpcdev_close; static d_ioctl_t rpcdev_ioctl; #if defined(__APPLE__) static d_select_t rpcdev_poll; #else static d_poll_t rpcdev_poll; #endif static struct cdevsw rpcdev_cdevsw = { .d_version = D_VERSION_00, .d_name = "RPCDEV", .d_open = rpcdev_open, .d_close = rpcdev_close, .d_ioctl = rpcdev_ioctl, #if defined(__APPLE__) .d_select = rpcdev_poll, #else .d_poll = rpcdev_poll, #endif }; static int rpcdev_reply(caddr_t); static int rpcdev_request(caddr_t); static int rpcdev_checkowner(dev_t, cthread_t *); /* Userland requests a new operation to service */ /* XXX Add funnel code for APPLE */ static int rpcdev_request(caddr_t addr) { struct rpcdev_upcall * u; struct rpcdev_msg * m = (struct rpcdev_msg *) addr; int error = 0; int type; RPCDEV_DEBUG("rpcdev: request in"); type = m->msg_type; if (m->msg_vers != RPCDEV_VERSION || type > RPCDEV_MAX_TYPE || m->msg_data == NULL || m->msg_len == 0) { error = EINVAL; goto bad; } cmtx_lock(&rpcdev_services[type].svc_newq_mtx); if (TAILQ_EMPTY(&rpcdev_services[type].svc_newq)) { cmtx_unlock(&rpcdev_services[type].svc_newq_mtx); error = EAGAIN; goto bad; } u = TAILQ_FIRST(&rpcdev_services[type].svc_newq); TAILQ_REMOVE(&rpcdev_services[type].svc_newq, u, up_entry); cmtx_unlock(&rpcdev_services[type].svc_newq_mtx); /* pass the message to userland */ if (m->msg_len < u->up_reqlen) { error = EINVAL; goto bad2; } m->msg_xid = u->up_xid; m->msg_error = 0; if ((error = copyout(u->up_req, m->msg_data, u->up_reqlen))) { RPCDEV_DEBUG("%s message error %d", rpcdev_services[type].svc_name, error); goto bad2; } m->msg_len = u->up_reqlen; cmtx_lock(&rpcdev_services[type].svc_waitq_mtx); TAILQ_INSERT_TAIL(&rpcdev_services[type].svc_waitq, u, up_entry); cmtx_unlock(&rpcdev_services[type].svc_waitq_mtx); selwakeup(&rpcdev_services[type].svc_si); RPCDEV_DEBUG("request out (success)"); return (0); bad2: /* put upcall back on the queue */ cmtx_lock(&rpcdev_services[type].svc_newq_mtx); TAILQ_INSERT_HEAD(&rpcdev_services[type].svc_newq, u, up_entry); cmtx_unlock(&rpcdev_services[type].svc_newq_mtx); bad: RPCDEV_DEBUG("request out (fail: %d)", error); /* return the error to userland */ m->msg_error = error; return (error); } static int rpcdev_reply(caddr_t addr) { struct rpcdev_upcall * u = NULL; struct rpcdev_msg * m = (struct rpcdev_msg *) addr; int error = 0; int type; RPCDEV_DEBUG("rpcdev: reply in"); if (m->msg_vers != RPCDEV_VERSION) { RPCDEV_DEBUG("version mismatch %x/%x", m->msg_vers, RPCDEV_VERSION); error = EINVAL; goto bad; } type = m->msg_type; if (type > RPCDEV_MAX_TYPE) { RPCDEV_DEBUG("rpcdev: unsupported message type %x", type); error = EINVAL; goto bad; } if (m->msg_len == 0) { RPCDEV_DEBUG("bad message length"); error = EINVAL; goto bad; } /* match the reply with a request */ cmtx_lock(&rpcdev_services[type].svc_waitq_mtx); TAILQ_FOREACH(u, &rpcdev_services[type].svc_waitq, up_entry) { if (m->msg_xid == u->up_xid) { if (m->msg_type == u->up_type) goto found; RPCDEV_DEBUG("rpcdev: op type mismatch!"); break; } } cmtx_unlock(&rpcdev_services[type].svc_waitq_mtx); RPCDEV_DEBUG("rpcdev msg op: %d xid: %x not found.", m->msg_type, m->msg_xid); error = EIO; goto bad; found: RPCDEV_DEBUG("found"); TAILQ_REMOVE(&rpcdev_services[type].svc_waitq, u, up_entry); cmtx_unlock(&rpcdev_services[type].svc_waitq_mtx); if (m->msg_error) { RPCDEV_DEBUG("%s message (xid: %x) returned error %d", rpcdev_services[type].svc_name, m->msg_xid, m->msg_error); error = m->msg_error; goto bad; } if (m->msg_len > *u->up_replen) { RPCDEV_DEBUG("%s message (xid: %x) replen too small", rpcdev_services[type].svc_name, m->msg_xid); error = EINVAL; goto bad; } if ((error = copyin(m->msg_data, u->up_rep, m->msg_len))) { RPCDEV_DEBUG("%s message copyin error %d", rpcdev_services[type].svc_name, error); goto bad; } *u->up_replen = m->msg_len; u->up_error = 0; wakeup(u); if (u->up_sleeponreply) { mtx_lock(&Giant); /* XXX */ tsleep(&rpcdev_services[type], PWAIT|PCATCH, "rpcdev", 0); mtx_unlock(&Giant); } RPCDEV_DEBUG("rpcdev: reply out (success)"); return (0); bad: u->up_error = error; wakeup(u); RPCDEV_DEBUG("rpcdev: reply out (fail: %d)", error); return (error); } void rpcdev_wakeup(int type) { wakeup(&rpcdev_services[type]); } void rpcdev_init(void) { int i; #if defined(__APPLE__) int major; #endif RPCDEV_DEBUG("init"); RANDOM(rpcdev_xid); cmtx_init(&rpcdev_xid_mtx, "rpcdev xid"); for (i=0; i <= RPCDEV_MAX_TYPE; i++) { RPCDEV_DEBUG("initializing device \"/dev/%s\"", rpcdev_services[i].svc_name); TAILQ_INIT(&rpcdev_services[i].svc_newq); cmtx_init(&rpcdev_services[i].svc_newq_mtx, "rpcdev queue"); TAILQ_INIT(&rpcdev_services[i].svc_waitq); cmtx_init(&rpcdev_services[i].svc_waitq_mtx, "rpcdev queue"); cmtx_init(&rpcdev_services[i].svc_opener_mtx, "rpcdev opener"); rpcdev_services[i].svc_opener = NULL; #if defined(__APPLE__) rpcdev_services[i].svc_devfsid = 0; #endif #if defined(__FreeBSD__) RPCDEV_DEBUG("making device %s", rpcdev_services[i].svc_name); rpcdev_services[i].svc_device = make_dev(&rpcdev_cdevsw, rpcdev_type2devminor(i), (uid_t) 0, (gid_t) 0, S_IRUSR | S_IWUSR, rpcdev_services[i].svc_name); #elif defined(__APPLE__) //major = cdevsw_add(-1, &rpcdev_cdevsw); //rpcdevice = makedev(major, CDEV_MINOR); //rpcdevfsid = devfs_make_node(rpcdevice, DEVFS_CHAR, 0, 0, 0660, RPCDEV_NAME); #else #error no devfs hook in rpcdev_init #endif } return; } void rpcdev_uninit(void) { int i; rpcdev_purge(); cmtx_destroy(&rpcdev_xid_mtx); for (i=0; i <= RPCDEV_MAX_TYPE; i++) { cthread_t *td; selwakeup(&rpcdev_services[i].svc_si); cmtx_destroy(&rpcdev_services[i].svc_newq_mtx); cmtx_destroy(&rpcdev_services[i].svc_waitq_mtx); cmtx_destroy(&rpcdev_services[i].svc_opener_mtx); if ((td = rpcdev_services[i].svc_opener) != NULL) { RPCDEV_DEBUG("type %s reader proc %d killed", rpcdev_services[i].svc_name, cthread_proc(td)->p_pid); #if defined(__FreeBSD__) PROC_LOCK(cthread_proc(td)); #endif psignal(cthread_proc(td), SIGKILL); #if defined(__FreeBSD__) PROC_UNLOCK(cthread_proc(td)); #endif } #if defined(__FreeBSD__) RPCDEV_DEBUG("destroying device /dev/%s - %x", rpcdev_services[i].svc_name, (unsigned int)rpcdev_services[i].svc_device); destroy_dev(rpcdev_services[i].svc_device); #elif defined(__APPLE__) //devfs_remove(rpcdevfsid); #else #error no devfs hook in rpcdev_uninit #endif } return; } /* device interface functions */ static int rpcdev_open(dev_t dev, int flags, int fmt, cthread_t *td) { uint32_t type = rpcdev_dev2type(dev); if (type > RPCDEV_MAX_TYPE) { RPCDEV_DEBUG("bad minor number"); return (ENODEV); } cmtx_lock(&rpcdev_services[type].svc_opener_mtx); /* XXX this is very redundant */ if (dev != rpcdev_services[type].svc_device) { cmtx_unlock(&rpcdev_services[type].svc_opener_mtx); RPCDEV_DEBUG("device mismatch"); return (ENXIO); } if (rpcdev_services[type].svc_opener != NULL) { cmtx_unlock(&rpcdev_services[type].svc_opener_mtx); RPCDEV_DEBUG("open failed! %s already attached to proc %d", rpcdev_services[type].svc_name, cthread_proc(rpcdev_services[type].svc_opener)->p_pid); return (EBUSY); } rpcdev_services[type].svc_opener = td; cmtx_unlock(&rpcdev_services[type].svc_opener_mtx); RPCDEV_DEBUG("service %s opened by proc %d", rpcdev_services[type].svc_name, (cthread_proc(td))->p_pid); return (0); } static int rpcdev_close(dev_t dev, int flags, int fmt, cthread_t *td) { struct rpcdev_upcall * u; int error = 0; int type; #ifdef __APPLE__ boolean_t funnel_state; funnel_state = thread_funnel_set(kernel_flock, TRUE); #endif /* __APPLE__ */ if ((error = rpcdev_checkowner(dev, td))) { RPCDEV_DEBUG("close owner error"); goto out; } type = rpcdev_dev2type(dev); cmtx_lock(&rpcdev_services[type].svc_waitq_mtx); while (!TAILQ_EMPTY(&rpcdev_services[type].svc_waitq)) { u = TAILQ_FIRST(&rpcdev_services[type].svc_waitq); TAILQ_REMOVE(&rpcdev_services[type].svc_waitq, u, up_entry); u->up_error = EINTR; wakeup(u); } cmtx_unlock(&rpcdev_services[type].svc_waitq_mtx); cmtx_lock(&rpcdev_services[type].svc_opener_mtx); rpcdev_services[type].svc_opener = NULL; cmtx_unlock(&rpcdev_services[type].svc_opener_mtx); RPCDEV_DEBUG("rpc_dev: proc %d unbound from service %s", cthread_proc(td)->p_pid, rpcdev_services[type].svc_name); out: #ifdef __APPLE__ (void) thread_funnel_set(kernel_flock, funnel_state); #endif /* __APPLE__ */ return (error); } static int rpcdev_ioctl(dev_t dev, u_long cmd, caddr_t data, int fflag, cthread_t *td) { int error; uint32_t type; if ((error = rpcdev_checkowner(dev, td))) { RPCDEV_DEBUG("ioctl: checkowner fails"); return (error); } if (data == NULL) return (EFAULT); type = rpcdev_dev2type(dev); if (type != (((struct rpcdev_msg *)data)->msg_type)) { RPCDEV_DEBUG("type mismatch"); return EINVAL; } switch (cmd) { case RPCDEVIOCGET: error = rpcdev_request(data); break; case RPCDEVIOCPUT: error = rpcdev_reply(data); break; default: RPCDEV_DEBUG("rpc_dev: %s unkown ioctl cmd %d", rpcdev_services[type].svc_name, (int)cmd); error = EOPNOTSUPP; break; } return (error); } #if defined(__APPLE__) static int rpcdev_poll(dev_t dev, int events, void * wql, cthread_t *td) #else static int rpcdev_poll(dev_t dev, int events, cthread_t *td) #endif { int revents = 0; int error; uint32_t type; if (!(events & (POLLIN|POLLOUT))) return (0); if ((error = rpcdev_checkowner(dev, td))) { RPCDEV_DEBUG("back check owner"); /* XXX return Error? */ return (0); } type = rpcdev_dev2type(dev); /* check readable data */ cmtx_lock(&rpcdev_services[type].svc_newq_mtx); if (!TAILQ_EMPTY(&rpcdev_services[type].svc_newq)) revents |= POLLIN; cmtx_unlock(&rpcdev_services[type].svc_newq_mtx); cmtx_lock(&rpcdev_services[type].svc_waitq_mtx); if (!TAILQ_EMPTY(&rpcdev_services[type].svc_waitq)) revents |= POLLOUT; cmtx_unlock(&rpcdev_services[type].svc_waitq_mtx); if ((revents & (POLLIN|POLLOUT)) != (POLLIN|POLLOUT)) selrecord(td, &rpcdev_services[type].svc_si); return (revents); } cthread_t * rpcdev_gethandlertd(uint32_t type) { cthread_t *td; if (type > RPCDEV_MAX_TYPE) return (NULL); cmtx_lock(&rpcdev_services[type].svc_opener_mtx); td = rpcdev_services[type].svc_opener; if (td == NULL) cmtx_unlock(&rpcdev_services[type].svc_opener_mtx); return (td); } int rpcdev_puthandlertd(uint32_t type) { if (type > RPCDEV_MAX_TYPE) return (-1); cmtx_unlock(&rpcdev_services[type].svc_opener_mtx); return (0); } int rpcdev_call(uint32_t type, caddr_t req_data, size_t req_len, caddr_t rep_data, size_t * rep_lenp, int sleeponreply) { struct rpcdev_upcall *u; int error = 0; unsigned int xtmp; #if defined(__APPLE__) boolean_t funnel_state; funnel_state = thread_funnel_set(kernel_flock, TRUE); #endif /* __APPLE__ */ if (req_data == NULL || rep_data == NULL || req_len == 0 || *rep_lenp == 0) { error = EFAULT; goto bad; } if (type > RPCDEV_MAX_TYPE) { error = EINVAL; goto bad; } cmtx_lock(&rpcdev_services[type].svc_opener_mtx); if (rpcdev_services[type].svc_opener == NULL) { /* no daemon attached */ cmtx_unlock(&rpcdev_services[type].svc_opener_mtx); error = EINVAL; goto bad; } cmtx_unlock(&rpcdev_services[type].svc_opener_mtx); RPCDEV_DEBUG("upcall %d/%d:%d", type, req_len, *rep_lenp); rpcdev_upcall_get(u); u->up_error = 0; u->up_rep = rep_data; u->up_replen = rep_lenp; u->up_type = type; u->up_req = req_data; u->up_reqlen = req_len; u->up_sleeponreply = sleeponreply; /* get new XID */ cmtx_lock(&rpcdev_xid_mtx); do { RANDOM(xtmp); } while ((xtmp % 256) == 0); rpcdev_xid += xtmp; u->up_xid = rpcdev_xid; cmtx_unlock(&rpcdev_xid_mtx); cmtx_lock(&rpcdev_services[type].svc_newq_mtx); TAILQ_INSERT_TAIL(&rpcdev_services[type].svc_newq, u, up_entry); cmtx_unlock(&rpcdev_services[type].svc_newq_mtx); selwakeup(&rpcdev_services[type].svc_si); RPCDEV_DEBUG("rpc_dev: %s message xid: %x sleeping", rpcdev_services[type].svc_name, u->up_xid); error = tsleep(u, PLOCK, "rpcdev", 0); if (error != 0) goto bad; /* XXX - what about u? */ /* upcall now removed from the queue */ RPCDEV_DEBUG("rpc_dev: %s message xid: %x continues...", rpcdev_services[type].svc_name, u->up_xid); if (u->up_error != 0) { error = u->up_error; RPCDEV_DEBUG("upcall error %d", u->up_error); } rpcdev_upcall_put(u); bad: #ifdef __APPLE__ (void) thread_funnel_set(kernel_flock, funnel_state); #endif /* __APPLE__ */ return (error); } void rpcdev_purge(void) { struct rpcdev_upcall *u; int i; #if defined(__APPLE__) boolean_t funnel_state; #endif #ifdef __APPLE__ funnel_state = thread_funnel_set(kernel_flock, TRUE); #endif /* __APPLE__ */ for (i=0; i <= RPCDEV_MAX_TYPE; i++) { cmtx_lock(&rpcdev_services[i].svc_newq_mtx); while (!TAILQ_EMPTY(&rpcdev_services[i].svc_newq)) { u = TAILQ_FIRST(&rpcdev_services[i].svc_newq); TAILQ_REMOVE(&rpcdev_services[i].svc_newq, u, up_entry); u->up_error = EPIPE; wakeup(u); } cmtx_unlock(&rpcdev_services[i].svc_newq_mtx); cmtx_lock(&rpcdev_services[i].svc_waitq_mtx); while (!TAILQ_EMPTY(&rpcdev_services[i].svc_waitq)) { u = TAILQ_FIRST(&rpcdev_services[i].svc_waitq); TAILQ_REMOVE(&rpcdev_services[i].svc_waitq, u, up_entry); u->up_error = EPIPE; wakeup(u); } cmtx_unlock(&rpcdev_services[i].svc_waitq_mtx); } #ifdef __APPLE__ (void) thread_funnel_set(kernel_flock, funnel_state); #endif /* __APPLE__ */ } static int rpcdev_checkowner(dev_t dev, cthread_t *td) { uint32_t type; if (td == NULL) panic("rpc_dev: null thread\n"); type = rpcdev_dev2type(dev); if (type > RPCDEV_MAX_TYPE) { RPCDEV_DEBUG("bad minor number"); return (ENODEV); } cmtx_lock(&rpcdev_services[type].svc_opener_mtx); if (dev != rpcdev_services[type].svc_device) { cmtx_unlock(&rpcdev_services[type].svc_opener_mtx); RPCDEV_DEBUG("minor <-> dev mismatch"); return (ENODEV); } if (cthread_proc(rpcdev_services[type].svc_opener)->p_pid != cthread_proc(td)->p_pid) { cmtx_unlock(&rpcdev_services[type].svc_opener_mtx); RPCDEV_DEBUG("proc %d is not the owner of service %s", cthread_proc(td)->p_pid, rpcdev_services[type].svc_name); return (EBUSY); } cmtx_unlock(&rpcdev_services[type].svc_opener_mtx); return (0); }