Top

第5章 ファイル読み書き

マウスカーソルを用意できたので、何かクリックしてみたいです。UEFIのファームウェアはFATファイルシステム上のファイルを操作するプロトコルを持っていますので、それを使ってファイル操作機能を追加してみます。

5.1 EFI_SIMPLE_FILE_SYSTEM_PROTOCOLとEFI_FILE_PROTOCOL

UEFIではFATファイルシステムを扱えます。ファイルシステムを操作するためのプロトコルが"EFI_SIMPLE_FILE_SYSTEM_PROTOCOL"と"EFI_FILE_PROTOCOL"です(仕様書"12.4 Simple File System Protocol(P.494)"と"12.5 EFI File Protocol(P.497)")。

EFI_SIMPLE_FILE_SYSTEM_PROTOCOLの定義はリスト5.1の通りです。

リスト5.1: EFI_SIMPLE_FILE_SYSTEM_PROTOCOLの定義

struct EFI_SIMPLE_FILE_SYSTEM_PROTOCOL {
        unsigned long long Revision;
        unsigned long long (*OpenVolume)(
                struct EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *This,
                struct EFI_FILE_PROTOCOL **Root);
};

リスト5.1は定義の全体で、関数はOpenVolumeのみを持っています。OpenVolumeは名前の通りボリュームを開く関数です。"ボリューム"はディスクのパーティションに当たるもので、Windows OSで言うところの"ドライブ"に相当するものです。OpenVolume関数の引数の意味は以下の通りです。

struct EFI_FILE_PROTOCOL **Root
ボリュームを開いて得られた最上位階層のディレクトリ(ルートディレクトリ)。

UEFIではファイル/ディレクトリの各エントリをEFI_FILE_PROTOCOLという構造体で扱います。OpenVolumeで得られるルートディレクトリエントリもEFI_FILE_PROTOCOLです。EFI_FILE_PROTOCOLの定義をリスト5.2に示します(本書で使用するもののみ定義しています)。

リスト5.2: EFI_FILE_PROTOCOLの定義

struct EFI_FILE_PROTOCOL {
        unsigned long long _buf;
        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);
        unsigned long long (*Close)(struct EFI_FILE_PROTOCOL *This);
        unsigned long long _buf2;
        unsigned long long (*Read)(struct EFI_FILE_PROTOCOL *This,
                                   unsigned long long *BufferSize,
                                   void *Buffer);
        unsigned long long (*Write)(struct EFI_FILE_PROTOCOL *This,
                                    unsigned long long *BufferSize,
                                    void *Buffer);
        unsigned long long _buf3[4];
        unsigned long long (*Flush)(struct EFI_FILE_PROTOCOL *This);
};

リスト5.2の各関数の使い方は、以降の節で説明します。

5.2 ルートディレクトリ直下のファイル/ディレクトリを一覧表示(ls)

ルートディレクトリ直下のファイル/ディレクトリを一覧表示する"ls"コマンドを作成してみます。サンプルのディレクトリは"sample5_1_ls"です。

まずは、EFI_SIMPLE_FILE_SYSTEM_PROTOCOLを使えるように、efi.cのefi_init関数へLocateProtocol関数の処理を追加します(リスト5.3)。

リスト5.3: sample5_1_ls/efi.c

 1: /* ・・・省略・・・ */
 2: struct EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *SFSP;       /* 追加 */
 3: 
 4: void efi_init(struct EFI_SYSTEM_TABLE *SystemTable)
 5: {
 6:     /* ・・・省略・・・ */
 7:     /* 追加(ここから) */
 8:     struct EFI_GUID sfsp_guid = {0x0964e5b22, 0x6459, 0x11d2, \
 9:                                  {0x8e, 0x39, 0x00, 0xa0,     \
10:                                   0xc9, 0x69, 0x72, 0x3b}};
11:             /* 追加(ここまで) */
12:     /* ・・・省略・・・ */
13:     /* 追加 */
14:     ST->BootServices->LocateProtocol(&sfsp_guid, NULL, (void **)&SFSP);
15: }

これで、グローバル変数"SFSP"を通してEFI_SIMPLE_FILE_SYSTEM_PROTOCOLを使用できるようになりました。次は、SFSP->OpenVolume関数を呼び出すことでルートディレクトリを開きます。コード例はリスト5.4の通りです。

リスト5.4: OpenVolume関数の使用例

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

