/** @file

  Simple File System overrides module.

**/

#include <Library/UefiLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/DebugLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>

#include "Common.h"


////////////////////////////////////////////
//
// Some helper function
//


/** Composes file name from Parent and FName. Allocates memory for result which should be released by caller. */
CHAR16*
GetNormalizedFName(IN CHAR16 *Parent, IN CHAR16 *FName)
{
	CHAR16			*TmpStr;
	CHAR16			*TmpStr2;
	UINTN			Len;
	
	//DBG("NormFName('%s' + '%s')", Parent, FName);
	// case: FName starts with \ "\System\Xx"
	// we'll just use it as is, but we are wrong if "\System\Xx\..\Yy\.\Zz" or similar
	if (FName[0] == L'\\') {
		FName = AllocateCopyPool(StrSize(FName), FName); // reusing FName
	}
	
	// case: FName is "."
	// we'll just copy Parent assuming Parent is normalized, which will be the case if this func will be correct once
	else if (FName[0] == L'.' && FName[1] == L'\0') {
		FName = AllocateCopyPool(StrSize(Parent), Parent);
	}
	
	// case: FName is ".."
	// we'll extract Parent's parent - also assuming Parent is normalized
	else if (FName[0] == L'.' && FName[1] == L'.' && FName[2] == L'\0') {
		TmpStr = GetStrLastCharOccurence(Parent, L'\\');
		// if there is L'\\' and not at the beginning ...
		if (TmpStr != NULL && TmpStr != Parent) {
			*TmpStr = L'\0'; // terminating Parent; will return L'\\' back
			FName = AllocateCopyPool(StrSize(Parent), Parent);
			*TmpStr = L'\\'; // return L'\\' back to Parent
		} else {
			// caller is doing something wrong - we'll default to L"\\"
			FName = AllocateCopyPool(StrSize(L"\\"), L"\\");
		}
	}
	
	// other cases: for now just do Parent + \ + FName
	// but check if Parent already ends with backslash
	else {
		Len = StrSize(Parent) + StrSize(FName); // has place for extra char (\\) if needed
		TmpStr = AllocateZeroPool(Len);
		StrCpyS(TmpStr, Len/sizeof(CHAR16), Parent);
		TmpStr2 = GetStrLastChar(Parent);
		if (TmpStr2 == NULL || *TmpStr2 != L'\\') {
			StrCatS(TmpStr, Len/sizeof(CHAR16), L"\\");
		}
		StrCatS(TmpStr, Len/sizeof(CHAR16), FName);
    return TmpStr;
	}
	//DBG("='%s' ", FName);
	return FName;
}



////////////////////////////////////////////
// 
// DUC_FILE_PROTOCOL
// our implementation of EFI_FILE_PROTOCOL
//

DUC_FILE_PROTOCOL* CreateOurFp(VOID);

/** EFI_FILE_PROTOCOL.Open - Opens a new file relative to the source file's location. */
EFI_STATUS
EFIAPI
DUC_FP_Open(
			IN EFI_FILE_PROTOCOL        *This,
			OUT EFI_FILE_PROTOCOL       **NewHandle,
			IN CHAR16                   *FileName,
			IN UINT64                   OpenMode,
			IN UINT64                   Attributes
			)
{
	EFI_STATUS				Status; // = EFI_DEVICE_ERROR;
	DUC_FILE_PROTOCOL		*OurFp;
	DUC_FILE_PROTOCOL		*NewFp;
	
	OurFp = DUC_FROM_FILE_PROTOCOL(This);
	PRINT("FP.%p[%s].Open('%s', %x, %x) ", This, OurFp->FName, FileName, OpenMode, Attributes);
	
	// call original implementation to get NewHandle
	Status = OurFp->OrigFp->Open(OurFp->OrigFp, NewHandle, FileName, OpenMode, Attributes);
	if (EFI_ERROR(Status)) {
		PRINT("= %r\n", Status);
		return Status;
	}
	
	// create our FP implementation
	NewFp = CreateOurFp();
	if (NewFp == NULL) {
		Status = EFI_OUT_OF_RESOURCES;
		PRINT("= %r\n", Status);
		return Status;
	}
	NewFp->FName = GetNormalizedFName(OurFp->FName, FileName);
	NewFp->OrigFp = *NewHandle;
	
	// set our implementation as a result
	*NewHandle = &(NewFp->Fp);
	
	PRINT("= EFI_SUCCESS -> FP.%p[%s]\n", *NewHandle, NewFp->FName);
	return EFI_SUCCESS;
}

