/*++

Copyright (c) 1991  Microsoft Corporation

Module Name:

    sh_irp.c

Abstract:

    This source file contains the functions that convert between NT IRPs
    and STREAMS messages.

    Most functions in this module are based on identically named routines
    in the SpiderStreams emulator source, stremul/msgrtns.c.

Author:

    Eric Chin (ericc)           August 16, 1991

Revision History:

--*/
#include "sh_inc.h"


/*
 * Local (Private) Functions
 */
STATIC void
mp_buf_free(
    IN char    *p
    );

STATIC int
mptoirp(
    IN mblk_t          *mp,
    IN PIRP             irp
    );



VOID
SHpGenReply(
    IN PIRP irp,
    IN int retval,
    IN int MyErrno
    )

/*++

Routine Description:

    This function is called to complete IRPs that convey generic STREAMS
    API's: ioctl(I_FDINSERT), ioctl(I_STR), putmsg(), ....

    By generic, we mean it returns a return value and possible an errno.

Arguments:

    irp     - irp to complete
    retval  - return value of the ioctl(,I_FDINSERT,) or putmsg()
    errno   - POSIX error value, if any

Return Value:

    none.

--*/

{
    PSTRM_ARGS_OUT outptr;
    PIO_STACK_LOCATION pIrpSp;

    IF_STRMDBG(CALL) {
        STRMTRACE(("SHEAD: SHpGenReply(irp = %lx, %lx, %lx) entered\n",
                    irp, retval, MyErrno));
    }

    pIrpSp = IoGetCurrentIrpStackLocation(irp);

    // Check size of output buffer.
    if (pIrpSp->Parameters.DeviceIoControl.OutputBufferLength < sizeof(STRM_ARGS_OUT))
    {
        shortreply(irp, STATUS_BUFFER_TOO_SMALL, 0);
        return;
    }

    //
    // have IopCompleteRequest() copy the following back to the user's
    // output buffer, laid out as:
    //
    //  typedef struct _STRM_ARGS_OUT_ {        // generic return parameters
    //      int     a_retval;                   //  return value
    //      int     a_errno;                    //  errno if retval == -1
    //
    //  } STRM_ARGS_OUT, *PSTRM_ARGS_OUT;
    //
    outptr           = (PSTRM_ARGS_OUT) irp->AssociatedIrp.SystemBuffer;
    outptr->a_retval = retval;
    outptr->a_errno  = MyErrno;

    shortreply(irp, STATUS_SUCCESS, sizeof(STRM_ARGS_OUT));

    IF_STRMDBG(CALL) {
        STRMTRACE(("SHEAD: SHpGenReply(irp = %lx) done\n", irp));
    }
    return;

} // SHpGenReply




int
iocreply(
    IN mblk_t *mp,
    IN PIRP    irp
    )

/*++

Routine Description:

    This function sends the reply to an M_IOCTL message back to the user,
    reverting a STREAMS message into an NT irp.  It is loosely based on the
    SpiderStreams functions, iocreply() and mptomsg(), in stremul/msgrtns.c.

    It should be called after the appropriate routine has taken the message
    off the queue.  If it fails to send a message, it returns a negative
    value.

    Acquire the lock of ms->e_strm before calling, and release it afterwards !!

Arguments:

    mp      -  pointer to the message to reply to
    irp     -  pointer to the IRP

Return Value:

    -1  - no message is ready to be sent to the user
    -2  - failed to send a message to the user


--*/

