/*++ 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; }