mirror of
https://git.FreeBSD.org/src.git
synced 2024-12-22 11:17:19 +00:00
b40ce4165d
Note ALL MODULES MUST BE RECOMPILED make the kernel aware that there are smaller units of scheduling than the process. (but only allow one thread per process at this time). This is functionally equivalent to teh previousl -current except that there is a thread associated with each process. Sorry john! (your next MFC will be a doosie!) Reviewed by: peter@freebsd.org, dillon@freebsd.org X-MFC after: ha ha ha ha
735 lines
14 KiB
C
735 lines
14 KiB
C
/*
|
|
*
|
|
* ===================================
|
|
* HARP | Host ATM Research Platform
|
|
* ===================================
|
|
*
|
|
*
|
|
* This Host ATM Research Platform ("HARP") file (the "Software") is
|
|
* made available by Network Computing Services, Inc. ("NetworkCS")
|
|
* "AS IS". NetworkCS does not provide maintenance, improvements or
|
|
* support of any kind.
|
|
*
|
|
* NETWORKCS MAKES NO WARRANTIES OR REPRESENTATIONS, EXPRESS OR IMPLIED,
|
|
* INCLUDING, BUT NOT LIMITED TO, IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
* AND FITNESS FOR A PARTICULAR PURPOSE, AS TO ANY ELEMENT OF THE
|
|
* SOFTWARE OR ANY SUPPORT PROVIDED IN CONNECTION WITH THIS SOFTWARE.
|
|
* In no event shall NetworkCS be responsible for any damages, including
|
|
* but not limited to consequential damages, arising from or relating to
|
|
* any use of the Software or related support.
|
|
*
|
|
* Copyright 1994-1998 Network Computing Services, Inc.
|
|
*
|
|
* Copies of this Software may be made, however, the above copyright
|
|
* notice must be reproduced on all copies.
|
|
*
|
|
* @(#) $FreeBSD$
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* Core ATM Services
|
|
* -----------------
|
|
*
|
|
* ATM DGRAM socket protocol processing
|
|
*
|
|
*/
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/sockio.h>
|
|
#include <sys/protosw.h>
|
|
#include <sys/socket.h>
|
|
#include <net/if.h>
|
|
#include <netatm/port.h>
|
|
#include <netatm/queue.h>
|
|
#include <netatm/atm.h>
|
|
#include <netatm/atm_sys.h>
|
|
#include <netatm/atm_sap.h>
|
|
#include <netatm/atm_cm.h>
|
|
#include <netatm/atm_if.h>
|
|
#include <netatm/atm_ioctl.h>
|
|
#include <netatm/atm_sigmgr.h>
|
|
#include <netatm/atm_stack.h>
|
|
#include <netatm/atm_pcb.h>
|
|
#include <netatm/atm_var.h>
|
|
|
|
#ifndef lint
|
|
__RCSID("@(#) $FreeBSD$");
|
|
#endif
|
|
|
|
|
|
/*
|
|
* Local functions
|
|
*/
|
|
static int atm_dgram_attach __P((struct socket *, int, struct thread *));
|
|
static int atm_dgram_control __P((struct socket *, u_long, caddr_t,
|
|
struct ifnet *, struct thread *));
|
|
static int atm_dgram_info __P((caddr_t));
|
|
|
|
|
|
/*
|
|
* New-style socket request routines
|
|
*/
|
|
#if (defined(__FreeBSD__) && (BSD >= 199506))
|
|
struct pr_usrreqs atm_dgram_usrreqs = {
|
|
atm_proto_notsupp1, /* pru_abort */
|
|
pru_accept_notsupp, /* pru_accept */
|
|
atm_dgram_attach, /* pru_attach */
|
|
atm_proto_notsupp2, /* pru_bind */
|
|
pru_connect_notsupp, /* pru_connect */
|
|
pru_connect2_notsupp, /* pru_connect2 */
|
|
atm_dgram_control, /* pru_control */
|
|
atm_proto_notsupp1, /* pru_detach */
|
|
atm_proto_notsupp1, /* pru_disconnect */
|
|
pru_listen_notsupp, /* pru_listen */
|
|
atm_proto_notsupp3, /* pru_peeraddr */
|
|
pru_rcvd_notsupp, /* pru_rcvd */
|
|
pru_rcvoob_notsupp, /* pru_rcvoob */
|
|
atm_proto_notsupp4, /* pru_send */
|
|
pru_sense_null, /* pru_sense */
|
|
atm_proto_notsupp1, /* pru_shutdown */
|
|
atm_proto_notsupp3, /* pru_sockaddr */
|
|
};
|
|
#endif
|
|
|
|
|
|
/*
|
|
* Handy common code macros
|
|
*/
|
|
#ifdef DIAGNOSTIC
|
|
#define ATM_INTRO() \
|
|
int s, err = 0; \
|
|
s = splnet(); \
|
|
/* \
|
|
* Stack queue should have been drained \
|
|
*/ \
|
|
if (atm_stackq_head != NULL) \
|
|
panic("atm_usrreq: stack queue not empty"); \
|
|
;
|
|
#else
|
|
#define ATM_INTRO() \
|
|
int s, err = 0; \
|
|
s = splnet(); \
|
|
;
|
|
#endif
|
|
|
|
#define ATM_OUTRO() \
|
|
/* \
|
|
* Drain any deferred calls \
|
|
*/ \
|
|
STACK_DRAIN(); \
|
|
(void) splx(s); \
|
|
return (err); \
|
|
;
|
|
|
|
#define ATM_RETERR(errno) { \
|
|
err = errno; \
|
|
goto out; \
|
|
}
|
|
|
|
|
|
/*
|
|
* Attach protocol to socket
|
|
*
|
|
* Arguments:
|
|
* so pointer to socket
|
|
* proto protocol identifier
|
|
* p pointer to process
|
|
*
|
|
* Returns:
|
|
* 0 request processed
|
|
* errno error processing request - reason indicated
|
|
*
|
|
*/
|
|
static int
|
|
atm_dgram_attach(so, proto, td)
|
|
struct socket *so;
|
|
int proto;
|
|
struct thread *td;
|
|
{
|
|
ATM_INTRO();
|
|
|
|
/*
|
|
* Nothing to do here for ioctl()-only sockets
|
|
*/
|
|
ATM_OUTRO();
|
|
}
|
|
|
|
|
|
/*
|
|
* Process ioctl system calls
|
|
*
|
|
* Arguments:
|
|
* so pointer to socket
|
|
* cmd ioctl code
|
|
* data pointer to code specific parameter data area
|
|
* ifp pointer to ifnet structure if it's an interface ioctl
|
|
* p pointer to process
|
|
*
|
|
* Returns:
|
|
* 0 request processed
|
|
* errno error processing request - reason indicated
|
|
*
|
|
*/
|
|
static int
|
|
atm_dgram_control(so, cmd, data, ifp, td)
|
|
struct socket *so;
|
|
u_long cmd;
|
|
caddr_t data;
|
|
struct ifnet *ifp;
|
|
struct thread *td;
|
|
{
|
|
ATM_INTRO();
|
|
|
|
/*
|
|
* First, figure out which ioctl we're dealing with and
|
|
* then process it based on the sub-op code
|
|
*/
|
|
switch (cmd) {
|
|
|
|
case AIOCCFG: {
|
|
struct atmcfgreq *acp = (struct atmcfgreq *)data;
|
|
struct atm_pif *pip;
|
|
|
|
if (td && (suser_td(td) != 0))
|
|
ATM_RETERR(EPERM);
|
|
|
|
switch (acp->acr_opcode) {
|
|
|
|
case AIOCS_CFG_ATT:
|
|
/*
|
|
* Attach signalling manager
|
|
*/
|
|
if ((pip = atm_pifname(acp->acr_att_intf)) == NULL)
|
|
ATM_RETERR(ENXIO);
|
|
err = atm_sigmgr_attach(pip, acp->acr_att_proto);
|
|
break;
|
|
|
|
case AIOCS_CFG_DET:
|
|
/*
|
|
* Detach signalling manager
|
|
*/
|
|
if ((pip = atm_pifname(acp->acr_det_intf)) == NULL)
|
|
ATM_RETERR(ENXIO);
|
|
err = atm_sigmgr_detach(pip);
|
|
break;
|
|
|
|
default:
|
|
err = EOPNOTSUPP;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case AIOCADD: {
|
|
struct atmaddreq *aap = (struct atmaddreq *)data;
|
|
Atm_endpoint *epp;
|
|
|
|
if (td && (suser_td(td) != 0))
|
|
ATM_RETERR(EPERM);
|
|
|
|
switch (aap->aar_opcode) {
|
|
|
|
case AIOCS_ADD_PVC:
|
|
/*
|
|
* Add a PVC definition
|
|
*/
|
|
|
|
/*
|
|
* Locate requested endpoint service
|
|
*/
|
|
epp = aap->aar_pvc_sap > ENDPT_MAX ? NULL :
|
|
atm_endpoints[aap->aar_pvc_sap];
|
|
if (epp == NULL)
|
|
ATM_RETERR(ENOPROTOOPT);
|
|
|
|
/*
|
|
* Let endpoint service handle it from here
|
|
*/
|
|
err = (*epp->ep_ioctl)(AIOCS_ADD_PVC, data, NULL);
|
|
break;
|
|
|
|
case AIOCS_ADD_ARP:
|
|
/*
|
|
* Add an ARP mapping
|
|
*/
|
|
epp = atm_endpoints[ENDPT_IP];
|
|
if (epp == NULL)
|
|
ATM_RETERR(ENOPROTOOPT);
|
|
|
|
/*
|
|
* Let IP/ATM endpoint handle this
|
|
*/
|
|
err = (*epp->ep_ioctl) (AIOCS_ADD_ARP, data, NULL);
|
|
break;
|
|
|
|
default:
|
|
err = EOPNOTSUPP;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case AIOCDEL: {
|
|
struct atmdelreq *adp = (struct atmdelreq *)data;
|
|
struct atm_pif *pip;
|
|
struct sigmgr *smp;
|
|
Atm_endpoint *epp;
|
|
|
|
if (td && (suser_td(td) != 0))
|
|
ATM_RETERR(EPERM);
|
|
|
|
switch (adp->adr_opcode) {
|
|
|
|
case AIOCS_DEL_PVC:
|
|
case AIOCS_DEL_SVC:
|
|
/*
|
|
* Delete a PVC or SVC
|
|
*/
|
|
|
|
/*
|
|
* Locate appropriate sigmgr
|
|
*/
|
|
if ((pip = atm_pifname(adp->adr_pvc_intf)) == NULL)
|
|
ATM_RETERR(ENXIO);
|
|
if ((smp = pip->pif_sigmgr) == NULL)
|
|
ATM_RETERR(ENOENT);
|
|
|
|
/*
|
|
* Let sigmgr handle it from here
|
|
*/
|
|
err = (*smp->sm_ioctl)(adp->adr_opcode, data,
|
|
(caddr_t)pip->pif_siginst);
|
|
break;
|
|
|
|
case AIOCS_DEL_ARP:
|
|
/*
|
|
* Delete an ARP mapping
|
|
*/
|
|
epp = atm_endpoints[ENDPT_IP];
|
|
if (epp == NULL)
|
|
ATM_RETERR(ENOPROTOOPT);
|
|
|
|
/*
|
|
* Let IP/ATM endpoint handle this
|
|
*/
|
|
err = (*epp->ep_ioctl) (AIOCS_DEL_ARP, data, NULL);
|
|
break;
|
|
|
|
default:
|
|
err = EOPNOTSUPP;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case AIOCSET: {
|
|
struct atmsetreq *asp = (struct atmsetreq *)data;
|
|
struct atm_pif *pip;
|
|
struct atm_nif *nip;
|
|
struct sigmgr *smp;
|
|
struct ifnet *ifp2;
|
|
|
|
if (td && (suser_td(td) != 0))
|
|
ATM_RETERR(EPERM);
|
|
|
|
switch (asp->asr_opcode) {
|
|
|
|
case AIOCS_SET_ASV:
|
|
/*
|
|
* Set an ARP server address
|
|
*/
|
|
|
|
/*
|
|
* Locate appropriate sigmgr
|
|
*/
|
|
if ((nip = atm_nifname(asp->asr_arp_intf)) == NULL)
|
|
ATM_RETERR(ENXIO);
|
|
pip = nip->nif_pif;
|
|
if ((smp = pip->pif_sigmgr) == NULL)
|
|
ATM_RETERR(ENOENT);
|
|
|
|
/*
|
|
* Let sigmgr handle it from here
|
|
*/
|
|
err = (*smp->sm_ioctl)(AIOCS_SET_ASV, data,
|
|
(caddr_t)nip);
|
|
break;
|
|
|
|
case AIOCS_SET_MAC:
|
|
/*
|
|
* Set physical interface MAC/ESI address
|
|
*/
|
|
|
|
/*
|
|
* Locate physical interface
|
|
*/
|
|
if ((pip = atm_pifname(asp->asr_mac_intf)) == NULL)
|
|
ATM_RETERR(ENXIO);
|
|
|
|
/*
|
|
* Interface must be detached
|
|
*/
|
|
if (pip->pif_sigmgr != NULL)
|
|
ATM_RETERR(EADDRINUSE);
|
|
|
|
/*
|
|
* Just plunk the address into the pif
|
|
*/
|
|
KM_COPY((caddr_t)&asp->asr_mac_addr,
|
|
(caddr_t)&pip->pif_macaddr,
|
|
sizeof(struct mac_addr));
|
|
break;
|
|
|
|
case AIOCS_SET_NIF:
|
|
/*
|
|
* Define network interfaces
|
|
*/
|
|
if ((pip = atm_pifname(asp->asr_nif_intf)) == NULL)
|
|
ATM_RETERR(ENXIO);
|
|
|
|
/*
|
|
* Validate interface count - logical interfaces
|
|
* are differentiated by the atm address selector.
|
|
*/
|
|
if ((asp->asr_nif_cnt <= 0) || (asp->asr_nif_cnt > 256))
|
|
ATM_RETERR(EINVAL);
|
|
|
|
/*
|
|
* Make sure prefix name is unique
|
|
*/
|
|
TAILQ_FOREACH(ifp2, &ifnet, if_link) {
|
|
if (!strcmp(ifp2->if_name, asp->asr_nif_pref)) {
|
|
/*
|
|
* If this is for the interface we're
|
|
* (re-)defining, let it through
|
|
*/
|
|
for (nip = pip->pif_nif; nip;
|
|
nip = nip->nif_pnext) {
|
|
if (&nip->nif_if == ifp2)
|
|
break;
|
|
}
|
|
if (nip)
|
|
continue;
|
|
ATM_RETERR(EEXIST);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Let interface handle it from here
|
|
*/
|
|
err = (*pip->pif_ioctl)(AIOCS_SET_NIF, data,
|
|
(caddr_t)pip);
|
|
break;
|
|
|
|
case AIOCS_SET_PRF:
|
|
/*
|
|
* Set interface NSAP Prefix
|
|
*/
|
|
|
|
/*
|
|
* Locate appropriate sigmgr
|
|
*/
|
|
if ((pip = atm_pifname(asp->asr_prf_intf)) == NULL)
|
|
ATM_RETERR(ENXIO);
|
|
if ((smp = pip->pif_sigmgr) == NULL)
|
|
ATM_RETERR(ENOENT);
|
|
|
|
/*
|
|
* Let sigmgr handle it from here
|
|
*/
|
|
err = (*smp->sm_ioctl)(AIOCS_SET_PRF, data,
|
|
(caddr_t)pip->pif_siginst);
|
|
break;
|
|
|
|
default:
|
|
err = EOPNOTSUPP;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case AIOCINFO:
|
|
err = atm_dgram_info(data);
|
|
break;
|
|
|
|
default:
|
|
err = EOPNOTSUPP;
|
|
}
|
|
|
|
out:
|
|
ATM_OUTRO();
|
|
}
|
|
|
|
|
|
/*
|
|
* Process AIOCINFO ioctl system calls
|
|
*
|
|
* Called at splnet.
|
|
*
|
|
* Arguments:
|
|
* data pointer to AIOCINFO parameter structure
|
|
*
|
|
* Returns:
|
|
* 0 request processed
|
|
* errno error processing request - reason indicated
|
|
*
|
|
*/
|
|
static int
|
|
atm_dgram_info(data)
|
|
caddr_t data;
|
|
{
|
|
struct atminfreq *aip = (struct atminfreq *)data;
|
|
struct atm_pif *pip;
|
|
struct atm_nif *nip;
|
|
struct sigmgr *smp;
|
|
Atm_endpoint *epp;
|
|
int len = aip->air_buf_len;
|
|
int err = 0;
|
|
|
|
switch (aip->air_opcode) {
|
|
|
|
case AIOCS_INF_VST:
|
|
case AIOCS_INF_CFG:
|
|
/*
|
|
* Get vendor interface information
|
|
*/
|
|
if (aip->air_vinfo_intf[0] != '\0') {
|
|
/*
|
|
* Interface specified
|
|
*/
|
|
if ((pip = atm_pifname(aip->air_vinfo_intf))) {
|
|
err = (*pip->pif_ioctl)(aip->air_opcode, data,
|
|
(caddr_t)pip);
|
|
} else {
|
|
err = ENXIO;
|
|
}
|
|
} else {
|
|
/*
|
|
* Want info for every interface
|
|
*/
|
|
for (pip = atm_interface_head; pip;
|
|
pip = pip->pif_next) {
|
|
err = (*pip->pif_ioctl)(aip->air_opcode, data,
|
|
(caddr_t)pip);
|
|
if (err)
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case AIOCS_INF_IPM:
|
|
/*
|
|
* Get IP Map information
|
|
*/
|
|
epp = atm_endpoints[ENDPT_IP];
|
|
if (epp) {
|
|
err = (*epp->ep_ioctl) (AIOCS_INF_IPM, data, NULL);
|
|
} else {
|
|
err = ENOPROTOOPT;
|
|
}
|
|
break;
|
|
|
|
case AIOCS_INF_ARP:
|
|
/*
|
|
* Get ARP table information
|
|
*/
|
|
for (pip = atm_interface_head; pip; pip = pip->pif_next) {
|
|
if ((smp = pip->pif_sigmgr) != NULL) {
|
|
err = (*smp->sm_ioctl)(AIOCS_INF_ARP,
|
|
data, (caddr_t)pip->pif_siginst);
|
|
}
|
|
if (err)
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case AIOCS_INF_ASV:
|
|
/*
|
|
* Get ARP server information
|
|
*/
|
|
if (aip->air_asrv_intf[0] != '\0') {
|
|
/*
|
|
* Interface specified
|
|
*/
|
|
if ((nip = atm_nifname(aip->air_asrv_intf))) {
|
|
if ((smp = nip->nif_pif->pif_sigmgr) != NULL) {
|
|
err = (*smp->sm_ioctl)(AIOCS_INF_ASV,
|
|
data, (caddr_t)nip);
|
|
}
|
|
} else {
|
|
err = ENXIO;
|
|
}
|
|
} else {
|
|
/*
|
|
* Want info for all arp servers
|
|
*/
|
|
for (pip = atm_interface_head; pip;
|
|
pip = pip->pif_next) {
|
|
if ((smp = pip->pif_sigmgr) != NULL) {
|
|
for (nip = pip->pif_nif; nip;
|
|
nip = nip->nif_pnext) {
|
|
err = (*smp->sm_ioctl)
|
|
(AIOCS_INF_ASV, data,
|
|
(caddr_t)nip);
|
|
if (err)
|
|
break;
|
|
}
|
|
if (err)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case AIOCS_INF_INT:
|
|
/*
|
|
* Get physical interface info
|
|
*/
|
|
if (aip->air_int_intf[0] != '\0') {
|
|
/*
|
|
* Interface specified
|
|
*/
|
|
if ((pip = atm_pifname(aip->air_int_intf))) {
|
|
err = (*pip->pif_ioctl)(AIOCS_INF_INT,
|
|
data, (caddr_t)pip);
|
|
} else {
|
|
err = ENXIO;
|
|
}
|
|
} else {
|
|
/*
|
|
* Want info for every physical interface
|
|
*/
|
|
for (pip = atm_interface_head; pip;
|
|
pip = pip->pif_next) {
|
|
err = (*pip->pif_ioctl)(AIOCS_INF_INT,
|
|
data, (caddr_t)pip);
|
|
if (err)
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case AIOCS_INF_VCC:
|
|
/*
|
|
* Get VCC information
|
|
*/
|
|
if (aip->air_vcc_intf[0] != '\0') {
|
|
/*
|
|
* Interface specified
|
|
*/
|
|
if ((pip = atm_pifname(aip->air_vcc_intf))) {
|
|
if ((smp = pip->pif_sigmgr) != NULL) {
|
|
err = (*smp->sm_ioctl)(AIOCS_INF_VCC,
|
|
data,
|
|
(caddr_t)pip->pif_siginst);
|
|
}
|
|
} else {
|
|
err = ENXIO;
|
|
}
|
|
} else {
|
|
/*
|
|
* Want info for every interface
|
|
*/
|
|
for (pip = atm_interface_head; pip;
|
|
pip = pip->pif_next) {
|
|
if ((smp = pip->pif_sigmgr) != NULL) {
|
|
err = (*smp->sm_ioctl)(AIOCS_INF_VCC,
|
|
data,
|
|
(caddr_t)pip->pif_siginst);
|
|
}
|
|
if (err)
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case AIOCS_INF_NIF:
|
|
/*
|
|
* Get network interface info
|
|
*/
|
|
if (aip->air_int_intf[0] != '\0') {
|
|
/*
|
|
* Interface specified
|
|
*/
|
|
if ((nip = atm_nifname(aip->air_int_intf))) {
|
|
pip = nip->nif_pif;
|
|
err = (*pip->pif_ioctl)(AIOCS_INF_NIF,
|
|
data, (caddr_t)nip);
|
|
} else {
|
|
err = ENXIO;
|
|
}
|
|
} else {
|
|
/*
|
|
* Want info for every network interface
|
|
*/
|
|
for (pip = atm_interface_head; pip;
|
|
pip = pip->pif_next) {
|
|
for (nip = pip->pif_nif; nip;
|
|
nip = nip->nif_pnext) {
|
|
err = (*pip->pif_ioctl)(AIOCS_INF_NIF,
|
|
data, (caddr_t)nip);
|
|
if (err)
|
|
break;
|
|
}
|
|
if (err)
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case AIOCS_INF_PIS:
|
|
/*
|
|
* Get physical interface statistics
|
|
*/
|
|
if (aip->air_physt_intf[0] != '\0') {
|
|
/*
|
|
* Interface specified
|
|
*/
|
|
if ((pip = atm_pifname(aip->air_physt_intf))) {
|
|
err = (*pip->pif_ioctl)(AIOCS_INF_PIS,
|
|
data, (caddr_t)pip);
|
|
} else {
|
|
err = ENXIO;
|
|
}
|
|
} else {
|
|
/*
|
|
* Want statistics for every physical interface
|
|
*/
|
|
for (pip = atm_interface_head; pip;
|
|
pip = pip->pif_next) {
|
|
err = (*pip->pif_ioctl)(AIOCS_INF_PIS,
|
|
data, (caddr_t)pip);
|
|
if (err)
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case AIOCS_INF_VER:
|
|
/*
|
|
* Get ATM software version
|
|
*/
|
|
if (len < sizeof(atm_version)) {
|
|
err = ENOSPC;
|
|
break;
|
|
}
|
|
if ((err = copyout((caddr_t)&atm_version,
|
|
aip->air_buf_addr,
|
|
sizeof(atm_version))) != 0) {
|
|
break;
|
|
}
|
|
aip->air_buf_addr += sizeof(atm_version);
|
|
aip->air_buf_len -= sizeof(atm_version);
|
|
break;
|
|
|
|
default:
|
|
err = EOPNOTSUPP;
|
|
}
|
|
|
|
/*
|
|
* Calculate returned buffer length
|
|
*/
|
|
aip->air_buf_len = len - aip->air_buf_len;
|
|
|
|
return (err);
|
|
}
|
|
|