/*++ Copyright (c) 1991 Microsoft Corporation Module Name: sh_get.c Abstract: This source file contains those functions of the Stream Head Driver that deal with receiving messages from a stream. Author: Eric Chin (ericc) August 16, 1991 Revision History: Notes: The read error state of a stream is represented by ms->e_rerror. Once set, this is never reset. This corresponds to the STREAMS semantics as defined by AT&T. Once a user is notified of a read error on a stream, about the only recourse is to close the stream. --*/ #include "sh_inc.h" // // Local (Private) Functions // STATIC VOID cancel_get( IN PDEVICE_OBJECT device, IN PIRP irp ); NTSTATUS do_getmsg( IN PIRP irp, IN PFILE_OBJECT pfileobj, IN int flags ); NTSTATUS SHDispGetMsg( IN PIRP irp, IN PIO_STACK_LOCATION irpsp ) /*++ Routine Description: This routine implements the getmsg(2) API. 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; NTSTATUS status; PSTREAM_ENDPOINT ms; int MyErrno, pri; PGETMSG_ARGS_INOUT inbuf; int ret; mblk_t *mp; int more = 0; struct strbuf *strbufp; int ctlsize, datasize, flags, *pretval, remains; ASSERT((irpsp->Parameters.DeviceIoControl.IoControlCode & 0x3) == METHOD_BUFFERED); ms = (PSTREAM_ENDPOINT) irpsp->FileObject->FsContext; if (irpsp->Parameters.DeviceIoControl.InputBufferLength < sizeof(GETMSG_ARGS_INOUT) - 1) { IF_STRMDBG(TERSE) { STRMTRACE(("SHEAD: SHDispGetMsg(%lx) insufficient nbytes = %lx\n", irp, irpsp->Parameters.DeviceIoControl.InputBufferLength)); } shortreply(irp, STATUS_INVALID_PARAMETER, 0); return(STATUS_INVALID_PARAMETER); } // Need to ensure that the output buffer is big enough. { int cbOut = irpsp->Parameters.DeviceIoControl.OutputBufferLength; NTSTATUS LengthStatus = STATUS_INVALID_PARAMETER; if (cbOut >= (sizeof(GETMSG_ARGS_INOUT) - 1)) { pretval = (int *) irp->AssociatedIrp.SystemBuffer; flags = * (pretval + 1); strbufp = (struct strbuf *) (pretval + 2); ctlsize = strbufp->maxlen; datasize = (++strbufp)->maxlen; cbOut -= (sizeof(GETMSG_ARGS_INOUT) - 1); if (cbOut >= ctlsize) { cbOut -= ctlsize; if (cbOut >= datasize) { // cbOut -= datasize; LengthStatus = STATUS_SUCCESS; } } } if (LengthStatus != STATUS_SUCCESS) { IF_STRMDBG(TERSE) { STRMTRACE(("SHEAD: SHDispGetMsg(%lx) outbuf insufficient nbytes = %lx\n", irp, irpsp->Parameters.DeviceIoControl.OutputBufferLength)); } shortreply(irp, LengthStatus, 0); return (LengthStatus); } } // // the caller marshalled the input arguments contiguously thus: // // typedef struct _GETMSG_ARGS_INOUT_ { // getmsg() // int a_retval; // ignore on input // long a_flags; // 0 or RS_HIPRI // struct strbuf a_ctlbuf; // (required) // struct strbuf a_databuf; // (required) // char a_stuff[1]; // a_ctlbuf.buf (optional) // // a_databuf.buf (optional) // } GETMSG_ARGS_INOUT, *PGETMSG_ARGS_INOUT; // inbuf = (PGETMSG_ARGS_INOUT) irp->AssociatedIrp.SystemBuffer; IF_STRMDBG(VERBOSE) { STRMTRACE(("SHEAD: SHDispGetMsg(irp = %lx)\n", irp)); } IoAcquireCancelSpinLock(&irp->CancelIrql); spl_level = lock_strm(ms->e_strm); if (ms->e_rerror) { MyErrno = ms->e_rerror; } else if (ms->e_linked) { MyErrno = EINVAL; } else { MyErrno = 0; } if (MyErrno) { IF_STRMDBG(TERSE) { STRMTRACE(("SHEAD: SHDispGetMsg() error = %d\n", MyErrno)); } unlock_strm(ms->e_strm, spl_level); IoReleaseCancelSpinLock(irp->CancelIrql); SHpGenReply(irp, -1, MyErrno); return(STATUS_SUCCESS); } pri = (inbuf->a_flags == RS_HIPRI) ? QPCTL : 0; if (shready(ms->e_strm, pri)) { IF_STRMDBG(VERBOSE) { STRMTRACE(("SHEAD: SHDispGetMsg() stream's shready()\n")); } // The two lines below are replaced by the lines between the vvv/^^^'s // temp = msgreply(ms, irp); // ASSERT(temp == 0); // vvvvvvvv // vvvvvvvv /* * 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); ASSERT(!ret); ASSERT(mp); // ^^^^^^^^ // ^^^^^^^^ unlock_strm(ms->e_strm, spl_level); IoReleaseCancelSpinLock(irp->CancelIrql); // vvvvvvvv // vvvvvvvv mptoirp(mp, irp); freemsg(mp); // ^^^^^^^^ // ^^^^^^^^ return(STATUS_SUCCESS); } if (ms->e_hup) { IF_STRMDBG(TERSE) { STRMTRACE(("SHEAD: SHDispGetMsg() stream's was hung up\n")); } unlock_strm(ms->e_strm, spl_level); IoReleaseCancelSpinLock(irp->CancelIrql); SHpGenReply(irp, -1, EINTR); return(STATUS_SUCCESS); } // // enqueue this request in the waiting list of readers. // IoMarkIrpPending(irp); if (irp->Cancel) { unlock_strm(ms->e_strm, spl_level); IoSetCancelRoutine(irp, NULL); IoReleaseCancelSpinLock(irp->CancelIrql); shortreply(irp, STATUS_CANCELLED, 0); return(STATUS_CANCELLED); } status = SHAddPendingIrp(&(ms->e_readers), FALSE, irp, NULL); ASSERT(!shready(ms->e_strm, 0)); unlock_strm(ms->e_strm, spl_level); if (status != STATUS_SUCCESS) { IF_STRMDBG(TERSE) { STRMTRACE(("SHEAD: SHDispGetMsg() failed to SHAddPendingIrp\n")); } IoReleaseCancelSpinLock(irp->CancelIrql); shortreply(irp, status, 0); return(status); } IoSetCancelRoutine(irp, cancel_get); IoReleaseCancelSpinLock(irp->CancelIrql); IF_STRMDBG(VERBOSE) { STRMTRACE(("SHEAD: SHDispGetMsg(irp = %lx) q_count = %ld\n", irp, RD(ms->e_strm->str_sq)->q_count )); } return(STATUS_PENDING); } // SHDispGetMsg int SHpStreamError( IN PLIST_ENTRY listhead, IN int error ) /*++ Routine Description: This routine completes the IRPs waiting on a stream when an M_ERROR or M_HANGUP arrives from downstream. Arguments: listhead - either e_readers, e_writers or e_ioctlers error - the POSIX error code to return Return Value: The number of pending IRPs that were completed. --*/ { int count = 0; PLIST_ENTRY tmp; PWAITING_IRP item; IF_STRMDBG(TERSE) { STRMTRACE(("SHEAD: ShpStreamError() for M_HANGUP/M_ERROR\n")); } while (!IsListEmpty(listhead)) { tmp = RemoveHeadList(listhead); item = CONTAINING_RECORD(tmp, WAITING_IRP, w_list); SHpGenReply(item->w_irp, -1, error); ExFreePool(item); count++; } return(count); } // SHpStreamError STATIC VOID cancel_get( IN PDEVICE_OBJECT device, IN PIRP irp ) /*++ Routine Description: This routine is called when a getmsg() 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_GETMSG); IF_STRMDBG(CALL) { STRMTRACE(("SHEAD: cancel_get(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_readers.Flink; tmp != &ms->e_readers; 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_get(irp = %lx) cancelled ok\n", irp)); } return; } unlock_strm(ms->e_strm, spl_level); IF_STRMDBG(CALL) { STRMTRACE(("SHEAD: cancel_get(irp = %lx) not found\n", irp)); } } // cancel_get void msgrdy( IN struct msg_strm *ms, IN int mtype ) /*++ Routine Description: This function is called by the Stream Head Driver when a message arrives at the read queue of the specified stream. Call this function with the stream endpoint locked !! Arguments: ms - pointer to the stream endpoint mtype - type of the STREAMS message Return Value: none. --*/ { PIRP irp; int check, pri; PLIST_ENTRY tmp; PWAITING_IRP item; PGETMSG_ARGS_INOUT inbuf; IF_STRMDBG(CALL) { STRMTRACE(("SHEAD: msgrdy(ms = %lx, mtype = %x)\n", ms, mtype)); } switch (mtype) { case M_DATA: case M_PROTO: if (ms->e_strm_flags & POLLIN) { ms->e_strm_flags &= ~POLLIN; KeReleaseSemaphore( &Poll_fired, // semaphore SEMAPHORE_INCREMENT, // priority increment 1, // adjustment FALSE // wait ); } break; case M_PCPROTO: if (ms->e_strm_flags & POLLPRI) { ms->e_strm_flags &= ~POLLPRI; KeReleaseSemaphore( &Poll_fired, // semaphore SEMAPHORE_INCREMENT, // priority increment 1, // adjustment FALSE // wait ); } break; case M_SIG: if (ms->e_strm_flags & POLLMSG) { ms->e_strm_flags &= ~POLLMSG; KeReleaseSemaphore( &Poll_fired, // semaphore SEMAPHORE_INCREMENT, // priority increment 1, // adjustment FALSE // wait ); } break; default: IF_STRMDBG(TERSE) { STRMTRACE(("SHEAD: msgrdy(), msg type = %x unexpected\n", mtype)); } ASSERT(0); } while (!IsListEmpty( &(ms->e_readers) )) { tmp = RemoveHeadList( &(ms->e_readers) ); item = CONTAINING_RECORD(tmp, WAITING_IRP, w_list); irp = item->w_irp; // // get the RS_HIPRI flag, if any, from the irp. // inbuf = (PGETMSG_ARGS_INOUT) irp->AssociatedIrp.SystemBuffer; pri = (inbuf->a_flags == RS_HIPRI) ? QPCTL : 0; if (!shready(ms->e_strm, pri)) { InsertHeadList( &(ms->e_readers), &(item->w_list) ); return; } check = msgreply(ms, irp); ASSERT(check == 0); ExFreePool(item); } IF_STRMDBG(CALL) { STRMTRACE(("SHEAD: msgrdy() completed ok\n")); } return; } // msgrdy void strmevent( IN STREAM_ENDPOINT *ms, IN int rerror, IN int werror, IN int t ) /*++ Routine Description: This function handles special messages that arrive at the stream head. It is based on the SpiderStreams function of the same name. Only M_ERROR and M_HANGUP messages are handled at present. M_ERROR is straightforward to deal with. The error status in the stream structure is set, and any pending requests failed. M_HANGUP is very similar, except that read requests are allowed to complete, and subsequent reads are treated as end of file, rather than an error condition. Arguments: ms - pointer to stream endpoint rerror - read queue error werror - write queue error t - type of STREAMS message Return Value: --*/ { PIRP irp; int succeeded; IF_STRMDBG(CALL) { STRMTRACE(("SHEAD: strmevent(ms = %lx) entered\n", ms)); } if (ms->TdiStreamPtr) { TdiStreamEvent(ms, rerror, werror, t); return; } if (rerror == NOERROR) { rerror = ms->e_rerror; // get current value } else { ms->e_rerror = rerror; // set read error status } if (werror == NOERROR) { werror = ms->e_werror; // get current value } else { ms->e_werror = werror; // set write error status } if ((rerror == 0) && (werror == 0)) { // errors zeroed out IF_STRMDBG(TERSE) { STRMTRACE(("SHEAD: strmevent(ms = %lx) [rw]error = 0\n", ms)); } return; } switch (t) { case M_HANGUP: ms->e_hup = 1; break; default: ASSERT(0); /* fall through */ case M_ERROR: break; } if (rerror) { SHpStreamError(&(ms->e_readers), rerror); } // // in response to an M_ERROR or M_HANGUP for the write-side, fail // any pending ioctl(), putmsg() or write() requests. // // If a pending ioctl() is being failed, don't forget to abort its // timeout !! // if (werror) { irp = ms->e_active_ioctl; if (irp) { ms->e_active_ioctl = NULL; succeeded = 1; if (irp->IoStatus.Information) { succeeded = untimeout((int)irp->IoStatus.Information); ASSERT((succeeded == 0) || (succeeded == 1)); } if (succeeded) { SHpGenReply(irp, -1, werror); } } SHpStreamError(&(ms->e_ioctlers), werror); SHpStreamError(&(ms->e_writers), werror); } // // let poll()'ers know of the error/hangup // KeReleaseSemaphore( &Poll_fired, // semaphore SEMAPHORE_INCREMENT, // priority increment 1, // adjustment FALSE // wait ); IF_STRMDBG(CALL) { STRMTRACE(("SHEAD: strmevent(ms = %lx) completed\n", ms)); } return; } // strmevent