EFI_FILE_PROTOCOLがディレクトリである場合、Read関数を呼び出す事で、ディレクトリ内に存在するファイル/ディレクトリ名を取得できます。EFI_FILE_PROTOCOLのRead関数の定義はリスト5.5の通りです。

リスト5.5: EFI_FILE_PROTOCOLのRead関数の定義

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

引数の意味は以下の通りです。

unsigned long long *BufferSize
第3引数"Buffer"のサイズ(バイト指定)を格納した変数のポインタを指定。Read関数実行後、このポインタで参照された変数にはreadしたデータサイズが格納される。EFI_FILE_PROTOCOLがディレクトリの場合、全てのファイル/ディレクトリ名を取得し終えると、0が格納される。
void *Buffer
readしたデータを格納するバッファの先頭アドレス。EFI_FILE_PROTOCOLがディレクトリの場合、1回のreadにつき1つのファイル/ディレクトリ名を格納する。

ファイル/ディレクトリに対する一通りの処理を終えたら、EFI_FILE_PROTOCOLのClose関数を実行します。Close関数の定義はリスト5.6の通りです。

リスト5.6: EFI_FILE_PROTOCOLのClose関数の定義

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

以上を踏まえて、起動ディスク直下のファイル/ディレクトリを一覧表示するコマンドを追加してみます。

まずは、ファイルの付帯情報を管理する構造体の配列"struct FILE file_list[]"を作成します。管理する付帯情報としては、今はまだ「ファイル名」だけです。行数は少ないのですが、file.hとfile.cというファイル名でリスト5.7とリスト5.8のソースコードを作成します。

リスト5.7: sample_5_1_ls/file.h

 1: #ifndef _FILE_H_
 2: #define _FILE_H_
 3: 
 4: #include "graphics.h"
 5: 
 6: #define MAX_FILE_NAME_LEN   4
 7: #define MAX_FILE_NUM        10
 8: #define MAX_FILE_BUF        1024
 9: 
10: struct FILE {
11:     unsigned short name[MAX_FILE_NAME_LEN];
12: };
13: 
14: extern struct FILE file_list[MAX_FILE_NUM];
15: 
16: #endif

リスト5.8: sample_5_1_ls/file.c

 1: #include "file.h"
 2: 
 3: struct FILE file_list[MAX_FILE_NUM];

そして、"ls"コマンドを追加したshell.cの内容はリスト5.9の通りです。

リスト5.9: sample5_1_ls/shell.c

 1: /* ・・・省略・・・ */
 2: 
 3: /* 追加(ここから) */
 4: int ls(void)
 5: {
 6:     unsigned long long status;
 7:     struct EFI_FILE_PROTOCOL *root;
 8:     unsigned long long buf_size;
 9:     unsigned char file_buf[MAX_FILE_BUF];
10:     struct EFI_FILE_INFO *file_info;
11:     int idx = 0;
12:     int file_num;
13: 
14:     status = SFSP->OpenVolume(SFSP, &root);
15:     assert(status, L"SFSP->OpenVolume");
16: 
17:     while (1) {
18:             buf_size = MAX_FILE_BUF;
19:             status = root->Read(root, &buf_size, (void *)file_buf);
20:             assert(status, L"root->Read");
21:             if (!buf_size) break;
22: 
23:             file_info = (struct EFI_FILE_INFO *)file_buf;
24:             strncpy(file_list[idx].name, file_info->FileName,
25:                     MAX_FILE_NAME_LEN - 1);
26:             file_list[idx].name[MAX_FILE_NAME_LEN - 1] = L'\0';
27:             puts(file_list[idx].name);
28:             puts(L" ");
29: 
30:             idx++;
31:     }
32:     puts(L"\r\n");
33:     file_num = idx;
34: 
35:     root->Close(root);
36: 
37:     return file_num;
38: }
39: /* 追加(ここまで) */
40: 
41: void shell(void)
42: {
43:     unsigned short com[MAX_COMMAND_LEN];
44:     struct RECT r = {10, 10, 100, 200};
45: 
46:     while (TRUE) {
47:             puts(L"poiOS> ");
48:             if (gets(com, MAX_COMMAND_LEN) <= 0)
49:                     continue;
50: 
51:             if (!strcmp(L"hello", com))
52:                     puts(L"Hello UEFI!\r\n");
53:             /* ・・・省略・・・ */
54:             else if (!strcmp(L"ls", com)) /* 追加 */
55:                     ls();   /* 追加 */
56:             else
57:                     puts(L"Command not found.\r\n");
58:     }
59: }

