xbox-kernel/private/ntos/fatx/create.c
2020-09-30 17:17:25 +02:00

1699 lines
50 KiB
C

/*++
Copyright (c) 2000-2001 Microsoft Corporation
Module Name:
create.c
Abstract:
This module implements routines related to handling IRP_MJ_CREATE.
--*/
#include "fatx.h"
NTSTATUS
FatxLookupElementNameInDirectory(
IN PFAT_VOLUME_EXTENSION VolumeExtension,
IN PIRP Irp,
IN PFAT_FCB DirectoryFcb,
IN POBJECT_STRING ElementName,
OUT PDIRENT ReturnedDirectoryEntry,
OUT PULONG ReturnedDirectoryByteOffset,
OUT PULONG ReturnedEmptyDirectoryByteOffset
)
/*++
Routine Description:
This routine looks up the supplied file name in the supplied directory
file control block.
Arguments:
VolumeExtension - Specifies the extension that the I/O request is for.
Irp - Specifies the packet that describes the I/O request.
DirectoryFcb - Specifies the file control block that describes where to
read the directory stream from.
ElementName - Specifies the name to search for in the directory.
ReturnedDirectoryEntry - Specifies the buffer to receive the directory entry
for the file if found.
ReturnedDirectoryByteOffset - Specifies the buffer to receive the byte
offset of the entry in the directory stream.
ReturnedEmptyDirectoryByteOffset - Specifies the buffer to receive the byte
offset of the first free directory empty. The value is only meaningful
if STATUS_OBJECT_NAME_NOT_FOUND is returned. MAXULONG is returned if we
don't find an empty slot before finding the end of the directory stream.
Return Value:
Status of operation.
--*/
{
NTSTATUS status;
ULONG EmptyDirectoryByteOffset;
ULONG DirectoryByteOffset;
ULONGLONG PhysicalByteOffset;
ULONG PhysicalRunLength;
PVOID CacheBuffer;
PDIRENT DirectoryEntry;
OBJECT_STRING DirectoryEntryFileName;
ULONG ClusterNumber;
ULONG ClusterBytesRemaining;
PDIRENT EndingDirectoryEntry;
ASSERT(FatxIsFlagSet(DirectoryFcb->Flags, FAT_FCB_DIRECTORY));
ASSERT(VolumeExtension->VolumeMutexExclusiveOwner == KeGetCurrentThread());
CacheBuffer = NULL;
EmptyDirectoryByteOffset = MAXULONG;
//
// If the directory byte offset lookup hint is non-zero, then the caller is
// enumerating files in this directory. To speed up opens for the file that
// was last enumerated, we'll compare the directory entry at this byte
// offset against the desired name. If this fails, reset the hint so that
// future lookups aren't impacted.
//
DirectoryByteOffset = DirectoryFcb->Directory.DirectoryByteOffsetLookupHint;
if (DirectoryByteOffset != 0) {
//
// Get the physical byte offset corresponding to the file's directory
// byte offset.
//
status = FatxFileByteOffsetToPhysicalByteOffset(VolumeExtension, Irp,
DirectoryFcb, DirectoryByteOffset, FALSE, &PhysicalByteOffset,
&PhysicalRunLength);
if (!NT_SUCCESS(status)) {
goto CleanupAndExit;
}
//
// Map the directory entry into the cache.
//
status = FscMapBuffer(&VolumeExtension->CacheExtension, Irp,
PhysicalByteOffset, FALSE, &CacheBuffer);
if (!NT_SUCCESS(status)) {
goto CleanupAndExit;
}
//
// Check if the file name matches the name we're looking for.
//
DirectoryEntry = (PDIRENT)CacheBuffer;
DirectoryEntryFileName.Length = DirectoryEntry->FileNameLength;
DirectoryEntryFileName.Buffer = DirectoryEntry->FileName;
if ((DirectoryEntryFileName.Length == ElementName->Length) &&
(DirectoryEntryFileName.Length <= FAT_FILE_NAME_LENGTH) &&
RtlEqualObjectString(&DirectoryEntryFileName, ElementName, TRUE)) {
//
// Copy the directory entry and its file byte offset back into the
// caller's buffers.
//
*ReturnedDirectoryEntry = *DirectoryEntry;
*ReturnedDirectoryByteOffset = DirectoryByteOffset;
status = STATUS_SUCCESS;
goto CleanupAndExit;
}
//
// Unmap this cache buffer.
//
FscUnmapBuffer(CacheBuffer);
CacheBuffer = NULL;
//
// Reset the directory byte offset lookup hint so that we don't keep
// checking this specific directory entry.
//
DirectoryFcb->Directory.DirectoryByteOffsetLookupHint = 0;
DirectoryByteOffset = 0;
}
//
// Get the first cluster of the directory stream and validate it.
//
ClusterNumber = DirectoryFcb->FirstCluster;
if (!FatxIsValidCluster(VolumeExtension, ClusterNumber)) {
FatxDbgPrint(("FATX: invalid starting cluster for directory\n"));
status = STATUS_FILE_CORRUPT_ERROR;
goto CleanupAndExit;
}
//
// Process the directory stream.
//
for (;;) {
//
// Compute the physical byte offset of the next cluster and the number
// of bytes remaining in this cluster.
//
PhysicalByteOffset = FatxClusterToPhysicalByteOffset(VolumeExtension,
ClusterNumber);
ClusterBytesRemaining = VolumeExtension->BytesPerCluster;
//
// The starting byte offset for the file area is always page aligned and
// the cluster size is a multiple of the page size, so we can make some
// optimizations below.
//
ASSERT(BYTE_OFFSET(PhysicalByteOffset) == 0);
ASSERT(BYTE_OFFSET(ClusterBytesRemaining) == 0);
do {
//
// Map in the next page of the cluster.
//
status = FscMapBuffer(&VolumeExtension->CacheExtension, Irp,
PhysicalByteOffset, FALSE, &CacheBuffer);
if (!NT_SUCCESS(status)) {
goto CleanupAndExit;
}
//
// Process the directory entries on this cache buffer.
//
DirectoryEntry = (PDIRENT)CacheBuffer;
EndingDirectoryEntry = (PDIRENT)((PUCHAR)CacheBuffer + PAGE_SIZE);
do {
//
// If we've found the last directory entry in the stream or a
// deleted directory entry, then save off its byte offset in
// case we want to create a new entry.
//
if ((DirectoryEntry->FileNameLength == FAT_DIRENT_NEVER_USED) ||
(DirectoryEntry->FileNameLength == FAT_DIRENT_NEVER_USED2) ||
(DirectoryEntry->FileNameLength == FAT_DIRENT_DELETED)) {
if (EmptyDirectoryByteOffset == MAXULONG) {
EmptyDirectoryByteOffset = DirectoryByteOffset;
}
}
//
// If we find a directory entry with a file name starting with
// FAT_DIRENT_NEVER_USED or FAT_DIRENT_NEVER_USED2, then we're
// at the end of the directory stream.
//
if ((DirectoryEntry->FileNameLength == FAT_DIRENT_NEVER_USED) ||
(DirectoryEntry->FileNameLength == FAT_DIRENT_NEVER_USED2)) {
status = STATUS_OBJECT_NAME_NOT_FOUND;
goto CleanupAndExit;
}
//
// Check if the file name matches the name we're looking for.
//
DirectoryEntryFileName.Length = DirectoryEntry->FileNameLength;
DirectoryEntryFileName.Buffer = DirectoryEntry->FileName;
if ((DirectoryEntryFileName.Length == ElementName->Length) &&
(DirectoryEntryFileName.Length <= FAT_FILE_NAME_LENGTH) &&
RtlEqualObjectString(&DirectoryEntryFileName, ElementName, TRUE)) {
//
// Copy the directory entry and its file byte offset back
// into the caller's buffers.
//
*ReturnedDirectoryEntry = *DirectoryEntry;
*ReturnedDirectoryByteOffset = DirectoryByteOffset;
status = STATUS_SUCCESS;
goto CleanupAndExit;
}
//
// Advance to the next directory entry.
//
DirectoryEntry++;
DirectoryByteOffset += sizeof(DIRENT);
} while (DirectoryEntry < EndingDirectoryEntry);
//
// Unmap this cache buffer.
//
FscUnmapBuffer(CacheBuffer);
CacheBuffer = NULL;
//
// Adjust the number of bytes remaining in this cluster and the next
// cache byte offset.
//
PhysicalByteOffset += PAGE_SIZE;
ClusterBytesRemaining -= PAGE_SIZE;
} while (ClusterBytesRemaining > 0);
//
// Advance to the next cluster.
//
status = FatxFileByteOffsetToCluster(VolumeExtension, Irp, DirectoryFcb,
DirectoryByteOffset, &ClusterNumber, NULL);
if (status == STATUS_END_OF_FILE) {
status = STATUS_OBJECT_NAME_NOT_FOUND;
}
if (!NT_SUCCESS(status)) {
goto CleanupAndExit;
}
//
// Don't allow a directory to exceed the maximum allowed directory size.
// We check for this case after the call to FatxFileByteOffsetToCluster
// so that a directory that's exactly the maximum allowed directory size
// will properly fill its allocation size and ending cluster number for
// the sake of code further downstream.
//
if (NT_SUCCESS(status) &&
(DirectoryByteOffset >= FAT_MAXIMUM_DIRECTORY_FILE_SIZE)) {
status = STATUS_FILE_CORRUPT_ERROR;
goto CleanupAndExit;
}
}
CleanupAndExit:
if (CacheBuffer != NULL) {
FscUnmapBuffer(CacheBuffer);
}
#if DBG
//
// We should only use the empty directory byte offset when we return
// STATUS_OBJECT_NAME_NOT_FOUND. For any other return code, return a bogus
// direcotory byte offset so that we catch illegal uses of the byte offset.
//
if (status != STATUS_OBJECT_NAME_NOT_FOUND) {
EmptyDirectoryByteOffset = MAXULONG - 1;
}
#endif
*ReturnedEmptyDirectoryByteOffset = EmptyDirectoryByteOffset;
return status;
}
NTSTATUS
FatxCheckDesiredAccess(
IN ACCESS_MASK DesiredAccess,
IN ULONG CreateOptions,
IN UCHAR FileAttributes,
IN BOOLEAN CreatingFile
)
/*++
Routine Description:
This routine checks that the desired access mask is compatible with the
supplied file attributes. For example, a file that's marked read-only
can't be opened for write access.
Arguments:
DesiredAccess - Specifies the access rights that the caller would like for
the file handle.
CreateOptions - Specifies the options controlling how the file is to be
created or opened.
FileAttributes - Specifies the file attributes of the file to be opened.
CreatingFile - Specifies whether or not we're in the process of creating a
new file.
Return Value:
Status of operation.
--*/
{
//
// Never allow a volume ID or device to be opened.
//
if (!FatxIsValidFileAttributes(FileAttributes)) {
return STATUS_ACCESS_DENIED;
}
//
// Check the access mask depending on whether this is a directory or not.
//
if (FatxIsFlagSet(FileAttributes, FILE_ATTRIBUTE_DIRECTORY)) {
if (FatxIsFlagSet(DesiredAccess, ~(DELETE | READ_CONTROL | WRITE_OWNER |
WRITE_DAC | SYNCHRONIZE | ACCESS_SYSTEM_SECURITY | FILE_WRITE_DATA |
FILE_READ_EA | FILE_WRITE_EA | FILE_READ_ATTRIBUTES |
FILE_WRITE_ATTRIBUTES | FILE_LIST_DIRECTORY | FILE_TRAVERSE |
FILE_DELETE_CHILD | FILE_APPEND_DATA))) {
return STATUS_ACCESS_DENIED;
}
} else {
if (FatxIsFlagSet(DesiredAccess, ~(DELETE | READ_CONTROL | WRITE_OWNER |
WRITE_DAC | SYNCHRONIZE | ACCESS_SYSTEM_SECURITY | FILE_READ_DATA |
FILE_WRITE_DATA | FILE_READ_EA | FILE_WRITE_EA | FILE_READ_ATTRIBUTES |
FILE_WRITE_ATTRIBUTES | FILE_EXECUTE | FILE_DELETE_CHILD |
FILE_APPEND_DATA))) {
return STATUS_ACCESS_DENIED;
}
}
//
// Check the access mask if this is a read-only file.
//
if (FatxIsFlagSet(FileAttributes, FILE_ATTRIBUTE_READONLY)) {
if (!CreatingFile &&
FatxIsFlagSet(DesiredAccess, ~(DELETE | READ_CONTROL | WRITE_OWNER |
WRITE_DAC | SYNCHRONIZE | ACCESS_SYSTEM_SECURITY | FILE_READ_DATA |
FILE_READ_EA | FILE_WRITE_EA | FILE_READ_ATTRIBUTES |
FILE_WRITE_ATTRIBUTES | FILE_EXECUTE | FILE_LIST_DIRECTORY |
FILE_TRAVERSE))) {
return STATUS_ACCESS_DENIED;
}
if (FatxIsFlagSet(CreateOptions, FILE_DELETE_ON_CLOSE)) {
return STATUS_CANNOT_DELETE;
}
}
return STATUS_SUCCESS;
}
NTSTATUS
FatxOpenTargetDirectory(
IN PFAT_VOLUME_EXTENSION VolumeExtension,
IN PIRP Irp,
IN PFAT_FCB DirectoryFcb,
IN ULONG FileExists,
IN BOOLEAN NoReferenceCount
)
/*++
Routine Description:
This routine opens the supplied target directory for a rename operation.
Arguments:
VolumeExtension - Specifies the extension that the I/O request is for.
Irp - Specifies the packet that describes the I/O request.
DirectoryFcb - Specifies the file control block of the target directory to
open.
FileExists - Specifies the value, FILE_EXISTS or FILE_DOES_NOT_EXIST, that
should be placed in the IRP's information field on success.
NoReferenceCount - Specifies whether or not the reference count for
the directory file control block should be incremented.
Return Value:
Status of operation.
--*/
{
NTSTATUS status;
PIO_STACK_LOCATION IrpSp;
ACCESS_MASK DesiredAccess;
USHORT ShareAccess;
PFILE_OBJECT FileObject;
IrpSp = IoGetCurrentIrpStackLocation(Irp);
DesiredAccess = IrpSp->Parameters.Create.DesiredAccess;
ShareAccess = IrpSp->Parameters.Create.ShareAccess;
FileObject = IrpSp->FileObject;
//
// It's possible for the file control block to be NULL if a file failed to
// be moved from one directory to another. In that case,
// FatxSetRenameInformation sets the file's parent file control block to
// NULL.
//
if (DirectoryFcb == NULL) {
return STATUS_FILE_CORRUPT_ERROR;
}
ASSERT(FatxIsFlagSet(DirectoryFcb->Flags, FAT_FCB_DIRECTORY));
//
// If this is the first open handle to the file, then initialize the sharing
// mode. Otherwise, verify that the requested sharing mode is compatible
// with the current sharing mode.
//
if (DirectoryFcb->ShareAccess.OpenCount == 0) {
IoSetShareAccess(DesiredAccess, ShareAccess, FileObject,
&DirectoryFcb->ShareAccess);
status = STATUS_SUCCESS;
} else {
status = IoCheckShareAccess(DesiredAccess, ShareAccess, FileObject,
&DirectoryFcb->ShareAccess, TRUE);
}
//
// Fill in the file object with the file control block.
//
if (NT_SUCCESS(status)) {
if (!NoReferenceCount) {
DirectoryFcb->ReferenceCount++;
}
FileObject->FsContext = DirectoryFcb;
FileObject->FsContext2 = NULL;
VolumeExtension->FileObjectCount++;
//
// Indicate to the caller whether or not a file already exists with the
// specified name.
//
Irp->IoStatus.Information = FileExists;
status = STATUS_SUCCESS;
}
return status;
}
NTSTATUS
FatxCreateNewFile(
IN PFAT_VOLUME_EXTENSION VolumeExtension,
IN PIRP Irp,
IN PFAT_FCB DirectoryFcb,
IN POBJECT_STRING ElementName,
IN ULONG EmptyDirectoryByteOffset,
IN UCHAR FileAttributes,
IN ULONG AllocationSize,
OUT PDIRENT DirectoryEntry,
OUT PFAT_FCB *ReturnedFcb
)
/*++
Routine Description:
This routine is called to create a new file or directory.
Arguments:
VolumeExtension - Specifies the extension that the I/O request is for.
Irp - Specifies the packet that describes the I/O request.
DirectoryFcb - Specifies the directory to create the new file in.
ElementName - Specifies the name of the file to create.
EmptyDirectoryByteOffset - Specifies the byte offset of the first free
directory entry or MAXULONG if there aren't any free entries in the
directory.
FileAttributes - Specifies the new attributes for the file.
AllocationSize - Specifies the new allocation size for the file.
DirectoryEntry - Specifies a local buffer that can be used to construct the
directory entry.
ReturnedFcb - Specifies the buffer to receive the created file control
block.
Return Value:
Status of operation.
--*/
{
NTSTATUS status;
FAT_CLUSTER_RUN ClusterRuns[FAT_MAXIMUM_CLUSTER_RUNS];
ULONG NumberOfClusterRuns;
ULONG FirstCluster;
ULONG EndingCluster;
LARGE_INTEGER CreationTime;
PFAT_FCB Fcb;
ASSERT(FatxIsFlagSet(DirectoryFcb->Flags, FAT_FCB_DIRECTORY));
ASSERT(ElementName->Length != 0);
ASSERT(ElementName->Length <= FAT_FILE_NAME_LENGTH);
//
// If there isn't an empty directory entry, then we'll need to add another
// cluster to the directory.
//
if (EmptyDirectoryByteOffset == MAXULONG) {
//
// If we found the end of the directory stream, then we must have
// established how many bytes are allocated to the file.
//
ASSERT(DirectoryFcb->AllocationSize != MAXULONG);
//
// The new directory empty will be placed at the start of the new
// extension.
//
EmptyDirectoryByteOffset = DirectoryFcb->AllocationSize;
//
// Attempt to add another cluster to the directory's allocation.
//
status = FatxExtendDirectoryAllocation(VolumeExtension, Irp,
DirectoryFcb);
if (!NT_SUCCESS(status)) {
return status;
}
}
//
// Figure out if we need to allocate some clusters to this file now or not.
//
if (FatxIsFlagSet(FileAttributes, FILE_ATTRIBUTE_DIRECTORY)) {
//
// Allocate a single cluster.
//
status = FatxAllocateClusters(VolumeExtension, Irp, FAT_CLUSTER_NULL, 1,
ClusterRuns, &NumberOfClusterRuns, &EndingCluster);
if (!NT_SUCCESS(status)) {
return status;
}
ASSERT(NumberOfClusterRuns == 1);
ASSERT(EndingCluster == ClusterRuns[0].PhysicalClusterNumber);
FirstCluster = ClusterRuns[0].PhysicalClusterNumber;
AllocationSize = VolumeExtension->BytesPerCluster;
//
// Initialize the contents of the directory cluster.
//
status = FatxInitializeDirectoryCluster(VolumeExtension, Irp, FirstCluster);
if (!NT_SUCCESS(status)) {
goto CleanupAndExit;
}
} else if (AllocationSize != 0) {
//
// Allocate the requested number of clusters.
//
AllocationSize = FatxRoundToClusters(VolumeExtension, AllocationSize);
status = FatxAllocateClusters(VolumeExtension, Irp, FAT_CLUSTER_NULL,
AllocationSize >> VolumeExtension->ClusterShift, ClusterRuns,
&NumberOfClusterRuns, &EndingCluster);
if (!NT_SUCCESS(status)) {
return status;
}
FirstCluster = ClusterRuns[0].PhysicalClusterNumber;
} else {
//
// The file doesn't require an initial allocation.
//
FirstCluster = FAT_CLUSTER_NULL;
EndingCluster = FAT_CLUSTER_NULL;
NumberOfClusterRuns = 0;
status = STATUS_SUCCESS;
}
//
// Construct a copy of the new directory entry on the stack.
//
RtlZeroMemory(DirectoryEntry, sizeof(DIRENT));
DirectoryEntry->FileNameLength = (UCHAR)ElementName->Length;
RtlCopyMemory(DirectoryEntry->FileName, ElementName->Buffer,
ElementName->Length);
DirectoryEntry->FileAttributes = FileAttributes;
DirectoryEntry->FirstCluster = FirstCluster;
KeQuerySystemTime(&CreationTime);
FatxTimeToFatTimestamp(&CreationTime, &DirectoryEntry->CreationTime);
DirectoryEntry->LastWriteTime = DirectoryEntry->CreationTime;
DirectoryEntry->LastAccessTime = DirectoryEntry->CreationTime;
//
// Construct a file control block for the desired file from the directory
// entry on the stack.
//
status = FatxCreateFcb(DirectoryFcb, FirstCluster, DirectoryEntry,
EmptyDirectoryByteOffset, &Fcb);
if (!NT_SUCCESS(status)) {
goto CleanupAndExit;
}
//
// Commit the directory entry.
//
status = FatxUpdateDirectoryEntry(VolumeExtension, Irp, Fcb);
if (!NT_SUCCESS(status)) {
FatxDereferenceFcb(Fcb);
goto CleanupAndExit;
}
//
// If the file or directory was given an initial allocation, then fill in
// the allocation size and ending cluster number for the file control block.
// Copy the allocated cluster runs into the file's cluster cache.
//
if (AllocationSize != 0) {
ASSERT(Fcb->AllocationSize == MAXULONG);
Fcb->AllocationSize = AllocationSize;
Fcb->EndingCluster = EndingCluster;
FatxAppendClusterRunsToClusterCache(Fcb, 0, ClusterRuns,
NumberOfClusterRuns);
} else {
ASSERT(Fcb->AllocationSize == 0);
ASSERT(Fcb->EndingCluster == FAT_CLUSTER_NULL);
}
*ReturnedFcb = Fcb;
status = STATUS_SUCCESS;
CleanupAndExit:
if (!NT_SUCCESS(status)) {
FatxFreeClusters(VolumeExtension, Irp, FirstCluster, FALSE);
}
return status;
}
NTSTATUS
FatxOverwriteExistingFile(
IN PFAT_VOLUME_EXTENSION VolumeExtension,
IN PIRP Irp,
IN PFAT_FCB FileFcb,
IN UCHAR FileAttributes,
IN ULONG AllocationSize
)
/*++
Routine Description:
This routine is called to overwrite an existing file with the file having
the supplied file attributes and allocation size.
Arguments:
VolumeExtension - Specifies the extension that the I/O request is for.
Irp - Specifies the packet that describes the I/O request.
FileFcb - Specifies the file control block that describes the file to
overwrite.
FileAttributes - Specifies the new attributes for the file.
AllocationSize - Specifies the new allocation size for the file.
Return Value:
Status of operation.
--*/
{
NTSTATUS status;
UCHAR OriginalFileAttributes;
ULONG OriginalFileSize;
LARGE_INTEGER OriginalLastWriteTime;
FAT_TIME_STAMP OriginalCreationTime;
FAT_TIME_STAMP OriginalLastAccessTime;
ASSERT(FatxIsFlagClear(FileFcb->Flags, FAT_FCB_DIRECTORY));
//
// Verify that the attributes don't try to change this file into a
// directory.
//
if (FatxIsFlagSet(FileAttributes, FILE_ATTRIBUTE_DIRECTORY)) {
return STATUS_INVALID_PARAMETER;
}
//
// If the file has already been opened, then we can't safely truncate the
// file. We hold the volume's mutex for exclusive access and would need to
// acquire the file's mutex for exclusive access, but that's a violation of
// our locking order. Instead of trying to rearrange the code to fix the
// locking order, treat this as a sharing violation.
//
// For the case of a reference count of exactly one, we know that no other
// thread could have a reference to this file control block, so there's no
// possibility that we can dead lock by acquiring the file's mutex for
// exclusive access here. We go to the effort of acquiring the file mutex
// to satisfy the assertions in FatxSetAllocationSize.
//
if (FileFcb->ReferenceCount >= 2) {
return STATUS_SHARING_VIOLATION;
}
FatxAcquireFileMutexExclusive(FileFcb);
//
// Save off the fields from the file control block that we're going to
// change so that we can back out any changes if we fail below.
//
OriginalFileAttributes = FileFcb->FileAttributes;
OriginalFileSize = FileFcb->FileSize;
OriginalLastWriteTime = FileFcb->LastWriteTime;
OriginalCreationTime = FileFcb->CreationTime;
OriginalLastAccessTime = FileFcb->LastAccessTime;
//
// Fill in the file control block with the values for an overwritten file
// and flag that the directory entry needs to be updated.
//
FileFcb->FileAttributes = FileAttributes;
FileFcb->FileSize = 0;
FileFcb->Flags &= ~FAT_FCB_DISABLE_LAST_WRITE_TIME;
FileFcb->Flags |= FAT_FCB_UPDATE_DIRECTORY_ENTRY;
KeQuerySystemTime(&FileFcb->LastWriteTime);
FatxTimeToFatTimestamp(&FileFcb->LastWriteTime, &FileFcb->CreationTime);
FileFcb->LastAccessTime = FileFcb->CreationTime;
//
// Set the allocation size for the file. The directory entry may be updated
// inside this call.
//
status = FatxSetAllocationSize(VolumeExtension, Irp, FileFcb, AllocationSize,
TRUE, FALSE);
//
// If changing the allocation size for the file didn't cause the directory
// entry to be updated, then do it here.
//
if (NT_SUCCESS(status) &&
FatxIsFlagSet(FileFcb->Flags, FAT_FCB_UPDATE_DIRECTORY_ENTRY)) {
status = FatxUpdateDirectoryEntry(VolumeExtension, Irp, FileFcb);
}
//
// If we failed for any reason, then restore the fields of the file control
// block so that we still mirror the on-disk structures.
//
if (!NT_SUCCESS(status)) {
FileFcb->FileAttributes = OriginalFileAttributes;
FileFcb->FileSize = OriginalFileSize;
FileFcb->LastWriteTime = OriginalLastWriteTime;
FileFcb->CreationTime = OriginalCreationTime;
FileFcb->LastAccessTime = OriginalLastAccessTime;
}
FatxReleaseFileMutex(FileFcb);
return status;
}
NTSTATUS
FatxFsdCreate(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
/*++
Routine Description:
This routine is called by the I/O manager to handle IRP_MJ_CREATE requests.
Arguments:
DeviceObject - Specifies the device object that the I/O request is for.
Irp - Specifies the packet that describes the I/O request.
Return Value:
Status of operation.
--*/
{
NTSTATUS status;
PFAT_VOLUME_EXTENSION VolumeExtension;
PIO_STACK_LOCATION IrpSp;
ACCESS_MASK DesiredAccess;
UCHAR FileAttributes;
USHORT ShareAccess;
ULONG CreateOptions;
ULONG CreateDisposition;
PFILE_OBJECT FileObject;
OBJECT_STRING RemainingName;
PFILE_OBJECT RelatedFileObject;
PFAT_FCB CurrentFcb;
BOOLEAN TrailingBackslash;
BOOLEAN CreateFcbCalled;
OBJECT_STRING ElementName;
DIRENT DirectoryEntry;
ULONG DirectoryByteOffset;
ULONG EmptyDirectoryByteOffset;
PFAT_FCB FoundOrNewFcb;
VolumeExtension = (PFAT_VOLUME_EXTENSION)DeviceObject->DeviceExtension;
IrpSp = IoGetCurrentIrpStackLocation(Irp);
DesiredAccess = IrpSp->Parameters.Create.DesiredAccess;
FileAttributes =
(UCHAR)FatxFilterFileAttributes(IrpSp->Parameters.Create.FileAttributes);
ShareAccess = IrpSp->Parameters.Create.ShareAccess;
CreateOptions = IrpSp->Parameters.Create.Options;
CreateDisposition = (CreateOptions >> 24) & 0xFF;
FileObject = IrpSp->FileObject;
RemainingName = *IrpSp->Parameters.Create.RemainingName;
RelatedFileObject = FileObject->RelatedFileObject;
//
// Always grab the exclusive lock because we could end up writing to the
// disk and we also use this lock to guard access to the file control block
// and file control block list.
//
FatxAcquireVolumeMutexExclusive(VolumeExtension);
//
// Check if the volume has been dismounted.
//
if (FatxIsFlagSet(VolumeExtension->Flags, FAT_VOLUME_DISMOUNTED)) {
status = STATUS_VOLUME_DISMOUNTED;
goto CleanupAndExit;
}
//
// Don't allow a file to be opened based on its file ID.
//
if (FatxIsFlagSet(CreateOptions, FILE_OPEN_BY_FILE_ID)) {
status = STATUS_NOT_IMPLEMENTED;
goto CleanupAndExit;
}
//
// Verify that the initial file allocation size is limited to 32-bits.
//
if (Irp->Overlay.AllocationSize.HighPart != 0) {
status = STATUS_INVALID_PARAMETER;
goto CleanupAndExit;
}
//
// Determine whether we parse the file name from the root of the volume or
// from a subdirectory by looking at RelatedFileObject.
//
if (RelatedFileObject != NULL) {
//
// Grab the file control block out of the related file object.
//
CurrentFcb = (PFAT_FCB)RelatedFileObject->FsContext;
//
// Verify that the related file object is really a directory object.
// Note that the file control block could be NULL if the file object was
// opened as a result of a direct device open in the I/O manager.
//
if ((CurrentFcb == NULL) ||
FatxIsFlagClear(CurrentFcb->Flags, FAT_FCB_DIRECTORY)) {
status = STATUS_INVALID_PARAMETER;
goto CleanupAndExit;
}
//
// If the directory is marked for deletion, then the directory won't
// contain any files and it won't allow any files to be created, so bail
// out now. This also catches code that uses a directory file object
// after its handle has been closed and the directory has been deleted,
// such as for symbolic link objects.
//
if (FatxIsFlagSet(CurrentFcb->Flags, FAT_FCB_DELETE_ON_CLOSE)) {
status = STATUS_DELETE_PENDING;
goto CleanupAndExit;
}
//
// Check if we're supposed to open the related directory.
//
if (RemainingName.Length == 0) {
if (FatxIsFlagSet(IrpSp->Flags, SL_OPEN_TARGET_DIRECTORY)) {
status = FatxOpenTargetDirectory(VolumeExtension, Irp,
CurrentFcb->ParentFcb, FILE_EXISTS, FALSE);
goto CleanupAndExit;
}
CurrentFcb->ReferenceCount++;
status = STATUS_SUCCESS;
goto OpenStartDirectoryFcb;
}
//
// Verify that this is not an absolute path.
//
if (RemainingName.Buffer[0] == OBJ_NAME_PATH_SEPARATOR) {
status = STATUS_OBJECT_NAME_INVALID;
goto CleanupAndExit;
}
//
// Verify that the path doesn't exceed the length restictions.
//
if ((CurrentFcb->PathNameLength + sizeof(OBJ_NAME_PATH_SEPARATOR) +
RemainingName.Length) > FAT_PATH_NAME_LIMIT * sizeof(OCHAR)) {
status = STATUS_OBJECT_NAME_INVALID;
goto CleanupAndExit;
}
} else {
//
// Check if we're supposed to open the physical volume.
//
if (RemainingName.Length == 0) {
CurrentFcb = (PFAT_FCB)&VolumeExtension->VolumeFcb;
//
// Verify that the create disposition allows us to open an existing
// file.
//
if ((CreateDisposition != FILE_OPEN) &&
(CreateDisposition != FILE_OPEN_IF)) {
status = STATUS_ACCESS_DENIED;
goto CleanupAndExit;
}
//
// The caller shouldn't be expecting to see a directory file.
//
if (FatxIsFlagSet(CreateOptions, FILE_DIRECTORY_FILE)) {
status = STATUS_NOT_A_DIRECTORY;
goto CleanupAndExit;
}
//
// The physical volume cannot be deleted.
//
if (FatxIsFlagSet(CreateOptions, FILE_DELETE_ON_CLOSE)) {
status = STATUS_CANNOT_DELETE;
goto CleanupAndExit;
}
//
// The physical volume cannot be renamed.
//
if (FatxIsFlagSet(IrpSp->Flags, SL_OPEN_TARGET_DIRECTORY)) {
status = STATUS_INVALID_PARAMETER;
goto CleanupAndExit;
}
//
// Physical volume access is always non-cached. Mark the file
// object as non-cached so that the I/O manager enforces alignment
// requirements.
//
FileObject->Flags |= FO_NO_INTERMEDIATE_BUFFERING;
//
// If this is the first open handle to the file, then initialize the
// sharing mode. Otherwise, verify that the requested sharing mode
// is compatible with the current sharing mode.
//
if (CurrentFcb->ShareAccess.OpenCount == 0) {
IoSetShareAccess(DesiredAccess, ShareAccess, FileObject,
&CurrentFcb->ShareAccess);
status = STATUS_SUCCESS;
} else {
status = IoCheckShareAccess(DesiredAccess, ShareAccess,
FileObject, &CurrentFcb->ShareAccess, TRUE);
}
//
// Fill in the file object with the file control block.
//
if (NT_SUCCESS(status)) {
CurrentFcb->ReferenceCount++;
FileObject->FsContext = CurrentFcb;
FileObject->FsContext2 = NULL;
VolumeExtension->FileObjectCount++;
//
// Indicate to the caller that we opened the file as opposed to creating
// or overwriting the file.
//
Irp->IoStatus.Information = FILE_OPENED;
status = STATUS_SUCCESS;
}
goto CleanupAndExit;
}
//
// Start searching relative to the root directory.
//
CurrentFcb = VolumeExtension->RootDirectoryFcb;
//
// Verify that this is an absolute path.
//
if (RemainingName.Buffer[0] != OBJ_NAME_PATH_SEPARATOR) {
status = STATUS_OBJECT_NAME_INVALID;
goto CleanupAndExit;
}
//
// Check if we're supposed to open the root directory.
//
if (RemainingName.Length == sizeof(OCHAR)) {
//
// The root directory cannot be renamed.
//
if (FatxIsFlagSet(IrpSp->Flags, SL_OPEN_TARGET_DIRECTORY)) {
status = STATUS_INVALID_PARAMETER;
goto CleanupAndExit;
}
CurrentFcb->ReferenceCount++;
status = STATUS_SUCCESS;
goto OpenStartDirectoryFcb;
}
//
// Verify that the path doesn't exceed the length restictions.
//
if (RemainingName.Length > FAT_PATH_NAME_LIMIT * sizeof(OCHAR)) {
status = STATUS_OBJECT_NAME_INVALID;
goto CleanupAndExit;
}
}
//
// Check if the file name ends in a backslash. If so, strip it off and set
// a flag so that we can later verify that the target file is a directory.
//
// We've already checked for an empty file name or a file name that consists
// of a single backslash above, so we know that before and after this check
// that the remaining name will still have some characters in it.
//
ASSERT(RemainingName.Length > 0);
if (RemainingName.Buffer[(RemainingName.Length / sizeof(OCHAR)) - 1] ==
OBJ_NAME_PATH_SEPARATOR) {
RemainingName.Length -= sizeof(OCHAR);
TrailingBackslash = TRUE;
} else {
TrailingBackslash = FALSE;
}
ASSERT(RemainingName.Length > 0);
//
// Process the file name. At this point, we're only walking the open file
// control block list.
//
for (;;) {
//
// Pull off the next element of the file name.
//
ObDissectName(RemainingName, &ElementName, &RemainingName);
//
// Verify that there aren't multiple backslashes in the name.
//
if ((RemainingName.Length != 0) && (RemainingName.Buffer[0] ==
OBJ_NAME_PATH_SEPARATOR)) {
status = STATUS_OBJECT_NAME_INVALID;
break;
}
//
// Check if a file control block already exists for this file name.
//
if (!FatxFindOpenChildFcb(CurrentFcb, &ElementName, &FoundOrNewFcb)) {
break;
}
CurrentFcb = FoundOrNewFcb;
//
// If we have consumed the entire name, then the file is already open.
// Bump up the reference count and skip past the on-disk search loop.
//
if (RemainingName.Length == 0) {
if (FatxIsFlagSet(IrpSp->Flags, SL_OPEN_TARGET_DIRECTORY)) {
status = FatxOpenTargetDirectory(VolumeExtension, Irp,
CurrentFcb->ParentFcb, FILE_EXISTS, FALSE);
goto CleanupAndExit;
}
CurrentFcb->ReferenceCount++;
status = STATUS_SUCCESS;
goto OpenCurrentFcb;
}
}
//
// Continue processing the file name. At this point, we're searching
// directory streams for the requested file.
//
CreateFcbCalled = FALSE;
do {
//
// On the first iteration of the loop, we've already dissected the name
// we're looking for so don't dissect another piece of the name.
//
if (CreateFcbCalled) {
//
// Pull off the next element of the file name.
//
ObDissectName(RemainingName, &ElementName, &RemainingName);
//
// Verify that there aren't multiple backslashes in the name.
//
if ((RemainingName.Length != 0) && (RemainingName.Buffer[0] ==
OBJ_NAME_PATH_SEPARATOR)) {
status = STATUS_OBJECT_NAME_INVALID;
break;
}
}
//
// Check if the name is a legal FAT file name.
//
if (!FatxIsValidFatFileName(&ElementName)) {
status = STATUS_OBJECT_NAME_INVALID;
break;
}
//
// Lookup the element in the directory.
//
status = FatxLookupElementNameInDirectory(VolumeExtension, Irp,
CurrentFcb, &ElementName, &DirectoryEntry, &DirectoryByteOffset,
&EmptyDirectoryByteOffset);
if (status == STATUS_OBJECT_NAME_NOT_FOUND) {
//
// If the element wasn't found and we still have more elements to
// process, then the path was not found.
//
if (RemainingName.Length != 0) {
status = STATUS_OBJECT_PATH_NOT_FOUND;
break;
}
//
// If the parent directory is marked for deletion, don't allow any
// files to be created.
//
if (FatxIsFlagSet(CurrentFcb->Flags, FAT_FCB_DELETE_ON_CLOSE)) {
status = STATUS_DELETE_PENDING;
break;
}
//
// If we're inside a move file operation, then call the helper to
// open the target directory and indicate that the file doesn't
// already exist.
//
if (FatxIsFlagSet(IrpSp->Flags, SL_OPEN_TARGET_DIRECTORY)) {
status = FatxOpenTargetDirectory(VolumeExtension, Irp,
CurrentFcb, FILE_DOES_NOT_EXIST, CreateFcbCalled);
if (NT_SUCCESS(status)) {
goto CleanupAndExit;
}
break;
}
//
// If the create disposition doesn't allow us to create files, then
// we're done.
//
if ((CreateDisposition == FILE_OPEN) ||
(CreateDisposition == FILE_OVERWRITE)) {
break;
}
//
// Don't allow a trailing backslash to be mixed with a request to
// create a non-directory file.
//
if (TrailingBackslash &&
FatxIsFlagSet(CreateOptions, FILE_NON_DIRECTORY_FILE)) {
status = STATUS_OBJECT_NAME_INVALID;
break;
}
//
// Ensure that the directory file attribute is set or clear
// depending on the creation options.
//
if (FatxIsFlagSet(CreateOptions, FILE_DIRECTORY_FILE)) {
FileAttributes |= FILE_ATTRIBUTE_DIRECTORY;
} else {
FileAttributes &= ~FILE_ATTRIBUTE_DIRECTORY;
}
//
// Verify that the desired access is compatible with the file's
// desired attributes.
//
status = FatxCheckDesiredAccess(DesiredAccess, CreateOptions,
FileAttributes, TRUE);
if (!NT_SUCCESS(status)) {
break;
}
//
// Create the new file.
//
status = FatxCreateNewFile(VolumeExtension, Irp, CurrentFcb,
&ElementName, EmptyDirectoryByteOffset, FileAttributes,
Irp->Overlay.AllocationSize.LowPart, &DirectoryEntry,
&FoundOrNewFcb);
if (!NT_SUCCESS(status)) {
break;
}
//
// If this is the second or greater iteration of this loop, then we want
// want to release the reference to the parent directory from
// FatxCreateNewFile. The parent directory already has a reference count
// of one from when we created that file control block.
//
if (CreateFcbCalled) {
ASSERT(CurrentFcb->ReferenceCount >= 2);
CurrentFcb->ReferenceCount--;
}
//
// Initializing the sharing mode.
//
IoSetShareAccess(DesiredAccess, ShareAccess, FileObject,
&FoundOrNewFcb->ShareAccess);
//
// Fill in the file object with the file control block that we
// created.
//
FileObject->FsContext = FoundOrNewFcb;
FileObject->FsContext2 = NULL;
VolumeExtension->FileObjectCount++;
//
// Apply the delete on close flag if necessary.
//
if (FatxIsFlagSet(CreateOptions, FILE_DELETE_ON_CLOSE) &&
FatxIsFlagClear(FoundOrNewFcb->Flags, FAT_FCB_DIRECTORY)) {
FoundOrNewFcb->Flags |= FAT_FCB_DELETE_ON_CLOSE;
}
//
// Indicate to the caller that we created a new file as opposed to
// opening an existing file.
//
Irp->IoStatus.Information = FILE_CREATED;
status = STATUS_SUCCESS;
goto CleanupAndExit;
} else if (!NT_SUCCESS(status)) {
break;
}
//
// If there's still more of a path name to process, then verify that the
// file we found is a directory.
//
if ((RemainingName.Length != 0) &&
FatxIsFlagClear(DirectoryEntry.FileAttributes, FILE_ATTRIBUTE_DIRECTORY)) {
status = STATUS_OBJECT_PATH_NOT_FOUND;
break;
}
//
// If there's no more path name to process and we're opening the file
// for a rename operation, then call the helper to open the target
// directory and indicate that the file already exists.
//
if ((RemainingName.Length == 0) &&
FatxIsFlagSet(IrpSp->Flags, SL_OPEN_TARGET_DIRECTORY)) {
status = FatxOpenTargetDirectory(VolumeExtension, Irp, CurrentFcb,
FILE_EXISTS, CreateFcbCalled);
if (NT_SUCCESS(status)) {
goto CleanupAndExit;
}
break;
}
//
// The file isn't already open. Construct a new file control block
// for this file.
//
status = FatxCreateFcb(CurrentFcb, DirectoryEntry.FirstCluster,
&DirectoryEntry, DirectoryByteOffset, &FoundOrNewFcb);
if (!NT_SUCCESS(status)) {
break;
}
//
// If this is the second or greater iteration of this loop, then we want
// to release the reference to the parent directory from FatxCreateFcb.
// The parent directory already has a reference count of one from when
// we created that file control block.
//
if (CreateFcbCalled) {
ASSERT(CurrentFcb->ReferenceCount >= 2);
CurrentFcb->ReferenceCount--;
}
CreateFcbCalled = TRUE;
CurrentFcb = FoundOrNewFcb;
} while (RemainingName.Length != 0);
//
// If we failed to open the file, then before bailing out, we may need to
// dereference the current file control block. If we haven't created any
// file control blocks, then there's no file control blocks to clean up.
//
if (!NT_SUCCESS(status)) {
if (CreateFcbCalled) {
FatxDereferenceFcb(CurrentFcb);
}
goto CleanupAndExit;
}
//
// If the caller is expecting to open only a file or directory file, then
// verify that the file type matches.
//
OpenCurrentFcb:
if (FatxIsFlagSet(CurrentFcb->Flags, FAT_FCB_DIRECTORY)) {
OpenStartDirectoryFcb:
if ((CreateDisposition != FILE_OPEN) &&
(CreateDisposition != FILE_OPEN_IF)) {
status = STATUS_OBJECT_NAME_COLLISION;
} else if (FatxIsFlagSet(CreateOptions, FILE_NON_DIRECTORY_FILE)) {
status = STATUS_FILE_IS_A_DIRECTORY;
}
} else {
if (CreateDisposition == FILE_CREATE) {
status = STATUS_OBJECT_NAME_COLLISION;
} else if (TrailingBackslash ||
FatxIsFlagSet(CreateOptions, FILE_DIRECTORY_FILE)) {
status = STATUS_NOT_A_DIRECTORY;
}
}
//
// By the time we get here, we should have already handled the flag that
// indicates a move file operation.
//
ASSERT(FatxIsFlagClear(IrpSp->Flags, SL_OPEN_TARGET_DIRECTORY));
//
// Verify that the desired access is compatible with the file's attributes.
//
if (NT_SUCCESS(status)) {
status = FatxCheckDesiredAccess(DesiredAccess, CreateOptions,
CurrentFcb->FileAttributes, FALSE);
}
//
// If this is the first open handle to the file, then initialize the sharing
// mode. Otherwise, verify that the requested sharing mode is compatible
// with the current sharing mode.
//
if (NT_SUCCESS(status)) {
if (CurrentFcb->ShareAccess.OpenCount == 0) {
IoSetShareAccess(DesiredAccess, ShareAccess, FileObject,
&CurrentFcb->ShareAccess);
} else {
status = IoCheckShareAccess(DesiredAccess, ShareAccess,
FileObject, &CurrentFcb->ShareAccess, TRUE);
}
}
//
// Check if we should overwrite or open the existing file.
//
if (NT_SUCCESS(status)) {
if ((CreateDisposition == FILE_SUPERSEDE) ||
(CreateDisposition == FILE_OVERWRITE) ||
(CreateDisposition == FILE_OVERWRITE_IF)) {
//
// The code above has already verified that CreateDisposition is
// either FILE_OPEN or FILE_OPEN_IF for directories.
//
ASSERT(FatxIsFlagClear(CurrentFcb->Flags, FAT_FCB_DIRECTORY));
status = FatxOverwriteExistingFile(VolumeExtension, Irp, CurrentFcb,
FileAttributes, Irp->Overlay.AllocationSize.LowPart);
if (NT_SUCCESS(status)) {
//
// Indicate to the caller that we overwrote an existing file as
// opposed to creating the file.
//
Irp->IoStatus.Information = FILE_OVERWRITTEN;
} else {
//
// Cleanup the sharing mode associated with the file object;
// we're going to be failing this creation.
//
IoRemoveShareAccess(FileObject, &CurrentFcb->ShareAccess);
}
} else {
//
// Indicate to the caller that we opened the file as opposed to
// creating or overwriting the file.
//
Irp->IoStatus.Information = FILE_OPENED;
}
}
//
// Fill in the file object with the file control block that we created.
//
if (NT_SUCCESS(status)) {
FileObject->FsContext = CurrentFcb;
FileObject->FsContext2 = NULL;
VolumeExtension->FileObjectCount++;
//
// Apply the delete on close flag if necessary.
//
if (FatxIsFlagSet(CreateOptions, FILE_DELETE_ON_CLOSE) &&
FatxIsFlagClear(CurrentFcb->Flags, FAT_FCB_DIRECTORY)) {
CurrentFcb->Flags |= FAT_FCB_DELETE_ON_CLOSE;
}
status = STATUS_SUCCESS;
goto CleanupAndExit;
}
//
// If we reach this point, then we're going to be failing the call so
// cleanup any file control block we're still holding on to.
//
FatxDereferenceFcb(CurrentFcb);
CleanupAndExit:
FatxReleaseVolumeMutex(VolumeExtension);
Irp->IoStatus.Status = status;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return status;
}