/*++

Copyright (c) 2012 Minoca Corp. All Rights Reserved

Module Name:

    filesys.c

Abstract:

    This module implements support for file system drivers.

Author:

    Evan Green 25-Sep-2012

Environment:

    Kernel

--*/

//
// ------------------------------------------------------------------- Includes
//

#include <minoca/kernel/kernel.h>
#include <minoca/devinfo/part.h>
#include "iop.h"

//
// ---------------------------------------------------------------- Definitions
//

//
// Define the maximum length of a volume name string, including the terminator.
//

#define VOLUME_NAME_LENGTH 11

//
// Define the number of times create or lookup volume is allowed to kick-start
// a failed device.
//

#define VOLUME_START_RETRY_MAX 1

//
// Define the maximum number of supported volumes in the system.
//

#define MAX_VOLUMES 10000

//
// Define the location of the drivers directory, relative to the system root.
//

#define SYSTEM_DRIVERS_DIRECTORY "drivers"

//
// ------------------------------------------------------ Data Type Definitions
//

/*++

Structure Description:

    This structure defines a registered file system.

Members:

    ListEntry - Stores pointers to the previous and next registered file
        systems.

    Driver - Supplies a pointer to the driver object.

--*/

typedef struct _FILE_SYSTEM {
    LIST_ENTRY ListEntry;
    PDRIVER Driver;
} FILE_SYSTEM, *PFILE_SYSTEM;

//
// ----------------------------------------------- Internal Function Prototypes
//

VOID
IopDestroyVolume (
    PVOLUME Volume
    );

PSTR
IopGetNewVolumeName (
    VOID
    );

//
// -------------------------------------------------------------------- Globals
//

//
// Keep the list of registered file systems.
//

LIST_ENTRY IoFileSystemList;

//
// This lock synchronizes access to the list of file systems.
//

PQUEUED_LOCK IoFileSystemListLock = NULL;

//
// Store a pointer to the volumes directory and the number of volumes in the
// system.
//

POBJECT_HEADER IoVolumeDirectory = NULL;

//
// Define the path from the system volume to the system directory. Set it to a
// default in case there is no boot entry (which there should really always be).
//

PSTR IoSystemDirectoryPath = "minoca";

//
// Store a pointer to the system volumes.
//

PVOLUME IoSystemVolume = NULL;

UUID IoPartitionDeviceInformationUuid = PARTITION_DEVICE_INFORMATION_UUID;

//
// ------------------------------------------------------------------ Functions
//

KERNEL_API
KSTATUS
IoRegisterFileSystem (
    PDRIVER Driver
    )

/*++

Routine Description:

    This routine registers the given driver as a file system driver.

Arguments:

    Driver - Supplies a pointer to the driver registering the file system
        support.

Return Value:

    Status code.

--*/

{

    PFILE_SYSTEM NewFileSystem;
    KSTATUS Status;

    //
    // Allocate and initialize the new file system.
    //

    NewFileSystem = MmAllocatePagedPool(sizeof(FILE_SYSTEM), FI_ALLOCATION_TAG);
    if (NewFileSystem == NULL) {
        Status = STATUS_NO_MEMORY;
        goto RegisterFileSystemEnd;
    }

    RtlZeroMemory(NewFileSystem, sizeof(FILE_SYSTEM));
    NewFileSystem->Driver = Driver;

    //
    // Add it to the list.
    //

    KeAcquireQueuedLock(IoFileSystemListLock);
    INSERT_AFTER(&(NewFileSystem->ListEntry), &IoFileSystemList);
    KeReleaseQueuedLock(IoFileSystemListLock);
    Status = STATUS_SUCCESS;

RegisterFileSystemEnd:
    return Status;
}

KSTATUS
IopAddFileSystem (
    PDEVICE Device
    )

/*++

Routine Description:

    This routine adds a file system to the given volume.

Arguments:

    Device - Supplies a pointer to the volume to attach a file system to.

Return Value:

    Status code.

--*/