リスト5.9では、ls関数実行の都度OpenVolume関数でルートディレクトリを開いています。これは、Readで一通りファイル/ディレクトリエントリを取得しきってしまうと、再度取得するには一度Closeし、再度OpenVolumeする必要があるためです。取得したファイル/ディレクトリエントリをキャッシュしておくという考え方もありますが、ここでは新鮮な結果を返すために(また、簡単のために)、都度OpenVolumeし、Readしています。

また、リスト5.9のls関数ではassert関数を呼び出しています。assert関数は引数で渡されたステータス値をチェックし、ステータス値が成功(=0)以外であれば、同じく引数で指定されたメッセージを出力して"while(1);"で固めます。なお、内部的にはステータスチェックとメッセージ表示はcheck_warn_errorという関数に分かれていて、assert関数はcheck_warn_errorの戻り値に応じて"while(1);"で固めるだけです。詳しくはcommon.cのソースコードを見てみてください。

サンプルを実行する際はUSBフラッシュメモリ直下にファイルをいくつか配置してみてください。例えば、"abc"と"hlo"の2つのファイルを置いてみると、図5.1の様に表示されます。

ファイル/ディレクトリ一覧を表示している様子

図5.1: ファイル/ディレクトリ一覧を表示している様子

5.3 GUIモードでファイル/ディレクトリ一覧を表示する

これまでGUIモードではファイルのアイコンに見立てた矩形が表示されるだけでした。ここでは、矩形内にファイル名を配置してみます。サンプルプログラムは"sample5_2_gui_ls"のディレクトリです。

処理の流れとしては、ファイル名のリストを表示した後、各ファイル名を矩形で囲みます。なお、簡単のためにファイル名は3文字とします。

まず、ファイルの付帯情報として矩形の位置・大きさとハイライト状態を追加します。変更箇所はfile.hで、リスト5.10の通りです。

リスト5.10: sample5_2_gui_ls/file.h

 1: /* ・・・省略・・・ */
 2: struct FILE {
 3:     struct RECT rect;       /* 追加 */
 4:     unsigned char is_highlight;     /* 追加 */
 5:     unsigned short name[MAX_FILE_NAME_LEN];
 6: };
 7: /* ・・・省略・・・ */

以上を踏まえてgui.cへ機能を追加するとリスト5.11の通りです。

リスト5.11: sample5_2_gui_ls/gui.c

 1: #include "efi.h"
 2: #include "common.h"
 3: #include "file.h"
 4: #include "graphics.h"
 5: #include "shell.h"
 6: #include "gui.h"
 7: 
 8: #define WIDTH_PER_CH        8       /* 追加 */
 9: #define HEIGHT_PER_CH       20      /* 追加 */
10: 
11: /* ・・・省略・・・ */
12: 
13: /* 追加(ここから) */
14: int ls_gui(void)
15: {
16:     int file_num;
17:     struct RECT t;
18:     int idx;
19: 
20:     ST->ConOut->ClearScreen(ST->ConOut);
21: 
22:     file_num = ls();
23: 
24:     t.x = 0;
25:     t.y = 0;
26:     t.w = (MAX_FILE_NAME_LEN - 1) * WIDTH_PER_CH;
27:     t.h = HEIGHT_PER_CH;
28:     for (idx = 0; idx < file_num; idx++) {
29:             file_list[idx].rect.x = t.x;
30:             file_list[idx].rect.y = t.y;
31:             file_list[idx].rect.w = t.w;
32:             file_list[idx].rect.h = t.h;
33:             draw_rect(file_list[idx].rect, white);
34:             t.x += file_list[idx].rect.w + WIDTH_PER_CH;
35: 
36:             file_list[idx].is_highlight = FALSE;
37:     }
38: 
39:     return file_num;
40: }
41: /* 追加(ここまで) */
42: 
43: void gui(void)
44: {
45:     unsigned long long status;
46:     struct EFI_SIMPLE_POINTER_STATE s;
47:     int px = 0, py = 0;
48:     unsigned long long waitidx;
49:     int file_num;   /* 追加 */
50:     int idx;        /* 追加 */
51: 
52:     SPP->Reset(SPP, FALSE);
53:     file_num = ls_gui();    /* 追加 */
54: 
55:     while (TRUE) {
56:             ST->BootServices->WaitForEvent(1, &(SPP->WaitForInput), &waitidx);
57:             status = SPP->GetState(SPP, &s);
58:             if (!status) {
59:                     /* マウスカーソル座標更新 */
60:                     /* ・・・省略・・・ */
61: 
62:                     /* マウスカーソル描画 */
63:                     put_cursor(px, py);
64: 
65:                     /* 追加(ここから) */
66:                     /* ファイルアイコン処理ループ */
67:                     for (idx = 0; idx < file_num; idx++) {
68:                             if (is_in_rect(px, py, file_list[idx].rect)) {
69:                                     if (!file_list[idx].is_highlight) {
70:                                             draw_rect(file_list[idx].rect,
71:                                                       yellow);
72:                                             file_list[idx].is_highlight = TRUE;
73:                                     }
74:                             } else {
75:                                     if (file_list[idx].is_highlight) {
76:                                             draw_rect(file_list[idx].rect,
77:                                                       white);
78:                                             file_list[idx].is_highlight =
79:                                                     FALSE;
80:                                     }
81:                             }
82:                     }
83:                     /* 追加(ここまで) */
84:             }
85:     }
86: }

