diff options
author | Boris Kolpackov <boris@codesynthesis.com> | 2016-07-30 16:36:53 +0200 |
---|---|---|
committer | Boris Kolpackov <boris@codesynthesis.com> | 2016-07-30 16:36:53 +0200 |
commit | e37cf91f24fc409fa0aa84500245f57c685fc8ea (patch) | |
tree | 2c0730f6c4226b054cb5eaf640fd0bb704536dae | |
parent | 6597c9b777b608a96974b4a7a8c15234b05ffdd8 (diff) |
Implement support for Windows path actualization
-rw-r--r-- | butl/filesystem | 4 | ||||
-rw-r--r-- | butl/filesystem.cxx | 1 | ||||
-rw-r--r-- | butl/path | 23 | ||||
-rw-r--r-- | butl/path-map | 4 | ||||
-rw-r--r-- | butl/path.cxx | 46 | ||||
-rw-r--r-- | butl/path.ixx | 26 | ||||
-rw-r--r-- | butl/path.txx | 50 | ||||
-rw-r--r-- | tests/path/driver.cxx | 22 |
8 files changed, 154 insertions, 22 deletions
diff --git a/butl/filesystem b/butl/filesystem index 566f398..118fa01 100644 --- a/butl/filesystem +++ b/butl/filesystem @@ -338,9 +338,9 @@ namespace butl dir_entry e_; #ifndef _WIN32 - DIR* h_ {nullptr}; + DIR* h_ = nullptr; #else - intptr_t h_ {-1}; + intptr_t h_ = -1; #endif }; diff --git a/butl/filesystem.cxx b/butl/filesystem.cxx index aa9319f..4aa2078 100644 --- a/butl/filesystem.cxx +++ b/butl/filesystem.cxx @@ -515,6 +515,7 @@ namespace butl break; } } + #else // dir_entry @@ -239,10 +239,14 @@ namespace butl realize (string_type&); #endif - private: + // Utilities. + // #ifdef _WIN32 static C tolower (C); + + static C + toupper (C); #endif }; @@ -716,13 +720,20 @@ namespace butl reverse_iterator rend () const {return reverse_iterator (begin ());} public: - // Normalize the path. This includes collapsing the '.' and '..' - // directories if possible, collapsing multiple directory separators, and - // converting all directory separators to the canonical form. Return - // *this. + // Normalize the path and return*this. Normalization involves collapsing + // the '.' and '..' directories if possible, collapsing multiple + // directory separators, and converting all directory separators to the + // canonical form. + // + // If actual is true, then for case-insensitive filesystems obtain the + // actual spelling of the path. Only an absolute path can be actualized. + // If a path component does not exist, then its (and all subsequent) + // spelling is unchanged. This is a potentially expensive operation. + // Normally one can assume that "well-known" directories (current, home, + // etc.) are returned in their actual spelling. // basic_path& - normalize (); + normalize (bool actual = false); // Make the path absolute using the current directory unless it is already // absolute. Return *this. diff --git a/butl/path-map b/butl/path-map index 3852723..4b0445e 100644 --- a/butl/path-map +++ b/butl/path-map @@ -14,8 +14,8 @@ namespace butl { // prefix_map for filesystem paths // - // Important: the paths should be normalized but don't have to be - // canonicalized. + // Important: the paths should be normalized but can use different case + // on case-insensitive platforms. // // Note that the path's representation of POSIX root ('/') is // inconsistent in that we have a trailing delimiter at the end of diff --git a/butl/path.cxx b/butl/path.cxx index 9325c1e..d7a0da9 100644 --- a/butl/path.cxx +++ b/butl/path.cxx @@ -7,8 +7,9 @@ #ifdef _WIN32 # include <butl/win32-utility> -# include <stdlib.h> // _MAX_PATH, _wgetenv() -# include <direct.h> // _[w]getcwd(), _[w]chdir() +# include <io.h> // _find*() +# include <stdlib.h> // _MAX_PATH, _wgetenv() +# include <direct.h> // _[w]getcwd(), _[w]chdir() # include <shlobj.h> // SHGetFolderPath*(), CSIDL_PROFILE # include <winerror.h> // SUCCEEDED() #else @@ -26,6 +27,7 @@ #include <atomic> #include <cassert> +#include <cstring> // strcpy() #include <system_error> #include <butl/export> @@ -341,4 +343,44 @@ namespace butl assert (false); // Implement if/when needed. } #endif + +#ifdef _WIN32 + template <> + LIBBUTL_EXPORT bool + basic_path_append_actual_name<char> (string& r, + const string& d, + const string& n) + { + assert (d.size () + n.size () + 1 < _MAX_PATH); + + char p[_MAX_PATH]; + strcpy (p, d.c_str ()); + p[d.size ()] = '\\'; + strcpy (p + d.size () + 1, n.c_str ()); + + // It could be that using FindFirstFile() is faster. + // + _finddata_t fi; + intptr_t h (_findfirst (p, &fi)); + + if (h == -1 && errno == ENOENT) + return false; + + if (h == -1 || _findclose (h) == -1) + throw system_error (errno, system_category ()); + + r += fi.name; + return true; + } + + template <> + LIBBUTL_EXPORT bool + basic_path_append_actual_name<wchar_t> (wstring&, + const wstring&, + const wstring&) + { + assert (false); // Implement if/when needed. + return false; + } +#endif } diff --git a/butl/path.ixx b/butl/path.ixx index 3d1f20c..a90922a 100644 --- a/butl/path.ixx +++ b/butl/path.ixx @@ -3,8 +3,8 @@ // license : MIT; see accompanying LICENSE file #ifdef _WIN32 -# include <cctype> // std::tolower -# include <cwctype> // std::towlower +# include <cctype> // tolower(), toupper() +# include <cwctype> // towlower(), towupper() #endif namespace butl @@ -23,6 +23,20 @@ namespace butl { return std::towlower (c); } + + template <> + inline char path_traits<char>:: + toupper (char c) + { + return std::toupper (c); + } + + template <> + inline wchar_t path_traits<wchar_t>:: + toupper (wchar_t c) + { + return std::towupper (c); + } #endif template <class C, class K1, class K2> @@ -223,10 +237,14 @@ namespace butl realize () { #ifdef _WIN32 + // This is not exactly the semantics of realpath(3). In particular, we + // don't fail if the path does not exist. But we could have seeing that + // we actualize it. + // complete (); - normalize (); + normalize (true); #else - traits::realize (this->path_); // Note: we retail trailing slash. + traits::realize (this->path_); // Note: we retain the trailing slash. #endif return *this; } diff --git a/butl/path.txx b/butl/path.txx index 1d6995e..08af340 100644 --- a/butl/path.txx +++ b/butl/path.txx @@ -3,6 +3,7 @@ // license : MIT; see accompanying LICENSE file #include <vector> +#include <cassert> namespace butl { @@ -97,18 +98,32 @@ namespace butl return r / leaf (d); } +#ifdef _WIN32 + // Find the actual spelling of a name in the specified dir. If the name is + // found, append it to the result and return true. Otherwise, return false. + // Throw system_error in case of other failures. Result and dir can be the + // same instance. + // + template <typename C> + bool + basic_path_append_actual_name (std::basic_string<C>& result, + const std::basic_string<C>& dir, + const std::basic_string<C>& name); +#endif + template <typename C, typename K> basic_path<C, K>& basic_path<C, K>:: - normalize () + normalize (bool actual) { if (empty ()) return *this; + bool abs (absolute ()); + assert (!actual || abs); // Only absolue can be actualized. + string_type& s (this->path_); difference_type& d (this->diff_); - bool abs (absolute ()); - typedef std::vector<string_type> paths; paths ps; @@ -188,14 +203,37 @@ namespace butl r.push_back (std::move (s)); } - // Reassemble the path. + // Reassemble the path, actualizing each component if requested. // string_type p; - for (typename paths::const_iterator i (r.begin ()), e (r.end ()); + for (typename paths::const_iterator b (r.begin ()), i (b), e (r.end ()); i != e;) { - p += *i; +#ifdef _WIN32 + if (actual) + { + if (i == b) + { + // The first component (the drive letter) we have to actualize + // ourselves. Capital seems to be canonical. This is, for example, + // what getcwd() returns. + // + p = *i; + p[0] = traits::toupper (p[0]); + } + else + { + if (!basic_path_append_actual_name (p, p, *i)) + { + p += *i; + actual = false; // Ignore for all subsequent components. + } + } + } + else +#endif + p += *i; if (++i != e) p += traits::directory_separator; diff --git a/tests/path/driver.cxx b/tests/path/driver.cxx index 7ca36b7..fbcaf33 100644 --- a/tests/path/driver.cxx +++ b/tests/path/driver.cxx @@ -478,6 +478,28 @@ main () assert (path::home ().absolute ()); //assert (wpath::home ().absolute ()); + // normalize and actualize + // +#ifdef _WIN32 + { + auto test = [] (const char* p) + { + return path (p).normalize (true).representation (); + }; + + assert (test ("c:") == "C:"); + assert (test ("c:/") == "C:\\"); + assert (test ("c:\\pROGRAM fILES/") == "C:\\Program Files\\"); + assert (test ("c:\\pROGRAM fILES/NonSense") == + "C:\\Program Files\\NonSense"); + assert (test ("c:\\pROGRAM fILES/NonSense\\sTUFF/") == + "C:\\Program Files\\NonSense\\sTUFF\\"); + + dir_path cwd (path::current ()); + assert (cwd.normalize (true).representation () == cwd.representation ()); + } +#endif + /* path p ("../foo"); p.complete (); |