列出目录下文件(ls命令)

我们先来实现一个ls命令来列出根目录下所有文件。本节示例代码的目录为ls (日文版为sample5_1_ls)。

首先,在efi.cefi_init函数中调用LocateProtocol函数来加载EFI_SIMPLE_FILE_SYSTEM_PROTOCOL(代码6.3)。

struct EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *SFSP; /* 新增 */ void efi_init(struct EFI_SYSTEM_TABLE *SystemTable) { /* ...省略... */ /* 新增(此处开始) */ struct EFI_GUID sfsp_guid = {0x0964e5b22, 0x6459, 0x11d2, \ {0x8e, 0x39, 0x00, 0xa0, \ 0xc9, 0x69, 0x72, 0x3b}}; /* 新增(此处结束) */ /* ...省略... */ /* 新增 */ ST->BootServices->LocateProtocol(&sfsp_guid, NULL, (void **)&SFSP); }

代码6.3: ls/efi.c

和之前一样,这里使用了全局变量SFSP来存放这个协议。接下来,我们通过调用SFSP->OpenVolume函数打开根目录。示例代码如代码6.4所示。

struct EFI_FILE_PROTOCOL *root; SFSP->OpenVolume(SFSP, &root);

代码6.4: 调用OpenVolume函数的例子

对于目录的EFI_FILE_PROTOCOL,调用Read函数将会得到一个目录中的文件/目录名。代码6.5展示了Read函数的定义。

unsigned long long (*Read)(struct EFI_FILE_PROTOCOL *This, unsigned long long *BufferSize, void *Buffer);

代码6.5: Read函数的定义

其参数的含义如下:

  • unsigned long long *BufferSize: 指向表示Buffer的大小的变量的指针。操作完成后,变量的值将会被设为读取到的内容的大小。对于目录,当所有该目录下的文件/目录名已被读取时,该值将会被设为0。
  • void *Buffer: 指向存放读取内容的缓冲区的指针。对于目录,每次读取会在其中放入一个文件/目录名。

在完成对文件/目录的操作之后,应当调用EFI_FILE_PROTOCOL中的Close函数来释放它。该函数的定义如代码6.6所示。

unsigned long long (*Close)(struct EFI_FILE_PROTOCOL *This);

代码6.6: Close函数的定义

在上面的内容的基础上,我们来实现一个列出启动盘根目录下的文件和目录的命令。

首先,我们建立一个存储文件信息的结构体数组struct FILE file_list[]。为了简化处理,这里我们的文件信息只有文件名一项。虽然这些代码并不长,我们仍将它们放在单独的文件file.hfile.c中,如代码6.7和6.8所示。

#ifndef _FILE_H_ #define _FILE_H_ #include "graphics.h" #define MAX_FILE_NAME_LEN 4 #define MAX_FILE_NUM 10 #define MAX_FILE_BUF 1024 struct FILE { unsigned short name[MAX_FILE_NAME_LEN]; }; extern struct FILE file_list[MAX_FILE_NUM]; #endif

代码6.7: ls/file.h

#include "file.h" struct FILE file_list[MAX_FILE_NUM];

代码6.8: ls/file.c

代码6.9展示了在Shell中加入ls命令的代码。

/* ...省略... */ /* 新增(此处开始) */ int ls(void) { unsigned long long status; struct EFI_FILE_PROTOCOL *root; unsigned long long buf_size; unsigned char file_buf[MAX_FILE_BUF]; struct EFI_FILE_INFO *file_info; int idx = 0; int file_num; status = SFSP->OpenVolume(SFSP, &root); assert(status, L"SFSP->OpenVolume"); while (1) { buf_size = MAX_FILE_BUF; status = root->Read(root, &buf_size, (void *)file_buf); assert(status, L"root->Read"); if (!buf_size) break; file_info = (struct EFI_FILE_INFO *)file_buf; strncpy(file_list[idx].name, file_info->FileName, MAX_FILE_NAME_LEN - 1); file_list[idx].name[MAX_FILE_NAME_LEN - 1] = L'\0'; puts(file_list[idx].name); puts(L" "); idx++; } puts(L"\r\n"); file_num = idx; root->Close(root); return file_num; } /* 新增(此处结束) */ 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"ls", com)) /* 新增 */ ls(); /* 新增 */ else puts(L"Command not found.\r\n"); } }

代码6.9: ls/shell.c

代码6.9中的ls函数每次执行时调用OpenVolume函数打开根目录。这是因为通过Read函数获取完一个目录下的所有文件/目录名之后,如果需要再次获取,则必须使用Close函数释放这个目录之后再调用OpenVolume重新打开它。虽然我们可以第一次读取时缓存该目录中的所有项,但我们出于得到最新的结果和简化代码的考虑,这里还是每次打开目录并读取它们。

此外,ls函数也调用了assert函数。这个函数检查参数中的状态值,如果这个状态值表示错误(非零),将会输出一条参数中指定的消息,并且使程序陷入一个无限循环中。assert函数的检查状态值和输出消息的功能是通过另一个名为check_warn_error的函数实现的,assert在它的基础上添加了在打印错误消息后陷入无限循环的功能。如果你想了解这两个函数的具体实现,可以阅读common.c中的代码。

图6.1展示了ls命令运行时的样子。

ls命令运行时的样子

图6.1: ls命令运行时的样子