/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file Copyright.txt or https://cmake.org/licensing for details. */ #if !defined(_WIN32) && !defined(__sun) && !defined(__OpenBSD__) // POSIX APIs are needed // NOLINTNEXTLINE(bugprone-reserved-identifier) # define _POSIX_C_SOURCE 200809L #endif #if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__QNX__) // For isascii // NOLINTNEXTLINE(bugprone-reserved-identifier) # define _XOPEN_SOURCE 700 #endif #include "cmSystemTools.h" #include #include #include #include "cmDuration.h" #include "cmELF.h" #include "cmMessageMetadata.h" #include "cmProcessOutput.h" #include "cmRange.h" #include "cmStringAlgorithms.h" #include "cmValue.h" #if !defined(CMAKE_BOOTSTRAP) # include # include # include "cmArchiveWrite.h" # include "cmLocale.h" # ifndef __LA_INT64_T # define __LA_INT64_T la_int64_t # endif # ifndef __LA_SSIZE_T # define __LA_SSIZE_T la_ssize_t # endif #endif #if !defined(CMAKE_BOOTSTRAP) # if defined(_WIN32) # include # endif # include "cmCryptoHash.h" #endif #if defined(CMake_USE_MACH_PARSER) # include "cmMachO.h" #endif #if defined(CMake_USE_XCOFF_PARSER) # include "cmXCOFF.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cmsys/Directory.hxx" #include "cmsys/Encoding.hxx" #include "cmsys/FStream.hxx" #include "cmsys/RegularExpression.hxx" #include "cmsys/System.h" #include "cmsys/Terminal.h" #if defined(_WIN32) # include // include wincrypt.h after windows.h # include #else # include # include # include #endif #if defined(_WIN32) && \ (defined(_MSC_VER) || defined(__WATCOMC__) || defined(__MINGW32__)) # include #endif #if defined(__APPLE__) # include #endif #ifdef __QNX__ # include /* for malloc/free on QNX */ #endif #if !defined(_WIN32) && !defined(__ANDROID__) # include #endif namespace { cmSystemTools::InterruptCallback s_InterruptCallback; cmSystemTools::MessageCallback s_MessageCallback; cmSystemTools::OutputCallback s_StderrCallback; cmSystemTools::OutputCallback s_StdoutCallback; } // namespace #if !defined(HAVE_ENVIRON_NOT_REQUIRE_PROTOTYPE) // For GetEnvironmentVariables # if defined(_WIN32) extern __declspec(dllimport) char** environ; # else extern char** environ; # endif #endif #if !defined(CMAKE_BOOTSTRAP) static std::string cm_archive_entry_pathname(struct archive_entry* entry) { # if cmsys_STL_HAS_WSTRING return cmsys::Encoding::ToNarrow(archive_entry_pathname_w(entry)); # else return archive_entry_pathname(entry); # endif } static int cm_archive_read_open_file(struct archive* a, const char* file, int block_size) { # if cmsys_STL_HAS_WSTRING std::wstring wfile = cmsys::Encoding::ToWide(file); return archive_read_open_filename_w(a, wfile.c_str(), block_size); # else return archive_read_open_filename(a, file, block_size); # endif } #endif #ifdef _WIN32 #elif defined(__APPLE__) # include # define environ (*_NSGetEnviron()) #endif bool cmSystemTools::s_RunCommandHideConsole = false; bool cmSystemTools::s_DisableRunCommandOutput = false; bool cmSystemTools::s_ErrorOccured = false; bool cmSystemTools::s_FatalErrorOccured = false; bool cmSystemTools::s_ForceUnixPaths = false; // replace replace with with as many times as it shows up in source. // write the result into source. #if defined(_WIN32) && !defined(__CYGWIN__) void cmSystemTools::ExpandRegistryValues(std::string& source, KeyWOW64 view) { // Regular expression to match anything inside [...] that begins in HKEY. // Note that there is a special rule for regular expressions to match a // close square-bracket inside a list delimited by square brackets. // The "[^]]" part of this expression will match any character except // a close square-bracket. The ']' character must be the first in the // list of characters inside the [^...] block of the expression. cmsys::RegularExpression regEntry("\\[(HKEY[^]]*)\\]"); // check for black line or comment while (regEntry.find(source)) { // the arguments are the second match std::string key = regEntry.match(1); std::string val; if (ReadRegistryValue(key.c_str(), val, view)) { std::string reg = cmStrCat('[', key, ']'); cmSystemTools::ReplaceString(source, reg.c_str(), val.c_str()); } else { std::string reg = cmStrCat('[', key, ']'); cmSystemTools::ReplaceString(source, reg.c_str(), "/registry"); } } } #else void cmSystemTools::ExpandRegistryValues(std::string& source, KeyWOW64 /*unused*/) { cmsys::RegularExpression regEntry("\\[(HKEY[^]]*)\\]"); while (regEntry.find(source)) { // the arguments are the second match std::string key = regEntry.match(1); std::string reg = cmStrCat('[', key, ']'); cmSystemTools::ReplaceString(source, reg.c_str(), "/registry"); } } #endif std::string cmSystemTools::HelpFileName(cm::string_view str) { std::string name(str); cmSystemTools::ReplaceString(name, "<", ""); cmSystemTools::ReplaceString(name, ">", ""); return name; } void cmSystemTools::Error(const std::string& m) { std::string message = "CMake Error: " + m; cmSystemTools::s_ErrorOccured = true; cmSystemTools::Message(message, "Error"); } void cmSystemTools::SetInterruptCallback(InterruptCallback f) { s_InterruptCallback = std::move(f); } bool cmSystemTools::GetInterruptFlag() { if (s_InterruptCallback) { return s_InterruptCallback(); } return false; } void cmSystemTools::SetMessageCallback(MessageCallback f) { s_MessageCallback = std::move(f); } void cmSystemTools::SetStdoutCallback(OutputCallback f) { s_StdoutCallback = std::move(f); } void cmSystemTools::SetStderrCallback(OutputCallback f) { s_StderrCallback = std::move(f); } void cmSystemTools::Stderr(const std::string& s) { if (s_StderrCallback) { s_StderrCallback(s); } else { std::cerr << s << std::flush; } } void cmSystemTools::Stdout(const std::string& s) { if (s_StdoutCallback) { s_StdoutCallback(s); } else { std::cout << s << std::flush; } } void cmSystemTools::Message(const std::string& m, const char* title) { cmMessageMetadata md; md.title = title; Message(m, md); } void cmSystemTools::Message(const std::string& m, const cmMessageMetadata& md) { if (s_MessageCallback) { s_MessageCallback(m, md); } else { std::cerr << m << std::endl; } } void cmSystemTools::ReportLastSystemError(const char* msg) { std::string m = cmStrCat(msg, ": System Error: ", Superclass::GetLastSystemError()); cmSystemTools::Error(m); } void cmSystemTools::ParseWindowsCommandLine(const char* command, std::vector& args) { // See the MSDN document "Parsing C Command-Line Arguments" at // http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx for rules // of parsing the windows command line. bool in_argument = false; bool in_quotes = false; int backslashes = 0; std::string arg; for (const char* c = command; *c; ++c) { if (*c == '\\') { ++backslashes; in_argument = true; } else if (*c == '"') { int backslash_pairs = backslashes >> 1; int backslash_escaped = backslashes & 1; arg.append(backslash_pairs, '\\'); backslashes = 0; if (backslash_escaped) { /* An odd number of backslashes precede this quote. It is escaped. */ arg.append(1, '"'); } else { /* An even number of backslashes precede this quote. It is not escaped. */ in_quotes = !in_quotes; } in_argument = true; } else { arg.append(backslashes, '\\'); backslashes = 0; if (cmIsSpace(*c)) { if (in_quotes) { arg.append(1, *c); } else if (in_argument) { args.push_back(arg); arg.clear(); in_argument = false; } } else { in_argument = true; arg.append(1, *c); } } } arg.append(backslashes, '\\'); if (in_argument) { args.push_back(arg); } } class cmSystemToolsArgV { char** ArgV; public: cmSystemToolsArgV(char** argv) : ArgV(argv) { } ~cmSystemToolsArgV() { for (char** arg = this->ArgV; arg && *arg; ++arg) { free(*arg); } free(this->ArgV); } cmSystemToolsArgV(const cmSystemToolsArgV&) = delete; cmSystemToolsArgV& operator=(const cmSystemToolsArgV&) = delete; void Store(std::vector& args) const { for (char** arg = this->ArgV; arg && *arg; ++arg) { args.emplace_back(*arg); } } }; void cmSystemTools::ParseUnixCommandLine(const char* command, std::vector& args) { // Invoke the underlying parser. cmSystemToolsArgV argv(cmsysSystem_Parse_CommandForUnix(command, 0)); argv.Store(args); } std::vector cmSystemTools::HandleResponseFile( std::vector::const_iterator argBeg, std::vector::const_iterator argEnd) { std::vector arg_full; for (std::string const& arg : cmMakeRange(argBeg, argEnd)) { if (cmHasLiteralPrefix(arg, "@")) { cmsys::ifstream responseFile(arg.substr(1).c_str(), std::ios::in); if (!responseFile) { std::string error = cmStrCat("failed to open for reading (", cmSystemTools::GetLastSystemError(), "):\n ", cm::string_view(arg).substr(1)); cmSystemTools::Error(error); } else { std::string line; cmSystemTools::GetLineFromStream(responseFile, line); std::vector args2; #ifdef _WIN32 cmSystemTools::ParseWindowsCommandLine(line.c_str(), args2); #else cmSystemTools::ParseUnixCommandLine(line.c_str(), args2); #endif cm::append(arg_full, args2); } } else { arg_full.push_back(arg); } } return arg_full; } std::vector cmSystemTools::ParseArguments(const std::string& cmd) { std::vector args; std::string arg; bool win_path = false; const char* command = cmd.c_str(); if (command[0] && command[1] && ((command[0] != '/' && command[1] == ':' && command[2] == '\\') || (command[0] == '\"' && command[1] != '/' && command[2] == ':' && command[3] == '\\') || (command[0] == '\'' && command[1] != '/' && command[2] == ':' && command[3] == '\\') || (command[0] == '\\' && command[1] == '\\'))) { win_path = true; } // Split the command into an argv array. for (const char* c = command; *c;) { // Skip over whitespace. while (*c == ' ' || *c == '\t') { ++c; } arg.clear(); if (*c == '"') { // Parse a quoted argument. ++c; while (*c && *c != '"') { arg.append(1, *c); ++c; } if (*c) { ++c; } args.push_back(arg); } else if (*c == '\'') { // Parse a quoted argument. ++c; while (*c && *c != '\'') { arg.append(1, *c); ++c; } if (*c) { ++c; } args.push_back(arg); } else if (*c) { // Parse an unquoted argument. while (*c && *c != ' ' && *c != '\t') { if (*c == '\\' && !win_path) { ++c; if (*c) { arg.append(1, *c); ++c; } } else { arg.append(1, *c); ++c; } } args.push_back(arg); } } return args; } bool cmSystemTools::SplitProgramFromArgs(std::string const& command, std::string& program, std::string& args) { const char* c = command.c_str(); // Skip leading whitespace. while (isspace(static_cast(*c))) { ++c; } // Parse one command-line element up to an unquoted space. bool in_escape = false; bool in_double = false; bool in_single = false; for (; *c; ++c) { if (in_single) { if (*c == '\'') { in_single = false; } else { program += *c; } } else if (in_escape) { in_escape = false; program += *c; } else if (*c == '\\') { in_escape = true; } else if (in_double) { if (*c == '"') { in_double = false; } else { program += *c; } } else if (*c == '"') { in_double = true; } else if (*c == '\'') { in_single = true; } else if (isspace(static_cast(*c))) { break; } else { program += *c; } } // The remainder of the command line holds unparsed arguments. args = c; return !in_single && !in_escape && !in_double; } size_t cmSystemTools::CalculateCommandLineLengthLimit() { size_t sz = #ifdef _WIN32 // There's a maximum of 65536 bytes and thus 32768 WCHARs on Windows // However, cmd.exe itself can only handle 8191 WCHARs and Ninja for // example uses it to spawn processes. size_t(8191); #elif defined(__linux) // MAX_ARG_STRLEN is the maximum length of a string permissible for // the execve() syscall on Linux. It's defined as (PAGE_SIZE * 32) // in Linux's binfmts.h static_cast(sysconf(_SC_PAGESIZE) * 32); #else size_t(0); #endif #if defined(_SC_ARG_MAX) // ARG_MAX is the maximum size of the command and environment // that can be passed to the exec functions on UNIX. // The value in climits does not need to be present and may // depend upon runtime memory constraints, hence sysconf() // should be used to query it. long szArgMax = sysconf(_SC_ARG_MAX); // A return value of -1 signifies an undetermined limit, but // it does not imply an infinite limit, and thus is ignored. if (szArgMax != -1) { // We estimate the size of the environment block to be 1000. // This isn't accurate at all, but leaves some headroom. szArgMax = szArgMax < 1000 ? 0 : szArgMax - 1000; # if defined(_WIN32) || defined(__linux) sz = std::min(sz, static_cast(szArgMax)); # else sz = static_cast(szArgMax); # endif } #endif return sz; } bool cmSystemTools::RunSingleCommand(std::vector const& command, std::string* captureStdOut, std::string* captureStdErr, int* retVal, const char* dir, OutputOption outputflag, cmDuration timeout, Encoding encoding) { std::vector argv; argv.reserve(command.size() + 1); for (std::string const& cmd : command) { argv.push_back(cmd.c_str()); } argv.push_back(nullptr); cmsysProcess* cp = cmsysProcess_New(); cmsysProcess_SetCommand(cp, argv.data()); cmsysProcess_SetWorkingDirectory(cp, dir); if (cmSystemTools::GetRunCommandHideConsole()) { cmsysProcess_SetOption(cp, cmsysProcess_Option_HideWindow, 1); } if (outputflag == OUTPUT_PASSTHROUGH) { cmsysProcess_SetPipeShared(cp, cmsysProcess_Pipe_STDOUT, 1); cmsysProcess_SetPipeShared(cp, cmsysProcess_Pipe_STDERR, 1); captureStdOut = nullptr; captureStdErr = nullptr; } else if (outputflag == OUTPUT_MERGE || (captureStdErr && captureStdErr == captureStdOut)) { cmsysProcess_SetOption(cp, cmsysProcess_Option_MergeOutput, 1); captureStdErr = nullptr; } assert(!captureStdErr || captureStdErr != captureStdOut); cmsysProcess_SetTimeout(cp, timeout.count()); cmsysProcess_Execute(cp); std::vector tempStdOut; std::vector tempStdErr; char* data; int length; int pipe; cmProcessOutput processOutput(encoding); std::string strdata; if (outputflag != OUTPUT_PASSTHROUGH && (captureStdOut || captureStdErr || outputflag != OUTPUT_NONE)) { while ((pipe = cmsysProcess_WaitForData(cp, &data, &length, nullptr)) > 0) { // Translate NULL characters in the output into valid text. for (int i = 0; i < length; ++i) { if (data[i] == '\0') { data[i] = ' '; } } if (pipe == cmsysProcess_Pipe_STDOUT) { if (outputflag != OUTPUT_NONE) { processOutput.DecodeText(data, length, strdata, 1); cmSystemTools::Stdout(strdata); } if (captureStdOut) { cm::append(tempStdOut, data, data + length); } } else if (pipe == cmsysProcess_Pipe_STDERR) { if (outputflag != OUTPUT_NONE) { processOutput.DecodeText(data, length, strdata, 2); cmSystemTools::Stderr(strdata); } if (captureStdErr) { cm::append(tempStdErr, data, data + length); } } } if (outputflag != OUTPUT_NONE) { processOutput.DecodeText(std::string(), strdata, 1); if (!strdata.empty()) { cmSystemTools::Stdout(strdata); } processOutput.DecodeText(std::string(), strdata, 2); if (!strdata.empty()) { cmSystemTools::Stderr(strdata); } } } cmsysProcess_WaitForExit(cp, nullptr); if (captureStdOut) { captureStdOut->assign(tempStdOut.begin(), tempStdOut.end()); processOutput.DecodeText(*captureStdOut, *captureStdOut); } if (captureStdErr) { captureStdErr->assign(tempStdErr.begin(), tempStdErr.end()); processOutput.DecodeText(*captureStdErr, *captureStdErr); } bool result = true; if (cmsysProcess_GetState(cp) == cmsysProcess_State_Exited) { if (retVal) { *retVal = cmsysProcess_GetExitValue(cp); } else { if (cmsysProcess_GetExitValue(cp) != 0) { result = false; } } } else if (cmsysProcess_GetState(cp) == cmsysProcess_State_Exception) { const char* exception_str = cmsysProcess_GetExceptionString(cp); if (outputflag != OUTPUT_NONE) { std::cerr << exception_str << std::endl; } if (captureStdErr) { captureStdErr->append(exception_str, strlen(exception_str)); } else if (captureStdOut) { captureStdOut->append(exception_str, strlen(exception_str)); } result = false; } else if (cmsysProcess_GetState(cp) == cmsysProcess_State_Error) { const char* error_str = cmsysProcess_GetErrorString(cp); if (outputflag != OUTPUT_NONE) { std::cerr << error_str << std::endl; } if (captureStdErr) { captureStdErr->append(error_str, strlen(error_str)); } else if (captureStdOut) { captureStdOut->append(error_str, strlen(error_str)); } result = false; } else if (cmsysProcess_GetState(cp) == cmsysProcess_State_Expired) { const char* error_str = "Process terminated due to timeout\n"; if (outputflag != OUTPUT_NONE) { std::cerr << error_str << std::endl; } if (captureStdErr) { captureStdErr->append(error_str, strlen(error_str)); } result = false; } cmsysProcess_Delete(cp); return result; } bool cmSystemTools::RunSingleCommand(const std::string& command, std::string* captureStdOut, std::string* captureStdErr, int* retVal, const char* dir, OutputOption outputflag, cmDuration timeout) { if (s_DisableRunCommandOutput) { outputflag = OUTPUT_NONE; } std::vector args = cmSystemTools::ParseArguments(command); if (args.empty()) { return false; } return cmSystemTools::RunSingleCommand(args, captureStdOut, captureStdErr, retVal, dir, outputflag, timeout); } std::string cmSystemTools::PrintSingleCommand( std::vector const& command) { if (command.empty()) { return std::string(); } return cmWrap('"', command, '"', " "); } bool cmSystemTools::DoesFileExistWithExtensions( const std::string& name, const std::vector& headerExts) { std::string hname; for (std::string const& headerExt : headerExts) { hname = cmStrCat(name, '.', headerExt); if (cmSystemTools::FileExists(hname)) { return true; } } return false; } std::string cmSystemTools::FileExistsInParentDirectories( const std::string& fname, const std::string& directory, const std::string& toplevel) { std::string file = fname; cmSystemTools::ConvertToUnixSlashes(file); std::string dir = directory; cmSystemTools::ConvertToUnixSlashes(dir); std::string prevDir; while (dir != prevDir) { std::string path = cmStrCat(dir, "/", file); if (cmSystemTools::FileExists(path)) { return path; } if (dir.size() < toplevel.size()) { break; } prevDir = dir; dir = cmSystemTools::GetParentDirectory(dir); } return ""; } #ifdef _WIN32 namespace { /* Helper class to save and restore the specified file (or directory) attribute bits. Instantiate this class as an automatic variable on the stack. Its constructor saves a copy of the file attributes, and then its destructor restores the original attribute settings. */ class SaveRestoreFileAttributes { public: SaveRestoreFileAttributes(std::wstring const& path, uint32_t file_attrs_to_set); ~SaveRestoreFileAttributes(); SaveRestoreFileAttributes(SaveRestoreFileAttributes const&) = delete; SaveRestoreFileAttributes& operator=(SaveRestoreFileAttributes const&) = delete; void SetPath(std::wstring const& path) { path_ = path; } private: std::wstring path_; uint32_t original_attr_bits_; }; SaveRestoreFileAttributes::SaveRestoreFileAttributes( std::wstring const& path, uint32_t file_attrs_to_set) : path_(path) , original_attr_bits_(0) { // Set the specified attributes for the source file/directory. original_attr_bits_ = GetFileAttributesW(path_.c_str()); if ((INVALID_FILE_ATTRIBUTES != original_attr_bits_) && ((file_attrs_to_set & original_attr_bits_) != file_attrs_to_set)) { SetFileAttributesW(path_.c_str(), original_attr_bits_ | file_attrs_to_set); } } // We set attribute bits. Now we need to restore their original state. SaveRestoreFileAttributes::~SaveRestoreFileAttributes() { DWORD last_error = GetLastError(); // Verify or restore the original attributes. const DWORD source_attr_bits = GetFileAttributesW(path_.c_str()); if (INVALID_FILE_ATTRIBUTES != source_attr_bits) { if (original_attr_bits_ != source_attr_bits) { // The file still exists, and its attributes aren't our saved values. // Time to restore them. SetFileAttributesW(path_.c_str(), original_attr_bits_); } } SetLastError(last_error); } struct WindowsFileRetryInit { cmSystemTools::WindowsFileRetry Retry; bool Explicit; }; WindowsFileRetryInit InitWindowsFileRetry(wchar_t const* const values[2], unsigned int const defaults[2]) { unsigned int data[2] = { 0, 0 }; HKEY const keys[2] = { HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE }; for (int k = 0; k < 2; ++k) { HKEY hKey; if (RegOpenKeyExW(keys[k], L"Software\\Kitware\\CMake\\Config", 0, KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS) { for (int v = 0; v < 2; ++v) { DWORD dwData, dwType, dwSize = 4; if (!data[v] && RegQueryValueExW(hKey, values[v], 0, &dwType, (BYTE*)&dwData, &dwSize) == ERROR_SUCCESS && dwType == REG_DWORD && dwSize == 4) { data[v] = static_cast(dwData); } } RegCloseKey(hKey); } } WindowsFileRetryInit init; init.Explicit = data[0] || data[1]; init.Retry.Count = data[0] ? data[0] : defaults[0]; init.Retry.Delay = data[1] ? data[1] : defaults[1]; return init; } WindowsFileRetryInit InitWindowsFileRetry() { static wchar_t const* const values[2] = { L"FilesystemRetryCount", L"FilesystemRetryDelay" }; static unsigned int const defaults[2] = { 5, 500 }; return InitWindowsFileRetry(values, defaults); } WindowsFileRetryInit InitWindowsDirectoryRetry() { static wchar_t const* const values[2] = { L"FilesystemDirectoryRetryCount", L"FilesystemDirectoryRetryDelay" }; static unsigned int const defaults[2] = { 120, 500 }; WindowsFileRetryInit dirInit = InitWindowsFileRetry(values, defaults); if (dirInit.Explicit) { return dirInit; } WindowsFileRetryInit fileInit = InitWindowsFileRetry(); if (fileInit.Explicit) { return fileInit; } return dirInit; } cmSystemTools::WindowsFileRetry GetWindowsRetry(std::wstring const& path) { // If we are performing a directory operation, then try and get the // appropriate timing info. DWORD const attrs = GetFileAttributesW(path.c_str()); if (attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_DIRECTORY)) { return cmSystemTools::GetWindowsDirectoryRetry(); } return cmSystemTools::GetWindowsFileRetry(); } } // end of anonymous namespace cmSystemTools::WindowsFileRetry cmSystemTools::GetWindowsFileRetry() { static WindowsFileRetry retry = InitWindowsFileRetry().Retry; return retry; } cmSystemTools::WindowsFileRetry cmSystemTools::GetWindowsDirectoryRetry() { static cmSystemTools::WindowsFileRetry retry = InitWindowsDirectoryRetry().Retry; return retry; } #endif std::string cmSystemTools::GetRealPathResolvingWindowsSubst( const std::string& path, std::string* errorMessage) { #ifdef _WIN32 // uv_fs_realpath uses Windows Vista API so fallback to kwsys if not found std::string resolved_path; uv_fs_t req; int err = uv_fs_realpath(NULL, &req, path.c_str(), NULL); if (!err) { resolved_path = std::string((char*)req.ptr); cmSystemTools::ConvertToUnixSlashes(resolved_path); // Normalize to upper-case drive letter as GetActualCaseForPath does. if (resolved_path.size() > 1 && resolved_path[1] == ':') { resolved_path[0] = toupper(resolved_path[0]); } } else if (err == UV_ENOSYS) { resolved_path = cmsys::SystemTools::GetRealPath(path, errorMessage); } else if (errorMessage) { LPSTR message = NULL; DWORD size = FormatMessageA( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&message, 0, NULL); *errorMessage = std::string(message, size); LocalFree(message); resolved_path = ""; } else { resolved_path = path; } return resolved_path; #else return cmsys::SystemTools::GetRealPath(path, errorMessage); #endif } void cmSystemTools::InitializeLibUV() { #if defined(_WIN32) // Perform libuv one-time initialization now, and then un-do its // global _fmode setting so that using libuv does not change the // default file text/binary mode. See libuv issue 840. if (uv_loop_t* loop = uv_default_loop()) { uv_loop_close(loop); } # ifdef _MSC_VER _set_fmode(_O_TEXT); # else _fmode = _O_TEXT; # endif // Replace libuv's report handler with our own to suppress popups. cmSystemTools::EnableMSVCDebugHook(); #endif } #ifdef _WIN32 namespace { bool cmMoveFile(std::wstring const& oldname, std::wstring const& newname, cmSystemTools::Replace replace) { // Not only ignore any previous error, but clear any memory of it. SetLastError(0); DWORD flags = 0; if (replace == cmSystemTools::Replace::Yes) { // Use MOVEFILE_REPLACE_EXISTING to replace an existing destination file. flags = flags | MOVEFILE_REPLACE_EXISTING; } return MoveFileExW(oldname.c_str(), newname.c_str(), flags); } } #endif bool cmSystemTools::CopySingleFile(const std::string& oldname, const std::string& newname) { return cmSystemTools::CopySingleFile(oldname, newname, CopyWhen::Always) == CopyResult::Success; } cmSystemTools::CopyResult cmSystemTools::CopySingleFile( std::string const& oldname, std::string const& newname, CopyWhen when, std::string* err) { switch (when) { case CopyWhen::Always: break; case CopyWhen::OnlyIfDifferent: if (!FilesDiffer(oldname, newname)) { return CopyResult::Success; } break; } mode_t perm = 0; cmsys::Status perms = SystemTools::GetPermissions(oldname, perm); // If files are the same do not copy if (SystemTools::SameFile(oldname, newname)) { return CopyResult::Success; } cmsys::Status status; status = cmsys::SystemTools::CloneFileContent(oldname, newname); if (!status) { // if cloning did not succeed, fall back to blockwise copy status = cmsys::SystemTools::CopyFileContentBlockwise(oldname, newname); } if (!status) { if (err) { *err = status.GetString(); } return CopyResult::Failure; } if (perms) { status = SystemTools::SetPermissions(newname, perm); if (!status) { if (err) { *err = status.GetString(); } return CopyResult::Failure; } } return CopyResult::Success; } bool cmSystemTools::RenameFile(const std::string& oldname, const std::string& newname) { return cmSystemTools::RenameFile(oldname, newname, Replace::Yes) == RenameResult::Success; } cmSystemTools::RenameResult cmSystemTools::RenameFile( std::string const& oldname, std::string const& newname, Replace replace, std::string* err) { #ifdef _WIN32 # ifndef INVALID_FILE_ATTRIBUTES # define INVALID_FILE_ATTRIBUTES ((DWORD)-1) # endif std::wstring const oldname_wstr = SystemTools::ConvertToWindowsExtendedPath(oldname); std::wstring const newname_wstr = SystemTools::ConvertToWindowsExtendedPath(newname); /* Windows MoveFileEx may not replace read-only or in-use files. If it fails then remove the read-only attribute from any existing destination. Try multiple times since we may be racing against another process creating/opening the destination file just before our MoveFileEx. */ WindowsFileRetry retry = GetWindowsRetry(oldname_wstr); // Use RAII to set the attribute bit blocking Microsoft Search Indexing, // and restore the previous value upon return. SaveRestoreFileAttributes save_restore_file_attributes( oldname_wstr, FILE_ATTRIBUTE_NOT_CONTENT_INDEXED); DWORD move_last_error = 0; while (!cmMoveFile(oldname_wstr, newname_wstr, replace) && --retry.Count) { move_last_error = GetLastError(); // There was no error ==> the operation is not yet complete. if (move_last_error == NO_ERROR) { break; } // Try again only if failure was due to access/sharing permissions. // Most often ERROR_ACCESS_DENIED (a.k.a. I/O error) for a directory, and // ERROR_SHARING_VIOLATION for a file, are caused by one of the following: // 1) Anti-Virus Software // 2) Windows Search Indexer // 3) Windows Explorer has an associated directory already opened. if (move_last_error != ERROR_ACCESS_DENIED && move_last_error != ERROR_SHARING_VIOLATION) { if (replace == Replace::No && move_last_error == ERROR_ALREADY_EXISTS) { return RenameResult::NoReplace; } if (err) { *err = cmsys::Status::Windows(move_last_error).GetString(); } return RenameResult::Failure; } DWORD const attrs = GetFileAttributesW(newname_wstr.c_str()); if ((attrs != INVALID_FILE_ATTRIBUTES) && (attrs & FILE_ATTRIBUTE_READONLY) && // FILE_ATTRIBUTE_READONLY is not honored on directories. !(attrs & FILE_ATTRIBUTE_DIRECTORY)) { // Remove the read-only attribute from the destination file. SetFileAttributesW(newname_wstr.c_str(), attrs & ~FILE_ATTRIBUTE_READONLY); } else { // The file may be temporarily in use so wait a bit. cmSystemTools::Delay(retry.Delay); } } // If we were successful, then there was no error. if (retry.Count > 0) { move_last_error = 0; // Restore the attributes on the new name. save_restore_file_attributes.SetPath(newname_wstr); } SetLastError(move_last_error); if (retry.Count > 0) { return RenameResult::Success; } if (replace == Replace::No && GetLastError() == ERROR_ALREADY_EXISTS) { return RenameResult::NoReplace; } if (err) { *err = cmsys::Status::Windows_GetLastError().GetString(); } return RenameResult::Failure; #else // On UNIX we have OS-provided calls to create 'newname' atomically. if (replace == Replace::No) { if (link(oldname.c_str(), newname.c_str()) == 0) { return RenameResult::Success; } if (errno == EEXIST) { return RenameResult::NoReplace; } if (err) { *err = cmsys::Status::POSIX_errno().GetString(); } return RenameResult::Failure; } if (rename(oldname.c_str(), newname.c_str()) == 0) { return RenameResult::Success; } if (err) { *err = cmsys::Status::POSIX_errno().GetString(); } return RenameResult::Failure; #endif } void cmSystemTools::MoveFileIfDifferent(const std::string& source, const std::string& destination) { if (FilesDiffer(source, destination)) { if (RenameFile(source, destination)) { return; } CopyFileAlways(source, destination); } RemoveFile(source); } #ifndef CMAKE_BOOTSTRAP std::string cmSystemTools::ComputeFileHash(const std::string& source, cmCryptoHash::Algo algo) { cmCryptoHash hash(algo); return hash.HashFile(source); } std::string cmSystemTools::ComputeStringMD5(const std::string& input) { cmCryptoHash md5(cmCryptoHash::AlgoMD5); return md5.HashString(input); } # ifdef _WIN32 std::string cmSystemTools::ComputeCertificateThumbprint( const std::string& source) { std::string thumbprint; CRYPT_INTEGER_BLOB cryptBlob; HCERTSTORE certStore = NULL; PCCERT_CONTEXT certContext = NULL; HANDLE certFile = CreateFileW( cmsys::Encoding::ToWide(source.c_str()).c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (certFile != INVALID_HANDLE_VALUE && certFile != NULL) { DWORD fileSize = GetFileSize(certFile, NULL); if (fileSize != INVALID_FILE_SIZE) { auto certData = cm::make_unique(fileSize); if (certData != NULL) { DWORD dwRead = 0; if (ReadFile(certFile, certData.get(), fileSize, &dwRead, NULL)) { cryptBlob.cbData = fileSize; cryptBlob.pbData = certData.get(); // Verify that this is a valid cert if (PFXIsPFXBlob(&cryptBlob)) { // Open the certificate as a store certStore = PFXImportCertStore(&cryptBlob, NULL, CRYPT_EXPORTABLE); if (certStore != NULL) { // There should only be 1 cert. certContext = CertEnumCertificatesInStore(certStore, certContext); if (certContext != NULL) { // The hash is 20 bytes BYTE hashData[20]; DWORD hashLength = 20; // Buffer to print the hash. Each byte takes 2 chars + // terminating character char hashPrint[41]; char* pHashPrint = hashPrint; // Get the hash property from the certificate if (CertGetCertificateContextProperty( certContext, CERT_HASH_PROP_ID, hashData, &hashLength)) { for (DWORD i = 0; i < hashLength; i++) { // Convert each byte to hexadecimal sprintf(pHashPrint, "%02X", hashData[i]); pHashPrint += 2; } *pHashPrint = '\0'; thumbprint = hashPrint; } CertFreeCertificateContext(certContext); } CertCloseStore(certStore, 0); } } } } } CloseHandle(certFile); } return thumbprint; } # endif #endif void cmSystemTools::Glob(const std::string& directory, const std::string& regexp, std::vector& files) { cmsys::Directory d; cmsys::RegularExpression reg(regexp.c_str()); if (d.Load(directory)) { size_t numf; unsigned int i; numf = d.GetNumberOfFiles(); for (i = 0; i < numf; i++) { std::string fname = d.GetFile(i); if (reg.find(fname)) { files.push_back(std::move(fname)); } } } } void cmSystemTools::GlobDirs(const std::string& path, std::vector& files) { std::string::size_type pos = path.find("/*"); if (pos == std::string::npos) { files.push_back(path); return; } std::string startPath = path.substr(0, pos); std::string finishPath = path.substr(pos + 2); cmsys::Directory d; if (d.Load(startPath)) { for (unsigned int i = 0; i < d.GetNumberOfFiles(); ++i) { if ((std::string(d.GetFile(i)) != ".") && (std::string(d.GetFile(i)) != "..")) { std::string fname = cmStrCat(startPath, '/', d.GetFile(i)); if (cmSystemTools::FileIsDirectory(fname)) { fname += finishPath; cmSystemTools::GlobDirs(fname, files); } } } } } bool cmSystemTools::SimpleGlob(const std::string& glob, std::vector& files, int type /* = 0 */) { files.clear(); if (glob.back() != '*') { return false; } std::string path = cmSystemTools::GetFilenamePath(glob); std::string ppath = cmSystemTools::GetFilenameName(glob); ppath = ppath.substr(0, ppath.size() - 1); if (path.empty()) { path = "/"; } bool res = false; cmsys::Directory d; if (d.Load(path)) { for (unsigned int i = 0; i < d.GetNumberOfFiles(); ++i) { if ((std::string(d.GetFile(i)) != ".") && (std::string(d.GetFile(i)) != "..")) { std::string fname = path; if (path.back() != '/') { fname += "/"; } fname += d.GetFile(i); std::string sfname = d.GetFile(i); if (type > 0 && cmSystemTools::FileIsDirectory(fname)) { continue; } if (type < 0 && !cmSystemTools::FileIsDirectory(fname)) { continue; } if (cmHasPrefix(sfname, ppath)) { files.push_back(fname); res = true; } } } } return res; } std::string cmSystemTools::ConvertToOutputPath(std::string const& path) { #if defined(_WIN32) && !defined(__CYGWIN__) if (s_ForceUnixPaths) { return cmSystemTools::ConvertToUnixOutputPath(path); } return cmSystemTools::ConvertToWindowsOutputPath(path); #else return cmSystemTools::ConvertToUnixOutputPath(path); #endif } void cmSystemTools::ConvertToOutputSlashes(std::string& path) { #if defined(_WIN32) && !defined(__CYGWIN__) if (!s_ForceUnixPaths) { // Convert to windows slashes. std::string::size_type pos = 0; while ((pos = path.find('/', pos)) != std::string::npos) { path[pos++] = '\\'; } } #else static_cast(path); #endif } void cmSystemTools::ConvertToLongPath(std::string& path) { #if defined(_WIN32) && !defined(__CYGWIN__) // Try to convert path to a long path only if the path contains character '~' if (path.find('~') == std::string::npos) { return; } std::wstring wPath = cmsys::Encoding::ToWide(path); DWORD ret = GetLongPathNameW(wPath.c_str(), nullptr, 0); std::vector buffer(ret); if (ret != 0) { ret = GetLongPathNameW(wPath.c_str(), buffer.data(), static_cast(buffer.size())); } if (ret != 0) { path = cmsys::Encoding::ToNarrow(buffer.data()); } #else static_cast(path); #endif } std::string cmSystemTools::ConvertToRunCommandPath(const std::string& path) { #if defined(_WIN32) && !defined(__CYGWIN__) return cmSystemTools::ConvertToWindowsOutputPath(path); #else return cmSystemTools::ConvertToUnixOutputPath(path); #endif } // compute the relative path from here to there std::string cmSystemTools::RelativePath(std::string const& local, std::string const& remote) { if (!cmSystemTools::FileIsFullPath(local)) { cmSystemTools::Error("RelativePath must be passed a full path to local: " + local); } if (!cmSystemTools::FileIsFullPath(remote)) { cmSystemTools::Error( "RelativePath must be passed a full path to remote: " + remote); } return cmsys::SystemTools::RelativePath(local, remote); } std::string cmSystemTools::ForceToRelativePath(std::string const& local_path, std::string const& remote_path) { // The paths should never be quoted. assert(local_path.front() != '\"'); assert(remote_path.front() != '\"'); // The local path should never have a trailing slash except if it is just the // bare root directory assert(local_path.empty() || local_path.back() != '/' || local_path.size() == 1 || (local_path.size() == 3 && local_path[1] == ':' && ((local_path[0] >= 'A' && local_path[0] <= 'Z') || (local_path[0] >= 'a' && local_path[0] <= 'z')))); // If the path is already relative then just return the path. if (!cmSystemTools::FileIsFullPath(remote_path)) { return remote_path; } // Identify the longest shared path component between the remote // path and the local path. std::vector local; cmSystemTools::SplitPath(local_path, local); std::vector remote; cmSystemTools::SplitPath(remote_path, remote); unsigned int common = 0; while (common < remote.size() && common < local.size() && cmSystemTools::ComparePath(remote[common], local[common])) { ++common; } // If no part of the path is in common then return the full path. if (common == 0) { return remote_path; } // If the entire path is in common then just return a ".". if (common == remote.size() && common == local.size()) { return "."; } // If the entire path is in common except for a trailing slash then // just return a "./". if (common + 1 == remote.size() && remote[common].empty() && common == local.size()) { return "./"; } // Construct the relative path. std::string relative; // First add enough ../ to get up to the level of the shared portion // of the path. Leave off the trailing slash. Note that the last // component of local will never be empty because local should never // have a trailing slash. for (unsigned int i = common; i < local.size(); ++i) { relative += ".."; if (i < local.size() - 1) { relative += "/"; } } // Now add the portion of the destination path that is not included // in the shared portion of the path. Add a slash the first time // only if there was already something in the path. If there was a // trailing slash in the input then the last iteration of the loop // will add a slash followed by an empty string which will preserve // the trailing slash in the output. if (!relative.empty() && !remote.empty()) { relative += "/"; } relative += cmJoin(cmMakeRange(remote).advance(common), "/"); // Finally return the path. return relative; } std::string cmSystemTools::RelativeIfUnder(std::string const& top, std::string const& in) { std::string out; if (in == top) { out = "."; } else if (cmSystemTools::IsSubDirectory(in, top)) { out = in.substr(top.size() + 1); } else { out = in; } return out; } #ifndef CMAKE_BOOTSTRAP bool cmSystemTools::UnsetEnv(const char* value) { # if !defined(HAVE_UNSETENV) std::string var = cmStrCat(value, '='); return cmSystemTools::PutEnv(var); # else unsetenv(value); return true; # endif } std::vector cmSystemTools::GetEnvironmentVariables() { std::vector env; int cc; for (cc = 0; environ[cc]; ++cc) { env.emplace_back(environ[cc]); } return env; } void cmSystemTools::AppendEnv(std::vector const& env) { for (std::string const& eit : env) { cmSystemTools::PutEnv(eit); } } cmSystemTools::SaveRestoreEnvironment::SaveRestoreEnvironment() { this->Env = cmSystemTools::GetEnvironmentVariables(); } cmSystemTools::SaveRestoreEnvironment::~SaveRestoreEnvironment() { // First clear everything in the current environment: std::vector currentEnv = GetEnvironmentVariables(); for (std::string var : currentEnv) { std::string::size_type pos = var.find('='); if (pos != std::string::npos) { var = var.substr(0, pos); } cmSystemTools::UnsetEnv(var.c_str()); } // Then put back each entry from the original environment: cmSystemTools::AppendEnv(this->Env); } #endif void cmSystemTools::EnableVSConsoleOutput() { #ifdef _WIN32 // Visual Studio tools like devenv may not // display output to the console unless this environment variable is // set. We need it to capture the output of these build tools. // Note for future work that one could pass "/out \\.\pipe\NAME" to // either of these executables where NAME is created with // CreateNamedPipe. This would bypass the internal buffering of the // output and allow it to be captured on the fly. cmSystemTools::PutEnv("vsconsoleoutput=1"); # ifndef CMAKE_BOOTSTRAP // VS sets an environment variable to tell MS tools like "cl" to report // output through a backdoor pipe instead of stdout/stderr. Unset the // environment variable to close this backdoor for any path of process // invocations that passes through CMake so we can capture the output. cmSystemTools::UnsetEnv("VS_UNICODE_OUTPUT"); # endif #endif } bool cmSystemTools::IsPathToFramework(const std::string& path) { return (cmSystemTools::FileIsFullPath(path) && cmHasLiteralSuffix(path, ".framework")); } bool cmSystemTools::IsPathToMacOSSharedLibrary(const std::string& path) { return (cmSystemTools::FileIsFullPath(path) && cmHasLiteralSuffix(path, ".dylib")); } bool cmSystemTools::CreateTar(const std::string& outFileName, const std::vector& files, cmTarCompression compressType, bool verbose, std::string const& mtime, std::string const& format, int compressionLevel) { #if !defined(CMAKE_BOOTSTRAP) std::string cwd = cmSystemTools::GetCurrentWorkingDirectory(); cmsys::ofstream fout(outFileName.c_str(), std::ios::out | std::ios::binary); if (!fout) { std::string e = cmStrCat("Cannot open output file \"", outFileName, "\": ", cmSystemTools::GetLastSystemError()); cmSystemTools::Error(e); return false; } cmArchiveWrite::Compress compress = cmArchiveWrite::CompressNone; switch (compressType) { case TarCompressGZip: compress = cmArchiveWrite::CompressGZip; break; case TarCompressBZip2: compress = cmArchiveWrite::CompressBZip2; break; case TarCompressXZ: compress = cmArchiveWrite::CompressXZ; break; case TarCompressZstd: compress = cmArchiveWrite::CompressZstd; break; case TarCompressNone: compress = cmArchiveWrite::CompressNone; break; } cmArchiveWrite a(fout, compress, format.empty() ? "paxr" : format, compressionLevel); if (!a.Open()) { cmSystemTools::Error(a.GetError()); return false; } a.SetMTime(mtime); a.SetVerbose(verbose); bool tarCreatedSuccessfully = true; for (auto path : files) { if (cmSystemTools::FileIsFullPath(path)) { // Get the relative path to the file. path = cmSystemTools::RelativePath(cwd, path); } if (!a.Add(path)) { cmSystemTools::Error(a.GetError()); tarCreatedSuccessfully = false; } } return tarCreatedSuccessfully; #else (void)outFileName; (void)files; (void)verbose; return false; #endif } #if !defined(CMAKE_BOOTSTRAP) namespace { # define BSDTAR_FILESIZE_PRINTF "%lu" # define BSDTAR_FILESIZE_TYPE unsigned long void list_item_verbose(FILE* out, struct archive_entry* entry) { char tmp[100]; size_t w; const char* p; const char* fmt; time_t tim; static time_t now; size_t u_width = 6; size_t gs_width = 13; /* * We avoid collecting the entire list in memory at once by * listing things as we see them. However, that also means we can't * just pre-compute the field widths. Instead, we start with guesses * and just widen them as necessary. These numbers are completely * arbitrary. */ if (!now) { time(&now); } fprintf(out, "%s %d ", archive_entry_strmode(entry), archive_entry_nlink(entry)); /* Use uname if it's present, else uid. */ p = archive_entry_uname(entry); if ((p == nullptr) || (*p == '\0')) { snprintf(tmp, sizeof(tmp), "%lu ", static_cast(archive_entry_uid(entry))); p = tmp; } w = strlen(p); if (w > u_width) { u_width = w; } fprintf(out, "%-*s ", static_cast(u_width), p); /* Use gname if it's present, else gid. */ p = archive_entry_gname(entry); if (p != nullptr && p[0] != '\0') { fprintf(out, "%s", p); w = strlen(p); } else { snprintf(tmp, sizeof(tmp), "%lu", static_cast(archive_entry_gid(entry))); w = strlen(tmp); fprintf(out, "%s", tmp); } /* * Print device number or file size, right-aligned so as to make * total width of group and devnum/filesize fields be gs_width. * If gs_width is too small, grow it. */ if (archive_entry_filetype(entry) == AE_IFCHR || archive_entry_filetype(entry) == AE_IFBLK) { unsigned long rdevmajor = archive_entry_rdevmajor(entry); unsigned long rdevminor = archive_entry_rdevminor(entry); snprintf(tmp, sizeof(tmp), "%lu,%lu", rdevmajor, rdevminor); } else { /* * Note the use of platform-dependent macros to format * the filesize here. We need the format string and the * corresponding type for the cast. */ snprintf(tmp, sizeof(tmp), BSDTAR_FILESIZE_PRINTF, static_cast(archive_entry_size(entry))); } if (w + strlen(tmp) >= gs_width) { gs_width = w + strlen(tmp) + 1; } fprintf(out, "%*s", static_cast(gs_width - w), tmp); /* Format the time using 'ls -l' conventions. */ tim = archive_entry_mtime(entry); # define HALF_YEAR ((time_t)365 * 86400 / 2) # if defined(_WIN32) && !defined(__CYGWIN__) /* Windows' strftime function does not support %e format. */ # define DAY_FMT "%d" # else # define DAY_FMT "%e" /* Day number without leading zeros */ # endif if (tim < now - HALF_YEAR || tim > now + HALF_YEAR) { fmt = DAY_FMT " %b %Y"; } else { fmt = DAY_FMT " %b %H:%M"; } strftime(tmp, sizeof(tmp), fmt, localtime(&tim)); fprintf(out, " %s ", tmp); fprintf(out, "%s", cm_archive_entry_pathname(entry).c_str()); /* Extra information for links. */ if (archive_entry_hardlink(entry)) /* Hard link */ { fprintf(out, " link to %s", archive_entry_hardlink(entry)); } else if (archive_entry_symlink(entry)) /* Symbolic link */ { fprintf(out, " -> %s", archive_entry_symlink(entry)); } fflush(out); } void ArchiveError(const char* m1, struct archive* a) { std::string message(m1); const char* m2 = archive_error_string(a); if (m2) { message += m2; } cmSystemTools::Error(message); } bool la_diagnostic(struct archive* ar, __LA_SSIZE_T r) { // See archive.h definition of ARCHIVE_OK for return values. if (r >= ARCHIVE_OK) { return true; } if (r >= ARCHIVE_WARN) { const char* warn = archive_error_string(ar); if (!warn) { warn = "unknown warning"; } std::cerr << "cmake -E tar: warning: " << warn << '\n'; return true; } // Error. const char* err = archive_error_string(ar); if (!err) { err = "unknown error"; } std::cerr << "cmake -E tar: error: " << err << '\n'; return false; } // Return 'true' on success bool copy_data(struct archive* ar, struct archive* aw) { long r; const void* buff; size_t size; # if defined(ARCHIVE_VERSION_NUMBER) && ARCHIVE_VERSION_NUMBER >= 3000000 __LA_INT64_T offset; # else off_t offset; # endif for (;;) { // See archive.h definition of ARCHIVE_OK for return values. r = archive_read_data_block(ar, &buff, &size, &offset); if (r == ARCHIVE_EOF) { return true; } if (!la_diagnostic(ar, r)) { return false; } // See archive.h definition of ARCHIVE_OK for return values. __LA_SSIZE_T const w = archive_write_data_block(aw, buff, size, offset); if (!la_diagnostic(ar, w)) { return false; } } # if !defined(__clang__) && !defined(__NVCOMPILER) && !defined(__HP_aCC) return false; /* this should not happen but it quiets some compilers */ # endif } bool extract_tar(const std::string& outFileName, const std::vector& files, bool verbose, cmSystemTools::cmTarExtractTimestamps extractTimestamps, bool extract) { cmLocaleRAII localeRAII; static_cast(localeRAII); struct archive* a = archive_read_new(); struct archive* ext = archive_write_disk_new(); archive_read_support_filter_all(a); archive_read_support_format_all(a); struct archive_entry* entry; struct archive* matching = archive_match_new(); if (matching == nullptr) { cmSystemTools::Error("Out of memory"); return false; } for (const auto& filename : files) { if (archive_match_include_pattern(matching, filename.c_str()) != ARCHIVE_OK) { cmSystemTools::Error("Failed to add to inclusion list: " + filename); return false; } } int r = cm_archive_read_open_file(a, outFileName.c_str(), 10240); if (r) { ArchiveError("Problem with archive_read_open_file(): ", a); archive_write_free(ext); archive_read_close(a); return false; } for (;;) { r = archive_read_next_header(a, &entry); if (r == ARCHIVE_EOF) { break; } if (r != ARCHIVE_OK) { ArchiveError("Problem with archive_read_next_header(): ", a); break; } if (archive_match_excluded(matching, entry)) { continue; } if (verbose) { if (extract) { cmSystemTools::Stdout("x "); cmSystemTools::Stdout(cm_archive_entry_pathname(entry)); } else { list_item_verbose(stdout, entry); } cmSystemTools::Stdout("\n"); } else if (!extract) { cmSystemTools::Stdout(cm_archive_entry_pathname(entry)); cmSystemTools::Stdout("\n"); } if (extract) { if (extractTimestamps == cmSystemTools::cmTarExtractTimestamps::Yes) { r = archive_write_disk_set_options(ext, ARCHIVE_EXTRACT_TIME); if (r != ARCHIVE_OK) { ArchiveError("Problem with archive_write_disk_set_options(): ", ext); break; } } r = archive_write_header(ext, entry); if (r == ARCHIVE_OK) { if (!copy_data(a, ext)) { break; } r = archive_write_finish_entry(ext); if (r != ARCHIVE_OK) { ArchiveError("Problem with archive_write_finish_entry(): ", ext); break; } } # ifdef _WIN32 else if (const char* linktext = archive_entry_symlink(entry)) { std::cerr << "cmake -E tar: warning: skipping symbolic link \"" << cm_archive_entry_pathname(entry) << "\" -> \"" << linktext << "\"." << std::endl; } # endif else { ArchiveError("Problem with archive_write_header(): ", ext); cmSystemTools::Error("Current file: " + cm_archive_entry_pathname(entry)); break; } } } bool error_occured = false; if (matching != nullptr) { const char* p; int ar; while ((ar = archive_match_path_unmatched_inclusions_next(matching, &p)) == ARCHIVE_OK) { cmSystemTools::Error("tar: " + std::string(p) + ": Not found in archive"); error_occured = true; } if (error_occured) { return false; } if (ar == ARCHIVE_FATAL) { cmSystemTools::Error("tar: Out of memory"); return false; } } archive_match_free(matching); archive_write_free(ext); archive_read_close(a); archive_read_free(a); return r == ARCHIVE_EOF || r == ARCHIVE_OK; } } #endif bool cmSystemTools::ExtractTar(const std::string& outFileName, const std::vector& files, cmTarExtractTimestamps extractTimestamps, bool verbose) { #if !defined(CMAKE_BOOTSTRAP) return extract_tar(outFileName, files, verbose, extractTimestamps, true); #else (void)outFileName; (void)files; (void)extractTimestamps; (void)verbose; return false; #endif } bool cmSystemTools::ListTar(const std::string& outFileName, const std::vector& files, bool verbose) { #if !defined(CMAKE_BOOTSTRAP) return extract_tar(outFileName, files, verbose, cmTarExtractTimestamps::Yes, false); #else (void)outFileName; (void)files; (void)verbose; return false; #endif } int cmSystemTools::WaitForLine(cmsysProcess* process, std::string& line, cmDuration timeout, std::vector& out, std::vector& err) { line.clear(); auto outiter = out.begin(); auto erriter = err.begin(); cmProcessOutput processOutput; std::string strdata; while (true) { // Check for a newline in stdout. for (; outiter != out.end(); ++outiter) { if ((*outiter == '\r') && ((outiter + 1) == out.end())) { break; } if (*outiter == '\n' || *outiter == '\0') { std::vector::size_type length = outiter - out.begin(); if (length > 1 && *(outiter - 1) == '\r') { --length; } if (length > 0) { line.append(&out[0], length); } out.erase(out.begin(), outiter + 1); return cmsysProcess_Pipe_STDOUT; } } // Check for a newline in stderr. for (; erriter != err.end(); ++erriter) { if ((*erriter == '\r') && ((erriter + 1) == err.end())) { break; } if (*erriter == '\n' || *erriter == '\0') { std::vector::size_type length = erriter - err.begin(); if (length > 1 && *(erriter - 1) == '\r') { --length; } if (length > 0) { line.append(&err[0], length); } err.erase(err.begin(), erriter + 1); return cmsysProcess_Pipe_STDERR; } } // No newlines found. Wait for more data from the process. int length; char* data; double timeoutAsDbl = timeout.count(); int pipe = cmsysProcess_WaitForData(process, &data, &length, &timeoutAsDbl); if (pipe == cmsysProcess_Pipe_Timeout) { // Timeout has been exceeded. return pipe; } if (pipe == cmsysProcess_Pipe_STDOUT) { processOutput.DecodeText(data, length, strdata, 1); // Append to the stdout buffer. std::vector::size_type size = out.size(); cm::append(out, strdata); outiter = out.begin() + size; } else if (pipe == cmsysProcess_Pipe_STDERR) { processOutput.DecodeText(data, length, strdata, 2); // Append to the stderr buffer. std::vector::size_type size = err.size(); cm::append(err, strdata); erriter = err.begin() + size; } else if (pipe == cmsysProcess_Pipe_None) { // Both stdout and stderr pipes have broken. Return leftover data. processOutput.DecodeText(std::string(), strdata, 1); if (!strdata.empty()) { std::vector::size_type size = out.size(); cm::append(out, strdata); outiter = out.begin() + size; } processOutput.DecodeText(std::string(), strdata, 2); if (!strdata.empty()) { std::vector::size_type size = err.size(); cm::append(err, strdata); erriter = err.begin() + size; } if (!out.empty()) { line.append(&out[0], outiter - out.begin()); out.erase(out.begin(), out.end()); return cmsysProcess_Pipe_STDOUT; } if (!err.empty()) { line.append(&err[0], erriter - err.begin()); err.erase(err.begin(), err.end()); return cmsysProcess_Pipe_STDERR; } return cmsysProcess_Pipe_None; } } } #ifdef _WIN32 static void EnsureStdPipe(DWORD fd) { if (GetStdHandle(fd) != INVALID_HANDLE_VALUE) { return; } SECURITY_ATTRIBUTES sa; sa.nLength = sizeof(sa); sa.lpSecurityDescriptor = NULL; sa.bInheritHandle = TRUE; HANDLE h = CreateFileW( L"NUL", fd == STD_INPUT_HANDLE ? FILE_GENERIC_READ : FILE_GENERIC_WRITE | FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE, &sa, OPEN_EXISTING, 0, NULL); if (h == INVALID_HANDLE_VALUE) { LPSTR message = NULL; DWORD size = FormatMessageA( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&message, 0, NULL); std::string msg = std::string(message, size); LocalFree(message); std::cerr << "failed to open NUL for missing stdio pipe: " << msg; abort(); } SetStdHandle(fd, h); } void cmSystemTools::EnsureStdPipes() { EnsureStdPipe(STD_INPUT_HANDLE); EnsureStdPipe(STD_OUTPUT_HANDLE); EnsureStdPipe(STD_ERROR_HANDLE); } #else static void EnsureStdPipe(int fd) { if (fcntl(fd, F_GETFD) != -1 || errno != EBADF) { return; } int f = open("/dev/null", fd == STDIN_FILENO ? O_RDONLY : O_WRONLY); if (f == -1) { perror("failed to open /dev/null for missing stdio pipe"); abort(); } if (f != fd) { dup2(f, fd); close(f); } } void cmSystemTools::EnsureStdPipes() { EnsureStdPipe(STDIN_FILENO); EnsureStdPipe(STDOUT_FILENO); EnsureStdPipe(STDERR_FILENO); } #endif void cmSystemTools::DoNotInheritStdPipes() { #ifdef _WIN32 // Check to see if we are attached to a console // if so, then do not stop the inherited pipes // or stdout and stderr will not show up in dos // shell windows CONSOLE_SCREEN_BUFFER_INFO hOutInfo; HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); if (GetConsoleScreenBufferInfo(hOut, &hOutInfo)) { return; } { HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); DuplicateHandle(GetCurrentProcess(), out, GetCurrentProcess(), &out, 0, FALSE, DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE); SetStdHandle(STD_OUTPUT_HANDLE, out); } { HANDLE out = GetStdHandle(STD_ERROR_HANDLE); DuplicateHandle(GetCurrentProcess(), out, GetCurrentProcess(), &out, 0, FALSE, DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE); SetStdHandle(STD_ERROR_HANDLE, out); } #endif } #ifdef _WIN32 # ifndef CRYPT_SILENT # define CRYPT_SILENT 0x40 /* Not defined by VS 6 version of header. */ # endif static int WinCryptRandom(void* data, size_t size) { int result = 0; HCRYPTPROV hProvider = 0; if (CryptAcquireContextW(&hProvider, 0, 0, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT | CRYPT_SILENT)) { result = CryptGenRandom(hProvider, (DWORD)size, (BYTE*)data) ? 1 : 0; CryptReleaseContext(hProvider, 0); } return result; } #endif unsigned int cmSystemTools::RandomSeed() { #if defined(_WIN32) && !defined(__CYGWIN__) unsigned int seed = 0; // Try using a real random source. if (WinCryptRandom(&seed, sizeof(seed))) { return seed; } // Fall back to the time and pid. FILETIME ft; GetSystemTimeAsFileTime(&ft); unsigned int t1 = static_cast(ft.dwHighDateTime); unsigned int t2 = static_cast(ft.dwLowDateTime); unsigned int pid = static_cast(GetCurrentProcessId()); return t1 ^ t2 ^ pid; #else union { unsigned int integer; char bytes[sizeof(unsigned int)]; } seed; // Try using a real random source. cmsys::ifstream fin; fin.rdbuf()->pubsetbuf(nullptr, 0); // Unbuffered read. fin.open("/dev/urandom"); if (fin.good() && fin.read(seed.bytes, sizeof(seed)) && fin.gcount() == sizeof(seed)) { return seed.integer; } // Fall back to the time and pid. struct timeval t; gettimeofday(&t, nullptr); unsigned int pid = static_cast(getpid()); unsigned int tv_sec = static_cast(t.tv_sec); unsigned int tv_usec = static_cast(t.tv_usec); // Since tv_usec never fills more than 11 bits we shift it to fill // in the slow-changing high-order bits of tv_sec. return tv_sec ^ (tv_usec << 21) ^ pid; #endif } static std::string cmSystemToolsCMakeCommand; static std::string cmSystemToolsCTestCommand; static std::string cmSystemToolsCPackCommand; static std::string cmSystemToolsCMakeCursesCommand; static std::string cmSystemToolsCMakeGUICommand; static std::string cmSystemToolsCMClDepsCommand; static std::string cmSystemToolsCMakeRoot; static std::string cmSystemToolsHTMLDoc; void cmSystemTools::FindCMakeResources(const char* argv0) { std::string exe_dir; #if defined(_WIN32) && !defined(__CYGWIN__) (void)argv0; // ignore this on windows wchar_t modulepath[_MAX_PATH]; ::GetModuleFileNameW(NULL, modulepath, sizeof(modulepath)); std::string path = cmsys::Encoding::ToNarrow(modulepath); std::string realPath = cmSystemTools::GetRealPathResolvingWindowsSubst(path, NULL); if (realPath.empty()) { realPath = path; } exe_dir = cmSystemTools::GetFilenamePath(realPath); #elif defined(__APPLE__) (void)argv0; // ignore this on OS X # define CM_EXE_PATH_LOCAL_SIZE 16384 char exe_path_local[CM_EXE_PATH_LOCAL_SIZE]; # if defined(MAC_OS_X_VERSION_10_3) && !defined(MAC_OS_X_VERSION_10_4) unsigned long exe_path_size = CM_EXE_PATH_LOCAL_SIZE; # else uint32_t exe_path_size = CM_EXE_PATH_LOCAL_SIZE; # endif # undef CM_EXE_PATH_LOCAL_SIZE char* exe_path = exe_path_local; if (_NSGetExecutablePath(exe_path, &exe_path_size) < 0) { exe_path = static_cast(malloc(exe_path_size)); _NSGetExecutablePath(exe_path, &exe_path_size); } exe_dir = cmSystemTools::GetFilenamePath(cmSystemTools::GetRealPath(exe_path)); if (exe_path != exe_path_local) { free(exe_path); } if (cmSystemTools::GetFilenameName(exe_dir) == "MacOS") { // The executable is inside an application bundle. // Look for .. (install tree) and then fall back to // ../../../bin (build tree). exe_dir = cmSystemTools::GetFilenamePath(exe_dir); if (cmSystemTools::FileExists(exe_dir + CMAKE_BIN_DIR "/cmake")) { exe_dir += CMAKE_BIN_DIR; } else { exe_dir = cmSystemTools::GetFilenamePath(exe_dir); exe_dir = cmSystemTools::GetFilenamePath(exe_dir); } } #else std::string errorMsg; std::string exe; if (cmSystemTools::FindProgramPath(argv0, exe, errorMsg)) { // remove symlinks exe = cmSystemTools::GetRealPath(exe); exe_dir = cmSystemTools::GetFilenamePath(exe); } else { // ??? } #endif exe_dir = cmSystemTools::GetActualCaseForPath(exe_dir); cmSystemToolsCMakeCommand = cmStrCat(exe_dir, "/cmake", cmSystemTools::GetExecutableExtension()); #ifdef CMAKE_BOOTSTRAP // The bootstrap cmake does not provide the other tools, // so use the directory where they are about to be built. exe_dir = CMAKE_BOOTSTRAP_BINARY_DIR "/bin"; #endif cmSystemToolsCTestCommand = cmStrCat(exe_dir, "/ctest", cmSystemTools::GetExecutableExtension()); cmSystemToolsCPackCommand = cmStrCat(exe_dir, "/cpack", cmSystemTools::GetExecutableExtension()); cmSystemToolsCMakeGUICommand = cmStrCat(exe_dir, "/cmake-gui", cmSystemTools::GetExecutableExtension()); if (!cmSystemTools::FileExists(cmSystemToolsCMakeGUICommand)) { cmSystemToolsCMakeGUICommand.clear(); } cmSystemToolsCMakeCursesCommand = cmStrCat(exe_dir, "/ccmake", cmSystemTools::GetExecutableExtension()); if (!cmSystemTools::FileExists(cmSystemToolsCMakeCursesCommand)) { cmSystemToolsCMakeCursesCommand.clear(); } cmSystemToolsCMClDepsCommand = cmStrCat(exe_dir, "/cmcldeps", cmSystemTools::GetExecutableExtension()); if (!cmSystemTools::FileExists(cmSystemToolsCMClDepsCommand)) { cmSystemToolsCMClDepsCommand.clear(); } #ifndef CMAKE_BOOTSTRAP // Install tree has // - "/cmake" // - "" // - "" if (cmHasLiteralSuffix(exe_dir, CMAKE_BIN_DIR)) { std::string const prefix = exe_dir.substr(0, exe_dir.size() - cmStrLen(CMAKE_BIN_DIR)); cmSystemToolsCMakeRoot = cmStrCat(prefix, CMAKE_DATA_DIR); if (cmSystemTools::FileExists( cmStrCat(prefix, CMAKE_DOC_DIR "/html/index.html"))) { cmSystemToolsHTMLDoc = cmStrCat(prefix, CMAKE_DOC_DIR "/html"); } } if (cmSystemToolsCMakeRoot.empty() || !cmSystemTools::FileExists( cmStrCat(cmSystemToolsCMakeRoot, "/Modules/CMake.cmake"))) { // Build tree has "/bin[/]/cmake" and // "/CMakeFiles/CMakeSourceDir.txt". std::string dir = cmSystemTools::GetFilenamePath(exe_dir); std::string src_dir_txt = cmStrCat(dir, "/CMakeFiles/CMakeSourceDir.txt"); cmsys::ifstream fin(src_dir_txt.c_str()); std::string src_dir; if (fin && cmSystemTools::GetLineFromStream(fin, src_dir) && cmSystemTools::FileIsDirectory(src_dir)) { cmSystemToolsCMakeRoot = src_dir; } else { dir = cmSystemTools::GetFilenamePath(dir); src_dir_txt = cmStrCat(dir, "/CMakeFiles/CMakeSourceDir.txt"); cmsys::ifstream fin2(src_dir_txt.c_str()); if (fin2 && cmSystemTools::GetLineFromStream(fin2, src_dir) && cmSystemTools::FileIsDirectory(src_dir)) { cmSystemToolsCMakeRoot = src_dir; } } if (!cmSystemToolsCMakeRoot.empty() && cmSystemToolsHTMLDoc.empty() && cmSystemTools::FileExists( cmStrCat(dir, "/Utilities/Sphinx/html/index.html"))) { cmSystemToolsHTMLDoc = cmStrCat(dir, "/Utilities/Sphinx/html"); } } #else // Bootstrap build knows its source. cmSystemToolsCMakeRoot = CMAKE_BOOTSTRAP_SOURCE_DIR; #endif } std::string const& cmSystemTools::GetCMakeCommand() { return cmSystemToolsCMakeCommand; } std::string const& cmSystemTools::GetCTestCommand() { return cmSystemToolsCTestCommand; } std::string const& cmSystemTools::GetCPackCommand() { return cmSystemToolsCPackCommand; } std::string const& cmSystemTools::GetCMakeCursesCommand() { return cmSystemToolsCMakeCursesCommand; } std::string const& cmSystemTools::GetCMakeGUICommand() { return cmSystemToolsCMakeGUICommand; } std::string const& cmSystemTools::GetCMClDepsCommand() { return cmSystemToolsCMClDepsCommand; } std::string const& cmSystemTools::GetCMakeRoot() { return cmSystemToolsCMakeRoot; } std::string const& cmSystemTools::GetHTMLDoc() { return cmSystemToolsHTMLDoc; } std::string cmSystemTools::GetCurrentWorkingDirectory() { return cmSystemTools::CollapseFullPath( cmsys::SystemTools::GetCurrentWorkingDirectory()); } void cmSystemTools::MakefileColorEcho(int color, const char* message, bool newline, bool enabled) { // On some platforms (an MSYS prompt) cmsysTerminal may not be able // to determine whether the stream is displayed on a tty. In this // case it assumes no unless we tell it otherwise. Since we want // color messages to be displayed for users we will assume yes. // However, we can test for some situations when the answer is most // likely no. int assumeTTY = cmsysTerminal_Color_AssumeTTY; if (cmSystemTools::HasEnv("DART_TEST_FROM_DART") || cmSystemTools::HasEnv("DASHBOARD_TEST_FROM_CTEST") || cmSystemTools::HasEnv("CTEST_INTERACTIVE_DEBUG_MODE")) { // Avoid printing color escapes during dashboard builds. assumeTTY = 0; } if (enabled && color != cmsysTerminal_Color_Normal) { // Print with color. Delay the newline until later so that // all color restore sequences appear before it. cmsysTerminal_cfprintf(color | assumeTTY, stdout, "%s", message); } else { // Color is disabled. Print without color. fprintf(stdout, "%s", message); } if (newline) { fprintf(stdout, "\n"); } } bool cmSystemTools::GuessLibrarySOName(std::string const& fullPath, std::string& soname) { // For ELF shared libraries use a real parser to get the correct // soname. cmELF elf(fullPath.c_str()); if (elf) { return elf.GetSOName(soname); } // If the file is not a symlink we have no guess for its soname. if (!cmSystemTools::FileIsSymlink(fullPath)) { return false; } if (!cmSystemTools::ReadSymlink(fullPath, soname)) { return false; } // If the symlink has a path component we have no guess for the soname. if (!cmSystemTools::GetFilenamePath(soname).empty()) { return false; } // If the symlink points at an extended version of the same name // assume it is the soname. std::string name = cmSystemTools::GetFilenameName(fullPath); return soname.length() > name.length() && soname.compare(0, name.length(), name) == 0; } bool cmSystemTools::GuessLibraryInstallName(std::string const& fullPath, std::string& soname) { #if defined(CMake_USE_MACH_PARSER) cmMachO macho(fullPath.c_str()); if (macho) { return macho.GetInstallName(soname); } #else (void)fullPath; (void)soname; #endif return false; } static std::string::size_type cmSystemToolsFindRPath( cm::string_view const& have, cm::string_view const& want) { std::string::size_type pos = 0; while (pos < have.size()) { // Look for an occurrence of the string. std::string::size_type const beg = have.find(want, pos); if (beg == std::string::npos) { return std::string::npos; } // Make sure it is separated from preceding entries. if (beg > 0 && have[beg - 1] != ':') { pos = beg + 1; continue; } // Make sure it is separated from following entries. std::string::size_type const end = beg + want.size(); if (end < have.size() && have[end] != ':') { pos = beg + 1; continue; } // Return the position of the path portion. return beg; } // The desired rpath was not found. return std::string::npos; } namespace { struct cmSystemToolsRPathInfo { unsigned long Position; unsigned long Size; std::string Name; std::string Value; }; using EmptyCallback = std::function; using AdjustCallback = std::function&, const std::string&, const char*, std::string*)>; cm::optional AdjustRPathELF(std::string const& file, const EmptyCallback& emptyCallback, const AdjustCallback& adjustCallback, std::string* emsg, bool* changed) { if (changed) { *changed = false; } int rp_count = 0; bool remove_rpath = true; cmSystemToolsRPathInfo rp[2]; { // Parse the ELF binary. cmELF elf(file.c_str()); if (!elf) { return cm::nullopt; // Not a valid ELF file. } // Get the RPATH and RUNPATH entries from it. int se_count = 0; cmELF::StringEntry const* se[2] = { nullptr, nullptr }; const char* se_name[2] = { nullptr, nullptr }; if (cmELF::StringEntry const* se_rpath = elf.GetRPath()) { se[se_count] = se_rpath; se_name[se_count] = "RPATH"; ++se_count; } if (cmELF::StringEntry const* se_runpath = elf.GetRunPath()) { se[se_count] = se_runpath; se_name[se_count] = "RUNPATH"; ++se_count; } if (se_count == 0) { return emptyCallback(emsg, elf); } for (int i = 0; i < se_count; ++i) { // If both RPATH and RUNPATH refer to the same string literal it // needs to be changed only once. if (rp_count && rp[0].Position == se[i]->Position) { continue; } // Store information about the entry in the file. rp[rp_count].Position = se[i]->Position; rp[rp_count].Size = se[i]->Size; rp[rp_count].Name = se_name[i]; // Adjust the rpath. cm::optional outRPath; if (!adjustCallback(outRPath, se[i]->Value, se_name[i], emsg)) { return false; } if (outRPath) { if (!outRPath->empty()) { remove_rpath = false; } // Make sure there is enough room to store the new rpath and at // least one null terminator. if (rp[rp_count].Size < outRPath->length() + 1) { if (emsg) { *emsg = cmStrCat("The replacement path is too long for the ", se_name[i], " entry."); } return false; } // This entry is ready for update. rp[rp_count].Value = std::move(*outRPath); ++rp_count; } else { remove_rpath = false; } } } // If no runtime path needs to be changed, we are done. if (rp_count == 0) { return true; } // If the resulting rpath is empty, just remove the entire entry instead. if (remove_rpath) { return cmSystemTools::RemoveRPath(file, emsg, changed); } { // Open the file for update. cmsys::ofstream f(file.c_str(), std::ios::in | std::ios::out | std::ios::binary); if (!f) { if (emsg) { *emsg = "Error opening file for update."; } return false; } // Store the new RPATH and RUNPATH strings. for (int i = 0; i < rp_count; ++i) { // Seek to the RPATH position. if (!f.seekp(rp[i].Position)) { if (emsg) { *emsg = cmStrCat("Error seeking to ", rp[i].Name, " position."); } return false; } // Write the new rpath. Follow it with enough null terminators to // fill the string table entry. f << rp[i].Value; for (unsigned long j = rp[i].Value.length(); j < rp[i].Size; ++j) { f << '\0'; } // Make sure it wrote correctly. if (!f) { if (emsg) { *emsg = cmStrCat("Error writing the new ", rp[i].Name, " string to the file."); } return false; } } } // Everything was updated successfully. if (changed) { *changed = true; } return true; } std::function MakeEmptyCallback( const std::string& newRPath) { return [newRPath](std::string* emsg, const cmELF& elf) -> bool { if (newRPath.empty()) { // The new rpath is empty and there is no rpath anyway so it is // okay. return true; } if (emsg) { *emsg = cmStrCat("No valid ELF RPATH or RUNPATH entry exists in the file; ", elf.GetErrorMessage()); } return false; }; } } static cm::optional ChangeRPathELF(std::string const& file, std::string const& oldRPath, std::string const& newRPath, bool removeEnvironmentRPath, std::string* emsg, bool* changed) { auto adjustCallback = [oldRPath, newRPath, removeEnvironmentRPath]( cm::optional& outRPath, const std::string& inRPath, const char* se_name, std::string* emsg2) -> bool { // Make sure the current rpath contains the old rpath. std::string::size_type pos = cmSystemToolsFindRPath(inRPath, oldRPath); if (pos == std::string::npos) { // If it contains the new rpath instead then it is okay. if (cmSystemToolsFindRPath(inRPath, newRPath) != std::string::npos) { return true; } if (emsg2) { std::ostringstream e; /* clang-format off */ e << "The current " << se_name << " is:\n" << " " << inRPath << "\n" << "which does not contain:\n" << " " << oldRPath << "\n" << "as was expected."; /* clang-format on */ *emsg2 = e.str(); } return false; } std::string::size_type prefix_len = pos; // If oldRPath was at the end of the file's RPath, and newRPath is empty, // we should remove the unnecessary ':' at the end. if (newRPath.empty() && pos > 0 && inRPath[pos - 1] == ':' && pos + oldRPath.length() == inRPath.length()) { prefix_len--; } // Construct the new value which preserves the part of the path // not being changed. outRPath.emplace(); if (!removeEnvironmentRPath) { *outRPath += inRPath.substr(0, prefix_len); } *outRPath += newRPath; *outRPath += inRPath.substr(pos + oldRPath.length()); return true; }; return AdjustRPathELF(file, MakeEmptyCallback(newRPath), adjustCallback, emsg, changed); } static cm::optional SetRPathELF(std::string const& file, std::string const& newRPath, std::string* emsg, bool* changed) { auto adjustCallback = [newRPath](cm::optional& outRPath, const std::string& inRPath, const char* /*se_name*/, std::string * /*emsg*/) -> bool { if (inRPath != newRPath) { outRPath = newRPath; } return true; }; return AdjustRPathELF(file, MakeEmptyCallback(newRPath), adjustCallback, emsg, changed); } static cm::optional ChangeRPathXCOFF(std::string const& file, std::string const& oldRPath, std::string const& newRPath, bool removeEnvironmentRPath, std::string* emsg, bool* changed) { if (changed) { *changed = false; } #if !defined(CMake_USE_XCOFF_PARSER) (void)file; (void)oldRPath; (void)newRPath; (void)removeEnvironmentRPath; (void)emsg; return cm::nullopt; #else bool chg = false; cmXCOFF xcoff(file.c_str(), cmXCOFF::Mode::ReadWrite); if (!xcoff) { return cm::nullopt; // Not a valid XCOFF file } if (cm::optional maybeLibPath = xcoff.GetLibPath()) { cm::string_view libPath = *maybeLibPath; // Make sure the current rpath contains the old rpath. std::string::size_type pos = cmSystemToolsFindRPath(libPath, oldRPath); if (pos == std::string::npos) { // If it contains the new rpath instead then it is okay. if (cmSystemToolsFindRPath(libPath, newRPath) != std::string::npos) { return true; } if (emsg) { std::ostringstream e; /* clang-format off */ e << "The current RPATH is:\n" << " " << libPath << "\n" << "which does not contain:\n" << " " << oldRPath << "\n" << "as was expected."; /* clang-format on */ *emsg = e.str(); } return false; } // The prefix is either empty or ends in a ':'. cm::string_view prefix = libPath.substr(0, pos); if (newRPath.empty() && !prefix.empty()) { prefix.remove_suffix(1); } // The suffix is either empty or starts in a ':'. cm::string_view suffix = libPath.substr(pos + oldRPath.length()); // Construct the new value which preserves the part of the path // not being changed. std::string newLibPath; if (!removeEnvironmentRPath) { newLibPath = std::string(prefix); } newLibPath += newRPath; newLibPath += suffix; chg = xcoff.SetLibPath(newLibPath); } if (!xcoff) { if (emsg) { *emsg = xcoff.GetErrorMessage(); } return false; } // Everything was updated successfully. if (changed) { *changed = chg; } return true; #endif } static cm::optional SetRPathXCOFF(std::string const& /*file*/, std::string const& /*newRPath*/, std::string* /*emsg*/, bool* /*changed*/) { return cm::nullopt; // Not implemented. } bool cmSystemTools::ChangeRPath(std::string const& file, std::string const& oldRPath, std::string const& newRPath, bool removeEnvironmentRPath, std::string* emsg, bool* changed) { if (cm::optional result = ChangeRPathELF( file, oldRPath, newRPath, removeEnvironmentRPath, emsg, changed)) { return result.value(); } if (cm::optional result = ChangeRPathXCOFF( file, oldRPath, newRPath, removeEnvironmentRPath, emsg, changed)) { return result.value(); } // The file format is not recognized. Assume it has no RPATH. if (newRPath.empty()) { // The caller wanted no RPATH anyway. return true; } if (emsg) { *emsg = "The file format is not recognized."; } return false; } bool cmSystemTools::SetRPath(std::string const& file, std::string const& newRPath, std::string* emsg, bool* changed) { if (cm::optional result = SetRPathELF(file, newRPath, emsg, changed)) { return result.value(); } if (cm::optional result = SetRPathXCOFF(file, newRPath, emsg, changed)) { return result.value(); } // The file format is not recognized. Assume it has no RPATH. if (newRPath.empty()) { // The caller wanted no RPATH anyway. return true; } if (emsg) { *emsg = "The file format is not recognized."; } return false; } namespace { bool VersionCompare(cmSystemTools::CompareOp op, const char* lhss, const char* rhss) { const char* endl = lhss; const char* endr = rhss; unsigned long lhs; unsigned long rhs; while (((*endl >= '0') && (*endl <= '9')) || ((*endr >= '0') && (*endr <= '9'))) { // Do component-wise comparison. lhs = strtoul(endl, const_cast(&endl), 10); rhs = strtoul(endr, const_cast(&endr), 10); if (lhs < rhs) { // lhs < rhs, so true if operation is LESS return (op & cmSystemTools::OP_LESS) != 0; } if (lhs > rhs) { // lhs > rhs, so true if operation is GREATER return (op & cmSystemTools::OP_GREATER) != 0; } if (*endr == '.') { endr++; } if (*endl == '.') { endl++; } } // lhs == rhs, so true if operation is EQUAL return (op & cmSystemTools::OP_EQUAL) != 0; } } bool cmSystemTools::VersionCompare(cmSystemTools::CompareOp op, const std::string& lhs, const std::string& rhs) { return ::VersionCompare(op, lhs.c_str(), rhs.c_str()); } bool cmSystemTools::VersionCompare(cmSystemTools::CompareOp op, const std::string& lhs, const char rhs[]) { return ::VersionCompare(op, lhs.c_str(), rhs); } bool cmSystemTools::VersionCompareEqual(std::string const& lhs, std::string const& rhs) { return cmSystemTools::VersionCompare(cmSystemTools::OP_EQUAL, lhs, rhs); } bool cmSystemTools::VersionCompareGreater(std::string const& lhs, std::string const& rhs) { return cmSystemTools::VersionCompare(cmSystemTools::OP_GREATER, lhs, rhs); } bool cmSystemTools::VersionCompareGreaterEq(std::string const& lhs, std::string const& rhs) { return cmSystemTools::VersionCompare(cmSystemTools::OP_GREATER_EQUAL, lhs, rhs); } static size_t cm_strverscmp_find_first_difference_or_end(const char* lhs, const char* rhs) { size_t i = 0; /* Step forward until we find a difference or both strings end together. The difference may lie on the null-terminator of one string. */ while (lhs[i] == rhs[i] && lhs[i] != 0) { ++i; } return i; } static size_t cm_strverscmp_find_digits_begin(const char* s, size_t i) { /* Step back until we are not preceded by a digit. */ while (i > 0 && isdigit(s[i - 1])) { --i; } return i; } static size_t cm_strverscmp_find_digits_end(const char* s, size_t i) { /* Step forward over digits. */ while (isdigit(s[i])) { ++i; } return i; } static size_t cm_strverscmp_count_leading_zeros(const char* s, size_t b) { size_t i = b; /* Step forward over zeros that are followed by another digit. */ while (s[i] == '0' && isdigit(s[i + 1])) { ++i; } return i - b; } static int cm_strverscmp(const char* lhs, const char* rhs) { size_t const i = cm_strverscmp_find_first_difference_or_end(lhs, rhs); if (lhs[i] != rhs[i]) { /* The strings differ starting at 'i'. Check for a digit sequence. */ size_t const b = cm_strverscmp_find_digits_begin(lhs, i); if (b != i || (isdigit(lhs[i]) && isdigit(rhs[i]))) { /* A digit sequence starts at 'b', preceding or at 'i'. */ /* Look for leading zeros, implying a leading decimal point. */ size_t const lhs_zeros = cm_strverscmp_count_leading_zeros(lhs, b); size_t const rhs_zeros = cm_strverscmp_count_leading_zeros(rhs, b); if (lhs_zeros != rhs_zeros) { /* The side with more leading zeros orders first. */ return rhs_zeros > lhs_zeros ? 1 : -1; } if (lhs_zeros == 0) { /* No leading zeros; compare digit sequence lengths. */ size_t const lhs_end = cm_strverscmp_find_digits_end(lhs, i); size_t const rhs_end = cm_strverscmp_find_digits_end(rhs, i); if (lhs_end != rhs_end) { /* The side with fewer digits orders first. */ return lhs_end > rhs_end ? 1 : -1; } } } } /* Ordering was not decided by digit sequence lengths; compare bytes. */ return lhs[i] - rhs[i]; } int cmSystemTools::strverscmp(std::string const& lhs, std::string const& rhs) { return cm_strverscmp(lhs.c_str(), rhs.c_str()); } static cm::optional RemoveRPathELF(std::string const& file, std::string* emsg, bool* removed) { if (removed) { *removed = false; } int zeroCount = 0; unsigned long zeroPosition[2] = { 0, 0 }; unsigned long zeroSize[2] = { 0, 0 }; unsigned long bytesBegin = 0; std::vector bytes; { // Parse the ELF binary. cmELF elf(file.c_str()); if (!elf) { return cm::nullopt; // Not a valid ELF file. } // Get the RPATH and RUNPATH entries from it and sort them by index // in the dynamic section header. int se_count = 0; cmELF::StringEntry const* se[2] = { nullptr, nullptr }; if (cmELF::StringEntry const* se_rpath = elf.GetRPath()) { se[se_count++] = se_rpath; } if (cmELF::StringEntry const* se_runpath = elf.GetRunPath()) { se[se_count++] = se_runpath; } if (se_count == 0) { // There is no RPATH or RUNPATH anyway. return true; } if (se_count == 2 && se[1]->IndexInSection < se[0]->IndexInSection) { std::swap(se[0], se[1]); } // Obtain a copy of the dynamic entries cmELF::DynamicEntryList dentries = elf.GetDynamicEntries(); if (dentries.empty()) { // This should happen only for invalid ELF files where a DT_NULL // appears before the end of the table. if (emsg) { *emsg = "DYNAMIC section contains a DT_NULL before the end."; } return false; } // Save information about the string entries to be zeroed. zeroCount = se_count; for (int i = 0; i < se_count; ++i) { zeroPosition[i] = se[i]->Position; zeroSize[i] = se[i]->Size; } // Get size of one DYNAMIC entry unsigned long const sizeof_dentry = elf.GetDynamicEntryPosition(1) - elf.GetDynamicEntryPosition(0); // Adjust the entry list as necessary to remove the run path unsigned long entriesErased = 0; for (auto it = dentries.begin(); it != dentries.end();) { if (it->first == cmELF::TagRPath || it->first == cmELF::TagRunPath) { it = dentries.erase(it); entriesErased++; continue; } if (it->first == cmELF::TagMipsRldMapRel && elf.IsMIPS()) { // Background: debuggers need to know the "linker map" which contains // the addresses each dynamic object is loaded at. Most arches use // the DT_DEBUG tag which the dynamic linker writes to (directly) and // contain the location of the linker map, however on MIPS the // .dynamic section is always read-only so this is not possible. MIPS // objects instead contain a DT_MIPS_RLD_MAP tag which contains the // address where the dynamic linker will write to (an indirect // version of DT_DEBUG). Since this doesn't work when using PIE, a // relative equivalent was created - DT_MIPS_RLD_MAP_REL. Since this // version contains a relative offset, moving it changes the // calculated address. This may cause the dynamic linker to write // into memory it should not be changing. // // To fix this, we adjust the value of DT_MIPS_RLD_MAP_REL here. If // we move it up by n bytes, we add n bytes to the value of this tag. it->second += entriesErased * sizeof_dentry; } it++; } // Encode new entries list bytes = elf.EncodeDynamicEntries(dentries); bytesBegin = elf.GetDynamicEntryPosition(0); } // Open the file for update. cmsys::ofstream f(file.c_str(), std::ios::in | std::ios::out | std::ios::binary); if (!f) { if (emsg) { *emsg = "Error opening file for update."; } return false; } // Write the new DYNAMIC table header. if (!f.seekp(bytesBegin)) { if (emsg) { *emsg = "Error seeking to DYNAMIC table header for RPATH."; } return false; } if (!f.write(&bytes[0], bytes.size())) { if (emsg) { *emsg = "Error replacing DYNAMIC table header."; } return false; } // Fill the RPATH and RUNPATH strings with zero bytes. for (int i = 0; i < zeroCount; ++i) { if (!f.seekp(zeroPosition[i])) { if (emsg) { *emsg = "Error seeking to RPATH position."; } return false; } for (unsigned long j = 0; j < zeroSize[i]; ++j) { f << '\0'; } if (!f) { if (emsg) { *emsg = "Error writing the empty rpath string to the file."; } return false; } } // Everything was updated successfully. if (removed) { *removed = true; } return true; } static cm::optional RemoveRPathXCOFF(std::string const& file, std::string* emsg, bool* removed) { if (removed) { *removed = false; } #if !defined(CMake_USE_XCOFF_PARSER) (void)file; (void)emsg; return cm::nullopt; // Cannot handle XCOFF files. #else cmXCOFF xcoff(file.c_str(), cmXCOFF::Mode::ReadWrite); if (!xcoff) { return cm::nullopt; // Not a valid XCOFF file. } bool rm = xcoff.RemoveLibPath(); if (!xcoff) { if (emsg) { *emsg = xcoff.GetErrorMessage(); } return false; } if (removed) { *removed = rm; } return true; #endif } bool cmSystemTools::RemoveRPath(std::string const& file, std::string* emsg, bool* removed) { if (cm::optional result = RemoveRPathELF(file, emsg, removed)) { return result.value(); } if (cm::optional result = RemoveRPathXCOFF(file, emsg, removed)) { return result.value(); } // The file format is not recognized. Assume it has no RPATH. return true; } bool cmSystemTools::CheckRPath(std::string const& file, std::string const& newRPath) { // Parse the ELF binary. cmELF elf(file.c_str()); if (elf) { // Get the RPATH or RUNPATH entry from it. cmELF::StringEntry const* se = elf.GetRPath(); if (!se) { se = elf.GetRunPath(); } // Make sure the current rpath contains the new rpath. if (newRPath.empty()) { if (!se) { return true; } } else { if (se && cmSystemToolsFindRPath(se->Value, newRPath) != std::string::npos) { return true; } } return false; } #if defined(CMake_USE_XCOFF_PARSER) // Parse the XCOFF binary. cmXCOFF xcoff(file.c_str()); if (xcoff) { if (cm::optional libPath = xcoff.GetLibPath()) { if (cmSystemToolsFindRPath(*libPath, newRPath) != std::string::npos) { return true; } } return false; } #endif // The file format is not recognized. Assume it has no RPATH. // Therefore we succeed if the new rpath is empty anyway. return newRPath.empty(); } bool cmSystemTools::RepeatedRemoveDirectory(const std::string& dir) { #ifdef _WIN32 // Windows sometimes locks files temporarily so try a few times. WindowsFileRetry retry = cmSystemTools::GetWindowsFileRetry(); for (unsigned int i = 0; i < retry.Count; ++i) { if (cmSystemTools::RemoveADirectory(dir)) { return true; } cmSystemTools::Delay(retry.Delay); } return false; #else return static_cast(cmSystemTools::RemoveADirectory(dir)); #endif } std::string cmSystemTools::EncodeURL(std::string const& in, bool escapeSlashes) { std::string out; for (char c : in) { char hexCh[4] = { 0, 0, 0, 0 }; hexCh[0] = c; switch (c) { case '+': case '?': case '\\': case '&': case ' ': case '=': case '%': snprintf(hexCh, sizeof(hexCh), "%%%02X", static_cast(c)); break; case '/': if (escapeSlashes) { strcpy(hexCh, "%2F"); } break; default: break; } out.append(hexCh); } return out; } cmsys::Status cmSystemTools::CreateSymlink(std::string const& origName, std::string const& newName, std::string* errorMessage) { uv_fs_t req; int flags = 0; #if defined(_WIN32) bool const isDir = cmsys::SystemTools::FileIsDirectory(origName); if (isDir) { flags |= UV_FS_SYMLINK_JUNCTION; } #endif int err = uv_fs_symlink(nullptr, &req, origName.c_str(), newName.c_str(), flags, nullptr); #if defined(_WIN32) if (err && uv_fs_get_system_error(&req) == ERROR_NOT_SUPPORTED && isDir) { // Try fallback to symlink for network (requires additional permissions). flags ^= UV_FS_SYMLINK_JUNCTION | UV_FS_SYMLINK_DIR; err = uv_fs_symlink(nullptr, &req, origName.c_str(), newName.c_str(), flags, nullptr); } #endif cmsys::Status status; if (err) { #if defined(_WIN32) status = cmsys::Status::Windows(uv_fs_get_system_error(&req)); #elif UV_VERSION_MAJOR > 1 || (UV_VERSION_MAJOR == 1 && UV_VERSION_MINOR >= 38) status = cmsys::Status::POSIX(uv_fs_get_system_error(&req)); #else status = cmsys::Status::POSIX(-err); #endif std::string e = cmStrCat("failed to create symbolic link '", newName, "': ", status.GetString()); if (errorMessage) { *errorMessage = std::move(e); } else { cmSystemTools::Error(e); } } return status; } cmsys::Status cmSystemTools::CreateLink(std::string const& origName, std::string const& newName, std::string* errorMessage) { uv_fs_t req; int err = uv_fs_link(nullptr, &req, origName.c_str(), newName.c_str(), nullptr); cmsys::Status status; if (err) { #if defined(_WIN32) status = cmsys::Status::Windows(uv_fs_get_system_error(&req)); #elif UV_VERSION_MAJOR > 1 || (UV_VERSION_MAJOR == 1 && UV_VERSION_MINOR >= 38) status = cmsys::Status::POSIX(uv_fs_get_system_error(&req)); #else status = cmsys::Status::POSIX(-err); #endif std::string e = cmStrCat("failed to create link '", newName, "': ", status.GetString()); if (errorMessage) { *errorMessage = std::move(e); } else { cmSystemTools::Error(e); } } return status; } cm::string_view cmSystemTools::GetSystemName() { #if defined(_WIN32) return "Windows"; #elif defined(__ANDROID__) return "Android"; #else static struct utsname uts_name; static bool initialized = false; static cm::string_view systemName; if (initialized) { return systemName; } if (uname(&uts_name) >= 0) { initialized = true; systemName = uts_name.sysname; if (cmIsOff(systemName)) { systemName = "UnknownOS"; } // fix for BSD/OS, remove the / static const cmsys::RegularExpression bsdOsRegex("BSD.OS"); cmsys::RegularExpressionMatch match; if (bsdOsRegex.find(uts_name.sysname, match)) { systemName = "BSDOS"; } // fix for GNU/kFreeBSD, remove the GNU/ if (systemName.find("kFreeBSD") != cm::string_view::npos) { systemName = "kFreeBSD"; } // fix for CYGWIN and MSYS which have windows version in them if (systemName.find("CYGWIN") != cm::string_view::npos) { systemName = "CYGWIN"; } if (systemName.find("MSYS") != cm::string_view::npos) { systemName = "MSYS"; } return systemName; } return ""; #endif }