/*
 * COPYRIGHT:        See COPYING in the top level directory
 * PROJECT:          ReactOS kernel
 * FILE:             drivers/fs/vfat/misc.c
 * PURPOSE:          VFAT Filesystem
 * PROGRAMMER:
 *
 */

/* INCLUDES *****************************************************************/

#define NDEBUG
#include "vfat.h"

/* GLOBALS ******************************************************************/

const char* MajorFunctionNames[] =
{
     "IRP_MJ_CREATE",
     "IRP_MJ_CREATE_NAMED_PIPE",
     "IRP_MJ_CLOSE",
     "IRP_MJ_READ",
     "IRP_MJ_WRITE",
     "IRP_MJ_QUERY_INFORMATION",
     "IRP_MJ_SET_INFORMATION",
     "IRP_MJ_QUERY_EA",
     "IRP_MJ_SET_EA",
     "IRP_MJ_FLUSH_BUFFERS",
     "IRP_MJ_QUERY_VOLUME_INFORMATION",
     "IRP_MJ_SET_VOLUME_INFORMATION",
     "IRP_MJ_DIRECTORY_CONTROL",
     "IRP_MJ_FILE_SYSTEM_CONTROL",
     "IRP_MJ_DEVICE_CONTROL",
     "IRP_MJ_INTERNAL_DEVICE_CONTROL",
     "IRP_MJ_SHUTDOWN",
     "IRP_MJ_LOCK_CONTROL",
     "IRP_MJ_CLEANUP",
     "IRP_MJ_CREATE_MAILSLOT",
     "IRP_MJ_QUERY_SECURITY",
     "IRP_MJ_SET_SECURITY",
     "IRP_MJ_POWER",
     "IRP_MJ_SYSTEM_CONTROL",
     "IRP_MJ_DEVICE_CHANGE",
     "IRP_MJ_QUERY_QUOTA",
     "IRP_MJ_SET_QUOTA",
     "IRP_MJ_PNP",
     "IRP_MJ_MAXIMUM_FUNCTION"
};

/* FUNCTIONS ****************************************************************/

static LONG QueueCount = 0;

static NTSTATUS VfatLockControl(
   IN PVFAT_IRP_CONTEXT IrpContext
   )
{
   PVFATFCB Fcb;
   NTSTATUS Status;

   DPRINT("VfatLockControl(IrpContext %p)\n", IrpContext);

   ASSERT(IrpContext);

   Fcb = (PVFATFCB)IrpContext->FileObject->FsContext;

   if (IrpContext->DeviceObject == VfatGlobalData->DeviceObject)
   {
      Status = STATUS_INVALID_DEVICE_REQUEST;
      goto Fail;
   }

   if (*Fcb->Attributes & FILE_ATTRIBUTE_DIRECTORY)
   {
      Status = STATUS_INVALID_PARAMETER;
      goto Fail;
   }

   Status = FsRtlProcessFileLock(&Fcb->FileLock,
                                 IrpContext->Irp,
                                 NULL
                                 );

   VfatFreeIrpContext(IrpContext);
   return Status;

Fail:;
   IrpContext->Irp->IoStatus.Status = Status;
   IoCompleteRequest(IrpContext->Irp, (CCHAR)(NT_SUCCESS(Status) ? IO_DISK_INCREMENT : IO_NO_INCREMENT));
   VfatFreeIrpContext(IrpContext);
   return Status;
}

static NTSTATUS
VfatDispatchRequest (IN PVFAT_IRP_CONTEXT IrpContext)
{
    DPRINT ("VfatDispatchRequest (IrpContext %p), is called for %s\n", IrpContext,
	    IrpContext->MajorFunction >= IRP_MJ_MAXIMUM_FUNCTION ? "????" : MajorFunctionNames[IrpContext->MajorFunction]);

   ASSERT(IrpContext);

   switch (IrpContext->MajorFunction)
   {
      case IRP_MJ_CLOSE:
         return VfatClose (IrpContext);
      case IRP_MJ_CREATE:
         return VfatCreate (IrpContext);
      case IRP_MJ_READ:
         return VfatRead (IrpContext);
      case IRP_MJ_WRITE:
         return VfatWrite (IrpContext);
      case IRP_MJ_FILE_SYSTEM_CONTROL:
         return VfatFileSystemControl(IrpContext);
      case IRP_MJ_QUERY_INFORMATION:
         return VfatQueryInformation (IrpContext);
      case IRP_MJ_SET_INFORMATION:
         return VfatSetInformation (IrpContext);
      case IRP_MJ_DIRECTORY_CONTROL:
         return VfatDirectoryControl(IrpContext);
      case IRP_MJ_QUERY_VOLUME_INFORMATION:
         return VfatQueryVolumeInformation(IrpContext);
      case IRP_MJ_SET_VOLUME_INFORMATION:
         return VfatSetVolumeInformation(IrpContext);
      case IRP_MJ_LOCK_CONTROL:
         return VfatLockControl(IrpContext);
      case IRP_MJ_CLEANUP:
         return VfatCleanup(IrpContext);
      case IRP_MJ_FLUSH_BUFFERS:
         return VfatFlush(IrpContext);
      case IRP_MJ_PNP:
         return VfatPnp(IrpContext);
      default:
         DPRINT1 ("Unexpected major function %x\n", IrpContext->MajorFunction);
         IrpContext->Irp->IoStatus.Status = STATUS_DRIVER_INTERNAL_ERROR;
         IoCompleteRequest(IrpContext->Irp, IO_NO_INCREMENT);
         VfatFreeIrpContext(IrpContext);
         return STATUS_DRIVER_INTERNAL_ERROR;
   }
}