リスト5.11で追加したls_gui関数は、shell.cのls関数をラップした関数です。画面クリアした後、ls関数を実行してファイル/ディレクトリのリストを画面に表示し、その後、矩形を描きます。また、ls_gui関数は、描画する矩形の位置と大きさ、ハイライト状態をfile_list配列のメンバへ設定します。

リスト5.11のgui関数へ追加した"ファイルアイコン処理ループ"では、マウス操作に応じた各ファイルの処理を記述しています。ここでは、マウスカーソルがアイコンの上に乗ったらアイコンの枠をハイライト色へ変更する事を行っています。

サンプルの実行結果は図5.2の通りです。

ファイルリスト対応版GUIモード実行の様子

図5.2: ファイルリスト対応版GUIモード実行の様子

5.4 ファイルを読んでみる(cat)

ファイルのリストを取得できたので、次はファイルの中身を読んでみます。まずはシェルコマンドとして"cat(もどき)"を作ってみます。サンプルのディレクトリは"sample5_3_cat"です。

なお、ここではUEFIファームウェアをどのように呼び出せばファイルを読めるのかが分かればよいので、簡単のため、catコマンドが読み出すファイル名は固定とします。

ファイルリスト取得の際にはディレクトリのEFI_FILE_PROTOCOLに対してRead関数を呼び出すことでファイル名を取得していました。ファイルの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)")。Open関数の定義はリスト5.12の通りです。

リスト5.12: EFI_FILE_PROTOCOLの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);

引数の意味は以下の通りです。

struct EFI_FILE_PROTOCOL **NewHandle
新しく開いたEFI_FILE_PROTOCOLを格納するポインタへのアドレス。
unsigned short *FileName
ファイル名。
unsigned long long OpenMode
ファイルを開くモード(後述)。
unsigned long long Attributes
属性ビット。ファイル作成時にファイルの属性ビットへ設定する値を指定。本書では使用しない。

OpenModeへ指定できる定数の定義はリスト5.13の通りです。

リスト5.13: Open関数のOpenModeへ指定できる定数

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

なお、OpenModeへ指定できる組み合わせは以下のいずれかです。

以上を踏まえてまとめると、"abc"というファイル名のファイルを読む際の処理の流れは以下の通りです。

  1. EFI_SIMPLE_FILE_SYSTEM_PROTOCOLのOpenVolume関数でボリュームを開く(ルートディレクトリのEFI_FILE_PROTOCOLを取得)
  2. 1.のEFI_FILE_PROTOCOLのOpen関数をファイル名に"abc"を指定して実行("abc"ファイルのEFI_FILE_PROTOCOLを取得)
  3. 2.のEFI_FILE_PROTOCOLのRead関数を呼び出す(ファイルの内容を取得)

catコマンドの実装はリスト5.14の通りです。処理の流れとしては上記の1.~3.の通りで、最後にClose処理を行っています。

