/*++

Copyright (c) 1998  Intel Corporation

Module Name:
    
    sread.c

Abstract:

    Simple read file access



Revision History

--*/

#include "lib.h"

#define SIMPLE_READ_SIGNATURE       EFI_SIGNATURE_32('s','r','d','r')
typedef struct _SIMPLE_READ_FILE {
    UINTN               Signature;
    BOOLEAN             FreeBuffer;
    VOID                *Source;
    UINTN               SourceSize;
    EFI_FILE_HANDLE     FileHandle;
} SIMPLE_READ_HANDLE;

       

EFI_STATUS
OpenSimpleReadFile (
    IN BOOLEAN                  BootPolicy,
    IN VOID                     *SourceBuffer   OPTIONAL,
    IN UINTN                    SourceSize,
    IN OUT EFI_DEVICE_PATH      **FilePath,
    OUT EFI_HANDLE              *DeviceHandle,
    OUT SIMPLE_READ_FILE        *SimpleReadHandle
    )
/*++

Routine Description:

    Opens a file for (simple) reading.  The simple read abstraction
    will access the file either from a memory copy, from a file
    system interface, or from the load file interface. 

Arguments:

Returns:

    A handle to access the file

--*/
{
    SIMPLE_READ_HANDLE          *FHand;
    EFI_DEVICE_PATH             *UserFilePath;
    EFI_DEVICE_PATH             *TempFilePath;
    EFI_DEVICE_PATH             *TempFilePathPtr;
    FILEPATH_DEVICE_PATH        *FilePathNode;
    EFI_FILE_HANDLE             FileHandle, LastHandle;
    EFI_STATUS                  Status;
    EFI_LOAD_FILE_INTERFACE     *LoadFile;
  
    FHand = NULL;
    UserFilePath = *FilePath;

    //
    // Allocate a new simple read handle structure
    //

    FHand = AllocateZeroPool (sizeof(SIMPLE_READ_HANDLE));
    if (!FHand) {
        Status = EFI_OUT_OF_RESOURCES;
        goto Done;
    }

    *SimpleReadHandle = (SIMPLE_READ_FILE) FHand;
    FHand->Signature = SIMPLE_READ_SIGNATURE;

    //
    // If the caller passed a copy of the file, then just use it
    //

    if (SourceBuffer) {
        FHand->Source = SourceBuffer;
        FHand->SourceSize = SourceSize;
        *DeviceHandle = NULL;
        Status = EFI_SUCCESS;
        goto Done;
    } 

    //
    // Attempt to access the file via a file system interface
    //

    FileHandle = NULL;
    Status = uefi_call_wrapper(BS->LocateDevicePath, 3, &FileSystemProtocol, FilePath, DeviceHandle);
    if (!EFI_ERROR(Status)) {
        FileHandle = LibOpenRoot (*DeviceHandle);
    }

    Status = FileHandle ? EFI_SUCCESS : EFI_UNSUPPORTED;

    //
    // To access as a filesystem, the filepath should only
    // contain filepath components.  Follow the filepath nodes
    // and find the target file
    //

    FilePathNode = (FILEPATH_DEVICE_PATH *) *FilePath;
    while (!IsDevicePathEnd(&FilePathNode->Header)) {

        //
        // For filesystem access each node should be a filepath component
        //

        if (DevicePathType(&FilePathNode->Header) != MEDIA_DEVICE_PATH ||
            DevicePathSubType(&FilePathNode->Header) != MEDIA_FILEPATH_DP) {
            Status = EFI_UNSUPPORTED;
        }

        //
        // If there's been an error, stop
        //

        if (EFI_ERROR(Status)) {
            break;
        }
        
        //
        // Open this file path node
        //

        LastHandle = FileHandle;
        FileHandle = NULL;

        Status = uefi_call_wrapper(
			LastHandle->Open,
			5, 
                        LastHandle,
                        &FileHandle,
                        FilePathNode->PathName,
                        EFI_FILE_MODE_READ,
                        0
                        );
        
        //
        // Close the last node
        //
        
        uefi_call_wrapper(LastHandle->Close, 1, LastHandle);

        //
        // Get the next node
        //

        FilePathNode = (FILEPATH_DEVICE_PATH *) NextDevicePathNode(&FilePathNode->Header);
    }

    //
    // If success, return the FHand
    //

    if (!EFI_ERROR(Status)) {
        ASSERT(FileHandle);
        FHand->FileHandle = FileHandle;
        goto Done;
    }

    //
    // Cleanup from filesystem access
    //

    if (FileHandle) {
        uefi_call_wrapper(FileHandle->Close, 1, FileHandle);
        FileHandle = NULL;
        *FilePath = UserFilePath;
    }

    //
    // If the error is something other then unsupported, return it
    //

    if (Status != EFI_UNSUPPORTED) {
        goto Done;
    }

    //
    // Attempt to access the file via the load file protocol
    //

    Status = LibDevicePathToInterface (&LoadFileProtocol, *FilePath, (VOID*)&LoadFile);
    if (!EFI_ERROR(Status)) {

        TempFilePath = DuplicateDevicePath (*FilePath);

        TempFilePathPtr = TempFilePath;

        Status = uefi_call_wrapper(BS->LocateDevicePath, 3, &LoadFileProtocol, &TempFilePath, DeviceHandle);

        FreePool (TempFilePathPtr);

        //
        // Determine the size of buffer needed to hold the file
        //

        SourceSize = 0;
        Status = uefi_call_wrapper(
		    LoadFile->LoadFile,
			5,
                    LoadFile,
                    *FilePath,
                    BootPolicy,
                    &SourceSize,
                    NULL
                    );

        //
        // We expect a buffer too small error to inform us 
        // of the buffer size needed
        //

        if (Status == EFI_BUFFER_TOO_SMALL) {
            SourceBuffer = AllocatePool (SourceSize);
            
            if (SourceBuffer) {
                FHand->FreeBuffer = TRUE;
                FHand->Source = SourceBuffer;
                FHand->SourceSize = SourceSize;

                Status = uefi_call_wrapper(
			    LoadFile->LoadFile,
				5,
                            LoadFile,
                            *FilePath,
                            BootPolicy,
                            &SourceSize,
                            SourceBuffer
                            );  
            }
        }

        //
        // If success, return FHand
        //

        if (!EFI_ERROR(Status) || Status == EFI_ALREADY_STARTED) {
            goto Done;
        }
    }

    //
    // Nothing else to try
    //

    DEBUG ((D_LOAD|D_WARN, "OpenSimpleReadFile: Device did not support a known load protocol\n"));
    Status = EFI_UNSUPPORTED;

Done:

    //
    // If the file was not accessed, clean up
    //
    if (EFI_ERROR(Status) && (Status != EFI_ALREADY_STARTED)) {
        if (FHand) {
            if (FHand->FreeBuffer) {
                FreePool (FHand->Source);
            }

            FreePool (FHand);
        }
    }

    return Status;
}

