/*++

Copyright (c) 2006 - 2007, Intel Corporation
All rights reserved. This program and the accompanying materials
are licensed and made available under the terms and conditions of the BSD License
which accompanies this distribution.  The full text of the license may be found at
http://opensource.org/licenses/bsd-license.php

THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.

Module Name:

    FileStorage.c

Abstract:

    handles variable store/reads on file

Revision History

--*/
#include "FSVariable.h"

VOID             *mSFSRegistration;

//
// Prototypes
//

VOID
EFIAPI
OnVirtualAddressChangeFs (
  IN EFI_EVENT            Event,
  IN VOID                 *Context
  );

EFI_STATUS
EFIAPI
FileEraseStore(
  IN VARIABLE_STORAGE     *This
  );

EFI_STATUS
EFIAPI
FileWriteStore (
  IN VARIABLE_STORAGE     *This,
  IN UINTN                Offset,
  IN UINTN                BufferSize,
  IN VOID                 *Buffer
  );

EFI_STATUS
OpenStore (
  IN  EFI_DEVICE_PATH_PROTOCOL  *Device,
  IN  CHAR16                    *FilePathName,
  IN  UINT64                    OpenMode,
  OUT EFI_FILE                  **File
  );

//
// Implementation below:
//
VOID
FileClose (
  IN  EFI_FILE                   *File
  )
{
  EFI_STATUS Status;

  Status = File->Flush (File);
  ASSERT_EFI_ERROR (Status);

  Status = File->Close (File);
  ASSERT_EFI_ERROR (Status);
}

EFI_STATUS
CheckStore (
  IN  EFI_HANDLE                 SimpleFileSystemHandle,
  IN  UINT32                     VolumeId,
  OUT EFI_DEVICE_PATH_PROTOCOL   **Device
  )
{
#define BLOCK_SIZE              0x200
#define FAT16_VOLUME_ID_OFFSET  39
#define FAT32_VOLUME_ID_OFFSET  67
  EFI_STATUS                      Status;
  EFI_BLOCK_IO_PROTOCOL           *BlkIo;
  UINT8                           BootSector[BLOCK_SIZE];

  *Device = NULL;
  Status  = gBS->HandleProtocol (
                   SimpleFileSystemHandle,
                   &gEfiBlockIoProtocolGuid, // BlockIo should be supported if it supports SimpleFileSystem
                   (VOID*)&BlkIo
                   );

  if (EFI_ERROR (Status)) {
    goto ErrHandle;
  }
  if (!BlkIo->Media->MediaPresent) {
    DEBUG ((EFI_D_ERROR, "FileStorage: Media not present!\n"));
    Status = EFI_NO_MEDIA;
    goto ErrHandle;
  }
  if (BlkIo->Media->ReadOnly) {
    DEBUG ((EFI_D_ERROR, "FileStorage: Media is read-only!\n"));
    Status = EFI_ACCESS_DENIED;
    goto ErrHandle;
  }

  Status = BlkIo->ReadBlocks(
                    BlkIo,
                    BlkIo->Media->MediaId,
                    0,
                    BLOCK_SIZE,
                    BootSector
                    );
  ASSERT_EFI_ERROR (Status);
  if ((*(UINT32 *) &BootSector[FAT16_VOLUME_ID_OFFSET] != VolumeId) &&
      (*(UINT32 *) &BootSector[FAT32_VOLUME_ID_OFFSET] != VolumeId)
      ) {
    Status = EFI_NOT_FOUND;
    goto ErrHandle;
  }

  *Device = DuplicateDevicePath (DevicePathFromHandle (SimpleFileSystemHandle));
  ASSERT (*Device != NULL);

ErrHandle:
  return Status;
}

EFI_STATUS
CheckStoreExists (
  IN  EFI_DEVICE_PATH_PROTOCOL   *Device
  )
{
  EFI_HANDLE                        Handle;
  EFI_SIMPLE_FILE_SYSTEM_PROTOCOL   *Volume;
  EFI_STATUS                        Status;

  Status = gBS->LocateDevicePath (
                  &gEfiSimpleFileSystemProtocolGuid,
                  &Device, 
                  &Handle
                  );

  if (EFI_ERROR (Status)) {
    return Status;
  }

  Status = gBS->HandleProtocol (
                  Handle,
                  &gEfiSimpleFileSystemProtocolGuid,
                  (VOID **) &Volume
                  );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  return EFI_SUCCESS;
}