リスト5.14: sample5_3_cat/shell.c

 1: /* ・・・省略・・・ */
 2: 
 3: /* 追加(ここから) */
 4: void cat(unsigned short *file_name)
 5: {
 6:     unsigned long long status;
 7:     struct EFI_FILE_PROTOCOL *root;
 8:     struct EFI_FILE_PROTOCOL *file;
 9:     unsigned long long buf_size = MAX_FILE_BUF;
10:     unsigned short file_buf[MAX_FILE_BUF / 2];
11: 
12:     status = SFSP->OpenVolume(SFSP, &root);
13:     assert(status, L"SFSP->OpenVolume");
14: 
15:     status = root->Open(root, &file, file_name, EFI_FILE_MODE_READ, 0);
16:     assert(status, L"root->Open");
17: 
18:     status = file->Read(file, &buf_size, (void *)file_buf);
19:     assert(status, L"file->Read");
20: 
21:     puts(file_buf);
22: 
23:     file->Close(file);
24:     root->Close(root);
25: }
26: /* 追加(ここまで) */
27: 
28: void shell(void)
29: {
30:     unsigned short com[MAX_COMMAND_LEN];
31:     struct RECT r = {10, 10, 100, 200};
32: 
33:     while (TRUE) {
34:             puts(L"poiOS> ");
35:             if (gets(com, MAX_COMMAND_LEN) <= 0)
36:                     continue;
37: 
38:             if (!strcmp(L"hello", com))
39:                     puts(L"Hello UEFI!\r\n");
40:             /* ・・・省略・・・ */
41:             else if (!strcmp(L"cat", com))        /* 追加 */
42:                     cat(L"abc");  /* 追加 */
43:             else
44:                     puts(L"Command not found.\r\n");
45:     }
46: }

サンプルの実行の様子を図5.3に示します。なお、UEFIファームウェアの機能で表示できるUnicodeファイルは以下のnkfコマンドで作成可能です。

$ nkf -w16L0 orig.txt > unicode.txt
catコマンド実行の様子

図5.3: catコマンド実行の様子

5.5 GUIモードへテキストファイル閲覧機能追加

ファイルの内容を取得する方法が分かったので、GUIモードを拡張してみます。ここでは以下の仕様とします。サンプルのディレクトリは"sample5_4_gui_cat"です。

  1. ファイルのアイコン(矩形)内を左クリックすると閲覧モード開始
  2. 閲覧モードでは、「(1)画面クリア」、「(2)ファイル内容をUnicodeで表示」を行う
  3. ESCキー押下で閲覧モード終了

主に追加・変更するソースコードはgui.cです。gui.cのソースコードをリスト5.15に示します。

リスト5.15: sample5_4_gui_cat/gui.c

 1: /* ・・・省略・・・ */
 2: 
 3: /* 追加(ここから) */
 4: void cat_gui(unsigned short *file_name)
 5: {
 6:     ST->ConOut->ClearScreen(ST->ConOut);
 7: 
 8:     cat(file_name);
 9: 
10:     while (getc() != SC_ESC);
11: }
12: /* 追加(ここまで) */
13: 
14: void gui(void)
15: {
16:     unsigned long long status;
17:     struct EFI_SIMPLE_POINTER_STATE s;
18:     int px = 0, py = 0;
19:     unsigned long long waitidx;
20:     int file_num;
21:     int idx;
22:     unsigned char prev_lb = FALSE;  /* 追加 */
23: 
24:     SPP->Reset(SPP, FALSE);
25:     file_num = ls_gui();
26: 
27:     while (TRUE) {
28:             ST->BootServices->WaitForEvent(1, &(SPP->WaitForInput), &waitidx);
29:             status = SPP->GetState(SPP, &s);
30:             if (!status) {
31:                     /* マウスカーソル座標更新 */
32:                     /* ・・・省略・・・ */
33: 
34:                     /* マウスカーソル描画 */
35:                     put_cursor(px, py);
36: 
37:                     /* ファイルアイコン処理ループ */
38:                     for (idx = 0; idx < file_num; idx++) {
39:                             if (is_in_rect(px, py, file_list[idx].rect)) {
40:                                     if (!file_list[idx].is_highlight) {
41:                                             draw_rect(file_list[idx].rect,
42:                                                       yellow);
43:                                             file_list[idx].is_highlight = TRUE;
44:                                     }
45:                                     /* 追加(ここから) */
46:                                     if (prev_lb && !s.LeftButton) {
47:                                             cat_gui(file_list[idx].name);
48:                                             file_num = ls_gui();
49:                                     }
50:                                     /* 追加(ここまで) */
51:                             } else {
52:                                     if (file_list[idx].is_highlight) {
53:                                             draw_rect(file_list[idx].rect,
54:                                                       white);
55:                                             file_list[idx].is_highlight =
56:                                                     FALSE;
57:                                     }
58:                             }
59:                     }
60: 
61:                     /* 追加(ここから) */
62:                     /* マウスの左ボタンの前回の状態を更新 */
63:                     prev_lb = s.LeftButton;
64:                     /* 追加(ここまで) */
65:             }
66:     }
67: }