{

    PDRIVER_ADD_DEVICE AddDevice;
    PLIST_ENTRY CurrentEntry;
    PFILE_SYSTEM CurrentFileSystem;
    PDRIVER Driver;
    ULONG OriginalStackSize;
    KSTATUS Status;

    ASSERT(Device->Header.Type == ObjectVolume);

    OriginalStackSize = Device->DriverStackSize;
    Status = STATUS_NO_DRIVERS;

    //
    // Loop through all file systems, calling AddDevice until a driver
    // attaches.
    //

    KeAcquireQueuedLock(IoFileSystemListLock);
    CurrentEntry = IoFileSystemList.Next;
    while (CurrentEntry != &IoFileSystemList) {
        CurrentFileSystem = LIST_VALUE(CurrentEntry, FILE_SYSTEM, ListEntry);
        Driver = CurrentFileSystem->Driver;

        //
        // Call the driver's AddDevice. The return value of AddDevice is
        // ignored, success is implied if the driver attached itself. Note that
        // the file system list lock is held as Add Device is called. Thus
        // a file system driver's Add Device routine cannot depend on any other
        // volume enumerations to complete, otherwise a deadlock would occur.
        //

        if ((Driver->Flags & DRIVER_FLAG_FAILED_DRIVER_ENTRY) == 0) {
            if (Driver->FunctionTable.AddDevice != NULL) {
                AddDevice = Driver->FunctionTable.AddDevice;
                AddDevice(Driver,
                          IoGetDeviceId(Device),
                          Device->ClassId,
                          Device->CompatibleIds,
                          Device);

                if (Device->DriverStackSize != OriginalStackSize) {
                    Status = STATUS_SUCCESS;
                    break;
                }

            } else {
                Status = STATUS_DRIVER_FUNCTION_MISSING;
                IopSetDeviceProblem(Device, DeviceProblemNoAddDevice, Status);
                goto AddFileSystemEnd;
            }
        }

        CurrentEntry = CurrentEntry->Next;
    }

    if (!KSUCCESS(Status)) {
        IopSetDeviceProblem(Device, DeviceProblemNoFileSystem, Status);
        goto AddFileSystemEnd;
    }

AddFileSystemEnd:
    KeReleaseQueuedLock(IoFileSystemListLock);
    return Status;
}

KSTATUS
IoCreateVolume (
    PDEVICE Device,
    PVOLUME *Volume
    )

/*++

Routine Description:

    This routine creates a new volume to be mounted by a file system.

Arguments:

    Device - Supplies a pointer to the physical device upon which the file
        system should be mounted.

    Volume - Supplies a pointer that receives a pointer to the newly created
        volume.

Return Value:

    Status code.

--*/

{

    BOOL LockHeld;
    PSTR NewName;
    PVOLUME NewVolume;
    KSTATUS Status;
    BOOL TargetAttached;

    ASSERT((Device->Flags & DEVICE_FLAG_MOUNTABLE) != 0);

    LockHeld = FALSE;
    TargetAttached = FALSE;

    //
    // Allocate the next available name for the volume.
    //

    NewName = IopGetNewVolumeName();
    if (NewName == NULL) {
        Status = STATUS_INSUFFICIENT_RESOURCES;
        goto CreateVolumeEnd;
    }

    //
    // Create the volume.
    //

    Status = IopCreateDevice(NULL,
                             NULL,
                             (PDEVICE)IoVolumeDirectory,
                             NewName,
                             NULL,
                             NULL,
                             ObjectVolume,
                             sizeof(VOLUME),
                             (PDEVICE *)&NewVolume);

    if (!KSUCCESS(Status)) {
        goto CreateVolumeEnd;
    }

    //
    // Now acquire the physical device's lock exclusively and attach the volume
    // to it. If the physical device is awaiting removal or removed, abort the
    // process.
    //

    KeAcquireSharedExclusiveLockExclusive(Device->Lock);
    LockHeld = TRUE;
    if ((Device->State == DeviceAwaitingRemoval) ||
        (Device->State == DeviceRemoved)) {

        Status = STATUS_PARENT_AWAITING_REMOVAL;
        goto CreateVolumeEnd;
    }

    //
    // Only allow one volume to be mounted per device.
    //

    if ((Device->Flags & DEVICE_FLAG_MOUNTED) != 0) {
        Status = STATUS_TOO_LATE;
        goto CreateVolumeEnd;
    }

    //
    // Reference the backing device, attach it to the volume and add the volume
    // to the device's active child list.
    //

    ObAddReference(Device);
    NewVolume->Device.TargetDevice = Device;
    INSERT_BEFORE(&(NewVolume->Device.ActiveListEntry),
                  &(Device->ActiveChildListHead));

    //
    // Set the volume specific referencee count to 1 or 2 depending on whether
    // or not the caller wants a pointer to the volume. Also add an object
    // manager reference that will be released when the volume reference drops
    // to 0.
    //

    ObAddReference(NewVolume);
    if (Volume == NULL) {
        NewVolume->ReferenceCount = 1;

    } else {
        NewVolume->ReferenceCount = 2;
    }

    TargetAttached = TRUE;
    Device->Flags |= DEVICE_FLAG_MOUNTED;
    KeReleaseSharedExclusiveLockExclusive(Device->Lock);
    LockHeld = FALSE;

    //
    // TODO: Determine if this volume should contain the page file.
    //

    NewVolume->Device.Flags |= DEVICE_FLAG_PAGING_DEVICE;

    //
    // Queue the work item to start the volume.
    //

    Status = IopQueueDeviceWork((PDEVICE)NewVolume, DeviceActionStart, NULL, 0);
    if (!KSUCCESS(Status)) {
        IopSetDeviceProblem((PDEVICE)NewVolume,
                            DeviceProblemFailedToQueueStart,
                            Status);

        goto CreateVolumeEnd;
    }

CreateVolumeEnd:
    if (LockHeld != FALSE) {
        KeReleaseSharedExclusiveLockExclusive(Device->Lock);
    }

    if (!KSUCCESS(Status)) {

        //
        // If the volume failed to attach, but was created, then release it.
        //

        if (TargetAttached == FALSE) {
            if (NewVolume != NULL) {
                ObReleaseReference(NewVolume);
            }

        //
        // Otherwise if the caller requested the new volume, release the second
        // reference taken. This will actually attempt to destroy the volume.
        // It may succeed, but it may not. Not much to do about this.
        //

        } else if (Volume != NULL) {

            ASSERT(TargetAttached != FALSE);

            IoVolumeReleaseReference(NewVolume);
        }

    } else {

        //
        // If the caller wanted a pointer to the volume, send it off.
        //

        if (Volume != NULL) {
            *Volume = NewVolume;
        }
    }

    if (NewName != NULL) {
        MmFreePagedPool(NewName);
    }

    return Status;
}

