//
// Copyright (c) Microsoft Corporation.  All rights reserved.
//
//
// Use of this source code is subject to the terms of the Microsoft shared
// source or premium shared source license agreement under which you licensed
// this source code. If you did not accept the terms of the license agreement,
// you are not authorized to use this source code. For the terms of the license,
// please see the license agreement between you and Microsoft or, if applicable,
// see the SOURCE.RTF on your install media or the root of your tools installation.
// THE SOURCE CODE IS PROVIDED "AS IS", WITH NO WARRANTIES.
//

#include "udf.h"

// -----------------------------------------------------------------------------
// CUDFMedia
// -----------------------------------------------------------------------------

// /////////////////////////////////////////////////////////////////////////////
// Constructor
//
CUDFMedia::CUDFMedia()
: m_pVolume( NULL )
{
}

// /////////////////////////////////////////////////////////////////////////////
// Intitialize
//
BOOL CUDFMedia::Initialize( CVolume* pVolume )
{
    ASSERT( pVolume != NULL );
    
    m_pVolume = pVolume;

    return TRUE;
}

// -----------------------------------------------------------------------------
// CReadOnlyMedia
// -----------------------------------------------------------------------------

// /////////////////////////////////////////////////////////////////////////////
// ReadRange
//
LRESULT CReadOnlyMedia::ReadRange( DWORD dwSector,
                                   DWORD dwSectorOffset, 
                                   DWORD dwSize, 
                                   BYTE* pBuffer,
                                   DWORD* pdwBytesRead )
{
    ASSERT(m_pVolume != NULL);

    LRESULT lResult = ERROR_SUCCESS;
    BYTE* pDummyBuffer = NULL;
    BYTE RequestBuffer[sizeof(CDROM_READ) + sizeof(SGX_BUF) * 2];
    PCDROM_READ pReadInfo = (PCDROM_READ)RequestBuffer;

    DWORD dwSectorSize = m_pVolume->GetSectorSize();
    DWORD dwPreBytes = dwSectorOffset;
    DWORD dwPostBytes = ((dwPreBytes + dwSize) & (dwSectorSize - 1)) 
                        ? dwSectorSize - ((dwPreBytes + dwSize) & (dwSectorSize - 1)) 
                        : 0;
    DWORD dwTotalSize = dwPreBytes + dwPostBytes + dwSize;
    DWORD dwBytesRead = 0;

    //
    // If after aligning the buffer it doesn't fit into a DWORD, then return an
    // error.
    //
    if( (ULONG_MAX - dwSize) < (dwPreBytes + dwPostBytes) )
    {
        return ERROR_INVALID_PARAMETER;
    }

    if( !pBuffer || !pdwBytesRead )
    {
        return ERROR_INVALID_PARAMETER;
    }

    *pdwBytesRead = 0;

    ZeroMemory( RequestBuffer, sizeof(RequestBuffer) );

    pReadInfo->bRawMode = FALSE;
    pReadInfo->TransferLength = dwTotalSize >> m_pVolume->GetShiftCount();
    pReadInfo->TrackMode = YellowMode2;
    pReadInfo->StartAddr.Mode = CDROM_ADDR_LBA;
    pReadInfo->StartAddr.Address.lba = dwSector;

    if( dwPreBytes || dwPostBytes )
    {
        pDummyBuffer = new (UDF_TAG_BYTE_BUFFER) BYTE[ dwSectorSize ];
        if( !pDummyBuffer )
        {
            return ERROR_NOT_ENOUGH_MEMORY;
        }
    }

    if( dwPreBytes )
    {
        pReadInfo->sglist[pReadInfo->sgcount].sb_buf = pDummyBuffer;
        pReadInfo->sglist[pReadInfo->sgcount].sb_len = dwPreBytes;
        pReadInfo->sgcount += 1;
    }

    pReadInfo->sglist[pReadInfo->sgcount].sb_buf = pBuffer;
    pReadInfo->sglist[pReadInfo->sgcount].sb_len = dwSize;
    pReadInfo->sgcount += 1;

    if( dwPostBytes )
    {
        pReadInfo->sglist[pReadInfo->sgcount].sb_buf = pDummyBuffer;
        pReadInfo->sglist[pReadInfo->sgcount].sb_len = dwPostBytes;
        pReadInfo->sgcount += 1;
    }

    if( !m_pVolume->UDFSDeviceIoControl( IOCTL_CDROM_READ_SG, 
                                         RequestBuffer,
                                         sizeof(CDROM_READ) + sizeof(SGX_BUF) * (pReadInfo->sgcount - 1),
                                         NULL, 
                                         0, 
                                         &dwBytesRead, 
                                         NULL ) )
    {
        DEBUGMSG(ZONE_IO|ZONE_ERROR,(TEXT("UDFS!ReadRange Unable to read from CD at sector 0x%x"), dwSector));
        lResult = GetLastError();
    }

    if( dwBytesRead < dwPreBytes + dwSize )
    {
        ASSERT( FALSE );
        //
        // TODO::Is there a better message for this?
        //
        lResult = ERROR_READ_FAULT;
    }

    *pdwBytesRead = dwSize;

    if( pDummyBuffer )
    {
        delete [] pDummyBuffer;
        pDummyBuffer = NULL;
    }
    
    return lResult;
}

