Windows2000/private/ntos/io/internal.c

8265 lines
264 KiB
C

/*++
Copyright (c) 1989-1993 Microsoft Corporation
Module Name:
internal.c
Abstract:
This module contains the internal subroutines used by the I/O system.
Author:
Darryl E. Havens (darrylh) 18-Apr-1989
Environment:
Kernel mode, local to I/O system
--*/
#include "iop.h"
#pragma hdrstop
#include <ioevent.h>
//PLJTMP
#if defined(_X86_)
VOID RtlAssert(IN PVOID FailedAssertion, IN PVOID FileName, IN ULONG LineNumber, IN PCHAR Message OPTIONAL);
#endif
//PLJTMPend
#define IsFileLocal( FileObject ) ( !((FileObject)->DeviceObject->Characteristics & FILE_REMOTE_DEVICE) )
typedef LINK_TRACKING_INFORMATION FILE_VOLUMEID_WITH_TYPE, *PFILE_VOLUMEID_WITH_TYPE;
typedef struct _TRACKING_BUFFER
{
FILE_TRACKING_INFORMATION TrackingInformation;
UCHAR Buffer[256];
} TRACKING_BUFFER, *PTRACKING_BUFFER;
typedef struct _REMOTE_LINK_BUFFER
{
REMOTE_LINK_TRACKING_INFORMATION TrackingInformation;
UCHAR Buffer[256];
} REMOTE_LINK_BUFFER, *PREMOTE_LINK_BUFFER;
PIRP IopDeadIrp;
NTSTATUS IopResurrectDriver(PDRIVER_OBJECT DriverObject);
VOID IopUserRundown(IN PKAPC Apc);
VOID IopMarshalIds(
OUT PTRACKING_BUFFER TrackingBuffer,
IN PFILE_VOLUMEID_WITH_TYPE TargetVolumeId,
IN PFILE_OBJECTID_BUFFER TargetObjectId,
IN PFILE_TRACKING_INFORMATION TrackingInfo
);
VOID
IopUnMarshalIds(
IN FILE_TRACKING_INFORMATION * TrackingInformation,
OUT FILE_VOLUMEID_WITH_TYPE * TargetVolumeId,
OUT GUID * TargetObjectId,
OUT GUID * TargetMachineId
);
NTSTATUS IopBootLogToFile(PUNICODE_STRING String);
VOID IopCopyBootLogRegistryToFile(VOID);
#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, IopAbortRequest)
#pragma alloc_text(PAGE, IopAcquireFileObjectLock)
#pragma alloc_text(PAGE, IopAllocateIrpCleanup)
#pragma alloc_text(PAGE, IopCancelAlertedRequest)
#pragma alloc_text(PAGE, IopCheckGetQuotaBufferValidity)
#pragma alloc_text(PAGE, IopConnectLinkTrackingPort)
#pragma alloc_text(PAGE, IopDeallocateApc)
#pragma alloc_text(PAGE, IopExceptionCleanup)
#pragma alloc_text(PAGE, IopGetDriverNameFromKeyNode)
#pragma alloc_text(PAGE, IopGetFileName)
#pragma alloc_text(PAGE, IopGetRegistryKeyInformation)
#pragma alloc_text(PAGE, IopGetRegistryValue)
#pragma alloc_text(PAGE, IopGetRegistryValues)
#pragma alloc_text(PAGE, IopGetSetObjectId)
#pragma alloc_text(PAGE, IopGetVolumeId)
#pragma alloc_text(PAGE, IopInvalidateVolumesForDevice)
#pragma alloc_text(PAGE, IopIsSameMachine)
#pragma alloc_text(PAGE, IopLoadDriver)
#pragma alloc_text(PAGE, IopLoadFileSystemDriver)
#pragma alloc_text(PAGE, IopLoadUnloadDriver)
#pragma alloc_text(PAGE, IopMountVolume)
#pragma alloc_text(PAGE, IopMarshalIds)
#pragma alloc_text(PAGE, IopOpenLinkOrRenameTarget)
#pragma alloc_text(PAGE, IopOpenRegistryKey)
#pragma alloc_text(PAGE, IopQueryXxxInformation)
#pragma alloc_text(PAGE, IopReadyDeviceObjects)
#pragma alloc_text(PAGE, IopSendMessageToTrackService)
#pragma alloc_text(PAGE, IopSetEaOrQuotaInformationFile)
#pragma alloc_text(PAGE, IopSetRemoteLink)
#pragma alloc_text(PAGE, IopSynchronousApiServiceTail)
#pragma alloc_text(PAGE, IopSynchronousServiceTail)
#pragma alloc_text(PAGE, IopTrackLink)
#pragma alloc_text(PAGE, IopUnMarshalIds)
#pragma alloc_text(PAGE, IopUserCompletion)
#pragma alloc_text(PAGE, IopUserRundown)
#pragma alloc_text(PAGE, IopXxxControlFile)
#pragma alloc_text(PAGE, IopLookupBusStringFromID)
#pragma alloc_text(PAGE, IopInitializeBootLogging)
#pragma alloc_text(PAGE, IopBootLog)
#pragma alloc_text(PAGE, IopCopyBootLogRegistryToFile)
#pragma alloc_text(PAGE, IopBootLogToFile)
#endif
VOID IopAbortRequest(IN PKAPC Apc)
/*++
Routine Description:
This routine is invoked to abort an I/O request. It is invoked during the rundown of a thread.
Arguments:
Apc - Pointer to the kernel APC structure. This structure is contained within the I/O Request Packet (IRP) itself.
Return Value:
None.
--*/
{
PAGED_CODE();
// Invoke the normal special kernel APC routine.
IopCompleteRequest(Apc, &Apc->NormalRoutine, &Apc->NormalContext, &Apc->SystemArgument1, &Apc->SystemArgument2);
}
NTSTATUS IopAcquireFileObjectLock(IN PFILE_OBJECT FileObject, IN KPROCESSOR_MODE RequestorMode, IN BOOLEAN Alertable, OUT PBOOLEAN Interrupted)
/*++
Routine Description:
This routine is invoked to acquire the lock for a file object whenever
there is contention and obtaining the fast lock for the file failed.
Arguments:
FileObject - Pointer to the file object whose lock is to be acquired.
RequestorMode - Processor access mode of the caller.
Alertable - Indicates whether or not the lock should be obtained in an alertable manner.
Interrupted - A variable to receive a BOOLEAN that indicates whether or
not the attempt to acquire the lock was interrupted by an alert or an APC.
Return Value:
The function status is the final status of the operation.
--*/
{
NTSTATUS status;
PAGED_CODE();
// Assume that the function will not be interrupted by an alert or an APC while attempting to acquire the lock.
*Interrupted = FALSE;
// Loop attempting to acquire the lock for the file object.
InterlockedIncrement(&FileObject->Waiters);
for (;;) {
if (!FileObject->Busy) {
// The file object appears to be un-owned, try to acquire it
if (IopAcquireFastLock(FileObject)) {
// Object was acquired. Remove our count and return success
InterlockedDecrement(&FileObject->Waiters);
return STATUS_SUCCESS;
}
}
// Wait for the event that indicates that the thread that currently
// owns the file object has released it.
status = KeWaitForSingleObject(&FileObject->Lock, Executive, RequestorMode, Alertable, (PLARGE_INTEGER)NULL);
// If the above wait was interrupted, then indicate so and return.
// Before returning, however, check the state of the ownership of
// the file object itself. If it is not currently owned (the busy
// flag is clear), then check to see whether or not there are any
// other waiters. If so, then set the event to the signaled state
// again so that they wake up and check the state of the busy flag.
if (status == STATUS_USER_APC || status == STATUS_ALERTED) {
InterlockedDecrement(&FileObject->Waiters);
if (!FileObject->Busy && FileObject->Waiters) {
KeSetEvent(&FileObject->Lock, 0, FALSE);
}
*Interrupted = TRUE;
return status;
}
}
}
VOID IopAllocateIrpCleanup(IN PFILE_OBJECT FileObject, IN PKEVENT EventObject OPTIONAL)
/*++
Routine Description:
This routine is invoked internally by those system services that attempt
to allocate an IRP and fail. This routine cleans up the file object
and any event object that has been references and releases any locks that were taken out.
Arguments:
FileObject - Pointer to the file object being worked on.
EventObject - Optional pointer to a referenced event to be dereferenced.
Return Value:
None.
--*/
{
PAGED_CODE();
// Begin by dereferencing the event, if one was specified.
if (ARGUMENT_PRESENT(EventObject)) {
ObDereferenceObject(EventObject);
}
// Release the synchronization semaphore if it is currently held and
// dereference the file object.
if (FileObject->Flags & FO_SYNCHRONOUS_IO) {
IopReleaseFileObjectLock(FileObject);
}
ObDereferenceObject(FileObject);
return;
}
PIRP IopAllocateIrpMustSucceed(IN CCHAR StackSize)
/*++
Routine Description:
This routine is invoked to allocate an IRP when there are no appropriate
packets remaining on the look-aside list, and no memory was available
from the general non-paged pool, and yet, the code path requiring the
packet has no way of backing out and simply returning an error. There-
fore, it must allocate an IRP. Hence, this routine is called to allocate that packet.
Arguments:
StackSize - Supplies the number of IRP I/O stack locations that the packet must have when allocated.
Return Value:
A pointer to the allocated I/O Request Packet.
--*/
{
PIRP irp;
USHORT packetSize;
// Attempt to allocate the IRP normally and failing that, allocate the
// IRP from nonpaged must succeed pool.
irp = IoAllocateIrp(StackSize, FALSE);
if (!irp) {
packetSize = IoSizeOfIrp(StackSize);
irp = ExAllocatePoolWithTag(NonPagedPoolMustSucceed, packetSize, ' prI');
IoInitializeIrp(irp, packetSize, StackSize);
irp->AllocationFlags |= IRP_ALLOCATED_MUST_SUCCEED;
}
return irp;
}
VOID IopApcHardError(IN PVOID StartContext)
/*++
Routine Description:
This function is invoked when we need to do a hard error pop-up, but the
Irp's originating thread is at APC level, ie. IoPageRead. We in a special
purpose thread that will go away when the user responds to the pop-up.
Arguments:
StartContext - Startup context, contains a IOP_APC_HARD_ERROR_PACKET.
Return Value:
None.
--*/
{
PIOP_APC_HARD_ERROR_PACKET packet;
packet = StartContext;
IopRaiseHardError(packet->Irp, packet->Vpb, packet->RealDeviceObject);
ExFreePool(packet);
}
VOID IopCancelAlertedRequest(IN PKEVENT Event, IN PIRP Irp)
/*++
Routine Description:
This routine is invoked when a synchronous I/O operation that is blocked in
the I/O system needs to be canceled because the thread making the request has
either been alerted because it is going away or because of a CTRL/C. This
routine carefully attempts to work its way out of the current operation so
that local events or other local data will not be accessed once the service
being interrupted returns.
Arguments:
Event - The address of a kernel event that will be set to the Signaled state by I/O completion when the request is complete.
Irp - Pointer to the I/O Request Packet (IRP) representing the current request.
Return Value:
None.
--*/
{
KIRQL irql;
LARGE_INTEGER deltaTime;
BOOLEAN canceled;
PAGED_CODE();
// Begin by blocking special kernel APCs so that the request cannot complete.
KeRaiseIrql(APC_LEVEL, &irql);
// Check the state of the event to determine whether or not the
// packet has already been completed.
if (KeReadStateEvent(Event) == 0) {
// The packet has not been completed, so attempt to cancel it.
canceled = IoCancelIrp(Irp);
KeLowerIrql(irql);
if (canceled) {
// The packet had a cancel routine, so it was canceled. Loop,
// waiting for the packet to complete. This should occur almost immediately.
deltaTime.QuadPart = -10 * 1000 * 10;
while (KeReadStateEvent(Event) == 0) {
KeDelayExecutionThread(KernelMode, FALSE, &deltaTime);
}
} else {
// The packet did not have a cancel routine, so simply wait for
// the event to be set to the Signaled state. This will save
// CPU time by not looping, since it is not known when the packet
// will actually complete. Note, however, that the cancel flag
// is set in the packet, so should a driver examine the flag
// at some point in the future, it will immediately stop
// processing the request.
(VOID)KeWaitForSingleObject(Event, Executive, KernelMode, FALSE, (PLARGE_INTEGER)NULL);
}
} else {
// The packet has already been completed, so simply lower the
// IRQL back to its original value and exit.
KeLowerIrql(irql);
}
}
NTSTATUS IopCheckGetQuotaBufferValidity(IN PFILE_GET_QUOTA_INFORMATION QuotaBuffer, IN ULONG QuotaLength, OUT PULONG_PTR ErrorOffset)
/*++
Routine Description:
This routine checks the validity of the specified get quota buffer to
guarantee that its format is proper, no fields hang over, that it is
not recursive, etc.
Arguments:
QuotaBuffer - Pointer to the buffer containing the get quota structure array to be checked.
QuotaLength - Specifies the length of the quota buffer.
ErrorOffset - A variable to receive the offset of the offending entry
in the quota buffer if an error is incurred. This variable is only valid if an error occurs.
Return Value:
The function value is STATUS_SUCCESS if the get quota buffer contains a
valid, properly formed list, otherwise STATUS_QUOTA_LIST_INCONSISTENT.
--*/
{
#define GET_OFFSET_LENGTH( CurrentSid, SidBase ) ( (ULONG) ((PCHAR) CurrentSid - (PCHAR) SidBase) )
LONG tempLength;
LONG entrySize;
PFILE_GET_QUOTA_INFORMATION sids;
PAGED_CODE();
// Walk the buffer and ensure that its format is valid. That is, ensure
// that it does not walk off the end of the buffer, is not recursive, etc.
sids = QuotaBuffer;
tempLength = QuotaLength;
for (;;) {
// Ensure that the current entry is valid.
if ((tempLength < (LONG)(FIELD_OFFSET(FILE_GET_QUOTA_INFORMATION, Sid.SubAuthority) + sizeof(sids->Sid.SubAuthority))) || !RtlValidSid(&sids->Sid)) {
*ErrorOffset = GET_OFFSET_LENGTH(sids, QuotaBuffer);
return STATUS_QUOTA_LIST_INCONSISTENT;
}
// Get the size of the current entry in the buffer.
entrySize = FIELD_OFFSET(FILE_GET_QUOTA_INFORMATION, Sid) + RtlLengthSid((&sids->Sid));
if (sids->NextEntryOffset) {
// There is another entry in the buffer and it must be longword
// aligned. Ensure that the offset indicates that it is. If it
// isn't, return an invalid parameter status.
if (entrySize > (LONG)sids->NextEntryOffset || sids->NextEntryOffset & (sizeof(ULONG) - 1)) {
*ErrorOffset = GET_OFFSET_LENGTH(sids, QuotaBuffer);
return STATUS_QUOTA_LIST_INCONSISTENT;
} else {
// There is another entry in the buffer, so account for the
// size of the current entry in the length and get a pointer to the next entry.
tempLength -= sids->NextEntryOffset;
if (tempLength < 0) {
*ErrorOffset = GET_OFFSET_LENGTH(sids, QuotaBuffer);
return STATUS_QUOTA_LIST_INCONSISTENT;
}
sids = (PFILE_GET_QUOTA_INFORMATION)((PCHAR)sids + sids->NextEntryOffset);
}
} else {
// There are no other entries in the buffer. Simply account for
// the overall buffer length according to the size of the current
// entry and exit the loop.
tempLength -= entrySize;
break;
}
}
// All of the entries in the buffer have been processed. Check to see
// whether the overall buffer length went negative. If so, return an error.
if (tempLength < 0) {
*ErrorOffset = GET_OFFSET_LENGTH(sids, QuotaBuffer);
return STATUS_QUOTA_LIST_INCONSISTENT;
}
// The format of the get quota buffer was correct, so simply return a success status code.
return STATUS_SUCCESS;
}
VOID IopCompleteUnloadOrDelete(IN PDEVICE_OBJECT DeviceObject, IN KIRQL Irql)
/*++
Routine Description:
This routine is invoked when the reference count on a device object
transitions to a zero and the driver is mark for unload or device has
been marked for delete. This means that it may be possible to actually
unload the driver or delete the device object. If all
of the devices have a reference count of zero, then the driver is
actually unloaded. Note that in order to ensure that this routine is
not invoked twice, at the same time, on two different processors, the
I/O database spin lock is still held at this point.
Arguments:
DeviceObject - Supplies a pointer to one of the driver's device objects, namely the one whose reference count just went to zero.
Irql - Specifies the IRQL of the processor at the time that the I/O database lock was acquired.
Return Value:
None.
--*/
{
PDRIVER_OBJECT driverObject;
PDEVICE_OBJECT deviceObject;
PDEVICE_OBJECT baseDeviceObject;
PDEVICE_OBJECT attachedDeviceObject;
PDEVOBJ_EXTENSION deviceExtension;
PDEVICE_NODE deviceNode;
BOOLEAN unload = TRUE;
driverObject = DeviceObject->DriverObject;
if (DeviceObject->DeviceObjectExtension->ExtensionFlags & DOE_REMOVE_PENDING) {
// Run some tests to determine if it is an appropriate time to notify
// PnP that all file objects in the attachment chain have gone away.
baseDeviceObject = IopGetDeviceAttachmentBase(DeviceObject);
deviceExtension = baseDeviceObject->DeviceObjectExtension;
deviceNode = (PDEVICE_NODE)deviceExtension->DeviceNode;
ASSERT(deviceNode != NULL);
// baseDeviceObject is a PDO, this is a PnP stack. See if an IRP_MN_REMOVE_DEVICE is pending.
ASSERT(deviceNode->Flags & DNF_REMOVE_PENDING_CLOSES);
// PnP wants to be notified as soon as all refcounts on all devices in this attachment chain go away.
attachedDeviceObject = baseDeviceObject;
while (attachedDeviceObject != NULL) {
if (attachedDeviceObject->ReferenceCount != 0) {
// At least one device object in the attachment chain has an outstanding open.
ExReleaseSpinLock(&IopDatabaseLock, Irql);
return;
}
attachedDeviceObject = attachedDeviceObject->AttachedDevice;
}
// Now one more time changing DOE_REMOVE_PENDING to DOE_REMOVE_PROCESSED.
attachedDeviceObject = baseDeviceObject;
while (attachedDeviceObject != NULL) {
deviceExtension = attachedDeviceObject->DeviceObjectExtension;
deviceExtension->ExtensionFlags &= ~DOE_REMOVE_PENDING;
deviceExtension->ExtensionFlags |= DOE_REMOVE_PROCESSED;
attachedDeviceObject = attachedDeviceObject->AttachedDevice;
}
// It is time to give PnP the notification it was waiting for. We have
// to release the spinlock before doing so.
ExReleaseSpinLock(&IopDatabaseLock, Irql);
IopChainDereferenceComplete(baseDeviceObject);
return;
}
if (DeviceObject->DeviceObjectExtension->ExtensionFlags & DOE_DELETE_PENDING) {
if ((DeviceObject->DeviceObjectExtension->ExtensionFlags & DOE_UNLOAD_PENDING) == 0 || driverObject->Flags & DRVO_UNLOAD_INVOKED) {
unload = FALSE;
}
// If another device is attached to this device, inform the former's
// driver that the device is being deleted.
if (DeviceObject->AttachedDevice) {
PFAST_IO_DISPATCH fastIoDispatch = DeviceObject->AttachedDevice->DriverObject->FastIoDispatch;
PDEVICE_OBJECT attachedDevice = DeviceObject->AttachedDevice;
// Increment the device reference count so the detach routine
// does not recurse back to here.
DeviceObject->ReferenceCount++;
ExReleaseSpinLock(&IopDatabaseLock, Irql);
if (fastIoDispatch &&
fastIoDispatch->SizeOfFastIoDispatch > FIELD_OFFSET(FAST_IO_DISPATCH, FastIoDetachDevice) &&
fastIoDispatch->FastIoDetachDevice) {
(fastIoDispatch->FastIoDetachDevice)(attachedDevice, DeviceObject);
}
ExAcquireSpinLock(&IopDatabaseLock, &Irql);
// Restore the reference count value.
DeviceObject->ReferenceCount--;
if (DeviceObject->AttachedDevice || DeviceObject->ReferenceCount != 0) {
ExReleaseSpinLock(&IopDatabaseLock, Irql);
return;
}
}
ExReleaseSpinLock(&IopDatabaseLock, Irql);
// Deallocate the memory for the security descriptor that was allocated for this device object.
if (DeviceObject->SecurityDescriptor != (PSECURITY_DESCRIPTOR)NULL) {
ExFreePool(DeviceObject->SecurityDescriptor);
}
// Remove this device object from the driver object's list.
IopInsertRemoveDevice(DeviceObject->DriverObject, DeviceObject, FALSE);
// Finally, dereference the object so it is deleted.
ObDereferenceObject(DeviceObject);
// Return if the unload does not need to be done.
if (!unload) {
return;
}
// Reacquire the spin lock make sure the unload routine does has not been called.
ExAcquireSpinLock(&IopDatabaseLock, &Irql);
if (driverObject->Flags & DRVO_UNLOAD_INVOKED) {
// Some other thread is doing the unload, release the lock and return.
ExReleaseSpinLock(&IopDatabaseLock, Irql);
return;
}
}
// Scan the list of device objects for this driver, looking for a
// non-zero reference count. If any reference count is non-zero, then the driver may not be unloaded.
deviceObject = driverObject->DeviceObject;
while (deviceObject) {
if (deviceObject->ReferenceCount || deviceObject->AttachedDevice ||
deviceObject->DeviceObjectExtension->ExtensionFlags & (DOE_DELETE_PENDING | DOE_REMOVE_PENDING)) {
unload = FALSE;
break;
}
deviceObject = deviceObject->NextDevice;
}
if (unload) {
driverObject->Flags |= DRVO_UNLOAD_INVOKED;
}
ExReleaseSpinLock(&IopDatabaseLock, Irql);
// If the reference counts for all of the devices is zero, then this
// driver can now be unloaded.
if (unload) {
LOAD_PACKET loadPacket;
KeInitializeEvent(&loadPacket.Event, NotificationEvent, FALSE);
loadPacket.DriverObject = driverObject;
ExInitializeWorkItem(&loadPacket.WorkQueueItem, IopLoadUnloadDriver, &loadPacket);
ExQueueWorkItem(&loadPacket.WorkQueueItem, DelayedWorkQueue);
(VOID)KeWaitForSingleObject(&loadPacket.Event, Executive, KernelMode, FALSE, (PLARGE_INTEGER)NULL);
ObMakeTemporaryObject(driverObject);
ObDereferenceObject(driverObject);
}
}
VOID IopCompletePageWrite(IN PKAPC Apc,
IN PKNORMAL_ROUTINE *NormalRoutine,
IN PVOID *NormalContext,
IN PVOID *SystemArgument1,
IN PVOID *SystemArgument2)
/*++
Routine Description:
This routine executes as a special kernel APC routine in the context of
the Modified Page Writer (MPW) system thread when an out-page operation has completed.
This routine performs the following tasks:
o The I/O status is copied.
o The Modified Page Writer's APC routine is invoked.
Arguments:
Apc - Supplies a pointer to kernel APC structure.
NormalRoutine - Supplies a pointer to a pointer to the normal function that was specified when the APC was initialized.
NormalContext - Supplies a pointer to a pointer to an arbitrary data structure that was specified when the APC was initialized.
SystemArgument1 - Supplies a pointer to an argument that contains an argument that is unused by this routine.
SystemArgument2 - Supplies a pointer to an argument that contains an argument that is unused by this routine.
Return Value:
None.
--*/
{
PIRP irp;
PIO_APC_ROUTINE apcRoutine;
PVOID apcContext;
PIO_STATUS_BLOCK ioStatus;
UNREFERENCED_PARAMETER(NormalRoutine);
UNREFERENCED_PARAMETER(NormalContext);
UNREFERENCED_PARAMETER(SystemArgument1);
UNREFERENCED_PARAMETER(SystemArgument2);
// Begin by getting the address of the I/O Request Packet from the APC.
irp = CONTAINING_RECORD(Apc, IRP, Tail.Apc);
// If this I/O operation did not complete successfully through the
// dispatch routine of the driver, then drop everything on the floor
// now and return to the original call point in the MPW.
if (!irp->PendingReturned && NT_ERROR(irp->IoStatus.Status)) {
IoFreeIrp(irp);
return;
}
// Copy the I/O status from the IRP into the caller's I/O status block.
*irp->UserIosb = irp->IoStatus;
// Copy the pertinent information from the I/O Request Packet into locals and free it.
apcRoutine = irp->Overlay.AsynchronousParameters.UserApcRoutine;
apcContext = irp->Overlay.AsynchronousParameters.UserApcContext;
ioStatus = irp->UserIosb;
IoFreeIrp(irp);
// Finally, invoke the MPW's APC routine.
apcRoutine(apcContext, ioStatus, 0);
return;
}
VOID IopCompleteRequest(IN PKAPC Apc,
IN PKNORMAL_ROUTINE *NormalRoutine,
IN PVOID *NormalContext,
IN PVOID *SystemArgument1,
IN PVOID *SystemArgument2)
/*++
Routine Description:
This routine executes as a special kernel APC routine in the context of
the thread which originally requested the I/O operation which is now being completed.
This routine performs the following tasks:
o A check is made to determine whether the specified request ended
with an error status. If so, and the error code qualifies as one
which should be reported to an error port, then an error port is
looked for in the thread/process. If one exists, then this routine
will attempt to set up an LPC to it. Otherwise, it will attempt to set up an LPC to the system error port.
o Copy buffers.
o Free MDLs.
o Copy I/O status.
o Set event, if any and dereference if appropriate.
o Dequeue the IRP from the thread queue as pending I/O request.
o Queue APC to thread, if any.
o If no APC is to be queued, then free the packet now.
Arguments:
Apc - Supplies a pointer to kernel APC structure.
NormalRoutine - Supplies a pointer to a pointer to the normal function that was specified when the APC was initialied.
NormalContext - Supplies a pointer to a pointer to an arbitrary data structure that was specified when the APC was initialized.
SystemArgument1 - Supplies a pointer to an argument that contains the address of the original file object for this I/O operation.
SystemArgument2 - Supplies a pointer to an argument that contains an argument that is used by this routine only in the case of STATUS_REPARSE.
Return Value:
None.
--*/
{
#define SynchronousIo( Irp, FileObject ) ( \
(Irp->Flags & IRP_SYNCHRONOUS_API) || \
(FileObject == NULL ? 0 : FileObject->Flags & FO_SYNCHRONOUS_IO) )
PIRP irp;
PMDL mdl, nextMdl;
PETHREAD thread;
PFILE_OBJECT fileObject;
NTSTATUS status;
UNREFERENCED_PARAMETER(NormalRoutine);
UNREFERENCED_PARAMETER(NormalContext);
// Begin by getting the address of the I/O Request Packet. Also, get
// the address of the current thread and the address of the original file
// object for this I/O operation.
irp = CONTAINING_RECORD(Apc, IRP, Tail.Apc);
thread = PsGetCurrentThread();
fileObject = (PFILE_OBJECT)*SystemArgument1;
IOVP_COMPLETE_REQUEST(Apc, SystemArgument1, SystemArgument2);
// Ensure that the packet is not being completed with a minus one. This
// is apparently a common problem in some drivers, and has no meaning
// as a status code.
ASSERT(irp->IoStatus.Status != 0xffffffff);
// See if we need to do the name transmogrify work.
if (*SystemArgument2 != NULL) {
PREPARSE_DATA_BUFFER reparseBuffer = NULL;
// The IO_REPARSE_TAG_MOUNT_POINT tag needs attention.
if (irp->IoStatus.Status == STATUS_REPARSE && irp->IoStatus.Information == IO_REPARSE_TAG_MOUNT_POINT) {
reparseBuffer = (PREPARSE_DATA_BUFFER)*SystemArgument2;
ASSERT(reparseBuffer->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT);
ASSERT(reparseBuffer->ReparseDataLength < MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
ASSERT(reparseBuffer->Reserved < MAXIMUM_REPARSE_DATA_BUFFER_SIZE);
IopDoNameTransmogrify(irp, fileObject, reparseBuffer);
}
}
// Check to see whether there is any data in a system buffer which needs
// to be copied to the caller's buffer. If so, copy the data and then
// free the system buffer if necessary.
if (irp->Flags & IRP_BUFFERED_IO) {
// Copy the data if this was an input operation. Note that no copy
// is performed if the status indicates that a verify operation is
// required, or if the final status was an error-level severity.
if (irp->Flags & IRP_INPUT_OPERATION &&
irp->IoStatus.Status != STATUS_VERIFY_REQUIRED &&
!NT_ERROR(irp->IoStatus.Status)) {
// Copy the information from the system buffer to the caller's
// buffer. This is done with an exception handler in case
// the operation fails because the caller's address space
// has gone away, or it's protection has been changed while
// the service was executing.
try {
RtlCopyMemory(irp->UserBuffer, irp->AssociatedIrp.SystemBuffer, irp->IoStatus.Information);
} except(IopExceptionFilter(GetExceptionInformation(), &status))
{
// An exception occurred while attempting to copy the
// system buffer contents to the caller's buffer. Set
// a new I/O completion status.
// If the status is a special one set by Mm then we need to
// return here and the operation will be retried in IoRetryIrpCompletions.
if (status == STATUS_MULTIPLE_FAULT_VIOLATION) {
irp->Tail.Overlay.OriginalFileObject = fileObject; /* Wiped out by APC overlay */
irp->Flags |= IRP_RETRY_IO_COMPLETION;
return;
}
irp->IoStatus.Status = GetExceptionCode();
}
}
// Free the buffer if needed.
if (irp->Flags & IRP_DEALLOCATE_BUFFER) {
ExFreePool(irp->AssociatedIrp.SystemBuffer);
}
}
irp->Flags &= ~(IRP_DEALLOCATE_BUFFER | IRP_BUFFERED_IO);
// If there is an MDL (or MDLs) associated with this I/O request,
// Free it (them) here. This is accomplished by walking the MDL list
// hanging off of the IRP and deallocating each MDL encountered.
if (irp->MdlAddress) {
for (mdl = irp->MdlAddress; mdl != NULL; mdl = nextMdl) {
nextMdl = mdl->Next;
IoFreeMdl(mdl);
}
}
irp->MdlAddress = NULL;
// Check to see whether or not the I/O operation actually completed. If
// it did, then proceed normally. Otherwise, cleanup everything and get
// out of here.
if (!NT_ERROR(irp->IoStatus.Status) ||
(NT_ERROR(irp->IoStatus.Status) &&
irp->PendingReturned &&
!SynchronousIo(irp, fileObject))) {
PVOID port = NULL;
PVOID key;
BOOLEAN createOperation = FALSE;
// If there is an I/O completion port object associated w/this request,
// save it here so that the file object can be dereferenced.
if (fileObject && fileObject->CompletionContext) {
port = fileObject->CompletionContext->Port;
key = fileObject->CompletionContext->Key;
}
// Copy the I/O status from the IRP into the caller's I/O status
// block. This is done using an exception handler in case the caller's
// virtual address space for the I/O status block was deleted or
// its protection was changed to readonly. Note that if the I/O
// status block cannot be written, the error is simply ignored since
// there is no way to tell the caller that something went wrong.
// This is, of course, by definition, since the I/O status block
// is where the caller will attempt to look for errors in the first
// place!
try {
// Since HasOverlappedIoCompleted and GetOverlappedResult only
// look at the Status field of the UserIosb to determine if the
// IRP has completed, the Information field must be written
// before the Status field.
#if defined(_M_ALPHA) && !defined(NT_UP)
#define MEMORY_BARRIER() __MB()
#else
#define MEMORY_BARRIER()
#endif
#if defined(_WIN64)
PIO_STATUS_BLOCK32 UserIosb32;
// If the caller passes a 32 bit IOSB the ApcRoutine has the LSB set to 1
if ((ULONG_PTR)(irp->Overlay.AsynchronousParameters.UserApcRoutine) & 1) {
UserIosb32 = (PIO_STATUS_BLOCK32)irp->UserIosb;
UserIosb32->Information = (ULONG)irp->IoStatus.Information;
MEMORY_BARRIER();
UserIosb32->Status = (NTSTATUS)irp->IoStatus.Status;
} else {
irp->UserIosb->Information = irp->IoStatus.Information;
MEMORY_BARRIER();
irp->UserIosb->Status = irp->IoStatus.Status;
}
#else
irp->UserIosb->Information = irp->IoStatus.Information;
MEMORY_BARRIER();
irp->UserIosb->Status = irp->IoStatus.Status;
#endif /*_WIN64 */
} except(IopExceptionFilter(GetExceptionInformation(), &status))
{
// An exception was incurred attempting to write the caller's
// I/O status block. Simply continue executing as if nothing
// ever happened since nothing can be done about it anyway.
// If the status is a multiple fault status, this is a special
// status sent by the Memory manager. Mark the IRP and return from
// this routine. Mm will call us back later and we will retry this
// operation (IoRetryIrpCompletions)
if (status == STATUS_MULTIPLE_FAULT_VIOLATION) {
irp->Tail.Overlay.OriginalFileObject = fileObject; /* Wiped out by APC overlay */
irp->Flags |= IRP_RETRY_IO_COMPLETION;
return;
}
}
// Determine whether the caller supplied an event that needs to be set
// to the Signaled state. If so, then set it; otherwise, set the event
// in the file object to the Signaled state.
// It is possible for the event to have been specified as a PKEVENT if
// this was an I/O operation hand-built for an FSP or an FSD, or
// some other types of operations such as synchronous I/O APIs. In
// any of these cases, the event was not referenced since it is not an
// object manager event, so it should not be dereferenced.
// Also, it is possible for there not to be a file object for this IRP.
// This occurs when an FSP is doing I/O operations to a device driver on
// behalf of a process doing I/O to a file. The file object cannot be
// dereferenced if this is the case. If this operation was a create
// operation then the object should not be dereferenced either. This
// is because the reference count must be one or it will go away for
// the caller (not much point in making an object that just got created
// go away).
if (irp->UserEvent) {
(VOID)KeSetEvent(irp->UserEvent, 0, FALSE);
if (fileObject) {
if (!(irp->Flags & IRP_SYNCHRONOUS_API)) {
ObDereferenceObject(irp->UserEvent);
}
if (fileObject->Flags & FO_SYNCHRONOUS_IO && !(irp->Flags & IRP_OB_QUERY_NAME)) {
(VOID)KeSetEvent(&fileObject->Event, 0, FALSE);
fileObject->FinalStatus = irp->IoStatus.Status;
}
if (irp->Flags & IRP_CREATE_OPERATION) {
createOperation = TRUE;
irp->Overlay.AsynchronousParameters.UserApcRoutine = (PIO_APC_ROUTINE)NULL;
}
}
} else if (fileObject) {
(VOID)KeSetEvent(&fileObject->Event, 0, FALSE);
fileObject->FinalStatus = irp->IoStatus.Status;
if (irp->Flags & IRP_CREATE_OPERATION) {
createOperation = TRUE;
irp->Overlay.AsynchronousParameters.UserApcRoutine = (PIO_APC_ROUTINE)NULL;
}
}
// If this is normal I/O, update the transfer count for this process.
if (!(irp->Flags & IRP_CREATE_OPERATION)) {
if (irp->Flags & IRP_READ_OPERATION) {
IopUpdateReadTransferCount((ULONG)irp->IoStatus.Information);
} else if (irp->Flags & IRP_WRITE_OPERATION) {
IopUpdateWriteTransferCount((ULONG)irp->IoStatus.Information);
} else {
// If the information field contains a pointer then skip the update.
// Some PNP IRPs contain this.
if (!((ULONG)irp->IoStatus.Information & 0x80000000)) {
IopUpdateOtherTransferCount((ULONG)irp->IoStatus.Information);
}
}
}
// Dequeue the packet from the thread's pending I/O request list.
IopDequeueThreadIrp(irp);
// If the caller requested an APC, queue it to the thread. If not, then
// simply free the packet now.
#ifdef _WIN64
// For 64 bit systems clear the LSB field of the ApcRoutine that indicates whether
// the IOSB is a 32 bit IOSB or a 64 bit IOSB.
irp->Overlay.AsynchronousParameters.UserApcRoutine =
(PIO_APC_ROUTINE)((LONG_PTR)(irp->Overlay.AsynchronousParameters.UserApcRoutine) & ~1);
#endif
if (irp->Overlay.AsynchronousParameters.UserApcRoutine) {
KeInitializeApc(&irp->Tail.Apc,
&thread->Tcb,
CurrentApcEnvironment,
IopUserCompletion,
(PKRUNDOWN_ROUTINE)IopUserRundown,
(PKNORMAL_ROUTINE)irp->Overlay.AsynchronousParameters.UserApcRoutine,
irp->RequestorMode,
irp->Overlay.AsynchronousParameters.UserApcContext);
KeInsertQueueApc(&irp->Tail.Apc,
irp->UserIosb,
NULL,
2);
} else if (port && irp->Overlay.AsynchronousParameters.UserApcContext) {
// If there is a completion context associated w/this I/O operation,
// send the message to the port. Tag completion packet as an Irp.
irp->Tail.CompletionKey = key;
irp->Tail.Overlay.PacketType = IopCompletionPacketIrp;
KeInsertQueue((PKQUEUE)port,
&irp->Tail.Overlay.ListEntry);
} else {
// Free the IRP now since it is no longer needed.
IoFreeIrp(irp);
}
if (fileObject && !createOperation) {
// Dereference the file object now.
ObDereferenceObject(fileObject);
}
} else {
if (irp->PendingReturned && fileObject) {
// This is an I/O operation that completed as an error for
// which a pending status was returned and the I/O operation
// is synchronous. For this case, the I/O system is waiting
// on behalf of the caller. If the reason that the I/O was
// synchronous is that the file object was opened for synchronous
// I/O, then the event associated with the file object is set
// to the signaled state. If the I/O operation was synchronous
// because this is a synchronous API, then the event is set to
// the signaled state.
// Note also that the status must be returned for both types
// of synchronous I/O. If this is a synchronous API, then the
// I/O system supplies its own status block so it can simply
// be written; otherwise, the I/O system will obtain the final
// status from the file object itself.
if (irp->Flags & IRP_SYNCHRONOUS_API) {
*irp->UserIosb = irp->IoStatus;
if (irp->UserEvent) {
(VOID)KeSetEvent(irp->UserEvent, 0, FALSE);
} else {
(VOID)KeSetEvent(&fileObject->Event, 0, FALSE);
}
} else {
fileObject->FinalStatus = irp->IoStatus.Status;
(VOID)KeSetEvent(&fileObject->Event, 0, FALSE);
}
}
// The operation was incomplete. Perform the general cleanup. Note
// that everything is basically dropped on the floor without doing
// anything. That is:
// IoStatusBlock - Do nothing.
// Event - Dereference without setting to Signaled state.
// FileObject - Dereference without setting to Signaled state.
// ApcRoutine - Do nothing.
if (fileObject) {
if (!(irp->Flags & IRP_CREATE_OPERATION)) {
ObDereferenceObject(fileObject);
}
}
if (irp->UserEvent &&
fileObject &&
!(irp->Flags & IRP_SYNCHRONOUS_API)) {
ObDereferenceObject(irp->UserEvent);
}
IopDequeueThreadIrp(irp);
IoFreeIrp(irp);
}
}
VOID
IopConnectLinkTrackingPort(
IN PVOID Parameter
)
/*++
Routine Description:
This routine is invoked to connect to the user-mode link tracking service's
LPC port. It makes a connection which establishes a handle to the port,
and then creates a referenced object pointer to the port.
Arguments:
Parameter - Pointer to the link tracking packet.
Return Value:
None.
--*/
{
#define MESSAGE_SIZE ( (2 * sizeof( FILE_VOLUMEID_WITH_TYPE )) + \
sizeof( FILE_OBJECTID_BUFFER ) + \
sizeof( GUID ) + \
sizeof( NTSTATUS ) + \
sizeof( ULONG ) )
PLINK_TRACKING_PACKET ltp;
HANDLE serviceHandle;
NTSTATUS status;
PAGED_CODE();
// Begin by getting a pointer to the link tracking packet.
ltp = (PLINK_TRACKING_PACKET)Parameter;
// Ensure that the port has not already been opened.
status = STATUS_SUCCESS;
if (!IopLinkTrackingServiceObject) {
UNICODE_STRING portName;
ULONG maxMessageLength;
SECURITY_QUALITY_OF_SERVICE dynamicQos;
if (KeReadStateEvent(IopLinkTrackingServiceEvent)) {
// Attempt to open a handle to the port.
// Set up the security quality of service parameters to use over the
// port. Use the most efficient (least overhead) which is dynamic
// rather than static tracking.
dynamicQos.ImpersonationLevel = SecurityImpersonation;
dynamicQos.ContextTrackingMode = SECURITY_DYNAMIC_TRACKING;
dynamicQos.EffectiveOnly = TRUE;
// Generate the string structure for describing the port.
RtlInitUnicodeString(&portName, L"\\Security\\TRKWKS_PORT");
status = NtConnectPort(&serviceHandle,
&portName,
&dynamicQos,
(PPORT_VIEW)NULL,
(PREMOTE_PORT_VIEW)NULL,
&maxMessageLength,
(PVOID)NULL,
(PULONG)NULL);
if (NT_SUCCESS(status)) {
if (maxMessageLength >= MESSAGE_SIZE) {
status = ObReferenceObjectByHandle(serviceHandle,
0,
LpcPortObjectType,
KernelMode,
&IopLinkTrackingServiceObject,
NULL);
NtClose(serviceHandle);
} else {
NtClose(serviceHandle);
status = STATUS_INVALID_PARAMETER;
}
}
} else {
// The service has not been started so the port does not exist.
status = STATUS_OBJECT_NAME_NOT_FOUND;
}
}
// Return final status and wake the caller up.
ltp->FinalStatus = status;
KeSetEvent(&ltp->Event, 0, FALSE);
}
VOID
IopDisassociateThreadIrp(
VOID
)
/*++
Routine Description:
This routine is invoked when the I/O requests for a thread are being
cancelled, but there is a packet at the end of the thread's queue that
has not been completed for such a long period of time that it has timed
out. It is this routine's responsibility to try to disassociate that
IRP with this thread.
Arguments:
None.
Return Value:
None.
--*/
{
KIRQL irql;
KIRQL spIrql;
PIRP irp;
PETHREAD thread;
PLIST_ENTRY entry;
PIO_STACK_LOCATION irpSp;
PDEVICE_OBJECT deviceObject;
PDRIVER_OBJECT driverObject;
WCHAR buffer[512];
POBJECT_NAME_INFORMATION nameInformation;
ULONG nameLength;
NTSTATUS status;
ULONG response;
PIO_ERROR_LOG_PACKET errorLogEntry;
// Begin by ensuring that the packet has not already been removed from
// the thread's queue.
KeRaiseIrql(APC_LEVEL, &irql);
thread = PsGetCurrentThread();
// If there are no packets on the IRP list, then simply return now.
// All of the packets have been fully completed, so the caller will also
// simply return to its caller.
if (IsListEmpty(&thread->IrpList)) {
KeLowerIrql(irql);
return;
}
// Get a pointer to the first packet on the queue, and begin examining
// it. Note that because the processor is at raised IRQL, and because
// the packet can only be removed in the context of the currently
// executing thread, that it is not possible for the packet to be removed
// from the list. On the other hand, it IS possible for the packet to
// be queued to the thread's APC list at this point, and this must be
// blocked/synchronized in order to examine the request.
// Begin, therefore, by acquiring the I/O completion spinlock, so that
// the packet can be safely examined.
ExAcquireSpinLock(&IopCompletionLock, &spIrql);
// Check to see whether or not the packet has been completed (that is,
// queued to the current thread). If not, change threads.
entry = thread->IrpList.Flink;
irp = CONTAINING_RECORD(entry, IRP, ThreadListEntry);
if (irp->CurrentLocation == irp->StackCount + 2) {
// The request has just gone through enough of completion that
// queueing it to the thread is inevitable. Simply release the
// lock and return.
ExReleaseSpinLock(&IopCompletionLock, spIrql);
KeLowerIrql(irql);
return;
}
// The packet has been located, and it is not going through completion
// at this point. Switch threads, so that it will not complete through
// this thread, remove the request from this thread's queue, and release
// the spinlock. Final processing of the IRP will occur when I/O
// completion notices that there is no thread associated with the
// request. It will essentially drop the I/O on the floor.
// Also, while the request is still held, attempt to determine on which
// device object the operation is being performed.
//DbgPrint( "Disassociating Irp: %x\n", irp );
//DbgBreakPoint();
IopDeadIrp = irp;
irp->Tail.Overlay.Thread = (PETHREAD)NULL;
entry = RemoveHeadList(&thread->IrpList);
// Initialize the thread entry. Otherwise the assertion in IoFreeIrp
// called via IopDeadIrp will fail.
InitializeListHead(&(irp)->ThreadListEntry);
irpSp = IoGetCurrentIrpStackLocation(irp);
if (irp->CurrentLocation <= irp->StackCount) {
deviceObject = irpSp->DeviceObject;
} else {
deviceObject = (PDEVICE_OBJECT)NULL;
}
ExReleaseSpinLock(&IopCompletionLock, spIrql);
KeLowerIrql(irql);
// If a device object could be identified then try to write to the event log about this
// device object.
if (deviceObject) {
errorLogEntry = IoAllocateErrorLogEntry(deviceObject, sizeof(IO_ERROR_LOG_PACKET));
if (errorLogEntry) {
errorLogEntry->ErrorCode = IO_DRIVER_CANCEL_TIMEOUT;
IoWriteErrorLogEntry(errorLogEntry);
}
}
return;
}
VOID
IopDeallocateApc(
IN PKAPC Apc,
IN PKNORMAL_ROUTINE *NormalRoutine,
IN PVOID *NormalContext,
IN PVOID *SystemArgument1,
IN PVOID *SystemArgument2
)
/*++
Routine Description:
This routine is invoked to deallocate an APC that was used to queue a
request to a target thread. It simple deallocates the APC.
Arguments:
Apc - Supplies a pointer to kernel APC structure.
NormalRoutine - Supplies a pointer to a pointer to the normal function
that was specified when the APC was initialied.
NormalContext - Supplies a pointer to a pointer to an arbitrary data
structure that was specified when the APC was initialized.
SystemArgument1, SystemArgument2 - Supplies a set of two pointers to
two arguments that contain untyped data.
Return Value:
None.
--*/
{
UNREFERENCED_PARAMETER(NormalRoutine);
UNREFERENCED_PARAMETER(NormalContext);
UNREFERENCED_PARAMETER(SystemArgument1);
UNREFERENCED_PARAMETER(SystemArgument2);
PAGED_CODE();
// Free the APC.
ExFreePool(Apc);
}
VOID
IopDropIrp(
IN PIRP Irp,
IN PFILE_OBJECT FileObject
)
/*++
Routine Description:
This routine attempts to drop everything about the specified IRP on the
floor.
Arguments:
Irp - Supplies the I/O Request Packet to be completed to the bit bucket.
FileObject - Supplies the file object for which the I/O Request Packet was
bound.
Return Value:
None.
--*/
{
PMDL mdl;
PMDL nextMdl;
// Free the resources associated with the IRP.
if (Irp->Flags & IRP_DEALLOCATE_BUFFER) {
ExFreePool(Irp->AssociatedIrp.SystemBuffer);
}
if (Irp->MdlAddress) {
for (mdl = Irp->MdlAddress; mdl; mdl = nextMdl) {
nextMdl = mdl->Next;
IoFreeMdl(mdl);
}
}
if (Irp->UserEvent &&
FileObject &&
!(Irp->Flags & IRP_SYNCHRONOUS_API)) {
ObDereferenceObject(Irp->UserEvent);
}
if (FileObject && !(Irp->Flags & IRP_CREATE_OPERATION)) {
ObDereferenceObject(FileObject);
}
// Finally, free the IRP itself.
IoFreeIrp(Irp);
}
LONG
IopExceptionFilter(
IN PEXCEPTION_POINTERS ExceptionPointer,
OUT PNTSTATUS ExceptionCode
)
/*++
Routine Description:
This routine is invoked when an exception occurs to determine whether or
not the exception was due to an error that caused an in-page error status
code exception to be raised. If so, then this routine changes the code
in the exception record to the actual error code that was originally
raised.
Arguments:
ExceptionPointer - Pointer to the exception record.
ExceptionCode - Variable to receive actual exception code.
Return Value:
The function value indicates that the exception handler is to be executed.
--*/
{
// Simply check for an in-page error status code and, if the conditions
// are right, replace it with the actual status code.
*ExceptionCode = ExceptionPointer->ExceptionRecord->ExceptionCode;
if (*ExceptionCode == STATUS_IN_PAGE_ERROR &&
ExceptionPointer->ExceptionRecord->NumberParameters >= 3) {
*ExceptionCode = (LONG)ExceptionPointer->ExceptionRecord->ExceptionInformation[2];
}
// Translate alignment warnings into alignment errors.
if (*ExceptionCode == STATUS_DATATYPE_MISALIGNMENT) {
*ExceptionCode = STATUS_DATATYPE_MISALIGNMENT_ERROR;
}
return EXCEPTION_EXECUTE_HANDLER;
}
VOID
IopExceptionCleanup(
IN PFILE_OBJECT FileObject,
IN PIRP Irp,
IN PKEVENT EventObject OPTIONAL,
IN PKEVENT KernelEvent OPTIONAL
)
/*++
Routine Description:
This routine performs generalized cleanup for the I/O system services when
an exception occurs during caller parameter processing. This routine
performs the following steps:
o If a system buffer was allocated it is freed.
o If an MDL was allocated it is freed.
o The IRP is freed.
o If the file object is opened for synchronous I/O, the semaphore
is released.
o If an event object was referenced it is dereferenced.
o If a kernel event was allocated, free it.
o The file object is dereferenced.
Arguments:
FileObject - Pointer to the file object currently being worked on.
Irp - Pointer to the IRP allocated to handle the I/O request.
EventObject - Optional pointer to a referenced event object.
KernelEvent - Optional pointer to an allocated kernel event.
Return Value:
None.
--*/
{
PAGED_CODE();
// If a system buffer was allocated from nonpaged pool, free it.
if (Irp->AssociatedIrp.SystemBuffer != NULL) {
ExFreePool(Irp->AssociatedIrp.SystemBuffer);
}
// If an MDL was allocated, free it.
if (Irp->MdlAddress != NULL) {
IoFreeMdl(Irp->MdlAddress);
}
// Free the I/O Request Packet.
IoFreeIrp(Irp);
// Finally, release the synchronization semaphore if it is currently
// held, dereference the event if one was specified, free the kernel
// event if one was allocated, and dereference the file object.
if (FileObject->Flags & FO_SYNCHRONOUS_IO) {
IopReleaseFileObjectLock(FileObject);
}
if (ARGUMENT_PRESENT(EventObject)) {
ObDereferenceObject(EventObject);
}
if (ARGUMENT_PRESENT(KernelEvent)) {
ExFreePool(KernelEvent);
}
ObDereferenceObject(FileObject);
return;
}
VOID
IopFreeIrpAndMdls(
IN PIRP Irp
)
/*++
Routine Description:
This routine frees the specified I/O Request Packet and all of its Memory
Descriptor Lists.
Arguments:
Irp - Pointer to the I/O Request Packet to be freed.
Return Value:
None.
--*/
{
PMDL mdl;
PMDL nextMdl;
// If there are any MDLs that need to be freed, free them now.
for (mdl = Irp->MdlAddress; mdl != (PMDL)NULL; mdl = nextMdl) {
nextMdl = mdl->Next;
IoFreeMdl(mdl);
}
// Free the IRP.
IoFreeIrp(Irp);
return;
}
NTSTATUS IopGetDriverNameFromKeyNode(IN HANDLE KeyHandle, OUT PUNICODE_STRING DriverName)
/*++
Routine Description:
Given a handle to a driver service list key in the registry, return the
name that represents the Object Manager name space string that should be used to locate/create the driver object.
Arguments:
KeyHandle - Supplies a handle to driver service entry in the registry.
DriverName - Supplies a Unicode string descriptor variable in which the name of the driver is returned.
Return Value:
The function value is the final status of the operation.
--*/
{
PKEY_VALUE_FULL_INFORMATION keyValueInformation;
PKEY_BASIC_INFORMATION keyBasicInformation;
ULONG keyBasicLength;
NTSTATUS status;
PAGED_CODE();
// Get the optional object name for this driver from the value for this key.
// If one exists, then its name overrides the default name of the driver.
status = IopGetRegistryValue(KeyHandle, L"ObjectName", &keyValueInformation);
if (NT_SUCCESS(status)) {
PWSTR src, dst;
ULONG i;
// The driver entry specifies an object name.
// This overrides the default name for the driver.
// Use this name to open the driver object.
if (!keyValueInformation->DataLength) {
ExFreePool(keyValueInformation);
return STATUS_ILL_FORMED_SERVICE_ENTRY;
}
DriverName->Length = (USHORT)(keyValueInformation->DataLength - sizeof(WCHAR));
DriverName->MaximumLength = (USHORT)keyValueInformation->DataLength;
src = (PWSTR)((PUCHAR)keyValueInformation + keyValueInformation->DataOffset);
dst = (PWSTR)keyValueInformation;
for (i = DriverName->Length; i; i--) {
*dst++ = *src++;
}
DriverName->Buffer = (PWSTR)keyValueInformation;
} else {
PULONG driverType;
PWSTR baseObjectName;
UNICODE_STRING remainderName;
// The driver node does not specify an object name, so determine
// what the default name for the driver object should be based on the information in the key.
status = IopGetRegistryValue(KeyHandle, L"Type", &keyValueInformation);
if (!NT_SUCCESS(status) || !keyValueInformation->DataLength) {
// There must be some type of "Type" associated with this driver,
// either DRIVER or FILE_SYSTEM. Otherwise, this node is ill-
// formed.
if (NT_SUCCESS(status)) {
ExFreePool(keyValueInformation);
}
return STATUS_ILL_FORMED_SERVICE_ENTRY;
}
// Now determine whether the type of this entry is a driver or a
// file system. Begin by assuming that it is a device driver.
baseObjectName = L"\\Driver\\";
DriverName->Length = 8 * 2;
driverType = (PULONG)((PUCHAR)keyValueInformation + keyValueInformation->DataOffset);
if (*driverType == FileSystemType || *driverType == RecognizerType) {
baseObjectName = L"\\FileSystem\\";
DriverName->Length = 12 * 2;
}
// Get the name of the key that is being used to describe this
// driver. This will return just the last component of the name
// string, which can be used to formulate the name of the driver.
status = ZwQueryKey(KeyHandle, KeyBasicInformation, (PVOID)NULL, 0, &keyBasicLength);
keyBasicInformation = ExAllocatePool(NonPagedPool, keyBasicLength);
if (!keyBasicInformation) {
ExFreePool(keyValueInformation);
return STATUS_INSUFFICIENT_RESOURCES;
}
status = ZwQueryKey(KeyHandle, KeyBasicInformation, keyBasicInformation, keyBasicLength, &keyBasicLength);
if (!NT_SUCCESS(status)) {
ExFreePool(keyBasicInformation);
ExFreePool(keyValueInformation);
return status;
}
// Allocate a buffer from pool that is large enough to contain the
// entire name string of the driver object.
DriverName->MaximumLength = (USHORT)(DriverName->Length + keyBasicInformation->NameLength);
DriverName->Buffer = ExAllocatePool(NonPagedPool, DriverName->MaximumLength);
if (!DriverName->Buffer) {
ExFreePool(keyBasicInformation);
ExFreePool(keyValueInformation);
return STATUS_INSUFFICIENT_RESOURCES;
}
// Now form the name of the object to be opened.
DriverName->Length = 0;
RtlAppendUnicodeToString(DriverName, baseObjectName);
remainderName.Length = (USHORT)keyBasicInformation->NameLength;
remainderName.MaximumLength = remainderName.Length;
remainderName.Buffer = &keyBasicInformation->Name[0];
RtlAppendUnicodeStringToString(DriverName, &remainderName);
ExFreePool(keyBasicInformation);
ExFreePool(keyValueInformation);
}
// Finally, simply return to the caller with the name filled in. Note
// that the caller must free the buffer pointed to by the Buffer field
// of the Unicode string descriptor.
return STATUS_SUCCESS;
}
NTSTATUS IopGetFileName(IN PFILE_OBJECT FileObject, IN ULONG Length, OUT PVOID FileInformation, OUT PULONG ReturnedLength)
/*++
Routine Description:
This routine is invoked to asynchronously obtain the name of a file object
when the file was opened for synchronous I/O, and the previous mode of the
caller was kernel mode, and the query was done through the Object Manager.
In this case, the situation is likely that the Lazy Writer has incurred a
write error, and it is attempting to obtain the name of the file so that it
can output a popup. In doing so, a deadlock can occur because another
thread has locked the file object synchronous I/O lock. Hence, this routine
obtains the name of the file w/o acquiring that lock.
Arguments:
FileObject - A pointer to the file object whose name is to be queried.
Length - Supplies the length of the buffer to receive the name.
FileInformation - A pointer to the buffer to receive the name.
ReturnedLength - A variable to receive the length of the name returned.
Return Value:
The status returned is the final completion status of the operation.
--*/
{
PIRP irp;
NTSTATUS status;
PDEVICE_OBJECT deviceObject;
KEVENT event;
PIO_STACK_LOCATION irpSp;
IO_STATUS_BLOCK localIoStatus;
PAGED_CODE();
// Reference the file object here so that no special checks need be made
// in I/O completion to determine whether or not to dereference the file
// object.
ObReferenceObject(FileObject);
// Initialize an event that will be used to synchronize the completion of
// the query operation. Note that this is the only way to synchronize this
// since the file object itself cannot be used since it was opened for
// synchronous I/O and may be busy.
KeInitializeEvent(&event, SynchronizationEvent, FALSE);
// Get the address of the target device object.
deviceObject = IoGetRelatedDeviceObject(FileObject);
// Allocate and initialize the I/O Request Packet (IRP) for this operation.
irp = IoAllocateIrp(deviceObject->StackSize, FALSE);
if (!irp) {
// An IRP could not be allocated. Cleanup and return an appropriate
// error status code.
IopAllocateIrpCleanup(FileObject, (PKEVENT)NULL);
return STATUS_INSUFFICIENT_RESOURCES;
}
irp->Tail.Overlay.OriginalFileObject = FileObject;
irp->Tail.Overlay.Thread = PsGetCurrentThread();
irp->RequestorMode = KernelMode;
// Fill in the service independent parameters in the IRP. Note that the
// setting of the special query name flag in the packet guarantees that the
// standard completion for a synchronous file object will not occur because
// this flag communicates to the I/O completion that it should not do so.
irp->UserEvent = &event;
irp->Flags = IRP_SYNCHRONOUS_API | IRP_OB_QUERY_NAME;
irp->UserIosb = &localIoStatus;
irp->Overlay.AsynchronousParameters.UserApcRoutine = (PIO_APC_ROUTINE)NULL;
// Get a pointer to the stack location for the first driver. This will be
// used to pass the original function codes and parameters.
irpSp = IoGetNextIrpStackLocation(irp);
irpSp->MajorFunction = IRP_MJ_QUERY_INFORMATION;
irpSp->FileObject = FileObject;
// Set the system buffer address to the address of the caller's buffer and
// set the flags so that the buffer is not deallocated.
irp->AssociatedIrp.SystemBuffer = FileInformation;
irp->Flags |= IRP_BUFFERED_IO;
// Copy the caller's parameters to the service-specific portion of the
// IRP.
irpSp->Parameters.QueryFile.Length = Length;
irpSp->Parameters.QueryFile.FileInformationClass = FileNameInformation;
// Insert the packet at the head of the IRP list for the thread.
IopQueueThreadIrp(irp);
// Now simply invoke the driver at its dispatch entry with the IRP.
status = IoCallDriver(deviceObject, irp);
// Now get the final status of the operation once the request completes
// and return the length of the buffer written.
if (status == STATUS_PENDING) {
(VOID)KeWaitForSingleObject(&event,
Executive,
KernelMode,
FALSE,
(PLARGE_INTEGER)NULL);
status = localIoStatus.Status;
}
*ReturnedLength = (ULONG)localIoStatus.Information;
return status;
}
BOOLEAN IopGetMountFlag(IN PDEVICE_OBJECT DeviceObject)
/*++
Routine Description:
This routine is invoked to determine whether or not the specified device is mounted.
Arguments:
DeviceObject - Supplies a pointer to the device object for which the mount flag is tested.
Return Value:
The function value is TRUE if the specified device is mounted, otherwise FALSE.
--*/
{
KIRQL irql;
BOOLEAN deviceMounted = FALSE;
// Check to see whether or not the device is mounted. Note that the caller
// has probably already looked to see whether or not the device has a VPB
// outside of owning the lock, so simply get the lock and check it again
// to start with, rather than checking to see whether or not the device
// still has a VPB without holding the lock.
ExAcquireFastLock(&IopVpbSpinLock, &irql);
if (DeviceObject->Vpb) {
if (DeviceObject->Vpb->Flags & VPB_MOUNTED) {
deviceMounted = TRUE;
}
}
ExReleaseFastLock(&IopVpbSpinLock, irql);
return deviceMounted;
}
NTSTATUS
IopGetRegistryKeyInformation(
IN HANDLE KeyHandle,
OUT PKEY_FULL_INFORMATION *Information
)
/*++
Routine Description:
This routine is invoked to retrieve the full key information for a
registry key. This is done by querying the full key information
of the key with a zero-length buffer to determine the size of the data,
and then allocating a buffer and actually querying the data into the buffer.
It is the responsibility of the caller to free the buffer.
Arguments:
KeyHandle - Supplies the key handle whose full key information is to
be queried
Information - Returns a pointer to the allocated data buffer.
Return Value:
The function value is the final status of the query operation.
--*/
{
NTSTATUS status;
PKEY_FULL_INFORMATION infoBuffer;
ULONG keyInfoLength;
PAGED_CODE();
// Figure out how big the data value is so that a buffer of the
// appropriate size can be allocated.
status = ZwQueryKey(KeyHandle,
KeyFullInformation,
(PVOID)NULL,
0,
&keyInfoLength);
if (status != STATUS_BUFFER_OVERFLOW &&
status != STATUS_BUFFER_TOO_SMALL) {
return status;
}
// Allocate a buffer large enough to contain the entire key data.
infoBuffer = ExAllocatePool(NonPagedPool, keyInfoLength);
if (!infoBuffer) {
return STATUS_INSUFFICIENT_RESOURCES;
}
// Query the full key data for the key.
status = ZwQueryKey(KeyHandle,
KeyFullInformation,
infoBuffer,
keyInfoLength,
&keyInfoLength);
if (!NT_SUCCESS(status)) {
ExFreePool(infoBuffer);
return status;
}
// Everything worked, so simply return the address of the allocated
// buffer to the caller, who is now responsible for freeing it.
*Information = infoBuffer;
return STATUS_SUCCESS;
}
NTSTATUS
IopGetRegistryValue(
IN HANDLE KeyHandle,
IN PWSTR ValueName,
OUT PKEY_VALUE_FULL_INFORMATION *Information
)
/*++
Routine Description:
This routine is invoked to retrieve the data for a registry key's value.
This is done by querying the value of the key with a zero-length buffer
to determine the size of the value, and then allocating a buffer and
actually querying the value into the buffer.
It is the responsibility of the caller to free the buffer.
Arguments:
KeyHandle - Supplies the key handle whose value is to be queried
ValueName - Supplies the null-terminated Unicode name of the value.
Information - Returns a pointer to the allocated data buffer.
Return Value:
The function value is the final status of the query operation.
--*/
{
UNICODE_STRING unicodeString;
NTSTATUS status;
PKEY_VALUE_FULL_INFORMATION infoBuffer;
ULONG keyValueLength;
PAGED_CODE();
RtlInitUnicodeString(&unicodeString, ValueName);
// Figure out how big the data value is so that a buffer of the
// appropriate size can be allocated.
status = ZwQueryValueKey(KeyHandle,
&unicodeString,
KeyValueFullInformation,
(PVOID)NULL,
0,
&keyValueLength);
if (status != STATUS_BUFFER_OVERFLOW &&
status != STATUS_BUFFER_TOO_SMALL) {
return status;
}
// Allocate a buffer large enough to contain the entire key data value.
infoBuffer = ExAllocatePool(NonPagedPool, keyValueLength);
if (!infoBuffer) {
return STATUS_INSUFFICIENT_RESOURCES;
}
// Query the data for the key value.
status = ZwQueryValueKey(KeyHandle,
&unicodeString,
KeyValueFullInformation,
infoBuffer,
keyValueLength,
&keyValueLength);
if (!NT_SUCCESS(status)) {
ExFreePool(infoBuffer);
return status;
}
// Everything worked, so simply return the address of the allocated
// buffer to the caller, who is now responsible for freeing it.
*Information = infoBuffer;
return STATUS_SUCCESS;
}
NTSTATUS
IopGetRegistryValues(
IN HANDLE KeyHandle,
IN PKEY_VALUE_FULL_INFORMATION *ValueList
)
/*++
Routine Description:
This routine is invoked to retrieve the *three* types of data for a
registry key's. This is done by calling the IopGetRegistryValue function
with the three valid key names.
It is the responsibility of the caller to free the three buffers.
Arguments:
KeyHandle - Supplies the key handle whose value is to be queried
ValueList - Pointer to a buffer in which the three pointers to the value
entries will be stored.
Return Value:
The function value is the final status of the query operation.
Note:
The values are stored in the order represented by the I/O query device
data format.
--*/
{
NTSTATUS status;
PAGED_CODE();
// Zero out all entries initially.
*ValueList = NULL;
*(ValueList + 1) = NULL;
*(ValueList + 2) = NULL;
// Get the information for each of the three types of entries available.
// Each time, check if an internal error occurred; If the object name was
// not found, it only means not data was present, and this does not
// constitute an error.
status = IopGetRegistryValue(KeyHandle,
L"Identifier",
ValueList);
if (!NT_SUCCESS(status) && (status != STATUS_OBJECT_NAME_NOT_FOUND)) {
return status;
}
status = IopGetRegistryValue(KeyHandle,
L"Configuration Data",
++ValueList);
if (!NT_SUCCESS(status) && (status != STATUS_OBJECT_NAME_NOT_FOUND)) {
return status;
}
status = IopGetRegistryValue(KeyHandle,
L"Component Information",
++ValueList);
if (!NT_SUCCESS(status) && (status != STATUS_OBJECT_NAME_NOT_FOUND)) {
return status;
}
return STATUS_SUCCESS;
}
NTSTATUS
IopGetSetObjectId(
IN PFILE_OBJECT FileObject,
IN OUT PVOID Buffer,
IN ULONG Length,
IN ULONG Function
)
/*++
Routine Description:
This routine is invoked to obtain or set the object ID for a file. If
one does not exist for the file, then one is created, provided that the
underlying file system supports object IDs in the first place (query).
Arguments:
FileObject - Supplies a pointer to the referenced file object whose ID is
to be returned or set.
Buffer - A variable to receive the object ID of the file (query) or that
contains the object ID that is to be set on the file.
Length - The length of the Buffer.
Function - The FSCTL to send.
FSCTL_LMR_GET_LINK_TRACKING_INFORMATION;
FSCTL_CREATE_OR_GET_OBJECT_ID;
FSCTL_GET_OBJECT_ID;
FSCTL_SET_OBJECT_ID_EXTENDED;
FSCTL_LMR_SET_LINK_TRACKING_INFORMATION;
FSCTL_SET_OBJECT_ID_EXTENDED;
FSCTL_SET_OBJECT_ID;
FSCTL_DELETE_OBJECT_ID;
Return Value:
The status returned is the final completion status of the operation.
--*/
{
IO_STATUS_BLOCK ioStatus;
NTSTATUS status;
PIRP irp;
KEVENT event;
PIO_STACK_LOCATION irpSp;
PDEVICE_OBJECT deviceObject;
PAGED_CODE();
// Initialize the event structure to synchronize completion of the I/O
// request.
KeInitializeEvent(&event,
NotificationEvent,
FALSE);
// Build an I/O Request Packet to be sent to the file system driver to get
// the object ID.
deviceObject = IoGetRelatedDeviceObject(FileObject);
irp = IoBuildDeviceIoControlRequest(Function,
deviceObject,
NULL,
0,
NULL,
0,
FALSE,
&event,
&ioStatus);
if (!irp) {
return STATUS_INSUFFICIENT_RESOURCES;
}
// Fill in the remainder of the IRP to retrieve the object ID for the
// file.
irp->Flags |= IRP_SYNCHRONOUS_API;
irp->UserBuffer = Buffer;
irp->AssociatedIrp.SystemBuffer = Buffer;
irp->Tail.Overlay.OriginalFileObject = FileObject;
irpSp = IoGetNextIrpStackLocation(irp);
irpSp->FileObject = FileObject;
irpSp->MajorFunction = IRP_MJ_FILE_SYSTEM_CONTROL;
irpSp->MinorFunction = IRP_MN_KERNEL_CALL;
if (Function == FSCTL_LMR_GET_LINK_TRACKING_INFORMATION ||
Function == FSCTL_CREATE_OR_GET_OBJECT_ID ||
Function == FSCTL_GET_OBJECT_ID) {
irpSp->Parameters.FileSystemControl.OutputBufferLength = Length;
} else {
irpSp->Parameters.FileSystemControl.InputBufferLength = Length;
}
// Take out another reference to the file object to guarantee that it does
// not get deleted.
ObReferenceObject(FileObject);
// Call the driver to get the request.
status = IoCallDriver(deviceObject, irp);
// Synchronize completion of the request.
if (status == STATUS_PENDING) {
status = KeWaitForSingleObject(&event,
Executive,
KernelMode,
FALSE,
(PLARGE_INTEGER)NULL);
status = ioStatus.Status;
}
return status;
}
NTSTATUS
IopGetVolumeId(
IN PFILE_OBJECT FileObject,
IN OUT PFILE_VOLUMEID_WITH_TYPE ObjectId,
IN ULONG Length
)
/*++
Routine Description:
This routine is invoked by the I/O System link tracking code to obtain the
volume ID for a file that has been moved or is being moved between volumes
and potentially between systems.
Arguments:
FileObject - Supplies the file object for the file.
ObjectId - A buffer to receive the volume object ID.
Length - Length of the buffer.
Return Value:
The final function value is the final completion status of the operation.
--*/
{
IO_STATUS_BLOCK ioStatus;
NTSTATUS status;
PIRP irp;
KEVENT event;
PIO_STACK_LOCATION irpSp;
PDEVICE_OBJECT deviceObject;
FILE_FS_OBJECTID_INFORMATION volumeId;
PAGED_CODE();
// Initialize the event structure to synchronize completion of the I/O
// request.
KeInitializeEvent(&event,
NotificationEvent,
FALSE);
// Build an I/O Request Packet to be sent to the file system driver to get
// the volume ID.
deviceObject = IoGetRelatedDeviceObject(FileObject);
irp = IoBuildDeviceIoControlRequest(0,
deviceObject,
NULL,
0,
NULL,
0,
FALSE,
&event,
&ioStatus);
if (!irp) {
return STATUS_INSUFFICIENT_RESOURCES;
}
// Fill in the remainder of the IRP to retrieve the volume ID for the
// file.
irp->Flags |= IRP_SYNCHRONOUS_API;
irp->UserBuffer = &volumeId;
irp->AssociatedIrp.SystemBuffer = &volumeId;
irp->Tail.Overlay.OriginalFileObject = FileObject;
irpSp = IoGetNextIrpStackLocation(irp);
irpSp->FileObject = FileObject;
irpSp->MajorFunction = IRP_MJ_QUERY_VOLUME_INFORMATION;
irpSp->Parameters.QueryVolume.Length = sizeof(volumeId);
irpSp->Parameters.QueryVolume.FsInformationClass = FileFsObjectIdInformation;
// Take out another reference to the file object to guarantee that it does
// not get deleted.
ObReferenceObject(FileObject);
// Call the driver to get the request.
status = IoCallDriver(deviceObject, irp);
// Synchronize completion of the request.
if (status == STATUS_PENDING) {
status = KeWaitForSingleObject(&event,
Executive,
KernelMode,
FALSE,
(PLARGE_INTEGER)NULL);
status = ioStatus.Status;
}
// If the file system returned the volume ID, copy it to the caller's
// buffer and set the file system tracking type.
if (NT_SUCCESS(status)) {
ObjectId->Type = NtfsLinkTrackingInformation;
RtlCopyMemory(ObjectId->VolumeId,
&volumeId.ObjectId,
sizeof(GUID));
}
return status;
}
VOID
IopHardErrorThread(
IN PVOID StartContext
)
/*++
Routine Description:
This function waits for work on the IopHardErrorQueue, and all calls
IopRaiseInformationalHardError to actually perform the pop-ups.
Arguments:
StartContext - Startup context; not used.
Return Value:
None.
--*/
{
KIRQL oldIrql;
PVOID entry;
ULONG parameterPresent;
ULONG_PTR errorParameter;
ULONG errorResponse;
BOOLEAN MoreEntries;
PIOP_HARD_ERROR_PACKET hardErrorPacket;
UNREFERENCED_PARAMETER(StartContext);
// Loop, waiting forever for a hard error packet to be sent to this thread.
// When one is placed onto the queue, wake up, process it, and continue
// the loop.
MoreEntries = TRUE;
do {
(VOID)KeWaitForSingleObject(&IopHardError.WorkQueueSemaphore,
Executive,
KernelMode,
FALSE,
(PLARGE_INTEGER)NULL);
ExAcquireFastLock(&IopHardError.WorkQueueSpinLock, &oldIrql);
// The work queue structures are now exclusively owned, so remove the
// first packet from the head of the list.
entry = RemoveHeadList(&IopHardError.WorkQueue);
hardErrorPacket = CONTAINING_RECORD(entry,
IOP_HARD_ERROR_PACKET,
WorkQueueLinks);
IopCurrentHardError = hardErrorPacket;
ExReleaseFastLock(&IopHardError.WorkQueueSpinLock, oldIrql);
// Simply raise the hard error if the system is ready to accept one.
errorParameter = (ULONG_PTR)&hardErrorPacket->String;
parameterPresent = (hardErrorPacket->String.Buffer != NULL);
if (ExReadyForErrors) {
(VOID)ExRaiseHardError(hardErrorPacket->ErrorStatus,
parameterPresent,
parameterPresent,
parameterPresent ? &errorParameter : NULL,
OptionOk,
&errorResponse);
}
// If this was the last entry, exit the thread and mark it as so.
ExAcquireFastLock(&IopHardError.WorkQueueSpinLock, &oldIrql);
IopCurrentHardError = NULL;
if (IsListEmpty(&IopHardError.WorkQueue)) {
IopHardError.ThreadStarted = FALSE;
MoreEntries = FALSE;
}
ExReleaseFastLock(&IopHardError.WorkQueueSpinLock, oldIrql);
// Now free the packet and the buffer, if one was specified.
if (hardErrorPacket->String.Buffer) {
ExFreePool(hardErrorPacket->String.Buffer);
}
ExFreePool(hardErrorPacket);
} while (MoreEntries);
}
NTSTATUS
IopInvalidDeviceRequest(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
/*++
Routine Description:
This function is the default dispatch routine for all driver entries
not implemented by drivers that have been loaded into the system. Its
responsibility is simply to set the status in the packet to indicate
that the operation requested is invalid for this device type, and then
complete the packet.
Arguments:
DeviceObject - Specifies the device object for which this request is
bound. Ignored by this routine.
Irp - Specifies the address of the I/O Request Packet (IRP) for this
request.
Return Value:
The final status is always STATUS_INVALID_DEVICE_REQUEST.
--*/
{
UNREFERENCED_PARAMETER(DeviceObject);
// Simply store the appropriate status, complete the request, and return
// the same status stored in the packet.
if ((IoGetCurrentIrpStackLocation(Irp))->MajorFunction == IRP_MJ_POWER) {
PoStartNextPowerIrp(Irp);
}
Irp->IoStatus.Status = STATUS_INVALID_DEVICE_REQUEST;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_INVALID_DEVICE_REQUEST;
}
BOOLEAN
IopIsSameMachine(
IN PFILE_OBJECT SourceFile,
IN HANDLE TargetFile
)
/*++
Routine Description:
This routine is invoked to determine whether two file objects that represent
files on remote machines actually reside on the same physical system.
Arguments:
SourceFile - Supplies the file object for the first file.
TargetFile - Supplies the file object for the second file.
Return Value:
The final function value is TRUE if the files reside on the same machine,
otherwise FALSE is returned.
--*/
{
PDEVICE_OBJECT deviceObject;
PFAST_IO_DISPATCH fastIoDispatch;
NTSTATUS status = STATUS_NOT_SAME_DEVICE;
IO_STATUS_BLOCK ioStatus;
HANDLE target = TargetFile;
PAGED_CODE();
// Simply invoke the device I/O control function to determine whether or
// not the two files are on the same server. If the fast I/O path does
// not exist, or the function fails for any reason, then the two files are
// assumed to not be on the same machine. Note that this simply means
// that there will be a performance penalty on open of the target, but
// the above will only fail if the two files really aren't on the same
// machine in the first place, or if there's a filter that doesn't under-
// stand what is being done here.
deviceObject = IoGetRelatedDeviceObject(SourceFile);
fastIoDispatch = deviceObject->DriverObject->FastIoDispatch;
if (fastIoDispatch && fastIoDispatch->FastIoDeviceControl) {
if (fastIoDispatch->FastIoDeviceControl(SourceFile,
TRUE,
(PVOID)&target,
sizeof(target),
(PVOID)NULL,
0,
IOCTL_LMR_ARE_FILE_OBJECTS_ON_SAME_SERVER,
&ioStatus,
deviceObject)) {
status = ioStatus.Status;
}
}
return status == STATUS_SUCCESS;
}
NTSTATUS IopLoadDriver(IN HANDLE KeyHandle, IN BOOLEAN CheckForSafeBoot)
/*++
Routine Description:
This routine is invoked to load a device or file system driver, either
during system initialization, or dynamically while the system is running.
Arguments:
KeyHandle - Supplies a handle to the driver service node in the registry that describes the driver to be loaded.
Return Value:
The function value is the final status of the load operation.
Notes:
Note that this routine closes the KeyHandle before returning.
--*/
{
NTSTATUS status;
PLIST_ENTRY nextEntry;
PLDR_DATA_TABLE_ENTRY driverEntry;
PKEY_BASIC_INFORMATION keyBasicInformation = NULL;
PKEY_VALUE_FULL_INFORMATION keyValueInformation = NULL;
ULONG keyBasicLength;
UNICODE_STRING baseName;
UNICODE_STRING serviceName = {0, 0, NULL};
OBJECT_ATTRIBUTES objectAttributes;
PVOID sectionPointer;
UNICODE_STRING driverName;
PDRIVER_OBJECT driverObject;
PIMAGE_NT_HEADERS ntHeaders;
PVOID imageBaseAddress;
ULONG_PTR entryPoint;
HANDLE driverHandle;
ULONG i;
POBJECT_NAME_INFORMATION registryPath;
#if DBG
LARGE_INTEGER stime, etime;
ULONG dtime;
#endif
PAGED_CODE();
driverName.Buffer = (PWSTR)NULL;
// Begin by formulating the name of the driver image file to be loaded.
// Note that this is used to determine whether or not the driver has
// already been loaded by the OS loader, not necessarily in actually
// loading the driver image, since the node can override that name.
status = NtQueryKey(KeyHandle, KeyBasicInformation, (PVOID)NULL, 0, &keyBasicLength);
if (status != STATUS_BUFFER_OVERFLOW && status != STATUS_BUFFER_TOO_SMALL) {
status = STATUS_ILL_FORMED_SERVICE_ENTRY;
goto IopLoadExit;
}
keyBasicInformation = ExAllocatePool(NonPagedPool, keyBasicLength + (4 * 2));
if (!keyBasicInformation) {
status = STATUS_INSUFFICIENT_RESOURCES;
goto IopLoadExit;
}
status = NtQueryKey(KeyHandle, KeyBasicInformation, keyBasicInformation, keyBasicLength, &keyBasicLength);
if (!NT_SUCCESS(status)) {
goto IopLoadExit;
}
// Create a Unicode string descriptor which forms the name of the driver.
baseName.Length = (USHORT)keyBasicInformation->NameLength;
baseName.MaximumLength = (USHORT)(baseName.Length + (4 * 2));
baseName.Buffer = &keyBasicInformation->Name[0];
//#if _PNP_POWER_
serviceName.Buffer = ExAllocatePool(PagedPool, baseName.Length + sizeof(UNICODE_NULL));
if (serviceName.Buffer) {
serviceName.Length = baseName.Length;
serviceName.MaximumLength = serviceName.Length + sizeof(UNICODE_NULL);
RtlMoveMemory(serviceName.Buffer, baseName.Buffer, baseName.Length);
serviceName.Buffer[serviceName.Length / sizeof(WCHAR)] = UNICODE_NULL;
}
#if DBG
else {
DbgPrint("IopLoadDriver: No memory available for Service Keyname\n");
}
#endif
//#endif
RtlAppendUnicodeToString(&baseName, L".SYS");
if (CheckForSafeBoot && InitSafeBootMode) {
BOOLEAN GroupIsGood = FALSE;
UNICODE_STRING string;
PKEY_VALUE_PARTIAL_INFORMATION keyValue;
UCHAR nameBuffer[FIELD_OFFSET(KEY_VALUE_PARTIAL_INFORMATION, Data) + 64];
ULONG length;
RtlInitUnicodeString(&string, L"Group");
keyValue = (PKEY_VALUE_PARTIAL_INFORMATION)nameBuffer;
RtlZeroMemory(nameBuffer, sizeof(nameBuffer));
status = NtQueryValueKey(KeyHandle, &string, KeyValuePartialInformation, keyValue, sizeof(nameBuffer), &length);
if (NT_SUCCESS(status)) {
string.Length = (USHORT)(keyValue->DataLength - sizeof(WCHAR));
string.MaximumLength = string.Length;
string.Buffer = (PWSTR)keyValue->Data;
if (IopSafebootDriverLoad(&string)) {
GroupIsGood = TRUE;
}
}
if (!GroupIsGood && !IopSafebootDriverLoad(&baseName)) {
IopBootLog(&baseName, FALSE);// don't load the driver
DbgPrint("SAFEBOOT: skipping device = %wZ(%wZ)\n", &baseName, &string);
return STATUS_SUCCESS;
}
}
// See if this driver has already been loaded by the boot loader.
//KeEnterCriticalRegion();
ExAcquireResourceShared(&PsLoadedModuleResource, TRUE);
nextEntry = PsLoadedModuleList.Flink;
while (nextEntry != &PsLoadedModuleList) {
// Look at the next boot driver in the list.
driverEntry = CONTAINING_RECORD(nextEntry, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);
// If this is not the kernel image (ntoskrnl) and not the HAL (hal),
// then this is a driver, so initialize it.
if ((driverEntry->Flags & LDRP_ENTRY_PROCESSED) &&
RtlEqualString((PSTRING)&baseName, (PSTRING)&driverEntry->FullDllName, TRUE)) {
status = STATUS_IMAGE_ALREADY_LOADED;
ExReleaseResource(&PsLoadedModuleResource);
//KeLeaveCriticalRegion();
IopBootLog(&baseName, TRUE);
goto IopLoadExit;
}
nextEntry = nextEntry->Flink;
}
ExReleaseResource(&PsLoadedModuleResource);
//KeLeaveCriticalRegion();
// This driver has not already been loaded by the OS loader. Form the
// full path name for this driver. Begin by attempting to determine
// whether or not the file has an image path. If so, then use that,
// otherwise, form one from the above driver name by putting the appropriate path name in front of it.
status = IopGetRegistryValue(KeyHandle, L"ImagePath", &keyValueInformation);
if (NT_SUCCESS(status) && keyValueInformation->DataLength) {
// The driver service node contained an image path name from which the driver is to be loaded.
ExFreePool(keyBasicInformation);
keyBasicInformation = NULL;
baseName.Length = (USHORT)keyValueInformation->DataLength;
if (baseName.Length > 0) {
baseName.Length -= sizeof(WCHAR);
}
baseName.MaximumLength = baseName.Length;
baseName.Buffer = (PWSTR)((PUCHAR)keyValueInformation + keyValueInformation->DataOffset);
if (baseName.Buffer[0] != L'\\') {
UNICODE_STRING prefixName;
UNICODE_STRING tmpName;
PWCHAR fileName;
RtlInitUnicodeString(&prefixName, L"\\SystemRoot\\");
fileName = ExAllocatePool(NonPagedPool, prefixName.Length + baseName.Length);
if (!fileName) {
status = STATUS_INSUFFICIENT_RESOURCES;
goto IopLoadExit;
}
tmpName.Length = baseName.Length;
tmpName.Buffer = baseName.Buffer;
baseName.MaximumLength = (USHORT)(prefixName.Length + baseName.Length);
baseName.Length = 0;
baseName.Buffer = fileName;
RtlAppendUnicodeStringToString(&baseName, &prefixName);
RtlAppendUnicodeStringToString(&baseName, &tmpName);
ExFreePool(keyValueInformation);
keyValueInformation = (PKEY_VALUE_FULL_INFORMATION)fileName;
}
} else {
UNICODE_STRING prefixName;
UNICODE_STRING fileName;
RtlInitUnicodeString(&prefixName, L"\\SystemRoot\\System32\\Drivers\\");
// Ensure that the driver entry did not actually contain an image path
// name, and if it did, free the appropriate pool because it was a key without a value.
if (NT_SUCCESS(status)) {
ExFreePool(keyValueInformation);
}
// The driver entry did not contain an image path name, so the above
// default name for the driver image is name of the file. Form a
// fully qualified path to get to the image file.
keyValueInformation = ExAllocatePool(NonPagedPool, baseName.MaximumLength + prefixName.Length);
if (!keyValueInformation) {
status = STATUS_INSUFFICIENT_RESOURCES;
goto IopLoadExit;
}
fileName.Length = baseName.Length;
fileName.MaximumLength = baseName.MaximumLength;
fileName.Buffer = baseName.Buffer;
baseName.Length = 0;
baseName.MaximumLength = (USHORT)(fileName.Length + prefixName.Length);
baseName.Buffer = (PWSTR)keyValueInformation;
RtlAppendUnicodeStringToString(&baseName, &prefixName);
RtlAppendUnicodeStringToString(&baseName, &fileName);
ExFreePool(keyBasicInformation);
keyBasicInformation = NULL;
}
// Now get the name of the driver object.
status = IopGetDriverNameFromKeyNode(KeyHandle, &driverName);
if (!NT_SUCCESS(status)) {
goto IopLoadExit;
}
InitializeObjectAttributes(&objectAttributes, &driverName, OBJ_PERMANENT, (HANDLE)NULL, (PSECURITY_DESCRIPTOR)NULL);
// Load the driver image into memory. If this fails partway through
// the operation, then it will automatically be unloaded.
status = MmLoadSystemImage(&baseName, NULL, NULL, FALSE, &sectionPointer, (PVOID *)&imageBaseAddress);
if (!NT_SUCCESS(status)) {
// If the image was not already loaded then exit.
if (status != STATUS_IMAGE_ALREADY_LOADED) {
IopBootLog(&baseName, FALSE);
goto IopLoadExit;
}
// Open the driver object.
status = ObOpenObjectByName(&objectAttributes, IoDriverObjectType, KernelMode, NULL, 0, NULL, &driverHandle);
if (!NT_SUCCESS(status)) {
IopBootLog(&baseName, FALSE);
goto IopLoadExit;
}
// Reference the handle and obtain a pointer to the driver object so that
// the handle can be deleted without the object going away.
status = ObReferenceObjectByHandle(driverHandle,
0,
IoDriverObjectType,
KeGetPreviousMode(),
(PVOID *)&driverObject,
(POBJECT_HANDLE_INFORMATION)NULL);
NtClose(driverHandle);
if (!NT_SUCCESS(status)) {
IopBootLog(&baseName, FALSE);
goto IopLoadExit;
}
status = IopResurrectDriver(driverObject);
// Regardless of the status the driver object should be dereferenced.
// if the unload has already run then driver is almost gone. If
// the driver has been resurrected then the I/O system still has its original reference.
ObDereferenceObject(driverObject);
IopBootLog(&baseName, FALSE);
goto IopLoadExit;
} else {
ntHeaders = RtlImageNtHeader(imageBaseAddress);
// Check should this driver be loaded. If yes, the enum subkey of the service will be prepared.
status = IopPrepareDriverLoading(&serviceName, KeyHandle, ntHeaders);
if (!NT_SUCCESS(status)) {
MmUnloadSystemImage(sectionPointer);
IopBootLog(&baseName, FALSE);
goto IopLoadExit;
}
}
// The driver image has now been loaded into memory. Create the driver
// object that represents this image.
status = ObCreateObject(KeGetPreviousMode(),
IoDriverObjectType,
&objectAttributes,
KernelMode,
(PVOID)NULL,
(ULONG)(sizeof(DRIVER_OBJECT) + sizeof(DRIVER_EXTENSION)),
0,
0,
(PVOID *)&driverObject);
if (!NT_SUCCESS(status)) {
IopBootLog(&baseName, FALSE);
goto IopLoadExit;
}
// Initialize this driver object and insert it into the object table.
RtlZeroMemory(driverObject, sizeof(DRIVER_OBJECT) + sizeof(DRIVER_EXTENSION));
driverObject->DriverExtension = (PDRIVER_EXTENSION)(driverObject + 1);
driverObject->DriverExtension->DriverObject = driverObject;
for (i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++) {
driverObject->MajorFunction[i] = IopInvalidDeviceRequest;
}
driverObject->Type = IO_TYPE_DRIVER;
driverObject->Size = sizeof(DRIVER_OBJECT);
ntHeaders = RtlImageNtHeader(imageBaseAddress);
entryPoint = ntHeaders->OptionalHeader.AddressOfEntryPoint;
entryPoint += (ULONG_PTR)imageBaseAddress;
if (!(ntHeaders->OptionalHeader.DllCharacteristics & IMAGE_DLLCHARACTERISTICS_WDM_DRIVER)) {
driverObject->Flags |= DRVO_LEGACY_DRIVER;
}
driverObject->DriverInit = (PDRIVER_INITIALIZE)entryPoint;
driverObject->DriverSection = sectionPointer;
driverObject->DriverStart = imageBaseAddress;
driverObject->DriverSize = ntHeaders->OptionalHeader.SizeOfImage;
status = ObInsertObject(driverObject,
(PACCESS_STATE)NULL,
FILE_READ_DATA,
0,
(PVOID *)NULL,
&driverHandle);
if (!NT_SUCCESS(status)) {
IopBootLog(&baseName, FALSE);
goto IopLoadExit;
}
// Reference the handle and obtain a pointer to the driver object so that
// the handle can be deleted without the object going away.
status = ObReferenceObjectByHandle(driverHandle,
0,
IoDriverObjectType,
KeGetPreviousMode(),
(PVOID *)&driverObject,
(POBJECT_HANDLE_INFORMATION)NULL);
NtClose(driverHandle);
// Load the Registry information in the appropriate fields of the device object.
driverObject->HardwareDatabase = &CmRegistryMachineHardwareDescriptionSystemName;
// Store the name of the device driver in the driver object so that it
// can be easily found by the error log thread.
driverObject->DriverName.Buffer = ExAllocatePool(PagedPool, driverName.MaximumLength);
if (driverObject->DriverName.Buffer) {
driverObject->DriverName.MaximumLength = driverName.MaximumLength;
driverObject->DriverName.Length = driverName.Length;
RtlCopyMemory(driverObject->DriverName.Buffer, driverName.Buffer, driverName.MaximumLength);
}
// Query the name of the registry path for this driver so that it can be passed to the driver.
registryPath = ExAllocatePool(NonPagedPool, PAGE_SIZE);
if (!registryPath) {
ObMakeTemporaryObject(driverObject);
ObDereferenceObject(driverObject);
status = STATUS_INSUFFICIENT_RESOURCES;
goto IopLoadExit;
}
status = NtQueryObject(KeyHandle, ObjectNameInformation, registryPath, PAGE_SIZE, &i);
if (!NT_SUCCESS(status)) {
ObMakeTemporaryObject(driverObject);
ObDereferenceObject(driverObject);
ExFreePool(registryPath);
goto IopLoadExit;
}
#if DBG
KeQuerySystemTime(&stime);
#endif
// Store the service key name of the device driver in the driver object
if (serviceName.Buffer) {
driverObject->DriverExtension->ServiceKeyName.Buffer = ExAllocatePool(NonPagedPool, serviceName.MaximumLength);
if (driverObject->DriverExtension->ServiceKeyName.Buffer) {
driverObject->DriverExtension->ServiceKeyName.MaximumLength = serviceName.MaximumLength;
driverObject->DriverExtension->ServiceKeyName.Length = serviceName.Length;
RtlCopyMemory(driverObject->DriverExtension->ServiceKeyName.Buffer,
serviceName.Buffer,
serviceName.MaximumLength);
}
}
// Now invoke the driver's initialization routine to initialize itself.
PERFINFO_DRIVER_INIT(driverObject);
status = driverObject->DriverInit(driverObject, &registryPath->Name);
PERFINFO_DRIVER_INIT_COMPLETE(driverObject);
#if DBG
// If DriverInit took longer than 5 seconds, print a message.
KeQuerySystemTime(&etime);
dtime = (ULONG)((etime.QuadPart - stime.QuadPart) / 1000000);
if (dtime > 50) {
DbgPrint("IOLOAD: Driver %wZ took %d.%ds to %s\n",
&driverName,
dtime / 10,
dtime % 10,
NT_SUCCESS(status) ? "initialize" : "fail initialization");
}
#endif
// Workaround for broken NT 4.0 3D labs driver
// They zero out some function table entries by mistake.
for (i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++) {
if (driverObject->MajorFunction[i] == NULL) {
ASSERT(driverObject->MajorFunction[i] != NULL);
driverObject->MajorFunction[i] = IopInvalidDeviceRequest;
}
}
// If DriverInit doesn't work, then simply unload the image and mark the driver
// object as temporary. This will cause everything to be deleted.
ExFreePool(registryPath);
// If we load the driver because we think it is a legacy driver and
// it does not create any device object in its DriverEntry. We will unload this driver.
if (NT_SUCCESS(status) && !IopIsLegacyDriver(driverObject)) {
if (driverObject->DeviceObject == NULL &&
serviceName.Buffer &&
!IopIsAnyDeviceInstanceEnabled(&serviceName, NULL, FALSE) &&
!(driverObject->Flags & DRVO_REINIT_REGISTERED)) {
IopDriverLoadingFailed(KeyHandle, NULL);
status = STATUS_PLUGPLAY_NO_DEVICE;
} else {
// Start the devices controlled by the driver and enumerate them
// At this point, we know there is at least one device controlled by the driver.
IopDeleteLegacyKey(driverObject);
if (PnPInitialized) {
status = IopStartDriverDevices(driverObject);
}
}
if (!NT_SUCCESS(status)) {
if (driverObject->DriverUnload) {
driverObject->Flags |= DRVO_UNLOAD_INVOKED;
driverObject->DriverUnload(driverObject);
IopBootLog(&baseName, FALSE);
} else {
#if DBG
DbgPrint("IopLoadDriver: A PnP driver %wZ does not support DriverUnload routine.\n", &driverName);
// ASSERT(0);
#endif
}
}
}
if (!NT_SUCCESS(status)) {
ObMakeTemporaryObject(driverObject);
ObDereferenceObject(driverObject);
} else {
// Free the memory occupied by the driver's initialization routines.
IopBootLog(&baseName, TRUE);
MmFreeDriverInitialization(driverObject->DriverSection);
IopReadyDeviceObjects(driverObject);
}
IopLoadExit:
// Free any pool that was allocated by this routine that has not yet been freed.
if (driverName.Buffer != NULL) {
ExFreePool(driverName.Buffer);
}
if (keyValueInformation != NULL) {
ExFreePool(keyValueInformation);
}
if (keyBasicInformation != NULL) {
ExFreePool(keyBasicInformation);
}
if (serviceName.Buffer != NULL) {
ExFreePool(serviceName.Buffer);
}
// If this routine is about to return a failure, then let the Configuration
// Manager know about it. But, if STATUS_PLUGPLAY_NO_DEVICE, the device was
// disabled by hardware profile. In this case we don't need to report it.
if (!NT_SUCCESS(status) && (status != STATUS_PLUGPLAY_NO_DEVICE)) {
NTSTATUS lStatus;
PULONG errorControl;
if (status != STATUS_IMAGE_ALREADY_LOADED) {
// If driver was loaded, do not call IopDriverLoadingFailed to change
// the driver loading status. Because, obviously, the driver is running.
IopDriverLoadingFailed(KeyHandle, NULL);
lStatus = IopGetRegistryValue(KeyHandle, L"ErrorControl", &keyValueInformation);
if (!NT_SUCCESS(lStatus) || !keyValueInformation->DataLength) {
if (NT_SUCCESS(lStatus)) {
ExFreePool(keyValueInformation);
}
} else {
errorControl = (PULONG)((PUCHAR)keyValueInformation + keyValueInformation->DataOffset);
CmBootLastKnownGood(*errorControl);
ExFreePool(keyValueInformation);
}
}
}
// Close the caller's handle and return the final status from the load operation.
NtClose(KeyHandle);
return status;
}
PDEVICE_OBJECT IopGetDeviceAttachmentBase(IN PDEVICE_OBJECT DeviceObject)
/*++
Routine Description:
This routine returns the lowest level device object associated with the specified device.
Arguments:
DeviceObject - Supplies a pointer to the device for which the bottom of attachment chain is to be found.
Return Value:
The function value is a reference to the lowest level device attached to the specified device.
If the supplied device object is that device object, then a pointer to it is returned.
N.B. Caller must own the IopDatabaseLock.
--*/
{
PDEVICE_OBJECT baseDeviceObject;
PDEVOBJ_EXTENSION deviceExtension;
// Descend down the attachment chain until we find a device object that isn't attached to anything else.
baseDeviceObject = DeviceObject;
deviceExtension = baseDeviceObject->DeviceObjectExtension;
while (deviceExtension->AttachedTo != NULL) {
baseDeviceObject = deviceExtension->AttachedTo;
deviceExtension = baseDeviceObject->DeviceObjectExtension;
}
return baseDeviceObject;
}
PDEVICE_OBJECT IopGetDeviceAttachmentBaseRef(IN PDEVICE_OBJECT DeviceObject)
/*++
Routine Description:
This routine returns the lowest level device object associated with the specified device.
Arguments:
DeviceObject - Supplies a pointer to the device for which the bottom of attachment chain is to be found.
Return Value:
The function value is a reference to the lowest level device attached to the specified device.
If the supplied device object is that device object, then a pointer to it is returned.
A reference is taken on the returned device object.
It is the responsibility of the caller to release it.
--*/
{
PDEVICE_OBJECT baseDeviceObject;
KIRQL irql;
// Any examination of attachment chain linkage must be done with IopDatabaseLock taken.
ExAcquireSpinLock(&IopDatabaseLock, &irql);
// Find the base of the attachment chain.
baseDeviceObject = IopGetDeviceAttachmentBase(DeviceObject);
// Reference the device object before releasing the database lock.
ObReferenceObject(baseDeviceObject);
ExReleaseSpinLock(&IopDatabaseLock, irql);
return baseDeviceObject;
}
VOID IopDecrementDeviceObjectRef(IN PDEVICE_OBJECT DeviceObject, IN BOOLEAN AlwaysUnload)
/*++
Routine Description:
The routine decrements the reference count on a device object. If the
reference count goes to zero and the device object is a candidate for deletion
then IopCompleteUnloadOrDelete is called. A device object is subject for
deletion if the AlwaysUnload flag is true, or the device object is pending
deletion or the driver is pending unload.
Arguments:
DeviceObject - Supplies the device object whose reference count is to be decremented.
AlwaysUnload - Indicates if the driver should be unloaded regardless of the state of the unload flag.
Return Value:
None.
--*/
{
KIRQL irql;
// Decrement the reference count on the device object. If this is the last
// last reason that this mini-file system recognizer needs to stay around, then unload it.
ExAcquireSpinLock(&IopDatabaseLock, &irql);
ASSERT(DeviceObject->ReferenceCount > 0);
DeviceObject->ReferenceCount--;
if (!DeviceObject->ReferenceCount && (AlwaysUnload ||
DeviceObject->DeviceObjectExtension->ExtensionFlags &
(DOE_DELETE_PENDING | DOE_UNLOAD_PENDING | DOE_REMOVE_PENDING))) {
IopCompleteUnloadOrDelete(DeviceObject, irql);
} else {
ExReleaseSpinLock(&IopDatabaseLock, irql);
}
}
VOID IopLoadFileSystemDriver(IN PDEVICE_OBJECT DeviceObject)
/*++
Routine Description:
This routine is invoked when a mini-file system recognizer driver recognizes
a volume as being a particular file system, but the driver for that file
system has not yet been loaded. This function allows the mini-driver to
load the real file system, and remove itself from the system, so that the
real file system can mount the device in question.
Arguments:
DeviceObject - Registered file system device object for the mini-driver.
Return Value:
None.
--*/
{
KEVENT event;
NTSTATUS status;
IO_STATUS_BLOCK ioStatus;
PIRP irp;
PIO_STACK_LOCATION irpSp;
PDEVICE_OBJECT attachedDevice;
PAGED_CODE();
attachedDevice = DeviceObject;
while (attachedDevice->AttachedDevice) {
attachedDevice = attachedDevice->AttachedDevice;
}
// Begin by building an I/O Request Packet to have the mini-file system
// driver load the real file system.
KeInitializeEvent(&event, NotificationEvent, FALSE);
irp = IoBuildDeviceIoControlRequest(IRP_MJ_DEVICE_CONTROL,
attachedDevice,
(PVOID)NULL,
0,
(PVOID)NULL,
0,
FALSE,
&event,
&ioStatus);
if (irp) {
// Change the actual major and minor function codes to be a file system
// control with a minor function code of load FS driver.
irpSp = IoGetNextIrpStackLocation(irp);
irpSp->MajorFunction = IRP_MJ_FILE_SYSTEM_CONTROL;
irpSp->MinorFunction = IRP_MN_LOAD_FILE_SYSTEM;
// Now issue the request.
status = IoCallDriver(attachedDevice, irp);
if (status == STATUS_PENDING) {
(VOID)KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, (PLARGE_INTEGER)NULL);
}
}
// Decrement the reference count on the device object. If this is the last
// last reason that this mini-file system recognizer needs to stay around, then unload it.
IopDecrementDeviceObjectRef(DeviceObject, TRUE);
return;
}
VOID IopLoadUnloadDriver(IN PVOID Parameter)
/*++
Routine Description:
This routine is executed as an EX worker thread routine when a driver is
to be loaded or unloaded dynamically. It is used because some drivers
need to create system threads in the context of the system process, which
cannot be done in the context of the caller of the system service that
was invoked to load or unload the specified driver.
Arguments:
Parameter - Pointer to the load packet describing what work is to be done.
Return Value:
None.
--*/
{
PLOAD_PACKET loadPacket;
NTSTATUS status;
HANDLE keyHandle;
PAGED_CODE();
// Begin by getting a pointer to the load packet.
loadPacket = (PLOAD_PACKET)Parameter;
// If the driver object field of the packet is non-NULL, then this is
// a request to complete the unload of a driver. Simply invoke the
// driver's unload routine. Note that the final status of the unload
// is ignored, so it is not set here.
if (loadPacket->DriverObject) {
loadPacket->DriverObject->DriverUnload(loadPacket->DriverObject);
status = STATUS_SUCCESS;
} else {
PLIST_ENTRY entry;
PREINIT_PACKET reinitEntry;
// The driver specified by the DriverServiceName is to be loaded.
// Begin by opening the registry node for this driver. Note
// that if this is successful, then the load driver routine is
// responsible for closing the handle.
status = IopOpenRegistryKey(&keyHandle,
(HANDLE)NULL,
loadPacket->DriverServiceName,
KEY_READ,
FALSE);
if (NT_SUCCESS(status)) {
// Invoke the internal common routine to perform the work.
// This is the same routine that is used by the I/O system
// initialization code to load drivers.
status = IopLoadDriver(keyHandle, TRUE);
// Walk the list reinitialization list in case this driver, or
// some other driver, has requested to be invoked at a re-initialization entry point.
while (entry = ExInterlockedRemoveHeadList(&IopDriverReinitializeQueueHead, &IopDatabaseLock)) {
reinitEntry = CONTAINING_RECORD(entry, REINIT_PACKET, ListEntry);
//#if _PNP_POWER_
reinitEntry->DriverObject->DriverExtension->Count++;
reinitEntry->DriverObject->Flags &= ~DRVO_REINIT_REGISTERED;
reinitEntry->DriverReinitializationRoutine(reinitEntry->DriverObject,
reinitEntry->Context,
reinitEntry->DriverObject->DriverExtension->Count);
//#else
#if 0
reinitEntry->DriverObject->Count++;
reinitEntry->DriverReinitializationRoutine(reinitEntry->DriverObject,
reinitEntry->Context,
reinitEntry->DriverObject->Count);
#endif // _PNP_POWER_
ExFreePool(reinitEntry);
}
}
}
// Set the final status of the load or unload operation, and indicate to
// the caller that the operation is now complete.
loadPacket->FinalStatus = status;
(VOID)KeSetEvent(&loadPacket->Event, 0, FALSE);
}
NTSTATUS
IopMountVolume(
IN PDEVICE_OBJECT DeviceObject,
IN BOOLEAN AllowRawMount,
IN BOOLEAN DeviceLockAlreadyHeld,
IN BOOLEAN Alertable
)
/*++
Routine Description:
This routine is used to mount a volume on the specified device. The Volume
Parameter Block (VPB) for the specified device is a "clean" VPB. That is,
it indicates that the volume has never been mounted. It is up to the file
system that eventually mounts the volume to determine whether the volume is,
or has been, mounted elsewhere.
Arguments:
DeviceObject - Pointer to device object on which the volume is to be mounted.
AllowRawMount - This parameter tells us if we should continue our
filesystem search to include the Raw file system. This flag will
only be passed in as TRUE as a result of a DASD open.
DeviceLockAlreadyHeld - If TRUE, then the caller has already acquired
the device lock and we should not attempt to acquire it. This is
currently passed in as TRUE when called from IoVerifyVolume.
Return Value:
The function value is a successful status code if a volume was successfully
mounted on the device. Otherwise, an error code is returned.
--*/
{
NTSTATUS status;
KEVENT event;
PIRP irp;
PDEVICE_OBJECT fsDeviceObject;
PDEVICE_OBJECT attachedDevice;
PLIST_ENTRY entry;
PLIST_ENTRY queueHeader;
IO_STATUS_BLOCK ioStatus;
PIO_STACK_LOCATION irpSp;
ULONG extraStack;
LIST_ENTRY dummy;
ULONG rawMountOnly;
PAGED_CODE();
// Obtain the lock for the device to be mounted. This guarantees that
// only one thread is attempting to mount (or verify) this particular
// device at a time.
if (!DeviceLockAlreadyHeld) {
status = KeWaitForSingleObject(&DeviceObject->DeviceLock,
Executive,
KeGetPreviousMode(),
Alertable,
(PLARGE_INTEGER)NULL);
// If the wait ended because of an alert or an APC, return now
// without mounting the device. Note that as the wait for the
// event was unsuccessful, we do not set it on exit.
if (status == STATUS_ALERTED || status == STATUS_USER_APC) {
return status;
}
}
// Now acquire the resource database lock for the I/O system to perform this
// operation. This resource protects access to the file system queue.
//KeEnterCriticalRegion();
(VOID)ExAcquireResourceShared(&IopDatabaseResource, TRUE);
// Check the 'mounted' flag of the VPB to ensure that it is still clear.
// If it is, then no one has gotten in before this to mount the volume.
// Attempt to mount the volume in this case.
if ((DeviceObject->Vpb->Flags & (VPB_MOUNTED | VPB_REMOVE_PENDING)) == 0) {
// This volume has never been mounted. Initialize the event and set the
// status to unsuccessful to set up for the loop. Also if the device
// has the verify bit set, clear it.
KeInitializeEvent(&event, NotificationEvent, FALSE);
status = STATUS_UNSUCCESSFUL;
DeviceObject->Flags &= ~DO_VERIFY_VOLUME;
// Get the actual device that this volume is to be mounted on. This
// device is the final device in the list of devices which are attached
// to the specified real device.
attachedDevice = DeviceObject;
while (attachedDevice->AttachedDevice) {
attachedDevice = attachedDevice->AttachedDevice;
}
// Reference the device object so it cannot go away.
ObReferenceObject(attachedDevice);
// Determine which type of file system should be invoked based on
// the device type of the device being mounted.
if (DeviceObject->DeviceType == FILE_DEVICE_DISK ||
DeviceObject->DeviceType == FILE_DEVICE_VIRTUAL_DISK) {
queueHeader = &IopDiskFileSystemQueueHead;
} else if (DeviceObject->DeviceType == FILE_DEVICE_CD_ROM) {
queueHeader = &IopCdRomFileSystemQueueHead;
} else {
queueHeader = &IopTapeFileSystemQueueHead;
}
rawMountOnly = (DeviceObject->Vpb->Flags & VPB_RAW_MOUNT);
// Now loop through each of the file systems which have been loaded in
// the system to see whether anyone understands the media in the device.
for (entry = queueHeader->Flink;
entry != queueHeader && !NT_SUCCESS(status);
entry = entry->Flink) {
PDEVICE_OBJECT savedFsDeviceObject;
// If this is the final entry (Raw file system), and it is also
// not the first entry, and a raw mount is not permitted, then
// break out of the loop at this point, as this volume cannot
// be mounted for the caller's purposes.
if (!AllowRawMount && entry->Flink == queueHeader && entry != queueHeader->Flink) {
break;
}
// If raw mount is the only one requested and this is not the last entry on the list
// then skip.
if (rawMountOnly && (entry->Flink != queueHeader)) {
continue;
}
fsDeviceObject = CONTAINING_RECORD(entry, DEVICE_OBJECT, Queue.ListEntry);
savedFsDeviceObject = fsDeviceObject;
// It is possible that the file system has been attached to, so
// walk the attached list for the file system. The number of stack
// locations that must be allocated in the IRP must include one for
// the file system itself, and then one for each driver that is
// attached to it. Account for all of the stack locations required
// to get through the mount process.
extraStack = 1;
while (fsDeviceObject->AttachedDevice) {
fsDeviceObject = fsDeviceObject->AttachedDevice;
extraStack++;
}
// Another file system has been found and the volume has still not
// been mounted. Attempt to mount the volume using this file
// system.
// Begin by resetting the event being used for synchronization with
// the I/O operation.
KeClearEvent(&event);
// Allocate and initialize an IRP for this mount operation. Notice
// that the flags for this operation appear the same as a page read
// operation. This is because the completion code for both of the
// operations is exactly the same logic.
irp = IopAllocateIrpMustSucceed((CCHAR)(attachedDevice->StackSize + extraStack));
irp->Flags = IRP_MOUNT_COMPLETION | IRP_SYNCHRONOUS_PAGING_IO;
irp->RequestorMode = KernelMode;
irp->UserEvent = &event;
irp->UserIosb = &ioStatus;
irp->Tail.Overlay.Thread = PsGetCurrentThread();
irpSp = IoGetNextIrpStackLocation(irp);
irpSp->MajorFunction = IRP_MJ_FILE_SYSTEM_CONTROL;
irpSp->MinorFunction = IRP_MN_MOUNT_VOLUME;
irpSp->Flags = AllowRawMount;
irpSp->Parameters.MountVolume.Vpb = DeviceObject->Vpb;
irpSp->Parameters.MountVolume.DeviceObject = attachedDevice;
status = IoCallDriver(fsDeviceObject, irp);
// Wait for the I/O operation to complete.
if (NT_SUCCESS(status)) {
(VOID)KeWaitForSingleObject(&event,
Executive,
KernelMode,
FALSE,
(PLARGE_INTEGER)NULL);
} else {
// Ensure that the proper status value gets picked up.
ioStatus.Status = status;
ioStatus.Information = 0;
}
// If the operation was successful then set the VPB as mounted.
if (NT_SUCCESS(ioStatus.Status)) {
status = ioStatus.Status;
DeviceObject->Vpb->Flags = VPB_MOUNTED;
// We explicitly propagate VPB_RAW_MOUNT as the previous
// statement that has been there for a long time in NT
// could be clearing other flags which should be cleared.
if (rawMountOnly) {
DeviceObject->Vpb->Flags |= VPB_RAW_MOUNT;
}
DeviceObject->Vpb->DeviceObject->StackSize = (UCHAR)(attachedDevice->StackSize + 1);
} else {
// The mount operation failed. Make a special check here to
// determine whether or not a popup was enabled, and if so,
// check to see whether or not the operation was to be aborted.
// If so, bail out now and return the error to the caller.
status = ioStatus.Status;
if (IoIsErrorUserInduced(status) &&
ioStatus.Information == IOP_ABORT) {
break;
}
// Also check to see whether or not this is a volume that has
// been recognized, but the file system for it needs to be
// loaded. If so, drop the locks held at this point, tell the
// mini-file system recognizer to load the driver, and then
// reacquire the locks.
if (status == STATUS_FS_DRIVER_REQUIRED) {
// Increment the number of reasons that this driver cannot
// be unloaded. Note that this must be done while still
// holding the database resource.
ExInterlockedAddUlong(&savedFsDeviceObject->ReferenceCount,
1,
&IopDatabaseLock);
// Release the locks, load the new file system, and unload
// the recognizer.
ExReleaseResource(&IopDatabaseResource);
//KeLeaveCriticalRegion();
if (!DeviceLockAlreadyHeld) {
KeSetEvent(&DeviceObject->DeviceLock, 0, FALSE);
}
IopLoadFileSystemDriver(savedFsDeviceObject);
// Now reacquire the locks, in the correct order, and check
// to see if the volume has been mounted before we could
// get back. If so, exit; otherwise, restart the file
// file system queue scan from the beginning.
if (!DeviceLockAlreadyHeld) {
status = KeWaitForSingleObject(&DeviceObject->DeviceLock,
Executive,
KeGetPreviousMode(),
Alertable,
(PLARGE_INTEGER)NULL);
if (status == STATUS_ALERTED || status == STATUS_USER_APC) {
// The device was not mounted by us so
// drop the reference before returning.
ObDereferenceObject(attachedDevice);
return status;
}
}
//KeEnterCriticalRegion();
(VOID)ExAcquireResourceShared(&IopDatabaseResource, TRUE);
if (DeviceObject->Vpb->Flags & VPB_MOUNTED) {
// This volume was mounted before we got back.
status = STATUS_SUCCESS;
break;
}
// Reset the list back to the beginning and start over
// again.
dummy.Flink = queueHeader->Flink;
entry = &dummy;
status = STATUS_UNRECOGNIZED_VOLUME;
}
// If the error wasn't STATUS_UNRECOGNIZED_VOLUME, and this
// request is not going to the Raw file system, then there
// is no reason to continue looping.
if (!AllowRawMount && (status != STATUS_UNRECOGNIZED_VOLUME) &&
FsRtlIsTotalDeviceFailure(status)) {
break;
}
}
}
if (!NT_SUCCESS(status)) {
// The device was not mounted by us so
// drop the reference.
ObDereferenceObject(attachedDevice);
}
} else if ((DeviceObject->Vpb->Flags & VPB_REMOVE_PENDING) != 0) {
// Pnp is attempting to remove this volume. Don't allow the mount.
status = STATUS_DEVICE_DOES_NOT_EXIST;
} else {
// The volume for this device has already been mounted. Return a
// success code.
status = STATUS_SUCCESS;
}
ExReleaseResource(&IopDatabaseResource);
//KeLeaveCriticalRegion();
// Release the I/O database resource lock and the synchronization event for
// the device.
if (!DeviceLockAlreadyHeld) {
KeSetEvent(&DeviceObject->DeviceLock, 0, FALSE);
}
// Finally, if the mount operation failed, and the target device is the
// boot partition, then bugcheck the system. It is not possible for the
// system to run properly if the system's boot partition cannot be mounted.
// Note: Don't bugcheck if the system is already booted.
if (!NT_SUCCESS(status) &&
DeviceObject->Flags & DO_SYSTEM_BOOT_PARTITION &&
InitializationPhase < 2) {
KeBugCheckEx(INACCESSIBLE_BOOT_DEVICE, (ULONG_PTR)DeviceObject, status, 0, 0);
}
return status;
}
NTSTATUS
IopInvalidateVolumesForDevice(
IN PDEVICE_OBJECT DeviceObject
)
/*++
Routine Description:
This routine is used to force filesystems to, as completely as possible, throw
out volumes which remain referenced for a given device.
Arguments:
DeviceObject - Pointer to device object for which volumes are to be
invalidated.
Return Value:
The function value is a successful status code if all filesystems accepted the
operation. Otherwise, an error code is returned.
--*/
{
NTSTATUS status;
NTSTATUS finalStatus;
KEVENT event;
PIRP irp;
PDEVICE_OBJECT fsDeviceObject;
PDEVICE_OBJECT attachedDevice;
PFILE_OBJECT storageFileObject;
HANDLE storageHandle;
PLIST_ENTRY entry;
PLIST_ENTRY queueHeader;
IO_STATUS_BLOCK ioStatus;
PIO_STACK_LOCATION irpSp;
PAGED_CODE();
// Now acquire the resource database lock for the I/O system to perform this
// operation. This resource protects access to the file system queue.
KeEnterCriticalRegion();
(VOID)ExAcquireResourceShared(&IopDatabaseResource, TRUE);
// Get the actual device that would be mounted on. This device is the final
// device in the list of devices which are attached to the specified real device.
attachedDevice = DeviceObject;
while (attachedDevice->AttachedDevice) {
attachedDevice = attachedDevice->AttachedDevice;
}
// Get a handle to this device for use in the fsctl. The way we have to do
// this is kind of loopy: note we wind up with two references to clean up.
// The only use of this fileobject/handle is to communicate the device to
// invalidate volumes on. It isn't used for anything else, and must not be.
try {
storageFileObject = NULL;
storageFileObject = IoCreateStreamFileObjectLite(NULL, attachedDevice);
storageFileObject->Vpb = attachedDevice->Vpb;
storageHandle = NULL;
status = ObOpenObjectByPointer(storageFileObject, OBJ_KERNEL_HANDLE, NULL, 0, IoFileObjectType, KernelMode, &storageHandle);
} except(EXCEPTION_EXECUTE_HANDLER)
{
status = GetExceptionCode();
}
if (NT_SUCCESS(status)) {
// Determine which type of file system should be invoked based on
// the device type of the device being invalidated.
if (DeviceObject->DeviceType == FILE_DEVICE_DISK || DeviceObject->DeviceType == FILE_DEVICE_VIRTUAL_DISK) {
queueHeader = &IopDiskFileSystemQueueHead;
} else if (DeviceObject->DeviceType == FILE_DEVICE_CD_ROM) {
queueHeader = &IopCdRomFileSystemQueueHead;
} else {
queueHeader = &IopTapeFileSystemQueueHead;
}
// Initialize the event and set the status to set up
// for the loop.
KeInitializeEvent(&event, NotificationEvent, FALSE);
finalStatus = STATUS_SUCCESS;
// Now loop through each of the file systems which have been loaded in
// the system and ask them to invalidate volumes they have had mounted
// on it.
for (entry = queueHeader->Flink; entry != queueHeader; entry = entry->Flink) {
// If this is the final entry (Raw file system), then break out of the
// loop at this point, as volumes cannot be invalidated for the caller's purposes in Raw.
if (entry->Flink == queueHeader) {
break;
}
fsDeviceObject = CONTAINING_RECORD(entry, DEVICE_OBJECT, Queue.ListEntry);
// It is possible that the file system has been attached to, so
// walk the attached list for the file system.
while (fsDeviceObject->AttachedDevice) {
fsDeviceObject = fsDeviceObject->AttachedDevice;
}
// Another file system has been found. Attempt to invalidate volumes
// using this file system.
// Begin by resetting the event being used for synchronization with
// the I/O operation.
KeClearEvent(&event);
// Build an IRP for this operation.
irp = IoBuildDeviceIoControlRequest(FSCTL_INVALIDATE_VOLUMES,
fsDeviceObject,
&storageHandle,
sizeof(HANDLE),
NULL,
0,
FALSE,
&event,
&ioStatus);
if (irp == NULL) {
finalStatus = STATUS_INSUFFICIENT_RESOURCES;
break;
}
irpSp = IoGetNextIrpStackLocation(irp);
irpSp->MajorFunction = IRP_MJ_FILE_SYSTEM_CONTROL;
status = IoCallDriver(fsDeviceObject, irp);
// Wait for the I/O operation to complete.
if (status == STATUS_PENDING) {
(VOID)KeWaitForSingleObject(&event,
Executive,
KernelMode,
FALSE,
(PLARGE_INTEGER)NULL);
status = ioStatus.Status;
} else {
// Ensure that the proper status value gets picked up.
ioStatus.Status = status;
ioStatus.Information = 0;
}
// Commute status' indicating the operation is not implemented
// to success. If a filesystem does not implement, it must not
// hold volumes that are not mounted.
if (status == STATUS_INVALID_DEVICE_REQUEST ||
status == STATUS_NOT_IMPLEMENTED) {
status = STATUS_SUCCESS;
}
// Hand back the first failure we get, but plow on anyway.
if (NT_SUCCESS(finalStatus) && !NT_SUCCESS(status)) {
finalStatus = status;
}
}
if (storageFileObject) {
ObDereferenceObject(storageFileObject);
if (storageHandle) {
ZwClose(storageHandle);
}
}
status = finalStatus;
}
ExReleaseResource(&IopDatabaseResource);
KeLeaveCriticalRegion();
return status;
}
BOOLEAN
IopNotifyPnpWhenChainDereferenced(
IN PDEVICE_OBJECT *PhysicalDeviceObjects,
IN ULONG DeviceObjectCount,
IN BOOLEAN Query,
OUT PDEVICE_OBJECT *VetoingDevice
)
/*++
Routine Description:
Called by PnP when processing a Surprise Removal or a Query Remove.
In the case of Surprise Removal this function will set DOE_REMOVE_PENDING
in the device extension flags of the each PDO and all its attached devices.
For each PDO (and its attachment chain) which currently has a zero
ReferenceCount DOE_REMOVE_PENDING is reset and DOE_REMOVE_PROCESSED is
set. IopChainDereferenceComplete is then called to notify PnP that
this PDO is ready for removal.
Then as each remaining PDO and its attachment chain's ReferenceCount drops
to zero IopCheckUnloadOrDelete will call IopChainDereferenceComplete
(supplied by PnP).
In the case of Query Remove this function set DOE_REMOVE_PROCESSED on the
PDO and all its attached devices to prevent further opens. It also checks
to see if the ReferenceCount for all the PDOs and their attached devices is
zero. If so it leaves the DOE_REMOVE_PROCESSED set and returns FALSE. If
not, it resets the DOE_REMOVE_PROCESSED on all the PDOs and their attached
devices and returns TRUE.
Arguments:
PhysicalDeviceObjects List of PDEVICE_OBJECTs for all of the PDOs to be
checked.
DeviceObjectCount Count of PDEVICE_OBJECTs in PhysicalDeviceObjects.
Query TRUE if this is for a Query Remove.
VetoingDevice Only used for Query Remove, Set to first PDO with a
ReferenceCount not equal to zero. This is used to
provide feedback to the user as to why the query
may have failed.
Return Value:
If Query is set then the return value is TRUE if there are outstanding
opens on any of the PDOs or the attached devices, otherwise FALSE is
returned.
If Query is NOT set then the return value is always TRUE.
--*/
{
PDEVOBJ_EXTENSION deviceExtension;
PDEVICE_OBJECT deviceObject;
PDEVICE_OBJECT attachedDeviceObject;
PDEVICE_NODE deviceNode;
ULONG referenced;
ULONG pass1SetFlag;
ULONG pass1ClearFlag;
LONG i;
KIRQL irql;
ExAcquireSpinLock(&IopDatabaseLock, &irql);
if (Query) {
pass1SetFlag = DOE_REMOVE_PROCESSED;
pass1ClearFlag = 0;
} else {
pass1SetFlag = DOE_REMOVE_PENDING;
pass1ClearFlag = DOE_REMOVE_PROCESSED;
}
for (i = 0; i < (LONG)DeviceObjectCount; i++) {
deviceObject = PhysicalDeviceObjects[i];
deviceExtension = deviceObject->DeviceObjectExtension;
deviceNode = (PDEVICE_NODE)deviceExtension->DeviceNode;
ASSERT(deviceNode != NULL);
// Assume that at least one device object has a reference. Walk the
// entire chain marking them with DOE_REMOVE_PENDING.
// We don't actually care how many aggregate references there actually
// are. All we're interested in is whether there are any. So we'll OR
// them together rather than adding them. That way we don't have to do
// testing or branching and we don't have to worry about overflow in the
// highly unlikely event that there are a total of more references than
// will fit in a ULONG.
referenced = 0;
attachedDeviceObject = deviceObject;
do {
deviceExtension = attachedDeviceObject->DeviceObjectExtension;
ASSERT(deviceExtension != NULL);
ASSERT(!(deviceExtension->ExtensionFlags & pass1SetFlag));
deviceExtension->ExtensionFlags &= ~pass1ClearFlag;
deviceExtension->ExtensionFlags |= pass1SetFlag;
referenced |= attachedDeviceObject->ReferenceCount;
attachedDeviceObject = attachedDeviceObject->AttachedDevice;
} while (attachedDeviceObject != NULL);
if (!Query && referenced == 0) {
// There aren't any outstanding references, retraverse the chain and
// mark them all DOE_REMOVE_PROCESSED. This will still prevent any
// opens or attaches from occuring but we won't call
// IopChainDereferenceComplete in IopCompleteUnloadOrDelete.
attachedDeviceObject = deviceObject;
do {
deviceExtension = attachedDeviceObject->DeviceObjectExtension;
deviceExtension->ExtensionFlags &= ~DOE_REMOVE_PENDING;
deviceExtension->ExtensionFlags |= DOE_REMOVE_PROCESSED;
attachedDeviceObject = attachedDeviceObject->AttachedDevice;
} while (attachedDeviceObject != NULL);
ExReleaseSpinLock(&IopDatabaseLock, irql);
IopChainDereferenceComplete(deviceObject);
ExAcquireSpinLock(&IopDatabaseLock, &irql);
} else if (Query && referenced != 0) {
break;
}
}
if (Query && referenced != 0) {
if (VetoingDevice != NULL) {
*VetoingDevice = deviceObject;
}
for (; i >= 0; i--) {
deviceObject = PhysicalDeviceObjects[i];
deviceExtension = deviceObject->DeviceObjectExtension;
// There are outstanding references, retraverse the chain and
// unset DOE_REMOVE_PROCESSED.
attachedDeviceObject = deviceObject;
do {
deviceExtension = attachedDeviceObject->DeviceObjectExtension;
deviceExtension->ExtensionFlags &= ~DOE_REMOVE_PROCESSED;
attachedDeviceObject = attachedDeviceObject->AttachedDevice;
} while (attachedDeviceObject != NULL);
}
}
ExReleaseSpinLock(&IopDatabaseLock, irql);
return !Query || referenced != 0;
}
NTSTATUS
IopOpenLinkOrRenameTarget(
OUT PHANDLE TargetHandle,
IN PIRP Irp,
IN PVOID RenameBuffer,
IN PFILE_OBJECT FileObject
)
/*++
Routine Description:
This routine is invoked by the rename, set link and set copy-on-write code
in the I/O system's NtSetInformationFile system service when the caller has
specified a fully qualified file name as the target of a rename, set link,
or set copy-on-write operation. This routine attempts to open the parent
of the specified file and checks the following:
o If the file itself exists, then the caller must have specified that
the target is to be replaced, otherwise an error is returned.
o Ensures that the target file specification refers to the same volume
upon which the source file exists.
Arguments:
TargetHandle - Supplies the address of a variable to return the handle to
the opened target file if no errors have occurred.
Irp - Supplies a pointer to the IRP that represents the current rename
request.
RenameBuffer - Supplies a pointer to the system intermediate buffer that
contains the caller's rename parameters.
FileObject - Supplies a pointer to the file object representing the file
being renamed.
Return Value:
The function value is the final status of the operation.
Note:
This function assumes that the layout of a rename, set link and set
copy-on-write information structure are exactly the same.
--*/
{
NTSTATUS status;
IO_STATUS_BLOCK ioStatus;
HANDLE handle;
OBJECT_ATTRIBUTES objectAttributes;
UNICODE_STRING newFileName;
PIO_STACK_LOCATION irpSp;
PFILE_OBJECT targetFileObject;
OBJECT_HANDLE_INFORMATION handleInformation;
PFILE_RENAME_INFORMATION renameBuffer = RenameBuffer;
PAGED_CODE();
ASSERT(sizeof(FILE_RENAME_INFORMATION) ==
sizeof(FILE_LINK_INFORMATION));
ASSERT(FIELD_OFFSET(FILE_RENAME_INFORMATION, ReplaceIfExists) ==
FIELD_OFFSET(FILE_LINK_INFORMATION, ReplaceIfExists));
ASSERT(FIELD_OFFSET(FILE_RENAME_INFORMATION, RootDirectory) ==
FIELD_OFFSET(FILE_LINK_INFORMATION, RootDirectory));
ASSERT(FIELD_OFFSET(FILE_RENAME_INFORMATION, FileNameLength) ==
FIELD_OFFSET(FILE_LINK_INFORMATION, FileNameLength));
ASSERT(FIELD_OFFSET(FILE_RENAME_INFORMATION, FileName) ==
FIELD_OFFSET(FILE_LINK_INFORMATION, FileName));
ASSERT(sizeof(FILE_RENAME_INFORMATION) ==
sizeof(FILE_MOVE_CLUSTER_INFORMATION));
ASSERT(FIELD_OFFSET(FILE_RENAME_INFORMATION, ReplaceIfExists) ==
FIELD_OFFSET(FILE_MOVE_CLUSTER_INFORMATION, ClusterCount));
ASSERT(FIELD_OFFSET(FILE_RENAME_INFORMATION, RootDirectory) ==
FIELD_OFFSET(FILE_MOVE_CLUSTER_INFORMATION, RootDirectory));
ASSERT(FIELD_OFFSET(FILE_RENAME_INFORMATION, FileNameLength) ==
FIELD_OFFSET(FILE_MOVE_CLUSTER_INFORMATION, FileNameLength));
ASSERT(FIELD_OFFSET(FILE_RENAME_INFORMATION, FileName) ==
FIELD_OFFSET(FILE_MOVE_CLUSTER_INFORMATION, FileName));
// A fully qualified file name was specified. Begin by attempting to open
// the parent directory of the specified target file.
newFileName.Length = (USHORT)renameBuffer->FileNameLength;
newFileName.MaximumLength = (USHORT)renameBuffer->FileNameLength;
newFileName.Buffer = renameBuffer->FileName;
InitializeObjectAttributes(&objectAttributes,
&newFileName,
FileObject->Flags & FO_OPENED_CASE_SENSITIVE ? 0 : OBJ_CASE_INSENSITIVE,
renameBuffer->RootDirectory,
(PSECURITY_DESCRIPTOR)NULL);
status = IoCreateFile(&handle,
FILE_WRITE_DATA | SYNCHRONIZE,
&objectAttributes,
&ioStatus,
(PLARGE_INTEGER)NULL,
0,
FILE_SHARE_READ | FILE_SHARE_WRITE,
FILE_OPEN,
FILE_OPEN_FOR_BACKUP_INTENT,
(PVOID)NULL,
0L,
CreateFileTypeNone,
(PVOID)NULL,
IO_NO_PARAMETER_CHECKING |
IO_OPEN_TARGET_DIRECTORY |
IO_FORCE_ACCESS_CHECK);
if (NT_SUCCESS(status)) {
// The open operation for the target file's parent directory was
// successful. Check to see whether or not the file exists.
irpSp = IoGetNextIrpStackLocation(Irp);
if (irpSp->Parameters.SetFile.FileInformationClass == FileLinkInformation &&
!renameBuffer->ReplaceIfExists &&
ioStatus.Information == FILE_EXISTS) {
// The target file exists, and the caller does not want to replace
// it. This is a name collision error so cleanup and return.
NtClose(handle);
status = STATUS_OBJECT_NAME_COLLISION;
} else {
// Everything up to this point is fine, so dereference the handle
// to a pointer to the file object and ensure that the two file
// specifications refer to the same device.
status = ObReferenceObjectByHandle(handle,
FILE_WRITE_DATA,
IoFileObjectType,
UserMode,
(PVOID *)&targetFileObject,
&handleInformation);
if (NT_SUCCESS(status)) {
ObDereferenceObject(targetFileObject);
if (IoGetRelatedDeviceObject(targetFileObject) !=
IoGetRelatedDeviceObject(FileObject)) {
// The two files refer to different devices. Clean everything
// up and return an appropriate error.
NtClose(handle);
status = STATUS_NOT_SAME_DEVICE;
} else {
// Otherwise, everything worked, so allow the rename operation
// to continue.
irpSp->Parameters.SetFile.FileObject = targetFileObject;
*TargetHandle = handle;
status = STATUS_SUCCESS;
}
} else {
// There was an error referencing the handle to what should
// have been the target directory. This generally means that
// there was a resource problem or the handle was invalid, etc.
// Simply attempt to close the handle and return the error.
NtClose(handle);
}
}
}
// Return the final status of the operation.
return status;
}
NTSTATUS
IopOpenRegistryKey(
OUT PHANDLE Handle,
IN HANDLE BaseHandle OPTIONAL,
IN PUNICODE_STRING KeyName,
IN ACCESS_MASK DesiredAccess,
IN BOOLEAN Create
)
/*++
Routine Description:
Opens or creates a VOLATILE registry key using the name passed in based
at the BaseHandle node.
Arguments:
Handle - Pointer to the handle which will contain the registry key that
was opened.
BaseHandle - Handle to the base path from which the key must be opened.
KeyName - Name of the Key that must be opened/created.
DesiredAccess - Specifies the desired access that the caller needs to
the key.
Create - Determines if the key is to be created if it does not exist.
Return Value:
The function value is the final status of the operation.
--*/
{
OBJECT_ATTRIBUTES objectAttributes;
ULONG disposition;
PAGED_CODE();
// Initialize the object for the key.
InitializeObjectAttributes(&objectAttributes,
KeyName,
OBJ_CASE_INSENSITIVE,
BaseHandle,
(PSECURITY_DESCRIPTOR)NULL);
// Create the key or open it, as appropriate based on the caller's
// wishes.
if (Create) {
return ZwCreateKey(Handle,
DesiredAccess,
&objectAttributes,
0,
(PUNICODE_STRING)NULL,
REG_OPTION_VOLATILE,
&disposition);
} else {
return ZwOpenKey(Handle,
DesiredAccess,
&objectAttributes);
}
}
NTSTATUS
IopQueryXxxInformation(
IN PFILE_OBJECT FileObject,
IN ULONG InformationClass,
IN ULONG Length,
OUT PVOID Information,
OUT PULONG ReturnedLength,
IN BOOLEAN FileInformation
)
/*++
Routine Description:
This routine returns the requested information about a specified file
or volume. The information returned is determined by the class that
is specified, and it is placed into the caller's output buffer.
Arguments:
FileObject - Supplies a pointer to the file object about which the requested
information is returned.
FsInformationClass - Specifies the type of information which should be
returned about the file/volume.
Length - Supplies the length of the buffer in bytes.
FsInformation - Supplies a buffer to receive the requested information
returned about the file. This buffer must not be pageable and must
reside in system space.
ReturnedLength - Supplies a variable that is to receive the length of the
information written to the buffer.
FileInformation - Boolean that indicates whether the information requested
is for a file or a volume.
Return Value:
The status returned is the final completion status of the operation.
--*/
{
PIRP irp;
NTSTATUS status;
PDEVICE_OBJECT deviceObject;
KEVENT event;
PIO_STACK_LOCATION irpSp;
IO_STATUS_BLOCK localIoStatus;
BOOLEAN synchronousIo;
PAGED_CODE();
// Reference the file object here so that no special checks need be made
// in I/O completion to determine whether or not to dereference the file
// object.
ObReferenceObject(FileObject);
// Make a special check here to determine whether this is a synchronous
// I/O operation. If it is, then wait here until the file is owned by
// the current thread. If this is not a (serialized) synchronous I/O
// operation, then initialize the local event.
if (FileObject->Flags & FO_SYNCHRONOUS_IO) {
BOOLEAN interrupted;
if (!IopAcquireFastLock(FileObject)) {
status = IopAcquireFileObjectLock(FileObject,
KernelMode,
(BOOLEAN)((FileObject->Flags & FO_ALERTABLE_IO) != 0),
&interrupted);
if (interrupted) {
ObDereferenceObject(FileObject);
return status;
}
}
KeClearEvent(&FileObject->Event);
synchronousIo = TRUE;
} else {
KeInitializeEvent(&event, SynchronizationEvent, FALSE);
synchronousIo = FALSE;
}
// Get the address of the target device object.
deviceObject = IoGetRelatedDeviceObject(FileObject);
// Allocate and initialize the I/O Request Packet (IRP) for this operation.
// The allocation is performed with an exception handler in case the
// caller does not have enough quota to allocate the packet.
irp = IoAllocateIrp(deviceObject->StackSize, TRUE);
if (!irp) {
// An IRP could not be allocated. Cleanup and return an appropriate
// error status code.
IopAllocateIrpCleanup(FileObject, (PKEVENT)NULL);
return STATUS_INSUFFICIENT_RESOURCES;
}
irp->Tail.Overlay.OriginalFileObject = FileObject;
irp->Tail.Overlay.Thread = PsGetCurrentThread();
irp->RequestorMode = KernelMode;
// Fill in the service independent parameters in the IRP.
if (synchronousIo) {
irp->UserEvent = (PKEVENT)NULL;
} else {
irp->UserEvent = &event;
irp->Flags = IRP_SYNCHRONOUS_API;
}
irp->UserIosb = &localIoStatus;
irp->Overlay.AsynchronousParameters.UserApcRoutine = (PIO_APC_ROUTINE)NULL;
// Get a pointer to the stack location for the first driver. This will be
// used to pass the original function codes and parameters.
irpSp = IoGetNextIrpStackLocation(irp);
irpSp->MajorFunction = FileInformation ?
IRP_MJ_QUERY_INFORMATION :
IRP_MJ_QUERY_VOLUME_INFORMATION;
irpSp->FileObject = FileObject;
// Set the system buffer address to the address of the caller's buffer and
// set the flags so that the buffer is not deallocated.
irp->AssociatedIrp.SystemBuffer = Information;
irp->Flags |= IRP_BUFFERED_IO;
// Copy the caller's parameters to the service-specific portion of the
// IRP.
if (FileInformation) {
irpSp->Parameters.QueryFile.Length = Length;
irpSp->Parameters.QueryFile.FileInformationClass = InformationClass;
} else {
irpSp->Parameters.QueryVolume.Length = Length;
irpSp->Parameters.QueryVolume.FsInformationClass = InformationClass;
}
// Insert the packet at the head of the IRP list for the thread.
IopQueueThreadIrp(irp);
// Now simply invoke the driver at its dispatch entry with the IRP.
status = IoCallDriver(deviceObject, irp);
// If this operation was a synchronous I/O operation, check the return
// status to determine whether or not to wait on the file object. If
// the file object is to be waited on, wait for the operation to complete
// and obtain the final status from the file object itself.
if (synchronousIo) {
if (status == STATUS_PENDING) {
status = KeWaitForSingleObject(&FileObject->Event,
Executive,
KernelMode,
(BOOLEAN)((FileObject->Flags & FO_ALERTABLE_IO) != 0),
(PLARGE_INTEGER)NULL);
if (status == STATUS_ALERTED) {
IopCancelAlertedRequest(&FileObject->Event, irp);
}
status = FileObject->FinalStatus;
}
IopReleaseFileObjectLock(FileObject);
} else {
// This is a normal synchronous I/O operation, as opposed to a
// serialized synchronous I/O operation. For this case, wait
// for the local event and copy the final status information
// back to the caller.
if (status == STATUS_PENDING) {
(VOID)KeWaitForSingleObject(&event,
Executive,
KernelMode,
FALSE,
(PLARGE_INTEGER)NULL);
status = localIoStatus.Status;
}
}
*ReturnedLength = (ULONG)localIoStatus.Information;
return status;
}
VOID
IopRaiseHardError(
IN PVOID NormalContext,
IN PVOID SystemArgument1,
IN PVOID SystemArgument2
)
/*++
Routine Description:
This routine raises a hard error popup in the context of the current
thread. The APC was used to get into the context of this thread so that
the popup would be sent to the appropriate port.
Arguments:
NormalContext - Supplies a pointer to the I/O Request Packet (IRP) that
was initially used to request the operation that has failed.
SystemArgument1 - Supplies a pointer to the media's volume parameter block.
See IoRaiseHardError documentation for more information.
SystemArgument2 - Supplies a pointer to the real device object. See
IoRaiseHardError documentation for more information.
Return Value:
None.
--*/
{
ULONG_PTR parameters[2];
ULONG numberOfParameters;
ULONG parameterMask;
ULONG response;
NTSTATUS status;
PIRP irp = (PIRP)NormalContext;
PVPB vpb = (PVPB)SystemArgument1;
PDEVICE_OBJECT realDeviceObject = (PDEVICE_OBJECT)SystemArgument2;
ULONG length;
POBJECT_NAME_INFORMATION objectName;
UNICODE_STRING labelName;
// Determine the name of the device and the volume label of the offending
// media. Start by determining the size of the DeviceName, and allocate
// enough storage for both the ObjectName structure and the string
// because "that's the ways Steve's routine works".
ObQueryNameString(realDeviceObject, NULL, 0, &length);
if ((objectName = ExAllocatePool(PagedPool, length)) == NULL) {
status = STATUS_INSUFFICIENT_RESOURCES;
} else {
status = STATUS_SUCCESS;
}
if (!NT_SUCCESS(status) ||
!NT_SUCCESS(status = ObQueryNameString(realDeviceObject,
objectName,
length,
&response))) {
// Allocation of the pool to put up this popup did not work or
// something else failed, so there isn't really much that can be
// done here. Simply return an error back to the user.
if (objectName) {
ExFreePool(objectName);
}
irp->IoStatus.Status = status;
irp->IoStatus.Information = 0;
IoCompleteRequest(irp, IO_DISK_INCREMENT);
return;
}
// The volume label has a max size of 32 characters (Unicode). Convert
// it to a Unicode string for output in the popup message.
if (vpb != NULL && vpb->Flags & VPB_MOUNTED) {
labelName.Buffer = &vpb->VolumeLabel[0];
labelName.Length = vpb->VolumeLabelLength;
labelName.MaximumLength = MAXIMUM_VOLUME_LABEL_LENGTH;
} else {
RtlInitUnicodeString(&labelName, NULL);
}
// Different pop-ups have different printf formats. Depending on the
// specific error value, adjust the parameters.
switch (irp->IoStatus.Status) {
case STATUS_MEDIA_WRITE_PROTECTED:
case STATUS_WRONG_VOLUME:
numberOfParameters = 2;
parameterMask = 3;
parameters[0] = (ULONG_PTR)&labelName;
parameters[1] = (ULONG_PTR)&objectName->Name;
break;
case STATUS_DEVICE_NOT_READY:
case STATUS_IO_TIMEOUT:
case STATUS_NO_MEDIA_IN_DEVICE:
case STATUS_UNRECOGNIZED_MEDIA:
numberOfParameters = 1;
parameterMask = 1;
parameters[0] = (ULONG_PTR)&objectName->Name;
parameters[1] = 0;
break;
default:
numberOfParameters = 0;
parameterMask = 0;
}
// Simply raise the hard error.
if (ExReadyForErrors) {
status = ExRaiseHardError(irp->IoStatus.Status,
numberOfParameters,
parameterMask,
parameters,
OptionCancelTryContinue,
&response);
} else {
status = STATUS_UNSUCCESSFUL;
response = ResponseReturnToCaller;
}
// Free any pool or other resources that were allocated to output the
// popup.
ExFreePool(objectName);
// If there was a problem, or the user didn't want to retry, just
// complete the request. Otherwise simply call the driver entry
// point and retry the IRP as if it had never been tried before.
if (!NT_SUCCESS(status) || response != ResponseTryAgain) {
// Before completing the request, make one last check. If this was
// a mount request, and the reason for the failure was t/o, no media,
// or unrecognized media, then set the Information field of the status
// block to indicate whether or not an abort was performed.
if (response == ResponseCancel) {
PIO_STACK_LOCATION irpSp = IoGetCurrentIrpStackLocation(irp);
if (irpSp->MajorFunction == IRP_MJ_FILE_SYSTEM_CONTROL &&
irpSp->MinorFunction == IRP_MN_MOUNT_VOLUME) {
irp->IoStatus.Information = IOP_ABORT;
} else {
irp->IoStatus.Status = STATUS_REQUEST_ABORTED;
}
}
// An error was incurred, so zero out the information field before
// completing the request if this was an input operation. Otherwise,
// IopCompleteRequest will try to copy to the user's buffer.
if (irp->Flags & IRP_INPUT_OPERATION) {
irp->IoStatus.Information = 0;
}
IoCompleteRequest(irp, IO_DISK_INCREMENT);
} else {
PIO_STACK_LOCATION irpSp = IoGetCurrentIrpStackLocation(irp);
PDEVICE_OBJECT fsDeviceObject = irpSp->DeviceObject;
PDRIVER_OBJECT driverObject = fsDeviceObject->DriverObject;
// Retry the request from the top.
PERFINFO_DRIVER_MAJORFUNCTION_CALL(irp, irpSp, driverObject);
driverObject->MajorFunction[irpSp->MajorFunction](fsDeviceObject,
irp);
PERFINFO_DRIVER_MAJORFUNCTION_RETURN(irp, irpSp, driverObject);
}
}
VOID
IopRaiseInformationalHardError(
IN PVOID NormalContext,
IN PVOID SystemArgument1,
IN PVOID SystemArgument2
)
/*++
Routine Description:
This routine performs the actual pop-up. It will called from either the
hard-error thread, or a APC routine in a user thread after exiting the
file system.
Arguments:
NormalContext - Contains the information for the pop-up
SystemArgument1 - not used.
SystemArgument1 - not used.
Return Value:
None.
--*/
{
ULONG parameterPresent;
ULONG_PTR errorParameter;
ULONG errorResponse;
PIOP_HARD_ERROR_PACKET hardErrorPacket;
UNREFERENCED_PARAMETER(SystemArgument1);
UNREFERENCED_PARAMETER(SystemArgument2);
hardErrorPacket = (PIOP_HARD_ERROR_PACKET)NormalContext;
// Simply raise the hard error if the system is ready to accept one.
errorParameter = (ULONG_PTR)&hardErrorPacket->String;
parameterPresent = (hardErrorPacket->String.Buffer != NULL);
if (ExReadyForErrors) {
(VOID)ExRaiseHardError(hardErrorPacket->ErrorStatus,
parameterPresent,
parameterPresent,
parameterPresent ? &errorParameter : NULL,
OptionOk,
&errorResponse);
}
// Now free the packet and the buffer, if one was specified.
if (hardErrorPacket->String.Buffer) {
ExFreePool(hardErrorPacket->String.Buffer);
}
ExFreePool(hardErrorPacket);
InterlockedDecrement(&IopHardError.NumPendingApcPopups);
}
VOID
IopReadyDeviceObjects(
IN PDRIVER_OBJECT DriverObject
)
/*++
Routine Description:
This routine is invoked to mark all of the device objects owned by the
specified driver as having been fully initialized and therefore ready
for access by other drivers/clients.
Arguments:
DriverObject - Supplies a pointer to the driver object for the driver
whose devices are to be marked as being "ready".
Return Value:
None.
--*/
{
PDEVICE_OBJECT deviceObject = DriverObject->DeviceObject;
PAGED_CODE();
// Loop through all of the driver's device objects, clearing the
// DO_DEVICE_INITIALIZING flag.
DriverObject->Flags |= DRVO_INITIALIZED;
while (deviceObject) {
deviceObject->Flags &= ~DO_DEVICE_INITIALIZING;
deviceObject = deviceObject->NextDevice;
}
}
NTSTATUS
IopResurrectDriver(
PDRIVER_OBJECT DriverObject
)
/*++
Routine Description:
This routine is invoked to clear unload pending flag on all of the device
objects owned by the specified driver, if the unload routine has not run.
This allows the driver to come back to life after a pending unload.
Arguments:
DriverObject - Supplies a pointer to the driver object for the driver
whose devices are to be cleared.
Return Value:
Status - Returns success if the driver's unload routine has not run;
otherwise STATUS_IMAGE_ALREADY_LOADED is returned.
--*/
{
PDEVICE_OBJECT deviceObject = DriverObject->DeviceObject;
KIRQL irql;
// Acquire the I/O spinlock that protects the device list and
// driver flags.
ExAcquireSpinLock(&IopDatabaseLock, &irql);
if (DriverObject->Flags & DRVO_UNLOAD_INVOKED || !deviceObject ||
!(deviceObject->DeviceObjectExtension->ExtensionFlags & DOE_UNLOAD_PENDING)) {
ExReleaseSpinLock(&IopDatabaseLock, irql);
return STATUS_IMAGE_ALREADY_LOADED;
}
// Loop through all of the driver's device objects, clearing the
// DOE_UNLOAD_PENDING flag.
while (deviceObject) {
deviceObject->DeviceObjectExtension->ExtensionFlags &= ~DOE_UNLOAD_PENDING;
deviceObject = deviceObject->NextDevice;
}
ExReleaseSpinLock(&IopDatabaseLock, irql);
return STATUS_SUCCESS;
}
VOID
IopMarshalIds(
OUT PTRACKING_BUFFER TrackingBuffer,
IN PFILE_VOLUMEID_WITH_TYPE TargetVolumeId,
IN PFILE_OBJECTID_BUFFER TargetObjectId,
IN PFILE_TRACKING_INFORMATION TrackingInfo
)
/*++
Routine Description:
This routine marshals the TargetVolumeId and TargetObjectId
into the supplied TrackingBuffer in a standard remotable format.
It also clears the DestinationFile handle to NULL, and sets the
ObjectInformationLength to the size of the marshalled data.
Arguments:
TrackingBuffer - The buffer to receive the marshalled parameters.
TargetVolumeId - The volume id to marshal.
TargetObjectId - The object id to marshal.
TrackingInfo - The additional tracking information to marshal.
--*/
{
ULONG ObjectInformationLength = 0;
TrackingBuffer->TrackingInformation.DestinationFile = (HANDLE)NULL;
RtlZeroMemory(&TrackingBuffer->TrackingInformation.ObjectInformation[ObjectInformationLength],
sizeof(TargetVolumeId->Type));
RtlCopyMemory(&TrackingBuffer->TrackingInformation.ObjectInformation[ObjectInformationLength],
&TargetVolumeId->Type,
sizeof(TargetVolumeId->Type));
ObjectInformationLength += sizeof(TargetVolumeId->Type);
RtlCopyMemory(&TrackingBuffer->TrackingInformation.ObjectInformation[ObjectInformationLength],
&TargetVolumeId->VolumeId[0],
sizeof(TargetVolumeId->VolumeId));
ObjectInformationLength += sizeof(TargetVolumeId->VolumeId);
RtlCopyMemory(&TrackingBuffer->TrackingInformation.ObjectInformation[ObjectInformationLength],
&TargetObjectId->ObjectId[0],
sizeof(TargetObjectId->ObjectId));
ObjectInformationLength += sizeof(TargetObjectId->ObjectId);
RtlCopyMemory(&TrackingBuffer->TrackingInformation.ObjectInformation[ObjectInformationLength],
&TrackingInfo->ObjectInformation[0],
TrackingInfo->ObjectInformationLength);
ObjectInformationLength += TrackingInfo->ObjectInformationLength;
TrackingBuffer->TrackingInformation.ObjectInformationLength = ObjectInformationLength;
}
VOID
IopUnMarshalIds(
IN FILE_TRACKING_INFORMATION * TrackingInformation,
OUT FILE_VOLUMEID_WITH_TYPE * TargetVolumeId,
OUT GUID * TargetObjectId,
OUT GUID * TargetMachineId
)
/*++
Routine Description:
This routine unmarshals the TargetVolumeId and TargetObjectId
from the supplied TrackingInformation from a standard remotable format.
Arguments:
TrackingInformation - The buffer containing the marshalled parameters.
TargetVolumeId - Buffer to receive the volume id.
TargetObjectId - Buffer to receive the object id.
TargetMachineId - Buffer to receieve the machine id.
--*/
{
ULONG ObjectInformationLength = 0;
RtlCopyMemory(&TargetVolumeId->Type,
&TrackingInformation->ObjectInformation[ObjectInformationLength],
sizeof(TargetVolumeId->Type));
ObjectInformationLength += sizeof(TargetVolumeId->Type);
RtlCopyMemory(&TargetVolumeId->VolumeId[0],
&TrackingInformation->ObjectInformation[ObjectInformationLength],
sizeof(TargetVolumeId->VolumeId));
ObjectInformationLength += sizeof(TargetVolumeId->VolumeId);
RtlCopyMemory(TargetObjectId,
&TrackingInformation->ObjectInformation[ObjectInformationLength],
sizeof(*TargetObjectId));
ObjectInformationLength += sizeof(*TargetObjectId);
if (TrackingInformation->ObjectInformationLength > ObjectInformationLength) {
RtlCopyMemory(TargetMachineId,
&TrackingInformation->ObjectInformation[ObjectInformationLength],
min(sizeof(*TargetMachineId), TrackingInformation->ObjectInformationLength - ObjectInformationLength));
// ObjectInformationLength += sizeof(GUID);
}
}
NTSTATUS
IopSendMessageToTrackService(
IN PFILE_VOLUMEID_WITH_TYPE SourceVolumeId,
IN PFILE_OBJECTID_BUFFER SourceObjectId,
IN PFILE_TRACKING_INFORMATION TargetObjectInformation
)
/*++
Routine Description:
This routine is invoked to send a message to the user-mode link tracking
service to inform it that a file has been moved so that it can track it
by its object ID.
Arguments:
SourceVolumeId - Volume ID of the source file.
SourceObjectId - Object ID of the source file.
TargetObjectInformation - Volume ID, object ID of the target file.
Return Value:
The final function value is the final completion status of the operation.
--*/
{
typedef struct _LINK_TRACKING_MESSAGE
{
NTSTATUS Status;
ULONG Request;
FILE_VOLUMEID_WITH_TYPE SourceVolumeId; // src vol type & id
FILE_OBJECTID_BUFFER SourceObjectId; // src obj id & birth info
FILE_VOLUMEID_WITH_TYPE TargetVolumeId; // tgt vol type & id
GUID TargetObjectId; // tgt obj id
GUID TargetMachineId;
} LINK_TRACKING_MESSAGE, *PLINK_TRACKING_MESSAGE;
typedef struct _LINK_TRACKING_RESPONSE
{
NTSTATUS Status;
} LINK_TRACKING_RESPONSE, *PLINK_TRACKING_RESPONSE;
PPORT_MESSAGE portMessage;
PPORT_MESSAGE portReplyMessage;
CHAR portReply[256];
PLINK_TRACKING_MESSAGE requestMessage;
PLINK_TRACKING_RESPONSE replyMessage;
LINK_TRACKING_PACKET ltp;
NTSTATUS status;
ULONG loopCount = 0;
PAGED_CODE();
// Begin by determining whether or not the LPC port to the link tracking
// service has been opened. If not, then attempt to open it now.
retry:
if (!IopLinkTrackingServiceObject) {
// The port has not yet been opened. Check to see whether or not
// the service has been started. If not, then get out now as there
// will be no port if the service is not running.
if (!KeReadStateEvent(IopLinkTrackingServiceEvent)) {
return STATUS_NO_TRACKING_SERVICE;
}
for (;; ) {
status = KeWaitForSingleObject(&IopLinkTrackingPortObject,
Executive,
KeGetPreviousMode(),
FALSE,
(PLARGE_INTEGER)NULL);
if ((status == STATUS_USER_APC) || (status == STATUS_ALERTED)) {
return status;
}
// There is no referenced object pointer to the
// link tracking port so open it.
if (!IopLinkTrackingServiceObject) {
ExInitializeWorkItem(
&IopLinkTrackingPacket.WorkQueueItem,
IopConnectLinkTrackingPort,
&IopLinkTrackingPacket);
(VOID)KeResetEvent(&IopLinkTrackingPacket.Event);
ExQueueWorkItem(&IopLinkTrackingPacket.WorkQueueItem,
DelayedWorkQueue);
status = KeWaitForSingleObject(
&IopLinkTrackingPacket.Event,
Executive,
KeGetPreviousMode(),
FALSE,
(PLARGE_INTEGER)NULL);
if ((status == STATUS_USER_APC) || (status == STATUS_ALERTED)) {
NOTHING;
} else if (!NT_SUCCESS(IopLinkTrackingPacket.FinalStatus)) {
status = IopLinkTrackingPacket.FinalStatus;
}
KeSetEvent(&IopLinkTrackingPortObject,
0,
FALSE);
if (status == STATUS_SUCCESS) {
break;
} else {
return status;
}
} else {
// The connection is established.
KeSetEvent(&IopLinkTrackingPortObject,
0,
FALSE);
break;
}
}
}
// Form a message from the input parameters and send it to the caller.
portMessage = ExAllocatePool(PagedPool,
sizeof(LINK_TRACKING_MESSAGE) +
sizeof(PORT_MESSAGE));
if (!portMessage) {
return STATUS_INSUFFICIENT_RESOURCES;
}
requestMessage = (PLINK_TRACKING_MESSAGE)(portMessage + 1);
RtlZeroMemory(requestMessage, sizeof(*requestMessage));
requestMessage->Status = STATUS_SUCCESS;
requestMessage->Request = 0;
RtlCopyMemory(&requestMessage->SourceVolumeId,
SourceVolumeId,
sizeof(FILE_VOLUMEID_WITH_TYPE));
RtlCopyMemory(&requestMessage->SourceObjectId,
SourceObjectId,
sizeof(FILE_OBJECTID_BUFFER));
IopUnMarshalIds(TargetObjectInformation,
&requestMessage->TargetVolumeId,
&requestMessage->TargetObjectId,
&requestMessage->TargetMachineId);
portMessage->u1.s1.TotalLength = (USHORT)(sizeof(PORT_MESSAGE) +
sizeof(LINK_TRACKING_MESSAGE));
portMessage->u1.s1.DataLength = (USHORT) sizeof(LINK_TRACKING_MESSAGE);
portMessage->u2.ZeroInit = 0;
status = LpcRequestWaitReplyPort(IopLinkTrackingServiceObject,
portMessage,
(PPORT_MESSAGE)&portReply[0]);
if (!NT_SUCCESS(status)) {
if (status == STATUS_PORT_DISCONNECTED) {
status = KeWaitForSingleObject(&IopLinkTrackingPortObject,
Executive,
KeGetPreviousMode(),
FALSE,
(PLARGE_INTEGER)NULL);
ObDereferenceObject(IopLinkTrackingServiceObject);
IopLinkTrackingServiceObject = NULL;
KeSetEvent(&IopLinkTrackingPortObject,
0,
FALSE);
if (!loopCount) {
loopCount += 1;
goto retry;
}
}
}
if (NT_SUCCESS(status)) {
portReplyMessage = (PPORT_MESSAGE)&portReply[0];
replyMessage = (PLINK_TRACKING_RESPONSE)(portReplyMessage + 1);
status = replyMessage->Status;
}
return status;
}
NTSTATUS
IopSetEaOrQuotaInformationFile(
IN HANDLE FileHandle,
OUT PIO_STATUS_BLOCK IoStatusBlock,
IN PVOID Buffer,
IN ULONG Length,
IN BOOLEAN SetEa
)
/*++
Routine Description:
This routine is invoked by the NtSetEa[Quota]InformationFile system services
to either modify the EAs on a file or the quota entries on a volume. All of
the specified entries in the buffer are made to the file or volume.
Arguments:
FileHandle - Supplies a handle to the file/volume for which the entries are
to be applied.
IoStatusBlock - Address of the caller's I/O status block.
Buffer - Supplies a buffer containing the entries to be added/modified.
Length - Supplies the length, in bytes, of the buffer.
SetEa - A BOOLEAN that indicates whether to change the EAs on a file or
the quota entries on the volume.
Return Value:
The status returned is the final completion status of the operation.
--*/
{
PIRP irp;
NTSTATUS status;
PFILE_OBJECT fileObject;
PDEVICE_OBJECT deviceObject;
PKEVENT event = (PKEVENT)NULL;
KPROCESSOR_MODE requestorMode;
PIO_STACK_LOCATION irpSp;
IO_STATUS_BLOCK localIoStatus;
BOOLEAN synchronousIo;
PAGED_CODE();
// Get the previous mode; i.e., the mode of the caller.
requestorMode = KeGetPreviousMode();
if (requestorMode != KernelMode) {
// The caller's access mode is user, so probe each of the arguments
// and capture them as necessary. If any failures occur, the condition
// handler will be invoked to handle them. It will simply cleanup and
// return an access violation status code back to the system service
// dispatcher.
try {
// The IoStatusBlock parameter must be writeable by the caller.
ProbeForWriteIoStatus(IoStatusBlock);
// The Buffer parameter must be readable by the caller.
ProbeForRead(Buffer, Length, sizeof(ULONG));
} except(EXCEPTION_EXECUTE_HANDLER)
{
// An exception was incurred while probing the caller's parameters.
// Cleanup and return an appropriate error status code.
return GetExceptionCode();
}
}
// There were no blatant errors so far, so reference the file object so
// the target device object can be found. Note that if the handle does
// not refer to a file object, or if the caller does not have the required
// access to the file, then it will fail.
status = ObReferenceObjectByHandle(FileHandle,
SetEa ? FILE_WRITE_EA : FILE_WRITE_DATA,
IoFileObjectType,
requestorMode,
(PVOID *)&fileObject,
NULL);
if (!NT_SUCCESS(status)) {
return status;
}
// Make a special check here to determine whether this is a synchronous
// I/O operation. If it is, then wait here until the file is owned by
// the current thread. If this is not a (serialized) synchronous I/O
// operation, then allocate and initialize the local event.
if (fileObject->Flags & FO_SYNCHRONOUS_IO) {
BOOLEAN interrupted;
if (!IopAcquireFastLock(fileObject)) {
status = IopAcquireFileObjectLock(fileObject,
requestorMode,
(BOOLEAN)((fileObject->Flags & FO_ALERTABLE_IO) != 0),
&interrupted);
if (interrupted) {
ObDereferenceObject(fileObject);
return status;
}
}
synchronousIo = TRUE;
} else {
// This is a synchronous API being invoked for a file that is opened
// for asynchronous I/O. This means that this system service is
// to synchronize the completion of the operation before returning
// to the caller. A local event is used to do this.
event = ExAllocatePool(NonPagedPool, sizeof(KEVENT));
if (!event) {
ObDereferenceObject(fileObject);
return STATUS_INSUFFICIENT_RESOURCES;
}
KeInitializeEvent(event, SynchronizationEvent, FALSE);
synchronousIo = FALSE;
}
// Set the file object to the Not-Signaled state.
KeClearEvent(&fileObject->Event);
// Get the address of the target device object.
deviceObject = IoGetRelatedDeviceObject(fileObject);
// Allocate and initialize the I/O Request Packet (IRP) for this operation.
// The allocation is performed with an exception handler in case the
// caller does not have enough quota to allocate the packet.
irp = IoAllocateIrp(deviceObject->StackSize, TRUE);
if (!irp) {
// An IRP could not be allocated. Cleanup and return an appropriate
// error status code.
if (!(fileObject->Flags & FO_SYNCHRONOUS_IO)) {
ExFreePool(event);
}
IopAllocateIrpCleanup(fileObject, (PKEVENT)NULL);
return STATUS_INSUFFICIENT_RESOURCES;
}
irp->Tail.Overlay.OriginalFileObject = fileObject;
irp->Tail.Overlay.Thread = PsGetCurrentThread();
irp->RequestorMode = requestorMode;
// Fill in the service independent parameters in the IRP.
if (synchronousIo) {
irp->UserEvent = (PKEVENT)NULL;
irp->UserIosb = IoStatusBlock;
} else {
irp->UserEvent = event;
irp->UserIosb = &localIoStatus;
irp->Flags = IRP_SYNCHRONOUS_API;
}
irp->Overlay.AsynchronousParameters.UserApcRoutine = (PIO_APC_ROUTINE)NULL;
// Get a pointer to the stack location for the first driver. This will be
// used to pass the original function codes and parameters.
irpSp = IoGetNextIrpStackLocation(irp);
irpSp->MajorFunction = SetEa ? IRP_MJ_SET_EA : IRP_MJ_SET_QUOTA;
irpSp->FileObject = fileObject;
// Now determine whether this driver expects to have data buffered to it
// or whether it performs direct I/O. This is based on the DO_BUFFERED_IO
// flag in the device object. if the flag is set, then a system buffer is
// allocated and driver's data is copied to it. If the DO_DIRECT_IO flag
// is set in the device object, then a Memory Descriptor List (MDL) is
// allocated and the caller's buffer is locked down using it. Finally, if
// the driver specifies neither of the flags, then simply pass the address
// and length of the buffer and allow the driver to perform all of the
// checking and buffering if any is required.
if (deviceObject->Flags & DO_BUFFERED_IO) {
PVOID systemBuffer;
ULONG errorOffset;
// The driver wishes the caller's buffer to be copied into an
// intermediary buffer. Allocate the system buffer and specify
// that it should be deallocated on completion. Also check to
// ensure that the caller's EA list or quota list is valid. All
// of this is performed within an exception handler that will perform
// cleanup if the operation fails.
try {
// Allocate the intermediary system buffer and charge the caller
// quota for its allocation. Copy the caller's buffer into the
// system buffer and check to ensure that it is valid.
systemBuffer = ExAllocatePoolWithQuota(NonPagedPool, Length);
irp->AssociatedIrp.SystemBuffer = systemBuffer;
RtlCopyMemory(systemBuffer, Buffer, Length);
if (SetEa) {
status = IoCheckEaBufferValidity(systemBuffer,
Length,
&errorOffset);
} else {
status = IoCheckQuotaBufferValidity(systemBuffer,
Length,
&errorOffset);
}
if (!NT_SUCCESS(status)) {
IoStatusBlock->Status = status;
IoStatusBlock->Information = errorOffset;
ExRaiseStatus(status);
}
} except(EXCEPTION_EXECUTE_HANDLER)
{
// An exception was incurred while allocating the buffer, copying
// the caller's data into it, or walking the buffer. Determine
// what happened, cleanup, and return an appropriate error status
// code.
IopExceptionCleanup(fileObject,
irp,
(PKEVENT)NULL,
event);
return GetExceptionCode();
}
// Set the flags so that the completion code knows to deallocate the
// buffer.
irp->Flags |= IRP_BUFFERED_IO | IRP_DEALLOCATE_BUFFER;
} else if (deviceObject->Flags & DO_DIRECT_IO) {
PMDL mdl;
// This is a direct I/O operation. Allocate an MDL and invoke the
// memory management routine to lock the buffer into memory. This is
// done using an exception handler that will perform cleanup if the
// operation fails.
mdl = (PMDL)NULL;
try {
// Allocate an MDL, charging quota for it, and hang it off of the
// IRP. Probe and lock the pages associated with the caller's
// buffer for read access and fill in the MDL with the PFNs of those
// pages.
mdl = IoAllocateMdl(Buffer, Length, FALSE, TRUE, irp);
if (!mdl) {
ExRaiseStatus(STATUS_INSUFFICIENT_RESOURCES);
}
MmProbeAndLockPages(mdl, requestorMode, IoReadAccess);
} except(EXCEPTION_EXECUTE_HANDLER)
{
// An exception was incurred while either probing the caller's
// buffer or allocating the MDL. Determine what actually happened,
// clean everything up, and return an appropriate error status code.
IopExceptionCleanup(fileObject,
irp,
(PKEVENT)NULL,
event);
return GetExceptionCode();
}
} else {
// Pass the address of the user's buffer so the driver has access to
// it. It is now the driver's responsibility to do everything.
irp->UserBuffer = Buffer;
}
// Copy the caller's parameters to the service-specific portion of the
// IRP.
if (SetEa) {
irpSp->Parameters.SetEa.Length = Length;
} else {
irpSp->Parameters.SetQuota.Length = Length;
}
// Queue the packet, call the driver, and synchronize appropriately with
// I/O completion.
status = IopSynchronousServiceTail(deviceObject,
irp,
fileObject,
FALSE,
requestorMode,
synchronousIo,
OtherTransfer);
// If the file for this operation was not opened for synchronous I/O, then
// synchronization of completion of the I/O operation has not yet occurred
// since the allocated event must be used for synchronous APIs on files
// opened for asynchronous I/O. Synchronize the completion of the I/O
// operation now.
if (!synchronousIo) {
status = IopSynchronousApiServiceTail(status,
event,
irp,
requestorMode,
&localIoStatus,
IoStatusBlock);
}
return status;
}
NTSTATUS
IopSetRemoteLink(
IN PFILE_OBJECT FileObject,
IN PFILE_OBJECT DestinationFileObject OPTIONAL,
IN PFILE_TRACKING_INFORMATION FileInformation OPTIONAL
)
/*++
Routine Description:
This routine is invoked to remote an NtSetInformationFile API call via an
FSCTL to the Redirector. The call will cause the remote system to perform
the service call to track the link for a file which was just moved.
Arguments:
FileObject - Supplies the file object for the file that was moved.
DestinationFileObject - Optionally supplies the file object for the new
destination location for the file.
FileInformation - Optionally supplies the volume and file object IDs of
the target file.
Return Value:
The final function value is the final completion status of the operation.
--*/
{
REMOTE_LINK_BUFFER remoteBuffer;
IO_STATUS_BLOCK ioStatus;
NTSTATUS status;
PIRP irp;
KEVENT event;
PIO_STACK_LOCATION irpSp;
PDEVICE_OBJECT deviceObject;
ULONG length = 0;
PAGED_CODE();
// Initialize the event structure to synchronize completion of the I/O
// request.
KeInitializeEvent(&event,
NotificationEvent,
FALSE);
// Build an I/O Request Packet to be sent to the file system driver to get
// the volume ID.
deviceObject = IoGetRelatedDeviceObject(FileObject);
irp = IoBuildDeviceIoControlRequest(FSCTL_LMR_SET_LINK_TRACKING_INFORMATION,
deviceObject,
NULL,
0,
NULL,
0,
FALSE,
&event,
&ioStatus);
if (!irp) {
return STATUS_INSUFFICIENT_RESOURCES;
}
// Initialize the remote link buffer according to the input information.
if (DestinationFileObject) {
// The FileObject and DestinationFileObject are on the same machine
remoteBuffer.TrackingInformation.TargetFileObject = DestinationFileObject;
if (FileInformation) {
// Copy the ObjectInformation from the FileInformation buffer into
// the TargetLinkTrackingInformationBuffer. Set 'length' to include
// this buffer.
remoteBuffer.TrackingInformation.TargetLinkTrackingInformationLength
= length = FileInformation->ObjectInformationLength;
RtlCopyMemory(&remoteBuffer.TrackingInformation.TargetLinkTrackingInformationBuffer,
FileInformation->ObjectInformation,
length);
} else {
// We don't have any extra FileInformation.
remoteBuffer.TrackingInformation.TargetLinkTrackingInformationLength = 0;
}
// Increment the length to include the size of the non-optional fields in
// REMOTE_LINK_TRACKING_INFORMATION.
length += sizeof(PFILE_OBJECT) + sizeof(ULONG);
} else {
// There's no DestinationFileObject, so all the necessary information is in the
// FileInformation structure.
length = FileInformation->ObjectInformationLength + sizeof(HANDLE) + sizeof(ULONG);
RtlCopyMemory(&remoteBuffer.TrackingInformation,
FileInformation,
length);
remoteBuffer.TrackingInformation.TargetFileObject = NULL;
}
// Fill in the remainder of the IRP to retrieve the object ID for the
// file.
irp->Flags |= IRP_SYNCHRONOUS_API;
irp->AssociatedIrp.SystemBuffer = &remoteBuffer;
irp->Tail.Overlay.OriginalFileObject = FileObject;
irpSp = IoGetNextIrpStackLocation(irp);
irpSp->FileObject = FileObject;
irpSp->MajorFunction = IRP_MJ_FILE_SYSTEM_CONTROL;
irpSp->MinorFunction = IRP_MN_KERNEL_CALL;
irpSp->Parameters.FileSystemControl.InputBufferLength = length;
// Take out another reference to the file object to guarantee that it does
// not get deleted.
ObReferenceObject(FileObject);
// Call the driver to get the request.
status = IoCallDriver(deviceObject, irp);
// Synchronize completion of the request.
if (status == STATUS_PENDING) {
status = KeWaitForSingleObject(&event,
Executive,
KernelMode,
FALSE,
(PLARGE_INTEGER)NULL);
status = ioStatus.Status;
}
return status;
}
VOID
IopStartApcHardError(
IN PVOID StartContext
)
/*++
Routine Description:
This function is invoked in an ExWorker thread when we need to do a
hard error pop-up, but the Irp's originating thread is at APC level,
ie. IoPageRead. It starts a thread to hold the pop-up.
Arguments:
StartContext - Startup context, contains a IOP_APC_HARD_ERROR_PACKET.
Return Value:
None.
--*/
{
HANDLE thread;
NTSTATUS status;
// Create the hard error pop-up thread. If for whatever reason we
// can't do this then just complete the Irp with the error.
status = PsCreateSystemThread(&thread,
0,
(POBJECT_ATTRIBUTES)NULL,
(HANDLE)0,
(PCLIENT_ID)NULL,
IopApcHardError,
StartContext);
if (!NT_SUCCESS(status)) {
IoCompleteRequest(((PIOP_APC_HARD_ERROR_PACKET)StartContext)->Irp,
IO_DISK_INCREMENT);
ExFreePool(StartContext);
return;
}
// Close thread handle
ZwClose(thread);
}
NTSTATUS
IopSynchronousApiServiceTail(
IN NTSTATUS ReturnedStatus,
IN PKEVENT Event,
IN PIRP Irp,
IN KPROCESSOR_MODE RequestorMode,
IN PIO_STATUS_BLOCK LocalIoStatus,
OUT PIO_STATUS_BLOCK IoStatusBlock
)
/*++
Routine Description:
This routine is invoked when a synchronous API is invoked for a file
that has been opened for asynchronous I/O. This function synchronizes
the completion of the I/O operation on the file.
Arguments:
ReturnedStatus - Supplies the status that was returned from the call to
IoCallDriver.
Event - Address of the allocated kernel event to be used for synchronization
of the I/O operation.
Irp - Address of the I/O Request Packet submitted to the driver.
RequestorMode - Processor mode of the caller when the operation was
requested.
LocalIoStatus - Address of the I/O status block used to capture the final
status by the service itself.
IoStatusBlock - Address of the I/O status block supplied by the caller of
the system service.
Return Value:
The function value is the final status of the operation.
--*/
{
NTSTATUS status;
PAGED_CODE();
// This is a normal synchronous I/O operation, as opposed to a
// serialized synchronous I/O operation. For this case, wait for
// the local event and copy the final status information back to
// the caller.
status = ReturnedStatus;
if (status == STATUS_PENDING) {
status = KeWaitForSingleObject(Event,
Executive,
RequestorMode,
FALSE,
(PLARGE_INTEGER)NULL);
if (status == STATUS_ALERTED || status == STATUS_USER_APC) {
// The wait request has ended either because the thread was
// alerted or an APC was queued to this thread, because of
// thread rundown or CTRL/C processing. In either case, try
// to bail out of this I/O request carefully so that the IRP
// completes before this routine exists or the event will not
// be around to set to the Signaled state.
IopCancelAlertedRequest(Event, Irp);
}
status = LocalIoStatus->Status;
}
try {
*IoStatusBlock = *LocalIoStatus;
} except(EXCEPTION_EXECUTE_HANDLER)
{
// An exception occurred attempting to write the caller's I/O
// status block. Simply change the final status of the operation
// to the exception code.
status = GetExceptionCode();
}
ExFreePool(Event);
return status;
}
NTSTATUS
IopSynchronousServiceTail(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PFILE_OBJECT FileObject,
IN BOOLEAN DeferredIoCompletion,
IN KPROCESSOR_MODE RequestorMode,
IN BOOLEAN SynchronousIo,
IN TRANSFER_TYPE TransferType
)
/*++
Routine Description:
This routine is invoked to complete the operation of a system service.
It queues the IRP to the thread's queue, updates the transfer count,
calls the driver, and finally synchronizes completion of the I/O.
Arguments:
DeviceObject - Device on which the I/O is to occur.
Irp - I/O Request Packet representing the I/O operation.
FileObject - File object for this open instantiation.
DeferredIoCompletion - Indicates whether deferred completion is possible.
RequestorMode - Mode in which request was made.
SynchronousIo - Indicates whether the operation is to be synchronous.
TransferType - Type of transfer being performed: read, write, or other.
Return Value:
The function value is the final status of the operation.
--*/
{
NTSTATUS status;
PAGED_CODE();
// Insert the packet at the head of the IRP list for the thread.
IopQueueThreadIrp(Irp);
// Update the operation count statistic for the current process.
switch (TransferType) {
case ReadTransfer:
IopUpdateReadOperationCount();
break;
case WriteTransfer:
IopUpdateWriteOperationCount();
break;
case OtherTransfer:
IopUpdateOtherOperationCount();
break;
}
// Now simply invoke the driver at its dispatch entry with the IRP.
status = IoCallDriver(DeviceObject, Irp);
// If deferred I/O completion is possible, check for pending returned
// from the driver. If the driver did not return pending, then the
// packet has not actually been completed yet, so complete it here.
if (DeferredIoCompletion) {
if (status != STATUS_PENDING) {
// The I/O operation was completed without returning a status of
// pending. This means that at this point, the IRP has not been
// fully completed. Complete it now.
PKNORMAL_ROUTINE normalRoutine;
PVOID normalContext;
KIRQL irql;
ASSERT(!Irp->PendingReturned);
KeRaiseIrql(APC_LEVEL, &irql);
IopCompleteRequest(&Irp->Tail.Apc,
&normalRoutine,
&normalContext,
(PVOID *)&FileObject,
&normalContext);
KeLowerIrql(irql);
}
}
// If this operation was a synchronous I/O operation, check the return
// status to determine whether or not to wait on the file object. If
// the file object is to be waited on, wait for the operation to complete
// and obtain the final status from the file object itself.
if (SynchronousIo) {
if (status == STATUS_PENDING) {
status = KeWaitForSingleObject(&FileObject->Event,
Executive,
RequestorMode,
(BOOLEAN)((FileObject->Flags & FO_ALERTABLE_IO) != 0),
(PLARGE_INTEGER)NULL);
if (status == STATUS_ALERTED || status == STATUS_USER_APC) {
// The wait request has ended either because the thread was alerted
// or an APC was queued to this thread, because of thread rundown or
// CTRL/C processing. In either case, try to bail out of this I/O
// request carefully so that the IRP completes before this routine
// exists so that synchronization with the file object will remain
// intact.
IopCancelAlertedRequest(&FileObject->Event, Irp);
}
status = FileObject->FinalStatus;
}
IopReleaseFileObjectLock(FileObject);
}
return status;
}
VOID
IopTimerDispatch(
IN PKDPC Dpc,
IN PVOID DeferredContext,
IN PVOID SystemArgument1,
IN PVOID SystemArgument2
)
/*++
Routine Description:
This routine scans the I/O system timer database and invokes each driver
that has enabled a timer in the list, once every second.
Arguments:
Dpc - Supplies a pointer to a control object of type DPC.
DeferredContext - Optional deferred context; not used.
SystemArgument1 - Optional argument 1; not used.
SystemArgument2 - Optional argument 2; not used.
Return Value:
None.
--*/
{
PLIST_ENTRY timerEntry;
PIO_TIMER timer;
LARGE_INTEGER deltaTime;
KIRQL irql;
ULONG i;
UNREFERENCED_PARAMETER(Dpc);
UNREFERENCED_PARAMETER(DeferredContext);
UNREFERENCED_PARAMETER(SystemArgument1);
UNREFERENCED_PARAMETER(SystemArgument2);
// Check to see whether or not there are any timers in the queue that
// have been enabled. If so, then walk the list and invoke all of the
// drivers' routines. Note that if the counter changes, which it can
// because the spin lock is not owned, then a timer routine may be
// missed. However, this is acceptable, since the driver inserting the
// entry could be context switched away from, etc. Therefore, this is
// not a critical resource for the most part.
if (IopTimerCount) {
// There is at least one timer entry in the queue that is enabled.
// Walk the queue and invoke each specified timer routine.
ExAcquireSpinLock(&IopTimerLock, &irql);
i = IopTimerCount;
timerEntry = IopTimerQueueHead.Flink;
// For each entry found that is enabled, invoke the driver's routine
// with its specified context parameter. The local count is used
// to abort the queue traversal when there are more entries in the
// queue, but they are not enabled.
for (timerEntry = IopTimerQueueHead.Flink;
(timerEntry != &IopTimerQueueHead) && i;
timerEntry = timerEntry->Flink) {
timer = CONTAINING_RECORD(timerEntry, IO_TIMER, TimerList);
if (timer->TimerFlag) {
timer->TimerRoutine(timer->DeviceObject, timer->Context);
i--;
}
}
ExReleaseSpinLock(&IopTimerLock, irql);
}
}
NTSTATUS
IopTrackLink(
IN PFILE_OBJECT FileObject,
IN OUT PIO_STATUS_BLOCK IoStatusBlock,
IN PFILE_TRACKING_INFORMATION FileInformation,
IN ULONG Length,
IN PKEVENT Event,
IN KPROCESSOR_MODE RequestorMode
)
/*++
Routine Description:
This routine is invoked to track a link. It tracks the source file's Object
ID to the target file so that links to the source will follow to the new
location of the target.
Arguments:
FileObject - Supplies a pointer to the referenced source file object.
IoStatusBlock - Pointer to the caller's I/O status block.
FileInformation - A buffer containing the parameters for the move that was
performed.
Length - Specifies the length of the FileInformation buffer.
Event - An event to be set to the Signaled state once the operation has been
performed, provided it was successful.
RequestorMode - Requestor mode of the caller.
N.B. - Note that the presence of an event indicates that the source file was
opened for asynchronous I/O, otherwise it was opened for synchronous I/O.
Return Value:
The status returned is the final completion status of the operation.
--*/
{
PFILE_TRACKING_INFORMATION trackingInfo = NULL;
PFILE_OBJECT dstFileObject = NULL;
FILE_VOLUMEID_WITH_TYPE SourceVolumeId;
FILE_OBJECTID_BUFFER SourceObjectId;
FILE_OBJECTID_BUFFER NormalizedObjectId;
FILE_OBJECTID_BUFFER CrossVolumeObjectId;
FILE_VOLUMEID_WITH_TYPE TargetVolumeId;
FILE_OBJECTID_BUFFER TargetObjectId;
TRACKING_BUFFER trackingBuffer;
NTSTATUS status;
PAGED_CODE();
// Begin by capturing the caller's buffer, if required.
if (RequestorMode != KernelMode) {
try {
trackingInfo = ExAllocatePoolWithQuota(PagedPool,
Length);
RtlCopyMemory(trackingInfo, FileInformation, Length);
if (!trackingInfo->DestinationFile ||
((Length - FIELD_OFFSET(FILE_TRACKING_INFORMATION, ObjectInformation))
< trackingInfo->ObjectInformationLength)) {
ExFreePool(trackingInfo);
return STATUS_INVALID_PARAMETER;
}
} except(EXCEPTION_EXECUTE_HANDLER)
{
// An exception was incurred while allocating the intermediary
// system buffer or while copying the caller's data into the
// buffer. Cleanup and return an appropriate error status code.
if (trackingInfo) {
ExFreePool(trackingInfo);
}
return GetExceptionCode();
}
} else {
trackingInfo = FileInformation;
}
// If a destination file handle was specified, convert it to a pointer to
// a file object.
if (trackingInfo->DestinationFile) {
status = ObReferenceObjectByHandle(trackingInfo->DestinationFile,
FILE_WRITE_DATA,
IoFileObjectType,
RequestorMode,
(PVOID *)&dstFileObject,
NULL);
if (!NT_SUCCESS(status)) {
if (RequestorMode != KernelMode) {
ExFreePool(trackingInfo);
}
return status;
}
}
try {
// Determine whether this is a local or a remote link tracking
// operation.
if (IsFileLocal(FileObject)) {
// The source file, i.e., the one being moved, is a file local to
// this system. Determine the form of the target file and track
// it accordingly.
if (trackingInfo->DestinationFile) {
if (IsFileLocal(dstFileObject)) {
BOOLEAN IdSetOnTarget = FALSE;
// The target file is specified as a handle and it is local.
// Simply perform the query and set locally. Note that if
// the source file does not have an object ID, then no
// tracking will be performed, but it will appear as if the
// operation worked.
status = IopGetSetObjectId(FileObject,
&SourceObjectId,
sizeof(SourceObjectId),
FSCTL_GET_OBJECT_ID);
if (status == STATUS_OBJECT_NAME_NOT_FOUND) {
return(STATUS_SUCCESS);
}
if (!NT_SUCCESS(status)) {
return status;
}
// If the extended info field is zero then this file
// has no interesting tracking information.
if (RtlCompareMemoryUlong(SourceObjectId.BirthObjectId,
sizeof(SourceObjectId.BirthObjectId),
0) == sizeof(SourceObjectId.BirthObjectId)) {
return (STATUS_SUCCESS);
}
// Get the volume ID of the source and destination
status = IopGetVolumeId(dstFileObject,
&TargetVolumeId,
sizeof(TargetVolumeId));
if (!NT_SUCCESS(status)) {
return status;
}
status = IopGetVolumeId(FileObject,
&SourceVolumeId,
sizeof(SourceVolumeId));
if (!NT_SUCCESS(status)) {
return status;
}
// Delete the ID from the source now, since the
// target may be on the same volume. If there's a
// subsequent error, we'll try to restore it.
status = IopGetSetObjectId(FileObject,
NULL,
0,
FSCTL_DELETE_OBJECT_ID);
if (!NT_SUCCESS(status)) {
return status;
}
// Set the ID on the target. If it's a cross-volume
// move, set the bit that indicates same.
CrossVolumeObjectId = TargetObjectId = SourceObjectId;
if (!RtlEqualMemory(&TargetVolumeId.VolumeId[0],
&SourceVolumeId.VolumeId[0],
sizeof(SourceVolumeId.VolumeId))) {
CrossVolumeObjectId.BirthVolumeId[0] |= 1;
}
status = IopGetSetObjectId(dstFileObject,
&CrossVolumeObjectId,
sizeof(CrossVolumeObjectId),
FSCTL_SET_OBJECT_ID);
if (status == STATUS_DUPLICATE_NAME ||
status == STATUS_OBJECT_NAME_COLLISION) {
// This object ID is already in use on the target volume,
// or the dest file already has an object ID.
// Get the file's ID (or have NTFS generate a new one).
status = IopGetSetObjectId(dstFileObject,
&TargetObjectId,
sizeof(TargetObjectId),
FSCTL_CREATE_OR_GET_OBJECT_ID);
if (NT_SUCCESS(status)) {
// Write the birth ID
status = IopGetSetObjectId(dstFileObject,
&CrossVolumeObjectId.ExtendedInfo[0],
sizeof(CrossVolumeObjectId.ExtendedInfo),
FSCTL_SET_OBJECT_ID_EXTENDED);
}
}
if (NT_SUCCESS(status)) {
IdSetOnTarget = TRUE;
// If this was a cross-volume move, notify the tracking service.
if (!RtlEqualMemory(&TargetVolumeId.VolumeId[0],
&SourceVolumeId.VolumeId[0],
sizeof(SourceVolumeId.VolumeId))) {
IopMarshalIds(&trackingBuffer, &TargetVolumeId, &TargetObjectId, trackingInfo);
// Bit 0 must be reset before notifying tracking service
NormalizedObjectId = SourceObjectId;
NormalizedObjectId.BirthVolumeId[0] &= 0xfe;
status = IopSendMessageToTrackService(&SourceVolumeId,
&NormalizedObjectId,
&trackingBuffer.TrackingInformation);
}
}
// If there was an error after the ObjectID was deleted
// from the source. Try to restore it before returning.
if (!NT_SUCCESS(status)) {
NTSTATUS statusT = STATUS_SUCCESS;
if (IdSetOnTarget) {
if (RtlEqualMemory(&TargetObjectId.ObjectId,
&SourceObjectId.ObjectId,
sizeof(TargetObjectId.ObjectId))) {
// This ID was set with FSCTL_SET_OBJECT_ID
statusT = IopGetSetObjectId(dstFileObject,
NULL,
0,
FSCTL_DELETE_OBJECT_ID);
} else {
// Restore the target's extended data.
statusT = IopGetSetObjectId(dstFileObject,
&TargetObjectId.ExtendedInfo[0],
sizeof(TargetObjectId.ExtendedInfo),
FSCTL_SET_OBJECT_ID_EXTENDED);
}
}
if (NT_SUCCESS(statusT)) {
IopGetSetObjectId(FileObject,
&SourceObjectId,
sizeof(SourceObjectId),
FSCTL_SET_OBJECT_ID);
}
return status;
}
} else { // if (IsFileLocal( dstFileObject ))
// The source file is local, but the destination file object
// is remote. For this case query the target file's object
// ID and notify the link tracking system that the file has
// been moved across systems.
// Begin by ensuring that the source file has an object ID
// already. If not, then just make it appear as if the
// operation worked.
status = IopGetSetObjectId(FileObject,
&SourceObjectId,
sizeof(SourceObjectId),
FSCTL_GET_OBJECT_ID);
if (!NT_SUCCESS(status)) {
return STATUS_SUCCESS;
}
// If the extended info field is zero then this file
// has no interesting tracking information.
if (RtlCompareMemoryUlong(&SourceObjectId.BirthObjectId,
sizeof(SourceObjectId.BirthObjectId),
0) == sizeof(SourceObjectId.BirthObjectId)) {
return (STATUS_SUCCESS);
}
// Query the volume ID of the target.
status = IopGetSetObjectId(dstFileObject,
&TargetVolumeId,
sizeof(FILE_VOLUMEID_WITH_TYPE),
FSCTL_LMR_GET_LINK_TRACKING_INFORMATION);
if (!NT_SUCCESS(status)) {
return status;
}
// Query the object ID of the target.
status = IopGetSetObjectId(dstFileObject,
&TargetObjectId,
sizeof(TargetObjectId),
FSCTL_CREATE_OR_GET_OBJECT_ID);
if (!NT_SUCCESS(status)) {
return status;
}
// Notify the tracking system of the move.
IopMarshalIds(&trackingBuffer, &TargetVolumeId, &TargetObjectId, trackingInfo);
status = IopTrackLink(FileObject,
IoStatusBlock,
&trackingBuffer.TrackingInformation,
FIELD_OFFSET(FILE_TRACKING_INFORMATION,
ObjectInformation) +
trackingBuffer.TrackingInformation.ObjectInformationLength,
Event,
KernelMode);
if (!NT_SUCCESS(status)) {
return status;
}
// Delete the ID from the source
status = IopGetSetObjectId(FileObject,
NULL,
0,
FSCTL_DELETE_OBJECT_ID);
if (!NT_SUCCESS(status)) {
return status;
}
// Set the Birth ID on the target, turning on the bit
// that indicates that this file has been involved in a cross-
// volume move.
CrossVolumeObjectId = SourceObjectId;
CrossVolumeObjectId.BirthVolumeId[0] |= 1;
status = IopGetSetObjectId(dstFileObject,
&CrossVolumeObjectId.ExtendedInfo[0],
sizeof(CrossVolumeObjectId.ExtendedInfo),
FSCTL_SET_OBJECT_ID_EXTENDED);
if (!NT_SUCCESS(status)) {
// Try to restore the source
IopGetSetObjectId(FileObject,
&SourceObjectId,
sizeof(SourceObjectId),
FSCTL_SET_OBJECT_ID);
return status;
}
} // if (IsFileLocal( dstFileObject ))
} else { // if (trackingInfo->DestinationFile)
// A destination file handle was not specified. Simply query
// the source file's object ID and call the link tracking code.
// Note that the function input buffer contains the volume ID
// and file object ID of the target. Note also that it is
// assumed that the source file has an object ID.
status = IopGetVolumeId(FileObject,
&SourceVolumeId,
sizeof(SourceVolumeId));
if (!NT_SUCCESS(status)) {
return status;
}
status = IopGetSetObjectId(FileObject,
&SourceObjectId,
sizeof(SourceObjectId),
FSCTL_GET_OBJECT_ID);
if (!NT_SUCCESS(status)) {
return status;
}
// If the extended info field is zero then this file
// has no interesting tracking information.
if (RtlCompareMemoryUlong(SourceObjectId.BirthObjectId,
sizeof(SourceObjectId.BirthObjectId),
0) == sizeof(SourceObjectId.BirthObjectId)) {
return (STATUS_SUCCESS);
}
// Inform the user-mode link tracking service that the file
// has been moved.
NormalizedObjectId = SourceObjectId;
NormalizedObjectId.BirthVolumeId[0] &= 0xfe;
status = IopSendMessageToTrackService(&SourceVolumeId,
&NormalizedObjectId,
FileInformation);
if (!NT_SUCCESS(status)) {
return status;
}
} // if (trackingInfo->DestinationFile) ... else
} else { // if (IsFileLocal( FileObject ))
// The source file is remote. For this case, remote the operation
// to the system on which the source file is located. Begin by
// ensuring that the source file actually has an object ID. If
// not, then get out now since there is nothing to be done.
status = IopGetSetObjectId(FileObject,
&SourceObjectId,
sizeof(SourceObjectId),
FSCTL_GET_OBJECT_ID);
if (status == STATUS_OBJECT_NAME_NOT_FOUND) {
return STATUS_SUCCESS;
}
if (!NT_SUCCESS(status)) {
return status;
}
// If the extended info field is zero then this file
// has no interesting tracking information.
if (RtlCompareMemoryUlong(SourceObjectId.BirthObjectId,
sizeof(SourceObjectId.BirthObjectId),
0) == sizeof(SourceObjectId.BirthObjectId)) {
return (STATUS_SUCCESS);
}
if (trackingInfo->DestinationFile) {
// A handle was specified for the destination file. Determine
// whether it is local or remote. If remote and both handles
// refer to the same machine, then ship the entire API to that
// machine and have it perform the operation.
// Otherwise, query the target file's object ID, and then redo
// the operation. This will cause the API to be remoted to the
// machine where the source file resides.
if (IsFileLocal(dstFileObject)) {
// The source is remote and the destination is local, so
// query the object ID of the target and recursively track
// the link from the source file's remote node.
status = IopGetVolumeId(dstFileObject,
&TargetVolumeId,
sizeof(TargetVolumeId));
if (!NT_SUCCESS(status)) {
return status;
}
status = IopGetSetObjectId(dstFileObject,
&TargetObjectId,
sizeof(TargetObjectId),
FSCTL_CREATE_OR_GET_OBJECT_ID);
if (!NT_SUCCESS(status)) {
return status;
}
// Notify the tracking system of the move.
IopMarshalIds(&trackingBuffer, &TargetVolumeId, &TargetObjectId, trackingInfo);
status = IopTrackLink(FileObject,
IoStatusBlock,
&trackingBuffer.TrackingInformation,
FIELD_OFFSET(FILE_TRACKING_INFORMATION,
ObjectInformation) +
trackingBuffer.TrackingInformation.ObjectInformationLength,
Event,
KernelMode);
if (!NT_SUCCESS(status)) {
return status;
}
// Delete the ID from the source
status = IopGetSetObjectId(FileObject,
NULL,
0,
FSCTL_DELETE_OBJECT_ID);
if (!NT_SUCCESS(status)) {
return status;
}
// Set the birth ID on the target, also turning on the bit
// that indicates that this file has moved across volumes.
CrossVolumeObjectId = SourceObjectId;
CrossVolumeObjectId.BirthVolumeId[0] |= 1;
status = IopGetSetObjectId(dstFileObject,
&CrossVolumeObjectId.ExtendedInfo[0],
sizeof(CrossVolumeObjectId.ExtendedInfo),
FSCTL_SET_OBJECT_ID_EXTENDED);
if (!NT_SUCCESS(status)) {
IopGetSetObjectId(FileObject,
&SourceObjectId,
sizeof(SourceObjectId),
FSCTL_SET_OBJECT_ID);
return status;
}
} // if (IsFileLocal( dstFileObject ))
else if (!IopIsSameMachine(FileObject, trackingInfo->DestinationFile)) {
// The source and the target are remote from each other and from
// this machine. Query the object ID of the target and recursively
// track the link from the source file's remote node.
// Query the volume ID of the target.
status = IopGetSetObjectId(dstFileObject,
&TargetVolumeId,
sizeof(FILE_VOLUMEID_WITH_TYPE),
FSCTL_LMR_GET_LINK_TRACKING_INFORMATION);
if (!NT_SUCCESS(status)) {
return status;
}
// Query the object ID of the target.
status = IopGetSetObjectId(dstFileObject,
&TargetObjectId,
sizeof(TargetObjectId),
FSCTL_CREATE_OR_GET_OBJECT_ID);
if (!NT_SUCCESS(status)) {
return status;
}
// Notify the tracking system of the move.
IopMarshalIds(&trackingBuffer, &TargetVolumeId, &TargetObjectId, trackingInfo);
status = IopTrackLink(FileObject,
IoStatusBlock,
&trackingBuffer.TrackingInformation,
FIELD_OFFSET(FILE_TRACKING_INFORMATION,
ObjectInformation) +
trackingBuffer.TrackingInformation.ObjectInformationLength,
Event,
KernelMode);
if (!NT_SUCCESS(status)) {
return status;
}
// Set the birth ID on the target, turning on the bit that indicates
// that this file has moved across volumes.
CrossVolumeObjectId = SourceObjectId;
CrossVolumeObjectId.BirthVolumeId[0] |= 1;
status = IopGetSetObjectId(dstFileObject,
&CrossVolumeObjectId.ExtendedInfo[0],
sizeof(CrossVolumeObjectId.ExtendedInfo),
FSCTL_SET_OBJECT_ID_EXTENDED);
if (!NT_SUCCESS(status)) {
IopGetSetObjectId(FileObject,
&SourceObjectId,
sizeof(SourceObjectId),
FSCTL_SET_OBJECT_ID);
return status;
}
} else { // else if (!IopIsSameMachine( FileObject, trackingInfo->DestinationFile))
// Both the source and the target are remote and they're
// both on the same remote machine. For this case, remote
// the entire API using the file object pointers.
status = IopSetRemoteLink(FileObject, dstFileObject, trackingInfo);
} // else if (!IopIsSameMachine( FileObject, trackingInfo->DestinationFile)) ... else
} else { // if (trackingInfo->DestinationFile)
// The source file is remote and the object ID of the target is
// contained w/in the tracking buffer. Simply remote the API
// to the remote machine using the source file object pointer
// and the object ID of the target in the buffer.
status = IopSetRemoteLink(FileObject, NULL, FileInformation);
} // if (trackingInfo->DestinationFile) ... else
} // if (IsFileLocal( FileObject )) ... else
} finally{
// Ensure that everything has been cleaned up.
if (RequestorMode != KernelMode && trackingInfo) {
ExFreePool(trackingInfo);
}
if (dstFileObject) {
ObDereferenceObject(dstFileObject);
}
KeSetEvent(Event, 0, FALSE);
}
return status;
}
VOID
IopUserCompletion(
IN PKAPC Apc,
IN PKNORMAL_ROUTINE *NormalRoutine,
IN PVOID *NormalContext,
IN PVOID *SystemArgument1,
IN PVOID *SystemArgument2
)
/*++
Routine Description:
This routine is invoked in the final processing of an IRP. Everything has
been completed except that the caller's APC routine must be invoked. The
system will do this as soon as this routine exits. The only processing
remaining to be completed by the I/O system is to free the I/O Request
Packet itself.
Arguments:
Apc - Supplies a pointer to kernel APC structure.
NormalRoutine - Supplies a pointer to a pointer to the normal function
that was specified when the APC was initialied.
NormalContext - Supplies a pointer to a pointer to an arbitrary data
structure that was specified when the APC was initialized.
SystemArgument1, SystemArgument2 - Supplies a set of two pointers to
two arguments that contain untyped data.
Return Value:
None.
Note:
If no other processing is ever needed, and the APC can be placed at the
beginning of the IRP, then this routine could be replaced by simply
specifying the address of the pool deallocation routine in the APC instead
of the address of this routine.
Caution:
This routine is also invoked as a general purpose rundown routine for APCs.
Should this code ever need to directly access any of the other parameters
other than Apc, this routine will need to be split into two separate
routines. The rundown routine should perform exactly the following code's
functionality.
--*/
{
UNREFERENCED_PARAMETER(NormalRoutine);
UNREFERENCED_PARAMETER(NormalContext);
UNREFERENCED_PARAMETER(SystemArgument1);
UNREFERENCED_PARAMETER(SystemArgument2);
PAGED_CODE();
// Free the packet.
IoFreeIrp(CONTAINING_RECORD(Apc, IRP, Tail.Apc));
}
VOID
IopUserRundown(
IN PKAPC Apc
)
/*++
Routine Description:
This routine is invoked during thread termination as the rundown routine
for it simply calls IopUserCompletion.
Arguments:
Apc - Supplies a pointer to kernel APC structure.
Return Value:
None.
--*/
{
PAGED_CODE();
// Free the packet.
IoFreeIrp(CONTAINING_RECORD(Apc, IRP, Tail.Apc));
}
NTSTATUS
IopXxxControlFile(
IN HANDLE FileHandle,
IN HANDLE Event OPTIONAL,
IN PIO_APC_ROUTINE ApcRoutine OPTIONAL,
IN PVOID ApcContext OPTIONAL,
OUT PIO_STATUS_BLOCK IoStatusBlock,
IN ULONG IoControlCode,
IN PVOID InputBuffer OPTIONAL,
IN ULONG InputBufferLength,
OUT PVOID OutputBuffer OPTIONAL,
IN ULONG OutputBufferLength,
IN BOOLEAN DeviceIoControl
)
/*++
Routine Description:
This service builds descriptors or MDLs for the supplied buffer(s) and
passes the untyped data to the driver associated with the file handle.
handle. It is up to the driver to check the input data and function
IoControlCode for validity, as well as to make the appropriate access
checks.
Arguments:
FileHandle - Supplies a handle to the file on which the service is being
performed.
Event - Supplies an optional event to be set to the Signaled state when
the service is complete.
ApcRoutine - Supplies an optional APC routine to be executed when the
service is complete.
ApcContext - Supplies a context parameter to be passed to the ApcRoutine,
if an ApcRoutine was specified.
IoStatusBlock - Address of the caller's I/O status block.
IoControlCode - Subfunction code to determine exactly what operation is
being performed.
InputBuffer - Optionally supplies an input buffer to be passed to the
driver. Whether or not the buffer is actually optional is dependent
on the IoControlCode.
InputBufferLength - Length of the InputBuffer in bytes.
OutputBuffer - Optionally supplies an output buffer to receive information
from the driver. Whether or not the buffer is actually optional is
dependent on the IoControlCode.
OutputBufferLength - Length of the OutputBuffer in bytes.
DeviceIoControl - Determines whether this is a Device or File System
Control function.
Return Value:
The status returned is success if the control operation was properly
queued to the I/O system. Once the operation completes, the status
can be determined by examining the Status field of the I/O status block.
--*/
{
PIRP irp;
NTSTATUS status;
PFILE_OBJECT fileObject;
PDEVICE_OBJECT deviceObject;
PKEVENT eventObject = (PKEVENT)NULL;
PIO_STACK_LOCATION irpSp;
ULONG method;
OBJECT_HANDLE_INFORMATION handleInformation;
BOOLEAN synchronousIo;
IO_STATUS_BLOCK localIoStatus;
PFAST_IO_DISPATCH fastIoDispatch;
POOL_TYPE poolType;
PULONG majorFunction;
KPROCESSOR_MODE requestorMode;
PAGED_CODE();
// Get the method that the buffers are being passed by.
method = IoControlCode & 3;
// Check the caller's parameters based on the mode of the caller.
requestorMode = KeGetPreviousMode();
if (requestorMode != KernelMode) {
// The caller's access mode is not kernel so probe each of the arguments
// and capture them as necessary. If any failures occur, the condition
// handler will be invoked to handle them. It will simply cleanup and
// return an access violation status code back to the system service
// dispatcher.
try {
// The IoStatusBlock parameter must be writeable by the caller.
ProbeForWriteIoStatusEx(IoStatusBlock, ApcRoutine);
// The output buffer can be used in any one of the following three ways,
// if it is specified:
// 0) It can be a normal, buffered output buffer.
// 1) It can be a DMA input buffer.
// 2) It can be a DMA output buffer.
// Which way the buffer is to be used it based on the low-order two bits
// of the IoControlCode.
// If the method is 0 we probe the output buffer for write access.
// If the method is not 3 we probe the input buffer for read access.
if (method == 0) {
if (ARGUMENT_PRESENT(OutputBuffer)) {
ProbeForWrite(OutputBuffer,
OutputBufferLength,
sizeof(UCHAR));
} else {
OutputBufferLength = 0;
}
}
if (method != 3) {
if (ARGUMENT_PRESENT(InputBuffer)) {
ProbeForRead(InputBuffer,
InputBufferLength,
sizeof(UCHAR));
} else {
InputBufferLength = 0;
}
}
} except(EXCEPTION_EXECUTE_HANDLER)
{
// An exception was incurred while attempting to probe or write
// one of the caller's parameters. Simply return an appropriate
// error status code.
return GetExceptionCode();
}
}
// There were no blatant errors so far, so reference the file object so
// the target device object can be found. Note that if the handle does
// not refer to a file object, or if the caller does not have the required
// access to the file, then it will fail.
status = ObReferenceObjectByHandle(FileHandle,
0L,
IoFileObjectType,
requestorMode,
(PVOID *)&fileObject,
&handleInformation);
if (!NT_SUCCESS(status)) {
return status;
}
// If this file has an I/O completion port associated w/it, then ensure
// that the caller did not supply an APC routine, as the two are mutually
// exclusive methods for I/O completion notification.
if (fileObject->CompletionContext && IopApcRoutinePresent(ApcRoutine)) {
ObDereferenceObject(fileObject);
return STATUS_INVALID_PARAMETER;
}
// Now check the access type for this control code to ensure that the
// caller has the appropriate access to this file object to perform the
// operation.
if (requestorMode != KernelMode) {
ULONG accessMode = (IoControlCode >> 14) & 3;
if (accessMode != FILE_ANY_ACCESS) {
// This I/O control requires that the caller have read, write,
// or read/write access to the object. If this is not the case,
// then cleanup and return an appropriate error status code.
if (SeComputeGrantedAccesses(handleInformation.GrantedAccess, accessMode) != accessMode) {
ObDereferenceObject(fileObject);
return STATUS_ACCESS_DENIED;
}
}
}
// Get the address of the event object and set the event to the Not-
// Signaled state, if an event was specified. Note here, too, that if
// the handle does not refer to an event, or if the event cannot be
// written, then the reference will fail.
if (ARGUMENT_PRESENT(Event)) {
status = ObReferenceObjectByHandle(Event,
EVENT_MODIFY_STATE,
ExEventObjectType,
requestorMode,
(PVOID *)&eventObject,
NULL);
if (!NT_SUCCESS(status)) {
ObDereferenceObject(fileObject);
return status;
} else {
KeClearEvent(eventObject);
}
}
// Make a special check here to determine whether this is a synchronous
// I/O operation. If it is, then wait here until the file is owned by
// the current thread.
if (fileObject->Flags & FO_SYNCHRONOUS_IO) {
BOOLEAN interrupted;
if (!IopAcquireFastLock(fileObject)) {
status = IopAcquireFileObjectLock(fileObject,
requestorMode,
(BOOLEAN)((fileObject->Flags & FO_ALERTABLE_IO) != 0),
&interrupted);
if (interrupted) {
if (eventObject) {
ObDereferenceObject(eventObject);
}
ObDereferenceObject(fileObject);
return status;
}
}
synchronousIo = TRUE;
} else {
synchronousIo = FALSE;
}
// Get the address of the target device object. If this file represents
// a device that was opened directly, then simply use the device or its
// attached device(s) directly.
if (!(fileObject->Flags & FO_DIRECT_DEVICE_OPEN)) {
deviceObject = IoGetRelatedDeviceObject(fileObject);
} else {
deviceObject = IoGetAttachedDevice(fileObject->DeviceObject);
}
if (DeviceIoControl) {
// Also get the address of the Fast I/O dispatch structure.
fastIoDispatch = deviceObject->DriverObject->FastIoDispatch;
// Turbo device control support. If the device has a fast I/O entry
// point for DeviceIoControlFile, call the entry point and give it a
// chance to try to complete the request. Note if FastIoDeviceControl
// returns FALSE or we get an I/O error, we simply fall through and
// go the "long way" and create an Irp.
if (fastIoDispatch && fastIoDispatch->FastIoDeviceControl) {
// Before we actually call the fast I/O routine in the driver,
// we must probe OutputBuffer if the method is 1 or 2.
if (requestorMode != KernelMode && ARGUMENT_PRESENT(OutputBuffer)) {
try {
if (method == 1) {
ProbeForRead(OutputBuffer,
OutputBufferLength,
sizeof(UCHAR));
} else if (method == 2) {
ProbeForWrite(OutputBuffer,
OutputBufferLength,
sizeof(UCHAR));
}
} except(EXCEPTION_EXECUTE_HANDLER)
{
// An exception was incurred while attempting to probe
// the output buffer. Clean up and return an
// appropriate error status code.
if (synchronousIo) {
IopReleaseFileObjectLock(fileObject);
}
if (eventObject) {
ObDereferenceObject(eventObject);
}
ObDereferenceObject(fileObject);
return GetExceptionCode();
}
}
// Call the driver's fast I/O routine.
if (fastIoDispatch->FastIoDeviceControl(fileObject,
TRUE,
InputBuffer,
InputBufferLength,
OutputBuffer,
OutputBufferLength,
IoControlCode,
&localIoStatus,
deviceObject)) {
// The driver successfully performed the I/O in it's
// fast device control routine. Carefully return the
// I/O status.
try {
*IoStatusBlock = localIoStatus;
} except(EXCEPTION_EXECUTE_HANDLER)
{
localIoStatus.Status = GetExceptionCode();
localIoStatus.Information = 0;
}
// If an event was specified, set it.
if (ARGUMENT_PRESENT(Event)) {
KeSetEvent(eventObject, 0, FALSE);
ObDereferenceObject(eventObject);
}
// Note that the file object event need not be set to the
// Signaled state, as it is already set. Release the
// file object lock, if necessary.
if (synchronousIo) {
IopReleaseFileObjectLock(fileObject);
}
// If this file object has a completion port associated with it
// and this request has a non-NULL APC context then a completion
// message needs to be queued.
if (fileObject->CompletionContext && ARGUMENT_PRESENT(ApcContext)) {
if (!NT_SUCCESS(IoSetIoCompletion(fileObject->CompletionContext->Port,
fileObject->CompletionContext->Key,
ApcContext,
localIoStatus.Status,
localIoStatus.Information,
TRUE))) {
localIoStatus.Status = STATUS_INSUFFICIENT_RESOURCES;
}
}
// Cleanup and return.
ObDereferenceObject(fileObject);
return localIoStatus.Status;
}
}
}
// Set the file object to the Not-Signaled state.
KeClearEvent(&fileObject->Event);
// Allocate and initialize the I/O Request Packet (IRP) for this operation.
irp = IopAllocateIrp(deviceObject->StackSize, TRUE);
if (!irp) {
// An IRP could not be allocated. Cleanup and return an appropriate
// error status code.
IopAllocateIrpCleanup(fileObject, eventObject);
return STATUS_INSUFFICIENT_RESOURCES;
}
irp->Tail.Overlay.OriginalFileObject = fileObject;
irp->Tail.Overlay.Thread = PsGetCurrentThread();
irp->Tail.Overlay.AuxiliaryBuffer = (PVOID)NULL;
irp->RequestorMode = requestorMode;
irp->PendingReturned = FALSE;
irp->Cancel = FALSE;
irp->CancelRoutine = (PDRIVER_CANCEL)NULL;
// Fill in the service independent parameters in the IRP.
irp->UserEvent = eventObject;
irp->UserIosb = IoStatusBlock;
irp->Overlay.AsynchronousParameters.UserApcRoutine = ApcRoutine;
irp->Overlay.AsynchronousParameters.UserApcContext = ApcContext;
// Get a pointer to the stack location for the first driver. This will be
// used to pass the original function codes and parameters. Note that
// setting the major function here also sets:
// MinorFunction = 0;
// Flags = 0;
// Control = 0;
irpSp = IoGetNextIrpStackLocation(irp);
majorFunction = (PULONG)(&irpSp->MajorFunction);
*majorFunction = DeviceIoControl ? IRP_MJ_DEVICE_CONTROL : IRP_MJ_FILE_SYSTEM_CONTROL;
irpSp->FileObject = fileObject;
// Copy the caller's parameters to the service-specific portion of the
// IRP for those parameters that are the same for all three methods.
irpSp->Parameters.DeviceIoControl.OutputBufferLength = OutputBufferLength;
irpSp->Parameters.DeviceIoControl.InputBufferLength = InputBufferLength;
irpSp->Parameters.DeviceIoControl.IoControlCode = IoControlCode;
// Set the pool type based on the type of function being performed.
poolType = DeviceIoControl ? NonPagedPoolCacheAligned : NonPagedPool;
// Based on the method that the buffer are being passed, either allocate
// buffers or build MDLs. Note that in some cases no probing has taken
// place so the exception handler must catch access violations.
irp->MdlAddress = (PMDL)NULL;
irp->AssociatedIrp.SystemBuffer = (PVOID)NULL;
switch (method) {
case 0:
// For this case, allocate a buffer that is large enough to contain
// both the input and the output buffers. Copy the input buffer to
// the allocated buffer and set the appropriate IRP fields.
irpSp->Parameters.DeviceIoControl.Type3InputBuffer = (PVOID)NULL;
try {
if (InputBufferLength || OutputBufferLength) {
irp->AssociatedIrp.SystemBuffer =
ExAllocatePoolWithQuota(poolType,
(InputBufferLength > OutputBufferLength) ? InputBufferLength : OutputBufferLength);
if (ARGUMENT_PRESENT(InputBuffer)) {
RtlCopyMemory(irp->AssociatedIrp.SystemBuffer,
InputBuffer,
InputBufferLength);
}
irp->Flags = IRP_BUFFERED_IO | IRP_DEALLOCATE_BUFFER;
irp->UserBuffer = OutputBuffer;
if (ARGUMENT_PRESENT(OutputBuffer)) {
irp->Flags |= IRP_INPUT_OPERATION;
}
} else {
irp->Flags = 0;
irp->UserBuffer = (PVOID)NULL;
}
} except(EXCEPTION_EXECUTE_HANDLER)
{
// An exception was incurred while either allocating the
// the system buffer or moving the caller's data. Determine
// what actually happened, cleanup accordingly, and return
// an appropriate error status code.
IopExceptionCleanup(fileObject,
irp,
eventObject,
(PKEVENT)NULL);
return GetExceptionCode();
}
break;
case 1:
case 2:
// For these two cases, allocate a buffer that is large enough to contain the input buffer, if any, and copy the information to
// the allocated buffer. Then build an MDL for either read or write access, depending on the method, for the output buffer. Note
// that the buffer length parameters have been jammed to zero for users if the buffer parameter was not passed. (Kernel callers
// should be calling the service correctly in the first place.)
// Note also that it doesn't make a whole lot of sense to specify
// either method #1 or #2 if the IOCTL does not require the caller to specify an output buffer.
irp->Flags = 0;
irpSp->Parameters.DeviceIoControl.Type3InputBuffer = (PVOID)NULL;
try {
if (InputBufferLength && ARGUMENT_PRESENT(InputBuffer)) {
irp->AssociatedIrp.SystemBuffer = ExAllocatePoolWithQuota(poolType, InputBufferLength);
RtlCopyMemory(irp->AssociatedIrp.SystemBuffer, InputBuffer, InputBufferLength);
irp->Flags = IRP_BUFFERED_IO | IRP_DEALLOCATE_BUFFER;
}
if (OutputBufferLength != 0) {
irp->MdlAddress = IoAllocateMdl(OutputBuffer, OutputBufferLength, FALSE, TRUE, irp);
if (irp->MdlAddress == NULL) {
ExRaiseStatus(STATUS_INSUFFICIENT_RESOURCES);
}
MmProbeAndLockPages(irp->MdlAddress, requestorMode, (LOCK_OPERATION)((method == 1) ? IoReadAccess : IoWriteAccess));
}
} except(EXCEPTION_EXECUTE_HANDLER)
{
// An exception was incurred while either allocating the system buffer, copying the caller's data, allocating the
// MDL, or probing and locking the caller's buffer.
// Determine what actually happened, cleanup accordingly, and return an appropriate error status code.
IopExceptionCleanup(fileObject, irp, eventObject, (PKEVENT)NULL);
return GetExceptionCode();
}
break;
case 3:
// For this case, do nothing. Everything is up to the driver.
// Simply give the driver a copy of the caller's parameters and let the driver do everything itself.
irp->Flags = 0;
irp->UserBuffer = OutputBuffer;
irpSp->Parameters.DeviceIoControl.Type3InputBuffer = InputBuffer;
}
// Defer I/O completion for FSCTL requests, but not for IOCTL requests,
// since file systems set pending properly but device driver do not.
if (!DeviceIoControl) {
irp->Flags |= IRP_DEFER_IO_COMPLETION;
}
// Queue the packet, call the driver, and synchronize appropriately with
// I/O completion.
return IopSynchronousServiceTail(deviceObject,
irp,
fileObject,
(BOOLEAN)!DeviceIoControl,
requestorMode,
synchronousIo,
OtherTransfer);
}
NTSTATUS
IopLookupBusStringFromID(
IN HANDLE KeyHandle,
IN INTERFACE_TYPE InterfaceType,
OUT PWCHAR Buffer,
IN ULONG Length,
OUT PULONG BusFlags OPTIONAL
)
/*++
Routine Description:
Translates INTERFACE_TYPE to its corresponding WCHAR[] string.
Arguments:
KeyHandle - Supplies a handle to the opened registry key,
HKLM\System\CurrentControlSet\Control\SystemResources\BusValues.
InterfaceType - Supplies the interface type for which a descriptive
name is to be retrieved.
Buffer - Supplies a pointer to a unicode character buffer that will
receive the bus name. Since this buffer is used in an
intermediate step to retrieve a KEY_VALUE_FULL_INFORMATION structure,
it must be large enough to contain this structure (including the
longest value name & data length under KeyHandle).
Length - Supplies the length, in bytes, of the Buffer.
BusFlags - Optionally receives the flags specified in the second
DWORD of the matching REG_BINARY value.
Return Value:
The function value is the final status of the operation.
--*/
{
NTSTATUS status;
ULONG Index, junk, i, j;
PULONG pl;
PKEY_VALUE_FULL_INFORMATION KeyInformation;
WCHAR c;
PAGED_CODE();
Index = 0;
KeyInformation = (PKEY_VALUE_FULL_INFORMATION)Buffer;
for (; ;) {
status = ZwEnumerateValueKey(
KeyHandle,
Index++,
KeyValueFullInformation,
Buffer,
Length,
&junk
);
if (!NT_SUCCESS(status)) {
return status;
}
if (KeyInformation->Type != REG_BINARY) {
continue;
}
pl = (PULONG)((PUCHAR)KeyInformation + KeyInformation->DataOffset);
if ((ULONG)InterfaceType != pl[0]) {
continue;
}
// Found a match - move the name to the start of the buffer
if (ARGUMENT_PRESENT(BusFlags)) {
*BusFlags = pl[1];
}
j = KeyInformation->NameLength / sizeof(WCHAR);
for (i = 0; i < j; i++) {
c = KeyInformation->Name[i];
Buffer[i] = c;
}
Buffer[i] = 0;
return STATUS_SUCCESS;
}
}
BOOLEAN IopSafebootDriverLoad(PUNICODE_STRING DriverId)
/*++
Routine Description:
Checks to see if a driver or service is included
in the current safeboot registry section.
Arguments:
DriverId - Specifies which driver is to be validated.
The string should contain a driver executable name
like foo.sys or a GUID for a pnp driver class.
Return Value:
TRUE - driver/service is in the registry
FALSE - driver/service is NOT in the registry
--*/
{
NTSTATUS status;
HANDLE hSafeBoot, hGuid;
UNICODE_STRING safeBootKey;
UNICODE_STRING SafeBootTypeString;
// set the first part of the registry key name
switch (InitSafeBootMode) {
case SAFEBOOT_MINIMAL:
RtlInitUnicodeString(&SafeBootTypeString, SAFEBOOT_MINIMAL_STR_W);
break;
case SAFEBOOT_NETWORK:
RtlInitUnicodeString(&SafeBootTypeString, SAFEBOOT_NETWORK_STR_W);
break;
case SAFEBOOT_DSREPAIR:
return TRUE;
default:
KdPrint(("SAFEBOOT: invalid safeboot option = %d\n", InitSafeBootMode));
return FALSE;
}
safeBootKey.Length = 0;
safeBootKey.MaximumLength = DriverId->Length + SafeBootTypeString.Length + (4 * sizeof(WCHAR));
safeBootKey.Buffer = (PWCHAR)ExAllocatePool(PagedPool, safeBootKey.MaximumLength);
if (!safeBootKey.Buffer) {
KdPrint(("SAFEBOOT: could not allocate pool\n"));
return FALSE;
}
RtlCopyUnicodeString(&safeBootKey, &SafeBootTypeString);
status = RtlAppendUnicodeToString(&safeBootKey, L"\\");
if (!NT_SUCCESS(status)) {
ExFreePool(safeBootKey.Buffer);
KdPrint(("SAFEBOOT: could not create registry key string = %x\n", status));
return FALSE;
}
status = RtlAppendUnicodeStringToString(&safeBootKey, DriverId);
if (!NT_SUCCESS(status)) {
ExFreePool(safeBootKey.Buffer);
KdPrint(("SAFEBOOT: could not create registry key string = %x\n", status));
return FALSE;
}
status = IopOpenRegistryKey(&hSafeBoot, NULL, &CmRegistryMachineSystemCurrentControlSetControlSafeBoot, KEY_ALL_ACCESS, FALSE);
if (NT_SUCCESS(status)) {
status = IopOpenRegistryKey(&hGuid, hSafeBoot, &safeBootKey, KEY_ALL_ACCESS, FALSE);
NtClose(hSafeBoot);
if (NT_SUCCESS(status)) {
NtClose(hGuid);
ExFreePool(safeBootKey.Buffer);
return TRUE;
}
}
ExFreePool(safeBootKey.Buffer);
return FALSE;
}
static PBOOT_LOG_RECORD BootLogRecord;
VOID IopInitializeBootLogging(PLOADER_PARAMETER_BLOCK LoaderBlock, PCHAR HeaderString)
/*++
Routine Description:
Initializes strings for boot logging.
Arguments:
LoaderBlock - the loader parameter block
Return Value:
VOID
--*/
{
PLDR_DATA_TABLE_ENTRY DataTableEntry;
PMESSAGE_RESOURCE_ENTRY MessageEntry;
ULONG MsgId = 0;
ANSI_STRING AnsiString;
NTSTATUS Status;
PLIST_ENTRY nextEntry;
PLDR_DATA_TABLE_ENTRY driverEntry;
PAGED_CODE();
if (BootLogRecord != NULL) {
return;
}
BootLogRecord = (PBOOT_LOG_RECORD)ExAllocatePool(NonPagedPool, sizeof(BOOT_LOG_RECORD));
if (BootLogRecord == NULL) {
return;
}
RtlZeroMemory(BootLogRecord, sizeof(BOOT_LOG_RECORD));
ExInitializeResource(&BootLogRecord->Resource);
ExAcquireResourceExclusive(&BootLogRecord->Resource, TRUE);
DataTableEntry = CONTAINING_RECORD(LoaderBlock->LoadOrderListHead.Flink, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);
Status = RtlFindMessage(DataTableEntry->DllBase, 11, 0, BOOTLOG_LOADED, &MessageEntry);
if (NT_SUCCESS(Status)) {
AnsiString.Buffer = MessageEntry->Text;
AnsiString.Length = strlen(MessageEntry->Text);
AnsiString.MaximumLength = AnsiString.Length + 1;
RtlAnsiStringToUnicodeString(&BootLogRecord->LoadedString, &AnsiString, TRUE);
// whack the crlf at the end of the string
if (BootLogRecord->LoadedString.Length > 2 * sizeof(WCHAR)) {
BootLogRecord->LoadedString.Length -= 2 * sizeof(WCHAR);
BootLogRecord->LoadedString.Buffer[BootLogRecord->LoadedString.Length / sizeof(WCHAR)] = UNICODE_NULL;
}
}
Status = RtlFindMessage(DataTableEntry->DllBase, 11, 0, BOOTLOG_NOT_LOADED, &MessageEntry);
if (NT_SUCCESS(Status)) {
AnsiString.Buffer = MessageEntry->Text;
AnsiString.Length = strlen(MessageEntry->Text);
AnsiString.MaximumLength = AnsiString.Length + 1;
RtlAnsiStringToUnicodeString(&BootLogRecord->NotLoadedString, &AnsiString, TRUE);
// whack the crlf at the end of the string
if (BootLogRecord->NotLoadedString.Length > 2 * sizeof(WCHAR)) {
BootLogRecord->NotLoadedString.Length -= 2 * sizeof(WCHAR);
BootLogRecord->NotLoadedString.Buffer[BootLogRecord->NotLoadedString.Length / sizeof(WCHAR)] = UNICODE_NULL;
}
}
// The header string (copied from DebugString in Phase1Initialization) appears to have a leading null byte
HeaderString++;
RtlCreateUnicodeStringFromAsciiz(&BootLogRecord->HeaderString, HeaderString);
// Log the drivers loaded by the boot loader
ExAcquireResourceShared(&PsLoadedModuleResource, TRUE);
nextEntry = PsLoadedModuleList.Flink;
while (nextEntry != &PsLoadedModuleList) {
// Look at the next boot driver in the list.
driverEntry = CONTAINING_RECORD(nextEntry,
LDR_DATA_TABLE_ENTRY,
InLoadOrderLinks);
if (driverEntry->Flags & LDRP_ENTRY_PROCESSED) {
IopBootLog(&driverEntry->FullDllName, TRUE);
}
nextEntry = nextEntry->Flink;
}
ExReleaseResource(&PsLoadedModuleResource);
ExReleaseResource(&BootLogRecord->Resource);
}
VOID IopBootLog(PUNICODE_STRING LogEntry, BOOLEAN Loaded)
/*++
Routine Description:
Create and write out a log entry. Before NtInitializeRegistry is called, log entries are spooled
into the registry. When NtInitalizeRegistry is called by the session manager, the
log file is created if necessary and truncated. Log entries in the registry are
then copied into the log file and the registry entries are deleted.
Arguments:
LogEntry - the text to log.
Loaded - indicates whether to prepend the "Loaded" string or the "Not Loaded" string.
Return Value:
VOID
--*/
{
WCHAR NameBuffer[BOOTLOG_STRSIZE];
UNICODE_STRING KeyName;
UNICODE_STRING ValueName;
UNICODE_STRING CrLf;
UNICODE_STRING Space;
NTSTATUS Status;
WCHAR MessageBuffer[BOOTLOG_STRSIZE];
UNICODE_STRING MessageString = {
0,
BOOTLOG_STRSIZE,
&MessageBuffer[0]
};
PAGED_CODE();
if (BootLogRecord == NULL) {
return;
}
ExAcquireResourceExclusive(&BootLogRecord->Resource, TRUE);
if (Loaded) {
RtlCopyUnicodeString(&MessageString, &BootLogRecord->LoadedString);
} else {
RtlCopyUnicodeString(&MessageString, &BootLogRecord->NotLoadedString);
}
// add a space after the message prefix
RtlInitUnicodeString(&Space, L" ");
RtlAppendUnicodeStringToString(&MessageString, &Space);
RtlAppendUnicodeStringToString(&MessageString, LogEntry);
// add a CR LF
RtlInitUnicodeString(&CrLf, L"\r\n");
RtlAppendUnicodeStringToString(&MessageString, &CrLf);
swprintf(NameBuffer, L"%d", BootLogRecord->NextKey++);
RtlCreateUnicodeString(&KeyName, NameBuffer);
RtlInitUnicodeString(&ValueName, L"");
if (!BootLogRecord->FileLogging) {
HANDLE hLogKey, hBootKey;
Status = IopOpenRegistryKey(&hBootKey, NULL, &CmRegistryMachineSystemCurrentControlSetControlBootLog, KEY_ALL_ACCESS, TRUE);
if (NT_SUCCESS(Status)) {
Status = IopOpenRegistryKey(&hLogKey, hBootKey, &KeyName, KEY_ALL_ACCESS, TRUE);
if (NT_SUCCESS(Status)) {
Status = IopSetRegistryStringValue(hLogKey, &ValueName, &MessageString);
ZwClose(hLogKey);
}
ZwClose(hBootKey);
}
} else {
IopBootLogToFile(&MessageString);
}
RtlFreeUnicodeString(&KeyName);
ExReleaseResource(&BootLogRecord->Resource);
}
VOID IopCopyBootLogRegistryToFile(VOID)
/*++
Routine Description:
Copy the text in the registry entries into the log file and delete the registry entries. Set the
flag that indicates direct logging to the log file.
Arguments:
NONE
Return Value:
VOID
--*/
{
UNICODE_STRING KeyName;
WCHAR NameBuffer[BOOTLOG_STRSIZE];
NTSTATUS Status;
HANDLE hLogKey, hBootKey;
ULONG Index;
PKEY_VALUE_FULL_INFORMATION Information;
IO_STATUS_BLOCK IoStatus;
LARGE_INTEGER LocalTime;
TIME_FIELDS TimeFields;
CHAR AnsiTimeBuffer[256];
ANSI_STRING AnsiTimeString;
UNICODE_STRING UnicodeTimeString;
UNICODE_STRING LogString;
PAGED_CODE();
if (BootLogRecord == NULL) {
return;
}
ExAcquireResourceExclusive(&BootLogRecord->Resource, TRUE);
IopBootLogToFile(&BootLogRecord->HeaderString);
ExSystemTimeToLocalTime(&KeBootTime, &LocalTime);
RtlTimeToTimeFields(&LocalTime, &TimeFields);
sprintf(
AnsiTimeBuffer,
"%2d %2d %4d %02d:%02d:%02d.%03d\r\n",
TimeFields.Month,
TimeFields.Day,
TimeFields.Year,
TimeFields.Hour,
TimeFields.Minute,
TimeFields.Second,
TimeFields.Milliseconds
);
RtlInitAnsiString(&AnsiTimeString, AnsiTimeBuffer);
RtlAnsiStringToUnicodeString(&UnicodeTimeString, &AnsiTimeString, TRUE);
IopBootLogToFile(&UnicodeTimeString);
RtlFreeUnicodeString(&UnicodeTimeString);
// Read all of the strings in the registry and write them to the log file.
// Delete the registry keys when done.
Status = IopOpenRegistryKey(&hBootKey, NULL, &CmRegistryMachineSystemCurrentControlSetControlBootLog, KEY_ALL_ACCESS, FALSE);
if (NT_SUCCESS(Status)) {
for (Index = 0; Index < BootLogRecord->NextKey; Index++) {
swprintf(NameBuffer, L"%d", Index);
RtlCreateUnicodeString(&KeyName, NameBuffer);
Status = IopOpenRegistryKey(&hLogKey, hBootKey, &KeyName, KEY_ALL_ACCESS, FALSE);
if (NT_SUCCESS(Status)) {
Status = IopGetRegistryValue(hLogKey, L"", &Information);
if (NT_SUCCESS(Status)) {
RtlInitUnicodeString(&LogString, (PWSTR)((PUCHAR)Information + Information->DataOffset));
IopBootLogToFile(&LogString);
}
ExFreePool(Information);
ZwDeleteKey(hLogKey);
ZwClose(hLogKey);
}
}
ZwDeleteKey(hBootKey);
ZwClose(hBootKey);
// Write directly to the file from now on.
BootLogRecord->FileLogging = TRUE;
}
ExReleaseResource(&BootLogRecord->Resource);
}
NTSTATUS IopBootLogToFile(PUNICODE_STRING String)
/*++
Routine Description:
Write the buffer into the log file.
Arguments:
Buffer - pointer to the string to write out.
Length - number of bytes to write
Return Value:
The function status is the final status of the operation.
--*/
{
OBJECT_ATTRIBUTES ObjA;
NTSTATUS Status;
IO_STATUS_BLOCK IoStatusBlock;
HANDLE FileHandle;
WCHAR UnicodeHeader = 0xfeff;
PAGED_CODE();
if (BootLogRecord == NULL) {
return STATUS_SUCCESS;
}
ExAcquireResourceExclusive(&BootLogRecord->Resource, TRUE);
if (BootLogRecord->LogFileName.Buffer == NULL) {
RtlInitUnicodeString(&BootLogRecord->LogFileName, L"\\SystemRoot\\ntbtlog.txt");
}
InitializeObjectAttributes(&ObjA, &BootLogRecord->LogFileName, OBJ_CASE_INSENSITIVE, NULL, NULL);
Status = ZwCreateFile(&FileHandle,
GENERIC_WRITE,
&ObjA,
&IoStatusBlock,
NULL,
FILE_ATTRIBUTE_NORMAL,
FILE_SHARE_READ,
FILE_OPEN_IF,
FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE | FILE_SEQUENTIAL_ONLY,
NULL,
0);
if (NT_SUCCESS(Status) && IoStatusBlock.Information == FILE_CREATED) {
Status = ZwWriteFile(FileHandle, NULL, NULL, NULL, &IoStatusBlock, (PVOID)&UnicodeHeader, sizeof(WCHAR), NULL, NULL);
}
if (NT_SUCCESS(Status)) {
LARGE_INTEGER EndOfFile;
EndOfFile.HighPart = 0xffffffff;
EndOfFile.LowPart = FILE_WRITE_TO_END_OF_FILE;
Status = ZwWriteFile(FileHandle, NULL, NULL, NULL, &IoStatusBlock, (PVOID)String->Buffer, String->Length, &EndOfFile, NULL);
ZwClose(FileHandle);
}
ExReleaseResource(&BootLogRecord->Resource);
return Status;
}