From e01a788ee05125b4fefa01a5b11f4088915cb54e Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Sat, 5 Dec 2009 17:31:00 +0000 Subject: [PATCH] Add support for MSW unique volume names to wxFileName. Recognize the paths starting with "\\?\Volume{GUID}" under MSW and provide a way to test for them. Closes #8874. git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@62782 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- docs/changes.txt | 1 + include/wx/filename.h | 5 +++ interface/wx/filename.h | 18 ++++++++ src/common/filename.cpp | 73 ++++++++++++++++++++++++++++++--- tests/filename/filenametest.cpp | 26 ++++++++++++ 5 files changed, 117 insertions(+), 6 deletions(-) diff --git a/docs/changes.txt b/docs/changes.txt index dda8f8f279..a642866566 100644 --- a/docs/changes.txt +++ b/docs/changes.txt @@ -432,6 +432,7 @@ All: wxVariant. - wxDateTime timezone functions now dynamic (no caching). - Added wxHttp::GetCookie and wxHttp::HasCookies (dodge). +- Added support for unique volume names to wxFileName (Neno Ganchev). Unix: diff --git a/include/wx/filename.h b/include/wx/filename.h index 7bb7a09460..1a6abd73eb 100644 --- a/include/wx/filename.h +++ b/include/wx/filename.h @@ -433,6 +433,11 @@ public: // is the char a path separator for this format? static bool IsPathSeparator(wxChar ch, wxPathFormat format = wxPATH_NATIVE); + // is this is a DOS path which beings with a windows unique volume name + // ('\\?\Volume{guid}\')? + static bool IsMSWUniqueVolumeNamePath(const wxString& path, + wxPathFormat format = wxPATH_NATIVE); + // Dir accessors size_t GetDirCount() const { return m_dirs.size(); } void AppendDir(const wxString& dir); diff --git a/interface/wx/filename.h b/interface/wx/filename.h index 050141ad67..3f40781ab7 100644 --- a/interface/wx/filename.h +++ b/interface/wx/filename.h @@ -819,6 +819,24 @@ public: static bool IsPathSeparator(wxChar ch, wxPathFormat format = wxPATH_NATIVE); + /** + Returns @true if the volume part of the path is a unique volume name. + + This function will always return @false if the path format is not + wxPATH_DOS. + + Unique volume names are Windows volume identifiers which remain the same + regardless of where the volume is actually mounted. Example of a path + using a volume name could be + @code + \\?\Volume{8089d7d7-d0ac-11db-9dd0-806d6172696f}\Program Files\setup.exe + @endcode + + @since 2.9.1 + */ + static bool IsMSWUniqueVolumeNamePath(const wxString& path, + wxPathFormat format = wxPATH_NATIVE); + /** Returns @true if this filename is not absolute. */ diff --git a/src/common/filename.cpp b/src/common/filename.cpp index 49c5ed0f57..ce4e9f2d31 100644 --- a/src/common/filename.cpp +++ b/src/common/filename.cpp @@ -22,7 +22,16 @@ drive:\dir1\dir2\...\dirN\filename.ext where drive is a single letter. "." and ".." as for Unix but no "~". - There are also UNC names of the form \\share\fullpath + There are also UNC names of the form \\share\fullpath and + MSW unique volume names of the form \\?\Volume{GUID}\fullpath. + + The latter provide a uniform way to access a volume regardless of + its current mount point, i.e. you can change a volume's mount + point from D: to E:, or even remove it, and still be able to + access it through its unique volume name. More on the subject can + be found in MSDN's article "Naming a Volume" that is currently at + http://msdn.microsoft.com/en-us/library/aa365248(VS.85).aspx. + wxPATH_MAC: Mac OS 8/9 and Mac OS X under CodeWarrior 7 format, absolute file names have the form @@ -296,7 +305,18 @@ static wxString wxGetVolumeString(const wxString& volume, wxPathFormat format) // although I didn't find any authoritative docs on this) if ( format == wxPATH_DOS && volume.length() > 1 ) { - path << wxFILE_SEP_PATH_DOS << wxFILE_SEP_PATH_DOS << volume; + // We also have to check for Windows unique volume names here and + // return it with '\\?\' prepended to it + if ( wxFileName::IsMSWUniqueVolumeNamePath("\\\\?\\" + volume + "\\", + format) ) + { + path << "\\\\?\\" << volume; + } + else + { + // it must be a UNC path + path << wxFILE_SEP_PATH_DOS << wxFILE_SEP_PATH_DOS << volume; + } } else if ( format == wxPATH_DOS || format == wxPATH_VMS ) { @@ -326,6 +346,13 @@ static bool IsUNCPath(const wxString& path, wxPathFormat format) !IsDOSPathSep(path[2u]); } +// ---------------------------------------------------------------------------- +// private constants +// ---------------------------------------------------------------------------- + +// length of \\?\Volume{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}\ string +static const size_t wxMSWUniqueVolumePrefixLength = 49; + } // anonymous namespace // ============================================================================ @@ -620,12 +647,17 @@ bool wxFileName::DirExists( const wxString &dirPath ) #if defined(__WINDOWS__) || defined(__OS2__) // Windows fails to find directory named "c:\dir\" even if "c:\dir" exists, // so remove all trailing backslashes from the path - but don't do this for - // the paths "d:\" (which are different from "d:") nor for just "\" + // the paths "d:\" (which are different from "d:"), for just "\" or for + // windows unique volume names ("\\?\Volume{GUID}\") while ( wxEndsWithPathSeparator(strPath) ) { size_t len = strPath.length(); - if ( len == 1 || (len == 3 && strPath[len - 2] == wxT(':')) ) + if ( len == 1 || (len == 3 && strPath[len - 2] == wxT(':')) || + (len == wxMSWUniqueVolumePrefixLength && + wxFileName::IsMSWUniqueVolumeNamePath(strPath))) + { break; + } strPath.Truncate(len - 1); } @@ -1810,6 +1842,18 @@ bool wxFileName::IsPathSeparator(wxChar ch, wxPathFormat format) return ch != wxT('\0') && GetPathSeparators(format).Find(ch) != wxNOT_FOUND; } +/* static */ +bool +wxFileName::IsMSWUniqueVolumeNamePath(const wxString& path, wxPathFormat format) +{ + // return true if the format used is the DOS/Windows one and the string begins + // with a Windows unique volume name ("\\?\Volume{guid}\") + return format == wxPATH_DOS && + path.length() >= wxMSWUniqueVolumePrefixLength && + path.StartsWith(wxS("\\\\?\\Volume{")) && + path[wxMSWUniqueVolumePrefixLength - 1] == wxFILE_SEP_PATH_DOS; +} + // ---------------------------------------------------------------------------- // path components manipulation // ---------------------------------------------------------------------------- @@ -2194,9 +2238,26 @@ wxFileName::SplitVolume(const wxString& fullpathWithVolume, wxString fullpath = fullpathWithVolume; - // special Windows UNC paths hack: transform \\share\path into share:path - if ( IsUNCPath(fullpath, format) ) + if ( IsMSWUniqueVolumeNamePath(fullpath, format) ) { + // special Windows unique volume names hack: transform + // \\?\Volume{guid}\path into Volume{guid}:path + // note: this check must be done before the check for UNC path + + // we know the last backslash from the unique volume name is located + // there from IsMSWUniqueVolumeNamePath + fullpath[wxMSWUniqueVolumePrefixLength - 1] = wxFILE_SEP_DSK; + + // paths starting with a unique volume name should always be absolute + fullpath.insert(wxMSWUniqueVolumePrefixLength, 1, wxFILE_SEP_PATH_DOS); + + // remove the leading "\\?\" part + fullpath.erase(0, 4); + } + else if ( IsUNCPath(fullpath, format) ) + { + // special Windows UNC paths hack: transform \\share\path into share:path + fullpath.erase(0, 2); size_t posFirstSlash = diff --git a/tests/filename/filenametest.cpp b/tests/filename/filenametest.cpp index 70d3dced98..c1a9c5e6d4 100644 --- a/tests/filename/filenametest.cpp +++ b/tests/filename/filenametest.cpp @@ -69,6 +69,10 @@ static struct TestFileNameInfo { "c:foo.bar", "c", "", "foo", "bar", false, wxPATH_DOS }, { "c:\\foo.bar", "c", "\\", "foo", "bar", true, wxPATH_DOS }, { "c:\\Windows\\command.com", "c", "\\Windows", "command", "com", true, wxPATH_DOS }, + { "\\\\?\\Volume{8089d7d7-d0ac-11db-9dd0-806d6172696f}\\", + "Volume{8089d7d7-d0ac-11db-9dd0-806d6172696f}", "\\", "", "", true, wxPATH_DOS }, + { "\\\\?\\Volume{8089d7d7-d0ac-11db-9dd0-806d6172696f}\\Program Files\\setup.exe", + "Volume{8089d7d7-d0ac-11db-9dd0-806d6172696f}", "\\Program Files", "setup", "exe", true, wxPATH_DOS }, #if 0 // NB: when using the wxFileName::GetLongPath() function on these two @@ -125,6 +129,7 @@ private: CPPUNIT_TEST( TestShortLongPath ); #endif // __WINDOWS__ CPPUNIT_TEST( TestUNC ); + CPPUNIT_TEST( TestVolumeUniqueName ); CPPUNIT_TEST_SUITE_END(); void TestConstruction(); @@ -138,6 +143,7 @@ private: void TestShortLongPath(); #endif // __WINDOWS__ void TestUNC(); + void TestVolumeUniqueName(); DECLARE_NO_COPY_CLASS(FileNameTestCase) }; @@ -507,3 +513,23 @@ void FileNameTestCase::TestUNC() CPPUNIT_ASSERT_EQUAL( "\\path2", fn.GetPath(wxPATH_NO_SEPARATOR, wxPATH_DOS) ); } +void FileNameTestCase::TestVolumeUniqueName() +{ + wxFileName fn("\\\\?\\Volume{8089d7d7-d0ac-11db-9dd0-806d6172696f}\\", + wxPATH_DOS); + CPPUNIT_ASSERT_EQUAL( "Volume{8089d7d7-d0ac-11db-9dd0-806d6172696f}", + fn.GetVolume() ); + CPPUNIT_ASSERT_EQUAL( "\\", fn.GetPath(wxPATH_NO_SEPARATOR, wxPATH_DOS) ); + CPPUNIT_ASSERT_EQUAL( "\\\\?\\Volume{8089d7d7-d0ac-11db-9dd0-806d6172696f}\\", + fn.GetFullPath(wxPATH_DOS) ); + + fn.Assign("\\\\?\\Volume{8089d7d7-d0ac-11db-9dd0-806d6172696f}\\" + "Program Files\\setup.exe", wxPATH_DOS); + CPPUNIT_ASSERT_EQUAL( "Volume{8089d7d7-d0ac-11db-9dd0-806d6172696f}", + fn.GetVolume() ); + CPPUNIT_ASSERT_EQUAL( "\\Program Files", + fn.GetPath(wxPATH_NO_SEPARATOR, wxPATH_DOS) ); + CPPUNIT_ASSERT_EQUAL( "\\\\?\\Volume{8089d7d7-d0ac-11db-9dd0-806d6172696f}\\" + "Program Files\\setup.exe", + fn.GetFullPath(wxPATH_DOS) ); +}