root/usr/src/lib/libnsl/nsl/_utility.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*      Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */
/*        All Rights Reserved   */

/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */
/*
 * Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
 */

#include "mt.h"
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <errno.h>
#include <stropts.h>
#include <sys/stream.h>
#define _SUN_TPI_VERSION 2
#include <sys/tihdr.h>
#include <sys/timod.h>
#include <sys/stat.h>
#include <xti.h>
#include <fcntl.h>
#include <signal.h>
#include <assert.h>
#include <syslog.h>
#include <limits.h>
#include <ucred.h>
#include "tx.h"

#define DEFSIZE 2048

/*
 * The following used to be in tiuser.h, but was causing too much namespace
 * pollution.
 */
#define ROUNDUP32(X)    ((X + 0x03)&~0x03)

static struct _ti_user  *find_tilink(int s);
static struct _ti_user  *add_tilink(int s);
static void _t_free_lookbufs(struct _ti_user *tiptr);
static unsigned int _t_setsize(t_scalar_t infosize, boolean_t option);
static int _t_cbuf_alloc(struct _ti_user *tiptr, char **retbuf);
static int _t_rbuf_alloc(struct _ti_user *tiptr, char **retbuf);
static int _t_adjust_state(int fd, int instate);
static int _t_alloc_bufs(int fd, struct _ti_user *tiptr,
        struct T_info_ack *tsap);

mutex_t _ti_userlock = DEFAULTMUTEX;    /* Protects hash_bucket[] */

/*
 * Checkfd - checks validity of file descriptor
 */
struct _ti_user *
_t_checkfd(int fd, int force_sync, int api_semantics)
{
        sigset_t mask;
        struct _ti_user *tiptr;
        int retval, timodpushed;

        if (fd < 0) {
                t_errno = TBADF;
                return (NULL);
        }

        if (!force_sync) {
                sig_mutex_lock(&_ti_userlock);
                tiptr = find_tilink(fd);
                sig_mutex_unlock(&_ti_userlock);
                if (tiptr != NULL)
                        return (tiptr);
        }

        /*
         * Not found or a forced sync is required.
         * check if this is a valid TLI/XTI descriptor.
         */
        timodpushed = 0;
        do {
                retval = ioctl(fd, I_FIND, "timod");
        } while (retval < 0 && errno == EINTR);

        if (retval < 0 || (retval == 0 && _T_IS_TLI(api_semantics))) {
                /*
                 * not a stream or a TLI endpoint with no timod
                 * XXX Note: If it is a XTI call, we push "timod" and
                 * try to convert it into a transport endpoint later.
                 * We do not do it for TLI and "retain" the old buggy
                 * behavior because ypbind and a lot of other deamons seem
                 * to use a buggy logic test of the form
                 * "(t_getstate(0) != -1 || t_errno != TBADF)" to see if
                 * they we ever invoked with request on stdin and drop into
                 * untested code. This test is in code generated by rpcgen
                 * which is why it is replicated test in many daemons too.
                 * We will need to fix that test too with "IsaTLIendpoint"
                 * test if we ever fix this for TLI
                 */
                t_errno = TBADF;
                return (NULL);
        }

        if (retval == 0) {
                /*
                 * "timod" not already on stream, then push it
                 */
                do {
                        /*
                         * Assumes (correctly) that I_PUSH  is
                         * atomic w.r.t signals (EINTR error)
                         */
                        retval = ioctl(fd, I_PUSH, "timod");
                } while (retval < 0 && errno == EINTR);

                if (retval < 0) {
                        t_errno = TSYSERR;
                        return (NULL);
                }
                timodpushed = 1;
        }
        /*
         * Try to (re)constitute the info at user level from state
         * in the kernel. This could be information that lost due
         * to an exec or being instantiated at a new descriptor due
         * to , open(), dup2() etc.
         *
         * _t_create() requires that all signals be blocked.
         * Note that sig_mutex_lock() only defers signals, it does not
         * block them, so interruptible syscalls could still get EINTR.
         */
        (void) thr_sigsetmask(SIG_SETMASK, &fillset, &mask);
        sig_mutex_lock(&_ti_userlock);
        tiptr = _t_create(fd, NULL, api_semantics, NULL);
        if (tiptr == NULL) {
                int sv_errno = errno;
                sig_mutex_unlock(&_ti_userlock);
                (void) thr_sigsetmask(SIG_SETMASK, &mask, NULL);
                /*
                 * restore to stream before timod pushed. It may
                 * not have been a network transport stream.
                 */
                if (timodpushed)
                        (void) ioctl(fd, I_POP, 0);
                errno = sv_errno;
                return (NULL);
        }
        sig_mutex_unlock(&_ti_userlock);
        (void) thr_sigsetmask(SIG_SETMASK, &mask, NULL);
        return (tiptr);
}

/*
 * copy data to output buffer making sure the output buffer is 32 bit
 * aligned, even though the input buffer may not be.
 */
int
_t_aligned_copy(
        struct strbuf *strbufp,
        int len,
        int init_offset,
        char *datap,
        t_scalar_t *rtn_offset)
{
        *rtn_offset = ROUNDUP32(init_offset);
        if ((*rtn_offset + len) > strbufp->maxlen) {
                /*
                 * Aligned copy will overflow buffer
                 */
                return (-1);
        }
        (void) memcpy(strbufp->buf + *rtn_offset, datap, (size_t)len);

        return (0);
}


/*
 * append data and control info in look buffer (list in the MT case)
 *
 * The only thing that can be in look buffer is a T_DISCON_IND,
 * T_ORDREL_IND or a T_UDERROR_IND.
 *
 * It also enforces priority of T_DISCONDs over any T_ORDREL_IND
 * already in the buffer. It assumes no T_ORDREL_IND is appended
 * when there is already something on the looklist (error case) and
 * that a T_ORDREL_IND if present will always be the first on the
 * list.
 *
 * This also assumes ti_lock is held via sig_mutex_lock(),
 * so signals are deferred here.
 */