KSTATUS
IopCreateOrLookupVolume (
    PDEVICE Device,
    PVOLUME *Volume
    )

/*++

Routine Description:

    This routine returns the volume associated with the given device, if such
    a volume exists. A reference is taken on the volume, which the caller is
    expected to release.

Arguments:

    Device - Supplies a pointer to the device whose volume is to be returned.

    Volume - Supplies a pointer that receives a pointer to the created or found
        volume.

    VolumeCreated - Supplies a pointer that receives a boolean indicating
        whether or not the volume was created.

Return Value:

    Status code.

--*/

{

    PDEVICE Child;
    PLIST_ENTRY CurrentEntry;
    PVOLUME FoundVolume;
    PVOLUME NewVolume;
    ULONG RetryCount;
    KSTATUS Status;

    ASSERT(Device != NULL);
    ASSERT(Volume != NULL);
    ASSERT((Device->Flags & DEVICE_FLAG_MOUNTABLE) != 0);

    FoundVolume = NULL;

    //
    // Loop until a volume is found or created.
    //

    while (TRUE) {

        //
        // If the OS has not already mounted a volume on the device, then try
        // to create a volume.
        //

        if ((Device->Flags & DEVICE_FLAG_MOUNTED) == 0) {

            //
            // Create a volume on the device. If this successfully creates a
            // volume, then it takes a reference on it. If it finds out that
            // someone else beat it to the punch, it returns a "too late"
            // status. If it fails outright, just exit.
            //

            Status = IoCreateVolume(Device, &NewVolume);
            if (!KSUCCESS(Status) && (Status != STATUS_TOO_LATE)) {
                goto CreateOrLookupVolumeEnd;
            }

            //
            // If a volume was successfully created, wait for the volume to
            // signal on ready or on failure.
            //

            if (KSUCCESS(Status)) {
                ObWaitOnObject(NewVolume, 0, WAIT_TIME_INDEFINITE);

                //
                // After the signal, if the volume is in the started state,
                // then this is a success. If the volume is not started, then
                // either there was a problem initializing the volume or it
                // was removed because of user interaction. Either way, fail.
                //

                FoundVolume = NewVolume;
                if (NewVolume->Device.State == DeviceStarted) {
                    Status = STATUS_SUCCESS;

                } else {
                    Status = STATUS_UNSUCCESSFUL;
                }

                goto CreateOrLookupVolumeEnd;
            }
        }

        //
        // A volume was already mounted when this routine was called or someone
        // else beat this routine to the punch. Lookup the volume.
        //

        FoundVolume = NULL;
        KeAcquireSharedExclusiveLockShared(Device->Lock);

        //
        // If the volume still remains, then search for it. If it has been
        // unmounted since the check above, there are a few options: (1) the
        // device is in the middle of removal - the next volume create will
        // fail; (2) the volume got removed - the next volume create should
        // succeed. This routine loops to try again either way.
        //

        if ((Device->Flags & DEVICE_FLAG_MOUNTED) != 0) {
            CurrentEntry = Device->ActiveChildListHead.Next;

            //
            // Search through the active children for the first volume. There
            // should only be one volume per device. Add a reference to this
            // volume and return it.
            //

            while (CurrentEntry != &(Device->ActiveChildListHead)) {
                Child = LIST_VALUE(CurrentEntry, DEVICE, ActiveListEntry);
                if (Child->Header.Type == ObjectVolume) {
                    FoundVolume = (PVOLUME)Child;
                    IoVolumeAddReference(FoundVolume);
                    break;
                }

                CurrentEntry = CurrentEntry->Next;
            }
        }

        KeReleaseSharedExclusiveLockShared(Device->Lock);

        //
        // If a volume was found, wait on it. If it signals from the start
        // state, proceed. If it signals from the removed state, then try
        // again. If it signals from any other state, try to kick-start it
        // once before giving up.
        //

        if (FoundVolume != NULL) {
            RetryCount = 0;
            while (TRUE) {
                ObWaitOnObject(FoundVolume, 0, WAIT_TIME_INDEFINITE);
                if (FoundVolume->Device.State == DeviceStarted) {
                    Status = STATUS_SUCCESS;
                    goto CreateOrLookupVolumeEnd;
                }

                //
                // Try to find or create the volume again if the volume has
                // been removed.
                //

                if (FoundVolume->Device.State == DeviceRemoved) {
                    IoVolumeReleaseReference(FoundVolume);
                    FoundVolume = NULL;
                    break;
                }

                if (RetryCount >= VOLUME_START_RETRY_MAX) {
                    Status = STATUS_UNSUCCESSFUL;
                    goto CreateOrLookupVolumeEnd;
                }

                //
                // Otherwise, kick it to see if it will come up.
                //

                ObSignalObject(FoundVolume, SignalOptionUnsignal);
                Status = IopQueueDeviceWork((PDEVICE)FoundVolume,
                                            DeviceActionStart,
                                            NULL,
                                            0);

                if (!KSUCCESS(Status)) {
                    IopSetDeviceProblem((PDEVICE)NewVolume,
                                        DeviceProblemFailedToQueueStart,
                                        Status);

                    goto CreateOrLookupVolumeEnd;
                }

                RetryCount += 1;
            }
        }
    }

CreateOrLookupVolumeEnd:
    if (!KSUCCESS(Status)) {
        if (FoundVolume != NULL) {
            IoVolumeReleaseReference(FoundVolume);
        }

    } else {
        *Volume = FoundVolume;
    }

    return Status;
}

