/*++

Copyright (c) 1992  Microsoft Corporation

Module Name:

    udfs_rec.c

Abstract:

    This module contains the mini-file system recognizer for UDFS.

Author:

    Dan Lovinger (danlo) 13-Feb-1997

Environment:

    Kernel mode, local to I/O system

Revision History:


--*/

#include "fs_rec.h"
#include "udfs_rec.h"

//
//  The local debug trace level
//

#define Dbg                              (FSREC_DEBUG_LEVEL_UDFS)

//
//  Tables of tokens we have to parse up from mount-time on-disk structures
//

PARSE_KEYVALUE VsdIdentParseTable[] = {
    { VSD_IDENT_BEA01, VsdIdentBEA01 },
    { VSD_IDENT_TEA01, VsdIdentTEA01 },
    { VSD_IDENT_CDROM, VsdIdentCDROM },
    { VSD_IDENT_CD001, VsdIdentCD001 },
    { VSD_IDENT_CDW01, VsdIdentCDW01 },
    { VSD_IDENT_CDW02, VsdIdentCDW02 },
    { VSD_IDENT_NSR01, VsdIdentNSR01 },
    { VSD_IDENT_NSR02, VsdIdentNSR02 },
    { VSD_IDENT_BOOT2, VsdIdentBOOT2 },
    { VSD_IDENT_NSR03, VsdIdentNSR03 },
    { NULL,            VsdIdentBad }
    };

NTSTATUS
UdfsRecGetLastSessionStart(
    IN PDEVICE_OBJECT DeviceObject,
    OUT PULONG Psn
    );

#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE,IsUdfsVolume)
#pragma alloc_text(PAGE,UdfsFindInParseTable)
#pragma alloc_text(PAGE,UdfsRecFsControl)
#pragma alloc_text(PAGE,UdfsRecGetLastSessionStart)
#endif // ALLOC_PRAGMA


//
//  This macro copies an unaligned src longword to a dst longword,
//  performing an little/big endian swap.
//

#define SwapCopyUchar4(Dst,Src) {                                        \
    *((UNALIGNED UCHAR *)(Dst)) = *((UNALIGNED UCHAR *)(Src) + 3);     \
    *((UNALIGNED UCHAR *)(Dst) + 1) = *((UNALIGNED UCHAR *)(Src) + 2); \
    *((UNALIGNED UCHAR *)(Dst) + 2) = *((UNALIGNED UCHAR *)(Src) + 1); \
    *((UNALIGNED UCHAR *)(Dst) + 3) = *((UNALIGNED UCHAR *)(Src));     \
}


NTSTATUS
UdfsRecGetLastSessionStart(
    IN PDEVICE_OBJECT DeviceObject,
    OUT PULONG Psn
    )