int
_t_register_lookevent(
        struct _ti_user *tiptr,
        caddr_t dptr,
        int dsize,
        caddr_t cptr,
        int csize)
{
        struct _ti_lookbufs *tlbs;
        int cbuf_size, dbuf_size;

        assert(MUTEX_HELD(&tiptr->ti_lock));

        cbuf_size = tiptr->ti_ctlsize;
        dbuf_size = tiptr->ti_rcvsize;

        if ((csize > cbuf_size) || dsize > dbuf_size) {
                /* can't fit - return error */
                return (-1);    /* error */
        }
        /*
         * Enforce priority of T_DISCON_IND over T_ORDREL_IND
         * queued earlier.
         * Note: Since there can be only at most one T_ORDREL_IND
         * queued (more than one is error case), and we look for it
         * on each append of T_DISCON_IND, it can only be at the
         * head of the list if it is there.
         */
        if (tiptr->ti_lookcnt > 0) { /* something already on looklist */
                if (cptr && csize >= (int)sizeof (struct T_discon_ind) &&
                    /* LINTED pointer cast */
                    *(t_scalar_t *)cptr == T_DISCON_IND) {
                        /* appending discon ind */
                        assert(tiptr->ti_servtype != T_CLTS);
                        /* LINTED pointer cast */
                        if (*(t_scalar_t *)tiptr->ti_lookbufs.tl_lookcbuf ==
                            T_ORDREL_IND) { /* T_ORDREL_IND is on list */
                                /*
                                 * Blow away T_ORDREL_IND
                                 */
                                _t_free_looklist_head(tiptr);
                        }
                }
        }
        tlbs = &tiptr->ti_lookbufs;
        if (tiptr->ti_lookcnt > 0) {
                int listcount = 0;
                /*
                 * Allocate and append a new lookbuf to the
                 * existing list. (Should only happen in MT case)
                 */
                while (tlbs->tl_next != NULL) {
                        listcount++;
                        tlbs = tlbs->tl_next;
                }
                assert(tiptr->ti_lookcnt == listcount);

                /*
                 * signals are deferred, calls to malloc() are safe.
                 */
                if ((tlbs->tl_next = malloc(sizeof (struct _ti_lookbufs))) ==
                    NULL)
                        return (-1); /* error */
                tlbs = tlbs->tl_next;
                /*
                 * Allocate the buffers. The sizes derived from the
                 * sizes of other related buffers. See _t_alloc_bufs()
                 * for details.
                 */
                if ((tlbs->tl_lookcbuf = malloc(cbuf_size)) == NULL) {
                        /* giving up - free other memory chunks */
                        free(tlbs);
                        return (-1); /* error */
                }
                if ((dsize > 0) &&
                    ((tlbs->tl_lookdbuf = malloc(dbuf_size)) == NULL)) {
                        /* giving up - free other memory chunks */
                        free(tlbs->tl_lookcbuf);
                        free(tlbs);
                        return (-1); /* error */
                }
        }

        (void) memcpy(tlbs->tl_lookcbuf, cptr, csize);
        if (dsize > 0)
                (void) memcpy(tlbs->tl_lookdbuf, dptr, dsize);
        tlbs->tl_lookdlen = dsize;
        tlbs->tl_lookclen = csize;
        tlbs->tl_next = NULL;
        tiptr->ti_lookcnt++;
        return (0);             /* ok return */
}

/*
 * Is there something that needs attention?
 * Assumes tiptr->ti_lock held and this threads signals blocked
 * in MT case.
 */
int
_t_is_event(int fd, struct _ti_user *tiptr)
{
        int size, retval;

        assert(MUTEX_HELD(&tiptr->ti_lock));
        if ((retval = ioctl(fd, I_NREAD, &size)) < 0) {
                t_errno = TSYSERR;
                return (-1);
        }

        if ((retval > 0) || (tiptr->ti_lookcnt > 0)) {
                t_errno = TLOOK;
                return (-1);
        }
        return (0);
}

/*
 * wait for T_OK_ACK
 * assumes tiptr->ti_lock held in MT case
 */
int
_t_is_ok(int fd, struct _ti_user *tiptr, t_scalar_t type)
{
        struct strbuf ctlbuf;
        struct strbuf databuf;
        union T_primitives *pptr;
        int retval, cntlflag;
        int size;
        int didalloc, didralloc;
        int flags = 0;

        assert(MUTEX_HELD(&tiptr->ti_lock));
        /*
         * Acquire ctlbuf for use in sending/receiving control part
         * of the message.
         */
        if (_t_acquire_ctlbuf(tiptr, &ctlbuf, &didalloc) < 0)
                return (-1);
        /*
         * Acquire databuf for use in sending/receiving data part
         */
        if (_t_acquire_databuf(tiptr, &databuf, &didralloc) < 0) {
                if (didalloc)
                        free(ctlbuf.buf);
                else
                        tiptr->ti_ctlbuf = ctlbuf.buf;
                return (-1);
        }

        /*
         * Temporarily convert a non blocking endpoint to a
         * blocking one and restore status later
         */
        cntlflag = fcntl(fd, F_GETFL, 0);
        if (cntlflag & (O_NDELAY | O_NONBLOCK))
                (void) fcntl(fd, F_SETFL, cntlflag & ~(O_NDELAY | O_NONBLOCK));

        flags = RS_HIPRI;

        while ((retval = getmsg(fd, &ctlbuf, &databuf, &flags)) < 0) {
                if (errno == EINTR)
                        continue;
                if (cntlflag & (O_NDELAY | O_NONBLOCK))
                        (void) fcntl(fd, F_SETFL, cntlflag);
                t_errno = TSYSERR;
                goto err_out;
        }

        /* did I get entire message */
        if (retval > 0) {
                if (cntlflag & (O_NDELAY | O_NONBLOCK))
                        (void) fcntl(fd, F_SETFL, cntlflag);
                t_errno = TSYSERR;
                errno = EIO;
                goto err_out;
        }

        /*
         * is ctl part large enough to determine type?
         */
        if (ctlbuf.len < (int)sizeof (t_scalar_t)) {
                if (cntlflag & (O_NDELAY | O_NONBLOCK))
                        (void) fcntl(fd, F_SETFL, cntlflag);
                t_errno = TSYSERR;
                errno = EPROTO;
                goto err_out;
        }

        if (cntlflag & (O_NDELAY | O_NONBLOCK))
                (void) fcntl(fd, F_SETFL, cntlflag);

        /* LINTED pointer cast */
        pptr = (union T_primitives *)ctlbuf.buf;

        switch (pptr->type) {
        case T_OK_ACK:
                if ((ctlbuf.len < (int)sizeof (struct T_ok_ack)) ||
                    (pptr->ok_ack.CORRECT_prim != type)) {
                        t_errno = TSYSERR;
                        errno = EPROTO;
                        goto err_out;
                }
                if (didalloc)
                        free(ctlbuf.buf);
                else
                        tiptr->ti_ctlbuf = ctlbuf.buf;
                if (didralloc)
                        free(databuf.buf);
                else
                        tiptr->ti_rcvbuf = databuf.buf;
                return (0);

        case T_ERROR_ACK:
                if ((ctlbuf.len < (int)sizeof (struct T_error_ack)) ||
                    (pptr->error_ack.ERROR_prim != type)) {
                        t_errno = TSYSERR;
                        errno = EPROTO;
                        goto err_out;
                }
                /*
                 * if error is out of state and there is something
                 * on read queue, then indicate to user that
                 * there is something that needs attention
                 */
                if (pptr->error_ack.TLI_error == TOUTSTATE) {
                        if ((retval = ioctl(fd, I_NREAD, &size)) < 0) {
                                t_errno = TSYSERR;
                                goto err_out;
                        }
                        if (retval > 0)
                                t_errno = TLOOK;
                        else
                                t_errno = TOUTSTATE;
                } else {
                        t_errno = pptr->error_ack.TLI_error;
                        if (t_errno == TSYSERR)
                                errno = pptr->error_ack.UNIX_error;
                }
                goto err_out;
        default:
                t_errno = TSYSERR;
                errno = EPROTO;
                /* fallthru to err_out: */
        }
err_out:
        if (didalloc)
                free(ctlbuf.buf);
        else
                tiptr->ti_ctlbuf = ctlbuf.buf;
        if (didralloc)
                free(databuf.buf);
        else
                tiptr->ti_rcvbuf = databuf.buf;
        return (-1);
}