VOID
IopVolumeArrival (
    PVOID Parameter
    )

/*++

Routine Description:

    This routine performs work associated with a new volume coming online.

Arguments:

    Parameter - Supplies a pointer to the arriving volume.

Return Value:

    None.

--*/

{

    IO_BOOT_INFORMATION BootInformation;
    UINTN BootInformationSize;
    BOOL Created;
    PSTR DeviceName;
    ULONG DeviceNameLength;
    PIO_HANDLE DriversDirectoryHandle;
    PFILE_OBJECT FileObject;
    ULONG FileObjectFlags;
    PKPROCESS KernelProcess;
    BOOL Match;
    PARTITION_DEVICE_INFORMATION PartitionInformation;
    UINTN PartitionInformationSize;
    PPATH_POINT PathPoint;
    FILE_PROPERTIES Properties;
    ULONG RootLookupFlags;
    KSTATUS Status;
    PIO_HANDLE SystemDirectoryHandle;
    BOOL SystemVolume;
    PDEVICE TargetDevice;
    PVOLUME Volume;
    PIO_HANDLE VolumeHandle;
    PSTR VolumeName;
    ULONG VolumeNameLength;

    DeviceName = NULL;
    DriversDirectoryHandle = NULL;
    FileObject = NULL;
    SystemDirectoryHandle = NULL;
    SystemVolume = FALSE;
    Volume = (PVOLUME)Parameter;
    VolumeHandle = NULL;

    ASSERT(Volume != NULL);
    ASSERT(Volume->Device.Header.Type == ObjectVolume);

    //
    // Get the partition information for the volume.
    //

    TargetDevice = IoGetTargetDevice((PDEVICE)Volume);

    ASSERT(TargetDevice != NULL);

    VolumeName = ObGetFullPath(Volume, IO_ALLOCATION_TAG);
    if (VolumeName == NULL) {
        Status = STATUS_INSUFFICIENT_RESOURCES;
        goto VolumeArrivalEnd;
    }

    DeviceName = ObGetFullPath(TargetDevice, IO_ALLOCATION_TAG);
    if (DeviceName == NULL) {
        Status = STATUS_INSUFFICIENT_RESOURCES;
        goto VolumeArrivalEnd;
    }

    DeviceNameLength = RtlStringLength(DeviceName) + 1;
    VolumeNameLength = RtlStringLength(VolumeName) + 1;

    //
    // Get the root path entry for the volume. Start by sending a root lookup
    // request to the volume. If it does not succeed, then the volume isn't
    // participating in the file system and there is nothing to do, really.
    //

    Status = IopSendRootLookupRequest(&(Volume->Device),
                                      &Properties,
                                      &RootLookupFlags);

    if (!KSUCCESS(Status)) {
        goto VolumeArrivalEnd;
    }

    Properties.DeviceId = Volume->Device.DeviceId;
    FileObjectFlags = 0;
    if ((RootLookupFlags & LOOKUP_FLAG_NON_CACHED) != 0) {
        FileObjectFlags |= FILE_OBJECT_FLAG_NON_CACHED;
    }

    //
    // Create or lookup a file object for the volume.
    //

    Status = IopCreateOrLookupFileObject(&Properties,
                                         &(Volume->Device),
                                         FileObjectFlags,
                                         &FileObject,
                                         &Created);

    if (!KSUCCESS(Status)) {
        goto VolumeArrivalEnd;
    }

    ASSERT(Created != FALSE);
    ASSERT(Volume->PathEntry == NULL);

    //
    // Make a path entry with the found file object. This does not take an
    // additional reference on the file object.
    //

    Volume->PathEntry = IopCreateAnonymousPathEntry(FileObject);
    if (Volume->PathEntry == NULL) {
        Status = STATUS_INSUFFICIENT_RESOURCES;
        goto VolumeArrivalEnd;
    }

    FileObject = NULL;

    //
    // The volume is completely set up now, so signal it as ready. This can
    // potentially race with the device removal sequence unsignalling the
    // volume. The worst is that something sneaks through with a short-lived
    // reference to the device. It won't be very useful once the remove IRP
    // is sent.
    //

    ObSignalObject(Volume, SignalOptionSignalAll);

    //
    // Mount the device on the volume. During this call, the mount code should
    // look up and find this volume as an active child of the given device.
    //

    Status = IoMount(TRUE,
                     VolumeName,
                     VolumeNameLength,
                     DeviceName,
                     DeviceNameLength,
                     MOUNT_FLAG_LINKED,
                     IO_ACCESS_READ | IO_ACCESS_WRITE);

    if (!KSUCCESS(Status)) {
        goto VolumeArrivalEnd;
    }

    //
    // Determine whether or not this is the system volume.
    //

    PartitionInformationSize = sizeof(PARTITION_DEVICE_INFORMATION);
    Status = IoGetSetDeviceInformation(TargetDevice->DeviceId,
                                       &IoPartitionDeviceInformationUuid,
                                       &PartitionInformation,
                                       &PartitionInformationSize,
                                       FALSE);

    if ((KSUCCESS(Status)) &&
        (PartitionInformationSize == sizeof(PARTITION_DEVICE_INFORMATION))) {

        //
        // Get the boot partition identifiers.
        //

        BootInformationSize = sizeof(IO_BOOT_INFORMATION);
        Status = KeGetSetSystemInformation(SystemInformationIo,
                                           IoInformationBoot,
                                           &BootInformation,
                                           &BootInformationSize,
                                           FALSE);

        if ((KSUCCESS(Status)) &&
            (BootInformationSize == sizeof(IO_BOOT_INFORMATION))) {

            ASSERT(sizeof(BootInformation.SystemPartitionIdentifier) ==
                   sizeof(PartitionInformation.PartitionId));

            Match = RtlCompareMemory(
                            BootInformation.SystemPartitionIdentifier,
                            PartitionInformation.PartitionId,
                            sizeof(BootInformation.SystemPartitionIdentifier));

            if ((Match != FALSE) && (IoSystemVolume == NULL)) {
                IoSystemVolume = Volume;
                SystemVolume = TRUE;
            }
        }
    }

    //
    // If this is the system volume, then open the drivers directory and change
    // the kernel's current directory to the driver's directory.
    //

    if (SystemVolume != FALSE) {

        //
        // Copy the system volume path. Synchronization would be needed if this
        // path changes.
        //

        ASSERT(VolumeNameLength != 0);

        Status = IoOpen(TRUE,
                        NULL,
                        VolumeName,
                        VolumeNameLength,
                        IO_ACCESS_READ,
                        OPEN_FLAG_DIRECTORY,
                        0,
                        &VolumeHandle);

        if (!KSUCCESS(Status)) {
            RtlDebugPrint("Failed to open system volume: %x\n", Status);
            goto VolumeArrivalEnd;
        }

        //
        // Attempt to open the system directory.
        //

        Status = IoOpen(TRUE,
                        VolumeHandle,
                        IoSystemDirectoryPath,
                        RtlStringLength(IoSystemDirectoryPath) + 1,
                        IO_ACCESS_READ,
                        OPEN_FLAG_DIRECTORY,
                        0,
                        &SystemDirectoryHandle);

        if (!KSUCCESS(Status)) {
            RtlDebugPrint("Failed to open system directory '%s': %x\n",
                          IoSystemDirectoryPath,
                          Status);

            goto VolumeArrivalEnd;
        }

        //
        // Attempt to open the driver directory.
        //

        Status = IoOpen(TRUE,
                        SystemDirectoryHandle,
                        SYSTEM_DRIVERS_DIRECTORY,
                        sizeof(SYSTEM_DRIVERS_DIRECTORY),
                        IO_ACCESS_READ,
                        OPEN_FLAG_DIRECTORY,
                        0,
                        &DriversDirectoryHandle);

        if (!KSUCCESS(Status)) {
            RtlDebugPrint("Failed to open driver directory '%s/%s': %x\n",
                          IoSystemDirectoryPath,
                          SYSTEM_DRIVERS_DIRECTORY,
                          Status);

            goto VolumeArrivalEnd;
        }

        //
        // Now set the kernel's current working directory to the drivers
        // directory.
        //

        KernelProcess = PsGetKernelProcess();

        ASSERT(KernelProcess == PsGetCurrentProcess());

        PathPoint = &(DriversDirectoryHandle->PathPoint);
        IO_PATH_POINT_ADD_REFERENCE(PathPoint);
        KeAcquireQueuedLock(KernelProcess->Paths.Lock);

        ASSERT(KernelProcess->Paths.CurrentDirectory.PathEntry == NULL);
        ASSERT(KernelProcess->Paths.CurrentDirectory.MountPoint == NULL);

        IO_COPY_PATH_POINT(&(KernelProcess->Paths.CurrentDirectory), PathPoint);
        KeReleaseQueuedLock(KernelProcess->Paths.Lock);
    }

    //
    // Tell the memory manager about volumes that can contain page files.
    //

    if ((Volume->Device.Flags & DEVICE_FLAG_PAGING_DEVICE) != 0) {
        MmVolumeArrival(VolumeName, VolumeNameLength, SystemVolume);
    }

    //
    // Tell the process library about the new volume.
    //

    PsVolumeArrival(VolumeName, VolumeNameLength, SystemVolume);

    //
    // Attempt to start any devices that had previously failed as a volume with
    // more drivers is potentially here.
    //

    if (SystemVolume != FALSE) {
        IopQueueDeviceWork(IoRootDevice,
                           DeviceActionStart,
                           NULL,
                           DEVICE_ACTION_SEND_TO_SUBTREE);
    }

    Status = STATUS_SUCCESS;

VolumeArrivalEnd:
    if (VolumeName != NULL) {
        MmFreePagedPool(VolumeName);
    }

    if (DeviceName != NULL) {
        MmFreePagedPool(DeviceName);
    }

    if (FileObject != NULL) {
        IopFileObjectReleaseReference(FileObject);
    }

    if (VolumeHandle != NULL) {
        IoClose(VolumeHandle);
    }

    if (SystemDirectoryHandle != NULL) {
        IoClose(SystemDirectoryHandle);
    }

    if (DriversDirectoryHandle != NULL) {
        IoClose(DriversDirectoryHandle);
    }

    if (!KSUCCESS(Status)) {
        IopSetDeviceProblem((PDEVICE)Volume,
                            DeviceProblemFailedVolumeArrival,
                            Status);
    }

    //
    // Relase the reference on the volume taken when this work item was
    // scheduled.
    //

    ObReleaseReference(Volume);
    return;
}

