#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/domain.h>
#include <sys/fcntl.h>
#include <sys/malloc.h>
#include <sys/proc.h>
#include <sys/file.h>
#include <sys/filedesc.h>
#include <sys/mbuf.h>
#include <sys/nlookup.h>
#include <sys/protosw.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/resourcevar.h>
#include <sys/stat.h>
#include <sys/mount.h>
#include <sys/sysctl.h>
#include <sys/un.h>
#include <sys/unpcb.h>
#include <sys/vnode.h>
#include <sys/kern_syscall.h>
#include <sys/taskqueue.h>
#include <sys/file2.h>
#include <sys/spinlock2.h>
#include <sys/socketvar2.h>
#include <sys/msgport2.h>
#define UNP_DETACHED UNP_PRIVATE1
#define UNP_CONNECTING UNP_PRIVATE2
#define UNP_DROPPED UNP_PRIVATE3
#define UNP_MARKER UNP_PRIVATE4
#define UNPGC_REF 0x1
#define UNPGC_DEAD 0x2
#define UNPGC_SCANNED 0x4
#define UNP_GCFILE_MAX 256
CTASSERT(sizeof(struct file *) >= sizeof(int));
#define UNP_ISATTACHED(unp) \
((unp) != NULL && ((unp)->unp_flags & UNP_DETACHED) == 0)
#ifdef INVARIANTS
#define UNP_ASSERT_TOKEN_HELD(unp) \
ASSERT_LWKT_TOKEN_HELD(lwkt_token_pool_lookup((unp)))
#else
#define UNP_ASSERT_TOKEN_HELD(unp)
#endif
struct unp_defdiscard {
SLIST_ENTRY(unp_defdiscard) next;
struct file *fp;
};
SLIST_HEAD(unp_defdiscard_list, unp_defdiscard);
TAILQ_HEAD(unpcb_qhead, unpcb);
struct unp_global_head {
struct unpcb_qhead list;
int count;
};
static MALLOC_DEFINE(M_UNPCB, "unpcb", "unpcb struct");
static unp_gen_t unp_gencnt;
static struct unp_global_head unp_stream_head;
static struct unp_global_head unp_dgram_head;
static struct unp_global_head unp_seqpkt_head;
static struct unp_global_head * const unp_heads[] =
{ &unp_stream_head, &unp_dgram_head, &unp_seqpkt_head, NULL };
static struct lwkt_token unp_token = LWKT_TOKEN_INITIALIZER(unp_token);
static struct taskqueue *unp_taskqueue;
static struct unp_defdiscard_list unp_defdiscard_head;
static struct spinlock unp_defdiscard_spin;
static struct task unp_defdiscard_task;
static struct sockaddr sun_noname = { sizeof(sun_noname), AF_LOCAL };
static int unp_attach (struct socket *, struct pru_attach_info *);
static void unp_detach (struct unpcb *);
static int unp_bind (struct unpcb *,struct sockaddr *, struct thread *);
static int unp_connect (struct socket *,struct sockaddr *,
struct thread *);
static void unp_disconnect(struct unpcb *, int);
static void unp_shutdown (struct unpcb *);
static void unp_gc(void *, int);
#ifdef UNP_GC_ALLFILES
static int unp_gc_clearmarks(struct file *, void *);
static int unp_gc_checkmarks(struct file *, void *);
static int unp_gc_checkrefs(struct file *, void *);
static void unp_mark(struct file *, void *data);
#endif
static void unp_scan (struct mbuf *, void (*)(struct file *, void *),
void *data);
static void unp_discard (struct file *, void *);
static int unp_internalize (struct mbuf *, struct thread *);
static int unp_listen (struct unpcb *, struct thread *);
static void unp_fp_externalize(struct lwp *lp, struct file *fp, int fd,
int flags);
static int unp_find_lockref(struct sockaddr *nam, struct thread *td,
short type, struct unpcb **unp_ret);
static int unp_connect_pair(struct unpcb *unp, struct unpcb *unp2);
static void unp_drop(struct unpcb *unp, int error);
static void unp_defdiscard_taskfunc(void *, int);
static int unp_rights;
static struct lwkt_token unp_rights_token =
LWKT_TOKEN_INITIALIZER(unp_rights_token);
static struct task unp_gc_task;
static struct unpcb *unp_gc_marker;
SYSCTL_DECL(_net_local);
SYSCTL_INT(_net_local, OID_AUTO, inflight, CTLFLAG_RD, &unp_rights, 0,
"File descriptors in flight");
static __inline void
unp_reference(struct unpcb *unp)
{
KKASSERT(unp->unp_refcnt > 0);
atomic_add_int(&unp->unp_refcnt, 1);
}
static __inline void
unp_free(struct unpcb *unp)
{
KKASSERT(unp->unp_refcnt > 0);
if (atomic_fetchadd_int(&unp->unp_refcnt, -1) == 1)
unp_detach(unp);
}
static __inline struct unpcb *
unp_getsocktoken(struct socket *so)
{
struct unpcb *unp;
while ((unp = so->so_pcb) != NULL) {
lwkt_getpooltoken(unp);
if (unp == so->so_pcb)
break;
lwkt_relpooltoken(unp);
}
return unp;
}
static __inline void
unp_reltoken(struct unpcb *unp)
{
if (unp != NULL)
lwkt_relpooltoken(unp);
}
static __inline void
unp_setflags(struct unpcb *unp, int flags)
{
atomic_set_int(&unp->unp_flags, flags);
}
static __inline void
unp_clrflags(struct unpcb *unp, int flags)
{
atomic_clear_int(&unp->unp_flags, flags);
}
static __inline struct unp_global_head *
unp_globalhead(short type)
{
switch (type) {
case SOCK_STREAM:
return &unp_stream_head;
case SOCK_DGRAM:
return &unp_dgram_head;
case SOCK_SEQPACKET:
return &unp_seqpkt_head;
default:
panic("unknown socket type %d", type);
}
}
static __inline struct unpcb *
unp_fp2unpcb(struct file *fp)
{
struct socket *so;
if (fp->f_type != DTYPE_SOCKET)
return NULL;
so = fp->f_data;
if (so == NULL)
return NULL;
if (so->so_proto->pr_domain != &localdomain)
return NULL;
return so->so_pcb;
}
static __inline void
unp_add_right(struct file *fp)
{
struct unpcb *unp;
ASSERT_LWKT_TOKEN_HELD(&unp_rights_token);
KASSERT(fp->f_count > 0, ("invalid f_count %d", fp->f_count));
unp = unp_fp2unpcb(fp);
if (unp != NULL) {
unp->unp_fp = fp;
unp->unp_msgcount++;
}
fp->f_msgcount++;
unp_rights++;
}
static __inline void
unp_del_right(struct file *fp)
{
struct unpcb *unp;
ASSERT_LWKT_TOKEN_HELD(&unp_rights_token);
KASSERT(fp->f_count > 0, ("invalid f_count %d", fp->f_count));
unp = unp_fp2unpcb(fp);
if (unp != NULL) {
KASSERT(unp->unp_msgcount > 0,
("invalid unp msgcount %d", unp->unp_msgcount));
unp->unp_msgcount--;
if (unp->unp_msgcount == 0)
unp->unp_fp = NULL;
}
fp->f_msgcount--;
unp_rights--;
}
static void
uipc_abort(netmsg_t msg)
{
struct unpcb *unp;
int error;
lwkt_gettoken(&unp_token);
unp = unp_getsocktoken(msg->base.nm_so);
if (UNP_ISATTACHED(unp)) {
unp_drop(unp, ECONNABORTED);
error = 0;
} else {
error = EINVAL;
}
unp_reltoken(unp);
lwkt_reltoken(&unp_token);
lwkt_replymsg(&msg->lmsg, error);
}
static void
uipc_accept(netmsg_t msg)
{
struct unpcb *unp;
int error;
lwkt_gettoken(&unp_token);
unp = unp_getsocktoken(msg->base.nm_so);
if (!UNP_ISATTACHED(unp)) {
error = EINVAL;
} else {
struct unpcb *unp2 = unp->unp_conn;
if (unp2 && unp2->unp_addr) {
unp_reference(unp2);
*msg->accept.nm_nam = dup_sockaddr(
(struct sockaddr *)unp2->unp_addr);
unp_free(unp2);
} else {
*msg->accept.nm_nam = dup_sockaddr(&sun_noname);
}
error = 0;
}
unp_reltoken(unp);
lwkt_reltoken(&unp_token);
lwkt_replymsg(&msg->lmsg, error);
}
static void
uipc_attach(netmsg_t msg)
{
int error;
lwkt_gettoken(&unp_token);
KASSERT(msg->base.nm_so->so_pcb == NULL, ("double unp attach"));
error = unp_attach(msg->base.nm_so, msg->attach.nm_ai);
lwkt_reltoken(&unp_token);
lwkt_replymsg(&msg->lmsg, error);
}
static void
uipc_bind(netmsg_t msg)
{
struct unpcb *unp;
int error;
lwkt_gettoken(&unp_token);
unp = unp_getsocktoken(msg->base.nm_so);
if (UNP_ISATTACHED(unp))
error = unp_bind(unp, msg->bind.nm_nam, msg->bind.nm_td);
else
error = EINVAL;
unp_reltoken(unp);
lwkt_reltoken(&unp_token);
lwkt_replymsg(&msg->lmsg, error);
}
static void
uipc_connect(netmsg_t msg)
{
int error;
error = unp_connect(msg->base.nm_so, msg->connect.nm_nam,
msg->connect.nm_td);
lwkt_replymsg(&msg->lmsg, error);
}
static void
uipc_connect2(netmsg_t msg)
{
int error;
error = unp_connect2(msg->connect2.nm_so1, msg->connect2.nm_so2,
msg->connect2.nm_cred);
lwkt_replymsg(&msg->lmsg, error);
}
static void
uipc_detach(netmsg_t msg)
{
struct unpcb *unp;
int error;
lwkt_gettoken(&unp_token);
unp = unp_getsocktoken(msg->base.nm_so);
if (UNP_ISATTACHED(unp)) {
unp_drop(unp, 0);
error = 0;
} else {
error = EINVAL;
}
unp_reltoken(unp);
lwkt_reltoken(&unp_token);
lwkt_replymsg(&msg->lmsg, error);
}
static void
uipc_disconnect(netmsg_t msg)
{
struct unpcb *unp;
int error;
lwkt_gettoken(&unp_token);
unp = unp_getsocktoken(msg->base.nm_so);
if (UNP_ISATTACHED(unp)) {
unp_disconnect(unp, 0);
error = 0;
} else {
error = EINVAL;
}
unp_reltoken(unp);
lwkt_reltoken(&unp_token);
lwkt_replymsg(&msg->lmsg, error);
}
static void
uipc_listen(netmsg_t msg)
{
struct unpcb *unp;
int error;
lwkt_gettoken(&unp_token);
unp = unp_getsocktoken(msg->base.nm_so);
if (!UNP_ISATTACHED(unp) || unp->unp_vnode == NULL)
error = EINVAL;
else
error = unp_listen(unp, msg->listen.nm_td);
unp_reltoken(unp);
lwkt_reltoken(&unp_token);
lwkt_replymsg(&msg->lmsg, error);
}
static void
uipc_peeraddr(netmsg_t msg)
{
struct unpcb *unp;
int error;
lwkt_gettoken(&unp_token);
unp = unp_getsocktoken(msg->base.nm_so);
if (!UNP_ISATTACHED(unp)) {
error = EINVAL;
} else if (unp->unp_conn && unp->unp_conn->unp_addr) {
struct unpcb *unp2 = unp->unp_conn;
unp_reference(unp2);
*msg->peeraddr.nm_nam = dup_sockaddr(
(struct sockaddr *)unp2->unp_addr);
unp_free(unp2);
error = 0;
} else {
*msg->peeraddr.nm_nam = dup_sockaddr(&sun_noname);
error = 0;
}
unp_reltoken(unp);
lwkt_reltoken(&unp_token);
lwkt_replymsg(&msg->lmsg, error);
}
static void
uipc_rcvd(netmsg_t msg)
{
struct unpcb *unp, *unp2;
struct socket *so;
struct socket *so2;
int error;
so = msg->base.nm_so;
unp = unp_getsocktoken(so);
if (!UNP_ISATTACHED(unp)) {
error = EINVAL;
goto done;
}
switch (so->so_type) {
case SOCK_DGRAM:
panic("uipc_rcvd DGRAM?");
case SOCK_STREAM:
case SOCK_SEQPACKET:
if (unp->unp_conn == NULL)
break;
unp2 = unp->unp_conn;
so2 = unp2->unp_socket;
unp_reference(unp2);
lwkt_gettoken(&so2->so_rcv.ssb_token);
if (so->so_rcv.ssb_cc < so2->so_snd.ssb_hiwat &&
so->so_rcv.ssb_mbcnt < so2->so_snd.ssb_mbmax
) {
atomic_clear_int(&so2->so_snd.ssb_flags, SSB_STOP);
sowwakeup(so2);
}
lwkt_reltoken(&so2->so_rcv.ssb_token);
unp_free(unp2);
break;
default:
panic("uipc_rcvd unknown socktype");
}
error = 0;
done:
unp_reltoken(unp);
lwkt_replymsg(&msg->lmsg, error);
}
static void
uipc_send(netmsg_t msg)
{
struct unpcb *unp, *unp2;
struct socket *so;
struct socket *so2;
struct mbuf *control;
struct mbuf *m;
int error = 0;
so = msg->base.nm_so;
control = msg->send.nm_control;
m = msg->send.nm_m;
so = msg->base.nm_so;
unp = unp_getsocktoken(so);
if (!UNP_ISATTACHED(unp)) {
error = EINVAL;
goto release;
}
if (msg->send.nm_flags & PRUS_OOB) {
error = EOPNOTSUPP;
goto release;
}
wakeup_start_delayed();
if (control && (error = unp_internalize(control, msg->send.nm_td)))
goto release;
switch (so->so_type) {
case SOCK_DGRAM:
{
struct sockaddr *from;
if (msg->send.nm_addr) {
if (unp->unp_conn) {
error = EISCONN;
break;
}
lwkt_gettoken(&unp_token);
error = unp_find_lockref(msg->send.nm_addr,
msg->send.nm_td, so->so_type, &unp2);
if (error) {
lwkt_reltoken(&unp_token);
break;
}
unp_reltoken(unp2);
lwkt_reltoken(&unp_token);
} else {
if (unp->unp_conn == NULL) {
error = ENOTCONN;
break;
}
unp2 = unp->unp_conn;
unp_reference(unp2);
}
so2 = unp2->unp_socket;
if (so2->so_options & SO_PASSCRED) {
struct mbuf **mp;
struct cmsghdr *cm;
struct cmsgcred cred;
struct mbuf *ncon;
mp = &control;
while ((ncon = *mp) != NULL) {
cm = mtod(ncon, struct cmsghdr *);
if (cm->cmsg_type == SCM_CREDS &&
cm->cmsg_level == SOL_SOCKET)
break;
mp = &ncon->m_next;
}
if (ncon == NULL) {
ncon = sbcreatecontrol(&cred, sizeof(cred),
SCM_CREDS, SOL_SOCKET);
unp_internalize(ncon, msg->send.nm_td);
*mp = ncon;
}
}
if (unp->unp_addr)
from = (struct sockaddr *)unp->unp_addr;
else
from = &sun_noname;
lwkt_gettoken(&so2->so_rcv.ssb_token);
if (ssb_appendaddr(&so2->so_rcv, from, m, control)) {
sorwakeup(so2);
m = NULL;
control = NULL;
} else {
error = ENOBUFS;
}
lwkt_reltoken(&so2->so_rcv.ssb_token);
unp_free(unp2);
break;
}
case SOCK_STREAM:
case SOCK_SEQPACKET:
if (unp->unp_conn == NULL) {
if (msg->send.nm_addr) {
error = unp_connect(so,
msg->send.nm_addr,
msg->send.nm_td);
if (error)
break;
}
if (unp->unp_conn == NULL) {
error = ENOTCONN;
break;
}
}
if (so->so_state & SS_CANTSENDMORE) {
error = EPIPE;
break;
}
unp2 = unp->unp_conn;
KASSERT(unp2 != NULL, ("unp is not connected"));
so2 = unp2->unp_socket;
unp_reference(unp2);
lwkt_gettoken(&so2->so_rcv.ssb_token);
if (control) {
if (ssb_appendcontrol(&so2->so_rcv, m, control)) {
control = NULL;
m = NULL;
}
} else if (so->so_type == SOCK_SEQPACKET) {
sbappendrecord(&so2->so_rcv.sb, m);
m = NULL;
} else {
sbappend(&so2->so_rcv.sb, m);
m = NULL;
}
if (so2->so_rcv.ssb_cc >= so->so_snd.ssb_hiwat ||
so2->so_rcv.ssb_mbcnt >= so->so_snd.ssb_mbmax
) {
atomic_set_int(&so->so_snd.ssb_flags, SSB_STOP);
}
lwkt_reltoken(&so2->so_rcv.ssb_token);
sorwakeup(so2);
unp_free(unp2);
break;
default:
panic("uipc_send unknown socktype");
}
if (msg->send.nm_flags & PRUS_EOF) {
socantsendmore(so);
unp_shutdown(unp);
}
if (control && error != 0)
unp_dispose(control);
release:
unp_reltoken(unp);
wakeup_end_delayed();
if (control)
m_freem(control);
if (m)
m_freem(m);
lwkt_replymsg(&msg->lmsg, error);
}
static void
uipc_sense(netmsg_t msg)
{
struct unpcb *unp;
struct socket *so;
struct stat *sb;
int error;
so = msg->base.nm_so;
sb = msg->sense.nm_stat;
unp = unp_getsocktoken(so);
if (!UNP_ISATTACHED(unp)) {
error = EINVAL;
goto done;
}
sb->st_blksize = so->so_snd.ssb_hiwat;
sb->st_dev = NOUDEV;
error = 0;
done:
unp_reltoken(unp);
lwkt_replymsg(&msg->lmsg, error);
}
static void
uipc_shutdown(netmsg_t msg)
{
struct socket *so;
struct unpcb *unp;
int error;
so = msg->base.nm_so;
unp = unp_getsocktoken(so);
if (UNP_ISATTACHED(unp)) {
socantsendmore(so);
unp_shutdown(unp);
error = 0;
} else {
error = EINVAL;
}
unp_reltoken(unp);
lwkt_replymsg(&msg->lmsg, error);
}
static void
uipc_sockaddr(netmsg_t msg)
{
struct unpcb *unp;
int error;
unp = unp_getsocktoken(msg->base.nm_so);
if (UNP_ISATTACHED(unp)) {
if (unp->unp_addr) {
*msg->sockaddr.nm_nam =
dup_sockaddr((struct sockaddr *)unp->unp_addr);
}
error = 0;
} else {
error = EINVAL;
}
unp_reltoken(unp);
lwkt_replymsg(&msg->lmsg, error);
}
struct pr_usrreqs uipc_usrreqs = {
.pru_abort = uipc_abort,
.pru_accept = uipc_accept,
.pru_attach = uipc_attach,
.pru_bind = uipc_bind,
.pru_connect = uipc_connect,
.pru_connect2 = uipc_connect2,
.pru_control = pr_generic_notsupp,
.pru_detach = uipc_detach,
.pru_disconnect = uipc_disconnect,
.pru_listen = uipc_listen,
.pru_peeraddr = uipc_peeraddr,
.pru_rcvd = uipc_rcvd,
.pru_rcvoob = pr_generic_notsupp,
.pru_send = uipc_send,
.pru_sense = uipc_sense,
.pru_shutdown = uipc_shutdown,
.pru_sockaddr = uipc_sockaddr,
.pru_sosend = sosend,
.pru_soreceive = soreceive
};
void
uipc_ctloutput(netmsg_t msg)
{
struct socket *so;
struct sockopt *sopt;
struct unpcb *unp;
int error = 0;
so = msg->base.nm_so;
sopt = msg->ctloutput.nm_sopt;
lwkt_gettoken(&unp_token);
unp = unp_getsocktoken(so);
if (!UNP_ISATTACHED(unp)) {
error = EINVAL;
goto done;
}
switch (sopt->sopt_dir) {
case SOPT_GET:
switch (sopt->sopt_name) {
case LOCAL_PEERCRED:
if (unp->unp_flags & UNP_HAVEPC)
soopt_from_kbuf(sopt, &unp->unp_peercred,
sizeof(unp->unp_peercred));
else {
if (so->so_type == SOCK_STREAM)
error = ENOTCONN;
else if (so->so_type == SOCK_SEQPACKET)
error = ENOTCONN;
else
error = EINVAL;
}
break;
default:
error = EOPNOTSUPP;
break;
}
break;
case SOPT_SET:
default:
error = EOPNOTSUPP;
break;
}
done:
unp_reltoken(unp);
lwkt_reltoken(&unp_token);
lwkt_replymsg(&msg->lmsg, error);
}
#ifndef PIPSIZ
#define PIPSIZ 65536
#endif
static u_long unpst_sendspace = PIPSIZ;
static u_long unpst_recvspace = PIPSIZ;
static u_long unpdg_sendspace = PIPSIZ;
static u_long unpdg_recvspace = PIPSIZ;
static u_long unpsp_sendspace = PIPSIZ;
static u_long unpsp_recvspace = PIPSIZ;
SYSCTL_DECL(_net_local_stream);
SYSCTL_DECL(_net_local_dgram);
SYSCTL_DECL(_net_local_seqpacket);
SYSCTL_ULONG(_net_local_stream, OID_AUTO, sendspace, CTLFLAG_RW,
&unpst_sendspace, 0, "Size of stream socket send buffer");
SYSCTL_ULONG(_net_local_stream, OID_AUTO, recvspace, CTLFLAG_RW,
&unpst_recvspace, 0, "Size of stream socket receive buffer");
SYSCTL_ULONG(_net_local_dgram, OID_AUTO, maxdgram, CTLFLAG_RW,
&unpdg_sendspace, 0, "Max datagram socket size");
SYSCTL_ULONG(_net_local_dgram, OID_AUTO, recvspace, CTLFLAG_RW,
&unpdg_recvspace, 0, "Size of datagram socket receive buffer");
SYSCTL_ULONG(_net_local_seqpacket, OID_AUTO, maxseqpacket, CTLFLAG_RW,
&unpsp_sendspace, 0, "Default seqpacket send space.");
SYSCTL_ULONG(_net_local_seqpacket, OID_AUTO, recvspace, CTLFLAG_RW,
&unpsp_recvspace, 0, "Default seqpacket receive space.");
static int
unp_attach(struct socket *so, struct pru_attach_info *ai)
{
struct unp_global_head *head;
struct unpcb *unp;
int error;
lwkt_gettoken(&unp_token);
if (so->so_snd.ssb_hiwat == 0 || so->so_rcv.ssb_hiwat == 0) {
switch (so->so_type) {
case SOCK_STREAM:
error = soreserve(so, unpst_sendspace, unpst_recvspace,
ai->sb_rlimit);
break;
case SOCK_DGRAM:
error = soreserve(so, unpdg_sendspace, unpdg_recvspace,
ai->sb_rlimit);
break;
case SOCK_SEQPACKET:
error = soreserve(so, unpsp_sendspace, unpsp_recvspace,
ai->sb_rlimit);
break;
default:
panic("unp_attach");
}
if (error)
goto failed;
}
if (so->so_type == SOCK_STREAM) {
atomic_set_int(&so->so_rcv.ssb_flags, SSB_STOPSUPP);
atomic_set_int(&so->so_snd.ssb_flags, SSB_STOPSUPP);
}
unp = kmalloc(sizeof(*unp), M_UNPCB, M_WAITOK | M_ZERO | M_NULLOK);
if (unp == NULL) {
error = ENOBUFS;
goto failed;
}
unp->unp_refcnt = 1;
unp->unp_gencnt = ++unp_gencnt;
LIST_INIT(&unp->unp_refs);
unp->unp_socket = so;
unp->unp_rvnode = ai->fd_rdir;
so->so_pcb = (caddr_t)unp;
soreference(so);
head = unp_globalhead(so->so_type);
TAILQ_INSERT_TAIL(&head->list, unp, unp_link);
head->count++;
error = 0;
failed:
lwkt_reltoken(&unp_token);
return error;
}
static void
unp_detach(struct unpcb *unp)
{
struct socket *so;
lwkt_gettoken(&unp_token);
lwkt_getpooltoken(unp);
so = unp->unp_socket;
unp->unp_gencnt = ++unp_gencnt;
if (unp->unp_vnode) {
unp->unp_vnode->v_socket = NULL;
vrele(unp->unp_vnode);
unp->unp_vnode = NULL;
}
soisdisconnected(so);
KKASSERT(so->so_pcb == unp);
so->so_pcb = NULL;
unp->unp_socket = NULL;
lwkt_relpooltoken(unp);
lwkt_reltoken(&unp_token);
sofree(so);
KASSERT(unp->unp_conn == NULL, ("unp is still connected"));
KASSERT(LIST_EMPTY(&unp->unp_refs), ("unp still has references"));
if (unp->unp_addr)
kfree(unp->unp_addr, M_SONAME);
kfree(unp, M_UNPCB);
if (unp_rights)
taskqueue_enqueue(unp_taskqueue, &unp_gc_task);
}
static int
unp_bind(struct unpcb *unp, struct sockaddr *nam, struct thread *td)
{
struct proc *p = td->td_proc;
struct sockaddr_un *soun = (struct sockaddr_un *)nam;
struct vnode *vp;
struct vattr vattr;
int error, namelen;
struct nlookupdata nd;
char buf[SOCK_MAXADDRLEN];
ASSERT_LWKT_TOKEN_HELD(&unp_token);
UNP_ASSERT_TOKEN_HELD(unp);
if (unp->unp_vnode != NULL)
return EINVAL;
namelen = soun->sun_len - offsetof(struct sockaddr_un, sun_path);
if (namelen <= 0)
return EINVAL;
strncpy(buf, soun->sun_path, namelen);
buf[namelen] = 0;
error = nlookup_init(&nd, buf, UIO_SYSSPACE,
NLC_LOCKVP | NLC_CREATE | NLC_REFDVP);
if (error == 0)
error = nlookup(&nd);
if (error == 0 && nd.nl_nch.ncp->nc_vp != NULL)
error = EADDRINUSE;
if (error == 0 && nd.nl_dvp == NULL)
error = EINVAL;
if (error)
goto done;
VATTR_NULL(&vattr);
vattr.va_type = VSOCK;
vattr.va_mode = (ACCESSPERMS & ~p->p_fd->fd_cmask);
error = VOP_NCREATE(&nd.nl_nch, nd.nl_dvp, &vp, nd.nl_cred, &vattr);
if (error == 0) {
if (unp->unp_vnode == NULL) {
vp->v_socket = unp->unp_socket;
unp->unp_vnode = vp;
unp->unp_addr = (struct sockaddr_un *)dup_sockaddr(nam);
vn_unlock(vp);
} else {
vput(vp);
error = EINVAL;
}
}
done:
nlookup_done(&nd);
return (error);
}
static int
unp_connect(struct socket *so, struct sockaddr *nam, struct thread *td)
{
struct unpcb *unp, *unp2;
int error, flags = 0;
lwkt_gettoken(&unp_token);
unp = unp_getsocktoken(so);
if (!UNP_ISATTACHED(unp)) {
error = EINVAL;
goto failed;
}
if ((unp->unp_flags & UNP_CONNECTING) || unp->unp_conn != NULL) {
error = EISCONN;
goto failed;
}
flags = UNP_CONNECTING;
unp_setflags(unp, flags);
error = unp_find_lockref(nam, td, so->so_type, &unp2);
if (error)
goto failed;
if (so->so_proto->pr_flags & PR_CONNREQUIRED) {
struct socket *so2, *so3;
struct unpcb *unp3;
so2 = unp2->unp_socket;
if (!(so2->so_options & SO_ACCEPTCONN) ||
!(unp2->unp_flags & UNP_HAVEPCCACHED) ||
(so3 = sonewconn_faddr(so2, 0, NULL,
TRUE )) == NULL) {
error = ECONNREFUSED;
goto done;
}
unp3 = unp_getsocktoken(so3);
if (!UNP_ISATTACHED(unp3)) {
unp_reltoken(unp3);
sofree(so3);
error = ECONNREFUSED;
goto done;
}
unp_reference(unp3);
sofree(so3);
if (unp2->unp_addr != NULL) {
unp3->unp_addr = (struct sockaddr_un *)
dup_sockaddr((struct sockaddr *)unp2->unp_addr);
}
cru2x(td->td_proc->p_ucred, &unp3->unp_peercred);
unp_setflags(unp3, UNP_HAVEPC);
KASSERT(unp2->unp_flags & UNP_HAVEPCCACHED,
("unp_connect: listener without cached peercred"));
memcpy(&unp->unp_peercred, &unp2->unp_peercred,
sizeof(unp->unp_peercred));
unp_setflags(unp, UNP_HAVEPC);
error = unp_connect_pair(unp, unp3);
if (error)
soabort_direct(so3);
unp_free(unp3);
unp_reltoken(unp3);
} else {
error = unp_connect_pair(unp, unp2);
}
done:
unp_free(unp2);
unp_reltoken(unp2);
failed:
if (flags)
unp_clrflags(unp, flags);
unp_reltoken(unp);
lwkt_reltoken(&unp_token);
return (error);
}
int
unp_connect2(struct socket *so, struct socket *so2, struct ucred *cred)
{
struct unpcb *unp, *unp2;
int error;
lwkt_gettoken(&unp_token);
if (so2->so_type != so->so_type) {
lwkt_reltoken(&unp_token);
return (EPROTOTYPE);
}
unp = unp_getsocktoken(so);
unp2 = unp_getsocktoken(so2);
cru2x(cred, &unp->unp_peercred);
cru2x(cred, &unp2->unp_peercred);
unp_setflags(unp, UNP_HAVEPC);
unp_setflags(unp2, UNP_HAVEPC);
if (!UNP_ISATTACHED(unp)) {
error = EINVAL;
goto done;
}
if (!UNP_ISATTACHED(unp2)) {
error = ECONNREFUSED;
goto done;
}
if (unp->unp_conn != NULL) {
error = EISCONN;
goto done;
}
if ((so->so_type == SOCK_STREAM || so->so_type == SOCK_SEQPACKET) &&
unp2->unp_conn != NULL) {
error = EISCONN;
goto done;
}
error = unp_connect_pair(unp, unp2);
done:
unp_reltoken(unp2);
unp_reltoken(unp);
lwkt_reltoken(&unp_token);
return (error);
}
static void
unp_disconnect(struct unpcb *unp, int error)
{
struct socket *so = unp->unp_socket;
struct unpcb *unp2;
ASSERT_LWKT_TOKEN_HELD(&unp_token);
UNP_ASSERT_TOKEN_HELD(unp);
if (error)
so->so_error = error;
while ((unp2 = unp->unp_conn) != NULL) {
lwkt_getpooltoken(unp2);
if (unp2 == unp->unp_conn)
break;
lwkt_relpooltoken(unp2);
}
if (unp2 == NULL)
return;
KASSERT((unp2->unp_flags & UNP_DROPPED) == 0, ("unp2 was dropped"));
unp->unp_conn = NULL;
switch (so->so_type) {
case SOCK_DGRAM:
LIST_REMOVE(unp, unp_reflink);
soclrstate(so, SS_ISCONNECTED);
break;
case SOCK_STREAM:
case SOCK_SEQPACKET:
unp_reference(unp2);
KASSERT(unp2->unp_conn == unp, ("unp_conn mismatch"));
unp2->unp_conn = NULL;
soisdisconnected(so);
soisdisconnected(unp2->unp_socket);
unp_free(unp2);
break;
}
lwkt_relpooltoken(unp2);
}
#ifdef notdef
void
unp_abort(struct unpcb *unp)
{
lwkt_gettoken(&unp_token);
unp_free(unp);
lwkt_reltoken(&unp_token);
}
#endif
static int
prison_unpcb(struct thread *td, struct unpcb *unp)
{
struct proc *p;
if (td == NULL)
return (0);
if ((p = td->td_proc) == NULL)
return (0);
if (!p->p_ucred->cr_prison)
return (0);
if (p->p_fd->fd_rdir == unp->unp_rvnode)
return (0);
return (1);
}
static int
unp_pcblist(SYSCTL_HANDLER_ARGS)
{
struct unp_global_head *head = arg1;
int error, i, n;
struct unpcb *unp, *marker;
KKASSERT(curproc != NULL);
if (req->oldptr == NULL) {
n = head->count;
req->oldidx = (n + n/8) * sizeof(struct xunpcb);
return 0;
}
if (req->newptr != NULL)
return EPERM;
marker = kmalloc(sizeof(*marker), M_UNPCB, M_WAITOK | M_ZERO);
marker->unp_flags |= UNP_MARKER;
lwkt_gettoken(&unp_token);
n = head->count;
i = 0;
error = 0;
TAILQ_INSERT_HEAD(&head->list, marker, unp_link);
while ((unp = TAILQ_NEXT(marker, unp_link)) != NULL && i < n) {
struct xunpcb xu;
TAILQ_REMOVE(&head->list, marker, unp_link);
TAILQ_INSERT_AFTER(&head->list, unp, marker, unp_link);
if (unp->unp_flags & UNP_MARKER)
continue;
if (prison_unpcb(req->td, unp))
continue;
xu.xu_len = sizeof(xu);
xu.xu_unpp = unp;
if (unp->unp_addr) {
bcopy(unp->unp_addr, &xu.xu_addr,
unp->unp_addr->sun_len);
}
if (unp->unp_conn && unp->unp_conn->unp_addr) {
bcopy(unp->unp_conn->unp_addr,
&xu.xu_caddr,
unp->unp_conn->unp_addr->sun_len);
}
bcopy(unp, &xu.xu_unp, sizeof(*unp));
sotoxsocket(unp->unp_socket, &xu.xu_socket);
error = SYSCTL_OUT(req, &xu, sizeof(xu));
if (error)
break;
++i;
}
TAILQ_REMOVE(&head->list, marker, unp_link);
lwkt_reltoken(&unp_token);
kfree(marker, M_UNPCB);
return error;
}
SYSCTL_PROC(_net_local_dgram, OID_AUTO, pcblist, CTLFLAG_RD,
&unp_dgram_head, 0, unp_pcblist, "S,xunpcb",
"List of active local datagram sockets");
SYSCTL_PROC(_net_local_stream, OID_AUTO, pcblist, CTLFLAG_RD,
&unp_stream_head, 0, unp_pcblist, "S,xunpcb",
"List of active local stream sockets");
SYSCTL_PROC(_net_local_seqpacket, OID_AUTO, pcblist, CTLFLAG_RD,
&unp_seqpkt_head, 0, unp_pcblist, "S,xunpcb",
"List of active local seqpacket sockets");
static void
unp_shutdown(struct unpcb *unp)
{
struct socket *so;
if ((unp->unp_socket->so_type == SOCK_STREAM ||
unp->unp_socket->so_type == SOCK_SEQPACKET) &&
unp->unp_conn != NULL && (so = unp->unp_conn->unp_socket)) {
socantrcvmore(so);
}
}
#ifdef notdef
void
unp_drain(void)
{
lwkt_gettoken(&unp_token);
lwkt_reltoken(&unp_token);
}
#endif
int
unp_externalize(struct mbuf *rights, int flags)
{
struct thread *td = curthread;
struct proc *p = td->td_proc;
struct lwp *lp = td->td_lwp;
struct cmsghdr *cm = mtod(rights, struct cmsghdr *);
int *fdp;
int i;
struct file **rp;
struct file *fp;
int newfds = (cm->cmsg_len - (CMSG_DATA(cm) - (u_char *)cm))
/ sizeof(struct file *);
int f;
lwkt_gettoken(&unp_rights_token);
if (!fdavail(p, newfds)) {
rp = (struct file **)CMSG_DATA(cm);
for (i = 0; i < newfds; i++) {
fp = *rp;
*rp++ = NULL;
unp_discard(fp, NULL);
}
lwkt_reltoken(&unp_rights_token);
return (EMSGSIZE);
}
lwkt_gettoken_shared(&revoke_token);
fdp = (int *)CMSG_DATA(cm);
rp = (struct file **)CMSG_DATA(cm);
for (i = 0; i < newfds; i++) {
if (fdalloc(p, 0, &f)) {
int j;
lwkt_reltoken(&revoke_token);
for (j = 0; j < i; j++)
kern_close(fdp[j]);
for (; i < newfds; i++)
unp_discard(rp[i], NULL);
for (i = 0; i < newfds; i++)
rp[i] = NULL;
lwkt_reltoken(&unp_rights_token);
return (EMSGSIZE);
}
fp = rp[i];
unp_fp_externalize(lp, fp, f, flags);
fdp[i] = f;
}
lwkt_reltoken(&revoke_token);
lwkt_reltoken(&unp_rights_token);
cm->cmsg_len = CMSG_LEN(newfds * sizeof(int));
rights->m_len = cm->cmsg_len;
return (0);
}
static void
unp_fp_externalize(struct lwp *lp, struct file *fp, int fd, int flags)
{
if (lp) {
struct filedesc *fdp = lp->lwp_proc->p_fd;
KKASSERT(fd >= 0);
if (fp->f_flag & FREVOKED) {
struct file *fx;
int error;
kprintf("Warning: revoked fp exiting unix socket\n");
error = falloc(lp, &fx, NULL);
if (error == 0) {
if (flags & MSG_CMSG_CLOEXEC)
fdp->fd_files[fd].fileflags |= UF_EXCLOSE;
if (flags & MSG_CMSG_CLOFORK)
fdp->fd_files[fd].fileflags |= UF_FOCLOSE;
fsetfd(fdp, fx, fd);
fdrop(fx);
} else {
fsetfd(fdp, NULL, fd);
}
} else {
if (flags & MSG_CMSG_CLOEXEC)
fdp->fd_files[fd].fileflags |= UF_EXCLOSE;
if (flags & MSG_CMSG_CLOFORK)
fdp->fd_files[fd].fileflags |= UF_FOCLOSE;
fsetfd(fdp, fp, fd);
}
}
unp_del_right(fp);
fdrop(fp);
}
void
unp_init(void)
{
TAILQ_INIT(&unp_stream_head.list);
TAILQ_INIT(&unp_dgram_head.list);
TAILQ_INIT(&unp_seqpkt_head.list);
SLIST_INIT(&unp_defdiscard_head);
spin_init(&unp_defdiscard_spin, "unpdisc");
TASK_INIT(&unp_defdiscard_task, 0, unp_defdiscard_taskfunc, NULL);
TASK_INIT(&unp_gc_task, 0, unp_gc, NULL);
unp_gc_marker = kmalloc(sizeof(*unp_gc_marker), M_UNPCB,
M_WAITOK | M_ZERO);
unp_gc_marker->unp_flags |= UNP_MARKER;
unp_taskqueue = taskqueue_create("unp_taskq", M_WAITOK,
taskqueue_thread_enqueue, &unp_taskqueue);
taskqueue_start_threads(&unp_taskqueue, 1, TDPRI_KERN_DAEMON,
ncpus - 1, "unp taskq");
}
static int
unp_internalize(struct mbuf *control, struct thread *td)
{
struct proc *p = td->td_proc;
struct filedesc *fdescp;
struct cmsghdr *cm = mtod(control, struct cmsghdr *);
struct file **rp;
struct file *fp;
int i, fd, *fdp;
struct cmsgcred *cmcred;
int oldfds;
u_int newlen;
int error;
KKASSERT(p);
if ((cm->cmsg_type != SCM_RIGHTS && cm->cmsg_type != SCM_CREDS) ||
cm->cmsg_level != SOL_SOCKET ||
control->m_len < sizeof(*cm) ||
cm->cmsg_len < sizeof(*cm) ||
cm->cmsg_len > control->m_len) {
return EINVAL;
}
if (cm->cmsg_type == SCM_CREDS) {
cmcred = (struct cmsgcred *)CMSG_DATA(cm);
cmcred->cmcred_pid = p->p_pid;
cmcred->cmcred_uid = p->p_ucred->cr_ruid;
cmcred->cmcred_gid = p->p_ucred->cr_rgid;
cmcred->cmcred_euid = p->p_ucred->cr_uid;
cmcred->cmcred_ngroups = MIN(p->p_ucred->cr_ngroups,
CMGROUP_MAX);
for (i = 0; i < cmcred->cmcred_ngroups; i++)
cmcred->cmcred_groups[i] = p->p_ucred->cr_groups[i];
return 0;
}
if (cm->cmsg_len < CMSG_LEN(0)) {
return EINVAL;
}
oldfds = (cm->cmsg_len - CMSG_LEN(0)) / sizeof(int);
newlen = CMSG_LEN(oldfds * sizeof(struct file *));
if (newlen > MCLBYTES)
return E2BIG;
if (newlen - control->m_len > M_TRAILINGSPACE(control)) {
if (control->m_flags & M_EXT)
return E2BIG;
MCLGET(control, M_WAITOK);
memcpy(mtod(control, char *), cm, cm->cmsg_len);
cm = mtod(control, struct cmsghdr *);
}
lwkt_gettoken(&unp_rights_token);
fdescp = p->p_fd;
spin_lock_shared(&fdescp->fd_spin);
fdp = (int *)CMSG_DATA(cm);
for (i = 0; i < oldfds; i++) {
fd = *fdp++;
if ((unsigned)fd >= fdescp->fd_nfiles ||
fdescp->fd_files[fd].fp == NULL) {
error = EBADF;
goto done;
}
if (fdescp->fd_files[fd].fp->f_type == DTYPE_KQUEUE) {
error = EOPNOTSUPP;
goto done;
}
}
cm->cmsg_len = newlen;
control->m_len = CMSG_ALIGN(newlen);
fdp = (int *)CMSG_DATA(cm) + oldfds - 1;
rp = (struct file **)CMSG_DATA(cm) + oldfds - 1;
for (i = 0; i < oldfds; i++) {
fp = fdescp->fd_files[*fdp--].fp;
*rp-- = fp;
fhold(fp);
unp_add_right(fp);
}
error = 0;
done:
spin_unlock_shared(&fdescp->fd_spin);
lwkt_reltoken(&unp_rights_token);
return error;
}
#ifdef UNP_GC_ALLFILES
struct unp_gc_info {
struct file **extra_ref;
struct file *locked_fp;
int defer;
int index;
int maxindex;
};
static void
unp_gc(void *arg __unused, int pending __unused)
{
struct unp_gc_info info;
struct file **fpp;
int i;
lwkt_gettoken(&unp_rights_token);
allfiles_scan_exclusive(unp_gc_clearmarks, NULL);
do {
info.defer = 0;
allfiles_scan_exclusive(unp_gc_checkmarks, &info);
if (info.defer)
tsleep(&info, 0, "gcagain", 1);
} while (info.defer);
info.extra_ref = kmalloc(256 * sizeof(struct file *), M_FILE, M_WAITOK);
info.maxindex = 256;
do {
info.index = 0;
allfiles_scan_exclusive(unp_gc_checkrefs, &info);
for (i = info.index, fpp = info.extra_ref; --i >= 0; ++fpp) {
struct file *tfp = *fpp;
if (tfp->f_type == DTYPE_SOCKET && tfp->f_data != NULL)
sorflush((struct socket *)(tfp->f_data));
}
for (i = info.index, fpp = info.extra_ref; --i >= 0; ++fpp)
fdrop(*fpp);
} while (info.index == info.maxindex);
kfree((caddr_t)info.extra_ref, M_FILE);
lwkt_reltoken(&unp_rights_token);
}
static int
unp_gc_checkrefs(struct file *fp, void *data)
{
struct unp_gc_info *info = data;
if (fp->f_count == 0)
return(0);
if (info->index == info->maxindex)
return(-1);
if (fp->f_count == fp->f_msgcount && !(fp->f_flag & FMARK)) {
info->extra_ref[info->index++] = fp;
fhold(fp);
}
return(0);
}
static int
unp_gc_clearmarks(struct file *fp, void *data __unused)
{
atomic_clear_int(&fp->f_flag, FMARK | FDEFER);
return(0);
}
static int
unp_gc_checkmarks(struct file *fp, void *data)
{
struct unp_gc_info *info = data;
struct socket *so;
if (fp->f_count == 0) {
if (fp->f_flag & FDEFER)
atomic_clear_int(&fp->f_flag, FDEFER);
return(0);
}
if (fp->f_flag & FDEFER) {
atomic_clear_int(&fp->f_flag, FDEFER);
} else {
if (fp->f_flag & FMARK)
return(0);
if (fp->f_count == fp->f_msgcount)
return(0);
atomic_set_int(&fp->f_flag, FMARK);
}
if (fp->f_type != DTYPE_SOCKET ||
(so = (struct socket *)fp->f_data) == NULL) {
return(0);
}
if (so->so_proto->pr_domain != &localdomain ||
!(so->so_proto->pr_flags & PR_RIGHTS)) {
return(0);
}
info->locked_fp = fp;
if (lwkt_trytoken(&so->so_rcv.ssb_token)) {
unp_scan(so->so_rcv.ssb_mb, unp_mark, info);
lwkt_reltoken(&so->so_rcv.ssb_token);
} else {
atomic_set_int(&fp->f_flag, FDEFER);
++info->defer;
}
return (0);
}
static void
unp_mark(struct file *fp, void *data)
{
struct unp_gc_info *info = data;
if ((fp->f_flag & FMARK) == 0) {
++info->defer;
atomic_set_int(&fp->f_flag, FMARK | FDEFER);
} else if (fp->f_flag & FDEFER) {
++info->defer;
}
}
#else
static int unp_marked;
static int unp_unreachable;
static void
unp_accessable(struct file *fp, void *data __unused)
{
struct unpcb *unp;
if ((unp = unp_fp2unpcb(fp)) == NULL)
return;
if (unp->unp_gcflags & UNPGC_REF)
return;
unp->unp_gcflags &= ~UNPGC_DEAD;
unp->unp_gcflags |= UNPGC_REF;
unp_marked++;
}
static void
unp_gc_process(struct unpcb *unp)
{
struct file *fp;
if (unp->unp_gcflags & UNPGC_SCANNED)
return;
fp = unp->unp_fp;
if ((unp->unp_gcflags & UNPGC_REF) == 0 && fp &&
unp->unp_msgcount != 0 && fp->f_count == unp->unp_msgcount) {
unp->unp_gcflags |= UNPGC_DEAD;
unp_unreachable++;
return;
}
if (UNP_ISATTACHED(unp)) {
struct signalsockbuf *ssb = &unp->unp_socket->so_rcv;
unp_reference(unp);
lwkt_gettoken(&ssb->ssb_token);
if (UNP_ISATTACHED(unp))
unp_scan(ssb->ssb_mb, unp_accessable, NULL);
lwkt_reltoken(&ssb->ssb_token);
unp->unp_gcflags |= UNPGC_SCANNED;
unp_free(unp);
} else {
unp->unp_gcflags |= UNPGC_SCANNED;
}
}
static void
unp_gc(void *arg __unused, int pending __unused)
{
struct unp_global_head *head;
int h, filemax, fileidx, filetot;
struct file **unref;
struct unpcb *unp;
lwkt_gettoken(&unp_rights_token);
lwkt_gettoken(&unp_token);
for (h = 0; unp_heads[h] != NULL; ++h) {
head = unp_heads[h];
TAILQ_FOREACH(unp, &head->list, unp_link)
unp->unp_gcflags = 0;
}
do {
unp_unreachable = 0;
unp_marked = 0;
for (h = 0; unp_heads[h] != NULL; ++h) {
head = unp_heads[h];
TAILQ_INSERT_HEAD(&head->list, unp_gc_marker, unp_link);
while ((unp = TAILQ_NEXT(unp_gc_marker, unp_link))
!= NULL) {
TAILQ_REMOVE(&head->list, unp_gc_marker,
unp_link);
TAILQ_INSERT_AFTER(&head->list, unp,
unp_gc_marker, unp_link);
if (unp->unp_flags & UNP_MARKER)
continue;
unp_gc_process(unp);
}
TAILQ_REMOVE(&head->list, unp_gc_marker, unp_link);
}
} while (unp_marked);
if (unp_unreachable == 0)
goto done;
filemax = unp_unreachable;
if (filemax > UNP_GCFILE_MAX)
filemax = UNP_GCFILE_MAX;
unref = kmalloc(filemax * sizeof(struct file *), M_TEMP, M_WAITOK);
filetot = 0;
do {
int i;
fileidx = 0;
for (h = 0; unp_heads[h] != NULL; ++h) {
head = unp_heads[h];
TAILQ_FOREACH(unp, &head->list, unp_link) {
struct file *fp;
if ((unp->unp_gcflags & UNPGC_DEAD) == 0)
continue;
unp->unp_gcflags &= ~UNPGC_DEAD;
fp = unp->unp_fp;
if (unp->unp_msgcount == 0 || fp == NULL ||
fp->f_count != unp->unp_msgcount)
continue;
fhold(fp);
KASSERT(fileidx < filemax,
("invalid fileidx %d, filemax %d",
fileidx, filemax));
unref[fileidx++] = fp;
KASSERT(filetot < unp_unreachable,
("invalid filetot %d and "
"unp_unreachable %d",
filetot, unp_unreachable));
++filetot;
if (fileidx == filemax ||
filetot == unp_unreachable)
goto dogc;
}
}
dogc:
for (i = 0; i < fileidx; ++i)
sorflush(unref[i]->f_data);
for (i = 0; i < fileidx; ++i)
fdrop(unref[i]);
} while (fileidx == filemax && filetot < unp_unreachable);
kfree(unref, M_TEMP);
done:
lwkt_reltoken(&unp_token);
lwkt_reltoken(&unp_rights_token);
}
#endif
void
unp_dispose(struct mbuf *m)
{
lwkt_gettoken(&unp_rights_token);
if (m)
unp_scan(m, unp_discard, NULL);
lwkt_reltoken(&unp_rights_token);
}
static int
unp_listen(struct unpcb *unp, struct thread *td)
{
struct proc *p = td->td_proc;
ASSERT_LWKT_TOKEN_HELD(&unp_token);
UNP_ASSERT_TOKEN_HELD(unp);
KKASSERT(p);
cru2x(p->p_ucred, &unp->unp_peercred);
unp_setflags(unp, UNP_HAVEPCCACHED);
return (0);
}
static void
unp_scan(struct mbuf *m0, void (*op)(struct file *, void *), void *data)
{
struct mbuf *m;
struct file **rp;
struct cmsghdr *cm;
int i;
int qfds;
while (m0) {
for (m = m0; m; m = m->m_next) {
if (m->m_type == MT_CONTROL &&
m->m_len >= sizeof(*cm)) {
cm = mtod(m, struct cmsghdr *);
if (cm->cmsg_level != SOL_SOCKET ||
cm->cmsg_type != SCM_RIGHTS)
continue;
qfds = (cm->cmsg_len - CMSG_LEN(0)) /
sizeof(void *);
rp = (struct file **)CMSG_DATA(cm);
for (i = 0; i < qfds; i++)
(*op)(*rp++, data);
break;
}
}
m0 = m0->m_nextpkt;
}
}
static void
unp_discard(struct file *fp, void *data __unused)
{
unp_del_right(fp);
if (unp_fp2unpcb(fp) != NULL) {
struct unp_defdiscard *d;
d = kmalloc(sizeof(*d), M_UNPCB, M_WAITOK);
d->fp = fp;
spin_lock(&unp_defdiscard_spin);
SLIST_INSERT_HEAD(&unp_defdiscard_head, d, next);
spin_unlock(&unp_defdiscard_spin);
taskqueue_enqueue(unp_taskqueue, &unp_defdiscard_task);
} else {
fdrop(fp);
}
}
static int
unp_find_lockref(struct sockaddr *nam, struct thread *td, short type,
struct unpcb **unp_ret)
{
struct proc *p = td->td_proc;
struct sockaddr_un *soun = (struct sockaddr_un *)nam;
struct vnode *vp = NULL;
struct socket *so;
struct unpcb *unp;
int error, len;
struct nlookupdata nd;
char buf[SOCK_MAXADDRLEN];
ASSERT_LWKT_TOKEN_HELD(&unp_token);
*unp_ret = NULL;
len = nam->sa_len - offsetof(struct sockaddr_un, sun_path);
if (len <= 0) {
error = EINVAL;
goto failed;
}
strncpy(buf, soun->sun_path, len);
buf[len] = 0;
error = nlookup_init(&nd, buf, UIO_SYSSPACE, NLC_FOLLOW);
if (error == 0)
error = nlookup(&nd);
if (error == 0)
error = cache_vget(&nd.nl_nch, nd.nl_cred, LK_EXCLUSIVE, &vp);
nlookup_done(&nd);
if (error) {
vp = NULL;
goto failed;
}
if (vp->v_type != VSOCK) {
error = ENOTSOCK;
goto failed;
}
error = VOP_EACCESS(vp, VWRITE, p->p_ucred);
if (error)
goto failed;
so = vp->v_socket;
if (so == NULL) {
error = ECONNREFUSED;
goto failed;
}
if (so->so_type != type) {
error = EPROTOTYPE;
goto failed;
}
unp = unp_getsocktoken(so);
if (!UNP_ISATTACHED(unp)) {
unp_reltoken(unp);
error = ECONNREFUSED;
goto failed;
}
unp_reference(unp);
*unp_ret = unp;
error = 0;
failed:
if (vp != NULL)
vput(vp);
return error;
}
static int
unp_connect_pair(struct unpcb *unp, struct unpcb *unp2)
{
struct socket *so = unp->unp_socket;
struct socket *so2 = unp2->unp_socket;
ASSERT_LWKT_TOKEN_HELD(&unp_token);
UNP_ASSERT_TOKEN_HELD(unp);
UNP_ASSERT_TOKEN_HELD(unp2);
KASSERT(so->so_type == so2->so_type,
("socket type mismatch, so %d, so2 %d", so->so_type, so2->so_type));
if (!UNP_ISATTACHED(unp))
return EINVAL;
if (!UNP_ISATTACHED(unp2))
return ECONNREFUSED;
KASSERT(unp->unp_conn == NULL, ("unp is already connected"));
unp->unp_conn = unp2;
switch (so->so_type) {
case SOCK_DGRAM:
LIST_INSERT_HEAD(&unp2->unp_refs, unp, unp_reflink);
soisconnected(so);
break;
case SOCK_STREAM:
case SOCK_SEQPACKET:
KASSERT(unp2->unp_conn == NULL, ("unp2 is already connected"));
unp2->unp_conn = unp;
soisconnected(so);
soisconnected(so2);
break;
default:
panic("unp_connect_pair: unknown socket type %d", so->so_type);
}
return 0;
}
static void
unp_drop(struct unpcb *unp, int error)
{
struct unp_global_head *head;
struct unpcb *unp2;
ASSERT_LWKT_TOKEN_HELD(&unp_token);
UNP_ASSERT_TOKEN_HELD(unp);
KASSERT((unp->unp_flags & (UNP_DETACHED | UNP_DROPPED)) == 0,
("unp is dropped"));
unp_setflags(unp, UNP_DETACHED);
head = unp_globalhead(unp->unp_socket->so_type);
KASSERT(head->count > 0, ("invalid unp count"));
TAILQ_REMOVE(&head->list, unp, unp_link);
head->count--;
unp_disconnect(unp, error);
while ((unp2 = LIST_FIRST(&unp->unp_refs)) != NULL) {
lwkt_getpooltoken(unp2);
unp_disconnect(unp2, ECONNRESET);
lwkt_relpooltoken(unp2);
}
unp_setflags(unp, UNP_DROPPED);
unp_free(unp);
}
static void
unp_defdiscard_taskfunc(void *arg __unused, int pending __unused)
{
struct unp_defdiscard *d;
spin_lock(&unp_defdiscard_spin);
while ((d = SLIST_FIRST(&unp_defdiscard_head)) != NULL) {
SLIST_REMOVE_HEAD(&unp_defdiscard_head, next);
spin_unlock(&unp_defdiscard_spin);
fdrop(d->fp);
kfree(d, M_UNPCB);
spin_lock(&unp_defdiscard_spin);
}
spin_unlock(&unp_defdiscard_spin);
}