/* * $Id$ * * COPYRIGHT: See COPYING in the top level directory * PROJECT: ReactOS kernel * FILE: services/fs/vfat/fat.c * PURPOSE: VFAT Filesystem * PROGRAMMER: Jason Filby (jasonfilby@yahoo.com) * */ /* INCLUDES *****************************************************************/ #include #include #include #define NDEBUG #include #include "vfat.h" /* GLOBALS ******************************************************************/ #define ROUND_DOWN(N, S) ((N) - ((N) % (S))) #define CACHEPAGESIZE(pDeviceExt) ((pDeviceExt)->FatInfo.BytesPerCluster > PAGE_SIZE ? \ (pDeviceExt)->FatInfo.BytesPerCluster : PAGE_SIZE) /* FUNCTIONS ****************************************************************/ NTSTATUS Fat32GetNextCluster(PDEVICE_EXTENSION DeviceExt, ULONG CurrentCluster, PULONG NextCluster) /* * FUNCTION: Retrieve the next FAT32 cluster from the FAT table via a physical * disk read */ { PVOID BaseAddress; ULONG FATOffset; ULONG ChunkSize; PVOID Context; LARGE_INTEGER Offset; ChunkSize = CACHEPAGESIZE(DeviceExt); FATOffset = CurrentCluster * sizeof(ULONG); Offset.QuadPart = ROUND_DOWN(FATOffset, ChunkSize); if(!CcMapData(DeviceExt->FATFileObject, &Offset, ChunkSize, 1, &Context, &BaseAddress)) { return STATUS_UNSUCCESSFUL; } CurrentCluster = (*(PULONG)((char*)BaseAddress + (FATOffset % ChunkSize))) & 0x0fffffff; if (CurrentCluster >= 0xffffff8 && CurrentCluster <= 0xfffffff) CurrentCluster = 0xffffffff; CcUnpinData(Context); *NextCluster = CurrentCluster; return (STATUS_SUCCESS); } NTSTATUS Fat16GetNextCluster(PDEVICE_EXTENSION DeviceExt, ULONG CurrentCluster, PULONG NextCluster) /* * FUNCTION: Retrieve the next FAT16 cluster from the FAT table */ { PVOID BaseAddress; ULONG FATOffset; ULONG ChunkSize; PVOID Context; LARGE_INTEGER Offset; ChunkSize = CACHEPAGESIZE(DeviceExt); FATOffset = CurrentCluster * 2; Offset.QuadPart = ROUND_DOWN(FATOffset, ChunkSize); if(!CcMapData(DeviceExt->FATFileObject, &Offset, ChunkSize, 1, &Context, &BaseAddress)) { return STATUS_UNSUCCESSFUL; } CurrentCluster = *((PUSHORT)((char*)BaseAddress + (FATOffset % ChunkSize))); if (CurrentCluster >= 0xfff8 && CurrentCluster <= 0xffff) CurrentCluster = 0xffffffff; CcUnpinData(Context); *NextCluster = CurrentCluster; return (STATUS_SUCCESS); } NTSTATUS Fat12GetNextCluster(PDEVICE_EXTENSION DeviceExt, ULONG CurrentCluster, PULONG NextCluster) /* * FUNCTION: Retrieve the next FAT12 cluster from the FAT table */ { PUSHORT CBlock; ULONG Entry; PVOID BaseAddress; PVOID Context; LARGE_INTEGER Offset; *NextCluster = 0; Offset.QuadPart = 0; if(!CcMapData(DeviceExt->FATFileObject, &Offset, DeviceExt->FatInfo.FATSectors * DeviceExt->FatInfo.BytesPerSector, 1, &Context, &BaseAddress)) { return STATUS_UNSUCCESSFUL; } CBlock = (PUSHORT)((char*)BaseAddress + (CurrentCluster * 12) / 8); if ((CurrentCluster % 2) == 0) { Entry = *CBlock & 0x0fff; } else { Entry = *CBlock >> 4; } // DPRINT("Entry %x\n",Entry); if (Entry >= 0xff8 && Entry <= 0xfff) Entry = 0xffffffff; // DPRINT("Returning %x\n",Entry); *NextCluster = Entry; CcUnpinData(Context); // return Entry == 0xffffffff ? STATUS_END_OF_FILE : STATUS_SUCCESS; return STATUS_SUCCESS; } NTSTATUS FAT16FindAvailableCluster(PDEVICE_EXTENSION DeviceExt, PULONG Cluster) /* * FUNCTION: Finds the first available cluster in a FAT16 table */ { ULONG FatLength; ULONG StartCluster; ULONG i, j; PVOID BaseAddress; ULONG ChunkSize; PVOID Context = 0; LARGE_INTEGER Offset; PUSHORT Block; ChunkSize = CACHEPAGESIZE(DeviceExt); FatLength = (DeviceExt->FatInfo.NumberOfClusters +2 ) * 2; *Cluster = 0; StartCluster = DeviceExt->LastAvailableCluster; for (j = 0; j < 2; j++) { for (i = StartCluster * 2; i < FatLength; i += 2, Block++) { if ((i % ChunkSize) == 0 || Context == NULL) { Offset.QuadPart = ROUND_DOWN(i, ChunkSize); if (Context != NULL) { CcUnpinData(Context); } CHECKPOINT; if(!CcMapData(DeviceExt->FATFileObject, &Offset, ChunkSize, 1, &Context, &BaseAddress)) { DPRINT1("CcMapData(Offset %x, Length %d) failed\n", (ULONG)Offset.QuadPart, ChunkSize); return STATUS_UNSUCCESSFUL; } CHECKPOINT; Block = (PUSHORT)((char*)BaseAddress + i % ChunkSize); } if (*Block == 0) { DPRINT("Found available cluster 0x%x\n", i / 2); DeviceExt->LastAvailableCluster = *Cluster = i / 2; CcUnpinData(Context); return(STATUS_SUCCESS); } } FatLength = StartCluster * 2; StartCluster = 2; if (Context != NULL) { CcUnpinData(Context); Context =NULL; } } return(STATUS_DISK_FULL); } NTSTATUS FAT12FindAvailableCluster(PDEVICE_EXTENSION DeviceExt, PULONG Cluster) /* * FUNCTION: Finds the first available cluster in a FAT12 table */ { ULONG FatLength; ULONG StartCluster; ULONG Entry; PUSHORT CBlock; ULONG i, j; PVOID BaseAddress; PVOID Context; LARGE_INTEGER Offset; FatLength = DeviceExt->FatInfo.NumberOfClusters + 2; *Cluster = 0; StartCluster = DeviceExt->LastAvailableCluster; Offset.QuadPart = 0; if(!CcMapData(DeviceExt->FATFileObject, &Offset, DeviceExt->FatInfo.FATSectors * DeviceExt->FatInfo.BytesPerSector, 1, &Context, &BaseAddress)) { DPRINT1("CcMapData(Offset %x, Length %d) failed\n", (ULONG)Offset.QuadPart, DeviceExt->FatInfo.FATSectors * DeviceExt->FatInfo.BytesPerSector); return STATUS_UNSUCCESSFUL; } for (j = 0; j < 2; j++) { for (i = StartCluster; i < FatLength; i++) { CBlock = (PUSHORT)((char*)BaseAddress + (i * 12) / 8); if ((i % 2) == 0) { Entry = *CBlock & 0xfff; } else { Entry = *CBlock >> 4; } if (Entry == 0) { DPRINT("Found available cluster 0x%x\n", i); DeviceExt->LastAvailableCluster = *Cluster = i; CcUnpinData(Context); return(STATUS_SUCCESS); } } FatLength = StartCluster; StartCluster = 2; } CcUnpinData(Context); return (STATUS_DISK_FULL); } NTSTATUS FAT32FindAvailableCluster (PDEVICE_EXTENSION DeviceExt, PULONG Cluster) /* * FUNCTION: Finds the first available cluster in a FAT32 table */ { ULONG FatLength; ULONG StartCluster; ULONG i, j; PVOID BaseAddress; ULONG ChunkSize; PVOID Context = 0; LARGE_INTEGER Offset; PULONG Block; ChunkSize = CACHEPAGESIZE(DeviceExt); FatLength = (DeviceExt->FatInfo.NumberOfClusters + 2) * 4; *Cluster = 0; StartCluster = DeviceExt->LastAvailableCluster; for (j = 0; j < 2; j++) { for (i = StartCluster * 4; i < FatLength; i += 4, Block++) { if ((i % ChunkSize) == 0 || Context == NULL) { Offset.QuadPart = ROUND_DOWN(i, ChunkSize); if (Context != NULL) { CcUnpinData(Context); } if(!CcMapData(DeviceExt->FATFileObject, &Offset, ChunkSize, 1, &Context, &BaseAddress)) { DPRINT1("CcMapData(Offset %x, Length %d) failed\n", (ULONG)Offset.QuadPart, ChunkSize); return STATUS_UNSUCCESSFUL; } Block = (PULONG)((char*)BaseAddress + i % ChunkSize); } if ((*Block & 0x0fffffff) == 0) { DPRINT("Found available cluster 0x%x\n", i / 4); DeviceExt->LastAvailableCluster = *Cluster = i / 4; CcUnpinData(Context); return(STATUS_SUCCESS); } } FatLength = StartCluster * 4; StartCluster = 2; if (Context != NULL) { CcUnpinData(Context); Context=NULL; } } return (STATUS_DISK_FULL); } NTSTATUS FAT12CountAvailableClusters(PDEVICE_EXTENSION DeviceExt) /* * FUNCTION: Counts free cluster in a FAT12 table */ { ULONG Entry; PVOID BaseAddress; ULONG ulCount = 0; ULONG i; ULONG numberofclusters; LARGE_INTEGER Offset; PVOID Context; PUSHORT CBlock; Offset.QuadPart = 0; if(!CcMapData(DeviceExt->FATFileObject, &Offset, DeviceExt->FatInfo.FATSectors * DeviceExt->FatInfo.BytesPerSector, 1, &Context, &BaseAddress)) { return STATUS_UNSUCCESSFUL; } numberofclusters = DeviceExt->FatInfo.NumberOfClusters + 2; for (i = 2; i < numberofclusters; i++) { CBlock = (PUSHORT)((char*)BaseAddress + (i * 12) / 8); if ((i % 2) == 0) { Entry = *CBlock & 0x0fff; } else { Entry = *CBlock >> 4; } if (Entry == 0) ulCount++; } CcUnpinData(Context); DeviceExt->AvailableClusters = ulCount; DeviceExt->AvailableClustersValid = TRUE; return(STATUS_SUCCESS); } NTSTATUS FAT16CountAvailableClusters(PDEVICE_EXTENSION DeviceExt) /* * FUNCTION: Counts free clusters in a FAT16 table */ { PUSHORT Block; PVOID BaseAddress = NULL; ULONG ulCount = 0; ULONG i; ULONG ChunkSize; PVOID Context = NULL; LARGE_INTEGER Offset; ULONG FatLength; ChunkSize = CACHEPAGESIZE(DeviceExt); FatLength = (DeviceExt->FatInfo.NumberOfClusters + 2) * 2; for (i = 4; i< FatLength; i += 2, Block++) { if ((i % ChunkSize) == 0 || Context == NULL) { DPRINT("%d\n", i/2); if (Context) { CcUnpinData(Context); } Offset.QuadPart = ROUND_DOWN(i, ChunkSize); if(!CcMapData(DeviceExt->FATFileObject, &Offset, ChunkSize, 1, &Context, &BaseAddress)) { return STATUS_UNSUCCESSFUL; } Block = (PUSHORT)((char*)BaseAddress + i % ChunkSize); } if (*Block == 0) ulCount++; } DeviceExt->AvailableClusters = ulCount; DeviceExt->AvailableClustersValid = TRUE; CcUnpinData(Context); return(STATUS_SUCCESS); } NTSTATUS FAT32CountAvailableClusters(PDEVICE_EXTENSION DeviceExt) /* * FUNCTION: Counts free clusters in a FAT32 table */ { PULONG Block; PVOID BaseAddress = NULL; ULONG ulCount = 0; ULONG i; ULONG ChunkSize; PVOID Context = NULL; LARGE_INTEGER Offset; ULONG FatLength; ChunkSize = CACHEPAGESIZE(DeviceExt); FatLength = (DeviceExt->FatInfo.NumberOfClusters + 2) * 4; for (i = 8; i< FatLength; i += 4, Block++) { if ((i % ChunkSize) == 0 || Context == NULL) { if (Context) { CcUnpinData(Context); } Offset.QuadPart = ROUND_DOWN(i, ChunkSize); if(!CcMapData(DeviceExt->FATFileObject, &Offset, ChunkSize, 1, &Context, &BaseAddress)) { return STATUS_UNSUCCESSFUL; } Block = (PULONG)((char*)BaseAddress + i % ChunkSize); } if ((*Block & 0x0fffffff) == 0) ulCount++; } DeviceExt->AvailableClusters = ulCount; DeviceExt->AvailableClustersValid = TRUE; CcUnpinData(Context); return(STATUS_SUCCESS); } NTSTATUS CountAvailableClusters(PDEVICE_EXTENSION DeviceExt, PLARGE_INTEGER Clusters) { NTSTATUS Status = STATUS_SUCCESS; ExAcquireResourceExclusiveLite (&DeviceExt->FatResource, TRUE); if (!DeviceExt->AvailableClustersValid) { if (DeviceExt->FatInfo.FatType == FAT12) Status = FAT12CountAvailableClusters(DeviceExt); else if (DeviceExt->FatInfo.FatType == FAT16) Status = FAT16CountAvailableClusters(DeviceExt); else Status = FAT32CountAvailableClusters(DeviceExt); } Clusters->QuadPart = DeviceExt->AvailableClusters; ExReleaseResourceLite (&DeviceExt->FatResource); return Status; } NTSTATUS FAT12WriteCluster(PDEVICE_EXTENSION DeviceExt, ULONG ClusterToWrite, ULONG NewValue, PULONG OldValue) /* * FUNCTION: Writes a cluster to the FAT12 physical and in-memory tables */ { ULONG FATsector; ULONG FATOffset; PUCHAR CBlock; PVOID BaseAddress; PVOID Context; LARGE_INTEGER Offset; Offset.QuadPart = 0; if(!CcMapData(DeviceExt->FATFileObject, &Offset, DeviceExt->FatInfo.FATSectors * DeviceExt->FatInfo.BytesPerSector, 1, &Context, &BaseAddress)) { return STATUS_UNSUCCESSFUL; } CBlock = (PUCHAR)BaseAddress; FATOffset = (ClusterToWrite * 12) / 8; DPRINT("Writing 0x%x for 0x%x at 0x%x\n", NewValue, ClusterToWrite, FATOffset); if ((ClusterToWrite % 2) == 0) { *OldValue = CBlock[FATOffset] + ((CBlock[FATOffset + 1] & 0x0f) << 8); CBlock[FATOffset] = (UCHAR)NewValue; CBlock[FATOffset + 1] &= 0xf0; CBlock[FATOffset + 1] |= (NewValue & 0xf00) >> 8; } else { *OldValue = (CBlock[FATOffset] >> 4) + (CBlock[FATOffset + 1] << 4); CBlock[FATOffset] &= 0x0f; CBlock[FATOffset] |= (NewValue & 0xf) << 4; CBlock[FATOffset + 1] = (UCHAR)(NewValue >> 4); } /* Write the changed FAT sector(s) to disk */ FATsector = FATOffset / DeviceExt->FatInfo.BytesPerSector; CcSetDirtyPinnedData(Context, NULL); CcUnpinData(Context); return(STATUS_SUCCESS); } NTSTATUS FAT16WriteCluster(PDEVICE_EXTENSION DeviceExt, ULONG ClusterToWrite, ULONG NewValue, PULONG OldValue) /* * FUNCTION: Writes a cluster to the FAT16 physical and in-memory tables */ { PVOID BaseAddress; ULONG FATOffset; ULONG ChunkSize; PVOID Context; LARGE_INTEGER Offset; PUSHORT Cluster; ChunkSize = CACHEPAGESIZE(DeviceExt); FATOffset = ClusterToWrite * 2; Offset.QuadPart = ROUND_DOWN(FATOffset, ChunkSize); if(!CcMapData(DeviceExt->FATFileObject, &Offset, ChunkSize, 1, &Context, &BaseAddress)) { return STATUS_UNSUCCESSFUL; } DPRINT("Writing 0x%x for offset 0x%x 0x%x\n", NewValue, FATOffset, ClusterToWrite); Cluster = ((PUSHORT)((char*)BaseAddress + (FATOffset % ChunkSize))); *OldValue = *Cluster; *Cluster = (USHORT)NewValue; CcSetDirtyPinnedData(Context, NULL); CcUnpinData(Context); return(STATUS_SUCCESS); } NTSTATUS FAT32WriteCluster(PDEVICE_EXTENSION DeviceExt, ULONG ClusterToWrite, ULONG NewValue, PULONG OldValue) /* * FUNCTION: Writes a cluster to the FAT32 physical tables */ { PVOID BaseAddress; ULONG FATOffset; ULONG ChunkSize; PVOID Context; LARGE_INTEGER Offset; PULONG Cluster; ChunkSize = CACHEPAGESIZE(DeviceExt); FATOffset = (ClusterToWrite * 4); Offset.QuadPart = ROUND_DOWN(FATOffset, ChunkSize); if(!CcMapData(DeviceExt->FATFileObject, &Offset, ChunkSize, 1, &Context, &BaseAddress)) { return STATUS_UNSUCCESSFUL; } DPRINT("Writing 0x%x for offset 0x%x 0x%x\n", NewValue, FATOffset, ClusterToWrite); Cluster = ((PULONG)((char*)BaseAddress + (FATOffset % ChunkSize))); *OldValue = *Cluster & 0x0fffffff; *Cluster = (*Cluster & 0xf0000000) | (NewValue & 0x0fffffff); CcSetDirtyPinnedData(Context, NULL); CcUnpinData(Context); return(STATUS_SUCCESS); } NTSTATUS WriteCluster(PDEVICE_EXTENSION DeviceExt, ULONG ClusterToWrite, ULONG NewValue) /* * FUNCTION: Write a changed FAT entry */ { NTSTATUS Status; ULONG OldValue; ExAcquireResourceExclusiveLite (&DeviceExt->FatResource, TRUE); if (DeviceExt->FatInfo.FatType == FAT16) { Status = FAT16WriteCluster(DeviceExt, ClusterToWrite, NewValue, &OldValue); } else if (DeviceExt->FatInfo.FatType == FAT32) { Status = FAT32WriteCluster(DeviceExt, ClusterToWrite, NewValue, &OldValue); } else { Status = FAT12WriteCluster(DeviceExt, ClusterToWrite, NewValue, &OldValue); } if (DeviceExt->AvailableClustersValid) { if (OldValue && NewValue == 0) InterlockedIncrement(&DeviceExt->AvailableClusters); else if (OldValue == 0 && NewValue) InterlockedDecrement(&DeviceExt->AvailableClusters); } ExReleaseResourceLite(&DeviceExt->FatResource); return(Status); } ULONGLONG ClusterToSector(PDEVICE_EXTENSION DeviceExt, ULONG Cluster) /* * FUNCTION: Converts the cluster number to a sector number for this physical * device */ { return DeviceExt->FatInfo.dataStart + ((ULONGLONG)(Cluster - 2) * DeviceExt->FatInfo.SectorsPerCluster); } NTSTATUS GetNextCluster(PDEVICE_EXTENSION DeviceExt, ULONG CurrentCluster, PULONG NextCluster, BOOLEAN Extend) /* * FUNCTION: Retrieve the next cluster depending on the FAT type */ { NTSTATUS Status; DPRINT ("GetNextCluster(DeviceExt %x, CurrentCluster %x)\n", DeviceExt, CurrentCluster); if (!Extend) { ExAcquireResourceSharedLite(&DeviceExt->FatResource, TRUE); } else { ExAcquireResourceExclusiveLite(&DeviceExt->FatResource, TRUE); } CHECKPOINT; /* * If the file hasn't any clusters allocated then we need special * handling */ if (CurrentCluster == 0 && Extend) { ULONG NewCluster; if (DeviceExt->FatInfo.FatType == FAT16) { CHECKPOINT; Status = FAT16FindAvailableCluster(DeviceExt, &NewCluster); CHECKPOINT; if (!NT_SUCCESS(Status)) { ExReleaseResourceLite(&DeviceExt->FatResource); return(Status); } } else if (DeviceExt->FatInfo.FatType == FAT32) { Status = FAT32FindAvailableCluster(DeviceExt, &NewCluster); if (!NT_SUCCESS(Status)) { ExReleaseResourceLite(&DeviceExt->FatResource); return(Status); } } else { Status = FAT12FindAvailableCluster(DeviceExt, &NewCluster); if (!NT_SUCCESS(Status)) { ExReleaseResourceLite(&DeviceExt->FatResource); return(Status); } } /* Mark the new AU as the EOF */ WriteCluster (DeviceExt, NewCluster, 0xFFFFFFFF); *NextCluster = NewCluster; ExReleaseResourceLite(&DeviceExt->FatResource); return(STATUS_SUCCESS); } else if (CurrentCluster == 0) { ExReleaseResourceLite(&DeviceExt->FatResource); return(STATUS_UNSUCCESSFUL); } if (DeviceExt->FatInfo.FatType == FAT16) { Status = Fat16GetNextCluster(DeviceExt, CurrentCluster, NextCluster); } else if (DeviceExt->FatInfo.FatType == FAT32) { Status = Fat32GetNextCluster(DeviceExt, CurrentCluster, NextCluster); } else { Status = Fat12GetNextCluster(DeviceExt, CurrentCluster, NextCluster); } if (Extend && (*NextCluster) == 0xFFFFFFFF) { ULONG NewCluster; /* We are after last existing cluster, we must add one to file */ /* Firstly, find the next available open allocation unit */ if (DeviceExt->FatInfo.FatType == FAT16) { Status = FAT16FindAvailableCluster(DeviceExt, &NewCluster); if (!NT_SUCCESS(Status)) { ExReleaseResourceLite(&DeviceExt->FatResource); return(Status); } } else if (DeviceExt->FatInfo.FatType == FAT32) { Status = FAT32FindAvailableCluster(DeviceExt, &NewCluster); if (!NT_SUCCESS(Status)) { ExReleaseResourceLite(&DeviceExt->FatResource); return(Status); } } else { Status = FAT12FindAvailableCluster(DeviceExt, &NewCluster); if (!NT_SUCCESS(Status)) { ExReleaseResourceLite(&DeviceExt->FatResource); return(Status); } } /* Mark the new AU as the EOF */ WriteCluster(DeviceExt, NewCluster, 0xFFFFFFFF); /* Now, write the AU of the LastCluster with the value of the newly found AU */ WriteCluster(DeviceExt, CurrentCluster, NewCluster); *NextCluster = NewCluster; } ExReleaseResourceLite(&DeviceExt->FatResource); return(Status); } /* EOF */