{
    mblk_t *tmp;
    int length;
    char *outbuf;
    int *pretval;
    int nbytes = 0;
    struct iocblk *iocp;
    struct strioctl *striop;


    IF_STRMDBG(CALL) {
        STRMTRACE(("SHEAD: iocreply(mp = %lx, irp = %lx) entered\n", mp, irp));
    }

    if (!mp) {
        return(-1);
    }
    outbuf   = irp->AssociatedIrp.SystemBuffer;
    pretval  = (int *) outbuf;
    *pretval = 0;

    switch (mp->b_datap->db_type) {

    /*
     * for ioctl(I_STR), arrange the return parameters contiguously in outbuf
     * in the format:
     *
     *      int return value            (required)
     *      union {
     *          int errno;              (required)
     *          struct strioctl;        (ic_cmd is not valid !!)
     *      }
     *      int                         (required)
     *      int                         (required)
     *      ic_dp buffer                (optional)
     */
    case M_IOCACK:
        iocp           = (struct iocblk *) mp->b_rptr;
        *pretval       = iocp->ioc_rval;
        striop         = (struct strioctl *) ((int *) outbuf + 1);
        striop->ic_len = 0;
        outbuf         = (char *) (striop + 1) + 2 * sizeof(int);

        for (tmp = mp->b_cont; tmp; tmp = tmp->b_cont) {
            ASSERT(tmp->b_datap->db_type == M_DATA);
            length = (int)(tmp->b_wptr - tmp->b_rptr);

            ASSERT(length >= 0);
            striop->ic_len += length;

            RtlCopyMemory(outbuf, tmp->b_rptr, length);
            outbuf += length;
        }
        nbytes = (int) ( outbuf - (char *) irp->AssociatedIrp.SystemBuffer );
        break;

    case M_IOCNAK:
        iocp           = (struct iocblk *) mp->b_rptr;
        *pretval       = -1;
        *(pretval + 1) = iocp->ioc_error;
        nbytes         = 2 * sizeof(int);
        break;

    default:
        ASSERT(0);                          /* shouldn't come here */
    }
    shortreply(irp, STATUS_SUCCESS, nbytes);
    freemsg(mp);

    IF_STRMDBG(CALL) {
        STRMTRACE(("SHEAD: iocreply(irp = %lx) nbytes = %lx, completed ok\n",
                        irp, nbytes));
    }
    return(0);

} // iocreply



mblk_t *
irptomp(
    IN PIRP     irp,
    IN int      pri,
    IN int      ctlsize,
    IN int      datasize,
    IN char    *mbuf
    )

/*++

Routine Description:

    This function converts the buffers associated with an NT irp into a
    STREAMS message.  It is based on the SpiderSTREAMS routine, msgtomp().

    The first block of the message created is either an M_PROTO or M_DATA
    block.  To make it M_PCPROTO, M_IOCTL, ..., set it yourself after
    this function returns !!

Arguments:

    irp      - pointer to the IRP.
    pri      - buffer allocation priority.  BPRI_LO, BPRI_MED or BPRI_HI.
    ctlsize  - length of control part message
    datasize - length of data part of message
    mbuf     - pointer to a contiguous chunk containing first the control
                part of the message, if any, and then the data part.

Return Value:

    pointer to the resulting STREAMS message, or NULL if unsuccessful.

--*/

{
    unsigned char *extra;
    mblk_t *mp  = (mblk_t *) NULL;
    mblk_t *cmp = (mblk_t *) NULL;
    frtn_t fr_rtn;

    IF_STRMDBG(CALL) {
        STRMTRACE(("SHEAD: irptomp(clen, dlen, mbuf = %lx, %lx, %lx) entered\n",
                                                ctlsize, datasize, mbuf));
    }

    /*
     * special case for constructing a zero length message.
     */
    if ((max(ctlsize, 0) + max(datasize, 0)) == 0) {

        mp = allocb(0, pri);

        if (!mp) {

            IF_STRMDBG(TERSE) {
                STRMTRACE(("SHEAD: irptomp(), allocb of 0 failed\n"));
            }

            return((mblk_t *) NULL);
        }
        ASSERT(mp->b_datap->db_type == M_DATA);

        if (ctlsize != -1) {
            mp->b_datap->db_type = M_PROTO;
        }
        ASSERT(mp->b_wptr == mp->b_rptr);
        return(mp);
    }
    fr_rtn.free_func = mp_buf_free;

    if (ctlsize >= 0) {

        if (ctlsize) {

            extra = ExAllocatePool(NonPagedPool, ctlsize);

            if (!extra) {
                return((mblk_t *) NULL);
            }
            RtlCopyMemory(extra, mbuf, ctlsize);

            fr_rtn.free_arg = (char *) extra;
            cmp = esballoc(extra, ctlsize, pri, &fr_rtn);
        }
        else {
            cmp = allocb(0, pri);
        }
        if (!cmp) {

            IF_STRMDBG(TERSE) {
                STRMTRACE(("SHEAD: irptomp(), esballoc %x failed\n", ctlsize));
            }

            return((mblk_t *) NULL);
        }
        ASSERT(cmp->b_datap->db_type == M_DATA);
        cmp->b_datap->db_type = M_PROTO;

        ASSERT(cmp->b_wptr == cmp->b_rptr);
        cmp->b_wptr += ctlsize;
    }

    if (datasize >= 0) {

        if (datasize) {
            extra = ExAllocatePool(NonPagedPool, datasize);

            if (!extra) {
                if (cmp) {
                    freemsg(cmp);
                }
                return((mblk_t *) NULL);
            }

            RtlCopyMemory(extra,
                            (ctlsize <= 0) ? mbuf : mbuf + ctlsize, datasize);

            fr_rtn.free_arg = (char *) extra;

            mp = esballoc(extra, datasize, pri, &fr_rtn);
            }
        else {
            mp = allocb(0, pri);
        }
        if (!mp) {

            IF_STRMDBG(TERSE) {
                STRMTRACE(("SHEAD: irptomp(), esballoc %x failed\n", ctlsize));
            }

            if (cmp) {
                freemsg(cmp);
            }
            return((mblk_t *) NULL);
        }
        ASSERT(mp->b_datap->db_type == M_DATA);
        ASSERT(mp->b_wptr == mp->b_rptr);
        mp->b_wptr += datasize;
    }
    if (cmp) {
        cmp->b_cont = mp;
        mp          = cmp;
    }

    IF_STRMDBG(CALL) {
        STRMTRACE(("SHEAD: irptomp(mbuf = %lx) returns, mp = %lx\n", mbuf, mp));
    }
    return(mp);

} // irptomp