EFI_STATUS
ReadSimpleReadFile (
    IN SIMPLE_READ_FILE     UserHandle,
    IN UINTN                Offset,
    IN OUT UINTN            *ReadSize,
    OUT VOID                *Buffer
    )
{
    UINTN                   EndPos;
    SIMPLE_READ_HANDLE      *FHand;
    EFI_STATUS              Status;

    FHand = UserHandle;
    ASSERT (FHand->Signature == SIMPLE_READ_SIGNATURE);
    if (FHand->Source) {

        //
        // Move data from our local copy of the file
        //

        EndPos = Offset + *ReadSize;
        if (EndPos > FHand->SourceSize) {
            *ReadSize = FHand->SourceSize - Offset;
            if (Offset >= FHand->SourceSize) {
                *ReadSize = 0;
            }
        }

        CopyMem (Buffer, (CHAR8 *) FHand->Source + Offset, *ReadSize);
        Status = EFI_SUCCESS;

    } else {

        //
        // Read data from the file
        //

        Status = uefi_call_wrapper(FHand->FileHandle->SetPosition, 2, FHand->FileHandle, Offset);

        if (!EFI_ERROR(Status)) {
            Status = uefi_call_wrapper(FHand->FileHandle->Read, 3, FHand->FileHandle, ReadSize, Buffer);
        }
    }

    return Status;
}


VOID
CloseSimpleReadFile (
    IN SIMPLE_READ_FILE     UserHandle
    )
{
    SIMPLE_READ_HANDLE      *FHand;

    FHand = UserHandle;
    ASSERT (FHand->Signature == SIMPLE_READ_SIGNATURE);

    //
    // Free any file handle we opened
    //

    if (FHand->FileHandle) {
        uefi_call_wrapper(FHand->FileHandle->Close, 1, FHand->FileHandle);
    }

    //
    // If we allocated the Source buffer, free it
    //

    if (FHand->FreeBuffer) {
        FreePool (FHand->Source);
    }

    //
    // Done with this simple read file handle
    //

    FreePool (FHand);
}