// /////////////////////////////////////////////////////////////////////////////
// WriteRange
//
// Easiest function ever to implement.  ROM media (or closed R) cannot be
// written to.
//
LRESULT CReadOnlyMedia::WriteRange( DWORD dwSector,
                                    DWORD dwSectorOffset,
                                    DWORD dwSize,
                                    BYTE* pBuffer,
                                    DWORD* pdwBytesWritten )
{
    ASSERT(m_pVolume != NULL);
    LRESULT lResult = ERROR_WRITE_PROTECT;

    return lResult;
}

// -----------------------------------------------------------------------------
// CWriteOnceMedia
// -----------------------------------------------------------------------------

// /////////////////////////////////////////////////////////////////////////////
// Constructor
//
CWriteOnceMedia::CWriteOnceMedia()
: CUDFMedia(),
  m_fDiscClosed( FALSE )
{
}

// /////////////////////////////////////////////////////////////////////////////
// Initialize
//
// There could be write once media without a VAT starting in UDF 2.60.  All
// mastered incremental media should be reported as ReadOnlyMedia.  But
// packet written incremental media that has been closed will still need to
// access through the VAT.
//
BOOL CWriteOnceMedia::Initialize( CVolume* pVolume )
{
    BOOL fResult = CUDFMedia::Initialize( pVolume );

    DWORD dwBytesReturned = 0;
    DISC_INFORMATION DiscInfo = { 0 };
    DISC_INFO di = DI_SCSI_INFO;

    if( !fResult )
    {
        goto exit_initialize;
    }

    //
    // Gather general information about the disc.
    //
    fResult = pVolume->UDFSDeviceIoControl( IOCTL_CDROM_DISC_INFO, 
                                            &di, 
                                            sizeof(DISC_INFO), 
                                            &DiscInfo, 
                                            sizeof( DiscInfo ), 
                                            &dwBytesReturned, 
                                            NULL );

    if( !fResult )
    {
        goto exit_initialize;
    }

    if( DiscInfo.DiscStatus == DISK_STATUS_COMPLETE )
    {
        //
        // If the disk is closed then there will never be a next writable
        // address but we will need to be able to reference the VAT.
        //
        m_fDiscClosed = TRUE;
    }    

exit_initialize:

    return fResult;
}

