aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2016-07-30 16:36:53 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2016-07-30 16:36:53 +0200
commite37cf91f24fc409fa0aa84500245f57c685fc8ea (patch)
tree2c0730f6c4226b054cb5eaf640fd0bb704536dae
parent6597c9b777b608a96974b4a7a8c15234b05ffdd8 (diff)
Implement support for Windows path actualization
-rw-r--r--butl/filesystem4
-rw-r--r--butl/filesystem.cxx1
-rw-r--r--butl/path23
-rw-r--r--butl/path-map4
-rw-r--r--butl/path.cxx46
-rw-r--r--butl/path.ixx26
-rw-r--r--butl/path.txx50
-rw-r--r--tests/path/driver.cxx22
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 <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 ();