minix/drivers/uds/ioc_uds.c
David van Moolenbroek e5cc85fdc4 Extend dupfrom(2) into copyfd(2)
This single function allows copying file descriptors from and to
processes, and closing a previously copied remote file descriptor.
This function replaces the five FD-related UDS backcalls. While it
limits the total number of in-flight file descriptors to OPEN_MAX,
this change greatly improves crash recovery support of UDS, since all
in-flight file descriptors will be closed instead of keeping them
open indefinitely (causing VFS to crash on system shutdown). With the
new copyfd call, UDS becomes simpler, and the concept of filps is no
longer exposed outside of VFS.

This patch also moves the checkperms(2) stub into libminlib, thus
fully abstracting away message details of VFS communication from UDS.

Change-Id: Idd32ad390a566143c8ef66955e5ae2c221cff966
2014-03-01 09:04:58 +01:00

1078 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,
UNIX_PATH_MAX) && 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 (checkperms(uds_fd_table[minor].owner, addr.sun_path,
UNIX_PATH_MAX) != OK)
return -errno;
/*
* 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,
UNIX_PATH_MAX))
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 (checkperms(uds_fd_table[minor].owner, addr.sun_path,
UNIX_PATH_MAX) != OK)
return -errno;
/* 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,
UNIX_PATH_MAX)) {
/* 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 -errno;
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 (checkperms(uds_fd_table[minor].owner, addr.sun_path,
UNIX_PATH_MAX) != OK)
return -errno;
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 -errno;
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) {
rc = -errno;
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) {
rc = -errno;
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_CREDENTIALS;
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, UNIX_PATH_MAX)) {
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;
}