今日はディレクトリジャンクションを作成してみる。
そのものズバリの API は存在しないので、
リパースデータバッファを自分で用意し、
DeviceIOControl を使って設定することになる。

========== part of mountpoint.c ==========

HRESULT MountPointCreate(const wchar_t *mountPointPath, const wchar_t *target) {

    unsigned short pathByteLength;
    char dataBuffer[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
    MOUNT_POINT_INFO *info = (MOUNT_POINT_INFO *)dataBuffer;

    /* パスのバイト数 */
    pathByteLength = (unsigned short)(wcslen(target) * sizeof (WCHAR));

    /* パスが長すぎる確認は割愛 */

    /* リパースデータバッファを初期化 */
    memset(dataBuffer, 0, MAXIMUM_REPARSE_DATA_BUFFER_SIZE);

    /* タグは IO_REPARSE_TAG_MOUNT_POINT */
    info->tag = IO_REPARSE_TAG_MOUNT_POINT;

    /* リンク先(念のため NUL 文字分も確保)*/
    info->targetOffset = 0;
    info->targetByteLength = pathByteLength;
    wcscpy((wchar_t *)info->buffer, target);

    /* 説明は空文字列 */
    info->descriptionOffset = pathByteLength + sizeof (WCHAR);
    info->descriptionByteLength = 0;

    /* 有効なデータ長 */
    info->dataLength = 8 + pathByteLength + sizeof (WCHAR) + sizeof (WCHAR);

    /* 準備 OK */

    /* ディレクトリを作成 */
    if (!CreateDirectory(mountPointPath, NULL)) {
        return HRESULT_FROM_WIN32(GetLastError());
    }

    {
        DWORD result;
        DWORD size;

        /* ディレクトリを開く */
        HANDLE dir = CreateFile(mountPointPath, GENERIC_WRITE, 0, NULL,
                OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);

        if (dir == INVALID_HANDLE_VALUE) goto error;

        /* リパースポイントに設定 */
        if (!DeviceIoControl(dir, FSCTL_SET_REPARSE_POINT, info,
                8 + info->dataLength, NULL, 0, &size, NULL)) goto error;

        CloseHandle(dir);
        return S_OK;

error:
        result = HRESULT_FROM_WIN32(GetLastError());

        if (dir != INVALID_HANDLE_VALUE) {
            CloseHandle(dir);
        }
        RemoveDirectory(mountPointPath);

        return result;
    }

}

========== end of part of mountpoint.c ==========

mountpoint.c は、以前のソースファイルだ。
そこに追記する形を取っている。

まず最初に、リパースデータバッファを作成する。

バッファのバイト数は、パスによって異なるので、
MAXIMUM_REPARSE_DATA_BUFFER_SIZE 分配列で用意し、
それから MOUNT_POINT_INFO にキャストして使う。
本当は、target の長さに応じて割り当て、
target が長すぎる場合はエラーにすべきである。

memset で 0 に初期化した後、構造体に値を入れていく。
まず、tag は IO_REPARSE_TAG_MOUNT_POINT だ。

リンク先は、targetOffset と targetByteLength。
バイト単位であることに注意すること。
説明は、descriptionOffset と descriptionByteLength。
今回は空文字列を代入しておく。

IO_REPARSE_TAG_MOUNT_POINT の構造の場合、
文字列の長さがヘッダに記録されているので、
文字列の終端に、NUL が必須ではなさそうなのだが、
SetVolumeMountPoint などで作成した場合、
自動的に NUL 文字列も終端に確保されているので、
それに準じることにする。

targetOffset がデータの最初に入るので 0、
そして descriptionOffset は、
リンク先のバイト長に NUL 文字を加えた値となる。
また、説明文も NUL 文字のみ確保しておくことになる。

最後に、リンク先の文字列をバッファにコピーし、
dataLength を正しい値に設定すると準備 OK。

実際にジャンクションを作成するためには、
最初にディレクトリを作成する必要がある。
これは、CreateDirectory API で行う。

次に、作成したディレクトリを CreateFile API で開く。
FILE_FLAG_BACKUP_SEMANTICS を忘れないこと。
今回は値を書き込むので、GENERIC_WRITE は必須だ。

なお、リパースポイントとして設定したディレクトリは、
FILE_ATTRIBUTE_REPARSE_POINT 属性を持つのだが、
この属性は、手動で設定する必要はない。

そして、リパースデータバッファを関連付けるために、
DeviceIoControl API を呼び出し、
デバイスドライバに制御コードを送る。
リパースポイントの設定は FSCTL_SET_REPARSE_POINT だ。

今回は書き込みなので、第 3, 4 引数でデータを渡す。
戻るデータはないので、第 5, 6 引数は必要ないが、
DeviceIoControl は必ず戻るデータの長さを返すので、
第 7 引数には必ずバッファを渡してやる必要がある。

最後に忘れずにディレクトリのハンドルを閉じて完了となる。

試しに mountPointPath として「C:\TEMP」
target として、「\??\C:\WINDOWS\TEMP」等とすると、
C:\WINDOWS\TEMP と C:\TEMP が同じ内容になる。

C:\TEMP が、C:\WINDOWS\TEMP を指す、
ディレクトリのシンボリックのような役割となるのである。