From e37cf91f24fc409fa0aa84500245f57c685fc8ea Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Sat, 30 Jul 2016 16:36:53 +0200 Subject: Implement support for Windows path actualization --- butl/filesystem | 4 ++-- butl/filesystem.cxx | 1 + butl/path | 23 +++++++++++++++++------ butl/path-map | 4 ++-- butl/path.cxx | 46 ++++++++++++++++++++++++++++++++++++++++++++-- butl/path.ixx | 26 ++++++++++++++++++++++---- butl/path.txx | 50 ++++++++++++++++++++++++++++++++++++++++++++------ 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 diff --git a/butl/path b/butl/path index 4a80b9b..8148b00 100644 --- a/butl/path +++ b/butl/path @@ -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 -# include // _MAX_PATH, _wgetenv() -# include // _[w]getcwd(), _[w]chdir() +# include // _find*() +# include // _MAX_PATH, _wgetenv() +# include // _[w]getcwd(), _[w]chdir() # include // SHGetFolderPath*(), CSIDL_PROFILE # include // SUCCEEDED() #else @@ -26,6 +27,7 @@ #include #include +#include // strcpy() #include #include @@ -341,4 +343,44 @@ namespace butl assert (false); // Implement if/when needed. } #endif + +#ifdef _WIN32 + template <> + LIBBUTL_EXPORT bool + basic_path_append_actual_name (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 (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 // std::tolower -# include // std::towlower +# include // tolower(), toupper() +# include // towlower(), towupper() #endif namespace butl @@ -23,6 +23,20 @@ namespace butl { return std::towlower (c); } + + template <> + inline char path_traits:: + toupper (char c) + { + return std::toupper (c); + } + + template <> + inline wchar_t path_traits:: + toupper (wchar_t c) + { + return std::towupper (c); + } #endif template @@ -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 +#include 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 + bool + basic_path_append_actual_name (std::basic_string& result, + const std::basic_string& dir, + const std::basic_string& name); +#endif + template basic_path& basic_path:: - 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 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 (); -- cgit v1.1