VOID
IoVolumeAddReference (
    PVOLUME Volume
    )

/*++

Routine Description:

    This routine increments a volume's reference count.

Arguments:

    Volume - Supplies a pointer to a volume device.

Return Value:

    None.

--*/

{

    ULONG OldReferenceCount;

    OldReferenceCount = RtlAtomicAdd32(&(Volume->ReferenceCount), 1);

    ASSERT(OldReferenceCount < 0x10000000);

    return;
}

VOID
IoVolumeReleaseReference (
    PVOLUME Volume
    )

/*++

Routine Description:

    This routine decrements a volume's reference count.

Arguments:

    Volume - Supplies a pointer to a volume device.

Return Value:

    None.

--*/

{

    BOOL DestroyVolume;
    ULONG OldReferenceCount;
    PDEVICE TargetDevice;

    TargetDevice = Volume->Device.TargetDevice;
    KeAcquireSharedExclusiveLockExclusive(TargetDevice->Lock);
    OldReferenceCount = RtlAtomicAdd32(&(Volume->ReferenceCount), (ULONG)-1);

    ASSERT((OldReferenceCount != 0) && (OldReferenceCount < 0x10000000));

    if (OldReferenceCount == 2) {
        DestroyVolume = TRUE;
        KeAcquireSharedExclusiveLockExclusive(Volume->Device.Lock);

        //
        // If the volume is already removed or in the process of being
        // unmounted there is no work to do. It's too late.
        //

        if ((Volume->Device.State == DeviceRemoved) ||
            ((Volume->Flags & VOLUME_FLAG_UNMOUNTING) != 0)) {

            DestroyVolume = FALSE;

        //
        // Prepare the volume for the destruction path.
        //

        } else {

            //
            // Mark that the volume is in the middle of the unmounting process
            // in order to prevent new path walks from succeeding.
            //

            Volume->Flags |= VOLUME_FLAG_UNMOUNTING;

            //
            // Before proceeding through the removal process, unsignal the
            // volume. The volume lookup routine waits on the device for its
            // state to settle.
            //

            ObSignalObject(Volume, SignalOptionUnsignal);

            //
            // Take a object manager reference on the volume. As soon as the
            // locks are released, another thread could come through and
            // release the last volume reference and, in turn, the last object
            // reference.
            //

            ObAddReference(Volume);
        }

        KeReleaseSharedExclusiveLockExclusive(Volume->Device.Lock);
        KeReleaseSharedExclusiveLockExclusive(TargetDevice->Lock);
        if (DestroyVolume != FALSE) {
            IopDestroyVolume(Volume);
            ObReleaseReference(Volume);
        }

    } else if (OldReferenceCount == 1) {
        KeReleaseSharedExclusiveLockExclusive(TargetDevice->Lock);

        //
        // Release the volume path entry if the volume is about to be taken out
        // of comission.
        //

        if (Volume->PathEntry != NULL) {

            ASSERT(Volume->PathEntry->Parent == NULL);

            IoPathEntryReleaseReference(Volume->PathEntry);
        }

        ObReleaseReference(Volume);

    } else {
        KeReleaseSharedExclusiveLockExclusive(TargetDevice->Lock);
    }

    return;
}

