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

817 lines
20 KiB
C
Raw Normal View History

2020-09-26 03:20:57 -05:00
/*++
Copyright (c) 1991 Microsoft Corporation
Module Name:
sh_ioctl.c
Abstract:
This source deals with those streamio(2) functions that are synchronous,
in that a message is sent downstream, and a reply is waited for.
It is based on the SpiderSTREAMS source, stremul\msgsrvr.c.
Author:
Eric Chin (ericc) January 6, 1992
Revision History:
Notes:
1. The O_NONBLOCK state of a stream does not affect the behaviour of an
ioctl(I_STR).
2. 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 "shead.h"
#include "sh_proto.h"
//
// Private Functions
//
STATIC VOID
cancel_ioctl(
IN PDEVICE_OBJECT device,
IN PIRP irp
);
STATIC NTSTATUS
do_sioctl(
IN PIRP irp,
IN BOOLEAN from_queue,
IN int *spl_levelp,
OUT BOOLEAN *ignored OPTIONAL
);
STATIC VOID
handle_ioctlers (
IN STREAM_ENDPOINT *ms,
IN int *spl_levelp
);
STATIC int
ioc_timeout(
IN char *arg
);
NTSTATUS
SHDispIStr(
IN PIRP irp
)
/*++
Routine Description:
This routine is called to process an ioctl(I_STR). It is based on the
SpiderSTREAMS emulator's routine, msgserver().
This routine merely peels open the IRP, checks the arguments for
consistency, locks the appropriate stream, and then calls do_sioctl(),
which does the bulk of the work.
Arguments:
irp - pointer to the IRP representing this request
Return Value:
an NT status code.
--*/
{
int timout;
int spl_level;
int spl_level2;
NTSTATUS status;
PSTREAM_ENDPOINT ms;
PISTR_ARGS_INOUT inbuf;
struct strioctl *striop;
PIO_STACK_LOCATION irpsp;
irpsp = IoGetCurrentIrpStackLocation(irp);
ASSERT((irpsp->Parameters.DeviceIoControl.IoControlCode & 0x3) ==
METHOD_BUFFERED);
ms = (STREAM_ENDPOINT *) irpsp->FileObject->FsContext;
if (irpsp->Parameters.DeviceIoControl.InputBufferLength <
sizeof(ISTR_ARGS_INOUT) - 1) {
IF_STRMDBG(TERSE) {
STRMTRACE(("SHEAD: SHDispIStr(%lx) insufficient nbytes = %lx\n",
irp, irpsp->Parameters.DeviceIoControl.InputBufferLength));
}
return(STATUS_INVALID_PARAMETER);
}
//
// the caller marshalled the input arguments contiguously thus:
//
// typedef struct _ISTR_ARGS_INOUT { // ioctl(,I_STR,)
// int a_iocode; // I_STR
// struct strioctl a_strio; // (required)
// int a_unused[2]; // (required)
// char a_stuff[1]; // ic_dp buffer (optional)
//
// } ISTR_ARGS_INOUT, PISTR_ARGS_INOUT;
//
//
//
inbuf = (PISTR_ARGS_INOUT) irp->AssociatedIrp.SystemBuffer;
striop = &(inbuf->a_strio);
IF_STRMDBG(VERBOSE) {
STRMTRACE(("SHEAD: SHDispIStr(ic_cmd, timout, len = %lx, %lx, %lx)\n",
striop->ic_cmd, striop->ic_timout, striop->ic_len));
}
//
// don't let user-defined ioctl codes coincide with the standard STREAMS
// ioctl codes. Otherwise, confusion will reign.
//
switch (striop->ic_cmd) {
case I_LINK:
case I_UNLINK:
case I_PLINK:
case I_PUNLINK:
SHpGenReply(irp, -1, EINVAL);
return(STATUS_SUCCESS);
break;
}
if ((striop->ic_timout < -1) || (striop->ic_len < 0)) {
SHpGenReply(irp, -1, EINVAL);
return(STATUS_SUCCESS);
}
IoAcquireCancelSpinLock(&irp->CancelIrql);
if (irp->Cancel) {
IoReleaseCancelSpinLock(irp->CancelIrql);
shortreply(irp, STATUS_CANCELLED, 0);
return(STATUS_CANCELLED);
}
spl_level = lock_strm(ms->e_strm);
if (shrange(ms->e_strm, sizeof(struct iocblk), striop->ic_len) < 0) {
unlock_strm(ms->e_strm, spl_level);
IoReleaseCancelSpinLock(irp->CancelIrql);
SHpGenReply(irp, -1, ERANGE);
return(STATUS_SUCCESS);
}
IoMarkIrpPending(irp);
//
// if the ioctl is to time out after a specific time, start a timer
// running. iocrdy() will clear the timer.
//
// Based on a tip from larryo, irp->IoStatus.Information is used only
// when an IRP is completed successfully. Hence, we keep the timer
// id there.
//
irp->IoStatus.Information = 0;
irp->IoStatus.Status = STATUS_PENDING;
if (striop->ic_timout != -1) {
timout = striop->ic_timout ? striop->ic_timout : STRTIMOUT;
irp->IoStatus.Information = timeout(ioc_timeout,
(char *) irp, timout * HZ);
}
//
// At any given time, there must only be one outstanding ioctl(I_STR) on
// a stream. If there is another ioctl outstanding, chain this IRP to
// the tail of the pending ioctl'ers list.
//
if (ms->e_active_ioctl) {
status = SHAddPendingIrp(&(ms->e_ioctlers), FALSE, irp, do_sioctl);
unlock_strm(ms->e_strm, spl_level);
if (status != STATUS_SUCCESS) {
IoReleaseCancelSpinLock(irp->CancelIrql);
shortreply(irp, status, 0);
return(status);
}
IoSetCancelRoutine(irp, cancel_ioctl);
IoReleaseCancelSpinLock(irp->CancelIrql);
return(STATUS_PENDING);
}
ms->e_active_ioctl = irp;
//
// do_sioctl() calls unlock_strm(ms->e_strm).
//
IoReleaseCancelSpinLock((KIRQL) spl_level);
spl_level2 = (int) irp->CancelIrql;
if (do_sioctl(irp, FALSE, &spl_level2, NULL) != STATUS_PENDING) {
spl_level = lock_strm(ms->e_strm);
handle_ioctlers(ms, &spl_level);
}
IF_STRMDBG(CALL) {
STRMTRACE(("SHEAD: SHDispIStr(ms = %lx) returns, irp pending\n", ms));
}
return(STATUS_PENDING);
} // SHDispIStr
STATIC VOID
cancel_ioctl(
IN PDEVICE_OBJECT device,
IN PIRP irp
)
/*++
Routine Description:
This routine is called when an ioctl(...,I_STR,...) 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);
int spl_level2;
ASSERT(device == (PDEVICE_OBJECT) StreamDevice);
ASSERT(irpsp->MajorFunction == IRP_MJ_DEVICE_CONTROL);
ASSERT(irpsp->Parameters.DeviceIoControl.IoControlCode ==
IOCTL_STREAMS_IOCTL);
IF_STRMDBG(CALL) {
STRMTRACE(("SHEAD: cancel_ioctl(irp = %lx) entered\n", irp));
}
IoSetCancelRoutine(irp, NULL); /* unnecessary, but cheap */
ms = (PSTREAM_ENDPOINT) irpsp->FileObject->FsContext;
spl_level = lock_strm(ms->e_strm);
//
// I'm releasing the cancel spinlock and stream lock in the reverse
// order of acquisition. Thus, I need to swap the irql's.
//
spl_level2 = (int) irp->CancelIrql;
IoReleaseCancelSpinLock((KIRQL) spl_level);
spl_level = spl_level2;
if (irp->IoStatus.Information) {
if (untimeout((int)irp->IoStatus.Information) == 0) {
//
// the timeout routine is already running. Just return and let it
// handle the irp.
//
unlock_strm(ms->e_strm, spl_level);
return;
}
irp->IoStatus.Information = 0;
}
if (irp == ms->e_active_ioctl) {
IF_STRMDBG(CALL) {
STRMTRACE(("SHEAD: cancel_ioctl(irp = %lx) cancelled\n", irp));
}
ms->e_active_ioctl = NULL;
handle_ioctlers(ms, &spl_level);
shortreply(irp, STATUS_CANCELLED, 0);
return;
}
for (tmp = ms->e_ioctlers.Flink;
tmp != &ms->e_ioctlers;
tmp = tmp->Flink) {
item = CONTAINING_RECORD(tmp,
WAITING_IRP,
w_list);
if (irp != item->w_irp) {
continue;
}
RemoveEntryList(&(item->w_list));
ExFreePool(item);
unlock_strm(ms->e_strm, spl_level);
shortreply(irp, STATUS_CANCELLED, 0);
IF_STRMDBG(CALL) {
STRMTRACE(("SHEAD: cancel_ioctl(irp = %lx) cancelled\n", irp));
}
return;
}
unlock_strm(ms->e_strm, spl_level);
IF_STRMDBG(CALL) {
STRMTRACE(("SHEAD: cancel_ioctl(irp = %lx) not found\n", irp));
}
} // cancel_ioctl
STATIC NTSTATUS
do_sioctl(
IN PIRP irp,
IN BOOLEAN ignored,
IN int *spl_levelp,
OUT BOOLEAN *must_be_null
)
/*++
Routine Description:
This function is called to put an M_IOCTL message down a stream. It
either sends the message or chains it to ms->e_writers. This function
is similar to do_putmsg(), both of which are based on the SpiderStreams
emulator's function, do_req().
Call this function with the stream locked !!!
Arguments:
irp - pointer to the IRP representing this request
ignored - this parameter is ignored
spl_levelp - pointer to the interrupt priority level at which the
stream was locked
must_be_null - since this function doesn't set this optional return
parameter, this must be NULL
Return Value:
an NT status code. Unless this is STATUS_PENDING, this function has
completed the IRP.
--*/
{
int MyErrno;
mblk_t *mp;
struct iocblk *iocp;
PSTREAM_ENDPOINT ms;
PISTR_ARGS_INOUT inbuf;
struct strioctl *striop;
PIO_STACK_LOCATION irpsp;
IF_STRMDBG(CALL) {
STRMTRACE(("SHEAD: do_sioctl(irp = %lx) entered\n", irp));
}
ASSERT(must_be_null == NULL);
irpsp = IoGetCurrentIrpStackLocation(irp);
//
// these were already verified by SHDispIStr().
//
ASSERT(irpsp->Parameters.DeviceIoControl.InputBufferLength >=
sizeof(ISTR_ARGS_INOUT) - 1);
ASSERT((irpsp->Parameters.DeviceIoControl.IoControlCode & 0x3) ==
METHOD_BUFFERED);
ms = (STREAM_ENDPOINT *) irpsp->FileObject->FsContext;
ASSERT(irp == ms->e_active_ioctl);
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_sioctl(%lx) error = %d\n", ms, MyErrno));
}
if (irp->IoStatus.Information) {
if (untimeout((int)irp->IoStatus.Information) == 0) {
//
// The timeout routine will handle this
//
unlock_strm(ms->e_strm, *spl_levelp);
return(STATUS_PENDING);
}
irp->IoStatus.Information = 0;
}
ms->e_active_ioctl = NULL;
unlock_strm(ms->e_strm, *spl_levelp);
SHpGenReply(irp, -1, MyErrno);
return(STATUS_SUCCESS);
}
//
// the caller marshalled the input arguments contiguously thus:
//
// typedef struct _ISTR_ARGS_INOUT { // ioctl(,I_STR,)
// int a_iocode; // I_STR
// struct strioctl a_strio; // (required)
// int a_unused[2]; // (required)
// char a_stuff[1]; // ic_dp buffer (optional)
//
// } ISTR_ARGS_INOUT, PISTR_ARGS_INOUT;
//
//
//
inbuf = (PISTR_ARGS_INOUT) irp->AssociatedIrp.SystemBuffer;
striop = &(inbuf->a_strio);
IF_STRMDBG(VERBOSE) {
STRMTRACE(("SHEAD: do_sioctl(ic_cmd, timout, len = %lx, %lx, %lx)\n",
striop->ic_cmd, striop->ic_timout, striop->ic_len));
}
mp = irptomp(irp, BPRI_LO, sizeof(struct iocblk), striop->ic_len,
(char *) &(inbuf->a_strio));
if (!mp) {
if (irp->IoStatus.Information) {
if (untimeout((int)irp->IoStatus.Information) == 0) {
//
// The timeout routine will handle this
//
unlock_strm(ms->e_strm, *spl_levelp);
return(STATUS_PENDING);
}
irp->IoStatus.Information = 0;
}
ms->e_active_ioctl = NULL;
unlock_strm(ms->e_strm, *spl_levelp);
shortreply(irp, STATUS_NO_MEMORY, 0);
return(STATUS_NO_MEMORY);
}
ASSERT(mp->b_datap->db_type == M_PROTO);
mp->b_datap->db_type = M_IOCTL;
iocp = (struct iocblk *) mp->b_rptr;
ASSERT(iocp);
iocp->ioc_cmd = striop->ic_cmd;
iocp->ioc_uid = 0;
iocp->ioc_gid = 0;
iocp->ioc_id = ++(ms->e_strm->str_iocid);
if (iocp->ioc_id == 0) {
iocp->ioc_id = ms->e_strm->str_iocid = 1;
}
iocp->ioc_count = striop->ic_len;
iocp->ioc_error = 0;
iocp->ioc_rval = 0;
//
// shput() calls unlock_strm(ms->e_strm).
//
shput(ms->e_strm, mp, 0, spl_levelp);
IF_STRMDBG(CALL) {
STRMTRACE(("SHEAD: do_sioctl(ms = %lx) returns, irp pending\n", ms));
}
return(STATUS_PENDING);
} // do_sioctl
STATIC VOID
handle_ioctlers (
IN STREAM_ENDPOINT *ms,
IN int *spl_levelp
)
/*++
Routine Description:
This routine starts the next ioctl() that is pending on a stream. It
is based on the SpiderStreams emulator function of the same name.
This routine is called with the stream locked, and unlocks the stream
before returning.
Arguments:
ms - pointer to the stream endpoint
spl_level - priority level to resume after releasing lock.
Return Value:
none.
--*/
{
PIRP irp;
PLIST_ENTRY tmp;
PWAITING_IRP item;
ASSERT(ms);
if (ms->e_active_ioctl) {
//
// Already processing an ioctl
//
unlock_strm(ms->e_strm, *spl_levelp);
return;
}
ASSERT(!ms->e_active_ioctl);
while (!IsListEmpty(&(ms->e_ioctlers))) {
tmp = RemoveHeadList( &(ms->e_ioctlers) );
item = CONTAINING_RECORD(tmp,
WAITING_IRP,
w_list);
ASSERT(item->w_function == do_sioctl);
irp = item->w_irp;
ms->e_active_ioctl = irp;
ExFreePool(item);
if (do_sioctl(irp, FALSE, spl_levelp, NULL) == STATUS_PENDING) {
return;
}
*spl_levelp = lock_strm(ms->e_strm);
}
unlock_strm(ms->e_strm, *spl_levelp);
return;
} // handle_ioctlers
STATIC int
ioc_timeout(
IN char *arg
)
/*++
Routine Description:
This function is called when an ioctl() times out. It is based on the
SpiderStreams emulator function of the same name.
Arguments:
arg - pointer to irp representing the ioctl() which timed out.
Return Value:
0
Discussion:
Suppose iocrdy() is called because our M_IOCACK arrives. iocrdy() calls
lock_strm(), and prepares to complete the IRP. Just then, our timeout
fires, and this function is called. We call lock_strm() and spin.
Then, iocrdy() completes the irp and calls unlock_strm(), unblocking us.
We unchain an irp from ms->e_ioctlers and completes it with an ETIME
error. However, this is the wrong IRP to complete !!
One possible solution is for arg to be a pointer to a structure containing
both ms and the IRP. Then, we can verify that the IRP at the head of the
list is ours. Still, what if the IRP is reused ?
The bug above may arise after any of the following situations:
1. a stream is dup()'ed,
2. we are called from a multi-threaded application,
3. when a stream is NtOpen()'ed without the SYNCHRONOUS flag,
4. an IRP is cancelled.
--*/
{
PIRP irp;
int spl_level;
PLIST_ENTRY tmp;
PWAITING_IRP item;
STREAM_ENDPOINT *ms;
PIO_STACK_LOCATION irpsp;
IF_STRMDBG(CALL) {
STRMTRACE(("SHEAD: ioc_timeout(irp = %lx) entered\n", arg));
}
irp = (PIRP) arg;
irpsp = IoGetCurrentIrpStackLocation(irp);
ms = (STREAM_ENDPOINT *) irpsp->FileObject->FsContext;
ASSERT(irpsp->MajorFunction == IRP_MJ_DEVICE_CONTROL);
ASSERT(irpsp->Parameters.DeviceIoControl.IoControlCode ==
IOCTL_STREAMS_IOCTL);
spl_level = lock_strm(ms->e_strm);
if (irp == ms->e_active_ioctl) {
ms->e_active_ioctl = NULL;
handle_ioctlers(ms, &spl_level);
}
else {
for (tmp = ms->e_ioctlers.Flink;
tmp != &(ms->e_ioctlers);
tmp = tmp->Flink) {
item = CONTAINING_RECORD(tmp,
WAITING_IRP,
w_list);
if (irp != item->w_irp) {
continue;
}
RemoveEntryList(&(item->w_list));
ExFreePool(item);
break;
}
unlock_strm(ms->e_strm, spl_level);
}
irp->IoStatus.Information = 0;
SHpGenReply(irp, -1, ETIME);
IF_STRMDBG(CALL) {
STRMTRACE(("SHEAD: ioc_timeout(irp = %lx) completed\n", irp));
}
return(0);
} // ioc_timeout
void
iocrdy(
IN STREAM_ENDPOINT *ms,
IN mblk_t *mp,
IN int *spl_levelp
)
/*++
Routine Description:
This function is called by the Stream Head Driver when either an M_IOCACK
or an M_IOCNAK message arrives at its read queue of the specified stream.
It is based on the SpiderStreams emulator function of the same name.
This function is called with the stream locked, and unlocks the stream
before returning.
Arguments:
ms - pointer to the stream endpoint
mp - pointer to the STREAMS message that arrived
spl_levelp - ptr to priority level to resume after releasing lock.
Return Value:
none.
--*/
{
PIRP irp;
struct iocblk *iocp = (struct iocblk *) mp->b_rptr;
IF_STRMDBG(CALL) {
STRMTRACE(("SHEAD: iocrdy(ms = %lx)\n", ms));
}
if (!ms) {
IF_STRMDBG(TERSE) {
STRMTRACE(("SHEAD: iocrdy(ms = NULL) !!\n"));
}
freemsg(mp);
unlock_strm(ms->e_strm, *spl_levelp);
return;
}
irp = ms->e_active_ioctl;
ms->e_active_ioctl = NULL;
//
// ensure that someone is still waiting for the reply.
//
if (!irp) {
freemsg(mp);
unlock_strm(ms->e_strm, *spl_levelp);
qenable((ms->e_strm)->str_sq);
// handle_ioctlers(ms, spl_levelp);
return;
}
//
// if there's an ioctl timer running, clear it now.
//
if (irp->IoStatus.Information) {
if (untimeout((int)irp->IoStatus.Information) == 0) {
//
// The timeout routine will handle this
//
ms->e_active_ioctl = irp;
unlock_strm(ms->e_strm, *spl_levelp);
freemsg(mp);
return;
}
irp->IoStatus.Information = 0;
}
switch (iocp->ioc_cmd) {
case I_LINK:
case I_UNLINK:
unlock_strm(ms->e_strm, *spl_levelp);
if (mp->b_datap->db_type == M_IOCACK) {
SHpGenReply(irp,
((struct linkblk *) (mp->b_cont->b_rptr))->l_index,
0);
}
else {
SHpGenReply(irp, -1, iocp->ioc_error);
}
// *spl_levelp = lock_strm(ms->e_strm);
freemsg(mp);
break;
case I_PLINK:
case I_PUNLINK:
ASSERT(0);
break;
//
// if we get here, it's an ioctl(I_STR). iocp->ioc_cmd is a some
// user-defined command code.
//
default:
unlock_strm(ms->e_strm, *spl_levelp);
(void) iocreply(mp, irp);
// *spl_levelp = lock_strm(ms->e_strm);
break;
}
qenable((ms->e_strm)->str_sq);
// handle_ioctlers(ms, spl_levelp);
return;
} // iocrdy