/** EFI_FILE_PROTOCOL.Close - Closes a specified file handle. */
EFI_STATUS
EFIAPI
DUC_FP_Close(IN EFI_FILE_PROTOCOL  *This)
{
	EFI_STATUS				Status; // = EFI_SUCCESS;
	DUC_FILE_PROTOCOL		*OurFp;
	
	OurFp = DUC_FROM_FILE_PROTOCOL(This);
	PRINT("FP.%p[%s].Close() ", This, OurFp->FName);
	
	// close target FP
	Status = OurFp->OrigFp->Close(OurFp->OrigFp);
	OurFp->OrigFp = NULL;
	if (OurFp->FName != NULL) {
		FreePool(OurFp->FName);
		OurFp->FName = NULL;
	}
	FreePool(OurFp);
	PRINT("= %r\n", Status);
	return Status;
}

/** EFI_FILE_PROTOCOL.Delete - Close and delete the file handle. */
EFI_STATUS
EFIAPI
DUC_FP_Delete(IN EFI_FILE_PROTOCOL  *This)
{
	EFI_STATUS				Status; // = EFI_WARN_DELETE_FAILURE;
	DUC_FILE_PROTOCOL		*OurFp;
	
	OurFp = DUC_FROM_FILE_PROTOCOL(This);
	PRINT("FP.%p[%s].Delete() ", This, OurFp->FName);
	
	Status = OurFp->OrigFp->Delete(OurFp->OrigFp);
	OurFp->OrigFp = NULL;
	if (OurFp->FName != NULL) {
		FreePool(OurFp->FName);
		OurFp->FName = NULL;
	}
	FreePool(OurFp);
	PRINT("= %r\n", Status);
	return Status;
}

/** EFI_FILE_PROTOCOL.Read - Reads data from a file. */
EFI_STATUS
EFIAPI
DUC_FP_Read(
			IN EFI_FILE_PROTOCOL	*This,
			IN OUT UINTN			*BufferSize,
			OUT VOID				*Buffer
			)
{
	EFI_STATUS				Status; // = EFI_DEVICE_ERROR;
	DUC_FILE_PROTOCOL		*OurFp;
	
	OurFp = DUC_FROM_FILE_PROTOCOL(This);
	PRINT("FP.%p[%s].Read(%d, %p) ", This, OurFp->FName, *BufferSize, Buffer);
	
	Status = OurFp->OrigFp->Read(OurFp->OrigFp, BufferSize, Buffer);
	PRINT("= %r\n", Status);
	return Status;
}

/** EFI_FILE_PROTOCOL.Write - Writes data to a file. */
EFI_STATUS
EFIAPI
DUC_FP_Write(
			 IN EFI_FILE_PROTOCOL	*This,
			 IN OUT UINTN			*BufferSize,
			 IN VOID				*Buffer
			 )
{
	EFI_STATUS				Status; // = EFI_DEVICE_ERROR;
	DUC_FILE_PROTOCOL		*OurFp;
	UINTN					InBufferSize = *BufferSize;
	
	OurFp = DUC_FROM_FILE_PROTOCOL(This);
	Status = OurFp->OrigFp->Write(OurFp->OrigFp, BufferSize, Buffer);
	PRINT("FP.%p[%s].Write(%d/%d, %p) = %r\n", This, OurFp->FName, InBufferSize, *BufferSize, Buffer, Status);
	return Status;
}

/** EFI_FILE_PROTOCOL.SetPosition - Sets a file's current position. */
EFI_STATUS
EFIAPI
DUC_FP_SetPosition(
				   IN EFI_FILE_PROTOCOL	*This,
				   IN UINT64			Position
				   )
{
	EFI_STATUS				Status; // = EFI_DEVICE_ERROR;
	DUC_FILE_PROTOCOL		*OurFp;
	
	OurFp = DUC_FROM_FILE_PROTOCOL(This);
	Status = OurFp->OrigFp->SetPosition(OurFp->OrigFp, Position);
	PRINT("FP.%p[%s].SetPosition(%ld) = %r\n", This, OurFp->FName, Position, Status);
	return Status;
}

/** EFI_FILE_PROTOCOL.GetPosition - Returns a file's current position. */
EFI_STATUS
EFIAPI
DUC_FP_GetPosition(
				   IN EFI_FILE_PROTOCOL	*This,
				   IN UINT64			*Position
				   )
{
	EFI_STATUS				Status; // = EFI_DEVICE_ERROR;
	DUC_FILE_PROTOCOL		*OurFp;
	
	OurFp = DUC_FROM_FILE_PROTOCOL(This);
	Status = OurFp->OrigFp->GetPosition(OurFp->OrigFp, Position);
	PRINT("FP.%p[%s].GetPosition(%ld) = %r\n", This, OurFp->FName, *Position, Status);
	return Status;
}