// this routine is still running in BS period, no limitation
// call FileInitStorage(), which load variable content file to memory
// read the store_header, init store_header if it has not been inited (read sth. about format/heathy)
// reclaim space using scratch memory

VOID
EFIAPI
OnSimpleFileSystemInstall (
  IN EFI_EVENT        Event,
  IN VOID             *Context
  )
{
  EFI_STATUS                Status;
  UINTN                     HandleSize;
  EFI_HANDLE                Handle;
  EFI_DEVICE_PATH_PROTOCOL  *Device;
  VS_DEV                    *Dev;
  EFI_FILE                  *File;
  UINTN                     NumBytes;

  Dev = (VS_DEV *) Context;
  
  if (VAR_FILE_DEVICEPATH (Dev) != NULL &&
      !EFI_ERROR (CheckStoreExists (VAR_FILE_DEVICEPATH (Dev)))
     ) {
    DEBUG ((EFI_D_ERROR, "FileStorage: Already mapped!\n"));
    return ;
  }

  while (TRUE) {
    HandleSize = sizeof (EFI_HANDLE);
    Status = gBS->LocateHandle (
                    ByRegisterNotify,
                    NULL,
                    mSFSRegistration,
                    &HandleSize,
                    &Handle
                    );
    if (EFI_ERROR (Status)) {
      return ;
    }
    
    Status = CheckStore (Handle, VAR_FILE_VOLUMEID (Dev), &Device);
    if (!EFI_ERROR (Status)) {
      break;
    }
  }

  VAR_FILE_DEVICEPATH (Dev) = Device;
  Status = OpenStore (
             VAR_FILE_DEVICEPATH (Dev), 
             VAR_FILE_FILEPATH (Dev), 
             EFI_FILE_MODE_WRITE | EFI_FILE_MODE_READ | EFI_FILE_MODE_CREATE,
             &File
             );
  ASSERT_EFI_ERROR (Status);
  
  NumBytes = Dev->Size;
  Status = File->Write (File, &NumBytes, VAR_DATA_PTR (Dev));
  ASSERT_EFI_ERROR (Status);
  // KEN: bugbug here if closing file, volume handle will be free,
  // and system will be hang when accessing volume handle in future.
  //FileClose (File);
  DEBUG ((EFI_D_ERROR, "FileStorage: Mapped to file!\n"));
}

EFI_STATUS
FileStorageConstructor (
  OUT VARIABLE_STORAGE      **VarStore,
  OUT EFI_EVENT_NOTIFY      *GoVirtualEvent,
  IN  EFI_PHYSICAL_ADDRESS  NvStorageBase,
  IN  UINTN                 Size,
  IN  UINT32                VolumeId,
  IN  CHAR16                *FilePath
  )
{
  VS_DEV                    *Dev;
  EFI_STATUS                Status;
  EFI_EVENT                 Event;

  Status = gBS->AllocatePool (EfiRuntimeServicesData, sizeof(VS_DEV), (VOID **) &Dev);
  ASSERT_EFI_ERROR (Status);
  ZeroMem (Dev, sizeof(VS_DEV));

  Dev->Signature          = VARIABLE_STORE_SIGNATURE;
  Dev->Size               = Size;
  VAR_DATA_PTR (Dev)      = (UINT8 *) (UINTN) NvStorageBase;
  VAR_FILE_VOLUMEID (Dev) = VolumeId;
  StrCpy (VAR_FILE_FILEPATH (Dev), FilePath);
  Dev->VarStore.Erase     = FileEraseStore;
  Dev->VarStore.Write     = FileWriteStore;

  DEBUG ((EFI_D_ERROR, "FileStorageConstructor(0x%0x:0x%0x): added!\n", NvStorageBase, Size));

  // add notify on SFS's installation.

  Status = gBS->CreateEvent (
                  EVT_NOTIFY_SIGNAL,
                  TPL_CALLBACK,
                  OnSimpleFileSystemInstall,
                  Dev,
                  &Event
                  );
  ASSERT_EFI_ERROR (Status);

  Status = gBS->RegisterProtocolNotify (
                  &gEfiSimpleFileSystemProtocolGuid,
                  Event,
                  &mSFSRegistration
                  );
  ASSERT_EFI_ERROR (Status);

  *VarStore       = &Dev->VarStore;
  *GoVirtualEvent = OnVirtualAddressChangeFs;
  return EFI_SUCCESS;
}