/*
 * timod ioctl
 */
int
_t_do_ioctl(int fd, char *buf, int size, int cmd, int *retlenp)
{
        int retval;
        struct strioctl strioc;

        strioc.ic_cmd = cmd;
        strioc.ic_timout = -1;
        strioc.ic_len = size;
        strioc.ic_dp = buf;

        if ((retval = ioctl(fd, I_STR, &strioc)) < 0) {
                t_errno = TSYSERR;
                return (-1);
        }

        if (retval > 0) {
                t_errno = retval & 0xff;
                if (t_errno == TSYSERR)
                        errno = (retval >> 8) & 0xff;
                return (-1);
        }
        if (retlenp)
                *retlenp = strioc.ic_len;
        return (0);
}

/*
 * alloc scratch buffers and look buffers
 */
/* ARGSUSED */
static int
_t_alloc_bufs(int fd, struct _ti_user *tiptr, struct T_info_ack *tsap)
{
        unsigned int size1, size2;
        t_scalar_t optsize;
        unsigned int csize, dsize, asize, osize;
        char *ctlbuf, *rcvbuf;
        char *lookdbuf, *lookcbuf;

        csize = _t_setsize(tsap->CDATA_size, B_FALSE);
        dsize = _t_setsize(tsap->DDATA_size, B_FALSE);

        size1 = _T_MAX(csize, dsize);

        if (size1 != 0) {
                if ((rcvbuf = malloc(size1)) == NULL)
                        return (-1);
                if ((lookdbuf = malloc(size1)) == NULL) {
                        free(rcvbuf);
                        return (-1);
                }
        } else {
                rcvbuf = NULL;
                lookdbuf = NULL;
        }

        asize = _t_setsize(tsap->ADDR_size, B_FALSE);
        if (tsap->OPT_size >= 0)
                /* compensate for XTI level options */
                optsize = tsap->OPT_size + TX_XTI_LEVEL_MAX_OPTBUF;
        else
                optsize = tsap->OPT_size;
        osize = _t_setsize(optsize, B_TRUE);

        /*
         * We compute the largest buffer size needed for this provider by
         * adding the components. [ An extra sizeof (t_scalar_t) is added to
         * take care of rounding off for alignment) for each buffer ]
         * The goal here is compute the size of largest possible buffer that
         * might be needed to hold a TPI message for the transport provider
         * on this endpoint.
         * Note: T_ADDR_ACK contains potentially two address buffers.
         */

        size2 = (unsigned int)sizeof (union T_primitives) /* TPI struct */
            + asize + (unsigned int)sizeof (t_scalar_t) +
                /* first addr buffer plus alignment */
            asize + (unsigned int)sizeof (t_scalar_t) +
                /* second addr buffer plus ailignment */
            osize + (unsigned int)sizeof (t_scalar_t);
                /* option buffer plus alignment */

        if ((ctlbuf = malloc(size2)) == NULL) {
                if (size1 != 0) {
                        free(rcvbuf);
                        free(lookdbuf);
                }
                return (-1);
        }

        if ((lookcbuf = malloc(size2)) == NULL) {
                if (size1 != 0) {
                        free(rcvbuf);
                        free(lookdbuf);
                }
                free(ctlbuf);
                return (-1);
        }

        tiptr->ti_rcvsize = size1;
        tiptr->ti_rcvbuf = rcvbuf;
        tiptr->ti_ctlsize = size2;
        tiptr->ti_ctlbuf = ctlbuf;

        /*
         * Note: The head of the lookbuffers list (and associated buffers)
         * is allocated here on initialization.
         * More allocated on demand.
         */
        tiptr->ti_lookbufs.tl_lookclen = 0;
        tiptr->ti_lookbufs.tl_lookcbuf = lookcbuf;
        tiptr->ti_lookbufs.tl_lookdlen = 0;
        tiptr->ti_lookbufs.tl_lookdbuf = lookdbuf;

        return (0);
}


/*
 * set sizes of buffers
 */
static unsigned int
_t_setsize(t_scalar_t infosize, boolean_t option)
{
        static size_t optinfsize;

        switch (infosize) {
        case T_INFINITE /* -1 */:
                if (option) {
                        if (optinfsize == 0) {
                                size_t uc = ucred_size();
                                if (uc < DEFSIZE/2)
                                        optinfsize = DEFSIZE;
                                else
                                        optinfsize = ucred_size() + DEFSIZE/2;
                        }
                        return ((unsigned int)optinfsize);
                }
                return (DEFSIZE);
        case T_INVALID /* -2 */:
                return (0);
        default:
                return ((unsigned int) infosize);
        }
}

static void
_t_reinit_tiptr(struct _ti_user *tiptr)
{
        /*
         * Note: This routine is designed for a "reinitialization"
         * Following fields are not modified here and preserved.
         *       - ti_fd field
         *       - ti_lock
         *       - ti_next
         *       - ti_prev
         * The above fields have to be separately initialized if this
         * is used for a fresh initialization.
         */

        tiptr->ti_flags = 0;
        tiptr->ti_rcvsize = 0;
        tiptr->ti_rcvbuf = NULL;
        tiptr->ti_ctlsize = 0;
        tiptr->ti_ctlbuf = NULL;
        tiptr->ti_lookbufs.tl_lookdbuf = NULL;
        tiptr->ti_lookbufs.tl_lookcbuf = NULL;
        tiptr->ti_lookbufs.tl_lookdlen = 0;
        tiptr->ti_lookbufs.tl_lookclen = 0;
        tiptr->ti_lookbufs.tl_next = NULL;
        tiptr->ti_maxpsz = 0;
        tiptr->ti_tsdusize = 0;
        tiptr->ti_etsdusize = 0;
        tiptr->ti_cdatasize = 0;
        tiptr->ti_ddatasize = 0;
        tiptr->ti_servtype = 0;
        tiptr->ti_lookcnt = 0;
        tiptr->ti_state = 0;
        tiptr->ti_ocnt = 0;
        tiptr->ti_prov_flag = 0;
        tiptr->ti_qlen = 0;
}

/*
 * Link manipulation routines.
 *
 * NBUCKETS hash buckets are used to give fast
 * access. The number is derived the file descriptor softlimit
 * number (64).
 */

#define NBUCKETS        64
static struct _ti_user          *hash_bucket[NBUCKETS];