/** EFI_FILE_PROTOCOL.GetInfo - Returns information about a file. */
EFI_STATUS
EFIAPI
DUC_FP_GetInfo(
			   IN EFI_FILE_PROTOCOL	*This,
			   IN EFI_GUID			*InformationType,
			   IN OUT UINTN			*BufferSize,
			   OUT VOID				*Buffer
			   )
{
	EFI_STATUS				Status; // = EFI_DEVICE_ERROR;
	DUC_FILE_PROTOCOL		*OurFp;
	
	OurFp = DUC_FROM_FILE_PROTOCOL(This);
	Status = OurFp->OrigFp->GetInfo(OurFp->OrigFp, InformationType, BufferSize, Buffer);
	PRINT("FP.%p[%s].GetInfo(%s, %d, %p) = %r\n", This, OurFp->FName, GuidStr(InformationType), *BufferSize, Buffer, Status);
	return Status;
}

/** EFI_FILE_PROTOCOL.SetInfo - Sets information about a file. */
EFI_STATUS
EFIAPI
DUC_FP_SetInfo(
			   IN EFI_FILE_PROTOCOL	*This,
			   IN EFI_GUID			*InformationType,
			   IN UINTN				BufferSize,
			   IN VOID				*Buffer
			   )
{
	EFI_STATUS				Status; // = EFI_DEVICE_ERROR;
	DUC_FILE_PROTOCOL		*OurFp;
	
	OurFp = DUC_FROM_FILE_PROTOCOL(This);
	Status = OurFp->OrigFp->SetInfo(OurFp->OrigFp, InformationType, BufferSize, Buffer);
	PRINT("FP.%p[%s].SetInfo(%s, %d, %p) = %r\n", This, OurFp->FName, GuidStr(InformationType), BufferSize, Buffer, Status);
	return Status;
}

/** EFI_FILE_PROTOCOL.Flush - Flushes all modified data associated with a file to a device. */
EFI_STATUS
EFIAPI
DUC_FP_Flush(
			 IN EFI_FILE_PROTOCOL	*This
			 )
{
	EFI_STATUS				Status; // = EFI_DEVICE_ERROR;
	DUC_FILE_PROTOCOL		*OurFp;
	
	OurFp = DUC_FROM_FILE_PROTOCOL(This);
	Status = OurFp->OrigFp->Flush(OurFp->OrigFp);
	PRINT("FP.%p[%s].Flush() = %r\n", This, OurFp->FName, Status);
	return Status;
}

/** Creates our DUC_FILE_PROTOCOL. */
DUC_FILE_PROTOCOL*
CreateOurFp(VOID)
{
	DUC_FILE_PROTOCOL		*OurFp;
	
	OurFp = AllocateZeroPool(sizeof(DUC_FILE_PROTOCOL));
	if (OurFp == NULL) {
		return NULL;
	}
	
	OurFp->Signature = DUC_FILE_PROTOCOL_SIGNATURE;
	OurFp->Fp.Revision = EFI_FILE_PROTOCOL_REVISION;
	OurFp->Fp.Open = DUC_FP_Open;
	OurFp->Fp.Close = DUC_FP_Close;
	OurFp->Fp.Delete = DUC_FP_Delete;
	OurFp->Fp.Read = DUC_FP_Read;
	OurFp->Fp.Write = DUC_FP_Write;
	OurFp->Fp.GetPosition = DUC_FP_GetPosition;
	OurFp->Fp.SetPosition = DUC_FP_SetPosition;
	OurFp->Fp.GetInfo = DUC_FP_GetInfo;
	OurFp->Fp.SetInfo = DUC_FP_SetInfo;
	OurFp->Fp.Flush = DUC_FP_Flush;
	OurFp->FName = NULL;
	OurFp->OrigFp = NULL;
	
	return OurFp;
}



////////////////////////////////////////////
//
// DUC_SIMPLE_FILE_SYSTEM_PROTOCOL
// our implementation of EFI_SIMPLE_FILE_SYSTEM_PROTOCOL
//

/**
 * EFI_SIMPLE_FILE_SYSTEM_PROTOCOL.OpenVolume implementation.
 */
EFI_STATUS
EFIAPI
DUC_FS_OpenVolume(
				   IN EFI_SIMPLE_FILE_SYSTEM_PROTOCOL	*This,
				   OUT EFI_FILE_PROTOCOL				**Root
				   )
{
	EFI_STATUS							Status;
	DUC_SIMPLE_FILE_SYSTEM_PROTOCOL		*OurFs;
	DUC_FILE_PROTOCOL					*OurFp;
	
	PRINT("FS.%p.OpenVolume() ", This);
	OurFs = DUC_FROM_SIMPLE_FILE_SYSTEM(This);
	
	// Open with orig FS
	Status = OurFs->OrigFs->OpenVolume(OurFs->OrigFs, Root);
	if (EFI_ERROR(Status)) {
		PRINT("= %r\n", Status);
		return Status;
	}
	
	// Wrap it into our implementation
	OurFp = CreateOurFp();
	if (OurFp == NULL) {
		Status = EFI_OUT_OF_RESOURCES;
		return Status;
	}
	OurFp->FName = AllocateCopyPool(StrSize(L"\\"), L"\\");
	OurFp->OrigFp = *Root;
	
	// set it as result
	*Root = &OurFp->Fp;
	PRINT("= %r -> FP.%p[%s]\n\n", Status, *Root, OurFp->FName);
	
	return Status;
} 