EFI_STATUS
EFIAPI
FileEraseStore(
  IN VARIABLE_STORAGE   *This
  )
{
  EFI_STATUS              Status;
  VS_DEV                  *Dev;
  EFI_FILE                *File;
  UINTN                   NumBytes;

  Status = EFI_SUCCESS;
  Dev    = DEV_FROM_THIS(This);

  SetMem (VAR_DATA_PTR (Dev), Dev->Size, VAR_DEFAULT_VALUE);

  if (!EfiAtRuntime () && VAR_FILE_DEVICEPATH (Dev) != NULL) {
    Status = OpenStore (
               VAR_FILE_DEVICEPATH (Dev), 
               VAR_FILE_FILEPATH (Dev), 
               EFI_FILE_MODE_WRITE | EFI_FILE_MODE_READ,
               &File
               );
    ASSERT_EFI_ERROR (Status);
    NumBytes = Dev->Size;
    Status = File->Write (File, &NumBytes, VAR_DATA_PTR (Dev));
    ASSERT_EFI_ERROR (Status);
    FileClose (File);
  }
  
  return Status;
}

EFI_STATUS
EFIAPI
FileWriteStore (
  IN VARIABLE_STORAGE     *This,
  IN UINTN                Offset,
  IN UINTN                BufferSize,
  IN VOID                 *Buffer
  )
{
  EFI_STATUS              Status;
  VS_DEV                  *Dev;
  EFI_FILE                *File;

  Status = EFI_SUCCESS;
  Dev    = DEV_FROM_THIS(This);

  ASSERT (Buffer != NULL);
  ASSERT (Offset + BufferSize <= Dev->Size);

  CopyMem (VAR_DATA_PTR (Dev) + Offset, Buffer, BufferSize);
  
  if (!EfiAtRuntime () && VAR_FILE_DEVICEPATH (Dev) != NULL) {
    Status = OpenStore (
               VAR_FILE_DEVICEPATH (Dev), 
               VAR_FILE_FILEPATH (Dev), 
               EFI_FILE_MODE_WRITE | EFI_FILE_MODE_READ,
               &File
               );
    Status = File->SetPosition (File, Offset);
    ASSERT_EFI_ERROR (Status);
    Status = File->Write (File, &BufferSize, Buffer);
    ASSERT_EFI_ERROR (Status);
    FileClose (File);
  }
  return Status;
}

VOID
EFIAPI
OnVirtualAddressChangeFs (
  IN EFI_EVENT            Event,
  IN VOID                 *Context
  )
{
  VS_DEV  *Dev;

  Dev = DEV_FROM_THIS (Context);

  EfiConvertPointer (0, (VOID **) &VAR_DATA_PTR (Dev));
  EfiConvertPointer (0, (VOID **) &Dev->VarStore.Erase);
  EfiConvertPointer (0, (VOID **) &Dev->VarStore.Write);
}

EFI_STATUS
OpenStore (
  IN  EFI_DEVICE_PATH_PROTOCOL  *Device,
  IN  CHAR16                    *FilePathName,
  IN  UINT64                    OpenMode,
  OUT EFI_FILE                  **File
  )
{
  EFI_HANDLE                        Handle;
  EFI_FILE_HANDLE                   Root;
  EFI_SIMPLE_FILE_SYSTEM_PROTOCOL   *Volume;
  EFI_STATUS                        Status;

  *File = NULL;

  Status = gBS->LocateDevicePath (
                  &gEfiSimpleFileSystemProtocolGuid,
                  &Device, 
                  &Handle
                  );

  if (EFI_ERROR (Status)) {
    return Status;
  }

  Status = gBS->HandleProtocol (
                  Handle,
                  &gEfiSimpleFileSystemProtocolGuid,
                  (VOID **) &Volume
                  );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  //
  // Open the root directory of the volume
  //
  Root = NULL;
  Status = Volume->OpenVolume (
                     Volume,
                     &Root
                     );
  ASSERT_EFI_ERROR (Status);
  ASSERT (Root != NULL);

  //
  // Open file
  //
  Status = Root->Open (
                   Root,
                   File,
                   FilePathName,
                   OpenMode,
                   0
                   );
  if (EFI_ERROR (Status)) {
    *File = NULL;
  }

  //
  // Close the Root directory
  //
  Root->Close (Root);
  return Status;
}