STATIC void
mp_buf_free(
    IN char    *p
    )
{
    IF_STRMDBG(CALL) {
        STRMTRACE(("SHEAD: mp_buf_free(%lx) entered\n", p));
    }
    if (p) {
        ExFreePool(p);
    }

    IF_STRMDBG(CALL) {
        STRMTRACE(("SHEAD: mp_buf_free(%lx) completed\n", p));
    }
    return;

} // mp_buf_free



STATIC int
mptoirp(
    IN mblk_t  *mp,
    IN PIRP     irp
    )

/*++

Routine Description:

    This function converts a STREAMS message into an NT irp.  It is based
    on the SpiderStreams routine, mptomsg(), in stremul/msgrtns.c.

    It should be called after the appropriate routine has taken the message
    off the queue.  If it fails to send a message, it returns a negative
    value.

    The caller must free the STREAMS message, mp.  This function doesn't !!

Arguments:

    mp
    irp

Return Value:

    number of bytes copied back to user space, or a negative value if
    unsuccessful.

--*/

{
    char *outbuf;
    int *pretval;
    int length, nbytes;
    struct strbuf *strbufp;

    IF_STRMDBG(CALL) {
        STRMTRACE(("SHEAD: mptoirp(mp = %lx, irp = %lx) entered\n", mp, irp));
    }

    /*
     * for getmsg(), arrange the return parameters contiguously in outbuf
     * in the format:
     *
     *      int return value            (required)
     *      flags / errno               (required)
     *      struct strbuf ctrlbuf       (required)
     *      struct strbuf databuf       (required)
     *      ctrl buffer                 (optional)
     *      data buffer                 (optional)
     */
    outbuf   = irp->AssociatedIrp.SystemBuffer;
    pretval  = (int *) outbuf;
    strbufp  = (struct strbuf *) (pretval + 2);     /* struct strbuf ctrlbuf */
    outbuf   = (char *) (strbufp + 2);              /* ctrl buffer */

    /*
     * ensure that the return value is copied back to the user-level runtime.
     * It was zeroed in ShDispGetmsg(), and the MORECTL, MOREDATA bits may
     * have set by st_getmsg().
     */
    nbytes = 2 * sizeof(int);

    switch (mp->b_datap->db_type) {

    case M_PCPROTO:
        *(pretval + 1) = RS_HIPRI;                  /* flags */
        goto doproto;

    case M_PROTO:
        *(pretval + 1) = 0;                         /* flags */

    doproto:
        length  = (int)(mp->b_wptr - mp->b_rptr);

        if (strbufp->maxlen < length) {
            length    = strbufp->maxlen;
            *pretval |= MORECTL;
        }
        strbufp->len = length;
        RtlCopyMemory(outbuf, mp->b_rptr, strbufp->len);

        mp = mp->b_cont;
        goto dodata;

    case M_DATA:
        *(pretval + 1) = 0;                         /* flags */
        strbufp->len   = 0;                         /* ctrlbuf->len */

    dodata:
        outbuf += strbufp->len;

        (++strbufp)->len = 0;

        for (; mp; mp = mp->b_cont) {
            ASSERT(mp->b_datap->db_type == M_DATA);
            length = (int)(mp->b_wptr - mp->b_rptr);

            ASSERT(length >= 0);

            if (strbufp->maxlen < length) {
                length    = strbufp->maxlen;
                *pretval |= MOREDATA;
            }
            RtlCopyMemory(outbuf, mp->b_rptr, length);
            outbuf       += length;
            strbufp->len += length;

            if ((strbufp->maxlen -= length) == 0) {
                break;
            }
        }
        nbytes = (int)( outbuf - (char *) irp->AssociatedIrp.SystemBuffer );
        break;

    default:
        IF_STRMDBG(TERSE) {
            STRMTRACE(("SHEAD: mptoirp(), unexpected db_type = %x\n",
                                                    mp->b_datap->db_type));
        }
        ASSERT(0);                              /* shouldn't come here */
        KeBugCheck(STREAMS_INTERNAL_ERROR);
        break;
    }

    /*
     * no matter what, we always pass back the return value and the flags
     * to the user-level runtime.
     */
    ASSERT(nbytes >= 2 * sizeof(int));
    shortreply(irp, STATUS_SUCCESS, nbytes);

    IF_STRMDBG(CALL) {
        STRMTRACE(("SHEAD: mptoirp(irp = %lx), %lx, completed\n", irp, nbytes));
    }
    return(nbytes);

} // mptoirp