KSTATUS
IopRemoveDevicePaths (
    PDEVICE Device
    )

/*++

Routine Description:

    This routine takes the device's paths offline.

Arguments:

    Device - Supplies a pointer to the departing device.

Return Value:

    Status code.

--*/

{

    PSTR DevicePath;
    PSTR Path;
    ULONG PathSize;
    PATH_POINT RootPathPoint;
    KSTATUS Status;

    ASSERT(IS_DEVICE_OR_VOLUME(Device));

    //
    // If it's a volume, it should be unmounting.
    //

    ASSERT((Device->Header.Type != ObjectVolume) ||
           ((((PVOLUME)Device)->Flags & VOLUME_FLAG_UNMOUNTING) != 0));

    ASSERT((Device->State == DeviceAwaitingRemoval) ||
           (Device->State == DeviceRemoved));

    DevicePath = NULL;
    RootPathPoint.PathEntry = NULL;

    //
    // If the device is a volume, it might have contained a page file, notify
    // the memory is volume is being removed.
    //

    if ((Device->Flags & DEVICE_FLAG_PAGING_DEVICE) != 0) {
        Status = MmVolumeRemoval(Device);
        if (!KSUCCESS(Status)) {
            goto RemoveDevicePathsEnd;
        }
    }

    //
    // Retrieve a path to the device's root. If this fails, then the
    // removal process needs to be rolled back. The system cannot close any
    // opens paths or remove mount points correctly.
    //

    DevicePath = ObGetFullPath(Device, IO_ALLOCATION_TAG);
    if (DevicePath == NULL) {
        Status = STATUS_INSUFFICIENT_RESOURCES;
        goto RemoveDevicePathsEnd;
    }

    //
    // Open a path to the device root. If this fails, it should be because
    // the parent path is marked closing, or the root lookup call never
    // went through because the volume is set as "unmounting". In either
    // case, there are no paths or mount points to destroy. Count it as
    // success.
    //

    Path = DevicePath;
    PathSize = RtlStringLength(Path) + 1;
    Status = IopPathWalk(TRUE,
                         NULL,
                         &Path,
                         &PathSize,
                         OPEN_FLAG_DIRECTORY,
                         IoObjectInvalid,
                         NULL,
                         FILE_PERMISSION_NONE,
                         &RootPathPoint);

    if (!KSUCCESS(Status)) {

        ASSERT((Status == STATUS_PATH_NOT_FOUND) ||
               (Status == STATUS_DEVICE_NOT_CONNECTED));

        Status = STATUS_SUCCESS;
        goto RemoveDevicePathsEnd;
    }

    //
    // Forcefully remove all mount points that exist under the root.
    //

    IopRemoveMountPoints(&RootPathPoint);

    //
    // Clean the cached path entries. Do this after removing mount points as
    // the work above closed a bunch of path entries.
    //

    IopPathCleanCache(RootPathPoint.PathEntry);
    Status = STATUS_SUCCESS;

RemoveDevicePathsEnd:
    if (DevicePath != NULL) {
        MmFreePagedPool(DevicePath);
    }

    if (RootPathPoint.PathEntry != NULL) {
        IO_PATH_POINT_RELEASE_REFERENCE(&RootPathPoint);
    }

    return Status;
}

