第3章 ファイルシステムドライバ製作編①

3. ファイルシステムドライバを作る前に

3.1. 最小限のファイルシステムドライバに必要なもの

ファイルシステムドライバ製作の参考となる公式資料として、WinDDKに含まれるFastFatサンプルなどがあります。 しかし、このサンプルは実際のFastFatドライバのソースのようで、最小構成としては実際の所どこまで作ればよいのかが分かりにくいものです。

そこで、本記事では本当の意味での最小構成から出発していきます。 第二章のカーネルモードドライバのサンプルをベースにして考えたとき、最小限のファイルシステムドライバには以下の違いがあります。

3.1.1. デバイスの作成と設定

デバイスの作成時に、FILE_DEVICE_UNKNOWNではなくFILE_DEVICE_DISK_FILE_SYSTEMにして作成します。 net useができない見かけ倒しですが、FILE_DEVICE_NETWORK_FILE_SYSTEMでも可能です。

この際、デバイスの付加情報も設定する必要があります。 厳密には必須ではありませんが、リムーバブルディスクやネットワークドライブの振りをさせておくことで、Windowsがごみ箱を使おうとして面倒なことになるのを回避できます。

【リムーバブルディスクの振りをする場合】

    status = IoCreateDevice(DriverObject, 0, &deviceNameUnicodeString, 
                            FILE_DEVICE_DISK_FILE_SYSTEM, FILE_REMOVABLE_MEDIA, FALSE, &deviceObject);
    if (!NT_SUCCESS(status)) {
        return status;
    }
【ネットワークドライブの振りをする場合】

    status = IoCreateDevice(DriverObject, 0, &deviceNameUnicodeString, 
                            FILE_DEVICE_NETWORK_FILE_SYSTEM, FILE_REMOTE_DEVICE, FALSE, &deviceObject);
    if (!NT_SUCCESS(status)) {
        return status;
    }

deviceObject->Flagsにも付加情報を設定する必要があります。 以下のようにしておけば基本的に問題は起こらないと想います。


    deviceObject->Flags |= DO_BUFFERED_IO; // データ受け渡しでSystemBufferを基本とする?指定しても他方式で来るときは来る気がする。
    deviceObject->Flags |= DO_LOW_PRIORITY_FILESYSTEM; // 低優先度で処理 なくてもよい

3.1.2. IRP処理関数の割り当て

独自仕様のカーネルモードドライバではIRP_MJ_CREATE、IRP_MJ_CLOSE、IRP_MJ_DEVICE_CONTROLの3つを処理すれば十分でしたが、ファイルシステムドライバでは他のIRPにも適切に応答する必要があります。

定義を見るとかなり沢山のIRPがありますが、最小限のファイルシステムドライバでは全てを真面目に実装する必要はありません。以下の表には実装すべき優先順で必要最低限のIRPを示しています。個別の具体的な実装方法については後述します。

とりあえず未実装のIRPに対しては、Irp->IoStatus.StatusとしてSTATUS_NOT_IMPLEMENTEDを、Irp->IoStatus.Informationとして0を返してください。

【Windowsシステムが使用する主なIRP】
IRP大雑把な意味
IRP_MJ_CREATEユーザーモードのCreateFile関数等から呼ばれる。Createとあるが、既存ファイルのオープンも管轄内。また、ディレクトリの作成とオープンもこれが担当する。
IRP_MJ_CLOSECreateFileで開いたハンドルを閉じる場合などに呼ばれる。開いたファイルの後片付けを行う。削除フラグが立っている場合、この段階で実際の削除を行う。
IRP_MJ_CLEANUPファイルをいつ閉じても良いように準備せよという指示。IRP_MJ_CLOSEの前に呼ばれる。実際にIRP_MJ_CLOSEが呼ばれるまでには時間差があるので、この段階でファイルロックの解除を行うこと。ただし、メモリマップトファイルの場合IRP_MJ_CLEANUPの後にIRP_MJ_READ等が来る場合があるので、それにも対処すること。
IRP_MJ_FILE_SYSTEM_CONTROLファイルシステムへの制御指示。最小限動かすために、一部のステータスとして非サポートや成功を返す必要がある。
IRP_MJ_LOCK_CONTROLファイルの排他ロックのための命令。最小限の構成なら無条件で成功を返せばよい。
IRP_MJ_FLUSH_BUFFERSキャッシュからファイルへの即時書き込みの指示。最小限の構成なら無条件で成功を返せばよい。
IRP_MJ_QUERY_VOLUME_INFORMATIONボリューム(マウントしたディスク)の情報を返す。ボリュームラベルや容量の情報等。
IRP_MJ_DIRECTORY_CONTROL指定したディレクトリ内のファイルの列挙や監視を行う。列挙が出来れば監視はとりあえず未実装でもよい。
IRP_MJ_QUERY_INFORMATIONIRP_MJ_CREATEで開いたファイルの情報を取得する。多数あるがWindowsシステムで使用されるものは限られている。
IRP_MJ_SET_INFORMATIONIRP_MJ_CREATEで開いたファイルの情報を設定する。多数あるがWindowsシステムで使用されるものは限られている。ファイルサイズの変更やファイルの移動、削除フラグを立てるなどの特殊な操作もある。読み取り専用なら実装不要。
IRP_MJ_READIRP_MJ_CREATEで開いたファイルの読み込みを行う。開いたものがディレクトリの場合は呼ばれない(はず)。
IRP_MJ_WRITEIRP_MJ_CREATEで開いたファイルの書き込みを行う。開いたものがディレクトリの場合は呼ばれない(はず)。読み取り専用なら実装不要。