NTSTATUS NTAPI VfatBuildRequest (
        IN PDEVICE_OBJECT DeviceObject,
        IN PIRP Irp)
{
   NTSTATUS Status;
   PVFAT_IRP_CONTEXT IrpContext;

   DPRINT ("VfatBuildRequest (DeviceObject %p, Irp %p)\n", DeviceObject, Irp);

   ASSERT(DeviceObject);
   ASSERT(Irp);
   IrpContext = VfatAllocateIrpContext(DeviceObject, Irp);
   if (IrpContext == NULL)
   {
      Status = STATUS_INSUFFICIENT_RESOURCES;
      Irp->IoStatus.Status = Status;
      IoCompleteRequest (Irp, IO_NO_INCREMENT);
   }
   else
   {
      FsRtlEnterFileSystem();
      Status = VfatDispatchRequest (IrpContext);
      FsRtlExitFileSystem();
   }
   return Status;
}

VOID VfatFreeIrpContext (PVFAT_IRP_CONTEXT IrpContext)
{
   ASSERT(IrpContext);
   ExFreeToNPagedLookasideList(&VfatGlobalData->IrpContextLookasideList, IrpContext);
}

PVFAT_IRP_CONTEXT VfatAllocateIrpContext(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
   PVFAT_IRP_CONTEXT IrpContext;
   /*PIO_STACK_LOCATION Stack;*/
   UCHAR MajorFunction;
   DPRINT ("VfatAllocateIrpContext(DeviceObject %p, Irp %p)\n", DeviceObject, Irp);

   ASSERT(DeviceObject);
   ASSERT(Irp);

   IrpContext = ExAllocateFromNPagedLookasideList(&VfatGlobalData->IrpContextLookasideList);
   if (IrpContext)
   {
      RtlZeroMemory(IrpContext, sizeof(VFAT_IRP_CONTEXT));
      IrpContext->Irp = Irp;
      IrpContext->DeviceObject = DeviceObject;
      IrpContext->DeviceExt = DeviceObject->DeviceExtension;
      IrpContext->Stack = IoGetCurrentIrpStackLocation(Irp);
      ASSERT(IrpContext->Stack);
      MajorFunction = IrpContext->MajorFunction = IrpContext->Stack->MajorFunction;
      IrpContext->MinorFunction = IrpContext->Stack->MinorFunction;
      IrpContext->FileObject = IrpContext->Stack->FileObject;
      IrpContext->Flags = 0;
      if (MajorFunction == IRP_MJ_FILE_SYSTEM_CONTROL ||
          MajorFunction == IRP_MJ_DEVICE_CONTROL ||
          MajorFunction == IRP_MJ_SHUTDOWN)
      {
         IrpContext->Flags |= IRPCONTEXT_CANWAIT;
      }
      else if (MajorFunction != IRP_MJ_CLEANUP &&
               MajorFunction != IRP_MJ_CLOSE &&
               IoIsOperationSynchronous(Irp))
      {
         IrpContext->Flags |= IRPCONTEXT_CANWAIT;
      }
      KeInitializeEvent(&IrpContext->Event, NotificationEvent, FALSE);
      IrpContext->RefCount = 0;
   }
   return IrpContext;
}

static WORKER_THREAD_ROUTINE VfatDoRequest;

static VOID NTAPI VfatDoRequest (PVOID IrpContext)
{
   InterlockedDecrement(&QueueCount);
   DPRINT ("VfatDoRequest (IrpContext %p), MajorFunction %x, %d\n", IrpContext, ((PVFAT_IRP_CONTEXT)IrpContext)->MajorFunction, QueueCount);
   FsRtlEnterFileSystem();
   VfatDispatchRequest((PVFAT_IRP_CONTEXT)IrpContext);
   FsRtlExitFileSystem();

}

NTSTATUS VfatQueueRequest(PVFAT_IRP_CONTEXT IrpContext)
{
   InterlockedIncrement(&QueueCount);
   DPRINT ("VfatQueueRequest (IrpContext %p), %d\n", IrpContext, QueueCount);

   ASSERT(IrpContext != NULL);
   ASSERT(IrpContext->Irp != NULL);

   IrpContext->Flags |= IRPCONTEXT_CANWAIT;
   IoMarkIrpPending (IrpContext->Irp);
   ExInitializeWorkItem (&IrpContext->WorkQueueItem, VfatDoRequest, IrpContext);
   ExQueueWorkItem(&IrpContext->WorkQueueItem, CriticalWorkQueue);
   return STATUS_PENDING;
}

PVOID VfatGetUserBuffer(IN PIRP Irp)
{
   ASSERT(Irp);

   if (Irp->MdlAddress)
   {
      /* This call may be in the paging path, so use maximum priority */
      /* FIXME: call with normal priority in the non-paging path */
      return MmGetSystemAddressForMdlSafe(Irp->MdlAddress, HighPagePriority);
   }
   else
   {
      return Irp->UserBuffer;
   }
}

NTSTATUS VfatLockUserBuffer(IN PIRP Irp, IN ULONG Length, IN LOCK_OPERATION Operation)
{
   ASSERT(Irp);

   if (Irp->MdlAddress)
   {
      return STATUS_SUCCESS;
   }

   IoAllocateMdl(Irp->UserBuffer, Length, FALSE, FALSE, Irp);

   if (!Irp->MdlAddress)
   {
      return STATUS_INSUFFICIENT_RESOURCES;
   }

   MmProbeAndLockPages(Irp->MdlAddress, Irp->RequestorMode, Operation);

   return STATUS_SUCCESS;
}

