From f0325f7946fba0a8888f93c19d528b88688e98ca Mon Sep 17 00:00:00 2001 From: Jack Andersen Date: Mon, 5 Oct 2015 15:49:23 -1000 Subject: [PATCH] Added link resolution to ProjectPath --- hecl/include/HECL/HECL.hpp | 31 ++++++++++++++- hecl/include/HECL/winsupport.hpp | 1 + hecl/lib/ProjectPath.cpp | 66 ++++++++++++++++++++++++++------ hecl/lib/winsupport.cpp | 44 ++++++++++++++++++++- 4 files changed, 128 insertions(+), 14 deletions(-) diff --git a/hecl/include/HECL/HECL.hpp b/hecl/include/HECL/HECL.hpp index 5bb7fd70d..3e3f4cff3 100644 --- a/hecl/include/HECL/HECL.hpp +++ b/hecl/include/HECL/HECL.hpp @@ -467,6 +467,28 @@ public: */ const SystemString& getAbsolutePath() const {return m_projRoot;} + /** + * @brief Make absolute path project relative + * @param absPath + * @return + */ + const SystemString getProjectRelativeFromAbsolute(const SystemString& absPath) const + { + if (absPath.size() > m_projRoot.size()) + { + if (!absPath.compare(0, m_projRoot.size(), m_projRoot)) + { + auto beginIt = absPath.cbegin() + m_projRoot.size(); + while (*beginIt == _S('/') || *beginIt == _S('\\')) + ++beginIt; + return SystemString(beginIt, absPath.cend()); + } + } + LogModule.report(LogVisor::FatalError, "unable to resolve '%s' as project relative '%s'", + absPath.c_str(), m_projRoot.c_str()); + return SystemString(); + } + /** * @brief Create directory at path * @@ -686,7 +708,8 @@ public: PT_NONE, /**< If path doesn't reference a valid filesystem entity, this is returned */ PT_FILE, /**< Singular file path (confirmed with filesystem) */ PT_DIRECTORY, /**< Singular directory path (confirmed with filesystem) */ - PT_GLOB /**< Glob-path (whenever one or more '*' occurs in syntax) */ + PT_GLOB, /**< Glob-path (whenever one or more '*' occurs in syntax) */ + PT_LINK /**< Link (symlink on POSIX, ShellLink on Windows) */ }; /** @@ -705,6 +728,12 @@ public: */ Time getModtime() const; + /** + * @brief For link paths, get the target path + * @return Target path + */ + ProjectPath resolveLink() const; + /** * @brief Insert directory children into list * @param outPaths list to append children to diff --git a/hecl/include/HECL/winsupport.hpp b/hecl/include/HECL/winsupport.hpp index 2db5d7f00..81a564690 100644 --- a/hecl/include/HECL/winsupport.hpp +++ b/hecl/include/HECL/winsupport.hpp @@ -9,5 +9,6 @@ void* memmem(const void *haystack, size_t hlen, const void *needle, size_t nlen); HRESULT CreateShellLink(LPCWSTR lpszPathObj, LPCWSTR lpszPathLink, LPCWSTR lpszDesc); HRESULT ResolveShellLink(LPCWSTR lpszLinkFile, LPWSTR lpszPath, int iPathBufferSize); +bool TestShellLink(LPCWSTR lpszLinkFile); #endif // _HECL_WINSUPPORT_H_ diff --git a/hecl/lib/ProjectPath.cpp b/hecl/lib/ProjectPath.cpp index 96eeb70fc..c4cd77058 100644 --- a/hecl/lib/ProjectPath.cpp +++ b/hecl/lib/ProjectPath.cpp @@ -8,15 +8,22 @@ static const SystemRegex regGLOB(_S("\\*"), SystemRegex::ECMAScript|SystemRegex: static const SystemRegex regPATHCOMP(_S("[/\\\\]*([^/\\\\]+)"), SystemRegex::ECMAScript|SystemRegex::optimize); static const SystemRegex regDRIVELETTER(_S("^([^/]*)/"), SystemRegex::ECMAScript|SystemRegex::optimize); -static SystemString canonRelPath(const SystemString& path) +static bool IsAbsolute(const SystemString& path) { - /* Absolute paths not allowed */ - if (path[0] == _S('/') || path[0] == _S('\\')) - { - LogModule.report(LogVisor::Error, "Absolute path provided; expected relative: %s", path.c_str()); - return _S("."); - } +#if WIN32 + if (path.size() && (path[0] == _S('\\') || path[0] == _S('/'))) + return true; + if (path.size() >= 2 && iswalpha(path[0]) && path[1] == _S(':')) + return true; +#else + if (path[0] == _S('/')) + return true; +#endif + return false; +} +static SystemString CanonRelPath(const SystemString& path) +{ /* Tokenize Path */ std::vector comps; HECL::SystemRegexMatch matches; @@ -48,18 +55,29 @@ static SystemString canonRelPath(const SystemString& path) SystemString retval = *it; for (++it ; it != comps.end() ; ++it) { - retval += _S('/'); - retval += *it; + if ((*it).size()) + { + retval += _S('/'); + retval += *it; + } } return retval; } return _S("."); } +static SystemString CanonRelPath(const SystemString& path, const ProjectRootPath& projectRoot) +{ + /* Absolute paths not allowed; attempt to make project-relative */ + if (IsAbsolute(path)) + return CanonRelPath(projectRoot.getProjectRelativeFromAbsolute(path)); + return CanonRelPath(path); +} + void ProjectPath::assign(Database::Project& project, const SystemString& path) { m_proj = &project; - m_relPath = canonRelPath(path); + m_relPath = CanonRelPath(path); m_absPath = project.getProjectRootPath().getAbsolutePath() + _S('/') + m_relPath; SanitizePath(m_relPath); SanitizePath(m_absPath); @@ -89,7 +107,7 @@ void ProjectPath::assign(Database::Project& project, const std::string& path) void ProjectPath::assign(const ProjectPath& parentPath, const SystemString& path) { m_proj = parentPath.m_proj; - m_relPath = canonRelPath(parentPath.m_relPath + _S('/') + path); + m_relPath = CanonRelPath(parentPath.m_relPath + _S('/') + path); m_absPath = m_proj->getProjectRootPath().getAbsolutePath() + _S('/') + m_relPath; SanitizePath(m_relPath); SanitizePath(m_absPath); @@ -124,6 +142,16 @@ ProjectPath ProjectPath::getCookedPath(const Database::DataSpecEntry& spec) cons ProjectPath::PathType ProjectPath::getPathType() const { +#if WIN32 + if (TestShellLink(m_absPath.c_str())) + return PT_LINK; +#else + HECL::Sstat lnStat; + if (lstat(m_absPath.c_str(), &lnStat)) + return PT_NONE; + if (S_ISLNK(lnStat.st_mode)) + return PT_LINK; +#endif if (std::regex_search(m_absPath, regGLOB)) return PT_GLOB; Sstat theStat; @@ -184,6 +212,22 @@ Time ProjectPath::getModtime() const return Time(); } +ProjectPath ProjectPath::resolveLink() const +{ +#if WIN32 + wchar_t target[2048]; + if (FAILED(ResolveShellLink(m_absPath.c_str(), target, 2048))) + LogModule.report(LogVisor::FatalError, _S("unable to resolve link '%s'"), m_absPath.c_str()); +#else + char target[2048]; + ssize_t targetSz; + if ((targetSz = readlink(m_absPath.c_str(), target, 2048)) < 0) + LogModule.report(LogVisor::FatalError, _S("unable to resolve link '%s': %s"), m_absPath.c_str(), strerror(errno)); + target[targetSz] = '\0'; +#endif + return ProjectPath(*this, target); +} + static void _recursiveGlob(Database::Project& proj, std::vector& outPaths, size_t level, diff --git a/hecl/lib/winsupport.cpp b/hecl/lib/winsupport.cpp index 735720fc8..a303c5765 100644 --- a/hecl/lib/winsupport.cpp +++ b/hecl/lib/winsupport.cpp @@ -106,8 +106,6 @@ HRESULT ResolveShellLink(LPCWSTR lpszLinkFile, LPWSTR lpszPath, int iPathBufferS *lpszPath = 0; // Assume failure - // Get a pointer to the IShellLink interface. It is assumed that CoInitialize - // has already been called. hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl); if (SUCCEEDED(hres)) { @@ -157,4 +155,46 @@ HRESULT ResolveShellLink(LPCWSTR lpszLinkFile, LPWSTR lpszPath, int iPathBufferS return hres; } +bool TestShellLink(LPCWSTR lpszLinkFile) +{ + HRESULT hres; + IShellLink* psl; + + hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl); + if (SUCCEEDED(hres)) + { + IPersistFile* ppf; + + // Get a pointer to the IPersistFile interface. + hres = psl->QueryInterface(IID_IPersistFile, (void**)&ppf); + + if (SUCCEEDED(hres)) + { + // Load the shortcut. + hres = ppf->Load(lpszLinkFile, STGM_READ); + + if (SUCCEEDED(hres)) + { + // Resolve the link. + HWND hwnd = GetConsoleWindow(); + if (!hwnd) + hwnd = GetTopWindow(nullptr); + hres = psl->Resolve(hwnd, 0); + + if (SUCCEEDED(hres)) + { + return true; + } + } + + // Release the pointer to the IPersistFile interface. + ppf->Release(); + } + + // Release the pointer to the IShellLink interface. + psl->Release(); + } + return false; +} +