リスト5.15に関して、gui関数へはマウスクリック時の処理を追加しています。マウスのボタンから指を離した瞬間をマウスクリックとするために、マウスボタンの前回状態をprev_lbという変数へ格納しています。そして、ファイルクリック時にファイル名を指定してcat_gui関数を呼び出す処理を、"ファイルアイコン処理ループ"内へ追加しています。cat_gui関数はcat関数をラップしている関数で、上述の閲覧モードを実現しています。

サンプルを実行し、guiコマンドでguiモードを起動すると図5.4のように表示されます。そして、ファイルをクリックすると図5.5のようにファイルの内容が画面に表示されます。

GUIモードでファイル一覧が表示される様子

図5.4: GUIモードでファイル一覧が表示される様子

ファイルの内容が表示される様子

図5.5: ファイルの内容が表示される様子

getcでスキャンコードも返せるよう修正(オガム文字の領域を使う)

リスト5.15のcat_gui関数では、ESCキーで閲覧モードを終了できるように、getc関数が"SC_ESC(ESCキーのスキャンコード)"を返すまで待機しています。実はこのサンプルコード時点で、common.cのgetc関数をスキャンコード「も」返すことができるように修正しています(リスト5.16)。

リスト5.16: sample_5_4_gui_cat/common_c(getc関数)

 1: unsigned short getc(void)
 2: {
 3:     struct EFI_INPUT_KEY key;
 4:     unsigned long long waitidx;
 5: 
 6:     ST->BootServices->WaitForEvent(1, &(ST->ConIn->WaitForKey),
 7:                                    &waitidx);
 8:     while (ST->ConIn->ReadKeyStroke(ST->ConIn, &key));
 9: 
10:     /* 変更 */
11:     return (key.UnicodeChar) ? key.UnicodeChar
12:             : (key.ScanCode + SC_OFS);
13: }

リスト5.16では、key.UnicodeCharが0(押下されたキーがUnicode範囲外)である時、key.ScanCodeに下駄(SC_OFS)を履かせた値を返しています。これは、スキャンコードの値の範囲(0x00〜0x17)が2バイトUnicodeの値の範囲(0x0000〜0xffff)と被っているためで、Unicodeの使わなそうな範囲をスキャンコードとして使っています。

ここでは0x1680〜0x1697をスキャンコードとしています。Unicodeのこの範囲は「オガム文字」という文字を扱う範囲らしく、「アイルランドを中心に発見された古アイルランド語の碑文に記されている文字で、中世初期に碑文用に用いられたといわれています。」とのことです(*1)。

[*1] http://www.asahi-net.or.jp/~ax2s-kmtn/ref/unicode/european.html

そのため、SC_OFSとSC_ESCはcommon.hでリスト5.17の様に定義しています。

リスト5.17: SC_OFSとSC_ESCの定義

#define SC_OFS  0x1680
#define SC_ESC  (SC_OFS + 0x0017)

5.6 ファイルへ書き込んでみる(edit)

ファイルを読むことができたので、次はファイルへの書き込みをしてみます。ここでは「画面上に記述した内容でファイルを上書きする」コマンドとしてeditコマンドを追加します。サンプルのディレクトリは"sample5_5_edit"です。

EFI_FILE_PROTOCOLのWrite関数でファイルへの書き込みが行えます。Write関数の定義はリスト5.18の通りです。

リスト5.18: EFI_FILE_PROTOCOLのWrite関数の定義

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

引数の意味は以下の通りです。

unsigned long long *BufferSize
第3引数"Buffer"のサイズ(バイト指定)を格納した変数のポインタを指定する。Write関数実行後、このポインタで参照された変数には書き込んだデータサイズが格納される。
void *Buffer
書き込むデータを格納したバッファ。