3.1.3. 割り当てるべきメモリ

ファイルシステムドライバでは、特定の場所にメモリを確保していることを前提にOS側がアクセスしてくることがあります。 必要なメモリを割り当てていない場合、カーネルモード例外でブルースクリーンになる場合があります。

IRP_MJ_CREATEで成功(STATUS_SUCCESS)を返す場合、以下のポインタをドライバで設定しておく必要があります。

【Windowsシステムが使用する最低限のメモリ領域】
場所割り当てるもの
IrpSp->FileObject-> FsContext先頭にFSRTL_COMMON_FCB_HEADER構造体を含むユーザー定義構造体へのポインタ(NonPagedPoolで割り当てたもの。初期値はゼロクリアしておくこと)。FSRTL_COMMON_FCB_HEADER部分はOSが使うので触ってはいけない。ドライバが使う任意のデータはその後ろに配置する。OPENする度に割り当て、CLOSEで解放すること。
IrpSp->FileObject-> SectionObjectPointerSECTION_OBJECT_POINTERS構造体へのポインタ(NonPagedPoolで割り当てたもの。初期値はゼロクリアしておくこと)。中身はOSが使うので触ってはいけない。同じファイルを開く場合は共通にし、SectionObjectPointerを参照している全てのファイルを閉じるまで解放しないこと。

※IrpSpはIoGetCurrentIrpStackLocation(Irp)によって得たIRPスタックへのポインタを表します。

※メモリの割り当てはExAllocatePoolを使って行います。必ずNonPagedPoolで割り当てること。

※MS公式ドキュメントを読むと、(翻訳が微妙なのか原文から駄目なのか分かりませんが)FsContextに設定すべきものがNULLでいいのか、そしてFSRTL_ADVANCED_FCB_HEADER構造体(FSRTL_COMMON_FCB_HEADER構造体の新OS用拡張)をどう含めればいいのかよく分かりません。色々試したところ、以下のようにFSRTL_COMMON_FCB_HEADER構造体をユーザー定義の構造体の先頭に置くというのが必須の要件です。これをしないとOS側が関係ないメモリにデータを書き込んで破壊して回ったり、範囲外メモリアクセスでブルースクリーンを頻発させたりします。

typedef struct tagSAMPLEFS_FSCONTEXT {
	FSRTL_COMMON_FCB_HEADER systemReserved; // OSが使うので触ってはいけない。32bit環境では40byte
    ULONG userData; // これ以降、ファイルシステムドライバ独自のデータ
    ULONG userDataArray[20]; // いちおう何byte使ってもよい(常識的な範囲で)
    PVOID userDataPtr; // 型も自由でよい(PagedPoolのポインタに飛ばす場合はIRQLに注意)
} SAMPLEFS_FSCONTEXT, *PSAMPLEFS_FSCONTEXT;

※SectionObjectPointerをどうすべきかについてもMS公式ドキュメントから見つけるのが難しいですが、よく読むとファイルを開く毎にドライバ側がNonPagedPoolから割り当てる必要があると書いてあります。更に「同じファイルを開いたときは同じものを返し、誰も参照しなくなったら削除しなければならない」という要件も実はあります。これをしないとメモリマップトファイル関係やEXE実行(裏でメモリマップトファイル使用)が正しく動かなくなります。

3.2. ファイルやディレクトリ操作の流れ

ファイルシステムドライバにおいて、ファイルやディレクトリは以下の流れで操作されます。 この流れで作ればよいということを念頭に置いておきましょう。

これらに適切に応答するようにすれば、エクスプローラで最小限操作可能な仮想ファイルシステムが作れます。

  1. IRP_MJ_QUERY_VOLUME_INFORMATIONを用いてボリュームの情報を取得
  2. IRP_MJ_DIRECTORY_CONTROLを用いてファイルやディレクトリを列挙
  3. IRP_MJ_CREATEによってファイルやディレクトリを開く
  4. IRP_MJ_QUERY_INFORMATIONによってファイルやディレクトリの情報を取得
  5. IRP_MJ_SET_INFORMATIONでファイルやディレクトリの情報を操作したり、IRP_MJ_READ/IRP_MJ_WRITEでファイルを読み書きしたりする
  6. IRP_MJ_CLEANUPによってファイルやディレクトリを閉じる準備をする(省略される場合もある)
  7. IRP_MJ_CLOSEによってファイルやディレクトリを閉じる

3.3. ファイルシステムドライバにおけるファイルとディレクトリ

ファイルシステムドライバにおいて、ファイルもフォルダも同じくIRP_MJ_CREATEで開いてIRP_MJ_CLOSEで閉じます。 ディレクトリは「ディレクトリという属性のついた特殊なファイル」というくらいに考えておきましょう。

なお、ディレクトリの場合は普通のファイルではないのでIRP_MJ_READ/IRP_MJ_WRITEが来ることはありません。 少なくとも私が試した限りではやってきませんでした。

第4章 ファイルシステムドライバ製作編② へ移動

最小構成の仮想ファイルシステムドライバの製作に戻る

資料集に戻る

トップに戻る