From eac644d81a030f6f9136a1b16507b75253e4d572 Mon Sep 17 00:00:00 2001 From: Fangrui Song Date: Thu, 30 Nov 2017 10:40:42 -0800 Subject: [PATCH] On *nix, use a realpath(3) variant (which does not expand symlinks) in NormalizePath Before this commit, when a source file is a symlink, cquery is not able to serve LSP requests for it. --- src/platform_linux.cc | 77 ++++++++++++++++++++++++++++++++++++++----- src/utils.h | 2 +- 2 files changed, 70 insertions(+), 9 deletions(-) diff --git a/src/platform_linux.cc b/src/platform_linux.cc index 256c2cdb..fa5e4b76 100644 --- a/src/platform_linux.cc +++ b/src/platform_linux.cc @@ -29,10 +29,8 @@ #include #include -#include /* For O_* constants */ #include #include -#include /* For mode constants */ #ifndef __APPLE__ #include @@ -42,6 +40,73 @@ #include #endif +namespace { + +// Returns the canonicalized absolute pathname, without expanding symbolic links. +// This is a variant of realpath(2), C++ rewrite of +// https://github.com/freebsd/freebsd/blob/master/lib/libc/stdlib/realpath.c +optional RealPathNotExpandSymlink(std::string path) { + if (path.empty()) { + errno = EINVAL; + return nullopt; + } + if (path[0] == '\0') { + errno = ENOENT; + return nullopt; + } + + // Do not use PATH_MAX because it is tricky on Linux. + // See https://eklitzke.org/path-max-is-tricky + char tmp[1024]; + std::string resolved; + size_t i = 0; + struct stat sb; + if (path[0] == '/') { + resolved = "/"; + i = 1; + } else { + if (!getcwd(tmp, sizeof tmp)) + return nullopt; + resolved = tmp; + } + + while (i < path.size()) { + auto j = path.find('/', i); + if (j == std::string::npos) + j = path.size(); + auto next_token = path.substr(i, j - i); + i = j + 1; + if (resolved.back() != '/') + resolved += '/'; + if (next_token.empty() || next_token == ".") { + // Handle consequential slashes and "." + continue; + } else if (next_token == "..") { + // Strip the last path component except when it is single "/" + if (resolved.size() > 1) + resolved.resize(resolved.rfind('/', resolved.size() - 2) + 1); + continue; + } + // Append the next path component. + // Here we differ from realpath(3), we use stat(2) instead of + // lstat(2) because we do not want to resolve symlinks. + resolved += next_token; + if (stat(resolved.c_str(), &sb) != 0) + return nullopt; + if (!S_ISDIR(sb.st_mode) && j < path.size()) { + errno = ENOTDIR; + return nullopt; + } + } + + // Remove trailing slash except when a single "/". + if (resolved.size() > 1 && resolved.back() == '/') + resolved.pop_back(); + return resolved; +} + +} // namespace + struct PlatformMutexLinux : public PlatformMutex { sem_t* sem_ = nullptr; @@ -155,12 +220,8 @@ std::string GetWorkingDirectory() { } std::string NormalizePath(const std::string& path) { - errno = 0; - char name[PATH_MAX + 1]; - (void)realpath(path.c_str(), name); - if (errno) - return path; - return name; + optional resolved = RealPathNotExpandSymlink(path); + return resolved ? *resolved : path; } bool TryMakeDirectory(const std::string& absolute_path) { diff --git a/src/utils.h b/src/utils.h index bf30180d..bf38864f 100644 --- a/src/utils.h +++ b/src/utils.h @@ -158,4 +158,4 @@ inline void hash_combine(std::size_t& seed, const T& v, Rest... rest) { float GetProcessMemoryUsedInMb(); -std::string FormatMicroseconds(long long microseconds); \ No newline at end of file +std::string FormatMicroseconds(long long microseconds);