Junctions と Symbolic Links を見分ける

Vista 以降では何ともやっかいです。
とあるフォルダーが ジャンクションなのか、シンボリックリンクなのかを区別する方法です。
ハードリンク、ジャンクション、シンボリックリンク自体の説明は多くの方が書いているので割愛しますが、

  • ハードリンクは、対象はファイル(Windows NT 3.1 以降)
  • ジャンクションは、対象はファイルとフォルダー(Windows 2000 以降)
  • シンボリックリンクは、対象はファイルとフォルダー(Windows Vista 以降)

となっています。フォルダーの属性を調べるので、まず思い浮かぶのは

です。これでフォルダーの属性を調べてみると、ジャンクションもシンボリックリンクも

DWORD dwAttr = GetFileAttributes(szFolder);

dwAttr ==> 0x00000410

FILE_ATTRIBUTE_DIRECTORY    0x00000010
FILE_ATTRIBUTE_REPARSE_POINT    0x00000400

となってしまい、区別がつきません。
GetFileAttributes() は、リンク先ではなく、その対象自体の情報を返してしまうためです。
では、どうやって調べるのかというと、上記属性のフォルダーを CreateFile() で開いてから、DeviceIoControl() を
使います。ちょっと面倒ですので、ヘルパーを作ってみました。


#define DIR_ATTR  (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT)

typedef struct {
    DWORD ReparseTag;
    DWORD ReparseDataLength;
    WORD Reserved;
    // IO_REPARSE_TAG_MOUNT_POINT specifics follow
    WORD ReparseTargetLength;
    WORD ReparseTargetMaximumLength;
    WORD Reserved1;
    WCHAR ReparseTarget[1];
} REPARSE_MOUNTPOINT_DATA_BUFFER, *PREPARSE_MOUNTPOINT_DATA_BUFFER;

enum _DIR_TYPES
{
    TYPE_JUNCTION = 0,
    TYPE_SYMBOLICLINK
};

DWORD IsDirJunctionOrSymbolicLink(LPCTSTR pszDir, _DIR_TYPES *pType)
{
    HANDLE hToken;
    TOKEN_PRIVILEGES tp;

    // DeviceIoControlの前にCreateFileを
    // FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICSで開くため
    // プロセスに権限が必要
    OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken);
    LookupPrivilegeValue(NULL, SE_BACKUP_NAME, &tp.Privileges[0].Luid);
    tp.PrivilegeCount = 1;
    tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
    AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL);
    CloseHandle(hToken);

    // ディレクトリを開く
    HANDLE hDir = CreateFile(pszDir, GENERIC_READ, 0, NULL, OPEN_EXISTING,
                        FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, NULL);
    if (hDir == INVALID_HANDLE_VALUE)
    {
        return GetLastError();
    }

    // デバイス情報を取得する
    BYTE buf[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
    REPARSE_MOUNTPOINT_DATA_BUFFER& ReparseBuffer =
        (REPARSE_MOUNTPOINT_DATA_BUFFER&)buf;
    DWORD dwRet = 0;
    BOOL br = DeviceIoControl(hDir, FSCTL_GET_REPARSE_POINT, NULL, 0, &ReparseBuffer,
                    MAXIMUM_REPARSE_DATA_BUFFER_SIZE, &dwRet, NULL);
    if (br == 0)
    {
        CloseHandle(hDir);
        return GetLastError();
    }
    CloseHandle(hDir);
 
    // Microsoft タグがどうかを判定する
    DWORD dwIsMSTag = IsReparseTagMicrosoft(ReparseBuffer.ReparseTag);
    if (dwIsMSTag != 0)
    {
        switch (ReparseBuffer.ReparseTag)
        {
            case IO_REPARSE_TAG_MOUNT_POINT:
                // Junctions
                *pType = TYPE_JUNCTION;
                break;
            case IO_REPARSE_TAG_SYMLINK:
                // Symbolic Links
                *pType = TYPE_SYMBOLICLINK;
                break;
        }
    }
    else
    {
        return 0xff;
    }

return 0;
}

ジャンクションとシンボリックリンクを調べるだけで、これだけのコードが必要になってしまうのは驚きですが
止む無しです。

上記では、ReparseTag だけを使っていますが、他の構造体のメンバーを調べると
いろいろな情報を調べることもできます。
なお、ジャンクションとシンボリックリンクは FindFirstFile() を使っても情報を取得することができます。
こっちの方が簡単かも…。

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中

%d人のブロガーが「いいね」をつけました。