// /////////////////////////////////////////////////////////////////////////////
// ReadRange
//
//
LRESULT CWriteOnceMedia::ReadRange( DWORD dwSector,
                                    DWORD dwSectorOffset, 
                                    DWORD dwSize, 
                                    BYTE* pBuffer,
                                    DWORD* pdwBytesRead )
{
    ASSERT(m_pVolume != NULL);
    LRESULT lResult = ERROR_SUCCESS;

    BYTE* pDummyBuffer = NULL;
    DWORD dwDummyBufferSize = 0;
    BYTE RequestBuffer[sizeof(CDROM_READ) + sizeof(SGX_BUF) * 2];
    PCDROM_READ pReadInfo = (PCDROM_READ)RequestBuffer;

    DWORD dwSectorSize = m_pVolume->GetSectorSize();
    DWORD dwShiftCount = m_pVolume->GetShiftCount();

    DWORD dwFirstSector = 0;
    DWORD dwNumSectors = 0;
    DWORD dwPreBytes = 0;
    DWORD dwPostBytes = 0;
    DWORD dwBytesRead = 0;


    dwFirstSector = dwSector;
    dwNumSectors = ((dwSize + dwSectorOffset + dwSectorSize - 1) >> dwShiftCount);

    dwPreBytes = dwSectorOffset;
    dwPostBytes = (dwNumSectors << dwShiftCount) - dwSize - dwPreBytes;

    //
    // If after aligning the buffer it doesn't fit into a DWORD, then return an
    // error.
    //
    if( (ULONG_MAX - dwSize) < (dwPreBytes + dwPostBytes) )
    {
        lResult = ERROR_INVALID_PARAMETER;
        goto exit_readrange;
    }

    if( !pBuffer || !pdwBytesRead )
    {
        lResult = ERROR_INVALID_PARAMETER;
        goto exit_readrange;
    }

    *pdwBytesRead = 0;

    ZeroMemory( RequestBuffer, sizeof(RequestBuffer) );

    pReadInfo->bRawMode = FALSE;
    pReadInfo->TransferLength = dwNumSectors;
    pReadInfo->TrackMode = YellowMode2;
    pReadInfo->StartAddr.Mode = CDROM_ADDR_LBA;
    pReadInfo->StartAddr.Address.lba = dwFirstSector;

    //
    // Allocate a buffer big enough to hold either the pre or post bytes.
    //
    if( dwPreBytes || dwPostBytes )
    {
        dwDummyBufferSize = dwPreBytes > dwPostBytes ? dwPreBytes : dwPostBytes;

        pDummyBuffer = new (UDF_TAG_BYTE_BUFFER) BYTE[dwDummyBufferSize];
        if( !pDummyBuffer )
        {
            lResult = ERROR_NOT_ENOUGH_MEMORY ;
            goto exit_readrange;
        }
    }

    //
    // Setup the scatter/gather structures.
    //
    if( dwPreBytes )
    {
        pReadInfo->sglist[pReadInfo->sgcount].sb_buf = pDummyBuffer;
        pReadInfo->sglist[pReadInfo->sgcount].sb_len = dwPreBytes;
        pReadInfo->sgcount += 1;
    }

    pReadInfo->sglist[pReadInfo->sgcount].sb_buf = pBuffer;
    pReadInfo->sglist[pReadInfo->sgcount].sb_len = dwSize;
    pReadInfo->sgcount += 1;

    if( dwPostBytes )
    {
        pReadInfo->sglist[pReadInfo->sgcount].sb_buf = pDummyBuffer;
        pReadInfo->sglist[pReadInfo->sgcount].sb_len = dwPostBytes;
        pReadInfo->sgcount += 1;
    }

    if( !m_pVolume->UDFSDeviceIoControl( IOCTL_CDROM_READ_SG, 
                                         RequestBuffer,
                                         sizeof(CDROM_READ) + sizeof(SGX_BUF) * (pReadInfo->sgcount - 1),
                                         NULL, 
                                         0, 
                                         &dwBytesRead, 
                                         NULL ) )
    {
        DEBUGMSG(ZONE_IO|ZONE_ERROR,(TEXT("UDFS!ReadRange Unable to read from CD at sector 0x%x"), dwSector));
        lResult = GetLastError();
        goto exit_readrange;
    }

    if( dwBytesRead < dwPreBytes + dwSize )
    {
        ASSERT( FALSE );
        //
        // TODO::Is there a better message for this?
        //
        lResult = ERROR_READ_FAULT;
    }

    *pdwBytesRead = dwSize;

exit_readrange:
    if( pDummyBuffer )
    {
        delete [] pDummyBuffer;
    }
    
    return lResult;
}

// /////////////////////////////////////////////////////////////////////////////
// WriteRange
//
LRESULT CWriteOnceMedia::WriteRange( DWORD dwSector,
                                     DWORD dwSectorOffset,
                                     DWORD dwSize,
                                     BYTE* pBuffer,
                                     DWORD* pdwBytesWritten )
{
    ASSERT(m_pVolume != NULL);
    LRESULT lResult = ERROR_CALL_NOT_IMPLEMENTED;

    return lResult;
}

