minix/drivers/uds/ioc_uds.c
Ben Gras 17587738d3 align <sys/ucontext.h> <sys/uio.h> <sys/un.h>
Change-Id: I70adf01fddf931a3a6931083adaa4bbe647ea6a3
2014-03-03 20:47:03 +01:00

1076 lines
26 KiB
C

/*
* Unix Domain Sockets Implementation (PF_UNIX, PF_LOCAL)
* This code handles ioctl(2) commands to implement the socket API.
* Some helper functions are also present.
*/
#include "uds.h"
static int
perform_connection(devminor_t minorx, devminor_t minory,
struct sockaddr_un *addr)
{
/*
* There are several places were a connection is established, the
* initiating call being one of accept(2), connect(2), socketpair(2).
*/
dprintf(("UDS: perform_connection(%d, %d)\n", minorx, minory));
/*
* Only connection-oriented types are acceptable and only equal
* types can connect to each other.
*/
if ((uds_fd_table[minorx].type != SOCK_SEQPACKET &&
uds_fd_table[minorx].type != SOCK_STREAM) ||
uds_fd_table[minorx].type != uds_fd_table[minory].type)
return EINVAL;
/* Connect the pair of sockets. */
uds_fd_table[minorx].peer = minory;
uds_fd_table[minory].peer = minorx;
/* Set the address of both sockets */
memcpy(&uds_fd_table[minorx].addr, addr, sizeof(struct sockaddr_un));
memcpy(&uds_fd_table[minory].addr, addr, sizeof(struct sockaddr_un));
return OK;
}
static int
do_accept(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant)
{
devminor_t minorparent; /* minor number of parent (server) */
devminor_t minorpeer;
int rc, i;
struct sockaddr_un addr;
dprintf(("UDS: do_accept(%d)\n", minor));
/*
* Somewhat weird logic is used in this function, so here's an
* overview... The minor number is the server's client socket
* (the socket to be returned by accept()). The data waiting
* for us in the IO Grant is the address that the server is
* listening on. This function uses the address to find the
* server's descriptor. From there we can perform the
* connection or suspend and wait for a connect().
*/
/* This IOCTL must be called on a 'fresh' socket. */
if (uds_fd_table[minor].type != -1)
return EINVAL;
/* Get the server's address */
if ((rc = sys_safecopyfrom(endpt, grant, 0, (vir_bytes) &addr,
sizeof(struct sockaddr_un))) != OK)
return rc;
/* Locate the server socket. */
for (i = 0; i < NR_FDS; i++) {
if (uds_fd_table[i].addr.sun_family == AF_UNIX &&
!strncmp(addr.sun_path, uds_fd_table[i].addr.sun_path,
sizeof(uds_fd_table[i].addr.sun_path)) &&
uds_fd_table[i].listening == 1)
break;
}
if (i == NR_FDS)
return EINVAL;
minorparent = i; /* parent */
/* We are the parent's child. */
uds_fd_table[minorparent].child = minor;
/*
* The peer has the same type as the parent. we need to be that
* type too.
*/
uds_fd_table[minor].type = uds_fd_table[minorparent].type;
/* Locate the peer to accept in the parent's backlog. */
minorpeer = -1;
for (i = 0; i < uds_fd_table[minorparent].backlog_size; i++) {
if (uds_fd_table[minorparent].backlog[i] != -1) {
minorpeer = uds_fd_table[minorparent].backlog[i];
uds_fd_table[minorparent].backlog[i] = -1;
break;
}
}
if (minorpeer == -1) {
dprintf(("UDS: do_accept(%d): suspend\n", minor));
/*
* There are no peers in the backlog, suspend and wait for one
* to show up.
*/
uds_fd_table[minor].suspended = UDS_SUSPENDED_ACCEPT;
return EDONTREPLY;
}
dprintf(("UDS: connecting %d to %d -- parent is %d\n", minor,
minorpeer, minorparent));
if ((rc = perform_connection(minor, minorpeer, &addr)) != OK) {
dprintf(("UDS: do_accept(%d): connection failed\n", minor));
return rc;
}
uds_fd_table[minorparent].child = -1;
/* If the peer is blocked on connect() or write(), revive the peer. */
if (uds_fd_table[minorpeer].suspended == UDS_SUSPENDED_CONNECT ||
uds_fd_table[minorpeer].suspended == UDS_SUSPENDED_WRITE) {
dprintf(("UDS: do_accept(%d): revive %d\n", minor, minorpeer));
uds_unsuspend(minorpeer);
}
/* See if we can satisfy an ongoing select. */
if ((uds_fd_table[minorpeer].sel_ops & CDEV_OP_WR) &&
uds_fd_table[minorpeer].size < UDS_BUF) {
/* A write on the peer is possible now. */
chardriver_reply_select(uds_fd_table[minorpeer].sel_endpt,
minorpeer, CDEV_OP_WR);
uds_fd_table[minorpeer].sel_ops &= ~CDEV_OP_WR;
}
return OK;
}
static int
do_connect(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant)
{
int child, peer;
struct sockaddr_un addr;
int rc, i, j;
dprintf(("UDS: do_connect(%d)\n", minor));
/* Only connection oriented sockets can connect. */
if (uds_fd_table[minor].type != SOCK_STREAM &&
uds_fd_table[minor].type != SOCK_SEQPACKET)
return EINVAL;
/* The socket must not be connecting or connected already. */
peer = uds_fd_table[minor].peer;
if (peer != -1) {
if (uds_fd_table[peer].peer == -1)
return EALREADY; /* connecting */
else
return EISCONN; /* connected */
}
if ((rc = sys_safecopyfrom(endpt, grant, 0, (vir_bytes) &addr,
sizeof(struct sockaddr_un))) != OK)
return rc;
if ((rc = checkperms(uds_fd_table[minor].owner, addr.sun_path,
sizeof(addr.sun_path))) != OK)
return rc;
/*
* Look for a socket of the same type that is listening on the
* address we want to connect to.
*/
for (i = 0; i < NR_FDS; i++) {
if (uds_fd_table[minor].type != uds_fd_table[i].type)
continue;
if (!uds_fd_table[i].listening)
continue;
if (uds_fd_table[i].addr.sun_family != AF_UNIX)
continue;
if (strncmp(addr.sun_path, uds_fd_table[i].addr.sun_path,
sizeof(uds_fd_table[i].addr.sun_path)))
continue;
/* Found a matching socket. */
break;
}
if (i == NR_FDS)
return ECONNREFUSED;
/* If the server is blocked on an accept, perform the connection. */
if ((child = uds_fd_table[i].child) != -1) {
rc = perform_connection(minor, child, &addr);
if (rc != OK)
return rc;
uds_fd_table[i].child = -1;
dprintf(("UDS: do_connect(%d): revive %d\n", minor, child));
/* Wake up the accepting party. */
uds_unsuspend(child);
return OK;
}
dprintf(("UDS: adding %d to %d's backlog\n", minor, i));
/* Look for a free slot in the backlog. */
rc = -1;
for (j = 0; j < uds_fd_table[i].backlog_size; j++) {
if (uds_fd_table[i].backlog[j] == -1) {
uds_fd_table[i].backlog[j] = minor;
rc = 0;
break;
}
}
if (rc == -1)
return ECONNREFUSED; /* backlog is full */
/* See if the server is blocked on select(). */
if (uds_fd_table[i].sel_ops & CDEV_OP_RD) {
/* Satisfy a read-type select on the server. */
chardriver_reply_select(uds_fd_table[i].sel_endpt, i,
CDEV_OP_RD);
uds_fd_table[i].sel_ops &= ~CDEV_OP_RD;
}
/* We found our server. */
uds_fd_table[minor].peer = i;
memcpy(&uds_fd_table[minor].addr, &addr, sizeof(struct sockaddr_un));
dprintf(("UDS: do_connect(%d): suspend\n", minor));
/* Suspend until the server side accepts the connection. */
uds_fd_table[minor].suspended = UDS_SUSPENDED_CONNECT;
return EDONTREPLY;
}
static int
do_listen(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant)
{
int rc;
int backlog_size;
dprintf(("UDS: do_listen(%d)\n", minor));
/* Ensure the socket has a type and is bound. */
if (uds_fd_table[minor].type == -1 ||
uds_fd_table[minor].addr.sun_family != AF_UNIX)
return EINVAL;
/* listen(2) supports only two socket types. */
if (uds_fd_table[minor].type != SOCK_STREAM &&
uds_fd_table[minor].type != SOCK_SEQPACKET)
return EOPNOTSUPP;
/*
* The POSIX standard doesn't say what to do if listen() has
* already been called. Well, there isn't an errno. We silently
* let it happen, but if listen() has already been called, we
* don't allow the backlog to shrink.
*/
if ((rc = sys_safecopyfrom(endpt, grant, 0, (vir_bytes) &backlog_size,
sizeof(backlog_size))) != OK)
return rc;
if (uds_fd_table[minor].listening == 0) {
/* Set the backlog size to a reasonable value. */
if (backlog_size <= 0 || backlog_size > UDS_SOMAXCONN)
backlog_size = UDS_SOMAXCONN;
uds_fd_table[minor].backlog_size = backlog_size;
} else {
/* Allow the user to expand the backlog size. */
if (backlog_size > uds_fd_table[minor].backlog_size &&
backlog_size < UDS_SOMAXCONN)
uds_fd_table[minor].backlog_size = backlog_size;
/*
* Don't let the user shrink the backlog_size, as we might
* have clients waiting in those slots.
*/
}
/* This socket is now listening. */
uds_fd_table[minor].listening = 1;
return OK;
}
static int
do_socket(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant)
{
int rc, type;
dprintf(("UDS: do_socket(%d)\n", minor));
/* The socket type can only be set once. */
if (uds_fd_table[minor].type != -1)
return EINVAL;
/* Get the requested type. */
if ((rc = sys_safecopyfrom(endpt, grant, 0, (vir_bytes) &type,
sizeof(type))) != OK)
return rc;
/* Assign the type if it is valid only. */
switch (type) {
case SOCK_STREAM:
case SOCK_DGRAM:
case SOCK_SEQPACKET:
uds_fd_table[minor].type = type;
return OK;
default:
return EINVAL;
}
}
static int
do_bind(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant)
{
struct sockaddr_un addr;
int rc, i;
dprintf(("UDS: do_bind(%d)\n", minor));
/* If the type hasn't been set by do_socket() yet, OR an attempt
* to re-bind() a non-SOCK_DGRAM socket is made, fail the call.
*/
if ((uds_fd_table[minor].type == -1) ||
(uds_fd_table[minor].addr.sun_family == AF_UNIX &&
uds_fd_table[minor].type != SOCK_DGRAM))
return EINVAL;
if ((rc = sys_safecopyfrom(endpt, grant, 0, (vir_bytes) &addr,
sizeof(struct sockaddr_un))) != OK)
return rc;
/* Do some basic sanity checks on the address. */
if (addr.sun_family != AF_UNIX)
return EAFNOSUPPORT;
if (addr.sun_path[0] == '\0')
return ENOENT;
if ((rc = checkperms(uds_fd_table[minor].owner, addr.sun_path,
sizeof(addr.sun_path))) != OK)
return rc;
/* Make sure the address isn't already in use by another socket. */
for (i = 0; i < NR_FDS; i++) {
if (uds_fd_table[i].addr.sun_family == AF_UNIX &&
!strncmp(addr.sun_path, uds_fd_table[i].addr.sun_path,
sizeof(uds_fd_table[i].addr.sun_path))) {
/* Another socket is bound to this sun_path. */
return EADDRINUSE;
}
}
/* Looks good, perform the bind(). */
memcpy(&uds_fd_table[minor].addr, &addr, sizeof(struct sockaddr_un));
return OK;
}
static int
do_getsockname(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant)
{
dprintf(("UDS: do_getsockname(%d)\n", minor));
/*
* Unconditionally send the address we have assigned to this socket.
* The POSIX standard doesn't say what to do if the address hasn't been
* set. If the address isn't currently set, then the user will get
* NULL bytes. Note: libc depends on this behavior.
*/
return sys_safecopyto(endpt, grant, 0,
(vir_bytes) &uds_fd_table[minor].addr, sizeof(struct sockaddr_un));
}
static int
do_getpeername(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant)
{
int peer_minor;
dprintf(("UDS: do_getpeername(%d)\n", minor));
/* Check that the socket is connected with a valid peer. */
if (uds_fd_table[minor].peer != -1) {
peer_minor = uds_fd_table[minor].peer;
/* Copy the address from the peer. */
return sys_safecopyto(endpt, grant, 0,
(vir_bytes) &uds_fd_table[peer_minor].addr,
sizeof(struct sockaddr_un));
} else if (uds_fd_table[minor].err == ECONNRESET) {
uds_fd_table[minor].err = 0;
return ECONNRESET;
} else
return ENOTCONN;
}
static int
do_shutdown(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant)
{
int rc, how;
dprintf(("UDS: do_shutdown(%d)\n", minor));
/* The socket must be connection oriented. */
if (uds_fd_table[minor].type != SOCK_STREAM &&
uds_fd_table[minor].type != SOCK_SEQPACKET)
return EINVAL;
if (uds_fd_table[minor].peer == -1) {
/* shutdown(2) is only valid for connected sockets. */
if (uds_fd_table[minor].err == ECONNRESET)
return ECONNRESET;
else
return ENOTCONN;
}
/* Get the 'how' parameter from the caller. */
if ((rc = sys_safecopyfrom(endpt, grant, 0, (vir_bytes) &how,
sizeof(how))) != OK)
return rc;
switch (how) {
case SHUT_RD: /* Take away read permission. */
uds_fd_table[minor].mode &= ~UDS_R;
break;
case SHUT_WR: /* Take away write permission. */
uds_fd_table[minor].mode &= ~UDS_W;
break;
case SHUT_RDWR: /* Shut down completely. */
uds_fd_table[minor].mode = 0;
break;
default:
return EINVAL;
}
return OK;
}
static int
do_socketpair(devminor_t minorx, endpoint_t endpt, cp_grant_id_t grant)
{
int rc;
dev_t minorin;
devminor_t minory;
struct sockaddr_un addr;
dprintf(("UDS: do_socketpair(%d)\n", minorx));
/* The ioctl argument is the minor number of the second socket. */
if ((rc = sys_safecopyfrom(endpt, grant, 0, (vir_bytes) &minorin,
sizeof(minorin))) != OK)
return rc;
minory = minor(minorin);
dprintf(("UDS: socketpair(%d, %d,)\n", minorx, minory));
/* Security check: both sockets must have the same owner endpoint. */
if (uds_fd_table[minorx].owner != uds_fd_table[minory].owner)
return EPERM;
addr.sun_family = AF_UNIX;
addr.sun_path[0] = 'X';
addr.sun_path[1] = '\0';
return perform_connection(minorx, minory, &addr);
}
static int
do_getsockopt_sotype(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant)
{
dprintf(("UDS: do_getsockopt_sotype(%d)\n", minor));
/* If the type hasn't been set yet, we fail the call. */
if (uds_fd_table[minor].type == -1)
return EINVAL;
return sys_safecopyto(endpt, grant, 0,
(vir_bytes) &uds_fd_table[minor].type, sizeof(int));
}
static int
do_getsockopt_peercred(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant)
{
int peer_minor;
int rc;
struct uucred cred;
dprintf(("UDS: do_getsockopt_peercred(%d)\n", minor));
if (uds_fd_table[minor].peer == -1) {
if (uds_fd_table[minor].err == ECONNRESET) {
uds_fd_table[minor].err = 0;
return ECONNRESET;
} else
return ENOTCONN;
}
peer_minor = uds_fd_table[minor].peer;
/* Obtain the peer's credentials and copy them out. */
if ((rc = getnucred(uds_fd_table[peer_minor].owner, &cred)) < 0)
return rc;
return sys_safecopyto(endpt, grant, 0, (vir_bytes) &cred,
sizeof(struct uucred));
}
static int
do_getsockopt_sndbuf(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant)
{
size_t sndbuf = UDS_BUF;
dprintf(("UDS: do_getsockopt_sndbuf(%d)\n", minor));
return sys_safecopyto(endpt, grant, 0, (vir_bytes) &sndbuf,
sizeof(sndbuf));
}
static int
do_setsockopt_sndbuf(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant)
{
int rc;
size_t sndbuf;
dprintf(("UDS: do_setsockopt_sndbuf(%d)\n", minor));
if ((rc = sys_safecopyfrom(endpt, grant, 0, (vir_bytes) &sndbuf,
sizeof(sndbuf))) != OK)
return rc;
/* The send buffer is limited to 32KB at the moment. */
if (sndbuf > UDS_BUF)
return ENOSYS;
/* FIXME: actually shrink the buffer. */
return OK;
}
static int
do_getsockopt_rcvbuf(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant)
{
size_t rcvbuf = UDS_BUF;
dprintf(("UDS: do_getsockopt_rcvbuf(%d)\n", minor));
return sys_safecopyto(endpt, grant, 0, (vir_bytes) &rcvbuf,
sizeof(rcvbuf));
}
static int
do_setsockopt_rcvbuf(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant)
{
int rc;
size_t rcvbuf;
dprintf(("UDS: do_setsockopt_rcvbuf(%d)\n", minor));
if ((rc = sys_safecopyfrom(endpt, grant, 0, (vir_bytes) &rcvbuf,
sizeof(rcvbuf))) != OK)
return rc;
/* The receive buffer is limited to 32KB at the moment. */
if (rcvbuf > UDS_BUF)
return ENOSYS;
/* FIXME: actually shrink the buffer. */
return OK;
}
static int
do_sendto(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant)
{
int rc;
struct sockaddr_un addr;
dprintf(("UDS: do_sendto(%d)\n", minor));
/* This IOCTL is only for SOCK_DGRAM sockets. */
if (uds_fd_table[minor].type != SOCK_DGRAM)
return EINVAL;
if ((rc = sys_safecopyfrom(endpt, grant, 0, (vir_bytes) &addr,
sizeof(struct sockaddr_un))) != OK)
return rc;
/* Do some basic sanity checks on the address. */
if (addr.sun_family != AF_UNIX || addr.sun_path[0] == '\0')
return EINVAL;
if ((rc = checkperms(uds_fd_table[minor].owner, addr.sun_path,
sizeof(addr.sun_path))) != OK)
return rc;
memcpy(&uds_fd_table[minor].target, &addr, sizeof(struct sockaddr_un));
return OK;
}
static int
do_recvfrom(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant)
{
dprintf(("UDS: do_recvfrom(%d)\n", minor));
return sys_safecopyto(endpt, grant, 0,
(vir_bytes) &uds_fd_table[minor].source,
sizeof(struct sockaddr_un));
}
static int
send_fds(devminor_t minor, struct msg_control *msg_ctrl,
struct ancillary *data)
{
int i, rc, nfds, totalfds;
endpoint_t from_ep;
struct msghdr msghdr;
struct cmsghdr *cmsg = NULL;
dprintf(("UDS: send_fds(%d)\n", minor));
from_ep = uds_fd_table[minor].owner;
/* Obtain this socket's credentials. */
if ((rc = getnucred(from_ep, &data->cred)) < 0)
return rc;
dprintf(("UDS: minor=%d cred={%d,%d,%d}\n", minor, data->cred.pid,
data->cred.uid, data->cred.gid));
totalfds = data->nfiledes;
memset(&msghdr, '\0', sizeof(struct msghdr));
msghdr.msg_control = msg_ctrl->msg_control;
msghdr.msg_controllen = msg_ctrl->msg_controllen;
for (cmsg = CMSG_FIRSTHDR(&msghdr); cmsg != NULL;
cmsg = CMSG_NXTHDR(&msghdr, cmsg)) {
if (cmsg->cmsg_level != SOL_SOCKET ||
cmsg->cmsg_type != SCM_RIGHTS)
continue;
nfds = MIN((cmsg->cmsg_len-CMSG_LEN(0))/sizeof(int), OPEN_MAX);
for (i = 0; i < nfds; i++) {
if (totalfds == OPEN_MAX)
return EOVERFLOW;
data->fds[totalfds] = ((int *) CMSG_DATA(cmsg))[i];
dprintf(("UDS: minor=%d fd[%d]=%d\n", minor, totalfds,
data->fds[totalfds]));
totalfds++;
}
}
for (i = data->nfiledes; i < totalfds; i++) {
if ((rc = copyfd(from_ep, data->fds[i], COPYFD_FROM)) < 0) {
printf("UDS: copyfd(COPYFD_FROM) failed: %d\n", rc);
/* Revert the successful copyfd() calls made so far. */
for (i--; i >= data->nfiledes; i--)
close(data->fds[i]);
return rc;
}
dprintf(("UDS: send_fds(): %d -> %d\n", data->fds[i], rc));
data->fds[i] = rc; /* this is now the local FD */
}
data->nfiledes = totalfds;
return OK;
}
/*
* This function calls close() for all of the FDs in flight. This is used
* when a Unix Domain Socket is closed and there exists references to file
* descriptors that haven't been received with recvmsg().
*/
int
uds_clear_fds(devminor_t minor, struct ancillary *data)
{
int i;
dprintf(("UDS: uds_clear_fds(%d)\n", minor));
for (i = 0; i < data->nfiledes; i++) {
dprintf(("UDS: uds_clear_fds() => %d\n", data->fds[i]));
close(data->fds[i]);
data->fds[i] = -1;
}
data->nfiledes = 0;
return OK;
}
static int
recv_fds(devminor_t minor, struct ancillary *data,
struct msg_control *msg_ctrl)
{
int rc, i, j, fds[OPEN_MAX];
struct msghdr msghdr;
struct cmsghdr *cmsg;
endpoint_t to_ep;
dprintf(("UDS: recv_fds(%d)\n", minor));
msghdr.msg_control = msg_ctrl->msg_control;
msghdr.msg_controllen = msg_ctrl->msg_controllen;
cmsg = CMSG_FIRSTHDR(&msghdr);
cmsg->cmsg_len = CMSG_LEN(sizeof(int) * data->nfiledes);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
to_ep = uds_fd_table[minor].owner;
/* Copy to the target endpoint. */
for (i = 0; i < data->nfiledes; i++) {
if ((rc = copyfd(to_ep, data->fds[i], COPYFD_TO)) < 0) {
printf("UDS: copyfd(COPYFD_TO) failed: %d\n", rc);
/* Revert the successful copyfd() calls made so far. */
for (i--; i >= 0; i--)
(void) copyfd(to_ep, fds[i], COPYFD_CLOSE);
return rc;
}
fds[i] = rc; /* this is now the remote FD */
}
/* Close the local copies only once the entire procedure succeeded. */
for (i = 0; i < data->nfiledes; i++) {
dprintf(("UDS: recv_fds(): %d -> %d\n", data->fds[i], fds[i]));
((int *)CMSG_DATA(cmsg))[i] = fds[i];
close(data->fds[i]);
data->fds[i] = -1;
}
data->nfiledes = 0;
return OK;
}
static int
recv_cred(devminor_t minor, struct ancillary *data,
struct msg_control *msg_ctrl)
{
struct msghdr msghdr;
struct cmsghdr *cmsg;
dprintf(("UDS: recv_cred(%d)\n", minor));
msghdr.msg_control = msg_ctrl->msg_control;
msghdr.msg_controllen = msg_ctrl->msg_controllen;
cmsg = CMSG_FIRSTHDR(&msghdr);
if (cmsg->cmsg_len > 0)
cmsg = CMSG_NXTHDR(&msghdr, cmsg);
cmsg->cmsg_len = CMSG_LEN(sizeof(struct uucred));
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_CREDS;
memcpy(CMSG_DATA(cmsg), &data->cred, sizeof(struct uucred));
return OK;
}
static int
do_sendmsg(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant)
{
int peer, rc, i;
struct msg_control msg_ctrl;
dprintf(("UDS: do_sendmsg(%d)\n", minor));
memset(&msg_ctrl, '\0', sizeof(struct msg_control));
if ((rc = sys_safecopyfrom(endpt, grant, 0, (vir_bytes) &msg_ctrl,
sizeof(struct msg_control))) != OK)
return rc;
/* Locate the peer. */
peer = -1;
if (uds_fd_table[minor].type == SOCK_DGRAM) {
if (uds_fd_table[minor].target.sun_path[0] == '\0' ||
uds_fd_table[minor].target.sun_family != AF_UNIX)
return EDESTADDRREQ;
for (i = 0; i < NR_FDS; i++) {
/*
* Look for a SOCK_DGRAM socket that is bound on the
* target address.
*/
if (uds_fd_table[i].type == SOCK_DGRAM &&
uds_fd_table[i].addr.sun_family == AF_UNIX &&
!strncmp(uds_fd_table[minor].target.sun_path,
uds_fd_table[i].addr.sun_path,
sizeof(uds_fd_table[i].addr.sun_path))) {
peer = i;
break;
}
}
if (peer == -1)
return ENOENT;
} else {
peer = uds_fd_table[minor].peer;
if (peer == -1)
return ENOTCONN;
}
dprintf(("UDS: sendmsg(%d) -- peer=%d\n", minor, peer));
/*
* Note: it's possible that there is already some file descriptors in
* ancillary_data if the peer didn't call recvmsg() yet. That's okay.
* The receiver will get the current file descriptors plus the new
* ones.
*/
return send_fds(minor, &msg_ctrl, &uds_fd_table[peer].ancillary_data);
}
static int
do_recvmsg(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant)
{
int rc;
struct msg_control msg_ctrl;
socklen_t clen_avail = 0;
socklen_t clen_needed = 0;
socklen_t clen_desired = 0;
dprintf(("UDS: do_recvmsg(%d)\n", minor));
dprintf(("UDS: minor=%d credentials={pid:%d,uid:%d,gid:%d}\n", minor,
uds_fd_table[minor].ancillary_data.cred.pid,
uds_fd_table[minor].ancillary_data.cred.uid,
uds_fd_table[minor].ancillary_data.cred.gid));
memset(&msg_ctrl, '\0', sizeof(struct msg_control));
/*
* Get the msg_control from the user. It will include the
* amount of space the user has allocated for control data.
*/
if ((rc = sys_safecopyfrom(endpt, grant, 0, (vir_bytes) &msg_ctrl,
sizeof(struct msg_control))) != OK)
return rc;
clen_avail = MIN(msg_ctrl.msg_controllen, MSG_CONTROL_MAX);
if (uds_fd_table[minor].ancillary_data.nfiledes > 0) {
clen_needed = CMSG_SPACE(sizeof(int) *
uds_fd_table[minor].ancillary_data.nfiledes);
}
/* if there is room we also include credentials */
clen_desired = clen_needed + CMSG_SPACE(sizeof(struct uucred));
if (clen_needed > clen_avail)
return EOVERFLOW;
if (uds_fd_table[minor].ancillary_data.nfiledes > 0) {
if ((rc = recv_fds(minor, &uds_fd_table[minor].ancillary_data,
&msg_ctrl)) != OK)
return rc;
}
if (clen_desired <= clen_avail) {
rc = recv_cred(minor, &uds_fd_table[minor].ancillary_data,
&msg_ctrl);
if (rc != OK)
return rc;
msg_ctrl.msg_controllen = clen_desired;
} else
msg_ctrl.msg_controllen = clen_needed;
/* Send the control data to the user. */
return sys_safecopyto(endpt, grant, 0, (vir_bytes) &msg_ctrl,
sizeof(struct msg_control));
}
static int
do_fionread(devminor_t minor, endpoint_t endpt, cp_grant_id_t grant)
{
int rc;
rc = uds_perform_read(minor, NONE, GRANT_INVALID, UDS_BUF, 1);
/* What should we do on error? Just set to zero for now. */
if (rc < 0)
rc = 0;
return sys_safecopyto(endpt, grant, 0, (vir_bytes) &rc, sizeof(rc));
}
int
uds_do_ioctl(devminor_t minor, unsigned long request, endpoint_t endpt,
cp_grant_id_t grant)
{
int rc;
switch (request) {
case NWIOSUDSCONN:
/* Connect to a listening socket -- connect(). */
rc = do_connect(minor, endpt, grant);
break;
case NWIOSUDSACCEPT:
/* Accept an incoming connection -- accept(). */
rc = do_accept(minor, endpt, grant);
break;
case NWIOSUDSBLOG:
/*
* Set the backlog_size and put the socket into the listening
* state -- listen().
*/
rc = do_listen(minor, endpt, grant);
break;
case NWIOSUDSTYPE:
/* Set the SOCK_ type for this socket -- socket(). */
rc = do_socket(minor, endpt, grant);
break;
case NWIOSUDSADDR:
/* Set the address for this socket -- bind(). */
rc = do_bind(minor, endpt, grant);
break;
case NWIOGUDSADDR:
/* Get the address for this socket -- getsockname(). */
rc = do_getsockname(minor, endpt, grant);
break;
case NWIOGUDSPADDR:
/* Get the address for the peer -- getpeername(). */
rc = do_getpeername(minor, endpt, grant);
break;
case NWIOSUDSSHUT:
/*
* Shut down a socket for reading, writing, or both --
* shutdown().
*/
rc = do_shutdown(minor, endpt, grant);
break;
case NWIOSUDSPAIR:
/* Connect two sockets -- socketpair(). */
rc = do_socketpair(minor, endpt, grant);
break;
case NWIOGUDSSOTYPE:
/* Get socket type -- getsockopt(SO_TYPE). */
rc = do_getsockopt_sotype(minor, endpt, grant);
break;
case NWIOGUDSPEERCRED:
/* Get peer endpoint -- getsockopt(SO_PEERCRED). */
rc = do_getsockopt_peercred(minor, endpt, grant);
break;
case NWIOSUDSTADDR:
/* Set target address -- sendto(). */
rc = do_sendto(minor, endpt, grant);
break;
case NWIOGUDSFADDR:
/* Get from address -- recvfrom(). */
rc = do_recvfrom(minor, endpt, grant);
break;
case NWIOGUDSSNDBUF:
/* Get the send buffer size -- getsockopt(SO_SNDBUF). */
rc = do_getsockopt_sndbuf(minor, endpt, grant);
break;
case NWIOSUDSSNDBUF:
/* Set the send buffer size -- setsockopt(SO_SNDBUF). */
rc = do_setsockopt_sndbuf(minor, endpt, grant);
break;
case NWIOGUDSRCVBUF:
/* Get the send buffer size -- getsockopt(SO_SNDBUF). */
rc = do_getsockopt_rcvbuf(minor, endpt, grant);
break;
case NWIOSUDSRCVBUF:
/* Set the send buffer size -- setsockopt(SO_SNDBUF). */
rc = do_setsockopt_rcvbuf(minor, endpt, grant);
break;
case NWIOSUDSCTRL:
/* Set the control data -- sendmsg(). */
rc = do_sendmsg(minor, endpt, grant);
break;
case NWIOGUDSCTRL:
/* Set the control data -- recvmsg(). */
rc = do_recvmsg(minor, endpt, grant);
break;
case FIONREAD:
/*
* Get the number of bytes immediately available for reading.
*/
rc = do_fionread(minor, endpt, grant);
break;
default:
/*
* The IOCTL command is not valid for /dev/uds -- this happens
* a lot and is normal. A lot of libc functions determine the
* socket type with IOCTLs. Any unrecognized requests simply
* get an ENOTTY response.
*/
rc = ENOTTY;
}
return rc;
}