diff --git a/nboxkrnl/io/cdrom/xiso.cpp b/nboxkrnl/io/cdrom/xiso.cpp index 0039e81..39e2180 100644 --- a/nboxkrnl/io/cdrom/xiso.cpp +++ b/nboxkrnl/io/cdrom/xiso.cpp @@ -4,14 +4,23 @@ #include "cdrom.hpp" #include "xiso.hpp" +#include "..\..\ex\ex.hpp" +#include "..\..\ob\obp.hpp" +#include +#include +#define XISO_DIRECTORY_FILE FILE_DIRECTORY_FILE // = 0x00000001 +#define XISO_VOLUME_FILE 0x80000000 + +NTSTATUS XBOXAPI XisoIrpCreate(PDEVICE_OBJECT DeviceObject, PIRP Irp); + static DRIVER_OBJECT XisoDriverObject = { nullptr, // DriverStartIo nullptr, // DriverDeleteDevice nullptr, // DriverDismountVolume { - IoInvalidDeviceRequest, // IRP_MJ_CREATE + XisoIrpCreate, // IRP_MJ_CREATE IoInvalidDeviceRequest, // IRP_MJ_CLOSE IoInvalidDeviceRequest, // IRP_MJ_READ IoInvalidDeviceRequest, // IRP_MJ_WRITE @@ -28,6 +37,67 @@ static DRIVER_OBJECT XisoDriverObject = { } }; +static INITIALIZE_GLOBAL_CRITICAL_SECTION(XisoMutex); + +static VOID XisoVolumeLockExclusive() +{ + RtlEnterCriticalSectionAndRegion(&XisoMutex); +} + +static VOID XisoVolumeUnlock() +{ + RtlLeaveCriticalSectionAndRegion(&XisoMutex); +} + +static NTSTATUS XisoCompleteRequest(PIRP Irp, NTSTATUS Status) +{ + XisoVolumeUnlock(); + Irp->IoStatus.Status = Status; + IofCompleteRequest(Irp, PRIORITY_BOOST_IO); + return Status; +} + +static PXISO_FILE_INFO XisoFindOpenFile(PXISO_VOLUME_EXTENSION VolumeExtension, POBJECT_STRING Name) +{ + RtlEnterCriticalSection(&VolumeExtension->FileInfoLock); + + PXISO_FILE_INFO FileInfo = nullptr; + PLIST_ENTRY Entry = VolumeExtension->OpenFileList.Blink; + while (Entry != &VolumeExtension->OpenFileList) { + PXISO_FILE_INFO CurrFileInfo = CONTAINING_RECORD(Entry, XISO_FILE_INFO, ListEntry); + OBJECT_STRING CurrString; + CurrString.Buffer = CurrFileInfo->FileName; + CurrString.Length = CurrFileInfo->FileNameLength; + if (RtlEqualString(Name, &CurrString, TRUE)) { + // Place the found element at the tail of the list + RemoveEntryList(Entry); + InsertTailList(&VolumeExtension->OpenFileList, Entry); + FileInfo = CurrFileInfo; + break; + } + + Entry = Entry->Blink; + } + + RtlLeaveCriticalSection(&VolumeExtension->FileInfoLock); + + return FileInfo; +} + +static VOID XisoInsertFile(PXISO_VOLUME_EXTENSION VolumeExtension, PXISO_FILE_INFO FileInfo) +{ + RtlEnterCriticalSection(&VolumeExtension->FileInfoLock); + InsertTailList(&VolumeExtension->OpenFileList, &FileInfo->ListEntry); + RtlLeaveCriticalSection(&VolumeExtension->FileInfoLock); +} + +static VOID XisoRemoveFile(PXISO_VOLUME_EXTENSION VolumeExtension, PXISO_FILE_INFO FileInfo) +{ + RtlEnterCriticalSection(&VolumeExtension->FileInfoLock); + RemoveEntryList(&FileInfo->ListEntry); + RtlLeaveCriticalSection(&VolumeExtension->FileInfoLock); +} + NTSTATUS XisoCreateVolume(PDEVICE_OBJECT DeviceObject) { DISK_GEOMETRY DiskGeometry; @@ -60,6 +130,11 @@ NTSTATUS XisoCreateVolume(PDEVICE_OBJECT DeviceObject) VolumeExtension->SectorSize = DiskGeometry.BytesPerSector; VolumeExtension->PartitionSectorCount = DiskGeometry.Cylinders.LowPart; VolumeExtension->PartitionLength.QuadPart = (ULONGLONG)DiskGeometry.Cylinders.LowPart << 11; + VolumeExtension->VolumeInfo.FileNameLength = 0; + VolumeExtension->VolumeInfo.HostHandle = CDROM_HANDLE; + VolumeExtension->VolumeInfo.Flags = XISO_VOLUME_FILE; + InitializeListHead(&VolumeExtension->OpenFileList); + RtlInitializeCriticalSection(&VolumeExtension->FileInfoLock); XisoDeviceObject->Flags &= ~DO_DEVICE_INITIALIZING; @@ -67,3 +142,261 @@ NTSTATUS XisoCreateVolume(PDEVICE_OBJECT DeviceObject) return STATUS_SUCCESS; } + +NTSTATUS XBOXAPI XisoIrpCreate(PDEVICE_OBJECT DeviceObject, PIRP Irp) +{ + XisoVolumeLockExclusive(); + + PIO_STACK_LOCATION IrpStackPointer = IoGetCurrentIrpStackLocation(Irp); + POBJECT_STRING RemainingName = IrpStackPointer->Parameters.Create.RemainingName; + PFILE_OBJECT FileObject = IrpStackPointer->FileObject; + PFILE_OBJECT RelatedFileObject = FileObject->RelatedFileObject; + ACCESS_MASK DesiredAccess = IrpStackPointer->Parameters.Create.DesiredAccess; + ULONG Disposition = (IrpStackPointer->Parameters.Create.Options >> 24) & 0xFF; + ULONG CreateOptions = IrpStackPointer->Parameters.Create.Options & 0xFFFFFF; + ULONG InitialSize = Irp->Overlay.AllocationSize.LowPart; + PXISO_VOLUME_EXTENSION VolumeExtension = (PXISO_VOLUME_EXTENSION)DeviceObject->DeviceExtension; + + if (VolumeExtension->Dismounted) { + return XisoCompleteRequest(Irp, STATUS_VOLUME_DISMOUNTED); + } + + if (IrpStackPointer->Flags & SL_OPEN_TARGET_DIRECTORY) { // cannot rename the root directory (xiso is read-only media) + return XisoCompleteRequest(Irp, STATUS_ACCESS_DENIED); + } + + if (DesiredAccess & (FILE_WRITE_ATTRIBUTES | FILE_WRITE_DATA | // don't allow any write access (xiso is read-only media) + FILE_WRITE_EA | FILE_ADD_FILE | FILE_ADD_SUBDIRECTORY | + FILE_APPEND_DATA | FILE_DELETE_CHILD | DELETE | WRITE_DAC)) { + return XisoCompleteRequest(Irp, STATUS_ACCESS_DENIED); + } + + if (CreateOptions & FILE_OPEN_BY_FILE_ID) { // not supported on xiso + return XisoCompleteRequest(Irp, STATUS_NOT_IMPLEMENTED); + } + + if ((Disposition != FILE_OPEN) && (Disposition != FILE_OPEN_IF)) { // only allow open operations (xiso is read-only media) + return XisoCompleteRequest(Irp, STATUS_ACCESS_DENIED); + } + + PXISO_FILE_INFO FileInfo; + OBJECT_STRING FileName; + CHAR RootName = OB_PATH_DELIMITER; + BOOLEAN HasBackslashAtEnd = FALSE; + if (RelatedFileObject) { + FileInfo = (PXISO_FILE_INFO)RelatedFileObject->FsContext2; + + if (!(FileInfo->Flags & XISO_DIRECTORY_FILE)) { + return XisoCompleteRequest(Irp, STATUS_INVALID_PARAMETER); + } + + if (RemainingName->Length == 0) { + HasBackslashAtEnd = TRUE; + FileName.Buffer = FileInfo->FileName; + FileName.Length = FileInfo->FileNameLength; + goto ByPassPathCheck; + } + goto OpenFile; + } + + if (RemainingName->Length == 0) { + // Special case: open the volume itself + + if (CreateOptions & FILE_DIRECTORY_FILE) { // volume is not a directory + return XisoCompleteRequest(Irp, STATUS_NOT_A_DIRECTORY); + } + + if (CreateOptions & FILE_DELETE_ON_CLOSE) { // cannot delete a volume + return XisoCompleteRequest(Irp, STATUS_CANNOT_DELETE); + } + + SHARE_ACCESS ShareAccess; + IoSetShareAccess(DesiredAccess, 0, FileObject, &ShareAccess); + VolumeExtension->VolumeInfo.RefCounter++; + FileObject->Flags |= FO_NO_INTERMEDIATE_BUFFERING; + + // No host I/O required for opening a volume + Irp->IoStatus.Information = FILE_OPENED; + return XisoCompleteRequest(Irp, STATUS_SUCCESS); + } + + if ((RemainingName->Length == 1) && (RemainingName->Buffer[0] == OB_PATH_DELIMITER)) { + // Special case: open the root directory of the volume + + HasBackslashAtEnd = TRUE; + FileName.Buffer = &RootName; + FileName.Length = 1; + FileInfo = XisoFindOpenFile(VolumeExtension, &FileName); + } + else { + OpenFile: + if (RemainingName->Buffer[RemainingName->Length - 1] == OB_PATH_DELIMITER) { + HasBackslashAtEnd = TRUE; // creating or opening a directory + } + + OBJECT_STRING FirstName, LocalRemainingName, OriName = *RemainingName; + while (true) { + // Iterate until we validate all path names + ObpParseName(&OriName, &FirstName, &LocalRemainingName); + + // NOTE: ObpParseName discards the backslash from a name, so LocalRemainingName must be checked separately + if (LocalRemainingName.Length && (LocalRemainingName.Buffer[0] == OB_PATH_DELIMITER)) { + // Another delimiter in the name is invalid + return XisoCompleteRequest(Irp, STATUS_OBJECT_NAME_INVALID); + } + + FileInfo = XisoFindOpenFile(VolumeExtension, &FirstName); + + if (LocalRemainingName.Length == 0) { + break; + } + + OriName = LocalRemainingName; + } + FileName = FirstName; + } +ByPassPathCheck: + + POBJECT_ATTRIBUTES ObjectAttributes = POBJECT_ATTRIBUTES(Irp->Tail.Overlay.DriverContext[0]); + OBJECT_STRING FullPath = *ObjectAttributes->ObjectName; + BOOLEAN FullPathBufferAllocated = FALSE; + PCHAR FullPathBuffer; + if ((FullPath.Length < 7) || (strncmp(FullPath.Buffer, "\\Device", 7))) { + // If the path doesn't start with "\\Device", then there's a sym link in the path. Note that it's not enough to check for RelatedFileObject, because the sym link + // might not point to a file object (and thus RelatedFileObject would not be set). The sym link created by the cdrom device triggers this case, since the target + // is the device object "CdRom0" instead of a file object + + USHORT ObjectAttributesLength = ObjectAttributes->ObjectName->Length; + for (USHORT i = 0; i < FullPath.Length; ++i) { + if (FullPath.Buffer[i] == OB_PATH_DELIMITER) { + // Set ObjectName->Length of ObjectAttributes to the sym link name, so that ObpReferenceObjectByName doesn't fail below when it attempts to resolve it + ObjectAttributes->ObjectName->Length = i; + break; + } + } + + POBJECT_SYMBOLIC_LINK SymbolicLink; + NTSTATUS Status = ObpReferenceObjectByName(ObjectAttributes, &ObSymbolicLinkObjectType, nullptr, (PVOID *)&SymbolicLink); + assert(SymbolicLink && NT_SUCCESS(Status)); + USHORT SymbolicLinkLength = GetObjDirInfoHeader(SymbolicLink)->Name.Length, Offset = 0; + assert(ObjectAttributes->ObjectName->Length == SymbolicLinkLength); + ObjectAttributes->ObjectName->Length = ObjectAttributesLength; + USHORT ResolvedSymbolicLinkLength = SymbolicLink->LinkTarget.Length; + + if (SymbolicLinkLength != FullPath.Length) { + assert(FullPath.Length > SymbolicLinkLength); + if ((SymbolicLink->LinkTarget.Buffer[ResolvedSymbolicLinkLength - 1] == OB_PATH_DELIMITER) && + (FullPath.Buffer[SymbolicLinkLength] == OB_PATH_DELIMITER)) { + Offset = 1; + } + // Allocate enough space for the resolved link and the remaining path name. e.g. D: -> \Device\CdRom0 and path is D:\default.xbe, then strlen(\Device\CdRom0) + strlen(default.xbe) + 1 + FullPathBuffer = (PCHAR)ExAllocatePool(ResolvedSymbolicLinkLength + RemainingName->Length + 1); + if (FullPathBuffer == nullptr) { + return XisoCompleteRequest(Irp, STATUS_INSUFFICIENT_RESOURCES); + } + // Copy the resolved sym link name, and then the remaining part of the path too + strncpy(FullPathBuffer, SymbolicLink->LinkTarget.Buffer, ResolvedSymbolicLinkLength); + USHORT RemainingPathLength = FullPath.Length - SymbolicLinkLength - Offset; + strncpy(&FullPathBuffer[ResolvedSymbolicLinkLength], &FullPath.Buffer[SymbolicLinkLength + Offset], RemainingPathLength); + Offset = RemainingPathLength; + } + else { + // FullPath consists only of the sym link, allocate enough space for the resolved link + FullPathBuffer = (PCHAR)ExAllocatePool(ResolvedSymbolicLinkLength); + if (FullPathBuffer == nullptr) { + return XisoCompleteRequest(Irp, STATUS_INSUFFICIENT_RESOURCES); + } + strncpy(FullPathBuffer, SymbolicLink->LinkTarget.Buffer, ResolvedSymbolicLinkLength); + } + FullPathBufferAllocated = TRUE; + FullPath.Buffer = FullPathBuffer; + FullPath.Length = ResolvedSymbolicLinkLength + Offset; + ObfDereferenceObject(SymbolicLink); + } + + // The optional creation of FileInfo must be the last thing to happen before SubmitIoRequestToHost. This, because if the IO fails, then IoParseDevice sets FileObject->DeviceObject + // to a nullptr, which causes it to invoke IopDeleteFile via ObfDereferenceObject. Because DeviceObject is nullptr, XisoIrpClose won't be called, thus leaking the host handle and + // the memory for FileInfo + BOOLEAN FileInfoCreated = FALSE; + if (FileInfo) { + if (FileInfo->Flags & XISO_DIRECTORY_FILE) { + if (CreateOptions & FILE_NON_DIRECTORY_FILE) { + if (FullPathBufferAllocated) { + ExFreePool(FullPathBuffer); + } + return XisoCompleteRequest(Irp, STATUS_FILE_IS_A_DIRECTORY); + } + } + else { + if (HasBackslashAtEnd || (CreateOptions & FILE_DIRECTORY_FILE)) { + if (FullPathBufferAllocated) { + ExFreePool(FullPathBuffer); + } + return XisoCompleteRequest(Irp, STATUS_NOT_A_DIRECTORY); + } + } + ++FileInfo->RefCounter; + } + else { + FileInfo = (PXISO_FILE_INFO)ExAllocatePool(sizeof(XISO_FILE_INFO) + FileName.Length); + if (FileInfo == nullptr) { + if (FullPathBufferAllocated) { + ExFreePool(FullPathBuffer); + } + return XisoCompleteRequest(Irp, STATUS_INSUFFICIENT_RESOURCES); + } + FileInfoCreated = TRUE; + FileInfo->HostHandle = InterlockedIncrement64(&IoHostFileHandle); + FileInfo->FileNameLength = FileName.Length; + FileInfo->FileName = (PCHAR)(FileInfo + 1); + FileInfo->FileSize = HasBackslashAtEnd ? 0 : InitialSize; + FileInfo->Flags = CreateOptions & FILE_DIRECTORY_FILE; + FileInfo->RefCounter = 1; + strncpy(FileInfo->FileName, FileName.Buffer, FileName.Length); + XisoInsertFile(VolumeExtension, FileInfo); + SHARE_ACCESS ShareAccess; + IoSetShareAccess(DesiredAccess, 0, FileObject, &ShareAccess); + } + + FileObject->FsContext2 = FileInfo; + + // Finally submit the I/O request to the host to do the actual work + // NOTE: we cannot use the xbox handle as the host file handle, because the xbox handle is created by OB only after this I/O request succeeds. This new handle + // should then be deleted when the file object goes away with IopCloseFile and/or IopDeleteFile + IoInfoBlock InfoBlock = SubmitIoRequestToHost( + (CreateOptions & FILE_DIRECTORY_FILE ? IoFlags::IsDirectory : 0) | (CreateOptions & FILE_NON_DIRECTORY_FILE ? IoFlags::MustNotBeADirectory : 0) | + (CreateOptions & FILE_DIRECTORY_FILE ? IoFlags::MustBeADirectory : 0) | DEV_TYPE(DEV_CDROM) | + (Disposition & 7) | IoRequestType::Open, + InitialSize, + FullPath.Length, + FileInfo->HostHandle, + (ULONG_PTR)(FullPath.Buffer) + ); + + NTSTATUS Status = HostToNtStatus(InfoBlock.Status); + if (!NT_SUCCESS(Status)) { + if (FileInfoCreated) { + XisoRemoveFile(VolumeExtension, FileInfo); + ExFreePool(FileInfo); + FileObject->FsContext2 = nullptr; + } + } + else if (Status == STATUS_PENDING) { + // Should not happen right now, because RetrieveIoRequestFromHost is always synchronous + RIP_API_MSG("Asynchronous IO is not supported"); + } + else { + ++VolumeExtension->FileObjectCount; + Irp->IoStatus.Information = InfoBlock.Info; + if (!HasBackslashAtEnd && FileInfoCreated && (InfoBlock.Info == Opened)) { + // Only update the file size if it was opened for the first time ever + FileInfo->FileSize = ULONG(InfoBlock.Info2OrId); + } + } + + if (FullPathBufferAllocated) { + ExFreePool(FullPathBuffer); + } + + return XisoCompleteRequest(Irp, Status); +} diff --git a/nboxkrnl/io/cdrom/xiso.hpp b/nboxkrnl/io/cdrom/xiso.hpp index 85e7fdb..4ba0f6a 100644 --- a/nboxkrnl/io/cdrom/xiso.hpp +++ b/nboxkrnl/io/cdrom/xiso.hpp @@ -5,13 +5,30 @@ #pragma once #include "..\..\io\io.hpp" +#include "..\..\rtl\rtl.hpp" +struct XISO_FILE_INFO { + USHORT FileNameLength; + PCHAR FileName; + ULONG FileSize; + ULONGLONG HostHandle; + ULONG Flags; + ULONG RefCounter; + LIST_ENTRY ListEntry; +}; +using PXISO_FILE_INFO = XISO_FILE_INFO *; + struct XISO_VOLUME_EXTENSION { + XISO_FILE_INFO VolumeInfo; PDEVICE_OBJECT TargetDeviceObject; ULONG SectorSize; ULONG PartitionSectorCount; + ULONG FileObjectCount; ULARGE_INTEGER PartitionLength; + BOOLEAN Dismounted; + RTL_CRITICAL_SECTION FileInfoLock; + LIST_ENTRY OpenFileList; }; using PXISO_VOLUME_EXTENSION = XISO_VOLUME_EXTENSION *; diff --git a/nboxkrnl/io/io.hpp b/nboxkrnl/io/io.hpp index 8f824af..157b36d 100644 --- a/nboxkrnl/io/io.hpp +++ b/nboxkrnl/io/io.hpp @@ -65,7 +65,9 @@ #define FILE_READ_DATA 0x0001 #define FILE_LIST_DIRECTORY 0x0001 #define FILE_WRITE_DATA 0x0002 +#define FILE_ADD_FILE 0x0002 #define FILE_APPEND_DATA 0x0004 +#define FILE_ADD_SUBDIRECTORY 0x0004 #define FILE_EXECUTE 0x0020 #define FILE_TRAVERSE 0x0020 #define FILE_DELETE_CHILD 0x0040 diff --git a/nboxkrnl/kernel.hpp b/nboxkrnl/kernel.hpp index dca2655..957e5a1 100644 --- a/nboxkrnl/kernel.hpp +++ b/nboxkrnl/kernel.hpp @@ -55,7 +55,7 @@ #define DEV_TYPE(n) ((n) << 23) // Special host handles -#define UNUSED_HANDLE +#define CDROM_HANDLE DEV_CDROM #define EEPROM_HANDLE DEV_EEPROM #define PARTITION0_HANDLE DEV_PARTITION0 #define PARTITION1_HANDLE DEV_PARTITION1