windows-nt/Source/XPSP1/NT/net/streams/sys/sh_put.c

796 lines
21 KiB
C
Raw Normal View History

2020-09-26 03:20:57 -05:00
/*++
Copyright (c) 1991 Microsoft Corporation
Module Name:
sh_put.c
Abstract:
This source file contains those functions of the Stream Head Driver that
deal with sending messages down a stream.
It is based on the SpiderSTREAMS source, stremul\msgsrvr.c.
Author:
Eric Chin (ericc) August 16, 1991
Revision History:
Notes:
The write error state of a stream is represented by ms->e_werror. Once
set, this is never reset. This corresponds to the STREAMS semantics as
defined by AT&T. Once a user is notified of a write error on a stream,
about the only recourse is to close the stream.
--*/
#include "sh_inc.h"
/*
* Private Functions
*/
STATIC VOID
cancel_put(
IN PDEVICE_OBJECT device,
IN PIRP irp
);
STATIC NTSTATUS
do_putmsg(
IN PIRP irp,
IN BOOLEAN from_queue,
IN int *spl_levelp,
OUT BOOLEAN *pmore
);
STATIC queue_t *
handle_to_queue (
IN HANDLE handle
);
NTSTATUS
SHDispFdInsert(
IN PIRP irp,
IN PIO_STACK_LOCATION irpsp
)
/*++
Routine Description:
This routine is called to put a message down a stream. It is based on
the SpiderStreams emulator's msgserver() routine.
This routine merely peels open the IRP, checks the putmsg() arguments
for consistency, locks the appropriate stream, and then calls
do_putmsg(), which does the bulk of the work.
Arguments:
irp - pointer to the IRP representing this request
irpsp - pointer to the IRP stack location for this request
Return Value:
An NT status code. Whatever the return value, this function will arrange
for the IRP to be completed.
--*/
{
int spl_level;
queue_t *iq = NULL;
PSTREAM_ENDPOINT ms;
PPUTMSG_ARGS_IN inbuf;
struct strbuf *ctrlptr, *dataptr;
ASSERT((irpsp->Parameters.DeviceIoControl.IoControlCode & 0x3) ==
METHOD_BUFFERED);
ms = (STREAM_ENDPOINT *) irpsp->FileObject->FsContext;
if (irpsp->Parameters.DeviceIoControl.InputBufferLength <
sizeof(PUTMSG_ARGS_IN) - 1) {
IF_STRMDBG(TERSE) {
STRMTRACE(("SHEAD: SHDispFdInsert(%lx) insufficient nbytes = %lx\n",
irp, irpsp->Parameters.DeviceIoControl.InputBufferLength));
}
shortreply(irp, STATUS_INVALID_PARAMETER, 0);
return(STATUS_INVALID_PARAMETER);
}
//
// the caller marshalled the input arguments contiguously thus:
//
// typedef struct _PUTMSG_ARGS_IN_ {
// int a_iocode; // I_FDINSERT
// long a_flags; // 0 | RS_HIPRI
// struct strbuf a_ctlbuf; // (required)
// struct strbuf a_databuf; // (required)
// HANDLE a_insert.i_fildes; // (required)
// int a_offset; // (required)
// char a_stuff[1]; // s_ctlbuf.buf (required)
// // s_databuf.buf (optional)
// } PUTMSG_ARGS_IN, *PPUTMSG_ARGS_IN;
//
// When the message has no data part, the caller must have set
// a_databuf.len = -1 !!
//
//
inbuf = (PPUTMSG_ARGS_IN) irp->AssociatedIrp.SystemBuffer;
ctrlptr = &(inbuf->a_ctlbuf);
dataptr = &(inbuf->a_databuf);
IF_STRMDBG(CALL) {
STRMTRACE(("SHEAD: SHDispFdInsert(flags, clen, dlen = %d, %lx, %lx)\n",
inbuf->a_flags, ctrlptr->len, dataptr->len));
}
if (((inbuf->a_flags != 0) && (inbuf->a_flags != RS_HIPRI)) ||
(ctrlptr->len <= 0)) {
SHpGenReply(irp, -1, EINVAL);
return(STATUS_SUCCESS);
}
if ((inbuf->a_offset < 0) ||
(inbuf->a_offset % sizeof(char *)) ||
(ctrlptr->len < (signed) (inbuf->a_offset + sizeof(char *)))) {
SHpGenReply(irp, -1, EINVAL);
return(STATUS_SUCCESS);
}
if (inbuf->a_insert.i_fildes &&
(inbuf->a_insert.i_fildes != INVALID_HANDLE_VALUE)) {
iq = handle_to_queue(inbuf->a_insert.i_fildes);
}
if (!iq) {
SHpGenReply(irp, -1, EINVAL);
return(STATUS_SUCCESS);
}
inbuf->a_insert.i_targetq = iq; // this is a union !!
//
// if no data part is to be sent, specify it unambiguously for
// irptomp()'s sake,
//
if (dataptr->len == 0) {
dataptr->len = -1;
}
spl_level = lock_strm(ms->e_strm);
if (shrange(ms->e_strm, ctrlptr->len, dataptr->len) < 0) {
unlock_strm(ms->e_strm, spl_level);
SHpGenReply(irp, -1, ERANGE);
return(STATUS_SUCCESS);
}
/*
* do_putmsg() has, or will arrange to, complete the IRP.
*/
do_putmsg(irp, FALSE, &spl_level, NULL);
IF_STRMDBG(CALL) {
STRMTRACE(("SHEAD: SHDispFdInsert(ms = %lx) completed ok\n", ms));
}
return(STATUS_PENDING);
} // SHDispFdInsert
NTSTATUS
SHDispPutMsg(
IN PIRP irp,
IN PIO_STACK_LOCATION irpsp
)
/*++
Routine Description:
This routine is called to put a message down a stream. It is based on
the SpiderStreams emulator's msgserver() routine.
This routine merely peels open the IRP, checks the putmsg() arguments
for consistency, locks the appropriate stream, and then calls
do_putmsg(), which does the bulk of the work.
Arguments:
irp - pointer to the IRP representing this request
irpsp - pointer to the IRP stack location for this request
Return Value:
An NT status code. Whatever the return value, this function will arrange
for the IRP to be completed.
--*/
{
int spl_level;
PSTREAM_ENDPOINT ms;
PPUTMSG_ARGS_IN inbuf;
struct strbuf *ctrlptr, *dataptr;
ASSERT((irpsp->Parameters.DeviceIoControl.IoControlCode & 0x3) ==
METHOD_BUFFERED);
ms = (STREAM_ENDPOINT *) irpsp->FileObject->FsContext;
if (irpsp->Parameters.DeviceIoControl.InputBufferLength <
sizeof(PUTMSG_ARGS_IN) - 1) {
IF_STRMDBG(TERSE) {
STRMTRACE(("SHEAD: SHDispPutMsg(%lx) insufficient nbytes = %lx\n",
irp, irpsp->Parameters.DeviceIoControl.InputBufferLength));
}
shortreply(irp, STATUS_INVALID_PARAMETER, 0);
return(STATUS_INVALID_PARAMETER);
}
//
// the caller marshalled the input arguments contiguously thus:
//
// typedef struct _PUTMSG_ARGS_IN_ {
// int a_iocode; // 0
// long a_flags; // 0 | RS_HIPRI
// struct strbuf a_ctlbuf; // (required)
// struct strbuf a_databuf; // (required)
// HANDLE a_fildes; // -1
// int a_offset; // 0
// char a_stuff[1]; // s_ctlbuf.buf (optional)
// // s_databuf.buf (optional)
// } PUTMSG_ARGS_IN, *PPUTMSG_ARGS_IN;
//
// When the message has no control part, the caller must have set
// a_ctlbuf.len = -1 !! Ditto for a_databuf.len.
//
//
inbuf = (PPUTMSG_ARGS_IN) irp->AssociatedIrp.SystemBuffer;
ctrlptr = &(inbuf->a_ctlbuf);
dataptr = &(inbuf->a_databuf);
ASSERT(inbuf->a_insert.i_fildes == INVALID_HANDLE_VALUE);
inbuf->a_insert.i_targetq = NULL;
IF_STRMDBG(CALL) {
STRMTRACE(("SHEAD: SHDispPutMsg(flags, clen, dlen = %d, %lx, %lx)\n",
inbuf->a_flags, ctrlptr->len, dataptr->len));
}
switch (inbuf->a_flags) {
case 0:
if ((ctrlptr->len < 0) && (dataptr->len < 0)) {
SHpGenReply(irp, 0, 0);
return(STATUS_SUCCESS);
}
break;
case RS_HIPRI:
if (ctrlptr->len >= 0) {
break;
}
/* fall through */
default:
SHpGenReply(irp, -1, EINVAL);
return(STATUS_SUCCESS);
}
spl_level = lock_strm(ms->e_strm);
/*
* ms->e_wropt may be changed by ioctl(I_SWROPT). However, the current
* state of ms->e_wropt applies to this put operation.
*/
if ((ctrlptr->len <= 0) &&
(dataptr->len <= 0) &&
!(ms->e_wropt & SNDZERO)) {
unlock_strm(ms->e_strm, spl_level);
SHpGenReply(irp, 0, 0);
return(STATUS_SUCCESS);
}
if (shrange(ms->e_strm, ctrlptr->len, dataptr->len) < 0) {
unlock_strm(ms->e_strm, spl_level);
SHpGenReply(irp, -1, ERANGE);
return(STATUS_SUCCESS);
}
/*
* do_putmsg() has, or will arrange to, complete the IRP.
*/
do_putmsg(irp, FALSE, &spl_level, NULL);
IF_STRMDBG(CALL) {
STRMTRACE(("SHEAD: SHDispPutMsg(ms = %lx) completed ok\n", ms));
}
return(STATUS_PENDING);
} // SHDispPutMsg
STATIC VOID
cancel_put(
IN PDEVICE_OBJECT device,
IN PIRP irp
)
/*++
Routine Description:
This routine is called when an put operation on a stream is cancelled.
It must release the cancel spinlock before returning !! The caller
has already acquired the cancel spinlock. ref: IoCancelIrp().
Arguments:
device - pointer to the device object
irp - pointer to the irp of this request
Return Value:
none.
--*/
{
int spl_level;
PLIST_ENTRY tmp;
PWAITING_IRP item;
PSTREAM_ENDPOINT ms;
PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(irp);
ASSERT(device == (PDEVICE_OBJECT) StreamDevice);
ASSERT(irpsp->MajorFunction == IRP_MJ_DEVICE_CONTROL);
ASSERT(irpsp->Parameters.DeviceIoControl.IoControlCode ==
IOCTL_STREAMS_PUTMSG);
IF_STRMDBG(CALL) {
STRMTRACE(("SHEAD: cancel_put(irp = %lx) entered\n", irp));
}
IoSetCancelRoutine(irp, NULL); /* unnecessary, but cheap */
IoReleaseCancelSpinLock(irp->CancelIrql);
ms = (PSTREAM_ENDPOINT) irpsp->FileObject->FsContext;
spl_level = lock_strm(ms->e_strm);
for (tmp = ms->e_writers.Flink; tmp != &ms->e_writers; tmp = tmp->Flink) {
item = CONTAINING_RECORD(tmp,
WAITING_IRP,
w_list);
if (irp != item->w_irp) {
continue;
}
RemoveEntryList(&(item->w_list));
unlock_strm(ms->e_strm, spl_level);
ExFreePool(item);
shortreply(irp, STATUS_CANCELLED, 0);
IF_STRMDBG(CALL) {
STRMTRACE(("SHEAD: cancel_put(irp = %lx) cancelled ok\n", irp));
}
return;
}
unlock_strm(ms->e_strm, spl_level);
IF_STRMDBG(CALL) {
STRMTRACE(("SHEAD: cancel_put(irp = %lx) not found\n", irp));
}
} // cancel_put
STATIC NTSTATUS
do_putmsg(
IN PIRP irp,
IN BOOLEAN from_queue,
IN int *spl_levelp,
OUT BOOLEAN *pmore OPTIONAL
)
/*++
Routine Description:
This function is called to put a message down a stream. It either
completes the irp or chains it to ms->e_writers. In any case,
once this function is called, it will arrange for the IRP to be
completed.
Call this function with the stream locked !!!
This function is based on the SpiderStreams emulator's function, do_req().
Arguments:
irp - pointer to the IRP representing this request
from_queue - TRUE if this is an IRP just unchained from ms->e_writers
spl_levelp - pointer to the interrupt priority level at which the
stream was locked
pmore - if not the stream is not flow-controlled when this
function returns, this will be TRUE. Otherwise, it
will be set to false.
*pmore is basically set to the value of !canput():
This is to accommodate the logic in shwsrv(), the primary caller of this
function. shwsrv() is most interested in the state of stream's write
queue: should it call this function again ?
Return Value:
an NT status code.
--*/
{
mblk_t *mp;
queue_t *iq;
NTSTATUS status;
int MyErrno, flags;
STREAM_ENDPOINT *ms;
PPUTMSG_ARGS_IN inbuf;
PIO_STACK_LOCATION irpsp;
struct strbuf *ctrlptr, *dataptr;
IF_STRMDBG(CALL) {
STRMTRACE(("SHEAD: do_putmsg(irp = %lx) entered\n", irp));
}
irpsp = IoGetCurrentIrpStackLocation(irp);
ms = (STREAM_ENDPOINT *) irpsp->FileObject->FsContext;
//
// this was already verified by SHDispFdInsert() or SHDispPutMsg().
//
ASSERT(irpsp->Parameters.DeviceIoControl.InputBufferLength >=
sizeof(PUTMSG_ARGS_IN) - 1);
//
// the caller marshalled the input arguments contiguously thus:
//
// typedef struct _PUTMSG_ARGS_IN_ {
// int a_iocode; // I_FDINSERT or 0
// long a_flags; // 0 or RS_HIPRI
// struct strbuf a_ctlbuf; // (required)
// struct strbuf a_databuf; // (required)
// struct queue *a_insert.i_targetq; // (optional)
// int a_offset; // (optional)
// char a_stuff[1]; // s_ctlbuf.buf (optional)
// // s_databuf.buf (optional)
// } PUTMSG_ARGS_IN, *PPUTMSG_ARGS_IN;
//
// When the message has no control part, the caller must have set
// ctrlbuf->len = -1 !! Ditto for databuf->len.
//
inbuf = (PPUTMSG_ARGS_IN) irp->AssociatedIrp.SystemBuffer;
flags = inbuf->a_flags;
ctrlptr = &(inbuf->a_ctlbuf);
dataptr = &(inbuf->a_databuf);
IF_STRMDBG(VERBOSE) {
STRMTRACE(("SHEAD: do_putmsg(flags, clen, dlen = %d, %lx, %lx)\n",
flags, ctrlptr->len, dataptr->len));
}
if (pmore) {
*pmore = TRUE;
}
if (ms->e_werror) {
MyErrno = ms->e_werror;
}
else if (ms->e_linked) {
MyErrno = EINVAL;
}
else {
MyErrno = 0;
}
if (MyErrno) {
IF_STRMDBG(TERSE) {
STRMTRACE(("SHEAD: do_putmsg(%lx) error = %d\n", ms, MyErrno));
}
unlock_strm(ms->e_strm, *spl_levelp);
SHpGenReply(irp, -1, MyErrno);
return(STATUS_SUCCESS);
}
//
// if downstream flow control is being exerted, enqueue this request in
// the waiting list of writers.
//
// High-priority messages are not subject to flow control.
//
// check whether this stream is nonblocking !!!
//
//
if ((flags != RS_HIPRI) &&
(!IsListEmpty( &ms->e_writers ) && !from_queue) ||
shblocked(ms->e_strm)) {
IF_STRMDBG(VERBOSE) {
STRMTRACE((
"SHEAD: do_putmsg(irp = %lx) flow-ctrl, (!%x && !%x) || %x\n", irp,
IsListEmpty( &ms->e_writers ), from_queue, shblocked(ms->e_strm)));
}
if (pmore) {
*pmore = FALSE;
}
IoMarkIrpPending(irp);
unlock_strm(ms->e_strm, *spl_levelp);
IoAcquireCancelSpinLock(&irp->CancelIrql);
if (irp->Cancel) {
IoReleaseCancelSpinLock(irp->CancelIrql);
shortreply(irp, STATUS_CANCELLED, 0);
return(STATUS_CANCELLED);
}
*spl_levelp = lock_strm(ms->e_strm);
status = SHAddPendingIrp(
&(ms->e_writers),
from_queue,
irp,
do_putmsg);
unlock_strm(ms->e_strm, *spl_levelp);
if (status != STATUS_SUCCESS) {
IoReleaseCancelSpinLock(irp->CancelIrql);
shortreply(irp, status, 0);
return(status);
}
IoSetCancelRoutine(irp, cancel_put);
IoReleaseCancelSpinLock(irp->CancelIrql);
return(STATUS_PENDING);
}
mp = irptomp(irp, BPRI_LO, ctrlptr->len, dataptr->len, inbuf->a_stuff);
if (!mp) {
unlock_strm(ms->e_strm, *spl_levelp);
shortreply(irp, STATUS_NO_MEMORY, 0);
return(STATUS_NO_MEMORY);
}
//
// Both SHDispFdInsert() and SHDispPutMsg() verified that if RS_HIPRI
// was set, a control part exists.
//
if (flags == RS_HIPRI) {
ASSERT(mp->b_datap->db_type == M_PROTO);
mp->b_datap->db_type = M_PCPROTO;
}
//
// The stream of a_insert.i_targetq should be locked in
// SHDispFdInsert(), and unlocked after the shput() ?
//
if (inbuf->a_insert.i_targetq) {
iq = inbuf->a_insert.i_targetq;
//
// Spider's do_req() has the line below before the while loop:
//
// iq = WR(iq);
//
// This is incorrect because:
//
// a) iq already points to the write queue,
// b) it causes iq to be traversed upstream, instead of downstream.
//
while (iq->q_next) {
iq = iq->q_next;
}
* ((queue_t **) (mp->b_rptr + inbuf->a_offset)) = RD(iq);
}
//
// since irptomp() made a copy, we can complete the putmsg() now.
//
SHpGenReply(irp, 0, 0);
//
// shput() does the unlock_strm(ms->e_strm) for us.
//
shput(ms->e_strm, mp, 0, spl_levelp);
IF_STRMDBG(CALL) {
STRMTRACE(("SHEAD: do_putmsg(irp = %lx) completed ok\n", irp));
}
return(STATUS_SUCCESS);
} // do_putmsg
STATIC queue_t *
handle_to_queue (
IN HANDLE handle
)
/*++
Routine Description:
This routine returns a pointer to a queue structure, given the NT handle
of its stream. It is based on the SpiderStreams Emulator function,
fd_to_queue().
Do not call this function at DISPATCH_LEVEL !! ObReferenceObjectByHandle()
must be called from either LOW_LEVEL or APC_LEVEL.
Arguments:
handle - handle relevant only in the current process' context
Return Value:
pointer to the queue structure associated with that handle
--*/
{
NTSTATUS status;
STREAM_ENDPOINT *ms;
PFILE_OBJECT pfileobj;
IF_STRMDBG(CALL) {
STRMTRACE(("SHEAD: handle_to_queue(%lx) \n", handle));
}
status = ObReferenceObjectByHandle(
handle, // Handle
FILE_READ_DATA, // DesiredAccess
*IoFileObjectType, // ObjectType
KernelMode, // AccessMode
(PVOID *) &pfileobj, // *object
NULL // HandleInformation
);
if (!NT_SUCCESS(status) ||
!((STREAM_ENDPOINT *) pfileobj->FsContext) ||
!((STREAM_ENDPOINT *) pfileobj->FsContext)->e_strm) {
return((queue_t *) NULL);
}
ms = (STREAM_ENDPOINT *) pfileobj->FsContext;
ObDereferenceObject(pfileobj);
return(ms->e_strm->str_sq->q_next);
} // handle_to_queue
void
shwsrv(
IN struct msg_strm *ms
)
/*++
Routine Description:
This function is called from two places: from the Stream Head's write
service procedure, headwsrv(), and when the Stream Head driver's
bufcall() and esbbcall() are triggered.
It is based on the SpiderStreams function of the same name.
Arguments:
ms - pointer to the stream endpoint
Return Value:
none.
--*/
{
PIRP irp;
int spl_level;
BOOLEAN carryon;
PLIST_ENTRY tmp;
PWAITING_IRP item;
START_FUNCTION function;
PTPI_OBJECT ObjectPtr;
PTPI_CONNECTION_OBJECT ConnectionPtr;
//
// ensure that the stream has not gone away.
//
if ( !(ms->e_strm) ) {
IF_STRMDBG(TERSE) {
STRMTRACE(("SHEAD: shwsrv(%lx) called on null stream\n", ms));
}
return;
}
spl_level = lock_strm(ms->e_strm);
//
// handle anyone waiting to put a message down this stream.
//
while (!IsListEmpty( &(ms->e_writers) )) {
tmp = RemoveHeadList( &(ms->e_writers) );
item = CONTAINING_RECORD(tmp,
WAITING_IRP,
w_list);
irp = item->w_irp;
function = item->w_function;
ExFreePool(item);
carryon = TRUE;
(void) (*function)(irp, TRUE, &spl_level, &carryon);
if (!carryon) {
return;
}
spl_level = lock_strm(ms->e_strm);
}
if (ms->e_strm_flags & POLLOUT) {
ms->e_strm_flags &= ~POLLOUT;
KeReleaseSemaphore(
&Poll_fired, // semaphore
SEMAPHORE_INCREMENT, // priority increment
1, // adjustment
FALSE // wait
);
}
//
// If this is a TdiStream and it has become unblocked, I need to do a
// SEND_POSSIBLE indication to the user.
//
if ((ObjectPtr = ms->TdiStreamPtr) &&
(ObjectPtr->Tag == TPI_CONNECTION_OBJECT_TYPE)) {
ConnectionPtr = &ObjectPtr->Object.TpiConnection;
//
// The Blocked flag is always accessed under the StreamLock
//
if (ConnectionPtr->Blocked) {
ConnectionPtr->Blocked = FALSE;
SHTdiEventSendPossible(ObjectPtr);
}
}
unlock_strm(ms->e_strm, spl_level);
} // shwsrv