1699 lines
50 KiB
C
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;
|
|
}
|