なお、Write関数を実行しても、ただちにディスクへ反映されるわけではありません。キャッシュされているWrite結果をディスクへ反映させるにはFlush関数を実行する必要があります。Flush関数の定義はリスト5.19の通りです。

リスト5.19: EFI_FILE_PROTOCOLのFlush関数の定義

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

以上を踏まえてeditコマンドを追加します。変更後のshell.cをリスト5.20に示します。

リスト5.20: sample5_5_edit/shell.c

 1: /* ・・・省略・・・ */
 2: 
 3: /* 追加(ここから) */
 4: void edit(unsigned short *file_name)
 5: {
 6:     unsigned long long status;
 7:     struct EFI_FILE_PROTOCOL *root;
 8:     struct EFI_FILE_PROTOCOL *file;
 9:     unsigned long long buf_size = MAX_FILE_BUF;
10:     unsigned short file_buf[MAX_FILE_BUF / 2];
11:     int i = 0;
12:     unsigned short ch;
13: 
14:     ST->ConOut->ClearScreen(ST->ConOut);
15: 
16:     while (TRUE) {
17:             ch = getc();
18: 
19:             if (ch == SC_ESC)
20:                     break;
21: 
22:             putc(ch);
23:             file_buf[i++] = ch;
24: 
25:             if (ch == L'\r') {
26:                     putc(L'\n');
27:                     file_buf[i++] = L'\n';
28:             }
29:     }
30:     file_buf[i] = L'\0';
31: 
32:     status = SFSP->OpenVolume(SFSP, &root);
33:     assert(status, L"SFSP->OpenVolume");
34: 
35:     status = root->Open(root, &file, file_name,
36:                         EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE, 0);
37:     assert(status, L"root->Open");
38: 
39:     status = file->Write(file, &buf_size, (void *)file_buf);
40:     assert(status, L"file->Write");
41: 
42:     file->Flush(file);
43: 
44:     file->Close(file);
45:     root->Close(root);
46: }
47: /* 追加(ここまで) */
48: 
49: void shell(void)
50: {
51:     /* ・・・省略・・・ */
52:     while (TRUE) {
53:             /* ・・・省略・・・ */
54:             if (!strcmp(L"hello", com))
55:                     puts(L"Hello UEFI!\r\n");
56:             /* ・・・省略・・・ */
57:             else if (!strcmp(L"edit", com))       /* 追加 */
58:                     edit(L"abc"); /* 追加 */
59:             else
60:                     puts(L"Command not found.\r\n");
61:     }
62: }

リスト5.20のedit関数について、"while (TRUE)"のコードブロックが上書き用のデータを作成している処理で、ESCキーでループを抜けます。その後、ファイルへの書き込み処理を行います。また、shell関数内に関して、editコマンドもcatコマンドと同様に対象のファイル名は固定で、"abc"というファイル名とします。

サンプルの実行結果は図5.6図5.7図5.8の通りです。

editコマンドを実行する様子

図5.6: editコマンドを実行する様子

上書き用データを作成している様子

図5.7: 上書き用データを作成している様子

上書きが反映されていることを確認する様子

図5.8: 上書きが反映されていることを確認する様子

5.7 GUIモードへテキストファイル上書き機能追加

shell.cへ追加したedit関数を使うように以下の仕様でGUIモードを拡張します。サンプルのディレクトリは"sample5_6_gui_edit"です。

  1. ファイルアイコン、あるいは何もない箇所を右クリックで上書きモード(edit関数)開始
  2. ESCキー押下で上書きモード終了

なお、何もない箇所を右クリックした場合はファイルを新規に作成するようにしてみます。ファイルを新規に作成するには、Open関数実行時に第3引数のOpenModeへEFI_FILE_MODE_CREATEを追加で指定するだけです。

主な変更箇所はgui.cとshell.cです。まずgui.cをリスト5.21に示します。

