/* * Grow the hash table. Rehash all the elements on the hash table. */ static void port_cache_grow_hashtbl(port_fdcache_t *pcp) { portfd_t **oldtbl; polldat_t *pdp; portfd_t *pfd; polldat_t *pdp1; int oldsize; int i; ASSERT(MUTEX_HELD(&pcp->pc_lock)); oldsize = pcp->pc_hashsize; oldtbl = pcp->pc_hash; pcp->pc_hashsize *= PORTHASH_MULT; pcp->pc_hash = kmem_zalloc(pcp->pc_hashsize * sizeof (portfd_t *), KM_SLEEP); /* * rehash existing elements */ pcp->pc_fdcount = 0; for (i = 0; i < oldsize; i++) { pfd = oldtbl[i]; pdp = PFTOD(pfd); while (pdp != NULL) { pdp1 = pdp->pd_hashnext; port_cache_insert_fd(pcp, pdp); pdp = pdp1; } } kmem_free(oldtbl, oldsize * sizeof (portfd_t *)); }
/* * The port_close_fd() function dissociates a file descriptor from a port * and removes all allocated resources. * close(2) detects in the uf_entry_t structure that the fd is associated * with a port (at least one port). * The fd can be associated with several ports. */ void port_close_pfd(portfd_t *pfd) { port_t *pp; port_fdcache_t *pcp; /* * the portfd_t passed in should be for this proc. */ ASSERT(curproc->p_pid == PFTOD(pfd)->pd_portev->portkev_pid); pp = PFTOD(pfd)->pd_portev->portkev_port; pcp = pp->port_queue.portq_pcp; mutex_enter(&pcp->pc_lock); (void) port_remove_fd_object(pfd, pp, pcp); mutex_exit(&pcp->pc_lock); }
/* * This routine removes a portfd_t from the fd cache's hash table. */ void port_pcache_remove_fd(port_fdcache_t *pcp, portfd_t *pfd) { polldat_t *lpdp; polldat_t *cpdp; portfd_t **bucket; polldat_t *pdp = PFTOD(pfd); ASSERT(MUTEX_HELD(&pcp->pc_lock)); bucket = PORT_FD_BUCKET(pcp, pdp->pd_fd); cpdp = PFTOD(*bucket); if (pdp == cpdp) { *bucket = PDTOF(pdp->pd_hashnext); if (--pcp->pc_fdcount == 0) { /* * signal the thread which may have blocked in * port_close_sourcefd() on lastclose waiting * for pc_fdcount to drop to 0. */ cv_signal(&pcp->pc_lclosecv); } kmem_free(pfd, sizeof (portfd_t)); return; } while (cpdp != NULL) { lpdp = cpdp; cpdp = cpdp->pd_hashnext; if (cpdp == pdp) { /* polldat struct found */ lpdp->pd_hashnext = pdp->pd_hashnext; if (--pcp->pc_fdcount == 0) { /* * signal the thread which may have blocked in * port_close_sourcefd() on lastclose waiting * for pc_fdcount to drop to 0. */ cv_signal(&pcp->pc_lclosecv); } break; } } ASSERT(cpdp != NULL); kmem_free(pfd, sizeof (portfd_t)); }
/* ARGSUSED */ static void port_close_sourcefd(void *arg, int port, pid_t pid, int lastclose) { port_t *pp = arg; port_fdcache_t *pcp; portfd_t **hashtbl; polldat_t *pdp; polldat_t *pdpnext; int index; pcp = pp->port_queue.portq_pcp; if (pcp == NULL) /* no cache available -> nothing to do */ return; mutex_enter(&pcp->pc_lock); /* * Scan the cache and free all allocated portfd_t and port_kevent_t * structures. */ hashtbl = pcp->pc_hash; for (index = 0; index < pcp->pc_hashsize; index++) { for (pdp = PFTOD(hashtbl[index]); pdp != NULL; pdp = pdpnext) { pdpnext = pdp->pd_hashnext; if (pid == pdp->pd_portev->portkev_pid) { /* * remove polldat + port_event_t from cache * only when current process did the * association. */ port_remove_portfd(pdp, pcp); } } } if (lastclose) { /* * Wait for all the portfd's to be freed. * The remaining portfd_t's are the once we did not * free in port_remove_portfd since some other thread * is closing the fd. These threads will free the portfd_t's * once we drop the pc_lock mutex. */ while (pcp->pc_fdcount) { (void) cv_wait_sig(&pcp->pc_lclosecv, &pcp->pc_lock); } /* event port vnode will be destroyed -> remove everything */ pp->port_queue.portq_pcp = NULL; } mutex_exit(&pcp->pc_lock); /* * last close: * pollwakeup() can not further interact with this cache * (all polldat structs are removed from pollhead entries). */ if (lastclose) port_pcache_destroy(pcp); }
/* ARGSUSED */ static int port_fd_callback(void *arg, int *events, pid_t pid, int flag, void *evp) { portfd_t *pfd = (portfd_t *)arg; polldat_t *pdp = PFTOD(pfd); port_fdcache_t *pcp; file_t *fp; int error; ASSERT((pdp != NULL) && (events != NULL)); switch (flag) { case PORT_CALLBACK_DEFAULT: if (curproc->p_pid != pid) { /* * Check if current process is allowed to retrieve * events from this fd. */ fp = getf(pdp->pd_fd); if (fp == NULL) { error = EACCES; /* deny delivery of events */ break; } releasef(pdp->pd_fd); if (fp != pdp->pd_fp) { error = EACCES; /* deny delivery of events */ break; } } *events = pdp->pd_portev->portkev_events; /* update events */ error = 0; break; case PORT_CALLBACK_DISSOCIATE: error = 0; break; case PORT_CALLBACK_CLOSE: /* remove polldat/portfd struct */ pdp->pd_portev = NULL; pcp = (port_fdcache_t *)pdp->pd_pcache; mutex_enter(&pcp->pc_lock); pdp->pd_fp = NULL; pdp->pd_events = 0; if (pdp->pd_php != NULL) { pollhead_delete(pdp->pd_php, pdp); pdp->pd_php = NULL; } port_pcache_remove_fd(pcp, pfd); mutex_exit(&pcp->pc_lock); error = 0; break; default: error = EINVAL; break; } return (error); }
/* * This routine inserts a polldat into the portcache's hash table. It * may be necessary to grow the size of the hash table. */ static void port_cache_insert_fd(port_fdcache_t *pcp, polldat_t *pdp) { portfd_t **bucket; ASSERT(MUTEX_HELD(&pcp->pc_lock)); if (pcp->pc_fdcount > (pcp->pc_hashsize * PORTHASH_MULT)) port_cache_grow_hashtbl(pcp); bucket = PORT_FD_BUCKET(pcp, pdp->pd_fd); pdp->pd_hashnext = PFTOD(*bucket); *bucket = PDTOF(pdp); pcp->pc_fdcount++; }
/* * Remove the fd from the event port cache. */ static void port_remove_fd_local(portfd_t *pfd, port_fdcache_t *pcp) { polldat_t *pdp = PFTOD(pfd); ASSERT(MUTEX_HELD(&pcp->pc_lock)); pdp->pd_fp = NULL; if (pdp->pd_php != NULL) { pollhead_delete(pdp->pd_php, pdp); pdp->pd_php = NULL; } port_free_event_local(pdp->pd_portev, 0); /* remove polldat struct */ port_pcache_remove_fd(pcp, pfd); }
/* * This routine returns a pointer to a cached poll fd entry, or NULL if it * does not find it in the hash table. * The fd is used as index. * The fd and the fp are used to detect a valid entry. * This function returns a pointer to a valid portfd_t structure only when * the fd and the fp in the args match the entries in polldat_t. */ portfd_t * port_cache_lookup_fp(port_fdcache_t *pcp, int fd, file_t *fp) { polldat_t *pdp; portfd_t **bucket; ASSERT(MUTEX_HELD(&pcp->pc_lock)); bucket = PORT_FD_BUCKET(pcp, fd); pdp = PFTOD(*bucket); while (pdp != NULL) { if (pdp->pd_fd == fd && pdp->pd_fp == fp) break; pdp = pdp->pd_hashnext; } return (PDTOF(pdp)); }
/* * The port_dissociate_fd() function dissociates the delivered file * descriptor from the event port and removes already fired events. * If a fd is shared between processes, all involved processes will get * the same rights related to re-association of the fd with the port and * retrieve of events from that fd. * The process which associated the fd with a port for the first time * becomes also the owner of the association. Only the owner of the * association is allowed to dissociate the fd from the port. */ int port_dissociate_fd(port_t *pp, uintptr_t object) { int fd; port_fdcache_t *pcp; portfd_t *pfd; file_t *fp; if (object > (uintptr_t)INT_MAX) return (EBADFD); fd = object; pcp = pp->port_queue.portq_pcp; mutex_enter(&pcp->pc_lock); if (pcp->pc_hash == NULL) { /* no file descriptor cache available */ mutex_exit(&pcp->pc_lock); return (0); } if ((fp = getf(fd)) == NULL) { mutex_exit(&pcp->pc_lock); return (EBADFD); } pfd = port_cache_lookup_fp(pcp, fd, fp); if (pfd == NULL) { releasef(fd); mutex_exit(&pcp->pc_lock); return (0); } /* only association owner is allowed to remove the association */ if (curproc->p_pid != PFTOD(pfd)->pd_portev->portkev_pid) { releasef(fd); mutex_exit(&pcp->pc_lock); return (EACCES); } /* remove port from the file descriptor interested list */ delfd_port(fd, pfd); releasef(fd); /* remove polldat & port event structure */ port_remove_fd_object(pfd, pp, pcp); mutex_exit(&pcp->pc_lock); return (0); }
/* * The port_remove_fd_object() function frees all resources associated with * delivered portfd_t structure. Returns 1 if the port_kevent was found * and removed from the port queue. */ int port_remove_fd_object(portfd_t *pfd, port_t *pp, port_fdcache_t *pcp) { port_queue_t *portq; polldat_t *pdp = PFTOD(pfd); port_kevent_t *pkevp; int error; int removed = 0; ASSERT(MUTEX_HELD(&pcp->pc_lock)); if (pdp->pd_php != NULL) { pollhead_delete(pdp->pd_php, pdp); pdp->pd_php = NULL; } pkevp = pdp->pd_portev; portq = &pp->port_queue; mutex_enter(&portq->portq_mutex); port_block(portq); if (pkevp->portkev_flags & PORT_KEV_DONEQ) { if (portq->portq_getn && portq->portq_tnent) { /* * move events from the temporary "get" queue * back to the port queue */ port_push_eventq(portq); } /* cleanup merged port queue */ port_remove_event_doneq(pkevp, portq); removed = 1; } port_unblock(portq); mutex_exit(&portq->portq_mutex); if (pkevp->portkev_callback) { (void) (*pkevp->portkev_callback)(pkevp->portkev_arg, &error, pkevp->portkev_pid, PORT_CALLBACK_DISSOCIATE, pkevp); } port_free_event_local(pkevp, 0); /* remove polldat struct */ port_pcache_remove_fd(pcp, pfd); return (removed); }
/* * port_associate_fd() * This function associates new file descriptors with a port or * reactivate already associated file descriptors. * The reactivation also updates the events types to be checked and the * attached user pointer. * Per port a cache is used to store associated file descriptors. * Internally the VOP_POLL interface is used to poll for existing events. * The VOP_POLL interface can also deliver a pointer to a pollhead_t structure * which is used to enqueue polldat_t structures with pending events. * If VOP_POLL immediately returns valid events (revents) then those events * will be submitted to the event port with port_send_event(). * Otherwise VOP_POLL does not return events but it delivers a pointer to a * pollhead_t structure. In such a case the corresponding file system behind * VOP_POLL will use the pollwakeup() function to notify about exisiting * events. */ int port_associate_fd(port_t *pp, int source, uintptr_t object, int events, void *user) { port_fdcache_t *pcp; int fd; struct pollhead *php = NULL; portfd_t *pfd; polldat_t *pdp; file_t *fp; port_kevent_t *pkevp; short revents; int error = 0; pcp = pp->port_queue.portq_pcp; if (object > (uintptr_t)INT_MAX) return (EBADFD); fd = object; if ((fp = getf(fd)) == NULL) return (EBADFD); mutex_enter(&pcp->pc_lock); if (pcp->pc_hash == NULL) { /* * This is the first time that a fd is being associated with * the current port: * - create PORT_SOURCE_FD cache * - associate PORT_SOURCE_FD source with the port */ error = port_associate_ksource(pp->port_fd, PORT_SOURCE_FD, NULL, port_close_sourcefd, pp, NULL); if (error) { mutex_exit(&pcp->pc_lock); releasef(fd); return (error); } /* create polldat cache */ pcp->pc_hashsize = PORTHASH_START; pcp->pc_hash = kmem_zalloc(pcp->pc_hashsize * sizeof (portfd_t *), KM_SLEEP); pfd = NULL; } else { /* Check if the fd/fp is already associated with the port */ pfd = port_cache_lookup_fp(pcp, fd, fp); } if (pfd == NULL) { /* * new entry * Allocate a polldat_t structure per fd * The use of the polldat_t structure to cache file descriptors * is required to be able to share the pollwakeup() function * with poll(2) and devpoll(7d). */ pfd = kmem_zalloc(sizeof (portfd_t), KM_SLEEP); pdp = PFTOD(pfd); pdp->pd_fd = fd; pdp->pd_fp = fp; pdp->pd_pcache = (void *)pcp; /* Allocate a port event structure per fd */ error = port_alloc_event_local(pp, source, PORT_ALLOC_CACHED, &pdp->pd_portev); if (error) { kmem_free(pfd, sizeof (portfd_t)); releasef(fd); mutex_exit(&pcp->pc_lock); return (error); } pkevp = pdp->pd_portev; pkevp->portkev_callback = port_fd_callback; pkevp->portkev_arg = pfd; /* add portfd_t entry to the cache */ port_cache_insert_fd(pcp, pdp); pkevp->portkev_object = fd; pkevp->portkev_user = user; /* * Add current port to the file descriptor interested list * The members of the list are notified when the file descriptor * is closed. */ addfd_port(fd, pfd); } else { /* * The file descriptor is already associated with the port */ pdp = PFTOD(pfd); pkevp = pdp->pd_portev; /* * Check if the re-association happens before the last * submitted event of the file descriptor was retrieved. * Clear the PORT_KEV_VALID flag if set. No new events * should get submitted after this flag is cleared. */ mutex_enter(&pkevp->portkev_lock); if (pkevp->portkev_flags & PORT_KEV_VALID) { pkevp->portkev_flags &= ~PORT_KEV_VALID; } if (pkevp->portkev_flags & PORT_KEV_DONEQ) { mutex_exit(&pkevp->portkev_lock); /* * Remove any events that where already fired * for this fd and are still in the port queue. */ port_remove_done_event(pkevp); } else { mutex_exit(&pkevp->portkev_lock); } pkevp->portkev_user = user; } mutex_enter(&pkevp->portkev_lock); pkevp->portkev_events = 0; /* no fired events */ pdp->pd_events = events; /* events associated */ /* * allow new events. */ pkevp->portkev_flags |= PORT_KEV_VALID; mutex_exit(&pkevp->portkev_lock); /* * do VOP_POLL and cache this poll fd. * * XXX - pollrelock() logic needs to know * which pollcache lock to grab. It'd be a * cleaner solution if we could pass pcp as * an arguement in VOP_POLL interface instead * of implicitly passing it using thread_t * struct. On the other hand, changing VOP_POLL * interface will require all driver/file system * poll routine to change. */ curthread->t_pollcache = (pollcache_t *)pcp; error = VOP_POLL(fp->f_vnode, events, 0, &revents, &php); curthread->t_pollcache = NULL; /* * To keep synchronization between VOP_POLL above and * pollhead_insert below, it is necessary to * call VOP_POLL() again (see port_bind_pollhead()). */ if (error) { /* dissociate the fd from the port */ delfd_port(fd, pfd); port_remove_fd_local(pfd, pcp); releasef(fd); mutex_exit(&pcp->pc_lock); return (error); } if (php != NULL) { /* * No events delivered yet. * Bind pollhead pointer with current polldat_t structure. * Sub-system will call pollwakeup() later with php as * argument. */ error = port_bind_pollhead(&php, pdp, &revents); if (error) { delfd_port(fd, pfd); port_remove_fd_local(pfd, pcp); releasef(fd); mutex_exit(&pcp->pc_lock); return (error); } } /* * Check if new events where detected and no events have been * delivered. The revents was already set after the VOP_POLL * above or it was updated in port_bind_pollhead(). */ mutex_enter(&pkevp->portkev_lock); if (revents && (pkevp->portkev_flags & PORT_KEV_VALID)) { ASSERT((pkevp->portkev_flags & PORT_KEV_DONEQ) == 0); pkevp->portkev_flags &= ~PORT_KEV_VALID; revents = revents & (pdp->pd_events | POLLHUP | POLLERR); /* send events to the event port */ pkevp->portkev_events = revents; /* * port_send_event will release the portkev_lock mutex. */ port_send_event(pkevp); } else { mutex_exit(&pkevp->portkev_lock); } releasef(fd); mutex_exit(&pcp->pc_lock); return (error); }