From: Joel Rosdahl Date: Tue, 11 Feb 2020 20:34:43 +0000 (+0100) Subject: Implement Util::real_path, replacing legacy x_realpath X-Git-Tag: v4.0~621 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=6c1dffd926b336ec8cfec05ebbac4d0acf4a8693;p=thirdparty%2Fccache.git Implement Util::real_path, replacing legacy x_realpath --- diff --git a/Makefile.in b/Makefile.in index cf2c81fbe..ebd29d833 100644 --- a/Makefile.in +++ b/Makefile.in @@ -65,7 +65,8 @@ non_third_party_sources = \ src/logging.cpp \ src/manifest.cpp \ src/result.cpp \ - src/stats.cpp + src/stats.cpp \ + src/win32compat.cpp generated_sources = \ src/version.cpp third_party_sources = \ diff --git a/src/Util.cpp b/src/Util.cpp index 591ef074d..59024626c 100644 --- a/src/Util.cpp +++ b/src/Util.cpp @@ -24,6 +24,10 @@ #include #include +#ifdef _WIN32 +# include "win32compat.hpp" +#endif + using nonstd::string_view; namespace { @@ -70,6 +74,21 @@ get_cache_files_internal(const std::string& dir, } } +size_t +path_max(const char* path) +{ +#ifdef PATH_MAX + (void)path; + return PATH_MAX; +#elif defined(MAXPATHLEN) + (void)path; + return MAXPATHLEN; +#elif defined(_PC_PATH_MAX) + long maxlen = pathconf(path, _PC_PATH_MAX); + return maxlen >= 4096 ? maxlen : 4096; +#endif +} + } // namespace namespace Util { @@ -262,6 +281,56 @@ read_file(const std::string& path) std::istreambuf_iterator()); } +std::string +real_path(const std::string& path, bool return_empty_on_error) +{ + const char* c_path = path.c_str(); + size_t buffer_size = path_max(c_path); + std::unique_ptr managed_buffer(new char[buffer_size]); + char* buffer = managed_buffer.get(); + char* resolved = nullptr; + +#if HAVE_REALPATH + resolved = realpath(c_path, buffer); +#elif defined(_WIN32) + if (c_path[0] == '/') { + c_path++; // Skip leading slash. + } + HANDLE path_handle = CreateFile(c_path, + GENERIC_READ, + FILE_SHARE_READ, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL); + if (INVALID_HANDLE_VALUE != path_handle) { +# ifdef HAVE_GETFINALPATHNAMEBYHANDLEW + GetFinalPathNameByHandle( + path_handle, buffer, buffer_size, FILE_NAME_NORMALIZED); +# else + GetFileNameFromHandle(path_handle, buffer, buffer_size); +# endif + CloseHandle(path_handle); + resolved = buffer + 4; // Strip \\?\ from the file name. + } else { + snprintf(buffer, buffer_size, "%s", c_path); + resolved = buffer; + } +#else + // Yes, there are such systems. This replacement relies on the fact that when + // we call x_realpath we only care about symlinks. + { + ssize_t len = readlink(c_path, buffer, buffer_size - 1); + if (len != -1) { + buffer[len] = 0; + resolved = buffer; + } + } +#endif + + return resolved ? resolved : (return_empty_on_error ? "" : path); +} + string_view remove_extension(string_view path) { diff --git a/src/Util.hpp b/src/Util.hpp index 19237c797..1d70aadcf 100644 --- a/src/Util.hpp +++ b/src/Util.hpp @@ -189,6 +189,12 @@ int parse_int(const std::string& value); // Throws Error on error. std::string read_file(const std::string& path); +// Return a canonicalized absolute path of `path`. On error (e.g. if the `path` +// doesn't exist) the empty string is returned if return_empty_on_error is true, +// otherwise `path` unmodified. +std::string real_path(const std::string& path, + bool return_empty_on_error = false); + // Return a view into `path` containing the given path without the filename // extension as determined by `get_extension()`. nonstd::string_view remove_extension(nonstd::string_view path); diff --git a/src/ccache.cpp b/src/ccache.cpp index 528ccbec5..d83c05e2d 100644 --- a/src/ccache.cpp +++ b/src/ccache.cpp @@ -421,7 +421,7 @@ get_current_working_dir(void) if (!current_working_dir) { char* cwd = get_cwd(); if (cwd) { - current_working_dir = x_realpath(cwd); + current_working_dir = x_strdup(Util::real_path(cwd).c_str()); free(cwd); } if (!current_working_dir) { @@ -647,11 +647,10 @@ make_relative_path(const char* path) char* dir = nullptr; char* path_suffix = nullptr; - char* canon_path = nullptr; char* relpath = nullptr; - // x_realpath only works for existing paths, so if path doesn't exist, try - // x_dirname(path) and assemble the path afterwards. We only bother to try + // Util::real_path only works for existing paths, so if path doesn't exist, + // try x_dirname(path) and assemble the path afterwards. We only bother to try // canonicalizing one of these two paths since a compiler path argument // typically only makes sense if path or x_dirname(path) exists. @@ -676,9 +675,9 @@ make_relative_path(const char* path) std::string result; - canon_path = x_realpath(path); - if (canon_path) { - relpath = get_relative_path(get_current_working_dir(), canon_path); + std::string canon_path = Util::real_path(path, true); + if (!canon_path.empty()) { + relpath = get_relative_path(get_current_working_dir(), canon_path.c_str()); if (path_suffix) { result = fmt::format("{}/{}", relpath, path_suffix); } else { @@ -694,7 +693,6 @@ make_relative_path(const char* path) #endif free(dir); free(path_suffix); - free(canon_path); free(relpath); return result; @@ -1677,23 +1675,19 @@ hash_common_info(Context& ctx, // Possibly hash the coverage data file path. if (ctx.args_info.generating_coverage && ctx.args_info.profile_arcs) { - char* dir = x_dirname(ctx.args_info.output_obj.c_str()); + std::string dir; if (!ctx.args_info.profile_dir.empty()) { - dir = x_strdup(ctx.args_info.profile_dir.c_str()); + dir = ctx.args_info.profile_dir; } else { - char* real_dir = x_realpath(dir); - free(dir); - dir = real_dir; - } - if (dir) { - string_view base_name = Util::base_name(ctx.args_info.output_obj); - string_view p = Util::remove_extension(base_name); - std::string gcda_path = fmt::format("{}/{}.gcda", dir, p); - cc_log("Hashing coverage path %s", gcda_path.c_str()); - hash_delimiter(hash, "gcda"); - hash_string(hash, gcda_path); - free(dir); + dir = + Util::real_path(std::string(Util::dir_name(ctx.args_info.output_obj))); } + string_view stem = + Util::remove_extension(Util::base_name(ctx.args_info.output_obj)); + std::string gcda_path = fmt::format("{}/{}.gcda", dir, stem); + cc_log("Hashing coverage path %s", gcda_path.c_str()); + hash_delimiter(hash, "gcda"); + hash_string(hash, gcda_path); } // Possibly hash the sanitize blacklist file path. @@ -2801,19 +2795,14 @@ cc_process_args(ArgsInfo& args_info, const char* arg_profile_dir = strchr(argv[i], '='); if (arg_profile_dir) { // Convert to absolute path. - char* dir = x_realpath(arg_profile_dir + 1); - if (!dir) { - // Directory doesn't exist. - dir = x_strdup(arg_profile_dir + 1); - } + std::string dir = Util::real_path(arg_profile_dir + 1); // We can get a better hit rate by using the real path here. free(arg); char* option = x_strndup(argv[i], arg_profile_dir - argv[i]); - arg = format("%s=%s", option, dir); + arg = format("%s=%s", option, dir.c_str()); cc_log("Rewriting %s to %s", argv[i], arg); free(option); - free(dir); } bool supported_profile_option = false; diff --git a/src/execute.cpp b/src/execute.cpp index 0413efc96..3d5045e11 100644 --- a/src/execute.cpp +++ b/src/execute.cpp @@ -349,21 +349,17 @@ find_executable_in_path(const char* name, return x_strdup(namebuf); } #else + assert(exclude_name); char* fname = format("%s/%s", tok, name); auto st1 = Stat::lstat(fname); auto st2 = Stat::stat(fname); // Look for a normal executable file. if (st1 && st2 && st2.is_regular() && access(fname, X_OK) == 0) { if (st1.is_symlink()) { - char* buf = x_realpath(fname); - if (buf) { - string_view p = Util::base_name(buf); - if (p == exclude_name) { - // It's a link to "ccache"! - free(buf); - continue; - } - free(buf); + std::string real_path = Util::real_path(fname, true); + if (Util::base_name(real_path) == exclude_name) { + // It's a link to "ccache"! + continue; } } diff --git a/src/legacy_util.cpp b/src/legacy_util.cpp index 728aeb0da..80832c20f 100644 --- a/src/legacy_util.cpp +++ b/src/legacy_util.cpp @@ -54,12 +54,6 @@ # endif #endif -#ifdef _WIN32 -# include -# include -# include -#endif - static long path_max(const char* path) { @@ -603,138 +597,6 @@ parse_size_with_suffix(const char* str, uint64_t* size) return true; } -#if !defined(HAVE_REALPATH) && defined(_WIN32) \ - && !defined(HAVE_GETFINALPATHNAMEBYHANDLEW) -static BOOL -GetFileNameFromHandle(HANDLE file_handle, TCHAR* filename, WORD cch_filename) -{ - BOOL success = FALSE; - - // Get the file size. - DWORD file_size_hi = 0; - DWORD file_size_lo = GetFileSize(file_handle, &file_size_hi); - if (file_size_lo == 0 && file_size_hi == 0) { - // Cannot map a file with a length of zero. - return FALSE; - } - - // Create a file mapping object. - HANDLE file_map = - CreateFileMapping(file_handle, NULL, PAGE_READONLY, 0, 1, NULL); - if (!file_map) { - return FALSE; - } - - // Create a file mapping to get the file name. - void* mem = MapViewOfFile(file_map, FILE_MAP_READ, 0, 0, 1); - if (mem) { - if (GetMappedFileName(GetCurrentProcess(), mem, filename, cch_filename)) { - // Translate path with device name to drive letters. - TCHAR temp[512]; - temp[0] = '\0'; - - if (GetLogicalDriveStrings(512 - 1, temp)) { - TCHAR name[MAX_PATH]; - TCHAR drive[3] = TEXT(" :"); - BOOL found = FALSE; - TCHAR* p = temp; - - do { - // Copy the drive letter to the template string. - *drive = *p; - - // Look up each device name. - if (QueryDosDevice(drive, name, MAX_PATH)) { - size_t name_len = _tcslen(name); - if (name_len < MAX_PATH) { - found = _tcsnicmp(filename, name, name_len) == 0 - && *(filename + name_len) == _T('\\'); - if (found) { - // Reconstruct filename using temp_file and replace device path - // with DOS path. - TCHAR temp_file[MAX_PATH]; - _sntprintf(temp_file, - MAX_PATH - 1, - TEXT("%s%s"), - drive, - filename + name_len); - strcpy(filename, temp_file); - } - } - } - - // Go to the next NULL character. - while (*p++) { - // Do nothing. - } - } while (!found && *p); // End of string. - } - } - success = TRUE; - UnmapViewOfFile(mem); - } - - CloseHandle(file_map); - return success; -} -#endif - -// A sane realpath() function, trying to cope with stupid path limits and a -// broken API. Caller frees. -char* -x_realpath(const char* path) -{ - long maxlen = path_max(path); - char* ret = static_cast(x_malloc(maxlen)); - char* p; - -#if HAVE_REALPATH - p = realpath(path, ret); -#elif defined(_WIN32) - if (path[0] == '/') { - path++; // Skip leading slash. - } - HANDLE path_handle = CreateFile(path, - GENERIC_READ, - FILE_SHARE_READ, - NULL, - OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL, - NULL); - if (INVALID_HANDLE_VALUE != path_handle) { -# ifdef HAVE_GETFINALPATHNAMEBYHANDLEW - GetFinalPathNameByHandle(path_handle, ret, maxlen, FILE_NAME_NORMALIZED); -# else - GetFileNameFromHandle(path_handle, ret, maxlen); -# endif - CloseHandle(path_handle); - p = ret + 4; // Strip \\?\ from the file name. - } else { - snprintf(ret, maxlen, "%s", path); - p = ret; - } -#else - // Yes, there are such systems. This replacement relies on the fact that when - // we call x_realpath we only care about symlinks. - { - int len = readlink(path, ret, maxlen - 1); - if (len == -1) { - free(ret); - return NULL; - } - ret[len] = 0; - p = ret; - } -#endif - if (p) { - p = x_strdup(p); - free(ret); - return p; - } - free(ret); - return NULL; -} - // A getcwd that will returns an allocated buffer. char* gnu_getcwd(void) diff --git a/src/legacy_util.hpp b/src/legacy_util.hpp index 55f8902c2..b21a25765 100644 --- a/src/legacy_util.hpp +++ b/src/legacy_util.hpp @@ -44,7 +44,6 @@ const char* get_extension(const char* path); char* format_human_readable_size(uint64_t size); char* format_parsable_size_with_suffix(uint64_t size); bool parse_size_with_suffix(const char* str, uint64_t* size); -char* x_realpath(const char* path); char* gnu_getcwd(); #ifndef HAVE_LOCALTIME_R struct tm* localtime_r(const time_t* timep, struct tm* result); diff --git a/src/win32compat.cpp b/src/win32compat.cpp new file mode 100644 index 000000000..7728b65d8 --- /dev/null +++ b/src/win32compat.cpp @@ -0,0 +1,102 @@ +// Copyright (C) 2020 Joel Rosdahl and other contributors +// +// See doc/AUTHORS.adoc for a complete list of contributors. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// this program; if not, write to the Free Software Foundation, Inc., 51 +// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#include "win32compat.hpp" + +#ifdef _WIN32 + +# include +# include +# include + +# if !defined(HAVE_REALPATH) && !defined(HAVE_GETFINALPATHNAMEBYHANDLEW) +BOOL +GetFileNameFromHandle(HANDLE file_handle, TCHAR* filename, WORD cch_filename) +{ + BOOL success = FALSE; + + // Get the file size. + DWORD file_size_hi = 0; + DWORD file_size_lo = GetFileSize(file_handle, &file_size_hi); + if (file_size_lo == 0 && file_size_hi == 0) { + // Cannot map a file with a length of zero. + return FALSE; + } + + // Create a file mapping object. + HANDLE file_map = + CreateFileMapping(file_handle, NULL, PAGE_READONLY, 0, 1, NULL); + if (!file_map) { + return FALSE; + } + + // Create a file mapping to get the file name. + void* mem = MapViewOfFile(file_map, FILE_MAP_READ, 0, 0, 1); + if (mem) { + if (GetMappedFileName(GetCurrentProcess(), mem, filename, cch_filename)) { + // Translate path with device name to drive letters. + TCHAR temp[512]; + temp[0] = '\0'; + + if (GetLogicalDriveStrings(512 - 1, temp)) { + TCHAR name[MAX_PATH]; + TCHAR drive[3] = TEXT(" :"); + BOOL found = FALSE; + TCHAR* p = temp; + + do { + // Copy the drive letter to the template string. + *drive = *p; + + // Look up each device name. + if (QueryDosDevice(drive, name, MAX_PATH)) { + size_t name_len = _tcslen(name); + if (name_len < MAX_PATH) { + found = _tcsnicmp(filename, name, name_len) == 0 + && *(filename + name_len) == _T('\\'); + if (found) { + // Reconstruct filename using temp_file and replace device path + // with DOS path. + TCHAR temp_file[MAX_PATH]; + _sntprintf(temp_file, + MAX_PATH - 1, + TEXT("%s%s"), + drive, + filename + name_len); + strcpy(filename, temp_file); + } + } + } + + // Go to the next NULL character. + while (*p++) { + // Do nothing. + } + } while (!found && *p); // End of string. + } + } + success = TRUE; + UnmapViewOfFile(mem); + } + + CloseHandle(file_map); + return success; +} +# endif + +#endif diff --git a/src/win32compat.hpp b/src/win32compat.hpp new file mode 100644 index 000000000..4d0e69988 --- /dev/null +++ b/src/win32compat.hpp @@ -0,0 +1,27 @@ +// Copyright (C) 2020 Joel Rosdahl and other contributors +// +// See doc/AUTHORS.adoc for a complete list of contributors. +// +// This program is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by the Free +// Software Foundation; either version 3 of the License, or (at your option) +// any later version. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +// more details. +// +// You should have received a copy of the GNU General Public License along with +// this program; if not, write to the Free Software Foundation, Inc., 51 +// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +#pragma once + +#ifdef _WIN32 +# include "system.hpp" + +BOOL +GetFileNameFromHandle(HANDLE file_handle, TCHAR* filename, WORD cch_filename); + +#endif diff --git a/unittest/test_argument_processing.cpp b/unittest/test_argument_processing.cpp index 6171f9bd4..ce8d9d9b0 100644 --- a/unittest/test_argument_processing.cpp +++ b/unittest/test_argument_processing.cpp @@ -20,6 +20,7 @@ #include "../src/Config.hpp" #include "../src/Context.hpp" +#include "../src/Util.hpp" #include "../src/args.hpp" #include "../src/ccache.hpp" #include "../src/legacy_globals.hpp" @@ -608,14 +609,13 @@ TEST(fprofile_flag_with_existing_dir_should_be_rewritten_to_real_path) struct args* act_extra = NULL; struct args* act_cc = NULL; - char *s, *path; + char* s; create_file("foo.c", ""); mkdir("some", 0777); mkdir("some/dir", 0777); - path = x_realpath("some/dir"); - s = format("-fprofile-generate=%s", path); - free(path); + std::string path = Util::real_path("some/dir"); + s = format("-fprofile-generate=%s", path.c_str()); args_add(exp_cpp, s); args_add(exp_cc, s); args_add(exp_cc, "-c");