/*
 * Allocates a new link and returns a pointer to it.
 * Assumes that the caller is holding _ti_userlock via sig_mutex_lock(),
 * so signals are deferred here.
 */
static struct _ti_user *
add_tilink(int s)
{
        struct _ti_user *tiptr;
        struct _ti_user *prevptr;
        struct _ti_user *curptr;
        int     x;
        struct stat stbuf;

        assert(MUTEX_HELD(&_ti_userlock));

        if (s < 0 || fstat(s, &stbuf) != 0)
                return (NULL);

        x = s % NBUCKETS;
        if (hash_bucket[x] != NULL) {
                /*
                 * Walk along the bucket looking for
                 * duplicate entry or the end.
                 */
                for (curptr = hash_bucket[x]; curptr != NULL;
                    curptr = curptr->ti_next) {
                        if (curptr->ti_fd == s) {
                                /*
                                 * This can happen when the user has close(2)'ed
                                 * a descriptor and then been allocated it again
                                 * via t_open().
                                 *
                                 * We will re-use the existing _ti_user struct
                                 * in this case rather than using the one
                                 * we allocated above.  If there are buffers
                                 * associated with the existing _ti_user
                                 * struct, they may not be the correct size,
                                 * so we can not use it.  We free them
                                 * here and re-allocate a new ones
                                 * later on.
                                 */
                                if (curptr->ti_rcvbuf != NULL)
                                        free(curptr->ti_rcvbuf);
                                free(curptr->ti_ctlbuf);
                                _t_free_lookbufs(curptr);
                                _t_reinit_tiptr(curptr);
                                curptr->ti_rdev = stbuf.st_rdev;
                                curptr->ti_ino = stbuf.st_ino;
                                return (curptr);
                        }
                        prevptr = curptr;
                }
                /*
                 * Allocate and link in a new one.
                 */
                if ((tiptr = malloc(sizeof (*tiptr))) == NULL)
                        return (NULL);
                /*
                 * First initialize fields common with reinitialization and
                 * then other fields too
                 */
                _t_reinit_tiptr(tiptr);
                prevptr->ti_next = tiptr;
                tiptr->ti_prev = prevptr;
        } else {
                /*
                 * First entry.
                 */
                if ((tiptr = malloc(sizeof (*tiptr))) == NULL)
                        return (NULL);
                _t_reinit_tiptr(tiptr);
                hash_bucket[x] = tiptr;
                tiptr->ti_prev = NULL;
        }
        tiptr->ti_next = NULL;
        tiptr->ti_fd = s;
        tiptr->ti_rdev = stbuf.st_rdev;
        tiptr->ti_ino = stbuf.st_ino;
        (void) mutex_init(&tiptr->ti_lock, USYNC_THREAD, NULL);
        return (tiptr);
}

/*
 * Find a link by descriptor
 * Assumes that the caller is holding _ti_userlock.
 */
static struct _ti_user *
find_tilink(int s)
{
        struct _ti_user *curptr;
        int     x;
        struct stat stbuf;

        assert(MUTEX_HELD(&_ti_userlock));

        if (s < 0 || fstat(s, &stbuf) != 0)
                return (NULL);

        x = s % NBUCKETS;
        /*
         * Walk along the bucket looking for the descriptor.
         */
        for (curptr = hash_bucket[x]; curptr; curptr = curptr->ti_next) {
                if (curptr->ti_fd == s) {
                        if (curptr->ti_rdev == stbuf.st_rdev &&
                            curptr->ti_ino == stbuf.st_ino)
                                return (curptr);
                        (void) _t_delete_tilink(s);
                }
        }
        return (NULL);
}

/*
 * Assumes that the caller is holding _ti_userlock.
 * Also assumes that all signals are blocked.
 */
int
_t_delete_tilink(int s)
{
        struct _ti_user *curptr;
        int     x;

        /*
         * Find the link.
         */
        assert(MUTEX_HELD(&_ti_userlock));
        if (s < 0)
                return (-1);
        x = s % NBUCKETS;
        /*
         * Walk along the bucket looking for
         * the descriptor.
         */
        for (curptr = hash_bucket[x]; curptr; curptr = curptr->ti_next) {
                if (curptr->ti_fd == s) {
                        struct _ti_user *nextptr;
                        struct _ti_user *prevptr;

                        nextptr = curptr->ti_next;
                        prevptr = curptr->ti_prev;
                        if (prevptr)
                                prevptr->ti_next = nextptr;
                        else
                                hash_bucket[x] = nextptr;
                        if (nextptr)
                                nextptr->ti_prev = prevptr;

                        /*
                         * free resource associated with the curptr
                         */
                        if (curptr->ti_rcvbuf != NULL)
                                free(curptr->ti_rcvbuf);
                        free(curptr->ti_ctlbuf);
                        _t_free_lookbufs(curptr);
                        (void) mutex_destroy(&curptr->ti_lock);
                        free(curptr);
                        return (0);
                }
        }
        return (-1);
}

/*
 * Allocate a TLI state structure and synch it with the kernel
 * *tiptr is returned
 * Assumes that the caller is holding the _ti_userlock and has blocked signals.
 *
 * This function may fail the first time it is called with given transport if it
 * doesn't support T_CAPABILITY_REQ TPI message.
 */