// /////////////////////////////////////////////////////////////////////////////
// GetMinBlocksPerRead
//
DWORD CWriteOnceMedia::GetMinBlocksPerRead() const
{
    //
    // Technically you can read in single sectors (at least on DVD), but that
    // won't help much becuase the device will actually read the complete ECC
    // block, de-interleave the sectors, and then return the data for that one
    // sector.  It is better just to read the entire block.
    //
    ASSERT(m_pVolume != NULL);
    return m_pVolume->GetMediaType() & MED_FLAG_DVD ? 16 : 32;
}

// /////////////////////////////////////////////////////////////////////////////
// GetMinBlocksPerWrite
//
DWORD CWriteOnceMedia::GetMinBlocksPerWrite() const
{
    ASSERT(m_pVolume != NULL);
    return m_pVolume->GetMediaType() & MED_FLAG_DVD ? 16 : 32;
}

// -----------------------------------------------------------------------------
// CRewritableMedia
// -----------------------------------------------------------------------------

// /////////////////////////////////////////////////////////////////////////////
// ReadRange
//
LRESULT CRewritableMedia::ReadRange( DWORD dwSector,
                                     DWORD dwSectorOffset,
                                     DWORD dwSize, 
                                     BYTE* pBuffer,
                                     DWORD* pdwBytesRead )
{
    ASSERT(m_pVolume != NULL);
    LRESULT lResult = ERROR_SUCCESS;

    BYTE* pDummyBuffer = NULL;
    DWORD dwDummyBufferSize = 0;
    BYTE RequestBuffer[sizeof(CDROM_READ) + sizeof(SGX_BUF) * 2];
    PCDROM_READ pReadInfo = (PCDROM_READ)RequestBuffer;

    DWORD dwSectorSize = m_pVolume->GetSectorSize();
    DWORD dwShiftCount = m_pVolume->GetShiftCount();

    DWORD dwFirstSector = 0;
    DWORD dwNumSectors = 0;
    DWORD dwPreBytes = 0;
    DWORD dwPostBytes = 0;
    DWORD dwBytesRead = 0;


    dwFirstSector = dwSector;
    dwNumSectors = ((dwSize + dwSectorOffset + dwSectorSize - 1) >> dwShiftCount);

    dwPreBytes = dwSectorOffset;
    dwPostBytes = (dwNumSectors << dwShiftCount) - dwSize - dwPreBytes;

    //
    // If after aligning the buffer it doesn't fit into a DWORD, then return an
    // error.
    //
    if( (ULONG_MAX - dwSize) < (dwPreBytes + dwPostBytes) )
    {
        lResult = ERROR_INVALID_PARAMETER;
        goto exit_readrange;
    }

    if( !pBuffer || !pdwBytesRead )
    {
        lResult = ERROR_INVALID_PARAMETER;
        goto exit_readrange;
    }

    *pdwBytesRead = 0;

    ZeroMemory( RequestBuffer, sizeof(RequestBuffer) );

    pReadInfo->bRawMode = FALSE;
    pReadInfo->TransferLength = dwNumSectors;
    pReadInfo->TrackMode = YellowMode2;
    pReadInfo->StartAddr.Mode = CDROM_ADDR_LBA;
    pReadInfo->StartAddr.Address.lba = dwFirstSector;

    //
    // Allocate a buffer big enough to hold either the pre or post bytes.
    //
    if( dwPreBytes || dwPostBytes )
    {
        dwDummyBufferSize = dwPreBytes > dwPostBytes ? dwPreBytes : dwPostBytes;

        pDummyBuffer = new (UDF_TAG_BYTE_BUFFER) BYTE[dwDummyBufferSize];
        if( !pDummyBuffer )
        {
            lResult = ERROR_NOT_ENOUGH_MEMORY ;
            goto exit_readrange;
        }
    }

    //
    // Setup the scatter/gather structures.
    //
    if( dwPreBytes )
    {
        pReadInfo->sglist[pReadInfo->sgcount].sb_buf = pDummyBuffer;
        pReadInfo->sglist[pReadInfo->sgcount].sb_len = dwPreBytes;
        pReadInfo->sgcount += 1;
    }

    pReadInfo->sglist[pReadInfo->sgcount].sb_buf = pBuffer;
    pReadInfo->sglist[pReadInfo->sgcount].sb_len = dwSize;
    pReadInfo->sgcount += 1;

    if( dwPostBytes )
    {
        pReadInfo->sglist[pReadInfo->sgcount].sb_buf = pDummyBuffer;
        pReadInfo->sglist[pReadInfo->sgcount].sb_len = dwPostBytes;
        pReadInfo->sgcount += 1;
    }

    if( !m_pVolume->UDFSDeviceIoControl( IOCTL_CDROM_READ_SG, 
                                         RequestBuffer,
                                         sizeof(CDROM_READ) + sizeof(SGX_BUF) * (pReadInfo->sgcount - 1),
                                         NULL, 
                                         0, 
                                         &dwBytesRead, 
                                         NULL ) )
    {
        DEBUGMSG(ZONE_IO|ZONE_ERROR,(TEXT("UDFS!ReadRange Unable to read from CD at sector 0x%x"), dwSector));
        lResult = GetLastError();
        goto exit_readrange;
    }

    if( dwBytesRead < dwPreBytes + dwSize )
    {
        ASSERT( FALSE );
        //
        // TODO::Is there a better message for this?
        //
        lResult = ERROR_READ_FAULT;
    }

    *pdwBytesRead = dwSize;

exit_readrange:
    if( pDummyBuffer )
    {
        delete [] pDummyBuffer;
    }
    
    return lResult;
}

