LoginSignup
5
4

More than 5 years have passed since last update.

GetFinalPathNameByHandleW が XP でも使いたい

Last updated at Posted at 2014-06-04

GetFinalPathNameByHandleW が使いたかったけど、XP には無かった…
…だけならわざわざ作りはしなかったけれども、c:\tmp の ディレクトリハンドルの GUID を取得した際に FindFirst/NextVolume で列挙したボリューム郡を指していない GUID を返してきたため、しぶしぶ作った。

static HRESULT GetVolumeNameForVolumeSerialNumberW(
    const DWORD dwVolumeSerialNumber, 
    CStringW &result);
static HRESULT GetVolumePathNamesForVolumeNameW(
    LPCWSTR szVolumeName,
    std::vector<CStringW> &VolumePaths);

HRESULT GetFinalPathNameByHandleW2(HANDLE hFile, DWORD dwFlags, CStringW &result) {
    BYTE data[sizeof(DWORD) + sizeof(WCHAR) * 32767];   //  32767 is a NTFS maximum filename length
    const DWORD fileNameMask = FILE_NAME_NORMALIZED | FILE_NAME_OPENED;
    const DWORD volumeNameMask = VOLUME_NAME_DOS | VOLUME_NAME_GUID | VOLUME_NAME_NT | VOLUME_NAME_NONE;
    CStringW trail;
    CStringW foundVolume;

    BOOL rc;
    HRESULT hr;

    DWORD dwVolumeSerialNumber;

    rc = GetFileInformationByHandleEx(hFile, FileNameInfo, &data, sizeof(data));
    if (rc == FALSE) {
        return HRESULT_FROM_WIN32(GetLastError());
    }

    {
        FILE_NAME_INFO &fiName = (FILE_NAME_INFO &)data;
        trail = CStringW(fiName.FileName, fiName.FileNameLength / sizeof(WCHAR));
        if (trail[0] == L'\\') {
            trail.Delete(0, 1);
        }

        switch (dwFlags & fileNameMask) {
        case FILE_NAME_NORMALIZED:
            /// ?('_.)? need change?
            break;
        case FILE_NAME_OPENED:
            /// ?('_.)? need change?
            break;
        }
    }

    rc = GetVolumeInformationByHandleW(hFile,
                                       NULL, 0,
                                       &dwVolumeSerialNumber,
                                       NULL,
                                       NULL,
                                       NULL, 0);
    if (rc == FALSE) {
        return HRESULT_FROM_WIN32(GetLastError());
    }

    hr = GetVolumeNameForVolumeSerialNumber(dwVolumeSerialNumber, foundVolume);

    if (hr == S_OK) {
        switch (dwFlags & volumeNameMask) {
        case VOLUME_NAME_DOS:
            //  \\?\c:\ type.
            {
                std::vector<CStringW> VolumePaths;
                GetVolumePathNamesForVolumeNameW(foundVolume, VolumePaths);

                if (VolumePaths.size() == 0) {
                    // ?('_.)? no change? 
                    break;
                }

                CStringW shortest;
                DWORD lenShortest = (DWORD)(-1);

                for (int i = 0; i < VolumePaths.size(); i++) {
                    size_t len = VolumePaths[i].GetLength();

                    if (len < lenShortest) {
                        lenShortest = len;
                        shortest = VolumePaths[i];
                    }
                }

                foundVolume = CStringW(L"\\\\?\\") + shortest;
            }
            break;
        case VOLUME_NAME_GUID:
            //  \\?\Volume{guid}\ type. no change.
            break;
        case VOLUME_NAME_NT:
            /// don't know format. 調べるのタルい.
            throw "not impremented.";
            break;
        case VOLUME_NAME_NONE:
            /// don't know format. 調べるのタルい.
            throw "not impremented.";
            break;
        }

        hr = S_OK;
    }
    else {
        //  maybe network file handle.
        switch (dwFlags & volumeNameMask) {
        case VOLUME_NAME_DOS:
            //  \\?\UNC\server\share\ type
            hr = S_OK;
            throw "not impremented.";
            break;
        case VOLUME_NAME_GUID:
            /// error.
            hr = E_FAIL;
            break;
        case VOLUME_NAME_NT:
            /// don't know format. 調べるのタルい.
            hr = S_OK;
            throw "not impremented.";
            break;
        case VOLUME_NAME_NONE:
            /// don't know format. 調べるのタルい.
            hr = S_OK;
            throw "not impremented.";
            break;
        }
    }

    if (SUCCEEDED(hr)) {
        result = foundVolume;

        if (result[result.GetLength() - 1] != L'\\') {
            result += L"\\";
        }

        result += trail;
    }
    return hr;
}


static HRESULT GetVolumeNameForVolumeSerialNumber(const DWORD dwVolumeSerialNumber, CStringW &result) {
    WCHAR VolumeNameBuffer[1024];

    HANDLE hFindVolume = FindFirstVolume(VolumeNameBuffer, _countof(VolumeNameBuffer));
    if (hFindVolume == NULL || hFindVolume == INVALID_HANDLE_VALUE) {
        return HRESULT_FROM_WIN32(GetLastError());
    }

    DWORD err;
    do {
        DWORD vsn;
        GetVolumeInformationW(VolumeNameBuffer,
                              NULL, 0,
                              &vsn,
                              NULL,
                              NULL,
                              NULL, 0);

        if (vsn != dwVolumeSerialNumber) {
            continue;
        }

        assert(GetLastError() == ERROR_MORE_DATA);
        result = VolumeNameBuffer;
        break;
    }
    while (FindNextVolumeW(hFindVolume, VolumeNameBuffer, _countof(VolumeNameBuffer)));
    err = GetLastError();

    FindVolumeClose(hFindVolume);

    return
        err == ERROR_NO_MORE_FILES ? HRESULT_FROM_WIN32(ERROR_NOT_FOUND) :
        err == ERROR_MORE_DATA ? S_OK :
        HRESULT_FROM_WIN32(err);
}


static HRESULT GetVolumePathNamesForVolumeNameW(
    LPCWSTR szVolumeName,
    std::vector<CStringW> &VolumePaths
) {
    CHeapPtr<WCHAR> heapNames;
    DWORD count;

    SetLastError(ERROR_SUCCESS);
    ::GetVolumePathNamesForVolumeNameW(szVolumeName, NULL, 0, &count);
    DWORD lastError = GetLastError();

    if (lastError != ERROR_MORE_DATA) {
        return HRESULT_FROM_WIN32(lastError);
    }

    if (heapNames.Allocate(count) == false) {
        return HRESULT_FROM_WIN32(ERROR_OUTOFMEMORY);
    }

    SetLastError(ERROR_SUCCESS);
    BOOL
    rc = ::GetVolumePathNamesForVolumeNameW(szVolumeName, heapNames.m_pData, count, &count);

    if (rc == FALSE) {
        DWORD lastError = GetLastError();
        return HRESULT_FROM_WIN32(lastError);
    }

    for (WCHAR *NameIdx = heapNames.m_pData;
            NameIdx[0] != L'\0';
            NameIdx += wcslen(NameIdx) + 1) {
        VolumePaths.push_back(CStringW(NameIdx));
    }

    return S_OK;
}

known issue.

\\localpc\share\dir\subdir\tatoeba.bmp を開いた HANDLE を食わせてやると、本家では \\?\UNC\localpc\share\dir\subdir\tatoeba.bmp を返してくるけれど、この関数では \\C:\test\sharedir\localpc\share\dir\subdir\tatoeba.bmp を返してくる。

follow link.

C++ - GetFinalPathNameByHandleW が 2000 でも使いたい

5
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4