リスト5.21: sample5_6_gui_edit/gui.c

 1: /* ・・・省略・・・ */
 2: 
 3: void gui(void)
 4: {
 5:     unsigned long long status;
 6:     struct EFI_SIMPLE_POINTER_STATE s;
 7:     int px = 0, py = 0;
 8:     unsigned long long waitidx;
 9:     int file_num;
10:     int idx;
11:     unsigned char prev_lb = FALSE;
12:     unsigned char prev_rb = FALSE, executed_rb;     /* 追加 */
13: 
14:     SPP->Reset(SPP, FALSE);
15:     file_num = ls_gui();
16: 
17:     while (TRUE) {
18:             ST->BootServices->WaitForEvent(1, &(SPP->WaitForInput), &waitidx);
19:             status = SPP->GetState(SPP, &s);
20:             if (!status) {
21:                     /* マウスカーソル座標更新 */
22:                     /* ・・・省略・・・ */
23: 
24:                     /* マウスカーソル描画 */
25:                     put_cursor(px, py);
26: 
27:                     /* 右クリックの実行済フラグをクリア */  /* 追加 */
28:                     executed_rb = FALSE;    /* 追加 */
29: 
30:                     /* ファイルアイコン処理ループ */
31:                     for (idx = 0; idx < file_num; idx++) {
32:                             if (is_in_rect(px, py, file_list[idx].rect)) {
33:                                     /* ・・・省略・・・ */
34:                                     if (prev_lb && !s.LeftButton) {
35:                                             cat_gui(file_list[idx].name);
36:                                             file_num = ls_gui();
37:                                     }
38:                                     /* 追加(ここから) */
39:                                     if (prev_rb && !s.RightButton) {
40:                                             edit(file_list[idx].name);
41:                                             file_num = ls_gui();
42:                                             executed_rb = TRUE;
43:                                     }
44:                                     /* 追加(ここまで) */
45:                             } else {
46:                                     /* ・・・省略・・・ */
47:                             }
48:                     }
49: 
50:                     /* 追加(ここから) */
51:                     /* ファイル新規作成・編集 */
52:                     if ((prev_rb && !s.RightButton) && !executed_rb) {
53:                             /* アイコン外を右クリックした場合 */
54:                             dialogue_get_filename(file_num);
55:                             edit(file_list[file_num].name);
56:                             ST->ConOut->ClearScreen(ST->ConOut);
57:                             file_num = ls_gui();
58:                     }
59:                     /* 追加(ここまで) */
60: 
61:                     /* マウスの左右ボタンの前回の状態を更新 */
62:                     prev_lb = s.LeftButton;
63:                     prev_rb = s.RightButton;        /* 追加 */
64:             }
65:     }
66: }

リスト5.21では右クリック時の処理を追加しています。"ファイルアイコン処理ループ"内に追加しているのがファイルを右クリックした場合の処理で、"ファイル新規作成・編集"のコードブロックがアイコン外を右クリックした場合の処理です。"ファイルアイコン処理ループ"内で右クリックを処理済みであることを示すために"executed_rb"変数を用意しています。

"ファイル新規作成・編集"時のdialogue_get_filename関数はshell.cへ追加しています。shell.cへは他に、edit関数実行時のOpenへEFI_FILE_MODE_CREATEを追加しています。変更後のshell.cをリスト5.22に示します。

リスト5.22: sample5_6_gui_edit/shell.c

 1: /* ・・・省略・・・ */
 2: 
 3: /* 追加(ここから) */
 4: void dialogue_get_filename(int idx)
 5: {
 6:     int i;
 7: 
 8:     ST->ConOut->ClearScreen(ST->ConOut);
 9: 
10:     puts(L"New File Name: ");
11:     for (i = 0; i < MAX_FILE_NAME_LEN; i++) {
12:             file_list[idx].name[i] = getc();
13:             if (file_list[idx].name[i] != L'\r')
14:                     putc(file_list[idx].name[i]);
15:             else
16:                     break;
17:     }
18:     file_list[idx].name[i] = L'\0';
19: }
20: /* 追加(ここまで) */
21: 
22: /* ・・・省略・・・ */
23: 
24: void edit(unsigned short *file_name)
25: {
26:     /* ・・・省略・・・ */
27:     status = root->Open(root, &file, file_name,
28:                         EFI_FILE_MODE_READ | EFI_FILE_MODE_WRITE | \
29:                         EFI_FILE_MODE_CREATE, 0);   /* 追加 */
30:     assert(status, L"root->Open");
31:     /* ・・・省略・・・ */
32: }
33: 
34: /* ・・・省略・・・ */

サンプルの実行結果は図5.9図5.10図5.11の通りです。ここではファイル新規作成を試しています。

アイコン外を右クリック

図5.9: アイコン外を右クリック

ファイル名入力画面

図5.10: ファイル名入力画面

ファイル内容入力

図5.11: ファイル内容入力


Top