UEFI的文件系统栈如图所示,在UEFI中都含有一个ESP(EFI System Patition)分区,这个分区上存放了启动文件。UEFI就需要有读写文件的功能。文件的读写管理通过文件系统来完成,要支持读写文件,UEFI必须首先能操作ESP上的文件系统。UEFI内置了EFI_SIMPLE_FILE_SYSTEM_PROTOCOL(简称FileSystemIo)用于操作FAT文件系统
首先可以通过两种方式获取EFI_SHELL_PROTOCOL协议接口,EFI_SHELL_PROTOCOL是UEFI规范中定义的一个协议,这个协议允许UEFI应用或者驱动程序与UEFI Shell交互,执行命令、运行其他应用程序、访问文件系统等。
步骤1:检查协议是否可用
使用OpenProtocol()打开协议并获取接口指针,其代码原型为
/**
查询一个句柄看它是否支持指定的协议。如果Handle支持该协议,将代表调用者打开这个协议
**/
typedef
EFI_STATUS
(EFIAPI *EFI_OPEN_PROTOCOL)(
IN EFI_HANDLE Handle, //正在打开协议接口的指针的句柄
IN EFI_GUID *Protocol, //协议的唯一标识符,指协议的GUID
OUT VOID **Interface OPTIONAL, //输出参数,返回指向相应协议接口的指针的地址
IN EFI_HANDLE AgentHandle, //打开由protocol和interface指定的协议接口的agent的句柄。agent通常是执行某操作的实体,例如UEFI应用或驱动程序,接口是指协议的具体实现
IN EFI_HANDLE ControllerHandle, //如果打开协议的agent是遵循UEFI驱动程序模型的驱动程序,则此参数是需要协议接口的控制器句柄,如果不遵循,则此参数是可选的,并且可能为NULL
IN UINT32 Attributes //由句柄和协议指定的协议接口的打开模式
);
其中协议接口的打开方式有如下几种:
#define EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL 0x00000001
#define EFI_OPEN_PROTOCOL_GET_PROTOCOL 0x00000002
#define EFI_OPEN_PROTOCOL_TEST_PROTOCOL 0x00000004
#define EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER 0x00000008
#define EFI_OPEN_PROTOCOL_BY_DRIVER 0x00000010
#define EFI_OPEN_PROTOCOL_EXCLUSIVE 0x00000020
/**
在UEFI环境中,OpenProtocol 函数用于打开一个协议接口,以便UEFI应用程序或驱动程序可以与该协议进行交互。以下是一些用于 OpenProtocol 函数的标志(Flags),它们定义了打开协议的不同方式和权限。
EFI_OPEN_PROTOCOL_BY_HANDLE_PROTOCOL:
值:0x00000001
描述:这个标志指示 OpenProtocol 应该通过一个句柄和一个协议GUID来打开协议。这是最常用的方式,用于打开由特定句柄支持的协议。
EFI_OPEN_PROTOCOL_GET_PROTOCOL:
值:0x00000002
描述:这个标志用于获取协议的接口指针,但不实际打开协议。它通常用于检查协议是否可用。
EFI_OPEN_PROTOCOL_TEST_PROTOCOL:
值:0x00000004
描述:这个标志用于测试一个句柄是否支持特定的协议,但不返回协议的接口指针。它用于确认协议的存在。
EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER:
值:0x00000008
描述:这个标志用于通过一个控制器的子控制器来打开协议。它通常用于在控制器和其子控制器之间打开协议。
EFI_OPEN_PROTOCOL_BY_DRIVER:
值:0x00000010
描述:这个标志用于指示 OpenProtocol 应该通过一个驱动程序句柄来打开协议。这通常用于驱动程序需要打开它们管理的设备的协议。
EFI_OPEN_PROTOCOL_EXCLUSIVE:
值:0x00000020
描述:这个标志用于指示协议应该以独占方式打开。这意味着一旦协议被一个代理以独占方式打开,其他代理就不能再打开它,直到它被关闭。
**/
步骤二2:获取相应的接口实例
使用LocateProtocol()打开协议并获取接口指针,其代码原型为
/**
Returns the first protocol instance that matches the given protocol.
返回执行协议的第一个协议实例
**/
typedef
EFI_STATUS
(EFIAPI *EFI_LOCATE_PROTOCOL)(
IN EFI_GUID *Protocol, //查找的协议GUID
IN VOID *Registration OPTIONAL, //从RegisterProtocolNoyify()返回的可选注册密钥
OUT VOID **Interface //输出参数,返回一个指针,指向与Protocol和Register匹配的第一个接口的指针。
);
在EFI_SHELL_PROTOCOL中有很多对操作,EFI_SHELL_OPEN_FILE_BY_NAME是其中一种,目的是根据文件名打开一个文件或目录,代码原型为
/**
按文件名打开文件或目录,此函数在指定的OpenMode中打开指定的文件,并返回文件句柄。
如果文件名以 '>v' 开头,则返回的文件句柄引用具有指定名称的 shell 环境变量。如果 shell 环境变量存在,是非易失性的,并且 OpenMode 指示EFI_FILE_MODE_WRITE,则返回 EFI_INVALID_PARAMETER。
如果文件名为 '>i',则返回的文件句柄引用标准输入。如果 OpenMode 指示 EFI_FILE_MODE_WRITE,则返回 EFI_INVALID_PARAMETER。
如果文件名为 '>o',则返回的文件句柄引用标准输出。如果 OpenMode 指示 EFI_FILE_MODE_READ,则返回 EFI_INVALID_PARAMETER。
如果文件名为“>e”,则返回的文件句柄引用标准错误。如果 OpenMode 指示 EFI_FILE_MODE_READ,则返回 EFI_INVALID_PARAMETER。
如果文件名为 'NUL',则返回的文件句柄引用标准 NUL 文件。如果 OpenMode 指示 EFI_FILE_MODE_READ,则返回 EFI_INVALID_PARAMETER。
如果返回 EFI_SUCCESS,则 FileHandle 是打开文件的句柄,否则,FileHandle 为 NULL。
**/
typedef
EFI_STATUS
(EFIAPI *EFI_SHELL_OPEN_FILE_BY_NAME)(
IN CONST CHAR16 *FileName, //指向以 NULL 结尾的 UCS-2 编码文件名。
OUT SHELL_FILE_HANDLE *FileHandle, //输出变量,指向文件句柄
IN UINT64 OpenMode //打开方式,有读、写、执行
);
完整代码如下:
#include <uefi.h>
#include <Library/UefiLib.h>
#include <Library/ShellCEntryLib.h>
#include <Library/UefiApplicationEntryPoint.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>
#include <Library/UefiRuntimeLib.h>
#include <Protocol/GraphicsOutput.h>
#include <Protocol/SimplePointer.h>
#include <Protocol/SimpleTextInEx.h>
#include <Protocol/SimpleFileSystem.h>
#include <Library/DebugLib.h>
#include <Guid/GlobalVariable.h>
#include <Guid/ShellLibHiiGuid.h>
#include <Protocol/Shell.h>
#include <Protocol/ShellParameters.h>
#include <Protocol/DevicePath.h>
#include <Protocol/LoadedImage.h>
#include <Protocol/UnicodeCollation.h>
#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/PcdLib.h>
#include <Library/ShellCommandLib.h>
#include <Library/ShellLib.h>
#include <Library/UefiLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>
#include <Library/HiiLib.h>
#include <Library/SortLib.h>
#include <Library/FileHandleLib.h>
#include <Protocol/EfiShell.h>
#include <guid/FileInfo.h>
EFI_STATUS
EFIAPI
BZero (
OUT CHAR16 *Destination,
IN UINTN Length
)
{
UINT32 i ;
CHAR8 * ptr = (CHAR8 * )Destination;
for (i = 0; i < 2*Length ; i ++){
ptr [i] = 0;
}
return EFI_SUCCESS;
}
EFI_STATUS OpenShellProtocol( EFI_SHELL_PROTOCOL **gEfiShellProtocol )
{
EFI_STATUS Status;
Status = gBS->OpenProtocol(
gImageHandle, //gImageHandle是一个常用的全局变量,用于存储当前正在执行的UEFI应用或驱动程序的句柄。
&gEfiShellProtocolGuid, //协议的GUID
(VOID **)gEfiShellProtocol, //输出参数,返回指向相应协议接口的指针的地址
gImageHandle, //打开协议接口的句柄
NULL,
EFI_OPEN_PROTOCOL_GET_PROTOCOL //打开模式,该方式获取协议的接口指针,但是不实际打开协议,通常用于检查协议是否可用
);
if (EFI_ERROR(Status)) {
//
// Search for the shell protocol
//
Status = gBS->LocateProtocol(
&gEfiShellProtocolGuid,
NULL,
(VOID **)gEfiShellProtocol
);
if (EFI_ERROR(Status)) {
gEfiShellProtocol = NULL;
}
}
return Status;
}
VOID
Ascii2UnicodeString (
CHAR8 *String,
CHAR16 *UniString
)
{
while (*String != '\0') {
*(UniString++) = (CHAR16) *(String++);
}
//
// End the UniString with a NULL.
//
*UniString = '\0';
}
INTN
EFIAPI
ShellAppMain( UINTN Argc, CHAR16 **Argv) //Argc是一个UINTN类型的参数,表示传递给Shell应用程序的命令行参数的数量。CHAR16 **Argv是一个指针数组,包含了实际的命令行参数字符串
{
CHAR16 * OldLogFileName = NULL;
CHAR16 *LineBuff = NULL;
CHAR16 NewFileName[128] = {0};
CHAR16 * ArrayBuffer = NULL;
EFI_STATUS Status ;
SHELL_FILE_HANDLE FileHandle;
UINTN Index = 0;
UINTN WbufSize = 0;
UINTN FileSize = 0;
UINTN i = 0;
UINTN StartIndex = 0;
CHAR8 *Ptr = NULL;
if (Argc <= 1){
Print(L"Please input file name!\n");
return (-1);
}
OldLogFileName = Argv[1];
Print(L"The Old file name is %s!\n",OldLogFileName);
EFI_SHELL_PROTOCOL *gEfiShellProtocol;
Status = OpenShellProtocol(&gEfiShellProtocol);
Status = gEfiShellProtocol->OpenFileByName((CONST CHAR16*)OldLogFileName, &FileHandle, EFI_FILE_MODE_READ); //以读的方式打开OldLogFileName文件,并获取该文件的句柄FileHandle
if (EFI_ERROR(Status)){
Print(L"Please Input Valid Filename!\n");
return (-1);
}
StrnCpyS(NewFileName,128,OldLogFileName,StrLen(OldLogFileName)-4);将OldLogFileName的长度-4个字节复制到大小为128的NewFileName中
StrCatS(NewFileName,128,L"_New.txt"); //strCatS可以将两个字符串拼接在一起,将_New.txt放到NewFileName的后面
Print(L"New FileName is %s\n",NewFileName);
//删除同名的文件
gEfiShellProtocol->DeleteFileByName(NewFileName);
//获取文件实际大小
Status = gEfiShellProtocol->GetFileSize(FileHandle,&FileSize);//获取文件的大小,并储存在FileSize中
if (EFI_ERROR (Status)) {
gEfiShellProtocol->CloseFile (FileHandle); //关闭文件句柄
return Status;
}
Print (L"File FileSize is %d!\n",FileSize);
if (FileSize < 0){
Print (L"File cotent is empty!\n");
return (-1);
}
FileSize += 1;
//根据文件大小申请对应大小的内存
Status = gBS -> AllocatePool (EfiReservedMemoryType, FileSize , &ArrayBuffer);
Status = gBS -> AllocatePool (EfiReservedMemoryType, FileSize , &LineBuff);
BZero(ArrayBuffer,FileSize);
BZero(LineBuff,FileSize);
Status = gEfiShellProtocol->ReadFile(FileHandle, &FileSize ,ArrayBuffer);
if (EFI_ERROR(Status)){
Print(L"Read Filename Error!\n");
return (-1);
}
//创建新的文件句柄
Status = gEfiShellProtocol->CreateFile((CONST CHAR16*)NewFileName,0 ,&FileHandle);
if (EFI_ERROR(Status)){
Print(L"Create Filename %s Fail!\n",NewFileName);
return (-1);
}
//读取的文件内容写入新建文件
WbufSize = FileSize;
Status = gEfiShellProtocol->WriteFile(FileHandle,&WbufSize,ArrayBuffer);
//关闭文件句柄
Status = gEfiShellProtocol->CloseFile(FileHandle);
Ptr = (CHAR8 * )ArrayBuffer;
for (i = 0 ; i < FileSize ; i ++ ){
if (Ptr[i] == '\n'){
Ptr[i] = '\0';
Ascii2UnicodeString(Ptr + StartIndex, LineBuff);
StartIndex = i + 1;
Index += 1;
//按行输出
Print(L"Line %d: %S!\n",Index,LineBuff);
BZero(LineBuff,FileSize);
}
}
return (0);
}