//
// --------------------------------------------------------- Internal Functions
//

VOID
IopDestroyVolume (
    PVOLUME Volume
    )

/*++

Routine Description:

    This routine attempts to destroy the given volume by queuing its removal.
    Remove is not queued if the volume is busy.

Arguments:

    Volume - Supplies a pointer to the volume to be destroyed.

Return Value:

    Status code.

--*/

{

    ULONG Flags;
    KSTATUS Status;
    PDEVICE TargetDevice;

    ASSERT(Volume->Device.Header.Type == ObjectVolume);
    ASSERT((Volume->Flags & VOLUME_FLAG_UNMOUNTING) != 0);

    TargetDevice = Volume->Device.TargetDevice;

    //
    // Flush the volume. This does not need to be synchronized, because the
    // underlying device is explicitly flushed after in hope of batching writes
    // to the device.
    //

    Status = IopFlushFileObjects(Volume->Device.DeviceId, 0, NULL);
    if (!KSUCCESS(Status)) {
        Volume->Flags &= ~VOLUME_FLAG_UNMOUNTING;
        IopSetDeviceProblem(&(Volume->Device),
                            DeviceProblemFailedVolumeRemoval,
                            Status);

        goto DestroyVolumeEnd;
    }

    //
    // Since volumes and their target devices are 1:1, flush the device's
    // cache entries now that the volume has been closed and flushed. In the
    // future, the partition manager will have to trigger the device cache
    // flush once all the volumes are unmounted.
    //

    Status = IopFlushFileObjects(TargetDevice->DeviceId, 0, NULL);
    if (!KSUCCESS(Status)) {
        Volume->Flags &= ~VOLUME_FLAG_UNMOUNTING;
        IopSetDeviceProblem(&(Volume->Device),
                            DeviceProblemFailedVolumeRemoval,
                            Status);

        goto DestroyVolumeEnd;
    }

    //
    // TODO: Notify the user that the device is now safe to remove.
    //

    //
    // Remove any cached path entries that are below the volume root.
    //

    if (Volume->PathEntry != NULL) {
        IopPathCleanCache(Volume->PathEntry);
    }

    //
    // Start the removal process for this volume. There isn't much recourse if
    // this fails other than to roll it back and let the caller know.
    //

    Flags = DEVICE_ACTION_SEND_TO_SUBTREE | DEVICE_ACTION_OPEN_QUEUE;
    Status = IopQueueDeviceWork(&(Volume->Device),
                                DeviceActionPrepareRemove,
                                NULL,
                                Flags);

    //
    // If there was a queue failure, set the problem state. Do not call the
    // queue failure handler as that might incorrectly roll back the device
    // tree state. Just assume that no parent is waiting on this device's state
    // and that is is safe to ignore the failure.
    //

    if (!KSUCCESS(Status) && (Status != STATUS_DEVICE_QUEUE_CLOSING)) {
        Volume->Flags &= ~VOLUME_FLAG_UNMOUNTING;
        IopSetDeviceProblem(&(Volume->Device),
                            DeviceProblemFailedToQueuePrepareRemove,
                            Status);

        goto DestroyVolumeEnd;
    }

    //
    // If this was the system volume, unset the global variable.
    //

    if (Volume == IoSystemVolume) {
        IoSystemVolume = NULL;
    }

DestroyVolumeEnd:
    return;
}