/*++

Routine Description:

    This function queries the underlying device for the address of the
    first track in the last session.  Does nothing for DISK devices.
    
Arguments:

    DeviceObject - Pointer to this driver's device object.

    Psn - receives physical sector number of first block in last session,
          0 for disk devices

Return Value:

    The function value is the final status of the operation.

 -*/
{
    KEVENT Event;
    NTSTATUS Status;
    IO_STATUS_BLOCK ioStatus;
    CDROM_TOC_SESSION_DATA SessionData;
    PIRP Irp;

    *Psn = 0;
    
    if (DeviceObject->DeviceType != FILE_DEVICE_CD_ROM) {

        return STATUS_SUCCESS;
    }

    KeInitializeEvent( &Event, SynchronizationEvent, FALSE );

    Irp = IoBuildDeviceIoControlRequest( IOCTL_CDROM_GET_LAST_SESSION,
                                         DeviceObject,
                                         (PVOID) NULL,
                                         0,
                                         &SessionData,
                                         sizeof( SessionData ),
                                         FALSE,
                                         &Event,
                                         &ioStatus );
    if (!Irp) {
    
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    //
    //  Override verify logic - we don't care. The fact we're in the picture means
    //  someone is trying to mount new/changed media in the first place.
    //
    
    SetFlag( IoGetNextIrpStackLocation( Irp )->Flags, SL_OVERRIDE_VERIFY_VOLUME );

    Status = IoCallDriver( DeviceObject, Irp );
    
    if (Status == STATUS_PENDING) {
    
        (VOID) KeWaitForSingleObject( &Event,
                                      Executive,
                                      KernelMode,
                                      FALSE,
                                      (PLARGE_INTEGER) NULL );
        Status = ioStatus.Status;
    }

    if (!NT_SUCCESS( Status )) {
    
        return Status;
    }

    if (SessionData.FirstCompleteSession != SessionData.LastCompleteSession)  {

        SwapCopyUchar4( Psn, &SessionData.TrackData[0].Address );
    }
    
    return STATUS_SUCCESS;
}



NTSTATUS
UdfsRecFsControl(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )

/*++

Routine Description:

    This function performs the mount and driver reload functions for this mini-
    file system recognizer driver.

Arguments:

    DeviceObject - Pointer to this driver's device object.

    Irp - Pointer to the I/O Request Packet (IRP) representing the function to
        be performed.

Return Value:

    The function value is the final status of the operation.


 -*/

{
    NTSTATUS status;
    PIO_STACK_LOCATION irpSp;
    PDEVICE_EXTENSION deviceExtension;
    UNICODE_STRING driverName;
    ULONG bytesPerSector;
    PDEVICE_OBJECT targetDevice;

    PAGED_CODE();

    //
    // Begin by determining what function that is to be performed.
    //

    deviceExtension = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;
    irpSp = IoGetCurrentIrpStackLocation( Irp );

    switch ( irpSp->MinorFunction ) {

    case IRP_MN_MOUNT_VOLUME:

        //
        // Attempt to mount a volume:  There are two different cases here:
        //
        //     1)  The device is being opened for DASD access, that is, no
        //         file system is required, thus it is OK to allow RAW to
        //         to open it.
        //
        //     2)  We need to rummage the media to see if this is a UDF volume.
        //

        status = STATUS_UNRECOGNIZED_VOLUME;

        targetDevice = irpSp->Parameters.MountVolume.DeviceObject;

        if (FsRecGetDeviceSectorSize( targetDevice,
                                      &bytesPerSector )) {
        
            if (IsUdfsVolume( targetDevice,
                              bytesPerSector )) {

                status = STATUS_FS_DRIVER_REQUIRED;
            }
        }

        break;

    case IRP_MN_LOAD_FILE_SYSTEM:

        status = FsRecLoadFileSystem( DeviceObject,
                                      L"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\Udfs" );
        break;

    default:
        status = STATUS_INVALID_DEVICE_REQUEST;

    }

    //
    // Finally, complete the request and return the same status code to the
    // caller.
    //

    Irp->IoStatus.Status = status;
    IoCompleteRequest( Irp, IO_NO_INCREMENT );

    return status;
}


BOOLEAN
IsUdfsVolume (
    IN PDEVICE_OBJECT DeviceObject,
    IN ULONG SectorSize
    )

/*++

Routine Description:

    This routine walks the Volume Recognition Sequence to determine
    whether this volume contains an NSR02 (ISO 13346 Section 4) image.
    
    Note: this routine is pretty much diked out of UdfsRecognizeVolume
    in the real filesystem, modulo fitting it into the fs recognizer.

Arguments:

    DeviceObject - device we are checking

    SectorSize - size of a physical sector on this device

Return Value:

    Boolean TRUE if we found NSR02, FALSE otherwise.

--*/

{
    BOOLEAN FoundNSR;

    BOOLEAN FoundBEA;
    BOOLEAN Resolved;

    ULONG LastSessionStartPsn;

    PVSD_GENERIC VolumeStructureDescriptor = NULL;
    PVOID Buffer = NULL;
    ULONGLONG Offset;

    PAGED_CODE();

    DebugTrace(( +1, Dbg,
                 "IsUdfsVolume, DevObj %08x SectorSize %08x\n",
                 DeviceObject,
                 SectorSize ));

    //
    //  Find the start of the last session
    //

    if (!NT_SUCCESS( UdfsRecGetLastSessionStart( DeviceObject, 
                                                 &LastSessionStartPsn)))  {

        return FALSE;
    }

Retry:

    DebugTrace(( 0, Dbg, "IsUdfsVolume, Looking at session starting Psn == 0x%x\n", LastSessionStartPsn));

    Offset = (SectorSize * LastSessionStartPsn) + SectorAlignN( SectorSize, VRA_BOUNDARY_LOCATION );

    FoundNSR = 
    FoundBEA =
    Resolved = FALSE;

    while (!Resolved) {

        //
        //  The VRS descriptors are specified at 2kb regardless of sector size.  So
        //  we only want to read if we've processed all 2kb blocks in the last read
        //  sector (latest gen MO media has 4kb sectors) i.e. if we have landed
        //  on a sector aligned offset after processing the last descriptor.
        //
        
        if (0 == (Offset & (SectorSize - 1)))  {

            if (!FsRecReadBlock( DeviceObject,
                                 (PLARGE_INTEGER)&Offset,
                                 sizeof(VSD_GENERIC),
                                 SectorSize,
                                 &Buffer,
                                 NULL ))  {
                break;
            }

            VolumeStructureDescriptor = Buffer;
        }

        //
        //  Now check the type of the descriptor. All ISO 13346 VSDs are
        //  of Type 0, 9660 PVDs are Type 1, 9660 SVDs are Type 2, and 9660
        //  terminating descriptors are Type 255.
        //
    
        if (VolumeStructureDescriptor->Type == 0) {

            //
            //  In order to properly recognize the volume, we must know all of the
            //  Structure identifiers in ISO 13346 so that we can terminate if a
            //  badly formatted (or, shockingly, non 13346) volume is presented to us.
            //

            switch (UdfsFindInParseTable( VsdIdentParseTable,
                                         VolumeStructureDescriptor->Ident,
                                         VSD_LENGTH_IDENT )) {
                case VsdIdentBEA01:

                    //
                    //  Only one BEA may exist and its version must be 1 (2/9.2.3)
                    //

                    DebugTrace(( 0, Dbg, "IsUdfsVolume, got a BEA01\n" ));


                    if ((FoundBEA &&
                         DebugTrace(( 0, Dbg,
                                      "IsUdfsVolume, ... but it is a duplicate!\n" ))) ||

                        (VolumeStructureDescriptor->Version != 1 &&
                         DebugTrace(( 0, Dbg,
                                      "IsUdfsVolume, ... but it has a wacky version number %02x != 1!\n",
                                      VolumeStructureDescriptor->Version )))) {

                        Resolved = TRUE;
                        break;
                    }

                    FoundBEA = TRUE;
                    break;

                case VsdIdentTEA01:

                    //
                    //  If we reach the TEA it must be the case that we don't recognize
                    //

                    DebugTrace(( 0, Dbg, "IsUdfsVolume, got a TEA01\n" ));

                    Resolved = TRUE;
                    break;

                case VsdIdentNSR02:
                case VsdIdentNSR03:

                    //
                    //  We recognize NSR02 version 1 embedded after a BEA (3/9.1.3).  For
                    //  simplicity we will not bother being a complete nitpick and check
                    //  for a bounding TEA, although we will be optimistic in the case where
                    //  we fail to match the version.
                    //

                    DebugTrace(( 0, Dbg, "IsUdfsVolume, got an NSR02/3\n" ));

                    if ((FoundBEA ||
                         !DebugTrace(( 0, Dbg, "IsUdfsVolume, ... but we haven't seen a BEA01 yet!\n" ))) &&

                        (VolumeStructureDescriptor->Version == 1 ||
                         !DebugTrace(( 0, Dbg, "IsUdfsVolume, ... but it has a wacky version number %02x != 1\n",
                                       VolumeStructureDescriptor->Version )))) {

                        FoundNSR = Resolved = TRUE;
                        break;
                    }

                    break;

                case VsdIdentCD001:
                case VsdIdentCDW01:
                case VsdIdentNSR01:
                case VsdIdentCDW02:
                case VsdIdentBOOT2:

                    DebugTrace(( 0, Dbg, "IsUdfsVolume, got a valid but uninteresting 13346 descriptor\n" ));

                    //
                    //  Valid but uninteresting (to us) descriptors
                    //

                    break;

                default:

                    DebugTrace(( 0, Dbg, "IsUdfsVolume, got an invalid 13346 descriptor\n" ));

                    //
                    //  Stumbling across something we don't know, it must be that this
                    //  is not a valid 13346 image
                    //

                    Resolved = TRUE;
                    break;

            }

        } else if (!FoundBEA && (VolumeStructureDescriptor->Type < 3 ||
                                 VolumeStructureDescriptor->Type == 255)) {

            DebugTrace(( 0, Dbg, "IsUdfsVolume, got a 9660 descriptor\n" ));

            //
            //  Only HSG (CDROM) and 9660 (CD001) are possible, and they are only legal
            //  before the ISO 13346 BEA/TEA extent.  By design, an ISO 13346 VSD precisely
            //  overlaps a 9660 PVD/SVD in the appropriate fields.
            //
            //  Note that we aren't being strict about the structure of the 9660 descriptors
            //  since that really isn't very interesting.  We care more about the 13346.
            //  
            //

            switch (UdfsFindInParseTable( VsdIdentParseTable,
                                          VolumeStructureDescriptor->Ident,
                                          VSD_LENGTH_IDENT )) {
                case VsdIdentCDROM:
                case VsdIdentCD001:

                    DebugTrace(( 0, Dbg, "IsUdfsVolume, ... seems we have 9660 here\n" ));

                    //
                    //  Note to our caller that we seem to have ISO 9660 here
                    //

                    break;

                default:

                    DebugTrace(( 0, Dbg, "IsUdfsVolume, ... but it looks wacky\n" ));

                    //
                    //  This probably was a false alert, but in any case there is nothing
                    //  on this volume for us.
                    //

                    Resolved = TRUE;
                    break;
            }

        } else {

            //
            //  Something else must be recorded on this volume.
            //

            DebugTrace(( 0, Dbg, "IsUdfsVolume, got an unrecognizeable descriptor, probably not 13346/9660\n" ));
            break;
        }

        //
        //  Descriptor size is 2kb.  Sector size is always a power of 2.  So just
        //  increase offset by desc. size.  This may fall on another 2k block within
        //  the current sector.
        //

        Offset += sizeof(VSD_GENERIC);
        VolumeStructureDescriptor = (PVSD_GENERIC)(((PUCHAR)VolumeStructureDescriptor) + sizeof( VSD_GENERIC));
    }

    //
    //  If we were looking in the last session,  and failed to find anything,  then
    //  go back and try the first.
    //
    
    if (!FoundNSR && (0 != LastSessionStartPsn))  {

        LastSessionStartPsn = 0;

        goto Retry;
    }

    DebugTrace(( -1, Dbg, "IsUdfsVolume -> %c\n", ( FoundNSR ? 'T' : 'F' )));

    //
    //  Free up our temporary buffer
    //

    if (Buffer) {
    
        ExFreePool( Buffer );
    }

    return FoundNSR;
}


ULONG
UdfsFindInParseTable (
    IN PPARSE_KEYVALUE ParseTable,
    IN PCHAR Id,
    IN ULONG MaxIdLen
    )

/*++

Routine Description:

    This routine walks a table of string key/value information for a match of the
    input Id.  MaxIdLen can be set to get a prefix match.

Arguments:

    Table - This is the table being searched.

    Id - Key value.

    MaxIdLen - Maximum possible length of Id.

Return Value:

    Value of matching entry, or the terminating (NULL) entry's value.

--*/

{
    PAGED_CODE();

    while (ParseTable->Key != NULL) {

        if (RtlEqualMemory(ParseTable->Key, Id, MaxIdLen)) {

            break;
        }

        ParseTable++;
    }

    return ParseTable->Value;
}