读取文本文件(cat命令)

在获取到文件列表之后,接下来我们实现读取文件内容的功能。这里我们在Shell中加入一个简易的cat命令。本节示例代码的目录为cat (日文版为sample5_3_cat)。

由于我们的主要目标是学习如何调用UEFI固件接口来读取文件,这里为了简化处理,我们要实现的cat命令所读取的文件是固定的。换句话说,这个cat命令不接受文件名作为参数。这里我们要读取的文件名为"abc"。

之前介绍了对于目录,调用EFI_FILE_PROTOCOL中的Read函数(标准文档"12.5 EFI File Protocol(P.504)")是读取目录中的一项文件/目录名。对于文件,调用这个函数则是读取其中的内容。

要获取某个文件的EFI_FILE_PROTOCOL,我们需要在它所在目录的EFI_FILE_PROTOCOL中调用Open函数(标准文档"12.5 EFI File Protocol(P.499)")来打开这个文件。代码6.12展示了Open函数的定义。

unsigned long long (*Open)(struct EFI_FILE_PROTOCOL *This,
                           struct EFI_FILE_PROTOCOL **NewHandle,
                           unsigned short *FileName,
                           unsigned long long OpenMode,
                           unsigned long long Attributes);

代码6.12: Open函数的定义

这个函数参数的含义如下:

  • struct EFI_FILE_PROTOCOL **NewHandle: 被打开的文件的EFI_FILE_PROTOCOL
  • unsigned short *FileName: 文件名
  • unsigned long long OpenMode: 文件打开的模式
  • unsigned long long Attributes: 新建文件时的属性。本书不使用。

OpenMode中的模式位的定义如代码6.13所示。

#define EFI_FILE_MODE_READ      0x0000000000000001
#define EFI_FILE_MODE_WRITE     0x0000000000000002
#define EFI_FILE_MODE_CREATE    0x8000000000000000

代码6.13: OpenMode中的模式位

此外,OpenMode中只容许下面这些组合:

  • READ (只读)
  • READ | WRITE (读写)
  • READ | WRITE | CREATE (读写,如果文件不存在,那么创建它)

了解了上面的内容后,可以看出,打开并读取指定的文件"abc"这个动作可以拆分成下面三步:

  1. 调用EFI_SIMPLE_FILE_SYSTEM_PROTOCOLOpenVolume函数打开卷(获取根目录的EFI_FILE_PROTOCOL
  2. 在根目录的EFI_FILE_PROTOCOL中调用Open函数打开文件"abc"(获取文件"abc"的EFI_FILE_PROTOCOL
  3. 在文件"abc"的EFI_FILE_PROTOCOL中调用Read函数读取文件(读取文件"abc"的内容)

代码6.14展示了实现这个简易的cat命令的代码。在上面三步的基础上,这里最后调用了Close函数来释放我们打开过的文件和目录。

/* ...省略... */

/* 新增(此处开始) */
void cat(unsigned short *file_name)
{
    unsigned long long status;
    struct EFI_FILE_PROTOCOL *root;
    struct EFI_FILE_PROTOCOL *file;
    unsigned long long buf_size = MAX_FILE_BUF;
    unsigned short file_buf[MAX_FILE_BUF / 2];

    status = SFSP->OpenVolume(SFSP, &root);
    assert(status, L"SFSP->OpenVolume");

    status = root->Open(root, &file, file_name, EFI_FILE_MODE_READ, 0);
    assert(status, L"root->Open");

    status = file->Read(file, &buf_size, (void *)file_buf);
    assert(status, L"file->Read");

    puts(file_buf);

    file->Close(file);
    root->Close(root);
}
/* 新增(此处结束) */

void shell(void)
{
    unsigned short com[MAX_COMMAND_LEN];
    struct RECT r = {10, 10, 100, 200};

    while (TRUE) {
        puts(L"poiOS> ");
        if (gets(com, MAX_COMMAND_LEN) <= 0)
            continue;

        if (!strcmp(L"hello", com))
            puts(L"Hello UEFI!\r\n");
        /* ...省略... */
        else if (!strcmp(L"cat", com))  /* 新增 */
            cat(L"abc");                /* 新增 */
        else
            puts(L"Command not found.\r\n");
    }
}

代码6.14: cat/shell.c

此外,我们可以使用iconvunix2dos命令来文本文件转换为UEFI可识别的Unicode编码。1

$ unix2dos < input.txt | iconv -f UTF-8 -t UCS-2LE > output.txt

上述代码执行时的样子如图6.3所示。

cat命令运行时的样子

图6.3: cat命令运行时的样子

1

译者注:原文介绍的是nkf命令,命令为nkf -w16L0 orig.txt > unicode.txt。这里用更常见的iconv命令替代。