// /////////////////////////////////////////////////////////////////////////////
// WriteRange
//
LRESULT CRewritableMedia::WriteRange( DWORD dwSector,
                                      DWORD dwSectorOffset,
                                      DWORD dwSize,
                                      BYTE* pBuffer,
                                      DWORD* pdwBytesWritten )
{
    ASSERT(m_pVolume != NULL);
    LRESULT lResult = ERROR_CALL_NOT_IMPLEMENTED;

    return lResult;
}

// /////////////////////////////////////////////////////////////////////////////
// GetMinBlocksPerRead
//
DWORD CRewritableMedia::GetMinBlocksPerRead() const
{
    ASSERT(m_pVolume != NULL);
    return m_pVolume->GetMediaType() & MED_FLAG_DVD ? 16 : 32;
}

// /////////////////////////////////////////////////////////////////////////////
// GetMinBlocksPerWrite
//
DWORD CRewritableMedia::GetMinBlocksPerWrite() const
{
    ASSERT(m_pVolume != NULL);
    return m_pVolume->GetMediaType() & MED_FLAG_DVD ? 16 : 32;
}

// -----------------------------------------------------------------------------
// CHardDiskMedia
// -----------------------------------------------------------------------------

// /////////////////////////////////////////////////////////////////////////////
// ReadRange
//
LRESULT CHardDiskMedia::ReadRange( DWORD dwSector,
                                   DWORD dwSectorOffset, 
                                   DWORD dwSize, 
                                   BYTE* pBuffer,
                                   DWORD* pdwBytesRead )
{
    ASSERT(m_pVolume != NULL);
    LRESULT lResult = ERROR_CALL_NOT_IMPLEMENTED;

    return lResult;
}

// /////////////////////////////////////////////////////////////////////////////
// WriteRange
//
LRESULT CHardDiskMedia::WriteRange( DWORD dwSector,
                                    DWORD dwSectorOffset,
                                    DWORD dwSize,
                                    BYTE* pBuffer,
                                    DWORD* pdwBytesWritten )
{
    ASSERT(m_pVolume != NULL);
    LRESULT lResult = ERROR_CALL_NOT_IMPLEMENTED;

    return lResult;
}

// /////////////////////////////////////////////////////////////////////////////
// GetMinBlocksPerRead
//
DWORD CHardDiskMedia::GetMinBlocksPerRead() const
{
    return 1;
}

// /////////////////////////////////////////////////////////////////////////////
// GetMinBlocksPerWrite
//
DWORD CHardDiskMedia::GetMinBlocksPerWrite() const
{
    return 1;
}

