]> git.proxmox.com Git - mirror_edk2.git/blobdiff - OvmfPkg/Library/NvVarsFileLib/FsAccess.c
Implement NvVarsFileLib to save and restore non-volatile variables using a file.
[mirror_edk2.git] / OvmfPkg / Library / NvVarsFileLib / FsAccess.c
diff --git a/OvmfPkg/Library/NvVarsFileLib/FsAccess.c b/OvmfPkg/Library/NvVarsFileLib/FsAccess.c
new file mode 100644 (file)
index 0000000..cbae15a
--- /dev/null
@@ -0,0 +1,540 @@
+/** @file
+  File System Access for NvVarsFileLib
+
+  Copyright (c) 2004 - 2009, Intel Corporation. <BR>
+  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.
+
+**/
+
+#include "NvVarsFileLib.h"
+
+#include <Library/BaseMemoryLib.h>
+#include <Library/DebugLib.h>
+#include <Library/MemoryAllocationLib.h>
+
+
+/**
+  Open the NvVars file for reading or writing
+
+  @param[in]  FsHandle - Handle for a gEfiSimpleFileSystemProtocolGuid instance
+  @param[in]  ReadingFile - TRUE: open the file for reading.  FALSE: writing
+  @param[out] NvVarsFile - If EFI_SUCCESS is returned, then this is updated
+                           with the opened NvVars file.
+
+  @return     EFI_SUCCESS if the file was opened
+
+**/
+EFI_STATUS
+GetNvVarsFile (
+  IN  EFI_HANDLE            FsHandle,
+  IN  BOOLEAN               ReadingFile,
+  OUT EFI_FILE_HANDLE       *NvVarsFile
+  )
+{
+  EFI_STATUS                            Status;
+  EFI_SIMPLE_FILE_SYSTEM_PROTOCOL       *Fs;
+  EFI_FILE_HANDLE                       Root;
+
+  //
+  // Get the FileSystem protocol on that handle
+  //
+  Status = gBS->HandleProtocol (
+                  FsHandle,
+                  &gEfiSimpleFileSystemProtocolGuid,
+                  (VOID **)&Fs
+                  );
+  if (EFI_ERROR (Status)) {
+    return Status;
+  }
+
+  //
+  // Get the volume (the root directory)
+  //
+  Status = Fs->OpenVolume (Fs, &Root);
+  if (EFI_ERROR (Status)) {
+    return Status;
+  }
+
+  //
+  // Attempt to open the NvVars file in the root directory
+  //
+  Status = Root->Open (
+                   Root,
+                   NvVarsFile,
+                   L"NvVars",
+                   ReadingFile ?
+                     EFI_FILE_MODE_READ :
+                     (
+                       EFI_FILE_MODE_CREATE |
+                       EFI_FILE_MODE_READ |
+                       EFI_FILE_MODE_WRITE
+                     ),
+                   0
+                   );
+  if (EFI_ERROR (Status)) {
+    return Status;
+  }
+
+  return Status;
+}
+
+
+/**
+  Open the NvVars file for reading or writing
+
+  @param[in]  File - The file to inspect
+  @param[out] Exists - Returns whether the file exists
+  @param[out] Size - Returns the size of the file
+                     (0 if the file does not exist)
+
+**/
+VOID
+NvVarsFileReadCheckup (
+  IN  EFI_FILE_HANDLE        File,
+  OUT BOOLEAN                *Exists,
+  OUT UINTN                  *Size
+  )
+{
+  EFI_FILE_INFO               *FileInfo;
+
+  *Exists = FALSE;
+  *Size = 0;
+
+  FileInfo = FileHandleGetInfo (File);
+  if (FileInfo == NULL) {
+    return;
+  }
+
+  if ((FileInfo->Attribute & EFI_FILE_DIRECTORY) != 0) {
+    FreePool (FileInfo);
+    return;
+  }
+
+  *Exists = TRUE;
+  *Size = FileInfo->FileSize;
+
+  FreePool (FileInfo);
+}
+
+
+/**
+  Open the NvVars file for reading or writing
+
+  @param[in]  File - The file to inspect
+  @param[out] Exists - Returns whether the file exists
+  @param[out] Size - Returns the size of the file
+                     (0 if the file does not exist)
+
+**/
+EFI_STATUS
+FileHandleEmpty (
+  IN  EFI_FILE_HANDLE        File
+  )
+{
+  EFI_STATUS                  Status;
+  EFI_FILE_INFO               *FileInfo;
+
+  //
+  // Retrieve the FileInfo structure
+  //
+  FileInfo = FileHandleGetInfo (File);
+  if (FileInfo == NULL) {
+    return EFI_INVALID_PARAMETER;
+  }
+
+  //
+  // If the path is a directory, then return an error
+  //
+  if ((FileInfo->Attribute & EFI_FILE_DIRECTORY) != 0) {
+    FreePool (FileInfo);
+    return EFI_INVALID_PARAMETER;
+  }
+
+  //
+  // If the file size is already 0, then it is empty, so
+  // we can return success.
+  //
+  if (FileInfo->FileSize == 0) {
+    FreePool (FileInfo);
+    return EFI_SUCCESS;
+  }
+
+  //
+  // Set the file size to 0.
+  //
+  FileInfo->FileSize = 0;
+  Status = FileHandleSetInfo (File, FileInfo);
+
+  FreePool (FileInfo);
+
+  return Status;
+}
+
+
+/**
+  Reads a file to a newly allocated buffer
+
+  @param[in]  File - The file to read
+  @param[in]  ReadSize - The size of data to read from the file
+
+  @return     Pointer to buffer allocated to hold the file
+              contents.  NULL if an error occured.
+
+**/
+VOID*
+FileHandleReadToNewBuffer (
+  IN EFI_FILE_HANDLE            FileHandle,
+  IN UINTN                      ReadSize
+  )
+{
+  EFI_STATUS                  Status;
+  UINTN                       ActualReadSize;
+  VOID                        *FileContents;
+
+  ActualReadSize = ReadSize;
+  FileContents = AllocatePool (ReadSize);
+  if (FileContents != NULL) {
+    Status = FileHandleRead (
+               FileHandle,
+               &ReadSize,
+               FileContents
+               );
+    if (EFI_ERROR (Status) || (ActualReadSize != ReadSize)) {
+      FreePool (FileContents);
+      return NULL;
+    }
+  }
+
+  return FileContents;
+}
+
+
+/**
+  Reads the contents of the NvVars file on the file system
+
+  @param[in]  FsHandle - Handle for a gEfiSimpleFileSystemProtocolGuid instance
+
+  @return     EFI_STATUS based on the success or failure of the file read
+
+**/
+EFI_STATUS
+ReadNvVarsFile (
+  IN  EFI_HANDLE            FsHandle
+  )
+{
+  EFI_STATUS                  Status;
+  EFI_FILE_HANDLE             File;
+  UINTN                       FileSize;
+  BOOLEAN                     FileExists;
+  VOID                        *FileContents;
+
+  Status = GetNvVarsFile (FsHandle, TRUE, &File);
+  if (EFI_ERROR (Status)) {
+    DEBUG ((EFI_D_INFO, "FsAccess.c: Could not open NV Variables file on this file system\n"));
+    return Status;
+  }
+
+  NvVarsFileReadCheckup (File, &FileExists, &FileSize);
+  if (FileSize == 0) {
+    FileHandleClose (File);
+    return EFI_UNSUPPORTED;
+  }
+
+  FileContents = FileHandleReadToNewBuffer (File, FileSize);
+  if (FileContents == NULL) {
+    FileHandleClose (File);
+    return EFI_UNSUPPORTED;
+  }
+
+  DEBUG ((
+    EFI_D_INFO,
+    "FsAccess.c: Read %d bytes from NV Variables file\n",
+    FileSize
+    ));
+
+  Status = SetVariablesFromBuffer (FileContents, FileSize);
+
+  FreePool (FileContents);
+  FileHandleClose (File);
+
+  return Status;
+}
+
+
+/**
+  Loads the non-volatile variables from the NvVars file on the
+  given file system.
+
+  @param[in]  FsHandle - Handle for a gEfiSimpleFileSystemProtocolGuid instance
+
+  @return     EFI_STATUS based on the success or failure of load operation
+
+**/
+EFI_STATUS
+LoadNvVarsFromFs (
+  EFI_HANDLE                            FsHandle
+  )
+{
+  EFI_STATUS                     Status;
+  BOOLEAN                        VarData;
+  UINTN                          Size;
+
+  DEBUG ((EFI_D_INFO, "FsAccess.c: LoadNvVarsFromFs\n"));
+
+  //
+  // We write a variable to indicate we've already loaded the
+  // variable data.  If it is found, we skip the loading.
+  //
+  // This is relevent if the non-volatile variable have been
+  // able to survive a reboot operation.  In that case, we don't
+  // want to re-load the file as it would overwrite newer changes
+  // made to the variables.
+  //
+  Size = sizeof (VarData);
+  VarData = TRUE;
+  Status = gRT->GetVariable (
+                  L"NvVars",
+                  &gEfiSimpleFileSystemProtocolGuid,
+                  NULL,
+                  &Size,
+                  (VOID*) &VarData
+                  );
+  if (Status == EFI_SUCCESS) {
+    DEBUG ((EFI_D_INFO, "NV Variables were already loaded\n"));
+    return EFI_ALREADY_STARTED;
+  }
+
+  //
+  // Attempt to restore the variables from the NvVars file.
+  //
+  Status = ReadNvVarsFile (FsHandle);
+  if (EFI_ERROR (Status)) {
+    DEBUG ((EFI_D_INFO, "Error while restoring NV variable data\n"));
+    return Status;
+  }
+
+  //
+  // Write a variable to indicate we've already loaded the
+  // variable data.  If it is found, we skip the loading on
+  // subsequent attempts.
+  //
+  Size = sizeof (VarData);
+  VarData = TRUE;
+  gRT->SetVariable (
+         L"NvVars",
+         &gEfiSimpleFileSystemProtocolGuid,
+         EFI_VARIABLE_NON_VOLATILE |
+           EFI_VARIABLE_BOOTSERVICE_ACCESS |
+           EFI_VARIABLE_RUNTIME_ACCESS,
+         Size,
+         (VOID*) &VarData
+         );
+
+  DEBUG ((
+    EFI_D_INFO,
+    "FsAccess.c: Read NV Variables file (size=%d)\n",
+    Size
+    ));
+
+  return Status;
+}
+
+
+/**
+  Saves the non-volatile variables into the NvVars file on the
+  given file system.
+
+  @param[in]  FsHandle - Handle for a gEfiSimpleFileSystemProtocolGuid instance
+
+  @return     EFI_STATUS based on the success or failure of load operation
+
+**/
+EFI_STATUS
+SaveNvVarsToFs (
+  EFI_HANDLE                            FsHandle
+  )
+{
+  EFI_STATUS                  Status;
+  EFI_FILE_HANDLE             File;
+  UINTN                       VariableNameBufferSize;
+  UINTN                       VariableNameSize;
+  CHAR16                      *VariableName;
+  EFI_GUID                    VendorGuid;
+  UINTN                       VariableDataBufferSize;
+  UINTN                       VariableDataSize;
+  VOID                        *VariableData;
+  UINT32                      VariableAttributes;
+  VOID                        *NewBuffer;
+
+  //
+  // Open the NvVars file for writing.
+  //
+  Status = GetNvVarsFile (FsHandle, FALSE, &File);
+  if (EFI_ERROR (Status)) {
+    DEBUG ((EFI_D_INFO, "FsAccess.c: Unable to open file to saved NV Variables\n"));
+    return Status;
+  }
+
+  //
+  // Empty the starting file contents.
+  //
+  Status = FileHandleEmpty (File);
+  if (EFI_ERROR (Status)) {
+    FileHandleClose (File);
+    return Status;
+  }
+
+  //
+  // Initialize the variable name and data buffer variables.
+  //
+  VariableNameBufferSize = sizeof (CHAR16);
+  VariableName = AllocateZeroPool (VariableNameBufferSize);
+
+  VariableDataBufferSize = 0;
+  VariableData = NULL;
+
+  for (;;) {
+    //
+    // Get the next variable name and guid
+    //
+    VariableNameSize = VariableNameBufferSize;
+    Status = gRT->GetNextVariableName (
+                    &VariableNameSize,
+                    VariableName,
+                    &VendorGuid
+                    );
+    if (Status == EFI_BUFFER_TOO_SMALL) {
+      //
+      // The currently allocated VariableName buffer is too small,
+      // so we allocate a larger buffer, and copy the old buffer
+      // to it.
+      //
+      NewBuffer = AllocatePool (VariableNameSize);
+      if (NewBuffer == NULL) {
+        Status = EFI_OUT_OF_RESOURCES;
+        break;
+      }
+      CopyMem (NewBuffer, VariableName, VariableNameBufferSize);
+      if (VariableName != NULL) {
+        FreePool (VariableName);
+      }
+      VariableName = NewBuffer;
+      VariableNameBufferSize = VariableNameSize;
+
+      //
+      // Try to get the next variable name again with the larger buffer.
+      //
+      Status = gRT->GetNextVariableName (
+                      &VariableNameSize,
+                      VariableName,
+                      &VendorGuid
+                      );
+    }
+
+    if (EFI_ERROR (Status)) {
+      if (Status == EFI_NOT_FOUND) {
+        Status = EFI_SUCCESS;
+      }
+      break;
+    }
+
+    //
+    // Get the variable data and attributes
+    //
+    VariableDataSize = VariableDataBufferSize;
+    Status = gRT->GetVariable (
+                    VariableName,
+                    &VendorGuid,
+                    &VariableAttributes,
+                    &VariableDataSize,
+                    VariableData
+                    );
+    if (Status == EFI_BUFFER_TOO_SMALL) {
+      //
+      // The currently allocated VariableData buffer is too small,
+      // so we allocate a larger buffer.
+      //
+      if (VariableDataBufferSize != 0) {
+        FreePool (VariableData);
+        VariableData = NULL;
+        VariableDataBufferSize = 0;
+      }
+      VariableData = AllocatePool (VariableDataSize);
+      if (VariableData == NULL) {
+        Status = EFI_OUT_OF_RESOURCES;
+        break;
+      }
+      VariableDataBufferSize = VariableDataSize;
+
+      //
+      // Try to read the variable again with the larger buffer.
+      //
+      Status = gRT->GetVariable (
+                      VariableName,
+                      &VendorGuid,
+                      &VariableAttributes,
+                      &VariableDataSize,
+                      VariableData
+                      );
+    }
+    if (EFI_ERROR (Status)) {
+      break;
+    }
+
+    //
+    // Skip volatile variables.  We only preserve non-volatile variables.
+    //
+    if ((VariableAttributes & EFI_VARIABLE_NON_VOLATILE) == 0) {
+      continue;
+    }
+
+    DEBUG ((
+      EFI_D_INFO,
+      "Saving variable %g:%s to file\n",
+      &VendorGuid,
+      VariableName
+      ));
+
+    //
+    // Write the variable information out to the file
+    //
+    Status = PackVariableIntoFile (
+               File,
+               VariableName,
+               VariableNameSize,
+               &VendorGuid,
+               VariableAttributes,
+               VariableData,
+               VariableDataSize
+               );
+    if (EFI_ERROR (Status)) {
+      break;
+    }
+
+  }
+
+  if (VariableName != NULL) {
+    FreePool (VariableName);
+  }
+
+  if (VariableData != NULL) {
+    FreePool (VariableData);
+  }
+
+  FileHandleClose (File);
+
+  if (!EFI_ERROR (Status)) {
+    DEBUG ((EFI_D_INFO, "Saved NV Variables to NvVars file\n"));
+  }
+
+  return Status;
+
+}
+
+