struct _ti_user *
_t_create(int fd, struct t_info *info, int api_semantics, int *t_capreq_failed)
{
        /*
         * Aligned data buffer for ioctl.
         */
        union {
                struct ti_sync_req ti_req;
                struct ti_sync_ack ti_ack;
                union T_primitives t_prim;
                char pad[128];
        } ioctl_data;
        void *ioctlbuf = &ioctl_data; /* TI_SYNC/GETINFO with room to grow */
                            /* preferred location first local variable */
                            /*  see note below */
        /*
         * Note: We use "ioctlbuf" allocated on stack above with
         * room to grow since (struct ti_sync_ack) can grow in size
         * on future kernels. (We do not use malloc'd "ti_ctlbuf" as that
         * part of instance structure which may not exist yet)
         * Its preferred declaration location is first local variable in this
         * procedure as bugs causing overruns will be detectable on
         * platforms where procedure calling conventions place return
         * address on stack (such as x86) instead of causing silent
         * memory corruption.
         */
        struct ti_sync_req *tsrp = (struct ti_sync_req *)ioctlbuf;
        struct ti_sync_ack *tsap = (struct ti_sync_ack *)ioctlbuf;
        struct T_capability_req *tcrp = (struct T_capability_req *)ioctlbuf;
        struct T_capability_ack *tcap = (struct T_capability_ack *)ioctlbuf;
        struct T_info_ack *tiap = &tcap->INFO_ack;
        struct _ti_user *ntiptr;
        int expected_acksize;
        int retlen, rstate, sv_errno, rval;

        assert(MUTEX_HELD(&_ti_userlock));

        /*
         * Use ioctl required for sync'ing state with kernel.
         * We use two ioctls. TI_CAPABILITY is used to get TPI information and
         * TI_SYNC is used to synchronise state with timod. Statically linked
         * TLI applications will no longer work on older releases where there
         * are no TI_SYNC and TI_CAPABILITY.
         */

        /*
         * Request info about transport.
         * Assumes that TC1_INFO should always be implemented.
         * For TI_CAPABILITY size argument to ioctl specifies maximum buffer
         * size.
         */
        tcrp->PRIM_type = T_CAPABILITY_REQ;
        tcrp->CAP_bits1 = TC1_INFO | TC1_ACCEPTOR_ID;
        rval = _t_do_ioctl(fd, (char *)ioctlbuf,
            (int)sizeof (struct T_capability_ack), TI_CAPABILITY, &retlen);
        expected_acksize = (int)sizeof (struct T_capability_ack);

        if (rval < 0) {
                /*
                 * TI_CAPABILITY may fail when transport provider doesn't
                 * support T_CAPABILITY_REQ message type. In this case file
                 * descriptor may be unusable (when transport provider sent
                 * M_ERROR in response to T_CAPABILITY_REQ). This should only
                 * happen once during system lifetime for given transport
                 * provider since timod will emulate TI_CAPABILITY after it
                 * detected the failure.
                 */
                if (t_capreq_failed != NULL)
                        *t_capreq_failed = 1;
                return (NULL);
        }

        if (retlen != expected_acksize) {
                t_errno = TSYSERR;
                errno = EIO;
                return (NULL);
        }

        if ((tcap->CAP_bits1 & TC1_INFO) == 0) {
                t_errno = TSYSERR;
                errno = EPROTO;
                return (NULL);
        }
        if (info != NULL) {
                if (tiap->PRIM_type != T_INFO_ACK) {
                        t_errno = TSYSERR;
                        errno = EPROTO;
                        return (NULL);
                }
                info->addr = tiap->ADDR_size;
                info->options = tiap->OPT_size;
                info->tsdu = tiap->TSDU_size;
                info->etsdu = tiap->ETSDU_size;
                info->connect = tiap->CDATA_size;
                info->discon = tiap->DDATA_size;
                info->servtype = tiap->SERV_type;
                if (_T_IS_XTI(api_semantics)) {
                        /*
                         * XTI ONLY - TLI "struct t_info" does not
                         * have "flags"
                         */
                        info->flags = 0;
                        if (tiap->PROVIDER_flag & (SENDZERO|OLD_SENDZERO))
                                info->flags |= T_SENDZERO;
                        /*
                         * Some day there MAY be a NEW bit in T_info_ack
                         * PROVIDER_flag namespace exposed by TPI header
                         * <sys/tihdr.h> which will functionally correspond to
                         * role played by T_ORDRELDATA in info->flags namespace
                         * When that bit exists, we can add a test to see if
                         * it is set and set T_ORDRELDATA.
                         * Note: Currently only mOSI ("minimal OSI") provider
                         * is specified to use T_ORDRELDATA so probability of
                         * needing it is minimal.
                         */
                }
        }

        /*
         * if first time or no instance (after fork/exec, dup etc,
         * then create initialize data structure
         * and allocate buffers
         */
        ntiptr = add_tilink(fd);
        if (ntiptr == NULL) {
                t_errno = TSYSERR;
                errno = ENOMEM;
                return (NULL);
        }

        /*
         * Allocate buffers for the new descriptor
         */
        if (_t_alloc_bufs(fd, ntiptr, tiap) < 0) {
                sv_errno = errno;
                (void) _t_delete_tilink(fd);
                t_errno = TSYSERR;
                errno = sv_errno;
                return (NULL);
        }

        /* Fill instance structure */

        ntiptr->ti_lookcnt = 0;
        ntiptr->ti_flags = USED;
        ntiptr->ti_state = T_UNINIT;
        ntiptr->ti_ocnt = 0;

        assert(tiap->TIDU_size > 0);
        ntiptr->ti_maxpsz = tiap->TIDU_size;
        assert(tiap->TSDU_size >= -2);
        ntiptr->ti_tsdusize = tiap->TSDU_size;
        assert(tiap->ETSDU_size >= -2);
        ntiptr->ti_etsdusize = tiap->ETSDU_size;
        assert(tiap->CDATA_size >= -2);
        ntiptr->ti_cdatasize = tiap->CDATA_size;
        assert(tiap->DDATA_size >= -2);
        ntiptr->ti_ddatasize = tiap->DDATA_size;
        ntiptr->ti_servtype = tiap->SERV_type;
        ntiptr->ti_prov_flag = tiap->PROVIDER_flag;

        if ((tcap->CAP_bits1 & TC1_ACCEPTOR_ID) != 0) {
                ntiptr->acceptor_id = tcap->ACCEPTOR_id;
                ntiptr->ti_flags |= V_ACCEPTOR_ID;
        }
        else
                ntiptr->ti_flags &= ~V_ACCEPTOR_ID;

        /*
         * Restore state from kernel (caveat some heuristics)
         */
        switch (tiap->CURRENT_state) {

        case TS_UNBND:
                ntiptr->ti_state = T_UNBND;
                break;

        case TS_IDLE:
                if ((rstate = _t_adjust_state(fd, T_IDLE)) < 0) {
                        sv_errno = errno;
                        (void) _t_delete_tilink(fd);
                        errno = sv_errno;
                        return (NULL);
                }
                ntiptr->ti_state = rstate;
                break;

        case TS_WRES_CIND:
                ntiptr->ti_state = T_INCON;
                break;

        case TS_WCON_CREQ:
                ntiptr->ti_state = T_OUTCON;
                break;

        case TS_DATA_XFER:
                if ((rstate = _t_adjust_state(fd, T_DATAXFER)) < 0)  {
                        sv_errno = errno;
                        (void) _t_delete_tilink(fd);
                        errno = sv_errno;
                        return (NULL);
                }
                ntiptr->ti_state = rstate;
                break;

        case TS_WIND_ORDREL:
                ntiptr->ti_state = T_OUTREL;
                break;

        case TS_WREQ_ORDREL:
                if ((rstate = _t_adjust_state(fd, T_INREL)) < 0)  {
                        sv_errno = errno;
                        (void) _t_delete_tilink(fd);
                        errno = sv_errno;
                        return (NULL);
                }
                ntiptr->ti_state = rstate;
                break;
        default:
                t_errno = TSTATECHNG;
                (void) _t_delete_tilink(fd);
                return (NULL);
        }

        /*
         * Sync information with timod.
         */
        tsrp->tsr_flags = TSRF_QLEN_REQ;

        rval = _t_do_ioctl(fd, ioctlbuf,
            (int)sizeof (struct ti_sync_req), TI_SYNC, &retlen);
        expected_acksize = (int)sizeof (struct ti_sync_ack);

        if (rval < 0) {
                sv_errno = errno;
                (void) _t_delete_tilink(fd);
                t_errno = TSYSERR;
                errno = sv_errno;
                return (NULL);
        }

        /*
         * This is a "less than" check as "struct ti_sync_ack" returned by
         * TI_SYNC can grow in size in future kernels. If/when a statically
         * linked application is run on a future kernel, it should not fail.
         */
        if (retlen < expected_acksize) {
                sv_errno = errno;
                (void) _t_delete_tilink(fd);
                t_errno = TSYSERR;
                errno = sv_errno;
                return (NULL);
        }

        if (_T_IS_TLI(api_semantics))
                tsap->tsa_qlen = 0; /* not needed for TLI */

        ntiptr->ti_qlen = tsap->tsa_qlen;

        return (ntiptr);
}