int
msgreply(
    IN STREAM_ENDPOINT *ms,
    IN PIRP             irp
    )

/*++

Routine Description:

    This function gets a STREAMS message to complete an IRP representing
    a getmsg().  It is based on the SpiderStreams emulator function of the
    same name.

    Lock the stream, ms->e_strm, before calling this function, and unlock
    it after this function returns !!

Arguments:

    ms      -  stream endpoint from whose read queue to get the message from
    irp     -  IRP to complete

Return Value:

     0  - successful completion
    -1  - no message is ready to be sent to the user
    -2  - failed to send a message to the user


--*/

{
    int ret;
    mblk_t *mp;
    int more = 0;
    struct strbuf *strbufp;
    int ctlsize, datasize, flags, *pretval, remains;

    /*
     * the arguments are marshalled in one contiguous chunk, laid out as:
     *
     *      an unused int             (required)
     *      flags                     (required)
     *      struct strbuf ctrlbuf     (required)
     *      struct strbuf databuf     (required)
     */
    pretval  = (int *) irp->AssociatedIrp.SystemBuffer;
    flags    = * (pretval + 1);
    strbufp  = (struct strbuf *) (pretval + 2);
    ctlsize  = strbufp->maxlen;
    datasize = (++strbufp)->maxlen;

    /*
     * st_getmsg() may set MORECTL and/or MOREDATA in *pretval; we must
     * return it to the user-level runtime !!
     */
    ret = st_getmsg(ms->e_strm, ctlsize, datasize, &flags, pretval,
                        &mp, &remains);
    if (ret) {
        ASSERT(0);
        shortreply(irp, STATUS_SUCCESS, 0);
        return(0);
    }

    if (!mp) {
        return(-1);
    }

    //
    // Unlike SpiderSTREAMS, our mptoirp() function never fails !!  Hence
    // the assertion.
    //
    if (mptoirp(mp, irp) < 0) {
        ASSERT(0);
        st_putback(ms->e_strm, mp, remains);
        return(-2);
    }

    /*
     * Spider frees mp by chasing mp->b_next.  Why ?
     */
    ASSERT(!(mp->b_next));

    freemsg(mp);
    return(0);

} // msgreply



int
shortreply(
    IN PIRP     irp,
    IN int      status,
    IN int      nbytes
    )

/*++

Routine Description:

    This function completes an IRP, and arranges for return parameters,
    if any, to be copied.

    Although somewhat a misnomer, this function is named after a similar
    function in the SpiderSTREAMS emulator.

Arguments:

    irp     -  pointer to the IRP to complete
    status  -  completion status of the IRP
    nbytes  -  number of bytes to return

Return Value:

    number of bytes copied back to the user.

--*/

{
    CCHAR priboost;

    IF_STRMDBG(CALL) {
        STRMTRACE((
        "SHEAD: shortreply(irp, status, nbytes = %lx, %lx, %lx) entered\n",
                    irp, status, nbytes));
    }

    //
    // set the irp's cancel routine to NULL, or the system may bugcheck
    // with the bugcode, CANCEL_STATE_IN_COMPLETED_IRP !!
    //
    // ref: IoCancelIrp(), ...\ntos\io\iosubs.c.
    //
    //
    IoAcquireCancelSpinLock(&irp->CancelIrql);
    IoSetCancelRoutine(irp, NULL);
    IoReleaseCancelSpinLock(irp->CancelIrql);


    //
    // irp->IoStatus.Information is meaningful only for STATUS_SUCCESS
    //
    ASSERT(!nbytes || (status == STATUS_SUCCESS));

    irp->IoStatus.Information = nbytes;
    irp->IoStatus.Status      = status;

    priboost = (CCHAR) ((status == STATUS_SUCCESS) ?
                        IO_NETWORK_INCREMENT : IO_NO_INCREMENT);

    IoCompleteRequest(irp, priboost);

    return(nbytes);

} // shortreply