/*++ Copyright (c) 2000-2001 Microsoft Corporation Module Name: create.c Abstract: This module implements routines related to handling IRP_MJ_CREATE. --*/ #include "udfx.h" // // Structure that wraps up all of the variables related to walk through a // directory stream. // typedef struct _UDF_DIRECTORY_MAPPING { PUDF_VOLUME_EXTENSION VolumeExtension; PIRP Irp; PUDF_FCB DirectoryFcb; ULONG LogicalSectorNumber; ULONG FileBytesRemaining; ULONG CacheBytesRemaining; PVOID CacheBuffer; PUCHAR CurrentBuffer; } UDF_DIRECTORY_MAPPING, *PUDF_DIRECTORY_MAPPING; NTSTATUS UdfxMapNextDirectorySector( IN OUT PUDF_DIRECTORY_MAPPING DirectoryMapping ) /*++ Routine Description: This routine maps the next directory sector into the cache using the state in the supplied mapping structure. Arguments: DirectoryMapping - Specifies a structure that describes the current sector mapping and how to map the next sector. Return Value: Status of operation. --*/ { NTSTATUS status; PUDF_VOLUME_EXTENSION VolumeExtension; PUCHAR LocalCurrentBuffer; ULONG LocalCacheBytesRemaining; // // If another sector has already been mapped, then unmap it now. // if (DirectoryMapping->CacheBuffer != NULL) { FscUnmapBuffer(DirectoryMapping->CacheBuffer); DirectoryMapping->CacheBuffer = NULL; } // // If there aren't any more bytes remaining in the directory stream, then // bail out now. // if (DirectoryMapping->FileBytesRemaining == 0) { return STATUS_OBJECT_NAME_NOT_FOUND; } // // Map the directory sector into the cache. // VolumeExtension = DirectoryMapping->VolumeExtension; status = UdfxMapLogicalSector(VolumeExtension, DirectoryMapping->Irp, DirectoryMapping->LogicalSectorNumber, &DirectoryMapping->CacheBuffer); if (!NT_SUCCESS(status)) { return status; } DirectoryMapping->LogicalSectorNumber++; LocalCurrentBuffer = (PUCHAR)DirectoryMapping->CacheBuffer; // // If the data is part of the file descriptor, then adjust the current // buffer to point to the real start of the data. The number of remaining // bytes will be adjusted below. // if (UdfxIsFlagSet(DirectoryMapping->DirectoryFcb->Flags, UDF_FCB_EMBEDDED_DATA)) { LocalCurrentBuffer += DirectoryMapping->DirectoryFcb->EmbeddedDataOffset; ASSERT(DirectoryMapping->FileBytesRemaining < UDF_CD_SECTOR_SIZE); } // // Compute the number of bytes remaining in the file and in this cache // buffer taking into account whether or not this is the last sector of the // directory stream. // if (DirectoryMapping->FileBytesRemaining > UDF_CD_SECTOR_SIZE) { LocalCacheBytesRemaining = UDF_CD_SECTOR_SIZE; DirectoryMapping->FileBytesRemaining -= UDF_CD_SECTOR_SIZE; } else { LocalCacheBytesRemaining = DirectoryMapping->FileBytesRemaining; DirectoryMapping->FileBytesRemaining = 0; } // // If we're to skip over bytes from a descriptor that crosses a sector // boundary, then do that adjustment here. // LocalCurrentBuffer -= DirectoryMapping->CacheBytesRemaining; LocalCacheBytesRemaining += DirectoryMapping->CacheBytesRemaining; // // Return the results back to the caller. // DirectoryMapping->CurrentBuffer = LocalCurrentBuffer; DirectoryMapping->CacheBytesRemaining = LocalCacheBytesRemaining; return STATUS_SUCCESS; } VOID UdfxCopyPartialDirectoryBuffer( IN OUT PUDF_DIRECTORY_MAPPING DirectoryMapping, OUT PVOID Destination, IN PVOID Source, IN SIZE_T Length ) /*++ Routine Description: This routine is a wrapper for RtlCopyMemory that asserts that the arguments are valid cache pointers in a debug build and reduces the code bloat in a retail build caused by RtlCopyMemory inlining. Arguments: DirectoryMapping - Specifies a structure that describes the current sector mapping and how to map the next sector. Destination - Specifies the destination of the move. Source - Specifies the memory to be copied. Length - Specifies the number of bytes to be copied. Return Value: Status of operation. --*/ { ASSERT((PUCHAR)Source >= (PUCHAR)DirectoryMapping->CacheBuffer); ASSERT((PUCHAR)Source < (PUCHAR)DirectoryMapping->CacheBuffer + UDF_CD_SECTOR_SIZE); ASSERT((PUCHAR)Source + Length <= (PUCHAR)DirectoryMapping->CacheBuffer + UDF_CD_SECTOR_SIZE); ASSERT(Length <= UDF_CD_SECTOR_SIZE); RtlCopyMemory(Destination, Source, Length); } NTSTATUS UdfxLookupElementNameInDirectory( IN PUDF_VOLUME_EXTENSION VolumeExtension, IN PIRP Irp, IN PUDF_FCB DirectoryFcb, IN POBJECT_STRING ElementName, OUT PLONGAD ElementIcb ) /*++ 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. ElementIcb - Specifies the buffer to receive the extent of the found file. Return Value: Status of operation. --*/ { NTSTATUS status; ULONG ExpectedFileID8BitLength; UDF_DIRECTORY_MAPPING DirectoryMapping; union { NSR_FID FileIdentifierDescriptor; UCHAR FileID[256]; } Stack; ULONG PartialStackBufferBytes; PNSR_FID FileIdentifierDescriptor; ULONG FileIdentifierDescriptorLength; ULONG FileIDLength; ULONG FileIDStartingOffset; PUCHAR FileID; ASSERT(UdfxIsFlagSet(DirectoryFcb->Flags, UDF_FCB_DIRECTORY)); // // Compute the expected lengths for a file identifier that will match the // supplied file name that we're searching for. We only care about 8-bit // encodings. // ExpectedFileID8BitLength = sizeof(UCHAR) + ElementName->Length / sizeof(OCHAR); // // Initialize the directory mapping structure. // DirectoryMapping.VolumeExtension = VolumeExtension; DirectoryMapping.Irp = Irp; DirectoryMapping.DirectoryFcb = DirectoryFcb; DirectoryMapping.LogicalSectorNumber = DirectoryFcb->AllocationSectorStart; DirectoryMapping.FileBytesRemaining = DirectoryFcb->FileSize.LowPart; DirectoryMapping.CacheBytesRemaining = 0; DirectoryMapping.CacheBuffer = NULL; DirectoryMapping.CurrentBuffer = NULL; // // Process the directory stream. // for (;;) { // // If we're out of bytes in the current cache buffer, then map in the // next sector. // if ((LONG)DirectoryMapping.CacheBytesRemaining <= 0) { status = UdfxMapNextDirectorySector(&DirectoryMapping); if (!NT_SUCCESS(status)) { break; } } // // If the current cache buffer doesn't have enough bytes to contain a // full descriptor, then we'll have to buffer the descriptor on the // stack and carefully cross sector boundaries. // if (DirectoryMapping.CacheBytesRemaining < ISONsrFidConstantSize) { // // If the bytes from this cache buffer and what remains in the file // won't be enough to contain a full descriptor, then bail out now. // if (DirectoryMapping.CacheBytesRemaining + DirectoryMapping.FileBytesRemaining < ISONsrFidConstantSize) { status = STATUS_OBJECT_NAME_NOT_FOUND; break; } // // Copy the first part of the descriptor to the stack. // PartialStackBufferBytes = DirectoryMapping.CacheBytesRemaining; UdfxCopyPartialDirectoryBuffer(&DirectoryMapping, &Stack.FileIdentifierDescriptor, DirectoryMapping.CurrentBuffer, PartialStackBufferBytes); // // Map in the next sector. Note that on return, the CurrentBuffer // CurrentBuffer and CacheBytesRemaining fields have been adjusted // adjusted to point back in memory as if the previous sector was // virtually mapped in order to make the below math easier. // status = UdfxMapNextDirectorySector(&DirectoryMapping); if (!NT_SUCCESS(status)) { break; } // // Copy the last part of the descriptor to the stack. // UdfxCopyPartialDirectoryBuffer(&DirectoryMapping, (PUCHAR)&Stack.FileIdentifierDescriptor + PartialStackBufferBytes, DirectoryMapping.CurrentBuffer + PartialStackBufferBytes, ISONsrFidConstantSize - PartialStackBufferBytes); FileIdentifierDescriptor = &Stack.FileIdentifierDescriptor; } else { // // There's enough bytes in the current buffer to contain at least // the header, so point directly into the cache buffer. // FileIdentifierDescriptor = (PNSR_FID)DirectoryMapping.CurrentBuffer; } // // Validate that the descriptor header looks valid and that its size // doesn't exceed a sector as specified by UDF or the number of // remaining bytes in the directory stream. We can't use // UdfxVerifyDescriptor here because we don't have a contiguous // descriptor in memory. // FileIdentifierDescriptorLength = ISONsrFidSize(FileIdentifierDescriptor); if ((FileIdentifierDescriptor->Destag.Version != DESTAG_VER_CURRENT) || (FileIdentifierDescriptor->Destag.Ident != DESTAG_ID_NSR_FID) || (FileIdentifierDescriptorLength > UDF_CD_SECTOR_SIZE) || (FileIdentifierDescriptorLength > DirectoryMapping.FileBytesRemaining + (ULONG)DirectoryMapping.CacheBytesRemaining)) { UdfxDbgPrint(("UDFX: file identifier descriptor doesn't verify\n")); status = STATUS_DISK_CORRUPT_ERROR; break; } FileIDLength = FileIdentifierDescriptor->FileIDLen; // // Only bother checking the file identifier descriptor if the file // identifier length matches the length of the element name we're // looking for. // if (FileIDLength == ExpectedFileID8BitLength) { // // Fill in the output buffer now since we'll shortly lose access to // the file identifier descriptor. // *ElementIcb = FileIdentifierDescriptor->Icb; // // Compute the byte offset of the file identifier. // FileIDStartingOffset = ISONsrFidConstantSize + FileIdentifierDescriptor->ImpUseLen; // // The below code can cause the current sector buffer to become // invalid, so we can no longer safely touch the // FileIdentifierDescriptor. // FileIdentifierDescriptor = NULL; // // If the current cache buffer doesn't have enough bytes to contain // the string, then we'll have to buffer the string on the stack and // carefully cross sector boundaries. // if (DirectoryMapping.CacheBytesRemaining < FileIDStartingOffset + FileIDLength) { // // Check if any of the string is inside the current cache // buffer. // if (DirectoryMapping.CacheBytesRemaining > FileIDStartingOffset) { // // Copy the first part of the string to the stack. // PartialStackBufferBytes = DirectoryMapping.CacheBytesRemaining - FileIDStartingOffset; UdfxCopyPartialDirectoryBuffer(&DirectoryMapping, &Stack.FileID, DirectoryMapping.CurrentBuffer + FileIDStartingOffset, PartialStackBufferBytes); } else { PartialStackBufferBytes = 0; } // // Map in the next sector. Note that on return, the // CurrentBuffer and CacheBytesRemaining fields have been // adjusted to point back in memory as if the previous sector // was virtually mapped in order to make the below math easier. // status = UdfxMapNextDirectorySector(&DirectoryMapping); if (!NT_SUCCESS(status)) { break; } // // Copy the last part of the string to the stack. // UdfxCopyPartialDirectoryBuffer(&DirectoryMapping, Stack.FileID + PartialStackBufferBytes, DirectoryMapping.CurrentBuffer + FileIDStartingOffset + PartialStackBufferBytes, FileIDLength - PartialStackBufferBytes); FileID = Stack.FileID; } else { // // There's enough bytes in the current buffer to contain all of // the string, so point directly into the cache. // FileID = DirectoryMapping.CurrentBuffer + FileIDStartingOffset; } if (UdfxEqualOSTACS0StringAndObjectString(FileID, FileIDLength, ElementName)) { status = STATUS_SUCCESS; break; } } // // Advance to the next descriptor. // DirectoryMapping.CacheBytesRemaining -= FileIdentifierDescriptorLength; DirectoryMapping.CurrentBuffer += FileIdentifierDescriptorLength; } if (DirectoryMapping.CacheBuffer != NULL) { FscUnmapBuffer(DirectoryMapping.CacheBuffer); } return status; } NTSTATUS UdfxFsdCreate( 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; PUDF_VOLUME_EXTENSION VolumeExtension; PIO_STACK_LOCATION IrpSp; ACCESS_MASK DesiredAccess; ULONG CreateOptions; PFILE_OBJECT FileObject; OBJECT_STRING RemainingName; PFILE_OBJECT RelatedFileObject; ULONG CreateDisposition; PUDF_FCB CurrentFcb; SHARE_ACCESS ShareAccess; BOOLEAN TrailingBackslash; BOOLEAN CreateFcbCalled; OBJECT_STRING ElementName; PUDF_FCB FoundOrNewFcb; LONGAD ElementIcb; VolumeExtension = (PUDF_VOLUME_EXTENSION)DeviceObject->DeviceExtension; IrpSp = IoGetCurrentIrpStackLocation(Irp); DesiredAccess = IrpSp->Parameters.Create.DesiredAccess; CreateOptions = IrpSp->Parameters.Create.Options; FileObject = IrpSp->FileObject; RemainingName = *IrpSp->Parameters.Create.RemainingName; RelatedFileObject = FileObject->RelatedFileObject; // // Synchronize access to the file control block lists by acquiring the // global mutex. // UdfxAcquireGlobalMutexExclusive(); // // Check if the volume has been dismounted. // if (VolumeExtension->Dismounted) { status = STATUS_VOLUME_DISMOUNTED; goto CleanupAndExit; } // // Don't allow the target directory to be opened. This is only used by the // I/O manager when it wants to rename a file across a directory on the same // volume. // if (UdfxIsFlagSet(IrpSp->Flags, SL_OPEN_TARGET_DIRECTORY)) { status = STATUS_ACCESS_DENIED; goto CleanupAndExit; } // // Check the desired access mask to make sure that no write rights are being // requested. // if (UdfxIsFlagSet(DesiredAccess, FILE_WRITE_ATTRIBUTES | FILE_WRITE_DATA | FILE_WRITE_EA | FILE_ADD_FILE | FILE_ADD_SUBDIRECTORY | FILE_APPEND_DATA | FILE_DELETE_CHILD | DELETE | WRITE_DAC)) { status = STATUS_ACCESS_DENIED; goto CleanupAndExit; } // // Don't allow a file to be opened based on its file ID. // if (UdfxIsFlagSet(CreateOptions, FILE_OPEN_BY_FILE_ID)) { status = STATUS_NOT_IMPLEMENTED; goto CleanupAndExit; } // // Verify that the create disposition is for open access only. // CreateDisposition = (CreateOptions >> 24) & 0xFF; if ((CreateDisposition != FILE_OPEN) && (CreateDisposition != FILE_OPEN_IF)) { status = STATUS_ACCESS_DENIED; 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 = (PUDF_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) || UdfxIsFlagClear(CurrentFcb->Flags, UDF_FCB_DIRECTORY)) { status = STATUS_INVALID_PARAMETER; goto CleanupAndExit; } // // Check if we're supposed to open the related directory. // if (RemainingName.Length == 0) { 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; } } else { // // Check if we're supposed to open the physical volume. // if (RemainingName.Length == 0) { CurrentFcb = VolumeExtension->VolumeFcb; // // The caller shouldn't be expecting to see a directory file. // if (UdfxIsFlagSet(CreateOptions, FILE_DIRECTORY_FILE)) { status = STATUS_NOT_A_DIRECTORY; 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; CurrentFcb->ReferenceCount++; goto InitializeFileObject; } // // 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)) { CurrentFcb->ReferenceCount++; status = STATUS_SUCCESS; goto OpenStartDirectoryFcb; } } // // 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 (!UdfxFindOpenChildFcb(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) { 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; } } // // Lookup the element in the directory. // status = UdfxLookupElementNameInDirectory(VolumeExtension, Irp, CurrentFcb, &ElementName, &ElementIcb); if (!NT_SUCCESS(status)) { // // If we failed to find the element and there's more of a path name // to process, then convert the error from "file not found" to // "path not found". // if ((status == STATUS_OBJECT_NAME_NOT_FOUND) && (RemainingName.Length != 0)) { status = STATUS_OBJECT_PATH_NOT_FOUND; } break; } // // Create a file control block for the file we found. // status = UdfxCreateFcbFromFileEntry(VolumeExtension, Irp, &ElementIcb, CurrentFcb, &ElementName, &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; // // If there's still more of a path name to process, then the file that // we found had better be a directory. // if ((RemainingName.Length != 0) && UdfxIsFlagClear(CurrentFcb->Flags, UDF_FCB_DIRECTORY)) { status = STATUS_OBJECT_PATH_NOT_FOUND; break; } } 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) { UdfxDereferenceFcb(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 (UdfxIsFlagSet(CurrentFcb->Flags, UDF_FCB_DIRECTORY)) { OpenStartDirectoryFcb: if (UdfxIsFlagSet(CreateOptions, FILE_NON_DIRECTORY_FILE)) { status = STATUS_FILE_IS_A_DIRECTORY; } } else { if (TrailingBackslash || UdfxIsFlagSet(CreateOptions, FILE_DIRECTORY_FILE)) { status = STATUS_NOT_A_DIRECTORY; } } // // If the resulting file looks acceptable, then fill out the file object // and return success. // if (NT_SUCCESS(status)) { InitializeFileObject: // // We don't care about sharing modes for this file system. // IoSetShareAccess(DesiredAccess, 0, FileObject, &ShareAccess); // // Fill in the file object with the file control block that we // created. // 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; } // // 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. // UdfxDereferenceFcb(CurrentFcb); CleanupAndExit: UdfxReleaseGlobalMutex(); Irp->IoStatus.Status = status; IoCompleteRequest(Irp, IO_NO_INCREMENT); return status; }