static int
_t_adjust_state(int fd, int instate)
{
        char ctlbuf[sizeof (t_scalar_t)];
        char databuf[sizeof (int)]; /* size unimportant - anything > 0 */
        struct strpeek arg;
        int outstate, retval;

        /*
         * Peek at message on stream head (if any)
         * and see if it is data
         */
        arg.ctlbuf.buf = ctlbuf;
        arg.ctlbuf.maxlen = (int)sizeof (ctlbuf);
        arg.ctlbuf.len = 0;

        arg.databuf.buf = databuf;
        arg.databuf.maxlen = (int)sizeof (databuf);
        arg.databuf.len = 0;

        arg.flags = 0;

        if ((retval = ioctl(fd, I_PEEK, &arg)) < 0)  {
                t_errno = TSYSERR;
                return (-1);
        }
        outstate = instate;
        /*
         * If peek shows something at stream head, then
         * Adjust "outstate" based on some heuristics.
         */
        if (retval > 0) {
                switch (instate) {
                case T_IDLE:
                        /*
                         * The following heuristic is to handle data
                         * ahead of T_DISCON_IND indications that might
                         * be at the stream head waiting to be
                         * read (T_DATA_IND or M_DATA)
                         */
                        if (((arg.ctlbuf.len == 4) &&
                            /* LINTED pointer cast */
                            ((*(int32_t *)arg.ctlbuf.buf) == T_DATA_IND)) ||
                            ((arg.ctlbuf.len == 0) && arg.databuf.len)) {
                                outstate = T_DATAXFER;
                        }
                        break;
                case T_DATAXFER:
                        /*
                         * The following heuristic is to handle
                         * the case where the connection is established
                         * and in data transfer state at the provider
                         * but the T_CONN_CON has not yet been read
                         * from the stream head.
                         */
                        if ((arg.ctlbuf.len == 4) &&
                            /* LINTED pointer cast */
                            ((*(int32_t *)arg.ctlbuf.buf) == T_CONN_CON))
                                outstate = T_OUTCON;
                        break;
                case T_INREL:
                        /*
                         * The following heuristic is to handle data
                         * ahead of T_ORDREL_IND indications that might
                         * be at the stream head waiting to be
                         * read (T_DATA_IND or M_DATA)
                         */
                        if (((arg.ctlbuf.len == 4) &&
                            /* LINTED pointer cast */
                            ((*(int32_t *)arg.ctlbuf.buf) == T_DATA_IND)) ||
                            ((arg.ctlbuf.len == 0) && arg.databuf.len)) {
                                outstate = T_DATAXFER;
                        }
                        break;
                default:
                        break;
                }
        }
        return (outstate);
}

/*
 * Assumes caller has blocked signals at least in this thread (for safe
 * malloc/free operations)
 */
static int
_t_cbuf_alloc(struct _ti_user *tiptr, char **retbuf)
{
        unsigned        size2;

        assert(MUTEX_HELD(&tiptr->ti_lock));
        size2 = tiptr->ti_ctlsize; /* same size as default ctlbuf */

        if ((*retbuf = malloc(size2)) == NULL) {
                return (-1);
        }
        return (size2);
}


/*
 * Assumes caller has blocked signals at least in this thread (for safe
 * malloc/free operations)
 */
int
_t_rbuf_alloc(struct _ti_user *tiptr, char **retbuf)
{
        unsigned        size1;

        assert(MUTEX_HELD(&tiptr->ti_lock));
        size1 = tiptr->ti_rcvsize; /* same size as default rcvbuf */

        if ((*retbuf = malloc(size1)) == NULL) {
                return (-1);
        }
        return (size1);
}

/*
 * Free lookbuffer structures and associated resources
 * Assumes ti_lock held for MT case.
 */
static void
_t_free_lookbufs(struct _ti_user *tiptr)
{
        struct _ti_lookbufs *tlbs, *prev_tlbs, *head_tlbs;

        /*
         * Assertion:
         * The structure lock should be held or the global list
         * manipulation lock. The assumption is that nothing
         * else can access the descriptor since global list manipulation
         * lock is held so it is OK to manipulate fields without the
         * structure lock
         */
        assert(MUTEX_HELD(&tiptr->ti_lock) || MUTEX_HELD(&_ti_userlock));

        /*
         * Free only the buffers in the first lookbuf
         */
        head_tlbs = &tiptr->ti_lookbufs;
        if (head_tlbs->tl_lookdbuf != NULL) {
                free(head_tlbs->tl_lookdbuf);
                head_tlbs->tl_lookdbuf = NULL;
        }
        free(head_tlbs->tl_lookcbuf);
        head_tlbs->tl_lookcbuf = NULL;
        /*
         * Free the node and the buffers in the rest of the
         * list
         */

        tlbs = head_tlbs->tl_next;
        head_tlbs->tl_next = NULL;

        while (tlbs != NULL) {
                if (tlbs->tl_lookdbuf != NULL)
                        free(tlbs->tl_lookdbuf);
                free(tlbs->tl_lookcbuf);
                prev_tlbs = tlbs;
                tlbs = tlbs->tl_next;
                free(prev_tlbs);
        }
}

/*
 * Free lookbuffer event list head.
 * Consume current lookbuffer event
 * Assumes ti_lock held for MT case.
 * Note: The head of this list is part of the instance
 * structure so the code is a little unorthodox.
 */
void
_t_free_looklist_head(struct _ti_user *tiptr)
{
        struct _ti_lookbufs *tlbs, *next_tlbs;

        tlbs = &tiptr->ti_lookbufs;

        if (tlbs->tl_next) {
                /*
                 * Free the control and data buffers
                 */
                if (tlbs->tl_lookdbuf != NULL)
                        free(tlbs->tl_lookdbuf);
                free(tlbs->tl_lookcbuf);
                /*
                 * Replace with next lookbuf event contents
                 */
                next_tlbs = tlbs->tl_next;
                tlbs->tl_next = next_tlbs->tl_next;
                tlbs->tl_lookcbuf = next_tlbs->tl_lookcbuf;
                tlbs->tl_lookclen = next_tlbs->tl_lookclen;
                tlbs->tl_lookdbuf = next_tlbs->tl_lookdbuf;
                tlbs->tl_lookdlen = next_tlbs->tl_lookdlen;
                free(next_tlbs);
                /*
                 * Decrement the flag - should never get to zero.
                 * in this path
                 */
                tiptr->ti_lookcnt--;
                assert(tiptr->ti_lookcnt > 0);
        } else {
                /*
                 * No more look buffer events - just clear the flag
                 * and leave the buffers alone
                 */
                assert(tiptr->ti_lookcnt == 1);
                tiptr->ti_lookcnt = 0;
        }
}

