reactos/drivers/filesystems/vfatfs/volume.c

556 lines
17 KiB
C
Raw Normal View History

/*
* PROJECT: VFAT Filesystem
* LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
* PURPOSE: Volume routines
* COPYRIGHT: Copyright 1998 Jason Filby <jasonfilby@yahoo.com>
* Copyright 2004-2022 Hervé Poussineau <hpoussin@reactos.org>
*/
/* INCLUDES *****************************************************************/
#include "vfat.h"
#define NDEBUG
#include <debug.h>
/* FUNCTIONS ****************************************************************/
static
NTSTATUS
FsdGetFsVolumeInformation(
PDEVICE_OBJECT DeviceObject,
PFILE_FS_VOLUME_INFORMATION FsVolumeInfo,
PULONG BufferLength)
{
NTSTATUS Status;
PDEVICE_EXTENSION DeviceExt;
DPRINT("FsdGetFsVolumeInformation()\n");
DPRINT("FsVolumeInfo = %p\n", FsVolumeInfo);
DPRINT("BufferLength %lu\n", *BufferLength);
DPRINT("Required length %lu\n", FIELD_OFFSET(FILE_FS_VOLUME_INFORMATION, VolumeLabel) + DeviceObject->Vpb->VolumeLabelLength);
DPRINT("LabelLength %hu\n", DeviceObject->Vpb->VolumeLabelLength);
DPRINT("Label %.*S\n", DeviceObject->Vpb->VolumeLabelLength / sizeof(WCHAR), DeviceObject->Vpb->VolumeLabel);
ASSERT(*BufferLength >= sizeof(FILE_FS_VOLUME_INFORMATION));
*BufferLength -= FIELD_OFFSET(FILE_FS_VOLUME_INFORMATION, VolumeLabel);
DeviceExt = DeviceObject->DeviceExtension;
/* valid entries */
FsVolumeInfo->VolumeSerialNumber = DeviceObject->Vpb->SerialNumber;
FsVolumeInfo->VolumeLabelLength = DeviceObject->Vpb->VolumeLabelLength;
if (*BufferLength < DeviceObject->Vpb->VolumeLabelLength)
{
Status = STATUS_BUFFER_OVERFLOW;
RtlCopyMemory(FsVolumeInfo->VolumeLabel,
DeviceObject->Vpb->VolumeLabel,
*BufferLength);
}
else
{
Status = STATUS_SUCCESS;
RtlCopyMemory(FsVolumeInfo->VolumeLabel,
DeviceObject->Vpb->VolumeLabel,
FsVolumeInfo->VolumeLabelLength);
*BufferLength -= DeviceObject->Vpb->VolumeLabelLength;
}
if (vfatVolumeIsFatX(DeviceExt))
{
FsdDosDateTimeToSystemTime(DeviceExt,
DeviceExt->VolumeFcb->entry.FatX.CreationDate,
DeviceExt->VolumeFcb->entry.FatX.CreationTime,
&FsVolumeInfo->VolumeCreationTime);
}
else
{
FsdDosDateTimeToSystemTime(DeviceExt,
DeviceExt->VolumeFcb->entry.Fat.CreationDate,
DeviceExt->VolumeFcb->entry.Fat.CreationTime,
&FsVolumeInfo->VolumeCreationTime);
}
FsVolumeInfo->SupportsObjects = FALSE;
DPRINT("Finished FsdGetFsVolumeInformation()\n");
DPRINT("BufferLength %lu\n", *BufferLength);
return Status;
}
static
NTSTATUS
FsdGetFsAttributeInformation(
PDEVICE_EXTENSION DeviceExt,
PFILE_FS_ATTRIBUTE_INFORMATION FsAttributeInfo,
PULONG BufferLength)
{
NTSTATUS Status;
PCWSTR pName;
ULONG Length;
DPRINT("FsdGetFsAttributeInformation()\n");
DPRINT("FsAttributeInfo = %p\n", FsAttributeInfo);
DPRINT("BufferLength %lu\n", *BufferLength);
ASSERT(*BufferLength >= sizeof(FILE_FS_ATTRIBUTE_INFORMATION));
*BufferLength -= FIELD_OFFSET(FILE_FS_ATTRIBUTE_INFORMATION, FileSystemName);
switch (DeviceExt->FatInfo.FatType)
{
case FAT12: pName = L"FAT"; break;
case FAT16: pName = L"FAT"; break;
case FAT32: pName = L"FAT32"; break;
case FATX16: pName = L"FATX"; break;
case FATX32: pName = L"FATX"; break;
default: return STATUS_NOT_SUPPORTED;
}
Length = wcslen(pName) * sizeof(WCHAR);
DPRINT("Required length %lu\n", (FIELD_OFFSET(FILE_FS_ATTRIBUTE_INFORMATION, FileSystemName) + Length));
- Change all FAT*FindAvailableCluster functions to new functions FAT*FindAndMarkAvailableCluster which have incorporated the setting of EOF marker in the File Allocation Table for the returned cluster. - Rewritten FAT32CountAvailableClusters, FAT32FindAndMarkAvailableCluster, FAT16CountAvailableClusters and FAT16FindAndMarkAvailableCluster for better performance. These functions were using one big loop for traversing the FAT and had a block of code in the loop for requesting cache manger for next chunk of FAT *if necessary* (which commonly wasn't the case). Now it's changed to request always a whole FAT chunk and process it at once in a nested loop. - Cache last offset + cluster pair while reading/writing from/to file. This ensures almost linear times when doing sequential reads/writes, because the whole FAT traversing for the file is not done again for every request. Previously there was code for this, but it was neither correctly used nor working. It stored the last offset + cluster pair in file CCB structure (that is unique for each opened file handle) and not FCB structure (which is shared among all instances of the same file). This resulted in inconsistent cluster + offset number when the file allocation chain was changed (eg. by enlarging/shrinking the file), but since the cached offsets weren't actully used it went unnoticed. - Remove old hack from NextCluster and fix the call to it in VfatAddEntry. Not much to say about it, there was an temporary hack in NextCluster and with fixed VfatAddEntry it's now no longer needed. - Add pointers to WriteCluster, FindAndMarkAvailableCluster and GetNextCluster functions to device extension (set during mount) and use them at appropriate locations. This avoids some nasty if's in the code and causes some unnoticable performance improvment in the low-level FAT code. - Lock the directory FCB in VfatAddEntry while modifying it. This should propably be done on more places, but it needs more investigations. - Increase the file cache allocation size in VfatRead/VfatWrite. It boosts the overall speed of the driver a lot. svn path=/trunk/; revision=10344
2004-08-01 21:57:18 +00:00
if (*BufferLength < Length)
{
Status = STATUS_BUFFER_OVERFLOW;
Length = *BufferLength;
}
else
{
Status = STATUS_SUCCESS;
}
FsAttributeInfo->FileSystemAttributes =
FILE_CASE_PRESERVED_NAMES | FILE_UNICODE_ON_DISK;
FsAttributeInfo->MaximumComponentNameLength = 255;
FsAttributeInfo->FileSystemNameLength = Length;
RtlCopyMemory(FsAttributeInfo->FileSystemName, pName, Length);
DPRINT("Finished FsdGetFsAttributeInformation()\n");
*BufferLength -= Length;
DPRINT("BufferLength %lu\n", *BufferLength);
return Status;
}
static
NTSTATUS
FsdGetFsSizeInformation(
PDEVICE_OBJECT DeviceObject,
PFILE_FS_SIZE_INFORMATION FsSizeInfo,
PULONG BufferLength)
{
PDEVICE_EXTENSION DeviceExt;
NTSTATUS Status;
DPRINT("FsdGetFsSizeInformation()\n");
DPRINT("FsSizeInfo = %p\n", FsSizeInfo);
ASSERT(*BufferLength >= sizeof(FILE_FS_SIZE_INFORMATION));
DeviceExt = DeviceObject->DeviceExtension;
Status = CountAvailableClusters(DeviceExt, &FsSizeInfo->AvailableAllocationUnits);
FsSizeInfo->TotalAllocationUnits.QuadPart = DeviceExt->FatInfo.NumberOfClusters;
FsSizeInfo->SectorsPerAllocationUnit = DeviceExt->FatInfo.SectorsPerCluster;
FsSizeInfo->BytesPerSector = DeviceExt->FatInfo.BytesPerSector;
DPRINT("Finished FsdGetFsSizeInformation()\n");
if (NT_SUCCESS(Status))
*BufferLength -= sizeof(FILE_FS_SIZE_INFORMATION);
return Status;
}
static
NTSTATUS
FsdGetFsDeviceInformation(
PDEVICE_OBJECT DeviceObject,
PFILE_FS_DEVICE_INFORMATION FsDeviceInfo,
PULONG BufferLength)
{
DPRINT("FsdGetFsDeviceInformation()\n");
DPRINT("FsDeviceInfo = %p\n", FsDeviceInfo);
DPRINT("BufferLength %lu\n", *BufferLength);
DPRINT("Required length %lu\n", sizeof(FILE_FS_DEVICE_INFORMATION));
ASSERT(*BufferLength >= sizeof(FILE_FS_DEVICE_INFORMATION));
FsDeviceInfo->DeviceType = FILE_DEVICE_DISK;
FsDeviceInfo->Characteristics = DeviceObject->Characteristics;
DPRINT("FsdGetFsDeviceInformation() finished.\n");
*BufferLength -= sizeof(FILE_FS_DEVICE_INFORMATION);
DPRINT("BufferLength %lu\n", *BufferLength);
return STATUS_SUCCESS;
}
static
NTSTATUS
FsdGetFsFullSizeInformation(
PDEVICE_OBJECT DeviceObject,
PFILE_FS_FULL_SIZE_INFORMATION FsSizeInfo,
PULONG BufferLength)
{
PDEVICE_EXTENSION DeviceExt;
NTSTATUS Status;
DPRINT("FsdGetFsFullSizeInformation()\n");
DPRINT("FsSizeInfo = %p\n", FsSizeInfo);
ASSERT(*BufferLength >= sizeof(FILE_FS_FULL_SIZE_INFORMATION));
DeviceExt = DeviceObject->DeviceExtension;
Status = CountAvailableClusters(DeviceExt, &FsSizeInfo->CallerAvailableAllocationUnits);
FsSizeInfo->TotalAllocationUnits.QuadPart = DeviceExt->FatInfo.NumberOfClusters;
FsSizeInfo->ActualAvailableAllocationUnits.QuadPart = FsSizeInfo->CallerAvailableAllocationUnits.QuadPart;
FsSizeInfo->SectorsPerAllocationUnit = DeviceExt->FatInfo.SectorsPerCluster;
FsSizeInfo->BytesPerSector = DeviceExt->FatInfo.BytesPerSector;
DPRINT("Finished FsdGetFsFullSizeInformation()\n");
if (NT_SUCCESS(Status))
*BufferLength -= sizeof(FILE_FS_FULL_SIZE_INFORMATION);
return Status;
}
static
NTSTATUS
FsdSetFsLabelInformation(
PDEVICE_OBJECT DeviceObject,
PFILE_FS_LABEL_INFORMATION FsLabelInfo)
{
PDEVICE_EXTENSION DeviceExt;
PVOID Context = NULL;
ULONG DirIndex = 0;
PDIR_ENTRY Entry;
PVFATFCB pRootFcb;
LARGE_INTEGER FileOffset;
BOOLEAN LabelFound = FALSE;
DIR_ENTRY VolumeLabelDirEntry;
ULONG VolumeLabelDirIndex;
ULONG LabelLen;
NTSTATUS Status = STATUS_UNSUCCESSFUL;
OEM_STRING StringO;
UNICODE_STRING StringW;
CHAR cString[43];
ULONG SizeDirEntry;
ULONG EntriesPerPage;
BOOLEAN IsFatX;
DPRINT("FsdSetFsLabelInformation()\n");
DeviceExt = (PDEVICE_EXTENSION)DeviceObject->DeviceExtension;
IsFatX = vfatVolumeIsFatX(DeviceExt);
if (sizeof(DeviceObject->Vpb->VolumeLabel) < FsLabelInfo->VolumeLabelLength)
{
return STATUS_NAME_TOO_LONG;
}
if (IsFatX)
{
if (FsLabelInfo->VolumeLabelLength / sizeof(WCHAR) > 42)
return STATUS_NAME_TOO_LONG;
SizeDirEntry = sizeof(FATX_DIR_ENTRY);
EntriesPerPage = FATX_ENTRIES_PER_PAGE;
}
else
{
if (FsLabelInfo->VolumeLabelLength / sizeof(WCHAR) > 11)
return STATUS_NAME_TOO_LONG;
SizeDirEntry = sizeof(FAT_DIR_ENTRY);
EntriesPerPage = FAT_ENTRIES_PER_PAGE;
}
/* Create Volume label dir entry */
LabelLen = FsLabelInfo->VolumeLabelLength / sizeof(WCHAR);
RtlZeroMemory(&VolumeLabelDirEntry, SizeDirEntry);
StringW.Buffer = FsLabelInfo->VolumeLabel;
StringW.Length = StringW.MaximumLength = (USHORT)FsLabelInfo->VolumeLabelLength;
StringO.Buffer = cString;
StringO.Length = 0;
StringO.MaximumLength = 42;
Status = RtlUnicodeStringToOemString(&StringO, &StringW, FALSE);
if (!NT_SUCCESS(Status))
return Status;
if (IsFatX)
{
RtlCopyMemory(VolumeLabelDirEntry.FatX.Filename, cString, LabelLen);
memset(&VolumeLabelDirEntry.FatX.Filename[LabelLen], ' ', 42 - LabelLen);
VolumeLabelDirEntry.FatX.Attrib = _A_VOLID;
}
else
{
RtlCopyMemory(VolumeLabelDirEntry.Fat.Filename, cString, max(sizeof(VolumeLabelDirEntry.Fat.Filename), LabelLen));
if (LabelLen > sizeof(VolumeLabelDirEntry.Fat.Filename))
{
memset(VolumeLabelDirEntry.Fat.Ext, ' ', sizeof(VolumeLabelDirEntry.Fat.Ext));
RtlCopyMemory(VolumeLabelDirEntry.Fat.Ext, cString + sizeof(VolumeLabelDirEntry.Fat.Filename), LabelLen - sizeof(VolumeLabelDirEntry.Fat.Filename));
}
else
{
memset(&VolumeLabelDirEntry.Fat.Filename[LabelLen], ' ', sizeof(VolumeLabelDirEntry.Fat.Filename) - LabelLen);
}
VolumeLabelDirEntry.Fat.Attrib = _A_VOLID;
}
pRootFcb = vfatOpenRootFCB(DeviceExt);
Status = vfatFCBInitializeCacheFromVolume(DeviceExt, pRootFcb);
if (!NT_SUCCESS(Status))
{
return Status;
}
/* Search existing volume entry on disk */
FileOffset.QuadPart = 0;
_SEH2_TRY
{
CcPinRead(pRootFcb->FileObject, &FileOffset, SizeDirEntry, PIN_WAIT, &Context, (PVOID*)&Entry);
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
Status = _SEH2_GetExceptionCode();
}
_SEH2_END;
if (NT_SUCCESS(Status))
{
while (TRUE)
{
if (ENTRY_VOLUME(IsFatX, Entry))
{
/* Update entry */
LabelFound = TRUE;
RtlCopyMemory(Entry, &VolumeLabelDirEntry, SizeDirEntry);
CcSetDirtyPinnedData(Context, NULL);
Status = STATUS_SUCCESS;
break;
}
if (ENTRY_END(IsFatX, Entry))
{
break;
}
DirIndex++;
Entry = (PDIR_ENTRY)((ULONG_PTR)Entry + SizeDirEntry);
if ((DirIndex % EntriesPerPage) == 0)
{
CcUnpinData(Context);
FileOffset.u.LowPart += PAGE_SIZE;
_SEH2_TRY
{
CcPinRead(pRootFcb->FileObject, &FileOffset, SizeDirEntry, PIN_WAIT, &Context, (PVOID*)&Entry);
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
Status = _SEH2_GetExceptionCode();
}
_SEH2_END;
if (!NT_SUCCESS(Status))
{
Context = NULL;
break;
}
}
}
if (Context)
{
CcUnpinData(Context);
}
}
if (!LabelFound)
{
/* Add new entry for label */
if (!vfatFindDirSpace(DeviceExt, pRootFcb, 1, &VolumeLabelDirIndex))
Status = STATUS_DISK_FULL;
else
{
FileOffset.u.HighPart = 0;
FileOffset.u.LowPart = VolumeLabelDirIndex * SizeDirEntry;
Status = STATUS_SUCCESS;
_SEH2_TRY
{
CcPinRead(pRootFcb->FileObject, &FileOffset, SizeDirEntry, PIN_WAIT, &Context, (PVOID*)&Entry);
}
_SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
{
Status = _SEH2_GetExceptionCode();
}
_SEH2_END;
if (NT_SUCCESS(Status))
{
RtlCopyMemory(Entry, &VolumeLabelDirEntry, SizeDirEntry);
CcSetDirtyPinnedData(Context, NULL);
CcUnpinData(Context);
Status = STATUS_SUCCESS;
}
}
}
vfatReleaseFCB(DeviceExt, pRootFcb);
if (!NT_SUCCESS(Status))
{
return Status;
}
/* Update volume label in memory */
DeviceObject->Vpb->VolumeLabelLength = (USHORT)FsLabelInfo->VolumeLabelLength;
RtlCopyMemory(DeviceObject->Vpb->VolumeLabel, FsLabelInfo->VolumeLabel, DeviceObject->Vpb->VolumeLabelLength);
return Status;
}
/*
* FUNCTION: Retrieve the specified volume information
*/
NTSTATUS
VfatQueryVolumeInformation(
PVFAT_IRP_CONTEXT IrpContext)
{
FS_INFORMATION_CLASS FsInformationClass;
NTSTATUS RC = STATUS_SUCCESS;
PVOID SystemBuffer;
ULONG BufferLength;
/* PRECONDITION */
ASSERT(IrpContext);
DPRINT("VfatQueryVolumeInformation(IrpContext %p)\n", IrpContext);
if (!ExAcquireResourceSharedLite(&((PDEVICE_EXTENSION)IrpContext->DeviceObject->DeviceExtension)->DirResource,
BooleanFlagOn(IrpContext->Flags, IRPCONTEXT_CANWAIT)))
{
DPRINT1("DirResource failed!\n");
return VfatMarkIrpContextForQueue(IrpContext);
}
/* INITIALIZATION */
FsInformationClass = IrpContext->Stack->Parameters.QueryVolume.FsInformationClass;
BufferLength = IrpContext->Stack->Parameters.QueryVolume.Length;
SystemBuffer = IrpContext->Irp->AssociatedIrp.SystemBuffer;
DPRINT("FsInformationClass %d\n", FsInformationClass);
DPRINT("SystemBuffer %p\n", SystemBuffer);
RtlZeroMemory(SystemBuffer, BufferLength);
switch (FsInformationClass)
{
case FileFsVolumeInformation:
RC = FsdGetFsVolumeInformation(IrpContext->DeviceObject,
SystemBuffer,
&BufferLength);
break;
case FileFsAttributeInformation:
RC = FsdGetFsAttributeInformation(IrpContext->DeviceObject->DeviceExtension,
SystemBuffer,
&BufferLength);
break;
case FileFsSizeInformation:
RC = FsdGetFsSizeInformation(IrpContext->DeviceObject,
SystemBuffer,
&BufferLength);
break;
case FileFsDeviceInformation:
RC = FsdGetFsDeviceInformation(IrpContext->DeviceObject,
SystemBuffer,
&BufferLength);
break;
case FileFsFullSizeInformation:
RC = FsdGetFsFullSizeInformation(IrpContext->DeviceObject,
SystemBuffer,
&BufferLength);
break;
default:
RC = STATUS_NOT_SUPPORTED;
}
ExReleaseResourceLite(&((PDEVICE_EXTENSION)IrpContext->DeviceObject->DeviceExtension)->DirResource);
IrpContext->Irp->IoStatus.Information =
IrpContext->Stack->Parameters.QueryVolume.Length - BufferLength;
return RC;
}
/*
* FUNCTION: Set the specified volume information
*/
NTSTATUS
VfatSetVolumeInformation(
PVFAT_IRP_CONTEXT IrpContext)
{
FS_INFORMATION_CLASS FsInformationClass;
NTSTATUS Status = STATUS_SUCCESS;
PVOID SystemBuffer;
ULONG BufferLength;
PIO_STACK_LOCATION Stack = IrpContext->Stack;
/* PRECONDITION */
ASSERT(IrpContext);
DPRINT("VfatSetVolumeInformation(IrpContext %p)\n", IrpContext);
if (!ExAcquireResourceExclusiveLite(&((PDEVICE_EXTENSION)IrpContext->DeviceObject->DeviceExtension)->DirResource,
BooleanFlagOn(IrpContext->Flags, IRPCONTEXT_CANWAIT)))
{
return VfatMarkIrpContextForQueue(IrpContext);
}
FsInformationClass = Stack->Parameters.SetVolume.FsInformationClass;
BufferLength = Stack->Parameters.SetVolume.Length;
SystemBuffer = IrpContext->Irp->AssociatedIrp.SystemBuffer;
DPRINT("FsInformationClass %d\n", FsInformationClass);
DPRINT("BufferLength %u\n", BufferLength);
DPRINT("SystemBuffer %p\n", SystemBuffer);
switch (FsInformationClass)
{
case FileFsLabelInformation:
Status = FsdSetFsLabelInformation(IrpContext->DeviceObject,
SystemBuffer);
break;
default:
Status = STATUS_NOT_SUPPORTED;
}
ExReleaseResourceLite(&((PDEVICE_EXTENSION)IrpContext->DeviceObject->DeviceExtension)->DirResource);
IrpContext->Irp->IoStatus.Information = 0;
return Status;
}
/* EOF */