| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541 |
- /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
- file Copyright.txt or https://cmake.org/licensing for details. */
- #include "cmPathResolver.h"
- #include <algorithm>
- #include <cerrno>
- #include <cstddef>
- #include <string>
- #include <utility>
- #include <cm/optional>
- #include <cm/string_view>
- #include <cmext/string_view>
- #ifdef _WIN32
- # include <cctype>
- # include <windows.h>
- #endif
- #define MAX_SYMBOLIC_LINKS 32
- namespace cm {
- namespace PathResolver {
- namespace {
- namespace Options {
- enum class ActualCase
- {
- No,
- Yes,
- };
- enum class Symlinks
- {
- None,
- Lazy,
- Eager,
- };
- enum class Existence
- {
- Agnostic,
- Required,
- };
- }
- enum class Root
- {
- None,
- POSIX,
- #ifdef _WIN32
- Drive,
- Network,
- #endif
- };
- struct Control
- {
- enum class Tag
- {
- Continue,
- Restart,
- Error,
- };
- Tag tag;
- union
- {
- std::string::size_type slash; // data for Continue
- cmsys::Status error; // data for Error
- };
- static Control Continue(std::string::size_type s)
- {
- Control c{ Tag::Continue };
- c.slash = s;
- return c;
- }
- static Control Restart() { return Control{ Tag::Restart }; }
- static Control Error(cmsys::Status e)
- {
- Control c{ Tag::Error };
- c.error = e;
- return c;
- }
- private:
- Control(Tag t)
- : tag(t)
- {
- }
- };
- Root ClassifyRoot(cm::string_view p)
- {
- #ifdef _WIN32
- if (p.size() >= 2 && std::isalpha(p[0]) && p[1] == ':') {
- return Root::Drive;
- }
- if (p.size() >= 3 && p[0] == '/' && p[1] == '/' && p[2] != '/') {
- return Root::Network;
- }
- #endif
- if (!p.empty() && p[0] == '/') {
- return Root::POSIX;
- }
- return Root::None;
- }
- class ImplBase
- {
- protected:
- ImplBase(System& os)
- : OS(os)
- {
- }
- System& OS;
- std::string P;
- std::size_t SymlinkDepth = 0;
- #ifdef _WIN32
- std::string GetWorkingDirectoryOnDrive(char letter);
- Control ResolveRootRelative();
- #endif
- cm::optional<std::string> ReadSymlink(std::string const& path,
- cmsys::Status& status);
- Control ResolveSymlink(Root root, std::string::size_type slash,
- std::string::size_type next_slash,
- std::string symlink_target);
- };
- template <class Policy>
- class Impl : public ImplBase
- {
- Control ResolveRelativePath();
- Control ResolveRoot(Root root);
- Control ResolveComponent(Root root, std::string::size_type root_slash,
- std::string::size_type slash);
- Control ResolvePath();
- public:
- Impl(System& os)
- : ImplBase(os)
- {
- }
- cmsys::Status Resolve(std::string in, std::string& out);
- };
- template <class Policy>
- Control Impl<Policy>::ResolveRelativePath()
- {
- // This is a relative path. Convert it to absolute and restart.
- std::string p = this->OS.GetWorkingDirectory();
- std::replace(p.begin(), p.end(), '\\', '/');
- if (ClassifyRoot(p) == Root::None) {
- p.insert(0, 1, '/');
- }
- if (p.back() != '/') {
- p.push_back('/');
- }
- P.insert(0, p);
- return Control::Restart();
- }
- #ifdef _WIN32
- std::string ImplBase::GetWorkingDirectoryOnDrive(char letter)
- {
- // Use the drive's working directory, if any.
- std::string d = this->OS.GetWorkingDirectoryOnDrive(letter);
- std::replace(d.begin(), d.end(), '\\', '/');
- if (d.size() >= 3 && std::toupper(d[0]) == std::toupper(letter) &&
- d[1] == ':' && d[2] == '/') {
- d[0] = letter;
- d.push_back('/');
- return d;
- }
- // Use the current working directory if the drive matches.
- d = this->OS.GetWorkingDirectory();
- if (d.size() >= 3 && std::toupper(d[0]) == std::toupper(letter) &&
- d[1] == ':' && d[2] == '/') {
- d[0] = letter;
- d.push_back('/');
- return d;
- }
- // Fall back to the root directory on the drive.
- d = "_:/";
- d[0] = letter;
- return d;
- }
- Control ImplBase::ResolveRootRelative()
- {
- // This is a root-relative path. Resolve the root drive and restart.
- P.replace(0, 2, this->GetWorkingDirectoryOnDrive(P[0]));
- return Control::Restart();
- }
- #endif
- cm::optional<std::string> ImplBase::ReadSymlink(std::string const& path,
- cmsys::Status& status)
- {
- cm::optional<std::string> result;
- std::string target;
- status = this->OS.ReadSymlink(path, target);
- if (status && ++this->SymlinkDepth >= MAX_SYMBOLIC_LINKS) {
- status = cmsys::Status::POSIX(ELOOP);
- }
- if (status) {
- if (!target.empty()) {
- result = std::move(target);
- }
- } else if (status.GetPOSIX() == EINVAL
- #ifdef _WIN32
- || status.GetWindows() == ERROR_NOT_A_REPARSE_POINT
- #endif
- ) {
- // The path was not a symlink.
- status = cmsys::Status::Success();
- }
- return result;
- }
- Control ImplBase::ResolveSymlink(Root root, std::string::size_type slash,
- std::string::size_type next_slash,
- std::string symlink_target)
- {
- std::replace(symlink_target.begin(), symlink_target.end(), '\\', '/');
- Root const symlink_target_root = ClassifyRoot(symlink_target);
- if (symlink_target_root == Root::None) {
- // This is a symlink to a relative path.
- // Resolve the symlink, while preserving the leading and
- // trailing (if any) slash:
- // "*/link/" => "*/dest/"
- // ^slash ^slash
- P.replace(slash + 1, next_slash - slash - 1, symlink_target);
- return Control::Continue(slash);
- }
- #ifdef _WIN32
- if (root == Root::Drive && symlink_target_root == Root::POSIX) {
- // This is a symlink to a POSIX absolute path,
- // but the current path is on a drive letter. Resolve the
- // symlink while preserving the drive letter, and start over:
- // "C:/*/link/" => "C:/dest/"
- // ^slash (restart)
- P.replace(2, next_slash - 2, symlink_target);
- return Control::Restart();
- }
- #else
- static_cast<void>(root);
- #endif
- // This is a symlink to an absolute path.
- // Resolve it and start over:
- // "*/link/" => "/dest/"
- // ^slash (restart)
- P.replace(0, next_slash, symlink_target);
- return Control::Restart();
- }
- template <class Policy>
- Control Impl<Policy>::ResolveRoot(Root root)
- {
- if (root == Root::None) {
- return this->ResolveRelativePath();
- }
- // POSIX absolute paths always start with a '/'.
- std::string::size_type root_slash = 0;
- #ifdef _WIN32
- if (root == Root::Drive) {
- if (P.size() == 2 || P[2] != '/') {
- return this->ResolveRootRelative();
- }
- if (Policy::ActualCase == Options::ActualCase::Yes) {
- // Normalize the drive letter to upper-case.
- P[0] = static_cast<char>(std::toupper(P[0]));
- }
- // The root is a drive letter. The root '/' immediately follows.
- root_slash = 2;
- } else if (root == Root::Network) {
- // The root is a network name. Find the root '/' after it.
- root_slash = P.find('/', 2);
- if (root_slash == std::string::npos) {
- root_slash = P.size();
- P.push_back('/');
- }
- }
- #endif
- if (Policy::Existence == Options::Existence::Required
- #ifdef _WIN32
- && root != Root::Network
- #endif
- ) {
- std::string path = P.substr(0, root_slash + 1);
- if (!this->OS.PathExists(path)) {
- P = std::move(path);
- return Control::Error(cmsys::Status::POSIX(ENOENT));
- }
- }
- return Control::Continue(root_slash);
- }
- template <class Policy>
- Control Impl<Policy>::ResolveComponent(Root root,
- std::string::size_type root_slash,
- std::string::size_type slash)
- {
- // Look for the '/' or end-of-input that ends this component.
- // The sample paths in comments below show the trailing slash
- // even if it is actually beyond the end of the path.
- std::string::size_type next_slash = P.find('/', slash + 1);
- if (next_slash == std::string::npos) {
- next_slash = P.size();
- }
- cm::string_view c =
- cm::string_view(P).substr(slash + 1, next_slash - (slash + 1));
- if (slash == root_slash) {
- if (c.empty() || c == "."_s || c == ".."_s) {
- // This is an empty, '.', or '..' component at the root.
- // Drop the component and its trailing slash, if any,
- // while preserving the root slash:
- // "//" => "/"
- // "/./" => "/"
- // "/../" => "/"
- // ^slash ^slash
- P.erase(slash + 1, next_slash - slash);
- return Control::Continue(slash);
- }
- } else {
- if (c.empty() || c == "."_s) {
- // This is an empty or '.' component not at the root.
- // Drop the component and its leading slash:
- // "*//" => "*/"
- // "*/./" => "*/"
- // ^slash ^slash
- P.erase(slash, next_slash - slash);
- return Control::Continue(slash);
- }
- if (c == ".."_s) {
- // This is a '..' component not at the root.
- // Rewind to the previous component:
- // "*/prev/../" => "*/prev/../"
- // ^slash ^slash
- next_slash = slash;
- slash = P.rfind('/', slash - 1);
- if (Policy::Symlinks == Options::Symlinks::Lazy) {
- cmsys::Status status;
- std::string path = P.substr(0, next_slash);
- if (cm::optional<std::string> maybe_symlink_target =
- this->ReadSymlink(path, status)) {
- return this->ResolveSymlink(root, slash, next_slash,
- std::move(*maybe_symlink_target));
- }
- if (!status && Policy::Existence == Options::Existence::Required) {
- P = std::move(path);
- return Control::Error(status);
- }
- }
- // This is not a symlink.
- // Drop the component, the following '..', and its trailing slash,
- // if any, while preserving the (possibly root) leading slash:
- // "*/dir/../" => "*/"
- // ^slash ^slash
- P.erase(slash + 1, next_slash + 3 - slash);
- return Control::Continue(slash);
- }
- }
- // This is a named component.
- if (Policy::Symlinks == Options::Symlinks::Eager) {
- cmsys::Status status;
- std::string path = P.substr(0, next_slash);
- if (cm::optional<std::string> maybe_symlink_target =
- this->ReadSymlink(path, status)) {
- return this->ResolveSymlink(root, slash, next_slash,
- std::move(*maybe_symlink_target));
- }
- if (!status && Policy::Existence == Options::Existence::Required) {
- P = std::move(path);
- return Control::Error(status);
- }
- }
- #if defined(_WIN32) || defined(__APPLE__)
- bool exists = false;
- if (Policy::ActualCase == Options::ActualCase::Yes) {
- std::string name;
- std::string path = P.substr(0, next_slash);
- if (cmsys::Status status = this->OS.ReadName(path, name)) {
- exists = true;
- if (!name.empty()) {
- // Rename this component:
- // "*/name/" => "*/Name/"
- // ^slash ^slash
- P.replace(slash + 1, next_slash - slash - 1, name);
- next_slash = slash + 1 + name.length();
- }
- } else if (Policy::Existence == Options::Existence::Required) {
- P = std::move(path);
- return Control::Error(status);
- }
- }
- #endif
- if (Policy::Existence == Options::Existence::Required
- #if defined(_WIN32) || defined(__APPLE__)
- && !exists
- #endif
- ) {
- std::string path = P.substr(0, next_slash);
- if (!this->OS.PathExists(path)) {
- P = std::move(path);
- return Control::Error(cmsys::Status::POSIX(ENOENT));
- }
- }
- // Keep this component:
- // "*/name/" => "*/name/"
- // ^slash ^slash
- return Control::Continue(next_slash);
- }
- template <class Policy>
- Control Impl<Policy>::ResolvePath()
- {
- Root const root = ClassifyRoot(P);
- // Resolve the root component. It always ends in a slash.
- Control control = this->ResolveRoot(root);
- if (control.tag != Control::Tag::Continue) {
- return control;
- }
- std::string::size_type const root_slash = control.slash;
- // Resolve later components. Every iteration that finishes
- // the loop body makes progress either by removing a component
- // or advancing the slash past it.
- for (std::string::size_type slash = root_slash;
- P.size() > root_slash + 1 && slash < P.size();) {
- control = this->ResolveComponent(root, root_slash, slash);
- if (control.tag != Control::Tag::Continue) {
- return control;
- }
- slash = control.slash;
- }
- return Control::Continue(P.size());
- }
- template <class Policy>
- cmsys::Status Impl<Policy>::Resolve(std::string in, std::string& out)
- {
- P = std::move(in);
- std::replace(P.begin(), P.end(), '\\', '/');
- for (;;) {
- Control control = this->ResolvePath();
- switch (control.tag) {
- case Control::Tag::Continue:
- out = std::move(P);
- return cmsys::Status::Success();
- case Control::Tag::Restart:
- continue;
- case Control::Tag::Error:
- out = std::move(P);
- return control.error;
- };
- }
- }
- }
- namespace Policies {
- struct NaivePath
- {
- #if defined(_WIN32) || defined(__APPLE__)
- static constexpr Options::ActualCase ActualCase = Options::ActualCase::No;
- #endif
- static constexpr Options::Symlinks Symlinks = Options::Symlinks::None;
- static constexpr Options::Existence Existence = Options::Existence::Agnostic;
- };
- struct RealPath
- {
- #if defined(_WIN32) || defined(__APPLE__)
- static constexpr Options::ActualCase ActualCase = Options::ActualCase::Yes;
- #endif
- static constexpr Options::Symlinks Symlinks = Options::Symlinks::Eager;
- static constexpr Options::Existence Existence = Options::Existence::Required;
- };
- struct LogicalPath
- {
- #if defined(_WIN32) || defined(__APPLE__)
- static constexpr Options::ActualCase ActualCase = Options::ActualCase::Yes;
- #endif
- static constexpr Options::Symlinks Symlinks = Options::Symlinks::Lazy;
- static constexpr Options::Existence Existence = Options::Existence::Agnostic;
- };
- #if defined(__SUNPRO_CC)
- constexpr Options::Symlinks NaivePath::Symlinks;
- constexpr Options::Existence NaivePath::Existence;
- constexpr Options::Symlinks RealPath::Symlinks;
- constexpr Options::Existence RealPath::Existence;
- constexpr Options::Symlinks LogicalPath::Symlinks;
- constexpr Options::Existence LogicalPath::Existence;
- #endif
- }
- template <class Policy>
- Resolver<Policy>::Resolver(System& os)
- : OS(os)
- {
- }
- template <class Policy>
- cmsys::Status Resolver<Policy>::Resolve(std::string in, std::string& out) const
- {
- return Impl<Policy>(OS).Resolve(std::move(in), out);
- }
- System::System() = default;
- System::~System() = default;
- template class Resolver<Policies::LogicalPath>;
- template class Resolver<Policies::RealPath>;
- template class Resolver<Policies::NaivePath>;
- }
- }
|