/*
 * Discard lookbuffer events.
 * Assumes ti_lock held for MT case.
 */
void
_t_flush_lookevents(struct _ti_user *tiptr)
{
        struct _ti_lookbufs *tlbs, *prev_tlbs;

        /*
         * Leave the first nodes buffers alone (i.e. allocated)
         * but reset the flag.
         */
        assert(MUTEX_HELD(&tiptr->ti_lock));
        tiptr->ti_lookcnt = 0;
        /*
         * Blow away the rest of the list
         */
        tlbs = tiptr->ti_lookbufs.tl_next;
        tiptr->ti_lookbufs.tl_next = NULL;
        while (tlbs != NULL) {
                if (tlbs->tl_lookdbuf != NULL)
                        free(tlbs->tl_lookdbuf);
                free(tlbs->tl_lookcbuf);
                prev_tlbs = tlbs;
                tlbs = tlbs->tl_next;
                free(prev_tlbs);
        }
}


/*
 * This routine checks if the receive. buffer in the instance structure
 * is available (non-null). If it is, the buffer is acquired and marked busy
 * (null). If it is busy (possible in MT programs), it allocates a new
 * buffer and sets a flag indicating new memory was allocated and the caller
 * has to free it.
 */
int
_t_acquire_ctlbuf(
        struct _ti_user *tiptr,
        struct strbuf *ctlbufp,
        int *didallocp)
{
        *didallocp = 0;

        ctlbufp->len = 0;
        if (tiptr->ti_ctlbuf) {
                ctlbufp->buf = tiptr->ti_ctlbuf;
                tiptr->ti_ctlbuf = NULL;
                ctlbufp->maxlen = tiptr->ti_ctlsize;
        } else {
                /*
                 * tiptr->ti_ctlbuf is in use
                 * allocate new buffer and free after use.
                 */
                if ((ctlbufp->maxlen = _t_cbuf_alloc(tiptr,
                    &ctlbufp->buf)) < 0) {
                        t_errno = TSYSERR;
                        return (-1);
                }
                *didallocp = 1;
        }
        return (0);
}

/*
 * This routine checks if the receive buffer in the instance structure
 * is available (non-null). If it is, the buffer is acquired and marked busy
 * (null). If it is busy (possible in MT programs), it allocates a new
 * buffer and sets a flag indicating new memory was allocated and the caller
 * has to free it.
 * Note: The receive buffer pointer can also be null if the transport
 * provider does not support connect/disconnect data, (e.g. TCP) - not
 * just when it is "busy". In that case, ti_rcvsize will be 0 and that is
 * used to instantiate the databuf which points to a null buffer of
 * length 0 which is the right thing to do for that case.
 */
int
_t_acquire_databuf(
        struct _ti_user *tiptr,
        struct strbuf *databufp,
        int *didallocp)
{
        *didallocp = 0;

        databufp->len = 0;
        if (tiptr->ti_rcvbuf) {
                assert(tiptr->ti_rcvsize != 0);
                databufp->buf = tiptr->ti_rcvbuf;
                tiptr->ti_rcvbuf = NULL;
                databufp->maxlen = tiptr->ti_rcvsize;
        } else if (tiptr->ti_rcvsize == 0) {
                databufp->buf = NULL;
                databufp->maxlen = 0;
        } else {
                /*
                 * tiptr->ti_rcvbuf is in use
                 * allocate new buffer and free after use.
                 */
                if ((databufp->maxlen = _t_rbuf_alloc(tiptr,
                    &databufp->buf)) < 0) {
                        t_errno = TSYSERR;
                        return (-1);
                }
                *didallocp = 1;
        }
        return (0);
}

/*
 * This routine requests timod to look for any expedited data
 * queued in the "receive buffers" in the kernel. Used for XTI
 * t_look() semantics for transports that send expedited data
 * data inline (e.g TCP).
 * Returns -1 for failure
 * Returns 0 for success
 *      On a successful return, the location pointed by "expedited_queuedp"
 *      contains
 *              0 if no expedited data is found queued in "receive buffers"
 *              1 if expedited data is found queued in "receive buffers"
 */

int
_t_expinline_queued(int fd, int *expedited_queuedp)
{
        union {
                struct ti_sync_req ti_req;
                struct ti_sync_ack ti_ack;
                char pad[128];
        } ioctl_data;
        void *ioctlbuf = &ioctl_data; /* for TI_SYNC with room to grow */
                            /* preferred location first local variable */
                            /* see note in _t_create above */
        struct ti_sync_req *tsrp = (struct ti_sync_req *)ioctlbuf;
        struct ti_sync_ack *tsap = (struct ti_sync_ack *)ioctlbuf;
        int rval, retlen;

        *expedited_queuedp = 0;
        /* request info on rq expinds  */
        tsrp->tsr_flags = TSRF_IS_EXP_IN_RCVBUF;
        do {
                rval = _t_do_ioctl(fd, ioctlbuf,
                    (int)sizeof (struct T_info_req), TI_SYNC, &retlen);
        } while (rval < 0 && errno == EINTR);

        if (rval < 0)
                return (-1);

        /*
         * This is a "less than" check as "struct ti_sync_ack" returned by
         * TI_SYNC can grow in size in future kernels. If/when a statically
         * linked application is run on a future kernel, it should not fail.
         */
        if (retlen < (int)sizeof (struct ti_sync_ack)) {
                t_errno = TSYSERR;
                errno = EIO;
                return (-1);
        }
        if (tsap->tsa_flags & TSAF_EXP_QUEUED)
                *expedited_queuedp = 1;
        return (0);
}

/*
 * Support functions for use by functions that do scatter/gather
 * like t_sndv(), t_rcvv() etc..follow below.
 */

/*
 * _t_bytecount_upto_intmax() :
 *          Sum of the lengths of the individual buffers in
 *          the t_iovec array. If the sum exceeds INT_MAX
 *          it is truncated to INT_MAX.
 */
unsigned int
_t_bytecount_upto_intmax(const struct t_iovec *tiov, unsigned int tiovcount)
{
        size_t nbytes;
        int i;

        nbytes = 0;
        for (i = 0; i < tiovcount && nbytes < INT_MAX; i++) {
                if (tiov[i].iov_len >= INT_MAX) {
                        nbytes = INT_MAX;
                        break;
                }
                nbytes += tiov[i].iov_len;
        }

        if (nbytes > INT_MAX)
                nbytes = INT_MAX;

        return ((unsigned int)nbytes);
}