PSTR
IopGetNewVolumeName (
    VOID
    )

/*++

Routine Description:

    This routine returns a name for a volume that does not collide with any
    existing volume names.

Arguments:

    None.

Return Value:

    Returns a new volume name on success, allocated from paged pool.

    NULL on failure.

--*/

{

    PVOID ExistingVolume;
    PSTR NewName;
    ULONG NewNameLength;
    ULONG VolumeIndex;

    NewName = MmAllocatePagedPool(VOLUME_NAME_LENGTH, FI_ALLOCATION_TAG);
    if (NewName == NULL) {
        goto GetNewVolumeNameEnd;
    }

    //
    // Iterate through possible volume names. If the volume doesn't exist,
    // return it.
    //

    for (VolumeIndex = 0; VolumeIndex < MAX_VOLUMES; VolumeIndex += 1) {
        NewNameLength = RtlPrintToString(NewName,
                                         VOLUME_NAME_LENGTH,
                                         CharacterEncodingDefault,
                                         "Volume%d",
                                         VolumeIndex);

        if (NewNameLength > VOLUME_NAME_LENGTH) {
            NewNameLength = VOLUME_NAME_LENGTH;
        }

        ExistingVolume = ObFindObject(NewName,
                                      NewNameLength,
                                      IoVolumeDirectory);

        if (ExistingVolume == NULL) {
            goto GetNewVolumeNameEnd;
        }

        //
        // The object exists, release the extra reference added by "finding" it.
        //

        ObReleaseReference(ExistingVolume);
        ExistingVolume = NULL;
    }

    //
    // There are too many volumes in the system! Give up.
    //

    MmFreePagedPool(NewName);
    NewName = NULL;

GetNewVolumeNameEnd:
    return NewName;
}

