2006 年 10 月 30 日 23 時 48 分

リパースデータバッファの取得


このアーカイブは同期化されません。 mixi の日記が更新されても、このアーカイブには反映されません。


では、実際にリパースデータバッファを取得し、
マウントポイントのリンク先を取得してみよう。

========== 1st half of mountpoint.c ==========

#define UNICODE
#define STRICT
#define WIN32_LEAN_AND_MEAN

#define _WIN32_WINNT  0x0500

#include <windows.h>
#include <winioctl.h>
#include <string.h>

typedef struct _MOUNT_POINT_INFO {
    unsigned long tag;
    unsigned short dataLength;
    unsigned short reserved;
    unsigned short targetOffset;
    unsigned short targetByteLength;
    unsigned short descriptionOffset;
    unsigned short descriptionByteLength;
    unsigned char buffer[1]; /* BYTE */
} MOUNT_POINT_INFO;

========== end of 1st half of mountpoint.c ==========

さて、デバイスの制御をするためには、
<winioctl.h> をインクルードする必要がある。
また、Windows 2000 以降の NT 固有の機能を使うため、
_WIN32_WINNT を定義しておく。

肝心のリパースポイントバッファだが、
これは REPARSE_DATA_BUFFER という構造体である。

これは特定の版の <winnt.h> には定義されていたのだが、
最新のヘッダファイルには見つからず、
代わりに <ntifs.h> の方に移されている。

しかしこの <ntifs.h>、通常の SDK に含まれないため、
ここではマウントポイント専用の構造体を
MOUNT_POINT_INFO として捏造することにする。

この MOUNT_POINT_INFO 構造体だが、実は正確ではない。
というのも、リパースデータバッファは可変長であるため、
C 言語の構造体では正確に表現できないのだ。

なので、実際にデータを取得するためには、
別に十分な容量を確保したバッファを用意し、
そこに読み込む必要がある。

========== 2nd half of mountpoint.c ==========

HRESULT MountPointGetTarget(const wchar_t *mountPointPath,
        wchar_t *target, unsigned int bufferSize) {

    HANDLE handle; /* ディレクトリハンドル */

    const wchar_t *targetPath;
    DWORD targetPathLength;

    DWORD dataSize;
    char dataBuffer[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
    MOUNT_POINT_INFO *info = (MOUNT_POINT_INFO *)dataBuffer;

    ZeroMemory(dataBuffer, MAXIMUM_REPARSE_DATA_BUFFER_SIZE);

    /* ディレクトリを開く */
    handle = CreateFile(mountPointPath, GENERIC_READ,
            FILE_SHARE_READ, NULL, OPEN_EXISTING,
            FILE_FLAG_OPEN_REPARSE_POINT
            | FILE_FLAG_BACKUP_SEMANTICS, NULL);

    if (handle == INVALID_HANDLE_VALUE) return 0;

    /* ドライバに制御コード FSCTL_GET_REPARSE_POINT を送る */
    if (!DeviceIoControl(handle, FSCTL_GET_REPARSE_POINT,
            NULL, 0, dataBuffer, MAXIMUM_REPARSE_DATA_BUFFER_SIZE,
            &dataSize, NULL)) {

        CloseHandle(handle);
        return HRESULT_FROM_WIN32(GetLastError());
    }

    /* ディレクトリを閉じる */
    CloseHandle(handle);

    /* タグが別の種類 */
    if (info->tag != IO_REPARSE_TAG_MOUNT_POINT)
            return HRESULT_FROM_WIN32(ERROR_REPARSE_TAG_MISMATCH);

    /* リンク先文字列 (NUL 終端) */
    targetPath = (const wchar_t *)(info->buffer + info->targetOffset);

    /* リンク先文字数 */
    targetPathLength = info->targetByteLength / sizeof (wchar_t);

    /* バッファ不足か? */
    if (targetPathLength >= bufferSize) {
        return HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
    }

    /* バッファにコピー */
    wcscpy(target, targetPath);

    return S_OK;
}

========== end of 2nd half of mountpoint.c ==========

最初にすることは、ディレクトリを開くことだ。
ディレクトリはファイルと同じように、
CreateFile で開くことができる。

ポイントは、FILE_FLAG_OPEN_REPARSE_POINT と、
FILE_FLAG_BACKUP_SEMANTICS を指定することだ。

前者は、リパースポイントを追跡しないことを示す。
これを指定しないと、ディレクトリ自身ではなく、
ディレクトリが指すボリュームを開いてしまう。
後者は、ディレクトリのハンドルを取得するために必要だ。

続いて、DeviceIoControl を呼び出し、
リパースデータバッファを取得する。
制御コードは FSCTL_GET_REPARSE_POINT である。

リパースデータバッファは可変長なので、
とり得る最大容量のバッファ dataBuffer を確保しておき、
DeviceIoControl でそこに読みこむ。
そして MOUNT_POINT_INFO にキャストして参照する。

最後に、タグの種類を調べ、
IO_REPARSE_TAG_MOUNT_POINT であれば、
リンク先文字列が格納されているポインタを取得し、
バッファにコピーして完了となる。

試しに、「D:\mnt\cdrom」に対して呼び出してみると、
「\??\Volume{f08e7d20-64f1-11db-b10d-001125868411}\」
という結果が得られた。

実は、ボリュームマウントポイントのリンク先は、
GetVolumeNameForVolumeMountPoint でも得られるが、
こちらの API には制約があるため、
今回は敢えて直接取得をためしてみた。



Copyright (c) 1994-2007 Project Loafer. All rights reserved.