/*
 * Gather the data in the t_iovec buffers, into a single linear buffer
 * starting at dataptr. Caller must have allocated sufficient space
 * starting at dataptr. The total amount of data that is gathered is
 * limited to INT_MAX. Any remaining data in the t_iovec buffers is
 * not copied.
 */
void
_t_gather(char *dataptr, const struct t_iovec *tiov, unsigned int tiovcount)
{
        char *curptr;
        unsigned int cur_count;
        unsigned int nbytes_remaining;
        int i;

        curptr = dataptr;
        cur_count = 0;

        nbytes_remaining = _t_bytecount_upto_intmax(tiov, tiovcount);
        for (i = 0; i < tiovcount && nbytes_remaining != 0; i++) {
                if (tiov[i].iov_len <= nbytes_remaining)
                        cur_count = (int)tiov[i].iov_len;
                else
                        cur_count = nbytes_remaining;
                (void) memcpy(curptr, tiov[i].iov_base, cur_count);
                curptr += cur_count;
                nbytes_remaining -= cur_count;
        }
}

/*
 * Scatter the data from the single linear buffer at pdatabuf->buf into
 * the t_iovec buffers.
 */
void
_t_scatter(struct strbuf *pdatabuf, struct t_iovec *tiov, int tiovcount)
{
        char *curptr;
        unsigned int nbytes_remaining;
        unsigned int curlen;
        int i;

        /*
         * There cannot be any uncopied data leftover in pdatabuf
         * at the conclusion of this function. (asserted below)
         */
        assert(pdatabuf->len <= _t_bytecount_upto_intmax(tiov, tiovcount));
        curptr = pdatabuf->buf;
        nbytes_remaining = pdatabuf->len;
        for (i = 0; i < tiovcount && nbytes_remaining != 0; i++) {
                if (tiov[i].iov_len < nbytes_remaining)
                        curlen = (unsigned int)tiov[i].iov_len;
                else
                        curlen = nbytes_remaining;
                (void) memcpy(tiov[i].iov_base, curptr, curlen);
                curptr += curlen;
                nbytes_remaining -= curlen;
        }
}

/*
 * Adjust the iovec array, for subsequent use. Examine each element in the
 * iovec array,and zero out the iov_len if the buffer was sent fully.
 * otherwise the buffer was only partially sent, so adjust both iov_len and
 * iov_base.
 *
 */
void
_t_adjust_iov(int bytes_sent, struct iovec *iov, int *iovcountp)
{

        int i;

        for (i = 0; i < *iovcountp && bytes_sent; i++) {
                if (iov[i].iov_len == 0)
                        continue;
                if (bytes_sent < iov[i].iov_len)
                        break;
                else {
                        bytes_sent -= iov[i].iov_len;
                        iov[i].iov_len = 0;
                }
        }
        iov[i].iov_len -= bytes_sent;
        iov[i].iov_base += bytes_sent;
}

/*
 * Copy the t_iovec array to the iovec array while taking care to see
 * that the sum of the buffer lengths in the result is not more than
 * INT_MAX. This function requires that T_IOV_MAX is no larger than
 * IOV_MAX. Otherwise the resulting array is not a suitable input to
 * writev(). If the sum of the lengths in t_iovec is zero, so is the
 * resulting iovec.
 */
void
_t_copy_tiov_to_iov(const struct t_iovec *tiov, int tiovcount,
    struct iovec *iov, int *iovcountp)
{
        int i;
        unsigned int nbytes_remaining;

        nbytes_remaining = _t_bytecount_upto_intmax(tiov, tiovcount);
        i = 0;
        do {
                iov[i].iov_base = tiov[i].iov_base;
                if (tiov[i].iov_len > nbytes_remaining)
                        iov[i].iov_len = nbytes_remaining;
                else
                        iov[i].iov_len  = tiov[i].iov_len;
                nbytes_remaining -= iov[i].iov_len;
                i++;
        } while (nbytes_remaining != 0 && i < tiovcount);

        *iovcountp = i;
}

/*
 * Routine called after connection establishment on transports where
 * connection establishment changes certain transport attributes such as
 * TIDU_size
 */
int
_t_do_postconn_sync(int fd, struct _ti_user *tiptr)
{
        union {
                struct T_capability_req tc_req;
                struct T_capability_ack tc_ack;
        } ioctl_data;

        void *ioctlbuf = &ioctl_data;
        int expected_acksize;
        int retlen, rval;
        struct T_capability_req *tc_reqp = (struct T_capability_req *)ioctlbuf;
        struct T_capability_ack *tc_ackp = (struct T_capability_ack *)ioctlbuf;
        struct T_info_ack *tiap;

        /*
         * This T_CAPABILITY_REQ should not fail, even if it is unsupported
         * by the transport provider. timod will emulate it in that case.
         */
        tc_reqp->PRIM_type = T_CAPABILITY_REQ;
        tc_reqp->CAP_bits1 = TC1_INFO;
        rval = _t_do_ioctl(fd, (char *)ioctlbuf,
            (int)sizeof (struct T_capability_ack), TI_CAPABILITY, &retlen);
        expected_acksize = (int)sizeof (struct T_capability_ack);

        if (rval < 0)
                return (-1);

        /*
         * T_capability TPI messages are extensible and can grow in future.
         * However timod will take care of returning no more information
         * than what was requested, and truncating the "extended"
         * information towards the end of the T_capability_ack, if necessary.
         */
        if (retlen != expected_acksize) {
                t_errno = TSYSERR;
                errno = EIO;
                return (-1);
        }

        /*
         * The T_info_ack part of the T_capability_ack is guaranteed to be
         * present only if the corresponding TC1_INFO bit is set
         */
        if ((tc_ackp->CAP_bits1 & TC1_INFO) == 0) {
                t_errno = TSYSERR;
                errno = EPROTO;
                return (-1);
        }

        tiap = &tc_ackp->INFO_ack;
        if (tiap->PRIM_type != T_INFO_ACK) {
                t_errno = TSYSERR;
                errno = EPROTO;
                return (-1);
        }

        /*
         * Note: Sync with latest information returned in "struct T_info_ack
         * but we deliberately not sync the state here as user level state
         * construction here is not required, only update of attributes which
         * may have changed because of negotations during connection
         * establsihment
         */
        assert(tiap->TIDU_size > 0);
        tiptr->ti_maxpsz = tiap->TIDU_size;
        assert(tiap->TSDU_size >= T_INVALID);
        tiptr->ti_tsdusize = tiap->TSDU_size;
        assert(tiap->ETSDU_size >= T_INVALID);
        tiptr->ti_etsdusize = tiap->ETSDU_size;
        assert(tiap->CDATA_size >= T_INVALID);
        tiptr->ti_cdatasize = tiap->CDATA_size;
        assert(tiap->DDATA_size >= T_INVALID);
        tiptr->ti_ddatasize = tiap->DDATA_size;
        tiptr->ti_prov_flag = tiap->PROVIDER_flag;

        return (0);
}