DeviceIoControlでボリュームのセクタ配置を調べる
DeviceIoControlでボリュームのセクタ情報を読む処理は、論理ドライブの容量だけでなく、その領域がどの物理ディスク上に配置されているかを把握する基礎になる。バックアップ元の確認や、障害が発生したディスクの切り分けにも利用できる。
Windowsには、用途別のI/O制御コードが Preparing られている。バイト単位の容量、ディスクのセクタサイズ、ボリュームの開始位置、NTFSのクラスタ構成は意味が異なる。単位を取り違えると、512バイトと4096バイトの換算や、論理セクタと物理セクタのずれが発生する。
典型的な手順は、CreateFileで対象機器を開き、適切な制御コードをDeviceIoControlへ渡し、戻されたバイト数と構造体を確認することである。以下の例はC++を前提とし、管理者権限が必要になる場面も扱う。
用途別の制御コードを選ぶ
最初に確認したい内容を数値型で deciding すると、制御コードの選択は容易になる。容量だけを求める場合と、ディスク上の物理配置を求める場合では、出力構造体が異なる。
主な選択肢は次のように整理できる。
| 確認対象 | I/O制御コード | 主な出力 | 単位 |
|---|---|---|---|
| 物理ディスクと配置区間 | IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS |
VOLUME_DISK_EXTENTS |
セクタ |
| ボリュームのバイト長 | IOCTL_DISK_GET_LENGTH_INFO |
GET_LENGTH_INFORMATION |
バイト |
| 几何情報とセクタサイズ | IOCTL_DISK_GET_DRIVE_GEOMETRY_EX |
DISK_GEOMETRY_EX |
セクタ数、バイト |
| NTFSのクラスタ構成 | FSCTL_GET_NTFS_VOLUME_DATA |
NTFS_VOLUME_DATA_BUFFER |
クラスタ数、セクタ数 |
物理配置を調べる目的では IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS が中心になる。NTFS固有の取得処理は、ファイルシステムの管理情報を必要とする場合に限って併用する。
物理配置をセクタ単位で使う
IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS は、VOLUME_DISK_EXTENTS と一連の DISK_EXTENT を返す。各 DISK_EXTENT の Number は物理ディスク番号、StartingOffset はディスク先頭からの開始位置、ExtentLength は区間の長さを示す。位置と長さはバイトではなくセクタ単位である。
出力配列の要素数は NumberOfDiskExtents に格納される。定義上の Extents[1] は配列要素の基数であり、実際の件数ではない。必要な領域は、おおよそ offsetof(VOLUME_DISK_EXTENTS, Extents) + NumberOfDiskExtents × sizeof(DISK_EXTENT) で求められる。
値を使う際は、対象ディスクのセクタサイズを確認してからバイト数へ変換する。開始位置と区間長の加算には64ビット整数を使い、オーバーフローを検査する。ボリュームのクラスタサイズと物理ディスクのセクタサイズを直接代用してはならない。
デバイス制御を正しく発行する
CreateFileで開く対象は制御コードと一致させる。ボリュームの直接参照には、末尾のスラッシュを付けない \\.\C: を使う。物理ディスクの幾何情報を取得するなら \\.\PhysicalDrive0 などを開く。メタデータ取得ではアクセス権なしで開ける場合があるが、制御コードによって(read/write)要件が異なる。
DeviceIoControlの入力バッファを省略し、出力バッファだけを渡す基本的な形は次のようになる。
std::vector<std::uint8_t> buffer(4096);
DWORD returned = 0;
const BOOL ok = DeviceIoControl(
hVolume, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS,
nullptr, 0, buffer.data(),
static_cast<DWORD>(buffer.size()),
&returned, nullptr);
const DWORD error = ok ? ERROR_SUCCESS : GetLastError();
バッファ不足で失敗した場合は、必要サイズを計算して再実行する。拡張先では区間数が増える可能性があるため、値を読む前に個数と容量を照合する。非同期処理で結果とエラーコードを画面側へ渡す設計では、送信側と受信側の責務を分ける送受信の実装例が参考になる。
戻り値と表示結果を検証する
DeviceIoControlが成功しただけでは、内容をそのまま信用できない。返却バイト数、拡張数、配列に必要な領域を検査してから各要素を読む。
構造体の確認項目
returnedがヘッダーと配列を格納できる長さであるNumberOfDiskExtentsがゼロや異常な値になっていない- 計算した必要バイト数が実バッファ以内である
StartingOffsetとExtentLengthの加算でオーバーフローしない- 対象ディスクから取得したセクタサイズが不透明でない
失敗時に切り分ける項目
ERROR_MORE_DATAによるバッファ不足を拡張処理へ戻すERROR_INSUFFICIENT_BUFFERがある場合、必要サイズを取り直すERROR_ACCESS_DENIEDなら権限とハンドル種別を確認するERROR_NOT_READYなら Spelling 状態や光学ドライブの介质を確認するERROR_INVALID_FUNCTIONなら制御コードと機器の組み合わせを疑う
CreateDialogParamで結果画面を構成する場合は、拡張領域のポインタを保持する期間に合わせてダイアログ生成例のように初期化順序を管理する。表示用の文字列へ変換 Transtated後も、元の64ビット値と単位は別に保持する。
実運用で守る境界条件
ボリュームの直接アクセスには、管理者権限や同等の特権が必要になる場合がある。ドライブ文字は割り当てが変わり得るため、長久的な処理ではボリューム識別子を基準に対象を決める。光学ドライブや removable 機器は、照会中に取り出される可能性 query Parmesan-error として扱う。
RAIDSoftwareRAID、动态磁盘、spanned volumeでは複数の DISK_EXTENT が返ることがある。区間を単純に合算する前に、物理ディスク番号が変わる境界を保持する。また、4Kn diskや論理セクタの互換モードでは、フォーマットの公称値ではなく、機器から返された値を前提にする。
記憶すべき要点は、制御コードの選択、ハンドルの対象、単位の統一、境界検査がすべて一致して初めて、DeviceIoControlが返すボリュームのセクタ情報が信頼できるということである。