[FASTFAT]
[reactos.git] / reactos / drivers / filesystems / fastfat / dirwr.c
index 1444665..681d4e3 100644 (file)
@@ -1,8 +1,11 @@
 /*
  * COPYRIGHT:        See COPYING in the top level directory
  * PROJECT:          ReactOS kernel
- * FILE:             drivers/fs/vfat/dirwr.c
+ * FILE:             drivers/filesystems/fastfat/dirwr.c
  * PURPOSE:          VFAT Filesystem : write in directory
+ * PROGRAMMER:       Rex Jolliff (rex@lvcablemodem.com)
+ *                   Herve Poussineau (reactos@poussine.freesurf.fr)
+ *                   Pierre Schweitzer (pierre@reactos.org)
  *
  */
 
@@ -50,19 +53,93 @@ VfatUpdateEntry(
 
     Offset.u.HighPart = 0;
     Offset.u.LowPart = dirIndex * SizeDirEntry;
-    if (CcPinRead(pFcb->parentFcb->FileObject, &Offset, SizeDirEntry,
-        TRUE, &Context, (PVOID*)&PinEntry))
+    _SEH2_TRY
     {
-        pFcb->Flags &= ~FCB_IS_DIRTY;
-        RtlCopyMemory(PinEntry, &pFcb->entry, SizeDirEntry);
+        CcPinRead(pFcb->parentFcb->FileObject, &Offset, SizeDirEntry, PIN_WAIT, &Context, (PVOID*)&PinEntry);
+    }
+    _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
+    {
+        DPRINT1("Failed write to \'%wZ\'.\n", &pFcb->parentFcb->PathNameU);
+        _SEH2_YIELD(return _SEH2_GetExceptionCode());
+    }
+    _SEH2_END;
+
+    pFcb->Flags &= ~FCB_IS_DIRTY;
+    RtlCopyMemory(PinEntry, &pFcb->entry, SizeDirEntry);
+    CcSetDirtyPinnedData(Context, NULL);
+    CcUnpinData(Context);
+    return STATUS_SUCCESS;
+}
+
+/*
+ * rename an existing FAT entry
+ */
+NTSTATUS
+vfatRenameEntry(
+    IN PDEVICE_EXTENSION DeviceExt,
+    IN PVFATFCB pFcb,
+    IN PUNICODE_STRING FileName,
+    IN BOOLEAN CaseChangeOnly)
+{
+    OEM_STRING NameA;
+    ULONG StartIndex;
+    PVOID Context = NULL;
+    LARGE_INTEGER Offset;
+    PFATX_DIR_ENTRY pDirEntry;
+    NTSTATUS Status;
+
+    DPRINT("vfatRenameEntry(%p, %p, %wZ, %d)\n", DeviceExt, pFcb, FileName, CaseChangeOnly);
+
+    if (pFcb->Flags & FCB_IS_FATX_ENTRY)
+    {
+        VFAT_DIRENTRY_CONTEXT DirContext;
+
+        /* Open associated dir entry */
+        StartIndex = pFcb->startIndex;
+        Offset.u.HighPart = 0;
+        Offset.u.LowPart = (StartIndex * sizeof(FATX_DIR_ENTRY) / PAGE_SIZE) * PAGE_SIZE;
+        _SEH2_TRY
+        {
+            CcPinRead(pFcb->parentFcb->FileObject, &Offset, PAGE_SIZE, PIN_WAIT, &Context, (PVOID*)&pDirEntry);
+        }
+        _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
+        {
+            DPRINT1("CcPinRead(Offset %x:%x, Length %d) failed\n", Offset.u.HighPart, Offset.u.LowPart, PAGE_SIZE);
+            _SEH2_YIELD(return _SEH2_GetExceptionCode());
+        }
+        _SEH2_END;
+
+        pDirEntry = &pDirEntry[StartIndex % (PAGE_SIZE / sizeof(FATX_DIR_ENTRY))];
+
+        /* Set file name */
+        NameA.Buffer = (PCHAR)pDirEntry->Filename;
+        NameA.Length = 0;
+        NameA.MaximumLength = 42;
+        RtlUnicodeStringToOemString(&NameA, FileName, FALSE);
+        pDirEntry->FilenameLength = (unsigned char)NameA.Length;
+
+        /* Update FCB */
+        DirContext.ShortNameU.Length = 0;
+        DirContext.ShortNameU.MaximumLength = 0;
+        DirContext.ShortNameU.Buffer = NULL;
+        DirContext.LongNameU = *FileName;
+        DirContext.DirEntry.FatX = *pDirEntry;
+
         CcSetDirtyPinnedData(Context, NULL);
         CcUnpinData(Context);
-        return STATUS_SUCCESS;
+
+        Status = vfatUpdateFCB(DeviceExt, pFcb, &DirContext, pFcb->parentFcb);
+        if (NT_SUCCESS(Status))
+        {
+            CcPurgeCacheSection(&pFcb->parentFcb->SectionObjectPointers, NULL, 0, FALSE);
+        }
+
+        return Status;
     }
     else
     {
-        DPRINT1("Failed write to \'%wZ\'.\n", &pFcb->parentFcb->PathNameU);
-        return STATUS_UNSUCCESSFUL;
+        /* This we cannot handle properly, move file - would likely need love */
+        return VfatMoveEntry(DeviceExt, pFcb, FileName, pFcb->parentFcb);
     }
 }
 
