2108 lines
60 KiB
C
2108 lines
60 KiB
C
/* uncomp_usrreq.c - AF_LOCAL/COMP protocol implementation */
|
|
|
|
/*
|
|
* Copyright (c) 2003-2009, 2011-2013 Wind River Systems, Inc.
|
|
*
|
|
* The right to copy, distribute, modify or otherwise make use
|
|
* of this software may be licensed only pursuant to the terms
|
|
* of an applicable Wind River license agreement.
|
|
*/
|
|
|
|
/*
|
|
modification history
|
|
--------------------
|
|
01z,04sep13,xms fix TAINTED_SCALAR error. (WIND00414265)
|
|
01y,11jul12,shi clean up warnings (WIND00357529)
|
|
01x,28jun11,shi LP64 conversion
|
|
01w,21apr09,hps LP64: move drv_val_t to private header iosLibP.h
|
|
01v,12may08,jpb Modifications for source build. Renamed
|
|
WV_INSTRUMENTATION to _WRS_CONFIG_SV_INSTRUMENTATION.
|
|
01u,19jun07,ebh Changes to allow COMP to run on SMP systems (SMP-safe)
|
|
01t,04may07,ebh update to handle EINTR on sends (Defect #91328)
|
|
01s,06sep06,kch corrected diab compiler errors.
|
|
01r,06sep06,kch cleanup coreip stack header files.
|
|
01q,21feb05,ebh update manually (zippy merge was not running)
|
|
01p,21jan05,vvv osdep.h cleanup
|
|
01o,03jan05,bwa BUG FIX: connect() returning wrong errno (SPR #102896).
|
|
01n,11oct04,p_r fixed pathname for dsi header (SPR #102446)
|
|
01m,30aug04,bwa fixed shutdown() exception when done on closed socket.
|
|
01l,13jul04,p_r removed e-mail address from comment (SPR #99392)
|
|
01k,01jun04,bwa added flow control (SPR #97987).
|
|
01j,26may04,bwa added WV instrumentation; fixed 'netun' includes (SPR #97714).
|
|
01i,23apr04,bwa removed references to soXXX routines.
|
|
01h,12apr04,bwa added '#include <dsiSockLib.h>'; #undef'ed DEBUG_SHOW.
|
|
01g,04mar04,bwa changed 'netinet' directory references to 'netun';
|
|
dropped max data queued from 64k to 64k-1;
|
|
optimized uncomp_send() and uncomp_rcvd() for the reg case;
|
|
BUG FIX: buffer leak when ENOTCONN and EINVAL error in send().
|
|
01f,10feb04,bwa added express messages (MSG_EXP) support;
|
|
BUG FIX: non-reclamation of buffers when EMSGSIZE error;
|
|
01e,06jan04,bwa BUG FIX: connect-send-close-accept sequence was not allowed.
|
|
01d,05dec03,xli change the include path;
|
|
01c,03dec03,bwa BUG FIX: peer's so_rcv.sb_cc was not updated properly;
|
|
BUG FIX: shutdown(0) would still allow send from peer;
|
|
BUG FIX: could send data larger than receiver's buffer.
|
|
01b,26nov03,bwa wrote explanation about DIAB warning when testing sblock()
|
|
return value; cleanup of warnings generated by GNU.
|
|
01a,03nov03,bwa written.
|
|
*/
|
|
|
|
/*
|
|
DESCRIPTION
|
|
This library implements the major protocol-specific routines of the
|
|
AF_LOCAL/COMP protocol for sockets.
|
|
|
|
The routines present in here are not to be called directly; they have to be
|
|
registered in the AF_LOCAL protocol switch entry for COMP. They are then called
|
|
by the socket upper layers (sockLib, bsdSockLib, uipc_socket libraries) when
|
|
needed.
|
|
|
|
INTERNAL:
|
|
SYSTEM VIEWER INSTRUMENTATION
|
|
|
|
Network Critical instrumentation level.
|
|
|
|
NOMANUAL
|
|
*/
|
|
|
|
/* include files */
|
|
|
|
/* VxWorks system includes */
|
|
|
|
#include <vxWorks.h>
|
|
#include <string.h> /* for strlen() */
|
|
#include <stdio.h> /* for (s)printf() */
|
|
#include <iosLib.h> /* for printErrno() */
|
|
#include <objLib.h> /* for objNameSet() */
|
|
#include <stdlib.h> /* for malloc/free */
|
|
#include <private/iosLibP.h>
|
|
|
|
/* networking includes */
|
|
|
|
#include <sys/socket.h>
|
|
#include <netBufLib.h>
|
|
#include <net/mbuf.h>
|
|
#include <net/domain.h>
|
|
#include <net/protosw.h>
|
|
#include <dsi/socketvar.h>
|
|
#include <net/unixLib.h>
|
|
|
|
#include <sys/un.h>
|
|
#include <dsi/backend/dsiSockLib.h>
|
|
#include <dsi/un/uncomp.h>
|
|
#include <dsi/un/uncomp_var.h>
|
|
#include <dsi/un/uncomp_fsm.h>
|
|
|
|
#include <wvNetDLib.h>
|
|
|
|
#ifdef _WRS_CONFIG_SV_INSTRUMENTATION
|
|
#ifdef INCLUDE_WVNETD
|
|
/* Set common fields of event identifiers for this module. */
|
|
LOCAL UCHAR wvNetDModuleId = WV_NETD_UNCOMP_USRREQ_MODULE; /* uncomp_usrreq.c */
|
|
LOCAL UCHAR wvNetDLocalFilter = WV_NETD_NONE; /* Available event filter */
|
|
LOCAL UCHAR wvNetDEntityId = WV_NETD_SOCKET; /* Entity ID */
|
|
#endif /* INCLUDE_WVNETD */
|
|
#endif /* _WRS_CONFIG_SV_INSTRUMENTATION */
|
|
|
|
/* set limits for largest message possible (64k-1 for COMP) */
|
|
|
|
/* XXX : NOTE - These limits should be the size of buffers allocated for UNCOMP
|
|
* usage. HOWEVER, this is not implemented yet and what it does
|
|
* is only enforcing limits on send() and recv() operations.
|
|
* It is thus impossible to send a packet larger than 64kB and
|
|
* impossible for the receiving socket to hold more than 64kB of
|
|
* data at once.
|
|
*/
|
|
|
|
LOCAL uint32_t uncomp_sendspace = (64 * 1024) - 1; /* UNCOMP send buffers */
|
|
LOCAL uint32_t uncomp_recvspace = (64 * 1024) - 1; /* UNCOMP recv buffers */
|
|
|
|
/*
|
|
* This is the list of AF_LOCAL/COMP sockets. It is a doubly-linked list in
|
|
* which the sockets are queued in ascending order by their name. If sockets
|
|
* a created following a connection and they are given the same name as the
|
|
* listening socket, they are queued after the parent, in the reverse order
|
|
* that they were created. One exception: socket that are not yet named, and
|
|
* thus sport the invalid name '0x0', are queued at the end of the list.
|
|
*/
|
|
|
|
extern DL_LIST uncompSocketList; /* list of AF_LOCAL/COMP sockets */
|
|
|
|
/* local defines */
|
|
|
|
#define UNCOMP_NAME_PREFIX "/comp/socket/0x"
|
|
#define UNCOMP_NAME_PREFIX_LEN 15
|
|
#define UNCOMP_NAME_SUFFIX_START 15
|
|
#define UNCOMP_NAME_LEN_MAX 19
|
|
|
|
#define UNCOMP_INVALID_NAME 0x0
|
|
#define UNCOMP_FIRST_VALID_NAME 0x1
|
|
#define UNCOMP_LAST_VALID_NAME 0xffff
|
|
|
|
#define UNCOMP_OFFSET_ASCII_0 0x30
|
|
#define UNCOMP_OFFSET_ASCII_A 0x37
|
|
#define UNCOMP_OFFSET_ASCII_a 0x57
|
|
|
|
/* debugging definitions */
|
|
|
|
#define UNCOMP_DEBUG 0
|
|
#define UNCOMP_DEBUG_SHOW 0
|
|
#define UNCOMP_USE_OLD_BSD_STUFF 0
|
|
|
|
/* function prototypes */
|
|
|
|
LOCAL int uncomp_abort (struct socket *so);
|
|
LOCAL int uncomp_accept (struct socket *so, struct sockaddr **nam);
|
|
LOCAL int uncomp_attach (struct socket *so, int proto, struct proc *p);
|
|
LOCAL int uncomp_bind (struct socket *so, struct sockaddr *nam,
|
|
struct proc *p);
|
|
LOCAL int uncomp_connect (struct socket *so, struct sockaddr *nam,
|
|
struct proc *p);
|
|
LOCAL int uncomp_connect2 (struct socket *so1, struct socket *so2);
|
|
LOCAL int uncomp_disconnect (struct socket *so);
|
|
LOCAL int uncomp_detach (struct socket *so);
|
|
LOCAL int uncomp_listen (struct socket *so, struct proc *p);
|
|
LOCAL int uncomp_peeraddr (struct socket *so, struct sockaddr **nam);
|
|
LOCAL int uncomp_rcvd (struct socket * so, int flags);
|
|
LOCAL int uncomp_send (struct socket *so, int flags, struct mbuf *m,
|
|
struct sockaddr *nam, struct mbuf *control,
|
|
struct proc *p);
|
|
LOCAL int uncomp_shutdown (struct socket *so);
|
|
LOCAL int uncomp_sockaddr (struct socket *so, struct sockaddr **nam);
|
|
|
|
/* create and fill in the protocol switch entry function table */
|
|
|
|
struct pr_usrreqs uncomp_usrreqs =
|
|
{
|
|
uncomp_abort, uncomp_accept, uncomp_attach, uncomp_bind,
|
|
uncomp_connect, uncomp_connect2, dsiControlNotSupp, uncomp_detach,
|
|
uncomp_disconnect, uncomp_listen, uncomp_peeraddr, uncomp_rcvd,
|
|
dsiRcvoobNotSupp, uncomp_send, dsiSenseNull, uncomp_shutdown,
|
|
uncomp_sockaddr, NULL, NULL, NULL
|
|
};
|
|
|
|
/* convenience routines */
|
|
|
|
/*******************************************************************************
|
|
*
|
|
* uncomp_firstAvailableNameGet - Returns first available COMP socket name
|
|
*
|
|
* RETURNS: A 16-bit number (the socket name), suffix of /comp/socket/0xXXXX, or
|
|
* UNCOMP_INVALID_NAME if list of names is exhausted
|
|
*
|
|
* POSSIBLE ERROR VALUES: N/A
|
|
*
|
|
* NOMANUAL
|
|
*/
|
|
|
|
LOCAL uint16_t uncomp_firstAvailableNameGet
|
|
(
|
|
UNCOMPCB ** ppSlot /* container, place where to insert the socket */
|
|
)
|
|
{
|
|
UNCOMPCB * pUnComp = (UNCOMPCB *) DLL_FIRST (&uncompSocketList);
|
|
UNCOMPCB * pUnCompPrev;
|
|
|
|
/* is list empty ? */
|
|
|
|
if (pUnComp == NULL)
|
|
{
|
|
*ppSlot = NULL;
|
|
return (UNCOMP_FIRST_VALID_NAME);
|
|
}
|
|
|
|
/* is slot before first node ? */
|
|
|
|
if (pUnComp->selfName != UNCOMP_FIRST_VALID_NAME)
|
|
{
|
|
*ppSlot = NULL;
|
|
return (UNCOMP_FIRST_VALID_NAME);
|
|
}
|
|
|
|
FOREVER /* find a slot: exhaust the list */
|
|
{
|
|
pUnCompPrev = pUnComp;
|
|
|
|
FOREVER /* skip name duplicates */
|
|
{
|
|
/* if there are duplicates up until the tail, tail is the slot */
|
|
|
|
if ((UNCOMPCB *) DLL_NEXT ((DL_NODE *)pUnComp) == NULL)
|
|
{
|
|
/* are all (2^16)-1 names already taken ? (HIGHLY unlikely) */
|
|
|
|
if (pUnComp->selfName == UNCOMP_LAST_VALID_NAME)
|
|
return (UNCOMP_INVALID_NAME);
|
|
|
|
/* if not, new socket will be inserted after the current one */
|
|
|
|
*ppSlot = pUnComp;
|
|
return (uint16_t)(pUnComp->selfName + 1);
|
|
}
|
|
|
|
/* next node is not NULL, move current indicator */
|
|
|
|
pUnComp = (UNCOMPCB *) DLL_NEXT ((DL_NODE *)pUnComp);
|
|
|
|
/* check if previous name == current name... */
|
|
|
|
if (pUnComp->selfName != pUnCompPrev->selfName)
|
|
{
|
|
/* ...it is not */
|
|
|
|
/* if (cur name != prev+1), there is a gap: this is the slot */
|
|
|
|
if (pUnComp->selfName != (pUnCompPrev->selfName + 1))
|
|
{
|
|
*ppSlot = (UNCOMPCB *) DLL_PREVIOUS ((DL_NODE *)pUnComp);
|
|
return (uint16_t)((*ppSlot)->selfName + 1);
|
|
}
|
|
else /* ...it is: no gap, continue searching */
|
|
break;
|
|
}
|
|
/*
|
|
* if we get here, two contiguous sockets have the same name:
|
|
* continue the loop to discard all duplicates
|
|
*/
|
|
}
|
|
}
|
|
}
|
|
|
|
/*******************************************************************************
|
|
*
|
|
* uncomp_nameDecode - extract the suffix from an AF_LOCAL/COMP socket name
|
|
*
|
|
* RETURNS: the XXXX part of /comp/socket/0xXXXX or UNCOMP_INVALID_NAME if
|
|
* illegal string
|
|
*
|
|
* POSSIBLE ERROR VALUES: N/A
|
|
*
|
|
* NOMANUAL
|
|
*/
|
|
|
|
LOCAL uint16_t uncomp_nameDecode
|
|
(
|
|
char * pName /* UNCOMP name in the format /comp/socket/0xXXXX */
|
|
)
|
|
{
|
|
uint16_t suffix = UNCOMP_INVALID_NAME;
|
|
int index = UNCOMP_NAME_PREFIX_LEN;
|
|
int len;
|
|
|
|
pName[103] = '\0';
|
|
len = (int)strlen (pName);
|
|
|
|
/*
|
|
* only names in the format /comp/socket/0xXXXX are accepted, where
|
|
* XXXX is a one-to-four digits hexadecimal number (i.e. 0x1 to 0xFFFF)
|
|
*/
|
|
|
|
/* verify lower and upper name len boundaries */
|
|
|
|
if (len < UNCOMP_NAME_PREFIX_LEN + 1) /* at least prefix + 1 */
|
|
return (UNCOMP_INVALID_NAME);
|
|
|
|
if (len > UNCOMP_NAME_LEN_MAX) /* at max prefix + 0xFFFF */
|
|
return (UNCOMP_INVALID_NAME);
|
|
|
|
/* verify the prefix passed is '/comp/socket/0x' */
|
|
|
|
if (strncmp (pName, UNCOMP_NAME_PREFIX, UNCOMP_NAME_PREFIX_LEN) != 0)
|
|
return UNCOMP_INVALID_NAME;
|
|
|
|
/* loop to numerize all digits in a XXXX hexadecimal character string */
|
|
|
|
len -= UNCOMP_NAME_PREFIX_LEN;
|
|
|
|
for (; len > 0; len--,index++)
|
|
{
|
|
if (pName[index] >= '0' && pName[index] <= '9')
|
|
suffix += (uint16_t)((pName[index] - UNCOMP_OFFSET_ASCII_0) << ((len-1) * 4));
|
|
else if (pName[index] >= 'A' && pName[index] <= 'F')
|
|
suffix += (uint16_t)((pName[index] - UNCOMP_OFFSET_ASCII_A) << ((len-1) * 4));
|
|
else if (pName[index] >= 'a' && pName[index] <= 'f')
|
|
suffix += (uint16_t)((pName[index] - UNCOMP_OFFSET_ASCII_a) << ((len-1) * 4));
|
|
else
|
|
return UNCOMP_INVALID_NAME;
|
|
}
|
|
|
|
return (suffix);
|
|
}
|
|
|
|
/*******************************************************************************
|
|
*
|
|
* uncomp_acceptSocketFind - find the accepting socket by name
|
|
*
|
|
* RETURNS: the accepting socket structure, or NULL if not found
|
|
*
|
|
* POSSIBLE ERROR VALUES: N/A
|
|
*
|
|
* NOMANUAL
|
|
*/
|
|
|
|
LOCAL struct socket * uncomp_acceptSocketFind
|
|
(
|
|
struct sockaddr_un * pName /* name of the socket to find, in format: */
|
|
/* /comp/socket/0xXXXX */
|
|
)
|
|
{
|
|
/* numerize name */
|
|
|
|
uint16_t name = uncomp_nameDecode (pName->sun_path);
|
|
|
|
/* grab first uncomp socket */
|
|
|
|
DL_NODE * pCur = (DL_NODE *) DLL_FIRST (&uncompSocketList);
|
|
|
|
/* something's wrong ... */
|
|
|
|
if ((name == UNCOMP_INVALID_NAME) || (pCur == NULL))
|
|
return (NULL);
|
|
|
|
/*
|
|
* loop through sockets list to find the correct accepting socket;
|
|
* the correct one is one with the correct name and free space on the
|
|
* accepting queue; bail out early if we're passed the wanted name.
|
|
*/
|
|
|
|
do
|
|
{
|
|
if (((UNCOMPCB *)pCur)->selfName == name)
|
|
if (((UNCOMPCB *)pCur)->pSelfSocket->so_qlen <
|
|
((UNCOMPCB *)pCur)->pSelfSocket->so_qlimit)
|
|
break;
|
|
|
|
if (((UNCOMPCB *)pCur)->selfName > name)
|
|
{
|
|
pCur = NULL;
|
|
break;
|
|
}
|
|
|
|
} while ((pCur = (DL_NODE *) DLL_NEXT (pCur)) != NULL);
|
|
|
|
if (pCur != NULL)
|
|
return (((UNCOMPCB *)pCur)->pSelfSocket);
|
|
else
|
|
return (NULL);
|
|
}
|
|
|
|
/*******************************************************************************
|
|
*
|
|
* uncomp_dupsockaddr - duplicate the sockaddr_un of a socket
|
|
*
|
|
* Memory is allocated, DSI_ADDR_FREE() must be called on the return value later
|
|
* if memory is to be reclaimed.
|
|
*
|
|
* RETURNS: the new instance of a sockaddr_un, or NULL if allocation failed
|
|
*
|
|
* POSSIBLE ERROR VALUES: N/A
|
|
*
|
|
* NOMANUAL
|
|
*/
|
|
|
|
LOCAL struct sockaddr_un * uncomp_dupsockaddr
|
|
(
|
|
UNCOMPCB * pUnComp /* the AF_LOCAL block in which to fetch the address */
|
|
)
|
|
{
|
|
struct sockaddr_un * pName;
|
|
|
|
int inc = UNCOMP_NAME_SUFFIX_START; /* increment counter */
|
|
char temp; /* temp holder for char */
|
|
int nameSize, pos;
|
|
|
|
pName = DSI_ADDR_ALLOC (struct sockaddr_un); /* allocate name structure */
|
|
|
|
if (pName == NULL)
|
|
return NULL;
|
|
|
|
pName->sun_len = UN_SOCKADDR_UN_SIZE; /* length of structure */
|
|
pName->sun_family = AF_LOCAL; /* address family */
|
|
|
|
strcpy (pName->sun_path, UNCOMP_NAME_PREFIX); /* prepend prefix */
|
|
|
|
/* go through all digits in the name and convert them to characters */
|
|
|
|
for (nameSize = 0x1000, pos = 4; pos > 0; nameSize >>= 4, pos--)
|
|
{
|
|
if (pUnComp->selfName >= nameSize)
|
|
{
|
|
temp = (char)((pUnComp->selfName >> ((pos - 1) * 4)) & 0xf);
|
|
temp += (char)(temp > 9 ? UNCOMP_OFFSET_ASCII_a : UNCOMP_OFFSET_ASCII_0);
|
|
pName->sun_path[inc++] = temp;
|
|
}
|
|
}
|
|
|
|
if (pUnComp->selfName == 0x0)
|
|
pName->sun_path[inc++] = '0';
|
|
|
|
pName->sun_path[inc] = '\0';
|
|
|
|
return (pName);
|
|
}
|
|
|
|
#if UNCOMP_DEBUG_SHOW
|
|
/*******************************************************************************
|
|
*
|
|
* uncomp_socketDump - dumps information about an AF_LOCAL/COMP socket
|
|
*
|
|
* Debug-only routine
|
|
*
|
|
* Usage:
|
|
* 'so' parameter: fd or socket pointer
|
|
* 'type' parameter: 0 tells 'so' is an fd, 1 tells 'so' is a pointer
|
|
*
|
|
* RETURNS: N/A
|
|
*
|
|
* POSSIBLE ERROR VALUES: N/A
|
|
*
|
|
* NOMANUAL
|
|
*/
|
|
|
|
void uncomp_socketDump
|
|
(
|
|
long so, /* socket to dump */
|
|
int type /* 0 = fd, 1 = socket ptr */
|
|
)
|
|
{
|
|
char * uncomp_stateGet (uint16_t state, char * pBuffer);
|
|
|
|
struct socket * pSocket = (type ?
|
|
(struct socket *)so :
|
|
(struct socket *)iosFdValue(so));
|
|
UNCOMPCB * pUnComp = (UNCOMPCB *)pSocket->so_pcb;
|
|
struct mbuf * pNext;
|
|
char pState[20];
|
|
|
|
printf ("--------------- %d ----------------\n", pSocket->so_fd);
|
|
printf ("socket dump for so %d\n", so);
|
|
printf ("socket structure: 0x%lx\n", (long)pSocket);
|
|
printf ("pSocket->so_state: 0x%x\n", pSocket->so_state);
|
|
printf ("pSocket->so_options: 0x%x\n", pSocket->so_options);
|
|
printf ("pSocket->so_head: 0x%lx\n", (long)pSocket->so_head);
|
|
printf ("pSocket->so_qlen: %d\n", pSocket->so_qlen);
|
|
printf ("first unaccepted socket: 0x%lx\n", (long)pSocket->so_comp.tqh_first);
|
|
printf ("pSocket->so_qlimit: %d\n", pSocket->so_qlimit);
|
|
printf ("pSocket->so_fd: %d\n", pSocket->so_fd);
|
|
printf ("&pSocket->so_snd: 0x%lx\n", (long)&pSocket->so_snd);
|
|
printf ("pSocket->so_snd.sb_lowat:0x%lx\n", (long)pSocket->so_snd.sb_lowat);
|
|
printf ("pSocket->so_snd.sb_hiwat:0x%lx\n", (long)pSocket->so_snd.sb_hiwat);
|
|
printf ("pSocket->so_snd.sb_mb: 0x%lx\n", (long)pSocket->so_snd.sb_mb);
|
|
printf ("&pSocket->so_rcv: 0x%lx\n", (long)&pSocket->so_rcv);
|
|
printf ("pSocket->so_rcv.sb_lowat:0x%lx\n", (long)pSocket->so_rcv.sb_lowat);
|
|
printf ("pSocket->so_rcv.sb_hiwat:0x%lx\n", (long)pSocket->so_rcv.sb_hiwat);
|
|
printf ("pSocket->so_rcv.sb_cc: 0x%lx\n", (long)pSocket->so_rcv.sb_cc);
|
|
printf ("pSocket->so_rcv.sb_mb: 0x%lx, len = %d\n",
|
|
(long)pSocket->so_rcv.sb_mb,
|
|
pSocket->so_rcv.sb_mb != NULL ? pSocket->so_rcv.sb_mb->m_len : 0);
|
|
if (pSocket->so_rcv.sb_mb == NULL)
|
|
goto uncomp_tag;
|
|
pNext = pSocket->so_rcv.sb_mb->m_nextpkt;
|
|
while (pNext)
|
|
{
|
|
printf (" 0x%lx, len = %d\n",
|
|
(long)pNext, pNext->m_len);
|
|
pNext = pNext->m_nextpkt;
|
|
}
|
|
uncomp_tag:
|
|
printf ("COMP: 0x%lx\n", (long)pUnComp);
|
|
printf ("COMP->pSelfSocket: 0x%lx\n", (long)pUnComp->pSelfSocket);
|
|
printf ("COMP->pPeerSocket: 0x%lx\n", (long)pUnComp->pPeerSocket);
|
|
printf ("COMP->selfName: 0x%x\n", pUnComp->selfName);
|
|
printf ("COMP->selfState: %s\n",
|
|
uncomp_stateGet (pUnComp->selfState, pState));
|
|
printf ("----------------------------------\n");
|
|
}
|
|
#endif /* UNCOMP_DEBUG_SHOW */
|
|
|
|
/*******************************************************************************
|
|
*
|
|
* uncomp_stateGet - returns a string for an AF_LOCAL/COMP state number
|
|
*
|
|
* Note: pBuffer is modified.
|
|
*
|
|
* RETURNS: the state string
|
|
*
|
|
* POSSIBLE ERROR VALUES: N/A
|
|
*
|
|
* NOMANUAL
|
|
*/
|
|
|
|
char * uncomp_stateGet
|
|
(
|
|
uint16_t state, /* state, UNCOMPS_XXX value */
|
|
char * pBuffer /* where to return the string */
|
|
)
|
|
{
|
|
switch (state)
|
|
{
|
|
case UNCOMPS_CLOSED:
|
|
bcopy ("CLOSED", pBuffer, strlen("CLOSED")+1 );
|
|
break;
|
|
case UNCOMPS_LISTENING:
|
|
bcopy ("LISTENING", pBuffer, strlen("LISTENING")+1 );
|
|
break;
|
|
case UNCOMPS_EXCHANGING:
|
|
bcopy ("EXCHANGING", pBuffer, strlen("EXCHANGING")+1);
|
|
break;
|
|
case UNCOMPS_DONE_SEND:
|
|
bcopy ("DONE_SEND", pBuffer, strlen("DONE_SEND")+1 );
|
|
break;
|
|
case UNCOMPS_DONE_RECV:
|
|
bcopy ("DONE_RECV", pBuffer, strlen("DONE_RECV")+1 );
|
|
break;
|
|
case UNCOMPS_REQUEST:
|
|
bcopy ("REQUEST", pBuffer, strlen("REQUEST")+1 );
|
|
break;
|
|
}
|
|
|
|
return pBuffer;
|
|
}
|
|
|
|
#if UNCOMP_DEBUG_SHOW
|
|
/*******************************************************************************
|
|
*
|
|
* uncomp_sdl - dumps the list of AF_LOCAL/COMP sockets and all their info
|
|
*
|
|
* Debug-only routine. 'sdl' stands for 'Socket Dump List'.
|
|
*
|
|
* RETURNS: N/A
|
|
*
|
|
* POSSIBLE ERROR VALUES: N/A
|
|
*
|
|
* NOMANUAL
|
|
*/
|
|
|
|
void uncomp_sdl (void)
|
|
{
|
|
DL_NODE * pCur = (DL_NODE *) DLL_FIRST (&uncompSocketList);
|
|
|
|
if (pCur == NULL)
|
|
{
|
|
printf ("uncompSocketList is empty\n");
|
|
return;
|
|
}
|
|
|
|
do
|
|
{
|
|
uncomp_socketDump ((long)((UNCOMPCB *)pCur)->pSelfSocket, 1);
|
|
} while ((pCur = (DL_NODE *) DLL_NEXT (pCur)) != NULL);
|
|
}
|
|
#endif /* UNCOMP_DEBUG_SHOW */
|
|
|
|
/* Local COMP protocol implementation */
|
|
|
|
/*******************************************************************************
|
|
*
|
|
* uncomp_abort - AF_LOCAL/COMP pru_abort routine
|
|
*
|
|
* Disconnect and detach a local socket and its peer; called when a close()
|
|
* command is issued, only when sockets are pending on the listen socket.
|
|
*
|
|
* RETURNS: 0 on success or an error value
|
|
*
|
|
* POSSIBLE ERROR VALUES:
|
|
* EINVAL
|
|
*
|
|
* NOMANUAL
|
|
*/
|
|
|
|
LOCAL int uncomp_abort
|
|
(
|
|
struct socket *so /* socket on which to abort */
|
|
)
|
|
{
|
|
UNCOMPCB * pUnComp = SO_TO_UNCOMPCB(so);
|
|
|
|
if (pUnComp == NULL)
|
|
return EINVAL;
|
|
|
|
so->so_error = ECONNABORTED;
|
|
|
|
uncomp_detach(so);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*******************************************************************************
|
|
*
|
|
* uncomp_accept - AF_LOCAL/COMP pru_accept routine
|
|
*
|
|
* Pass back name of connected socket. This routine is actually called AFTER
|
|
* the sockets are connected via a connect() call by the client.
|
|
*
|
|
* RETURNS: 0 on success or an error value
|
|
*
|
|
* POSSIBLE ERROR VALUES:
|
|
* EINVAL
|
|
* ENOBUFS
|
|
*
|
|
* NOMANUAL
|
|
*/
|
|
|
|
LOCAL int uncomp_accept
|
|
(
|
|
struct socket *so, /* new socket created by connect2()(dsiSoNewConn3) */
|
|
struct sockaddr **nam /* container for the name of the peer socket */
|
|
)
|
|
{
|
|
UNCOMPCB * pUnComp = SO_TO_UNCOMPCB(so);
|
|
|
|
if (pUnComp == NULL)
|
|
return EINVAL;
|
|
|
|
/*
|
|
* Pass back name of connected socket, if it was bound and we are still
|
|
* connected (our peer may have closed already!).
|
|
*/
|
|
|
|
if ((pUnComp->selfState == UNCOMPS_EXCHANGING) &&
|
|
(pUnComp->pPeerSocket != NULL))
|
|
{
|
|
/* returning peer's addr */
|
|
|
|
pUnComp = SO_TO_UNCOMPCB (pUnComp->pPeerSocket);
|
|
*nam = (struct sockaddr *) uncomp_dupsockaddr (pUnComp);
|
|
|
|
if (*nam == NULL)
|
|
return ENOBUFS;
|
|
}
|
|
|
|
/* else do nothing, there is no peer, so no address to return */
|
|
|
|
/*
|
|
* NOTE: the only case when this routine is called and there is no peer
|
|
* is if a peer did a socket-connect-send-close series of calls,
|
|
* and the peer has closed before the accept is done; in this case,
|
|
* the accept() call should succeed, not fail.
|
|
*/
|
|
|
|
return (0);
|
|
}
|
|
|
|
/*******************************************************************************
|
|
*
|
|
* uncomp_attach - AF_LOCAL/COMP pru_attach routine
|
|
*
|
|
* Create the 'so_pcb' field of an AF_LOCAL/COMP socket. The data structure
|
|
* created is of type 'UNCOMPCB'.
|
|
*
|
|
* RETURNS: 0 on success or an error value
|
|
*
|
|
* POSSIBLE ERROR VALUES:
|
|
* EISCONN
|
|
* ENOBUFS
|
|
*
|
|
* NOMANUAL
|
|
*/
|
|
|
|
LOCAL int uncomp_attach
|
|
(
|
|
struct socket *so, /* socket that needs the new UNCOMPCB data struct */
|
|
int proto, /* protocol, unused for now */
|
|
struct proc *p /* always NULL */
|
|
)
|
|
{
|
|
UNCOMPCB * pUnComp = SO_TO_UNCOMPCB(so);
|
|
int error = 0;
|
|
|
|
/* is socket already set up ? */
|
|
|
|
if (pUnComp != NULL)
|
|
return EISCONN;
|
|
|
|
/* set buffer limits */
|
|
|
|
if (so->so_snd.sb_hiwat == 0 || so->so_rcv.sb_hiwat == 0)
|
|
error = dsiSoReserve (so, uncomp_sendspace, uncomp_recvspace);
|
|
|
|
if (error)
|
|
return (error);
|
|
|
|
/* allocate space for uncompcb */
|
|
|
|
pUnComp = DSI_PCB_ALLOC(UNCOMPCB);
|
|
|
|
if (pUnComp == NULL)
|
|
return (ENOBUFS);
|
|
|
|
/* initialize uncompcb part of socket */
|
|
|
|
bzero((char *)pUnComp, sizeof (UNCOMPCB)); /* ZERO everyting */
|
|
|
|
pUnComp->pSelfSocket = so; /* store back ptr to socket */
|
|
pUnComp->selfState = UNCOMPS_CLOSED; /* socket is in CLOSED state */
|
|
pUnComp->selfName = UNCOMP_INVALID_NAME; /* bogus name, not named yet */
|
|
|
|
dllAdd (&uncompSocketList, &pUnComp->node); /* add socket in uncomp list */
|
|
|
|
so->so_pcb = (caddr_t)pUnComp; /* store uncompcb in socket */
|
|
|
|
return (0);
|
|
}
|
|
|
|
/*******************************************************************************
|
|
*
|
|
* uncomp_bind - AF_LOCAL/COMP pru_bind routine
|
|
*
|
|
* Bind a user-provided name (AF_LOCAL address) to a socket. If the user didn't
|
|
* provide a name, one is assigned by the system.
|
|
*
|
|
* RETURNS: 0 on success or an error value
|
|
*
|
|
* POSSIBLE ERROR VALUES:
|
|
* EINVAL
|
|
* EADDRINUSE
|
|
* EMFILE
|
|
*
|
|
* NOMANUAL
|
|
*/
|
|
|
|
LOCAL int uncomp_bind
|
|
(
|
|
struct socket *so, /* socket to bind name 'nam' on */
|
|
struct sockaddr *nam, /* name to be bound to socket 'so' */
|
|
struct proc *p /* always NULL */
|
|
)
|
|
{
|
|
UNCOMPCB * pUnComp = SO_TO_UNCOMPCB(so);
|
|
UNCOMPCB * pSlot; /* slot where to insert socket in list */
|
|
struct sockaddr_un * pName = (struct sockaddr_un *)nam;
|
|
uint16_t selfName; /* name of socket */
|
|
|
|
/* is it a local COMP socket ? */
|
|
|
|
if (pUnComp == NULL)
|
|
return EINVAL;
|
|
|
|
/* did user provide a name ? */
|
|
|
|
if ((pName == NULL) || (pName->sun_path[0] == '\0'))
|
|
{
|
|
/* find available socket name and slot where to put it in the queue */
|
|
|
|
pUnComp->selfName = uncomp_firstAvailableNameGet (&pSlot);
|
|
if (pUnComp->selfName == UNCOMP_INVALID_NAME)
|
|
return (EMFILE);
|
|
|
|
/* remove socket from tail of queue and put it after slot found */
|
|
|
|
dllRemove (&uncompSocketList, &pUnComp->node);
|
|
dllInsert (&uncompSocketList, pSlot != NULL ? &pSlot->node : NULL,
|
|
&pUnComp->node);
|
|
|
|
return (0);
|
|
}
|
|
|
|
/* grab real name (suffix from /comp/socket/suffix) */
|
|
|
|
selfName = uncomp_nameDecode (pName->sun_path);
|
|
|
|
/* is it a valid name ? */
|
|
|
|
if (selfName == UNCOMP_INVALID_NAME)
|
|
return (EINVAL);
|
|
|
|
/* traverse list to find insertion spot */
|
|
|
|
for (pSlot = (UNCOMPCB *)DLL_LAST (&uncompSocketList);
|
|
pSlot != NULL;
|
|
pSlot = (UNCOMPCB *)DLL_PREVIOUS (pSlot))
|
|
{
|
|
if ((pSlot->selfName < selfName) && (pSlot->selfName != 0))
|
|
break;
|
|
|
|
if (pSlot->selfName == selfName)
|
|
return (EADDRINUSE);
|
|
}
|
|
|
|
/* store name in uncompcb part of socket */
|
|
|
|
pUnComp->selfName = selfName;
|
|
|
|
dllRemove (&uncompSocketList, &pUnComp->node);
|
|
dllInsert (&uncompSocketList, pSlot != NULL ? &pSlot->node : NULL,
|
|
&pUnComp->node);
|
|
|
|
return (0);
|
|
}
|
|
|
|
/*******************************************************************************
|
|
*
|
|
* uncomp_connect - AF_LOCAL/COMP pru_connect routine
|
|
*
|
|
* Connect the local socket 'so' to another socket at the address 'nam'. Both
|
|
* sockets are local, this being the implementation of the AF_LOCAL/COMP
|
|
* protocol.
|
|
*
|
|
* RETURNS: 0 on success or an error value
|
|
*
|
|
* POSSIBLE ERROR VALUES:
|
|
* EINVAL
|
|
* ECONNREFUSED
|
|
* EMFILE
|
|
*
|
|
* NOMANUAL
|
|
*/
|
|
|
|
LOCAL int uncomp_connect
|
|
(
|
|
struct socket *so, /* socket to connect */
|
|
struct sockaddr *nam, /* name of listening socket */
|
|
struct proc *p /* always NULL */
|
|
)
|
|
{
|
|
UNCOMPCB * pUnComp = SO_TO_UNCOMPCB(so); /* PCB of socket */
|
|
UNCOMPCB * pSlot; /* slot where to insert socket */
|
|
struct sockaddr_un * pName = (struct sockaddr_un *) nam;/* nam conversion */
|
|
struct socket * pAcceptSocket;
|
|
struct socket * pConnectedPeerSocket;
|
|
|
|
/* is socket a local COMP socket ? */
|
|
|
|
if (pUnComp == NULL)
|
|
return EINVAL;
|
|
|
|
/* can we find accepting socket that is named 'nam' ? */
|
|
|
|
if ((pAcceptSocket = uncomp_acceptSocketFind (pName)) == NULL)
|
|
return ECONNREFUSED;
|
|
|
|
/* is socket found really an accepting socket ? */
|
|
|
|
if ((pAcceptSocket->so_options & SO_ACCEPTCONN) == 0)
|
|
return ECONNREFUSED;
|
|
|
|
/* create new peer socket and place it in the 'CONNECTED' state */
|
|
|
|
pConnectedPeerSocket = dsiSoNewConn3 (pAcceptSocket, SS_ISCONNECTED, p);
|
|
|
|
if (pConnectedPeerSocket == NULL)
|
|
return ECONNREFUSED;
|
|
|
|
/* new connected socket's peer's name == same as the accepting socket's */
|
|
|
|
SO_TO_UNCOMPCB(pConnectedPeerSocket)->selfName =
|
|
SO_TO_UNCOMPCB(pAcceptSocket)->selfName;
|
|
|
|
/*
|
|
* remove new socket from the tail of the AF_LOCAL/COMP sockets queue
|
|
* and put it after the accepting socket in that queue
|
|
*/
|
|
|
|
dllRemove (&uncompSocketList,
|
|
&(SO_TO_UNCOMPCB(pConnectedPeerSocket)->node));
|
|
dllInsert (&uncompSocketList, &(SO_TO_UNCOMPCB(pAcceptSocket)->node),
|
|
&(SO_TO_UNCOMPCB(pConnectedPeerSocket)->node));
|
|
|
|
/* allocate a name for the socket we are connecting */
|
|
|
|
/* is socket already bound ? */
|
|
|
|
if (pUnComp->selfName == UNCOMP_INVALID_NAME)
|
|
{
|
|
/* find available socket name and slot where to put it in the list */
|
|
|
|
pUnComp->selfName = uncomp_firstAvailableNameGet (&pSlot);
|
|
if (pUnComp->selfName == UNCOMP_INVALID_NAME)
|
|
return (EMFILE);
|
|
|
|
/* remove socket from tail of queue and put it after slot found */
|
|
|
|
dllRemove (&uncompSocketList, &pUnComp->node);
|
|
dllInsert (&uncompSocketList, pSlot != NULL ? &pSlot->node : NULL,
|
|
&pUnComp->node);
|
|
}
|
|
|
|
/* finally, connect the two sockets locally */
|
|
|
|
return (uncomp_connect2 (so, pConnectedPeerSocket));
|
|
}
|
|
|
|
/*******************************************************************************
|
|
*
|
|
* uncomp_connect2 - AF_LOCAL/COMP pru_connect2 routine
|
|
*
|
|
* Connect two local sockets 'so1' and 'so2' together.
|
|
*
|
|
* RETURNS: 0 on success or an error value
|
|
*
|
|
* POSSIBLE ERROR VALUES:
|
|
* EINVAL
|
|
*
|
|
* NOMANUAL
|
|
*/
|
|
|
|
LOCAL int uncomp_connect2
|
|
(
|
|
struct socket *so1, /* connecting socket */
|
|
struct socket *so2 /* socket created by connection */
|
|
)
|
|
{
|
|
UNCOMPCB *pUnComp1 = SO_TO_UNCOMPCB(so1);
|
|
UNCOMPCB *pUnComp2 = SO_TO_UNCOMPCB(so2);
|
|
|
|
/* are the two sockets valid local COMP sockets ? */
|
|
|
|
if ((pUnComp1 == NULL) || (pUnComp2 == NULL))
|
|
return EINVAL;
|
|
|
|
/* exchange local COMP information between peer sockets */
|
|
|
|
pUnComp2->pPeerSocket = pUnComp1->pSelfSocket;
|
|
pUnComp1->pPeerSocket = pUnComp2->pSelfSocket;
|
|
|
|
/* the two sockets are now connected */
|
|
|
|
dsiSoIsConnected (so1);
|
|
dsiSoIsConnected (so2);
|
|
|
|
pUnComp1->selfState = UNCOMPS_EXCHANGING;
|
|
pUnComp2->selfState = UNCOMPS_EXCHANGING;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* control is EOPNOTSUPP */
|
|
|
|
/*******************************************************************************
|
|
*
|
|
* uncomp_detach - AF_LOCAL/COMP pru_detach routine
|
|
*
|
|
* RETURNS: 0 on success or an error value
|
|
*
|
|
* POSSIBLE ERROR VALUES:
|
|
* EINVAL
|
|
*
|
|
* NOMANUAL
|
|
*/
|
|
|
|
LOCAL int uncomp_detach
|
|
(
|
|
struct socket *so /* socket that is closing */
|
|
)
|
|
{
|
|
UNCOMPCB *pUnComp = SO_TO_UNCOMPCB(so);
|
|
|
|
if (pUnComp == NULL)
|
|
return EINVAL;
|
|
|
|
/* remove socket from list of active AF_LOCAL/COMP socket */
|
|
|
|
DLL_REMOVE(&uncompSocketList, pUnComp);
|
|
|
|
/* disconnect if connected */
|
|
|
|
if (pUnComp->pPeerSocket != NULL)
|
|
uncomp_disconnect(pUnComp->pSelfSocket);
|
|
|
|
DSI_PCB_FREE(pUnComp);
|
|
so->so_pcb = NULL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*******************************************************************************
|
|
*
|
|
* uncomp_disconnect - AF_LOCAL/COMP pru_disconnect routine
|
|
*
|
|
* Disconnect socket 'so' from its peer, and vice-versa. Both sockets are placed
|
|
* in the 'CLOSED' state.
|
|
*
|
|
* This routine is called by sodisconnect() (which is called by close() and
|
|
* soconnect() (in the case an error occurs)) and uncomp_detach().
|
|
*
|
|
* RETURNS: 0 on success or an error value
|
|
*
|
|
* POSSIBLE ERROR VALUES:
|
|
* EINVAL
|
|
* ENOTCONN
|
|
*
|
|
* NOMANUAL
|
|
*/
|
|
|
|
LOCAL int uncomp_disconnect
|
|
(
|
|
struct socket *so /* socket that is disconnecting */
|
|
)
|
|
{
|
|
UNCOMPCB * pUnCompPeer;
|
|
UNCOMPCB * pUnCompSelf = SO_TO_UNCOMPCB(so);
|
|
|
|
/* valid local COMP socket ? */
|
|
|
|
if (pUnCompSelf == NULL)
|
|
return EINVAL;
|
|
|
|
/* is socket connected ? */
|
|
|
|
if ((pUnCompPeer = SO_TO_UNCOMPCB(pUnCompSelf->pPeerSocket)) == NULL)
|
|
return ENOTCONN;
|
|
|
|
/* close both ends of the connection */
|
|
|
|
pUnCompSelf->pPeerSocket = pUnCompPeer->pPeerSocket = NULL;
|
|
|
|
pUnCompSelf->selfState = UNCOMPS_CLOSED;
|
|
pUnCompPeer->selfState = UNCOMPS_CLOSED;
|
|
|
|
dsiSoIsDisconnected (pUnCompSelf->pSelfSocket);
|
|
dsiSoIsDisconnected (pUnCompPeer->pSelfSocket);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*******************************************************************************
|
|
*
|
|
* uncomp_listen - AF_LOCAL/COMP pru_listen routine
|
|
*
|
|
* Called by solisten. Does little besides setting the listening socket's state
|
|
* to 'LISTENING'.
|
|
*
|
|
* bind()s a name if not already bound.
|
|
*
|
|
* RETURNS: 0 on success or an error value
|
|
*
|
|
* POSSIBLE ERROR VALUES:
|
|
* EINVAL
|
|
*
|
|
* NOMANUAL
|
|
*/
|
|
|
|
LOCAL int uncomp_listen
|
|
(
|
|
struct socket *so, /* socket that is listening */
|
|
struct proc *p /* always NULL */
|
|
)
|
|
{
|
|
UNCOMPCB * pUnComp = SO_TO_UNCOMPCB(so);
|
|
int error;
|
|
|
|
if ((pUnComp == NULL) || (pUnComp->selfState != UNCOMPS_CLOSED))
|
|
return EINVAL;
|
|
|
|
if (pUnComp->selfName == UNCOMP_INVALID_NAME)
|
|
if ((error = uncomp_bind (so, NULL, p)) != 0)
|
|
return (error);
|
|
|
|
pUnComp->selfState = UNCOMPS_LISTENING;
|
|
|
|
return (0);
|
|
}
|
|
|
|
/*******************************************************************************
|
|
*
|
|
* uncomp_peeraddr - AF_LOCAL/COMP pru_peeraddr routine
|
|
*
|
|
* Return the address of the socket connected to socket 'so' through 'nam'.
|
|
*
|
|
* RETURNS: 0 on success or an error value
|
|
*
|
|
* POSSIBLE ERROR VALUES:
|
|
* ENOBUFS
|
|
* ENOTCONN
|
|
*
|
|
* NOMANUAL
|
|
*/
|
|
|
|
LOCAL int uncomp_peeraddr
|
|
(
|
|
struct socket *so, /* socket to get peer's address from */
|
|
struct sockaddr **nam /* container, name of peer */
|
|
)
|
|
{
|
|
UNCOMPCB *pUnComp = SO_TO_UNCOMPCB(so);
|
|
|
|
/* return peer's address if socket is a COMP local one and is connected */
|
|
|
|
if (pUnComp == NULL)
|
|
return EINVAL;
|
|
|
|
if ((pUnComp->selfState == UNCOMPS_EXCHANGING) ||
|
|
(pUnComp->selfState == UNCOMPS_DONE_RECV) ||
|
|
(pUnComp->selfState == UNCOMPS_DONE_SEND))
|
|
{
|
|
pUnComp = SO_TO_UNCOMPCB(pUnComp->pPeerSocket);
|
|
|
|
/* memory allocation done here, caller will free memory */
|
|
|
|
*nam = (struct sockaddr *) uncomp_dupsockaddr(pUnComp);
|
|
|
|
if (*nam == NULL)
|
|
return ENOBUFS;
|
|
}
|
|
else
|
|
return ENOTCONN;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*******************************************************************************
|
|
*
|
|
* uncomp_rcvd - AF_LOCAL/COMP pru_rcvd routine
|
|
*
|
|
* Work done on AF_LOCAL/COMP sockets at the end of the recv operation.
|
|
*
|
|
* RETURNS: 0
|
|
*
|
|
* NOMANUAL
|
|
*/
|
|
|
|
LOCAL int uncomp_rcvd
|
|
(
|
|
struct socket * so, /* socket receiving */
|
|
int flags /* recv flags */
|
|
)
|
|
{
|
|
UNCOMPCB * pUnComp = SO_TO_UNCOMPCB(so);
|
|
|
|
/* update pointers */
|
|
|
|
if (pUnComp->pLastReg == pUnComp->pRecvPacket)
|
|
pUnComp->pLastReg = NULL;
|
|
else if (pUnComp->pLastExp == pUnComp->pRecvPacket)
|
|
pUnComp->pLastExp = NULL;
|
|
|
|
pUnComp->pRecvPacket = so->so_rcv.sb_mb;
|
|
|
|
/* only try to permit flow from peer if peer (still) exists */
|
|
|
|
if (pUnComp->pPeerSocket != NULL)
|
|
DSI_SO_W_WAKEUP (pUnComp->pPeerSocket);
|
|
|
|
return (0);
|
|
}
|
|
|
|
/* pru_rcvoob is EOPNOTSUPP */
|
|
|
|
/*******************************************************************************
|
|
*
|
|
* uncomp_send - AF_LOCAL/COMP pru_send routine
|
|
*
|
|
* Send data to peer socket. AF_LOCAL/COMP sockets MUST be connected to
|
|
* pass data.
|
|
*
|
|
* Packets are treated as whole messages and can't be split.
|
|
*
|
|
* RETURNS: 0 on success or an error value
|
|
*
|
|
* POSSIBLE ERROR VALUES:
|
|
* EINVAL
|
|
* ENOTCONN
|
|
* EMSGSIZE
|
|
*
|
|
* NOMANUAL
|
|
*/
|
|
|
|
LOCAL int uncomp_send
|
|
(
|
|
struct socket *so, /* socket to send on */
|
|
int flags, /* send flags */
|
|
struct mbuf *m, /* the message itself */
|
|
struct sockaddr *nam, /* always NULL */
|
|
struct mbuf *control, /* always NULL */
|
|
struct proc *p /* always NULL */
|
|
)
|
|
{
|
|
struct mbuf ** ppExpTail; /* tail of express q */
|
|
struct mbuf ** ppRegTail; /* tail of regular q */
|
|
struct mbuf ** ppRecvPacket; /* next received packet */
|
|
UNCOMPCB * pUnComp = SO_TO_UNCOMPCB(so); /* socket's PCB */
|
|
UNCOMPCB * pPeerUnComp; /* peer socket's PCB */
|
|
int pktSz = 0; /* size of sent packet */
|
|
long clCnt = 0; /* cluster cnt of sent pkt */
|
|
int error = 0;
|
|
|
|
/* is socket an local COMP socket ? */
|
|
|
|
if (pUnComp == NULL)
|
|
{
|
|
netMblkClChainFree (m);
|
|
return EINVAL;
|
|
}
|
|
|
|
/*
|
|
* to send, socket must be connected, i.e. must meet these criteria:
|
|
* 1. must have a peer socket;
|
|
* 2. must be in a connected state, either
|
|
* a) EXCHANGING (both sides can send)
|
|
* or b) DONE_RECV (other side done sending)
|
|
* 3. must NOT have a peer that can't receive.
|
|
*
|
|
* The third condition might seem a bit weird, but the reason is that
|
|
* uncomp_shutdown() is not called by shutdown(0), and thus the COMP
|
|
* state is not updated.
|
|
*/
|
|
|
|
if ((pUnComp->pPeerSocket == NULL) ||
|
|
((pUnComp->selfState != UNCOMPS_EXCHANGING) &&
|
|
(pUnComp->selfState != UNCOMPS_DONE_RECV)) ||
|
|
(pUnComp->pPeerSocket->so_state & SS_CANTRCVMORE) != 0)
|
|
{
|
|
netMblkClChainFree (m);
|
|
|
|
WV_NET_EVENT_4 (NETD_IP4_DATAPATH_EVENT, WV_NETD_CRITICAL,
|
|
1, 1, WV_NETDEVENT_CRITICAL, WV_NETD_SEND,
|
|
uncomp_send, 0, ENOTCONN, so->so_fd)
|
|
|
|
return ENOTCONN;
|
|
}
|
|
|
|
/* obtain size and cluster count of packet */
|
|
|
|
DSI_PKT_SZ (m, pktSz, clCnt);
|
|
|
|
pPeerUnComp = SO_TO_UNCOMPCB(pUnComp->pPeerSocket);
|
|
|
|
/* flow control: loop until there is enough space in peer's recv buffer */
|
|
|
|
do
|
|
{
|
|
/* verify the packet doesn't bust the receiver's limit */
|
|
|
|
if (pUnComp->pPeerSocket->so_rcv.sb_hiwat >=
|
|
(pUnComp->pPeerSocket->so_rcv.sb_cc + pktSz))
|
|
{
|
|
goto enqueuepacket;
|
|
}
|
|
|
|
/* does message even fit in the whole receiver's buffer ? */
|
|
|
|
if (pktSz > pUnComp->pPeerSocket->so_rcv.sb_hiwat)
|
|
{
|
|
error = EMSGSIZE;
|
|
break;
|
|
}
|
|
|
|
if (so->so_state & SS_NBIO) /* non-blocking I/O: don't wait */
|
|
error = EWOULDBLOCK;
|
|
else /* sender wants to wait based on T/O */
|
|
{
|
|
if ((error = dsiSbWait (&so->so_snd)) == 0)
|
|
{
|
|
/*
|
|
* was awakened: must check that the sockets are still
|
|
* transmitting between each other.
|
|
*/
|
|
|
|
if (pUnComp->selfState != UNCOMPS_CLOSED)
|
|
{
|
|
if (!(pUnComp->pPeerSocket->so_state & SS_CANTRCVMORE))
|
|
continue;
|
|
else
|
|
{
|
|
error = EPIPE;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
netMblkClChainFree (m);
|
|
|
|
WV_NET_EVENT_4 (NETD_IP4_DATAPATH_EVENT,
|
|
WV_NETD_CRITICAL,
|
|
1, 2, WV_NETDEVENT_CRITICAL,
|
|
WV_NETD_SEND, uncomp_send, 0,
|
|
EPIPE, so->so_fd)
|
|
return EPIPE;
|
|
}
|
|
}
|
|
}
|
|
} while ((error != EWOULDBLOCK) && (error != EINTR)); /* this error signals a timeout */
|
|
|
|
pPeerUnComp->dropped++; /* update dropped packets stats */
|
|
|
|
netMblkClChainFree (m);
|
|
|
|
WV_NET_EVENT_4 (NETD_IP4_DATAPATH_EVENT, WV_NETD_CRITICAL,
|
|
1, 3, WV_NETDEVENT_CRITICAL, WV_NETD_SEND,
|
|
uncomp_send, 0, error, so->so_fd)
|
|
|
|
return error;
|
|
|
|
enqueuepacket:
|
|
|
|
/* update allocated buffer information in socket structure */
|
|
|
|
DSI_SB_ALLOC (&pUnComp->pPeerSocket->so_rcv, pktSz, clCnt);
|
|
|
|
/* update high watermark statistics on receiving socket */
|
|
|
|
if (pUnComp->pPeerSocket->so_rcv.sb_cc > pPeerUnComp->hiwatermark)
|
|
pPeerUnComp->hiwatermark = (uint16_t)(pUnComp->pPeerSocket->so_rcv.sb_cc);
|
|
|
|
/*
|
|
* if sending an EXP message, put it at the end of the EXP queue;
|
|
* if not, put it at the end of the 'regular message' queue
|
|
*/
|
|
|
|
ppExpTail = &pPeerUnComp->pLastExp;
|
|
ppRegTail = &pPeerUnComp->pLastReg;
|
|
ppRecvPacket = &pPeerUnComp->pRecvPacket;
|
|
|
|
if (!(flags & PRUS_EXP))
|
|
{
|
|
if (*ppRegTail == NULL)
|
|
{
|
|
if (*ppExpTail == NULL) /* msg q was completely empty */
|
|
{
|
|
pUnComp->pPeerSocket->so_rcv.sb_mb = m;
|
|
m->m_nextpkt = NULL;
|
|
*ppRecvPacket = *ppRegTail = m;
|
|
/* DSI_SO_R_WAKEUP_ONE(pUnComp->pPeerSocket); */
|
|
}
|
|
else /* exp msgs present, put m after them */
|
|
{
|
|
(*ppExpTail)->m_nextpkt = m;
|
|
m->m_nextpkt = NULL;
|
|
*ppExpTail = m;
|
|
}
|
|
}
|
|
else /* reg msgs present, put m at tail */
|
|
{
|
|
(*ppRegTail)->m_nextpkt = m;
|
|
m->m_nextpkt = NULL;
|
|
*ppRegTail = m;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (*ppExpTail == NULL) /* q is empty, m is the new head */
|
|
{
|
|
m->m_nextpkt = pUnComp->pPeerSocket->so_rcv.sb_mb;
|
|
pUnComp->pPeerSocket->so_rcv.sb_mb = m;
|
|
*ppRecvPacket = *ppExpTail = m;
|
|
|
|
#if 0
|
|
if (*ppRegTail == NULL)
|
|
{
|
|
DSI_SO_R_WAKEUP_ONE(pUnComp->pPeerSocket);
|
|
}
|
|
#endif /* 0 */
|
|
}
|
|
else /* put m at tail of queue */
|
|
{
|
|
m->m_nextpkt = (*ppExpTail)->m_nextpkt;
|
|
(*ppExpTail)->m_nextpkt = m;
|
|
*ppExpTail = m;
|
|
}
|
|
}
|
|
|
|
/* always call to give one counting semaphore - not just when q was empty */
|
|
DSI_SO_R_WAKEUP_ONE(pUnComp->pPeerSocket);
|
|
return 0;
|
|
}
|
|
|
|
/* pru_sense is EOPNOTSUPP */
|
|
|
|
/*******************************************************************************
|
|
*
|
|
* uncomp_shutdown - AF_LOCAL/COMP pru_shutdown routine.
|
|
*
|
|
* This routine is called when a user does a call to shutdown with the SD_WR
|
|
* option.
|
|
*
|
|
* NOTE : this routine is actually only called to close the sending side
|
|
*
|
|
* RETURNS: 0 on success or an error value
|
|
*
|
|
* POSSIBLE ERROR VALUES:
|
|
* EINVAL
|
|
*
|
|
* NOMANUAL
|
|
*/
|
|
|
|
LOCAL int uncomp_shutdown
|
|
(
|
|
struct socket *so /* socket that is shutting down */
|
|
)
|
|
{
|
|
UNCOMPCB * pUnComp = SO_TO_UNCOMPCB(so);
|
|
|
|
/* is socket a local COMP socket ? */
|
|
|
|
if (pUnComp == NULL)
|
|
return EINVAL;
|
|
|
|
/* socket can't send anymore */
|
|
|
|
dsiSoCantSendMore(so);
|
|
|
|
/* if connected, socket can't send more and peer can't receive more */
|
|
|
|
if (pUnComp->selfState == UNCOMPS_EXCHANGING)
|
|
{
|
|
SO_TO_UNCOMPCB(pUnComp->pPeerSocket)->selfState = UNCOMPS_DONE_RECV;
|
|
pUnComp->selfState = UNCOMPS_DONE_SEND;
|
|
dsiSoCantRcvMore (pUnComp->pPeerSocket);
|
|
}
|
|
else if (pUnComp->selfState == UNCOMPS_DONE_RECV)
|
|
uncomp_disconnect (so);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*******************************************************************************
|
|
*
|
|
* uncomp_sockaddr - AF_LOCAL/COMP pru_sockaddr routine
|
|
*
|
|
* Return the socket's address
|
|
*
|
|
* RETURNS: 0 on success or an error value
|
|
*
|
|
* POSSIBLE ERROR VALUES:
|
|
* EINVAL
|
|
* ENOMEM
|
|
*
|
|
* NOMANUAL
|
|
*/
|
|
|
|
LOCAL int uncomp_sockaddr
|
|
(
|
|
struct socket *so, /* socket from which to get address */
|
|
struct sockaddr **nam /* container for the socket's name */
|
|
)
|
|
{
|
|
UNCOMPCB * pUnComp = SO_TO_UNCOMPCB(so);
|
|
|
|
if (pUnComp == NULL)
|
|
return EINVAL;
|
|
|
|
/* memory allocation done here, caller will free memory */
|
|
|
|
*nam = (struct sockaddr *) uncomp_dupsockaddr(pUnComp);
|
|
|
|
if (*nam == NULL)
|
|
return ENOBUFS;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* XXX : below this point is kept for reference only (work-in-progress) */
|
|
|
|
int
|
|
uncomp_ctloutput(so, sopt)
|
|
struct socket *so;
|
|
struct sockopt *sopt;
|
|
{
|
|
int error = 0;
|
|
#if UNCOMP_USE_OLD_BSD_STUFF
|
|
struct unpcb *unp = sotounpcb(so);
|
|
|
|
switch (sopt->sopt_dir) {
|
|
case SOPT_GET:
|
|
switch (sopt->sopt_name) {
|
|
case LOCAL_PEERCRED:
|
|
if (unp->unp_flags & UNP_HAVEPC)
|
|
error = sooptcopyout(sopt, &unp->unp_peercred,
|
|
sizeof(unp->unp_peercred));
|
|
else {
|
|
if (so->so_type == SOCK_STREAM)
|
|
error = ENOTCONN;
|
|
else
|
|
error = EINVAL;
|
|
}
|
|
break;
|
|
default:
|
|
error = EOPNOTSUPP;
|
|
break;
|
|
}
|
|
break;
|
|
case SOPT_SET:
|
|
default:
|
|
error = EOPNOTSUPP;
|
|
break;
|
|
}
|
|
#endif /* UNCOMP_USE_OLD_BSD_STUFF */
|
|
return (error);
|
|
}
|
|
|
|
#if 0
|
|
#if UNCOMP_USE_OLD_BSD_STUFF
|
|
/*
|
|
* Both send and receive buffers are allocated PIPSIZ bytes of buffering
|
|
* for stream sockets, although the total for sender and receiver is
|
|
* actually only PIPSIZ.
|
|
* Datagram sockets really use the sendspace as the maximum datagram size,
|
|
* and don't really want to reserve the sendspace. Their recvspace should
|
|
* be large enough for at least one max-size datagram plus address.
|
|
*/
|
|
#ifndef PIPSIZ
|
|
#define PIPSIZ 8192
|
|
#endif /* PIPSIZ */
|
|
static u_long unpst_sendspace = PIPSIZ;
|
|
static u_long unpst_recvspace = PIPSIZ;
|
|
static u_long unpdg_sendspace = 2*1024; /* really max datagram size */
|
|
static u_long unpdg_recvspace = 4*1024;
|
|
|
|
static int unp_rights; /* file descriptors in flight */
|
|
|
|
SYSCTL_DECL(_net_local_stream);
|
|
SYSCTL_INT(_net_local_stream, OID_AUTO, sendspace, CTLFLAG_RW,
|
|
&unpst_sendspace, 0, "");
|
|
SYSCTL_INT(_net_local_stream, OID_AUTO, recvspace, CTLFLAG_RW,
|
|
&unpst_recvspace, 0, "");
|
|
SYSCTL_DECL(_net_local_dgram);
|
|
SYSCTL_INT(_net_local_dgram, OID_AUTO, maxdgram, CTLFLAG_RW,
|
|
&unpdg_sendspace, 0, "");
|
|
SYSCTL_INT(_net_local_dgram, OID_AUTO, recvspace, CTLFLAG_RW,
|
|
&unpdg_recvspace, 0, "");
|
|
SYSCTL_DECL(_net_local);
|
|
SYSCTL_INT(_net_local, OID_AUTO, inflight, CTLFLAG_RD, &unp_rights, 0, "");
|
|
#endif /* UNCOMP_USE_OLD_BSD_STUFF */
|
|
|
|
static int
|
|
prison_unpcb(struct proc *p, struct uncompcb *unp)
|
|
{
|
|
#if UNCOMP_USE_OLD_BSD_STUFF
|
|
if (!p->p_prison)
|
|
return (0);
|
|
if (p->p_fd->fd_rdir == unp->unp_rvnode)
|
|
return (0);
|
|
#endif /* UNCOMP_USE_OLD_BSD_STUFF */
|
|
return (1);
|
|
}
|
|
|
|
static int
|
|
unp_pcblist(SYSCTL_HANDLER_ARGS)
|
|
{
|
|
#if UNCOMP_USE_OLD_BSD_STUFF
|
|
int error, i, n;
|
|
struct unpcb *unp, **unp_list;
|
|
unp_gen_t gencnt;
|
|
struct xunpgen xug;
|
|
struct unp_head *head;
|
|
|
|
head = ((intptr_t)arg1 == SOCK_DGRAM ? &unp_dhead : &unp_shead);
|
|
|
|
/*
|
|
* The process of preparing the PCB list is too time-consuming and
|
|
* resource-intensive to repeat twice on every request.
|
|
*/
|
|
if (req->oldptr == 0) {
|
|
n = unp_count;
|
|
req->oldidx = 2 * (sizeof xug)
|
|
+ (n + n/8) * sizeof(struct xunpcb);
|
|
return 0;
|
|
}
|
|
|
|
if (req->newptr != 0)
|
|
return EPERM;
|
|
|
|
/*
|
|
* OK, now we're committed to doing something.
|
|
*/
|
|
gencnt = unp_gencnt;
|
|
n = unp_count;
|
|
|
|
xug.xug_len = sizeof xug;
|
|
xug.xug_count = n;
|
|
xug.xug_gen = gencnt;
|
|
xug.xug_sogen = so_gencnt;
|
|
error = SYSCTL_OUT(req, &xug, sizeof xug);
|
|
if (error)
|
|
return error;
|
|
|
|
unp_list = malloc(n * sizeof *unp_list, M_TEMP, M_WAITOK);
|
|
if (unp_list == 0)
|
|
return ENOMEM;
|
|
|
|
for (unp = LIST_FIRST(head), i = 0; unp && i < n;
|
|
unp = LIST_NEXT(unp, unp_link)) {
|
|
if (unp->unp_gencnt <= gencnt && !prison_unpcb(req->p, unp))
|
|
unp_list[i++] = unp;
|
|
}
|
|
n = i; /* in case we lost some during malloc */
|
|
|
|
error = 0;
|
|
for (i = 0; i < n; i++) {
|
|
unp = unp_list[i];
|
|
if (unp->unp_gencnt <= gencnt) {
|
|
struct xunpcb xu;
|
|
xu.xu_len = sizeof xu;
|
|
xu.xu_unpp = unp;
|
|
/*
|
|
* XXX - need more locking here to protect against
|
|
* connect/disconnect races for SMP.
|
|
*/
|
|
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) {
|
|
/*
|
|
* Give the user an updated idea of our state.
|
|
* If the generation differs from what we told
|
|
* her before, she knows that something happened
|
|
* while we were processing this request, and it
|
|
* might be necessary to retry.
|
|
*/
|
|
xug.xug_gen = unp_gencnt;
|
|
xug.xug_sogen = so_gencnt;
|
|
xug.xug_count = unp_count;
|
|
error = SYSCTL_OUT(req, &xug, sizeof xug);
|
|
}
|
|
free(unp_list, M_TEMP);
|
|
#endif /* UNCOMP_USE_OLD_BSD_STUFF */
|
|
|
|
int error = 0;
|
|
return error;
|
|
}
|
|
|
|
#if UNCOMP_USE_OLD_BSD_STUFF
|
|
SYSCTL_PROC(_net_local_dgram, OID_AUTO, pcblist, CTLFLAG_RD,
|
|
(caddr_t)(long)SOCK_DGRAM, 0, unp_pcblist, "S,xunpcb",
|
|
"List of active local datagram sockets");
|
|
SYSCTL_PROC(_net_local_stream, OID_AUTO, pcblist, CTLFLAG_RD,
|
|
(caddr_t)(long)SOCK_STREAM, 0, unp_pcblist, "S,xunpcb",
|
|
"List of active local stream sockets");
|
|
#endif /* UNCOMP_USE_OLD_BSD_STUFF */
|
|
|
|
int
|
|
unp_externalize(rights)
|
|
struct mbuf *rights;
|
|
{
|
|
#if UNCOMP_USE_OLD_BSD_STUFF
|
|
struct proc *p = curproc; /* XXX */
|
|
register int i;
|
|
register struct cmsghdr *cm = mtod(rights, struct cmsghdr *);
|
|
register int *fdp;
|
|
register struct file **rp;
|
|
register struct file *fp;
|
|
int newfds = (cm->cmsg_len - (CMSG_DATA(cm) - (u_char *)cm))
|
|
/ sizeof (struct file *);
|
|
int f;
|
|
|
|
/*
|
|
* if the new FD's will not fit, then we free them all
|
|
*/
|
|
if (!fdavail(p, newfds)) {
|
|
rp = (struct file **)CMSG_DATA(cm);
|
|
for (i = 0; i < newfds; i++) {
|
|
fp = *rp;
|
|
/*
|
|
* zero the pointer before calling unp_discard,
|
|
* since it may end up in unp_gc()..
|
|
*/
|
|
*rp++ = 0;
|
|
unp_discard(fp);
|
|
}
|
|
return (EMSGSIZE);
|
|
}
|
|
/*
|
|
* now change each pointer to an fd in the global table to
|
|
* an integer that is the index to the local fd table entry
|
|
* that we set up to point to the global one we are transferring.
|
|
* If sizeof (struct file *) is bigger than or equal to sizeof int,
|
|
* then do it in forward order. In that case, an integer will
|
|
* always come in the same place or before its corresponding
|
|
* struct file pointer.
|
|
* If sizeof (struct file *) is smaller than sizeof int, then
|
|
* do it in reverse order.
|
|
*/
|
|
if (sizeof (struct file *) >= sizeof (int)) {
|
|
fdp = (int *)(cm + 1);
|
|
rp = (struct file **)CMSG_DATA(cm);
|
|
for (i = 0; i < newfds; i++) {
|
|
if (fdalloc(p, 0, &f))
|
|
panic("unp_externalize");
|
|
fp = *rp++;
|
|
p->p_fd->fd_ofiles[f] = fp;
|
|
fp->f_msgcount--;
|
|
unp_rights--;
|
|
*fdp++ = f;
|
|
}
|
|
} else {
|
|
fdp = (int *)(cm + 1) + newfds - 1;
|
|
rp = (struct file **)CMSG_DATA(cm) + newfds - 1;
|
|
for (i = 0; i < newfds; i++) {
|
|
if (fdalloc(p, 0, &f))
|
|
panic("unp_externalize");
|
|
fp = *rp--;
|
|
p->p_fd->fd_ofiles[f] = fp;
|
|
fp->f_msgcount--;
|
|
unp_rights--;
|
|
*fdp-- = f;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Adjust length, in case sizeof(struct file *) and sizeof(int)
|
|
* differs.
|
|
*/
|
|
cm->cmsg_len = CMSG_LEN(newfds * sizeof(int));
|
|
rights->m_len = cm->cmsg_len;
|
|
#endif /* UNCOMP_USE_OLD_BSD_STUFF */
|
|
return (0);
|
|
}
|
|
|
|
void
|
|
unp_init(void)
|
|
{
|
|
#if UNCOMP_USE_OLD_BSD_STUFF
|
|
unp_zone = zinit("unpcb", sizeof(struct unpcb), nmbclusters, 0, 0);
|
|
if (unp_zone == 0)
|
|
panic("unp_init");
|
|
LIST_INIT(&unp_dhead);
|
|
LIST_INIT(&unp_shead);
|
|
#endif /* UNCOMP_USE_OLD_BSD_STUFF */
|
|
}
|
|
|
|
#ifndef MIN
|
|
#define MIN(a,b) (((a)<(b))?(a):(b))
|
|
#endif /* MIN */
|
|
|
|
static int
|
|
unp_internalize(control, p)
|
|
struct mbuf *control;
|
|
struct proc *p;
|
|
{
|
|
#if UNCOMP_USE_OLD_BSD_STUFF
|
|
struct filedesc *fdescp = p->p_fd;
|
|
register struct cmsghdr *cm = mtod(control, struct cmsghdr *);
|
|
register struct file **rp;
|
|
register struct file *fp;
|
|
register int i, fd, *fdp;
|
|
register struct cmsgcred *cmcred;
|
|
int oldfds;
|
|
u_int newlen;
|
|
|
|
if ((cm->cmsg_type != SCM_RIGHTS && cm->cmsg_type != SCM_CREDS) ||
|
|
cm->cmsg_level != SOL_SOCKET || cm->cmsg_len != control->m_len)
|
|
return (EINVAL);
|
|
|
|
/*
|
|
* Fill in credential information.
|
|
*/
|
|
if (cm->cmsg_type == SCM_CREDS) {
|
|
cmcred = (struct cmsgcred *)(cm + 1);
|
|
cmcred->cmcred_pid = p->p_pid;
|
|
cmcred->cmcred_uid = p->p_cred->p_ruid;
|
|
cmcred->cmcred_gid = p->p_cred->p_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);
|
|
}
|
|
|
|
oldfds = (cm->cmsg_len - sizeof (*cm)) / sizeof (int);
|
|
/*
|
|
* check that all the FDs passed in refer to legal OPEN files
|
|
* If not, reject the entire operation.
|
|
*/
|
|
fdp = (int *)(cm + 1);
|
|
for (i = 0; i < oldfds; i++) {
|
|
fd = *fdp++;
|
|
if ((unsigned)fd >= fdescp->fd_nfiles ||
|
|
fdescp->fd_ofiles[fd] == NULL)
|
|
return (EBADF);
|
|
if (fdescp->fd_ofiles[fd]->f_type == DTYPE_KQUEUE)
|
|
return (EOPNOTSUPP);
|
|
}
|
|
/*
|
|
* Now replace the integer FDs with pointers to
|
|
* the associated global file table entry..
|
|
* Allocate a bigger buffer as necessary. But if an cluster is not
|
|
* enough, return E2BIG.
|
|
*/
|
|
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_WAIT);
|
|
if ((control->m_flags & M_EXT) == 0)
|
|
return (ENOBUFS);
|
|
|
|
/* copy the data to the cluster */
|
|
memcpy(mtod(control, char *), cm, cm->cmsg_len);
|
|
cm = mtod(control, struct cmsghdr *);
|
|
}
|
|
|
|
/*
|
|
* Adjust length, in case sizeof(struct file *) and sizeof(int)
|
|
* differs.
|
|
*/
|
|
control->m_len = cm->cmsg_len = newlen;
|
|
|
|
/*
|
|
* Transform the file descriptors into struct file pointers.
|
|
* If sizeof (struct file *) is bigger than or equal to sizeof int,
|
|
* then do it in reverse order so that the int won't get until
|
|
* we're done.
|
|
* If sizeof (struct file *) is smaller than sizeof int, then
|
|
* do it in forward order.
|
|
*/
|
|
if (sizeof (struct file *) >= sizeof (int)) {
|
|
fdp = (int *)(cm + 1) + oldfds - 1;
|
|
rp = (struct file **)CMSG_DATA(cm) + oldfds - 1;
|
|
for (i = 0; i < oldfds; i++) {
|
|
fp = fdescp->fd_ofiles[*fdp--];
|
|
*rp-- = fp;
|
|
fp->f_count++;
|
|
fp->f_msgcount++;
|
|
unp_rights++;
|
|
}
|
|
} else {
|
|
fdp = (int *)(cm + 1);
|
|
rp = (struct file **)CMSG_DATA(cm);
|
|
for (i = 0; i < oldfds; i++) {
|
|
fp = fdescp->fd_ofiles[*fdp++];
|
|
*rp++ = fp;
|
|
fp->f_count++;
|
|
fp->f_msgcount++;
|
|
unp_rights++;
|
|
}
|
|
}
|
|
#endif /* UNCOMP_USE_OLD_BSD_STUFF */
|
|
return (0);
|
|
}
|
|
|
|
static int unp_defer, unp_gcing;
|
|
|
|
static void
|
|
unp_gc()
|
|
{
|
|
#if UNCOMP_USE_OLD_BSD_STUFF
|
|
register struct file *fp, *nextfp;
|
|
register struct socket *so;
|
|
struct file **extra_ref, **fpp;
|
|
int nunref, i;
|
|
|
|
if (unp_gcing)
|
|
return;
|
|
unp_gcing = 1;
|
|
unp_defer = 0;
|
|
/*
|
|
* before going through all this, set all FDs to
|
|
* be NOT defered and NOT externally accessible
|
|
*/
|
|
LIST_FOREACH(fp, &filehead, f_list)
|
|
fp->f_flag &= ~(FMARK|FDEFER);
|
|
do {
|
|
LIST_FOREACH(fp, &filehead, f_list) {
|
|
/*
|
|
* If the file is not open, skip it
|
|
*/
|
|
if (fp->f_count == 0)
|
|
continue;
|
|
/*
|
|
* If we already marked it as 'defer' in a
|
|
* previous pass, then try process it this time
|
|
* and un-mark it
|
|
*/
|
|
if (fp->f_flag & FDEFER) {
|
|
fp->f_flag &= ~FDEFER;
|
|
unp_defer--;
|
|
} else {
|
|
/*
|
|
* if it's not defered, then check if it's
|
|
* already marked.. if so skip it
|
|
*/
|
|
if (fp->f_flag & FMARK)
|
|
continue;
|
|
/*
|
|
* If all references are from messages
|
|
* in transit, then skip it. it's not
|
|
* externally accessible.
|
|
*/
|
|
if (fp->f_count == fp->f_msgcount)
|
|
continue;
|
|
/*
|
|
* If it got this far then it must be
|
|
* externally accessible.
|
|
*/
|
|
fp->f_flag |= FMARK;
|
|
}
|
|
/*
|
|
* either it was defered, or it is externally
|
|
* accessible and not already marked so.
|
|
* Now check if it is possibly one of OUR sockets.
|
|
*/
|
|
if (fp->f_type != DTYPE_SOCKET ||
|
|
(so = (struct socket *)fp->f_data) == 0)
|
|
continue;
|
|
if (so->so_proto->pr_domain != &localdomain ||
|
|
(so->so_proto->pr_flags&PR_RIGHTS) == 0)
|
|
continue;
|
|
#ifdef notdef
|
|
if (so->so_rcv.sb_flags & SB_LOCK) {
|
|
/*
|
|
* This is problematical; it's not clear
|
|
* we need to wait for the sockbuf to be
|
|
* unlocked (on a uniprocessor, at least),
|
|
* and it's also not clear what to do
|
|
* if sbwait returns an error due to receipt
|
|
* of a signal. If sbwait does return
|
|
* an error, we'll go into an infinite
|
|
* loop. Delete all of this for now.
|
|
*/
|
|
(void) sbwait(&so->so_rcv);
|
|
goto restart;
|
|
}
|
|
#endif /* notdef */
|
|
/*
|
|
* So, Ok, it's one of our sockets and it IS externally
|
|
* accessible (or was defered). Now we look
|
|
* to see if we hold any file descriptors in its
|
|
* message buffers. Follow those links and mark them
|
|
* as accessible too.
|
|
*/
|
|
unp_scan(so->so_rcv.sb_mb, unp_mark);
|
|
}
|
|
} while (unp_defer);
|
|
/*
|
|
* We grab an extra reference to each of the file table entries
|
|
* that are not otherwise accessible and then free the rights
|
|
* that are stored in messages on them.
|
|
*
|
|
* The bug in the orginal code is a little tricky, so I'll describe
|
|
* what's wrong with it here.
|
|
*
|
|
* It is incorrect to simply unp_discard each entry for f_msgcount
|
|
* times -- consider the case of sockets A and B that contain
|
|
* references to each other. On a last close of some other socket,
|
|
* we trigger a gc since the number of outstanding rights (unp_rights)
|
|
* is non-zero. If during the sweep phase the gc code un_discards,
|
|
* we end up doing a (full) closef on the descriptor. A closef on A
|
|
* results in the following chain. Closef calls soo_close, which
|
|
* calls soclose. Soclose calls first (through the switch
|
|
* uncomp_usrreq) unp_detach, which re-invokes unp_gc. Unp_gc simply
|
|
* returns because the previous instance had set unp_gcing, and
|
|
* we return all the way back to soclose, which marks the socket
|
|
* with SS_NOFDREF, and then calls sofree. Sofree calls sorflush
|
|
* to free up the rights that are queued in messages on the socket A,
|
|
* i.e., the reference on B. The sorflush calls via the dom_dispose
|
|
* switch unp_dispose, which unp_scans with unp_discard. This second
|
|
* instance of unp_discard just calls closef on B.
|
|
*
|
|
* Well, a similar chain occurs on B, resulting in a sorflush on B,
|
|
* which results in another closef on A. Unfortunately, A is already
|
|
* being closed, and the descriptor has already been marked with
|
|
* SS_NOFDREF, and soclose panics at this point.
|
|
*
|
|
* Here, we first take an extra reference to each inaccessible
|
|
* descriptor. Then, we call sorflush ourself, since we know
|
|
* it is a Unix domain socket anyhow. After we destroy all the
|
|
* rights carried in messages, we do a last closef to get rid
|
|
* of our extra reference. This is the last close, and the
|
|
* unp_detach etc will shut down the socket.
|
|
*
|
|
*/
|
|
extra_ref = malloc(nfiles * sizeof(struct file *), M_FILE, M_WAITOK);
|
|
for (nunref = 0, fp = LIST_FIRST(&filehead), fpp = extra_ref; fp != 0;
|
|
fp = nextfp) {
|
|
nextfp = LIST_NEXT(fp, f_list);
|
|
/*
|
|
* If it's not open, skip it
|
|
*/
|
|
if (fp->f_count == 0)
|
|
continue;
|
|
/*
|
|
* If all refs are from msgs, and it's not marked accessible
|
|
* then it must be referenced from some unreachable cycle
|
|
* of (shut-down) FDs, so include it in our
|
|
* list of FDs to remove
|
|
*/
|
|
if (fp->f_count == fp->f_msgcount && !(fp->f_flag & FMARK)) {
|
|
*fpp++ = fp;
|
|
nunref++;
|
|
fp->f_count++;
|
|
}
|
|
}
|
|
/*
|
|
* for each FD on our hit list, do the following two things
|
|
*/
|
|
for (i = nunref, fpp = 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 = nunref, fpp = extra_ref; --i >= 0; ++fpp)
|
|
closef(*fpp, (struct proc *) NULL);
|
|
free((caddr_t)extra_ref, M_FILE);
|
|
unp_gcing = 0;
|
|
#endif /* UNCOMP_USE_OLD_BSD_STUFF */
|
|
}
|
|
|
|
void
|
|
unp_dispose(m)
|
|
struct mbuf *m;
|
|
{
|
|
#if UNCOMP_USE_OLD_BSD_STUFF
|
|
|
|
if (m)
|
|
unp_scan(m, unp_discard);
|
|
#endif /* UNCOMP_USE_OLD_BSD_STUFF */
|
|
}
|
|
|
|
static void
|
|
unp_scan(m0, op)
|
|
register struct mbuf *m0;
|
|
void (*op) __P((struct file *));
|
|
{
|
|
#if UNCOMP_USE_OLD_BSD_STUFF
|
|
register struct mbuf *m;
|
|
register struct file **rp;
|
|
register struct cmsghdr *cm;
|
|
register 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_DATA(cm) - (u_char *)cm))
|
|
/ sizeof (struct file *);
|
|
rp = (struct file **)CMSG_DATA(cm);
|
|
for (i = 0; i < qfds; i++)
|
|
(*op)(*rp++);
|
|
break; /* XXX, but saves time */
|
|
}
|
|
m0 = m0->m_act;
|
|
}
|
|
#endif /* UNCOMP_USE_OLD_BSD_STUFF */
|
|
}
|
|
|
|
static void
|
|
unp_mark(fp)
|
|
struct file *fp;
|
|
{
|
|
#if UNCOMP_USE_OLD_BSD_STUFF
|
|
|
|
if (fp->f_flag & FMARK)
|
|
return;
|
|
unp_defer++;
|
|
fp->f_flag |= (FMARK|FDEFER);
|
|
#endif /* UNCOMP_USE_OLD_BSD_STUFF */
|
|
}
|
|
|
|
static void
|
|
unp_discard(fp)
|
|
struct file *fp;
|
|
{
|
|
#if UNCOMP_USE_OLD_BSD_STUFF
|
|
|
|
fp->f_msgcount--;
|
|
unp_rights--;
|
|
(void) closef(fp, (struct proc *)NULL);
|
|
#endif /* UNCOMP_USE_OLD_BSD_STUFF */
|
|
}
|
|
#endif /* if 0 */
|