/*++ Copyright (c) 2013 Minoca Corp. All Rights Reserved Module Name: pipe.c Abstract: This module implements support for pipes. Author: Evan Green 25-Apr-2013 Environment: Kernel --*/ // // ------------------------------------------------------------------- Includes // #include #include "iop.h" // // ---------------------------------------------------------------- Definitions // // // Define pipe flags. // // // This flag is set if the pipe has a name in the object manager directory. // Note that normal named pipes coming from the file system do not have this // flag set. // #define PIPE_FLAG_OBJECT_NAMED 0x00000001 // // ------------------------------------------------------ Data Type Definitions // /*++ Structure Description: This structure defines a data pipe. Members: Header - Stores the standard object header. Flags - Stores the flags used when the pipe was created. StreamBuffer - Stores a pointer to the stream buffer backing the pipe. ReaderCount - Stores the number of readers that have the pipe open. WriterCount - Stores the number of writers that have the pipe open. --*/ typedef struct _PIPE { OBJECT_HEADER Header; ULONG Flags; PSTREAM_BUFFER StreamBuffer; ULONG ReaderCount; ULONG WriterCount; } PIPE, *PPIPE; /*++ Structure Description: This structure defines the parameters needed to create a pipe. Members: BufferSize - Stores the suggested size for the internal stream buffer. Supply 0 to use the system default size. --*/ typedef struct _PIPE_CREATION_PARAMETERS { ULONG BufferSize; } PIPE_CREATION_PARAMETERS, *PPIPE_CREATION_PARAMETERS; // // ----------------------------------------------- Internal Function Prototypes // VOID IopDestroyPipe ( PVOID PipeObject ); // // -------------------------------------------------------------------- Globals // // // Store a pointer to the pipes directory. // POBJECT_HEADER IoPipeDirectory; // // ------------------------------------------------------------------ Functions // KERNEL_API KSTATUS IoCreatePipe ( BOOL FromKernelMode, PIO_HANDLE Directory, PSTR Path, ULONG PathLength, ULONG OpenFlags, FILE_PERMISSIONS CreatePermissions, PIO_HANDLE *ReadHandle, PIO_HANDLE *WriteHandle ) /*++ Routine Description: This routine creates and opens a new pipe. Arguments: FromKernelMode - Supplies a boolean indicating whether this request is originating from kernel mode (and should use the root path as a base) or user mode. Directory - Supplies an optional pointer to an open handle to a directory for relative paths. Supply NULL to use the current working directory. Path - Supplies an optional pointer to the path to open. PathLength - Supplies the length of the path buffer in bytes, including the null terminator. OpenFlags - Supplies the open flags for the pipe. See OPEN_FLAG_* definitions. OPEN_FLAG_CREATE and OPEN_FLAG_FAIL_IF_EXISTS are automatically applied. CreatePermissions - Supplies the permissions to apply to the created pipe. ReadHandle - Supplies a pointer where a handle to the read side of the pipe will be returned. WriteHandle - Supplies a pointer where a handle to the write side of the pipe will be returned. Return Value: Status code. --*/ { KSTATUS Status; *ReadHandle = NULL; *WriteHandle = NULL; // // Create and open the read side. // Status = IopOpen(FromKernelMode, Directory, Path, PathLength, IO_ACCESS_READ, OpenFlags | OPEN_FLAG_CREATE | OPEN_FLAG_FAIL_IF_EXISTS, IoObjectPipe, NULL, CreatePermissions, ReadHandle); if (!KSUCCESS(Status)) { goto CreatePipeEnd; } // // Also open the write side. // Status = IopOpenPathPoint(&((*ReadHandle)->PathPoint), IO_ACCESS_WRITE, OpenFlags, WriteHandle); if (!KSUCCESS(Status)) { goto CreatePipeEnd; } CreatePipeEnd: if (!KSUCCESS(Status)) { if (*ReadHandle != NULL) { IoClose(*ReadHandle); *ReadHandle = NULL; } if (*WriteHandle != NULL) { IoClose(*WriteHandle); *WriteHandle = NULL; } } return Status; } POBJECT_HEADER IopGetPipeDirectory ( VOID ) /*++ Routine Description: This routine returns the pipe root directory in the object system. This is the only place in the object system pipe creation is allowed. Arguments: None. Return Value: Returns a pointer to the pipe directory. --*/ { return IoPipeDirectory; } KSTATUS IopCreatePipe ( PSTR Name, ULONG NameSize, FILE_PERMISSIONS Permissions, PFILE_OBJECT *FileObject ) /*++ Routine Description: This routine actually creates a new pipe. Arguments: Name - Supplies an optional pointer to the pipe name. This is only used for named pipes created in the pipe directory. NameSize - Supplies the size of the name in bytes including the null terminator. Permissions - Supplies the permissions to give to the file object. FileObject - Supplies a pointer where a pointer to a newly created pipe file object will be returned on success. Return Value: Status code. --*/ { BOOL Created; PPIPE ExistingPipe; FILE_PROPERTIES FileProperties; PFILE_OBJECT NewFileObject; PPIPE NewPipe; KSTATUS Status; PKTHREAD Thread; NewFileObject = NULL; NewPipe = NULL; // // Make sure there is not already an existing pipe by the same name. The // caller should have the appropriate locks to make the check and create // synchronous. // if (Name != NULL) { ExistingPipe = ObFindObject(Name, NameSize, IoPipeDirectory); if (ExistingPipe != NULL) { ObReleaseReference(ExistingPipe); Status = STATUS_FILE_EXISTS; goto CreatePipeEnd; } } // // Create the actual object. This reference is transferred to the file // object's special I/O member on success. // NewPipe = ObCreateObject(ObjectPipe, IoPipeDirectory, Name, NameSize, sizeof(PIPE), IopDestroyPipe, 0, IO_ALLOCATION_TAG); if (NewPipe == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto CreatePipeEnd; } // // Record if the pipe got a name in the pipe directory. // if (Name != NULL) { NewPipe->Flags |= PIPE_FLAG_OBJECT_NAMED; } // // Create a file object if needed. // if (*FileObject == NULL) { Thread = KeGetCurrentThread(); IopFillOutFilePropertiesForObject(&FileProperties, &(NewPipe->Header)); FileProperties.Permissions = Permissions; FileProperties.Type = IoObjectPipe; FileProperties.UserId = Thread->Identity.EffectiveUserId; FileProperties.GroupId = Thread->Identity.EffectiveGroupId; Status = IopCreateOrLookupFileObject(&FileProperties, ObGetRootObject(), 0, &NewFileObject, &Created); if (!KSUCCESS(Status)) { // // Release the references added by filling out the file properties. // ObReleaseReference(NewPipe); goto CreatePipeEnd; } ASSERT(Created != FALSE); *FileObject = NewFileObject; } ASSERT((*FileObject)->Properties.Type == IoObjectPipe); // // Now fill in the pipe with the I/O object state. // ASSERT((*FileObject)->IoState != NULL); NewPipe->StreamBuffer = IoCreateStreamBuffer((*FileObject)->IoState, 0, 0, PIPE_ATOMIC_WRITE_SIZE); if (NewPipe->StreamBuffer == NULL) { Status = STATUS_INSUFFICIENT_RESOURCES; goto CreatePipeEnd; } // // Now that the pipe's ready, release anyone else who happened to find this // file object in the mean time. // ASSERT(((*FileObject)->SpecialIo == NULL) && ((KeGetEventState((*FileObject)->ReadyEvent) == NotSignaled) || (KeGetEventState((*FileObject)->ReadyEvent) == NotSignaledWithWaiters))); (*FileObject)->SpecialIo = NewPipe; NewPipe = NULL; Status = STATUS_SUCCESS; CreatePipeEnd: // // On both success and failure, the file object's ready event needs to be // signaled. Other threads may be waiting on the event. // if (*FileObject != NULL) { KeSignalEvent((*FileObject)->ReadyEvent, SignalOptionSignalAll); } if (!KSUCCESS(Status)) { if (NewFileObject != NULL) { *FileObject = NULL; IopFileObjectReleaseReference(NewFileObject); } if (NewPipe != NULL) { ObReleaseReference(NewPipe); NewPipe = NULL; } } return Status; } KSTATUS IopUnlinkPipe ( PFILE_OBJECT FileObject, PBOOL Unlinked ) /*++ Routine Description: This routine unlinks a pipe from the accessible namespace. Arguments: FileObject - Supplies a pointer to the pipe's file object. Unlinked - Supplies a pointer to a boolean that receives whether or not the terminal was successfully unlinked. Return Value: Status code. --*/ { PPIPE Pipe; KSTATUS Status; ASSERT(FileObject->Properties.Type == IoObjectPipe); ASSERT(KeIsSharedExclusiveLockHeldExclusive(FileObject->Lock) != FALSE); Pipe = FileObject->SpecialIo; ASSERT(Pipe != NULL); ASSERT((Pipe->Flags & PIPE_FLAG_OBJECT_NAMED) != 0); *Unlinked = FALSE; Status = ObUnlinkObject(Pipe); if (KSUCCESS(Status)) { *Unlinked = TRUE; } return Status; } KSTATUS IopOpenPipe ( PIO_HANDLE IoHandle ) /*++ Routine Description: This routine is called when a pipe is opened. Arguments: IoHandle - Supplies a pointer to the new I/O handle. Return Value: Status code. --*/ { PFILE_OBJECT FileObject; PIO_OBJECT_STATE IoState; BOOL NonBlocking; PPIPE Pipe; BOOL PipeOpened; ULONG ReturnedEvents; KSTATUS Status; PipeOpened = FALSE; FileObject = IoHandle->FileObject; ASSERT(FileObject->Properties.Type == IoObjectPipe); KeAcquireSharedExclusiveLockExclusive(FileObject->Lock); Pipe = FileObject->SpecialIo; if (Pipe == NULL) { ASSERT(FALSE); Status = STATUS_TOO_LATE; goto OpenPipeEnd; } if ((IoHandle->Access & IO_ACCESS_EXECUTE) != 0) { Status = STATUS_INVALID_PARAMETER; goto OpenPipeEnd; } IoState = IoStreamBufferGetIoObjectState(Pipe->StreamBuffer); if ((IoHandle->Access & IO_ACCESS_READ) != 0) { Pipe->ReaderCount += 1; // // Clear the error event. // IoSetIoObjectState(IoState, POLL_EVENT_ERROR, FALSE); } if ((IoHandle->Access & IO_ACCESS_WRITE) != 0) { Pipe->WriterCount += 1; // // Clear the disconnect event. // IoSetIoObjectState(IoState, POLL_EVENT_DISCONNECTED, FALSE); } PipeOpened = TRUE; // // Determine whether this is a blocking or non-blocking open. The initial // create/open call is also set non-blocking, and this relies a bit on the // fact that the read end is opened first. // NonBlocking = FALSE; if (((IoHandle->OpenFlags & OPEN_FLAG_NON_BLOCKING) != 0) || ((IoHandle->OpenFlags & (OPEN_FLAG_CREATE | OPEN_FLAG_FAIL_IF_EXISTS)) == (OPEN_FLAG_CREATE | OPEN_FLAG_FAIL_IF_EXISTS))) { NonBlocking = TRUE; } // // In non-blocking mode, open access for write only returns an error if no // process currently has the pipe open for reading. // if (NonBlocking != FALSE) { if (((IoHandle->Access & IO_ACCESS_WRITE) != 0) && (Pipe->ReaderCount == 0)) { Status = STATUS_NO_SUCH_DEVICE_OR_ADDRESS; goto OpenPipeEnd; } // // Handle a blocking open on a pipe, which blocks until the other end // connects. // } else { // // If there's no jelly for your peanut buffer, wait for some to arrive. // Borrow the write event to block on. // if ((((IoHandle->Access & IO_ACCESS_WRITE) != 0) && (Pipe->ReaderCount == 0)) || (((IoHandle->Access & IO_ACCESS_READ) != 0) && (Pipe->WriterCount == 0))) { IoSetIoObjectState(IoState, POLL_EVENT_OUT, FALSE); KeReleaseSharedExclusiveLockExclusive(FileObject->Lock); Status = IoWaitForIoObjectState(IoState, POLL_EVENT_OUT | POLL_EVENT_ERROR, TRUE, WAIT_TIME_INDEFINITE, &ReturnedEvents); KeAcquireSharedExclusiveLockExclusive(FileObject->Lock); if (!KSUCCESS(Status)) { goto OpenPipeEnd; } if ((ReturnedEvents & POLL_EVENT_OUT) == 0) { Status = STATUS_NOT_READY; goto OpenPipeEnd; } } } // // Reset the I/O object state, which sets the in and out poll events // properly. // Status = IoStreamBufferConnect(Pipe->StreamBuffer); if (!KSUCCESS(Status)) { goto OpenPipeEnd; } Status = STATUS_SUCCESS; OpenPipeEnd: KeReleaseSharedExclusiveLockExclusive(FileObject->Lock); if (!KSUCCESS(Status)) { if (PipeOpened != FALSE) { IopClosePipe(IoHandle); } } return Status; } KSTATUS IopClosePipe ( PIO_HANDLE IoHandle ) /*++ Routine Description: This routine is called when a pipe is closed. Arguments: IoHandle - Supplies a pointer to the I/O handle being closed. Return Value: Status code. --*/ { PFILE_OBJECT FileObject; PFILE_PROPERTIES FileProperties; PIO_OBJECT_STATE IoState; BOOL LockHeld; PPIPE Pipe; FileObject = IoHandle->FileObject; FileProperties = &(FileObject->Properties); ASSERT(FileProperties->Type == IoObjectPipe); KeAcquireSharedExclusiveLockExclusive(FileObject->Lock); LockHeld = TRUE; Pipe = FileObject->SpecialIo; IoState = IoStreamBufferGetIoObjectState(Pipe->StreamBuffer); if ((IoHandle->Access & IO_ACCESS_READ) != 0) { Pipe->ReaderCount -= 1; if (Pipe->ReaderCount == 0) { // // The last reader just closed, so clear the hangup event and the // out event. Set the error event. // IoSetIoObjectState(IoState, POLL_EVENT_OUT | POLL_EVENT_DISCONNECTED, FALSE); IoSetIoObjectState(IoState, POLL_EVENT_ERROR, TRUE); } } if ((IoHandle->Access & IO_ACCESS_WRITE) != 0) { Pipe->WriterCount -= 1; if (Pipe->WriterCount == 0) { // // Clear the out event, set the hangup event, and set the read // event. // IoSetIoObjectState(IoState, POLL_EVENT_OUT, FALSE); IoSetIoObjectState(IoState, POLL_EVENT_DISCONNECTED | POLL_EVENT_IN, TRUE); } } // // Pipes that are named in the object directory need to be unlinked on // the last close. Check to see if the reader and writer counts are both // zero. If so, unlink the object. It may be that another thread is about // to open the pipe for read and/or write. This is OK, it's got a reference // on the file object and can proceed without concern. When it closes the // pipe it will attempt the unlink again, but that's fine. No new lookups // can occur after the first unlink attempt. // if ((Pipe->Flags & PIPE_FLAG_OBJECT_NAMED) != 0) { if ((Pipe->WriterCount == 0) && (Pipe->ReaderCount == 0)) { KeReleaseSharedExclusiveLockExclusive(FileObject->Lock); LockHeld = FALSE; IopDeleteByHandle(TRUE, IoHandle, 0); } } if (LockHeld != FALSE) { KeReleaseSharedExclusiveLockExclusive(FileObject->Lock); } return STATUS_SUCCESS; } KSTATUS IopPerformPipeIoOperation ( PIO_HANDLE Handle, PIO_CONTEXT IoContext ) /*++ Routine Description: This routine reads from or writes to a pipe. Arguments: Handle - Supplies a pointer to the pipe I/O handle. IoContext - Supplies a pointer to the I/O context. Return Value: Status code. A failing status code does not necessarily mean no I/O made it in or out. Check the bytes completed value in the I/O context to find out how much occurred. --*/ { PFILE_OBJECT FileObject; BOOL NonBlocking; PPIPE Pipe; UINTN PipeBytesCompleted; KSTATUS Status; FileObject = Handle->FileObject; ASSERT(IoContext->IoBuffer != NULL); ASSERT(FileObject->Properties.Type == IoObjectPipe); Pipe = FileObject->SpecialIo; PipeBytesCompleted = 0; NonBlocking = FALSE; if (IoContext->Write != FALSE) { // // If there are no readers, send a pipe signal to the calling // application. // if (Pipe->ReaderCount == 0) { Status = STATUS_BROKEN_PIPE; } else { Status = IoWriteStreamBuffer(Pipe->StreamBuffer, IoContext->IoBuffer, IoContext->SizeInBytes, IoContext->TimeoutInMilliseconds, NonBlocking, &PipeBytesCompleted); } } else { if (Pipe->WriterCount == 0) { NonBlocking = TRUE; } Status = IoReadStreamBuffer(Pipe->StreamBuffer, IoContext->IoBuffer, IoContext->SizeInBytes, IoContext->TimeoutInMilliseconds, NonBlocking, &PipeBytesCompleted); if ((Status == STATUS_NO_DATA_AVAILABLE) && (Pipe->WriterCount == 0)) { ASSERT(PipeBytesCompleted == 0); Status = STATUS_END_OF_FILE; } } IoContext->BytesCompleted = PipeBytesCompleted; return Status; } // // --------------------------------------------------------- Internal Functions // VOID IopDestroyPipe ( PVOID PipeObject ) /*++ Routine Description: This routine destroys all resources associated with a pipe. Arguments: PipeObject - Supplies a pointer to the pipe object being destroyed. Return Value: None. --*/ { PPIPE Pipe; Pipe = (PPIPE)PipeObject; if (Pipe->StreamBuffer != NULL) { IoDestroyStreamBuffer(Pipe->StreamBuffer); } return; }