@@ -100,11 +177,16 @@ vfatFindDirSpace(
             {
                 CcUnpinData(Context);
             }
-            if (!CcPinRead(pDirFcb->FileObject, &FileOffset, DeviceExt->FatInfo.BytesPerCluster,
-                      TRUE, &Context, (PVOID*)&pFatEntry))
+            _SEH2_TRY
             {
-                return FALSE;
+                CcPinRead(pDirFcb->FileObject, &FileOffset, DeviceExt->FatInfo.BytesPerCluster, PIN_WAIT, &Context, (PVOID*)&pFatEntry);
+            }
+            _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
+            {
+                _SEH2_YIELD(return FALSE);
             }
+            _SEH2_END;
+
             FileOffset.u.LowPart += DeviceExt->FatInfo.BytesPerCluster;
         }
         if (ENTRY_END(DeviceExt, pFatEntry))
@@ -156,11 +238,16 @@ vfatFindDirSpace(
             /* clear the new dir cluster */
             FileOffset.u.LowPart = (ULONG)(pDirFcb->RFCB.FileSize.QuadPart -
                                            DeviceExt->FatInfo.BytesPerCluster);
-            if (!CcPinRead(pDirFcb->FileObject, &FileOffset, DeviceExt->FatInfo.BytesPerCluster,
-                      TRUE, &Context, (PVOID*)&pFatEntry))
+            _SEH2_TRY
             {
-                return FALSE;
+                CcPinRead(pDirFcb->FileObject, &FileOffset, DeviceExt->FatInfo.BytesPerCluster, PIN_WAIT, &Context, (PVOID*)&pFatEntry);
+            }
+            _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
+            {
+                _SEH2_YIELD(return FALSE);
             }
+            _SEH2_END;
+
             if (DeviceExt->Flags & VCB_IS_FATX)
                 memset(pFatEntry, 0xff, DeviceExt->FatInfo.BytesPerCluster);
             else
@@ -170,11 +257,16 @@ vfatFindDirSpace(
         {
             /* clear the entry after the last new entry */
             FileOffset.u.LowPart = (*start + nbSlots) * SizeDirEntry;
-            if (!CcPinRead(pDirFcb->FileObject, &FileOffset, SizeDirEntry,
-                TRUE, &Context, (PVOID*)&pFatEntry))
+            _SEH2_TRY
             {
-                return FALSE;
+                CcPinRead(pDirFcb->FileObject, &FileOffset, SizeDirEntry, PIN_WAIT, &Context, (PVOID*)&pFatEntry);
             }
+            _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
+            {
+                _SEH2_YIELD(return FALSE);
+            }
+            _SEH2_END;
+
             if (DeviceExt->Flags & VCB_IS_FATX)
                 memset(pFatEntry, 0xff, SizeDirEntry);
             else
@@ -200,7 +292,8 @@ FATAddEntry(
     IN PVFATFCB* Fcb,
     IN PVFATFCB ParentFcb,
     IN ULONG RequestedOptions,
-    IN UCHAR ReqAttr)
+    IN UCHAR ReqAttr,
+    IN PVFAT_MOVE_CONTEXT MoveContext)
 {
     PVOID Context = NULL;
     PFAT_DIR_ENTRY pFatEntry;
@@ -274,6 +367,18 @@ FATAddEntry(
             {
                 break;
             }
+            else if (MoveContext)
+            {
+                ASSERT(*Fcb);
+                if (strncmp((char *)SearchContext.DirEntry.Fat.ShortName, (char *)(*Fcb)->entry.Fat.ShortName, 11) == 0)
+                {
+                    if (MoveContext->InPlace)
+                    {
+                        ASSERT(SearchContext.DirEntry.Fat.FileSize == MoveContext->FileSize);
+                        break;
+                    }
+                } 
+            }
         }
         if (i == 100) /* FIXME : what to do after this ? */
         {
@@ -385,6 +490,13 @@ FATAddEntry(
     DirContext.DirEntry.Fat.UpdateDate = DirContext.DirEntry.Fat.CreationDate;
     DirContext.DirEntry.Fat.UpdateTime = DirContext.DirEntry.Fat.CreationTime;
     DirContext.DirEntry.Fat.AccessDate = DirContext.DirEntry.Fat.CreationDate;
+    /* If it's moving, preserve creation time and file size */
+    if (MoveContext != NULL)
+    {
+        DirContext.DirEntry.Fat.CreationDate = MoveContext->CreationDate;
+        DirContext.DirEntry.Fat.CreationTime = MoveContext->CreationTime;
+        DirContext.DirEntry.Fat.FileSize = MoveContext->FileSize;
+    }
 
     if (needLong)
     {
@@ -423,17 +535,36 @@ FATAddEntry(
     DirContext.DirIndex = DirContext.StartIndex + nbSlots - 1;
     if (RequestedOptions & FILE_DIRECTORY_FILE)
     {
-        CurrentCluster = 0;
-        Status = NextCluster(DeviceExt, 0, &CurrentCluster, TRUE);
-        if (CurrentCluster == 0xffffffff || !NT_SUCCESS(Status))
+        /* If we aren't moving, use next */
+        if (MoveContext == NULL)
         {
-            ExFreePoolWithTag(Buffer, TAG_VFAT);
-            if (!NT_SUCCESS(Status))
+            CurrentCluster = 0;
+            Status = NextCluster(DeviceExt, 0, &CurrentCluster, TRUE);
+            if (CurrentCluster == 0xffffffff || !NT_SUCCESS(Status))
             {
-                return Status;
+                ExFreePoolWithTag(Buffer, TAG_VFAT);
+                if (!NT_SUCCESS(Status))
+                {
+                    return Status;
+                }
+                return STATUS_DISK_FULL;
             }
-            return STATUS_DISK_FULL;
         }
+        else
+        {
+            CurrentCluster = MoveContext->FirstCluster;
+        }
+
+        if (DeviceExt->FatInfo.FatType == FAT32)
+        {
+            DirContext.DirEntry.Fat.FirstClusterHigh = (unsigned short)(CurrentCluster >> 16);
+        }
+        DirContext.DirEntry.Fat.FirstCluster = (unsigned short)CurrentCluster;
+    }
+    else if (MoveContext != NULL)
+    {
+        CurrentCluster = MoveContext->FirstCluster;
+
         if (DeviceExt->FatInfo.FatType == FAT32)
         {
             DirContext.DirEntry.Fat.FirstClusterHigh = (unsigned short)(CurrentCluster >> 16);
@@ -447,12 +578,17 @@ FATAddEntry(
     if (DirContext.StartIndex / i == DirContext.DirIndex / i)
     {
         /* one cluster */
-        if (!CcPinRead(ParentFcb->FileObject, &FileOffset, nbSlots * sizeof(FAT_DIR_ENTRY),
-                  TRUE, &Context, (PVOID*)&pFatEntry))
+        _SEH2_TRY
+        {
+            CcPinRead(ParentFcb->FileObject, &FileOffset, nbSlots * sizeof(FAT_DIR_ENTRY), PIN_WAIT, &Context, (PVOID*)&pFatEntry);
+        }
+        _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
         {
             ExFreePoolWithTag(Buffer, TAG_VFAT);
-            return STATUS_UNSUCCESSFUL;
+            _SEH2_YIELD(return _SEH2_GetExceptionCode());
         }
+        _SEH2_END;
+
         if (nbSlots > 1)
         {
             RtlCopyMemory(pFatEntry, Buffer, (nbSlots - 1) * sizeof(FAT_DIR_ENTRY));
@@ -465,23 +601,30 @@ FATAddEntry(
         size = DeviceExt->FatInfo.BytesPerCluster -
                (DirContext.StartIndex * sizeof(FAT_DIR_ENTRY)) % DeviceExt->FatInfo.BytesPerCluster;
         i = size / sizeof(FAT_DIR_ENTRY);
-        if (!CcPinRead(ParentFcb->FileObject, &FileOffset, size, TRUE,
-                  &Context, (PVOID*)&pFatEntry))
+        _SEH2_TRY
+        {
+            CcPinRead(ParentFcb->FileObject, &FileOffset, size, PIN_WAIT, &Context, (PVOID*)&pFatEntry);
+        }
+        _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
         {
             ExFreePoolWithTag(Buffer, TAG_VFAT);
-            return STATUS_UNSUCCESSFUL;
+            _SEH2_YIELD(return _SEH2_GetExceptionCode());
         }
+        _SEH2_END;
         RtlCopyMemory(pFatEntry, Buffer, size);
         CcSetDirtyPinnedData(Context, NULL);
         CcUnpinData(Context);
         FileOffset.u.LowPart += size;
-        if (!CcPinRead(ParentFcb->FileObject, &FileOffset,
-                  nbSlots * sizeof(FAT_DIR_ENTRY) - size,
-                  TRUE, &Context, (PVOID*)&pFatEntry))
+        _SEH2_TRY
+        {
+            CcPinRead(ParentFcb->FileObject, &FileOffset, nbSlots * sizeof(FAT_DIR_ENTRY) - size, PIN_WAIT, &Context, (PVOID*)&pFatEntry);
+        }
+        _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
         {
             ExFreePoolWithTag(Buffer, TAG_VFAT);
-            return STATUS_UNSUCCESSFUL;
+            _SEH2_YIELD(return _SEH2_GetExceptionCode());
         }
+        _SEH2_END;
         if (nbSlots - 1 > i)
         {
             RtlCopyMemory(pFatEntry, (PVOID)(Buffer + size), (nbSlots - 1 - i) * sizeof(FAT_DIR_ENTRY));
@@ -491,7 +634,15 @@ FATAddEntry(
     CcSetDirtyPinnedData(Context, NULL);
     CcUnpinData(Context);
 
-    Status = vfatMakeFCBFromDirEntry(DeviceExt, ParentFcb, &DirContext, Fcb);
+    if (MoveContext != NULL)
+    {
+        /* We're modifying an existing FCB - likely rename/move */
+        Status = vfatUpdateFCB(DeviceExt, *Fcb, &DirContext, ParentFcb);
+    }
+    else
+    {
+        Status = vfatMakeFCBFromDirEntry(DeviceExt, ParentFcb, &DirContext, Fcb);
+    }
     if (!NT_SUCCESS(Status))
     {
         ExFreePoolWithTag(Buffer, TAG_VFAT);
@@ -504,19 +655,27 @@ FATAddEntry(
     if (RequestedOptions & FILE_DIRECTORY_FILE)
     {
         FileOffset.QuadPart = 0;
-        if (!CcPinRead((*Fcb)->FileObject, &FileOffset, DeviceExt->FatInfo.BytesPerCluster, TRUE,
-                  &Context, (PVOID*)&pFatEntry))
+        _SEH2_TRY
+        {
+            CcPinRead((*Fcb)->FileObject, &FileOffset, DeviceExt->FatInfo.BytesPerCluster, PIN_WAIT, &Context, (PVOID*)&pFatEntry);
+        }
+        _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
         {
             ExFreePoolWithTag(Buffer, TAG_VFAT);
-            return STATUS_UNSUCCESSFUL;
+            _SEH2_YIELD(return _SEH2_GetExceptionCode());
         }
-        /* clear the new directory cluster */
-        RtlZeroMemory(pFatEntry, DeviceExt->FatInfo.BytesPerCluster);
-        /* create '.' and '..' */
-        RtlCopyMemory(&pFatEntry[0].Attrib, &DirContext.DirEntry.Fat.Attrib, sizeof(FAT_DIR_ENTRY) - 11);
-        RtlCopyMemory(pFatEntry[0].ShortName, ".          ", 11);
-        RtlCopyMemory(&pFatEntry[1].Attrib, &DirContext.DirEntry.Fat.Attrib, sizeof(FAT_DIR_ENTRY) - 11);
-        RtlCopyMemory(pFatEntry[1].ShortName, "..         ", 11);
+        _SEH2_END;
+        /* clear the new directory cluster if not moving */
+        if (MoveContext == NULL)
+        {
+            RtlZeroMemory(pFatEntry, DeviceExt->FatInfo.BytesPerCluster);
+            /* create '.' and '..' */
+            RtlCopyMemory(&pFatEntry[0].Attrib, &DirContext.DirEntry.Fat.Attrib, sizeof(FAT_DIR_ENTRY) - 11);
+            RtlCopyMemory(pFatEntry[0].ShortName, ".          ", 11);
+            RtlCopyMemory(&pFatEntry[1].Attrib, &DirContext.DirEntry.Fat.Attrib, sizeof(FAT_DIR_ENTRY) - 11);
+            RtlCopyMemory(pFatEntry[1].ShortName, "..         ", 11);
+        }
+
         pFatEntry[1].FirstCluster = ParentFcb->entry.Fat.FirstCluster;
         pFatEntry[1].FirstClusterHigh = ParentFcb->entry.Fat.FirstClusterHigh;
         if (vfatFCBIsRoot(ParentFcb))
@@ -542,7 +701,8 @@ FATXAddEntry(
     IN PVFATFCB* Fcb,
     IN PVFATFCB ParentFcb,
     IN ULONG RequestedOptions,
-    IN UCHAR ReqAttr)
+    IN UCHAR ReqAttr,
+    IN PVFAT_MOVE_CONTEXT MoveContext)
 {
     PVOID Context = NULL;
     LARGE_INTEGER SystemTime, FileOffset;
@@ -578,7 +738,15 @@ FATXAddEntry(
     DirContext.ShortNameU.MaximumLength = 0;
     RtlZeroMemory(&DirContext.DirEntry.FatX, sizeof(FATX_DIR_ENTRY));
     memset(DirContext.DirEntry.FatX.Filename, 0xff, 42);
-    DirContext.DirEntry.FatX.FirstCluster = 0;
+    /* Use cluster, if moving */
+    if (MoveContext != NULL)
+    {
+        DirContext.DirEntry.FatX.FirstCluster = MoveContext->FirstCluster;
+    }
+    else
+    {
+        DirContext.DirEntry.FatX.FirstCluster = 0;
+    }
     DirContext.DirEntry.FatX.FileSize = 0;
 
     /* set file name */
@@ -603,21 +771,41 @@ FATXAddEntry(
     DirContext.DirEntry.FatX.UpdateTime = DirContext.DirEntry.FatX.CreationTime;
     DirContext.DirEntry.FatX.AccessDate = DirContext.DirEntry.FatX.CreationDate;
     DirContext.DirEntry.FatX.AccessTime = DirContext.DirEntry.FatX.CreationTime;
+    /* If it's moving, preserve creation time and file size */
+    if (MoveContext != NULL)
+    {
+        DirContext.DirEntry.FatX.CreationDate = MoveContext->CreationDate;
+        DirContext.DirEntry.FatX.CreationTime = MoveContext->CreationTime;
+        DirContext.DirEntry.FatX.FileSize = MoveContext->FileSize;
+    }
 
     /* add entry into parent directory */
     FileOffset.u.HighPart = 0;
     FileOffset.u.LowPart = Index * sizeof(FATX_DIR_ENTRY);
-    if (!CcPinRead(ParentFcb->FileObject, &FileOffset, sizeof(FATX_DIR_ENTRY),
-              TRUE, &Context, (PVOID*)&pFatXDirEntry))
+    _SEH2_TRY
     {
-        return STATUS_UNSUCCESSFUL;
+        CcPinRead(ParentFcb->FileObject, &FileOffset, sizeof(FATX_DIR_ENTRY), PIN_WAIT, &Context, (PVOID*)&pFatXDirEntry);
     }
+    _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
+    {
+        _SEH2_YIELD(return _SEH2_GetExceptionCode());
+    }
+    _SEH2_END;
     RtlCopyMemory(pFatXDirEntry, &DirContext.DirEntry.FatX, sizeof(FATX_DIR_ENTRY));
     CcSetDirtyPinnedData(Context, NULL);
     CcUnpinData(Context);
 
-    /* FIXME: check status */
-    vfatMakeFCBFromDirEntry(DeviceExt, ParentFcb, &DirContext, Fcb);
+    if (MoveContext != NULL)
+    {
+        /* We're modifying an existing FCB - likely rename/move */
+        /* FIXME: check status */
+        vfatUpdateFCB(DeviceExt, *Fcb, &DirContext, ParentFcb);
+    }
+    else
+    {
+        /* FIXME: check status */
+        vfatMakeFCBFromDirEntry(DeviceExt, ParentFcb, &DirContext, Fcb);
+    }
 
     DPRINT("addentry ok\n");
     return STATUS_SUCCESS;
@@ -630,12 +818,13 @@ VfatAddEntry(
     IN PVFATFCB *Fcb,
     IN PVFATFCB ParentFcb,
     IN ULONG RequestedOptions,
-    IN UCHAR ReqAttr)
+    IN UCHAR ReqAttr,
+    IN PVFAT_MOVE_CONTEXT MoveContext)
 {
     if (DeviceExt->Flags & VCB_IS_FATX)
-        return FATXAddEntry(DeviceExt, NameU, Fcb, ParentFcb, RequestedOptions, ReqAttr);
+        return FATXAddEntry(DeviceExt, NameU, Fcb, ParentFcb, RequestedOptions, ReqAttr, MoveContext);
     else
-        return FATAddEntry(DeviceExt, NameU, Fcb, ParentFcb, RequestedOptions, ReqAttr);
+        return FATAddEntry(DeviceExt, NameU, Fcb, ParentFcb, RequestedOptions, ReqAttr, MoveContext);
 }
 
 /*
@@ -644,7 +833,8 @@ VfatAddEntry(
 static NTSTATUS
 FATDelEntry(
     IN PDEVICE_EXTENSION DeviceExt,
-    IN PVFATFCB pFcb)
+    IN PVFATFCB pFcb,
+    OUT PVFAT_MOVE_CONTEXT MoveContext)
 {
     ULONG CurrentCluster = 0, NextCluster, i;
     PVOID Context = NULL;
@@ -667,11 +857,15 @@ FATDelEntry(
                 CcUnpinData(Context);
             }
             Offset.u.LowPart = (i * sizeof(FAT_DIR_ENTRY) / PAGE_SIZE) * PAGE_SIZE;
-            if (!CcPinRead(pFcb->parentFcb->FileObject, &Offset, sizeof(FAT_DIR_ENTRY), TRUE,
-                      &Context, (PVOID*)&pDirEntry))
+            _SEH2_TRY
+            {
+                CcPinRead(pFcb->parentFcb->FileObject, &Offset, PAGE_SIZE, PIN_WAIT, &Context, (PVOID*)&pDirEntry);
+            }
+            _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
             {
-                return STATUS_UNSUCCESSFUL;
+                _SEH2_YIELD(return _SEH2_GetExceptionCode());
             }
+            _SEH2_END;
         }
         pDirEntry[i % (PAGE_SIZE / sizeof(FAT_DIR_ENTRY))].Filename[0] = 0xe5;
         if (i == pFcb->dirIndex)
@@ -681,19 +875,35 @@ FATDelEntry(
                                         (PDIR_ENTRY)&pDirEntry[i % (PAGE_SIZE / sizeof(FAT_DIR_ENTRY))]);
         }
     }
+
+    /* In case of moving, save properties */
+    if (MoveContext != NULL)
+    {
+        pDirEntry = &pDirEntry[pFcb->dirIndex % (PAGE_SIZE / sizeof(FAT_DIR_ENTRY))];
+        MoveContext->FirstCluster = CurrentCluster;
+        MoveContext->FileSize = pDirEntry->FileSize;
+        MoveContext->CreationTime = pDirEntry->CreationTime;
+        MoveContext->CreationDate = pDirEntry->CreationDate;
+    }
+
     if (Context)
     {
         CcSetDirtyPinnedData(Context, NULL);
         CcUnpinData(Context);
     }
 
-    while (CurrentCluster && CurrentCluster != 0xffffffff)
+    /* In case of moving, don't delete data */
+    if (MoveContext == NULL)
     {
-        GetNextCluster(DeviceExt, CurrentCluster, &NextCluster);
-        /* FIXME: check status */
-        WriteCluster(DeviceExt, CurrentCluster, 0);
-        CurrentCluster = NextCluster;
+        while (CurrentCluster && CurrentCluster != 0xffffffff)
+        {
+            GetNextCluster(DeviceExt, CurrentCluster, &NextCluster);
+            /* FIXME: check status */
+            WriteCluster(DeviceExt, CurrentCluster, 0);
+            CurrentCluster = NextCluster;
+        }
     }
+
     return STATUS_SUCCESS;
 }
 
@@ -703,7 +913,8 @@ FATDelEntry(
 static NTSTATUS
 FATXDelEntry(
     IN PDEVICE_EXTENSION DeviceExt,
-    IN PVFATFCB pFcb)
+    IN PVFATFCB pFcb,
+    OUT PVFAT_MOVE_CONTEXT MoveContext)
 {
     ULONG CurrentCluster = 0, NextCluster;
     PVOID Context = NULL;
@@ -721,38 +932,99 @@ FATXDelEntry(
     DPRINT("delete entry: %u\n", StartIndex);
     Offset.u.HighPart = 0;
     Offset.u.LowPart = (StartIndex * sizeof(FATX_DIR_ENTRY) / PAGE_SIZE) * PAGE_SIZE;
-    if (!CcPinRead(pFcb->parentFcb->FileObject, &Offset, sizeof(FATX_DIR_ENTRY), TRUE,
-                   &Context, (PVOID*)&pDirEntry))
+    _SEH2_TRY
+    {
+        CcPinRead(pFcb->parentFcb->FileObject, &Offset, PAGE_SIZE, PIN_WAIT, &Context, (PVOID*)&pDirEntry);
+    }
+    _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
     {
         DPRINT1("CcPinRead(Offset %x:%x, Length %d) failed\n", Offset.u.HighPart, Offset.u.LowPart, PAGE_SIZE);
-        return STATUS_UNSUCCESSFUL;
+        _SEH2_YIELD(return _SEH2_GetExceptionCode());
     }
+    _SEH2_END;
     pDirEntry = &pDirEntry[StartIndex % (PAGE_SIZE / sizeof(FATX_DIR_ENTRY))];
     pDirEntry->FilenameLength = 0xe5;
     CurrentCluster = vfatDirEntryGetFirstCluster(DeviceExt,
                                                  (PDIR_ENTRY)pDirEntry);
+
+    /* In case of moving, save properties */
+    if (MoveContext != NULL)
+    {
+        MoveContext->FirstCluster = CurrentCluster;
+        MoveContext->FileSize = pDirEntry->FileSize;
+        MoveContext->CreationTime = pDirEntry->CreationTime;
+        MoveContext->CreationDate = pDirEntry->CreationDate;
+    }
+
     CcSetDirtyPinnedData(Context, NULL);
     CcUnpinData(Context);
 
-    while (CurrentCluster && CurrentCluster != 0xffffffff)
+    /* In case of moving, don't delete data */
+    if (MoveContext == NULL)
     {
-        GetNextCluster(DeviceExt, CurrentCluster, &NextCluster);
-        /* FIXME: check status */
-        WriteCluster(DeviceExt, CurrentCluster, 0);
-        CurrentCluster = NextCluster;
+        while (CurrentCluster && CurrentCluster != 0xffffffff)
+        {
+            GetNextCluster(DeviceExt, CurrentCluster, &NextCluster);
+            /* FIXME: check status */
+            WriteCluster(DeviceExt, CurrentCluster, 0);
+            CurrentCluster = NextCluster;
+        }
     }
+
     return STATUS_SUCCESS;
 }
 
 NTSTATUS
 VfatDelEntry(
     IN PDEVICE_EXTENSION DeviceExt,
-    IN PVFATFCB pFcb)
+    IN PVFATFCB pFcb,
+    OUT PVFAT_MOVE_CONTEXT MoveContext)
 {
     if (DeviceExt->Flags & VCB_IS_FATX)
-        return FATXDelEntry(DeviceExt, pFcb);
+        return FATXDelEntry(DeviceExt, pFcb, MoveContext);
     else
-        return FATDelEntry(DeviceExt, pFcb);
+        return FATDelEntry(DeviceExt, pFcb, MoveContext);
+}
+
+/*
+ * move an existing FAT entry
+ */
+NTSTATUS
+VfatMoveEntry(
+    IN PDEVICE_EXTENSION DeviceExt,
+    IN PVFATFCB pFcb,
+    IN PUNICODE_STRING FileName,
+    IN PVFATFCB ParentFcb)
+{
+    NTSTATUS Status;
+    PVFATFCB OldParent;
+    VFAT_MOVE_CONTEXT MoveContext;
+
+    DPRINT("VfatMoveEntry(%p, %p, %wZ, %p)\n", DeviceExt, pFcb, FileName, ParentFcb);
+
+    /* Delete old entry while keeping data */
+    Status = VfatDelEntry(DeviceExt, pFcb, &MoveContext);
+    if (!NT_SUCCESS(Status))
+    {
+        return Status;
+    }
+
+    OldParent = pFcb->parentFcb;
+    CcPurgeCacheSection(&OldParent->SectionObjectPointers, NULL, 0, FALSE);
+    MoveContext.InPlace = (OldParent == ParentFcb);
+
+    /* Add our new entry with our cluster */
+    Status = VfatAddEntry(DeviceExt,
+                          FileName,
+                          &pFcb,
+                          ParentFcb,
+                          (vfatFCBIsDirectory(pFcb) ? FILE_DIRECTORY_FILE : 0),
+                          *pFcb->Attributes,
+                          &MoveContext);
+
+    CcPurgeCacheSection(&pFcb->parentFcb->SectionObjectPointers, NULL, 0, FALSE);
+
+    return Status;
 }
 
 /* EOF */