////////////////////////////////////////////
//
// Init functions
//


/** Installs our SFS overrides for given handle. */
EFI_STATUS
OvrFsForHandle (IN EFI_HANDLE Handle)
{
	EFI_STATUS							Status;
	EFI_SIMPLE_FILE_SYSTEM_PROTOCOL		*OrigFs;
	DUC_SIMPLE_FILE_SYSTEM_PROTOCOL		*OurFs;
	
	PRINT("Overriding Simple File System for handle %p ", Handle);
	
	// Get existing EFI_SIMPLE_FILE_SYSTEM_PROTOCOL
	Status = gBS->OpenProtocol(Handle, &gEfiSimpleFileSystemProtocolGuid, (VOID **)&OrigFs, gImageHandle, NULL, EFI_OPEN_PROTOCOL_GET_PROTOCOL);
	if (EFI_ERROR(Status)) {
		PRINT(" - OpenProtocol(gEfiSimpleFileSystemProtocolGuid): %r\n", Status);
		return Status;
	}
	
	// Check if this is already our implementation
	OurFs = BASE_CR(OrigFs, DUC_SIMPLE_FILE_SYSTEM_PROTOCOL, Fs);
	if (OurFs->Signature == DUC_SIMPLE_FILE_SYSTEM_PROTOCOL_SIGNATURE) {
		PRINT(" - Already overriden\n");
		return EFI_SUCCESS;
	}
	
	// Create our implementation
	OurFs = AllocateZeroPool(sizeof(DUC_SIMPLE_FILE_SYSTEM_PROTOCOL));
	if (OurFs == NULL) {
		Status = EFI_OUT_OF_RESOURCES;
		PRINT("-  AllocateZeroPool(DUC_SIMPLE_FILE_SYSTEM_PROTOCOL): %r\n", Status);
		return Status;
	}
	OurFs->Signature = DUC_SIMPLE_FILE_SYSTEM_PROTOCOL_SIGNATURE;
	OurFs->Fs.Revision = EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_REVISION;
	OurFs->Fs.OpenVolume = DUC_FS_OpenVolume;
	OurFs->Handle = Handle;
	OurFs->OrigFs = OrigFs;
	
	// Replace existing orig EFI_SIMPLE_FILE_SYSTEM_PROTOCOL with out implementation
	Status = gBS->ReinstallProtocolInterface(Handle, &gEfiSimpleFileSystemProtocolGuid, OrigFs, &OurFs->Fs);
	if (EFI_ERROR(Status)) {
		FreePool(OurFs);
		PRINT(" - ReinstallProtocolInterface(): %r\n", Status);
		return Status;
	}
	
	PRINT(" - installed\n");
	return EFI_SUCCESS;
}

/** SFS protocol notify registration. */
VOID          *gEfiSFSProtocolNotifyReg;

/** SFS protocol notify event. */
EFI_EVENT     gEfiSFSProtocolEvent;

/** SFS protocol notify event handler. Get's called when new SFS appears in the system. */
VOID
EFIAPI
OnSimpleFileSystemInstall (
						   IN EFI_EVENT        Event,
						   IN VOID             *Context
						   )
{
	EFI_STATUS				Status;
	UINTN					NoHandles = 0;
	EFI_HANDLE				*Handle;
	UINTN					Index;
	
	//
	// Locate Handles with our gEfiSFSProtocolNotifyReg
	//
	Status = gBS->LocateHandleBuffer (
									  ByRegisterNotify,
									  &gEfiSimpleFileSystemProtocolGuid,
									  gEfiSFSProtocolNotifyReg,
									  &NoHandles,
									  &Handle
									  );
	if (EFI_ERROR(Status)) {
		PRINT("Error overriding Simple File Systems: %r\n", Status);
	}
	
	for (Index = 0; Index < NoHandles; Index++) {
		OvrFsForHandle(Handle[Index]);
	}
	
	if (NoHandles != 0) {
		FreePool(Handle);
	}
}

/** Installs our Simple File System overrides. */
EFI_STATUS EFIAPI
OvrFs(VOID)
{
	//
	// Install SFS event notification
	//
	gEfiSFSProtocolEvent = EfiCreateProtocolNotifyEvent (
														&gEfiSimpleFileSystemProtocolGuid,
														TPL_CALLBACK,
														OnSimpleFileSystemInstall,
														NULL,
														&gEfiSFSProtocolNotifyReg
														);
	
	return EFI_SUCCESS;
}

